Merge branch 'upstream/master'
Change-Id: Ifca8b7f13839b643fefe0c9258a11fab75623f2a
diff --git a/.github/ISSUE_TEMPLATE/00-bug_report.md b/.github/ISSUE_TEMPLATE/00-bug_report.md
deleted file mode 100644
index 1edf3de..0000000
--- a/.github/ISSUE_TEMPLATE/00-bug_report.md
+++ /dev/null
@@ -1,41 +0,0 @@
----
-name: Bug report
-about: Create a report to help us improve
-title: ''
-labels: 'bug'
-assignees: ''
----
-
-**Describe the bug**
-
-Include a clear and concise description of what the problem is, including what
-you expected to happen, and what actually happened.
-
-**Steps to reproduce the bug**
-
-It's important that we are able to reproduce the problem that you are
-experiencing. Please provide all code and relevant steps to reproduce the
-problem, including your `BUILD`/`CMakeLists.txt` file and build commands. Links
-to a GitHub branch or [godbolt.org](https://godbolt.org/) that demonstrate the
-problem are also helpful.
-
-**What version of Abseil are you using?**
-
-**What operating system and version are you using**
-
-If you are using a Linux distribution please include the name and version of the
-distribution as well.
-
-**What compiler and version are you using?**
-
-Please include the output of `gcc -v` or `clang -v`, or the equivalent for your
-compiler.
-
-**What build system are you using?**
-
-Please include the output of `bazel --version` or `cmake --version`, or the
-equivalent for your build system.
-
-**Additional context**
-
-Add any other context about the problem here.
diff --git a/.github/ISSUE_TEMPLATE/00-bug_report.yml b/.github/ISSUE_TEMPLATE/00-bug_report.yml
new file mode 100644
index 0000000..32cebe1
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/00-bug_report.yml
@@ -0,0 +1,53 @@
+name: Bug Report
+description: Let us know that something does not work as expected.
+title: "[Bug]: Please title this bug report"
+body:
+ - type: textarea
+ id: what-happened
+ attributes:
+ label: Describe the issue
+ description: What happened, and what did you expect to happen?
+ validations:
+ required: true
+ - type: textarea
+ id: steps
+ attributes:
+ label: Steps to reproduce the problem
+ description: It is important that we are able to reproduce the problem that you are experiencing. Please provide all code and relevant steps to reproduce the problem, including your `BUILD`/`CMakeLists.txt` file and build commands. Links to a GitHub branch or [godbolt.org](https://godbolt.org/) that demonstrate the problem are also helpful.
+ validations:
+ required: true
+ - type: textarea
+ id: version
+ attributes:
+ label: What version of Abseil are you using?
+ description: Please include the output of `git rev-parse HEAD` or the name of the LTS release that you are using.
+ validations:
+ required: true
+ - type: textarea
+ id: os
+ attributes:
+ label: What operating system and version are you using?
+ description: If you are using a Linux distribution please include the name and version of the distribution as well.
+ validations:
+ required: true
+ - type: textarea
+ id: compiler
+ attributes:
+ label: What compiler and version are you using?
+ description: Please include the output of `gcc -v` or `clang -v`, or the equivalent for your compiler.
+ validations:
+ required: true
+ - type: textarea
+ id: buildsystem
+ attributes:
+ label: What build system are you using?
+ description: Please include the output of `bazel --version` or `cmake --version`, or the equivalent for your build system.
+ validations:
+ required: true
+ - type: textarea
+ id: additional
+ attributes:
+ label: Additional context
+ description: Add any other context about the problem here.
+ validations:
+ required: false
diff --git a/.github/ISSUE_TEMPLATE/90-question.md b/.github/ISSUE_TEMPLATE/90-question.md
deleted file mode 100644
index 84cf349..0000000
--- a/.github/ISSUE_TEMPLATE/90-question.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-name: Question
-about: Have a question? Ask us anything! :-)
-title: ''
-labels: 'question'
-assignees: ''
----
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index 0086358..c690fd9 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -1 +1,5 @@
-blank_issues_enabled: true
+blank_issues_enabled: false
+contact_links:
+ - name: Question
+ url: https://github.com/abseil/abseil-cpp/discussions
+ about: Have a question? Ask us anything! :-)
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..ff55e35
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,8 @@
+Thank you for your contribution to Abseil!
+
+Before submitting this PR, please be sure to read our [contributing
+guidelines](https://github.com/abseil/abseil-cpp/blob/master/CONTRIBUTING.md).
+
+If you are a Googler, please also note that it is required that you send us a
+Piper CL instead of using the GitHub pull-request process. The code propagation
+process will deliver the change to GitHub.
diff --git a/.gitignore b/.gitignore
index d54fa5a..3a729f4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
+# Bzlmod lockfile
+MODULE.bazel.lock
# Ignore all bazel-* symlinks.
/bazel-*
# Ignore Bazel verbose explanations
diff --git a/BUILD.bazel b/BUILD.bazel
index 79fb0ec..03122a9 100644
--- a/BUILD.bazel
+++ b/BUILD.bazel
@@ -1,4 +1,3 @@
-#
# Copyright 2020 The Abseil Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -23,3 +22,14 @@
"AUTHORS",
"LICENSE",
])
+
+# For building with clang-cl.
+# https://bazel.build/configure/windows#clang
+platform(
+ name = "x64_windows-clang-cl",
+ constraint_values = [
+ "@platforms//cpu:x86_64",
+ "@platforms//os:windows",
+ "@bazel_tools//tools/cpp:clang-cl",
+ ],
+)
diff --git a/CMake/AbseilDll.cmake b/CMake/AbseilDll.cmake
index 8bdf5a5..47f3bee 100644
--- a/CMake/AbseilDll.cmake
+++ b/CMake/AbseilDll.cmake
@@ -13,12 +13,11 @@
"base/internal/atomic_hook.h"
"base/internal/cycleclock.cc"
"base/internal/cycleclock.h"
+ "base/internal/cycleclock_config.h"
"base/internal/direct_mmap.h"
"base/internal/dynamic_annotations.h"
"base/internal/endian.h"
"base/internal/errno_saver.h"
- "base/internal/exponential_biased.cc"
- "base/internal/exponential_biased.h"
"base/internal/fast_type_id.h"
"base/internal/hide_ptr.h"
"base/internal/identity.h"
@@ -27,9 +26,9 @@
"base/internal/low_level_alloc.cc"
"base/internal/low_level_alloc.h"
"base/internal/low_level_scheduling.h"
+ "base/internal/nullability_impl.h"
"base/internal/per_thread_tls.h"
- "base/internal/periodic_sampler.cc"
- "base/internal/periodic_sampler.h"
+ "base/prefetch.h"
"base/internal/pretty_function.h"
"base/internal/raw_logging.cc"
"base/internal/raw_logging.h"
@@ -44,7 +43,6 @@
"base/internal/spinlock_wait.h"
"base/internal/sysinfo.cc"
"base/internal/sysinfo.h"
- "base/internal/thread_annotations.h"
"base/internal/thread_identity.cc"
"base/internal/thread_identity.h"
"base/internal/throw_delegate.cc"
@@ -53,9 +51,12 @@
"base/internal/unaligned_access.h"
"base/internal/unscaledcycleclock.cc"
"base/internal/unscaledcycleclock.h"
+ "base/internal/unscaledcycleclock_config.h"
"base/log_severity.cc"
"base/log_severity.h"
"base/macros.h"
+ "base/no_destructor.h"
+ "base/nullability.h"
"base/optimization.h"
"base/options.h"
"base/policy_checks.h"
@@ -72,9 +73,9 @@
"container/internal/btree.h"
"container/internal/btree_container.h"
"container/internal/common.h"
+ "container/internal/common_policy_traits.h"
"container/internal/compressed_tuple.h"
"container/internal/container_memory.h"
- "container/internal/counting_allocator.h"
"container/internal/hash_function_defaults.h"
"container/internal/hash_policy_traits.h"
"container/internal/hashtable_debug.h"
@@ -82,20 +83,38 @@
"container/internal/hashtablez_sampler.cc"
"container/internal/hashtablez_sampler.h"
"container/internal/hashtablez_sampler_force_weak_definition.cc"
- "container/internal/have_sse.h"
"container/internal/inlined_vector.h"
"container/internal/layout.h"
- "container/internal/node_hash_policy.h"
+ "container/internal/node_slot_policy.h"
"container/internal/raw_hash_map.h"
"container/internal/raw_hash_set.cc"
"container/internal/raw_hash_set.h"
"container/internal/tracked.h"
"container/node_hash_map.h"
"container/node_hash_set.h"
+ "crc/crc32c.cc"
+ "crc/crc32c.h"
+ "crc/internal/cpu_detect.cc"
+ "crc/internal/cpu_detect.h"
+ "crc/internal/crc32c.h"
+ "crc/internal/crc32c_inline.h"
+ "crc/internal/crc32_x86_arm_combined_simd.h"
+ "crc/internal/crc.cc"
+ "crc/internal/crc.h"
+ "crc/internal/crc_cord_state.cc"
+ "crc/internal/crc_cord_state.h"
+ "crc/internal/crc_internal.h"
+ "crc/internal/crc_x86_arm_combined.cc"
+ "crc/internal/crc_memcpy_fallback.cc"
+ "crc/internal/crc_memcpy.h"
+ "crc/internal/crc_memcpy_x86_arm_combined.cc"
+ "crc/internal/crc_non_temporal_memcpy.cc"
+ "crc/internal/crc_x86_arm_combined.cc"
+ "crc/internal/non_temporal_arm_intrinsics.h"
+ "crc/internal/non_temporal_memcpy.h"
"debugging/failure_signal_handler.cc"
"debugging/failure_signal_handler.h"
"debugging/leak_check.h"
- "debugging/leak_check_disable.cc"
"debugging/stacktrace.cc"
"debugging/stacktrace.h"
"debugging/symbolize.cc"
@@ -114,18 +133,68 @@
"debugging/internal/symbolize.h"
"debugging/internal/vdso_support.cc"
"debugging/internal/vdso_support.h"
+ "functional/any_invocable.h"
"functional/internal/front_binder.h"
"functional/bind_front.h"
"functional/function_ref.h"
+ "functional/internal/any_invocable.h"
"functional/internal/function_ref.h"
+ "functional/overload.h"
"hash/hash.h"
"hash/internal/city.h"
"hash/internal/city.cc"
"hash/internal/hash.h"
"hash/internal/hash.cc"
"hash/internal/spy_hash_state.h"
- "hash/internal/wyhash.h"
- "hash/internal/wyhash.cc"
+ "hash/internal/low_level_hash.h"
+ "hash/internal/low_level_hash.cc"
+ "log/absl_check.h"
+ "log/absl_log.h"
+ "log/absl_vlog_is_on.h"
+ "log/check.h"
+ "log/die_if_null.cc"
+ "log/die_if_null.h"
+ "log/globals.cc"
+ "log/globals.h"
+ "log/internal/append_truncated.h"
+ "log/internal/check_impl.h"
+ "log/internal/check_op.cc"
+ "log/internal/check_op.h"
+ "log/internal/conditions.cc"
+ "log/internal/conditions.h"
+ "log/internal/config.h"
+ "log/internal/fnmatch.h"
+ "log/internal/fnmatch.cc"
+ "log/internal/globals.cc"
+ "log/internal/globals.h"
+ "log/internal/log_format.cc"
+ "log/internal/log_format.h"
+ "log/internal/log_impl.h"
+ "log/internal/log_message.cc"
+ "log/internal/log_message.h"
+ "log/internal/log_sink_set.cc"
+ "log/internal/log_sink_set.h"
+ "log/internal/nullguard.cc"
+ "log/internal/nullguard.h"
+ "log/internal/nullstream.h"
+ "log/internal/proto.h"
+ "log/internal/proto.cc"
+ "log/internal/strip.h"
+ "log/internal/structured.h"
+ "log/internal/vlog_config.cc"
+ "log/internal/vlog_config.h"
+ "log/internal/voidify.h"
+ "log/initialize.cc"
+ "log/initialize.h"
+ "log/log.h"
+ "log/log_entry.cc"
+ "log/log_entry.h"
+ "log/log_sink.cc"
+ "log/log_sink.h"
+ "log/log_sink_registry.h"
+ "log/log_streamer.h"
+ "log/structured.h"
+ "log/vlog_is_on.h"
"memory/memory.h"
"meta/type_traits.h"
"numeric/bits.h"
@@ -133,6 +202,11 @@
"numeric/int128.h"
"numeric/internal/bits.h"
"numeric/internal/representation.h"
+ "profiling/internal/exponential_biased.cc"
+ "profiling/internal/exponential_biased.h"
+ "profiling/internal/periodic_sampler.cc"
+ "profiling/internal/periodic_sampler.h"
+ "profiling/internal/sample_recorder.h"
"random/bernoulli_distribution.h"
"random/beta_distribution.h"
"random/bit_gen_ref.h"
@@ -147,7 +221,6 @@
"random/internal/fast_uniform_bits.h"
"random/internal/generate_real.h"
"random/internal/iostream_state_saver.h"
- "random/internal/mock_helpers.h"
"random/internal/nonsecure_base.h"
"random/internal/pcg_engine.h"
"random/internal/platform.h"
@@ -182,6 +255,7 @@
"random/uniform_real_distribution.h"
"random/zipf_distribution.h"
"status/internal/status_internal.h"
+ "status/internal/status_internal.cc"
"status/internal/statusor_internal.h"
"status/status.h"
"status/status.cc"
@@ -193,28 +267,33 @@
"strings/ascii.h"
"strings/charconv.cc"
"strings/charconv.h"
+ "strings/charset.h"
"strings/cord.cc"
"strings/cord.h"
+ "strings/cord_analysis.cc"
+ "strings/cord_analysis.h"
+ "strings/cord_buffer.cc"
+ "strings/cord_buffer.h"
"strings/escaping.cc"
"strings/escaping.h"
"strings/internal/charconv_bigint.cc"
"strings/internal/charconv_bigint.h"
"strings/internal/charconv_parse.cc"
"strings/internal/charconv_parse.h"
+ "strings/internal/cord_data_edge.h"
"strings/internal/cord_internal.cc"
"strings/internal/cord_internal.h"
- "strings/internal/cord_rep_consume.h"
- "strings/internal/cord_rep_consume.cc"
"strings/internal/cord_rep_btree.cc"
"strings/internal/cord_rep_btree.h"
"strings/internal/cord_rep_btree_navigator.cc"
"strings/internal/cord_rep_btree_navigator.h"
"strings/internal/cord_rep_btree_reader.cc"
"strings/internal/cord_rep_btree_reader.h"
+ "strings/internal/cord_rep_crc.cc"
+ "strings/internal/cord_rep_crc.h"
+ "strings/internal/cord_rep_consume.h"
+ "strings/internal/cord_rep_consume.cc"
"strings/internal/cord_rep_flat.h"
- "strings/internal/cord_rep_ring.cc"
- "strings/internal/cord_rep_ring.h"
- "strings/internal/cord_rep_ring_reader.h"
"strings/internal/cordz_functions.cc"
"strings/internal/cordz_functions.h"
"strings/internal/cordz_handle.cc"
@@ -226,8 +305,15 @@
"strings/internal/cordz_statistics.h"
"strings/internal/cordz_update_scope.h"
"strings/internal/cordz_update_tracker.h"
+ "strings/internal/damerau_levenshtein_distance.h"
+ "strings/internal/damerau_levenshtein_distance.cc"
"strings/internal/stl_type_traits.h"
"strings/internal/string_constant.h"
+ "strings/internal/stringify_sink.h"
+ "strings/internal/stringify_sink.cc"
+ "strings/internal/has_absl_stringify.h"
+ "strings/has_absl_stringify.h"
+ "strings/has_ostream_operator.h"
"strings/match.cc"
"strings/match.h"
"strings/numbers.cc"
@@ -245,7 +331,6 @@
"strings/strip.h"
"strings/substitute.cc"
"strings/substitute.h"
- "strings/internal/char_map.h"
"strings/internal/escaping.h"
"strings/internal/escaping.cc"
"strings/internal/memutil.cc"
@@ -260,6 +345,7 @@
"strings/internal/str_format/bind.cc"
"strings/internal/str_format/bind.h"
"strings/internal/str_format/checker.h"
+ "strings/internal/str_format/constexpr_parser.h"
"strings/internal/str_format/extension.cc"
"strings/internal/str_format/extension.h"
"strings/internal/str_format/float_conversion.cc"
@@ -283,14 +369,26 @@
"synchronization/internal/create_thread_identity.cc"
"synchronization/internal/create_thread_identity.h"
"synchronization/internal/futex.h"
+ "synchronization/internal/futex_waiter.h"
+ "synchronization/internal/futex_waiter.cc"
"synchronization/internal/graphcycles.cc"
"synchronization/internal/graphcycles.h"
"synchronization/internal/kernel_timeout.h"
+ "synchronization/internal/kernel_timeout.cc"
"synchronization/internal/per_thread_sem.cc"
"synchronization/internal/per_thread_sem.h"
+ "synchronization/internal/pthread_waiter.h"
+ "synchronization/internal/pthread_waiter.cc"
+ "synchronization/internal/sem_waiter.h"
+ "synchronization/internal/sem_waiter.cc"
+ "synchronization/internal/stdcpp_waiter.h"
+ "synchronization/internal/stdcpp_waiter.cc"
"synchronization/internal/thread_pool.h"
- "synchronization/internal/waiter.cc"
"synchronization/internal/waiter.h"
+ "synchronization/internal/waiter_base.h"
+ "synchronization/internal/waiter_base.cc"
+ "synchronization/internal/win32_waiter.h"
+ "synchronization/internal/win32_waiter.cc"
"time/civil_time.cc"
"time/civil_time.h"
"time/clock.cc"
@@ -328,137 +426,238 @@
"types/bad_variant_access.cc"
"types/bad_variant_access.h"
"types/compare.h"
- "types/internal/conformance_aliases.h"
- "types/internal/conformance_archetype.h"
- "types/internal/conformance_profile.h"
- "types/internal/parentheses.h"
- "types/internal/transform_args.h"
"types/internal/variant.h"
"types/optional.h"
"types/internal/optional.h"
"types/span.h"
"types/internal/span.h"
"types/variant.h"
+ "utility/internal/if_constexpr.h"
"utility/utility.h"
+ "debugging/leak_check.cc"
)
set(ABSL_INTERNAL_DLL_TARGETS
- "stacktrace"
- "symbolize"
- "examine_stack"
- "failure_signal_handler"
- "debugging_internal"
- "demangle_internal"
- "leak_check"
- "leak_check_disable"
- "stack_consumption"
- "debugging"
- "hash"
- "spy_hash_state"
- "city"
- "memory"
- "strings"
- "strings_internal"
- "cord"
- "str_format"
- "str_format_internal"
- "pow10_helper"
- "int128"
- "numeric"
- "utility"
- "any"
- "bad_any_cast"
- "bad_any_cast_impl"
- "span"
- "optional"
- "bad_optional_access"
- "bad_variant_access"
- "variant"
- "compare"
+ "absl_check"
+ "absl_log"
"algorithm"
"algorithm_container"
- "graphcycles_internal"
- "kernel_timeout_internal"
- "synchronization"
- "thread_pool"
- "bind_front"
- "function_ref"
+ "any"
+ "any_invocable"
"atomic_hook"
- "log_severity"
- "raw_logging_internal"
- "spinlock_wait"
- "config"
- "dynamic_annotations"
- "core_headers"
- "malloc_internal"
- "base_internal"
+ "bad_any_cast"
+ "bad_any_cast_impl"
+ "bad_optional_access"
+ "bad_variant_access"
"base"
- "throw_delegate"
- "pretty_function"
- "endian"
+ "base_internal"
+ "bind_front"
"bits"
- "exponential_biased"
- "periodic_sampler"
- "scoped_set_env"
- "type_traits"
- "meta"
- "random_random"
- "random_bit_gen_ref"
- "random_distributions"
- "random_seed_gen_exception"
- "random_seed_sequences"
- "random_internal_traits"
- "random_internal_distribution_caller"
- "random_internal_distributions"
- "random_internal_fast_uniform_bits"
- "random_internal_seed_material"
- "random_internal_pool_urbg"
- "random_internal_explicit_seed_seq"
- "random_internal_sequence_urbg"
- "random_internal_salted_seed_seq"
- "random_internal_iostream_state_saver"
- "random_internal_generate_real"
- "random_internal_wide_multiply"
- "random_internal_fastmath"
- "random_internal_nonsecure_base"
- "random_internal_pcg_engine"
- "random_internal_randen_engine"
- "random_internal_platform"
- "random_internal_randen"
- "random_internal_randen_slow"
- "random_internal_randen_hwaes"
- "random_internal_randen_hwaes_impl"
- "random_internal_uniform_helper"
- "status"
- "time"
- "civil_time"
- "time_zone"
- "container"
"btree"
+ "check"
+ "city"
+ "civil_time"
+ "compare"
"compressed_tuple"
- "fixed_array"
- "inlined_vector_internal"
- "inlined_vector"
+ "config"
+ "container"
+ "container_common"
+ "container_memory"
+ "cord"
+ "cord_internal"
+ "cordz_functions"
+ "cordz_handle"
+ "cordz_info"
+ "cordz_sample_token"
+ "core_headers"
"counting_allocator"
+ "crc_cord_state"
+ "crc_cpu_detect"
+ "crc_internal"
+ "crc32c"
+ "debugging"
+ "debugging_internal"
+ "demangle_internal"
+ "die_if_null"
+ "dynamic_annotations"
+ "endian"
+ "examine_stack"
+ "exponential_biased"
+ "failure_signal_handler"
+ "fixed_array"
"flat_hash_map"
"flat_hash_set"
- "node_hash_map"
- "node_hash_set"
- "container_memory"
+ "function_ref"
+ "graphcycles_internal"
+ "hash"
"hash_function_defaults"
"hash_policy_traits"
- "hashtablez_sampler"
"hashtable_debug"
"hashtable_debug_hooks"
- "have_sse"
- "node_hash_policy"
- "raw_hash_map"
- "container_common"
- "raw_hash_set"
+ "hashtablez_sampler"
+ "inlined_vector"
+ "inlined_vector_internal"
+ "int128"
+ "kernel_timeout_internal"
"layout"
+ "leak_check"
+ "log_internal_check_impl"
+ "log_internal_check_op"
+ "log_internal_conditions"
+ "log_internal_config"
+ "log_internal_format"
+ "log_internal_globals"
+ "log_internal_log_impl"
+ "log_internal_proto"
+ "log_internal_message"
+ "log_internal_log_sink_set"
+ "log_internal_nullguard"
+ "log_internal_nullstream"
+ "log_internal_strip"
+ "log_internal_voidify"
+ "log_internal_append_truncated"
+ "log_globals"
+ "log_initialize"
+ "log"
+ "log_entry"
+ "log_sink"
+ "log_sink_registry"
+ "log_streamer"
+ "log_internal_structured"
+ "log_severity"
+ "log_structured"
+ "low_level_hash"
+ "malloc_internal"
+ "memory"
+ "meta"
+ "node_hash_map"
+ "node_hash_set"
+ "node_slot_policy"
+ "non_temporal_arm_intrinsics"
+ "non_temporal_memcpy"
+ "numeric"
+ "optional"
+ "periodic_sampler"
+ "pow10_helper"
+ "pretty_function"
+ "random_bit_gen_ref"
+ "random_distributions"
+ "random_internal_distribution_caller"
+ "random_internal_distributions"
+ "random_internal_explicit_seed_seq"
+ "random_internal_fastmath"
+ "random_internal_fast_uniform_bits"
+ "random_internal_generate_real"
+ "random_internal_iostream_state_saver"
+ "random_internal_nonsecure_base"
+ "random_internal_pcg_engine"
+ "random_internal_platform"
+ "random_internal_pool_urbg"
+ "random_internal_randen"
+ "random_internal_randen_engine"
+ "random_internal_randen_hwaes"
+ "random_internal_randen_hwaes_impl"
+ "random_internal_randen_slow"
+ "random_internal_salted_seed_seq"
+ "random_internal_seed_material"
+ "random_internal_sequence_urbg"
+ "random_internal_traits"
+ "random_internal_uniform_helper"
+ "random_internal_wide_multiply"
+ "random_random"
+ "random_seed_gen_exception"
+ "random_seed_sequences"
+ "raw_hash_map"
+ "raw_hash_set"
+ "raw_logging_internal"
+ "sample_recorder"
+ "scoped_set_env"
+ "span"
+ "spinlock_wait"
+ "spy_hash_state"
+ "stack_consumption"
+ "stacktrace"
+ "status"
+ "statusor"
+ "str_format"
+ "str_format_internal"
+ "strerror"
+ "strings"
+ "strings_internal"
+ "symbolize"
+ "synchronization"
+ "thread_pool"
+ "throw_delegate"
+ "time"
+ "time_zone"
"tracked"
+ "type_traits"
+ "utility"
+ "variant"
)
+set(ABSL_INTERNAL_TEST_DLL_FILES
+ "hash/hash_testing.h"
+ "log/scoped_mock_log.cc"
+ "log/scoped_mock_log.h"
+ "random/internal/chi_square.cc"
+ "random/internal/chi_square.h"
+ "random/internal/distribution_test_util.cc"
+ "random/internal/distribution_test_util.h"
+ "random/internal/mock_helpers.h"
+ "random/internal/mock_overload_set.h"
+ "random/mocking_bit_gen.h"
+ "random/mock_distributions.h"
+ "strings/cordz_test_helpers.h"
+ "strings/cord_test_helpers.h"
+)
+
+set(ABSL_INTERNAL_TEST_DLL_TARGETS
+ "cord_test_helpers"
+ "cordz_test_helpers"
+ "hash_testing"
+ "random_mocking_bit_gen"
+ "random_internal_distribution_test_util"
+ "random_internal_mock_overload_set"
+ "scoped_mock_log"
+)
+
+include(CheckCXXSourceCompiles)
+
+check_cxx_source_compiles(
+ [==[
+#ifdef _MSC_VER
+# if _MSVC_LANG < 201703L
+# error "The compiler defaults or is configured for C++ < 17"
+# endif
+#elif __cplusplus < 201703L
+# error "The compiler defaults or is configured for C++ < 17"
+#endif
+int main() { return 0; }
+]==]
+ ABSL_INTERNAL_AT_LEAST_CXX17)
+
+check_cxx_source_compiles(
+ [==[
+#ifdef _MSC_VER
+# if _MSVC_LANG < 202002L
+# error "The compiler defaults or is configured for C++ < 20"
+# endif
+#elif __cplusplus < 202002L
+# error "The compiler defaults or is configured for C++ < 20"
+#endif
+int main() { return 0; }
+]==]
+ ABSL_INTERNAL_AT_LEAST_CXX20)
+
+if(ABSL_INTERNAL_AT_LEAST_CXX20)
+ set(ABSL_INTERNAL_CXX_STD_FEATURE cxx_std_20)
+elseif(ABSL_INTERNAL_AT_LEAST_CXX17)
+ set(ABSL_INTERNAL_CXX_STD_FEATURE cxx_std_17)
+else()
+ set(ABSL_INTERNAL_CXX_STD_FEATURE cxx_std_14)
+endif()
+
function(absl_internal_dll_contains)
cmake_parse_arguments(ABSL_INTERNAL_DLL
""
@@ -481,6 +680,28 @@
endif()
endfunction()
+function(absl_internal_test_dll_contains)
+ cmake_parse_arguments(ABSL_INTERNAL_TEST_DLL
+ ""
+ "OUTPUT;TARGET"
+ ""
+ ${ARGN}
+ )
+
+ STRING(REGEX REPLACE "^absl::" "" _target ${ABSL_INTERNAL_TEST_DLL_TARGET})
+
+ list(FIND
+ ABSL_INTERNAL_TEST_DLL_TARGETS
+ "${_target}"
+ _index)
+
+ if (${_index} GREATER -1)
+ set(${ABSL_INTERNAL_TEST_DLL_OUTPUT} 1 PARENT_SCOPE)
+ else()
+ set(${ABSL_INTERNAL_TEST_DLL_OUTPUT} 0 PARENT_SCOPE)
+ endif()
+endfunction()
+
function(absl_internal_dll_targets)
cmake_parse_arguments(ABSL_INTERNAL_DLL
""
@@ -491,9 +712,12 @@
set(_deps "")
foreach(dep IN LISTS ABSL_INTERNAL_DLL_DEPS)
- absl_internal_dll_contains(TARGET ${dep} OUTPUT _contains)
- if (_contains)
+ absl_internal_dll_contains(TARGET ${dep} OUTPUT _dll_contains)
+ absl_internal_test_dll_contains(TARGET ${dep} OUTPUT _test_dll_contains)
+ if (_dll_contains)
list(APPEND _deps abseil_dll)
+ elseif (_test_dll_contains)
+ list(APPEND _deps abseil_test_dll)
else()
list(APPEND _deps ${dep})
endif()
@@ -505,41 +729,109 @@
endfunction()
function(absl_make_dll)
+ cmake_parse_arguments(ABSL_INTERNAL_MAKE_DLL
+ ""
+ "TEST"
+ ""
+ ${ARGN}
+ )
+
+ if (ABSL_INTERNAL_MAKE_DLL_TEST)
+ set(_dll "abseil_test_dll")
+ set(_dll_files ${ABSL_INTERNAL_TEST_DLL_FILES})
+ set(_dll_libs "abseil_dll" "GTest::gtest" "GTest::gmock")
+ set(_dll_compile_definitions "GTEST_LINKED_AS_SHARED_LIBRARY=1")
+ set(_dll_includes ${absl_gtest_src_dir}/googletest/include ${absl_gtest_src_dir}/googlemock/include)
+ set(_dll_consume "ABSL_CONSUME_TEST_DLL")
+ set(_dll_build "ABSL_BUILD_TEST_DLL")
+ else()
+ set(_dll "abseil_dll")
+ set(_dll_files ${ABSL_INTERNAL_DLL_FILES})
+ set(_dll_libs "")
+ set(_dll_compile_definitions "")
+ set(_dll_includes "")
+ set(_dll_consume "ABSL_CONSUME_DLL")
+ set(_dll_build "ABSL_BUILD_DLL")
+ endif()
+
add_library(
- abseil_dll
+ ${_dll}
SHARED
- "${ABSL_INTERNAL_DLL_FILES}"
+ ${_dll_files}
)
target_link_libraries(
- abseil_dll
+ ${_dll}
PRIVATE
+ ${_dll_libs}
${ABSL_DEFAULT_LINKOPTS}
)
- set_property(TARGET abseil_dll PROPERTY LINKER_LANGUAGE "CXX")
+ set_property(TARGET ${_dll} PROPERTY LINKER_LANGUAGE "CXX")
target_include_directories(
- abseil_dll
+ ${_dll}
PUBLIC
"$<BUILD_INTERFACE:${ABSL_COMMON_INCLUDE_DIRS}>"
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
+ PRIVATE
+ ${_dll_includes}
)
target_compile_options(
- abseil_dll
+ ${_dll}
PRIVATE
${ABSL_DEFAULT_COPTS}
)
+ foreach(cflag ${ABSL_CC_LIB_COPTS})
+ if(${cflag} MATCHES "^(-Wno|/wd)")
+ # These flags are needed to suppress warnings that might fire in our headers.
+ set(PC_CFLAGS "${PC_CFLAGS} ${cflag}")
+ elseif(${cflag} MATCHES "^(-W|/w[1234eo])")
+ # Don't impose our warnings on others.
+ else()
+ set(PC_CFLAGS "${PC_CFLAGS} ${cflag}")
+ endif()
+ endforeach()
+ string(REPLACE ";" " " PC_LINKOPTS "${ABSL_CC_LIB_LINKOPTS}")
+
+ FILE(GENERATE OUTPUT "${CMAKE_BINARY_DIR}/lib/pkgconfig/${_dll}.pc" CONTENT "\
+prefix=${CMAKE_INSTALL_PREFIX}\n\
+exec_prefix=\${prefix}\n\
+libdir=${CMAKE_INSTALL_FULL_LIBDIR}\n\
+includedir=${CMAKE_INSTALL_FULL_INCLUDEDIR}\n\
+\n\
+Name: ${_dll}\n\
+Description: Abseil DLL library\n\
+URL: https://abseil.io/\n\
+Version: ${absl_VERSION}\n\
+Libs: -L\${libdir} $<$<NOT:$<BOOL:${ABSL_CC_LIB_IS_INTERFACE}>>:-l${_dll}> ${PC_LINKOPTS}\n\
+Cflags: -I\${includedir}${PC_CFLAGS}\n")
+ INSTALL(FILES "${CMAKE_BINARY_DIR}/lib/pkgconfig/${_dll}.pc"
+ DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig")
+
target_compile_definitions(
- abseil_dll
+ ${_dll}
+ PUBLIC
+ ${_dll_compile_definitions}
PRIVATE
- ABSL_BUILD_DLL
+ ${_dll_build}
NOMINMAX
INTERFACE
${ABSL_CC_LIB_DEFINES}
+ ${_dll_consume}
)
- install(TARGETS abseil_dll EXPORT ${PROJECT_NAME}Targets
+
+ if(ABSL_PROPAGATE_CXX_STD)
+ # Abseil libraries require C++14 as the current minimum standard. When
+ # compiled with a higher minimum (either because it is the compiler's
+ # default or explicitly requested), then Abseil requires that standard.
+ target_compile_features(${_dll} PUBLIC ${ABSL_INTERNAL_CXX_STD_FEATURE})
+ endif()
+
+ install(TARGETS ${_dll} EXPORT ${PROJECT_NAME}Targets
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
+
+ add_library(absl::${_dll} ALIAS ${_dll})
endfunction()
diff --git a/CMake/AbseilHelpers.cmake b/CMake/AbseilHelpers.cmake
index 17c4f44..44f5bb3 100644
--- a/CMake/AbseilHelpers.cmake
+++ b/CMake/AbseilHelpers.cmake
@@ -26,6 +26,12 @@
set(ABSL_IDE_FOLDER Abseil)
endif()
+if(ABSL_USE_SYSTEM_INCLUDES)
+ set(ABSL_INTERNAL_INCLUDE_WARNING_GUARD SYSTEM)
+else()
+ set(ABSL_INTERNAL_INCLUDE_WARNING_GUARD "")
+endif()
+
# absl_cc_library()
#
# CMake function to imitate Bazel's cc_library rule.
@@ -40,7 +46,8 @@
# LINKOPTS: List of link options
# PUBLIC: Add this so that this library will be exported under absl::
# Also in IDE, target will appear in Abseil folder while non PUBLIC will be in Abseil/internal.
-# TESTONLY: When added, this target will only be built if BUILD_TESTING=ON.
+# TESTONLY: When added, this target will only be built if both
+# BUILD_TESTING=ON and ABSL_BUILD_TESTING=ON.
#
# Note:
# By default, absl_cc_library will always create a library named absl_${NAME},
@@ -73,7 +80,7 @@
# absl::fantastic_lib
# )
#
-# TODO: Implement "ALWAYSLINK"
+# TODO(b/320467376): Implement "ALWAYSLINK".
function(absl_cc_library)
cmake_parse_arguments(ABSL_CC_LIB
"DISABLE_INSTALL;PUBLIC;TESTONLY"
@@ -82,7 +89,9 @@
${ARGN}
)
- if(ABSL_CC_LIB_TESTONLY AND NOT BUILD_TESTING)
+ if(ABSL_CC_LIB_TESTONLY AND
+ NOT ((BUILD_TESTING AND ABSL_BUILD_TESTING) OR
+ (ABSL_BUILD_TEST_HELPERS AND ABSL_CC_LIB_PUBLIC)))
return()
endif()
@@ -123,10 +132,12 @@
if (${ABSL_BUILD_DLL})
if(ABSL_ENABLE_INSTALL)
absl_internal_dll_contains(TARGET ${_NAME} OUTPUT _in_dll)
+ absl_internal_test_dll_contains(TARGET ${_NAME} OUTPUT _in_test_dll)
else()
absl_internal_dll_contains(TARGET ${ABSL_CC_LIB_NAME} OUTPUT _in_dll)
+ absl_internal_test_dll_contains(TARGET ${ABSL_CC_LIB_NAME} OUTPUT _in_test_dll)
endif()
- if (${_in_dll})
+ if (${_in_dll} OR ${_in_test_dll})
# This target should be replaced by the DLL
set(_build_type "dll")
set(ABSL_CC_LIB_IS_INTERFACE 1)
@@ -141,34 +152,63 @@
endif()
# Generate a pkg-config file for every library:
- if((_build_type STREQUAL "static" OR _build_type STREQUAL "shared")
- AND ABSL_ENABLE_INSTALL)
- if(NOT ABSL_CC_LIB_TESTONLY)
- if(absl_VERSION)
- set(PC_VERSION "${absl_VERSION}")
- else()
- set(PC_VERSION "head")
- endif()
- foreach(dep ${ABSL_CC_LIB_DEPS})
- if(${dep} MATCHES "^absl::(.*)")
- # Join deps with commas.
+ if(ABSL_ENABLE_INSTALL)
+ if(absl_VERSION)
+ set(PC_VERSION "${absl_VERSION}")
+ else()
+ set(PC_VERSION "head")
+ endif()
+ if(NOT _build_type STREQUAL "dll")
+ set(LNK_LIB "${LNK_LIB} -labsl_${_NAME}")
+ endif()
+ foreach(dep ${ABSL_CC_LIB_DEPS})
+ if(${dep} MATCHES "^absl::(.*)")
+ # for DLL builds many libs are not created, but add
+ # the pkgconfigs nevertheless, pointing to the dll.
+ if(_build_type STREQUAL "dll")
+ # hide this MATCHES in an if-clause so it doesn't overwrite
+ # the CMAKE_MATCH_1 from (${dep} MATCHES "^absl::(.*)")
+ if(NOT PC_DEPS MATCHES "abseil_dll")
+ # Join deps with commas.
+ if(PC_DEPS)
+ set(PC_DEPS "${PC_DEPS},")
+ endif()
+ # don't duplicate dll-dep if it exists already
+ set(PC_DEPS "${PC_DEPS} abseil_dll = ${PC_VERSION}")
+ set(LNK_LIB "${LNK_LIB} -labseil_dll")
+ endif()
+ else()
+ # Join deps with commas.
if(PC_DEPS)
set(PC_DEPS "${PC_DEPS},")
endif()
set(PC_DEPS "${PC_DEPS} absl_${CMAKE_MATCH_1} = ${PC_VERSION}")
endif()
- endforeach()
- foreach(cflag ${ABSL_CC_LIB_COPTS})
- if(${cflag} MATCHES "^(-Wno|/wd)")
- # These flags are needed to suppress warnings that might fire in our headers.
- set(PC_CFLAGS "${PC_CFLAGS} ${cflag}")
- elseif(${cflag} MATCHES "^(-W|/w[1234eo])")
- # Don't impose our warnings on others.
- else()
- set(PC_CFLAGS "${PC_CFLAGS} ${cflag}")
- endif()
- endforeach()
- FILE(GENERATE OUTPUT "${CMAKE_BINARY_DIR}/lib/pkgconfig/absl_${_NAME}.pc" CONTENT "\
+ endif()
+ endforeach()
+ set(skip_next_cflag OFF)
+ foreach(cflag ${ABSL_CC_LIB_COPTS})
+ if(skip_next_cflag)
+ set(skip_next_cflag OFF)
+ elseif(${cflag} MATCHES "^-Xarch_")
+ # An -Xarch_ flag implies that its successor only applies to the
+ # specified platform. Filter both of them out before the successor
+ # reaches the "^-m" filter.
+ set(skip_next_cflag ON)
+ elseif(${cflag} MATCHES "^(-Wno|/wd)")
+ # These flags are needed to suppress warnings that might fire in our headers.
+ set(PC_CFLAGS "${PC_CFLAGS} ${cflag}")
+ elseif(${cflag} MATCHES "^(-W|/w[1234eo])")
+ # Don't impose our warnings on others.
+ elseif(${cflag} MATCHES "^-m")
+ # Don't impose CPU instruction requirements on others, as
+ # the code performs feature detection on runtime.
+ else()
+ set(PC_CFLAGS "${PC_CFLAGS} ${cflag}")
+ endif()
+ endforeach()
+ string(REPLACE ";" " " PC_LINKOPTS "${ABSL_CC_LIB_LINKOPTS}")
+ FILE(GENERATE OUTPUT "${CMAKE_BINARY_DIR}/lib/pkgconfig/absl_${_NAME}.pc" CONTENT "\
prefix=${CMAKE_INSTALL_PREFIX}\n\
exec_prefix=\${prefix}\n\
libdir=${CMAKE_INSTALL_FULL_LIBDIR}\n\
@@ -179,11 +219,10 @@
URL: https://abseil.io/\n\
Version: ${PC_VERSION}\n\
Requires:${PC_DEPS}\n\
-Libs: -L\${libdir} $<JOIN:${ABSL_CC_LIB_LINKOPTS}, > $<$<NOT:$<BOOL:${ABSL_CC_LIB_IS_INTERFACE}>>:-labsl_${_NAME}>\n\
+Libs: -L\${libdir} $<$<NOT:$<BOOL:${ABSL_CC_LIB_IS_INTERFACE}>>:${LNK_LIB}> ${PC_LINKOPTS}\n\
Cflags: -I\${includedir}${PC_CFLAGS}\n")
- INSTALL(FILES "${CMAKE_BINARY_DIR}/lib/pkgconfig/absl_${_NAME}.pc"
- DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig")
- endif()
+ INSTALL(FILES "${CMAKE_BINARY_DIR}/lib/pkgconfig/absl_${_NAME}.pc"
+ DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig")
endif()
if(NOT ABSL_CC_LIB_IS_INTERFACE)
@@ -236,7 +275,7 @@
# unconditionally.
set_property(TARGET ${_NAME} PROPERTY LINKER_LANGUAGE "CXX")
- target_include_directories(${_NAME}
+ target_include_directories(${_NAME} ${ABSL_INTERNAL_INCLUDE_WARNING_GUARD}
PUBLIC
"$<BUILD_INTERFACE:${ABSL_COMMON_INCLUDE_DIRS}>"
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
@@ -255,21 +294,10 @@
endif()
if(ABSL_PROPAGATE_CXX_STD)
- # Abseil libraries require C++11 as the current minimum standard.
- # Top-level application CMake projects should ensure a consistent C++
- # standard for all compiled sources by setting CMAKE_CXX_STANDARD.
- target_compile_features(${_NAME} PUBLIC cxx_std_11)
- else()
- # Note: This is legacy (before CMake 3.8) behavior. Setting the
- # target-level CXX_STANDARD property to ABSL_CXX_STANDARD (which is
- # initialized by CMAKE_CXX_STANDARD) should have no real effect, since
- # that is the default value anyway.
- #
- # CXX_STANDARD_REQUIRED does guard against the top-level CMake project
- # not having enabled CMAKE_CXX_STANDARD_REQUIRED (which prevents
- # "decaying" to an older standard if the requested one isn't available).
- set_property(TARGET ${_NAME} PROPERTY CXX_STANDARD ${ABSL_CXX_STANDARD})
- set_property(TARGET ${_NAME} PROPERTY CXX_STANDARD_REQUIRED ON)
+ # Abseil libraries require C++14 as the current minimum standard. When
+ # compiled with a higher standard (either because it is the compiler's
+ # default or explicitly requested), then Abseil requires that standard.
+ target_compile_features(${_NAME} PUBLIC ${ABSL_INTERNAL_CXX_STD_FEATURE})
endif()
# When being installed, we lose the absl_ prefix. We want to put it back
@@ -284,7 +312,7 @@
else()
# Generating header-only library
add_library(${_NAME} INTERFACE)
- target_include_directories(${_NAME}
+ target_include_directories(${_NAME} ${ABSL_INTERNAL_INCLUDE_WARNING_GUARD}
INTERFACE
"$<BUILD_INTERFACE:${ABSL_COMMON_INCLUDE_DIRS}>"
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
@@ -303,19 +331,14 @@
target_compile_definitions(${_NAME} INTERFACE ${ABSL_CC_LIB_DEFINES})
if(ABSL_PROPAGATE_CXX_STD)
- # Abseil libraries require C++11 as the current minimum standard.
+ # Abseil libraries require C++14 as the current minimum standard.
# Top-level application CMake projects should ensure a consistent C++
# standard for all compiled sources by setting CMAKE_CXX_STANDARD.
- target_compile_features(${_NAME} INTERFACE cxx_std_11)
-
- # (INTERFACE libraries can't have the CXX_STANDARD property set, so there
- # is no legacy behavior else case).
+ target_compile_features(${_NAME} INTERFACE ${ABSL_INTERNAL_CXX_STD_FEATURE})
endif()
endif()
- # TODO currently we don't install googletest alongside abseil sources, so
- # installed abseil can't be tested.
- if(NOT ABSL_CC_LIB_TESTONLY AND ABSL_ENABLE_INSTALL)
+ if(ABSL_ENABLE_INSTALL)
install(TARGETS ${_NAME} EXPORT ${PROJECT_NAME}Targets
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
@@ -364,7 +387,7 @@
# GTest::gtest_main
# )
function(absl_cc_test)
- if(NOT BUILD_TESTING)
+ if(NOT (BUILD_TESTING AND ABSL_BUILD_TESTING))
return()
endif()
@@ -381,7 +404,7 @@
target_sources(${_NAME} PRIVATE ${ABSL_CC_TEST_SRCS})
target_include_directories(${_NAME}
PUBLIC ${ABSL_COMMON_INCLUDE_DIRS}
- PRIVATE ${GMOCK_INCLUDE_DIRS} ${GTEST_INCLUDE_DIRS}
+ PRIVATE ${absl_gtest_src_dir}/googletest/include ${absl_gtest_src_dir}/googlemock/include
)
if (${ABSL_BUILD_DLL})
@@ -389,6 +412,7 @@
PUBLIC
${ABSL_CC_TEST_DEFINES}
ABSL_CONSUME_DLL
+ ABSL_CONSUME_TEST_DLL
GTEST_LINKED_AS_SHARED_LIBRARY=1
)
@@ -397,6 +421,10 @@
DEPS ${ABSL_CC_TEST_DEPS}
OUTPUT ABSL_CC_TEST_DEPS
)
+ absl_internal_dll_targets(
+ DEPS ${ABSL_CC_TEST_LINKOPTS}
+ OUTPUT ABSL_CC_TEST_LINKOPTS
+ )
else()
target_compile_definitions(${_NAME}
PUBLIC
@@ -415,30 +443,11 @@
set_property(TARGET ${_NAME} PROPERTY FOLDER ${ABSL_IDE_FOLDER}/test)
if(ABSL_PROPAGATE_CXX_STD)
- # Abseil libraries require C++11 as the current minimum standard.
+ # Abseil libraries require C++14 as the current minimum standard.
# Top-level application CMake projects should ensure a consistent C++
# standard for all compiled sources by setting CMAKE_CXX_STANDARD.
- target_compile_features(${_NAME} PUBLIC cxx_std_11)
- else()
- # Note: This is legacy (before CMake 3.8) behavior. Setting the
- # target-level CXX_STANDARD property to ABSL_CXX_STANDARD (which is
- # initialized by CMAKE_CXX_STANDARD) should have no real effect, since
- # that is the default value anyway.
- #
- # CXX_STANDARD_REQUIRED does guard against the top-level CMake project
- # not having enabled CMAKE_CXX_STANDARD_REQUIRED (which prevents
- # "decaying" to an older standard if the requested one isn't available).
- set_property(TARGET ${_NAME} PROPERTY CXX_STANDARD ${ABSL_CXX_STANDARD})
- set_property(TARGET ${_NAME} PROPERTY CXX_STANDARD_REQUIRED ON)
+ target_compile_features(${_NAME} PUBLIC ${ABSL_INTERNAL_CXX_STD_FEATURE})
endif()
add_test(NAME ${_NAME} COMMAND ${_NAME})
endfunction()
-
-
-function(check_target my_target)
- if(NOT TARGET ${my_target})
- message(FATAL_ERROR " ABSL: compiling absl requires a ${my_target} CMake target in your project,
- see CMake/README.md for more details")
- endif(NOT TARGET ${my_target})
-endfunction()
diff --git a/CMake/Googletest/CMakeLists.txt.in b/CMake/Googletest/CMakeLists.txt.in
index 5769e3a..75691b1 100644
--- a/CMake/Googletest/CMakeLists.txt.in
+++ b/CMake/Googletest/CMakeLists.txt.in
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8.2)
+cmake_minimum_required(VERSION 3.10)
project(googletest-external NONE)
diff --git a/CMake/README.md b/CMake/README.md
index f8b27e6..c7ddee6 100644
--- a/CMake/README.md
+++ b/CMake/README.md
@@ -20,8 +20,10 @@
### Step-by-Step Instructions
1. If you want to build the Abseil tests, integrate the Abseil dependency
-[Google Test](https://github.com/google/googletest) into your CMake project. To disable Abseil tests, you have to pass
-`-DBUILD_TESTING=OFF` when configuring your project with CMake.
+[Google Test](https://github.com/google/googletest) into your CMake
+project. To disable Abseil tests, you have to pass either
+`-DBUILD_TESTING=OFF` or `-DABSL_BUILD_TESTING=OFF` when configuring your
+project with CMake.
2. Download Abseil and copy it into a subdirectory in your CMake project or add
Abseil as a [git submodule](https://git-scm.com/docs/git-submodule) in your
@@ -37,12 +39,12 @@
Here is a short CMakeLists.txt example of an application project using Abseil.
```cmake
-cmake_minimum_required(VERSION 3.8.2)
+cmake_minimum_required(VERSION 3.10)
project(my_app_project)
# Pick the C++ standard to compile with.
-# Abseil currently supports C++11, C++14, and C++17.
-set(CMAKE_CXX_STANDARD 11)
+# Abseil currently supports C++14, C++17, and C++20.
+set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_subdirectory(abseil-cpp)
@@ -60,7 +62,7 @@
example:
```cmake
-cmake_minimum_required(VERSION 3.8.2)
+cmake_minimum_required(VERSION 3.10)
project(my_lib_project)
# Leave C++ standard up to the root application, so set it only if this is the
@@ -91,7 +93,8 @@
### Running Abseil Tests with CMake
-Use the `-DBUILD_TESTING=ON` flag to run Abseil tests.
+Use the `-DABSL_BUILD_TESTING=ON` flag to run Abseil tests. Note that
+BUILD_TESTING must also be on (the default).
You will need to provide Abseil with a Googletest dependency. There are two
options for how to do this:
@@ -109,7 +112,7 @@
cd path/to/abseil-cpp
mkdir build
cd build
-cmake -DBUILD_TESTING=ON -DABSL_USE_GOOGLETEST_HEAD=ON ..
+cmake -DABSL_BUILD_TESTING=ON -DABSL_USE_GOOGLETEST_HEAD=ON ..
make -j
ctest
```
@@ -167,7 +170,7 @@
cmake --build /temporary/build/abseil-cpp --target install
```
-# CMake Option Synposis
+# CMake Option Synopsis
## Enable Standard CMake Installation
@@ -175,7 +178,7 @@
## Google Test Options
-`-DBUILD_TESTING=ON` must be set to enable testing
+`-DABSL_BUILD_TESTING=ON` must be set to enable testing
- Have Abseil download and build Google Test for you: `-DABSL_USE_EXTERNAL_GOOGLETEST=OFF` (default)
- Download and build latest Google Test: `-DABSL_USE_GOOGLETEST_HEAD=ON`
diff --git a/CMake/install_test_project/CMakeLists.txt b/CMake/install_test_project/CMakeLists.txt
index eebfe61..30c23b2 100644
--- a/CMake/install_test_project/CMakeLists.txt
+++ b/CMake/install_test_project/CMakeLists.txt
@@ -15,11 +15,11 @@
# A simple CMakeLists.txt for testing cmake installation
-cmake_minimum_required(VERSION 3.5)
+cmake_minimum_required(VERSION 3.10)
project(absl_cmake_testing CXX)
add_executable(simple simple.cc)
find_package(absl REQUIRED)
-target_link_libraries(simple absl::strings)
+target_link_libraries(simple absl::strings absl::config)
diff --git a/CMake/install_test_project/simple.cc b/CMake/install_test_project/simple.cc
index e9e3529..7daa7f0 100644
--- a/CMake/install_test_project/simple.cc
+++ b/CMake/install_test_project/simple.cc
@@ -14,8 +14,17 @@
// limitations under the License.
#include <iostream>
+#include "absl/base/config.h"
#include "absl/strings/substitute.h"
+#if !defined(ABSL_LTS_RELEASE_VERSION) || ABSL_LTS_RELEASE_VERSION != 99998877
+#error ABSL_LTS_RELEASE_VERSION is not set correctly.
+#endif
+
+#if !defined(ABSL_LTS_RELEASE_PATCH_LEVEL) || ABSL_LTS_RELEASE_PATCH_LEVEL != 0
+#error ABSL_LTS_RELEASE_PATCH_LEVEL is not set correctly.
+#endif
+
int main(int argc, char** argv) {
for (int i = 0; i < argc; ++i) {
std::cout << absl::Substitute("Arg $0: $1\n", i, argv[i]);
diff --git a/CMake/install_test_project/test.sh b/CMake/install_test_project/test.sh
index 5a78c92..cc028ba 100755
--- a/CMake/install_test_project/test.sh
+++ b/CMake/install_test_project/test.sh
@@ -55,10 +55,10 @@
-DABSL_USE_EXTERNAL_GOOGLETEST=ON \
-DABSL_FIND_GOOGLETEST=ON \
-DCMAKE_BUILD_TYPE=Release \
- -DBUILD_TESTING=ON \
+ -DABSL_BUILD_TESTING=ON \
-DBUILD_SHARED_LIBS="${build_shared_libs}"
make -j $(nproc)
-ctest -j $(nproc)
+ctest -j $(nproc) --output-on-failure
make install
ldconfig
popd
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7c8bfff..87f8ae8 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -14,12 +14,9 @@
# limitations under the License.
#
-# Most widely used distributions have cmake 3.5 or greater available as of March
-# 2019. A notable exception is RHEL-7 (CentOS7). You can install a current
-# version of CMake by first installing Extra Packages for Enterprise Linux
-# (https://fedoraproject.org/wiki/EPEL#Extra_Packages_for_Enterprise_Linux_.28EPEL.29)
-# and then issuing `yum install cmake3` on the command line.
-cmake_minimum_required(VERSION 3.5)
+# https://github.com/google/oss-policies-info/blob/main/foundational-cxx-support-matrix.md
+# As of 2022-09-06, CMake 3.10 is the minimum supported version.
+cmake_minimum_required(VERSION 3.10)
# Compiler id for Apple Clang is now AppleClang.
if (POLICY CMP0025)
@@ -36,6 +33,11 @@
cmake_policy(SET CMP0048 NEW)
endif (POLICY CMP0048)
+# Honor the GTest_ROOT variable if specified
+if (POLICY CMP0074)
+ cmake_policy(SET CMP0074 NEW)
+endif (POLICY CMP0074)
+
# option() honor variables
if (POLICY CMP0077)
cmake_policy(SET CMP0077 NEW)
@@ -46,9 +48,15 @@
cmake_policy(SET CMP0091 NEW)
endif (POLICY CMP0091)
-# Set BUILD_TESTING to OFF by default.
-# This must come before the project() and include(CTest) lines.
-OPTION(BUILD_TESTING "Build tests" OFF)
+# try_compile() honors the CMAKE_CXX_STANDARD value
+if (POLICY CMP0067)
+ cmake_policy(SET CMP0067 NEW)
+endif (POLICY CMP0067)
+
+# Allow the user to specify the CMAKE_MSVC_DEBUG_INFORMATION_FORMAT
+if (POLICY CMP0141)
+ cmake_policy(SET CMP0141 NEW)
+endif (POLICY CMP0141)
project(absl LANGUAGES CXX)
include(CTest)
@@ -68,12 +76,16 @@
endif()
option(ABSL_PROPAGATE_CXX_STD
- "Use CMake C++ standard meta features (e.g. cxx_std_11) that propagate to targets that link to Abseil"
+ "Use CMake C++ standard meta features (e.g. cxx_std_14) that propagate to targets that link to Abseil"
OFF) # TODO: Default to ON for CMake 3.8 and greater.
-if((${CMAKE_VERSION} VERSION_GREATER_EQUAL 3.8) AND (NOT ABSL_PROPAGATE_CXX_STD))
+if(NOT ABSL_PROPAGATE_CXX_STD)
message(WARNING "A future Abseil release will default ABSL_PROPAGATE_CXX_STD to ON for CMake 3.8 and up. We recommend enabling this option to ensure your project still builds correctly.")
endif()
+option(ABSL_USE_SYSTEM_INCLUDES
+ "Silence warnings in Abseil headers by marking them as SYSTEM includes"
+ OFF)
+
list(APPEND CMAKE_MODULE_PATH
${CMAKE_CURRENT_LIST_DIR}/CMake
${CMAKE_CURRENT_LIST_DIR}/absl/copts
@@ -111,8 +123,15 @@
include(CMakeDependentOption)
+option(ABSL_BUILD_TESTING
+ "If ON, Abseil will build all of Abseil's own tests." OFF)
+
+option(ABSL_BUILD_TEST_HELPERS
+ "If ON, Abseil will build libraries that you can use to write tests against Abseil code. This option requires that Abseil is configured to use GoogleTest."
+ OFF)
+
option(ABSL_USE_EXTERNAL_GOOGLETEST
- "If ON, Abseil will assume that the targets for GoogleTest are already provided by the including project. This makes sense when Abseil is used with add_subproject." OFF)
+ "If ON, Abseil will assume that the targets for GoogleTest are already provided by the including project. This makes sense when Abseil is used with add_subdirectory." OFF)
cmake_dependent_option(ABSL_FIND_GOOGLETEST
"If ON, Abseil will use find_package(GTest) rather than assuming that GoogleTest is already provided by the including project."
@@ -130,13 +149,18 @@
"If ABSL_USE_GOOGLETEST_HEAD is OFF and ABSL_GOOGLETEST_URL is not set, specifies the directory of a local GoogleTest checkout."
)
-if(BUILD_TESTING)
- ## check targets
+if((BUILD_TESTING AND ABSL_BUILD_TESTING) OR ABSL_BUILD_TEST_HELPERS)
if (ABSL_USE_EXTERNAL_GOOGLETEST)
if (ABSL_FIND_GOOGLETEST)
find_package(GTest REQUIRED)
- else()
- if (NOT TARGET gtest AND NOT TARGET GTest::gtest)
+ elseif(NOT TARGET GTest::gtest)
+ if(TARGET gtest)
+ # When Google Test is included directly rather than through find_package, the aliases are missing.
+ add_library(GTest::gtest ALIAS gtest)
+ add_library(GTest::gtest_main ALIAS gtest_main)
+ add_library(GTest::gmock ALIAS gmock)
+ add_library(GTest::gmock_main ALIAS gmock_main)
+ else()
message(FATAL_ERROR "ABSL_USE_EXTERNAL_GOOGLETEST is ON and ABSL_FIND_GOOGLETEST is OFF, which means that the top-level project must build the Google Test project. However, the target gtest was not found.")
endif()
endif()
@@ -146,7 +170,7 @@
message(FATAL_ERROR "Do not set both ABSL_USE_GOOGLETEST_HEAD and ABSL_GOOGLETEST_DOWNLOAD_URL")
endif()
if(ABSL_USE_GOOGLETEST_HEAD)
- set(absl_gtest_download_url "https://github.com/google/googletest/archive/master.zip")
+ set(absl_gtest_download_url "https://github.com/google/googletest/archive/main.zip")
elseif(ABSL_GOOGLETEST_DOWNLOAD_URL)
set(absl_gtest_download_url ${ABSL_GOOGLETEST_DOWNLOAD_URL})
endif()
@@ -157,25 +181,6 @@
endif()
include(CMake/Googletest/DownloadGTest.cmake)
endif()
-
- if (NOT ABSL_FIND_GOOGLETEST)
- # When Google Test is included directly rather than through find_package, the aliases are missing.
- add_library(GTest::gtest_main ALIAS gtest_main)
- add_library(GTest::gtest ALIAS gtest)
- add_library(GTest::gmock ALIAS gmock)
- endif()
-
- check_target(GTest::gtest)
- check_target(GTest::gtest_main)
- check_target(GTest::gmock)
- check_target(GTest::gmock_main)
-
- list(APPEND ABSL_TEST_COMMON_LIBRARIES
- GTest::gtest_main
- GTest::gtest
- GTest::gmock
- ${CMAKE_THREAD_LIBS_INIT}
- )
endif()
add_subdirectory(absl)
@@ -230,4 +235,46 @@
PATTERN "copts" EXCLUDE
PATTERN "testdata" EXCLUDE
)
+
+ # Rewrite options.h to use the compiled ABI.
+ file(READ "absl/base/options.h" ABSL_INTERNAL_OPTIONS_H_CONTENTS)
+
+ # Handle features that require at least C++20.
+ if (ABSL_INTERNAL_AT_LEAST_CXX20)
+ foreach(FEATURE "ORDERING")
+ string(REPLACE
+ "#define ABSL_OPTION_USE_STD_${FEATURE} 2"
+ "#define ABSL_OPTION_USE_STD_${FEATURE} 1"
+ ABSL_INTERNAL_OPTIONS_H_PINNED
+ "${ABSL_INTERNAL_OPTIONS_H_CONTENTS}")
+ set(ABSL_INTERNAL_OPTIONS_H_CONTENTS "${ABSL_INTERNAL_OPTIONS_H_PINNED}")
+ endforeach()
+ endif()
+
+ # Handle features that require at least C++17.
+ if (ABSL_INTERNAL_AT_LEAST_CXX17)
+ foreach(FEATURE "ANY" "OPTIONAL" "STRING_VIEW" "VARIANT")
+ string(REPLACE
+ "#define ABSL_OPTION_USE_STD_${FEATURE} 2"
+ "#define ABSL_OPTION_USE_STD_${FEATURE} 1"
+ ABSL_INTERNAL_OPTIONS_H_PINNED
+ "${ABSL_INTERNAL_OPTIONS_H_CONTENTS}")
+ set(ABSL_INTERNAL_OPTIONS_H_CONTENTS "${ABSL_INTERNAL_OPTIONS_H_PINNED}")
+ endforeach()
+ endif()
+
+ # Any feature that still has the value of 2 (because it was not handled above)
+ # should be set to 0.
+ string(REGEX REPLACE
+ "#define ABSL_OPTION_USE_STD_([^ ]*) 2"
+ "#define ABSL_OPTION_USE_STD_\\1 0"
+ ABSL_INTERNAL_OPTIONS_H_PINNED
+ "${ABSL_INTERNAL_OPTIONS_H_CONTENTS}")
+
+ file(WRITE "${CMAKE_BINARY_DIR}/options-pinned.h" "${ABSL_INTERNAL_OPTIONS_H_PINNED}")
+
+ install(FILES "${CMAKE_BINARY_DIR}/options-pinned.h"
+ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/absl/base
+ RENAME "options.h")
+
endif() # ABSL_ENABLE_INSTALL
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 9dadae9..a87254c 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -75,9 +75,9 @@
## Guidelines for Pull Requests
-* If you are a Googler, it is preferable to first create an internal CL and
- have it reviewed and submitted. The code propagation process will deliver
- the change to GitHub.
+* If you are a Googler, it is required that you send us a Piper CL instead of
+ using the GitHub pull-request process. The code propagation process will
+ deliver the change to GitHub.
* Create **small PRs** that are narrowly focused on **addressing a single
concern**. We often receive PRs that are trying to fix several things at a
diff --git a/MODULE.bazel b/MODULE.bazel
new file mode 100644
index 0000000..fc1534a
--- /dev/null
+++ b/MODULE.bazel
@@ -0,0 +1,42 @@
+# Copyright 2024 The Abseil Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# https://bazel.build/external/overview#bzlmod
+
+module(
+ name = "abseil-cpp",
+ version = "head",
+ compatibility_level = 1,
+)
+
+cc_configure = use_extension("@bazel_tools//tools/cpp:cc_configure.bzl", "cc_configure_extension")
+use_repo(cc_configure, "local_config_cc")
+
+# Only direct dependencies need to be listed below.
+# Please keep the versions in sync with the versions in the WORKSPACE file.
+
+bazel_dep(name = "bazel_skylib",
+ version = "1.5.0")
+
+bazel_dep(name = "google_benchmark",
+ version = "1.8.3",
+ repo_name = "com_github_google_benchmark",
+ dev_dependency = True)
+
+bazel_dep(name = "googletest",
+ version = "1.14.0.bcr.1",
+ repo_name = "com_google_googletest")
+
+bazel_dep(name = "platforms",
+ version = "0.0.8")
diff --git a/PrivacyInfo.xcprivacy b/PrivacyInfo.xcprivacy
new file mode 100644
index 0000000..3ff4a9d
--- /dev/null
+++ b/PrivacyInfo.xcprivacy
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>NSPrivacyTracking</key>
+ <false/>
+ <key>NSPrivacyCollectedDataTypes</key>
+ <array/>
+ <key>NSPrivacyTrackingDomains</key>
+ <array/>
+ <key>NSPrivacyAccessedAPITypes</key>
+ <array/>
+</dict>
+</plist>
diff --git a/README.md b/README.md
index 264c4b3..f834fcd 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
# Abseil - C++ Common Libraries
The repository contains the Abseil C++ library code. Abseil is an open-source
-collection of C++ code (compliant to C++11) designed to augment the C++
+collection of C++ code (compliant to C++14) designed to augment the C++
standard library.
## Table of Contents
@@ -46,26 +46,28 @@
[Bazel](https://bazel.build) and [CMake](https://cmake.org/) are the official
build systems for Abseil.
-
See the [quickstart](https://abseil.io/docs/cpp/quickstart) for more information
on building Abseil using the Bazel build system.
-
If you require CMake support, please check the [CMake build
instructions](CMake/README.md) and [CMake
Quickstart](https://abseil.io/docs/cpp/quickstart-cmake).
+<a name="support"></a>
## Support
-Abseil is officially supported on many platforms. See the [Abseil
-platform support
-guide](https://abseil.io/docs/cpp/platforms/platforms) for details on
-supported operating systems, compilers, CPUs, etc.
+Abseil follows Google's [Foundational C++ Support
+Policy](https://opensource.google/documentation/policies/cplusplus-support). See
+[this
+table](https://github.com/google/oss-policies-info/blob/main/foundational-cxx-support-matrix.md)
+for a list of currently supported versions compilers, platforms, and build
+tools.
+<a name="codemap"></a>
## Codemap
Abseil contains the following C++ library components:
-* [`base`](absl/base/) Abseil Fundamentals
+* [`base`](absl/base/)
<br /> The `base` library contains initialization code and other code which
all other Abseil code depends on. Code within `base` may not depend on any
other code (other than the C++ standard library).
@@ -78,26 +80,42 @@
* [`container`](absl/container/)
<br /> The `container` library contains additional STL-style containers,
including Abseil's unordered "Swiss table" containers.
+* [`crc`](absl/crc/) The `crc` library contains code for
+ computing error-detecting cyclic redundancy checks on data.
* [`debugging`](absl/debugging/)
<br /> The `debugging` library contains code useful for enabling leak
checks, and stacktrace and symbolization utilities.
+* [`flags`](absl/flags/)
+ <br /> The `flags` library contains code for handling command line flags for
+ libraries and binaries built with Abseil.
* [`hash`](absl/hash/)
<br /> The `hash` library contains the hashing framework and default hash
functor implementations for hashable types in Abseil.
+* [`log`](absl/log/)
+ <br /> The `log` library contains `LOG` and `CHECK` macros and facilities
+ for writing logged messages out to disk, `stderr`, or user-extensible
+ destinations.
* [`memory`](absl/memory/)
- <br /> The `memory` library contains C++11-compatible versions of
- `std::make_unique()` and related memory management facilities.
+ <br /> The `memory` library contains memory management facilities that augment
+ C++'s `<memory>` library.
* [`meta`](absl/meta/)
- <br /> The `meta` library contains C++11-compatible versions of type checks
+ <br /> The `meta` library contains compatible versions of type checks
available within C++14 and C++17 versions of the C++ `<type_traits>` library.
* [`numeric`](absl/numeric/)
- <br /> The `numeric` library contains C++11-compatible 128-bit integers.
+ <br /> The `numeric` library contains 128-bit integer types as well as
+ implementations of C++20's bitwise math functions.
+* [`profiling`](absl/profiling/)
+ <br /> The `profiling` library contains utility code for profiling C++
+ entities. It is currently a private dependency of other Abseil libraries.
+* [`random`](absl/random/)
+ <br /> The `random` library contains functions for generating psuedorandom
+ values.
* [`status`](absl/status/)
- <br /> The `status` contains abstractions for error handling, specifically
- `absl::Status` and `absl::StatusOr<T>`.
+ <br /> The `status` library contains abstractions for error handling,
+ specifically `absl::Status` and `absl::StatusOr<T>`.
* [`strings`](absl/strings/)
<br /> The `strings` library contains a variety of strings routines and
- utilities, including a C++11-compatible version of the C++17
+ utilities, including a C++14-compatible version of the C++17
`std::string_view` type.
* [`synchronization`](absl/synchronization/)
<br /> The `synchronization` library contains concurrency primitives (Abseil's
@@ -109,10 +127,11 @@
time zones.
* [`types`](absl/types/)
<br /> The `types` library contains non-container utility types, like a
- C++11-compatible version of the C++17 `std::optional` type.
+ C++14-compatible version of the C++17 `std::optional` type.
* [`utility`](absl/utility/)
<br /> The `utility` library contains utility and helper code.
+<a name="releases"></a>
## Releases
Abseil recommends users "live-at-head" (update to the latest commit from the
@@ -122,11 +141,13 @@
fixes for severe bugs. See our [release
management](https://abseil.io/about/releases) document for more details.
+<a name="license"></a>
## License
The Abseil C++ library is licensed under the terms of the Apache
license. See [LICENSE](LICENSE) for more information.
+<a name="links"></a>
## Links
For more information about Abseil:
diff --git a/UPGRADES.md b/UPGRADES.md
index 35599d0..3cac141 100644
--- a/UPGRADES.md
+++ b/UPGRADES.md
@@ -1,6 +1,6 @@
# C++ Upgrade Tools
-Abseil may occassionally release API-breaking changes. As noted in our
+Abseil may occasionally release API-breaking changes. As noted in our
[Compatibility Guidelines][compatibility-guide], we will aim to provide a tool
to do the work of effecting such API-breaking changes, when absolutely
necessary.
diff --git a/WORKSPACE b/WORKSPACE
index f4a5c47..0d88609 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -20,25 +20,40 @@
# GoogleTest/GoogleMock framework. Used by most unit-tests.
http_archive(
- name = "com_google_googletest", # 2021-07-09T13:28:13Z
- sha256 = "12ef65654dc01ab40f6f33f9d02c04f2097d2cd9fbe48dc6001b29543583b0ad",
- strip_prefix = "googletest-8d51ffdfab10b3fba636ae69bc03da4b54f8c235",
- # Keep this URL in sync with ABSL_GOOGLETEST_COMMIT in ci/cmake_common.sh.
- urls = ["https://github.com/google/googletest/archive/8d51ffdfab10b3fba636ae69bc03da4b54f8c235.zip"],
+ name = "com_google_googletest",
+ sha256 = "8ad598c73ad796e0d8280b082cebd82a630d73e73cd3c70057938a6501bba5d7",
+ strip_prefix = "googletest-1.14.0",
+ # Keep this URL in sync with ABSL_GOOGLETEST_COMMIT in ci/cmake_common.sh and
+ # ci/windows_msvc_cmake.bat.
+ urls = ["https://github.com/google/googletest/archive/refs/tags/v1.14.0.tar.gz"],
+)
+
+# RE2 (the regular expression library used by GoogleTest)
+http_archive(
+ name = "com_googlesource_code_re2",
+ sha256 = "828341ad08524618a626167bd320b0c2acc97bd1c28eff693a9ea33a7ed2a85f",
+ strip_prefix = "re2-2023-11-01",
+ urls = ["https://github.com/google/re2/releases/download/2023-11-01/re2-2023-11-01.zip"],
)
# Google benchmark.
http_archive(
- name = "com_github_google_benchmark", # 2021-07-01T09:02:54Z
- sha256 = "1cb4b97a90aa1fd9c8e412a6bc29fc13fc140162a4a0db3811af40befd8c9ea5",
- strip_prefix = "benchmark-e451e50e9b8af453f076dec10bd6890847f1624e",
- urls = ["https://github.com/google/benchmark/archive/e451e50e9b8af453f076dec10bd6890847f1624e.zip"],
+ name = "com_github_google_benchmark",
+ sha256 = "6bc180a57d23d4d9515519f92b0c83d61b05b5bab188961f36ac7b06b0d9e9ce",
+ strip_prefix = "benchmark-1.8.3",
+ urls = ["https://github.com/google/benchmark/archive/refs/tags/v1.8.3.tar.gz"],
)
-# C++ rules for Bazel.
+# Bazel Skylib.
http_archive(
- name = "rules_cc", # 2021-06-07T16:41:49Z
- sha256 = "b295cad8c5899e371dde175079c0a2cdc0151f5127acc92366a8c986beb95c76",
- strip_prefix = "rules_cc-daf6ace7cfeacd6a83e9ff2ed659f416537b6c74",
- urls = ["https://github.com/bazelbuild/rules_cc/archive/daf6ace7cfeacd6a83e9ff2ed659f416537b6c74.zip"],
+ name = "bazel_skylib",
+ sha256 = "cd55a062e763b9349921f0f5db8c3933288dc8ba4f76dd9416aac68acee3cb94",
+ urls = ["https://github.com/bazelbuild/bazel-skylib/releases/download/1.5.0/bazel-skylib-1.5.0.tar.gz"],
+)
+
+# Bazel platform rules.
+http_archive(
+ name = "platforms",
+ sha256 = "8150406605389ececb6da07cbcb509d5637a3ab9a24bc69b1101531367d89d74",
+ urls = ["https://github.com/bazelbuild/platforms/releases/download/0.0.8/platforms-0.0.8.tar.gz"],
)
diff --git a/WORKSPACE.bzlmod b/WORKSPACE.bzlmod
new file mode 100644
index 0000000..83e67ba
--- /dev/null
+++ b/WORKSPACE.bzlmod
@@ -0,0 +1,19 @@
+# Copyright 2024 The Abseil Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# https://bazel.build/external/migration#workspace.bzlmod
+#
+# This file is intentionally empty. When bzlmod is enabled and this
+# file exists, the contents of WORKSPACE is ignored. This prevents
+# bzlmod builds from unintentionally depending on the WORKSPACE file.
diff --git a/absl/BUILD.bazel b/absl/BUILD.bazel
index c9d4a2d..253c0ae 100644
--- a/absl/BUILD.bazel
+++ b/absl/BUILD.bazel
@@ -12,6 +12,9 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+#
+
+load("@bazel_skylib//lib:selects.bzl", "selects")
package(default_visibility = ["//visibility:public"])
@@ -26,6 +29,30 @@
)
config_setting(
+ name = "gcc_compiler",
+ flag_values = {
+ "@bazel_tools//tools/cpp:compiler": "gcc",
+ },
+ visibility = [":__subpackages__"],
+)
+
+config_setting(
+ name = "mingw_unspecified_compiler",
+ flag_values = {
+ "@bazel_tools//tools/cpp:compiler": "mingw",
+ },
+ visibility = [":__subpackages__"],
+)
+
+config_setting(
+ name = "mingw-gcc_compiler",
+ flag_values = {
+ "@bazel_tools//tools/cpp:compiler": "mingw-gcc",
+ },
+ visibility = [":__subpackages__"],
+)
+
+config_setting(
name = "msvc_compiler",
flag_values = {
"@bazel_tools//tools/cpp:compiler": "msvc-cl",
@@ -44,14 +71,14 @@
config_setting(
name = "osx",
constraint_values = [
- "@bazel_tools//platforms:osx",
+ "@platforms//os:osx",
],
)
config_setting(
name = "ios",
constraint_values = [
- "@bazel_tools//platforms:ios",
+ "@platforms//os:ios",
],
)
@@ -64,9 +91,61 @@
)
config_setting(
- name = "wasm",
+ name = "cpu_wasm",
+ values = {
+ "cpu": "wasm",
+ },
+ visibility = [":__subpackages__"],
+)
+
+config_setting(
+ name = "cpu_wasm32",
values = {
"cpu": "wasm32",
},
visibility = [":__subpackages__"],
)
+
+config_setting(
+ name = "platforms_wasm32",
+ constraint_values = [
+ "@platforms//cpu:wasm32",
+ ],
+ visibility = [":__subpackages__"],
+)
+
+config_setting(
+ name = "platforms_wasm64",
+ constraint_values = [
+ "@platforms//cpu:wasm64",
+ ],
+ visibility = [":__subpackages__"],
+)
+
+selects.config_setting_group(
+ name = "wasm",
+ match_any = [
+ ":cpu_wasm",
+ ":cpu_wasm32",
+ ":platforms_wasm32",
+ ":platforms_wasm64",
+ ],
+ visibility = [":__subpackages__"],
+)
+
+config_setting(
+ name = "fuchsia",
+ values = {
+ "cpu": "fuchsia",
+ },
+ visibility = [":__subpackages__"],
+)
+
+selects.config_setting_group(
+ name = "mingw_compiler",
+ match_any = [
+ ":mingw_unspecified_compiler",
+ ":mingw-gcc_compiler",
+ ],
+ visibility = [":__subpackages__"],
+)
diff --git a/absl/CMakeLists.txt b/absl/CMakeLists.txt
index a41e1ee..3a7c12f 100644
--- a/absl/CMakeLists.txt
+++ b/absl/CMakeLists.txt
@@ -18,13 +18,16 @@
add_subdirectory(algorithm)
add_subdirectory(cleanup)
add_subdirectory(container)
+add_subdirectory(crc)
add_subdirectory(debugging)
add_subdirectory(flags)
add_subdirectory(functional)
add_subdirectory(hash)
+add_subdirectory(log)
add_subdirectory(memory)
add_subdirectory(meta)
add_subdirectory(numeric)
+add_subdirectory(profiling)
add_subdirectory(random)
add_subdirectory(status)
add_subdirectory(strings)
@@ -35,4 +38,7 @@
if (${ABSL_BUILD_DLL})
absl_make_dll()
+ if (${ABSL_BUILD_TEST_HELPERS})
+ absl_make_dll(TEST ON)
+ endif()
endif()
diff --git a/absl/abseil.podspec.gen.py b/absl/abseil.podspec.gen.py
index 6375298..c83edbf 100755
--- a/absl/abseil.podspec.gen.py
+++ b/absl/abseil.podspec.gen.py
@@ -30,6 +30,9 @@
:git => 'https://github.com/abseil/abseil-cpp.git',
:tag => '${tag}',
}
+ s.resource_bundles = {
+ s.module_name => 'PrivacyInfo.xcprivacy',
+ }
s.module_name = 'absl'
s.header_mappings_dir = 'absl'
s.header_dir = 'absl'
diff --git a/absl/algorithm/BUILD.bazel b/absl/algorithm/BUILD.bazel
index a3002b7..ddf9e11 100644
--- a/absl/algorithm/BUILD.bazel
+++ b/absl/algorithm/BUILD.bazel
@@ -14,7 +14,6 @@
# limitations under the License.
#
-load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test")
load(
"//absl:copts/configure_copts.bzl",
"ABSL_DEFAULT_COPTS",
@@ -22,7 +21,14 @@
"ABSL_TEST_COPTS",
)
-package(default_visibility = ["//visibility:public"])
+package(
+ default_visibility = ["//visibility:public"],
+ features = [
+ "header_modules",
+ "layering_check",
+ "parse_headers",
+ ],
+)
licenses(["notice"])
@@ -44,23 +50,12 @@
linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
":algorithm",
+ "//absl/base:config",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
-cc_test(
- name = "algorithm_benchmark",
- srcs = ["equal_benchmark.cc"],
- copts = ABSL_TEST_COPTS,
- linkopts = ABSL_DEFAULT_LINKOPTS,
- tags = ["benchmark"],
- deps = [
- ":algorithm",
- "//absl/base:core_headers",
- "@com_github_google_benchmark//:benchmark_main",
- ],
-)
-
cc_library(
name = "container",
hdrs = [
@@ -71,6 +66,7 @@
deps = [
":algorithm",
"//absl/base:core_headers",
+ "//absl/base:nullability",
"//absl/meta:type_traits",
],
)
@@ -86,6 +82,7 @@
"//absl/base:core_headers",
"//absl/memory",
"//absl/types:span",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
diff --git a/absl/algorithm/CMakeLists.txt b/absl/algorithm/CMakeLists.txt
index 609d858..5577164 100644
--- a/absl/algorithm/CMakeLists.txt
+++ b/absl/algorithm/CMakeLists.txt
@@ -35,6 +35,7 @@
${ABSL_TEST_COPTS}
DEPS
absl::algorithm
+ absl::config
GTest::gmock_main
)
@@ -49,6 +50,7 @@
absl::algorithm
absl::core_headers
absl::meta
+ absl::nullability
PUBLIC
)
diff --git a/absl/algorithm/algorithm.h b/absl/algorithm/algorithm.h
index e9b4733..59aeed7 100644
--- a/absl/algorithm/algorithm.h
+++ b/absl/algorithm/algorithm.h
@@ -31,92 +31,17 @@
namespace absl {
ABSL_NAMESPACE_BEGIN
-namespace algorithm_internal {
-
-// Performs comparisons with operator==, similar to C++14's `std::equal_to<>`.
-struct EqualTo {
- template <typename T, typename U>
- bool operator()(const T& a, const U& b) const {
- return a == b;
- }
-};
-
-template <typename InputIter1, typename InputIter2, typename Pred>
-bool EqualImpl(InputIter1 first1, InputIter1 last1, InputIter2 first2,
- InputIter2 last2, Pred pred, std::input_iterator_tag,
- std::input_iterator_tag) {
- while (true) {
- if (first1 == last1) return first2 == last2;
- if (first2 == last2) return false;
- if (!pred(*first1, *first2)) return false;
- ++first1;
- ++first2;
- }
-}
-
-template <typename InputIter1, typename InputIter2, typename Pred>
-bool EqualImpl(InputIter1 first1, InputIter1 last1, InputIter2 first2,
- InputIter2 last2, Pred&& pred, std::random_access_iterator_tag,
- std::random_access_iterator_tag) {
- return (last1 - first1 == last2 - first2) &&
- std::equal(first1, last1, first2, std::forward<Pred>(pred));
-}
-
-// When we are using our own internal predicate that just applies operator==, we
-// forward to the non-predicate form of std::equal. This enables an optimization
-// in libstdc++ that can result in std::memcmp being used for integer types.
-template <typename InputIter1, typename InputIter2>
-bool EqualImpl(InputIter1 first1, InputIter1 last1, InputIter2 first2,
- InputIter2 last2, algorithm_internal::EqualTo /* unused */,
- std::random_access_iterator_tag,
- std::random_access_iterator_tag) {
- return (last1 - first1 == last2 - first2) &&
- std::equal(first1, last1, first2);
-}
-
-template <typename It>
-It RotateImpl(It first, It middle, It last, std::true_type) {
- return std::rotate(first, middle, last);
-}
-
-template <typename It>
-It RotateImpl(It first, It middle, It last, std::false_type) {
- std::rotate(first, middle, last);
- return std::next(first, std::distance(middle, last));
-}
-
-} // namespace algorithm_internal
-
// equal()
+// rotate()
//
-// Compares the equality of two ranges specified by pairs of iterators, using
-// the given predicate, returning true iff for each corresponding iterator i1
-// and i2 in the first and second range respectively, pred(*i1, *i2) == true
+// Historical note: Abseil once provided implementations of these algorithms
+// prior to their adoption in C++14. New code should prefer to use the std
+// variants.
//
-// This comparison takes at most min(`last1` - `first1`, `last2` - `first2`)
-// invocations of the predicate. Additionally, if InputIter1 and InputIter2 are
-// both random-access iterators, and `last1` - `first1` != `last2` - `first2`,
-// then the predicate is never invoked and the function returns false.
-//
-// This is a C++11-compatible implementation of C++14 `std::equal`. See
-// https://en.cppreference.com/w/cpp/algorithm/equal for more information.
-template <typename InputIter1, typename InputIter2, typename Pred>
-bool equal(InputIter1 first1, InputIter1 last1, InputIter2 first2,
- InputIter2 last2, Pred&& pred) {
- return algorithm_internal::EqualImpl(
- first1, last1, first2, last2, std::forward<Pred>(pred),
- typename std::iterator_traits<InputIter1>::iterator_category{},
- typename std::iterator_traits<InputIter2>::iterator_category{});
-}
-
-// Overload of equal() that performs comparison of two ranges specified by pairs
-// of iterators using operator==.
-template <typename InputIter1, typename InputIter2>
-bool equal(InputIter1 first1, InputIter1 last1, InputIter2 first2,
- InputIter2 last2) {
- return absl::equal(first1, last1, first2, last2,
- algorithm_internal::EqualTo{});
-}
+// See the documentation for the STL <algorithm> header for more information:
+// https://en.cppreference.com/w/cpp/header/algorithm
+using std::equal;
+using std::rotate;
// linear_search()
//
@@ -133,26 +58,6 @@
return std::find(first, last, value) != last;
}
-// rotate()
-//
-// Performs a left rotation on a range of elements (`first`, `last`) such that
-// `middle` is now the first element. `rotate()` returns an iterator pointing to
-// the first element before rotation. This function is exactly the same as
-// `std::rotate`, but fixes a bug in gcc
-// <= 4.9 where `std::rotate` returns `void` instead of an iterator.
-//
-// The complexity of this algorithm is the same as that of `std::rotate`, but if
-// `ForwardIterator` is not a random-access iterator, then `absl::rotate`
-// performs an additional pass over the range to construct the return value.
-template <typename ForwardIterator>
-ForwardIterator rotate(ForwardIterator first, ForwardIterator middle,
- ForwardIterator last) {
- return algorithm_internal::RotateImpl(
- first, middle, last,
- std::is_same<decltype(std::rotate(first, middle, last)),
- ForwardIterator>());
-}
-
ABSL_NAMESPACE_END
} // namespace absl
diff --git a/absl/algorithm/algorithm_test.cc b/absl/algorithm/algorithm_test.cc
index 81fccb6..e6ee469 100644
--- a/absl/algorithm/algorithm_test.cc
+++ b/absl/algorithm/algorithm_test.cc
@@ -20,132 +20,10 @@
#include "gmock/gmock.h"
#include "gtest/gtest.h"
+#include "absl/base/config.h"
namespace {
-TEST(EqualTest, DefaultComparisonRandomAccess) {
- std::vector<int> v1{1, 2, 3};
- std::vector<int> v2 = v1;
- std::vector<int> v3 = {1, 2};
- std::vector<int> v4 = {1, 2, 4};
-
- EXPECT_TRUE(absl::equal(v1.begin(), v1.end(), v2.begin(), v2.end()));
- EXPECT_FALSE(absl::equal(v1.begin(), v1.end(), v3.begin(), v3.end()));
- EXPECT_FALSE(absl::equal(v1.begin(), v1.end(), v4.begin(), v4.end()));
-}
-
-TEST(EqualTest, DefaultComparison) {
- std::list<int> lst1{1, 2, 3};
- std::list<int> lst2 = lst1;
- std::list<int> lst3{1, 2};
- std::list<int> lst4{1, 2, 4};
-
- EXPECT_TRUE(absl::equal(lst1.begin(), lst1.end(), lst2.begin(), lst2.end()));
- EXPECT_FALSE(absl::equal(lst1.begin(), lst1.end(), lst3.begin(), lst3.end()));
- EXPECT_FALSE(absl::equal(lst1.begin(), lst1.end(), lst4.begin(), lst4.end()));
-}
-
-TEST(EqualTest, EmptyRange) {
- std::vector<int> v1{1, 2, 3};
- std::vector<int> empty1;
- std::vector<int> empty2;
-
- EXPECT_FALSE(absl::equal(v1.begin(), v1.end(), empty1.begin(), empty1.end()));
- EXPECT_FALSE(absl::equal(empty1.begin(), empty1.end(), v1.begin(), v1.end()));
- EXPECT_TRUE(
- absl::equal(empty1.begin(), empty1.end(), empty2.begin(), empty2.end()));
-}
-
-TEST(EqualTest, MixedIterTypes) {
- std::vector<int> v1{1, 2, 3};
- std::list<int> lst1{v1.begin(), v1.end()};
- std::list<int> lst2{1, 2, 4};
- std::list<int> lst3{1, 2};
-
- EXPECT_TRUE(absl::equal(v1.begin(), v1.end(), lst1.begin(), lst1.end()));
- EXPECT_FALSE(absl::equal(v1.begin(), v1.end(), lst2.begin(), lst2.end()));
- EXPECT_FALSE(absl::equal(v1.begin(), v1.end(), lst3.begin(), lst3.end()));
-}
-
-TEST(EqualTest, MixedValueTypes) {
- std::vector<int> v1{1, 2, 3};
- std::vector<char> v2{1, 2, 3};
- std::vector<char> v3{1, 2};
- std::vector<char> v4{1, 2, 4};
-
- EXPECT_TRUE(absl::equal(v1.begin(), v1.end(), v2.begin(), v2.end()));
- EXPECT_FALSE(absl::equal(v1.begin(), v1.end(), v3.begin(), v3.end()));
- EXPECT_FALSE(absl::equal(v1.begin(), v1.end(), v4.begin(), v4.end()));
-}
-
-TEST(EqualTest, WeirdIterators) {
- std::vector<bool> v1{true, false};
- std::vector<bool> v2 = v1;
- std::vector<bool> v3{true};
- std::vector<bool> v4{true, true, true};
-
- EXPECT_TRUE(absl::equal(v1.begin(), v1.end(), v2.begin(), v2.end()));
- EXPECT_FALSE(absl::equal(v1.begin(), v1.end(), v3.begin(), v3.end()));
- EXPECT_FALSE(absl::equal(v1.begin(), v1.end(), v4.begin(), v4.end()));
-}
-
-TEST(EqualTest, CustomComparison) {
- int n[] = {1, 2, 3, 4};
- std::vector<int*> v1{&n[0], &n[1], &n[2]};
- std::vector<int*> v2 = v1;
- std::vector<int*> v3{&n[0], &n[1], &n[3]};
- std::vector<int*> v4{&n[0], &n[1]};
-
- auto eq = [](int* a, int* b) { return *a == *b; };
-
- EXPECT_TRUE(absl::equal(v1.begin(), v1.end(), v2.begin(), v2.end(), eq));
- EXPECT_FALSE(absl::equal(v1.begin(), v1.end(), v3.begin(), v3.end(), eq));
- EXPECT_FALSE(absl::equal(v1.begin(), v1.end(), v4.begin(), v4.end(), eq));
-}
-
-TEST(EqualTest, MoveOnlyPredicate) {
- std::vector<int> v1{1, 2, 3};
- std::vector<int> v2{4, 5, 6};
-
- // move-only equality predicate
- struct Eq {
- Eq() = default;
- Eq(Eq &&) = default;
- Eq(const Eq &) = delete;
- Eq &operator=(const Eq &) = delete;
- bool operator()(const int a, const int b) const { return a == b; }
- };
-
- EXPECT_TRUE(absl::equal(v1.begin(), v1.end(), v1.begin(), v1.end(), Eq()));
- EXPECT_FALSE(absl::equal(v1.begin(), v1.end(), v2.begin(), v2.end(), Eq()));
-}
-
-struct CountingTrivialPred {
- int* count;
- bool operator()(int, int) const {
- ++*count;
- return true;
- }
-};
-
-TEST(EqualTest, RandomAccessComplexity) {
- std::vector<int> v1{1, 1, 3};
- std::vector<int> v2 = v1;
- std::vector<int> v3{1, 2};
-
- do {
- int count = 0;
- absl::equal(v1.begin(), v1.end(), v2.begin(), v2.end(),
- CountingTrivialPred{&count});
- EXPECT_LE(count, 3);
- } while (std::next_permutation(v2.begin(), v2.end()));
-
- int count = 0;
- absl::equal(v1.begin(), v1.end(), v3.begin(), v3.end(),
- CountingTrivialPred{&count});
- EXPECT_EQ(count, 0);
-}
-
class LinearSearchTest : public testing::Test {
protected:
LinearSearchTest() : container_{1, 2, 3} {}
@@ -169,14 +47,4 @@
absl::linear_search(const_container->begin(), const_container->end(), 4));
}
-TEST(RotateTest, Rotate) {
- std::vector<int> v{0, 1, 2, 3, 4};
- EXPECT_EQ(*absl::rotate(v.begin(), v.begin() + 2, v.end()), 0);
- EXPECT_THAT(v, testing::ElementsAreArray({2, 3, 4, 0, 1}));
-
- std::list<int> l{0, 1, 2, 3, 4};
- EXPECT_EQ(*absl::rotate(l.begin(), std::next(l.begin(), 3), l.end()), 0);
- EXPECT_THAT(l, testing::ElementsAreArray({3, 4, 0, 1, 2}));
-}
-
} // namespace
diff --git a/absl/algorithm/container.h b/absl/algorithm/container.h
index c38a4a6..c7bafae 100644
--- a/absl/algorithm/container.h
+++ b/absl/algorithm/container.h
@@ -52,6 +52,7 @@
#include "absl/algorithm/algorithm.h"
#include "absl/base/macros.h"
+#include "absl/base/nullability.h"
#include "absl/meta/type_traits.h"
namespace absl {
@@ -77,9 +78,8 @@
decltype(std::make_pair(ContainerIter<C1>(), ContainerIter<C2>()));
template <typename C>
-using ContainerDifferenceType =
- decltype(std::distance(std::declval<ContainerIter<C>>(),
- std::declval<ContainerIter<C>>()));
+using ContainerDifferenceType = decltype(std::distance(
+ std::declval<ContainerIter<C>>(), std::declval<ContainerIter<C>>()));
template <typename C>
using ContainerPointerType =
@@ -97,10 +97,14 @@
// These are meant for internal use only.
template <typename C>
-ContainerIter<C> c_begin(C& c) { return begin(c); }
+ContainerIter<C> c_begin(C& c) {
+ return begin(c);
+}
template <typename C>
-ContainerIter<C> c_end(C& c) { return end(c); }
+ContainerIter<C> c_end(C& c) {
+ return end(c);
+}
template <typename T>
struct IsUnorderedContainer : std::false_type {};
@@ -113,18 +117,6 @@
struct IsUnorderedContainer<std::unordered_set<Key, Hash, KeyEqual, Allocator>>
: std::true_type {};
-// container_algorithm_internal::c_size. It is meant for internal use only.
-
-template <class C>
-auto c_size(C& c) -> decltype(c.size()) {
- return c.size();
-}
-
-template <class T, std::size_t N>
-constexpr std::size_t c_size(T (&)[N]) {
- return N;
-}
-
} // namespace container_algorithm_internal
// PUBLIC API
@@ -166,7 +158,7 @@
// c_all_of()
//
// Container-based version of the <algorithm> `std::all_of()` function to
-// test a condition on all elements within a container.
+// test if all elements within a container satisfy a condition.
template <typename C, typename Pred>
bool c_all_of(const C& c, Pred&& pred) {
return std::all_of(container_algorithm_internal::c_begin(c),
@@ -343,80 +335,47 @@
// return the first element where two ordered containers differ. Applies `==` to
// the first N elements of `c1` and `c2`, where N = min(size(c1), size(c2)).
template <typename C1, typename C2>
-container_algorithm_internal::ContainerIterPairType<C1, C2>
-c_mismatch(C1& c1, C2& c2) {
- auto first1 = container_algorithm_internal::c_begin(c1);
- auto last1 = container_algorithm_internal::c_end(c1);
- auto first2 = container_algorithm_internal::c_begin(c2);
- auto last2 = container_algorithm_internal::c_end(c2);
-
- for (; first1 != last1 && first2 != last2; ++first1, (void)++first2) {
- // Negates equality because Cpp17EqualityComparable doesn't require clients
- // to overload both `operator==` and `operator!=`.
- if (!(*first1 == *first2)) {
- break;
- }
- }
-
- return std::make_pair(first1, first2);
+container_algorithm_internal::ContainerIterPairType<C1, C2> c_mismatch(C1& c1,
+ C2& c2) {
+ return std::mismatch(container_algorithm_internal::c_begin(c1),
+ container_algorithm_internal::c_end(c1),
+ container_algorithm_internal::c_begin(c2),
+ container_algorithm_internal::c_end(c2));
}
// Overload of c_mismatch() for using a predicate evaluation other than `==` as
// the function's test condition. Applies `pred`to the first N elements of `c1`
// and `c2`, where N = min(size(c1), size(c2)).
template <typename C1, typename C2, typename BinaryPredicate>
-container_algorithm_internal::ContainerIterPairType<C1, C2>
-c_mismatch(C1& c1, C2& c2, BinaryPredicate pred) {
- auto first1 = container_algorithm_internal::c_begin(c1);
- auto last1 = container_algorithm_internal::c_end(c1);
- auto first2 = container_algorithm_internal::c_begin(c2);
- auto last2 = container_algorithm_internal::c_end(c2);
-
- for (; first1 != last1 && first2 != last2; ++first1, (void)++first2) {
- if (!pred(*first1, *first2)) {
- break;
- }
- }
-
- return std::make_pair(first1, first2);
+container_algorithm_internal::ContainerIterPairType<C1, C2> c_mismatch(
+ C1& c1, C2& c2, BinaryPredicate pred) {
+ return std::mismatch(container_algorithm_internal::c_begin(c1),
+ container_algorithm_internal::c_end(c1),
+ container_algorithm_internal::c_begin(c2),
+ container_algorithm_internal::c_end(c2), pred);
}
// c_equal()
//
// Container-based version of the <algorithm> `std::equal()` function to
// test whether two containers are equal.
-//
-// NOTE: the semantics of c_equal() are slightly different than those of
-// equal(): while the latter iterates over the second container only up to the
-// size of the first container, c_equal() also checks whether the container
-// sizes are equal. This better matches expectations about c_equal() based on
-// its signature.
-//
-// Example:
-// vector v1 = <1, 2, 3>;
-// vector v2 = <1, 2, 3, 4>;
-// equal(std::begin(v1), std::end(v1), std::begin(v2)) returns true
-// c_equal(v1, v2) returns false
-
template <typename C1, typename C2>
bool c_equal(const C1& c1, const C2& c2) {
- return ((container_algorithm_internal::c_size(c1) ==
- container_algorithm_internal::c_size(c2)) &&
- std::equal(container_algorithm_internal::c_begin(c1),
- container_algorithm_internal::c_end(c1),
- container_algorithm_internal::c_begin(c2)));
+ return std::equal(container_algorithm_internal::c_begin(c1),
+ container_algorithm_internal::c_end(c1),
+ container_algorithm_internal::c_begin(c2),
+ container_algorithm_internal::c_end(c2));
}
// Overload of c_equal() for using a predicate evaluation other than `==` as
// the function's test condition.
template <typename C1, typename C2, typename BinaryPredicate>
bool c_equal(const C1& c1, const C2& c2, BinaryPredicate&& pred) {
- return ((container_algorithm_internal::c_size(c1) ==
- container_algorithm_internal::c_size(c2)) &&
- std::equal(container_algorithm_internal::c_begin(c1),
- container_algorithm_internal::c_end(c1),
- container_algorithm_internal::c_begin(c2),
- std::forward<BinaryPredicate>(pred)));
+ return std::equal(container_algorithm_internal::c_begin(c1),
+ container_algorithm_internal::c_end(c1),
+ container_algorithm_internal::c_begin(c2),
+ container_algorithm_internal::c_end(c2),
+ std::forward<BinaryPredicate>(pred));
}
// c_is_permutation()
@@ -425,20 +384,20 @@
// to test whether a container is a permutation of another.
template <typename C1, typename C2>
bool c_is_permutation(const C1& c1, const C2& c2) {
- using std::begin;
- using std::end;
- return c1.size() == c2.size() &&
- std::is_permutation(begin(c1), end(c1), begin(c2));
+ return std::is_permutation(container_algorithm_internal::c_begin(c1),
+ container_algorithm_internal::c_end(c1),
+ container_algorithm_internal::c_begin(c2),
+ container_algorithm_internal::c_end(c2));
}
// Overload of c_is_permutation() for using a predicate evaluation other than
// `==` as the function's test condition.
template <typename C1, typename C2, typename BinaryPredicate>
bool c_is_permutation(const C1& c1, const C2& c2, BinaryPredicate&& pred) {
- using std::begin;
- using std::end;
- return c1.size() == c2.size() &&
- std::is_permutation(begin(c1), end(c1), begin(c2),
+ return std::is_permutation(container_algorithm_internal::c_begin(c1),
+ container_algorithm_internal::c_end(c1),
+ container_algorithm_internal::c_begin(c2),
+ container_algorithm_internal::c_end(c2),
std::forward<BinaryPredicate>(pred));
}
@@ -655,11 +614,10 @@
// some condition, and return the results within an iterator.
template <typename C, typename OutputIterator, typename Pred, typename T>
OutputIterator c_replace_copy_if(const C& c, OutputIterator result, Pred&& pred,
- T&& new_value) {
+ const T& new_value) {
return std::replace_copy_if(container_algorithm_internal::c_begin(c),
container_algorithm_internal::c_end(c), result,
- std::forward<Pred>(pred),
- std::forward<T>(new_value));
+ std::forward<Pred>(pred), new_value);
}
// c_fill()
@@ -667,9 +625,9 @@
// Container-based version of the <algorithm> `std::fill()` function to fill a
// container with some value.
template <typename C, typename T>
-void c_fill(C& c, T&& value) {
+void c_fill(C& c, const T& value) {
std::fill(container_algorithm_internal::c_begin(c),
- container_algorithm_internal::c_end(c), std::forward<T>(value));
+ container_algorithm_internal::c_end(c), value);
}
// c_fill_n()
@@ -677,9 +635,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, T&& value) {
- std::fill_n(container_algorithm_internal::c_begin(c), n,
- std::forward<T>(value));
+void c_fill_n(C& c, Size n, const T& value) {
+ std::fill_n(container_algorithm_internal::c_begin(c), n, value);
}
// c_generate()
@@ -716,10 +673,11 @@
// copy a container's elements while removing any elements matching the given
// `value`.
template <typename C, typename OutputIterator, typename T>
-OutputIterator c_remove_copy(const C& c, OutputIterator result, T&& value) {
+OutputIterator c_remove_copy(const C& c, OutputIterator result,
+ const T& value) {
return std::remove_copy(container_algorithm_internal::c_begin(c),
container_algorithm_internal::c_end(c), result,
- std::forward<T>(value));
+ value);
}
// c_remove_copy_if()
@@ -816,6 +774,36 @@
std::forward<UniformRandomBitGenerator>(gen));
}
+// c_sample()
+//
+// Container-based version of the <algorithm> `std::sample()` function to
+// randomly sample elements from the container without replacement using a
+// `gen()` uniform random number generator and write them to an iterator range.
+template <typename C, typename OutputIterator, typename Distance,
+ typename UniformRandomBitGenerator>
+OutputIterator c_sample(const C& c, OutputIterator result, Distance n,
+ UniformRandomBitGenerator&& gen) {
+#if defined(__cpp_lib_sample) && __cpp_lib_sample >= 201603L
+ return std::sample(container_algorithm_internal::c_begin(c),
+ container_algorithm_internal::c_end(c), result, n,
+ std::forward<UniformRandomBitGenerator>(gen));
+#else
+ // Fall back to a stable selection-sampling implementation.
+ auto first = container_algorithm_internal::c_begin(c);
+ Distance unsampled_elements = c_distance(c);
+ n = (std::min)(n, unsampled_elements);
+ for (; n != 0; ++first) {
+ Distance r =
+ std::uniform_int_distribution<Distance>(0, --unsampled_elements)(gen);
+ if (r < n) {
+ *result++ = *first;
+ --n;
+ }
+ }
+ return result;
+#endif
+}
+
//------------------------------------------------------------------------------
// <algorithm> Partition functions
//------------------------------------------------------------------------------
@@ -1064,20 +1052,19 @@
// which does not compare less than `value`.
template <typename Sequence, typename T>
container_algorithm_internal::ContainerIter<Sequence> c_lower_bound(
- Sequence& sequence, T&& value) {
+ Sequence& sequence, const T& value) {
return std::lower_bound(container_algorithm_internal::c_begin(sequence),
- container_algorithm_internal::c_end(sequence),
- std::forward<T>(value));
+ container_algorithm_internal::c_end(sequence), value);
}
// Overload of c_lower_bound() for performing a `comp` comparison other than
// the default `operator<`.
template <typename Sequence, typename T, typename LessThan>
container_algorithm_internal::ContainerIter<Sequence> c_lower_bound(
- Sequence& sequence, T&& value, LessThan&& comp) {
+ Sequence& sequence, const T& value, LessThan&& comp) {
return std::lower_bound(container_algorithm_internal::c_begin(sequence),
- container_algorithm_internal::c_end(sequence),
- std::forward<T>(value), std::forward<LessThan>(comp));
+ container_algorithm_internal::c_end(sequence), value,
+ std::forward<LessThan>(comp));
}
// c_upper_bound()
@@ -1087,20 +1074,19 @@
// which is greater than `value`.
template <typename Sequence, typename T>
container_algorithm_internal::ContainerIter<Sequence> c_upper_bound(
- Sequence& sequence, T&& value) {
+ Sequence& sequence, const T& value) {
return std::upper_bound(container_algorithm_internal::c_begin(sequence),
- container_algorithm_internal::c_end(sequence),
- std::forward<T>(value));
+ container_algorithm_internal::c_end(sequence), value);
}
// Overload of c_upper_bound() for performing a `comp` comparison other than
// the default `operator<`.
template <typename Sequence, typename T, typename LessThan>
container_algorithm_internal::ContainerIter<Sequence> c_upper_bound(
- Sequence& sequence, T&& value, LessThan&& comp) {
+ Sequence& sequence, const T& value, LessThan&& comp) {
return std::upper_bound(container_algorithm_internal::c_begin(sequence),
- container_algorithm_internal::c_end(sequence),
- std::forward<T>(value), std::forward<LessThan>(comp));
+ container_algorithm_internal::c_end(sequence), value,
+ std::forward<LessThan>(comp));
}
// c_equal_range()
@@ -1110,20 +1096,19 @@
// sorted container which compare equal to `value`.
template <typename Sequence, typename T>
container_algorithm_internal::ContainerIterPairType<Sequence, Sequence>
-c_equal_range(Sequence& sequence, T&& value) {
+c_equal_range(Sequence& sequence, const T& value) {
return std::equal_range(container_algorithm_internal::c_begin(sequence),
- container_algorithm_internal::c_end(sequence),
- std::forward<T>(value));
+ container_algorithm_internal::c_end(sequence), value);
}
// Overload of c_equal_range() for performing a `comp` comparison other than
// the default `operator<`.
template <typename Sequence, typename T, typename LessThan>
container_algorithm_internal::ContainerIterPairType<Sequence, Sequence>
-c_equal_range(Sequence& sequence, T&& value, LessThan&& comp) {
+c_equal_range(Sequence& sequence, const T& value, LessThan&& comp) {
return std::equal_range(container_algorithm_internal::c_begin(sequence),
- container_algorithm_internal::c_end(sequence),
- std::forward<T>(value), std::forward<LessThan>(comp));
+ container_algorithm_internal::c_end(sequence), value,
+ std::forward<LessThan>(comp));
}
// c_binary_search()
@@ -1132,20 +1117,20 @@
// to test if any element in the sorted container contains a value equivalent to
// 'value'.
template <typename Sequence, typename T>
-bool c_binary_search(Sequence&& sequence, T&& value) {
+bool c_binary_search(const Sequence& sequence, const T& value) {
return std::binary_search(container_algorithm_internal::c_begin(sequence),
container_algorithm_internal::c_end(sequence),
- std::forward<T>(value));
+ value);
}
// Overload of c_binary_search() for performing a `comp` comparison other than
// the default `operator<`.
template <typename Sequence, typename T, typename LessThan>
-bool c_binary_search(Sequence&& sequence, T&& value, LessThan&& comp) {
+bool c_binary_search(const Sequence& sequence, const T& value,
+ LessThan&& comp) {
return std::binary_search(container_algorithm_internal::c_begin(sequence),
container_algorithm_internal::c_end(sequence),
- std::forward<T>(value),
- std::forward<LessThan>(comp));
+ value, std::forward<LessThan>(comp));
}
//------------------------------------------------------------------------------
@@ -1560,8 +1545,8 @@
// smallest and largest values, respectively, using `operator<` to make the
// comparisons.
template <typename C>
-container_algorithm_internal::ContainerIterPairType<C, C>
-c_minmax_element(C& c) {
+container_algorithm_internal::ContainerIterPairType<C, C> c_minmax_element(
+ C& c) {
return std::minmax_element(container_algorithm_internal::c_begin(c),
container_algorithm_internal::c_end(c));
}
@@ -1569,8 +1554,8 @@
// Overload of c_minmax_element() for performing `comp` comparisons other than
// `operator<`.
template <typename C, typename LessThan>
-container_algorithm_internal::ContainerIterPairType<C, C>
-c_minmax_element(C& c, LessThan&& comp) {
+container_algorithm_internal::ContainerIterPairType<C, C> c_minmax_element(
+ C& c, LessThan&& comp) {
return std::minmax_element(container_algorithm_internal::c_begin(c),
container_algorithm_internal::c_end(c),
std::forward<LessThan>(comp));
@@ -1588,7 +1573,8 @@
// that capital letters ("A-Z") have ASCII values less than lowercase letters
// ("a-z").
template <typename Sequence1, typename Sequence2>
-bool c_lexicographical_compare(Sequence1&& sequence1, Sequence2&& sequence2) {
+bool c_lexicographical_compare(const Sequence1& sequence1,
+ const Sequence2& sequence2) {
return std::lexicographical_compare(
container_algorithm_internal::c_begin(sequence1),
container_algorithm_internal::c_end(sequence1),
@@ -1599,8 +1585,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(Sequence1&& sequence1, Sequence2&& sequence2,
- LessThan&& comp) {
+bool c_lexicographical_compare(const Sequence1& sequence1,
+ const Sequence2& sequence2, LessThan&& comp) {
return std::lexicographical_compare(
container_algorithm_internal::c_begin(sequence1),
container_algorithm_internal::c_end(sequence1),
@@ -1655,18 +1641,18 @@
// c_iota()
//
-// Container-based version of the <algorithm> `std::iota()` function
+// Container-based version of the <numeric> `std::iota()` function
// to compute successive values of `value`, as if incremented with `++value`
-// after each element is written. and write them to the container.
+// after each element is written, and write them to the container.
template <typename Sequence, typename T>
-void c_iota(Sequence& sequence, T&& value) {
+void c_iota(Sequence& sequence, const T& value) {
std::iota(container_algorithm_internal::c_begin(sequence),
- container_algorithm_internal::c_end(sequence),
- std::forward<T>(value));
+ container_algorithm_internal::c_end(sequence), value);
}
+
// c_accumulate()
//
-// Container-based version of the <algorithm> `std::accumulate()` function
+// Container-based version of the <numeric> `std::accumulate()` function
// to accumulate the element values of a container to `init` and return that
// accumulation by value.
//
@@ -1693,7 +1679,7 @@
// c_inner_product()
//
-// Container-based version of the <algorithm> `std::inner_product()` function
+// Container-based version of the <numeric> `std::inner_product()` function
// to compute the cumulative inner product of container element pairs.
//
// Note: Due to a language technicality this function has return type
@@ -1724,7 +1710,7 @@
// c_adjacent_difference()
//
-// Container-based version of the <algorithm> `std::adjacent_difference()`
+// Container-based version of the <numeric> `std::adjacent_difference()`
// function to compute the difference between each element and the one preceding
// it and write it to an iterator.
template <typename InputSequence, typename OutputIt>
@@ -1747,7 +1733,7 @@
// c_partial_sum()
//
-// Container-based version of the <algorithm> `std::partial_sum()` function
+// Container-based version of the <numeric> `std::partial_sum()` function
// to compute the partial sum of the elements in a sequence and write them
// to an iterator. The partial sum is the sum of all element values so far in
// the sequence.
diff --git a/absl/algorithm/container_test.cc b/absl/algorithm/container_test.cc
index 605afc8..c01f5fc 100644
--- a/absl/algorithm/container_test.cc
+++ b/absl/algorithm/container_test.cc
@@ -14,6 +14,7 @@
#include "absl/algorithm/container.h"
+#include <algorithm>
#include <functional>
#include <initializer_list>
#include <iterator>
@@ -40,8 +41,10 @@
using ::testing::ElementsAre;
using ::testing::Gt;
using ::testing::IsNull;
+using ::testing::IsSubsetOf;
using ::testing::Lt;
using ::testing::Pointee;
+using ::testing::SizeIs;
using ::testing::Truly;
using ::testing::UnorderedElementsAre;
@@ -67,13 +70,16 @@
bool IsOdd(int x) { return x % 2 != 0; }
TEST_F(NonMutatingTest, Distance) {
- EXPECT_EQ(container_.size(), absl::c_distance(container_));
- EXPECT_EQ(sequence_.size(), absl::c_distance(sequence_));
- EXPECT_EQ(vector_.size(), absl::c_distance(vector_));
- EXPECT_EQ(ABSL_ARRAYSIZE(array_), absl::c_distance(array_));
+ EXPECT_EQ(container_.size(),
+ static_cast<size_t>(absl::c_distance(container_)));
+ EXPECT_EQ(sequence_.size(), static_cast<size_t>(absl::c_distance(sequence_)));
+ EXPECT_EQ(vector_.size(), static_cast<size_t>(absl::c_distance(vector_)));
+ EXPECT_EQ(ABSL_ARRAYSIZE(array_),
+ static_cast<size_t>(absl::c_distance(array_)));
// Works with a temporary argument.
- EXPECT_EQ(vector_.size(), absl::c_distance(std::vector<int>(vector_)));
+ EXPECT_EQ(vector_.size(),
+ static_cast<size_t>(absl::c_distance(std::vector<int>(vector_))));
}
TEST_F(NonMutatingTest, Distance_OverloadedBeginEnd) {
@@ -960,12 +966,29 @@
EXPECT_THAT(actual, ElementsAre(3, 4, 1, 2, 5));
}
+template <typename T>
+T RandomlySeededPrng() {
+ std::random_device rdev;
+ std::seed_seq::result_type data[T::state_size];
+ std::generate_n(data, T::state_size, std::ref(rdev));
+ std::seed_seq prng_seed(data, data + T::state_size);
+ return T(prng_seed);
+}
+
TEST(MutatingTest, Shuffle) {
std::vector<int> actual = {1, 2, 3, 4, 5};
- absl::c_shuffle(actual, std::random_device());
+ absl::c_shuffle(actual, RandomlySeededPrng<std::mt19937_64>());
EXPECT_THAT(actual, UnorderedElementsAre(1, 2, 3, 4, 5));
}
+TEST(MutatingTest, Sample) {
+ std::vector<int> actual;
+ absl::c_sample(std::vector<int>{1, 2, 3, 4, 5}, std::back_inserter(actual), 3,
+ RandomlySeededPrng<std::mt19937_64>());
+ EXPECT_THAT(actual, IsSubsetOf({1, 2, 3, 4, 5}));
+ EXPECT_THAT(actual, SizeIs(3));
+}
+
TEST(MutatingTest, PartialSort) {
std::vector<int> sequence{5, 3, 42, 0};
absl::c_partial_sort(sequence, sequence.begin() + 2);
diff --git a/absl/algorithm/equal_benchmark.cc b/absl/algorithm/equal_benchmark.cc
deleted file mode 100644
index 7bf62c9..0000000
--- a/absl/algorithm/equal_benchmark.cc
+++ /dev/null
@@ -1,126 +0,0 @@
-// Copyright 2017 The Abseil Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <cstdint>
-#include <cstring>
-
-#include "benchmark/benchmark.h"
-#include "absl/algorithm/algorithm.h"
-
-namespace {
-
-// The range of sequence sizes to benchmark.
-constexpr int kMinBenchmarkSize = 1024;
-constexpr int kMaxBenchmarkSize = 8 * 1024 * 1024;
-
-// A user-defined type for use in equality benchmarks. Note that we expect
-// std::memcmp to win for this type: libstdc++'s std::equal only defers to
-// memcmp for integral types. This is because it is not straightforward to
-// guarantee that std::memcmp would produce a result "as-if" compared by
-// operator== for other types (example gotchas: NaN floats, structs with
-// padding).
-struct EightBits {
- explicit EightBits(int /* unused */) : data(0) {}
- bool operator==(const EightBits& rhs) const { return data == rhs.data; }
- uint8_t data;
-};
-
-template <typename T>
-void BM_absl_equal_benchmark(benchmark::State& state) {
- std::vector<T> xs(state.range(0), T(0));
- std::vector<T> ys = xs;
- while (state.KeepRunning()) {
- const bool same = absl::equal(xs.begin(), xs.end(), ys.begin(), ys.end());
- benchmark::DoNotOptimize(same);
- }
-}
-
-template <typename T>
-void BM_std_equal_benchmark(benchmark::State& state) {
- std::vector<T> xs(state.range(0), T(0));
- std::vector<T> ys = xs;
- while (state.KeepRunning()) {
- const bool same = std::equal(xs.begin(), xs.end(), ys.begin());
- benchmark::DoNotOptimize(same);
- }
-}
-
-template <typename T>
-void BM_memcmp_benchmark(benchmark::State& state) {
- std::vector<T> xs(state.range(0), T(0));
- std::vector<T> ys = xs;
- while (state.KeepRunning()) {
- const bool same =
- std::memcmp(xs.data(), ys.data(), xs.size() * sizeof(T)) == 0;
- benchmark::DoNotOptimize(same);
- }
-}
-
-// The expectation is that the compiler should be able to elide the equality
-// comparison altogether for sufficiently simple types.
-template <typename T>
-void BM_absl_equal_self_benchmark(benchmark::State& state) {
- std::vector<T> xs(state.range(0), T(0));
- while (state.KeepRunning()) {
- const bool same = absl::equal(xs.begin(), xs.end(), xs.begin(), xs.end());
- benchmark::DoNotOptimize(same);
- }
-}
-
-BENCHMARK_TEMPLATE(BM_absl_equal_benchmark, uint8_t)
- ->Range(kMinBenchmarkSize, kMaxBenchmarkSize);
-BENCHMARK_TEMPLATE(BM_std_equal_benchmark, uint8_t)
- ->Range(kMinBenchmarkSize, kMaxBenchmarkSize);
-BENCHMARK_TEMPLATE(BM_memcmp_benchmark, uint8_t)
- ->Range(kMinBenchmarkSize, kMaxBenchmarkSize);
-BENCHMARK_TEMPLATE(BM_absl_equal_self_benchmark, uint8_t)
- ->Range(kMinBenchmarkSize, kMaxBenchmarkSize);
-
-BENCHMARK_TEMPLATE(BM_absl_equal_benchmark, uint16_t)
- ->Range(kMinBenchmarkSize, kMaxBenchmarkSize);
-BENCHMARK_TEMPLATE(BM_std_equal_benchmark, uint16_t)
- ->Range(kMinBenchmarkSize, kMaxBenchmarkSize);
-BENCHMARK_TEMPLATE(BM_memcmp_benchmark, uint16_t)
- ->Range(kMinBenchmarkSize, kMaxBenchmarkSize);
-BENCHMARK_TEMPLATE(BM_absl_equal_self_benchmark, uint16_t)
- ->Range(kMinBenchmarkSize, kMaxBenchmarkSize);
-
-BENCHMARK_TEMPLATE(BM_absl_equal_benchmark, uint32_t)
- ->Range(kMinBenchmarkSize, kMaxBenchmarkSize);
-BENCHMARK_TEMPLATE(BM_std_equal_benchmark, uint32_t)
- ->Range(kMinBenchmarkSize, kMaxBenchmarkSize);
-BENCHMARK_TEMPLATE(BM_memcmp_benchmark, uint32_t)
- ->Range(kMinBenchmarkSize, kMaxBenchmarkSize);
-BENCHMARK_TEMPLATE(BM_absl_equal_self_benchmark, uint32_t)
- ->Range(kMinBenchmarkSize, kMaxBenchmarkSize);
-
-BENCHMARK_TEMPLATE(BM_absl_equal_benchmark, uint64_t)
- ->Range(kMinBenchmarkSize, kMaxBenchmarkSize);
-BENCHMARK_TEMPLATE(BM_std_equal_benchmark, uint64_t)
- ->Range(kMinBenchmarkSize, kMaxBenchmarkSize);
-BENCHMARK_TEMPLATE(BM_memcmp_benchmark, uint64_t)
- ->Range(kMinBenchmarkSize, kMaxBenchmarkSize);
-BENCHMARK_TEMPLATE(BM_absl_equal_self_benchmark, uint64_t)
- ->Range(kMinBenchmarkSize, kMaxBenchmarkSize);
-
-BENCHMARK_TEMPLATE(BM_absl_equal_benchmark, EightBits)
- ->Range(kMinBenchmarkSize, kMaxBenchmarkSize);
-BENCHMARK_TEMPLATE(BM_std_equal_benchmark, EightBits)
- ->Range(kMinBenchmarkSize, kMaxBenchmarkSize);
-BENCHMARK_TEMPLATE(BM_memcmp_benchmark, EightBits)
- ->Range(kMinBenchmarkSize, kMaxBenchmarkSize);
-BENCHMARK_TEMPLATE(BM_absl_equal_self_benchmark, EightBits)
- ->Range(kMinBenchmarkSize, kMaxBenchmarkSize);
-
-} // namespace
diff --git a/absl/base/BUILD.bazel b/absl/base/BUILD.bazel
index 65ff0dd..f0e3e63 100644
--- a/absl/base/BUILD.bazel
+++ b/absl/base/BUILD.bazel
@@ -14,7 +14,6 @@
# limitations under the License.
#
-load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library", "cc_test")
load(
"//absl:copts/configure_copts.bzl",
"ABSL_DEFAULT_COPTS",
@@ -22,7 +21,14 @@
"ABSL_TEST_COPTS",
)
-package(default_visibility = ["//visibility:public"])
+package(
+ default_visibility = ["//visibility:public"],
+ features = [
+ "header_modules",
+ "layering_check",
+ "parse_headers",
+ ],
+)
licenses(["notice"])
@@ -64,6 +70,29 @@
)
cc_library(
+ name = "no_destructor",
+ hdrs = ["no_destructor.h"],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ ":config",
+ ":nullability",
+ ],
+)
+
+cc_library(
+ name = "nullability",
+ srcs = ["internal/nullability_impl.h"],
+ hdrs = ["nullability.h"],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ ":core_headers",
+ "//absl/meta:type_traits",
+ ],
+)
+
+cc_library(
name = "raw_logging_internal",
srcs = ["internal/raw_logging.cc"],
hdrs = ["internal/raw_logging.h"],
@@ -76,6 +105,7 @@
":atomic_hook",
":config",
":core_headers",
+ ":errno_saver",
":log_severity",
],
)
@@ -114,6 +144,23 @@
)
cc_library(
+ name = "cycleclock_internal",
+ hdrs = [
+ "internal/cycleclock_config.h",
+ "internal/unscaledcycleclock_config.h",
+ ],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ visibility = [
+ "//absl:__subpackages__",
+ ],
+ deps = [
+ ":base_internal",
+ ":config",
+ ],
+)
+
+cc_library(
name = "dynamic_annotations",
srcs = [
"internal/dynamic_annotations.h",
@@ -131,9 +178,6 @@
cc_library(
name = "core_headers",
- srcs = [
- "internal/thread_annotations.h",
- ],
hdrs = [
"attributes.h",
"const_init.h",
@@ -158,7 +202,9 @@
"internal/direct_mmap.h",
"internal/low_level_alloc.h",
],
- copts = ABSL_DEFAULT_COPTS,
+ copts = ABSL_DEFAULT_COPTS + select({
+ "//conditions:default": [],
+ }),
linkopts = select({
"//absl:msvc_compiler": [],
"//absl:clang-cl_compiler": [],
@@ -227,6 +273,10 @@
"//absl:clang-cl_compiler": [
"-DEFAULTLIB:advapi32.lib",
],
+ "//absl:mingw_compiler": [
+ "-DEFAULTLIB:advapi32.lib",
+ "-ladvapi32",
+ ],
"//absl:wasm": [],
"//conditions:default": ["-pthread"],
}) + ABSL_DEFAULT_LINKOPTS,
@@ -235,8 +285,10 @@
":base_internal",
":config",
":core_headers",
+ ":cycleclock_internal",
":dynamic_annotations",
":log_severity",
+ ":nullability",
":raw_logging_internal",
":spinlock_wait",
"//absl/meta:type_traits",
@@ -245,7 +297,7 @@
cc_library(
name = "atomic_hook_test_helper",
- testonly = 1,
+ testonly = True,
srcs = ["internal/atomic_hook_test_helper.cc"],
hdrs = ["internal/atomic_hook_test_helper.h"],
copts = ABSL_DEFAULT_COPTS,
@@ -266,6 +318,7 @@
":atomic_hook",
":atomic_hook_test_helper",
":core_headers",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -281,6 +334,7 @@
deps = [
":base",
":core_headers",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -308,6 +362,7 @@
deps = [
":config",
":throw_delegate",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -321,13 +376,14 @@
deps = [
":errno_saver",
":strerror",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
cc_library(
name = "exception_testing",
- testonly = 1,
+ testonly = True,
hdrs = ["internal/exception_testing.h"],
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
@@ -344,12 +400,14 @@
name = "pretty_function",
hdrs = ["internal/pretty_function.h"],
linkopts = ABSL_DEFAULT_LINKOPTS,
- visibility = ["//absl:__subpackages__"],
+ visibility = [
+ "//absl:__subpackages__",
+ ],
)
cc_library(
name = "exception_safety_testing",
- testonly = 1,
+ testonly = True,
srcs = ["internal/exception_safety_testing.cc"],
hdrs = ["internal/exception_safety_testing.h"],
copts = ABSL_TEST_COPTS,
@@ -373,6 +431,7 @@
deps = [
":exception_safety_testing",
"//absl/memory",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -390,6 +449,7 @@
linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
":base_internal",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -404,6 +464,7 @@
":base_internal",
"//absl/memory",
"//absl/strings",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -412,7 +473,7 @@
# AbslInternalSpinLockDelay and AbslInternalSpinLockWake.
cc_library(
name = "spinlock_test_common",
- testonly = 1,
+ testonly = True,
srcs = ["spinlock_test_common.cc"],
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
@@ -433,19 +494,23 @@
srcs = ["spinlock_test_common.cc"],
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
+ tags = [
+ "no_test_wasm",
+ ],
deps = [
":base",
":base_internal",
":config",
":core_headers",
"//absl/synchronization",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
cc_library(
name = "spinlock_benchmark_common",
- testonly = 1,
+ testonly = True,
srcs = ["internal/spinlock_benchmark.cc"],
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
@@ -455,6 +520,7 @@
deps = [
":base",
":base_internal",
+ ":no_destructor",
":raw_logging_internal",
"//absl/synchronization",
"@com_github_google_benchmark//:benchmark_main",
@@ -464,7 +530,7 @@
cc_binary(
name = "spinlock_benchmark",
- testonly = 1,
+ testonly = True,
copts = ABSL_DEFAULT_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
tags = ["benchmark"],
@@ -486,6 +552,7 @@
":base",
":config",
":core_headers",
+ ":nullability",
],
)
@@ -496,6 +563,7 @@
deps = [
":config",
":endian",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -508,6 +576,7 @@
deps = [
":config",
"//absl/synchronization:thread_pool",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -521,6 +590,47 @@
":base",
":core_headers",
"//absl/synchronization",
+ "@com_google_googletest//:gtest",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "no_destructor_test",
+ srcs = ["no_destructor_test.cc"],
+ copts = ABSL_TEST_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ ":config",
+ ":no_destructor",
+ ":raw_logging_internal",
+ "@com_google_googletest//:gtest",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_binary(
+ name = "no_destructor_benchmark",
+ testonly = True,
+ srcs = ["no_destructor_benchmark.cc"],
+ copts = ABSL_TEST_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ tags = ["benchmark"],
+ visibility = ["//visibility:private"],
+ deps = [
+ ":no_destructor",
+ ":raw_logging_internal",
+ "@com_github_google_benchmark//:benchmark_main",
+ ],
+)
+
+cc_test(
+ name = "nullability_test",
+ srcs = ["nullability_test.cc"],
+ deps = [
+ ":core_headers",
+ ":nullability",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -533,6 +643,7 @@
deps = [
":raw_logging_internal",
"//absl/strings",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -546,6 +657,7 @@
deps = [
":base",
"//absl/synchronization",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -558,6 +670,7 @@
linkopts = ABSL_DEFAULT_LINKOPTS,
tags = [
"no_test_ios_x86_64",
+ "no_test_wasm",
],
deps = [
":malloc_internal",
@@ -571,10 +684,14 @@
srcs = ["internal/thread_identity_test.cc"],
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
+ tags = [
+ "no_test_wasm",
+ ],
deps = [
":base",
":core_headers",
"//absl/synchronization",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -590,81 +707,13 @@
":base",
"//absl/synchronization",
"@com_github_google_benchmark//:benchmark_main",
- ],
-)
-
-cc_library(
- name = "exponential_biased",
- srcs = ["internal/exponential_biased.cc"],
- hdrs = ["internal/exponential_biased.h"],
- linkopts = ABSL_DEFAULT_LINKOPTS,
- visibility = [
- "//absl:__subpackages__",
- ],
- deps = [
- ":config",
- ":core_headers",
- ],
-)
-
-cc_test(
- name = "exponential_biased_test",
- size = "small",
- srcs = ["internal/exponential_biased_test.cc"],
- copts = ABSL_TEST_COPTS,
- linkopts = ABSL_DEFAULT_LINKOPTS,
- visibility = ["//visibility:private"],
- deps = [
- ":exponential_biased",
- "//absl/strings",
- "@com_google_googletest//:gtest_main",
- ],
-)
-
-cc_library(
- name = "periodic_sampler",
- srcs = ["internal/periodic_sampler.cc"],
- hdrs = ["internal/periodic_sampler.h"],
- copts = ABSL_DEFAULT_COPTS,
- linkopts = ABSL_DEFAULT_LINKOPTS,
- deps = [
- ":core_headers",
- ":exponential_biased",
- ],
-)
-
-cc_test(
- name = "periodic_sampler_test",
- size = "small",
- srcs = ["internal/periodic_sampler_test.cc"],
- copts = ABSL_TEST_COPTS,
- linkopts = ABSL_DEFAULT_LINKOPTS,
- visibility = ["//visibility:private"],
- deps = [
- ":core_headers",
- ":periodic_sampler",
- "@com_google_googletest//:gtest_main",
- ],
-)
-
-cc_binary(
- name = "periodic_sampler_benchmark",
- testonly = 1,
- srcs = ["internal/periodic_sampler_benchmark.cc"],
- copts = ABSL_TEST_COPTS,
- linkopts = ABSL_DEFAULT_LINKOPTS,
- tags = ["benchmark"],
- visibility = ["//visibility:private"],
- deps = [
- ":core_headers",
- ":periodic_sampler",
- "@com_github_google_benchmark//:benchmark_main",
+ "@com_google_googletest//:gtest",
],
)
cc_library(
name = "scoped_set_env",
- testonly = 1,
+ testonly = True,
srcs = ["internal/scoped_set_env.cc"],
hdrs = ["internal/scoped_set_env.h"],
linkopts = ABSL_DEFAULT_LINKOPTS,
@@ -685,6 +734,7 @@
linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
":scoped_set_env",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -700,6 +750,7 @@
"//absl/flags:flag_internal",
"//absl/flags:marshalling",
"//absl/strings",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -729,13 +780,14 @@
deps = [
":strerror",
"//absl/strings",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
cc_binary(
name = "strerror_benchmark",
- testonly = 1,
+ testonly = True,
srcs = ["internal/strerror_benchmark.cc"],
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
@@ -768,6 +820,35 @@
linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
":fast_type_id",
+ "@com_google_googletest//:gtest",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_library(
+ name = "prefetch",
+ hdrs = [
+ "prefetch.h",
+ ],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ ":config",
+ ":core_headers",
+ ],
+)
+
+cc_test(
+ name = "prefetch_test",
+ size = "small",
+ srcs = [
+ "prefetch_test.cc",
+ ],
+ copts = ABSL_TEST_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ ":prefetch",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -782,6 +863,7 @@
deps = [
":core_headers",
"//absl/strings",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -795,6 +877,7 @@
deps = [
":core_headers",
"//absl/types:optional",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
diff --git a/absl/base/CMakeLists.txt b/absl/base/CMakeLists.txt
index 7d56aa1..09c622a 100644
--- a/absl/base/CMakeLists.txt
+++ b/absl/base/CMakeLists.txt
@@ -16,6 +16,7 @@
find_library(LIBRT rt)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
atomic_hook
@@ -28,6 +29,7 @@
${ABSL_DEFAULT_COPTS}
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
errno_saver
@@ -47,6 +49,7 @@
SRCS
"log_severity.cc"
DEPS
+ absl::config
absl::core_headers
COPTS
${ABSL_DEFAULT_COPTS}
@@ -54,6 +57,46 @@
absl_cc_library(
NAME
+ no_destructor
+ HDRS
+ "no_destructor.h"
+ DEPS
+ absl::config
+ absl::nullability
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+)
+
+absl_cc_library(
+ NAME
+ nullability
+ HDRS
+ "nullability.h"
+ SRCS
+ "internal/nullability_impl.h"
+ DEPS
+ absl::core_headers
+ absl::type_traits
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+)
+
+absl_cc_test(
+ NAME
+ nullability_test
+ SRCS
+ "nullability_test.cc"
+ COPTS
+ ${ABSL_TEST_COPTS}
+ DEPS
+ absl::core_headers
+ absl::nullability
+ GTest::gtest_main
+)
+
+# Internal-only target, do not depend on directly.
+absl_cc_library(
+ NAME
raw_logging_internal
HDRS
"internal/raw_logging.h"
@@ -63,11 +106,13 @@
absl::atomic_hook
absl::config
absl::core_headers
+ absl::errno_saver
absl::log_severity
COPTS
${ABSL_DEFAULT_COPTS}
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
spinlock_wait
@@ -123,7 +168,6 @@
"optimization.h"
"port.h"
"thread_annotations.h"
- "internal/thread_annotations.h"
COPTS
${ABSL_DEFAULT_COPTS}
DEPS
@@ -131,6 +175,7 @@
PUBLIC
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
malloc_internal
@@ -151,6 +196,7 @@
Threads::Threads
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
base_internal
@@ -174,6 +220,7 @@
"call_once.h"
"casts.h"
"internal/cycleclock.h"
+ "internal/cycleclock_config.h"
"internal/low_level_scheduling.h"
"internal/per_thread_tls.h"
"internal/spinlock.h"
@@ -181,6 +228,7 @@
"internal/thread_identity.h"
"internal/tsan_mutex_interface.h"
"internal/unscaledcycleclock.h"
+ "internal/unscaledcycleclock_config.h"
SRCS
"internal/cycleclock.cc"
"internal/spinlock.cc"
@@ -192,7 +240,7 @@
LINKOPTS
${ABSL_DEFAULT_LINKOPTS}
$<$<BOOL:${LIBRT}>:-lrt>
- $<$<BOOL:${MINGW}>:"advapi32">
+ $<$<BOOL:${MINGW}>:-ladvapi32>
DEPS
absl::atomic_hook
absl::base_internal
@@ -200,6 +248,7 @@
absl::core_headers
absl::dynamic_annotations
absl::log_severity
+ absl::nullability
absl::raw_logging_internal
absl::spinlock_wait
absl::type_traits
@@ -207,6 +256,7 @@
PUBLIC
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
throw_delegate
@@ -221,6 +271,7 @@
absl::raw_logging_internal
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
exception_testing
@@ -234,6 +285,7 @@
TESTONLY
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
pretty_function
@@ -243,6 +295,7 @@
${ABSL_DEFAULT_COPTS}
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
exception_safety_testing
@@ -276,6 +329,7 @@
GTest::gtest_main
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
atomic_hook_test_helper
@@ -375,6 +429,7 @@
GTest::gtest_main
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
spinlock_test_common
@@ -409,6 +464,7 @@
GTest::gtest_main
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
endian
@@ -421,6 +477,7 @@
absl::base
absl::config
absl::core_headers
+ absl::nullability
PUBLIC
)
@@ -467,6 +524,20 @@
absl_cc_test(
NAME
+ no_destructor_test
+ SRCS
+ "no_destructor_test.cc"
+ COPTS
+ ${ABSL_TEST_COPTS}
+ DEPS
+ absl::no_destructor
+ absl::config
+ absl::raw_logging_internal
+ GTest::gtest_main
+)
+
+absl_cc_test(
+ NAME
raw_logging_test
SRCS
"raw_logging_test.cc"
@@ -519,60 +590,7 @@
GTest::gtest_main
)
-absl_cc_library(
- NAME
- exponential_biased
- SRCS
- "internal/exponential_biased.cc"
- HDRS
- "internal/exponential_biased.h"
- COPTS
- ${ABSL_DEFAULT_COPTS}
- DEPS
- absl::config
- absl::core_headers
-)
-
-absl_cc_test(
- NAME
- exponential_biased_test
- SRCS
- "internal/exponential_biased_test.cc"
- COPTS
- ${ABSL_TEST_COPTS}
- DEPS
- absl::exponential_biased
- absl::strings
- GTest::gmock_main
-)
-
-absl_cc_library(
- NAME
- periodic_sampler
- SRCS
- "internal/periodic_sampler.cc"
- HDRS
- "internal/periodic_sampler.h"
- COPTS
- ${ABSL_DEFAULT_COPTS}
- DEPS
- absl::core_headers
- absl::exponential_biased
-)
-
-absl_cc_test(
- NAME
- periodic_sampler_test
- SRCS
- "internal/periodic_sampler_test.cc"
- COPTS
- ${ABSL_TEST_COPTS}
- DEPS
- absl::core_headers
- absl::periodic_sampler
- GTest::gmock_main
-)
-
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
scoped_set_env
@@ -624,6 +642,7 @@
GTest::gtest_main
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
strerror
@@ -655,6 +674,7 @@
GTest::gtest_main
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
fast_type_id
@@ -680,6 +700,32 @@
GTest::gtest_main
)
+absl_cc_library(
+ NAME
+ prefetch
+ HDRS
+ "prefetch.h"
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ LINKOPTS
+ ${ABSL_DEFAULT_LINKOPTS}
+ DEPS
+ absl::config
+ absl::core_headers
+)
+
+absl_cc_test(
+ NAME
+ prefetch_test
+ SRCS
+ "prefetch_test.cc"
+ COPTS
+ ${ABSL_TEST_COPTS}
+ DEPS
+ absl::prefetch
+ GTest::gtest_main
+)
+
absl_cc_test(
NAME
optimization_test
diff --git a/absl/base/attributes.h b/absl/base/attributes.h
index 5213955..38086a8 100644
--- a/absl/base/attributes.h
+++ b/absl/base/attributes.h
@@ -136,9 +136,10 @@
// for further information.
// The MinGW compiler doesn't complain about the weak attribute until the link
// step, presumably because Windows doesn't use ELF binaries.
-#if (ABSL_HAVE_ATTRIBUTE(weak) || \
- (defined(__GNUC__) && !defined(__clang__))) && \
- (!defined(_WIN32) || __clang_major__ < 9) && !defined(__MINGW32__)
+#if (ABSL_HAVE_ATTRIBUTE(weak) || \
+ (defined(__GNUC__) && !defined(__clang__))) && \
+ (!defined(_WIN32) || (defined(__clang__) && __clang_major__ >= 9)) && \
+ !defined(__MINGW32__)
#undef ABSL_ATTRIBUTE_WEAK
#define ABSL_ATTRIBUTE_WEAK __attribute__((weak))
#define ABSL_HAVE_ATTRIBUTE_WEAK 1
@@ -210,8 +211,20 @@
// out of bounds or does other scary things with memory.
// NOTE: GCC supports AddressSanitizer(asan) since 4.8.
// https://gcc.gnu.org/gcc-4.8/changes.html
-#if ABSL_HAVE_ATTRIBUTE(no_sanitize_address)
+#if defined(ABSL_HAVE_ADDRESS_SANITIZER) && \
+ ABSL_HAVE_ATTRIBUTE(no_sanitize_address)
#define ABSL_ATTRIBUTE_NO_SANITIZE_ADDRESS __attribute__((no_sanitize_address))
+#elif defined(ABSL_HAVE_ADDRESS_SANITIZER) && defined(_MSC_VER) && \
+ _MSC_VER >= 1928
+// https://docs.microsoft.com/en-us/cpp/cpp/no-sanitize-address
+#define ABSL_ATTRIBUTE_NO_SANITIZE_ADDRESS __declspec(no_sanitize_address)
+#elif defined(ABSL_HAVE_HWADDRESS_SANITIZER) && ABSL_HAVE_ATTRIBUTE(no_sanitize)
+// HWAddressSanitizer is a sanitizer similar to AddressSanitizer, which uses CPU
+// features to detect similar bugs with less CPU and memory overhead.
+// NOTE: GCC supports HWAddressSanitizer(hwasan) since 11.
+// https://gcc.gnu.org/gcc-11/changes.html
+#define ABSL_ATTRIBUTE_NO_SANITIZE_ADDRESS \
+ __attribute__((no_sanitize("hwaddress")))
#else
#define ABSL_ATTRIBUTE_NO_SANITIZE_ADDRESS
#endif
@@ -261,7 +274,7 @@
//
// Tells the ControlFlowIntegrity sanitizer to not instrument a given function.
// See https://clang.llvm.org/docs/ControlFlowIntegrity.html for details.
-#if ABSL_HAVE_ATTRIBUTE(no_sanitize)
+#if ABSL_HAVE_ATTRIBUTE(no_sanitize) && defined(__llvm__)
#define ABSL_ATTRIBUTE_NO_SANITIZE_CFI __attribute__((no_sanitize("cfi")))
#else
#define ABSL_ATTRIBUTE_NO_SANITIZE_CFI
@@ -311,15 +324,22 @@
__attribute__((section(#name))) __attribute__((noinline))
#endif
-
// ABSL_ATTRIBUTE_SECTION_VARIABLE
//
// Tells the compiler/linker to put a given variable into a section and define
// `__start_ ## name` and `__stop_ ## name` symbols to bracket the section.
// This functionality is supported by GNU linker.
#ifndef ABSL_ATTRIBUTE_SECTION_VARIABLE
+#ifdef _AIX
+// __attribute__((section(#name))) on AIX is achieved by using the `.csect`
+// psudo op which includes an additional integer as part of its syntax indcating
+// alignment. If data fall under different alignments then you might get a
+// compilation error indicating a `Section type conflict`.
+#define ABSL_ATTRIBUTE_SECTION_VARIABLE(name)
+#else
#define ABSL_ATTRIBUTE_SECTION_VARIABLE(name) __attribute__((section(#name)))
#endif
+#endif
// ABSL_DECLARE_ATTRIBUTE_SECTION_VARS
//
@@ -330,8 +350,8 @@
// a no-op on ELF but not on Mach-O.
//
#ifndef ABSL_DECLARE_ATTRIBUTE_SECTION_VARS
-#define ABSL_DECLARE_ATTRIBUTE_SECTION_VARS(name) \
- extern char __start_##name[] ABSL_ATTRIBUTE_WEAK; \
+#define ABSL_DECLARE_ATTRIBUTE_SECTION_VARS(name) \
+ extern char __start_##name[] ABSL_ATTRIBUTE_WEAK; \
extern char __stop_##name[] ABSL_ATTRIBUTE_WEAK
#endif
#ifndef ABSL_DEFINE_ATTRIBUTE_SECTION_VARS
@@ -392,6 +412,9 @@
//
// Tells the compiler to warn about unused results.
//
+// For code or headers that are assured to only build with C++17 and up, prefer
+// just using the standard `[[nodiscard]]` directly over this macro.
+//
// When annotating a function, it must appear as the first part of the
// declaration or definition. The compiler will warn if the return value from
// such a function is unused:
@@ -418,9 +441,10 @@
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66425
//
// Note: past advice was to place the macro after the argument list.
-#if ABSL_HAVE_ATTRIBUTE(nodiscard)
-#define ABSL_MUST_USE_RESULT [[nodiscard]]
-#elif defined(__clang__) && ABSL_HAVE_ATTRIBUTE(warn_unused_result)
+//
+// TODO(b/176172494): Use ABSL_HAVE_CPP_ATTRIBUTE(nodiscard) when all code is
+// compliant with the stricter [[nodiscard]].
+#if defined(__clang__) && ABSL_HAVE_ATTRIBUTE(warn_unused_result)
#define ABSL_MUST_USE_RESULT __attribute__((warn_unused_result))
#else
#define ABSL_MUST_USE_RESULT
@@ -490,7 +514,7 @@
#define ABSL_XRAY_NEVER_INSTRUMENT [[clang::xray_never_instrument]]
#if ABSL_HAVE_CPP_ATTRIBUTE(clang::xray_log_args)
#define ABSL_XRAY_LOG_ARGS(N) \
- [[clang::xray_always_instrument, clang::xray_log_args(N)]]
+ [[clang::xray_always_instrument, clang::xray_log_args(N)]]
#else
#define ABSL_XRAY_LOG_ARGS(N) [[clang::xray_always_instrument]]
#endif
@@ -548,13 +572,19 @@
// ABSL_ATTRIBUTE_PACKED
//
// Instructs the compiler not to use natural alignment for a tagged data
-// structure, but instead to reduce its alignment to 1. This attribute can
-// either be applied to members of a structure or to a structure in its
-// entirety. Applying this attribute (judiciously) to a structure in its
-// entirety to optimize the memory footprint of very commonly-used structs is
-// fine. Do not apply this attribute to a structure in its entirety if the
-// purpose is to control the offsets of the members in the structure. Instead,
-// apply this attribute only to structure members that need it.
+// structure, but instead to reduce its alignment to 1.
+//
+// Therefore, 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.
+//
+// This attribute can either be applied to members of a structure or to a
+// structure in its entirety. Applying this attribute (judiciously) to a
+// structure in its entirety to optimize the memory footprint of very
+// commonly-used structs is fine. Do not apply this attribute to a structure in
+// its entirety if the purpose is to control the offsets of the members in the
+// structure. Instead, apply this attribute only to structure members that need
+// it.
//
// When applying ABSL_ATTRIBUTE_PACKED only to specific structure members the
// natural alignment of structure members not annotated is preserved. Aligned
@@ -628,6 +658,9 @@
// declarations. The macro argument is used as a custom diagnostic message (e.g.
// suggestion of a better alternative).
//
+// For code or headers that are assured to only build with C++14 and up, prefer
+// just using the standard `[[deprecated("message")]]` directly over this macro.
+//
// Examples:
//
// class ABSL_DEPRECATED("Use Bar instead") Foo {...};
@@ -638,26 +671,60 @@
// ABSL_DEPRECATED("Use DoThat() instead")
// void DoThis();
//
+// enum FooEnum {
+// kBar ABSL_DEPRECATED("Use kBaz instead"),
+// };
+//
// Every usage of a deprecated entity will trigger a warning when compiled with
-// clang's `-Wdeprecated-declarations` option. This option is turned off by
-// default, but the warnings will be reported by clang-tidy.
-#if defined(__clang__) && defined(__cplusplus) && __cplusplus >= 201103L
+// GCC/Clang's `-Wdeprecated-declarations` option. Google's production toolchain
+// turns this warning off by default, instead relying on clang-tidy to report
+// new uses of deprecated code.
+#if ABSL_HAVE_ATTRIBUTE(deprecated)
#define ABSL_DEPRECATED(message) __attribute__((deprecated(message)))
-#endif
-
-#ifndef ABSL_DEPRECATED
+#else
#define ABSL_DEPRECATED(message)
#endif
+// When deprecating Abseil code, it is sometimes necessary to turn off the
+// warning within Abseil, until the deprecated code is actually removed. The
+// deprecated code can be surrounded with these directives to achieve that
+// result.
+//
+// class ABSL_DEPRECATED("Use Bar instead") Foo;
+//
+// ABSL_INTERNAL_DISABLE_DEPRECATED_DECLARATION_WARNING
+// Baz ComputeBazFromFoo(Foo f);
+// ABSL_INTERNAL_RESTORE_DEPRECATED_DECLARATION_WARNING
+#if defined(__GNUC__) || defined(__clang__)
+// Clang also supports these GCC pragmas.
+#define ABSL_INTERNAL_DISABLE_DEPRECATED_DECLARATION_WARNING \
+ _Pragma("GCC diagnostic push") \
+ _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"")
+#define ABSL_INTERNAL_RESTORE_DEPRECATED_DECLARATION_WARNING \
+ _Pragma("GCC diagnostic pop")
+#else
+#define ABSL_INTERNAL_DISABLE_DEPRECATED_DECLARATION_WARNING
+#define ABSL_INTERNAL_RESTORE_DEPRECATED_DECLARATION_WARNING
+#endif // defined(__GNUC__) || defined(__clang__)
+
// ABSL_CONST_INIT
//
// A variable declaration annotated with the `ABSL_CONST_INIT` attribute will
// not compile (on supported platforms) unless the variable has a constant
// initializer. This is useful for variables with static and thread storage
// duration, because it guarantees that they will not suffer from the so-called
-// "static init order fiasco". Prefer to put this attribute on the most visible
-// declaration of the variable, if there's more than one, because code that
-// accesses the variable can then use the attribute for optimization.
+// "static init order fiasco".
+//
+// This attribute must be placed on the initializing declaration of the
+// variable. Some compilers will give a -Wmissing-constinit warning when this
+// attribute is placed on some other declaration but missing from the
+// initializing declaration.
+//
+// In some cases (notably with thread_local variables), `ABSL_CONST_INIT` can
+// also be used in a non-initializing declaration to tell the compiler that a
+// variable is already initialized, reducing overhead that would otherwise be
+// incurred by a hidden guard variable. Thus annotating all declarations with
+// this attribute is recommended to potentially enhance optimization.
//
// Example:
//
@@ -666,14 +733,19 @@
// ABSL_CONST_INIT static MyType my_var;
// };
//
-// MyType MyClass::my_var = MakeMyType(...);
+// ABSL_CONST_INIT MyType MyClass::my_var = MakeMyType(...);
+//
+// For code or headers that are assured to only build with C++20 and up, prefer
+// just using the standard `constinit` keyword directly over this macro.
//
// Note that this attribute is redundant if the variable is declared constexpr.
-#if ABSL_HAVE_CPP_ATTRIBUTE(clang::require_constant_initialization)
+#if defined(__cpp_constinit) && __cpp_constinit >= 201907L
+#define ABSL_CONST_INIT constinit
+#elif ABSL_HAVE_CPP_ATTRIBUTE(clang::require_constant_initialization)
#define ABSL_CONST_INIT [[clang::require_constant_initialization]]
#else
#define ABSL_CONST_INIT
-#endif // ABSL_HAVE_CPP_ATTRIBUTE(clang::require_constant_initialization)
+#endif
// ABSL_ATTRIBUTE_PURE_FUNCTION
//
@@ -687,13 +759,39 @@
//
// Example:
//
-// ABSL_ATTRIBUTE_PURE_FUNCTION int64_t ToInt64Milliseconds(Duration d);
+// ABSL_ATTRIBUTE_PURE_FUNCTION std::string FormatTime(Time t);
#if ABSL_HAVE_CPP_ATTRIBUTE(gnu::pure)
#define ABSL_ATTRIBUTE_PURE_FUNCTION [[gnu::pure]]
#elif ABSL_HAVE_ATTRIBUTE(pure)
#define ABSL_ATTRIBUTE_PURE_FUNCTION __attribute__((pure))
#else
-#define ABSL_ATTRIBUTE_PURE_FUNCTION
+// If the attribute isn't defined, we'll fallback to ABSL_MUST_USE_RESULT since
+// pure functions are useless if its return is ignored.
+#define ABSL_ATTRIBUTE_PURE_FUNCTION ABSL_MUST_USE_RESULT
+#endif
+
+// ABSL_ATTRIBUTE_CONST_FUNCTION
+//
+// ABSL_ATTRIBUTE_CONST_FUNCTION is used to annotate declarations of "const"
+// functions. A const function is similar to a pure function, with one
+// exception: Pure functions may return value that depend on a non-volatile
+// object that isn't provided as a function argument, while the const function
+// is guaranteed to return the same result given the same arguments.
+//
+// Example:
+//
+// ABSL_ATTRIBUTE_CONST_FUNCTION int64_t ToInt64Milliseconds(Duration d);
+#if defined(_MSC_VER) && !defined(__clang__)
+// Put the MSVC case first since MSVC seems to parse const as a C++ keyword.
+#define ABSL_ATTRIBUTE_CONST_FUNCTION ABSL_ATTRIBUTE_PURE_FUNCTION
+#elif ABSL_HAVE_CPP_ATTRIBUTE(gnu::const)
+#define ABSL_ATTRIBUTE_CONST_FUNCTION [[gnu::const]]
+#elif ABSL_HAVE_ATTRIBUTE(const)
+#define ABSL_ATTRIBUTE_CONST_FUNCTION __attribute__((const))
+#else
+// Since const functions are more restrictive pure function, we'll fallback to a
+// pure function if the const attribute is not handled.
+#define ABSL_ATTRIBUTE_CONST_FUNCTION ABSL_ATTRIBUTE_PURE_FUNCTION
#endif
// ABSL_ATTRIBUTE_LIFETIME_BOUND indicates that a resource owned by a function
@@ -718,4 +816,106 @@
#define ABSL_ATTRIBUTE_LIFETIME_BOUND
#endif
+// ABSL_ATTRIBUTE_TRIVIAL_ABI
+// Indicates that a type is "trivially relocatable" -- meaning it can be
+// relocated without invoking the constructor/destructor, using a form of move
+// elision.
+//
+// From a memory safety point of view, putting aside destructor ordering, it's
+// safe to apply ABSL_ATTRIBUTE_TRIVIAL_ABI if an object's location
+// can change over the course of its lifetime: if a constructor can be run one
+// place, and then the object magically teleports to another place where some
+// methods are run, and then the object teleports to yet another place where it
+// is destroyed. This is notably not true for self-referential types, where the
+// move-constructor must keep the self-reference up to date. If the type changed
+// location without invoking the move constructor, it would have a dangling
+// self-reference.
+//
+// The use of this teleporting machinery means that the number of paired
+// move/destroy operations can change, and so it is a bad idea to apply this to
+// a type meant to count the number of moves.
+//
+// Warning: applying this can, rarely, break callers. Objects passed by value
+// will be destroyed at the end of the call, instead of the end of the
+// full-expression containing the call. In addition, it changes the ABI
+// of functions accepting this type by value (e.g. to pass in registers).
+//
+// See also the upstream documentation:
+// https://clang.llvm.org/docs/AttributeReference.html#trivial-abi
+//
+// b/321691395 - This is currently disabled in open-source builds since
+// compiler support differs. If system libraries compiled with GCC are mixed
+// with libraries compiled with Clang, types will have different ideas about
+// their ABI, leading to hard to debug crashes.
+#define ABSL_ATTRIBUTE_TRIVIAL_ABI
+
+// ABSL_ATTRIBUTE_NO_UNIQUE_ADDRESS
+//
+// Indicates a data member can be optimized to occupy no space (if it is empty)
+// and/or its tail padding can be used for other members.
+//
+// For code that is assured to only build with C++20 or later, prefer using
+// the standard attribute `[[no_unique_address]]` directly instead of this
+// macro.
+//
+// https://devblogs.microsoft.com/cppblog/msvc-cpp20-and-the-std-cpp20-switch/#c20-no_unique_address
+// Current versions of MSVC have disabled `[[no_unique_address]]` since it
+// breaks ABI compatibility, but offers `[[msvc::no_unique_address]]` for
+// situations when it can be assured that it is desired. Since Abseil does not
+// claim ABI compatibility in mixed builds, we can offer it unconditionally.
+#if defined(_MSC_VER) && _MSC_VER >= 1929
+#define ABSL_ATTRIBUTE_NO_UNIQUE_ADDRESS [[msvc::no_unique_address]]
+#elif ABSL_HAVE_CPP_ATTRIBUTE(no_unique_address)
+#define ABSL_ATTRIBUTE_NO_UNIQUE_ADDRESS [[no_unique_address]]
+#else
+#define ABSL_ATTRIBUTE_NO_UNIQUE_ADDRESS
+#endif
+
+// ABSL_ATTRIBUTE_UNINITIALIZED
+//
+// GCC and Clang support a flag `-ftrivial-auto-var-init=<option>` (<option>
+// can be "zero" or "pattern") that can be used to initialize automatic stack
+// variables. Variables with this attribute will be left uninitialized,
+// overriding the compiler flag.
+//
+// See https://clang.llvm.org/docs/AttributeReference.html#uninitialized
+// and https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attributes.html#index-uninitialized-variable-attribute
+#if ABSL_HAVE_CPP_ATTRIBUTE(clang::uninitialized)
+#define ABSL_ATTRIBUTE_UNINITIALIZED [[clang::uninitialized]]
+#elif ABSL_HAVE_CPP_ATTRIBUTE(gnu::uninitialized)
+#define ABSL_ATTRIBUTE_UNINITIALIZED [[gnu::uninitialized]]
+#elif ABSL_HAVE_ATTRIBUTE(uninitialized)
+#define ABSL_ATTRIBUTE_UNINITIALIZED __attribute__((uninitialized))
+#else
+#define ABSL_ATTRIBUTE_UNINITIALIZED
+#endif
+
+// ABSL_ATTRIBUTE_WARN_UNUSED
+//
+// Compilers routinely warn about trivial variables that are unused. For
+// non-trivial types, this warning is suppressed since the
+// constructor/destructor may be intentional and load-bearing, for example, with
+// a RAII scoped lock.
+//
+// For example:
+//
+// class ABSL_ATTRIBUTE_WARN_UNUSED MyType {
+// public:
+// MyType();
+// ~MyType();
+// };
+//
+// void foo() {
+// // Warns with ABSL_ATTRIBUTE_WARN_UNUSED attribute present.
+// MyType unused;
+// }
+//
+// See https://clang.llvm.org/docs/AttributeReference.html#warn-unused and
+// https://gcc.gnu.org/onlinedocs/gcc/C_002b_002b-Attributes.html#index-warn_005funused-type-attribute
+#if ABSL_HAVE_CPP_ATTRIBUTE(gnu::warn_unused)
+#define ABSL_ATTRIBUTE_WARN_UNUSED [[gnu::warn_unused]]
+#else
+#define ABSL_ATTRIBUTE_WARN_UNUSED
+#endif
+
#endif // ABSL_BASE_ATTRIBUTES_H_
diff --git a/absl/base/call_once.h b/absl/base/call_once.h
index 96109f5..7b0e69c 100644
--- a/absl/base/call_once.h
+++ b/absl/base/call_once.h
@@ -37,6 +37,7 @@
#include "absl/base/internal/scheduling_mode.h"
#include "absl/base/internal/spinlock_wait.h"
#include "absl/base/macros.h"
+#include "absl/base/nullability.h"
#include "absl/base/optimization.h"
#include "absl/base/port.h"
@@ -46,7 +47,8 @@
class once_flag;
namespace base_internal {
-std::atomic<uint32_t>* ControlWord(absl::once_flag* flag);
+absl::Nonnull<std::atomic<uint32_t>*> ControlWord(
+ absl::Nonnull<absl::once_flag*> flag);
} // namespace base_internal
// call_once()
@@ -89,7 +91,8 @@
once_flag& operator=(const once_flag&) = delete;
private:
- friend std::atomic<uint32_t>* base_internal::ControlWord(once_flag* flag);
+ friend absl::Nonnull<std::atomic<uint32_t>*> base_internal::ControlWord(
+ absl::Nonnull<once_flag*> flag);
std::atomic<uint32_t> control_;
};
@@ -103,7 +106,8 @@
// Like call_once, but uses KERNEL_ONLY scheduling. Intended to be used to
// initialize entities used by the scheduler implementation.
template <typename Callable, typename... Args>
-void LowLevelCallOnce(absl::once_flag* flag, Callable&& fn, Args&&... args);
+void LowLevelCallOnce(absl::Nonnull<absl::once_flag*> flag, Callable&& fn,
+ Args&&... args);
// Disables scheduling while on stack when scheduling mode is non-cooperative.
// No effect for cooperative scheduling modes.
@@ -123,7 +127,7 @@
private:
base_internal::SchedulingMode mode_;
- bool guard_result_;
+ bool guard_result_ = false;
};
// Bit patterns for call_once state machine values. Internal implementation
@@ -143,10 +147,10 @@
};
template <typename Callable, typename... Args>
-ABSL_ATTRIBUTE_NOINLINE
-void CallOnceImpl(std::atomic<uint32_t>* control,
- base_internal::SchedulingMode scheduling_mode, Callable&& fn,
- Args&&... args) {
+ABSL_ATTRIBUTE_NOINLINE void CallOnceImpl(
+ absl::Nonnull<std::atomic<uint32_t>*> control,
+ base_internal::SchedulingMode scheduling_mode, Callable&& fn,
+ Args&&... args) {
#ifndef NDEBUG
{
uint32_t old_control = control->load(std::memory_order_relaxed);
@@ -185,12 +189,14 @@
} // else *control is already kOnceDone
}
-inline std::atomic<uint32_t>* ControlWord(once_flag* flag) {
+inline absl::Nonnull<std::atomic<uint32_t>*> ControlWord(
+ absl::Nonnull<once_flag*> flag) {
return &flag->control_;
}
template <typename Callable, typename... Args>
-void LowLevelCallOnce(absl::once_flag* flag, Callable&& fn, Args&&... args) {
+void LowLevelCallOnce(absl::Nonnull<absl::once_flag*> flag, Callable&& fn,
+ Args&&... args) {
std::atomic<uint32_t>* once = base_internal::ControlWord(flag);
uint32_t s = once->load(std::memory_order_acquire);
if (ABSL_PREDICT_FALSE(s != base_internal::kOnceDone)) {
diff --git a/absl/base/casts.h b/absl/base/casts.h
index 83c6912..e0b11bb 100644
--- a/absl/base/casts.h
+++ b/absl/base/casts.h
@@ -29,6 +29,10 @@
#include <type_traits>
#include <utility>
+#if defined(__cpp_lib_bit_cast) && __cpp_lib_bit_cast >= 201806L
+#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/macros.h"
#include "absl/meta/type_traits.h"
@@ -36,19 +40,6 @@
namespace absl {
ABSL_NAMESPACE_BEGIN
-namespace internal_casts {
-
-template <class Dest, class Source>
-struct is_bitcastable
- : std::integral_constant<
- bool,
- sizeof(Dest) == sizeof(Source) &&
- type_traits_internal::is_trivially_copyable<Source>::value &&
- type_traits_internal::is_trivially_copyable<Dest>::value &&
- std::is_default_constructible<Dest>::value> {};
-
-} // namespace internal_casts
-
// implicit_cast()
//
// Performs an implicit conversion between types following the language
@@ -99,87 +90,89 @@
//
// Such implicit cast chaining may be useful within template logic.
template <typename To>
-constexpr To implicit_cast(typename absl::internal::identity_t<To> to) {
+constexpr To implicit_cast(typename absl::internal::type_identity_t<To> to) {
return to;
}
// bit_cast()
//
-// Performs a bitwise cast on a type without changing the underlying bit
-// representation of that type's value. The two types must be of the same size
-// and both types must be trivially copyable. As with most casts, use with
-// caution. A `bit_cast()` might be needed when you need to temporarily treat a
-// type as some other type, such as in the following cases:
+// Creates a value of the new type `Dest` whose representation is the same as
+// that of the argument, which is of (deduced) type `Source` (a "bitwise cast";
+// every bit in the value representation of the result is equal to the
+// corresponding bit in the object representation of the source). Source and
+// destination types must be of the same size, and both types must be trivially
+// copyable.
//
-// * Serialization (casting temporarily to `char *` for those purposes is
-// always allowed by the C++ standard)
-// * Managing the individual bits of a type within mathematical operations
-// that are not normally accessible through that type
-// * Casting non-pointer types to pointer types (casting the other way is
-// allowed by `reinterpret_cast()` but round-trips cannot occur the other
-// way).
-//
-// Example:
+// As with most casts, use with caution. A `bit_cast()` might be needed when you
+// need to treat a value as the value of some other type, for example, to access
+// the individual bits of an object which are not normally accessible through
+// the object's type, such as for working with the binary representation of a
+// floating point value:
//
// float f = 3.14159265358979;
-// int i = bit_cast<int32_t>(f);
+// int i = bit_cast<int>(f);
// // i = 0x40490fdb
//
-// Casting non-pointer types to pointer types and then dereferencing them
-// traditionally produces undefined behavior.
+// Reinterpreting and accessing a value directly as a different type (as shown
+// below) usually results in undefined behavior.
//
// Example:
//
// // WRONG
-// float f = 3.14159265358979; // WRONG
-// int i = * reinterpret_cast<int*>(&f); // WRONG
+// float f = 3.14159265358979;
+// int i = reinterpret_cast<int&>(f); // Wrong
+// int j = *reinterpret_cast<int*>(&f); // Equally wrong
+// int k = *bit_cast<int*>(&f); // Equally wrong
//
-// The address-casting method produces undefined behavior according to the ISO
-// C++ specification section [basic.lval]. Roughly, this section says: if an
-// object in memory has one type, and a program accesses it with a different
-// type, the result is undefined behavior for most values of "different type".
+// Reinterpret-casting results in undefined behavior according to the ISO C++
+// specification, section [basic.lval]. Roughly, this section says: if an object
+// in memory has one type, and a program accesses it with a different type, the
+// result is undefined behavior for most "different type".
+//
+// Using bit_cast on a pointer and then dereferencing it is no better than using
+// reinterpret_cast. You should only use bit_cast on the value itself.
//
// Such casting results in type punning: holding an object in memory of one type
// and reading its bits back using a different type. A `bit_cast()` avoids this
-// issue by implementing its casts using `memcpy()`, which avoids introducing
-// this undefined behavior.
+// issue by copying the object representation to a new value, which avoids
+// introducing this undefined behavior (since the original value is never
+// accessed in the wrong way).
//
-// NOTE: The requirements here are more strict than the bit_cast of standard
-// proposal p0476 due to the need for workarounds and lack of intrinsics.
-// Specifically, this implementation also requires `Dest` to be
-// default-constructible.
+// The requirements of `absl::bit_cast` are more strict than that of
+// `std::bit_cast` unless compiler support is available. Specifically, without
+// compiler support, this implementation also requires `Dest` to be
+// default-constructible. In C++20, `absl::bit_cast` is replaced by
+// `std::bit_cast`.
+#if defined(__cpp_lib_bit_cast) && __cpp_lib_bit_cast >= 201806L
+
+using std::bit_cast;
+
+#else // defined(__cpp_lib_bit_cast) && __cpp_lib_bit_cast >= 201806L
+
template <
typename Dest, typename Source,
- typename std::enable_if<internal_casts::is_bitcastable<Dest, Source>::value,
+ typename std::enable_if<sizeof(Dest) == sizeof(Source) &&
+ std::is_trivially_copyable<Source>::value &&
+ std::is_trivially_copyable<Dest>::value
+#if !ABSL_HAVE_BUILTIN(__builtin_bit_cast)
+ && std::is_default_constructible<Dest>::value
+#endif // !ABSL_HAVE_BUILTIN(__builtin_bit_cast)
+ ,
int>::type = 0>
+#if ABSL_HAVE_BUILTIN(__builtin_bit_cast)
+inline constexpr Dest bit_cast(const Source& source) {
+ return __builtin_bit_cast(Dest, source);
+}
+#else // ABSL_HAVE_BUILTIN(__builtin_bit_cast)
inline Dest bit_cast(const Source& source) {
Dest dest;
memcpy(static_cast<void*>(std::addressof(dest)),
static_cast<const void*>(std::addressof(source)), sizeof(dest));
return dest;
}
+#endif // ABSL_HAVE_BUILTIN(__builtin_bit_cast)
-// NOTE: This overload is only picked if the requirements of bit_cast are
-// not met. It is therefore UB, but is provided temporarily as previous
-// versions of this function template were unchecked. Do not use this in
-// new code.
-template <
- typename Dest, typename Source,
- typename std::enable_if<
- !internal_casts::is_bitcastable<Dest, Source>::value,
- int>::type = 0>
-ABSL_DEPRECATED(
- "absl::bit_cast type requirements were violated. Update the types "
- "being used such that they are the same size and are both "
- "TriviallyCopyable.")
-inline Dest bit_cast(const Source& source) {
- static_assert(sizeof(Dest) == sizeof(Source),
- "Source and destination types should have equal sizes.");
-
- Dest dest;
- memcpy(&dest, &source, sizeof(dest));
- return dest;
-}
+#endif // defined(__cpp_lib_bit_cast) && __cpp_lib_bit_cast >= 201806L
ABSL_NAMESPACE_END
} // namespace absl
diff --git a/absl/base/config.h b/absl/base/config.h
index 0524196..5fa9f0e 100644
--- a/absl/base/config.h
+++ b/absl/base/config.h
@@ -56,6 +56,31 @@
#include <cstddef>
#endif // __cplusplus
+// ABSL_INTERNAL_CPLUSPLUS_LANG
+//
+// MSVC does not set the value of __cplusplus correctly, but instead uses
+// _MSVC_LANG as a stand-in.
+// https://docs.microsoft.com/en-us/cpp/preprocessor/predefined-macros
+//
+// However, there are reports that MSVC even sets _MSVC_LANG incorrectly at
+// times, for example:
+// https://github.com/microsoft/vscode-cpptools/issues/1770
+// https://reviews.llvm.org/D70996
+//
+// For this reason, this symbol is considered INTERNAL and code outside of
+// Abseil must not use it.
+#if defined(_MSVC_LANG)
+#define ABSL_INTERNAL_CPLUSPLUS_LANG _MSVC_LANG
+#elif defined(__cplusplus)
+#define ABSL_INTERNAL_CPLUSPLUS_LANG __cplusplus
+#endif
+
+#if defined(ABSL_INTERNAL_CPLUSPLUS_LANG) && \
+ ABSL_INTERNAL_CPLUSPLUS_LANG >= 202002L
+// Include library feature test macros.
+#include <version>
+#endif
+
#if defined(__APPLE__)
// Included for TARGET_OS_IPHONE, __IPHONE_OS_VERSION_MIN_REQUIRED,
// __IPHONE_8_0.
@@ -66,6 +91,35 @@
#include "absl/base/options.h"
#include "absl/base/policy_checks.h"
+// Abseil long-term support (LTS) releases will define
+// `ABSL_LTS_RELEASE_VERSION` to the integer representing the date string of the
+// LTS release version, and will define `ABSL_LTS_RELEASE_PATCH_LEVEL` to the
+// integer representing the patch-level for that release.
+//
+// For example, for LTS release version "20300401.2", this would give us
+// ABSL_LTS_RELEASE_VERSION == 20300401 && ABSL_LTS_RELEASE_PATCH_LEVEL == 2
+//
+// These symbols will not be defined in non-LTS code.
+//
+// Abseil recommends that clients live-at-head. Therefore, if you are using
+// these symbols to assert a minimum version requirement, we recommend you do it
+// as
+//
+// #if defined(ABSL_LTS_RELEASE_VERSION) && ABSL_LTS_RELEASE_VERSION < 20300401
+// #error Project foo requires Abseil LTS version >= 20300401
+// #endif
+//
+// The `defined(ABSL_LTS_RELEASE_VERSION)` part of the check excludes
+// live-at-head clients from the minimum version assertion.
+//
+// See https://abseil.io/about/releases for more information on Abseil release
+// management.
+//
+// LTS releases can be obtained from
+// https://github.com/abseil/abseil-cpp/releases.
+#undef ABSL_LTS_RELEASE_VERSION
+#undef ABSL_LTS_RELEASE_PATCH_LEVEL
+
// Helper macro to convert a CPP variable to a string literal.
#define ABSL_INTERNAL_DO_TOKEN_STR(x) #x
#define ABSL_INTERNAL_TOKEN_STR(x) ABSL_INTERNAL_DO_TOKEN_STR(x)
@@ -154,12 +208,6 @@
#define ABSL_HAVE_BUILTIN(x) 0
#endif
-#if defined(__is_identifier)
-#define ABSL_INTERNAL_HAS_KEYWORD(x) !(__is_identifier(x))
-#else
-#define ABSL_INTERNAL_HAS_KEYWORD(x) 0
-#endif
-
#ifdef __has_feature
#define ABSL_HAVE_FEATURE(f) __has_feature(f)
#else
@@ -183,25 +231,20 @@
#endif
// ABSL_HAVE_TLS is defined to 1 when __thread should be supported.
-// We assume __thread is supported on Linux when compiled with Clang or compiled
-// against libstdc++ with _GLIBCXX_HAVE_TLS defined.
+// We assume __thread is supported on Linux or Asylo when compiled with Clang or
+// compiled against libstdc++ with _GLIBCXX_HAVE_TLS defined.
#ifdef ABSL_HAVE_TLS
#error ABSL_HAVE_TLS cannot be directly set
-#elif defined(__linux__) && (defined(__clang__) || defined(_GLIBCXX_HAVE_TLS))
+#elif (defined(__linux__) || defined(__ASYLO__)) && \
+ (defined(__clang__) || defined(_GLIBCXX_HAVE_TLS))
#define ABSL_HAVE_TLS 1
#endif
// ABSL_HAVE_STD_IS_TRIVIALLY_DESTRUCTIBLE
//
// Checks whether `std::is_trivially_destructible<T>` is supported.
-//
-// Notes: All supported compilers using libc++ support this feature, as does
-// gcc >= 4.8.1 using libstdc++, and Visual Studio.
#ifdef ABSL_HAVE_STD_IS_TRIVIALLY_DESTRUCTIBLE
#error ABSL_HAVE_STD_IS_TRIVIALLY_DESTRUCTIBLE cannot be directly set
-#elif defined(_LIBCPP_VERSION) || defined(_MSC_VER) || \
- (!defined(__clang__) && defined(__GLIBCXX__) && \
- ABSL_INTERNAL_HAVE_MIN_GNUC_VERSION(4, 8))
#define ABSL_HAVE_STD_IS_TRIVIALLY_DESTRUCTIBLE 1
#endif
@@ -209,36 +252,27 @@
//
// Checks whether `std::is_trivially_default_constructible<T>` and
// `std::is_trivially_copy_constructible<T>` are supported.
+#ifdef ABSL_HAVE_STD_IS_TRIVIALLY_CONSTRUCTIBLE
+#error ABSL_HAVE_STD_IS_TRIVIALLY_CONSTRUCTIBLE cannot be directly set
+#else
+#define ABSL_HAVE_STD_IS_TRIVIALLY_CONSTRUCTIBLE 1
+#endif
// ABSL_HAVE_STD_IS_TRIVIALLY_ASSIGNABLE
//
// Checks whether `std::is_trivially_copy_assignable<T>` is supported.
-
-// Notes: Clang with libc++ supports these features, as does gcc >= 5.1 with
-// either libc++ or libstdc++, and Visual Studio (but not NVCC).
-#if defined(ABSL_HAVE_STD_IS_TRIVIALLY_CONSTRUCTIBLE)
-#error ABSL_HAVE_STD_IS_TRIVIALLY_CONSTRUCTIBLE cannot be directly set
-#elif defined(ABSL_HAVE_STD_IS_TRIVIALLY_ASSIGNABLE)
-#error ABSL_HAVE_STD_IS_TRIVIALLY_ASSIGNABLE cannot directly set
-#elif (defined(__clang__) && defined(_LIBCPP_VERSION)) || \
- (!defined(__clang__) && ABSL_INTERNAL_HAVE_MIN_GNUC_VERSION(7, 4) && \
- (defined(_LIBCPP_VERSION) || defined(__GLIBCXX__))) || \
- (defined(_MSC_VER) && !defined(__NVCC__))
-#define ABSL_HAVE_STD_IS_TRIVIALLY_CONSTRUCTIBLE 1
+#ifdef ABSL_HAVE_STD_IS_TRIVIALLY_ASSIGNABLE
+#error ABSL_HAVE_STD_IS_TRIVIALLY_ASSIGNABLE cannot be directly set
+#else
#define ABSL_HAVE_STD_IS_TRIVIALLY_ASSIGNABLE 1
#endif
-// ABSL_HAVE_SOURCE_LOCATION_CURRENT
+// ABSL_HAVE_STD_IS_TRIVIALLY_COPYABLE
//
-// Indicates whether `absl::SourceLocation::current()` will return useful
-// information in some contexts.
-#ifndef ABSL_HAVE_SOURCE_LOCATION_CURRENT
-#if ABSL_INTERNAL_HAS_KEYWORD(__builtin_LINE) && \
- ABSL_INTERNAL_HAS_KEYWORD(__builtin_FILE)
-#define ABSL_HAVE_SOURCE_LOCATION_CURRENT 1
-#elif ABSL_INTERNAL_HAVE_MIN_GNUC_VERSION(5, 0)
-#define ABSL_HAVE_SOURCE_LOCATION_CURRENT 1
-#endif
+// Checks whether `std::is_trivially_copyable<T>` is supported.
+#ifdef ABSL_HAVE_STD_IS_TRIVIALLY_COPYABLE
+#error ABSL_HAVE_STD_IS_TRIVIALLY_COPYABLE cannot be directly set
+#define ABSL_HAVE_STD_IS_TRIVIALLY_COPYABLE 1
#endif
// ABSL_HAVE_THREAD_LOCAL
@@ -304,8 +338,8 @@
#ifdef ABSL_HAVE_INTRINSIC_INT128
#error ABSL_HAVE_INTRINSIC_INT128 cannot be directly set
#elif defined(__SIZEOF_INT128__)
-#if (defined(__clang__) && !defined(_WIN32)) || \
- (defined(__CUDACC__) && __CUDACC_VER_MAJOR__ >= 9) || \
+#if (defined(__clang__) && !defined(_WIN32)) || \
+ (defined(__CUDACC__) && __CUDACC_VER_MAJOR__ >= 9) || \
(defined(__GNUC__) && !defined(__clang__) && !defined(__CUDACC__))
#define ABSL_HAVE_INTRINSIC_INT128 1
#elif defined(__CUDACC__)
@@ -345,9 +379,7 @@
#define ABSL_HAVE_EXCEPTIONS 1
#endif // defined(__EXCEPTIONS) && ABSL_HAVE_FEATURE(cxx_exceptions)
// Handle remaining special cases and default to exceptions being supported.
-#elif !(defined(__GNUC__) && (__GNUC__ < 5) && !defined(__EXCEPTIONS)) && \
- !(ABSL_INTERNAL_HAVE_MIN_GNUC_VERSION(5, 0) && \
- !defined(__cpp_exceptions)) && \
+#elif !(defined(__GNUC__) && !defined(__cpp_exceptions)) && \
!(defined(_MSC_VER) && !defined(_CPPUNWIND))
#define ABSL_HAVE_EXCEPTIONS 1
#endif
@@ -367,7 +399,7 @@
// Windows _WIN32
// NaCL __native_client__
// AsmJS __asmjs__
-// WebAssembly __wasm__
+// WebAssembly (Emscripten) __EMSCRIPTEN__
// Fuchsia __Fuchsia__
//
// Note that since Android defines both __ANDROID__ and __linux__, one
@@ -379,10 +411,12 @@
// POSIX.1-2001.
#ifdef ABSL_HAVE_MMAP
#error ABSL_HAVE_MMAP cannot be directly set
-#elif defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) || \
- defined(__ros__) || defined(__native_client__) || defined(__asmjs__) || \
- defined(__wasm__) || defined(__Fuchsia__) || defined(__sun) || \
- defined(__ASYLO__) || defined(__myriad2__)
+#elif defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) || \
+ defined(_AIX) || defined(__ros__) || defined(__native_client__) || \
+ defined(__asmjs__) || defined(__EMSCRIPTEN__) || defined(__Fuchsia__) || \
+ defined(__sun) || defined(__ASYLO__) || defined(__myriad2__) || \
+ defined(__HAIKU__) || defined(__OpenBSD__) || defined(__NetBSD__) || \
+ defined(__QNX__) || defined(__VXWORKS__) || defined(__hexagon__)
#define ABSL_HAVE_MMAP 1
#endif
@@ -393,7 +427,8 @@
#ifdef ABSL_HAVE_PTHREAD_GETSCHEDPARAM
#error ABSL_HAVE_PTHREAD_GETSCHEDPARAM cannot be directly set
#elif defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) || \
- defined(__ros__)
+ defined(_AIX) || defined(__ros__) || defined(__OpenBSD__) || \
+ defined(__NetBSD__) || defined(__VXWORKS__)
#define ABSL_HAVE_PTHREAD_GETSCHEDPARAM 1
#endif
@@ -412,7 +447,8 @@
// POSIX.1-2001.
#ifdef ABSL_HAVE_SCHED_YIELD
#error ABSL_HAVE_SCHED_YIELD cannot be directly set
-#elif defined(__linux__) || defined(__ros__) || defined(__native_client__)
+#elif defined(__linux__) || defined(__ros__) || defined(__native_client__) || \
+ defined(__VXWORKS__)
#define ABSL_HAVE_SCHED_YIELD 1
#endif
@@ -427,7 +463,7 @@
// platforms.
#ifdef ABSL_HAVE_SEMAPHORE_H
#error ABSL_HAVE_SEMAPHORE_H cannot be directly set
-#elif defined(__linux__) || defined(__ros__)
+#elif defined(__linux__) || defined(__ros__) || defined(__VXWORKS__)
#define ABSL_HAVE_SEMAPHORE_H 1
#endif
@@ -452,9 +488,13 @@
// https://sourceforge.net/p/mingw-w64/mingw-w64/ci/master/tree/mingw-w64-crt/misc/alarm.c
#elif defined(__EMSCRIPTEN__)
// emscripten doesn't support signals
+#elif defined(__wasi__)
+// WASI doesn't support signals
#elif defined(__Fuchsia__)
// Signals don't exist on fuchsia.
#elif defined(__native_client__)
+// Signals don't exist on hexagon/QuRT
+#elif defined(__hexagon__)
#else
// other standard libraries
#define ABSL_HAVE_ALARM 1
@@ -488,22 +528,29 @@
#error "absl endian detection needs to be set up for your compiler"
#endif
-// macOS 10.13 and iOS 10.11 don't let you use <any>, <optional>, or <variant>
-// even though the headers exist and are publicly noted to work. See
+// macOS < 10.13 and iOS < 12 don't support <any>, <optional>, or <variant>
+// because the libc++ shared library shipped on the system doesn't have the
+// requisite exported symbols. See
// https://github.com/abseil/abseil-cpp/issues/207 and
// https://developer.apple.com/documentation/xcode_release_notes/xcode_10_release_notes
+//
// libc++ spells out the availability requirements in the file
// llvm-project/libcxx/include/__config via the #define
-// _LIBCPP_AVAILABILITY_BAD_OPTIONAL_ACCESS.
-#if defined(__APPLE__) && defined(_LIBCPP_VERSION) && \
- ((defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) && \
- __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ < 101400) || \
- (defined(__ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__) && \
- __ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__ < 120000) || \
- (defined(__ENVIRONMENT_WATCH_OS_VERSION_MIN_REQUIRED__) && \
- __ENVIRONMENT_WATCH_OS_VERSION_MIN_REQUIRED__ < 50000) || \
- (defined(__ENVIRONMENT_TV_OS_VERSION_MIN_REQUIRED__) && \
- __ENVIRONMENT_TV_OS_VERSION_MIN_REQUIRED__ < 120000))
+// _LIBCPP_AVAILABILITY_BAD_OPTIONAL_ACCESS. The set of versions has been
+// modified a few times, via
+// https://github.com/llvm/llvm-project/commit/7fb40e1569dd66292b647f4501b85517e9247953
+// and
+// https://github.com/llvm/llvm-project/commit/0bc451e7e137c4ccadcd3377250874f641ca514a
+// The second has the actually correct versions, thus, is what we copy here.
+#if defined(__APPLE__) && \
+ ((defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) && \
+ __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ < 101300) || \
+ (defined(__ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__) && \
+ __ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__ < 120000) || \
+ (defined(__ENVIRONMENT_WATCH_OS_VERSION_MIN_REQUIRED__) && \
+ __ENVIRONMENT_WATCH_OS_VERSION_MIN_REQUIRED__ < 50000) || \
+ (defined(__ENVIRONMENT_TV_OS_VERSION_MIN_REQUIRED__) && \
+ __ENVIRONMENT_TV_OS_VERSION_MIN_REQUIRED__ < 120000))
#define ABSL_INTERNAL_APPLE_CXX17_TYPES_UNAVAILABLE 1
#else
#define ABSL_INTERNAL_APPLE_CXX17_TYPES_UNAVAILABLE 0
@@ -511,30 +558,28 @@
// ABSL_HAVE_STD_ANY
//
-// Checks whether C++17 std::any is available by checking whether <any> exists.
+// Checks whether C++17 std::any is available.
#ifdef ABSL_HAVE_STD_ANY
#error "ABSL_HAVE_STD_ANY cannot be directly set."
-#endif
-
-#ifdef __has_include
-#if __has_include(<any>) && defined(__cplusplus) && __cplusplus >= 201703L && \
+#elif defined(__cpp_lib_any) && __cpp_lib_any >= 201606L
+#define ABSL_HAVE_STD_ANY 1
+#elif defined(ABSL_INTERNAL_CPLUSPLUS_LANG) && \
+ ABSL_INTERNAL_CPLUSPLUS_LANG >= 201703L && \
!ABSL_INTERNAL_APPLE_CXX17_TYPES_UNAVAILABLE
#define ABSL_HAVE_STD_ANY 1
#endif
-#endif
// ABSL_HAVE_STD_OPTIONAL
//
// Checks whether C++17 std::optional is available.
#ifdef ABSL_HAVE_STD_OPTIONAL
#error "ABSL_HAVE_STD_OPTIONAL cannot be directly set."
-#endif
-
-#ifdef __has_include
-#if __has_include(<optional>) && defined(__cplusplus) && \
- __cplusplus >= 201703L && !ABSL_INTERNAL_APPLE_CXX17_TYPES_UNAVAILABLE
+#elif defined(__cpp_lib_optional) && __cpp_lib_optional >= 202106L
#define ABSL_HAVE_STD_OPTIONAL 1
-#endif
+#elif defined(ABSL_INTERNAL_CPLUSPLUS_LANG) && \
+ ABSL_INTERNAL_CPLUSPLUS_LANG >= 201703L && \
+ !ABSL_INTERNAL_APPLE_CXX17_TYPES_UNAVAILABLE
+#define ABSL_HAVE_STD_OPTIONAL 1
#endif
// ABSL_HAVE_STD_VARIANT
@@ -542,13 +587,12 @@
// Checks whether C++17 std::variant is available.
#ifdef ABSL_HAVE_STD_VARIANT
#error "ABSL_HAVE_STD_VARIANT cannot be directly set."
-#endif
-
-#ifdef __has_include
-#if __has_include(<variant>) && defined(__cplusplus) && \
- __cplusplus >= 201703L && !ABSL_INTERNAL_APPLE_CXX17_TYPES_UNAVAILABLE
+#elif defined(__cpp_lib_variant) && __cpp_lib_variant >= 201606L
#define ABSL_HAVE_STD_VARIANT 1
-#endif
+#elif defined(ABSL_INTERNAL_CPLUSPLUS_LANG) && \
+ ABSL_INTERNAL_CPLUSPLUS_LANG >= 201703L && \
+ !ABSL_INTERNAL_APPLE_CXX17_TYPES_UNAVAILABLE
+#define ABSL_HAVE_STD_VARIANT 1
#endif
// ABSL_HAVE_STD_STRING_VIEW
@@ -556,29 +600,27 @@
// Checks whether C++17 std::string_view is available.
#ifdef ABSL_HAVE_STD_STRING_VIEW
#error "ABSL_HAVE_STD_STRING_VIEW cannot be directly set."
-#endif
-
-#ifdef __has_include
-#if __has_include(<string_view>) && defined(__cplusplus) && \
- __cplusplus >= 201703L
+#elif defined(__cpp_lib_string_view) && __cpp_lib_string_view >= 201606L
+#define ABSL_HAVE_STD_STRING_VIEW 1
+#elif defined(ABSL_INTERNAL_CPLUSPLUS_LANG) && \
+ ABSL_INTERNAL_CPLUSPLUS_LANG >= 201703L
#define ABSL_HAVE_STD_STRING_VIEW 1
#endif
-#endif
-// For MSVC, `__has_include` is supported in VS 2017 15.3, which is later than
-// the support for <optional>, <any>, <string_view>, <variant>. So we use
-// _MSC_VER to check whether we have VS 2017 RTM (when <optional>, <any>,
-// <string_view>, <variant> is implemented) or higher. Also, `__cplusplus` is
-// not correctly set by MSVC, so we use `_MSVC_LANG` to check the language
-// version.
-// TODO(zhangxy): fix tests before enabling aliasing for `std::any`.
-#if defined(_MSC_VER) && _MSC_VER >= 1910 && \
- ((defined(_MSVC_LANG) && _MSVC_LANG > 201402) || \
- (defined(__cplusplus) && __cplusplus > 201402))
-// #define ABSL_HAVE_STD_ANY 1
-#define ABSL_HAVE_STD_OPTIONAL 1
-#define ABSL_HAVE_STD_VARIANT 1
-#define ABSL_HAVE_STD_STRING_VIEW 1
+// ABSL_HAVE_STD_ORDERING
+//
+// Checks whether C++20 std::{partial,weak,strong}_ordering are available.
+//
+// __cpp_lib_three_way_comparison is missing on libc++
+// (https://github.com/llvm/llvm-project/issues/73953) so treat it as defined
+// when building in C++20 mode.
+#ifdef ABSL_HAVE_STD_ORDERING
+#error "ABSL_HAVE_STD_ORDERING cannot be directly set."
+#elif (defined(__cpp_lib_three_way_comparison) && \
+ __cpp_lib_three_way_comparison >= 201907L) || \
+ (defined(ABSL_INTERNAL_CPLUSPLUS_LANG) && \
+ ABSL_INTERNAL_CPLUSPLUS_LANG >= 202002L)
+#define ABSL_HAVE_STD_ORDERING 1
#endif
// ABSL_USES_STD_ANY
@@ -643,6 +685,22 @@
#error options.h is misconfigured.
#endif
+// ABSL_USES_STD_ORDERING
+//
+// Indicates whether absl::{partial,weak,strong}_ordering are aliases for the
+// std:: ordering types.
+#if !defined(ABSL_OPTION_USE_STD_ORDERING)
+#error options.h is misconfigured.
+#elif ABSL_OPTION_USE_STD_ORDERING == 0 || \
+ (ABSL_OPTION_USE_STD_ORDERING == 2 && !defined(ABSL_HAVE_STD_ORDERING))
+#undef ABSL_USES_STD_ORDERING
+#elif ABSL_OPTION_USE_STD_ORDERING == 1 || \
+ (ABSL_OPTION_USE_STD_ORDERING == 2 && defined(ABSL_HAVE_STD_ORDERING))
+#define ABSL_USES_STD_ORDERING 1
+#else
+#error options.h is misconfigured.
+#endif
+
// In debug mode, MSVC 2017's std::variant throws a EXCEPTION_ACCESS_VIOLATION
// SEH exception from emplace for variant<SomeStruct> when constructing the
// struct can throw. This defeats some of variant_test and
@@ -673,8 +731,6 @@
#endif
#endif
-#undef ABSL_INTERNAL_HAS_KEYWORD
-
// ABSL_DLL
//
// When building Abseil as a DLL, this macro expands to `__declspec(dllexport)`
@@ -694,14 +750,24 @@
#define ABSL_DLL
#endif // defined(_MSC_VER)
+#if defined(_MSC_VER)
+#if defined(ABSL_BUILD_TEST_DLL)
+#define ABSL_TEST_DLL __declspec(dllexport)
+#elif defined(ABSL_CONSUME_TEST_DLL)
+#define ABSL_TEST_DLL __declspec(dllimport)
+#else
+#define ABSL_TEST_DLL
+#endif
+#else
+#define ABSL_TEST_DLL
+#endif // defined(_MSC_VER)
+
// ABSL_HAVE_MEMORY_SANITIZER
//
// MemorySanitizer (MSan) is a detector of uninitialized reads. It consists of
// a compiler instrumentation module and a run-time library.
#ifdef ABSL_HAVE_MEMORY_SANITIZER
#error "ABSL_HAVE_MEMORY_SANITIZER cannot be directly set."
-#elif defined(__SANITIZE_MEMORY__)
-#define ABSL_HAVE_MEMORY_SANITIZER 1
#elif !defined(__native_client__) && ABSL_HAVE_FEATURE(memory_sanitizer)
#define ABSL_HAVE_MEMORY_SANITIZER 1
#endif
@@ -728,6 +794,59 @@
#define ABSL_HAVE_ADDRESS_SANITIZER 1
#endif
+// ABSL_HAVE_HWADDRESS_SANITIZER
+//
+// Hardware-Assisted AddressSanitizer (or HWASAN) is even faster than asan
+// memory error detector which can use CPU features like ARM TBI, Intel LAM or
+// AMD UAI.
+#ifdef ABSL_HAVE_HWADDRESS_SANITIZER
+#error "ABSL_HAVE_HWADDRESS_SANITIZER cannot be directly set."
+#elif defined(__SANITIZE_HWADDRESS__)
+#define ABSL_HAVE_HWADDRESS_SANITIZER 1
+#elif ABSL_HAVE_FEATURE(hwaddress_sanitizer)
+#define ABSL_HAVE_HWADDRESS_SANITIZER 1
+#endif
+
+// ABSL_HAVE_DATAFLOW_SANITIZER
+//
+// Dataflow Sanitizer (or DFSAN) is a generalised dynamic data flow analysis.
+#ifdef ABSL_HAVE_DATAFLOW_SANITIZER
+#error "ABSL_HAVE_DATAFLOW_SANITIZER cannot be directly set."
+#elif defined(DATAFLOW_SANITIZER)
+// GCC provides no method for detecting the presence of the standalone
+// DataFlowSanitizer (-fsanitize=dataflow), so GCC users of -fsanitize=dataflow
+// should also use -DDATAFLOW_SANITIZER.
+#define ABSL_HAVE_DATAFLOW_SANITIZER 1
+#elif ABSL_HAVE_FEATURE(dataflow_sanitizer)
+#define ABSL_HAVE_DATAFLOW_SANITIZER 1
+#endif
+
+// ABSL_HAVE_LEAK_SANITIZER
+//
+// LeakSanitizer (or lsan) is a detector of memory leaks.
+// https://clang.llvm.org/docs/LeakSanitizer.html
+// https://github.com/google/sanitizers/wiki/AddressSanitizerLeakSanitizer
+//
+// The macro ABSL_HAVE_LEAK_SANITIZER can be used to detect at compile-time
+// whether the LeakSanitizer is potentially available. However, just because the
+// LeakSanitizer is available does not mean it is active. Use the
+// always-available run-time interface in //absl/debugging/leak_check.h for
+// interacting with LeakSanitizer.
+#ifdef ABSL_HAVE_LEAK_SANITIZER
+#error "ABSL_HAVE_LEAK_SANITIZER cannot be directly set."
+#elif defined(LEAK_SANITIZER)
+// GCC provides no method for detecting the presence of the standalone
+// LeakSanitizer (-fsanitize=leak), so GCC users of -fsanitize=leak should also
+// use -DLEAK_SANITIZER.
+#define ABSL_HAVE_LEAK_SANITIZER 1
+// Clang standalone LeakSanitizer (-fsanitize=leak)
+#elif ABSL_HAVE_FEATURE(leak_sanitizer)
+#define ABSL_HAVE_LEAK_SANITIZER 1
+#elif defined(ABSL_HAVE_ADDRESS_SANITIZER)
+// GCC or Clang using the LeakSanitizer integrated into AddressSanitizer.
+#define ABSL_HAVE_LEAK_SANITIZER 1
+#endif
+
// ABSL_HAVE_CLASS_TEMPLATE_ARGUMENT_DEDUCTION
//
// Class template argument deduction is a language feature added in C++17.
@@ -737,4 +856,145 @@
#define ABSL_HAVE_CLASS_TEMPLATE_ARGUMENT_DEDUCTION 1
#endif
+// ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL
+//
+// Prior to C++17, static constexpr variables defined in classes required a
+// separate definition outside of the class body, for example:
+//
+// class Foo {
+// static constexpr int kBar = 0;
+// };
+// constexpr int Foo::kBar;
+//
+// In C++17, these variables defined in classes are considered inline variables,
+// and the extra declaration is redundant. Since some compilers warn on the
+// extra declarations, ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL can be used
+// conditionally ignore them:
+//
+// #ifdef ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL
+// constexpr int Foo::kBar;
+// #endif
+#if defined(ABSL_INTERNAL_CPLUSPLUS_LANG) && \
+ ABSL_INTERNAL_CPLUSPLUS_LANG < 201703L
+#define ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL 1
+#endif
+
+// `ABSL_INTERNAL_HAS_RTTI` determines whether abseil is being compiled with
+// RTTI support.
+#ifdef ABSL_INTERNAL_HAS_RTTI
+#error ABSL_INTERNAL_HAS_RTTI cannot be directly set
+#elif ABSL_HAVE_FEATURE(cxx_rtti)
+#define ABSL_INTERNAL_HAS_RTTI 1
+#elif defined(__GNUC__) && defined(__GXX_RTTI)
+#define ABSL_INTERNAL_HAS_RTTI 1
+#elif defined(_MSC_VER) && defined(_CPPRTTI)
+#define ABSL_INTERNAL_HAS_RTTI 1
+#elif !defined(__GNUC__) && !defined(_MSC_VER)
+// Unknown compiler, default to RTTI
+#define ABSL_INTERNAL_HAS_RTTI 1
+#endif
+
+// `ABSL_INTERNAL_HAS_CXA_DEMANGLE` determines whether `abi::__cxa_demangle` is
+// available.
+#ifdef ABSL_INTERNAL_HAS_CXA_DEMANGLE
+#error ABSL_INTERNAL_HAS_CXA_DEMANGLE cannot be directly set
+#elif defined(OS_ANDROID) && (defined(__i386__) || defined(__x86_64__))
+#define ABSL_INTERNAL_HAS_CXA_DEMANGLE 0
+#elif defined(__GNUC__) && !defined(__mips__)
+#define ABSL_INTERNAL_HAS_CXA_DEMANGLE 1
+#elif defined(__clang__) && !defined(_MSC_VER)
+#define ABSL_INTERNAL_HAS_CXA_DEMANGLE 1
+#endif
+
+// ABSL_INTERNAL_HAVE_SSE is used for compile-time detection of SSE support.
+// See https://gcc.gnu.org/onlinedocs/gcc/x86-Options.html for an overview of
+// which architectures support the various x86 instruction sets.
+#ifdef ABSL_INTERNAL_HAVE_SSE
+#error ABSL_INTERNAL_HAVE_SSE cannot be directly set
+#elif defined(__SSE__)
+#define ABSL_INTERNAL_HAVE_SSE 1
+#elif (defined(_M_X64) || (defined(_M_IX86_FP) && _M_IX86_FP >= 1)) && \
+ !defined(_M_ARM64EC)
+// MSVC only defines _M_IX86_FP for x86 32-bit code, and _M_IX86_FP >= 1
+// indicates that at least SSE was targeted with the /arch:SSE option.
+// All x86-64 processors support SSE, so support can be assumed.
+// https://docs.microsoft.com/en-us/cpp/preprocessor/predefined-macros
+#define ABSL_INTERNAL_HAVE_SSE 1
+#endif
+
+// ABSL_INTERNAL_HAVE_SSE2 is used for compile-time detection of SSE2 support.
+// See https://gcc.gnu.org/onlinedocs/gcc/x86-Options.html for an overview of
+// which architectures support the various x86 instruction sets.
+#ifdef ABSL_INTERNAL_HAVE_SSE2
+#error ABSL_INTERNAL_HAVE_SSE2 cannot be directly set
+#elif defined(__SSE2__)
+#define ABSL_INTERNAL_HAVE_SSE2 1
+#elif (defined(_M_X64) || (defined(_M_IX86_FP) && _M_IX86_FP >= 2)) && \
+ !defined(_M_ARM64EC)
+// MSVC only defines _M_IX86_FP for x86 32-bit code, and _M_IX86_FP >= 2
+// indicates that at least SSE2 was targeted with the /arch:SSE2 option.
+// All x86-64 processors support SSE2, so support can be assumed.
+// https://docs.microsoft.com/en-us/cpp/preprocessor/predefined-macros
+#define ABSL_INTERNAL_HAVE_SSE2 1
+#endif
+
+// ABSL_INTERNAL_HAVE_SSSE3 is used for compile-time detection of SSSE3 support.
+// See https://gcc.gnu.org/onlinedocs/gcc/x86-Options.html for an overview of
+// which architectures support the various x86 instruction sets.
+//
+// MSVC does not have a mode that targets SSSE3 at compile-time. To use SSSE3
+// with MSVC requires either assuming that the code will only every run on CPUs
+// that support SSSE3, otherwise __cpuid() can be used to detect support at
+// runtime and fallback to a non-SSSE3 implementation when SSSE3 is unsupported
+// by the CPU.
+#ifdef ABSL_INTERNAL_HAVE_SSSE3
+#error ABSL_INTERNAL_HAVE_SSSE3 cannot be directly set
+#elif defined(__SSSE3__)
+#define ABSL_INTERNAL_HAVE_SSSE3 1
+#endif
+
+// ABSL_INTERNAL_HAVE_ARM_NEON is used for compile-time detection of NEON (ARM
+// SIMD).
+//
+// If __CUDA_ARCH__ is defined, then we are compiling CUDA code in device mode.
+// In device mode, NEON intrinsics are not available, regardless of host
+// platform.
+// https://llvm.org/docs/CompileCudaWithLLVM.html#detecting-clang-vs-nvcc-from-code
+#ifdef ABSL_INTERNAL_HAVE_ARM_NEON
+#error ABSL_INTERNAL_HAVE_ARM_NEON cannot be directly set
+#elif defined(__ARM_NEON) && !defined(__CUDA_ARCH__)
+#define ABSL_INTERNAL_HAVE_ARM_NEON 1
+#endif
+
+// ABSL_HAVE_CONSTANT_EVALUATED is used for compile-time detection of
+// constant evaluation support through `absl::is_constant_evaluated`.
+#ifdef ABSL_HAVE_CONSTANT_EVALUATED
+#error ABSL_HAVE_CONSTANT_EVALUATED cannot be directly set
+#endif
+#ifdef __cpp_lib_is_constant_evaluated
+#define ABSL_HAVE_CONSTANT_EVALUATED 1
+#elif ABSL_HAVE_BUILTIN(__builtin_is_constant_evaluated)
+#define ABSL_HAVE_CONSTANT_EVALUATED 1
+#endif
+
+// ABSL_INTERNAL_EMSCRIPTEN_VERSION combines Emscripten's three version macros
+// into an integer that can be compared against.
+#ifdef ABSL_INTERNAL_EMSCRIPTEN_VERSION
+#error ABSL_INTERNAL_EMSCRIPTEN_VERSION cannot be directly set
+#endif
+#ifdef __EMSCRIPTEN__
+#include <emscripten/version.h>
+#ifdef __EMSCRIPTEN_major__
+#if __EMSCRIPTEN_minor__ >= 1000
+#error __EMSCRIPTEN_minor__ is too big to fit in ABSL_INTERNAL_EMSCRIPTEN_VERSION
+#endif
+#if __EMSCRIPTEN_tiny__ >= 1000
+#error __EMSCRIPTEN_tiny__ is too big to fit in ABSL_INTERNAL_EMSCRIPTEN_VERSION
+#endif
+#define ABSL_INTERNAL_EMSCRIPTEN_VERSION \
+ ((__EMSCRIPTEN_major__) * 1000000 + (__EMSCRIPTEN_minor__) * 1000 + \
+ (__EMSCRIPTEN_tiny__))
+#endif
+#endif
+
#endif // ABSL_BASE_CONFIG_H_
diff --git a/absl/base/dynamic_annotations.h b/absl/base/dynamic_annotations.h
index 03d7096..7ba8912 100644
--- a/absl/base/dynamic_annotations.h
+++ b/absl/base/dynamic_annotations.h
@@ -46,6 +46,7 @@
#define ABSL_BASE_DYNAMIC_ANNOTATIONS_H_
#include <stddef.h>
+#include <stdint.h>
#include "absl/base/attributes.h"
#include "absl/base/config.h"
@@ -53,6 +54,10 @@
#include "absl/base/macros.h"
#endif
+#ifdef ABSL_HAVE_HWADDRESS_SANITIZER
+#include <sanitizer/hwasan_interface.h>
+#endif
+
// TODO(rogeeff): Remove after the backward compatibility period.
#include "absl/base/internal/dynamic_annotations.h" // IWYU pragma: export
@@ -111,7 +116,7 @@
#if ABSL_INTERNAL_RACE_ANNOTATIONS_ENABLED == 1
// Some of the symbols used in this section (e.g. AnnotateBenignRaceSized) are
-// defined by the compiler-based santizer implementation, not by the Abseil
+// defined by the compiler-based sanitizer implementation, not by the Abseil
// library. Therefore they do not use ABSL_INTERNAL_C_SYMBOL.
// -------------------------------------------------------------
@@ -433,31 +438,6 @@
#endif
-#ifdef __cplusplus
-#ifdef ABSL_HAVE_THREAD_SANITIZER
-ABSL_INTERNAL_BEGIN_EXTERN_C
-int RunningOnValgrind();
-double ValgrindSlowdown();
-ABSL_INTERNAL_END_EXTERN_C
-#else
-namespace absl {
-ABSL_NAMESPACE_BEGIN
-namespace base_internal {
-ABSL_DEPRECATED(
- "Don't use this interface. It is misleading and is being deleted.")
-ABSL_ATTRIBUTE_ALWAYS_INLINE inline int RunningOnValgrind() { return 0; }
-ABSL_DEPRECATED(
- "Don't use this interface. It is misleading and is being deleted.")
-ABSL_ATTRIBUTE_ALWAYS_INLINE inline double ValgrindSlowdown() { return 1.0; }
-} // namespace base_internal
-ABSL_NAMESPACE_END
-} // namespace absl
-
-using absl::base_internal::RunningOnValgrind;
-using absl::base_internal::ValgrindSlowdown;
-#endif
-#endif
-
// -------------------------------------------------------------------------
// Address sanitizer annotations
@@ -482,6 +462,26 @@
#endif // ABSL_HAVE_ADDRESS_SANITIZER
// -------------------------------------------------------------------------
+// HWAddress sanitizer annotations
+
+#ifdef __cplusplus
+namespace absl {
+#ifdef ABSL_HAVE_HWADDRESS_SANITIZER
+// Under HWASAN changes the tag of the pointer.
+template <typename T>
+T* HwasanTagPointer(T* ptr, uintptr_t tag) {
+ return reinterpret_cast<T*>(__hwasan_tag_pointer(ptr, tag));
+}
+#else
+template <typename T>
+T* HwasanTagPointer(T* ptr, uintptr_t) {
+ return ptr;
+}
+#endif
+} // namespace absl
+#endif
+
+// -------------------------------------------------------------------------
// Undefine the macros intended only for this file.
#undef ABSL_INTERNAL_RACE_ANNOTATIONS_ENABLED
diff --git a/absl/base/exception_safety_testing_test.cc b/absl/base/exception_safety_testing_test.cc
index a59be29..bf5aa7c 100644
--- a/absl/base/exception_safety_testing_test.cc
+++ b/absl/base/exception_safety_testing_test.cc
@@ -148,7 +148,7 @@
ThrowingValue<> bomb1, bomb2;
TestOp([&bomb1]() { ~bomb1; });
- TestOp([&]() { bomb1& bomb2; });
+ TestOp([&]() { bomb1 & bomb2; });
TestOp([&]() { bomb1 | bomb2; });
TestOp([&]() { bomb1 ^ bomb2; });
}
@@ -332,13 +332,16 @@
constexpr int kArrayLen = 2;
// We intentionally create extra space to store the tag allocated by placement
// new[].
- constexpr int kStorageLen = 4;
+ constexpr size_t kExtraSpaceLen = sizeof(size_t) * 2;
alignas(ThrowingValue<>) unsigned char buf[sizeof(ThrowingValue<>)];
alignas(ThrowingValue<>) unsigned char
- array_buf[sizeof(ThrowingValue<>[kStorageLen])];
+ array_buf[kExtraSpaceLen + sizeof(ThrowingValue<>[kArrayLen])];
auto* placed = new (&buf) ThrowingValue<>(1);
auto placed_array = new (&array_buf) ThrowingValue<>[kArrayLen];
+ auto* placed_array_end = reinterpret_cast<unsigned char*>(placed_array) +
+ sizeof(ThrowingValue<>[kArrayLen]);
+ EXPECT_LE(placed_array_end, array_buf + sizeof(array_buf));
SetCountdown();
ExpectNoThrow([placed, &buf]() {
@@ -701,7 +704,10 @@
static constexpr int kExceptionSentinel = 9999;
};
+
+#ifdef ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL
constexpr int BasicGuaranteeWithExtraContracts::kExceptionSentinel;
+#endif
TEST(ExceptionCheckTest, BasicGuaranteeWithExtraContracts) {
auto tester_with_val =
diff --git a/absl/base/internal/atomic_hook_test_helper.h b/absl/base/internal/atomic_hook_test_helper.h
index 3e72b49..c72015e 100644
--- a/absl/base/internal/atomic_hook_test_helper.h
+++ b/absl/base/internal/atomic_hook_test_helper.h
@@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#ifndef ABSL_BASE_ATOMIC_HOOK_TEST_HELPER_H_
-#define ABSL_BASE_ATOMIC_HOOK_TEST_HELPER_H_
+#ifndef ABSL_BASE_INTERNAL_ATOMIC_HOOK_TEST_HELPER_H_
+#define ABSL_BASE_INTERNAL_ATOMIC_HOOK_TEST_HELPER_H_
#include "absl/base/internal/atomic_hook.h"
@@ -31,4 +31,4 @@
ABSL_NAMESPACE_END
} // namespace absl
-#endif // ABSL_BASE_ATOMIC_HOOK_TEST_HELPER_H_
+#endif // ABSL_BASE_INTERNAL_ATOMIC_HOOK_TEST_HELPER_H_
diff --git a/absl/base/internal/cycleclock.cc b/absl/base/internal/cycleclock.cc
index 0e65005..902e3f5 100644
--- a/absl/base/internal/cycleclock.cc
+++ b/absl/base/internal/cycleclock.cc
@@ -25,6 +25,8 @@
#include <atomic>
#include <chrono> // NOLINT(build/c++11)
+#include "absl/base/attributes.h"
+#include "absl/base/config.h"
#include "absl/base/internal/unscaledcycleclock.h"
namespace absl {
@@ -33,44 +35,20 @@
#if ABSL_USE_UNSCALED_CYCLECLOCK
-namespace {
-
-#ifdef NDEBUG
-#ifdef ABSL_INTERNAL_UNSCALED_CYCLECLOCK_FREQUENCY_IS_CPU_FREQUENCY
-// Not debug mode and the UnscaledCycleClock frequency is the CPU
-// frequency. Scale the CycleClock to prevent overflow if someone
-// tries to represent the time as cycles since the Unix epoch.
-static constexpr int32_t kShift = 1;
-#else
-// Not debug mode and the UnscaledCycleClock isn't operating at the
-// raw CPU frequency. There is no need to do any scaling, so don't
-// needlessly sacrifice precision.
-static constexpr int32_t kShift = 0;
-#endif
-#else
-// In debug mode use a different shift to discourage depending on a
-// particular shift value.
-static constexpr int32_t kShift = 2;
+#ifdef ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL
+constexpr int32_t CycleClock::kShift;
+constexpr double CycleClock::kFrequencyScale;
#endif
-static constexpr double kFrequencyScale = 1.0 / (1 << kShift);
-static std::atomic<CycleClockSourceFunc> cycle_clock_source;
+ABSL_CONST_INIT std::atomic<CycleClockSourceFunc>
+ CycleClock::cycle_clock_source_{nullptr};
-CycleClockSourceFunc LoadCycleClockSource() {
- // Optimize for the common case (no callback) by first doing a relaxed load;
- // this is significantly faster on non-x86 platforms.
- if (cycle_clock_source.load(std::memory_order_relaxed) == nullptr) {
- return nullptr;
- }
- // This corresponds to the store(std::memory_order_release) in
- // CycleClockSource::Register, and makes sure that any updates made prior to
- // registering the callback are visible to this thread before the callback is
- // invoked.
- return cycle_clock_source.load(std::memory_order_acquire);
+void CycleClockSource::Register(CycleClockSourceFunc source) {
+ // Corresponds to the load(std::memory_order_acquire) in LoadCycleClockSource.
+ CycleClock::cycle_clock_source_.store(source, std::memory_order_release);
}
-} // namespace
-
+#ifdef _WIN32
int64_t CycleClock::Now() {
auto fn = LoadCycleClockSource();
if (fn == nullptr) {
@@ -78,15 +56,7 @@
}
return fn() >> kShift;
}
-
-double CycleClock::Frequency() {
- return kFrequencyScale * base_internal::UnscaledCycleClock::Frequency();
-}
-
-void CycleClockSource::Register(CycleClockSourceFunc source) {
- // Corresponds to the load(std::memory_order_acquire) in LoadCycleClockSource.
- cycle_clock_source.store(source, std::memory_order_release);
-}
+#endif
#else
diff --git a/absl/base/internal/cycleclock.h b/absl/base/internal/cycleclock.h
index a18b584..cbfdf57 100644
--- a/absl/base/internal/cycleclock.h
+++ b/absl/base/internal/cycleclock.h
@@ -42,14 +42,20 @@
#ifndef ABSL_BASE_INTERNAL_CYCLECLOCK_H_
#define ABSL_BASE_INTERNAL_CYCLECLOCK_H_
+#include <atomic>
#include <cstdint>
+#include "absl/base/attributes.h"
#include "absl/base/config.h"
+#include "absl/base/internal/cycleclock_config.h"
+#include "absl/base/internal/unscaledcycleclock.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace base_internal {
+using CycleClockSourceFunc = int64_t (*)();
+
// -----------------------------------------------------------------------------
// CycleClock
// -----------------------------------------------------------------------------
@@ -68,12 +74,21 @@
static double Frequency();
private:
+#if ABSL_USE_UNSCALED_CYCLECLOCK
+ static CycleClockSourceFunc LoadCycleClockSource();
+
+ static constexpr int32_t kShift = kCycleClockShift;
+ static constexpr double kFrequencyScale = kCycleClockFrequencyScale;
+
+ ABSL_CONST_INIT static std::atomic<CycleClockSourceFunc> cycle_clock_source_;
+#endif // ABSL_USE_UNSCALED_CYCLECLOC
+
CycleClock() = delete; // no instances
CycleClock(const CycleClock&) = delete;
CycleClock& operator=(const CycleClock&) = delete;
-};
-using CycleClockSourceFunc = int64_t (*)();
+ friend class CycleClockSource;
+};
class CycleClockSource {
private:
@@ -87,6 +102,41 @@
static void Register(CycleClockSourceFunc source);
};
+#if ABSL_USE_UNSCALED_CYCLECLOCK
+
+inline CycleClockSourceFunc CycleClock::LoadCycleClockSource() {
+#if !defined(__x86_64__)
+ // Optimize for the common case (no callback) by first doing a relaxed load;
+ // this is significantly faster on non-x86 platforms.
+ if (cycle_clock_source_.load(std::memory_order_relaxed) == nullptr) {
+ return nullptr;
+ }
+#endif // !defined(__x86_64__)
+
+ // This corresponds to the store(std::memory_order_release) in
+ // CycleClockSource::Register, and makes sure that any updates made prior to
+ // registering the callback are visible to this thread before the callback
+ // is invoked.
+ return cycle_clock_source_.load(std::memory_order_acquire);
+}
+
+// Accessing globals in inlined code in Window DLLs is problematic.
+#ifndef _WIN32
+inline int64_t CycleClock::Now() {
+ auto fn = LoadCycleClockSource();
+ if (fn == nullptr) {
+ return base_internal::UnscaledCycleClock::Now() >> kShift;
+ }
+ return fn() >> kShift;
+}
+#endif
+
+inline double CycleClock::Frequency() {
+ return kFrequencyScale * base_internal::UnscaledCycleClock::Frequency();
+}
+
+#endif // ABSL_USE_UNSCALED_CYCLECLOCK
+
} // namespace base_internal
ABSL_NAMESPACE_END
} // namespace absl
diff --git a/absl/base/internal/cycleclock_config.h b/absl/base/internal/cycleclock_config.h
new file mode 100644
index 0000000..191112b
--- /dev/null
+++ b/absl/base/internal/cycleclock_config.h
@@ -0,0 +1,55 @@
+// Copyright 2022 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef ABSL_BASE_INTERNAL_CYCLECLOCK_CONFIG_H_
+#define ABSL_BASE_INTERNAL_CYCLECLOCK_CONFIG_H_
+
+#include <cstdint>
+
+#include "absl/base/config.h"
+#include "absl/base/internal/inline_variable.h"
+#include "absl/base/internal/unscaledcycleclock_config.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace base_internal {
+
+#if ABSL_USE_UNSCALED_CYCLECLOCK
+#ifdef NDEBUG
+#ifdef ABSL_INTERNAL_UNSCALED_CYCLECLOCK_FREQUENCY_IS_CPU_FREQUENCY
+// Not debug mode and the UnscaledCycleClock frequency is the CPU
+// frequency. Scale the CycleClock to prevent overflow if someone
+// tries to represent the time as cycles since the Unix epoch.
+ABSL_INTERNAL_INLINE_CONSTEXPR(int32_t, kCycleClockShift, 1);
+#else
+// Not debug mode and the UnscaledCycleClock isn't operating at the
+// raw CPU frequency. There is no need to do any scaling, so don't
+// needlessly sacrifice precision.
+ABSL_INTERNAL_INLINE_CONSTEXPR(int32_t, kCycleClockShift, 0);
+#endif
+#else // NDEBUG
+// In debug mode use a different shift to discourage depending on a
+// particular shift value.
+ABSL_INTERNAL_INLINE_CONSTEXPR(int32_t, kCycleClockShift, 2);
+#endif // NDEBUG
+
+ABSL_INTERNAL_INLINE_CONSTEXPR(double, kCycleClockFrequencyScale,
+ 1.0 / (1 << kCycleClockShift));
+#endif // ABSL_USE_UNSCALED_CYCLECLOC
+
+} // namespace base_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_BASE_INTERNAL_CYCLECLOCK_CONFIG_H_
diff --git a/absl/base/internal/direct_mmap.h b/absl/base/internal/direct_mmap.h
index 274054c..1beb2ee 100644
--- a/absl/base/internal/direct_mmap.h
+++ b/absl/base/internal/direct_mmap.h
@@ -20,7 +20,7 @@
#include "absl/base/config.h"
-#if ABSL_HAVE_MMAP
+#ifdef ABSL_HAVE_MMAP
#include <sys/mman.h>
@@ -41,13 +41,13 @@
#ifdef __mips__
// Include definitions of the ABI currently in use.
-#ifdef __BIONIC__
+#if defined(__BIONIC__) || !defined(__GLIBC__)
// Android doesn't have sgidefs.h, but does have asm/sgidefs.h, which has the
// definitions we need.
#include <asm/sgidefs.h>
#else
#include <sgidefs.h>
-#endif // __BIONIC__
+#endif // __BIONIC__ || !__GLIBC__
#endif // __mips__
// SYS_mmap and SYS_munmap are not defined in Android.
@@ -72,7 +72,7 @@
// Platform specific logic extracted from
// https://chromium.googlesource.com/linux-syscall-support/+/master/linux_syscall_support.h
inline void* DirectMmap(void* start, size_t length, int prot, int flags, int fd,
- off64_t offset) noexcept {
+ off_t offset) noexcept {
#if defined(__i386__) || defined(__ARM_ARCH_3__) || defined(__ARM_EABI__) || \
defined(__m68k__) || defined(__sh__) || \
(defined(__hppa__) && !defined(__LP64__)) || \
@@ -97,11 +97,12 @@
#ifdef __BIONIC__
// SYS_mmap2 has problems on Android API level <= 16.
// Workaround by invoking __mmap2() instead.
- return __mmap2(start, length, prot, flags, fd, offset / pagesize);
+ return __mmap2(start, length, prot, flags, fd,
+ static_cast<size_t>(offset / pagesize));
#else
return reinterpret_cast<void*>(
syscall(SYS_mmap2, start, length, prot, flags, fd,
- static_cast<off_t>(offset / pagesize)));
+ static_cast<unsigned long>(offset / pagesize))); // NOLINT
#endif
#elif defined(__s390x__)
// On s390x, mmap() arguments are passed in memory.
diff --git a/absl/base/internal/endian.h b/absl/base/internal/endian.h
index dad0e9a..943f3d9 100644
--- a/absl/base/internal/endian.h
+++ b/absl/base/internal/endian.h
@@ -16,65 +16,23 @@
#ifndef ABSL_BASE_INTERNAL_ENDIAN_H_
#define ABSL_BASE_INTERNAL_ENDIAN_H_
-// The following guarantees declaration of the byte swap functions
-#ifdef _MSC_VER
-#include <stdlib.h> // NOLINT(build/include)
-#elif defined(__FreeBSD__)
-#include <sys/endian.h>
-#elif defined(__GLIBC__)
-#include <byteswap.h> // IWYU pragma: export
-#endif
-
#include <cstdint>
+#include <cstdlib>
+
#include "absl/base/casts.h"
#include "absl/base/config.h"
#include "absl/base/internal/unaligned_access.h"
+#include "absl/base/nullability.h"
#include "absl/base/port.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
-// Use compiler byte-swapping intrinsics if they are available. 32-bit
-// and 64-bit versions are available in Clang and GCC as of GCC 4.3.0.
-// The 16-bit version is available in Clang and GCC only as of GCC 4.8.0.
-// For simplicity, we enable them all only for GCC 4.8.0 or later.
-#if defined(__clang__) || \
- (defined(__GNUC__) && \
- ((__GNUC__ == 4 && __GNUC_MINOR__ >= 8) || __GNUC__ >= 5))
inline uint64_t gbswap_64(uint64_t host_int) {
+#if ABSL_HAVE_BUILTIN(__builtin_bswap64) || defined(__GNUC__)
return __builtin_bswap64(host_int);
-}
-inline uint32_t gbswap_32(uint32_t host_int) {
- return __builtin_bswap32(host_int);
-}
-inline uint16_t gbswap_16(uint16_t host_int) {
- return __builtin_bswap16(host_int);
-}
-
#elif defined(_MSC_VER)
-inline uint64_t gbswap_64(uint64_t host_int) {
return _byteswap_uint64(host_int);
-}
-inline uint32_t gbswap_32(uint32_t host_int) {
- return _byteswap_ulong(host_int);
-}
-inline uint16_t gbswap_16(uint16_t host_int) {
- return _byteswap_ushort(host_int);
-}
-
-#else
-inline uint64_t gbswap_64(uint64_t host_int) {
-#if defined(__GNUC__) && defined(__x86_64__) && !defined(__APPLE__)
- // Adapted from /usr/include/byteswap.h. Not available on Mac.
- if (__builtin_constant_p(host_int)) {
- return __bswap_constant_64(host_int);
- } else {
- uint64_t result;
- __asm__("bswap %0" : "=r"(result) : "0"(host_int));
- return result;
- }
-#elif defined(__GLIBC__)
- return bswap_64(host_int);
#else
return (((host_int & uint64_t{0xFF}) << 56) |
((host_int & uint64_t{0xFF00}) << 40) |
@@ -84,12 +42,14 @@
((host_int & uint64_t{0xFF0000000000}) >> 24) |
((host_int & uint64_t{0xFF000000000000}) >> 40) |
((host_int & uint64_t{0xFF00000000000000}) >> 56));
-#endif // bswap_64
+#endif
}
inline uint32_t gbswap_32(uint32_t host_int) {
-#if defined(__GLIBC__)
- return bswap_32(host_int);
+#if ABSL_HAVE_BUILTIN(__builtin_bswap32) || defined(__GNUC__)
+ return __builtin_bswap32(host_int);
+#elif defined(_MSC_VER)
+ return _byteswap_ulong(host_int);
#else
return (((host_int & uint32_t{0xFF}) << 24) |
((host_int & uint32_t{0xFF00}) << 8) |
@@ -99,33 +59,29 @@
}
inline uint16_t gbswap_16(uint16_t host_int) {
-#if defined(__GLIBC__)
- return bswap_16(host_int);
+#if ABSL_HAVE_BUILTIN(__builtin_bswap16) || defined(__GNUC__)
+ return __builtin_bswap16(host_int);
+#elif defined(_MSC_VER)
+ return _byteswap_ushort(host_int);
#else
return (((host_int & uint16_t{0xFF}) << 8) |
((host_int & uint16_t{0xFF00}) >> 8));
#endif
}
-#endif // intrinsics available
-
#ifdef ABSL_IS_LITTLE_ENDIAN
-// Definitions for ntohl etc. that don't require us to include
-// netinet/in.h. We wrap gbswap_32 and gbswap_16 in functions rather
-// than just #defining them because in debug mode, gcc doesn't
-// correctly handle the (rather involved) definitions of bswap_32.
-// gcc guarantees that inline functions are as fast as macros, so
-// this isn't a performance hit.
+// Portable definitions for htonl (host-to-network) and friends on little-endian
+// architectures.
inline uint16_t ghtons(uint16_t x) { return gbswap_16(x); }
inline uint32_t ghtonl(uint32_t x) { return gbswap_32(x); }
inline uint64_t ghtonll(uint64_t x) { return gbswap_64(x); }
#elif defined ABSL_IS_BIG_ENDIAN
-// These definitions are simpler on big-endian machines
-// These are functions instead of macros to avoid self-assignment warnings
-// on calls such as "i = ghtnol(i);". This also provides type checking.
+// Portable definitions for htonl (host-to-network) etc on big-endian
+// architectures. These definitions are simpler since the host byte order is the
+// same as network byte order.
inline uint16_t ghtons(uint16_t x) { return x; }
inline uint32_t ghtonl(uint32_t x) { return x; }
inline uint64_t ghtonll(uint64_t x) { return x; }
@@ -205,27 +161,27 @@
}
// Functions to do unaligned loads and stores in little-endian order.
-inline uint16_t Load16(const void *p) {
+inline uint16_t Load16(absl::Nonnull<const void *> p) {
return ToHost16(ABSL_INTERNAL_UNALIGNED_LOAD16(p));
}
-inline void Store16(void *p, uint16_t v) {
+inline void Store16(absl::Nonnull<void *> p, uint16_t v) {
ABSL_INTERNAL_UNALIGNED_STORE16(p, FromHost16(v));
}
-inline uint32_t Load32(const void *p) {
+inline uint32_t Load32(absl::Nonnull<const void *> p) {
return ToHost32(ABSL_INTERNAL_UNALIGNED_LOAD32(p));
}
-inline void Store32(void *p, uint32_t v) {
+inline void Store32(absl::Nonnull<void *> p, uint32_t v) {
ABSL_INTERNAL_UNALIGNED_STORE32(p, FromHost32(v));
}
-inline uint64_t Load64(const void *p) {
+inline uint64_t Load64(absl::Nonnull<const void *> p) {
return ToHost64(ABSL_INTERNAL_UNALIGNED_LOAD64(p));
}
-inline void Store64(void *p, uint64_t v) {
+inline void Store64(absl::Nonnull<void *> p, uint64_t v) {
ABSL_INTERNAL_UNALIGNED_STORE64(p, FromHost64(v));
}
@@ -295,27 +251,27 @@
}
// Functions to do unaligned loads and stores in big-endian order.
-inline uint16_t Load16(const void *p) {
+inline uint16_t Load16(absl::Nonnull<const void *> p) {
return ToHost16(ABSL_INTERNAL_UNALIGNED_LOAD16(p));
}
-inline void Store16(void *p, uint16_t v) {
+inline void Store16(absl::Nonnull<void *> p, uint16_t v) {
ABSL_INTERNAL_UNALIGNED_STORE16(p, FromHost16(v));
}
-inline uint32_t Load32(const void *p) {
+inline uint32_t Load32(absl::Nonnull<const void *> p) {
return ToHost32(ABSL_INTERNAL_UNALIGNED_LOAD32(p));
}
-inline void Store32(void *p, uint32_t v) {
+inline void Store32(absl::Nonnull<void *>p, uint32_t v) {
ABSL_INTERNAL_UNALIGNED_STORE32(p, FromHost32(v));
}
-inline uint64_t Load64(const void *p) {
+inline uint64_t Load64(absl::Nonnull<const void *> p) {
return ToHost64(ABSL_INTERNAL_UNALIGNED_LOAD64(p));
}
-inline void Store64(void *p, uint64_t v) {
+inline void Store64(absl::Nonnull<void *> p, uint64_t v) {
ABSL_INTERNAL_UNALIGNED_STORE64(p, FromHost64(v));
}
diff --git a/absl/base/internal/exception_safety_testing.h b/absl/base/internal/exception_safety_testing.h
index 77a5aec..c106154 100644
--- a/absl/base/internal/exception_safety_testing.h
+++ b/absl/base/internal/exception_safety_testing.h
@@ -946,7 +946,7 @@
* `std::unique_ptr<T> operator()() const` where T is the type being tested.
* It is used for reliably creating identical T instances to test on.
*
- * - Operation: The operation object (passsed in via tester.WithOperation(...)
+ * - Operation: The operation object (passed in via tester.WithOperation(...)
* or tester.Test(...)) must be invocable with the signature
* `void operator()(T*) const` where T is the type being tested. It is used
* for performing steps on a T instance that may throw and that need to be
diff --git a/absl/base/internal/fast_type_id.h b/absl/base/internal/fast_type_id.h
index 3db59e8..a547b3a 100644
--- a/absl/base/internal/fast_type_id.h
+++ b/absl/base/internal/fast_type_id.h
@@ -28,8 +28,10 @@
constexpr static char dummy_var = 0;
};
+#ifdef ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL
template <typename Type>
constexpr char FastTypeTag<Type>::dummy_var;
+#endif
// FastTypeId<Type>() evaluates at compile/link-time to a unique pointer for the
// passed-in type. These are meant to be good match for keys into maps or
diff --git a/absl/base/internal/identity.h b/absl/base/internal/identity.h
index a3154ed..365207b 100644
--- a/absl/base/internal/identity.h
+++ b/absl/base/internal/identity.h
@@ -22,13 +22,15 @@
ABSL_NAMESPACE_BEGIN
namespace internal {
+// This is a back-fill of C++20's `std::type_identity`.
template <typename T>
-struct identity {
+struct type_identity {
typedef T type;
};
+// This is a back-fill of C++20's `std::type_identity_t`.
template <typename T>
-using identity_t = typename identity<T>::type;
+using type_identity_t = typename type_identity<T>::type;
} // namespace internal
ABSL_NAMESPACE_END
diff --git a/absl/base/internal/inline_variable.h b/absl/base/internal/inline_variable.h
index 130d8c2..09daf0f 100644
--- a/absl/base/internal/inline_variable.h
+++ b/absl/base/internal/inline_variable.h
@@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#ifndef ABSL_BASE_INTERNAL_INLINE_VARIABLE_EMULATION_H_
-#define ABSL_BASE_INTERNAL_INLINE_VARIABLE_EMULATION_H_
+#ifndef ABSL_BASE_INTERNAL_INLINE_VARIABLE_H_
+#define ABSL_BASE_INTERNAL_INLINE_VARIABLE_H_
#include <type_traits>
@@ -63,12 +63,12 @@
// Bug: https://bugs.llvm.org/show_bug.cgi?id=35862
//
// Note:
-// identity_t is used here so that the const and name are in the
+// type_identity_t is used here so that the const and name are in the
// appropriate place for pointer types, reference types, function pointer
// types, etc..
#if defined(__clang__)
#define ABSL_INTERNAL_EXTERN_DECL(type, name) \
- extern const ::absl::internal::identity_t<type> name;
+ extern const ::absl::internal::type_identity_t<type> name;
#else // Otherwise, just define the macro to do nothing.
#define ABSL_INTERNAL_EXTERN_DECL(type, name)
#endif // defined(__clang__)
@@ -76,32 +76,33 @@
// See above comment at top of file for details.
#define ABSL_INTERNAL_INLINE_CONSTEXPR(type, name, init) \
ABSL_INTERNAL_EXTERN_DECL(type, name) \
- inline constexpr ::absl::internal::identity_t<type> name = init
+ inline constexpr ::absl::internal::type_identity_t<type> name = init
#else
// See above comment at top of file for details.
//
// Note:
-// identity_t is used here so that the const and name are in the
+// type_identity_t is used here so that the const and name are in the
// appropriate place for pointer types, reference types, function pointer
// types, etc..
-#define ABSL_INTERNAL_INLINE_CONSTEXPR(var_type, name, init) \
- template <class /*AbslInternalDummy*/ = void> \
- struct AbslInternalInlineVariableHolder##name { \
- static constexpr ::absl::internal::identity_t<var_type> kInstance = init; \
- }; \
- \
- template <class AbslInternalDummy> \
- constexpr ::absl::internal::identity_t<var_type> \
- AbslInternalInlineVariableHolder##name<AbslInternalDummy>::kInstance; \
- \
- static constexpr const ::absl::internal::identity_t<var_type>& \
- name = /* NOLINT */ \
- AbslInternalInlineVariableHolder##name<>::kInstance; \
- static_assert(sizeof(void (*)(decltype(name))) != 0, \
+#define ABSL_INTERNAL_INLINE_CONSTEXPR(var_type, name, init) \
+ template <class /*AbslInternalDummy*/ = void> \
+ struct AbslInternalInlineVariableHolder##name { \
+ static constexpr ::absl::internal::type_identity_t<var_type> kInstance = \
+ init; \
+ }; \
+ \
+ template <class AbslInternalDummy> \
+ constexpr ::absl::internal::type_identity_t<var_type> \
+ AbslInternalInlineVariableHolder##name<AbslInternalDummy>::kInstance; \
+ \
+ static constexpr const ::absl::internal::type_identity_t<var_type>& \
+ name = /* NOLINT */ \
+ AbslInternalInlineVariableHolder##name<>::kInstance; \
+ static_assert(sizeof(void (*)(decltype(name))) != 0, \
"Silence unused variable warnings.")
#endif // __cpp_inline_variables
-#endif // ABSL_BASE_INTERNAL_INLINE_VARIABLE_EMULATION_H_
+#endif // ABSL_BASE_INTERNAL_INLINE_VARIABLE_H_
diff --git a/absl/base/internal/inline_variable_testing.h b/absl/base/internal/inline_variable_testing.h
index 3856b9f..f3c8145 100644
--- a/absl/base/internal/inline_variable_testing.h
+++ b/absl/base/internal/inline_variable_testing.h
@@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#ifndef ABSL_BASE_INLINE_VARIABLE_TESTING_H_
-#define ABSL_BASE_INLINE_VARIABLE_TESTING_H_
+#ifndef ABSL_BASE_INTERNAL_INLINE_VARIABLE_TESTING_H_
+#define ABSL_BASE_INTERNAL_INLINE_VARIABLE_TESTING_H_
#include "absl/base/internal/inline_variable.h"
@@ -43,4 +43,4 @@
ABSL_NAMESPACE_END
} // namespace absl
-#endif // ABSL_BASE_INLINE_VARIABLE_TESTING_H_
+#endif // ABSL_BASE_INTERNAL_INLINE_VARIABLE_TESTING_H_
diff --git a/absl/base/internal/invoke.h b/absl/base/internal/invoke.h
index 5c71f32..643c2a4 100644
--- a/absl/base/internal/invoke.h
+++ b/absl/base/internal/invoke.h
@@ -14,6 +14,8 @@
//
// absl::base_internal::invoke(f, args...) is an implementation of
// INVOKE(f, args...) from section [func.require] of the C++ standard.
+// When compiled as C++17 and later versions, it is implemented as an alias of
+// std::invoke.
//
// [func.require]
// Define INVOKE (f, t1, t2, ..., tN) as follows:
@@ -35,6 +37,26 @@
#ifndef ABSL_BASE_INTERNAL_INVOKE_H_
#define ABSL_BASE_INTERNAL_INVOKE_H_
+#include "absl/base/config.h"
+
+#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 201703L
+
+#include <functional>
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace base_internal {
+
+using std::invoke;
+using std::invoke_result_t;
+using std::is_invocable_r;
+
+} // namespace base_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#else // ABSL_INTERNAL_CPLUSPLUS_LANG >= 201703L
+
#include <algorithm>
#include <type_traits>
#include <utility>
@@ -80,8 +102,18 @@
static decltype((std::declval<Obj>().*
std::declval<MemFun>())(std::declval<Args>()...))
Invoke(MemFun&& mem_fun, Obj&& obj, Args&&... args) {
+// Ignore bogus GCC warnings on this line.
+// See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=101436 for similar example.
+#if ABSL_INTERNAL_HAVE_MIN_GNUC_VERSION(11, 0)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Warray-bounds"
+#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
+#endif
return (std::forward<Obj>(obj).*
std::forward<MemFun>(mem_fun))(std::forward<Args>(args)...);
+#if ABSL_INTERNAL_HAVE_MIN_GNUC_VERSION(11, 0)
+#pragma GCC diagnostic pop
+#endif
}
};
@@ -180,8 +212,30 @@
return Invoker<F, Args...>::type::Invoke(std::forward<F>(f),
std::forward<Args>(args)...);
}
+
+template <typename AlwaysVoid, typename, typename, typename...>
+struct IsInvocableRImpl : std::false_type {};
+
+template <typename R, typename F, typename... Args>
+struct IsInvocableRImpl<
+ absl::void_t<absl::base_internal::invoke_result_t<F, Args...> >, R, F,
+ Args...>
+ : std::integral_constant<
+ bool,
+ std::is_convertible<absl::base_internal::invoke_result_t<F, Args...>,
+ R>::value ||
+ std::is_void<R>::value> {};
+
+// Type trait whose member `value` is true if invoking `F` with `Args` is valid,
+// and either the return type is convertible to `R`, or `R` is void.
+// C++11-compatible version of `std::is_invocable_r`.
+template <typename R, typename F, typename... Args>
+using is_invocable_r = IsInvocableRImpl<void, R, F, Args...>;
+
} // namespace base_internal
ABSL_NAMESPACE_END
} // namespace absl
+#endif // ABSL_INTERNAL_CPLUSPLUS_LANG >= 201703L
+
#endif // ABSL_BASE_INTERNAL_INVOKE_H_
diff --git a/absl/base/internal/low_level_alloc.cc b/absl/base/internal/low_level_alloc.cc
index 229ab91..a563f7b 100644
--- a/absl/base/internal/low_level_alloc.cc
+++ b/absl/base/internal/low_level_alloc.cc
@@ -42,25 +42,25 @@
#include <windows.h>
#endif
+#ifdef __linux__
+#include <sys/prctl.h>
+#endif
+
#include <string.h>
+
#include <algorithm>
#include <atomic>
#include <cerrno>
#include <cstddef>
-#include <new> // for placement-new
+#include <new> // for placement-new
#include "absl/base/dynamic_annotations.h"
#include "absl/base/internal/raw_logging.h"
#include "absl/base/internal/spinlock.h"
-// MAP_ANONYMOUS
-#if defined(__APPLE__)
-// For mmap, Linux defines both MAP_ANONYMOUS and MAP_ANON and says MAP_ANON is
-// deprecated. In Darwin, MAP_ANON is all there is.
-#if !defined MAP_ANONYMOUS
+#if defined(MAP_ANON) && !defined(MAP_ANONYMOUS)
#define MAP_ANONYMOUS MAP_ANON
-#endif // !MAP_ANONYMOUS
-#endif // __APPLE__
+#endif
namespace absl {
ABSL_NAMESPACE_BEGIN
@@ -122,7 +122,7 @@
static int Random(uint32_t *state) {
uint32_t r = *state;
int result = 1;
- while ((((r = r*1103515245 + 12345) >> 30) & 1) == 0) {
+ while ((((r = r * 1103515245 + 12345) >> 30) & 1) == 0) {
result++;
}
*state = r;
@@ -144,7 +144,7 @@
size_t max_fit = (size - offsetof(AllocList, next)) / sizeof(AllocList *);
int level = IntLog2(size, base) + (random != nullptr ? Random(random) : 1);
if (static_cast<size_t>(level) > max_fit) level = static_cast<int>(max_fit);
- if (level > kMaxLevel-1) level = kMaxLevel - 1;
+ if (level > kMaxLevel - 1) level = kMaxLevel - 1;
ABSL_RAW_CHECK(level >= 1, "block not big enough for even one level");
return level;
}
@@ -153,8 +153,8 @@
// For 0 <= i < head->levels, set prev[i] to "no_greater", where no_greater
// points to the last element at level i in the AllocList less than *e, or is
// head if no such element exists.
-static AllocList *LLA_SkiplistSearch(AllocList *head,
- AllocList *e, AllocList **prev) {
+static AllocList *LLA_SkiplistSearch(AllocList *head, AllocList *e,
+ AllocList **prev) {
AllocList *p = head;
for (int level = head->levels - 1; level >= 0; level--) {
for (AllocList *n; (n = p->next[level]) != nullptr && n < e; p = n) {
@@ -190,7 +190,7 @@
prev[i]->next[i] = e->next[i];
}
while (head->levels > 0 && head->next[head->levels - 1] == nullptr) {
- head->levels--; // reduce head->levels if level unused
+ head->levels--; // reduce head->levels if level unused
}
}
@@ -249,9 +249,9 @@
// Returns a global arena that does not call into hooks. Used by NewArena()
// when kCallMallocHook is not set.
-LowLevelAlloc::Arena* UnhookedArena() {
+LowLevelAlloc::Arena *UnhookedArena() {
base_internal::LowLevelCallOnce(&create_globals_once, CreateGlobalArenas);
- return reinterpret_cast<LowLevelAlloc::Arena*>(&unhooked_arena_storage);
+ return reinterpret_cast<LowLevelAlloc::Arena *>(&unhooked_arena_storage);
}
#ifndef ABSL_LOW_LEVEL_ALLOC_ASYNC_SIGNAL_SAFE_MISSING
@@ -269,7 +269,7 @@
// Returns the default arena, as used by LowLevelAlloc::Alloc() and friends.
LowLevelAlloc::Arena *LowLevelAlloc::DefaultArena() {
base_internal::LowLevelCallOnce(&create_globals_once, CreateGlobalArenas);
- return reinterpret_cast<LowLevelAlloc::Arena*>(&default_arena_storage);
+ return reinterpret_cast<LowLevelAlloc::Arena *>(&default_arena_storage);
}
// magic numbers to identify allocated and unallocated blocks
@@ -329,10 +329,10 @@
SYSTEM_INFO system_info;
GetSystemInfo(&system_info);
return std::max(system_info.dwPageSize, system_info.dwAllocationGranularity);
-#elif defined(__wasm__) || defined(__asmjs__)
+#elif defined(__wasm__) || defined(__asmjs__) || defined(__hexagon__)
return getpagesize();
#else
- return sysconf(_SC_PAGESIZE);
+ return static_cast<size_t>(sysconf(_SC_PAGESIZE));
#endif
}
@@ -356,15 +356,14 @@
min_size(2 * round_up),
random(0) {
freelist.header.size = 0;
- freelist.header.magic =
- Magic(kMagicUnallocated, &freelist.header);
+ freelist.header.magic = Magic(kMagicUnallocated, &freelist.header);
freelist.header.arena = this;
freelist.levels = 0;
memset(freelist.next, 0, sizeof(freelist.next));
}
// L < meta_data_arena->mu
-LowLevelAlloc::Arena *LowLevelAlloc::NewArena(int32_t flags) {
+LowLevelAlloc::Arena *LowLevelAlloc::NewArena(uint32_t flags) {
Arena *meta_data_arena = DefaultArena();
#ifndef ABSL_LOW_LEVEL_ALLOC_ASYNC_SIGNAL_SAFE_MISSING
if ((flags & LowLevelAlloc::kAsyncSignalSafe) != 0) {
@@ -375,7 +374,7 @@
meta_data_arena = UnhookedArena();
}
Arena *result =
- new (AllocWithArena(sizeof (*result), meta_data_arena)) Arena(flags);
+ new (AllocWithArena(sizeof(*result), meta_data_arena)) Arena(flags);
return result;
}
@@ -480,8 +479,8 @@
AllocList *prev[kMaxLevel];
LLA_SkiplistDelete(&arena->freelist, n, prev);
LLA_SkiplistDelete(&arena->freelist, a, prev);
- a->levels = LLA_SkiplistLevels(a->header.size, arena->min_size,
- &arena->random);
+ a->levels =
+ LLA_SkiplistLevels(a->header.size, arena->min_size, &arena->random);
LLA_SkiplistInsert(&arena->freelist, a, prev);
}
}
@@ -489,27 +488,27 @@
// Adds block at location "v" to the free list
// L >= arena->mu
static void AddToFreelist(void *v, LowLevelAlloc::Arena *arena) {
- AllocList *f = reinterpret_cast<AllocList *>(
- reinterpret_cast<char *>(v) - sizeof (f->header));
+ AllocList *f = reinterpret_cast<AllocList *>(reinterpret_cast<char *>(v) -
+ sizeof(f->header));
ABSL_RAW_CHECK(f->header.magic == Magic(kMagicAllocated, &f->header),
"bad magic number in AddToFreelist()");
ABSL_RAW_CHECK(f->header.arena == arena,
"bad arena pointer in AddToFreelist()");
- f->levels = LLA_SkiplistLevels(f->header.size, arena->min_size,
- &arena->random);
+ f->levels =
+ LLA_SkiplistLevels(f->header.size, arena->min_size, &arena->random);
AllocList *prev[kMaxLevel];
LLA_SkiplistInsert(&arena->freelist, f, prev);
f->header.magic = Magic(kMagicUnallocated, &f->header);
- Coalesce(f); // maybe coalesce with successor
- Coalesce(prev[0]); // maybe coalesce with predecessor
+ Coalesce(f); // maybe coalesce with successor
+ Coalesce(prev[0]); // maybe coalesce with predecessor
}
// Frees storage allocated by LowLevelAlloc::Alloc().
// L < arena->mu
void LowLevelAlloc::Free(void *v) {
if (v != nullptr) {
- AllocList *f = reinterpret_cast<AllocList *>(
- reinterpret_cast<char *>(v) - sizeof (f->header));
+ AllocList *f = reinterpret_cast<AllocList *>(reinterpret_cast<char *>(v) -
+ sizeof(f->header));
LowLevelAlloc::Arena *arena = f->header.arena;
ArenaLock section(arena);
AddToFreelist(v, arena);
@@ -524,21 +523,21 @@
static void *DoAllocWithArena(size_t request, LowLevelAlloc::Arena *arena) {
void *result = nullptr;
if (request != 0) {
- AllocList *s; // will point to region that satisfies request
+ AllocList *s; // will point to region that satisfies request
ArenaLock section(arena);
// round up with header
- size_t req_rnd = RoundUp(CheckedAdd(request, sizeof (s->header)),
- arena->round_up);
- for (;;) { // loop until we find a suitable region
+ size_t req_rnd =
+ RoundUp(CheckedAdd(request, sizeof(s->header)), arena->round_up);
+ for (;;) { // loop until we find a suitable region
// find the minimum levels that a block of this size must have
int i = LLA_SkiplistLevels(req_rnd, arena->min_size, nullptr) - 1;
- if (i < arena->freelist.levels) { // potential blocks exist
+ if (i < arena->freelist.levels) { // potential blocks exist
AllocList *before = &arena->freelist; // predecessor of s
while ((s = Next(i, before, arena)) != nullptr &&
s->header.size < req_rnd) {
before = s;
}
- if (s != nullptr) { // we found a region
+ if (s != nullptr) { // we found a region
break;
}
}
@@ -550,7 +549,7 @@
size_t new_pages_size = RoundUp(req_rnd, arena->pagesize * 16);
void *new_pages;
#ifdef _WIN32
- new_pages = VirtualAlloc(0, new_pages_size,
+ new_pages = VirtualAlloc(nullptr, new_pages_size,
MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
ABSL_RAW_CHECK(new_pages != nullptr, "VirtualAlloc failed");
#else
@@ -570,6 +569,18 @@
ABSL_RAW_LOG(FATAL, "mmap error: %d", errno);
}
+#ifdef __linux__
+#if defined(PR_SET_VMA) && defined(PR_SET_VMA_ANON_NAME)
+ // Attempt to name the allocated address range in /proc/$PID/smaps on
+ // Linux.
+ //
+ // This invocation of prctl() may fail if the Linux kernel was not
+ // configured with the CONFIG_ANON_VMA_NAME option. This is OK since
+ // the naming of arenas is primarily a debugging aid.
+ prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, new_pages, new_pages_size,
+ "absl");
+#endif
+#endif // __linux__
#endif // _WIN32
arena->mu.Lock();
s = reinterpret_cast<AllocList *>(new_pages);
@@ -580,12 +591,12 @@
AddToFreelist(&s->levels, arena); // insert new region into free list
}
AllocList *prev[kMaxLevel];
- LLA_SkiplistDelete(&arena->freelist, s, prev); // remove from free list
+ LLA_SkiplistDelete(&arena->freelist, s, prev); // remove from free list
// s points to the first free region that's big enough
if (CheckedAdd(req_rnd, arena->min_size) <= s->header.size) {
// big enough to split
- AllocList *n = reinterpret_cast<AllocList *>
- (req_rnd + reinterpret_cast<char *>(s));
+ AllocList *n =
+ reinterpret_cast<AllocList *>(req_rnd + reinterpret_cast<char *>(s));
n->header.size = s->header.size - req_rnd;
n->header.magic = Magic(kMagicAllocated, &n->header);
n->header.arena = arena;
diff --git a/absl/base/internal/low_level_alloc.h b/absl/base/internal/low_level_alloc.h
index db91951..c2f1f25 100644
--- a/absl/base/internal/low_level_alloc.h
+++ b/absl/base/internal/low_level_alloc.h
@@ -46,7 +46,8 @@
// for more information.
#ifdef ABSL_LOW_LEVEL_ALLOC_ASYNC_SIGNAL_SAFE_MISSING
#error ABSL_LOW_LEVEL_ALLOC_ASYNC_SIGNAL_SAFE_MISSING cannot be directly set
-#elif defined(_WIN32) || defined(__asmjs__) || defined(__wasm__)
+#elif defined(_WIN32) || defined(__asmjs__) || defined(__wasm__) || \
+ defined(__hexagon__)
#define ABSL_LOW_LEVEL_ALLOC_ASYNC_SIGNAL_SAFE_MISSING 1
#endif
@@ -103,7 +104,7 @@
// the provided flags. For example, the call NewArena(kAsyncSignalSafe)
// is itself async-signal-safe, as well as generatating an arena that provides
// async-signal-safe Alloc/Free.
- static Arena *NewArena(int32_t flags);
+ static Arena *NewArena(uint32_t flags);
// Destroys an arena allocated by NewArena and returns true,
// provided no allocated blocks remain in the arena.
diff --git a/absl/base/internal/low_level_alloc_test.cc b/absl/base/internal/low_level_alloc_test.cc
index 31abb88..8fdec09 100644
--- a/absl/base/internal/low_level_alloc_test.cc
+++ b/absl/base/internal/low_level_alloc_test.cc
@@ -86,7 +86,7 @@
AllocMap::iterator it;
BlockDesc block_desc;
int rnd;
- LowLevelAlloc::Arena *arena = 0;
+ LowLevelAlloc::Arena *arena = nullptr;
if (use_new_arena) {
int32_t flags = call_malloc_hook ? LowLevelAlloc::kCallMallocHook : 0;
arena = LowLevelAlloc::NewArena(flags);
@@ -101,11 +101,10 @@
case 0: // coin came up heads: add a block
using_low_level_alloc = true;
block_desc.len = rand() & 0x3fff;
- block_desc.ptr =
- reinterpret_cast<char *>(
- arena == 0
- ? LowLevelAlloc::Alloc(block_desc.len)
- : LowLevelAlloc::AllocWithArena(block_desc.len, arena));
+ block_desc.ptr = reinterpret_cast<char *>(
+ arena == nullptr
+ ? LowLevelAlloc::Alloc(block_desc.len)
+ : LowLevelAlloc::AllocWithArena(block_desc.len, arena));
using_low_level_alloc = false;
RandomizeBlockDesc(&block_desc);
rnd = rand();
diff --git a/absl/base/internal/nullability_impl.h b/absl/base/internal/nullability_impl.h
new file mode 100644
index 0000000..36e1b33
--- /dev/null
+++ b/absl/base/internal/nullability_impl.h
@@ -0,0 +1,106 @@
+// Copyright 2023 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef ABSL_BASE_INTERNAL_NULLABILITY_IMPL_H_
+#define ABSL_BASE_INTERNAL_NULLABILITY_IMPL_H_
+
+#include <memory>
+#include <type_traits>
+
+#include "absl/base/attributes.h"
+#include "absl/meta/type_traits.h"
+
+namespace absl {
+
+namespace nullability_internal {
+
+// `IsNullabilityCompatible` checks whether its first argument is a class
+// explicitly tagged as supporting nullability annotations. The tag is the type
+// declaration `absl_nullability_compatible`.
+template <typename, typename = void>
+struct IsNullabilityCompatible : std::false_type {};
+
+template <typename T>
+struct IsNullabilityCompatible<
+ T, absl::void_t<typename T::absl_nullability_compatible>> : std::true_type {
+};
+
+template <typename T>
+constexpr bool IsSupportedType = IsNullabilityCompatible<T>::value;
+
+template <typename T>
+constexpr bool IsSupportedType<T*> = true;
+
+template <typename T, typename U>
+constexpr bool IsSupportedType<T U::*> = true;
+
+template <typename T, typename... Deleter>
+constexpr bool IsSupportedType<std::unique_ptr<T, Deleter...>> = true;
+
+template <typename T>
+constexpr bool IsSupportedType<std::shared_ptr<T>> = true;
+
+template <typename T>
+struct EnableNullable {
+ static_assert(nullability_internal::IsSupportedType<std::remove_cv_t<T>>,
+ "Template argument must be a raw or supported smart pointer "
+ "type. See absl/base/nullability.h.");
+ using type = T;
+};
+
+template <typename T>
+struct EnableNonnull {
+ static_assert(nullability_internal::IsSupportedType<std::remove_cv_t<T>>,
+ "Template argument must be a raw or supported smart pointer "
+ "type. See absl/base/nullability.h.");
+ using type = T;
+};
+
+template <typename T>
+struct EnableNullabilityUnknown {
+ static_assert(nullability_internal::IsSupportedType<std::remove_cv_t<T>>,
+ "Template argument must be a raw or supported smart pointer "
+ "type. See absl/base/nullability.h.");
+ using type = T;
+};
+
+// Note: we do not apply Clang nullability attributes (e.g. _Nullable). These
+// only support raw pointers, and conditionally enabling them only for raw
+// pointers inhibits template arg deduction. Ideally, they would support all
+// pointer-like types.
+template <typename T, typename = typename EnableNullable<T>::type>
+using NullableImpl
+#if ABSL_HAVE_CPP_ATTRIBUTE(clang::annotate)
+ [[clang::annotate("Nullable")]]
+#endif
+ = T;
+
+template <typename T, typename = typename EnableNonnull<T>::type>
+using NonnullImpl
+#if ABSL_HAVE_CPP_ATTRIBUTE(clang::annotate)
+ [[clang::annotate("Nonnull")]]
+#endif
+ = T;
+
+template <typename T, typename = typename EnableNullabilityUnknown<T>::type>
+using NullabilityUnknownImpl
+#if ABSL_HAVE_CPP_ATTRIBUTE(clang::annotate)
+ [[clang::annotate("Nullability_Unspecified")]]
+#endif
+ = T;
+
+} // namespace nullability_internal
+} // namespace absl
+
+#endif // ABSL_BASE_INTERNAL_NULLABILITY_IMPL_H_
diff --git a/absl/base/internal/raw_logging.cc b/absl/base/internal/raw_logging.cc
index 074e026..d32b40a 100644
--- a/absl/base/internal/raw_logging.cc
+++ b/absl/base/internal/raw_logging.cc
@@ -14,15 +14,21 @@
#include "absl/base/internal/raw_logging.h"
-#include <stddef.h>
#include <cstdarg>
+#include <cstddef>
#include <cstdio>
#include <cstdlib>
#include <cstring>
+#include <string>
+
+#ifdef __EMSCRIPTEN__
+#include <emscripten/console.h>
+#endif
#include "absl/base/attributes.h"
#include "absl/base/config.h"
#include "absl/base/internal/atomic_hook.h"
+#include "absl/base/internal/errno_saver.h"
#include "absl/base/log_severity.h"
// We know how to perform low-level writes to stderr in POSIX and Windows. For
@@ -36,7 +42,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(__Fuchsia__) || defined(__native_client__) || \
+ defined(__hexagon__) || defined(__Fuchsia__) || \
+ defined(__native_client__) || defined(__OpenBSD__) || \
defined(__EMSCRIPTEN__) || defined(__ASYLO__)
#include <unistd.h>
@@ -69,20 +76,13 @@
namespace absl {
ABSL_NAMESPACE_BEGIN
-namespace raw_logging_internal {
+namespace raw_log_internal {
namespace {
// TODO(gfalcon): We want raw-logging to work on as many platforms as possible.
// Explicitly `#error` out when not `ABSL_LOW_LEVEL_WRITE_SUPPORTED`, except for
// a selected set of platforms for which we expect not to be able to raw log.
-ABSL_INTERNAL_ATOMIC_HOOK_ATTRIBUTES
- absl::base_internal::AtomicHook<LogPrefixHook>
- log_prefix_hook;
-ABSL_INTERNAL_ATOMIC_HOOK_ATTRIBUTES
- absl::base_internal::AtomicHook<AbortHook>
- abort_hook;
-
#ifdef ABSL_LOW_LEVEL_WRITE_SUPPORTED
constexpr char kTruncated[] = " ... (message truncated)\n";
@@ -93,12 +93,13 @@
bool VADoRawLog(char** buf, int* size, const char* format, va_list ap)
ABSL_PRINTF_ATTRIBUTE(3, 0);
bool VADoRawLog(char** buf, int* size, const char* format, va_list ap) {
- int n = vsnprintf(*buf, *size, format, ap);
+ if (*size < 0) return false;
+ int n = vsnprintf(*buf, static_cast<size_t>(*size), format, ap);
bool result = true;
if (n < 0 || n > *size) {
result = false;
if (static_cast<size_t>(*size) > sizeof(kTruncated)) {
- n = *size - sizeof(kTruncated); // room for truncation message
+ n = *size - static_cast<int>(sizeof(kTruncated));
} else {
n = 0; // no room for truncation message
}
@@ -120,9 +121,10 @@
bool DoRawLog(char** buf, int* size, const char* format, ...)
ABSL_PRINTF_ATTRIBUTE(3, 4);
bool DoRawLog(char** buf, int* size, const char* format, ...) {
+ if (*size < 0) return false;
va_list ap;
va_start(ap, format);
- int n = vsnprintf(*buf, *size, format, ap);
+ int n = vsnprintf(*buf, static_cast<size_t>(*size), format, ap);
va_end(ap);
if (n < 0 || n > *size) return false;
*size -= n;
@@ -130,6 +132,18 @@
return true;
}
+bool DefaultLogFilterAndPrefix(absl::LogSeverity, const char* file, int line,
+ char** buf, int* buf_size) {
+ DoRawLog(buf, buf_size, "[%s : %d] RAW: ", file, line);
+ return true;
+}
+
+ABSL_INTERNAL_ATOMIC_HOOK_ATTRIBUTES
+absl::base_internal::AtomicHook<LogFilterAndPrefixHook>
+ log_filter_and_prefix_hook(DefaultLogFilterAndPrefix);
+ABSL_INTERNAL_ATOMIC_HOOK_ATTRIBUTES
+absl::base_internal::AtomicHook<AbortHook> abort_hook;
+
void RawLogVA(absl::LogSeverity severity, const char* file, int line,
const char* format, va_list ap) ABSL_PRINTF_ATTRIBUTE(4, 0);
void RawLogVA(absl::LogSeverity severity, const char* file, int line,
@@ -150,14 +164,7 @@
}
#endif
- auto log_prefix_hook_ptr = log_prefix_hook.Load();
- if (log_prefix_hook_ptr) {
- enabled = log_prefix_hook_ptr(severity, file, line, &buf, &size);
- } else {
- if (enabled) {
- DoRawLog(&buf, &size, "[%s : %d] RAW: ", file, line);
- }
- }
+ enabled = log_filter_and_prefix_hook(severity, file, line, &buf, &size);
const char* const prefix_end = buf;
#ifdef ABSL_LOW_LEVEL_WRITE_SUPPORTED
@@ -168,11 +175,12 @@
} else {
DoRawLog(&buf, &size, "%s", kTruncated);
}
- SafeWriteToStderr(buffer, strlen(buffer));
+ AsyncSignalSafeWriteError(buffer, strlen(buffer));
}
#else
static_cast<void>(format);
static_cast<void>(ap);
+ static_cast<void>(enabled);
#endif
// Abort the process after logging a FATAL message, even if the output itself
@@ -195,17 +203,45 @@
} // namespace
-void SafeWriteToStderr(const char *s, size_t len) {
-#if defined(ABSL_HAVE_SYSCALL_WRITE)
+void AsyncSignalSafeWriteError(const char* s, size_t len) {
+ if (!len) return;
+ absl::base_internal::ErrnoSaver errno_saver;
+#if defined(__EMSCRIPTEN__)
+ // In WebAssembly, bypass filesystem emulation via fwrite.
+ if (s[len - 1] == '\n') {
+ // Skip a trailing newline character as emscripten_errn adds one itself.
+ len--;
+ }
+ // emscripten_errn was introduced in 3.1.41 but broken in standalone mode
+ // until 3.1.43.
+#if ABSL_INTERNAL_EMSCRIPTEN_VERSION >= 3001043
+ emscripten_errn(s, len);
+#else
+ char buf[kLogBufSize];
+ if (len >= kLogBufSize) {
+ len = kLogBufSize - 1;
+ constexpr size_t trunc_len = sizeof(kTruncated) - 2;
+ memcpy(buf + len - trunc_len, kTruncated, trunc_len);
+ buf[len] = '\0';
+ len -= trunc_len;
+ } else {
+ buf[len] = '\0';
+ }
+ memcpy(buf, s, len);
+ _emscripten_err(buf);
+#endif
+#elif defined(ABSL_HAVE_SYSCALL_WRITE)
+ // We prefer calling write via `syscall` to minimize the risk of libc doing
+ // something "helpful".
syscall(SYS_write, STDERR_FILENO, s, len);
#elif defined(ABSL_HAVE_POSIX_WRITE)
write(STDERR_FILENO, s, len);
#elif defined(ABSL_HAVE_RAW_IO)
- _write(/* stderr */ 2, s, len);
+ _write(/* stderr */ 2, s, static_cast<unsigned>(len));
#else
// stderr logging unsupported on this platform
- (void) s;
- (void) len;
+ (void)s;
+ (void)len;
#endif
}
@@ -220,7 +256,7 @@
bool RawLoggingFullySupported() {
#ifdef ABSL_LOW_LEVEL_WRITE_SUPPORTED
return true;
-#else // !ABSL_LOW_LEVEL_WRITE_SUPPORTED
+#else // !ABSL_LOW_LEVEL_WRITE_SUPPORTED
return false;
#endif // !ABSL_LOW_LEVEL_WRITE_SUPPORTED
}
@@ -229,7 +265,9 @@
absl::base_internal::AtomicHook<InternalLogFunction>
internal_log_function(DefaultInternalLog);
-void RegisterLogPrefixHook(LogPrefixHook func) { log_prefix_hook.Store(func); }
+void RegisterLogFilterAndPrefixHook(LogFilterAndPrefixHook func) {
+ log_filter_and_prefix_hook.Store(func);
+}
void RegisterAbortHook(AbortHook func) { abort_hook.Store(func); }
@@ -237,6 +275,6 @@
internal_log_function.Store(func);
}
-} // namespace raw_logging_internal
+} // namespace raw_log_internal
ABSL_NAMESPACE_END
} // namespace absl
diff --git a/absl/base/internal/raw_logging.h b/absl/base/internal/raw_logging.h
index 2bf7aab..d7cfbc5 100644
--- a/absl/base/internal/raw_logging.h
+++ b/absl/base/internal/raw_logging.h
@@ -43,12 +43,12 @@
#define ABSL_RAW_LOG(severity, ...) \
do { \
- constexpr const char* absl_raw_logging_internal_basename = \
- ::absl::raw_logging_internal::Basename(__FILE__, \
- sizeof(__FILE__) - 1); \
- ::absl::raw_logging_internal::RawLog(ABSL_RAW_LOGGING_INTERNAL_##severity, \
- absl_raw_logging_internal_basename, \
- __LINE__, __VA_ARGS__); \
+ constexpr const char* absl_raw_log_internal_basename = \
+ ::absl::raw_log_internal::Basename(__FILE__, sizeof(__FILE__) - 1); \
+ ::absl::raw_log_internal::RawLog(ABSL_RAW_LOG_INTERNAL_##severity, \
+ absl_raw_log_internal_basename, __LINE__, \
+ __VA_ARGS__); \
+ ABSL_RAW_LOG_INTERNAL_MAYBE_UNREACHABLE_##severity; \
} while (0)
// Similar to CHECK(condition) << message, but for low-level modules:
@@ -72,14 +72,13 @@
//
// The API is a subset of the above: each macro only takes two arguments. Use
// StrCat if you need to build a richer message.
-#define ABSL_INTERNAL_LOG(severity, message) \
- do { \
- constexpr const char* absl_raw_logging_internal_filename = __FILE__; \
- ::absl::raw_logging_internal::internal_log_function( \
- ABSL_RAW_LOGGING_INTERNAL_##severity, \
- absl_raw_logging_internal_filename, __LINE__, message); \
- if (ABSL_RAW_LOGGING_INTERNAL_##severity == ::absl::LogSeverity::kFatal) \
- ABSL_INTERNAL_UNREACHABLE; \
+#define ABSL_INTERNAL_LOG(severity, message) \
+ do { \
+ constexpr const char* absl_raw_log_internal_filename = __FILE__; \
+ ::absl::raw_log_internal::internal_log_function( \
+ ABSL_RAW_LOG_INTERNAL_##severity, absl_raw_log_internal_filename, \
+ __LINE__, message); \
+ ABSL_RAW_LOG_INTERNAL_MAYBE_UNREACHABLE_##severity; \
} while (0)
#define ABSL_INTERNAL_CHECK(condition, message) \
@@ -91,16 +90,38 @@
} \
} while (0)
-#define ABSL_RAW_LOGGING_INTERNAL_INFO ::absl::LogSeverity::kInfo
-#define ABSL_RAW_LOGGING_INTERNAL_WARNING ::absl::LogSeverity::kWarning
-#define ABSL_RAW_LOGGING_INTERNAL_ERROR ::absl::LogSeverity::kError
-#define ABSL_RAW_LOGGING_INTERNAL_FATAL ::absl::LogSeverity::kFatal
-#define ABSL_RAW_LOGGING_INTERNAL_LEVEL(severity) \
+#ifndef NDEBUG
+
+#define ABSL_RAW_DLOG(severity, ...) ABSL_RAW_LOG(severity, __VA_ARGS__)
+#define ABSL_RAW_DCHECK(condition, message) ABSL_RAW_CHECK(condition, message)
+
+#else // NDEBUG
+
+#define ABSL_RAW_DLOG(severity, ...) \
+ while (false) ABSL_RAW_LOG(severity, __VA_ARGS__)
+#define ABSL_RAW_DCHECK(condition, message) \
+ while (false) ABSL_RAW_CHECK(condition, message)
+
+#endif // NDEBUG
+
+#define ABSL_RAW_LOG_INTERNAL_INFO ::absl::LogSeverity::kInfo
+#define ABSL_RAW_LOG_INTERNAL_WARNING ::absl::LogSeverity::kWarning
+#define ABSL_RAW_LOG_INTERNAL_ERROR ::absl::LogSeverity::kError
+#define ABSL_RAW_LOG_INTERNAL_FATAL ::absl::LogSeverity::kFatal
+#define ABSL_RAW_LOG_INTERNAL_DFATAL ::absl::kLogDebugFatal
+#define ABSL_RAW_LOG_INTERNAL_LEVEL(severity) \
::absl::NormalizeLogSeverity(severity)
+#define ABSL_RAW_LOG_INTERNAL_MAYBE_UNREACHABLE_INFO
+#define ABSL_RAW_LOG_INTERNAL_MAYBE_UNREACHABLE_WARNING
+#define ABSL_RAW_LOG_INTERNAL_MAYBE_UNREACHABLE_ERROR
+#define ABSL_RAW_LOG_INTERNAL_MAYBE_UNREACHABLE_FATAL ABSL_UNREACHABLE()
+#define ABSL_RAW_LOG_INTERNAL_MAYBE_UNREACHABLE_DFATAL
+#define ABSL_RAW_LOG_INTERNAL_MAYBE_UNREACHABLE_LEVEL(severity)
+
namespace absl {
ABSL_NAMESPACE_BEGIN
-namespace raw_logging_internal {
+namespace raw_log_internal {
// Helper function to implement ABSL_RAW_LOG
// Logs format... at "severity" level, reporting it
@@ -109,12 +130,9 @@
void RawLog(absl::LogSeverity severity, const char* file, int line,
const char* format, ...) ABSL_PRINTF_ATTRIBUTE(4, 5);
-// Writes the provided buffer directly to stderr, in a safe, low-level manner.
-//
-// In POSIX this means calling write(), which is async-signal safe and does
-// not malloc. If the platform supports the SYS_write syscall, we invoke that
-// directly to side-step any libc interception.
-void SafeWriteToStderr(const char *s, size_t len);
+// Writes the provided buffer directly to stderr, in a signal-safe, low-level
+// manner. Preserves errno.
+void AsyncSignalSafeWriteError(const char* s, size_t len);
// compile-time function to get the "base" filename, that is, the part of
// a filename after the last "/" or "\" path separator. The search starts at
@@ -133,7 +151,7 @@
// TODO(gfalcon): Come up with a better name for this method.
bool RawLoggingFullySupported();
-// Function type for a raw_logging customization hook for suppressing messages
+// Function type for a raw_log customization hook for suppressing messages
// by severity, and for writing custom prefixes on non-suppressed messages.
//
// The installed hook is called for every raw log invocation. The message will
@@ -142,19 +160,20 @@
// also provided with an output buffer, where it can write a custom log message
// prefix.
//
-// The raw_logging system does not allocate memory or grab locks. User-provided
+// The raw_log system does not allocate memory or grab locks. User-provided
// hooks must avoid these operations, and must not throw exceptions.
//
// 'severity' is the severity level of the message being written.
// 'file' and 'line' are the file and line number where the ABSL_RAW_LOG macro
// was located.
-// 'buffer' and 'buf_size' are pointers to the buffer and buffer size. If the
-// hook writes a prefix, it must increment *buffer and decrement *buf_size
+// 'buf' and 'buf_size' are pointers to the buffer and buffer size. If the
+// hook writes a prefix, it must increment *buf and decrement *buf_size
// accordingly.
-using LogPrefixHook = bool (*)(absl::LogSeverity severity, const char* file,
- int line, char** buffer, int* buf_size);
+using LogFilterAndPrefixHook = bool (*)(absl::LogSeverity severity,
+ const char* file, int line, char** buf,
+ int* buf_size);
-// Function type for a raw_logging customization hook called to abort a process
+// Function type for a raw_log customization hook called to abort a process
// when a FATAL message is logged. If the provided AbortHook() returns, the
// logging system will call abort().
//
@@ -162,7 +181,10 @@
// was located.
// The NUL-terminated logged message lives in the buffer between 'buf_start'
// and 'buf_end'. 'prefix_end' points to the first non-prefix character of the
-// buffer (as written by the LogPrefixHook.)
+// buffer (as written by the LogFilterAndPrefixHook.)
+//
+// The lifetime of the filename and message buffers will not end while the
+// process remains alive.
using AbortHook = void (*)(const char* file, int line, const char* buf_start,
const char* prefix_end, const char* buf_end);
@@ -184,11 +206,11 @@
//
// These functions are safe to call at any point during initialization; they do
// not block or malloc, and are async-signal safe.
-void RegisterLogPrefixHook(LogPrefixHook func);
+void RegisterLogFilterAndPrefixHook(LogFilterAndPrefixHook func);
void RegisterAbortHook(AbortHook func);
void RegisterInternalLogFunction(InternalLogFunction func);
-} // namespace raw_logging_internal
+} // namespace raw_log_internal
ABSL_NAMESPACE_END
} // namespace absl
diff --git a/absl/base/internal/spinlock.cc b/absl/base/internal/spinlock.cc
index 35c0696..381b913 100644
--- a/absl/base/internal/spinlock.cc
+++ b/absl/base/internal/spinlock.cc
@@ -19,6 +19,7 @@
#include <limits>
#include "absl/base/attributes.h"
+#include "absl/base/config.h"
#include "absl/base/internal/atomic_hook.h"
#include "absl/base/internal/cycleclock.h"
#include "absl/base/internal/spinlock_wait.h"
@@ -66,12 +67,14 @@
submit_profile_data.Store(fn);
}
+#ifdef ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL
// Static member variable definitions.
constexpr uint32_t SpinLock::kSpinLockHeld;
constexpr uint32_t SpinLock::kSpinLockCooperative;
constexpr uint32_t SpinLock::kSpinLockDisabledScheduling;
constexpr uint32_t SpinLock::kSpinLockSleeper;
constexpr uint32_t SpinLock::kWaitTimeMask;
+#endif
// Uncommon constructors.
SpinLock::SpinLock(base_internal::SchedulingMode mode)
@@ -175,7 +178,7 @@
// reserve a unitary wait time to represent that a waiter exists without our
// own acquisition having been contended.
if ((lock_value & kWaitTimeMask) != kSpinLockSleeper) {
- const uint64_t wait_cycles = DecodeWaitCycles(lock_value);
+ const int64_t wait_cycles = DecodeWaitCycles(lock_value);
ABSL_TSAN_MUTEX_PRE_DIVERT(this, 0);
submit_profile_data(this, wait_cycles);
ABSL_TSAN_MUTEX_POST_DIVERT(this, 0);
@@ -217,9 +220,9 @@
return clamped;
}
-uint64_t SpinLock::DecodeWaitCycles(uint32_t lock_value) {
+int64_t SpinLock::DecodeWaitCycles(uint32_t lock_value) {
// Cast to uint32_t first to ensure bits [63:32] are cleared.
- const uint64_t scaled_wait_time =
+ const int64_t scaled_wait_time =
static_cast<uint32_t>(lock_value & kWaitTimeMask);
return scaled_wait_time << (kProfileTimestampShift - kLockwordReservedShift);
}
diff --git a/absl/base/internal/spinlock.h b/absl/base/internal/spinlock.h
index c73b5e0..36f823a 100644
--- a/absl/base/internal/spinlock.h
+++ b/absl/base/internal/spinlock.h
@@ -16,21 +16,21 @@
// Most users requiring mutual exclusion should use Mutex.
// SpinLock is provided for use in two situations:
-// - for use in code that Mutex itself depends on
+// - for use by Abseil internal code that Mutex itself depends on
// - for async signal safety (see below)
-// SpinLock is async signal safe. If a spinlock is used within a signal
-// handler, all code that acquires the lock must ensure that the signal cannot
-// arrive while they are holding the lock. Typically, this is done by blocking
-// the signal.
+// SpinLock with a base_internal::SchedulingMode::SCHEDULE_KERNEL_ONLY is async
+// signal safe. If a spinlock is used within a signal handler, all code that
+// acquires the lock must ensure that the signal cannot arrive while they are
+// holding the lock. Typically, this is done by blocking the signal.
+//
+// Threads waiting on a SpinLock may be woken in an arbitrary order.
#ifndef ABSL_BASE_INTERNAL_SPINLOCK_H_
#define ABSL_BASE_INTERNAL_SPINLOCK_H_
-#include <stdint.h>
-#include <sys/types.h>
-
#include <atomic>
+#include <cstdint>
#include "absl/base/attributes.h"
#include "absl/base/const_init.h"
@@ -39,15 +39,21 @@
#include "absl/base/internal/raw_logging.h"
#include "absl/base/internal/scheduling_mode.h"
#include "absl/base/internal/tsan_mutex_interface.h"
-#include "absl/base/macros.h"
-#include "absl/base/port.h"
#include "absl/base/thread_annotations.h"
+namespace tcmalloc {
+namespace tcmalloc_internal {
+
+class AllocationGuardSpinLockHolder;
+
+} // namespace tcmalloc_internal
+} // namespace tcmalloc
+
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace base_internal {
-class ABSL_LOCKABLE SpinLock {
+class ABSL_LOCKABLE ABSL_ATTRIBUTE_WARN_UNUSED SpinLock {
public:
SpinLock() : lockword_(kSpinLockCooperative) {
ABSL_TSAN_MUTEX_CREATE(this, __tsan_mutex_not_static);
@@ -118,6 +124,14 @@
return (lockword_.load(std::memory_order_relaxed) & kSpinLockHeld) != 0;
}
+ // Return immediately if this thread holds the SpinLock exclusively.
+ // Otherwise, report an error by crashing with a diagnostic.
+ inline void AssertHeld() const ABSL_ASSERT_EXCLUSIVE_LOCK() {
+ if (!IsHeld()) {
+ ABSL_RAW_LOG(FATAL, "thread should hold the lock on SpinLock");
+ }
+ }
+
protected:
// These should not be exported except for testing.
@@ -127,10 +141,11 @@
int64_t wait_end_time);
// Extract number of wait cycles in a lock value.
- static uint64_t DecodeWaitCycles(uint32_t lock_value);
+ static int64_t DecodeWaitCycles(uint32_t lock_value);
// Provide access to protected method above. Use for testing only.
friend struct SpinLockTest;
+ friend class tcmalloc::tcmalloc_internal::AllocationGuardSpinLockHolder;
private:
// lockword_ is used to store the following:
@@ -165,6 +180,10 @@
return scheduling_mode == base_internal::SCHEDULE_COOPERATIVE_AND_KERNEL;
}
+ bool IsCooperative() const {
+ return lockword_.load(std::memory_order_relaxed) & kSpinLockCooperative;
+ }
+
uint32_t TryLockInternal(uint32_t lock_value, uint32_t wait_cycles);
void SlowLock() ABSL_ATTRIBUTE_COLD;
void SlowUnlock(uint32_t lock_value) ABSL_ATTRIBUTE_COLD;
diff --git a/absl/base/internal/spinlock_benchmark.cc b/absl/base/internal/spinlock_benchmark.cc
index 0451c65..1790d96 100644
--- a/absl/base/internal/spinlock_benchmark.cc
+++ b/absl/base/internal/spinlock_benchmark.cc
@@ -18,22 +18,39 @@
#include "absl/base/internal/raw_logging.h"
#include "absl/base/internal/scheduling_mode.h"
#include "absl/base/internal/spinlock.h"
+#include "absl/base/no_destructor.h"
#include "absl/synchronization/internal/create_thread_identity.h"
#include "benchmark/benchmark.h"
namespace {
template <absl::base_internal::SchedulingMode scheduling_mode>
-static void BM_SpinLock(benchmark::State& state) {
- // Ensure a ThreadIdentity is installed.
+static void BM_TryLock(benchmark::State& state) {
+ // Ensure a ThreadIdentity is installed so that KERNEL_ONLY has an effect.
ABSL_INTERNAL_CHECK(
absl::synchronization_internal::GetOrCreateCurrentThreadIdentity() !=
nullptr,
"GetOrCreateCurrentThreadIdentity() failed");
- static auto* spinlock = new absl::base_internal::SpinLock(scheduling_mode);
+ static absl::NoDestructor<absl::base_internal::SpinLock> spinlock(
+ scheduling_mode);
for (auto _ : state) {
- absl::base_internal::SpinLockHolder holder(spinlock);
+ if (spinlock->TryLock()) spinlock->Unlock();
+ }
+}
+
+template <absl::base_internal::SchedulingMode scheduling_mode>
+static void BM_SpinLock(benchmark::State& state) {
+ // Ensure a ThreadIdentity is installed so that KERNEL_ONLY has an effect.
+ ABSL_INTERNAL_CHECK(
+ absl::synchronization_internal::GetOrCreateCurrentThreadIdentity() !=
+ nullptr,
+ "GetOrCreateCurrentThreadIdentity() failed");
+
+ static absl::NoDestructor<absl::base_internal::SpinLock> spinlock(
+ scheduling_mode);
+ for (auto _ : state) {
+ absl::base_internal::SpinLockHolder holder(spinlock.get());
}
}
@@ -49,4 +66,15 @@
->Threads(1)
->ThreadPerCpu();
+BENCHMARK_TEMPLATE(BM_TryLock, absl::base_internal::SCHEDULE_KERNEL_ONLY)
+ ->UseRealTime()
+ ->Threads(1)
+ ->ThreadPerCpu();
+
+BENCHMARK_TEMPLATE(BM_TryLock,
+ absl::base_internal::SCHEDULE_COOPERATIVE_AND_KERNEL)
+ ->UseRealTime()
+ ->Threads(1)
+ ->ThreadPerCpu();
+
} // namespace
diff --git a/absl/base/internal/spinlock_linux.inc b/absl/base/internal/spinlock_linux.inc
index 202f7cd..fe8ba67 100644
--- a/absl/base/internal/spinlock_linux.inc
+++ b/absl/base/internal/spinlock_linux.inc
@@ -57,13 +57,10 @@
extern "C" {
ABSL_ATTRIBUTE_WEAK void ABSL_INTERNAL_C_SYMBOL(AbslInternalSpinLockDelay)(
- std::atomic<uint32_t> *w, uint32_t value, int loop,
+ std::atomic<uint32_t> *w, uint32_t value, int,
absl::base_internal::SchedulingMode) {
absl::base_internal::ErrnoSaver errno_saver;
- struct timespec tm;
- tm.tv_sec = 0;
- tm.tv_nsec = absl::base_internal::SpinLockSuggestedDelayNS(loop);
- syscall(SYS_futex, w, FUTEX_WAIT | FUTEX_PRIVATE_FLAG, value, &tm);
+ syscall(SYS_futex, w, FUTEX_WAIT | FUTEX_PRIVATE_FLAG, value, nullptr);
}
ABSL_ATTRIBUTE_WEAK void ABSL_INTERNAL_C_SYMBOL(AbslInternalSpinLockWake)(
diff --git a/absl/base/internal/spinlock_wait.h b/absl/base/internal/spinlock_wait.h
index 579bd09..9a1adcd 100644
--- a/absl/base/internal/spinlock_wait.h
+++ b/absl/base/internal/spinlock_wait.h
@@ -39,6 +39,8 @@
// satisfying 0<=i<n && trans[i].done, atomically make the transition,
// then return the old value of *w. Make any other atomic transitions
// where !trans[i].done, but continue waiting.
+//
+// Wakeups for threads blocked on SpinLockWait do not respect priorities.
uint32_t SpinLockWait(std::atomic<uint32_t> *w, int n,
const SpinLockWaitTransition trans[],
SchedulingMode scheduling_mode);
diff --git a/absl/base/internal/spinlock_win32.inc b/absl/base/internal/spinlock_win32.inc
index 9d22481..934c201 100644
--- a/absl/base/internal/spinlock_win32.inc
+++ b/absl/base/internal/spinlock_win32.inc
@@ -27,7 +27,10 @@
} else if (loop == 1) {
Sleep(0);
} else {
- Sleep(absl::base_internal::SpinLockSuggestedDelayNS(loop) / 1000000);
+ // SpinLockSuggestedDelayNS() always returns a positive integer, so this
+ // static_cast is safe.
+ Sleep(static_cast<DWORD>(
+ absl::base_internal::SpinLockSuggestedDelayNS(loop) / 1000000));
}
}
diff --git a/absl/base/internal/strerror.cc b/absl/base/internal/strerror.cc
index 0d6226f..de91c05 100644
--- a/absl/base/internal/strerror.cc
+++ b/absl/base/internal/strerror.cc
@@ -66,8 +66,8 @@
std::array<std::string, kSysNerr>* NewStrErrorTable() {
auto* table = new std::array<std::string, kSysNerr>;
- for (int i = 0; i < static_cast<int>(table->size()); ++i) {
- (*table)[i] = StrErrorInternal(i);
+ for (size_t i = 0; i < table->size(); ++i) {
+ (*table)[i] = StrErrorInternal(static_cast<int>(i));
}
return table;
}
@@ -77,8 +77,8 @@
std::string StrError(int errnum) {
absl::base_internal::ErrnoSaver errno_saver;
static const auto* table = NewStrErrorTable();
- if (errnum >= 0 && errnum < static_cast<int>(table->size())) {
- return (*table)[errnum];
+ if (errnum >= 0 && static_cast<size_t>(errnum) < table->size()) {
+ return (*table)[static_cast<size_t>(errnum)];
}
return StrErrorInternal(errnum);
}
diff --git a/absl/base/internal/sysinfo.cc b/absl/base/internal/sysinfo.cc
index 08a1e28..79eaba3 100644
--- a/absl/base/internal/sysinfo.cc
+++ b/absl/base/internal/sysinfo.cc
@@ -34,6 +34,14 @@
#include <sys/sysctl.h>
#endif
+#ifdef __FreeBSD__
+#include <pthread_np.h>
+#endif
+
+#ifdef __NetBSD__
+#include <lwp.h>
+#endif
+
#if defined(__myriad2__)
#include <rtems.h>
#endif
@@ -41,6 +49,7 @@
#include <string.h>
#include <cassert>
+#include <cerrno>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
@@ -117,25 +126,26 @@
}
}
free(info);
- return logicalProcessorCount;
+ return static_cast<int>(logicalProcessorCount);
}
#endif
} // namespace
-
static int GetNumCPUs() {
#if defined(__myriad2__)
return 1;
#elif defined(_WIN32)
- const unsigned hardware_concurrency = Win32NumCPUs();
+ const int hardware_concurrency = Win32NumCPUs();
return hardware_concurrency ? hardware_concurrency : 1;
+#elif defined(_AIX)
+ return sysconf(_SC_NPROCESSORS_ONLN);
#else
// Other possibilities:
// - Read /sys/devices/system/cpu/online and use cpumask_parse()
// - sysconf(_SC_NPROCESSORS_ONLN)
- return std::thread::hardware_concurrency();
+ return static_cast<int>(std::thread::hardware_concurrency());
#endif
}
@@ -158,7 +168,7 @@
DWORD type = 0;
DWORD data = 0;
DWORD data_size = sizeof(data);
- auto result = RegQueryValueExA(key, "~MHz", 0, &type,
+ auto result = RegQueryValueExA(key, "~MHz", nullptr, &type,
reinterpret_cast<LPBYTE>(&data), &data_size);
RegCloseKey(key);
if (result == ERROR_SUCCESS && type == REG_DWORD &&
@@ -188,12 +198,21 @@
// and the memory location pointed to by value is set to the value read.
static bool ReadLongFromFile(const char *file, long *value) {
bool ret = false;
- int fd = open(file, O_RDONLY);
+#if defined(_POSIX_C_SOURCE)
+ const int file_mode = (O_RDONLY | O_CLOEXEC);
+#else
+ const int file_mode = O_RDONLY;
+#endif
+
+ int fd = open(file, file_mode);
if (fd != -1) {
char line[1024];
char *err;
memset(line, '\0', sizeof(line));
- int len = read(fd, line, sizeof(line) - 1);
+ ssize_t len;
+ do {
+ len = read(fd, line, sizeof(line) - 1);
+ } while (len < 0 && errno == EINTR);
if (len <= 0) {
ret = false;
} else {
@@ -221,8 +240,8 @@
int rc = clock_gettime(CLOCK_MONOTONIC, &t);
#endif
if (rc != 0) {
- perror("clock_gettime() failed");
- abort();
+ ABSL_INTERNAL_LOG(
+ FATAL, "clock_gettime() failed: (" + std::to_string(errno) + ")");
}
return int64_t{t.tv_sec} * 1000000000 + t.tv_nsec;
}
@@ -375,7 +394,7 @@
#endif
pid_t GetTID() {
- return syscall(SYS_gettid);
+ return static_cast<pid_t>(syscall(SYS_gettid));
}
#elif defined(__akaros__)
@@ -410,81 +429,45 @@
return tid;
}
+#elif defined(__APPLE__)
+
+pid_t GetTID() {
+ uint64_t tid;
+ // `nullptr` here implies this thread. This only fails if the specified
+ // thread is invalid or the pointer-to-tid is null, so we needn't worry about
+ // it.
+ pthread_threadid_np(nullptr, &tid);
+ return static_cast<pid_t>(tid);
+}
+
+#elif defined(__FreeBSD__)
+
+pid_t GetTID() { return static_cast<pid_t>(pthread_getthreadid_np()); }
+
+#elif defined(__OpenBSD__)
+
+pid_t GetTID() { return getthrid(); }
+
+#elif defined(__NetBSD__)
+
+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);
+}
+
#else
-// Fallback implementation of GetTID using pthread_getspecific.
-ABSL_CONST_INIT static once_flag tid_once;
-ABSL_CONST_INIT static pthread_key_t tid_key;
-ABSL_CONST_INIT static absl::base_internal::SpinLock tid_lock(
- absl::kConstInit, base_internal::SCHEDULE_KERNEL_ONLY);
-
-// We set a bit per thread in this array to indicate that an ID is in
-// use. ID 0 is unused because it is the default value returned by
-// pthread_getspecific().
-ABSL_CONST_INIT static std::vector<uint32_t> *tid_array
- ABSL_GUARDED_BY(tid_lock) = nullptr;
-static constexpr int kBitsPerWord = 32; // tid_array is uint32_t.
-
-// Returns the TID to tid_array.
-static void FreeTID(void *v) {
- intptr_t tid = reinterpret_cast<intptr_t>(v);
- int word = tid / kBitsPerWord;
- uint32_t mask = ~(1u << (tid % kBitsPerWord));
- absl::base_internal::SpinLockHolder lock(&tid_lock);
- assert(0 <= word && static_cast<size_t>(word) < tid_array->size());
- (*tid_array)[word] &= mask;
-}
-
-static void InitGetTID() {
- if (pthread_key_create(&tid_key, FreeTID) != 0) {
- // The logging system calls GetTID() so it can't be used here.
- perror("pthread_key_create failed");
- abort();
- }
-
- // Initialize tid_array.
- absl::base_internal::SpinLockHolder lock(&tid_lock);
- tid_array = new std::vector<uint32_t>(1);
- (*tid_array)[0] = 1; // ID 0 is never-allocated.
-}
-
-// Return a per-thread small integer ID from pthread's thread-specific data.
+// Fallback implementation of `GetTID` using `pthread_self`.
pid_t GetTID() {
- absl::call_once(tid_once, InitGetTID);
-
- intptr_t tid = reinterpret_cast<intptr_t>(pthread_getspecific(tid_key));
- if (tid != 0) {
- return tid;
- }
-
- int bit; // tid_array[word] = 1u << bit;
- size_t word;
- {
- // Search for the first unused ID.
- absl::base_internal::SpinLockHolder lock(&tid_lock);
- // First search for a word in the array that is not all ones.
- word = 0;
- while (word < tid_array->size() && ~(*tid_array)[word] == 0) {
- ++word;
- }
- if (word == tid_array->size()) {
- tid_array->push_back(0); // No space left, add kBitsPerWord more IDs.
- }
- // Search for a zero bit in the word.
- bit = 0;
- while (bit < kBitsPerWord && (((*tid_array)[word] >> bit) & 1) != 0) {
- ++bit;
- }
- tid = (word * kBitsPerWord) + bit;
- (*tid_array)[word] |= 1u << bit; // Mark the TID as allocated.
- }
-
- if (pthread_setspecific(tid_key, reinterpret_cast<void *>(tid)) != 0) {
- perror("pthread_setspecific failed");
- abort();
- }
-
- return static_cast<pid_t>(tid);
+ // `pthread_t` need not be arithmetic per POSIX; platforms where it isn't
+ // should be handled above.
+ return static_cast<pid_t>(pthread_self());
}
#endif
diff --git a/absl/base/internal/sysinfo_test.cc b/absl/base/internal/sysinfo_test.cc
index 5f9e45f..f305b6c 100644
--- a/absl/base/internal/sysinfo_test.cc
+++ b/absl/base/internal/sysinfo_test.cc
@@ -37,29 +37,6 @@
<< "NumCPUs() should not have the default value of 0";
}
-// Ensure that NominalCPUFrequency returns a reasonable value, or 1.00 on
-// platforms where the CPU frequency is not available through sysfs.
-//
-// POWER is particularly problematic here; some Linux kernels expose the CPU
-// frequency, while others do not. Since we can't predict a priori what a given
-// machine is going to do, just disable this test on POWER on Linux.
-#if !(defined(__linux) && (defined(__ppc64__) || defined(__PPC64__)))
-TEST(SysinfoTest, NominalCPUFrequency) {
- // Linux only exposes the CPU frequency on certain architectures, and
- // Emscripten doesn't expose it at all.
-#if defined(__linux__) && \
- (defined(__aarch64__) || defined(__hppa__) || defined(__mips__) || \
- defined(__riscv) || defined(__s390x__)) || \
- defined(__EMSCRIPTEN__)
- EXPECT_EQ(NominalCPUFrequency(), 1.0)
- << "CPU frequency detection was fixed! Please update unittest.";
-#else
- EXPECT_GE(NominalCPUFrequency(), 1000.0)
- << "NominalCPUFrequency() did not return a reasonable value";
-#endif
-}
-#endif
-
TEST(SysinfoTest, GetTID) {
EXPECT_EQ(GetTID(), GetTID()); // Basic compile and equality test.
#ifdef __native_client__
diff --git a/absl/base/internal/thread_annotations.h b/absl/base/internal/thread_annotations.h
deleted file mode 100644
index 4dab6a9..0000000
--- a/absl/base/internal/thread_annotations.h
+++ /dev/null
@@ -1,271 +0,0 @@
-// Copyright 2019 The Abseil Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-// -----------------------------------------------------------------------------
-// File: thread_annotations.h
-// -----------------------------------------------------------------------------
-//
-// WARNING: This is a backwards compatible header and it will be removed after
-// the migration to prefixed thread annotations is finished; please include
-// "absl/base/thread_annotations.h".
-//
-// This header file contains macro definitions for thread safety annotations
-// that allow developers to document the locking policies of multi-threaded
-// code. The annotations can also help program analysis tools to identify
-// potential thread safety issues.
-//
-// These annotations are implemented using compiler attributes. Using the macros
-// defined here instead of raw attributes allow for portability and future
-// compatibility.
-//
-// When referring to mutexes in the arguments of the attributes, you should
-// use variable names or more complex expressions (e.g. my_object->mutex_)
-// that evaluate to a concrete mutex object whenever possible. If the mutex
-// you want to refer to is not in scope, you may use a member pointer
-// (e.g. &MyClass::mutex_) to refer to a mutex in some (unknown) object.
-
-#ifndef ABSL_BASE_INTERNAL_THREAD_ANNOTATIONS_H_
-#define ABSL_BASE_INTERNAL_THREAD_ANNOTATIONS_H_
-
-#if defined(__clang__)
-#define THREAD_ANNOTATION_ATTRIBUTE__(x) __attribute__((x))
-#else
-#define THREAD_ANNOTATION_ATTRIBUTE__(x) // no-op
-#endif
-
-// GUARDED_BY()
-//
-// Documents if a shared field or global variable needs to be protected by a
-// mutex. GUARDED_BY() allows the user to specify a particular mutex that
-// should be held when accessing the annotated variable.
-//
-// Although this annotation (and PT_GUARDED_BY, below) cannot be applied to
-// local variables, a local variable and its associated mutex can often be
-// combined into a small class or struct, thereby allowing the annotation.
-//
-// Example:
-//
-// class Foo {
-// Mutex mu_;
-// int p1_ GUARDED_BY(mu_);
-// ...
-// };
-#define GUARDED_BY(x) THREAD_ANNOTATION_ATTRIBUTE__(guarded_by(x))
-
-// PT_GUARDED_BY()
-//
-// Documents if the memory location pointed to by a pointer should be guarded
-// by a mutex when dereferencing the pointer.
-//
-// Example:
-// class Foo {
-// Mutex mu_;
-// int *p1_ PT_GUARDED_BY(mu_);
-// ...
-// };
-//
-// Note that a pointer variable to a shared memory location could itself be a
-// shared variable.
-//
-// Example:
-//
-// // `q_`, guarded by `mu1_`, points to a shared memory location that is
-// // guarded by `mu2_`:
-// int *q_ GUARDED_BY(mu1_) PT_GUARDED_BY(mu2_);
-#define PT_GUARDED_BY(x) THREAD_ANNOTATION_ATTRIBUTE__(pt_guarded_by(x))
-
-// ACQUIRED_AFTER() / ACQUIRED_BEFORE()
-//
-// Documents the acquisition order between locks that can be held
-// simultaneously by a thread. For any two locks that need to be annotated
-// to establish an acquisition order, only one of them needs the annotation.
-// (i.e. You don't have to annotate both locks with both ACQUIRED_AFTER
-// and ACQUIRED_BEFORE.)
-//
-// As with GUARDED_BY, this is only applicable to mutexes that are shared
-// fields or global variables.
-//
-// Example:
-//
-// Mutex m1_;
-// Mutex m2_ ACQUIRED_AFTER(m1_);
-#define ACQUIRED_AFTER(...) \
- THREAD_ANNOTATION_ATTRIBUTE__(acquired_after(__VA_ARGS__))
-
-#define ACQUIRED_BEFORE(...) \
- THREAD_ANNOTATION_ATTRIBUTE__(acquired_before(__VA_ARGS__))
-
-// EXCLUSIVE_LOCKS_REQUIRED() / SHARED_LOCKS_REQUIRED()
-//
-// Documents a function that expects a mutex to be held prior to entry.
-// The mutex is expected to be held both on entry to, and exit from, the
-// function.
-//
-// An exclusive lock allows read-write access to the guarded data member(s), and
-// only one thread can acquire a lock exclusively at any one time. A shared lock
-// allows read-only access, and any number of threads can acquire a shared lock
-// concurrently.
-//
-// Generally, non-const methods should be annotated with
-// EXCLUSIVE_LOCKS_REQUIRED, while const methods should be annotated with
-// SHARED_LOCKS_REQUIRED.
-//
-// Example:
-//
-// Mutex mu1, mu2;
-// int a GUARDED_BY(mu1);
-// int b GUARDED_BY(mu2);
-//
-// void foo() EXCLUSIVE_LOCKS_REQUIRED(mu1, mu2) { ... }
-// void bar() const SHARED_LOCKS_REQUIRED(mu1, mu2) { ... }
-#define EXCLUSIVE_LOCKS_REQUIRED(...) \
- THREAD_ANNOTATION_ATTRIBUTE__(exclusive_locks_required(__VA_ARGS__))
-
-#define SHARED_LOCKS_REQUIRED(...) \
- THREAD_ANNOTATION_ATTRIBUTE__(shared_locks_required(__VA_ARGS__))
-
-// LOCKS_EXCLUDED()
-//
-// Documents the locks acquired in the body of the function. These locks
-// cannot be held when calling this function (as Abseil's `Mutex` locks are
-// non-reentrant).
-#define LOCKS_EXCLUDED(...) \
- THREAD_ANNOTATION_ATTRIBUTE__(locks_excluded(__VA_ARGS__))
-
-// LOCK_RETURNED()
-//
-// Documents a function that returns a mutex without acquiring it. For example,
-// a public getter method that returns a pointer to a private mutex should
-// be annotated with LOCK_RETURNED.
-#define LOCK_RETURNED(x) \
- THREAD_ANNOTATION_ATTRIBUTE__(lock_returned(x))
-
-// LOCKABLE
-//
-// Documents if a class/type is a lockable type (such as the `Mutex` class).
-#define LOCKABLE \
- THREAD_ANNOTATION_ATTRIBUTE__(lockable)
-
-// SCOPED_LOCKABLE
-//
-// Documents if a class does RAII locking (such as the `MutexLock` class).
-// The constructor should use `LOCK_FUNCTION()` to specify the mutex that is
-// acquired, and the destructor should use `UNLOCK_FUNCTION()` with no
-// arguments; the analysis will assume that the destructor unlocks whatever the
-// constructor locked.
-#define SCOPED_LOCKABLE \
- THREAD_ANNOTATION_ATTRIBUTE__(scoped_lockable)
-
-// EXCLUSIVE_LOCK_FUNCTION()
-//
-// Documents functions that acquire a lock in the body of a function, and do
-// not release it.
-#define EXCLUSIVE_LOCK_FUNCTION(...) \
- THREAD_ANNOTATION_ATTRIBUTE__(exclusive_lock_function(__VA_ARGS__))
-
-// SHARED_LOCK_FUNCTION()
-//
-// Documents functions that acquire a shared (reader) lock in the body of a
-// function, and do not release it.
-#define SHARED_LOCK_FUNCTION(...) \
- THREAD_ANNOTATION_ATTRIBUTE__(shared_lock_function(__VA_ARGS__))
-
-// UNLOCK_FUNCTION()
-//
-// Documents functions that expect a lock to be held on entry to the function,
-// and release it in the body of the function.
-#define UNLOCK_FUNCTION(...) \
- THREAD_ANNOTATION_ATTRIBUTE__(unlock_function(__VA_ARGS__))
-
-// EXCLUSIVE_TRYLOCK_FUNCTION() / SHARED_TRYLOCK_FUNCTION()
-//
-// Documents functions that try to acquire a lock, and return success or failure
-// (or a non-boolean value that can be interpreted as a boolean).
-// The first argument should be `true` for functions that return `true` on
-// success, or `false` for functions that return `false` on success. The second
-// argument specifies the mutex that is locked on success. If unspecified, this
-// mutex is assumed to be `this`.
-#define EXCLUSIVE_TRYLOCK_FUNCTION(...) \
- THREAD_ANNOTATION_ATTRIBUTE__(exclusive_trylock_function(__VA_ARGS__))
-
-#define SHARED_TRYLOCK_FUNCTION(...) \
- THREAD_ANNOTATION_ATTRIBUTE__(shared_trylock_function(__VA_ARGS__))
-
-// ASSERT_EXCLUSIVE_LOCK() / ASSERT_SHARED_LOCK()
-//
-// Documents functions that dynamically check to see if a lock is held, and fail
-// if it is not held.
-#define ASSERT_EXCLUSIVE_LOCK(...) \
- THREAD_ANNOTATION_ATTRIBUTE__(assert_exclusive_lock(__VA_ARGS__))
-
-#define ASSERT_SHARED_LOCK(...) \
- THREAD_ANNOTATION_ATTRIBUTE__(assert_shared_lock(__VA_ARGS__))
-
-// NO_THREAD_SAFETY_ANALYSIS
-//
-// Turns off thread safety checking within the body of a particular function.
-// This annotation is used to mark functions that are known to be correct, but
-// the locking behavior is more complicated than the analyzer can handle.
-#define NO_THREAD_SAFETY_ANALYSIS \
- THREAD_ANNOTATION_ATTRIBUTE__(no_thread_safety_analysis)
-
-//------------------------------------------------------------------------------
-// Tool-Supplied Annotations
-//------------------------------------------------------------------------------
-
-// TS_UNCHECKED should be placed around lock expressions that are not valid
-// C++ syntax, but which are present for documentation purposes. These
-// annotations will be ignored by the analysis.
-#define TS_UNCHECKED(x) ""
-
-// TS_FIXME is used to mark lock expressions that are not valid C++ syntax.
-// It is used by automated tools to mark and disable invalid expressions.
-// The annotation should either be fixed, or changed to TS_UNCHECKED.
-#define TS_FIXME(x) ""
-
-// Like NO_THREAD_SAFETY_ANALYSIS, this turns off checking within the body of
-// a particular function. However, this attribute is used to mark functions
-// that are incorrect and need to be fixed. It is used by automated tools to
-// avoid breaking the build when the analysis is updated.
-// Code owners are expected to eventually fix the routine.
-#define NO_THREAD_SAFETY_ANALYSIS_FIXME NO_THREAD_SAFETY_ANALYSIS
-
-// Similar to NO_THREAD_SAFETY_ANALYSIS_FIXME, this macro marks a GUARDED_BY
-// annotation that needs to be fixed, because it is producing thread safety
-// warning. It disables the GUARDED_BY.
-#define GUARDED_BY_FIXME(x)
-
-// Disables warnings for a single read operation. This can be used to avoid
-// warnings when it is known that the read is not actually involved in a race,
-// but the compiler cannot confirm that.
-#define TS_UNCHECKED_READ(x) thread_safety_analysis::ts_unchecked_read(x)
-
-
-namespace thread_safety_analysis {
-
-// Takes a reference to a guarded data member, and returns an unguarded
-// reference.
-template <typename T>
-inline const T& ts_unchecked_read(const T& v) NO_THREAD_SAFETY_ANALYSIS {
- return v;
-}
-
-template <typename T>
-inline T& ts_unchecked_read(T& v) NO_THREAD_SAFETY_ANALYSIS {
- return v;
-}
-
-} // namespace thread_safety_analysis
-
-#endif // ABSL_BASE_INTERNAL_THREAD_ANNOTATIONS_H_
diff --git a/absl/base/internal/thread_identity.cc b/absl/base/internal/thread_identity.cc
index 9950e63..0471a25 100644
--- a/absl/base/internal/thread_identity.cc
+++ b/absl/base/internal/thread_identity.cc
@@ -14,10 +14,14 @@
#include "absl/base/internal/thread_identity.h"
-#ifndef _WIN32
+#if !defined(_WIN32) || defined(__MINGW32__)
#include <pthread.h>
+#ifndef __wasi__
+// WASI does not provide this header, either way we disable use
+// of signals with it below.
#include <signal.h>
#endif
+#endif
#include <atomic>
#include <cassert>
@@ -56,19 +60,21 @@
// *different* instances of this ptr.
// Apple platforms have the visibility attribute, but issue a compile warning
// that protected visibility is unsupported.
+ABSL_CONST_INIT // Must come before __attribute__((visibility("protected")))
#if ABSL_HAVE_ATTRIBUTE(visibility) && !defined(__APPLE__)
-__attribute__((visibility("protected")))
+ __attribute__((visibility("protected")))
#endif // ABSL_HAVE_ATTRIBUTE(visibility) && !defined(__APPLE__)
#if ABSL_PER_THREAD_TLS
-// Prefer __thread to thread_local as benchmarks indicate it is a bit faster.
-ABSL_PER_THREAD_TLS_KEYWORD ThreadIdentity* thread_identity_ptr = nullptr;
+ // Prefer __thread to thread_local as benchmarks indicate it is a bit
+ // faster.
+ ABSL_PER_THREAD_TLS_KEYWORD ThreadIdentity* thread_identity_ptr = nullptr;
#elif defined(ABSL_HAVE_THREAD_LOCAL)
-thread_local ThreadIdentity* thread_identity_ptr = nullptr;
+ thread_local ThreadIdentity* thread_identity_ptr = nullptr;
#endif // ABSL_PER_THREAD_TLS
#endif // TLS or CPP11
-void SetCurrentThreadIdentity(
- ThreadIdentity* identity, ThreadIdentityReclaimerFunction reclaimer) {
+void SetCurrentThreadIdentity(ThreadIdentity* identity,
+ ThreadIdentityReclaimerFunction reclaimer) {
assert(CurrentThreadIdentityIfPresent() == nullptr);
// Associate our destructor.
// NOTE: This call to pthread_setspecific is currently the only immovable
@@ -78,10 +84,12 @@
absl::call_once(init_thread_identity_key_once, AllocateThreadIdentityKey,
reclaimer);
-#if defined(__EMSCRIPTEN__) || defined(__MINGW32__)
- // Emscripten and MinGW pthread implementations does not support signals.
- // See https://kripken.github.io/emscripten-site/docs/porting/pthreads.html
- // for more information.
+#if defined(__wasi__) || defined(__EMSCRIPTEN__) || defined(__MINGW32__) || \
+ defined(__hexagon__)
+ // Emscripten, WASI and MinGW pthread implementations does not support
+ // signals. See
+ // https://kripken.github.io/emscripten-site/docs/porting/pthreads.html for
+ // more information.
pthread_setspecific(thread_identity_pthread_key,
reinterpret_cast<void*>(identity));
#else
@@ -133,7 +141,7 @@
ABSL_THREAD_IDENTITY_MODE == ABSL_THREAD_IDENTITY_MODE_USE_CPP11
thread_identity_ptr = nullptr;
#elif ABSL_THREAD_IDENTITY_MODE == \
- ABSL_THREAD_IDENTITY_MODE_USE_POSIX_SETSPECIFIC
+ ABSL_THREAD_IDENTITY_MODE_USE_POSIX_SETSPECIFIC
// pthread_setspecific expected to clear value on destruction
assert(CurrentThreadIdentityIfPresent() == nullptr);
#endif
diff --git a/absl/base/internal/thread_identity.h b/absl/base/internal/thread_identity.h
index 659694b..b6e917c 100644
--- a/absl/base/internal/thread_identity.h
+++ b/absl/base/internal/thread_identity.h
@@ -62,8 +62,8 @@
return reinterpret_cast<ThreadIdentity*>(this);
}
- PerThreadSynch *next; // Circular waiter queue; initialized to 0.
- PerThreadSynch *skip; // If non-zero, all entries in Mutex queue
+ PerThreadSynch* next; // Circular waiter queue; initialized to 0.
+ PerThreadSynch* skip; // If non-zero, all entries in Mutex queue
// up to and including "skip" have same
// condition as this, and will be woken later
bool may_skip; // if false while on mutex queue, a mutex unlocker
@@ -104,10 +104,7 @@
//
// Transitions from kAvailable to kQueued require no barrier, they
// are externally ordered by the Mutex.
- enum State {
- kAvailable,
- kQueued
- };
+ enum State { kAvailable, kQueued };
std::atomic<State> state;
// The wait parameters of the current wait. waitp is null if the
@@ -122,18 +119,22 @@
// pointer unchanged.
SynchWaitParams* waitp;
- intptr_t readers; // Number of readers in mutex.
+ intptr_t readers; // Number of readers in mutex.
// When priority will next be read (cycles).
int64_t next_priority_read_cycles;
// Locks held; used during deadlock detection.
// Allocated in Synch_GetAllLocks() and freed in ReclaimThreadIdentity().
- SynchLocksHeld *all_locks;
+ SynchLocksHeld* all_locks;
};
// The instances of this class are allocated in NewThreadIdentity() with an
// alignment of PerThreadSynch::kAlignment.
+//
+// NOTE: The layout of fields in this structure is critical, please do not
+// add, remove, or modify the field placements without fully auditing the
+// layout.
struct ThreadIdentity {
// Must be the first member. The Mutex implementation requires that
// the PerThreadSynch object associated with each thread is
@@ -143,7 +144,7 @@
// Private: Reserved for absl::synchronization_internal::Waiter.
struct WaiterState {
- alignas(void*) char data[128];
+ alignas(void*) char data[256];
} waiter_state;
// Used by PerThreadSem::{Get,Set}ThreadBlockedCounter().
@@ -166,7 +167,10 @@
//
// Does not malloc(*), and is async-signal safe.
// [*] Technically pthread_setspecific() does malloc on first use; however this
-// is handled internally within tcmalloc's initialization already.
+// is handled internally within tcmalloc's initialization already. Note that
+// darwin does *not* use tcmalloc, so this can catch you if using MallocHooks
+// on Apple platforms. Whatever function is calling your MallocHooks will need
+// to watch for recursion on Apple platforms.
//
// New ThreadIdentity objects can be constructed and associated with a thread
// by calling GetOrCreateCurrentThreadIdentity() in per-thread-sem.h.
@@ -213,7 +217,7 @@
#define ABSL_THREAD_IDENTITY_MODE ABSL_THREAD_IDENTITY_MODE_USE_CPP11
#elif defined(__APPLE__) && defined(ABSL_HAVE_THREAD_LOCAL)
#define ABSL_THREAD_IDENTITY_MODE ABSL_THREAD_IDENTITY_MODE_USE_CPP11
-#elif ABSL_PER_THREAD_TLS && defined(__GOOGLE_GRTE_VERSION__) && \
+#elif ABSL_PER_THREAD_TLS && defined(__GOOGLE_GRTE_VERSION__) && \
(__GOOGLE_GRTE_VERSION__ >= 20140228L)
// Support for async-safe TLS was specifically added in GRTEv4. It's not
// present in the upstream eglibc.
diff --git a/absl/base/internal/thread_identity_test.cc b/absl/base/internal/thread_identity_test.cc
index 46a6f74..5f17553 100644
--- a/absl/base/internal/thread_identity_test.cc
+++ b/absl/base/internal/thread_identity_test.cc
@@ -95,7 +95,7 @@
}
TEST(ThreadIdentityTest, ReusedThreadIdentityMutexTest) {
- // This test repeatly creates and joins a series of threads, each of
+ // This test repeatedly creates and joins a series of threads, each of
// which acquires and releases shared Mutex locks. This verifies
// Mutex operations work correctly under a reused
// ThreadIdentity. Note that the most likely failure mode of this
diff --git a/absl/base/internal/throw_delegate.cc b/absl/base/internal/throw_delegate.cc
index c260ff1..337e870 100644
--- a/absl/base/internal/throw_delegate.cc
+++ b/absl/base/internal/throw_delegate.cc
@@ -26,22 +26,13 @@
ABSL_NAMESPACE_BEGIN
namespace base_internal {
-// NOTE: The various STL exception throwing functions are placed within the
-// #ifdef blocks so the symbols aren't exposed on platforms that don't support
-// them, such as the Android NDK. For example, ANGLE fails to link when building
-// within AOSP without them, since the STL functions don't exist.
-namespace {
-#ifdef ABSL_HAVE_EXCEPTIONS
-template <typename T>
-[[noreturn]] void Throw(const T& error) {
- throw error;
-}
-#endif
-} // namespace
+// NOTE: The exception types, like `std::logic_error`, do not exist on all
+// platforms. (For example, the Android NDK does not have them.)
+// Therefore, their use must be guarded by `#ifdef` or equivalent.
void ThrowStdLogicError(const std::string& what_arg) {
#ifdef ABSL_HAVE_EXCEPTIONS
- Throw(std::logic_error(what_arg));
+ throw std::logic_error(what_arg);
#else
ABSL_RAW_LOG(FATAL, "%s", what_arg.c_str());
std::abort();
@@ -49,7 +40,7 @@
}
void ThrowStdLogicError(const char* what_arg) {
#ifdef ABSL_HAVE_EXCEPTIONS
- Throw(std::logic_error(what_arg));
+ throw std::logic_error(what_arg);
#else
ABSL_RAW_LOG(FATAL, "%s", what_arg);
std::abort();
@@ -57,7 +48,7 @@
}
void ThrowStdInvalidArgument(const std::string& what_arg) {
#ifdef ABSL_HAVE_EXCEPTIONS
- Throw(std::invalid_argument(what_arg));
+ throw std::invalid_argument(what_arg);
#else
ABSL_RAW_LOG(FATAL, "%s", what_arg.c_str());
std::abort();
@@ -65,7 +56,7 @@
}
void ThrowStdInvalidArgument(const char* what_arg) {
#ifdef ABSL_HAVE_EXCEPTIONS
- Throw(std::invalid_argument(what_arg));
+ throw std::invalid_argument(what_arg);
#else
ABSL_RAW_LOG(FATAL, "%s", what_arg);
std::abort();
@@ -74,7 +65,7 @@
void ThrowStdDomainError(const std::string& what_arg) {
#ifdef ABSL_HAVE_EXCEPTIONS
- Throw(std::domain_error(what_arg));
+ throw std::domain_error(what_arg);
#else
ABSL_RAW_LOG(FATAL, "%s", what_arg.c_str());
std::abort();
@@ -82,7 +73,7 @@
}
void ThrowStdDomainError(const char* what_arg) {
#ifdef ABSL_HAVE_EXCEPTIONS
- Throw(std::domain_error(what_arg));
+ throw std::domain_error(what_arg);
#else
ABSL_RAW_LOG(FATAL, "%s", what_arg);
std::abort();
@@ -91,7 +82,7 @@
void ThrowStdLengthError(const std::string& what_arg) {
#ifdef ABSL_HAVE_EXCEPTIONS
- Throw(std::length_error(what_arg));
+ throw std::length_error(what_arg);
#else
ABSL_RAW_LOG(FATAL, "%s", what_arg.c_str());
std::abort();
@@ -99,7 +90,7 @@
}
void ThrowStdLengthError(const char* what_arg) {
#ifdef ABSL_HAVE_EXCEPTIONS
- Throw(std::length_error(what_arg));
+ throw std::length_error(what_arg);
#else
ABSL_RAW_LOG(FATAL, "%s", what_arg);
std::abort();
@@ -108,7 +99,7 @@
void ThrowStdOutOfRange(const std::string& what_arg) {
#ifdef ABSL_HAVE_EXCEPTIONS
- Throw(std::out_of_range(what_arg));
+ throw std::out_of_range(what_arg);
#else
ABSL_RAW_LOG(FATAL, "%s", what_arg.c_str());
std::abort();
@@ -116,7 +107,7 @@
}
void ThrowStdOutOfRange(const char* what_arg) {
#ifdef ABSL_HAVE_EXCEPTIONS
- Throw(std::out_of_range(what_arg));
+ throw std::out_of_range(what_arg);
#else
ABSL_RAW_LOG(FATAL, "%s", what_arg);
std::abort();
@@ -125,7 +116,7 @@
void ThrowStdRuntimeError(const std::string& what_arg) {
#ifdef ABSL_HAVE_EXCEPTIONS
- Throw(std::runtime_error(what_arg));
+ throw std::runtime_error(what_arg);
#else
ABSL_RAW_LOG(FATAL, "%s", what_arg.c_str());
std::abort();
@@ -133,7 +124,7 @@
}
void ThrowStdRuntimeError(const char* what_arg) {
#ifdef ABSL_HAVE_EXCEPTIONS
- Throw(std::runtime_error(what_arg));
+ throw std::runtime_error(what_arg);
#else
ABSL_RAW_LOG(FATAL, "%s", what_arg);
std::abort();
@@ -142,7 +133,7 @@
void ThrowStdRangeError(const std::string& what_arg) {
#ifdef ABSL_HAVE_EXCEPTIONS
- Throw(std::range_error(what_arg));
+ throw std::range_error(what_arg);
#else
ABSL_RAW_LOG(FATAL, "%s", what_arg.c_str());
std::abort();
@@ -150,7 +141,7 @@
}
void ThrowStdRangeError(const char* what_arg) {
#ifdef ABSL_HAVE_EXCEPTIONS
- Throw(std::range_error(what_arg));
+ throw std::range_error(what_arg);
#else
ABSL_RAW_LOG(FATAL, "%s", what_arg);
std::abort();
@@ -159,7 +150,7 @@
void ThrowStdOverflowError(const std::string& what_arg) {
#ifdef ABSL_HAVE_EXCEPTIONS
- Throw(std::overflow_error(what_arg));
+ throw std::overflow_error(what_arg);
#else
ABSL_RAW_LOG(FATAL, "%s", what_arg.c_str());
std::abort();
@@ -167,7 +158,7 @@
}
void ThrowStdOverflowError(const char* what_arg) {
#ifdef ABSL_HAVE_EXCEPTIONS
- Throw(std::overflow_error(what_arg));
+ throw std::overflow_error(what_arg);
#else
ABSL_RAW_LOG(FATAL, "%s", what_arg);
std::abort();
@@ -176,7 +167,7 @@
void ThrowStdUnderflowError(const std::string& what_arg) {
#ifdef ABSL_HAVE_EXCEPTIONS
- Throw(std::underflow_error(what_arg));
+ throw std::underflow_error(what_arg);
#else
ABSL_RAW_LOG(FATAL, "%s", what_arg.c_str());
std::abort();
@@ -184,7 +175,7 @@
}
void ThrowStdUnderflowError(const char* what_arg) {
#ifdef ABSL_HAVE_EXCEPTIONS
- Throw(std::underflow_error(what_arg));
+ throw std::underflow_error(what_arg);
#else
ABSL_RAW_LOG(FATAL, "%s", what_arg);
std::abort();
@@ -193,7 +184,7 @@
void ThrowStdBadFunctionCall() {
#ifdef ABSL_HAVE_EXCEPTIONS
- Throw(std::bad_function_call());
+ throw std::bad_function_call();
#else
std::abort();
#endif
@@ -201,7 +192,7 @@
void ThrowStdBadAlloc() {
#ifdef ABSL_HAVE_EXCEPTIONS
- Throw(std::bad_alloc());
+ throw std::bad_alloc();
#else
std::abort();
#endif
diff --git a/absl/base/internal/unaligned_access.h b/absl/base/internal/unaligned_access.h
index 093dd9b..4fea457 100644
--- a/absl/base/internal/unaligned_access.h
+++ b/absl/base/internal/unaligned_access.h
@@ -23,6 +23,7 @@
#include "absl/base/attributes.h"
#include "absl/base/config.h"
+#include "absl/base/nullability.h"
// unaligned APIs
@@ -35,29 +36,35 @@
ABSL_NAMESPACE_BEGIN
namespace base_internal {
-inline uint16_t UnalignedLoad16(const void *p) {
+inline uint16_t UnalignedLoad16(absl::Nonnull<const void *> p) {
uint16_t t;
memcpy(&t, p, sizeof t);
return t;
}
-inline uint32_t UnalignedLoad32(const void *p) {
+inline uint32_t UnalignedLoad32(absl::Nonnull<const void *> p) {
uint32_t t;
memcpy(&t, p, sizeof t);
return t;
}
-inline uint64_t UnalignedLoad64(const void *p) {
+inline uint64_t UnalignedLoad64(absl::Nonnull<const void *> p) {
uint64_t t;
memcpy(&t, p, sizeof t);
return t;
}
-inline void UnalignedStore16(void *p, uint16_t v) { memcpy(p, &v, sizeof v); }
+inline void UnalignedStore16(absl::Nonnull<void *> p, uint16_t v) {
+ memcpy(p, &v, sizeof v);
+}
-inline void UnalignedStore32(void *p, uint32_t v) { memcpy(p, &v, sizeof v); }
+inline void UnalignedStore32(absl::Nonnull<void *> p, uint32_t v) {
+ memcpy(p, &v, sizeof v);
+}
-inline void UnalignedStore64(void *p, uint64_t v) { memcpy(p, &v, sizeof v); }
+inline void UnalignedStore64(absl::Nonnull<void *> p, uint64_t v) {
+ memcpy(p, &v, sizeof v);
+}
} // namespace base_internal
ABSL_NAMESPACE_END
diff --git a/absl/base/internal/unscaledcycleclock.cc b/absl/base/internal/unscaledcycleclock.cc
index 1545288..05e0e7b 100644
--- a/absl/base/internal/unscaledcycleclock.cc
+++ b/absl/base/internal/unscaledcycleclock.cc
@@ -24,8 +24,13 @@
#ifdef __GLIBC__
#include <sys/platform/ppc.h>
#elif defined(__FreeBSD__)
-#include <sys/sysctl.h>
+// clang-format off
+// This order does actually matter =(.
#include <sys/types.h>
+#include <sys/sysctl.h>
+// clang-format on
+
+#include "absl/base/call_once.h"
#endif
#endif
@@ -49,12 +54,6 @@
#elif defined(__x86_64__)
-int64_t UnscaledCycleClock::Now() {
- uint64_t low, high;
- __asm__ volatile("rdtsc" : "=a"(low), "=d"(high));
- return (high << 32) | low;
-}
-
double UnscaledCycleClock::Frequency() {
return base_internal::NominalCPUFrequency();
}
@@ -72,13 +71,12 @@
#else
int32_t tbu, tbl, tmp;
asm volatile(
- "0:\n"
"mftbu %[hi32]\n"
"mftb %[lo32]\n"
"mftbu %[tmp]\n"
"cmpw %[tmp],%[hi32]\n"
- "bne 0b\n"
- : [ hi32 ] "=r"(tbu), [ lo32 ] "=r"(tbl), [ tmp ] "=r"(tmp));
+ "bne $-16\n" // Retry on failure.
+ : [hi32] "=r"(tbu), [lo32] "=r"(tbl), [tmp] "=r"(tmp));
return (static_cast<int64_t>(tbu) << 32) | tbl;
#endif
#endif
@@ -87,6 +85,10 @@
double UnscaledCycleClock::Frequency() {
#ifdef __GLIBC__
return __ppc_get_timebase_freq();
+#elif defined(_AIX)
+ // This is the same constant value as returned by
+ // __ppc_get_timebase_freq().
+ return static_cast<double>(512000000);
#elif defined(__FreeBSD__)
static once_flag init_timebase_frequency_once;
static double timebase_frequency = 0.0;
@@ -119,6 +121,18 @@
return aarch64_timer_frequency;
}
+#elif defined(__riscv)
+
+int64_t UnscaledCycleClock::Now() {
+ int64_t virtual_timer_value;
+ asm volatile("rdcycle %0" : "=r"(virtual_timer_value));
+ return virtual_timer_value;
+}
+
+double UnscaledCycleClock::Frequency() {
+ return base_internal::NominalCPUFrequency();
+}
+
#elif defined(_M_IX86) || defined(_M_X64)
#pragma intrinsic(__rdtsc)
diff --git a/absl/base/internal/unscaledcycleclock.h b/absl/base/internal/unscaledcycleclock.h
index 82f2c87..cc1276b 100644
--- a/absl/base/internal/unscaledcycleclock.h
+++ b/absl/base/internal/unscaledcycleclock.h
@@ -42,49 +42,11 @@
#include <TargetConditionals.h>
#endif
-#include "absl/base/port.h"
-
-// The following platforms have an implementation of a hardware counter.
-#if defined(__i386__) || defined(__x86_64__) || defined(__aarch64__) || \
- defined(__powerpc__) || defined(__ppc__) || \
- defined(_M_IX86) || defined(_M_X64)
-#define ABSL_HAVE_UNSCALED_CYCLECLOCK_IMPLEMENTATION 1
-#else
-#define ABSL_HAVE_UNSCALED_CYCLECLOCK_IMPLEMENTATION 0
-#endif
-
-// The following platforms often disable access to the hardware
-// counter (through a sandbox) even if the underlying hardware has a
-// usable counter. The CycleTimer interface also requires a *scaled*
-// CycleClock that runs at atleast 1 MHz. We've found some Android
-// ARM64 devices where this is not the case, so we disable it by
-// default on Android ARM64.
-#if defined(__native_client__) || \
- (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) || \
- (defined(__ANDROID__) && defined(__aarch64__))
-#define ABSL_USE_UNSCALED_CYCLECLOCK_DEFAULT 0
-#else
-#define ABSL_USE_UNSCALED_CYCLECLOCK_DEFAULT 1
-#endif
-
-// UnscaledCycleClock is an optional internal feature.
-// Use "#if ABSL_USE_UNSCALED_CYCLECLOCK" to test for its presence.
-// Can be overridden at compile-time via -DABSL_USE_UNSCALED_CYCLECLOCK=0|1
-#if !defined(ABSL_USE_UNSCALED_CYCLECLOCK)
-#define ABSL_USE_UNSCALED_CYCLECLOCK \
- (ABSL_HAVE_UNSCALED_CYCLECLOCK_IMPLEMENTATION && \
- ABSL_USE_UNSCALED_CYCLECLOCK_DEFAULT)
-#endif
+#include "absl/base/config.h"
+#include "absl/base/internal/unscaledcycleclock_config.h"
#if ABSL_USE_UNSCALED_CYCLECLOCK
-// This macro can be used to test if UnscaledCycleClock::Frequency()
-// is NominalCPUFrequency() on a particular platform.
-#if (defined(__i386__) || defined(__x86_64__) || \
- defined(_M_IX86) || defined(_M_X64))
-#define ABSL_INTERNAL_UNSCALED_CYCLECLOCK_FREQUENCY_IS_CPU_FREQUENCY
-#endif
-
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace time_internal {
@@ -115,6 +77,16 @@
friend class base_internal::UnscaledCycleClockWrapperForInitializeFrequency;
};
+#if defined(__x86_64__)
+
+inline int64_t UnscaledCycleClock::Now() {
+ uint64_t low, high;
+ __asm__ volatile("rdtsc" : "=a"(low), "=d"(high));
+ return static_cast<int64_t>((high << 32) | low);
+}
+
+#endif
+
} // namespace base_internal
ABSL_NAMESPACE_END
} // namespace absl
diff --git a/absl/base/internal/unscaledcycleclock_config.h b/absl/base/internal/unscaledcycleclock_config.h
new file mode 100644
index 0000000..24b324a
--- /dev/null
+++ b/absl/base/internal/unscaledcycleclock_config.h
@@ -0,0 +1,62 @@
+// Copyright 2022 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef ABSL_BASE_INTERNAL_UNSCALEDCYCLECLOCK_CONFIG_H_
+#define ABSL_BASE_INTERNAL_UNSCALEDCYCLECLOCK_CONFIG_H_
+
+#if defined(__APPLE__)
+#include <TargetConditionals.h>
+#endif
+
+// The following platforms have an implementation of a hardware counter.
+#if defined(__i386__) || defined(__x86_64__) || defined(__aarch64__) || \
+ defined(__powerpc__) || defined(__ppc__) || defined(__riscv) || \
+ defined(_M_IX86) || (defined(_M_X64) && !defined(_M_ARM64EC))
+#define ABSL_HAVE_UNSCALED_CYCLECLOCK_IMPLEMENTATION 1
+#else
+#define ABSL_HAVE_UNSCALED_CYCLECLOCK_IMPLEMENTATION 0
+#endif
+
+// The following platforms often disable access to the hardware
+// counter (through a sandbox) even if the underlying hardware has a
+// usable counter. The CycleTimer interface also requires a *scaled*
+// CycleClock that runs at atleast 1 MHz. We've found some Android
+// ARM64 devices where this is not the case, so we disable it by
+// default on Android ARM64.
+#if defined(__native_client__) || (defined(__APPLE__)) || \
+ (defined(__ANDROID__) && defined(__aarch64__))
+#define ABSL_USE_UNSCALED_CYCLECLOCK_DEFAULT 0
+#else
+#define ABSL_USE_UNSCALED_CYCLECLOCK_DEFAULT 1
+#endif
+
+// UnscaledCycleClock is an optional internal feature.
+// Use "#if ABSL_USE_UNSCALED_CYCLECLOCK" to test for its presence.
+// Can be overridden at compile-time via -DABSL_USE_UNSCALED_CYCLECLOCK=0|1
+#if !defined(ABSL_USE_UNSCALED_CYCLECLOCK)
+#define ABSL_USE_UNSCALED_CYCLECLOCK \
+ (ABSL_HAVE_UNSCALED_CYCLECLOCK_IMPLEMENTATION && \
+ ABSL_USE_UNSCALED_CYCLECLOCK_DEFAULT)
+#endif
+
+#if ABSL_USE_UNSCALED_CYCLECLOCK
+// This macro can be used to test if UnscaledCycleClock::Frequency()
+// is NominalCPUFrequency() on a particular platform.
+#if (defined(__i386__) || defined(__x86_64__) || defined(__riscv) || \
+ defined(_M_IX86) || defined(_M_X64))
+#define ABSL_INTERNAL_UNSCALED_CYCLECLOCK_FREQUENCY_IS_CPU_FREQUENCY
+#endif
+#endif
+
+#endif // ABSL_BASE_INTERNAL_UNSCALEDCYCLECLOCK_CONFIG_H_
diff --git a/absl/base/invoke_test.cc b/absl/base/invoke_test.cc
index bcdef36..7be26f6 100644
--- a/absl/base/invoke_test.cc
+++ b/absl/base/invoke_test.cc
@@ -31,6 +31,14 @@
int Function(int a, int b) { return a - b; }
+void VoidFunction(int& a, int& b) {
+ a += b;
+ b = a - b;
+ a -= b;
+}
+
+int ZeroArgFunction() { return -1937; }
+
int Sink(std::unique_ptr<int> p) {
return *p;
}
@@ -223,6 +231,100 @@
EXPECT_THAT(CallMaybeWithArg(Factory), ::testing::Pointee(42));
}
+TEST(IsInvocableRTest, CallableExactMatch) {
+ static_assert(
+ base_internal::is_invocable_r<int, decltype(Function), int, int>::value,
+ "Should be true for exact match of types on a free function");
+}
+
+TEST(IsInvocableRTest, CallableArgumentConversionMatch) {
+ static_assert(
+ base_internal::is_invocable_r<int, decltype(Function), char, int>::value,
+ "Should be true for convertible argument type");
+}
+
+TEST(IsInvocableRTest, CallableReturnConversionMatch) {
+ static_assert(base_internal::is_invocable_r<double, decltype(Function), int,
+ int>::value,
+ "Should be true for convertible return type");
+}
+
+TEST(IsInvocableRTest, CallableReturnVoid) {
+ static_assert(base_internal::is_invocable_r<void, decltype(VoidFunction),
+ int&, int&>::value,
+ "Should be true for void expected and actual return types");
+ static_assert(
+ base_internal::is_invocable_r<void, decltype(Function), int, int>::value,
+ "Should be true for void expected and non-void actual return types");
+}
+
+TEST(IsInvocableRTest, CallableRefQualifierMismatch) {
+ static_assert(!base_internal::is_invocable_r<void, decltype(VoidFunction),
+ int&, const int&>::value,
+ "Should be false for reference constness mismatch");
+ static_assert(!base_internal::is_invocable_r<void, decltype(VoidFunction),
+ int&&, int&>::value,
+ "Should be false for reference value category mismatch");
+}
+
+TEST(IsInvocableRTest, CallableArgumentTypeMismatch) {
+ static_assert(!base_internal::is_invocable_r<int, decltype(Function),
+ std::string, int>::value,
+ "Should be false for argument type mismatch");
+}
+
+TEST(IsInvocableRTest, CallableReturnTypeMismatch) {
+ static_assert(!base_internal::is_invocable_r<std::string, decltype(Function),
+ int, int>::value,
+ "Should be false for return type mismatch");
+}
+
+TEST(IsInvocableRTest, CallableTooFewArgs) {
+ static_assert(
+ !base_internal::is_invocable_r<int, decltype(Function), int>::value,
+ "Should be false for too few arguments");
+}
+
+TEST(IsInvocableRTest, CallableTooManyArgs) {
+ static_assert(!base_internal::is_invocable_r<int, decltype(Function), int,
+ int, int>::value,
+ "Should be false for too many arguments");
+}
+
+TEST(IsInvocableRTest, MemberFunctionAndReference) {
+ static_assert(base_internal::is_invocable_r<int, decltype(&Class::Method),
+ Class&, int, int>::value,
+ "Should be true for exact match of types on a member function "
+ "and class reference");
+}
+
+TEST(IsInvocableRTest, MemberFunctionAndPointer) {
+ static_assert(base_internal::is_invocable_r<int, decltype(&Class::Method),
+ Class*, int, int>::value,
+ "Should be true for exact match of types on a member function "
+ "and class pointer");
+}
+
+TEST(IsInvocableRTest, DataMemberAndReference) {
+ static_assert(base_internal::is_invocable_r<int, decltype(&Class::member),
+ Class&>::value,
+ "Should be true for exact match of types on a data member and "
+ "class reference");
+}
+
+TEST(IsInvocableRTest, DataMemberAndPointer) {
+ static_assert(base_internal::is_invocable_r<int, decltype(&Class::member),
+ Class*>::value,
+ "Should be true for exact match of types on a data member and "
+ "class pointer");
+}
+
+TEST(IsInvocableRTest, CallableZeroArgs) {
+ static_assert(
+ base_internal::is_invocable_r<int, decltype(ZeroArgFunction)>::value,
+ "Should be true for exact match for a zero-arg free function");
+}
+
} // namespace
} // namespace base_internal
ABSL_NAMESPACE_END
diff --git a/absl/base/log_severity.cc b/absl/base/log_severity.cc
index 72312af..8e7bbbc 100644
--- a/absl/base/log_severity.cc
+++ b/absl/base/log_severity.cc
@@ -16,6 +16,9 @@
#include <ostream>
+#include "absl/base/attributes.h"
+#include "absl/base/config.h"
+
namespace absl {
ABSL_NAMESPACE_BEGIN
@@ -23,5 +26,31 @@
if (s == absl::NormalizeLogSeverity(s)) return os << absl::LogSeverityName(s);
return os << "absl::LogSeverity(" << static_cast<int>(s) << ")";
}
+
+std::ostream& operator<<(std::ostream& os, absl::LogSeverityAtLeast s) {
+ switch (s) {
+ case absl::LogSeverityAtLeast::kInfo:
+ case absl::LogSeverityAtLeast::kWarning:
+ case absl::LogSeverityAtLeast::kError:
+ case absl::LogSeverityAtLeast::kFatal:
+ return os << ">=" << static_cast<absl::LogSeverity>(s);
+ case absl::LogSeverityAtLeast::kInfinity:
+ return os << "INFINITY";
+ }
+ return os;
+}
+
+std::ostream& operator<<(std::ostream& os, absl::LogSeverityAtMost s) {
+ switch (s) {
+ case absl::LogSeverityAtMost::kInfo:
+ case absl::LogSeverityAtMost::kWarning:
+ case absl::LogSeverityAtMost::kError:
+ case absl::LogSeverityAtMost::kFatal:
+ return os << "<=" << static_cast<absl::LogSeverity>(s);
+ case absl::LogSeverityAtMost::kNegativeInfinity:
+ return os << "NEGATIVE_INFINITY";
+ }
+ return os;
+}
ABSL_NAMESPACE_END
} // namespace absl
diff --git a/absl/base/log_severity.h b/absl/base/log_severity.h
index 2236422..de9702a 100644
--- a/absl/base/log_severity.h
+++ b/absl/base/log_severity.h
@@ -64,6 +64,8 @@
// --my_log_level=info
// --my_log_level=0
//
+// `DFATAL` and `kLogDebugFatal` are similarly accepted.
+//
// Unparsing a flag produces the same result as `absl::LogSeverityName()` for
// the standard levels and a base-ten integer otherwise.
enum class LogSeverity : int {
@@ -82,18 +84,28 @@
absl::LogSeverity::kError, absl::LogSeverity::kFatal}};
}
+// `absl::kLogDebugFatal` equals `absl::LogSeverity::kFatal` in debug builds
+// (i.e. when `NDEBUG` is not defined) and `absl::LogSeverity::kError`
+// otherwise. Avoid ODR-using this variable as it has internal linkage and thus
+// distinct storage in different TUs.
+#ifdef NDEBUG
+static constexpr absl::LogSeverity kLogDebugFatal = absl::LogSeverity::kError;
+#else
+static constexpr absl::LogSeverity kLogDebugFatal = absl::LogSeverity::kFatal;
+#endif
+
// LogSeverityName()
//
// Returns the all-caps string representation (e.g. "INFO") of the specified
// severity level if it is one of the standard levels and "UNKNOWN" otherwise.
constexpr const char* LogSeverityName(absl::LogSeverity s) {
- return s == absl::LogSeverity::kInfo
- ? "INFO"
- : s == absl::LogSeverity::kWarning
- ? "WARNING"
- : s == absl::LogSeverity::kError
- ? "ERROR"
- : s == absl::LogSeverity::kFatal ? "FATAL" : "UNKNOWN";
+ switch (s) {
+ case absl::LogSeverity::kInfo: return "INFO";
+ case absl::LogSeverity::kWarning: return "WARNING";
+ case absl::LogSeverity::kError: return "ERROR";
+ case absl::LogSeverity::kFatal: return "FATAL";
+ }
+ return "UNKNOWN";
}
// NormalizeLogSeverity()
@@ -101,9 +113,10 @@
// Values less than `kInfo` normalize to `kInfo`; values greater than `kFatal`
// normalize to `kError` (**NOT** `kFatal`).
constexpr absl::LogSeverity NormalizeLogSeverity(absl::LogSeverity s) {
- return s < absl::LogSeverity::kInfo
- ? absl::LogSeverity::kInfo
- : s > absl::LogSeverity::kFatal ? absl::LogSeverity::kError : s;
+ absl::LogSeverity n = s;
+ if (n < absl::LogSeverity::kInfo) n = absl::LogSeverity::kInfo;
+ if (n > absl::LogSeverity::kFatal) n = absl::LogSeverity::kError;
+ return n;
}
constexpr absl::LogSeverity NormalizeLogSeverity(int s) {
return absl::NormalizeLogSeverity(static_cast<absl::LogSeverity>(s));
@@ -115,6 +128,57 @@
// unspecified; do not rely on it.
std::ostream& operator<<(std::ostream& os, absl::LogSeverity s);
+// Enums representing a lower bound for LogSeverity. APIs that only operate on
+// messages of at least a certain level (for example, `SetMinLogLevel()`) use
+// this type to specify that level. absl::LogSeverityAtLeast::kInfinity is
+// a level above all threshold levels and therefore no log message will
+// ever meet this threshold.
+enum class LogSeverityAtLeast : int {
+ kInfo = static_cast<int>(absl::LogSeverity::kInfo),
+ kWarning = static_cast<int>(absl::LogSeverity::kWarning),
+ kError = static_cast<int>(absl::LogSeverity::kError),
+ kFatal = static_cast<int>(absl::LogSeverity::kFatal),
+ kInfinity = 1000,
+};
+
+std::ostream& operator<<(std::ostream& os, absl::LogSeverityAtLeast s);
+
+// Enums representing an upper bound for LogSeverity. APIs that only operate on
+// messages of at most a certain level (for example, buffer all messages at or
+// below a certain level) use this type to specify that level.
+// absl::LogSeverityAtMost::kNegativeInfinity is a level below all threshold
+// levels and therefore will exclude all log messages.
+enum class LogSeverityAtMost : int {
+ kNegativeInfinity = -1000,
+ kInfo = static_cast<int>(absl::LogSeverity::kInfo),
+ kWarning = static_cast<int>(absl::LogSeverity::kWarning),
+ kError = static_cast<int>(absl::LogSeverity::kError),
+ kFatal = static_cast<int>(absl::LogSeverity::kFatal),
+};
+
+std::ostream& operator<<(std::ostream& os, absl::LogSeverityAtMost s);
+
+#define COMPOP(op1, op2, T) \
+ constexpr bool operator op1(absl::T lhs, absl::LogSeverity rhs) { \
+ return static_cast<absl::LogSeverity>(lhs) op1 rhs; \
+ } \
+ constexpr bool operator op2(absl::LogSeverity lhs, absl::T rhs) { \
+ return lhs op2 static_cast<absl::LogSeverity>(rhs); \
+ }
+
+// Comparisons between `LogSeverity` and `LogSeverityAtLeast`/
+// `LogSeverityAtMost` are only supported in one direction.
+// Valid checks are:
+// LogSeverity >= LogSeverityAtLeast
+// LogSeverity < LogSeverityAtLeast
+// LogSeverity <= LogSeverityAtMost
+// LogSeverity > LogSeverityAtMost
+COMPOP(>, <, LogSeverityAtLeast)
+COMPOP(<=, >=, LogSeverityAtLeast)
+COMPOP(<, >, LogSeverityAtMost)
+COMPOP(>=, <=, LogSeverityAtMost)
+#undef COMPOP
+
ABSL_NAMESPACE_END
} // namespace absl
diff --git a/absl/base/log_severity_test.cc b/absl/base/log_severity_test.cc
index 2c6872b..3394ecd 100644
--- a/absl/base/log_severity_test.cc
+++ b/absl/base/log_severity_test.cc
@@ -35,7 +35,8 @@
using ::testing::TestWithParam;
using ::testing::Values;
-std::string StreamHelper(absl::LogSeverity value) {
+template <typename T>
+std::string StreamHelper(T value) {
std::ostringstream stream;
stream << value;
return stream.str();
@@ -52,9 +53,9 @@
Eq("absl::LogSeverity(4)"));
}
-static_assert(
- absl::flags_internal::FlagUseOneWordStorage<absl::LogSeverity>::value,
- "Flags of type absl::LogSeverity ought to be lock-free.");
+static_assert(absl::flags_internal::FlagUseValueAndInitBitStorage<
+ absl::LogSeverity>::value,
+ "Flags of type absl::LogSeverity ought to be lock-free.");
using ParseFlagFromOutOfRangeIntegerTest = TestWithParam<int64_t>;
INSTANTIATE_TEST_SUITE_P(
@@ -145,7 +146,12 @@
std::make_tuple("fatal", absl::LogSeverity::kFatal),
std::make_tuple("kFatal", absl::LogSeverity::kFatal),
std::make_tuple("FaTaL", absl::LogSeverity::kFatal),
- std::make_tuple("KfAtAl", absl::LogSeverity::kFatal)));
+ std::make_tuple("KfAtAl", absl::LogSeverity::kFatal),
+ std::make_tuple("DFATAL", absl::kLogDebugFatal),
+ std::make_tuple("dfatal", absl::kLogDebugFatal),
+ std::make_tuple("kLogDebugFatal", absl::kLogDebugFatal),
+ std::make_tuple("dFaTaL", absl::kLogDebugFatal),
+ std::make_tuple("kLoGdEbUgFaTaL", absl::kLogDebugFatal)));
TEST_P(ParseFlagFromEnumeratorTest, YieldsExpectedValue) {
const absl::string_view to_parse = std::get<0>(GetParam());
const absl::LogSeverity expected = std::get<1>(GetParam());
@@ -157,7 +163,8 @@
using ParseFlagFromGarbageTest = TestWithParam<absl::string_view>;
INSTANTIATE_TEST_SUITE_P(Instantiation, ParseFlagFromGarbageTest,
- Values("", "\0", " ", "garbage", "kkinfo", "I"));
+ Values("", "\0", " ", "garbage", "kkinfo", "I",
+ "kDFATAL", "LogDebugFatal", "lOgDeBuGfAtAl"));
TEST_P(ParseFlagFromGarbageTest, ReturnsError) {
const absl::string_view to_parse = GetParam();
absl::LogSeverity value;
@@ -201,4 +208,44 @@
IsTrue());
EXPECT_THAT(reparsed_value, Eq(to_unparse));
}
+
+TEST(LogThresholdTest, LogSeverityAtLeastTest) {
+ EXPECT_LT(absl::LogSeverity::kError, absl::LogSeverityAtLeast::kFatal);
+ EXPECT_GT(absl::LogSeverityAtLeast::kError, absl::LogSeverity::kInfo);
+
+ EXPECT_LE(absl::LogSeverityAtLeast::kInfo, absl::LogSeverity::kError);
+ EXPECT_GE(absl::LogSeverity::kError, absl::LogSeverityAtLeast::kInfo);
+}
+
+TEST(LogThresholdTest, LogSeverityAtMostTest) {
+ EXPECT_GT(absl::LogSeverity::kError, absl::LogSeverityAtMost::kWarning);
+ EXPECT_LT(absl::LogSeverityAtMost::kError, absl::LogSeverity::kFatal);
+
+ EXPECT_GE(absl::LogSeverityAtMost::kFatal, absl::LogSeverity::kError);
+ EXPECT_LE(absl::LogSeverity::kWarning, absl::LogSeverityAtMost::kError);
+}
+
+TEST(LogThresholdTest, Extremes) {
+ EXPECT_LT(absl::LogSeverity::kFatal, absl::LogSeverityAtLeast::kInfinity);
+ EXPECT_GT(absl::LogSeverity::kInfo,
+ absl::LogSeverityAtMost::kNegativeInfinity);
+}
+
+TEST(LogThresholdTest, Output) {
+ EXPECT_THAT(StreamHelper(absl::LogSeverityAtLeast::kInfo), Eq(">=INFO"));
+ EXPECT_THAT(StreamHelper(absl::LogSeverityAtLeast::kWarning),
+ Eq(">=WARNING"));
+ EXPECT_THAT(StreamHelper(absl::LogSeverityAtLeast::kError), Eq(">=ERROR"));
+ EXPECT_THAT(StreamHelper(absl::LogSeverityAtLeast::kFatal), Eq(">=FATAL"));
+ EXPECT_THAT(StreamHelper(absl::LogSeverityAtLeast::kInfinity),
+ Eq("INFINITY"));
+
+ EXPECT_THAT(StreamHelper(absl::LogSeverityAtMost::kInfo), Eq("<=INFO"));
+ EXPECT_THAT(StreamHelper(absl::LogSeverityAtMost::kWarning), Eq("<=WARNING"));
+ EXPECT_THAT(StreamHelper(absl::LogSeverityAtMost::kError), Eq("<=ERROR"));
+ EXPECT_THAT(StreamHelper(absl::LogSeverityAtMost::kFatal), Eq("<=FATAL"));
+ EXPECT_THAT(StreamHelper(absl::LogSeverityAtMost::kNegativeInfinity),
+ Eq("NEGATIVE_INFINITY"));
+}
+
} // namespace
diff --git a/absl/base/macros.h b/absl/base/macros.h
index 3e085a9..33c1cd1 100644
--- a/absl/base/macros.h
+++ b/absl/base/macros.h
@@ -103,17 +103,11 @@
// aborts the program in release mode (when NDEBUG is defined). The
// implementation should abort the program as quickly as possible and ideally it
// should not be possible to ignore the abort request.
-#if (ABSL_HAVE_BUILTIN(__builtin_trap) && \
- ABSL_HAVE_BUILTIN(__builtin_unreachable)) || \
- (defined(__GNUC__) && !defined(__clang__))
-#define ABSL_INTERNAL_HARDENING_ABORT() \
- do { \
- __builtin_trap(); \
- __builtin_unreachable(); \
+#define ABSL_INTERNAL_HARDENING_ABORT() \
+ do { \
+ ABSL_INTERNAL_IMMEDIATE_ABORT_IMPL(); \
+ ABSL_INTERNAL_UNREACHABLE_IMPL(); \
} while (false)
-#else
-#define ABSL_INTERNAL_HARDENING_ABORT() abort()
-#endif
// ABSL_HARDENING_ASSERT()
//
@@ -144,15 +138,40 @@
#define ABSL_INTERNAL_RETHROW do {} while (false)
#endif // ABSL_HAVE_EXCEPTIONS
-// `ABSL_INTERNAL_UNREACHABLE` is an unreachable statement. A program which
-// reaches one has undefined behavior, and the compiler may optimize
-// accordingly.
-#if defined(__GNUC__) || ABSL_HAVE_BUILTIN(__builtin_unreachable)
-#define ABSL_INTERNAL_UNREACHABLE __builtin_unreachable()
-#elif defined(_MSC_VER)
-#define ABSL_INTERNAL_UNREACHABLE __assume(0)
+// ABSL_DEPRECATE_AND_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:
+//
+// 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:
+//
+// ABSL_DEPRECATE_AND_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.
+//
+// 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]]
#else
-#define ABSL_INTERNAL_UNREACHABLE
+#define ABSL_DEPRECATE_AND_INLINE()
#endif
#endif // ABSL_BASE_MACROS_H_
diff --git a/absl/base/no_destructor.h b/absl/base/no_destructor.h
new file mode 100644
index 0000000..7b46456
--- /dev/null
+++ b/absl/base/no_destructor.h
@@ -0,0 +1,218 @@
+// Copyright 2023 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// -----------------------------------------------------------------------------
+// File: no_destructor.h
+// -----------------------------------------------------------------------------
+//
+// This header file defines the absl::NoDestructor<T> wrapper for defining a
+// static type that does not need to be destructed upon program exit. Instead,
+// such an object survives during program exit (and can be safely accessed at
+// any time).
+//
+// Objects of such type, if constructed safely and under the right conditions,
+// provide two main benefits over other alternatives:
+//
+// * Global objects not normally allowed due to concerns of destruction order
+// (i.e. no "complex globals") can be safely allowed, provided that such
+// objects can be constant initialized.
+// * Function scope static objects can be optimized to avoid heap allocation,
+// pointer chasing, and allow lazy construction.
+//
+// See below for complete details.
+
+
+#ifndef ABSL_BASE_NO_DESTRUCTOR_H_
+#define ABSL_BASE_NO_DESTRUCTOR_H_
+
+#include <new>
+#include <type_traits>
+#include <utility>
+
+#include "absl/base/config.h"
+#include "absl/base/nullability.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+
+// absl::NoDestructor<T>
+//
+// NoDestructor<T> is a wrapper around an object of type T that behaves as an
+// object of type T but never calls T's destructor. NoDestructor<T> makes it
+// safer and/or more efficient to use such objects in static storage contexts:
+// as global or function scope static variables.
+//
+// An instance of absl::NoDestructor<T> has similar type semantics to an
+// instance of T:
+//
+// * Constructs in the same manner as an object of type T through perfect
+// forwarding.
+// * Provides pointer/reference semantic access to the object of type T via
+// `->`, `*`, and `get()`.
+// (Note that `const NoDestructor<T>` works like a pointer to const `T`.)
+//
+// An object of type NoDestructor<T> should be defined in static storage:
+// as either a global static object, or as a function scope static variable.
+//
+// Additionally, NoDestructor<T> provides the following benefits:
+//
+// * Never calls T's destructor for the object
+// * If the object is a function-local static variable, the type can be
+// lazily constructed.
+//
+// An object of type NoDestructor<T> is "trivially destructible" in the notion
+// that its destructor is never run. Provided that an object of this type can be
+// safely initialized and does not need to be cleaned up on program shutdown,
+// NoDestructor<T> allows you to define global static variables, since Google's
+// C++ style guide ban on such objects doesn't apply to objects that are
+// trivially destructible.
+//
+// Usage as Global Static Variables
+//
+// NoDestructor<T> allows declaration of a global object with a non-trivial
+// constructor in static storage without needing to add a destructor.
+// However, such objects still need to worry about initialization order, so
+// such objects should be const initialized:
+//
+// // Global or namespace scope.
+// constinit absl::NoDestructor<MyRegistry> reg{"foo", "bar", 8008};
+//
+// Note that if your object already has a trivial destructor, you don't need to
+// use NoDestructor<T>.
+//
+// Usage as Function Scope Static Variables
+//
+// Function static objects will be lazily initialized within static storage:
+//
+// // Function scope.
+// const std::string& MyString() {
+// static const absl::NoDestructor<std::string> x("foo");
+// return *x;
+// }
+//
+// For function static variables, NoDestructor avoids heap allocation and can be
+// inlined in static storage, resulting in exactly-once, thread-safe
+// construction of an object, and very fast access thereafter (the cost is a few
+// extra cycles).
+//
+// Using NoDestructor<T> in this manner is generally better than other patterns
+// which require pointer chasing:
+//
+// // Prefer using absl::NoDestructor<T> instead for the static variable.
+// const std::string& MyString() {
+// static const std::string* x = new std::string("foo");
+// return *x;
+// }
+//
+template <typename T>
+class NoDestructor {
+ public:
+ // Forwards arguments to the T's constructor: calls T(args...).
+ template <typename... Ts,
+ // Disable this overload when it might collide with copy/move.
+ typename std::enable_if<!std::is_same<void(std::decay_t<Ts>&...),
+ void(NoDestructor&)>::value,
+ int>::type = 0>
+ explicit constexpr NoDestructor(Ts&&... args)
+ : impl_(std::forward<Ts>(args)...) {}
+
+ // Forwards copy and move construction for T. Enables usage like this:
+ // static NoDestructor<std::array<string, 3>> x{{{"1", "2", "3"}}};
+ // static NoDestructor<std::vector<int>> x{{1, 2, 3}};
+ explicit constexpr NoDestructor(const T& x) : impl_(x) {}
+ explicit constexpr NoDestructor(T&& x)
+ : impl_(std::move(x)) {}
+
+ // No copying.
+ NoDestructor(const NoDestructor&) = delete;
+ NoDestructor& operator=(const NoDestructor&) = delete;
+
+ // Pretend to be a smart pointer to T with deep constness.
+ // Never returns a null pointer.
+ T& operator*() { return *get(); }
+ absl::Nonnull<T*> operator->() { return get(); }
+ absl::Nonnull<T*> get() { return impl_.get(); }
+ const T& operator*() const { return *get(); }
+ absl::Nonnull<const T*> operator->() const { return get(); }
+ absl::Nonnull<const T*> get() const { return impl_.get(); }
+
+ private:
+ class DirectImpl {
+ public:
+ template <typename... Args>
+ explicit constexpr DirectImpl(Args&&... args)
+ : value_(std::forward<Args>(args)...) {}
+ absl::Nonnull<const T*> get() const { return &value_; }
+ absl::Nonnull<T*> get() { return &value_; }
+
+ private:
+ T value_;
+ };
+
+ class PlacementImpl {
+ public:
+ template <typename... Args>
+ explicit PlacementImpl(Args&&... args) {
+ new (&space_) T(std::forward<Args>(args)...);
+ }
+ absl::Nonnull<const T*> get() const {
+ return Launder(reinterpret_cast<const T*>(&space_));
+ }
+ absl::Nonnull<T*> get() { return Launder(reinterpret_cast<T*>(&space_)); }
+
+ private:
+ template <typename P>
+ static absl::Nonnull<P*> Launder(absl::Nonnull<P*> p) {
+#if defined(__cpp_lib_launder) && __cpp_lib_launder >= 201606L
+ return std::launder(p);
+#elif ABSL_HAVE_BUILTIN(__builtin_launder)
+ return __builtin_launder(p);
+#else
+ // When `std::launder` or equivalent are not available, we rely on
+ // undefined behavior, which works as intended on Abseil's officially
+ // supported platforms as of Q3 2023.
+#if defined(__GNUC__) && !defined(__clang__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wstrict-aliasing"
+#endif
+ return p;
+#if defined(__GNUC__) && !defined(__clang__)
+#pragma GCC diagnostic pop
+#endif
+#endif
+ }
+
+ alignas(T) unsigned char space_[sizeof(T)];
+ };
+
+ // If the object is trivially destructible we use a member directly to avoid
+ // potential once-init runtime initialization. It somewhat defeats the
+ // purpose of NoDestructor in this case, but this makes the class more
+ // friendly to generic code.
+ std::conditional_t<std::is_trivially_destructible<T>::value, DirectImpl,
+ PlacementImpl>
+ impl_;
+};
+
+#ifdef ABSL_HAVE_CLASS_TEMPLATE_ARGUMENT_DEDUCTION
+// Provide 'Class Template Argument Deduction': the type of NoDestructor's T
+// will be the same type as the argument passed to NoDestructor's constructor.
+template <typename T>
+NoDestructor(T) -> NoDestructor<T>;
+#endif // ABSL_HAVE_CLASS_TEMPLATE_ARGUMENT_DEDUCTION
+
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_BASE_NO_DESTRUCTOR_H_
diff --git a/absl/base/no_destructor_benchmark.cc b/absl/base/no_destructor_benchmark.cc
new file mode 100644
index 0000000..5fc88f1
--- /dev/null
+++ b/absl/base/no_destructor_benchmark.cc
@@ -0,0 +1,165 @@
+// Copyright 2023 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <cstdint>
+
+#include "absl/base/internal/raw_logging.h"
+#include "absl/base/no_destructor.h"
+#include "benchmark/benchmark.h"
+
+namespace {
+
+// Number of static-NoDestructor-in-a-function to exercise.
+// This must be low enough not to hit template instantiation limits
+// (happens around 1000).
+constexpr int kNumObjects = 1; // set to 512 when doing benchmarks
+ // 1 is faster to compile: just one templated
+ // function instantiation
+
+// Size of individual objects to benchmark static-NoDestructor-in-a-function
+// usage with.
+constexpr int kObjSize = sizeof(void*)*1;
+
+// Simple object of kObjSize bytes (rounded to int).
+// We benchmark complete reading of its state via Verify().
+class BM_Blob {
+ public:
+ BM_Blob(int val) { for (auto& d : data_) d = val; }
+ BM_Blob() : BM_Blob(-1) {}
+ void Verify(int val) const { // val must be the c-tor argument
+ for (auto& d : data_) ABSL_INTERNAL_CHECK(d == val, "");
+ }
+ private:
+ int data_[kObjSize / sizeof(int) > 0 ? kObjSize / sizeof(int) : 1];
+};
+
+// static-NoDestructor-in-a-function pattern instances.
+// We'll instantiate kNumObjects of them.
+template<int i>
+const BM_Blob& NoDestrBlobFunc() {
+ static absl::NoDestructor<BM_Blob> x(i);
+ return *x;
+}
+
+// static-heap-ptr-in-a-function pattern instances
+// We'll instantiate kNumObjects of them.
+template<int i>
+const BM_Blob& OnHeapBlobFunc() {
+ static BM_Blob* x = new BM_Blob(i);
+ return *x;
+}
+
+// Type for NoDestrBlobFunc or OnHeapBlobFunc.
+typedef const BM_Blob& (*FuncType)();
+
+// ========================================================================= //
+// Simple benchmarks that read a single BM_Blob over and over, hence
+// all they touch fits into L1 CPU cache:
+
+// Direct non-POD global variable (style guide violation) as a baseline.
+static BM_Blob direct_blob(0);
+
+void BM_Direct(benchmark::State& state) {
+ for (auto s : state) {
+ direct_blob.Verify(0);
+ }
+}
+BENCHMARK(BM_Direct);
+
+void BM_NoDestr(benchmark::State& state) {
+ for (auto s : state) {
+ NoDestrBlobFunc<0>().Verify(0);
+ }
+}
+BENCHMARK(BM_NoDestr);
+
+void BM_OnHeap(benchmark::State& state) {
+ for (auto s : state) {
+ OnHeapBlobFunc<0>().Verify(0);
+ }
+}
+BENCHMARK(BM_OnHeap);
+
+// ========================================================================= //
+// Benchmarks that read kNumObjects of BM_Blob over and over, hence with
+// appropriate values of sizeof(BM_Blob) and kNumObjects their working set
+// can exceed a given layer of CPU cache.
+
+// Type of benchmark to select between NoDestrBlobFunc and OnHeapBlobFunc.
+enum BM_Type { kNoDestr, kOnHeap, kDirect };
+
+// BlobFunc<n>(t, i) returns the i-th function of type t.
+// n must be larger than i (we'll use kNumObjects for n).
+template<int n>
+FuncType BlobFunc(BM_Type t, int i) {
+ if (i == n) {
+ switch (t) {
+ case kNoDestr: return &NoDestrBlobFunc<n>;
+ case kOnHeap: return &OnHeapBlobFunc<n>;
+ case kDirect: return nullptr;
+ }
+ }
+ return BlobFunc<n-1>(t, i);
+}
+
+template<>
+FuncType BlobFunc<0>(BM_Type t, int i) {
+ ABSL_INTERNAL_CHECK(i == 0, "");
+ switch (t) {
+ case kNoDestr: return &NoDestrBlobFunc<0>;
+ case kOnHeap: return &OnHeapBlobFunc<0>;
+ case kDirect: return nullptr;
+ }
+ return nullptr;
+}
+
+// Direct non-POD global variables (style guide violation) as a baseline.
+static BM_Blob direct_blobs[kNumObjects];
+
+// Helper that cheaply maps benchmark iteration to randomish index in
+// [0, kNumObjects).
+int RandIdx(int i) {
+ // int64 is to avoid overflow and generating negative return values:
+ return (static_cast<int64_t>(i) * 13) % kNumObjects;
+}
+
+// Generic benchmark working with kNumObjects for any of the possible BM_Type.
+template <BM_Type t>
+void BM_Many(benchmark::State& state) {
+ FuncType funcs[kNumObjects];
+ for (int i = 0; i < kNumObjects; ++i) {
+ funcs[i] = BlobFunc<kNumObjects-1>(t, i);
+ }
+ if (t == kDirect) {
+ for (auto s : state) {
+ int idx = RandIdx(state.iterations());
+ direct_blobs[idx].Verify(-1);
+ }
+ } else {
+ for (auto s : state) {
+ int idx = RandIdx(state.iterations());
+ funcs[idx]().Verify(idx);
+ }
+ }
+}
+
+void BM_DirectMany(benchmark::State& state) { BM_Many<kDirect>(state); }
+void BM_NoDestrMany(benchmark::State& state) { BM_Many<kNoDestr>(state); }
+void BM_OnHeapMany(benchmark::State& state) { BM_Many<kOnHeap>(state); }
+
+BENCHMARK(BM_DirectMany);
+BENCHMARK(BM_NoDestrMany);
+BENCHMARK(BM_OnHeapMany);
+
+} // namespace
diff --git a/absl/base/no_destructor_test.cc b/absl/base/no_destructor_test.cc
new file mode 100644
index 0000000..71693c7
--- /dev/null
+++ b/absl/base/no_destructor_test.cc
@@ -0,0 +1,209 @@
+// Copyright 2023 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/base/no_destructor.h"
+
+#include <array>
+#include <initializer_list>
+#include <string>
+#include <type_traits>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/base/config.h"
+#include "absl/base/internal/raw_logging.h"
+
+namespace {
+
+struct Blob {
+ Blob() : val(42) {}
+ Blob(int x, int y) : val(x + y) {}
+ Blob(std::initializer_list<int> xs) {
+ val = 0;
+ for (auto& x : xs) val += x;
+ }
+
+ Blob(const Blob& /*b*/) = delete;
+ Blob(Blob&& b) noexcept : val(b.val) {
+ b.moved_out = true;
+ } // moving is fine
+
+ // no crash: NoDestructor indeed does not destruct (the moved-out Blob
+ // temporaries do get destroyed though)
+ ~Blob() { ABSL_INTERNAL_CHECK(moved_out, "~Blob"); }
+
+ int val;
+ bool moved_out = false;
+};
+
+struct TypeWithDeletedDestructor {
+ ~TypeWithDeletedDestructor() = delete;
+};
+
+TEST(NoDestructorTest, DestructorNeverCalled) {
+ absl::NoDestructor<TypeWithDeletedDestructor> a;
+ (void)a;
+}
+
+TEST(NoDestructorTest, Noncopyable) {
+ using T = absl::NoDestructor<int>;
+
+ EXPECT_FALSE((std::is_constructible<T, T>::value));
+ EXPECT_FALSE((std::is_constructible<T, const T>::value));
+ EXPECT_FALSE((std::is_constructible<T, T&>::value));
+ EXPECT_FALSE((std::is_constructible<T, const T&>::value));
+
+ EXPECT_FALSE((std::is_assignable<T&, T>::value));
+ EXPECT_FALSE((std::is_assignable<T&, const T>::value));
+ EXPECT_FALSE((std::is_assignable<T&, T&>::value));
+ EXPECT_FALSE((std::is_assignable<T&, const T&>::value));
+}
+
+TEST(NoDestructorTest, Interface) {
+ EXPECT_TRUE(std::is_trivially_destructible<absl::NoDestructor<Blob>>::value);
+ EXPECT_TRUE(
+ std::is_trivially_destructible<absl::NoDestructor<const Blob>>::value);
+ {
+ absl::NoDestructor<Blob> b; // default c-tor
+ // access: *, ->, get()
+ EXPECT_EQ(42, (*b).val);
+ (*b).val = 55;
+ EXPECT_EQ(55, b->val);
+ b->val = 66;
+ EXPECT_EQ(66, b.get()->val);
+ b.get()->val = 42; // NOLINT
+ EXPECT_EQ(42, (*b).val);
+ }
+ {
+ absl::NoDestructor<const Blob> b(70, 7); // regular c-tor, const
+ EXPECT_EQ(77, (*b).val);
+ EXPECT_EQ(77, b->val);
+ EXPECT_EQ(77, b.get()->val);
+ }
+ {
+ const absl::NoDestructor<Blob> b{
+ {20, 28, 40}}; // init-list c-tor, deep const
+ // This only works in clang, not in gcc:
+ // const absl::NoDestructor<Blob> b({20, 28, 40});
+ EXPECT_EQ(88, (*b).val);
+ EXPECT_EQ(88, b->val);
+ EXPECT_EQ(88, b.get()->val);
+ }
+}
+
+TEST(NoDestructorTest, SfinaeRegressionAbstractArg) {
+ struct Abstract {
+ virtual ~Abstract() = default;
+ virtual int foo() const = 0;
+ };
+
+ struct Concrete : Abstract {
+ int foo() const override { return 17; }
+ };
+
+ struct UsesAbstractInConstructor {
+ explicit UsesAbstractInConstructor(const Abstract& abstract)
+ : i(abstract.foo()) {}
+ int i;
+ };
+
+ Concrete input;
+ absl::NoDestructor<UsesAbstractInConstructor> foo1(input);
+ EXPECT_EQ(foo1->i, 17);
+ absl::NoDestructor<UsesAbstractInConstructor> foo2(
+ static_cast<const Abstract&>(input));
+ EXPECT_EQ(foo2->i, 17);
+}
+
+// ========================================================================= //
+
+std::string* Str0() {
+ static absl::NoDestructor<std::string> x;
+ return x.get();
+}
+
+extern const std::string& Str2();
+
+const char* Str1() {
+ static absl::NoDestructor<std::string> x(Str2() + "_Str1");
+ return x->c_str();
+}
+
+const std::string& Str2() {
+ static absl::NoDestructor<std::string> x("Str2");
+ return *x;
+}
+
+const std::string& Str2Copy() {
+ // Exercise copy construction
+ static absl::NoDestructor<std::string> x(Str2());
+ return *x;
+}
+
+typedef std::array<std::string, 3> MyArray;
+const MyArray& Array() {
+ static absl::NoDestructor<MyArray> x{{{"foo", "bar", "baz"}}};
+ // This only works in clang, not in gcc:
+ // static absl::NoDestructor<MyArray> x({{"foo", "bar", "baz"}});
+ return *x;
+}
+
+typedef std::vector<int> MyVector;
+const MyVector& Vector() {
+ static absl::NoDestructor<MyVector> x{{1, 2, 3}};
+ return *x;
+}
+
+const int& Int() {
+ static absl::NoDestructor<int> x;
+ return *x;
+}
+
+TEST(NoDestructorTest, StaticPattern) {
+ EXPECT_TRUE(
+ std::is_trivially_destructible<absl::NoDestructor<std::string>>::value);
+ EXPECT_TRUE(
+ std::is_trivially_destructible<absl::NoDestructor<MyArray>>::value);
+ EXPECT_TRUE(
+ std::is_trivially_destructible<absl::NoDestructor<MyVector>>::value);
+ EXPECT_TRUE(std::is_trivially_destructible<absl::NoDestructor<int>>::value);
+
+ EXPECT_EQ(*Str0(), "");
+ Str0()->append("foo");
+ EXPECT_EQ(*Str0(), "foo");
+
+ EXPECT_EQ(std::string(Str1()), "Str2_Str1");
+
+ EXPECT_EQ(Str2(), "Str2");
+ EXPECT_EQ(Str2Copy(), "Str2");
+
+ EXPECT_THAT(Array(), testing::ElementsAre("foo", "bar", "baz"));
+
+ EXPECT_THAT(Vector(), testing::ElementsAre(1, 2, 3));
+
+ EXPECT_EQ(0, Int()); // should get zero-initialized
+}
+
+#ifdef ABSL_HAVE_CLASS_TEMPLATE_ARGUMENT_DEDUCTION
+// This would fail to compile if Class Template Argument Deduction was not
+// provided for absl::NoDestructor.
+TEST(NoDestructorTest, ClassTemplateArgumentDeduction) {
+ absl::NoDestructor i(1);
+ static_assert(std::is_same<decltype(i), absl::NoDestructor<int>>::value,
+ "Expected deduced type to be int.");
+}
+#endif
+
+} // namespace
diff --git a/absl/base/nullability.h b/absl/base/nullability.h
new file mode 100644
index 0000000..6f49b6f
--- /dev/null
+++ b/absl/base/nullability.h
@@ -0,0 +1,224 @@
+// Copyright 2023 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// -----------------------------------------------------------------------------
+// File: nullability.h
+// -----------------------------------------------------------------------------
+//
+// This header file defines a set of "templated annotations" for designating the
+// expected nullability of pointers. These annotations allow you to designate
+// pointers in one of three classification states:
+//
+// * "Non-null" (for pointers annotated `Nonnull<T>`), indicating that it is
+// invalid for the given pointer to ever be null.
+// * "Nullable" (for pointers annotated `Nullable<T>`), indicating that it is
+// valid for the given pointer to be null.
+// * "Unknown" (for pointers annotated `NullabilityUnknown<T>`), indicating
+// that the given pointer has not been yet classified as either nullable or
+// non-null. This is the default state of unannotated pointers.
+//
+// NOTE: unannotated pointers implicitly bear the annotation
+// `NullabilityUnknown<T>`; you should rarely, if ever, see this annotation used
+// in the codebase explicitly.
+//
+// -----------------------------------------------------------------------------
+// Nullability and Contracts
+// -----------------------------------------------------------------------------
+//
+// These nullability annotations allow you to more clearly specify contracts on
+// software components by narrowing the *preconditions*, *postconditions*, and
+// *invariants* of pointer state(s) in any given interface. It then depends on
+// context who is responsible for fulfilling the annotation's requirements.
+//
+// For example, a function may receive a pointer argument. Designating that
+// pointer argument as "non-null" tightens the precondition of the contract of
+// that function. It is then the responsibility of anyone calling such a
+// function to ensure that the passed pointer is not null.
+//
+// Similarly, a function may have a pointer as a return value. Designating that
+// return value as "non-null" tightens the postcondition of the contract of that
+// function. In this case, however, it is the responsibility of the function
+// itself to ensure that the returned pointer is not null.
+//
+// Clearly defining these contracts allows providers (and consumers) of such
+// pointers to have more confidence in their null state. If a function declares
+// a return value as "non-null", for example, the caller should not need to
+// check whether the returned value is `nullptr`; it can simply assume the
+// pointer is valid.
+//
+// Of course most interfaces already have expectations on the nullability state
+// of pointers, and these expectations are, in effect, a contract; often,
+// however, those contracts are either poorly or partially specified, assumed,
+// or misunderstood. These nullability annotations are designed to allow you to
+// formalize those contracts within the codebase.
+//
+// -----------------------------------------------------------------------------
+// Using Nullability Annotations
+// -----------------------------------------------------------------------------
+//
+// It is important to note that these annotations are not distinct strong
+// *types*. They are alias templates defined to be equal to the underlying
+// pointer type. A pointer annotated `Nonnull<T*>`, for example, is simply a
+// pointer of type `T*`. Each annotation acts as a form of documentation about
+// the contract for the given pointer. Each annotation requires providers or
+// consumers of these pointers across API boundaries to take appropriate steps
+// when setting or using these pointers:
+//
+// * "Non-null" pointers should never be null. It is the responsibility of the
+// provider of this pointer to ensure that the pointer may never be set to
+// null. Consumers of such pointers can treat such pointers as non-null.
+// * "Nullable" pointers may or may not be null. Consumers of such pointers
+// should precede any usage of that pointer (e.g. a dereference operation)
+// with a a `nullptr` check.
+// * "Unknown" pointers may be either "non-null" or "nullable" but have not been
+// definitively determined to be in either classification state. Providers of
+// such pointers across API boundaries should determine -- over time -- to
+// annotate the pointer in either of the above two states. Consumers of such
+// pointers across an API boundary should continue to treat such pointers as
+// they currently do.
+//
+// Example:
+//
+// // PaySalary() requires the passed pointer to an `Employee` to be non-null.
+// void PaySalary(absl::Nonnull<Employee *> e) {
+// pay(e->salary); // OK to dereference
+// }
+//
+// // CompleteTransaction() guarantees the returned pointer to an `Account` to
+// // be non-null.
+// absl::Nonnull<Account *> balance CompleteTransaction(double fee) {
+// ...
+// }
+//
+// // Note that specifying a nullability annotation does not prevent someone
+// // from violating the contract:
+//
+// Nullable<Employee *> find(Map& employees, std::string_view name);
+//
+// void g(Map& employees) {
+// Employee *e = find(employees, "Pat");
+// // `e` can now be null.
+// PaySalary(e); // Violates contract, but compiles!
+// }
+//
+// Nullability annotations, in other words, are useful for defining and
+// narrowing contracts; *enforcement* of those contracts depends on use and any
+// additional (static or dynamic analysis) tooling.
+//
+// NOTE: The "unknown" annotation state indicates that a pointer's contract has
+// not yet been positively identified. The unknown state therefore acts as a
+// form of documentation of your technical debt, and a codebase that adopts
+// nullability annotations should aspire to annotate every pointer as either
+// "non-null" or "nullable".
+//
+// -----------------------------------------------------------------------------
+// Applicability of Nullability Annotations
+// -----------------------------------------------------------------------------
+//
+// By default, nullability annotations are applicable to raw and smart
+// pointers. User-defined types can indicate compatibility with nullability
+// annotations by providing an `absl_nullability_compatible` nested type. The
+// actual definition of this inner type is not relevant as it is used merely as
+// a marker. It is common to use a using declaration of
+// `absl_nullability_compatible` set to void.
+//
+// // Example:
+// struct MyPtr {
+// using absl_nullability_compatible = void;
+// ...
+// };
+//
+// DISCLAIMER:
+// ===========================================================================
+// These nullability annotations are primarily a human readable signal about the
+// intended contract of the pointer. They are not *types* and do not currently
+// provide any correctness guarantees. For example, a pointer annotated as
+// `Nonnull<T*>` is *not guaranteed* to be non-null, and the compiler won't
+// alert or prevent assignment of a `Nullable<T*>` to a `Nonnull<T*>`.
+// ===========================================================================
+#ifndef ABSL_BASE_NULLABILITY_H_
+#define ABSL_BASE_NULLABILITY_H_
+
+#include "absl/base/internal/nullability_impl.h"
+
+namespace absl {
+
+// absl::Nonnull
+//
+// The indicated pointer is never null. It is the responsibility of the provider
+// of this pointer across an API boundary to ensure that the pointer is never be
+// set to null. Consumers of this pointer across an API boundary may safely
+// dereference the pointer.
+//
+// Example:
+//
+// // `employee` is designated as not null.
+// void PaySalary(absl::Nonnull<Employee *> employee) {
+// pay(*employee); // OK to dereference
+// }
+template <typename T>
+using Nonnull = nullability_internal::NonnullImpl<T>;
+
+// absl::Nullable
+//
+// The indicated pointer may, by design, be either null or non-null. Consumers
+// of this pointer across an API boundary should perform a `nullptr` check
+// before performing any operation using the pointer.
+//
+// Example:
+//
+// // `employee` may be null.
+// void PaySalary(absl::Nullable<Employee *> employee) {
+// if (employee != nullptr) {
+// Pay(*employee); // OK to dereference
+// }
+// }
+template <typename T>
+using Nullable = nullability_internal::NullableImpl<T>;
+
+// absl::NullabilityUnknown (default)
+//
+// The indicated pointer has not yet been determined to be definitively
+// "non-null" or "nullable." Providers of such pointers across API boundaries
+// should, over time, annotate such pointers as either "non-null" or "nullable."
+// Consumers of these pointers across an API boundary should treat such pointers
+// with the same caution they treat currently unannotated pointers. Most
+// existing code will have "unknown" pointers, which should eventually be
+// migrated into one of the above two nullability states: `Nonnull<T>` or
+// `Nullable<T>`.
+//
+// NOTE: Because this annotation is the global default state, pointers without
+// any annotation are assumed to have "unknown" semantics. This assumption is
+// designed to minimize churn and reduce clutter within the codebase.
+//
+// Example:
+//
+// // `employee`s nullability state is unknown.
+// void PaySalary(absl::NullabilityUnknown<Employee *> employee) {
+// Pay(*employee); // Potentially dangerous. API provider should investigate.
+// }
+//
+// Note that a pointer without an annotation, by default, is assumed to have the
+// annotation `NullabilityUnknown`.
+//
+// // `employee`s nullability state is unknown.
+// void PaySalary(Employee* employee) {
+// Pay(*employee); // Potentially dangerous. API provider should investigate.
+// }
+template <typename T>
+using NullabilityUnknown = nullability_internal::NullabilityUnknownImpl<T>;
+
+} // namespace absl
+
+#endif // ABSL_BASE_NULLABILITY_H_
diff --git a/absl/base/nullability_test.cc b/absl/base/nullability_test.cc
new file mode 100644
index 0000000..028ea6c
--- /dev/null
+++ b/absl/base/nullability_test.cc
@@ -0,0 +1,129 @@
+// Copyright 2023 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/base/nullability.h"
+
+#include <cassert>
+#include <memory>
+#include <utility>
+
+#include "gtest/gtest.h"
+#include "absl/base/attributes.h"
+
+namespace {
+using ::absl::Nonnull;
+using ::absl::NullabilityUnknown;
+using ::absl::Nullable;
+
+void funcWithNonnullArg(Nonnull<int*> /*arg*/) {}
+template <typename T>
+void funcWithDeducedNonnullArg(Nonnull<T*> /*arg*/) {}
+
+TEST(NonnullTest, NonnullArgument) {
+ int var = 0;
+ funcWithNonnullArg(&var);
+ funcWithDeducedNonnullArg(&var);
+}
+
+Nonnull<int*> funcWithNonnullReturn() {
+ static int var = 0;
+ return &var;
+}
+
+TEST(NonnullTest, NonnullReturn) {
+ auto var = funcWithNonnullReturn();
+ (void)var;
+}
+
+TEST(PassThroughTest, PassesThroughRawPointerToInt) {
+ EXPECT_TRUE((std::is_same<Nonnull<int*>, int*>::value));
+ EXPECT_TRUE((std::is_same<Nullable<int*>, int*>::value));
+ EXPECT_TRUE((std::is_same<NullabilityUnknown<int*>, int*>::value));
+}
+
+TEST(PassThroughTest, PassesThroughRawPointerToVoid) {
+ EXPECT_TRUE((std::is_same<Nonnull<void*>, void*>::value));
+ EXPECT_TRUE((std::is_same<Nullable<void*>, void*>::value));
+ EXPECT_TRUE((std::is_same<NullabilityUnknown<void*>, void*>::value));
+}
+
+TEST(PassThroughTest, PassesThroughUniquePointerToInt) {
+ using T = std::unique_ptr<int>;
+ EXPECT_TRUE((std::is_same<Nonnull<T>, T>::value));
+ EXPECT_TRUE((std::is_same<Nullable<T>, T>::value));
+ EXPECT_TRUE((std::is_same<NullabilityUnknown<T>, T>::value));
+}
+
+TEST(PassThroughTest, PassesThroughSharedPointerToInt) {
+ using T = std::shared_ptr<int>;
+ EXPECT_TRUE((std::is_same<Nonnull<T>, T>::value));
+ EXPECT_TRUE((std::is_same<Nullable<T>, T>::value));
+ EXPECT_TRUE((std::is_same<NullabilityUnknown<T>, T>::value));
+}
+
+TEST(PassThroughTest, PassesThroughSharedPointerToVoid) {
+ using T = std::shared_ptr<void>;
+ EXPECT_TRUE((std::is_same<Nonnull<T>, T>::value));
+ EXPECT_TRUE((std::is_same<Nullable<T>, T>::value));
+ EXPECT_TRUE((std::is_same<NullabilityUnknown<T>, T>::value));
+}
+
+TEST(PassThroughTest, PassesThroughPointerToMemberObject) {
+ using T = decltype(&std::pair<int, int>::first);
+ EXPECT_TRUE((std::is_same<Nonnull<T>, T>::value));
+ EXPECT_TRUE((std::is_same<Nullable<T>, T>::value));
+ EXPECT_TRUE((std::is_same<NullabilityUnknown<T>, T>::value));
+}
+
+TEST(PassThroughTest, PassesThroughPointerToMemberFunction) {
+ using T = decltype(&std::unique_ptr<int>::reset);
+ EXPECT_TRUE((std::is_same<Nonnull<T>, T>::value));
+ EXPECT_TRUE((std::is_same<Nullable<T>, T>::value));
+ EXPECT_TRUE((std::is_same<NullabilityUnknown<T>, T>::value));
+}
+
+} // namespace
+
+// Nullable ADL lookup test
+namespace util {
+// Helper for NullableAdlTest. Returns true, denoting that argument-dependent
+// lookup found this implementation of DidAdlWin. Must be in namespace
+// util itself, not a nested anonymous namespace.
+template <typename T>
+bool DidAdlWin(T*) {
+ return true;
+}
+
+// Because this type is defined in namespace util, an unqualified call to
+// DidAdlWin with a pointer to MakeAdlWin will find the above implementation.
+struct MakeAdlWin {};
+} // namespace util
+
+namespace {
+// Returns false, denoting that ADL did not inspect namespace util. If it
+// had, the better match (T*) above would have won out over the (...) here.
+bool DidAdlWin(...) { return false; }
+
+TEST(NullableAdlTest, NullableAddsNothingToArgumentDependentLookup) {
+ // Treatment: util::Nullable<int*> contributes nothing to ADL because
+ // int* itself doesn't.
+ EXPECT_FALSE(DidAdlWin((int*)nullptr));
+ EXPECT_FALSE(DidAdlWin((Nullable<int*>)nullptr));
+
+ // Control: Argument-dependent lookup does find the implementation in
+ // namespace util when the underlying pointee type resides there.
+ EXPECT_TRUE(DidAdlWin((util::MakeAdlWin*)nullptr));
+ EXPECT_TRUE(DidAdlWin((Nullable<util::MakeAdlWin*>)nullptr));
+}
+} // namespace
diff --git a/absl/base/optimization.h b/absl/base/optimization.h
index d090be1..f985995 100644
--- a/absl/base/optimization.h
+++ b/absl/base/optimization.h
@@ -25,6 +25,7 @@
#include <assert.h>
#include "absl/base/config.h"
+#include "absl/base/options.h"
// ABSL_BLOCK_TAIL_CALL_OPTIMIZATION
//
@@ -91,6 +92,7 @@
#define ABSL_CACHELINE_SIZE 64
#endif
#endif
+#endif
#ifndef ABSL_CACHELINE_SIZE
// A reasonable default guess. Note that overestimates tend to waste more
@@ -141,12 +143,11 @@
// the generated machine code.
// 3) Prefer applying this attribute to individual variables. Avoid
// applying it to types. This tends to localize the effect.
+#if defined(__clang__) || defined(__GNUC__)
#define ABSL_CACHELINE_ALIGNED __attribute__((aligned(ABSL_CACHELINE_SIZE)))
#elif defined(_MSC_VER)
-#define ABSL_CACHELINE_SIZE 64
#define ABSL_CACHELINE_ALIGNED __declspec(align(ABSL_CACHELINE_SIZE))
#else
-#define ABSL_CACHELINE_SIZE 64
#define ABSL_CACHELINE_ALIGNED
#endif
@@ -181,38 +182,98 @@
#define ABSL_PREDICT_TRUE(x) (x)
#endif
-// ABSL_INTERNAL_ASSUME(cond)
+// `ABSL_INTERNAL_IMMEDIATE_ABORT_IMPL()` aborts the program in the fastest
+// possible way, with no attempt at logging. One use is to implement hardening
+// aborts with ABSL_OPTION_HARDENED. Since this is an internal symbol, it
+// should not be used directly outside of Abseil.
+#if ABSL_HAVE_BUILTIN(__builtin_trap) || \
+ (defined(__GNUC__) && !defined(__clang__))
+#define ABSL_INTERNAL_IMMEDIATE_ABORT_IMPL() __builtin_trap()
+#else
+#define ABSL_INTERNAL_IMMEDIATE_ABORT_IMPL() abort()
+#endif
+
+// `ABSL_INTERNAL_UNREACHABLE_IMPL()` is the platform specific directive to
+// indicate that a statement is unreachable, and to allow the compiler to
+// optimize accordingly. Clients should use `ABSL_UNREACHABLE()`, which is
+// defined below.
+#if defined(__cpp_lib_unreachable) && __cpp_lib_unreachable >= 202202L
+#define ABSL_INTERNAL_UNREACHABLE_IMPL() std::unreachable()
+#elif defined(__GNUC__) || ABSL_HAVE_BUILTIN(__builtin_unreachable)
+#define ABSL_INTERNAL_UNREACHABLE_IMPL() __builtin_unreachable()
+#elif ABSL_HAVE_BUILTIN(__builtin_assume)
+#define ABSL_INTERNAL_UNREACHABLE_IMPL() __builtin_assume(false)
+#elif defined(_MSC_VER)
+#define ABSL_INTERNAL_UNREACHABLE_IMPL() __assume(false)
+#else
+#define ABSL_INTERNAL_UNREACHABLE_IMPL()
+#endif
+
+// `ABSL_UNREACHABLE()` is an unreachable statement. A program which reaches
+// one has undefined behavior, and the compiler may optimize accordingly.
+#if ABSL_OPTION_HARDENED == 1 && defined(NDEBUG)
+// Abort in hardened mode to avoid dangerous undefined behavior.
+#define ABSL_UNREACHABLE() \
+ do { \
+ ABSL_INTERNAL_IMMEDIATE_ABORT_IMPL(); \
+ ABSL_INTERNAL_UNREACHABLE_IMPL(); \
+ } while (false)
+#else
+// The assert only fires in debug mode to aid in debugging.
+// When NDEBUG is defined, reaching ABSL_UNREACHABLE() is undefined behavior.
+#define ABSL_UNREACHABLE() \
+ do { \
+ /* NOLINTNEXTLINE: misc-static-assert */ \
+ assert(false && "ABSL_UNREACHABLE reached"); \
+ ABSL_INTERNAL_UNREACHABLE_IMPL(); \
+ } while (false)
+#endif
+
+// ABSL_ASSUME(cond)
+//
// Informs the compiler that a condition is always true and that it can assume
-// it to be true for optimization purposes. The call has undefined behavior if
-// the condition is false.
+// it to be true for optimization purposes.
+//
+// WARNING: If the condition is false, the program can produce undefined and
+// potentially dangerous behavior.
+//
// In !NDEBUG mode, the condition is checked with an assert().
-// NOTE: The expression must not have side effects, as it will only be evaluated
-// in some compilation modes and not others.
+//
+// NOTE: The expression must not have side effects, as it may only be evaluated
+// in some compilation modes and not others. Some compilers may issue a warning
+// if the compiler cannot prove the expression has no side effects. For example,
+// the expression should not use a function call since the compiler cannot prove
+// that a function call does not have side effects.
//
// Example:
//
// int x = ...;
-// ABSL_INTERNAL_ASSUME(x >= 0);
+// ABSL_ASSUME(x >= 0);
// // The compiler can optimize the division to a simple right shift using the
// // assumption specified above.
// int y = x / 16;
//
#if !defined(NDEBUG)
-#define ABSL_INTERNAL_ASSUME(cond) assert(cond)
+#define ABSL_ASSUME(cond) assert(cond)
#elif ABSL_HAVE_BUILTIN(__builtin_assume)
-#define ABSL_INTERNAL_ASSUME(cond) __builtin_assume(cond)
+#define ABSL_ASSUME(cond) __builtin_assume(cond)
+#elif defined(_MSC_VER)
+#define ABSL_ASSUME(cond) __assume(cond)
+#elif defined(__cpp_lib_unreachable) && __cpp_lib_unreachable >= 202202L
+#define ABSL_ASSUME(cond) \
+ do { \
+ if (!(cond)) std::unreachable(); \
+ } while (false)
#elif defined(__GNUC__) || ABSL_HAVE_BUILTIN(__builtin_unreachable)
-#define ABSL_INTERNAL_ASSUME(cond) \
+#define ABSL_ASSUME(cond) \
do { \
if (!(cond)) __builtin_unreachable(); \
- } while (0)
-#elif defined(_MSC_VER)
-#define ABSL_INTERNAL_ASSUME(cond) __assume(cond)
+ } while (false)
#else
-#define ABSL_INTERNAL_ASSUME(cond) \
+#define ABSL_ASSUME(cond) \
do { \
static_cast<void>(false && (cond)); \
- } while (0)
+ } while (false)
#endif
// ABSL_INTERNAL_UNIQUE_SMALL_NAME(cond)
diff --git a/absl/base/options.h b/absl/base/options.h
index 230bf1e..a169658 100644
--- a/absl/base/options.h
+++ b/absl/base/options.h
@@ -67,12 +67,6 @@
#ifndef ABSL_BASE_OPTIONS_H_
#define ABSL_BASE_OPTIONS_H_
-// Include a standard library header to allow configuration based on the
-// standard library in use.
-#ifdef __cplusplus
-#include <ciso646>
-#endif
-
// -----------------------------------------------------------------------------
// Type Compatibility Options
// -----------------------------------------------------------------------------
@@ -182,6 +176,32 @@
#define ABSL_OPTION_USE_STD_VARIANT 2
+// ABSL_OPTION_USE_STD_ORDERING
+//
+// This option controls whether absl::{partial,weak,strong}_ordering are
+// implemented as aliases to the std:: ordering types, or as an independent
+// implementation.
+//
+// A value of 0 means to use Abseil's implementation. This requires only C++11
+// support, and is expected to work on every toolchain we support.
+//
+// A value of 1 means to use aliases. This requires that all code using Abseil
+// is built in C++20 mode or later.
+//
+// A value of 2 means to detect the C++ version being used to compile Abseil,
+// and use an alias only if working std:: ordering types are available. This
+// option is useful when you are building your program from source. It should
+// not be used otherwise -- for example, if you are distributing Abseil in a
+// binary package manager -- since in mode 2, they will name different types,
+// with different mangled names and binary layout, depending on the compiler
+// flags passed by the end user. For more info, see
+// https://abseil.io/about/design/dropin-types.
+//
+// User code should not inspect this macro. To check in the preprocessor if
+// the ordering types are aliases of std:: ordering types, use the feature macro
+// ABSL_USES_STD_ORDERING.
+
+#define ABSL_OPTION_USE_STD_ORDERING 2
// ABSL_OPTION_USE_INLINE_NAMESPACE
// ABSL_OPTION_INLINE_NAMESPACE_NAME
diff --git a/absl/base/policy_checks.h b/absl/base/policy_checks.h
index 06b3243..372e848 100644
--- a/absl/base/policy_checks.h
+++ b/absl/base/policy_checks.h
@@ -44,17 +44,17 @@
// Toolchain Check
// -----------------------------------------------------------------------------
-// We support MSVC++ 14.0 update 2 and later.
+// We support Visual Studio 2019 (MSVC++ 16.0) and later.
// This minimum will go up.
-#if defined(_MSC_FULL_VER) && _MSC_FULL_VER < 190023918 && !defined(__clang__)
-#error "This package requires Visual Studio 2015 Update 2 or higher."
+#if defined(_MSC_VER) && _MSC_VER < 1920 && !defined(__clang__)
+#error "This package requires Visual Studio 2019 (MSVC++ 16.0) or higher."
#endif
-// We support gcc 4.7 and later.
+// We support GCC 7 and later.
// This minimum will go up.
#if defined(__GNUC__) && !defined(__clang__)
-#if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 7)
-#error "This package requires gcc 4.7 or higher."
+#if __GNUC__ < 7
+#error "This package requires GCC 7 or higher."
#endif
#endif
@@ -69,13 +69,15 @@
// C++ Version Check
// -----------------------------------------------------------------------------
-// Enforce C++11 as the minimum. Note that Visual Studio has not
-// advanced __cplusplus despite being good enough for our purposes, so
-// so we exempt it from the check.
-#if defined(__cplusplus) && !defined(_MSC_VER)
-#if __cplusplus < 201103L
-#error "C++ versions less than C++11 are not supported."
-#endif
+// Enforce C++14 as the minimum.
+#if defined(_MSVC_LANG)
+#if _MSVC_LANG < 201402L
+#error "C++ versions less than C++14 are not supported."
+#endif // _MSVC_LANG < 201402L
+#elif defined(__cplusplus)
+#if __cplusplus < 201402L
+#error "C++ versions less than C++14 are not supported."
+#endif // __cplusplus < 201402L
#endif
// -----------------------------------------------------------------------------
diff --git a/absl/base/prefetch.h b/absl/base/prefetch.h
new file mode 100644
index 0000000..482cde3
--- /dev/null
+++ b/absl/base/prefetch.h
@@ -0,0 +1,209 @@
+// Copyright 2023 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// -----------------------------------------------------------------------------
+// File: prefetch.h
+// -----------------------------------------------------------------------------
+//
+// This header file defines prefetch functions to prefetch memory contents
+// into the first level cache (L1) for the current CPU. The prefetch logic
+// offered in this header is limited to prefetching first level cachelines
+// only, and is aimed at relatively 'simple' prefetching logic.
+//
+#ifndef ABSL_BASE_PREFETCH_H_
+#define ABSL_BASE_PREFETCH_H_
+
+#include "absl/base/attributes.h"
+#include "absl/base/config.h"
+
+#if defined(ABSL_INTERNAL_HAVE_SSE)
+#include <xmmintrin.h>
+#endif
+
+#if defined(_MSC_VER)
+#include <intrin.h>
+#if defined(ABSL_INTERNAL_HAVE_SSE)
+#pragma intrinsic(_mm_prefetch)
+#endif
+#endif
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+
+// Moves data into the L1 cache before it is read, or "prefetches" it.
+//
+// The value of `addr` is the address of the memory to prefetch. If
+// the target and compiler support it, data prefetch instructions are
+// generated. If the prefetch is done some time before the memory is
+// read, it may be in the cache by the time the read occurs.
+//
+// This method prefetches data with the highest degree of temporal locality;
+// data is prefetched where possible into all levels of the cache.
+//
+// Incorrect or gratuitous use of this function can degrade performance.
+// Use this function only when representative benchmarks show an improvement.
+//
+// Example:
+//
+// // Computes incremental checksum for `data`.
+// int ComputeChecksum(int sum, absl::string_view data);
+//
+// // Computes cumulative checksum for all values in `data`
+// int ComputeChecksum(absl::Span<const std::string> data) {
+// int sum = 0;
+// auto it = data.begin();
+// auto pit = data.begin();
+// auto end = data.end();
+// for (int dist = 8; dist > 0 && pit != data.end(); --dist, ++pit) {
+// absl::PrefetchToLocalCache(pit->data());
+// }
+// for (; pit != end; ++pit, ++it) {
+// sum = ComputeChecksum(sum, *it);
+// absl::PrefetchToLocalCache(pit->data());
+// }
+// for (; it != end; ++it) {
+// sum = ComputeChecksum(sum, *it);
+// }
+// return sum;
+// }
+//
+void PrefetchToLocalCache(const void* addr);
+
+// Moves data into the L1 cache before it is read, or "prefetches" it.
+//
+// This function is identical to `PrefetchToLocalCache()` except that it has
+// non-temporal locality: the fetched data should not be left in any of the
+// cache tiers. This is useful for cases where the data is used only once /
+// short term, for example, invoking a destructor on an object.
+//
+// Incorrect or gratuitous use of this function can degrade performance.
+// Use this function only when representative benchmarks show an improvement.
+//
+// Example:
+//
+// template <typename Iterator>
+// void DestroyPointers(Iterator begin, Iterator end) {
+// size_t distance = std::min(8U, bars.size());
+//
+// int dist = 8;
+// auto prefetch_it = begin;
+// while (prefetch_it != end && --dist;) {
+// absl::PrefetchToLocalCacheNta(*prefetch_it++);
+// }
+// while (prefetch_it != end) {
+// delete *begin++;
+// absl::PrefetchToLocalCacheNta(*prefetch_it++);
+// }
+// while (begin != end) {
+// delete *begin++;
+// }
+// }
+//
+void PrefetchToLocalCacheNta(const void* addr);
+
+// Moves data into the L1 cache with the intent to modify it.
+//
+// This function is similar to `PrefetchToLocalCache()` except that it
+// prefetches cachelines with an 'intent to modify' This typically includes
+// invalidating cache entries for this address in all other cache tiers, and an
+// exclusive access intent.
+//
+// Incorrect or gratuitous use of this function can degrade performance. As this
+// function can invalidate cached cachelines on other caches and computer cores,
+// incorrect usage of this function can have an even greater negative impact
+// than incorrect regular prefetches.
+// Use this function only when representative benchmarks show an improvement.
+//
+// Example:
+//
+// void* Arena::Allocate(size_t size) {
+// void* ptr = AllocateBlock(size);
+// absl::PrefetchToLocalCacheForWrite(ptr);
+// return ptr;
+// }
+//
+void PrefetchToLocalCacheForWrite(const void* addr);
+
+#if ABSL_HAVE_BUILTIN(__builtin_prefetch) || defined(__GNUC__)
+
+#define ABSL_HAVE_PREFETCH 1
+
+// See __builtin_prefetch:
+// https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html.
+//
+ABSL_ATTRIBUTE_ALWAYS_INLINE inline void PrefetchToLocalCache(
+ const void* addr) {
+ __builtin_prefetch(addr, 0, 3);
+}
+
+ABSL_ATTRIBUTE_ALWAYS_INLINE inline void PrefetchToLocalCacheNta(
+ const void* addr) {
+ __builtin_prefetch(addr, 0, 0);
+}
+
+ABSL_ATTRIBUTE_ALWAYS_INLINE inline void PrefetchToLocalCacheForWrite(
+ const void* addr) {
+ // [x86] gcc/clang don't generate PREFETCHW for __builtin_prefetch(.., 1)
+ // unless -march=broadwell or newer; this is not generally the default, so we
+ // manually emit prefetchw. PREFETCHW is recognized as a no-op on older Intel
+ // processors and has been present on AMD processors since the K6-2.
+#if defined(__x86_64__) && !defined(__PRFCHW__)
+ asm("prefetchw %0" : : "m"(*reinterpret_cast<const char*>(addr)));
+#else
+ __builtin_prefetch(addr, 1, 3);
+#endif
+}
+
+#elif defined(ABSL_INTERNAL_HAVE_SSE)
+
+#define ABSL_HAVE_PREFETCH 1
+
+ABSL_ATTRIBUTE_ALWAYS_INLINE inline void PrefetchToLocalCache(
+ const void* addr) {
+ _mm_prefetch(reinterpret_cast<const char*>(addr), _MM_HINT_T0);
+}
+
+ABSL_ATTRIBUTE_ALWAYS_INLINE inline void PrefetchToLocalCacheNta(
+ const void* addr) {
+ _mm_prefetch(reinterpret_cast<const char*>(addr), _MM_HINT_NTA);
+}
+
+ABSL_ATTRIBUTE_ALWAYS_INLINE inline void PrefetchToLocalCacheForWrite(
+ const void* addr) {
+#if defined(_MM_HINT_ET0)
+ _mm_prefetch(reinterpret_cast<const char*>(addr), _MM_HINT_ET0);
+#elif !defined(_MSC_VER) && defined(__x86_64__)
+ // _MM_HINT_ET0 is not universally supported. As we commented further
+ // up, PREFETCHW is recognized as a no-op on older Intel processors
+ // and has been present on AMD processors since the K6-2. We have this
+ // disabled for MSVC compilers as this miscompiles on older MSVC compilers.
+ asm("prefetchw %0" : : "m"(*reinterpret_cast<const char*>(addr)));
+#endif
+}
+
+#else
+
+ABSL_ATTRIBUTE_ALWAYS_INLINE inline void PrefetchToLocalCache(
+ const void* addr) {}
+ABSL_ATTRIBUTE_ALWAYS_INLINE inline void PrefetchToLocalCacheNta(
+ const void* addr) {}
+ABSL_ATTRIBUTE_ALWAYS_INLINE inline void PrefetchToLocalCacheForWrite(
+ const void* addr) {}
+
+#endif
+
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_BASE_PREFETCH_H_
diff --git a/absl/base/prefetch_test.cc b/absl/base/prefetch_test.cc
new file mode 100644
index 0000000..ee21989
--- /dev/null
+++ b/absl/base/prefetch_test.cc
@@ -0,0 +1,64 @@
+// Copyright 2023 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/base/prefetch.h"
+
+#include <memory>
+
+#include "gtest/gtest.h"
+
+namespace {
+
+// Below tests exercise the functions only to guarantee they compile and execute
+// correctly. We make no attempt at verifying any prefetch instructions being
+// generated and executed: we assume the various implementation in terms of
+// __builtin_prefetch() or x86 intrinsics to be correct and well tested.
+
+TEST(PrefetchTest, PrefetchToLocalCache_StackA) {
+ char buf[100] = {};
+ absl::PrefetchToLocalCache(buf);
+ absl::PrefetchToLocalCacheNta(buf);
+ absl::PrefetchToLocalCacheForWrite(buf);
+}
+
+TEST(PrefetchTest, PrefetchToLocalCache_Heap) {
+ auto memory = std::make_unique<char[]>(200 << 10);
+ memset(memory.get(), 0, 200 << 10);
+ absl::PrefetchToLocalCache(memory.get());
+ absl::PrefetchToLocalCacheNta(memory.get());
+ absl::PrefetchToLocalCacheForWrite(memory.get());
+ absl::PrefetchToLocalCache(memory.get() + (50 << 10));
+ absl::PrefetchToLocalCacheNta(memory.get() + (50 << 10));
+ absl::PrefetchToLocalCacheForWrite(memory.get() + (50 << 10));
+ absl::PrefetchToLocalCache(memory.get() + (100 << 10));
+ absl::PrefetchToLocalCacheNta(memory.get() + (100 << 10));
+ absl::PrefetchToLocalCacheForWrite(memory.get() + (100 << 10));
+ absl::PrefetchToLocalCache(memory.get() + (150 << 10));
+ absl::PrefetchToLocalCacheNta(memory.get() + (150 << 10));
+ absl::PrefetchToLocalCacheForWrite(memory.get() + (150 << 10));
+}
+
+TEST(PrefetchTest, PrefetchToLocalCache_Nullptr) {
+ absl::PrefetchToLocalCache(nullptr);
+ absl::PrefetchToLocalCacheNta(nullptr);
+ absl::PrefetchToLocalCacheForWrite(nullptr);
+}
+
+TEST(PrefetchTest, PrefetchToLocalCache_InvalidPtr) {
+ absl::PrefetchToLocalCache(reinterpret_cast<const void*>(0x785326532L));
+ absl::PrefetchToLocalCacheNta(reinterpret_cast<const void*>(0x785326532L));
+ absl::PrefetchToLocalCacheForWrite(reinterpret_cast<const void*>(0x78532L));
+}
+
+} // namespace
diff --git a/absl/base/spinlock_test_common.cc b/absl/base/spinlock_test_common.cc
index 2b572c5..e904715 100644
--- a/absl/base/spinlock_test_common.cc
+++ b/absl/base/spinlock_test_common.cc
@@ -34,7 +34,7 @@
#include "absl/synchronization/blocking_counter.h"
#include "absl/synchronization/notification.h"
-constexpr int32_t kNumThreads = 10;
+constexpr uint32_t kNumThreads = 10;
constexpr int32_t kIters = 1000;
namespace absl {
@@ -48,14 +48,16 @@
int64_t wait_end_time) {
return SpinLock::EncodeWaitCycles(wait_start_time, wait_end_time);
}
- static uint64_t DecodeWaitCycles(uint32_t lock_value) {
+ static int64_t DecodeWaitCycles(uint32_t lock_value) {
return SpinLock::DecodeWaitCycles(lock_value);
}
+
+ static bool IsCooperative(const SpinLock& l) { return l.IsCooperative(); }
};
namespace {
-static constexpr int kArrayLength = 10;
+static constexpr size_t kArrayLength = 10;
static uint32_t values[kArrayLength];
ABSL_CONST_INIT static SpinLock static_cooperative_spinlock(
@@ -79,11 +81,11 @@
return c;
}
-static void TestFunction(int thread_salt, SpinLock* spinlock) {
+static void TestFunction(uint32_t thread_salt, SpinLock* spinlock) {
for (int i = 0; i < kIters; i++) {
SpinLockHolder h(spinlock);
- for (int j = 0; j < kArrayLength; j++) {
- const int index = (j + thread_salt) % kArrayLength;
+ for (size_t j = 0; j < kArrayLength; j++) {
+ const size_t index = (j + thread_salt) % kArrayLength;
values[index] = Hash32(values[index], thread_salt);
std::this_thread::yield();
}
@@ -93,7 +95,7 @@
static void ThreadedTest(SpinLock* spinlock) {
std::vector<std::thread> threads;
threads.reserve(kNumThreads);
- for (int i = 0; i < kNumThreads; ++i) {
+ for (uint32_t i = 0; i < kNumThreads; ++i) {
threads.push_back(std::thread(TestFunction, i, spinlock));
}
for (auto& thread : threads) {
@@ -101,7 +103,7 @@
}
SpinLockHolder h(spinlock);
- for (int i = 1; i < kArrayLength; i++) {
+ for (size_t i = 1; i < kArrayLength; i++) {
EXPECT_EQ(values[0], values[i]);
}
}
@@ -133,28 +135,28 @@
// but the lower kProfileTimestampShift will be dropped.
const int kMaxCyclesShift =
32 - kLockwordReservedShift + kProfileTimestampShift;
- const uint64_t kMaxCycles = (int64_t{1} << kMaxCyclesShift) - 1;
+ const int64_t kMaxCycles = (int64_t{1} << kMaxCyclesShift) - 1;
// These bits should be zero after encoding.
const uint32_t kLockwordReservedMask = (1 << kLockwordReservedShift) - 1;
// These bits are dropped when wait cycles are encoded.
- const uint64_t kProfileTimestampMask = (1 << kProfileTimestampShift) - 1;
+ const int64_t kProfileTimestampMask = (1 << kProfileTimestampShift) - 1;
// Test a bunch of random values
std::default_random_engine generator;
// Shift to avoid overflow below.
- std::uniform_int_distribution<uint64_t> time_distribution(
- 0, std::numeric_limits<uint64_t>::max() >> 4);
- std::uniform_int_distribution<uint64_t> cycle_distribution(0, kMaxCycles);
+ std::uniform_int_distribution<int64_t> time_distribution(
+ 0, std::numeric_limits<int64_t>::max() >> 3);
+ std::uniform_int_distribution<int64_t> cycle_distribution(0, kMaxCycles);
for (int i = 0; i < 100; i++) {
int64_t start_time = time_distribution(generator);
int64_t cycles = cycle_distribution(generator);
int64_t end_time = start_time + cycles;
uint32_t lock_value = SpinLockTest::EncodeWaitCycles(start_time, end_time);
- EXPECT_EQ(0, lock_value & kLockwordReservedMask);
- uint64_t decoded = SpinLockTest::DecodeWaitCycles(lock_value);
+ EXPECT_EQ(0u, lock_value & kLockwordReservedMask);
+ int64_t decoded = SpinLockTest::DecodeWaitCycles(lock_value);
EXPECT_EQ(0, decoded & kProfileTimestampMask);
EXPECT_EQ(cycles & ~kProfileTimestampMask, decoded);
}
@@ -178,21 +180,21 @@
// Test clamping
uint32_t max_value =
SpinLockTest::EncodeWaitCycles(start_time, start_time + kMaxCycles);
- uint64_t max_value_decoded = SpinLockTest::DecodeWaitCycles(max_value);
- uint64_t expected_max_value_decoded = kMaxCycles & ~kProfileTimestampMask;
+ int64_t max_value_decoded = SpinLockTest::DecodeWaitCycles(max_value);
+ int64_t expected_max_value_decoded = kMaxCycles & ~kProfileTimestampMask;
EXPECT_EQ(expected_max_value_decoded, max_value_decoded);
const int64_t step = (1 << kProfileTimestampShift);
uint32_t after_max_value =
SpinLockTest::EncodeWaitCycles(start_time, start_time + kMaxCycles + step);
- uint64_t after_max_value_decoded =
+ int64_t after_max_value_decoded =
SpinLockTest::DecodeWaitCycles(after_max_value);
EXPECT_EQ(expected_max_value_decoded, after_max_value_decoded);
uint32_t before_max_value = SpinLockTest::EncodeWaitCycles(
start_time, start_time + kMaxCycles - step);
- uint64_t before_max_value_decoded =
- SpinLockTest::DecodeWaitCycles(before_max_value);
+ int64_t before_max_value_decoded =
+ SpinLockTest::DecodeWaitCycles(before_max_value);
EXPECT_GT(expected_max_value_decoded, before_max_value_decoded);
}
@@ -266,6 +268,17 @@
base_internal::NumCPUs() * 2);
}
+TEST(SpinLockTest, IsCooperative) {
+ SpinLock default_constructor;
+ EXPECT_TRUE(SpinLockTest::IsCooperative(default_constructor));
+
+ SpinLock cooperative(base_internal::SCHEDULE_COOPERATIVE_AND_KERNEL);
+ EXPECT_TRUE(SpinLockTest::IsCooperative(cooperative));
+
+ SpinLock kernel_only(base_internal::SCHEDULE_KERNEL_ONLY);
+ EXPECT_FALSE(SpinLockTest::IsCooperative(kernel_only));
+}
+
} // namespace
} // namespace base_internal
ABSL_NAMESPACE_END
diff --git a/absl/base/thread_annotations.h b/absl/base/thread_annotations.h
index 9695f6d..4a3f3e3 100644
--- a/absl/base/thread_annotations.h
+++ b/absl/base/thread_annotations.h
@@ -36,8 +36,6 @@
#include "absl/base/attributes.h"
#include "absl/base/config.h"
-// TODO(mbonadei): Remove after the backward compatibility period.
-#include "absl/base/internal/thread_annotations.h" // IWYU pragma: export
// ABSL_GUARDED_BY()
//
@@ -154,8 +152,8 @@
// ABSL_LOCKS_EXCLUDED()
//
-// Documents the locks acquired in the body of the function. These locks
-// cannot be held when calling this function (as Abseil's `Mutex` locks are
+// Documents the locks that cannot be held by callers of this function, as they
+// might be acquired by this function (Abseil's `Mutex` locks are
// non-reentrant).
#if ABSL_HAVE_ATTRIBUTE(locks_excluded)
#define ABSL_LOCKS_EXCLUDED(...) __attribute__((locks_excluded(__VA_ARGS__)))
diff --git a/absl/base/throw_delegate_test.cc b/absl/base/throw_delegate_test.cc
index 5ba4ce5..e74362b 100644
--- a/absl/base/throw_delegate_test.cc
+++ b/absl/base/throw_delegate_test.cc
@@ -78,29 +78,97 @@
#endif
}
-TEST(ThrowHelper, Test) {
- // Not using EXPECT_THROW because we want to check the .what() message too.
+TEST(ThrowDelegate, ThrowStdLogicErrorChar) {
ExpectThrowChar<std::logic_error>(ThrowStdLogicError);
+}
+
+TEST(ThrowDelegate, ThrowStdInvalidArgumentChar) {
ExpectThrowChar<std::invalid_argument>(ThrowStdInvalidArgument);
+}
+
+TEST(ThrowDelegate, ThrowStdDomainErrorChar) {
ExpectThrowChar<std::domain_error>(ThrowStdDomainError);
+}
+
+TEST(ThrowDelegate, ThrowStdLengthErrorChar) {
ExpectThrowChar<std::length_error>(ThrowStdLengthError);
+}
+
+TEST(ThrowDelegate, ThrowStdOutOfRangeChar) {
ExpectThrowChar<std::out_of_range>(ThrowStdOutOfRange);
+}
+
+TEST(ThrowDelegate, ThrowStdRuntimeErrorChar) {
ExpectThrowChar<std::runtime_error>(ThrowStdRuntimeError);
+}
+
+TEST(ThrowDelegate, ThrowStdRangeErrorChar) {
ExpectThrowChar<std::range_error>(ThrowStdRangeError);
+}
+
+TEST(ThrowDelegate, ThrowStdOverflowErrorChar) {
ExpectThrowChar<std::overflow_error>(ThrowStdOverflowError);
+}
+
+TEST(ThrowDelegate, ThrowStdUnderflowErrorChar) {
ExpectThrowChar<std::underflow_error>(ThrowStdUnderflowError);
+}
+TEST(ThrowDelegate, ThrowStdLogicErrorString) {
ExpectThrowString<std::logic_error>(ThrowStdLogicError);
- ExpectThrowString<std::invalid_argument>(ThrowStdInvalidArgument);
- ExpectThrowString<std::domain_error>(ThrowStdDomainError);
- ExpectThrowString<std::length_error>(ThrowStdLengthError);
- ExpectThrowString<std::out_of_range>(ThrowStdOutOfRange);
- ExpectThrowString<std::runtime_error>(ThrowStdRuntimeError);
- ExpectThrowString<std::range_error>(ThrowStdRangeError);
- ExpectThrowString<std::overflow_error>(ThrowStdOverflowError);
- ExpectThrowString<std::underflow_error>(ThrowStdUnderflowError);
+}
- ExpectThrowNoWhat<std::bad_function_call>(ThrowStdBadFunctionCall);
+TEST(ThrowDelegate, ThrowStdInvalidArgumentString) {
+ ExpectThrowString<std::invalid_argument>(ThrowStdInvalidArgument);
+}
+
+TEST(ThrowDelegate, ThrowStdDomainErrorString) {
+ ExpectThrowString<std::domain_error>(ThrowStdDomainError);
+}
+
+TEST(ThrowDelegate, ThrowStdLengthErrorString) {
+ ExpectThrowString<std::length_error>(ThrowStdLengthError);
+}
+
+TEST(ThrowDelegate, ThrowStdOutOfRangeString) {
+ ExpectThrowString<std::out_of_range>(ThrowStdOutOfRange);
+}
+
+TEST(ThrowDelegate, ThrowStdRuntimeErrorString) {
+ ExpectThrowString<std::runtime_error>(ThrowStdRuntimeError);
+}
+
+TEST(ThrowDelegate, ThrowStdRangeErrorString) {
+ ExpectThrowString<std::range_error>(ThrowStdRangeError);
+}
+
+TEST(ThrowDelegate, ThrowStdOverflowErrorString) {
+ ExpectThrowString<std::overflow_error>(ThrowStdOverflowError);
+}
+
+TEST(ThrowDelegate, ThrowStdUnderflowErrorString) {
+ ExpectThrowString<std::underflow_error>(ThrowStdUnderflowError);
+}
+
+TEST(ThrowDelegate, ThrowStdBadFunctionCallNoWhat) {
+#ifdef ABSL_HAVE_EXCEPTIONS
+ try {
+ ThrowStdBadFunctionCall();
+ FAIL() << "Didn't throw";
+ } catch (const std::bad_function_call&) {
+ }
+#ifdef _LIBCPP_VERSION
+ catch (const std::exception&) {
+ // https://reviews.llvm.org/D92397 causes issues with the vtable for
+ // std::bad_function_call when using libc++ as a shared library.
+ }
+#endif
+#else
+ EXPECT_DEATH_IF_SUPPORTED(ThrowStdBadFunctionCall(), "");
+#endif
+}
+
+TEST(ThrowDelegate, ThrowStdBadAllocNoWhat) {
ExpectThrowNoWhat<std::bad_alloc>(ThrowStdBadAlloc);
}
diff --git a/absl/cleanup/BUILD.bazel b/absl/cleanup/BUILD.bazel
index 5cca898..984d571 100644
--- a/absl/cleanup/BUILD.bazel
+++ b/absl/cleanup/BUILD.bazel
@@ -12,7 +12,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test")
load(
"//absl:copts/configure_copts.bzl",
"ABSL_DEFAULT_COPTS",
@@ -20,7 +19,14 @@
"ABSL_TEST_COPTS",
)
-package(default_visibility = ["//visibility:public"])
+package(
+ default_visibility = ["//visibility:public"],
+ features = [
+ "header_modules",
+ "layering_check",
+ "parse_headers",
+ ],
+)
licenses(["notice"])
@@ -61,6 +67,7 @@
":cleanup",
"//absl/base:config",
"//absl/utility",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
diff --git a/absl/cleanup/CMakeLists.txt b/absl/cleanup/CMakeLists.txt
index 26a6d0d..f5af40b 100644
--- a/absl/cleanup/CMakeLists.txt
+++ b/absl/cleanup/CMakeLists.txt
@@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
cleanup_internal
diff --git a/absl/cleanup/cleanup.h b/absl/cleanup/cleanup.h
index 61b53d5..960ccd0 100644
--- a/absl/cleanup/cleanup.h
+++ b/absl/cleanup/cleanup.h
@@ -86,25 +86,25 @@
"Callbacks that return values are not supported.");
public:
- Cleanup(Callback callback) // NOLINT
- : storage_(std::move(callback), /* is_callback_engaged = */ true) {}
+ Cleanup(Callback callback) : storage_(std::move(callback)) {} // NOLINT
Cleanup(Cleanup&& other) = default;
void Cancel() && {
ABSL_HARDENING_ASSERT(storage_.IsCallbackEngaged());
- storage_.DisengageCallback();
+ storage_.DestroyCallback();
}
void Invoke() && {
ABSL_HARDENING_ASSERT(storage_.IsCallbackEngaged());
- storage_.DisengageCallback();
storage_.InvokeCallback();
+ storage_.DestroyCallback();
}
~Cleanup() {
if (storage_.IsCallbackEngaged()) {
storage_.InvokeCallback();
+ storage_.DestroyCallback();
}
}
diff --git a/absl/cleanup/cleanup_test.cc b/absl/cleanup/cleanup_test.cc
index 792595d..46b8858 100644
--- a/absl/cleanup/cleanup_test.cc
+++ b/absl/cleanup/cleanup_test.cc
@@ -264,4 +264,48 @@
EXPECT_FALSE(called); // Destructor shouldn't invoke the callback
}
+int DestructionCount = 0;
+
+struct DestructionCounter {
+ void operator()() {}
+
+ ~DestructionCounter() { ++DestructionCount; }
+};
+
+TYPED_TEST(CleanupTest, DestructorDestroys) {
+ {
+ auto cleanup =
+ absl::MakeCleanup(TypeParam::AsCallback(DestructionCounter()));
+ DestructionCount = 0;
+ }
+
+ EXPECT_EQ(DestructionCount, 1); // Engaged cleanup destroys
+}
+
+TYPED_TEST(CleanupTest, CancelDestroys) {
+ {
+ auto cleanup =
+ absl::MakeCleanup(TypeParam::AsCallback(DestructionCounter()));
+ DestructionCount = 0;
+
+ std::move(cleanup).Cancel();
+ EXPECT_EQ(DestructionCount, 1); // Cancel destroys
+ }
+
+ EXPECT_EQ(DestructionCount, 1); // Canceled cleanup does not double destroy
+}
+
+TYPED_TEST(CleanupTest, InvokeDestroys) {
+ {
+ auto cleanup =
+ absl::MakeCleanup(TypeParam::AsCallback(DestructionCounter()));
+ DestructionCount = 0;
+
+ std::move(cleanup).Invoke();
+ EXPECT_EQ(DestructionCount, 1); // Invoke destroys
+ }
+
+ EXPECT_EQ(DestructionCount, 1); // Invoked cleanup does not double destroy
+}
+
} // namespace
diff --git a/absl/cleanup/internal/cleanup.h b/absl/cleanup/internal/cleanup.h
index b4c4073..2783fcb 100644
--- a/absl/cleanup/internal/cleanup.h
+++ b/absl/cleanup/internal/cleanup.h
@@ -15,10 +15,12 @@
#ifndef ABSL_CLEANUP_INTERNAL_CLEANUP_H_
#define ABSL_CLEANUP_INTERNAL_CLEANUP_H_
+#include <new>
#include <type_traits>
#include <utility>
#include "absl/base/internal/invoke.h"
+#include "absl/base/macros.h"
#include "absl/base/thread_annotations.h"
#include "absl/utility/utility.h"
@@ -45,14 +47,22 @@
public:
Storage() = delete;
- Storage(Callback callback, bool is_callback_engaged)
- : callback_(std::move(callback)),
- is_callback_engaged_(is_callback_engaged) {}
+ explicit Storage(Callback callback) {
+ // Placement-new into a character buffer is used for eager destruction when
+ // the cleanup is invoked or cancelled. To ensure this optimizes well, the
+ // behavior is implemented locally instead of using an absl::optional.
+ ::new (GetCallbackBuffer()) Callback(std::move(callback));
+ is_callback_engaged_ = true;
+ }
- Storage(Storage&& other)
- : callback_(std::move(other.callback_)),
- is_callback_engaged_(
- absl::exchange(other.is_callback_engaged_, false)) {}
+ Storage(Storage&& other) {
+ ABSL_HARDENING_ASSERT(other.IsCallbackEngaged());
+
+ ::new (GetCallbackBuffer()) Callback(std::move(other.GetCallback()));
+ is_callback_engaged_ = true;
+
+ other.DestroyCallback();
+ }
Storage(const Storage& other) = delete;
@@ -60,17 +70,26 @@
Storage& operator=(const Storage& other) = delete;
+ void* GetCallbackBuffer() { return static_cast<void*>(+callback_buffer_); }
+
+ Callback& GetCallback() {
+ return *reinterpret_cast<Callback*>(GetCallbackBuffer());
+ }
+
bool IsCallbackEngaged() const { return is_callback_engaged_; }
- void DisengageCallback() { is_callback_engaged_ = false; }
+ void DestroyCallback() {
+ is_callback_engaged_ = false;
+ GetCallback().~Callback();
+ }
void InvokeCallback() ABSL_NO_THREAD_SAFETY_ANALYSIS {
- std::move(callback_)();
+ std::move(GetCallback())();
}
private:
- Callback callback_;
bool is_callback_engaged_;
+ alignas(Callback) char callback_buffer_[sizeof(Callback)];
};
} // namespace cleanup_internal
diff --git a/absl/container/BUILD.bazel b/absl/container/BUILD.bazel
index f22fdc6..0de4526 100644
--- a/absl/container/BUILD.bazel
+++ b/absl/container/BUILD.bazel
@@ -14,7 +14,6 @@
# limitations under the License.
#
-load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library", "cc_test")
load(
"//absl:copts/configure_copts.bzl",
"ABSL_DEFAULT_COPTS",
@@ -22,7 +21,14 @@
"ABSL_TEST_COPTS",
)
-package(default_visibility = ["//visibility:public"])
+package(
+ default_visibility = ["//visibility:public"],
+ features = [
+ "header_modules",
+ "layering_check",
+ "parse_headers",
+ ],
+)
licenses(["notice"])
@@ -48,6 +54,7 @@
"//absl/types:any",
"//absl/types:optional",
"//absl/utility",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -74,12 +81,13 @@
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
- ":counting_allocator",
":fixed_array",
+ ":test_allocator",
"//absl/base:config",
"//absl/base:exception_testing",
"//absl/hash:hash_testing",
"//absl/memory",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -93,12 +101,14 @@
":fixed_array",
"//absl/base:config",
"//absl/base:exception_safety_testing",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
-cc_test(
+cc_binary(
name = "fixed_array_benchmark",
+ testonly = True,
srcs = ["fixed_array_benchmark.cc"],
copts = ABSL_TEST_COPTS + ["$(STACK_FRAME_UNLIMITED)"],
linkopts = ABSL_DEFAULT_LINKOPTS,
@@ -116,6 +126,7 @@
linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
":compressed_tuple",
+ "//absl/base:config",
"//absl/base:core_headers",
"//absl/memory",
"//absl/meta:type_traits",
@@ -134,17 +145,17 @@
"//absl/base:core_headers",
"//absl/base:throw_delegate",
"//absl/memory",
+ "//absl/meta:type_traits",
],
)
cc_library(
- name = "counting_allocator",
- testonly = 1,
- hdrs = ["internal/counting_allocator.h"],
- copts = ABSL_DEFAULT_COPTS,
+ name = "test_allocator",
+ testonly = True,
+ copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
+ textual_hdrs = ["internal/test_allocator.h"],
visibility = ["//visibility:private"],
- deps = ["//absl/base:config"],
)
cc_test(
@@ -153,22 +164,24 @@
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
- ":counting_allocator",
":inlined_vector",
+ ":test_allocator",
":test_instance_tracker",
"//absl/base:config",
"//absl/base:core_headers",
"//absl/base:exception_testing",
- "//absl/base:raw_logging_internal",
"//absl/hash:hash_testing",
+ "//absl/log:check",
"//absl/memory",
"//absl/strings",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
-cc_test(
+cc_binary(
name = "inlined_vector_benchmark",
+ testonly = True,
srcs = ["inlined_vector_benchmark.cc"],
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
@@ -190,13 +203,14 @@
":inlined_vector",
"//absl/base:config",
"//absl/base:exception_safety_testing",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
cc_library(
name = "test_instance_tracker",
- testonly = 1,
+ testonly = True,
srcs = ["internal/test_instance_tracker.cc"],
hdrs = ["internal/test_instance_tracker.h"],
copts = ABSL_DEFAULT_COPTS,
@@ -214,15 +228,11 @@
linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
":test_instance_tracker",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
-NOTEST_TAGS_NONMOBILE = [
- "no_test_darwin_x86_64",
- "no_test_loonix",
-]
-
NOTEST_TAGS_MOBILE = [
"no_test_android_arm",
"no_test_android_arm64",
@@ -230,8 +240,6 @@
"no_test_ios_x86_64",
]
-NOTEST_TAGS = NOTEST_TAGS_MOBILE + NOTEST_TAGS_NONMOBILE
-
cc_library(
name = "flat_hash_map",
hdrs = ["flat_hash_map.h"],
@@ -242,6 +250,7 @@
":hash_function_defaults",
":raw_hash_map",
"//absl/algorithm:container",
+ "//absl/base:core_headers",
"//absl/memory",
],
)
@@ -251,16 +260,19 @@
srcs = ["flat_hash_map_test.cc"],
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
- tags = NOTEST_TAGS_NONMOBILE,
+ tags = ["no_test_loonix"],
deps = [
":flat_hash_map",
":hash_generator_testing",
+ ":test_allocator",
":unordered_map_constructor_test",
":unordered_map_lookup_test",
":unordered_map_members_test",
":unordered_map_modifiers_test",
- "//absl/base:raw_logging_internal",
+ "//absl/log:check",
+ "//absl/meta:type_traits",
"//absl/types:any",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -285,17 +297,21 @@
srcs = ["flat_hash_set_test.cc"],
copts = ABSL_TEST_COPTS + ["-DUNORDERED_SET_CXX17"],
linkopts = ABSL_DEFAULT_LINKOPTS,
- tags = NOTEST_TAGS_NONMOBILE,
+ tags = ["no_test_loonix"],
deps = [
+ ":container_memory",
":flat_hash_set",
":hash_generator_testing",
+ ":test_allocator",
":unordered_set_constructor_test",
":unordered_set_lookup_test",
":unordered_set_members_test",
":unordered_set_modifiers_test",
- "//absl/base:raw_logging_internal",
+ "//absl/base:config",
+ "//absl/log:check",
"//absl/memory",
"//absl/strings",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -308,9 +324,10 @@
deps = [
":container_memory",
":hash_function_defaults",
- ":node_hash_policy",
+ ":node_slot_policy",
":raw_hash_map",
"//absl/algorithm:container",
+ "//absl/base:core_headers",
"//absl/memory",
],
)
@@ -320,7 +337,7 @@
srcs = ["node_hash_map_test.cc"],
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
- tags = NOTEST_TAGS_NONMOBILE,
+ tags = ["no_test_loonix"],
deps = [
":hash_generator_testing",
":node_hash_map",
@@ -329,6 +346,7 @@
":unordered_map_lookup_test",
":unordered_map_members_test",
":unordered_map_modifiers_test",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -339,10 +357,12 @@
copts = ABSL_DEFAULT_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
+ ":container_memory",
":hash_function_defaults",
- ":node_hash_policy",
+ ":node_slot_policy",
":raw_hash_set",
"//absl/algorithm:container",
+ "//absl/base:core_headers",
"//absl/memory",
],
)
@@ -352,13 +372,14 @@
srcs = ["node_hash_set_test.cc"],
copts = ABSL_TEST_COPTS + ["-DUNORDERED_SET_CXX17"],
linkopts = ABSL_DEFAULT_LINKOPTS,
- tags = NOTEST_TAGS_NONMOBILE,
+ tags = ["no_test_loonix"],
deps = [
":node_hash_set",
":unordered_set_constructor_test",
":unordered_set_lookup_test",
":unordered_set_members_test",
":unordered_set_modifiers_test",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -381,11 +402,14 @@
srcs = ["internal/container_memory_test.cc"],
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
- tags = NOTEST_TAGS_NONMOBILE,
+ tags = ["no_test_loonix"],
deps = [
":container_memory",
":test_instance_tracker",
+ "//absl/base:no_destructor",
+ "//absl/meta:type_traits",
"//absl/strings",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -395,9 +419,14 @@
hdrs = ["internal/hash_function_defaults.h"],
copts = ABSL_DEFAULT_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
+ visibility = [
+ "//visibility:private",
+ ],
deps = [
+ ":common",
"//absl/base:config",
"//absl/hash",
+ "//absl/meta:type_traits",
"//absl/strings",
"//absl/strings:cord",
],
@@ -408,27 +437,31 @@
srcs = ["internal/hash_function_defaults_test.cc"],
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
- tags = NOTEST_TAGS,
+ tags = NOTEST_TAGS_MOBILE + ["no_test_loonix"],
deps = [
+ ":flat_hash_map",
+ ":flat_hash_set",
":hash_function_defaults",
"//absl/hash",
"//absl/random",
"//absl/strings",
"//absl/strings:cord",
"//absl/strings:cord_test_helpers",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
cc_library(
name = "hash_generator_testing",
- testonly = 1,
+ testonly = True,
srcs = ["internal/hash_generator_testing.cc"],
hdrs = ["internal/hash_generator_testing.h"],
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
":hash_policy_testing",
+ "//absl/base:no_destructor",
"//absl/memory",
"//absl/meta:type_traits",
"//absl/strings",
@@ -437,7 +470,7 @@
cc_library(
name = "hash_policy_testing",
- testonly = 1,
+ testonly = True,
hdrs = ["internal/hash_policy_testing.h"],
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
@@ -454,6 +487,7 @@
linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
":hash_policy_testing",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -463,7 +497,10 @@
hdrs = ["internal/hash_policy_traits.h"],
copts = ABSL_DEFAULT_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
- deps = ["//absl/meta:type_traits"],
+ deps = [
+ ":common_policy_traits",
+ "//absl/meta:type_traits",
+ ],
)
cc_test(
@@ -472,7 +509,31 @@
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
+ ":container_memory",
":hash_policy_traits",
+ "@com_google_googletest//:gtest",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_library(
+ name = "common_policy_traits",
+ hdrs = ["internal/common_policy_traits.h"],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ visibility = ["//visibility:private"],
+ deps = ["//absl/meta:type_traits"],
+)
+
+cc_test(
+ name = "common_policy_traits_test",
+ srcs = ["internal/common_policy_traits_test.cc"],
+ copts = ABSL_TEST_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ ":common_policy_traits",
+ "//absl/base:config",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -507,13 +568,17 @@
copts = ABSL_DEFAULT_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
- ":have_sse",
"//absl/base",
+ "//absl/base:config",
"//absl/base:core_headers",
- "//absl/base:exponential_biased",
+ "//absl/base:no_destructor",
+ "//absl/base:raw_logging_internal",
"//absl/debugging:stacktrace",
"//absl/memory",
+ "//absl/profiling:exponential_biased",
+ "//absl/profiling:sample_recorder",
"//absl/synchronization",
+ "//absl/time",
"//absl/utility",
],
)
@@ -522,33 +587,40 @@
name = "hashtablez_sampler_test",
srcs = ["internal/hashtablez_sampler_test.cc"],
linkopts = ABSL_DEFAULT_LINKOPTS,
+ tags = [
+ "no_test_wasm",
+ ],
deps = [
":hashtablez_sampler",
- ":have_sse",
+ "//absl/base:config",
"//absl/base:core_headers",
+ "//absl/profiling:sample_recorder",
"//absl/synchronization",
"//absl/synchronization:thread_pool",
"//absl/time",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
cc_library(
- name = "node_hash_policy",
- hdrs = ["internal/node_hash_policy.h"],
+ name = "node_slot_policy",
+ hdrs = ["internal/node_slot_policy.h"],
copts = ABSL_DEFAULT_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
deps = ["//absl/base:config"],
)
cc_test(
- name = "node_hash_policy_test",
- srcs = ["internal/node_hash_policy_test.cc"],
+ name = "node_slot_policy_test",
+ srcs = ["internal/node_slot_policy_test.cc"],
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
":hash_policy_traits",
- ":node_hash_policy",
+ ":node_slot_policy",
+ "//absl/base:config",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -561,19 +633,13 @@
deps = [
":container_memory",
":raw_hash_set",
+ "//absl/base:config",
+ "//absl/base:core_headers",
"//absl/base:throw_delegate",
],
)
cc_library(
- name = "have_sse",
- hdrs = ["internal/have_sse.h"],
- copts = ABSL_DEFAULT_COPTS,
- linkopts = ABSL_DEFAULT_LINKOPTS,
- visibility = ["//visibility:private"],
-)
-
-cc_library(
name = "common",
hdrs = ["internal/common.h"],
copts = ABSL_DEFAULT_COPTS,
@@ -597,11 +663,13 @@
":hash_policy_traits",
":hashtable_debug_hooks",
":hashtablez_sampler",
- ":have_sse",
- ":layout",
"//absl/base:config",
"//absl/base:core_headers",
+ "//absl/base:dynamic_annotations",
"//absl/base:endian",
+ "//absl/base:prefetch",
+ "//absl/base:raw_logging_internal",
+ "//absl/hash",
"//absl/memory",
"//absl/meta:type_traits",
"//absl/numeric:bits",
@@ -614,31 +682,45 @@
srcs = ["internal/raw_hash_set_test.cc"],
copts = ABSL_TEST_COPTS,
linkstatic = 1,
- tags = NOTEST_TAGS,
+ tags = NOTEST_TAGS_MOBILE + [
+ "no_test_loonix",
+ # TODO(b/237097643): investigate race and remove
+ "noarm_gemu",
+ ],
deps = [
":container_memory",
+ ":flat_hash_map",
+ ":flat_hash_set",
":hash_function_defaults",
":hash_policy_testing",
":hashtable_debug",
+ ":hashtablez_sampler",
":raw_hash_set",
+ ":test_allocator",
"//absl/base",
"//absl/base:config",
"//absl/base:core_headers",
- "//absl/base:raw_logging_internal",
+ "//absl/base:prefetch",
+ "//absl/hash",
+ "//absl/log",
+ "//absl/memory",
+ "//absl/meta:type_traits",
"//absl/strings",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
cc_binary(
name = "raw_hash_set_benchmark",
- testonly = 1,
+ testonly = True,
srcs = ["internal/raw_hash_set_benchmark.cc"],
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
tags = ["benchmark"],
visibility = ["//visibility:private"],
deps = [
+ ":container_memory",
":hash_function_defaults",
":raw_hash_set",
"//absl/base:raw_logging_internal",
@@ -649,7 +731,7 @@
cc_binary(
name = "raw_hash_set_probe_benchmark",
- testonly = 1,
+ testonly = True,
srcs = ["internal/raw_hash_set_probe_benchmark.cc"],
copts = ABSL_TEST_COPTS,
linkopts = select({
@@ -662,10 +744,12 @@
":hash_function_defaults",
":hashtable_debug",
":raw_hash_set",
+ "//absl/base:no_destructor",
"//absl/random",
"//absl/random:distributions",
"//absl/strings",
"//absl/strings:str_format",
+ "//absl/types:optional",
],
)
@@ -676,9 +760,11 @@
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
+ ":container_memory",
":raw_hash_set",
":tracked",
- "//absl/base:core_headers",
+ "//absl/base:config",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -691,6 +777,7 @@
deps = [
"//absl/base:config",
"//absl/base:core_headers",
+ "//absl/debugging:demangle_internal",
"//absl/meta:type_traits",
"//absl/strings",
"//absl/types:span",
@@ -704,21 +791,22 @@
srcs = ["internal/layout_test.cc"],
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
- tags = NOTEST_TAGS,
+ tags = NOTEST_TAGS_MOBILE + ["no_test_loonix"],
visibility = ["//visibility:private"],
deps = [
":layout",
"//absl/base:config",
- "//absl/base:core_headers",
- "//absl/base:raw_logging_internal",
+ "//absl/log:check",
"//absl/types:span",
+ "//absl/utility",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
cc_binary(
name = "layout_benchmark",
- testonly = 1,
+ testonly = True,
srcs = ["internal/layout_benchmark.cc"],
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
@@ -734,7 +822,7 @@
cc_library(
name = "tracked",
- testonly = 1,
+ testonly = True,
hdrs = ["internal/tracked.h"],
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
@@ -745,7 +833,7 @@
cc_library(
name = "unordered_map_constructor_test",
- testonly = 1,
+ testonly = True,
hdrs = ["internal/unordered_map_constructor_test.h"],
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
@@ -758,7 +846,7 @@
cc_library(
name = "unordered_map_lookup_test",
- testonly = 1,
+ testonly = True,
hdrs = ["internal/unordered_map_lookup_test.h"],
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
@@ -771,7 +859,7 @@
cc_library(
name = "unordered_map_modifiers_test",
- testonly = 1,
+ testonly = True,
hdrs = ["internal/unordered_map_modifiers_test.h"],
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
@@ -784,7 +872,7 @@
cc_library(
name = "unordered_set_constructor_test",
- testonly = 1,
+ testonly = True,
hdrs = ["internal/unordered_set_constructor_test.h"],
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
@@ -798,7 +886,7 @@
cc_library(
name = "unordered_set_members_test",
- testonly = 1,
+ testonly = True,
hdrs = ["internal/unordered_set_members_test.h"],
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
@@ -810,7 +898,7 @@
cc_library(
name = "unordered_map_members_test",
- testonly = 1,
+ testonly = True,
hdrs = ["internal/unordered_map_members_test.h"],
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
@@ -822,7 +910,7 @@
cc_library(
name = "unordered_set_lookup_test",
- testonly = 1,
+ testonly = True,
hdrs = ["internal/unordered_set_lookup_test.h"],
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
@@ -835,7 +923,7 @@
cc_library(
name = "unordered_set_modifiers_test",
- testonly = 1,
+ testonly = True,
hdrs = ["internal/unordered_set_modifiers_test.h"],
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
@@ -851,12 +939,13 @@
srcs = ["internal/unordered_set_test.cc"],
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
- tags = NOTEST_TAGS_NONMOBILE,
+ tags = ["no_test_loonix"],
deps = [
":unordered_set_constructor_test",
":unordered_set_lookup_test",
":unordered_set_members_test",
":unordered_set_modifiers_test",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -866,12 +955,30 @@
srcs = ["internal/unordered_map_test.cc"],
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
- tags = NOTEST_TAGS_NONMOBILE,
+ tags = ["no_test_loonix"],
deps = [
":unordered_map_constructor_test",
":unordered_map_lookup_test",
":unordered_map_members_test",
":unordered_map_modifiers_test",
+ "@com_google_googletest//:gtest",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "sample_element_size_test",
+ srcs = ["sample_element_size_test.cc"],
+ copts = ABSL_TEST_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ tags = ["no_test_loonix"],
+ visibility = ["//visibility:private"],
+ deps = [
+ ":flat_hash_map",
+ ":flat_hash_set",
+ ":node_hash_map",
+ ":node_hash_set",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -891,10 +998,12 @@
visibility = ["//visibility:public"],
deps = [
":common",
+ ":common_policy_traits",
":compressed_tuple",
":container_memory",
":layout",
"//absl/base:core_headers",
+ "//absl/base:raw_logging_internal",
"//absl/base:throw_delegate",
"//absl/memory",
"//absl/meta:type_traits",
@@ -907,7 +1016,7 @@
cc_library(
name = "btree_test_common",
- testonly = 1,
+ testonly = True,
hdrs = ["btree_test.h"],
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
@@ -930,27 +1039,35 @@
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
shard_count = 10,
+ tags = [
+ "no_test:os:ios",
+ "no_test_ios",
+ "no_test_wasm",
+ ],
visibility = ["//visibility:private"],
deps = [
":btree",
":btree_test_common",
- ":counting_allocator",
+ ":test_allocator",
":test_instance_tracker",
+ "//absl/algorithm:container",
"//absl/base:core_headers",
"//absl/base:raw_logging_internal",
"//absl/flags:flag",
"//absl/hash:hash_testing",
"//absl/memory",
- "//absl/meta:type_traits",
+ "//absl/random",
"//absl/strings",
"//absl/types:compare",
+ "//absl/types:optional",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
cc_binary(
name = "btree_benchmark",
- testonly = 1,
+ testonly = True,
srcs = [
"btree_benchmark.cc",
],
@@ -964,13 +1081,16 @@
":flat_hash_map",
":flat_hash_set",
":hashtable_debug",
+ "//absl/algorithm:container",
"//absl/base:raw_logging_internal",
- "//absl/flags:flag",
"//absl/hash",
+ "//absl/log",
"//absl/memory",
+ "//absl/random",
"//absl/strings:cord",
"//absl/strings:str_format",
"//absl/time",
"@com_github_google_benchmark//:benchmark_main",
+ "@com_google_googletest//:gtest",
],
)
diff --git a/absl/container/CMakeLists.txt b/absl/container/CMakeLists.txt
index 91c4015..4b08e6a 100644
--- a/absl/container/CMakeLists.txt
+++ b/absl/container/CMakeLists.txt
@@ -28,6 +28,7 @@
${ABSL_DEFAULT_LINKOPTS}
DEPS
absl::container_common
+ absl::common_policy_traits
absl::compare
absl::compressed_tuple
absl::container_memory
@@ -35,12 +36,14 @@
absl::core_headers
absl::layout
absl::memory
+ absl::raw_logging_internal
absl::strings
absl::throw_delegate
absl::type_traits
absl::utility
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
btree_test_common
@@ -69,20 +72,23 @@
LINKOPTS
${ABSL_DEFAULT_LINKOPTS}
DEPS
+ absl::algorithm_container
absl::btree
absl::btree_test_common
absl::compare
absl::core_headers
- absl::counting_allocator
absl::flags
absl::hash_testing
+ absl::optional
+ absl::random_random
absl::raw_logging_internal
absl::strings
+ absl::test_allocator
absl::test_instance_tracker
- absl::type_traits
GTest::gmock_main
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
compressed_tuple
@@ -139,11 +145,11 @@
${ABSL_TEST_COPTS}
DEPS
absl::fixed_array
- absl::counting_allocator
absl::config
absl::exception_testing
absl::hash_testing
absl::memory
+ absl::test_allocator
GTest::gmock_main
)
@@ -161,6 +167,7 @@
GTest::gmock_main
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
inlined_vector_internal
@@ -170,6 +177,7 @@
${ABSL_DEFAULT_COPTS}
DEPS
absl::compressed_tuple
+ absl::config
absl::core_headers
absl::memory
absl::span
@@ -190,18 +198,22 @@
absl::inlined_vector_internal
absl::throw_delegate
absl::memory
+ absl::type_traits
PUBLIC
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
- counting_allocator
+ test_allocator
HDRS
- "internal/counting_allocator.h"
+ "internal/test_allocator.h"
COPTS
${ABSL_DEFAULT_COPTS}
DEPS
absl::config
+ GTest::gmock
+ TESTONLY
)
absl_cc_test(
@@ -212,16 +224,16 @@
COPTS
${ABSL_TEST_COPTS}
DEPS
- absl::counting_allocator
- absl::inlined_vector
- absl::test_instance_tracker
+ absl::check
absl::config
absl::core_headers
absl::exception_testing
absl::hash_testing
+ absl::inlined_vector
absl::memory
- absl::raw_logging_internal
absl::strings
+ absl::test_allocator
+ absl::test_instance_tracker
GTest::gmock_main
)
@@ -239,6 +251,7 @@
GTest::gmock_main
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
test_instance_tracker
@@ -274,6 +287,7 @@
${ABSL_DEFAULT_COPTS}
DEPS
absl::container_memory
+ absl::core_headers
absl::hash_function_defaults
absl::raw_hash_map
absl::algorithm_container
@@ -289,14 +303,16 @@
COPTS
${ABSL_TEST_COPTS}
DEPS
+ absl::any
+ absl::check
absl::flat_hash_map
absl::hash_generator_testing
+ absl::test_allocator
+ absl::type_traits
absl::unordered_map_constructor_test
absl::unordered_map_lookup_test
absl::unordered_map_members_test
absl::unordered_map_modifiers_test
- absl::any
- absl::raw_logging_internal
GTest::gmock_main
)
@@ -326,15 +342,18 @@
${ABSL_TEST_COPTS}
"-DUNORDERED_SET_CXX17"
DEPS
+ absl::check
+ absl::config
+ absl::container_memory
absl::flat_hash_set
absl::hash_generator_testing
+ absl::memory
+ absl::strings
+ absl::test_allocator
absl::unordered_set_constructor_test
absl::unordered_set_lookup_test
absl::unordered_set_members_test
absl::unordered_set_modifiers_test
- absl::memory
- absl::raw_logging_internal
- absl::strings
GTest::gmock_main
)
@@ -347,8 +366,9 @@
${ABSL_DEFAULT_COPTS}
DEPS
absl::container_memory
+ absl::core_headers
absl::hash_function_defaults
- absl::node_hash_policy
+ absl::node_slot_policy
absl::raw_hash_map
absl::algorithm_container
absl::memory
@@ -381,8 +401,10 @@
COPTS
${ABSL_DEFAULT_COPTS}
DEPS
+ absl::container_memory
+ absl::core_headers
absl::hash_function_defaults
- absl::node_hash_policy
+ absl::node_slot_policy
absl::raw_hash_set
absl::algorithm_container
absl::memory
@@ -407,6 +429,7 @@
GTest::gmock_main
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
container_memory
@@ -431,11 +454,14 @@
${ABSL_TEST_COPTS}
DEPS
absl::container_memory
+ absl::no_destructor
absl::strings
absl::test_instance_tracker
+ absl::type_traits
GTest::gmock_main
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
hash_function_defaults
@@ -445,9 +471,11 @@
${ABSL_DEFAULT_COPTS}
DEPS
absl::config
+ absl::container_common
absl::cord
absl::hash
absl::strings
+ absl::type_traits
PUBLIC
)
@@ -461,6 +489,8 @@
DEPS
absl::cord
absl::cord_test_helpers
+ absl::flat_hash_map
+ absl::flat_hash_set
absl::hash_function_defaults
absl::hash
absl::random_random
@@ -468,6 +498,7 @@
GTest::gmock_main
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
hash_generator_testing
@@ -481,10 +512,12 @@
absl::hash_policy_testing
absl::memory
absl::meta
+ absl::no_destructor
absl::strings
TESTONLY
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
hash_policy_testing
@@ -510,6 +543,7 @@
GTest::gmock_main
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
hash_policy_traits
@@ -518,6 +552,7 @@
COPTS
${ABSL_DEFAULT_COPTS}
DEPS
+ absl::common_policy_traits
absl::meta
PUBLIC
)
@@ -530,10 +565,38 @@
COPTS
${ABSL_TEST_COPTS}
DEPS
+ absl::container_memory
absl::hash_policy_traits
GTest::gmock_main
)
+# Internal-only target, do not depend on directly.
+absl_cc_library(
+ NAME
+ common_policy_traits
+ HDRS
+ "internal/common_policy_traits.h"
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ DEPS
+ absl::meta
+ PUBLIC
+)
+
+absl_cc_test(
+ NAME
+ common_policy_traits_test
+ SRCS
+ "internal/common_policy_traits_test.cc"
+ COPTS
+ ${ABSL_TEST_COPTS}
+ DEPS
+ absl::common_policy_traits
+ absl::config
+ GTest::gmock_main
+)
+
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
hashtablez_sampler
@@ -546,9 +609,13 @@
${ABSL_DEFAULT_COPTS}
DEPS
absl::base
+ absl::config
absl::exponential_biased
- absl::have_sse
+ absl::no_destructor
+ absl::raw_logging_internal
+ absl::sample_recorder
absl::synchronization
+ absl::time
)
absl_cc_test(
@@ -559,11 +626,12 @@
COPTS
${ABSL_TEST_COPTS}
DEPS
+ absl::config
absl::hashtablez_sampler
- absl::have_sse
GTest::gmock_main
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
hashtable_debug
@@ -575,6 +643,7 @@
absl::hashtable_debug_hooks
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
hashtable_debug_hooks
@@ -587,20 +656,12 @@
PUBLIC
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
- have_sse
+ node_slot_policy
HDRS
- "internal/have_sse.h"
- COPTS
- ${ABSL_DEFAULT_COPTS}
-)
-
-absl_cc_library(
- NAME
- node_hash_policy
- HDRS
- "internal/node_hash_policy.h"
+ "internal/node_slot_policy.h"
COPTS
${ABSL_DEFAULT_COPTS}
DEPS
@@ -610,17 +671,19 @@
absl_cc_test(
NAME
- node_hash_policy_test
+ node_slot_policy_test
SRCS
- "internal/node_hash_policy_test.cc"
+ "internal/node_slot_policy_test.cc"
COPTS
${ABSL_TEST_COPTS}
DEPS
+ absl::config
absl::hash_policy_traits
- absl::node_hash_policy
+ absl::node_slot_policy
GTest::gmock_main
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
raw_hash_map
@@ -629,12 +692,15 @@
COPTS
${ABSL_DEFAULT_COPTS}
DEPS
+ absl::config
absl::container_memory
+ absl::core_headers
absl::raw_hash_set
absl::throw_delegate
PUBLIC
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
container_common
@@ -646,6 +712,7 @@
absl::type_traits
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
raw_hash_set
@@ -662,16 +729,18 @@
absl::container_common
absl::container_memory
absl::core_headers
+ absl::dynamic_annotations
absl::endian
+ absl::hash
absl::hash_policy_traits
absl::hashtable_debug_hooks
- absl::have_sse
- absl::layout
+ absl::hashtablez_sampler
absl::memory
absl::meta
absl::optional
+ absl::prefetch
+ absl::raw_logging_internal
absl::utility
- absl::hashtablez_sampler
PUBLIC
)
@@ -683,16 +752,24 @@
COPTS
${ABSL_TEST_COPTS}
DEPS
+ absl::base
+ absl::config
absl::container_memory
+ absl::core_headers
+ absl::flat_hash_map
+ absl::flat_hash_set
+ absl::hash
absl::hash_function_defaults
absl::hash_policy_testing
absl::hashtable_debug
+ absl::hashtablez_sampler
+ absl::log
+ absl::memory
+ absl::prefetch
absl::raw_hash_set
- absl::base
- absl::config
- absl::core_headers
- absl::raw_logging_internal
absl::strings
+ absl::test_allocator
+ absl::type_traits
GTest::gmock_main
)
@@ -704,12 +781,14 @@
COPTS
${ABSL_TEST_COPTS}
DEPS
+ absl::config
+ absl::container_memory
absl::raw_hash_set
absl::tracked
- absl::core_headers
GTest::gmock_main
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
layout
@@ -720,6 +799,7 @@
DEPS
absl::config
absl::core_headers
+ absl::debugging_internal
absl::meta
absl::strings
absl::span
@@ -736,13 +816,14 @@
${ABSL_TEST_COPTS}
DEPS
absl::layout
+ absl::check
absl::config
- absl::core_headers
- absl::raw_logging_internal
absl::span
+ absl::utility
GTest::gmock_main
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
tracked
@@ -755,6 +836,7 @@
TESTONLY
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
unordered_map_constructor_test
@@ -769,6 +851,7 @@
TESTONLY
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
unordered_map_lookup_test
@@ -783,6 +866,7 @@
TESTONLY
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
unordered_map_members_test
@@ -796,6 +880,7 @@
TESTONLY
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
unordered_map_modifiers_test
@@ -810,6 +895,7 @@
TESTONLY
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
unordered_set_constructor_test
@@ -824,6 +910,7 @@
TESTONLY
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
unordered_set_lookup_test
@@ -838,6 +925,7 @@
TESTONLY
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
unordered_set_members_test
@@ -851,6 +939,7 @@
TESTONLY
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
unordered_set_modifiers_test
@@ -894,3 +983,18 @@
absl::unordered_map_modifiers_test
GTest::gmock_main
)
+
+absl_cc_test(
+ NAME
+ sample_element_size_test
+ SRCS
+ "sample_element_size_test.cc"
+ COPTS
+ ${ABSL_TEST_COPTS}
+ DEPS
+ absl::flat_hash_map
+ absl::flat_hash_set
+ absl::node_hash_map
+ absl::node_hash_set
+ GTest::gmock_main
+)
diff --git a/absl/container/btree_benchmark.cc b/absl/container/btree_benchmark.cc
index 65b6790..0d26fd4 100644
--- a/absl/container/btree_benchmark.cc
+++ b/absl/container/btree_benchmark.cc
@@ -27,6 +27,7 @@
#include <vector>
#include "benchmark/benchmark.h"
+#include "absl/algorithm/container.h"
#include "absl/base/internal/raw_logging.h"
#include "absl/container/btree_map.h"
#include "absl/container/btree_set.h"
@@ -34,9 +35,10 @@
#include "absl/container/flat_hash_map.h"
#include "absl/container/flat_hash_set.h"
#include "absl/container/internal/hashtable_debug.h"
-#include "absl/flags/flag.h"
#include "absl/hash/hash.h"
+#include "absl/log/log.h"
#include "absl/memory/memory.h"
+#include "absl/random/random.h"
#include "absl/strings/cord.h"
#include "absl/strings/str_format.h"
#include "absl/time/time.h"
@@ -153,9 +155,9 @@
BM_LookupImpl<T>(state, true);
}
-// Benchmark deletion of values from a container.
+// Benchmark erasing values from a container.
template <typename T>
-void BM_Delete(benchmark::State& state) {
+void BM_Erase(benchmark::State& state) {
using V = typename remove_pair_const<typename T::value_type>::type;
typename KeyOfValue<typename T::key_type, V>::type key_of_value;
std::vector<V> values = GenerateValues<V>(kBenchmarkValues);
@@ -180,9 +182,9 @@
}
}
-// Benchmark deletion of multiple values from a container.
+// Benchmark erasing multiple values from a container.
template <typename T>
-void BM_DeleteRange(benchmark::State& state) {
+void BM_EraseRange(benchmark::State& state) {
using V = typename remove_pair_const<typename T::value_type>::type;
typename KeyOfValue<typename T::key_type, V>::type key_of_value;
std::vector<V> values = GenerateValues<V>(kBenchmarkValues);
@@ -222,6 +224,40 @@
}
}
+// Predicate that erases every other element. We can't use a lambda because
+// C++11 doesn't support generic lambdas.
+// TODO(b/207389011): consider adding benchmarks that remove different fractions
+// of keys (e.g. 10%, 90%).
+struct EraseIfPred {
+ uint64_t i = 0;
+ template <typename T>
+ bool operator()(const T&) {
+ return ++i % 2;
+ }
+};
+
+// Benchmark erasing multiple values from a container with a predicate.
+template <typename T>
+void BM_EraseIf(benchmark::State& state) {
+ using V = typename remove_pair_const<typename T::value_type>::type;
+ std::vector<V> values = GenerateValues<V>(kBenchmarkValues);
+
+ // Removes half of the keys per batch.
+ const int batch_size = (kBenchmarkValues + 1) / 2;
+ EraseIfPred pred;
+ while (state.KeepRunningBatch(batch_size)) {
+ state.PauseTiming();
+ {
+ T container(values.begin(), values.end());
+ state.ResumeTiming();
+ erase_if(container, pred);
+ benchmark::DoNotOptimize(container);
+ state.PauseTiming();
+ }
+ state.ResumeTiming();
+ }
+}
+
// Benchmark steady-state insert (into first half of range) and remove (from
// second half of range), treating the container approximately like a queue with
// log-time access for all elements. This benchmark does not test the case where
@@ -477,14 +513,14 @@
void BM_##type##_##func(benchmark::State& state) { BM_##func<type>(state); } \
BENCHMARK(BM_##type##_##func)
-#define MY_BENCHMARK3(type) \
+#define MY_BENCHMARK3_STL(type) \
MY_BENCHMARK4(type, Insert); \
MY_BENCHMARK4(type, InsertSorted); \
MY_BENCHMARK4(type, InsertSmall); \
MY_BENCHMARK4(type, Lookup); \
MY_BENCHMARK4(type, FullLookup); \
- MY_BENCHMARK4(type, Delete); \
- MY_BENCHMARK4(type, DeleteRange); \
+ MY_BENCHMARK4(type, Erase); \
+ MY_BENCHMARK4(type, EraseRange); \
MY_BENCHMARK4(type, QueueAddRem); \
MY_BENCHMARK4(type, MixedAddRem); \
MY_BENCHMARK4(type, Fifo); \
@@ -492,9 +528,13 @@
MY_BENCHMARK4(type, InsertRangeRandom); \
MY_BENCHMARK4(type, InsertRangeSorted)
+#define MY_BENCHMARK3(type) \
+ MY_BENCHMARK4(type, EraseIf); \
+ MY_BENCHMARK3_STL(type)
+
#define MY_BENCHMARK2_SUPPORTS_MULTI_ONLY(type) \
- MY_BENCHMARK3(stl_##type); \
- MY_BENCHMARK3(stl_unordered_##type); \
+ MY_BENCHMARK3_STL(stl_##type); \
+ MY_BENCHMARK3_STL(stl_unordered_##type); \
MY_BENCHMARK3(btree_256_##type)
#define MY_BENCHMARK2(type) \
@@ -684,17 +724,40 @@
btree_set<BigTypePtr<SIZE>>; \
using btree_256_map_size##SIZE##copies##SIZE##ptr = \
btree_map<int, BigTypePtr<SIZE>>; \
- MY_BENCHMARK3(stl_set_size##SIZE##copies##SIZE##ptr); \
- MY_BENCHMARK3(stl_unordered_set_size##SIZE##copies##SIZE##ptr); \
+ MY_BENCHMARK3_STL(stl_set_size##SIZE##copies##SIZE##ptr); \
+ MY_BENCHMARK3_STL(stl_unordered_set_size##SIZE##copies##SIZE##ptr); \
MY_BENCHMARK3(flat_hash_set_size##SIZE##copies##SIZE##ptr); \
MY_BENCHMARK3(btree_256_set_size##SIZE##copies##SIZE##ptr); \
- MY_BENCHMARK3(stl_map_size##SIZE##copies##SIZE##ptr); \
- MY_BENCHMARK3(stl_unordered_map_size##SIZE##copies##SIZE##ptr); \
+ MY_BENCHMARK3_STL(stl_map_size##SIZE##copies##SIZE##ptr); \
+ MY_BENCHMARK3_STL(stl_unordered_map_size##SIZE##copies##SIZE##ptr); \
MY_BENCHMARK3(flat_hash_map_size##SIZE##copies##SIZE##ptr); \
MY_BENCHMARK3(btree_256_map_size##SIZE##copies##SIZE##ptr)
BIG_TYPE_PTR_BENCHMARKS(32);
+void BM_BtreeSet_IteratorSubtraction(benchmark::State& state) {
+ absl::InsecureBitGen bitgen;
+ std::vector<int> vec;
+ // Randomize the set's insertion order so the nodes aren't all full.
+ vec.reserve(state.range(0));
+ for (int i = 0; i < state.range(0); ++i) vec.push_back(i);
+ absl::c_shuffle(vec, bitgen);
+
+ absl::btree_set<int> set;
+ for (int i : vec) set.insert(i);
+
+ size_t distance = absl::Uniform(bitgen, 0u, set.size());
+ while (state.KeepRunningBatch(distance)) {
+ size_t end = absl::Uniform(bitgen, distance, set.size());
+ size_t begin = end - distance;
+ benchmark::DoNotOptimize(set.find(static_cast<int>(end)) -
+ set.find(static_cast<int>(begin)));
+ distance = absl::Uniform(bitgen, 0u, set.size());
+ }
+}
+
+BENCHMARK(BM_BtreeSet_IteratorSubtraction)->Range(1 << 10, 1 << 20);
+
} // namespace
} // namespace container_internal
ABSL_NAMESPACE_END
diff --git a/absl/container/btree_map.h b/absl/container/btree_map.h
index ea49d44..0f62f0b 100644
--- a/absl/container/btree_map.h
+++ b/absl/container/btree_map.h
@@ -35,24 +35,39 @@
//
// However, these types should not be considered drop-in replacements for
// `std::map` and `std::multimap` as there are some API differences, which are
-// noted in this header file.
+// noted in this header file. The most consequential differences with respect to
+// migrating to b-tree from the STL types are listed in the next paragraph.
+// Other API differences are minor.
//
// Importantly, insertions and deletions may invalidate outstanding iterators,
// pointers, and references to elements. Such invalidations are typically only
// an issue if insertion and deletion operations are interleaved with the use of
-// more than one iterator, pointer, or reference simultaneously. For this
-// reason, `insert()` and `erase()` return a valid iterator at the current
-// position.
+// more than one iterator, pointer, or reference simultaneously. For this
+// reason, `insert()`, `erase()`, and `extract_and_get_next()` return a valid
+// iterator at the current position. Another important difference is that
+// key-types must be copy-constructible.
+//
+// Another API difference is that btree iterators can be subtracted, and this
+// is faster than using std::distance.
#ifndef ABSL_CONTAINER_BTREE_MAP_H_
#define ABSL_CONTAINER_BTREE_MAP_H_
+#include "absl/base/attributes.h"
#include "absl/container/internal/btree.h" // IWYU pragma: export
#include "absl/container/internal/btree_container.h" // IWYU pragma: export
namespace absl {
ABSL_NAMESPACE_BEGIN
+namespace container_internal {
+
+template <typename Key, typename Data, typename Compare, typename Alloc,
+ int TargetNodeSize, bool IsMulti>
+struct map_params;
+
+} // namespace container_internal
+
// absl::btree_map<>
//
// An `absl::btree_map<K, V>` is an ordered associative container of
@@ -74,7 +89,7 @@
: public container_internal::btree_map_container<
container_internal::btree<container_internal::map_params<
Key, Value, Compare, Alloc, /*TargetNodeSize=*/256,
- /*Multi=*/false>>> {
+ /*IsMulti=*/false>>> {
using Base = typename btree_map::btree_map_container;
public:
@@ -311,7 +326,8 @@
// btree_map::extract()
//
// Extracts the indicated element, erasing it in the process, and returns it
- // as a C++17-compatible node handle. Overloads are listed below.
+ // as a C++17-compatible node handle. Any references, pointers, or iterators
+ // are invalidated. Overloads are listed below.
//
// node_type extract(const_iterator position):
//
@@ -336,6 +352,21 @@
// It does NOT refer to the data layout of the underlying btree.
using Base::extract;
+ // btree_map::extract_and_get_next()
+ //
+ // Extracts the indicated element, erasing it in the process, and returns it
+ // as a C++17-compatible node handle along with an iterator to the next
+ // element.
+ //
+ // extract_and_get_next_return_type extract_and_get_next(
+ // const_iterator position):
+ //
+ // Extracts the element at the indicated position, returns a struct
+ // containing a member named `node`: a node handle owning that extracted
+ // data and a member named `next`: an iterator pointing to the next element
+ // in the btree.
+ using Base::extract_and_get_next;
+
// btree_map::merge()
//
// Extracts elements from a given `source` btree_map into this
@@ -366,8 +397,8 @@
// Determines whether an element comparing equal to the given `key` exists
// within the `btree_map`, returning `true` if so or `false` otherwise.
//
- // Supports heterogeneous lookup, provided that the map is provided a
- // compatible heterogeneous comparator.
+ // Supports heterogeneous lookup, provided that the map has a compatible
+ // heterogeneous comparator.
using Base::contains;
// btree_map::count()
@@ -378,8 +409,8 @@
// the `btree_map`. Note that this function will return either `1` or `0`
// since duplicate elements are not allowed within a `btree_map`.
//
- // Supports heterogeneous lookup, provided that the map is provided a
- // compatible heterogeneous comparator.
+ // Supports heterogeneous lookup, provided that the map has a compatible
+ // heterogeneous comparator.
using Base::count;
// btree_map::equal_range()
@@ -395,10 +426,34 @@
//
// Finds an element with the passed `key` within the `btree_map`.
//
- // Supports heterogeneous lookup, provided that the map is provided a
- // compatible heterogeneous comparator.
+ // Supports heterogeneous lookup, provided that the map has a compatible
+ // heterogeneous comparator.
using Base::find;
+ // btree_map::lower_bound()
+ //
+ // template <typename K> iterator lower_bound(const K& key):
+ // template <typename K> const_iterator lower_bound(const K& key) const:
+ //
+ // Finds the first element with a key that is not less than `key` within the
+ // `btree_map`.
+ //
+ // Supports heterogeneous lookup, provided that the map has a compatible
+ // heterogeneous comparator.
+ using Base::lower_bound;
+
+ // btree_map::upper_bound()
+ //
+ // template <typename K> iterator upper_bound(const K& key):
+ // template <typename K> const_iterator upper_bound(const K& key) const:
+ //
+ // Finds the first element with a key that is greater than `key` within the
+ // `btree_map`.
+ //
+ // Supports heterogeneous lookup, provided that the map has a compatible
+ // heterogeneous comparator.
+ using Base::upper_bound;
+
// btree_map::operator[]()
//
// Returns a reference to the value mapped to the passed key within the
@@ -443,15 +498,11 @@
// absl::erase_if(absl::btree_map<>, Pred)
//
// Erases all elements that satisfy the predicate pred from the container.
+// Returns the number of erased elements.
template <typename K, typename V, typename C, typename A, typename Pred>
-void erase_if(btree_map<K, V, C, A> &map, Pred pred) {
- for (auto it = map.begin(); it != map.end();) {
- if (pred(*it)) {
- it = map.erase(it);
- } else {
- ++it;
- }
- }
+typename btree_map<K, V, C, A>::size_type erase_if(
+ btree_map<K, V, C, A> &map, Pred pred) {
+ return container_internal::btree_access::erase_if(map, std::move(pred));
}
// absl::btree_multimap
@@ -476,7 +527,7 @@
: public container_internal::btree_multimap_container<
container_internal::btree<container_internal::map_params<
Key, Value, Compare, Alloc, /*TargetNodeSize=*/256,
- /*Multi=*/true>>> {
+ /*IsMulti=*/true>>> {
using Base = typename btree_multimap::btree_multimap_container;
public:
@@ -667,11 +718,25 @@
// It does NOT refer to the data layout of the underlying btree.
using Base::extract;
+ // btree_multimap::extract_and_get_next()
+ //
+ // Extracts the indicated element, erasing it in the process, and returns it
+ // as a C++17-compatible node handle along with an iterator to the next
+ // element.
+ //
+ // extract_and_get_next_return_type extract_and_get_next(
+ // const_iterator position):
+ //
+ // Extracts the element at the indicated position, returns a struct
+ // containing a member named `node`: a node handle owning that extracted
+ // data and a member named `next`: an iterator pointing to the next element
+ // in the btree.
+ using Base::extract_and_get_next;
+
// btree_multimap::merge()
//
- // Extracts elements from a given `source` btree_multimap into this
- // `btree_multimap`. If the destination `btree_multimap` already contains an
- // element with an equivalent key, that element is not extracted.
+ // Extracts all elements from a given `source` btree_multimap into this
+ // `btree_multimap`.
using Base::merge;
// btree_multimap::swap(btree_multimap& other)
@@ -691,8 +756,8 @@
// Determines whether an element comparing equal to the given `key` exists
// within the `btree_multimap`, returning `true` if so or `false` otherwise.
//
- // Supports heterogeneous lookup, provided that the map is provided a
- // compatible heterogeneous comparator.
+ // Supports heterogeneous lookup, provided that the map has a compatible
+ // heterogeneous comparator.
using Base::contains;
// btree_multimap::count()
@@ -702,8 +767,8 @@
// Returns the number of elements comparing equal to the given `key` within
// the `btree_multimap`.
//
- // Supports heterogeneous lookup, provided that the map is provided a
- // compatible heterogeneous comparator.
+ // Supports heterogeneous lookup, provided that the map has a compatible
+ // heterogeneous comparator.
using Base::count;
// btree_multimap::equal_range()
@@ -720,10 +785,34 @@
//
// Finds an element with the passed `key` within the `btree_multimap`.
//
- // Supports heterogeneous lookup, provided that the map is provided a
- // compatible heterogeneous comparator.
+ // Supports heterogeneous lookup, provided that the map has a compatible
+ // heterogeneous comparator.
using Base::find;
+ // btree_multimap::lower_bound()
+ //
+ // template <typename K> iterator lower_bound(const K& key):
+ // template <typename K> const_iterator lower_bound(const K& key) const:
+ //
+ // Finds the first element with a key that is not less than `key` within the
+ // `btree_multimap`.
+ //
+ // Supports heterogeneous lookup, provided that the map has a compatible
+ // heterogeneous comparator.
+ using Base::lower_bound;
+
+ // btree_multimap::upper_bound()
+ //
+ // template <typename K> iterator upper_bound(const K& key):
+ // template <typename K> const_iterator upper_bound(const K& key) const:
+ //
+ // Finds the first element with a key that is greater than `key` within the
+ // `btree_multimap`.
+ //
+ // Supports heterogeneous lookup, provided that the map has a compatible
+ // heterogeneous comparator.
+ using Base::upper_bound;
+
// btree_multimap::get_allocator()
//
// Returns the allocator function associated with this `btree_multimap`.
@@ -751,17 +840,47 @@
// absl::erase_if(absl::btree_multimap<>, Pred)
//
// Erases all elements that satisfy the predicate pred from the container.
+// Returns the number of erased elements.
template <typename K, typename V, typename C, typename A, typename Pred>
-void erase_if(btree_multimap<K, V, C, A> &map, Pred pred) {
- for (auto it = map.begin(); it != map.end();) {
- if (pred(*it)) {
- it = map.erase(it);
- } else {
- ++it;
- }
- }
+typename btree_multimap<K, V, C, A>::size_type erase_if(
+ btree_multimap<K, V, C, A> &map, Pred pred) {
+ return container_internal::btree_access::erase_if(map, std::move(pred));
}
+namespace container_internal {
+
+// 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;
+ 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.
+ using slot_policy = typename super_type::slot_policy;
+ using slot_type = typename super_type::slot_type;
+ using value_type = typename super_type::value_type;
+ using init_type = typename super_type::init_type;
+
+ template <typename V>
+ static auto key(const V &value ABSL_ATTRIBUTE_LIFETIME_BOUND)
+ -> decltype((value.first)) {
+ return value.first;
+ }
+ static const Key &key(const slot_type *s) { return slot_policy::key(s); }
+ static const Key &key(slot_type *s) { return slot_policy::key(s); }
+ // For use in node handle.
+ static auto mutable_key(slot_type *s)
+ -> decltype(slot_policy::mutable_key(s)) {
+ return slot_policy::mutable_key(s);
+ }
+ static mapped_type &value(value_type *value) { return value->second; }
+};
+
+} // namespace container_internal
+
ABSL_NAMESPACE_END
} // namespace absl
diff --git a/absl/container/btree_set.h b/absl/container/btree_set.h
index 21ef0a0..51dc42b 100644
--- a/absl/container/btree_set.h
+++ b/absl/container/btree_set.h
@@ -35,14 +35,19 @@
//
// However, these types should not be considered drop-in replacements for
// `std::set` and `std::multiset` as there are some API differences, which are
-// noted in this header file.
+// noted in this header file. The most consequential differences with respect to
+// migrating to b-tree from the STL types are listed in the next paragraph.
+// Other API differences are minor.
//
// Importantly, insertions and deletions may invalidate outstanding iterators,
// pointers, and references to elements. Such invalidations are typically only
// an issue if insertion and deletion operations are interleaved with the use of
// more than one iterator, pointer, or reference simultaneously. For this
-// reason, `insert()` and `erase()` return a valid iterator at the current
-// position.
+// reason, `insert()`, `erase()`, and `extract_and_get_next()` return a valid
+// iterator at the current position.
+//
+// Another API difference is that btree iterators can be subtracted, and this
+// is faster than using std::distance.
#ifndef ABSL_CONTAINER_BTREE_SET_H_
#define ABSL_CONTAINER_BTREE_SET_H_
@@ -53,6 +58,17 @@
namespace absl {
ABSL_NAMESPACE_BEGIN
+namespace container_internal {
+
+template <typename Key>
+struct set_slot_policy;
+
+template <typename Key, typename Compare, typename Alloc, int TargetNodeSize,
+ bool IsMulti>
+struct set_params;
+
+} // namespace container_internal
+
// absl::btree_set<>
//
// An `absl::btree_set<K>` is an ordered associative container of unique key
@@ -74,7 +90,7 @@
: public container_internal::btree_set_container<
container_internal::btree<container_internal::set_params<
Key, Compare, Alloc, /*TargetNodeSize=*/256,
- /*Multi=*/false>>> {
+ /*IsMulti=*/false>>> {
using Base = typename btree_set::btree_set_container;
public:
@@ -256,7 +272,8 @@
// btree_set::extract()
//
// Extracts the indicated element, erasing it in the process, and returns it
- // as a C++17-compatible node handle. Overloads are listed below.
+ // as a C++17-compatible node handle. Any references, pointers, or iterators
+ // are invalidated. Overloads are listed below.
//
// node_type extract(const_iterator position):
//
@@ -276,6 +293,21 @@
// It does NOT refer to the data layout of the underlying btree.
using Base::extract;
+ // btree_set::extract_and_get_next()
+ //
+ // Extracts the indicated element, erasing it in the process, and returns it
+ // as a C++17-compatible node handle along with an iterator to the next
+ // element.
+ //
+ // extract_and_get_next_return_type extract_and_get_next(
+ // const_iterator position):
+ //
+ // Extracts the element at the indicated position, returns a struct
+ // containing a member named `node`: a node handle owning that extracted
+ // data and a member named `next`: an iterator pointing to the next element
+ // in the btree.
+ using Base::extract_and_get_next;
+
// btree_set::merge()
//
// Extracts elements from a given `source` btree_set into this
@@ -300,8 +332,8 @@
// Determines whether an element comparing equal to the given `key` exists
// within the `btree_set`, returning `true` if so or `false` otherwise.
//
- // Supports heterogeneous lookup, provided that the set is provided a
- // compatible heterogeneous comparator.
+ // Supports heterogeneous lookup, provided that the set has a compatible
+ // heterogeneous comparator.
using Base::contains;
// btree_set::count()
@@ -312,8 +344,8 @@
// the `btree_set`. Note that this function will return either `1` or `0`
// since duplicate elements are not allowed within a `btree_set`.
//
- // Supports heterogeneous lookup, provided that the set is provided a
- // compatible heterogeneous comparator.
+ // Supports heterogeneous lookup, provided that the set has a compatible
+ // heterogeneous comparator.
using Base::count;
// btree_set::equal_range()
@@ -330,10 +362,32 @@
//
// Finds an element with the passed `key` within the `btree_set`.
//
- // Supports heterogeneous lookup, provided that the set is provided a
- // compatible heterogeneous comparator.
+ // Supports heterogeneous lookup, provided that the set has a compatible
+ // heterogeneous comparator.
using Base::find;
+ // btree_set::lower_bound()
+ //
+ // template <typename K> iterator lower_bound(const K& key):
+ // template <typename K> const_iterator lower_bound(const K& key) const:
+ //
+ // Finds the first element that is not less than `key` within the `btree_set`.
+ //
+ // Supports heterogeneous lookup, provided that the set has a compatible
+ // heterogeneous comparator.
+ using Base::lower_bound;
+
+ // btree_set::upper_bound()
+ //
+ // template <typename K> iterator upper_bound(const K& key):
+ // template <typename K> const_iterator upper_bound(const K& key) const:
+ //
+ // Finds the first element that is greater than `key` within the `btree_set`.
+ //
+ // Supports heterogeneous lookup, provided that the set has a compatible
+ // heterogeneous comparator.
+ using Base::upper_bound;
+
// btree_set::get_allocator()
//
// Returns the allocator function associated with this `btree_set`.
@@ -363,15 +417,11 @@
// absl::erase_if(absl::btree_set<>, Pred)
//
// Erases all elements that satisfy the predicate pred from the container.
+// Returns the number of erased elements.
template <typename K, typename C, typename A, typename Pred>
-void erase_if(btree_set<K, C, A> &set, Pred pred) {
- for (auto it = set.begin(); it != set.end();) {
- if (pred(*it)) {
- it = set.erase(it);
- } else {
- ++it;
- }
- }
+typename btree_set<K, C, A>::size_type erase_if(btree_set<K, C, A> &set,
+ Pred pred) {
+ return container_internal::btree_access::erase_if(set, std::move(pred));
}
// absl::btree_multiset<>
@@ -396,7 +446,7 @@
: public container_internal::btree_multiset_container<
container_internal::btree<container_internal::set_params<
Key, Compare, Alloc, /*TargetNodeSize=*/256,
- /*Multi=*/true>>> {
+ /*IsMulti=*/true>>> {
using Base = typename btree_multiset::btree_multiset_container;
public:
@@ -580,11 +630,25 @@
// It does NOT refer to the data layout of the underlying btree.
using Base::extract;
+ // btree_multiset::extract_and_get_next()
+ //
+ // Extracts the indicated element, erasing it in the process, and returns it
+ // as a C++17-compatible node handle along with an iterator to the next
+ // element.
+ //
+ // extract_and_get_next_return_type extract_and_get_next(
+ // const_iterator position):
+ //
+ // Extracts the element at the indicated position, returns a struct
+ // containing a member named `node`: a node handle owning that extracted
+ // data and a member named `next`: an iterator pointing to the next element
+ // in the btree.
+ using Base::extract_and_get_next;
+
// btree_multiset::merge()
//
- // Extracts elements from a given `source` btree_multiset into this
- // `btree_multiset`. If the destination `btree_multiset` already contains an
- // element with an equivalent key, that element is not extracted.
+ // Extracts all elements from a given `source` btree_multiset into this
+ // `btree_multiset`.
using Base::merge;
// btree_multiset::swap(btree_multiset& other)
@@ -604,8 +668,8 @@
// Determines whether an element comparing equal to the given `key` exists
// within the `btree_multiset`, returning `true` if so or `false` otherwise.
//
- // Supports heterogeneous lookup, provided that the set is provided a
- // compatible heterogeneous comparator.
+ // Supports heterogeneous lookup, provided that the set has a compatible
+ // heterogeneous comparator.
using Base::contains;
// btree_multiset::count()
@@ -615,8 +679,8 @@
// Returns the number of elements comparing equal to the given `key` within
// the `btree_multiset`.
//
- // Supports heterogeneous lookup, provided that the set is provided a
- // compatible heterogeneous comparator.
+ // Supports heterogeneous lookup, provided that the set has a compatible
+ // heterogeneous comparator.
using Base::count;
// btree_multiset::equal_range()
@@ -633,10 +697,34 @@
//
// Finds an element with the passed `key` within the `btree_multiset`.
//
- // Supports heterogeneous lookup, provided that the set is provided a
- // compatible heterogeneous comparator.
+ // Supports heterogeneous lookup, provided that the set has a compatible
+ // heterogeneous comparator.
using Base::find;
+ // btree_multiset::lower_bound()
+ //
+ // template <typename K> iterator lower_bound(const K& key):
+ // template <typename K> const_iterator lower_bound(const K& key) const:
+ //
+ // Finds the first element that is not less than `key` within the
+ // `btree_multiset`.
+ //
+ // Supports heterogeneous lookup, provided that the set has a compatible
+ // heterogeneous comparator.
+ using Base::lower_bound;
+
+ // btree_multiset::upper_bound()
+ //
+ // template <typename K> iterator upper_bound(const K& key):
+ // template <typename K> const_iterator upper_bound(const K& key) const:
+ //
+ // Finds the first element that is greater than `key` within the
+ // `btree_multiset`.
+ //
+ // Supports heterogeneous lookup, provided that the set has a compatible
+ // heterogeneous comparator.
+ using Base::upper_bound;
+
// btree_multiset::get_allocator()
//
// Returns the allocator function associated with this `btree_multiset`.
@@ -666,17 +754,67 @@
// absl::erase_if(absl::btree_multiset<>, Pred)
//
// Erases all elements that satisfy the predicate pred from the container.
+// Returns the number of erased elements.
template <typename K, typename C, typename A, typename Pred>
-void erase_if(btree_multiset<K, C, A> &set, Pred pred) {
- for (auto it = set.begin(); it != set.end();) {
- if (pred(*it)) {
- it = set.erase(it);
- } else {
- ++it;
- }
- }
+typename btree_multiset<K, C, A>::size_type erase_if(
+ btree_multiset<K, C, A> & set, Pred pred) {
+ return container_internal::btree_access::erase_if(set, std::move(pred));
}
+namespace container_internal {
+
+// This type implements the necessary functions from the
+// absl::container_internal::slot_type interface for btree_(multi)set.
+template <typename Key>
+struct set_slot_policy {
+ using slot_type = Key;
+ using value_type = Key;
+ using mutable_value_type = Key;
+
+ static value_type &element(slot_type *slot) { return *slot; }
+ static const value_type &element(const slot_type *slot) { return *slot; }
+
+ template <typename Alloc, class... Args>
+ static void construct(Alloc *alloc, slot_type *slot, Args &&...args) {
+ absl::allocator_traits<Alloc>::construct(*alloc, slot,
+ std::forward<Args>(args)...);
+ }
+
+ template <typename Alloc>
+ static void construct(Alloc *alloc, slot_type *slot, slot_type *other) {
+ absl::allocator_traits<Alloc>::construct(*alloc, slot, std::move(*other));
+ }
+
+ template <typename Alloc>
+ static void construct(Alloc *alloc, slot_type *slot, const slot_type *other) {
+ absl::allocator_traits<Alloc>::construct(*alloc, slot, *other);
+ }
+
+ template <typename Alloc>
+ static void destroy(Alloc *alloc, slot_type *slot) {
+ absl::allocator_traits<Alloc>::destroy(*alloc, slot);
+ }
+};
+
+// 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>> {
+ using value_type = Key;
+ using slot_type = typename set_params::common_params::slot_type;
+
+ template <typename V>
+ static const V &key(const V &value) {
+ return value;
+ }
+ static const Key &key(const slot_type *slot) { return *slot; }
+ static const Key &key(slot_type *slot) { return *slot; }
+};
+
+} // namespace container_internal
+
ABSL_NAMESPACE_END
} // namespace absl
diff --git a/absl/container/btree_test.cc b/absl/container/btree_test.cc
index d27cf27..d7102fe 100644
--- a/absl/container/btree_test.cc
+++ b/absl/container/btree_test.cc
@@ -14,31 +14,40 @@
#include "absl/container/btree_test.h"
+#include <algorithm>
+#include <array>
#include <cstdint>
+#include <functional>
+#include <iostream>
+#include <iterator>
#include <limits>
#include <map>
#include <memory>
+#include <numeric>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <utility>
+#include <vector>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
+#include "absl/algorithm/container.h"
#include "absl/base/internal/raw_logging.h"
#include "absl/base/macros.h"
#include "absl/container/btree_map.h"
#include "absl/container/btree_set.h"
-#include "absl/container/internal/counting_allocator.h"
+#include "absl/container/internal/test_allocator.h"
#include "absl/container/internal/test_instance_tracker.h"
#include "absl/flags/flag.h"
#include "absl/hash/hash_testing.h"
#include "absl/memory/memory.h"
-#include "absl/meta/type_traits.h"
+#include "absl/random/random.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_split.h"
#include "absl/strings/string_view.h"
#include "absl/types/compare.h"
+#include "absl/types/optional.h"
ABSL_FLAG(int, test_values, 10000, "The number of values to use for tests");
@@ -649,111 +658,6 @@
}
template <typename T>
-struct PropagatingCountingAlloc : public CountingAllocator<T> {
- using propagate_on_container_copy_assignment = std::true_type;
- using propagate_on_container_move_assignment = std::true_type;
- using propagate_on_container_swap = std::true_type;
-
- using Base = CountingAllocator<T>;
- using Base::Base;
-
- template <typename U>
- explicit PropagatingCountingAlloc(const PropagatingCountingAlloc<U> &other)
- : Base(other.bytes_used_) {}
-
- template <typename U>
- struct rebind {
- using other = PropagatingCountingAlloc<U>;
- };
-};
-
-template <typename T>
-void BtreeAllocatorTest() {
- using value_type = typename T::value_type;
-
- int64_t bytes1 = 0, bytes2 = 0;
- PropagatingCountingAlloc<T> allocator1(&bytes1);
- PropagatingCountingAlloc<T> allocator2(&bytes2);
- Generator<value_type> generator(1000);
-
- // Test that we allocate properly aligned memory. If we don't, then Layout
- // will assert fail.
- auto unused1 = allocator1.allocate(1);
- auto unused2 = allocator2.allocate(1);
-
- // Test copy assignment
- {
- T b1(typename T::key_compare(), allocator1);
- T b2(typename T::key_compare(), allocator2);
-
- int64_t original_bytes1 = bytes1;
- b1.insert(generator(0));
- EXPECT_GT(bytes1, original_bytes1);
-
- // This should propagate the allocator.
- b1 = b2;
- EXPECT_EQ(b1.size(), 0);
- EXPECT_EQ(b2.size(), 0);
- EXPECT_EQ(bytes1, original_bytes1);
-
- for (int i = 1; i < 1000; i++) {
- b1.insert(generator(i));
- }
-
- // We should have allocated out of allocator2.
- EXPECT_GT(bytes2, bytes1);
- }
-
- // Test move assignment
- {
- T b1(typename T::key_compare(), allocator1);
- T b2(typename T::key_compare(), allocator2);
-
- int64_t original_bytes1 = bytes1;
- b1.insert(generator(0));
- EXPECT_GT(bytes1, original_bytes1);
-
- // This should propagate the allocator.
- b1 = std::move(b2);
- EXPECT_EQ(b1.size(), 0);
- EXPECT_EQ(bytes1, original_bytes1);
-
- for (int i = 1; i < 1000; i++) {
- b1.insert(generator(i));
- }
-
- // We should have allocated out of allocator2.
- EXPECT_GT(bytes2, bytes1);
- }
-
- // Test swap
- {
- T b1(typename T::key_compare(), allocator1);
- T b2(typename T::key_compare(), allocator2);
-
- int64_t original_bytes1 = bytes1;
- b1.insert(generator(0));
- EXPECT_GT(bytes1, original_bytes1);
-
- // This should swap the allocators.
- swap(b1, b2);
- EXPECT_EQ(b1.size(), 0);
- EXPECT_EQ(b2.size(), 1);
- EXPECT_GT(bytes1, original_bytes1);
-
- for (int i = 1; i < 1000; i++) {
- b1.insert(generator(i));
- }
-
- // We should have allocated out of allocator2.
- EXPECT_GT(bytes2, bytes1);
- }
-
- allocator1.deallocate(unused1, 1);
- allocator2.deallocate(unused2, 1);
-}
-
-template <typename T>
void BtreeMapTest() {
using value_type = typename T::value_type;
using mapped_type = typename T::mapped_type;
@@ -792,10 +696,7 @@
sizeof(absl::btree_set<K>),
2 * sizeof(void *) + sizeof(typename absl::btree_set<K>::size_type));
using BtreeSet = absl::btree_set<K>;
- using CountingBtreeSet =
- absl::btree_set<K, std::less<K>, PropagatingCountingAlloc<K>>;
BtreeTest<BtreeSet, std::set<K>>();
- BtreeAllocatorTest<CountingBtreeSet>();
}
template <typename K, int N = 256>
@@ -804,24 +705,16 @@
sizeof(absl::btree_map<K, K>),
2 * sizeof(void *) + sizeof(typename absl::btree_map<K, K>::size_type));
using BtreeMap = absl::btree_map<K, K>;
- using CountingBtreeMap =
- absl::btree_map<K, K, std::less<K>,
- PropagatingCountingAlloc<std::pair<const K, K>>>;
BtreeTest<BtreeMap, std::map<K, K>>();
- BtreeAllocatorTest<CountingBtreeMap>();
BtreeMapTest<BtreeMap>();
}
TEST(Btree, set_int32) { SetTest<int32_t>(); }
-TEST(Btree, set_int64) { SetTest<int64_t>(); }
TEST(Btree, set_string) { SetTest<std::string>(); }
TEST(Btree, set_cord) { SetTest<absl::Cord>(); }
-TEST(Btree, set_pair) { SetTest<std::pair<int, int>>(); }
TEST(Btree, map_int32) { MapTest<int32_t>(); }
-TEST(Btree, map_int64) { MapTest<int64_t>(); }
TEST(Btree, map_string) { MapTest<std::string>(); }
TEST(Btree, map_cord) { MapTest<absl::Cord>(); }
-TEST(Btree, map_pair) { MapTest<std::pair<int, int>>(); }
template <typename K, int N = 256>
void MultiSetTest() {
@@ -829,10 +722,7 @@
sizeof(absl::btree_multiset<K>),
2 * sizeof(void *) + sizeof(typename absl::btree_multiset<K>::size_type));
using BtreeMSet = absl::btree_multiset<K>;
- using CountingBtreeMSet =
- absl::btree_multiset<K, std::less<K>, PropagatingCountingAlloc<K>>;
BtreeMultiTest<BtreeMSet, std::multiset<K>>();
- BtreeAllocatorTest<CountingBtreeMSet>();
}
template <typename K, int N = 256>
@@ -841,24 +731,16 @@
2 * sizeof(void *) +
sizeof(typename absl::btree_multimap<K, K>::size_type));
using BtreeMMap = absl::btree_multimap<K, K>;
- using CountingBtreeMMap =
- absl::btree_multimap<K, K, std::less<K>,
- PropagatingCountingAlloc<std::pair<const K, K>>>;
BtreeMultiTest<BtreeMMap, std::multimap<K, K>>();
BtreeMultiMapTest<BtreeMMap>();
- BtreeAllocatorTest<CountingBtreeMMap>();
}
TEST(Btree, multiset_int32) { MultiSetTest<int32_t>(); }
-TEST(Btree, multiset_int64) { MultiSetTest<int64_t>(); }
TEST(Btree, multiset_string) { MultiSetTest<std::string>(); }
TEST(Btree, multiset_cord) { MultiSetTest<absl::Cord>(); }
-TEST(Btree, multiset_pair) { MultiSetTest<std::pair<int, int>>(); }
TEST(Btree, multimap_int32) { MultiMapTest<int32_t>(); }
-TEST(Btree, multimap_int64) { MultiMapTest<int64_t>(); }
TEST(Btree, multimap_string) { MultiMapTest<std::string>(); }
TEST(Btree, multimap_cord) { MultiMapTest<absl::Cord>(); }
-TEST(Btree, multimap_pair) { MultiMapTest<std::pair<int, int>>(); }
struct CompareIntToString {
bool operator()(const std::string &a, const std::string &b) const {
@@ -1212,6 +1094,13 @@
constexpr static bool UsesLinearNodeSearch() {
return btree_node<typename Btree::params_type>::use_linear_search::value;
}
+
+ template <typename Btree>
+ constexpr static bool FieldTypeEqualsSlotType() {
+ return std::is_same<
+ typename btree_node<typename Btree::params_type>::field_type,
+ typename btree_node<typename Btree::params_type>::slot_type>::value;
+ }
};
namespace {
@@ -1285,7 +1174,7 @@
std::unique_ptr<std::string> &v = m["A"];
EXPECT_TRUE(v == nullptr);
- v.reset(new std::string("X"));
+ v = absl::make_unique<std::string>("X");
auto iter = m.find("A");
EXPECT_EQ("X", *iter->second);
@@ -1344,38 +1233,34 @@
EXPECT_EQ(++it, range.second);
}
-template <typename Compare, typename K>
-void AssertKeyCompareToAdapted() {
- using Adapted = typename key_compare_to_adapter<Compare>::type;
- static_assert(!std::is_same<Adapted, Compare>::value,
- "key_compare_to_adapter should have adapted this comparator.");
+template <typename Compare, typename Key>
+void AssertKeyCompareStringAdapted() {
+ using Adapted = typename key_compare_adapter<Compare, Key>::type;
static_assert(
- std::is_same<absl::weak_ordering,
- absl::result_of_t<Adapted(const K &, const K &)>>::value,
- "Adapted comparator should be a key-compare-to comparator.");
+ std::is_same<Adapted, StringBtreeDefaultLess>::value ||
+ std::is_same<Adapted, StringBtreeDefaultGreater>::value,
+ "key_compare_adapter should have string-adapted this comparator.");
}
-template <typename Compare, typename K>
-void AssertKeyCompareToNotAdapted() {
- using Unadapted = typename key_compare_to_adapter<Compare>::type;
+template <typename Compare, typename Key>
+void AssertKeyCompareNotStringAdapted() {
+ using Adapted = typename key_compare_adapter<Compare, Key>::type;
static_assert(
- std::is_same<Unadapted, Compare>::value,
- "key_compare_to_adapter shouldn't have adapted this comparator.");
- static_assert(
- std::is_same<bool,
- absl::result_of_t<Unadapted(const K &, const K &)>>::value,
- "Un-adapted comparator should return bool.");
+ !std::is_same<Adapted, StringBtreeDefaultLess>::value &&
+ !std::is_same<Adapted, StringBtreeDefaultGreater>::value,
+ "key_compare_adapter shouldn't have string-adapted this comparator.");
}
-TEST(Btree, KeyCompareToAdapter) {
- AssertKeyCompareToAdapted<std::less<std::string>, std::string>();
- AssertKeyCompareToAdapted<std::greater<std::string>, std::string>();
- AssertKeyCompareToAdapted<std::less<absl::string_view>, absl::string_view>();
- AssertKeyCompareToAdapted<std::greater<absl::string_view>,
- absl::string_view>();
- AssertKeyCompareToAdapted<std::less<absl::Cord>, absl::Cord>();
- AssertKeyCompareToAdapted<std::greater<absl::Cord>, absl::Cord>();
- AssertKeyCompareToNotAdapted<std::less<int>, int>();
- AssertKeyCompareToNotAdapted<std::greater<int>, int>();
+TEST(Btree, KeyCompareAdapter) {
+ AssertKeyCompareStringAdapted<std::less<std::string>, std::string>();
+ AssertKeyCompareStringAdapted<std::greater<std::string>, std::string>();
+ AssertKeyCompareStringAdapted<std::less<absl::string_view>,
+ absl::string_view>();
+ AssertKeyCompareStringAdapted<std::greater<absl::string_view>,
+ absl::string_view>();
+ AssertKeyCompareStringAdapted<std::less<absl::Cord>, absl::Cord>();
+ AssertKeyCompareStringAdapted<std::greater<absl::Cord>, absl::Cord>();
+ AssertKeyCompareNotStringAdapted<std::less<int>, int>();
+ AssertKeyCompareNotStringAdapted<std::greater<int>, int>();
}
TEST(Btree, RValueInsert) {
@@ -1425,17 +1310,25 @@
EXPECT_EQ(tracker.swaps(), 0);
}
-// A btree set with a specific number of values per node.
+template <typename Cmp>
+struct CheckedCompareOptedOutCmp : Cmp, BtreeTestOnlyCheckedCompareOptOutBase {
+ using Cmp::Cmp;
+ CheckedCompareOptedOutCmp() {}
+ CheckedCompareOptedOutCmp(Cmp cmp) : Cmp(std::move(cmp)) {} // NOLINT
+};
+
+// A btree set with a specific number of values per node. Opt out of
+// checked_compare so that we can expect exact numbers of comparisons.
template <typename Key, int TargetValuesPerNode, typename Cmp = std::less<Key>>
class SizedBtreeSet
: public btree_set_container<btree<
- set_params<Key, Cmp, std::allocator<Key>,
+ set_params<Key, CheckedCompareOptedOutCmp<Cmp>, std::allocator<Key>,
BtreeNodePeer::GetTargetNodeSize<Key>(TargetValuesPerNode),
/*Multi=*/false>>> {
using Base = typename SizedBtreeSet::btree_set_container;
public:
- SizedBtreeSet() {}
+ SizedBtreeSet() = default;
using Base::Base;
};
@@ -1453,9 +1346,18 @@
tracker->ResetCopiesMovesSwaps();
}
+#if defined(ABSL_HAVE_ADDRESS_SANITIZER) || \
+ defined(ABSL_HAVE_HWADDRESS_SANITIZER)
+constexpr bool kAsan = true;
+#else
+constexpr bool kAsan = false;
+#endif
+
// Note: when the values in this test change, it is expected to have an impact
// on performance.
TEST(Btree, MovesComparisonsCopiesSwapsTracking) {
+ if (kAsan) GTEST_SKIP() << "We do extra operations in ASan mode.";
+
InstanceTracker tracker;
// Note: this is minimum number of values per node.
SizedBtreeSet<MovableOnlyInstance, /*TargetValuesPerNode=*/4> set4;
@@ -1474,7 +1376,8 @@
EXPECT_EQ(BtreeNodePeer::GetNumSlotsPerNode<decltype(set100)>(), 100);
if (sizeof(void *) == 8) {
EXPECT_EQ(BtreeNodePeer::GetNumSlotsPerNode<absl::btree_set<int32_t>>(),
- BtreeNodePeer::GetNumSlotsPerNode<decltype(set61)>());
+ // When we have generations, there is one fewer slot.
+ BtreeGenerationsEnabled() ? 60 : 61);
}
// Test key insertion/deletion in random order.
@@ -1505,6 +1408,8 @@
// Note: when the values in this test change, it is expected to have an impact
// on performance.
TEST(Btree, MovesComparisonsCopiesSwapsTrackingThreeWayCompare) {
+ if (kAsan) GTEST_SKIP() << "We do extra operations in ASan mode.";
+
InstanceTracker tracker;
// Note: this is minimum number of values per node.
SizedBtreeSet<MovableOnlyInstance, /*TargetValuesPerNode=*/4,
@@ -1529,7 +1434,8 @@
EXPECT_EQ(BtreeNodePeer::GetNumSlotsPerNode<decltype(set100)>(), 100);
if (sizeof(void *) == 8) {
EXPECT_EQ(BtreeNodePeer::GetNumSlotsPerNode<absl::btree_set<int32_t>>(),
- BtreeNodePeer::GetNumSlotsPerNode<decltype(set61)>());
+ // When we have generations, there is one fewer slot.
+ BtreeGenerationsEnabled() ? 60 : 61);
}
// Test key insertion/deletion in random order.
@@ -1631,10 +1537,9 @@
auto iter = s.emplace(value_to_insert);
ASSERT_NE(iter, s.end());
EXPECT_EQ(*iter, value_to_insert);
- auto iter2 = s.emplace(value_to_insert);
- EXPECT_NE(iter2, iter);
- ASSERT_NE(iter2, s.end());
- EXPECT_EQ(*iter2, value_to_insert);
+ iter = s.emplace(value_to_insert);
+ ASSERT_NE(iter, s.end());
+ EXPECT_EQ(*iter, value_to_insert);
auto result = s.equal_range(value_to_insert);
EXPECT_EQ(std::distance(result.first, result.second), 2);
}
@@ -1645,44 +1550,45 @@
auto iter = s.emplace(value_to_insert);
ASSERT_NE(iter, s.end());
EXPECT_EQ(*iter, value_to_insert);
- auto emplace_iter = s.emplace_hint(iter, value_to_insert);
- EXPECT_NE(emplace_iter, iter);
- ASSERT_NE(emplace_iter, s.end());
- EXPECT_EQ(*emplace_iter, value_to_insert);
+ iter = s.emplace_hint(iter, value_to_insert);
+ // The new element should be before the previously inserted one.
+ EXPECT_EQ(iter, s.lower_bound(value_to_insert));
+ ASSERT_NE(iter, s.end());
+ EXPECT_EQ(*iter, value_to_insert);
}
TEST(Btree, BtreeMultimapEmplace) {
const int key_to_insert = 123456;
const char value0[] = "a";
- absl::btree_multimap<int, std::string> s;
- auto iter = s.emplace(key_to_insert, value0);
- ASSERT_NE(iter, s.end());
+ absl::btree_multimap<int, std::string> m;
+ auto iter = m.emplace(key_to_insert, value0);
+ ASSERT_NE(iter, m.end());
EXPECT_EQ(iter->first, key_to_insert);
EXPECT_EQ(iter->second, value0);
const char value1[] = "b";
- auto iter2 = s.emplace(key_to_insert, value1);
- EXPECT_NE(iter2, iter);
- ASSERT_NE(iter2, s.end());
- EXPECT_EQ(iter2->first, key_to_insert);
- EXPECT_EQ(iter2->second, value1);
- auto result = s.equal_range(key_to_insert);
+ iter = m.emplace(key_to_insert, value1);
+ ASSERT_NE(iter, m.end());
+ EXPECT_EQ(iter->first, key_to_insert);
+ EXPECT_EQ(iter->second, value1);
+ auto result = m.equal_range(key_to_insert);
EXPECT_EQ(std::distance(result.first, result.second), 2);
}
TEST(Btree, BtreeMultimapEmplaceHint) {
const int key_to_insert = 123456;
const char value0[] = "a";
- absl::btree_multimap<int, std::string> s;
- auto iter = s.emplace(key_to_insert, value0);
- ASSERT_NE(iter, s.end());
+ absl::btree_multimap<int, std::string> m;
+ auto iter = m.emplace(key_to_insert, value0);
+ ASSERT_NE(iter, m.end());
EXPECT_EQ(iter->first, key_to_insert);
EXPECT_EQ(iter->second, value0);
const char value1[] = "b";
- auto emplace_iter = s.emplace_hint(iter, key_to_insert, value1);
- EXPECT_NE(emplace_iter, iter);
- ASSERT_NE(emplace_iter, s.end());
- EXPECT_EQ(emplace_iter->first, key_to_insert);
- EXPECT_EQ(emplace_iter->second, value1);
+ iter = m.emplace_hint(iter, key_to_insert, value1);
+ // The new element should be before the previously inserted one.
+ EXPECT_EQ(iter, m.lower_bound(key_to_insert));
+ ASSERT_NE(iter, m.end());
+ EXPECT_EQ(iter->first, key_to_insert);
+ EXPECT_EQ(iter->second, value1);
}
TEST(Btree, ConstIteratorAccessors) {
@@ -1748,6 +1654,22 @@
EXPECT_FALSE(m2.value_comp()(std::make_pair("b", 0), std::make_pair("a", 0)));
}
+// Test that we have the protected members from the std::map::value_compare API.
+// See https://en.cppreference.com/w/cpp/container/map/value_compare.
+TEST(Btree, MapValueCompProtected) {
+ struct key_compare {
+ bool operator()(int l, int r) const { return l < r; }
+ int id;
+ };
+ using value_compare = absl::btree_map<int, int, key_compare>::value_compare;
+ struct value_comp_child : public value_compare {
+ explicit value_comp_child(key_compare kc) : value_compare(kc) {}
+ int GetId() const { return comp.id; }
+ };
+ value_comp_child c(key_compare{10});
+ EXPECT_EQ(c.GetId(), 10);
+}
+
TEST(Btree, DefaultConstruction) {
absl::btree_set<int> s;
absl::btree_map<int, int> m;
@@ -2077,6 +1999,79 @@
}
}
+TEST(Btree, ExtractAndGetNextSet) {
+ absl::btree_set<int> src = {1, 2, 3, 4, 5};
+ auto it = src.find(3);
+ auto extracted_and_next = src.extract_and_get_next(it);
+ EXPECT_THAT(src, ElementsAre(1, 2, 4, 5));
+ EXPECT_EQ(extracted_and_next.node.value(), 3);
+ EXPECT_EQ(*extracted_and_next.next, 4);
+}
+
+TEST(Btree, ExtractAndGetNextMultiSet) {
+ absl::btree_multiset<int> src = {1, 2, 3, 4, 5};
+ auto it = src.find(3);
+ auto extracted_and_next = src.extract_and_get_next(it);
+ EXPECT_THAT(src, ElementsAre(1, 2, 4, 5));
+ EXPECT_EQ(extracted_and_next.node.value(), 3);
+ EXPECT_EQ(*extracted_and_next.next, 4);
+}
+
+TEST(Btree, ExtractAndGetNextMap) {
+ absl::btree_map<int, int> src = {{1, 2}, {3, 4}, {5, 6}};
+ auto it = src.find(3);
+ auto extracted_and_next = src.extract_and_get_next(it);
+ EXPECT_THAT(src, ElementsAre(Pair(1, 2), Pair(5, 6)));
+ EXPECT_EQ(extracted_and_next.node.key(), 3);
+ EXPECT_EQ(extracted_and_next.node.mapped(), 4);
+ EXPECT_THAT(*extracted_and_next.next, Pair(5, 6));
+}
+
+TEST(Btree, ExtractAndGetNextMultiMap) {
+ absl::btree_multimap<int, int> src = {{1, 2}, {3, 4}, {5, 6}};
+ auto it = src.find(3);
+ auto extracted_and_next = src.extract_and_get_next(it);
+ EXPECT_THAT(src, ElementsAre(Pair(1, 2), Pair(5, 6)));
+ EXPECT_EQ(extracted_and_next.node.key(), 3);
+ EXPECT_EQ(extracted_and_next.node.mapped(), 4);
+ EXPECT_THAT(*extracted_and_next.next, Pair(5, 6));
+}
+
+TEST(Btree, ExtractAndGetNextEndIter) {
+ absl::btree_set<int> src = {1, 2, 3, 4, 5};
+ auto it = src.find(5);
+ auto extracted_and_next = src.extract_and_get_next(it);
+ EXPECT_THAT(src, ElementsAre(1, 2, 3, 4));
+ EXPECT_EQ(extracted_and_next.node.value(), 5);
+ EXPECT_EQ(extracted_and_next.next, src.end());
+}
+
+TEST(Btree, ExtractDoesntCauseExtraMoves) {
+#ifdef _MSC_VER
+ GTEST_SKIP() << "This test fails on MSVC.";
+#endif
+
+ using Set = absl::btree_set<MovableOnlyInstance>;
+ std::array<std::function<void(Set &)>, 3> extracters = {
+ [](Set &s) { auto node = s.extract(s.begin()); },
+ [](Set &s) { auto ret = s.extract_and_get_next(s.begin()); },
+ [](Set &s) { auto node = s.extract(MovableOnlyInstance(0)); }};
+
+ InstanceTracker tracker;
+ for (int i = 0; i < 3; ++i) {
+ Set s;
+ s.insert(MovableOnlyInstance(0));
+ tracker.ResetCopiesMovesSwaps();
+
+ extracters[i](s);
+ // We expect to see exactly 1 move: from the original slot into the
+ // extracted node.
+ EXPECT_EQ(tracker.copies(), 0) << i;
+ EXPECT_EQ(tracker.moves(), 1) << i;
+ EXPECT_EQ(tracker.swaps(), 0) << i;
+ }
+}
+
// For multisets, insert with hint also affects correctness because we need to
// insert immediately before the hint if possible.
struct InsertMultiHintData {
@@ -2297,7 +2292,9 @@
};
using Cmp = decltype(cmp);
- absl::btree_map<int, int, Cmp> m(cmp);
+ // Use a map that is opted out of key_compare being adapted so we can expect
+ // strict comparison call limits.
+ absl::btree_map<int, int, CheckedCompareOptedOutCmp<Cmp>> m(cmp);
for (int i = 0; i < 128; ++i) {
m.emplace(i, i);
}
@@ -2393,50 +2390,23 @@
EXPECT_EQ(std::string(10, 'a'), m[1]);
}
-TEST(Btree, MoveAssignmentAllocatorPropagation) {
- InstanceTracker tracker;
+template <typename Alloc>
+using BtreeSetAlloc = absl::btree_set<int, std::less<int>, Alloc>;
- int64_t bytes1 = 0, bytes2 = 0;
- PropagatingCountingAlloc<MovableOnlyInstance> allocator1(&bytes1);
- PropagatingCountingAlloc<MovableOnlyInstance> allocator2(&bytes2);
- std::less<MovableOnlyInstance> cmp;
+TEST(Btree, AllocatorPropagation) {
+ TestAllocPropagation<BtreeSetAlloc>();
+}
- // Test propagating allocator_type.
- {
- absl::btree_set<MovableOnlyInstance, std::less<MovableOnlyInstance>,
- PropagatingCountingAlloc<MovableOnlyInstance>>
- set1(cmp, allocator1), set2(cmp, allocator2);
+TEST(Btree, MinimumAlignmentAllocator) {
+ absl::btree_set<int8_t, std::less<int8_t>, MinimumAlignmentAlloc<int8_t>> set;
- for (int i = 0; i < 100; ++i) set1.insert(MovableOnlyInstance(i));
+ // Do some basic operations. Test that everything is fine when allocator uses
+ // minimal alignment.
+ for (int8_t i = 0; i < 100; ++i) set.insert(i);
+ set.erase(set.find(50), set.end());
+ for (int8_t i = 51; i < 101; ++i) set.insert(i);
- tracker.ResetCopiesMovesSwaps();
- set2 = std::move(set1);
- EXPECT_EQ(tracker.moves(), 0);
- }
- // Test non-propagating allocator_type with equal allocators.
- {
- absl::btree_set<MovableOnlyInstance, std::less<MovableOnlyInstance>,
- CountingAllocator<MovableOnlyInstance>>
- set1(cmp, allocator1), set2(cmp, allocator1);
-
- for (int i = 0; i < 100; ++i) set1.insert(MovableOnlyInstance(i));
-
- tracker.ResetCopiesMovesSwaps();
- set2 = std::move(set1);
- EXPECT_EQ(tracker.moves(), 0);
- }
- // Test non-propagating allocator_type with different allocators.
- {
- absl::btree_set<MovableOnlyInstance, std::less<MovableOnlyInstance>,
- CountingAllocator<MovableOnlyInstance>>
- set1(cmp, allocator1), set2(cmp, allocator2);
-
- for (int i = 0; i < 100; ++i) set1.insert(MovableOnlyInstance(i));
-
- tracker.ResetCopiesMovesSwaps();
- set2 = std::move(set1);
- EXPECT_GE(tracker.moves(), 100);
- }
+ EXPECT_EQ(set.size(), 100);
}
TEST(Btree, EmptyTree) {
@@ -2452,23 +2422,28 @@
// Test that erase_if works with all the container types and supports lambdas.
{
absl::btree_set<int> s = {1, 3, 5, 6, 100};
- erase_if(s, [](int k) { return k > 3; });
+ EXPECT_EQ(erase_if(s, [](int k) { return k > 3; }), 3);
EXPECT_THAT(s, ElementsAre(1, 3));
}
{
absl::btree_multiset<int> s = {1, 3, 3, 5, 6, 6, 100};
- erase_if(s, [](int k) { return k <= 3; });
+ EXPECT_EQ(erase_if(s, [](int k) { return k <= 3; }), 3);
EXPECT_THAT(s, ElementsAre(5, 6, 6, 100));
}
{
absl::btree_map<int, int> m = {{1, 1}, {3, 3}, {6, 6}, {100, 100}};
- erase_if(m, [](std::pair<const int, int> kv) { return kv.first > 3; });
+ EXPECT_EQ(
+ erase_if(m, [](std::pair<const int, int> kv) { return kv.first > 3; }),
+ 2);
EXPECT_THAT(m, ElementsAre(Pair(1, 1), Pair(3, 3)));
}
{
absl::btree_multimap<int, int> m = {{1, 1}, {3, 3}, {3, 6},
{6, 6}, {6, 7}, {100, 6}};
- erase_if(m, [](std::pair<const int, int> kv) { return kv.second == 6; });
+ EXPECT_EQ(
+ erase_if(m,
+ [](std::pair<const int, int> kv) { return kv.second == 6; }),
+ 3);
EXPECT_THAT(m, ElementsAre(Pair(1, 1), Pair(3, 3), Pair(6, 7)));
}
// Test that erasing all elements from a large set works and test support for
@@ -2476,15 +2451,29 @@
{
absl::btree_set<int> s;
for (int i = 0; i < 1000; ++i) s.insert(2 * i);
- erase_if(s, IsEven);
+ EXPECT_EQ(erase_if(s, IsEven), 1000);
EXPECT_THAT(s, IsEmpty());
}
// Test that erase_if supports other format of function pointers.
{
absl::btree_set<int> s = {1, 3, 5, 6, 100};
- erase_if(s, &IsEven);
+ EXPECT_EQ(erase_if(s, &IsEven), 2);
EXPECT_THAT(s, ElementsAre(1, 3, 5));
}
+ // Test that erase_if invokes the predicate once per element.
+ {
+ absl::btree_set<int> s;
+ for (int i = 0; i < 1000; ++i) s.insert(i);
+ int pred_calls = 0;
+ EXPECT_EQ(erase_if(s,
+ [&pred_calls](int k) {
+ ++pred_calls;
+ return k % 2;
+ }),
+ 500);
+ EXPECT_THAT(s, SizeIs(500));
+ EXPECT_EQ(pred_calls, 1000);
+ }
}
TEST(Btree, InsertOrAssign) {
@@ -2813,6 +2802,20 @@
EXPECT_EQ(set.count(2), 2);
}
+TEST(Btree, SetIteratorsAreConst) {
+ using Set = absl::btree_set<int>;
+ EXPECT_TRUE(
+ (std::is_same<typename Set::iterator::reference, const int &>::value));
+ EXPECT_TRUE(
+ (std::is_same<typename Set::iterator::pointer, const int *>::value));
+
+ using MSet = absl::btree_multiset<int>;
+ EXPECT_TRUE(
+ (std::is_same<typename MSet::iterator::reference, const int &>::value));
+ EXPECT_TRUE(
+ (std::is_same<typename MSet::iterator::pointer, const int *>::value));
+}
+
TEST(Btree, AllocConstructor) {
using Alloc = CountingAllocator<int>;
using Set = absl::btree_set<int, std::less<int>, Alloc>;
@@ -2948,6 +2951,510 @@
absl::btree_set<MultiKey, MultiKeyComp> set = {{}, MultiKeyComp{}};
}
+TEST(Btree, InvalidComparatorsCaught) {
+ if (!IsAssertEnabled()) GTEST_SKIP() << "Assertions not enabled.";
+
+ {
+ struct ZeroAlwaysLessCmp {
+ bool operator()(int lhs, int rhs) const {
+ if (lhs == 0) return true;
+ return lhs < rhs;
+ }
+ };
+ absl::btree_set<int, ZeroAlwaysLessCmp> set;
+ EXPECT_DEATH(set.insert({0, 1, 2}), "is_self_equivalent");
+ }
+ {
+ struct ThreeWayAlwaysLessCmp {
+ absl::weak_ordering operator()(int, int) const {
+ return absl::weak_ordering::less;
+ }
+ };
+ absl::btree_set<int, ThreeWayAlwaysLessCmp> set;
+ EXPECT_DEATH(set.insert({0, 1, 2}), "is_self_equivalent");
+ }
+ {
+ struct SumGreaterZeroCmp {
+ bool operator()(int lhs, int rhs) const {
+ // First, do equivalence correctly - so we can test later condition.
+ if (lhs == rhs) return false;
+ return lhs + rhs > 0;
+ }
+ };
+ absl::btree_set<int, SumGreaterZeroCmp> set;
+ // Note: '!' only needs to be escaped when it's the first character.
+ EXPECT_DEATH(set.insert({0, 1, 2}),
+ R"regex(\!lhs_comp_rhs \|\| !comp\(\)\(rhs, lhs\))regex");
+ }
+ {
+ struct ThreeWaySumGreaterZeroCmp {
+ absl::weak_ordering operator()(int lhs, int rhs) const {
+ // First, do equivalence correctly - so we can test later condition.
+ if (lhs == rhs) return absl::weak_ordering::equivalent;
+
+ if (lhs + rhs > 0) return absl::weak_ordering::less;
+ if (lhs + rhs == 0) return absl::weak_ordering::equivalent;
+ return absl::weak_ordering::greater;
+ }
+ };
+ absl::btree_set<int, ThreeWaySumGreaterZeroCmp> set;
+ EXPECT_DEATH(set.insert({0, 1, 2}), "lhs_comp_rhs < 0 -> rhs_comp_lhs > 0");
+ }
+ // Verify that we detect cases of comparators that violate transitivity.
+ // When the comparators below check for the presence of an optional field,
+ // they violate transitivity because instances that have the optional field
+ // compare differently with each other from how they compare with instances
+ // that don't have the optional field.
+ struct ClockTime {
+ absl::optional<int> hour;
+ int minute;
+ };
+ // `comp(a,b) && comp(b,c) && !comp(a,c)` violates transitivity.
+ ClockTime a = {absl::nullopt, 1};
+ ClockTime b = {2, 5};
+ ClockTime c = {6, 0};
+ {
+ struct NonTransitiveTimeCmp {
+ bool operator()(ClockTime lhs, ClockTime rhs) const {
+ if (lhs.hour.has_value() && rhs.hour.has_value() &&
+ *lhs.hour != *rhs.hour) {
+ return *lhs.hour < *rhs.hour;
+ }
+ return lhs.minute < rhs.minute;
+ }
+ };
+ NonTransitiveTimeCmp cmp;
+ ASSERT_TRUE(cmp(a, b) && cmp(b, c) && !cmp(a, c));
+ absl::btree_set<ClockTime, NonTransitiveTimeCmp> set;
+ EXPECT_DEATH(set.insert({a, b, c}), "is_ordered_correctly");
+ absl::btree_multiset<ClockTime, NonTransitiveTimeCmp> mset;
+ EXPECT_DEATH(mset.insert({a, a, b, b, c, c}), "is_ordered_correctly");
+ }
+ {
+ struct ThreeWayNonTransitiveTimeCmp {
+ absl::weak_ordering operator()(ClockTime lhs, ClockTime rhs) const {
+ if (lhs.hour.has_value() && rhs.hour.has_value() &&
+ *lhs.hour != *rhs.hour) {
+ return *lhs.hour < *rhs.hour ? absl::weak_ordering::less
+ : absl::weak_ordering::greater;
+ }
+ return lhs.minute < rhs.minute ? absl::weak_ordering::less
+ : lhs.minute == rhs.minute ? absl::weak_ordering::equivalent
+ : absl::weak_ordering::greater;
+ }
+ };
+ ThreeWayNonTransitiveTimeCmp cmp;
+ ASSERT_TRUE(cmp(a, b) < 0 && cmp(b, c) < 0 && cmp(a, c) > 0);
+ absl::btree_set<ClockTime, ThreeWayNonTransitiveTimeCmp> set;
+ EXPECT_DEATH(set.insert({a, b, c}), "is_ordered_correctly");
+ absl::btree_multiset<ClockTime, ThreeWayNonTransitiveTimeCmp> mset;
+ EXPECT_DEATH(mset.insert({a, a, b, b, c, c}), "is_ordered_correctly");
+ }
+}
+
+TEST(Btree, MutatedKeysCaught) {
+ if (!IsAssertEnabled()) GTEST_SKIP() << "Assertions not enabled.";
+
+ struct IntPtrCmp {
+ bool operator()(int *lhs, int *rhs) const { return *lhs < *rhs; }
+ };
+ {
+ absl::btree_set<int *, IntPtrCmp> set;
+ int arr[] = {0, 1, 2};
+ set.insert({&arr[0], &arr[1], &arr[2]});
+ arr[0] = 100;
+ EXPECT_DEATH(set.insert(&arr[0]), "is_ordered_correctly");
+ }
+ {
+ absl::btree_multiset<int *, IntPtrCmp> set;
+ int arr[] = {0, 1, 2};
+ set.insert({&arr[0], &arr[0], &arr[1], &arr[1], &arr[2], &arr[2]});
+ arr[0] = 100;
+ EXPECT_DEATH(set.insert(&arr[0]), "is_ordered_correctly");
+ }
+}
+
+#ifndef _MSC_VER
+// This test crashes on MSVC.
+TEST(Btree, InvalidIteratorUse) {
+ if (!BtreeGenerationsEnabled())
+ GTEST_SKIP() << "Generation validation for iterators is disabled.";
+
+ // Invalid memory use can trigger use-after-free in ASan, HWASAN or
+ // invalidated iterator assertions.
+ constexpr const char *kInvalidMemoryDeathMessage =
+ "use-after-free|invalidated iterator";
+
+ {
+ absl::btree_set<int> set;
+ for (int i = 0; i < 10; ++i) set.insert(i);
+ auto it = set.begin();
+ set.erase(it++);
+ EXPECT_DEATH(set.erase(it++), kInvalidMemoryDeathMessage);
+ }
+ {
+ absl::btree_set<int> set;
+ for (int i = 0; i < 10; ++i) set.insert(i);
+ auto it = set.insert(20).first;
+ set.insert(30);
+ EXPECT_DEATH(*it, kInvalidMemoryDeathMessage);
+ }
+ {
+ absl::btree_set<int> set;
+ for (int i = 0; i < 10000; ++i) set.insert(i);
+ auto it = set.find(5000);
+ ASSERT_NE(it, set.end());
+ set.erase(1);
+ EXPECT_DEATH(*it, kInvalidMemoryDeathMessage);
+ }
+ {
+ absl::btree_set<int> set;
+ for (int i = 0; i < 10; ++i) set.insert(i);
+ auto it = set.insert(20).first;
+ set.insert(30);
+ EXPECT_DEATH(void(it == set.begin()), kInvalidMemoryDeathMessage);
+ EXPECT_DEATH(void(set.begin() == it), kInvalidMemoryDeathMessage);
+ }
+}
+#endif
+
+class OnlyConstructibleByAllocator {
+ explicit OnlyConstructibleByAllocator(int i) : i_(i) {}
+
+ public:
+ OnlyConstructibleByAllocator(const OnlyConstructibleByAllocator &other)
+ : i_(other.i_) {}
+ OnlyConstructibleByAllocator &operator=(
+ const OnlyConstructibleByAllocator &other) {
+ i_ = other.i_;
+ return *this;
+ }
+ int Get() const { return i_; }
+ bool operator==(int i) const { return i_ == i; }
+
+ private:
+ template <typename T>
+ friend class OnlyConstructibleAllocator;
+
+ int i_;
+};
+
+template <typename T = OnlyConstructibleByAllocator>
+class OnlyConstructibleAllocator : public std::allocator<T> {
+ public:
+ OnlyConstructibleAllocator() = default;
+ template <class U>
+ explicit OnlyConstructibleAllocator(const OnlyConstructibleAllocator<U> &) {}
+
+ void construct(OnlyConstructibleByAllocator *p, int i) {
+ new (p) OnlyConstructibleByAllocator(i);
+ }
+ template <typename Pair>
+ void construct(Pair *p, const int i) {
+ OnlyConstructibleByAllocator only(i);
+ new (p) Pair(std::move(only), i);
+ }
+
+ template <class U>
+ struct rebind {
+ using other = OnlyConstructibleAllocator<U>;
+ };
+};
+
+struct OnlyConstructibleByAllocatorComp {
+ using is_transparent = void;
+ bool operator()(OnlyConstructibleByAllocator a,
+ OnlyConstructibleByAllocator b) const {
+ return a.Get() < b.Get();
+ }
+ bool operator()(int a, OnlyConstructibleByAllocator b) const {
+ return a < b.Get();
+ }
+ bool operator()(OnlyConstructibleByAllocator a, int b) const {
+ return a.Get() < b;
+ }
+};
+
+TEST(Btree, OnlyConstructibleByAllocatorType) {
+ const std::array<int, 2> arr = {3, 4};
+ {
+ absl::btree_set<OnlyConstructibleByAllocator,
+ OnlyConstructibleByAllocatorComp,
+ OnlyConstructibleAllocator<>>
+ set;
+ set.emplace(1);
+ set.emplace_hint(set.end(), 2);
+ set.insert(arr.begin(), arr.end());
+ EXPECT_THAT(set, ElementsAre(1, 2, 3, 4));
+ }
+ {
+ absl::btree_multiset<OnlyConstructibleByAllocator,
+ OnlyConstructibleByAllocatorComp,
+ OnlyConstructibleAllocator<>>
+ set;
+ set.emplace(1);
+ set.emplace_hint(set.end(), 2);
+ // TODO(ezb): fix insert_multi to allow this to compile.
+ // set.insert(arr.begin(), arr.end());
+ EXPECT_THAT(set, ElementsAre(1, 2));
+ }
+ {
+ absl::btree_map<OnlyConstructibleByAllocator, int,
+ OnlyConstructibleByAllocatorComp,
+ OnlyConstructibleAllocator<>>
+ map;
+ map.emplace(1);
+ map.emplace_hint(map.end(), 2);
+ map.insert(arr.begin(), arr.end());
+ EXPECT_THAT(map,
+ ElementsAre(Pair(1, 1), Pair(2, 2), Pair(3, 3), Pair(4, 4)));
+ }
+ {
+ absl::btree_multimap<OnlyConstructibleByAllocator, int,
+ OnlyConstructibleByAllocatorComp,
+ OnlyConstructibleAllocator<>>
+ map;
+ map.emplace(1);
+ map.emplace_hint(map.end(), 2);
+ // TODO(ezb): fix insert_multi to allow this to compile.
+ // map.insert(arr.begin(), arr.end());
+ EXPECT_THAT(map, ElementsAre(Pair(1, 1), Pair(2, 2)));
+ }
+}
+
+class NotAssignable {
+ public:
+ explicit NotAssignable(int i) : i_(i) {}
+ NotAssignable(const NotAssignable &other) : i_(other.i_) {}
+ NotAssignable &operator=(NotAssignable &&other) = delete;
+ int Get() const { return i_; }
+ bool operator==(int i) const { return i_ == i; }
+ friend bool operator<(NotAssignable a, NotAssignable b) {
+ return a.i_ < b.i_;
+ }
+
+ private:
+ int i_;
+};
+
+TEST(Btree, NotAssignableType) {
+ {
+ absl::btree_set<NotAssignable> set;
+ set.emplace(1);
+ set.emplace_hint(set.end(), 2);
+ set.insert(NotAssignable(3));
+ set.insert(set.end(), NotAssignable(4));
+ EXPECT_THAT(set, ElementsAre(1, 2, 3, 4));
+ set.erase(set.begin());
+ EXPECT_THAT(set, ElementsAre(2, 3, 4));
+ }
+ {
+ absl::btree_multiset<NotAssignable> set;
+ set.emplace(1);
+ set.emplace_hint(set.end(), 2);
+ set.insert(NotAssignable(2));
+ set.insert(set.end(), NotAssignable(3));
+ EXPECT_THAT(set, ElementsAre(1, 2, 2, 3));
+ set.erase(set.begin());
+ EXPECT_THAT(set, ElementsAre(2, 2, 3));
+ }
+ {
+ absl::btree_map<NotAssignable, int> map;
+ map.emplace(NotAssignable(1), 1);
+ map.emplace_hint(map.end(), NotAssignable(2), 2);
+ map.insert({NotAssignable(3), 3});
+ map.insert(map.end(), {NotAssignable(4), 4});
+ EXPECT_THAT(map,
+ ElementsAre(Pair(1, 1), Pair(2, 2), Pair(3, 3), Pair(4, 4)));
+ map.erase(map.begin());
+ EXPECT_THAT(map, ElementsAre(Pair(2, 2), Pair(3, 3), Pair(4, 4)));
+ }
+ {
+ absl::btree_multimap<NotAssignable, int> map;
+ map.emplace(NotAssignable(1), 1);
+ map.emplace_hint(map.end(), NotAssignable(2), 2);
+ map.insert({NotAssignable(2), 3});
+ map.insert(map.end(), {NotAssignable(3), 3});
+ EXPECT_THAT(map,
+ ElementsAre(Pair(1, 1), Pair(2, 2), Pair(2, 3), Pair(3, 3)));
+ map.erase(map.begin());
+ EXPECT_THAT(map, ElementsAre(Pair(2, 2), Pair(2, 3), Pair(3, 3)));
+ }
+}
+
+struct ArenaLike {
+ void* recycled = nullptr;
+ size_t recycled_size = 0;
+};
+
+// A very simple implementation of arena allocation.
+template <typename T>
+class ArenaLikeAllocator : public std::allocator<T> {
+ public:
+ // Standard library containers require the ability to allocate objects of
+ // different types which they can do so via rebind.other.
+ template <typename U>
+ struct rebind {
+ using other = ArenaLikeAllocator<U>;
+ };
+
+ explicit ArenaLikeAllocator(ArenaLike* arena) noexcept : arena_(arena) {}
+
+ ~ArenaLikeAllocator() {
+ if (arena_->recycled != nullptr) {
+ delete [] static_cast<T*>(arena_->recycled);
+ arena_->recycled = nullptr;
+ }
+ }
+
+ template<typename U>
+ explicit ArenaLikeAllocator(const ArenaLikeAllocator<U>& other) noexcept
+ : arena_(other.arena_) {}
+
+ T* allocate(size_t num_objects, const void* = nullptr) {
+ size_t size = num_objects * sizeof(T);
+ if (arena_->recycled != nullptr && arena_->recycled_size == size) {
+ T* result = static_cast<T*>(arena_->recycled);
+ arena_->recycled = nullptr;
+ return result;
+ }
+ return new T[num_objects];
+ }
+
+ void deallocate(T* p, size_t num_objects) {
+ size_t size = num_objects * sizeof(T);
+
+ // Simulate writing to the freed memory as an actual arena allocator might
+ // do. This triggers an error report if the memory is poisoned.
+ memset(p, 0xde, size);
+
+ if (arena_->recycled == nullptr) {
+ arena_->recycled = p;
+ arena_->recycled_size = size;
+ } else {
+ delete [] p;
+ }
+ }
+
+ ArenaLike* arena_;
+};
+
+// This test verifies that an arena allocator that reuses memory will not be
+// asked to free poisoned BTree memory.
+TEST(Btree, ReusePoisonMemory) {
+ using Alloc = ArenaLikeAllocator<int64_t>;
+ using Set = absl::btree_set<int64_t, std::less<int64_t>, Alloc>;
+ ArenaLike arena;
+ Alloc alloc(&arena);
+ Set set(alloc);
+
+ set.insert(0);
+ set.erase(0);
+ set.insert(0);
+}
+
+TEST(Btree, IteratorSubtraction) {
+ absl::BitGen bitgen;
+ std::vector<int> vec;
+ // Randomize the set's insertion order so the nodes aren't all full.
+ for (int i = 0; i < 1000000; ++i) vec.push_back(i);
+ absl::c_shuffle(vec, bitgen);
+
+ absl::btree_set<int> set;
+ for (int i : vec) set.insert(i);
+
+ for (int i = 0; i < 1000; ++i) {
+ size_t begin = absl::Uniform(bitgen, 0u, set.size());
+ size_t end = absl::Uniform(bitgen, begin, set.size());
+ ASSERT_EQ(end - begin, set.find(end) - set.find(begin))
+ << begin << " " << end;
+ }
+}
+
+TEST(Btree, DereferencingEndIterator) {
+ if (!IsAssertEnabled()) GTEST_SKIP() << "Assertions not enabled.";
+
+ absl::btree_set<int> set;
+ for (int i = 0; i < 1000; ++i) set.insert(i);
+ EXPECT_DEATH(*set.end(), R"regex(Dereferencing end\(\) iterator)regex");
+}
+
+TEST(Btree, InvalidIteratorComparison) {
+ if (!IsAssertEnabled()) GTEST_SKIP() << "Assertions not enabled.";
+
+ absl::btree_set<int> set1, set2;
+ for (int i = 0; i < 1000; ++i) {
+ set1.insert(i);
+ set2.insert(i);
+ }
+
+ constexpr const char *kValueInitDeathMessage =
+ "Comparing default-constructed iterator with .*non-default-constructed "
+ "iterator";
+ typename absl::btree_set<int>::iterator iter1, iter2;
+ EXPECT_EQ(iter1, iter2);
+ EXPECT_DEATH(void(set1.begin() == iter1), kValueInitDeathMessage);
+ EXPECT_DEATH(void(iter1 == set1.begin()), kValueInitDeathMessage);
+
+ constexpr const char *kDifferentContainerDeathMessage =
+ "Comparing iterators from different containers";
+ iter1 = set1.begin();
+ iter2 = set2.begin();
+ EXPECT_DEATH(void(iter1 == iter2), kDifferentContainerDeathMessage);
+ EXPECT_DEATH(void(iter2 == iter1), kDifferentContainerDeathMessage);
+}
+
+TEST(Btree, InvalidPointerUse) {
+ if (!kAsan)
+ GTEST_SKIP() << "We only detect invalid pointer use in ASan mode.";
+
+ absl::btree_set<int> set;
+ set.insert(0);
+ const int *ptr = &*set.begin();
+ set.insert(1);
+ EXPECT_DEATH(std::cout << *ptr, "use-after-free");
+ size_t slots_per_node = BtreeNodePeer::GetNumSlotsPerNode<decltype(set)>();
+ for (int i = 2; i < slots_per_node - 1; ++i) set.insert(i);
+ ptr = &*set.begin();
+ set.insert(static_cast<int>(slots_per_node));
+ EXPECT_DEATH(std::cout << *ptr, "use-after-free");
+}
+
+template<typename Set>
+void TestBasicFunctionality(Set set) {
+ using value_type = typename Set::value_type;
+ for (int i = 0; i < 100; ++i) { set.insert(value_type(i)); }
+ for (int i = 50; i < 100; ++i) { set.erase(value_type(i)); }
+ auto it = set.begin();
+ for (int i = 0; i < 50; ++i, ++it) {
+ ASSERT_EQ(set.find(value_type(i)), it) << i;
+ }
+}
+
+template<size_t align>
+struct alignas(align) OveralignedKey {
+ explicit OveralignedKey(int i) : key(i) {}
+ bool operator<(const OveralignedKey &other) const { return key < other.key; }
+ int key = 0;
+};
+
+TEST(Btree, OveralignedKey) {
+ // Test basic functionality with both even and odd numbers of slots per node.
+ // The goal here is to detect cases where alignment may be incorrect.
+ TestBasicFunctionality(
+ SizedBtreeSet<OveralignedKey<16>, /*TargetValuesPerNode=*/8>());
+ TestBasicFunctionality(
+ SizedBtreeSet<OveralignedKey<16>, /*TargetValuesPerNode=*/9>());
+}
+
+TEST(Btree, FieldTypeEqualsSlotType) {
+ // This breaks if we try to do layout_type::Pointer<slot_type> because
+ // slot_type is the same as field_type.
+ using set_type = absl::btree_set<uint8_t>;
+ static_assert(BtreeNodePeer::FieldTypeEqualsSlotType<set_type>(), "");
+ TestBasicFunctionality(set_type());
+}
+
} // namespace
} // namespace container_internal
ABSL_NAMESPACE_END
diff --git a/absl/container/fixed_array.h b/absl/container/fixed_array.h
index 839ba0b..9f1c813 100644
--- a/absl/container/fixed_array.h
+++ b/absl/container/fixed_array.h
@@ -62,11 +62,10 @@
// A `FixedArray` provides a run-time fixed-size array, allocating a small array
// inline for efficiency.
//
-// Most users should not specify an `inline_elements` argument and let
-// `FixedArray` automatically determine the number of elements
-// to store inline based on `sizeof(T)`. If `inline_elements` is specified, the
-// `FixedArray` implementation will use inline storage for arrays with a
-// length <= `inline_elements`.
+// Most users should not specify the `N` template parameter and let `FixedArray`
+// automatically determine the number of elements to store inline based on
+// `sizeof(T)`. If `N` is specified, the `FixedArray` implementation will use
+// inline storage for arrays with a length <= `N`.
//
// Note that a `FixedArray` constructed with a `size_type` argument will
// default-initialize its values by leaving trivially constructible types
@@ -118,14 +117,20 @@
(N == kFixedArrayUseDefault ? kInlineBytesDefault / sizeof(value_type)
: static_cast<size_type>(N));
- FixedArray(
- const FixedArray& other,
- const allocator_type& a = allocator_type()) noexcept(NoexceptCopyable())
+ FixedArray(const FixedArray& other) noexcept(NoexceptCopyable())
+ : FixedArray(other,
+ AllocatorTraits::select_on_container_copy_construction(
+ other.storage_.alloc())) {}
+
+ FixedArray(const FixedArray& other,
+ const allocator_type& a) noexcept(NoexceptCopyable())
: FixedArray(other.begin(), other.end(), a) {}
- FixedArray(
- FixedArray&& other,
- const allocator_type& a = allocator_type()) noexcept(NoexceptMovable())
+ FixedArray(FixedArray&& other) noexcept(NoexceptMovable())
+ : FixedArray(std::move(other), other.storage_.alloc()) {}
+
+ FixedArray(FixedArray&& other,
+ const allocator_type& a) noexcept(NoexceptMovable())
: FixedArray(std::make_move_iterator(other.begin()),
std::make_move_iterator(other.end()), a) {}
@@ -201,18 +206,22 @@
//
// Returns a const T* pointer to elements of the `FixedArray`. This pointer
// can be used to access (but not modify) the contained elements.
- const_pointer data() const { return AsValueType(storage_.begin()); }
+ const_pointer data() const ABSL_ATTRIBUTE_LIFETIME_BOUND {
+ return AsValueType(storage_.begin());
+ }
// Overload of FixedArray::data() to return a T* pointer to elements of the
// fixed array. This pointer can be used to access and modify the contained
// elements.
- pointer data() { return AsValueType(storage_.begin()); }
+ pointer data() ABSL_ATTRIBUTE_LIFETIME_BOUND {
+ return AsValueType(storage_.begin());
+ }
// FixedArray::operator[]
//
// Returns a reference the ith element of the fixed array.
// REQUIRES: 0 <= i < size()
- reference operator[](size_type i) {
+ reference operator[](size_type i) ABSL_ATTRIBUTE_LIFETIME_BOUND {
ABSL_HARDENING_ASSERT(i < size());
return data()[i];
}
@@ -220,7 +229,7 @@
// Overload of FixedArray::operator()[] to return a const reference to the
// ith element of the fixed array.
// REQUIRES: 0 <= i < size()
- const_reference operator[](size_type i) const {
+ const_reference operator[](size_type i) const ABSL_ATTRIBUTE_LIFETIME_BOUND {
ABSL_HARDENING_ASSERT(i < size());
return data()[i];
}
@@ -229,7 +238,7 @@
//
// Bounds-checked access. Returns a reference to the ith element of the fixed
// array, or throws std::out_of_range
- reference at(size_type i) {
+ reference at(size_type i) ABSL_ATTRIBUTE_LIFETIME_BOUND {
if (ABSL_PREDICT_FALSE(i >= size())) {
base_internal::ThrowStdOutOfRange("FixedArray::at failed bounds check");
}
@@ -238,7 +247,7 @@
// Overload of FixedArray::at() to return a const reference to the ith element
// of the fixed array.
- const_reference at(size_type i) const {
+ const_reference at(size_type i) const ABSL_ATTRIBUTE_LIFETIME_BOUND {
if (ABSL_PREDICT_FALSE(i >= size())) {
base_internal::ThrowStdOutOfRange("FixedArray::at failed bounds check");
}
@@ -248,14 +257,14 @@
// FixedArray::front()
//
// Returns a reference to the first element of the fixed array.
- reference front() {
+ reference front() ABSL_ATTRIBUTE_LIFETIME_BOUND {
ABSL_HARDENING_ASSERT(!empty());
return data()[0];
}
// Overload of FixedArray::front() to return a reference to the first element
// of a fixed array of const values.
- const_reference front() const {
+ const_reference front() const ABSL_ATTRIBUTE_LIFETIME_BOUND {
ABSL_HARDENING_ASSERT(!empty());
return data()[0];
}
@@ -263,14 +272,14 @@
// FixedArray::back()
//
// Returns a reference to the last element of the fixed array.
- reference back() {
+ reference back() ABSL_ATTRIBUTE_LIFETIME_BOUND {
ABSL_HARDENING_ASSERT(!empty());
return data()[size() - 1];
}
// Overload of FixedArray::back() to return a reference to the last element
// of a fixed array of const values.
- const_reference back() const {
+ const_reference back() const ABSL_ATTRIBUTE_LIFETIME_BOUND {
ABSL_HARDENING_ASSERT(!empty());
return data()[size() - 1];
}
@@ -278,62 +287,74 @@
// FixedArray::begin()
//
// Returns an iterator to the beginning of the fixed array.
- iterator begin() { return data(); }
+ iterator begin() ABSL_ATTRIBUTE_LIFETIME_BOUND { return data(); }
// Overload of FixedArray::begin() to return a const iterator to the
// beginning of the fixed array.
- const_iterator begin() const { return data(); }
+ const_iterator begin() const ABSL_ATTRIBUTE_LIFETIME_BOUND { return data(); }
// FixedArray::cbegin()
//
// Returns a const iterator to the beginning of the fixed array.
- const_iterator cbegin() const { return begin(); }
+ const_iterator cbegin() const ABSL_ATTRIBUTE_LIFETIME_BOUND {
+ return begin();
+ }
// FixedArray::end()
//
// Returns an iterator to the end of the fixed array.
- iterator end() { return data() + size(); }
+ iterator end() ABSL_ATTRIBUTE_LIFETIME_BOUND { return data() + size(); }
// Overload of FixedArray::end() to return a const iterator to the end of the
// fixed array.
- const_iterator end() const { return data() + size(); }
+ const_iterator end() const ABSL_ATTRIBUTE_LIFETIME_BOUND {
+ return data() + size();
+ }
// FixedArray::cend()
//
// Returns a const iterator to the end of the fixed array.
- const_iterator cend() const { return end(); }
+ const_iterator cend() const ABSL_ATTRIBUTE_LIFETIME_BOUND { return end(); }
// FixedArray::rbegin()
//
// Returns a reverse iterator from the end of the fixed array.
- reverse_iterator rbegin() { return reverse_iterator(end()); }
+ reverse_iterator rbegin() ABSL_ATTRIBUTE_LIFETIME_BOUND {
+ return reverse_iterator(end());
+ }
// Overload of FixedArray::rbegin() to return a const reverse iterator from
// the end of the fixed array.
- const_reverse_iterator rbegin() const {
+ const_reverse_iterator rbegin() const ABSL_ATTRIBUTE_LIFETIME_BOUND {
return const_reverse_iterator(end());
}
// FixedArray::crbegin()
//
// Returns a const reverse iterator from the end of the fixed array.
- const_reverse_iterator crbegin() const { return rbegin(); }
+ const_reverse_iterator crbegin() const ABSL_ATTRIBUTE_LIFETIME_BOUND {
+ return rbegin();
+ }
// FixedArray::rend()
//
// Returns a reverse iterator from the beginning of the fixed array.
- reverse_iterator rend() { return reverse_iterator(begin()); }
+ reverse_iterator rend() ABSL_ATTRIBUTE_LIFETIME_BOUND {
+ return reverse_iterator(begin());
+ }
// Overload of FixedArray::rend() for returning a const reverse iterator
// from the beginning of the fixed array.
- const_reverse_iterator rend() const {
+ const_reverse_iterator rend() const ABSL_ATTRIBUTE_LIFETIME_BOUND {
return const_reverse_iterator(begin());
}
// FixedArray::crend()
//
// Returns a reverse iterator from the beginning of the fixed array.
- const_reverse_iterator crend() const { return rend(); }
+ const_reverse_iterator crend() const ABSL_ATTRIBUTE_LIFETIME_BOUND {
+ return rend();
+ }
// FixedArray::fill()
//
@@ -343,7 +364,7 @@
// Relational operators. Equality operators are elementwise using
// `operator==`, while order operators order FixedArrays lexicographically.
friend bool operator==(const FixedArray& lhs, const FixedArray& rhs) {
- return absl::equal(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
+ return std::equal(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
}
friend bool operator!=(const FixedArray& lhs, const FixedArray& rhs) {
@@ -465,12 +486,18 @@
StorageElement* begin() const { return data_; }
StorageElement* end() const { return begin() + size(); }
allocator_type& alloc() { return size_alloc_.template get<1>(); }
+ const allocator_type& alloc() const {
+ return size_alloc_.template get<1>();
+ }
private:
static bool UsingInlinedStorage(size_type n) {
return n <= inline_elements;
}
+#ifdef ABSL_HAVE_ADDRESS_SANITIZER
+ ABSL_ATTRIBUTE_NOINLINE
+#endif // ABSL_HAVE_ADDRESS_SANITIZER
StorageElement* InitializeData() {
if (UsingInlinedStorage(size())) {
InlinedStorage::AnnotateConstruct(size());
@@ -489,12 +516,14 @@
Storage storage_;
};
+#ifdef ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL
template <typename T, size_t N, typename A>
constexpr size_t FixedArray<T, N, A>::kInlineBytesDefault;
template <typename T, size_t N, typename A>
constexpr typename FixedArray<T, N, A>::size_type
FixedArray<T, N, A>::inline_elements;
+#endif
template <typename T, size_t N, typename A>
void FixedArray<T, N, A>::NonEmptyInlinedStorage::AnnotateConstruct(
diff --git a/absl/container/fixed_array_benchmark.cc b/absl/container/fixed_array_benchmark.cc
index 3c7a5a7..db6663e 100644
--- a/absl/container/fixed_array_benchmark.cc
+++ b/absl/container/fixed_array_benchmark.cc
@@ -16,8 +16,8 @@
#include <string>
-#include "benchmark/benchmark.h"
#include "absl/container/fixed_array.h"
+#include "benchmark/benchmark.h"
namespace {
diff --git a/absl/container/fixed_array_test.cc b/absl/container/fixed_array_test.cc
index 49598e7..2421b5f 100644
--- a/absl/container/fixed_array_test.cc
+++ b/absl/container/fixed_array_test.cc
@@ -30,7 +30,7 @@
#include "absl/base/config.h"
#include "absl/base/internal/exception_testing.h"
#include "absl/base/options.h"
-#include "absl/container/internal/counting_allocator.h"
+#include "absl/container/internal/test_allocator.h"
#include "absl/hash/hash_testing.h"
#include "absl/memory/memory.h"
@@ -768,6 +768,22 @@
}
}
+TEST(AllocatorSupportTest, PropagatesStatefulAllocator) {
+ constexpr size_t inlined_size = 4;
+ using Alloc = absl::container_internal::CountingAllocator<int>;
+ using AllocFxdArr = absl::FixedArray<int, inlined_size, Alloc>;
+
+ auto len = inlined_size * 2;
+ auto val = 0;
+ int64_t allocated = 0;
+ AllocFxdArr arr(len, val, Alloc(&allocated));
+
+ EXPECT_EQ(allocated, len * sizeof(int));
+
+ AllocFxdArr copy = arr;
+ EXPECT_EQ(allocated, len * sizeof(int) * 2);
+}
+
#ifdef ABSL_HAVE_ADDRESS_SANITIZER
TEST(FixedArrayTest, AddressSanitizerAnnotations1) {
absl::FixedArray<int, 32> a(10);
diff --git a/absl/container/flat_hash_map.h b/absl/container/flat_hash_map.h
index 74def0d..a33c794 100644
--- a/absl/container/flat_hash_map.h
+++ b/absl/container/flat_hash_map.h
@@ -36,6 +36,7 @@
#include <utility>
#include "absl/algorithm/container.h"
+#include "absl/base/macros.h"
#include "absl/container/internal/container_memory.h"
#include "absl/container/internal/hash_function_defaults.h" // IWYU pragma: export
#include "absl/container/internal/raw_hash_map.h" // IWYU pragma: export
@@ -61,9 +62,9 @@
// * Requires values that are MoveConstructible
// * Supports heterogeneous lookup, through `find()`, `operator[]()` and
// `insert()`, provided that the map is provided a compatible heterogeneous
-// hashing function and equality operator.
+// hashing function and equality operator. See below for details.
// * Invalidates any references and pointers to elements within the table after
-// `rehash()`.
+// `rehash()` and when the table is moved.
// * Contains a `capacity()` member function indicating the number of element
// slots (open, deleted, and empty) within the hash map.
// * Returns `void` from the `erase(iterator)` overload.
@@ -75,6 +76,23 @@
// absl/hash/hash.h for information on extending Abseil hashing to user-defined
// types.
//
+// Using `absl::flat_hash_map` at interface boundaries in dynamically loaded
+// libraries (e.g. .dll, .so) is unsupported due to way `absl::Hash` values may
+// be randomized across dynamically loaded libraries.
+//
+// To achieve heterogeneous lookup for custom types either `Hash` and `Eq` type
+// parameters can be used or `T` should have public inner types
+// `absl_container_hash` and (optionally) `absl_container_eq`. In either case,
+// `typename Hash::is_transparent` and `typename Eq::is_transparent` should be
+// well-formed. Both types are basically functors:
+// * `Hash` should support `size_t operator()(U val) const` that returns a hash
+// for the given `val`.
+// * `Eq` should support `bool operator()(U lhs, V rhs) const` that returns true
+// if `lhs` is equal to `rhs`.
+//
+// In most cases `T` needs only to provide the `absl_container_hash`. In this
+// case `std::equal_to<void>` will be used instead of `eq` part.
+//
// NOTE: A `flat_hash_map` stores its value types directly inside its
// implementation array to avoid memory indirection. Because a `flat_hash_map`
// is designed to move data when rehashed, map values will not retain pointer
@@ -230,7 +248,11 @@
// iterator erase(const_iterator first, const_iterator last):
//
// Erases the elements in the open interval [`first`, `last`), returning an
- // iterator pointing to `last`.
+ // iterator pointing to `last`. The special case of calling
+ // `erase(begin(), end())` resets the reserved growth such that if
+ // `reserve(N)` has previously been called and there has been no intervening
+ // call to `clear()`, then after calling `erase(begin(), end())`, it is safe
+ // to assume that inserting N elements will not cause a rehash.
//
// size_type erase(const key_type& key):
//
@@ -356,8 +378,8 @@
// `flat_hash_map`.
//
// iterator try_emplace(const_iterator hint,
- // const init_type& k, Args&&... args):
- // iterator try_emplace(const_iterator hint, init_type&& k, Args&&... args):
+ // const key_type& k, Args&&... args):
+ // iterator try_emplace(const_iterator hint, key_type&& k, Args&&... args):
//
// Inserts (via copy or move) the element of the specified key into the
// `flat_hash_map` using the position of `hint` as a non-binding suggestion
@@ -541,10 +563,12 @@
// erase_if(flat_hash_map<>, Pred)
//
// Erases all elements that satisfy the predicate `pred` from the container `c`.
+// Returns the number of erased elements.
template <typename K, typename V, typename H, typename E, typename A,
typename Predicate>
-void erase_if(flat_hash_map<K, V, H, E, A>& c, Predicate pred) {
- container_internal::EraseIf(pred, &c);
+typename flat_hash_map<K, V, H, E, A>::size_type erase_if(
+ flat_hash_map<K, V, H, E, A>& c, Predicate pred) {
+ return container_internal::EraseIf(pred, &c);
}
namespace container_internal {
@@ -562,15 +586,16 @@
slot_policy::construct(alloc, slot, std::forward<Args>(args)...);
}
+ // Returns std::true_type in case destroy is trivial.
template <class Allocator>
- static void destroy(Allocator* alloc, slot_type* slot) {
- slot_policy::destroy(alloc, slot);
+ static auto destroy(Allocator* alloc, slot_type* slot) {
+ return slot_policy::destroy(alloc, slot);
}
template <class Allocator>
- static void transfer(Allocator* alloc, slot_type* new_slot,
+ static auto transfer(Allocator* alloc, slot_type* new_slot,
slot_type* old_slot) {
- slot_policy::transfer(alloc, new_slot, old_slot);
+ return slot_policy::transfer(alloc, new_slot, old_slot);
}
template <class F, class... Args>
@@ -581,6 +606,13 @@
std::forward<Args>(args)...);
}
+ template <class Hash>
+ static constexpr HashSlotFn get_hash_slot_fn() {
+ return memory_internal::IsLayoutCompatible<K, V>::value
+ ? &TypeErasedApplyToSlotFn<Hash, K>
+ : nullptr;
+ }
+
static size_t space_used(const slot_type*) { return 0; }
static std::pair<const K, V>& element(slot_type* slot) { return slot->value; }
diff --git a/absl/container/flat_hash_map_test.cc b/absl/container/flat_hash_map_test.cc
index 8dda1d3..8ef1a62 100644
--- a/absl/container/flat_hash_map_test.cc
+++ b/absl/container/flat_hash_map_test.cc
@@ -14,14 +14,21 @@
#include "absl/container/flat_hash_map.h"
+#include <cstddef>
#include <memory>
+#include <type_traits>
+#include <utility>
+#include <vector>
-#include "absl/base/internal/raw_logging.h"
+#include "gtest/gtest.h"
#include "absl/container/internal/hash_generator_testing.h"
+#include "absl/container/internal/test_allocator.h"
#include "absl/container/internal/unordered_map_constructor_test.h"
#include "absl/container/internal/unordered_map_lookup_test.h"
#include "absl/container/internal/unordered_map_members_test.h"
#include "absl/container/internal/unordered_map_modifiers_test.h"
+#include "absl/log/check.h"
+#include "absl/meta/type_traits.h"
#include "absl/types/any.h"
namespace absl {
@@ -40,10 +47,10 @@
BeforeMain() {
absl::flat_hash_map<int, int> x;
x.insert({1, 1});
- ABSL_RAW_CHECK(x.find(0) == x.end(), "x should not contain 0");
+ CHECK(x.find(0) == x.end()) << "x should not contain 0";
auto it = x.find(1);
- ABSL_RAW_CHECK(it != x.end(), "x should contain 1");
- ABSL_RAW_CHECK(it->second, "1 should map to 1");
+ CHECK(it != x.end()) << "x should contain 1";
+ CHECK(it->second) << "1 should map to 1";
}
};
const BeforeMain before_main;
@@ -102,6 +109,34 @@
}
}
+TEST(FlatHashMap, Relocatability) {
+ static_assert(absl::is_trivially_relocatable<int>::value, "");
+ static_assert(
+ absl::is_trivially_relocatable<std::pair<const int, int>>::value, "");
+ static_assert(
+ std::is_same<decltype(absl::container_internal::FlatHashMapPolicy<
+ int, int>::transfer<std::allocator<char>>(nullptr,
+ nullptr,
+ nullptr)),
+ std::true_type>::value,
+ "");
+
+ struct NonRelocatable {
+ NonRelocatable() = default;
+ NonRelocatable(NonRelocatable&&) {}
+ NonRelocatable& operator=(NonRelocatable&&) { return *this; }
+ void* self = nullptr;
+ };
+
+ EXPECT_FALSE(absl::is_trivially_relocatable<NonRelocatable>::value);
+ EXPECT_TRUE(
+ (std::is_same<decltype(absl::container_internal::FlatHashMapPolicy<
+ int, NonRelocatable>::
+ transfer<std::allocator<char>>(nullptr, nullptr,
+ nullptr)),
+ std::false_type>::value));
+}
+
// gcc becomes unhappy if this is inside the method, so pull it out here.
struct balast {};
@@ -150,9 +185,7 @@
struct Eq {
using is_transparent = void;
- bool operator()(size_t lhs, size_t rhs) const {
- return lhs == rhs;
- }
+ bool operator()(size_t lhs, size_t rhs) const { return lhs == rhs; }
bool operator()(size_t lhs, const LazyInt& rhs) const {
return lhs == rhs.value;
}
@@ -236,33 +269,36 @@
// Erase all elements.
{
flat_hash_map<int, int> s = {{1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}};
- erase_if(s, [](std::pair<const int, int>) { return true; });
+ EXPECT_EQ(erase_if(s, [](std::pair<const int, int>) { return true; }), 5);
EXPECT_THAT(s, IsEmpty());
}
// Erase no elements.
{
flat_hash_map<int, int> s = {{1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}};
- erase_if(s, [](std::pair<const int, int>) { return false; });
+ EXPECT_EQ(erase_if(s, [](std::pair<const int, int>) { return false; }), 0);
EXPECT_THAT(s, UnorderedElementsAre(Pair(1, 1), Pair(2, 2), Pair(3, 3),
Pair(4, 4), Pair(5, 5)));
}
// Erase specific elements.
{
flat_hash_map<int, int> s = {{1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}};
- erase_if(s,
- [](std::pair<const int, int> kvp) { return kvp.first % 2 == 1; });
+ EXPECT_EQ(erase_if(s,
+ [](std::pair<const int, int> kvp) {
+ return kvp.first % 2 == 1;
+ }),
+ 3);
EXPECT_THAT(s, UnorderedElementsAre(Pair(2, 2), Pair(4, 4)));
}
// Predicate is function reference.
{
flat_hash_map<int, int> s = {{1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}};
- erase_if(s, FirstIsEven);
+ EXPECT_EQ(erase_if(s, FirstIsEven), 2);
EXPECT_THAT(s, UnorderedElementsAre(Pair(1, 1), Pair(3, 3), Pair(5, 5)));
}
// Predicate is function pointer.
{
flat_hash_map<int, int> s = {{1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}};
- erase_if(s, &FirstIsEven);
+ EXPECT_EQ(erase_if(s, &FirstIsEven), 2);
EXPECT_THAT(s, UnorderedElementsAre(Pair(1, 1), Pair(3, 3), Pair(5, 5)));
}
}
@@ -308,6 +344,25 @@
}
}
+TEST(FlatHashMap, RecursiveTypeCompiles) {
+ struct RecursiveType {
+ flat_hash_map<int, RecursiveType> m;
+ };
+ RecursiveType t;
+ t.m[0] = RecursiveType{};
+}
+
+TEST(FlatHashMap, FlatHashMapPolicyDestroyReturnsTrue) {
+ EXPECT_TRUE(
+ (decltype(FlatHashMapPolicy<int, char>::destroy<std::allocator<char>>(
+ nullptr, nullptr))()));
+ EXPECT_FALSE(
+ (decltype(FlatHashMapPolicy<int, char>::destroy<CountingAllocator<char>>(
+ nullptr, nullptr))()));
+ EXPECT_FALSE((decltype(FlatHashMapPolicy<int, std::unique_ptr<int>>::destroy<
+ std::allocator<char>>(nullptr, nullptr))()));
+}
+
} // namespace
} // namespace container_internal
ABSL_NAMESPACE_END
diff --git a/absl/container/flat_hash_set.h b/absl/container/flat_hash_set.h
index 6b89da6..5f72f95 100644
--- a/absl/container/flat_hash_set.h
+++ b/absl/container/flat_hash_set.h
@@ -29,6 +29,8 @@
#ifndef ABSL_CONTAINER_FLAT_HASH_SET_H_
#define ABSL_CONTAINER_FLAT_HASH_SET_H_
+#include <cstddef>
+#include <memory>
#include <type_traits>
#include <utility>
@@ -58,20 +60,37 @@
// * Requires keys that are CopyConstructible
// * Supports heterogeneous lookup, through `find()` and `insert()`, provided
// that the set is provided a compatible heterogeneous hashing function and
-// equality operator.
+// equality operator. See below for details.
// * Invalidates any references and pointers to elements within the table after
-// `rehash()`.
+// `rehash()` and when the table is moved.
// * Contains a `capacity()` member function indicating the number of element
// slots (open, deleted, and empty) within the hash set.
// * Returns `void` from the `erase(iterator)` overload.
//
// By default, `flat_hash_set` uses the `absl::Hash` hashing framework. All
// fundamental and Abseil types that support the `absl::Hash` framework have a
-// compatible equality operator for comparing insertions into `flat_hash_map`.
+// compatible equality operator for comparing insertions into `flat_hash_set`.
// If your type is not yet supported by the `absl::Hash` framework, see
// absl/hash/hash.h for information on extending Abseil hashing to user-defined
// types.
//
+// Using `absl::flat_hash_set` at interface boundaries in dynamically loaded
+// libraries (e.g. .dll, .so) is unsupported due to way `absl::Hash` values may
+// be randomized across dynamically loaded libraries.
+//
+// To achieve heterogeneous lookup for custom types either `Hash` and `Eq` type
+// parameters can be used or `T` should have public inner types
+// `absl_container_hash` and (optionally) `absl_container_eq`. In either case,
+// `typename Hash::is_transparent` and `typename Eq::is_transparent` should be
+// well-formed. Both types are basically functors:
+// * `Hash` should support `size_t operator()(U val) const` that returns a hash
+// for the given `val`.
+// * `Eq` should support `bool operator()(U lhs, V rhs) const` that returns true
+// if `lhs` is equal to `rhs`.
+//
+// In most cases `T` needs only to provide the `absl_container_hash`. In this
+// case `std::equal_to<void>` will be used instead of `eq` part.
+//
// NOTE: A `flat_hash_set` stores its keys directly inside its implementation
// array to avoid memory indirection. Because a `flat_hash_set` is designed to
// move data when rehashed, set keys will not retain pointer stability. If you
@@ -106,7 +125,7 @@
public:
// Constructors and Assignment Operators
//
- // A flat_hash_set supports the same overload set as `std::unordered_map`
+ // A flat_hash_set supports the same overload set as `std::unordered_set`
// for construction and assignment:
//
// * Default constructor
@@ -173,7 +192,7 @@
// available within the `flat_hash_set`.
//
// NOTE: this member function is particular to `absl::flat_hash_set` and is
- // not provided in the `std::unordered_map` API.
+ // not provided in the `std::unordered_set` API.
using Base::capacity;
// flat_hash_set::empty()
@@ -223,7 +242,11 @@
// iterator erase(const_iterator first, const_iterator last):
//
// Erases the elements in the open interval [`first`, `last`), returning an
- // iterator pointing to `last`.
+ // iterator pointing to `last`. The special case of calling
+ // `erase(begin(), end())` resets the reserved growth such that if
+ // `reserve(N)` has previously been called and there has been no intervening
+ // call to `clear()`, then after calling `erase(begin(), end())`, it is safe
+ // to assume that inserting N elements will not cause a rehash.
//
// size_type erase(const key_type& key):
//
@@ -332,15 +355,15 @@
// flat_hash_set::swap(flat_hash_set& other)
//
// Exchanges the contents of this `flat_hash_set` with those of the `other`
- // flat hash map, avoiding invocation of any move, copy, or swap operations on
+ // flat hash set, avoiding invocation of any move, copy, or swap operations on
// individual elements.
//
// All iterators and references on the `flat_hash_set` remain valid, excepting
// for the past-the-end iterator, which is invalidated.
//
// `swap()` requires that the flat hash set's hashing and key equivalence
- // functions be Swappable, and are exchaged using unqualified calls to
- // non-member `swap()`. If the map's allocator has
+ // functions be Swappable, and are exchanged using unqualified calls to
+ // non-member `swap()`. If the set's allocator has
// `std::allocator_traits<allocator_type>::propagate_on_container_swap::value`
// set to `true`, the allocators are also exchanged using an unqualified call
// to non-member `swap()`; otherwise, the allocators are not swapped.
@@ -395,14 +418,14 @@
// flat_hash_set::bucket_count()
//
// Returns the number of "buckets" within the `flat_hash_set`. Note that
- // because a flat hash map contains all elements within its internal storage,
+ // because a flat hash set contains all elements within its internal storage,
// this value simply equals the current capacity of the `flat_hash_set`.
using Base::bucket_count;
// flat_hash_set::load_factor()
//
// Returns the current load factor of the `flat_hash_set` (the average number
- // of slots occupied with a value within the hash map).
+ // of slots occupied with a value within the hash set).
using Base::load_factor;
// flat_hash_set::max_load_factor()
@@ -443,9 +466,11 @@
// erase_if(flat_hash_set<>, Pred)
//
// Erases all elements that satisfy the predicate `pred` from the container `c`.
+// Returns the number of erased elements.
template <typename T, typename H, typename E, typename A, typename Predicate>
-void erase_if(flat_hash_set<T, H, E, A>& c, Predicate pred) {
- container_internal::EraseIf(pred, &c);
+typename flat_hash_set<T, H, E, A>::size_type erase_if(
+ flat_hash_set<T, H, E, A>& c, Predicate pred) {
+ return container_internal::EraseIf(pred, &c);
}
namespace container_internal {
@@ -463,16 +488,11 @@
std::forward<Args>(args)...);
}
+ // Return std::true_type in case destroy is trivial.
template <class Allocator>
- static void destroy(Allocator* alloc, slot_type* slot) {
+ static auto destroy(Allocator* alloc, slot_type* slot) {
absl::allocator_traits<Allocator>::destroy(*alloc, slot);
- }
-
- template <class Allocator>
- static void transfer(Allocator* alloc, slot_type* new_slot,
- slot_type* old_slot) {
- construct(alloc, new_slot, std::move(*old_slot));
- destroy(alloc, old_slot);
+ return IsDestructionTrivial<Allocator, slot_type>();
}
static T& element(slot_type* slot) { return *slot; }
@@ -486,6 +506,11 @@
}
static size_t space_used(const T*) { return 0; }
+
+ template <class Hash>
+ static constexpr HashSlotFn get_hash_slot_fn() {
+ return &TypeErasedApplyToSlotFn<Hash, T>;
+ }
};
} // namespace container_internal
diff --git a/absl/container/flat_hash_set_test.cc b/absl/container/flat_hash_set_test.cc
index 8f6f994..9ce9267 100644
--- a/absl/container/flat_hash_set_test.cc
+++ b/absl/container/flat_hash_set_test.cc
@@ -14,14 +14,23 @@
#include "absl/container/flat_hash_set.h"
+#include <cstdint>
+#include <memory>
+#include <type_traits>
+#include <utility>
#include <vector>
-#include "absl/base/internal/raw_logging.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/base/config.h"
+#include "absl/container/internal/container_memory.h"
#include "absl/container/internal/hash_generator_testing.h"
+#include "absl/container/internal/test_allocator.h"
#include "absl/container/internal/unordered_set_constructor_test.h"
#include "absl/container/internal/unordered_set_lookup_test.h"
#include "absl/container/internal/unordered_set_members_test.h"
#include "absl/container/internal/unordered_set_modifiers_test.h"
+#include "absl/log/check.h"
#include "absl/memory/memory.h"
#include "absl/strings/string_view.h"
@@ -42,8 +51,8 @@
BeforeMain() {
absl::flat_hash_set<int> x;
x.insert(1);
- ABSL_RAW_CHECK(!x.contains(0), "x should not contain 0");
- ABSL_RAW_CHECK(x.contains(1), "x should contain 1");
+ CHECK(!x.contains(0)) << "x should not contain 0";
+ CHECK(x.contains(1)) << "x should contain 1";
}
};
const BeforeMain before_main;
@@ -143,35 +152,103 @@
// Erase all elements.
{
flat_hash_set<int> s = {1, 2, 3, 4, 5};
- erase_if(s, [](int) { return true; });
+ EXPECT_EQ(erase_if(s, [](int) { return true; }), 5);
EXPECT_THAT(s, IsEmpty());
}
// Erase no elements.
{
flat_hash_set<int> s = {1, 2, 3, 4, 5};
- erase_if(s, [](int) { return false; });
+ EXPECT_EQ(erase_if(s, [](int) { return false; }), 0);
EXPECT_THAT(s, UnorderedElementsAre(1, 2, 3, 4, 5));
}
// Erase specific elements.
{
flat_hash_set<int> s = {1, 2, 3, 4, 5};
- erase_if(s, [](int k) { return k % 2 == 1; });
+ EXPECT_EQ(erase_if(s, [](int k) { return k % 2 == 1; }), 3);
EXPECT_THAT(s, UnorderedElementsAre(2, 4));
}
// Predicate is function reference.
{
flat_hash_set<int> s = {1, 2, 3, 4, 5};
- erase_if(s, IsEven);
+ EXPECT_EQ(erase_if(s, IsEven), 2);
EXPECT_THAT(s, UnorderedElementsAre(1, 3, 5));
}
// Predicate is function pointer.
{
flat_hash_set<int> s = {1, 2, 3, 4, 5};
- erase_if(s, &IsEven);
+ EXPECT_EQ(erase_if(s, &IsEven), 2);
EXPECT_THAT(s, UnorderedElementsAre(1, 3, 5));
}
}
+class PoisonInline {
+ int64_t data_;
+
+ public:
+ explicit PoisonInline(int64_t d) : data_(d) {
+ SanitizerPoisonObject(&data_);
+ }
+ PoisonInline(const PoisonInline& that) : PoisonInline(*that) {}
+ ~PoisonInline() { SanitizerUnpoisonObject(&data_); }
+
+ int64_t operator*() const {
+ SanitizerUnpoisonObject(&data_);
+ const int64_t ret = data_;
+ SanitizerPoisonObject(&data_);
+ return ret;
+ }
+ template <typename H>
+ friend H AbslHashValue(H h, const PoisonInline& pi) {
+ return H::combine(std::move(h), *pi);
+ }
+ bool operator==(const PoisonInline& rhs) const { return **this == *rhs; }
+};
+
+// Tests that we don't touch the poison_ member of PoisonInline.
+TEST(FlatHashSet, PoisonInline) {
+ PoisonInline a(0), b(1);
+ { // basic usage
+ flat_hash_set<PoisonInline> set;
+ set.insert(a);
+ EXPECT_THAT(set, UnorderedElementsAre(a));
+ set.insert(b);
+ EXPECT_THAT(set, UnorderedElementsAre(a, b));
+ set.erase(a);
+ EXPECT_THAT(set, UnorderedElementsAre(b));
+ set.rehash(0); // shrink to inline
+ EXPECT_THAT(set, UnorderedElementsAre(b));
+ }
+ { // test move constructor from inline to inline
+ flat_hash_set<PoisonInline> set;
+ set.insert(a);
+ flat_hash_set<PoisonInline> set2(std::move(set));
+ EXPECT_THAT(set2, UnorderedElementsAre(a));
+ }
+ { // test move assignment from inline to inline
+ flat_hash_set<PoisonInline> set, set2;
+ set.insert(a);
+ set2 = std::move(set);
+ EXPECT_THAT(set2, UnorderedElementsAre(a));
+ }
+ { // test alloc move constructor from inline to inline
+ flat_hash_set<PoisonInline> set;
+ set.insert(a);
+ flat_hash_set<PoisonInline> set2(std::move(set),
+ std::allocator<PoisonInline>());
+ EXPECT_THAT(set2, UnorderedElementsAre(a));
+ }
+}
+
+TEST(FlatHashSet, FlatHashSetPolicyDestroyReturnsTrue) {
+ EXPECT_TRUE((decltype(FlatHashSetPolicy<int>::destroy<std::allocator<int>>(
+ nullptr, nullptr))()));
+ EXPECT_FALSE(
+ (decltype(FlatHashSetPolicy<int>::destroy<CountingAllocator<int>>(
+ nullptr, nullptr))()));
+ EXPECT_FALSE((decltype(FlatHashSetPolicy<std::unique_ptr<int>>::destroy<
+ std::allocator<int>>(nullptr, nullptr))()));
+}
+
} // namespace
} // namespace container_internal
ABSL_NAMESPACE_END
diff --git a/absl/container/inlined_vector.h b/absl/container/inlined_vector.h
index 7c18234..974b652 100644
--- a/absl/container/inlined_vector.h
+++ b/absl/container/inlined_vector.h
@@ -36,7 +36,6 @@
#define ABSL_CONTAINER_INLINED_VECTOR_H_
#include <algorithm>
-#include <cassert>
#include <cstddef>
#include <cstdlib>
#include <cstring>
@@ -53,6 +52,7 @@
#include "absl/base/port.h"
#include "absl/container/internal/inlined_vector.h"
#include "absl/memory/memory.h"
+#include "absl/meta/type_traits.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
@@ -72,37 +72,49 @@
using Storage = inlined_vector_internal::Storage<T, N, A>;
- using AllocatorTraits = typename Storage::AllocatorTraits;
- using RValueReference = typename Storage::RValueReference;
- using MoveIterator = typename Storage::MoveIterator;
- using IsMemcpyOk = typename Storage::IsMemcpyOk;
+ template <typename TheA>
+ using AllocatorTraits = inlined_vector_internal::AllocatorTraits<TheA>;
+ template <typename TheA>
+ using MoveIterator = inlined_vector_internal::MoveIterator<TheA>;
+ template <typename TheA>
+ using IsMoveAssignOk = inlined_vector_internal::IsMoveAssignOk<TheA>;
- template <typename Iterator>
+ template <typename TheA, typename Iterator>
using IteratorValueAdapter =
- typename Storage::template IteratorValueAdapter<Iterator>;
- using CopyValueAdapter = typename Storage::CopyValueAdapter;
- using DefaultValueAdapter = typename Storage::DefaultValueAdapter;
+ inlined_vector_internal::IteratorValueAdapter<TheA, Iterator>;
+ template <typename TheA>
+ using CopyValueAdapter = inlined_vector_internal::CopyValueAdapter<TheA>;
+ template <typename TheA>
+ using DefaultValueAdapter =
+ inlined_vector_internal::DefaultValueAdapter<TheA>;
template <typename Iterator>
using EnableIfAtLeastForwardIterator = absl::enable_if_t<
- inlined_vector_internal::IsAtLeastForwardIterator<Iterator>::value>;
+ inlined_vector_internal::IsAtLeastForwardIterator<Iterator>::value, int>;
template <typename Iterator>
using DisableIfAtLeastForwardIterator = absl::enable_if_t<
- !inlined_vector_internal::IsAtLeastForwardIterator<Iterator>::value>;
+ !inlined_vector_internal::IsAtLeastForwardIterator<Iterator>::value, int>;
+
+ using MemcpyPolicy = typename Storage::MemcpyPolicy;
+ using ElementwiseAssignPolicy = typename Storage::ElementwiseAssignPolicy;
+ using ElementwiseConstructPolicy =
+ typename Storage::ElementwiseConstructPolicy;
+ using MoveAssignmentPolicy = typename Storage::MoveAssignmentPolicy;
public:
- using allocator_type = typename Storage::allocator_type;
- using value_type = typename Storage::value_type;
- using pointer = typename Storage::pointer;
- using const_pointer = typename Storage::const_pointer;
- using size_type = typename Storage::size_type;
- using difference_type = typename Storage::difference_type;
- using reference = typename Storage::reference;
- using const_reference = typename Storage::const_reference;
- using iterator = typename Storage::iterator;
- using const_iterator = typename Storage::const_iterator;
- using reverse_iterator = typename Storage::reverse_iterator;
- using const_reverse_iterator = typename Storage::const_reverse_iterator;
+ using allocator_type = A;
+ using value_type = inlined_vector_internal::ValueType<A>;
+ using pointer = inlined_vector_internal::Pointer<A>;
+ using const_pointer = inlined_vector_internal::ConstPointer<A>;
+ using size_type = inlined_vector_internal::SizeType<A>;
+ using difference_type = inlined_vector_internal::DifferenceType<A>;
+ using reference = inlined_vector_internal::Reference<A>;
+ using const_reference = inlined_vector_internal::ConstReference<A>;
+ using iterator = inlined_vector_internal::Iterator<A>;
+ using const_iterator = inlined_vector_internal::ConstIterator<A>;
+ using reverse_iterator = inlined_vector_internal::ReverseIterator<A>;
+ using const_reverse_iterator =
+ inlined_vector_internal::ConstReverseIterator<A>;
// ---------------------------------------------------------------------------
// InlinedVector Constructors and Destructor
@@ -111,28 +123,28 @@
// Creates an empty inlined vector with a value-initialized allocator.
InlinedVector() noexcept(noexcept(allocator_type())) : storage_() {}
- // Creates an empty inlined vector with a copy of `alloc`.
- explicit InlinedVector(const allocator_type& alloc) noexcept
- : storage_(alloc) {}
+ // Creates an empty inlined vector with a copy of `allocator`.
+ explicit InlinedVector(const allocator_type& allocator) noexcept
+ : storage_(allocator) {}
// Creates an inlined vector with `n` copies of `value_type()`.
explicit InlinedVector(size_type n,
- const allocator_type& alloc = allocator_type())
- : storage_(alloc) {
- storage_.Initialize(DefaultValueAdapter(), n);
+ const allocator_type& allocator = allocator_type())
+ : storage_(allocator) {
+ storage_.Initialize(DefaultValueAdapter<A>(), n);
}
// Creates an inlined vector with `n` copies of `v`.
InlinedVector(size_type n, const_reference v,
- const allocator_type& alloc = allocator_type())
- : storage_(alloc) {
- storage_.Initialize(CopyValueAdapter(v), n);
+ const allocator_type& allocator = allocator_type())
+ : storage_(allocator) {
+ storage_.Initialize(CopyValueAdapter<A>(std::addressof(v)), n);
}
// Creates an inlined vector with copies of the elements of `list`.
InlinedVector(std::initializer_list<value_type> list,
- const allocator_type& alloc = allocator_type())
- : InlinedVector(list.begin(), list.end(), alloc) {}
+ const allocator_type& allocator = allocator_type())
+ : InlinedVector(list.begin(), list.end(), allocator) {}
// Creates an inlined vector with elements constructed from the provided
// forward iterator range [`first`, `last`).
@@ -141,40 +153,50 @@
// this constructor with two integral arguments and a call to the above
// `InlinedVector(size_type, const_reference)` constructor.
template <typename ForwardIterator,
- EnableIfAtLeastForwardIterator<ForwardIterator>* = nullptr>
+ EnableIfAtLeastForwardIterator<ForwardIterator> = 0>
InlinedVector(ForwardIterator first, ForwardIterator last,
- const allocator_type& alloc = allocator_type())
- : storage_(alloc) {
- storage_.Initialize(IteratorValueAdapter<ForwardIterator>(first),
- std::distance(first, last));
+ const allocator_type& allocator = allocator_type())
+ : storage_(allocator) {
+ storage_.Initialize(IteratorValueAdapter<A, ForwardIterator>(first),
+ static_cast<size_t>(std::distance(first, last)));
}
// Creates an inlined vector with elements constructed from the provided input
// iterator range [`first`, `last`).
template <typename InputIterator,
- DisableIfAtLeastForwardIterator<InputIterator>* = nullptr>
+ DisableIfAtLeastForwardIterator<InputIterator> = 0>
InlinedVector(InputIterator first, InputIterator last,
- const allocator_type& alloc = allocator_type())
- : storage_(alloc) {
+ const allocator_type& allocator = allocator_type())
+ : storage_(allocator) {
std::copy(first, last, std::back_inserter(*this));
}
// Creates an inlined vector by copying the contents of `other` using
// `other`'s allocator.
InlinedVector(const InlinedVector& other)
- : InlinedVector(other, *other.storage_.GetAllocPtr()) {}
+ : InlinedVector(other, other.storage_.GetAllocator()) {}
- // Creates an inlined vector by copying the contents of `other` using `alloc`.
- InlinedVector(const InlinedVector& other, const allocator_type& alloc)
- : storage_(alloc) {
+ // Creates an inlined vector by copying the contents of `other` using the
+ // provided `allocator`.
+ InlinedVector(const InlinedVector& other, const allocator_type& allocator)
+ : storage_(allocator) {
+ // Fast path: if the other vector is empty, there's nothing for us to do.
if (other.empty()) {
- // Empty; nothing to do.
- } else if (IsMemcpyOk::value && !other.storage_.GetIsAllocated()) {
- // Memcpy-able and do not need allocation.
- storage_.MemcpyFrom(other.storage_);
- } else {
- storage_.InitFrom(other.storage_);
+ return;
}
+
+ // Fast path: if the value type is trivially copy constructible, we know the
+ // allocator doesn't do anything fancy, and there is nothing on the heap
+ // then we know it is legal for us to simply memcpy the other vector's
+ // inlined bytes to form our copy of its elements.
+ if (absl::is_trivially_copy_constructible<value_type>::value &&
+ std::is_same<A, std::allocator<value_type>>::value &&
+ !other.storage_.GetIsAllocated()) {
+ storage_.MemcpyFrom(other.storage_);
+ return;
+ }
+
+ storage_.InitFrom(other.storage_);
}
// Creates an inlined vector by moving in the contents of `other` without
@@ -194,55 +216,81 @@
InlinedVector(InlinedVector&& other) noexcept(
absl::allocator_is_nothrow<allocator_type>::value ||
std::is_nothrow_move_constructible<value_type>::value)
- : storage_(*other.storage_.GetAllocPtr()) {
- if (IsMemcpyOk::value) {
+ : storage_(other.storage_.GetAllocator()) {
+ // Fast path: if the value type can be trivially relocated (i.e. moved from
+ // and destroyed), and we know the allocator doesn't do anything fancy, then
+ // it's safe for us to simply adopt the contents of the storage for `other`
+ // and remove its own reference to them. It's as if we had individually
+ // move-constructed each value and then destroyed the original.
+ if (absl::is_trivially_relocatable<value_type>::value &&
+ std::is_same<A, std::allocator<value_type>>::value) {
storage_.MemcpyFrom(other.storage_);
-
other.storage_.SetInlinedSize(0);
- } else if (other.storage_.GetIsAllocated()) {
- storage_.SetAllocatedData(other.storage_.GetAllocatedData(),
- other.storage_.GetAllocatedCapacity());
+ return;
+ }
+
+ // Fast path: if the other vector is on the heap, we can simply take over
+ // its allocation.
+ if (other.storage_.GetIsAllocated()) {
+ storage_.SetAllocation({other.storage_.GetAllocatedData(),
+ other.storage_.GetAllocatedCapacity()});
storage_.SetAllocatedSize(other.storage_.GetSize());
other.storage_.SetInlinedSize(0);
- } else {
- IteratorValueAdapter<MoveIterator> other_values(
- MoveIterator(other.storage_.GetInlinedData()));
-
- inlined_vector_internal::ConstructElements(
- storage_.GetAllocPtr(), storage_.GetInlinedData(), &other_values,
- other.storage_.GetSize());
-
- storage_.SetInlinedSize(other.storage_.GetSize());
+ return;
}
+
+ // Otherwise we must move each element individually.
+ IteratorValueAdapter<A, MoveIterator<A>> other_values(
+ MoveIterator<A>(other.storage_.GetInlinedData()));
+
+ inlined_vector_internal::ConstructElements<A>(
+ storage_.GetAllocator(), storage_.GetInlinedData(), other_values,
+ other.storage_.GetSize());
+
+ storage_.SetInlinedSize(other.storage_.GetSize());
}
// Creates an inlined vector by moving in the contents of `other` with a copy
- // of `alloc`.
+ // of `allocator`.
//
- // NOTE: if `other`'s allocator is not equal to `alloc`, even if `other`
+ // NOTE: if `other`'s allocator is not equal to `allocator`, even if `other`
// contains allocated memory, this move constructor will still allocate. Since
// allocation is performed, this constructor can only be `noexcept` if the
// specified allocator is also `noexcept`.
- InlinedVector(InlinedVector&& other, const allocator_type& alloc) noexcept(
- absl::allocator_is_nothrow<allocator_type>::value)
- : storage_(alloc) {
- if (IsMemcpyOk::value) {
+ InlinedVector(
+ InlinedVector&& other,
+ const allocator_type&
+ allocator) noexcept(absl::allocator_is_nothrow<allocator_type>::value)
+ : storage_(allocator) {
+ // Fast path: if the value type can be trivially relocated (i.e. moved from
+ // and destroyed), and we know the allocator doesn't do anything fancy, then
+ // it's safe for us to simply adopt the contents of the storage for `other`
+ // and remove its own reference to them. It's as if we had individually
+ // move-constructed each value and then destroyed the original.
+ if (absl::is_trivially_relocatable<value_type>::value &&
+ std::is_same<A, std::allocator<value_type>>::value) {
storage_.MemcpyFrom(other.storage_);
-
other.storage_.SetInlinedSize(0);
- } else if ((*storage_.GetAllocPtr() == *other.storage_.GetAllocPtr()) &&
- other.storage_.GetIsAllocated()) {
- storage_.SetAllocatedData(other.storage_.GetAllocatedData(),
- other.storage_.GetAllocatedCapacity());
+ return;
+ }
+
+ // Fast path: if the other vector is on the heap and shared the same
+ // allocator, we can simply take over its allocation.
+ if ((storage_.GetAllocator() == other.storage_.GetAllocator()) &&
+ other.storage_.GetIsAllocated()) {
+ storage_.SetAllocation({other.storage_.GetAllocatedData(),
+ other.storage_.GetAllocatedCapacity()});
storage_.SetAllocatedSize(other.storage_.GetSize());
other.storage_.SetInlinedSize(0);
- } else {
- storage_.Initialize(
- IteratorValueAdapter<MoveIterator>(MoveIterator(other.data())),
- other.size());
+ return;
}
+
+ // Otherwise we must move each element individually.
+ storage_.Initialize(
+ IteratorValueAdapter<A, MoveIterator<A>>(MoveIterator<A>(other.data())),
+ other.size());
}
~InlinedVector() {}
@@ -267,8 +315,10 @@
size_type max_size() const noexcept {
// One bit of the size storage is used to indicate whether the inlined
// vector contains allocated memory. As a result, the maximum size that the
- // inlined vector can express is half of the max for `size_type`.
- return (std::numeric_limits<size_type>::max)() / 2;
+ // inlined vector can express is the minimum of the limit of how many
+ // objects we can allocate and std::numeric_limits<size_type>::max() / 2.
+ return (std::min)(AllocatorTraits<A>::max_size(storage_.GetAllocator()),
+ (std::numeric_limits<size_type>::max)() / 2);
}
// `InlinedVector::capacity()`
@@ -291,7 +341,7 @@
// can be used to access and modify the contained elements.
//
// NOTE: only elements within [`data()`, `data() + size()`) are valid.
- pointer data() noexcept {
+ pointer data() noexcept ABSL_ATTRIBUTE_LIFETIME_BOUND {
return storage_.GetIsAllocated() ? storage_.GetAllocatedData()
: storage_.GetInlinedData();
}
@@ -301,7 +351,7 @@
// modify the contained elements.
//
// NOTE: only elements within [`data()`, `data() + size()`) are valid.
- const_pointer data() const noexcept {
+ const_pointer data() const noexcept ABSL_ATTRIBUTE_LIFETIME_BOUND {
return storage_.GetIsAllocated() ? storage_.GetAllocatedData()
: storage_.GetInlinedData();
}
@@ -309,14 +359,14 @@
// `InlinedVector::operator[](...)`
//
// Returns a `reference` to the `i`th element of the inlined vector.
- reference operator[](size_type i) {
+ reference operator[](size_type i) ABSL_ATTRIBUTE_LIFETIME_BOUND {
ABSL_HARDENING_ASSERT(i < size());
return data()[i];
}
// Overload of `InlinedVector::operator[](...)` that returns a
// `const_reference` to the `i`th element of the inlined vector.
- const_reference operator[](size_type i) const {
+ const_reference operator[](size_type i) const ABSL_ATTRIBUTE_LIFETIME_BOUND {
ABSL_HARDENING_ASSERT(i < size());
return data()[i];
}
@@ -327,7 +377,7 @@
//
// NOTE: if `i` is not within the required range of `InlinedVector::at(...)`,
// in both debug and non-debug builds, `std::out_of_range` will be thrown.
- reference at(size_type i) {
+ reference at(size_type i) ABSL_ATTRIBUTE_LIFETIME_BOUND {
if (ABSL_PREDICT_FALSE(i >= size())) {
base_internal::ThrowStdOutOfRange(
"`InlinedVector::at(size_type)` failed bounds check");
@@ -340,7 +390,7 @@
//
// NOTE: if `i` is not within the required range of `InlinedVector::at(...)`,
// in both debug and non-debug builds, `std::out_of_range` will be thrown.
- const_reference at(size_type i) const {
+ const_reference at(size_type i) const ABSL_ATTRIBUTE_LIFETIME_BOUND {
if (ABSL_PREDICT_FALSE(i >= size())) {
base_internal::ThrowStdOutOfRange(
"`InlinedVector::at(size_type) const` failed bounds check");
@@ -351,14 +401,14 @@
// `InlinedVector::front()`
//
// Returns a `reference` to the first element of the inlined vector.
- reference front() {
+ reference front() ABSL_ATTRIBUTE_LIFETIME_BOUND {
ABSL_HARDENING_ASSERT(!empty());
return data()[0];
}
// Overload of `InlinedVector::front()` that returns a `const_reference` to
// the first element of the inlined vector.
- const_reference front() const {
+ const_reference front() const ABSL_ATTRIBUTE_LIFETIME_BOUND {
ABSL_HARDENING_ASSERT(!empty());
return data()[0];
}
@@ -366,14 +416,14 @@
// `InlinedVector::back()`
//
// Returns a `reference` to the last element of the inlined vector.
- reference back() {
+ reference back() ABSL_ATTRIBUTE_LIFETIME_BOUND {
ABSL_HARDENING_ASSERT(!empty());
return data()[size() - 1];
}
// Overload of `InlinedVector::back()` that returns a `const_reference` to the
// last element of the inlined vector.
- const_reference back() const {
+ const_reference back() const ABSL_ATTRIBUTE_LIFETIME_BOUND {
ABSL_HARDENING_ASSERT(!empty());
return data()[size() - 1];
}
@@ -381,68 +431,87 @@
// `InlinedVector::begin()`
//
// Returns an `iterator` to the beginning of the inlined vector.
- iterator begin() noexcept { return data(); }
+ iterator begin() noexcept ABSL_ATTRIBUTE_LIFETIME_BOUND { return data(); }
// Overload of `InlinedVector::begin()` that returns a `const_iterator` to
// the beginning of the inlined vector.
- const_iterator begin() const noexcept { return data(); }
+ const_iterator begin() const noexcept ABSL_ATTRIBUTE_LIFETIME_BOUND {
+ return data();
+ }
// `InlinedVector::end()`
//
// Returns an `iterator` to the end of the inlined vector.
- iterator end() noexcept { return data() + size(); }
+ iterator end() noexcept ABSL_ATTRIBUTE_LIFETIME_BOUND {
+ return data() + size();
+ }
// Overload of `InlinedVector::end()` that returns a `const_iterator` to the
// end of the inlined vector.
- const_iterator end() const noexcept { return data() + size(); }
+ const_iterator end() const noexcept ABSL_ATTRIBUTE_LIFETIME_BOUND {
+ return data() + size();
+ }
// `InlinedVector::cbegin()`
//
// Returns a `const_iterator` to the beginning of the inlined vector.
- const_iterator cbegin() const noexcept { return begin(); }
+ const_iterator cbegin() const noexcept ABSL_ATTRIBUTE_LIFETIME_BOUND {
+ return begin();
+ }
// `InlinedVector::cend()`
//
// Returns a `const_iterator` to the end of the inlined vector.
- const_iterator cend() const noexcept { return end(); }
+ const_iterator cend() const noexcept ABSL_ATTRIBUTE_LIFETIME_BOUND {
+ return end();
+ }
// `InlinedVector::rbegin()`
//
// Returns a `reverse_iterator` from the end of the inlined vector.
- reverse_iterator rbegin() noexcept { return reverse_iterator(end()); }
+ reverse_iterator rbegin() noexcept ABSL_ATTRIBUTE_LIFETIME_BOUND {
+ return reverse_iterator(end());
+ }
// Overload of `InlinedVector::rbegin()` that returns a
// `const_reverse_iterator` from the end of the inlined vector.
- const_reverse_iterator rbegin() const noexcept {
+ const_reverse_iterator rbegin() const noexcept ABSL_ATTRIBUTE_LIFETIME_BOUND {
return const_reverse_iterator(end());
}
// `InlinedVector::rend()`
//
// Returns a `reverse_iterator` from the beginning of the inlined vector.
- reverse_iterator rend() noexcept { return reverse_iterator(begin()); }
+ reverse_iterator rend() noexcept ABSL_ATTRIBUTE_LIFETIME_BOUND {
+ return reverse_iterator(begin());
+ }
// Overload of `InlinedVector::rend()` that returns a `const_reverse_iterator`
// from the beginning of the inlined vector.
- const_reverse_iterator rend() const noexcept {
+ const_reverse_iterator rend() const noexcept ABSL_ATTRIBUTE_LIFETIME_BOUND {
return const_reverse_iterator(begin());
}
// `InlinedVector::crbegin()`
//
// Returns a `const_reverse_iterator` from the end of the inlined vector.
- const_reverse_iterator crbegin() const noexcept { return rbegin(); }
+ const_reverse_iterator crbegin() const noexcept
+ ABSL_ATTRIBUTE_LIFETIME_BOUND {
+ return rbegin();
+ }
// `InlinedVector::crend()`
//
// Returns a `const_reverse_iterator` from the beginning of the inlined
// vector.
- const_reverse_iterator crend() const noexcept { return rend(); }
+ const_reverse_iterator crend() const noexcept ABSL_ATTRIBUTE_LIFETIME_BOUND {
+ return rend();
+ }
// `InlinedVector::get_allocator()`
//
// Returns a copy of the inlined vector's allocator.
- allocator_type get_allocator() const { return *storage_.GetAllocPtr(); }
+ allocator_type get_allocator() const { return storage_.GetAllocator(); }
// ---------------------------------------------------------------------------
// InlinedVector Member Mutators
@@ -476,18 +545,7 @@
// unspecified state.
InlinedVector& operator=(InlinedVector&& other) {
if (ABSL_PREDICT_TRUE(this != std::addressof(other))) {
- if (IsMemcpyOk::value || other.storage_.GetIsAllocated()) {
- inlined_vector_internal::DestroyElements(storage_.GetAllocPtr(), data(),
- size());
- storage_.DeallocateIfAllocated();
- storage_.MemcpyFrom(other.storage_);
-
- other.storage_.SetInlinedSize(0);
- } else {
- storage_.Assign(IteratorValueAdapter<MoveIterator>(
- MoveIterator(other.storage_.GetInlinedData())),
- other.size());
- }
+ MoveAssignment(MoveAssignmentPolicy{}, std::move(other));
}
return *this;
@@ -497,7 +555,7 @@
//
// Replaces the contents of the inlined vector with `n` copies of `v`.
void assign(size_type n, const_reference v) {
- storage_.Assign(CopyValueAdapter(v), n);
+ storage_.Assign(CopyValueAdapter<A>(std::addressof(v)), n);
}
// Overload of `InlinedVector::assign(...)` that replaces the contents of the
@@ -511,10 +569,10 @@
//
// NOTE: this overload is for iterators that are "forward" category or better.
template <typename ForwardIterator,
- EnableIfAtLeastForwardIterator<ForwardIterator>* = nullptr>
+ EnableIfAtLeastForwardIterator<ForwardIterator> = 0>
void assign(ForwardIterator first, ForwardIterator last) {
- storage_.Assign(IteratorValueAdapter<ForwardIterator>(first),
- std::distance(first, last));
+ storage_.Assign(IteratorValueAdapter<A, ForwardIterator>(first),
+ static_cast<size_t>(std::distance(first, last)));
}
// Overload of `InlinedVector::assign(...)` to replace the contents of the
@@ -522,7 +580,7 @@
//
// NOTE: this overload is for iterators that are "input" category.
template <typename InputIterator,
- DisableIfAtLeastForwardIterator<InputIterator>* = nullptr>
+ DisableIfAtLeastForwardIterator<InputIterator> = 0>
void assign(InputIterator first, InputIterator last) {
size_type i = 0;
for (; i < size() && first != last; ++i, static_cast<void>(++first)) {
@@ -541,7 +599,7 @@
// is larger than `size()`, new elements are value-initialized.
void resize(size_type n) {
ABSL_HARDENING_ASSERT(n <= max_size());
- storage_.Resize(DefaultValueAdapter(), n);
+ storage_.Resize(DefaultValueAdapter<A>(), n);
}
// Overload of `InlinedVector::resize(...)` that resizes the inlined vector to
@@ -551,33 +609,49 @@
// is larger than `size()`, new elements are copied-constructed from `v`.
void resize(size_type n, const_reference v) {
ABSL_HARDENING_ASSERT(n <= max_size());
- storage_.Resize(CopyValueAdapter(v), n);
+ storage_.Resize(CopyValueAdapter<A>(std::addressof(v)), n);
}
// `InlinedVector::insert(...)`
//
// Inserts a copy of `v` at `pos`, returning an `iterator` to the newly
// inserted element.
- iterator insert(const_iterator pos, const_reference v) {
+ iterator insert(const_iterator pos,
+ const_reference v) ABSL_ATTRIBUTE_LIFETIME_BOUND {
return emplace(pos, v);
}
// Overload of `InlinedVector::insert(...)` that inserts `v` at `pos` using
// move semantics, returning an `iterator` to the newly inserted element.
- iterator insert(const_iterator pos, RValueReference v) {
+ iterator insert(const_iterator pos,
+ value_type&& v) ABSL_ATTRIBUTE_LIFETIME_BOUND {
return emplace(pos, std::move(v));
}
// Overload of `InlinedVector::insert(...)` that inserts `n` contiguous copies
// of `v` starting at `pos`, returning an `iterator` pointing to the first of
// the newly inserted elements.
- iterator insert(const_iterator pos, size_type n, const_reference v) {
+ iterator insert(const_iterator pos, size_type n,
+ const_reference v) ABSL_ATTRIBUTE_LIFETIME_BOUND {
ABSL_HARDENING_ASSERT(pos >= begin());
ABSL_HARDENING_ASSERT(pos <= end());
if (ABSL_PREDICT_TRUE(n != 0)) {
value_type dealias = v;
- return storage_.Insert(pos, CopyValueAdapter(dealias), n);
+ // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=102329#c2
+ // It appears that GCC thinks that since `pos` is a const pointer and may
+ // point to uninitialized memory at this point, a warning should be
+ // issued. But `pos` is actually only used to compute an array index to
+ // write to.
+#if !defined(__clang__) && defined(__GNUC__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
+#endif
+ return storage_.Insert(pos, CopyValueAdapter<A>(std::addressof(dealias)),
+ n);
+#if !defined(__clang__) && defined(__GNUC__)
+#pragma GCC diagnostic pop
+#endif
} else {
return const_cast<iterator>(pos);
}
@@ -586,7 +660,8 @@
// Overload of `InlinedVector::insert(...)` that inserts copies of the
// elements of `list` starting at `pos`, returning an `iterator` pointing to
// the first of the newly inserted elements.
- iterator insert(const_iterator pos, std::initializer_list<value_type> list) {
+ iterator insert(const_iterator pos, std::initializer_list<value_type> list)
+ ABSL_ATTRIBUTE_LIFETIME_BOUND {
return insert(pos, list.begin(), list.end());
}
@@ -596,15 +671,16 @@
//
// NOTE: this overload is for iterators that are "forward" category or better.
template <typename ForwardIterator,
- EnableIfAtLeastForwardIterator<ForwardIterator>* = nullptr>
+ EnableIfAtLeastForwardIterator<ForwardIterator> = 0>
iterator insert(const_iterator pos, ForwardIterator first,
- ForwardIterator last) {
+ ForwardIterator last) ABSL_ATTRIBUTE_LIFETIME_BOUND {
ABSL_HARDENING_ASSERT(pos >= begin());
ABSL_HARDENING_ASSERT(pos <= end());
if (ABSL_PREDICT_TRUE(first != last)) {
- return storage_.Insert(pos, IteratorValueAdapter<ForwardIterator>(first),
- std::distance(first, last));
+ return storage_.Insert(
+ pos, IteratorValueAdapter<A, ForwardIterator>(first),
+ static_cast<size_type>(std::distance(first, last)));
} else {
return const_cast<iterator>(pos);
}
@@ -616,12 +692,13 @@
//
// NOTE: this overload is for iterators that are "input" category.
template <typename InputIterator,
- DisableIfAtLeastForwardIterator<InputIterator>* = nullptr>
- iterator insert(const_iterator pos, InputIterator first, InputIterator last) {
+ DisableIfAtLeastForwardIterator<InputIterator> = 0>
+ iterator insert(const_iterator pos, InputIterator first,
+ InputIterator last) ABSL_ATTRIBUTE_LIFETIME_BOUND {
ABSL_HARDENING_ASSERT(pos >= begin());
ABSL_HARDENING_ASSERT(pos <= end());
- size_type index = std::distance(cbegin(), pos);
+ size_type index = static_cast<size_type>(std::distance(cbegin(), pos));
for (size_type i = index; first != last; ++i, static_cast<void>(++first)) {
insert(data() + i, *first);
}
@@ -634,15 +711,28 @@
// Constructs and inserts an element using `args...` in the inlined vector at
// `pos`, returning an `iterator` pointing to the newly emplaced element.
template <typename... Args>
- iterator emplace(const_iterator pos, Args&&... args) {
+ iterator emplace(const_iterator pos,
+ Args&&... args) ABSL_ATTRIBUTE_LIFETIME_BOUND {
ABSL_HARDENING_ASSERT(pos >= begin());
ABSL_HARDENING_ASSERT(pos <= end());
value_type dealias(std::forward<Args>(args)...);
+ // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=102329#c2
+ // It appears that GCC thinks that since `pos` is a const pointer and may
+ // point to uninitialized memory at this point, a warning should be
+ // issued. But `pos` is actually only used to compute an array index to
+ // write to.
+#if !defined(__clang__) && defined(__GNUC__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
+#endif
return storage_.Insert(pos,
- IteratorValueAdapter<MoveIterator>(
- MoveIterator(std::addressof(dealias))),
+ IteratorValueAdapter<A, MoveIterator<A>>(
+ MoveIterator<A>(std::addressof(dealias))),
1);
+#if !defined(__clang__) && defined(__GNUC__)
+#pragma GCC diagnostic pop
+#endif
}
// `InlinedVector::emplace_back(...)`
@@ -650,7 +740,7 @@
// Constructs and inserts an element using `args...` in the inlined vector at
// `end()`, returning a `reference` to the newly emplaced element.
template <typename... Args>
- reference emplace_back(Args&&... args) {
+ reference emplace_back(Args&&... args) ABSL_ATTRIBUTE_LIFETIME_BOUND {
return storage_.EmplaceBack(std::forward<Args>(args)...);
}
@@ -661,7 +751,7 @@
// Overload of `InlinedVector::push_back(...)` for inserting `v` at `end()`
// using move semantics.
- void push_back(RValueReference v) {
+ void push_back(value_type&& v) {
static_cast<void>(emplace_back(std::move(v)));
}
@@ -671,7 +761,7 @@
void pop_back() noexcept {
ABSL_HARDENING_ASSERT(!empty());
- AllocatorTraits::destroy(*storage_.GetAllocPtr(), data() + (size() - 1));
+ AllocatorTraits<A>::destroy(storage_.GetAllocator(), data() + (size() - 1));
storage_.SubtractSize(1);
}
@@ -680,20 +770,34 @@
// Erases the element at `pos`, returning an `iterator` pointing to where the
// erased element was located.
//
- // NOTE: may return `end()`, which is not dereferencable.
- iterator erase(const_iterator pos) {
+ // NOTE: may return `end()`, which is not dereferenceable.
+ iterator erase(const_iterator pos) ABSL_ATTRIBUTE_LIFETIME_BOUND {
ABSL_HARDENING_ASSERT(pos >= begin());
ABSL_HARDENING_ASSERT(pos < end());
+ // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=102329#c2
+ // It appears that GCC thinks that since `pos` is a const pointer and may
+ // point to uninitialized memory at this point, a warning should be
+ // issued. But `pos` is actually only used to compute an array index to
+ // write to.
+#if !defined(__clang__) && defined(__GNUC__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
+#pragma GCC diagnostic ignored "-Wuninitialized"
+#endif
return storage_.Erase(pos, pos + 1);
+#if !defined(__clang__) && defined(__GNUC__)
+#pragma GCC diagnostic pop
+#endif
}
// Overload of `InlinedVector::erase(...)` that erases every element in the
// range [`from`, `to`), returning an `iterator` pointing to where the first
// erased element was located.
//
- // NOTE: may return `end()`, which is not dereferencable.
- iterator erase(const_iterator from, const_iterator to) {
+ // NOTE: may return `end()`, which is not dereferenceable.
+ iterator erase(const_iterator from,
+ const_iterator to) ABSL_ATTRIBUTE_LIFETIME_BOUND {
ABSL_HARDENING_ASSERT(from >= begin());
ABSL_HARDENING_ASSERT(from <= to);
ABSL_HARDENING_ASSERT(to <= end());
@@ -710,8 +814,8 @@
// Destroys all elements in the inlined vector, setting the size to `0` and
// deallocating any held memory.
void clear() noexcept {
- inlined_vector_internal::DestroyElements(storage_.GetAllocPtr(), data(),
- size());
+ inlined_vector_internal::DestroyAdapter<A>::DestroyElements(
+ storage_.GetAllocator(), data(), size());
storage_.DeallocateIfAllocated();
storage_.SetInlinedSize(0);
@@ -724,15 +828,12 @@
// `InlinedVector::shrink_to_fit()`
//
- // Reduces memory usage by freeing unused memory. After being called, calls to
- // `capacity()` will be equal to `max(N, size())`.
+ // Attempts to reduce memory usage by moving elements to (or keeping elements
+ // in) the smallest available buffer sufficient for containing `size()`
+ // elements.
//
- // If `size() <= N` and the inlined vector contains allocated memory, the
- // elements will all be moved to the inlined space and the allocated memory
- // will be deallocated.
- //
- // If `size() > N` and `size() < capacity()`, the elements will be moved to a
- // smaller allocation.
+ // If `size()` is sufficiently small, the elements will be moved into (or kept
+ // in) the inlined space.
void shrink_to_fit() {
if (storage_.GetIsAllocated()) {
storage_.ShrinkToFit();
@@ -752,6 +853,73 @@
template <typename H, typename TheT, size_t TheN, typename TheA>
friend H AbslHashValue(H h, const absl::InlinedVector<TheT, TheN, TheA>& a);
+ void MoveAssignment(MemcpyPolicy, InlinedVector&& other) {
+ // Assumption check: we shouldn't be told to use memcpy to implement move
+ // assignment unless we have trivially destructible elements and an
+ // allocator that does nothing fancy.
+ static_assert(absl::is_trivially_destructible<value_type>::value, "");
+ static_assert(std::is_same<A, std::allocator<value_type>>::value, "");
+
+ // Throw away our existing heap allocation, if any. There is no need to
+ // destroy the existing elements one by one because we know they are
+ // trivially destructible.
+ storage_.DeallocateIfAllocated();
+
+ // Adopt the other vector's inline elements or heap allocation.
+ storage_.MemcpyFrom(other.storage_);
+ other.storage_.SetInlinedSize(0);
+ }
+
+ // Destroy our existing elements, if any, and adopt the heap-allocated
+ // elements of the other vector.
+ //
+ // REQUIRES: other.storage_.GetIsAllocated()
+ void DestroyExistingAndAdopt(InlinedVector&& other) {
+ ABSL_HARDENING_ASSERT(other.storage_.GetIsAllocated());
+
+ inlined_vector_internal::DestroyAdapter<A>::DestroyElements(
+ storage_.GetAllocator(), data(), size());
+ storage_.DeallocateIfAllocated();
+
+ storage_.MemcpyFrom(other.storage_);
+ other.storage_.SetInlinedSize(0);
+ }
+
+ void MoveAssignment(ElementwiseAssignPolicy, InlinedVector&& other) {
+ // Fast path: if the other vector is on the heap then we don't worry about
+ // actually move-assigning each element. Instead we only throw away our own
+ // existing elements and adopt the heap allocation of the other vector.
+ if (other.storage_.GetIsAllocated()) {
+ DestroyExistingAndAdopt(std::move(other));
+ return;
+ }
+
+ storage_.Assign(IteratorValueAdapter<A, MoveIterator<A>>(
+ MoveIterator<A>(other.storage_.GetInlinedData())),
+ other.size());
+ }
+
+ void MoveAssignment(ElementwiseConstructPolicy, InlinedVector&& other) {
+ // Fast path: if the other vector is on the heap then we don't worry about
+ // actually move-assigning each element. Instead we only throw away our own
+ // existing elements and adopt the heap allocation of the other vector.
+ if (other.storage_.GetIsAllocated()) {
+ DestroyExistingAndAdopt(std::move(other));
+ return;
+ }
+
+ inlined_vector_internal::DestroyAdapter<A>::DestroyElements(
+ storage_.GetAllocator(), data(), size());
+ storage_.DeallocateIfAllocated();
+
+ IteratorValueAdapter<A, MoveIterator<A>> other_values(
+ MoveIterator<A>(other.storage_.GetInlinedData()));
+ inlined_vector_internal::ConstructElements<A>(
+ storage_.GetAllocator(), storage_.GetInlinedData(), other_values,
+ other.storage_.GetSize());
+ storage_.SetInlinedSize(other.storage_.GetSize());
+ }
+
Storage storage_;
};
@@ -776,7 +944,7 @@
const absl::InlinedVector<T, N, A>& b) {
auto a_data = a.data();
auto b_data = b.data();
- return absl::equal(a_data, a_data + a.size(), b_data, b_data + b.size());
+ return std::equal(a_data, a_data + a.size(), b_data, b_data + b.size());
}
// `operator!=(...)`
diff --git a/absl/container/inlined_vector_benchmark.cc b/absl/container/inlined_vector_benchmark.cc
index e256fad..5a04277 100644
--- a/absl/container/inlined_vector_benchmark.cc
+++ b/absl/container/inlined_vector_benchmark.cc
@@ -16,11 +16,11 @@
#include <string>
#include <vector>
-#include "benchmark/benchmark.h"
#include "absl/base/internal/raw_logging.h"
#include "absl/base/macros.h"
#include "absl/container/inlined_vector.h"
#include "absl/strings/str_cat.h"
+#include "benchmark/benchmark.h"
namespace {
@@ -66,7 +66,7 @@
BENCHMARK(BM_StdVectorFill)->Range(1, 256);
// The purpose of the next two benchmarks is to verify that
-// absl::InlinedVector is efficient when moving is more efficent than
+// absl::InlinedVector is efficient when moving is more efficient than
// copying. To do so, we use strings that are larger than the short
// string optimization.
bool StringRepresentedInline(std::string s) {
diff --git a/absl/container/inlined_vector_test.cc b/absl/container/inlined_vector_test.cc
index 98aff33..6954262 100644
--- a/absl/container/inlined_vector_test.cc
+++ b/absl/container/inlined_vector_test.cc
@@ -15,25 +15,28 @@
#include "absl/container/inlined_vector.h"
#include <algorithm>
+#include <cstddef>
#include <forward_list>
+#include <iterator>
#include <list>
#include <memory>
#include <scoped_allocator>
#include <sstream>
#include <stdexcept>
#include <string>
+#include <utility>
#include <vector>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/base/attributes.h"
#include "absl/base/internal/exception_testing.h"
-#include "absl/base/internal/raw_logging.h"
#include "absl/base/macros.h"
#include "absl/base/options.h"
-#include "absl/container/internal/counting_allocator.h"
+#include "absl/container/internal/test_allocator.h"
#include "absl/container/internal/test_instance_tracker.h"
#include "absl/hash/hash_testing.h"
+#include "absl/log/check.h"
#include "absl/memory/memory.h"
#include "absl/strings/str_cat.h"
@@ -49,14 +52,13 @@
using testing::ElementsAreArray;
using testing::Eq;
using testing::Gt;
+using testing::Pointee;
+using testing::Pointwise;
using testing::PrintToString;
+using testing::SizeIs;
using IntVec = absl::InlinedVector<int, 8>;
-MATCHER_P(SizeIs, n, "") {
- return testing::ExplainMatchResult(n, arg.size(), result_listener);
-}
-
MATCHER_P(CapacityIs, n, "") {
return testing::ExplainMatchResult(n, arg.capacity(), result_listener);
}
@@ -101,13 +103,13 @@
}
void Ref() const {
- ABSL_RAW_CHECK(count_ != nullptr, "");
+ CHECK_NE(count_, nullptr);
++(*count_);
}
void Unref() const {
--(*count_);
- ABSL_RAW_CHECK(*count_ >= 0, "");
+ CHECK_GE(*count_, 0);
}
int value_;
@@ -126,20 +128,20 @@
// Append 0..len-1 to *v
template <typename Container>
-static void Fill(Container* v, int len, int offset = 0) {
- for (int i = 0; i < len; i++) {
- v->push_back(i + offset);
+static void Fill(Container* v, size_t len, int offset = 0) {
+ for (size_t i = 0; i < len; i++) {
+ v->push_back(static_cast<int>(i) + offset);
}
}
-static IntVec Fill(int len, int offset = 0) {
+static IntVec Fill(size_t len, int offset = 0) {
IntVec v;
Fill(&v, len, offset);
return v;
}
TEST(IntVec, SimpleOps) {
- for (int len = 0; len < 20; len++) {
+ for (size_t len = 0; len < 20; len++) {
IntVec v;
const IntVec& cv = v; // const alias
@@ -147,42 +149,42 @@
EXPECT_EQ(len, v.size());
EXPECT_LE(len, v.capacity());
- for (int i = 0; i < len; i++) {
- EXPECT_EQ(i, v[i]);
- EXPECT_EQ(i, v.at(i));
+ for (size_t i = 0; i < len; i++) {
+ EXPECT_EQ(static_cast<int>(i), v[i]);
+ EXPECT_EQ(static_cast<int>(i), v.at(i));
}
EXPECT_EQ(v.begin(), v.data());
EXPECT_EQ(cv.begin(), cv.data());
- int counter = 0;
+ size_t counter = 0;
for (IntVec::iterator iter = v.begin(); iter != v.end(); ++iter) {
- EXPECT_EQ(counter, *iter);
+ EXPECT_EQ(static_cast<int>(counter), *iter);
counter++;
}
EXPECT_EQ(counter, len);
counter = 0;
for (IntVec::const_iterator iter = v.begin(); iter != v.end(); ++iter) {
- EXPECT_EQ(counter, *iter);
+ EXPECT_EQ(static_cast<int>(counter), *iter);
counter++;
}
EXPECT_EQ(counter, len);
counter = 0;
for (IntVec::const_iterator iter = v.cbegin(); iter != v.cend(); ++iter) {
- EXPECT_EQ(counter, *iter);
+ EXPECT_EQ(static_cast<int>(counter), *iter);
counter++;
}
EXPECT_EQ(counter, len);
if (len > 0) {
EXPECT_EQ(0, v.front());
- EXPECT_EQ(len - 1, v.back());
+ EXPECT_EQ(static_cast<int>(len - 1), v.back());
v.pop_back();
EXPECT_EQ(len - 1, v.size());
- for (int i = 0; i < v.size(); ++i) {
- EXPECT_EQ(i, v[i]);
- EXPECT_EQ(i, v.at(i));
+ for (size_t i = 0; i < v.size(); ++i) {
+ EXPECT_EQ(static_cast<int>(i), v[i]);
+ EXPECT_EQ(static_cast<int>(i), v.at(i));
}
}
}
@@ -191,7 +193,7 @@
TEST(IntVec, PopBackNoOverflow) {
IntVec v = {1};
v.pop_back();
- EXPECT_EQ(v.size(), 0);
+ EXPECT_EQ(v.size(), 0u);
}
TEST(IntVec, AtThrows) {
@@ -202,47 +204,47 @@
}
TEST(IntVec, ReverseIterator) {
- for (int len = 0; len < 20; len++) {
+ for (size_t len = 0; len < 20; len++) {
IntVec v;
Fill(&v, len);
- int counter = len;
+ size_t counter = len;
for (IntVec::reverse_iterator iter = v.rbegin(); iter != v.rend(); ++iter) {
counter--;
- EXPECT_EQ(counter, *iter);
+ EXPECT_EQ(static_cast<int>(counter), *iter);
}
- EXPECT_EQ(counter, 0);
+ EXPECT_EQ(counter, 0u);
counter = len;
for (IntVec::const_reverse_iterator iter = v.rbegin(); iter != v.rend();
++iter) {
counter--;
- EXPECT_EQ(counter, *iter);
+ EXPECT_EQ(static_cast<int>(counter), *iter);
}
- EXPECT_EQ(counter, 0);
+ EXPECT_EQ(counter, 0u);
counter = len;
for (IntVec::const_reverse_iterator iter = v.crbegin(); iter != v.crend();
++iter) {
counter--;
- EXPECT_EQ(counter, *iter);
+ EXPECT_EQ(static_cast<int>(counter), *iter);
}
- EXPECT_EQ(counter, 0);
+ EXPECT_EQ(counter, 0u);
}
}
TEST(IntVec, Erase) {
- for (int len = 1; len < 20; len++) {
- for (int i = 0; i < len; ++i) {
+ for (size_t len = 1; len < 20; len++) {
+ for (size_t i = 0; i < len; ++i) {
IntVec v;
Fill(&v, len);
v.erase(v.begin() + i);
EXPECT_EQ(len - 1, v.size());
- for (int j = 0; j < i; ++j) {
- EXPECT_EQ(j, v[j]);
+ for (size_t j = 0; j < i; ++j) {
+ EXPECT_EQ(static_cast<int>(j), v[j]);
}
- for (int j = i; j < len - 1; ++j) {
- EXPECT_EQ(j + 1, v[j]);
+ for (size_t j = i; j < len - 1; ++j) {
+ EXPECT_EQ(static_cast<int>(j + 1), v[j]);
}
}
}
@@ -254,51 +256,175 @@
EXPECT_EQ(v[9], 9);
#if !defined(NDEBUG) || ABSL_OPTION_HARDENED
EXPECT_DEATH_IF_SUPPORTED(v[10], "");
- EXPECT_DEATH_IF_SUPPORTED(v[-1], "");
+ EXPECT_DEATH_IF_SUPPORTED(v[static_cast<size_t>(-1)], "");
+ EXPECT_DEATH_IF_SUPPORTED(v.resize(v.max_size() + 1), "");
#endif
}
+// Move construction of a container of unique pointers should work fine, with no
+// leaks, despite the fact that unique pointers are trivially relocatable but
+// not trivially destructible.
+TEST(UniquePtr, MoveConstruct) {
+ for (size_t size = 0; size < 16; ++size) {
+ SCOPED_TRACE(size);
+
+ absl::InlinedVector<std::unique_ptr<size_t>, 2> a;
+ for (size_t i = 0; i < size; ++i) {
+ a.push_back(std::make_unique<size_t>(i));
+ }
+
+ absl::InlinedVector<std::unique_ptr<size_t>, 2> b(std::move(a));
+
+ ASSERT_THAT(b, SizeIs(size));
+ for (size_t i = 0; i < size; ++i) {
+ ASSERT_THAT(b[i], Pointee(i));
+ }
+ }
+}
+
+// Move assignment of a container of unique pointers should work fine, with no
+// leaks, despite the fact that unique pointers are trivially relocatable but
+// not trivially destructible.
+TEST(UniquePtr, MoveAssign) {
+ for (size_t size = 0; size < 16; ++size) {
+ SCOPED_TRACE(size);
+
+ absl::InlinedVector<std::unique_ptr<size_t>, 2> a;
+ for (size_t i = 0; i < size; ++i) {
+ a.push_back(std::make_unique<size_t>(i));
+ }
+
+ absl::InlinedVector<std::unique_ptr<size_t>, 2> b;
+ b = std::move(a);
+
+ ASSERT_THAT(b, SizeIs(size));
+ for (size_t i = 0; i < size; ++i) {
+ ASSERT_THAT(b[i], Pointee(i));
+ }
+ }
+}
+
+// Swapping containers of unique pointers should work fine, with no
+// leaks, despite the fact that unique pointers are trivially relocatable but
+// not trivially destructible.
+// TODO(absl-team): Using unique_ptr here is technically correct, but
+// a trivially relocatable struct would be less semantically confusing.
+TEST(UniquePtr, Swap) {
+ for (size_t size1 = 0; size1 < 5; ++size1) {
+ for (size_t size2 = 0; size2 < 5; ++size2) {
+ absl::InlinedVector<std::unique_ptr<size_t>, 2> a;
+ absl::InlinedVector<std::unique_ptr<size_t>, 2> b;
+ for (size_t i = 0; i < size1; ++i) {
+ a.push_back(std::make_unique<size_t>(i + 10));
+ }
+ for (size_t i = 0; i < size2; ++i) {
+ b.push_back(std::make_unique<size_t>(i + 20));
+ }
+ a.swap(b);
+ ASSERT_THAT(a, SizeIs(size2));
+ ASSERT_THAT(b, SizeIs(size1));
+ for (size_t i = 0; i < a.size(); ++i) {
+ ASSERT_THAT(a[i], Pointee(i + 20));
+ }
+ for (size_t i = 0; i < b.size(); ++i) {
+ ASSERT_THAT(b[i], Pointee(i + 10));
+ }
+ }
+ }
+}
+
+// Erasing from a container of unique pointers should work fine, with no
+// leaks, despite the fact that unique pointers are trivially relocatable but
+// not trivially destructible.
+// TODO(absl-team): Using unique_ptr here is technically correct, but
+// a trivially relocatable struct would be less semantically confusing.
+TEST(UniquePtr, EraseSingle) {
+ for (size_t size = 4; size < 16; ++size) {
+ absl::InlinedVector<std::unique_ptr<size_t>, 8> a;
+ for (size_t i = 0; i < size; ++i) {
+ a.push_back(std::make_unique<size_t>(i));
+ }
+ a.erase(a.begin());
+ ASSERT_THAT(a, SizeIs(size - 1));
+ for (size_t i = 0; i < size - 1; ++i) {
+ ASSERT_THAT(a[i], Pointee(i + 1));
+ }
+ a.erase(a.begin() + 2);
+ ASSERT_THAT(a, SizeIs(size - 2));
+ ASSERT_THAT(a[0], Pointee(1));
+ ASSERT_THAT(a[1], Pointee(2));
+ for (size_t i = 2; i < size - 2; ++i) {
+ ASSERT_THAT(a[i], Pointee(i + 2));
+ }
+ }
+}
+
+// Erasing from a container of unique pointers should work fine, with no
+// leaks, despite the fact that unique pointers are trivially relocatable but
+// not trivially destructible.
+// TODO(absl-team): Using unique_ptr here is technically correct, but
+// a trivially relocatable struct would be less semantically confusing.
+TEST(UniquePtr, EraseMulti) {
+ for (size_t size = 5; size < 16; ++size) {
+ absl::InlinedVector<std::unique_ptr<size_t>, 8> a;
+ for (size_t i = 0; i < size; ++i) {
+ a.push_back(std::make_unique<size_t>(i));
+ }
+ a.erase(a.begin(), a.begin() + 2);
+ ASSERT_THAT(a, SizeIs(size - 2));
+ for (size_t i = 0; i < size - 2; ++i) {
+ ASSERT_THAT(a[i], Pointee(i + 2));
+ }
+ a.erase(a.begin() + 1, a.begin() + 3);
+ ASSERT_THAT(a, SizeIs(size - 4));
+ ASSERT_THAT(a[0], Pointee(2));
+ for (size_t i = 1; i < size - 4; ++i) {
+ ASSERT_THAT(a[i], Pointee(i + 4));
+ }
+ }
+}
+
// At the end of this test loop, the elements between [erase_begin, erase_end)
// should have reference counts == 0, and all others elements should have
// reference counts == 1.
TEST(RefCountedVec, EraseBeginEnd) {
- for (int len = 1; len < 20; ++len) {
- for (int erase_begin = 0; erase_begin < len; ++erase_begin) {
- for (int erase_end = erase_begin; erase_end <= len; ++erase_end) {
+ for (size_t len = 1; len < 20; ++len) {
+ for (size_t erase_begin = 0; erase_begin < len; ++erase_begin) {
+ for (size_t erase_end = erase_begin; erase_end <= len; ++erase_end) {
std::vector<int> counts(len, 0);
RefCountedVec v;
- for (int i = 0; i < len; ++i) {
- v.push_back(RefCounted(i, &counts[i]));
+ for (size_t i = 0; i < len; ++i) {
+ v.push_back(RefCounted(static_cast<int>(i), &counts[i]));
}
- int erase_len = erase_end - erase_begin;
+ size_t erase_len = erase_end - erase_begin;
v.erase(v.begin() + erase_begin, v.begin() + erase_end);
EXPECT_EQ(len - erase_len, v.size());
// Check the elements before the first element erased.
- for (int i = 0; i < erase_begin; ++i) {
- EXPECT_EQ(i, v[i].value_);
+ for (size_t i = 0; i < erase_begin; ++i) {
+ EXPECT_EQ(static_cast<int>(i), v[i].value_);
}
// Check the elements after the first element erased.
- for (int i = erase_begin; i < v.size(); ++i) {
- EXPECT_EQ(i + erase_len, v[i].value_);
+ for (size_t i = erase_begin; i < v.size(); ++i) {
+ EXPECT_EQ(static_cast<int>(i + erase_len), v[i].value_);
}
// Check that the elements at the beginning are preserved.
- for (int i = 0; i < erase_begin; ++i) {
+ for (size_t i = 0; i < erase_begin; ++i) {
EXPECT_EQ(1, counts[i]);
}
// Check that the erased elements are destroyed
- for (int i = erase_begin; i < erase_end; ++i) {
+ for (size_t i = erase_begin; i < erase_end; ++i) {
EXPECT_EQ(0, counts[i]);
}
// Check that the elements at the end are preserved.
- for (int i = erase_end; i < len; ++i) {
+ for (size_t i = erase_end; i < len; ++i) {
EXPECT_EQ(1, counts[i]);
}
}
@@ -377,21 +503,21 @@
absl::InlinedVector<std::pair<std::string, int>, 1> v;
v.shrink_to_fit();
- EXPECT_EQ(v.capacity(), 1);
+ EXPECT_EQ(v.capacity(), 1u);
v.emplace_back("answer", 42);
v.shrink_to_fit();
- EXPECT_EQ(v.capacity(), 1);
+ EXPECT_EQ(v.capacity(), 1u);
v.emplace_back("taxicab", 1729);
- EXPECT_GE(v.capacity(), 2);
+ EXPECT_GE(v.capacity(), 2u);
v.shrink_to_fit();
- EXPECT_EQ(v.capacity(), 2);
+ EXPECT_EQ(v.capacity(), 2u);
v.reserve(100);
- EXPECT_GE(v.capacity(), 100);
+ EXPECT_GE(v.capacity(), 100u);
v.shrink_to_fit();
- EXPECT_EQ(v.capacity(), 2);
+ EXPECT_EQ(v.capacity(), 2u);
}
TEST(InlinedVectorTest, ShrinkToFitEdgeCases) {
@@ -399,10 +525,10 @@
absl::InlinedVector<std::pair<std::string, int>, 1> v;
v.emplace_back("answer", 42);
v.emplace_back("taxicab", 1729);
- EXPECT_GE(v.capacity(), 2);
+ EXPECT_GE(v.capacity(), 2u);
v.pop_back();
v.shrink_to_fit();
- EXPECT_EQ(v.capacity(), 1);
+ EXPECT_EQ(v.capacity(), 1u);
EXPECT_EQ(v[0].first, "answer");
EXPECT_EQ(v[0].second, 42);
}
@@ -411,34 +537,34 @@
absl::InlinedVector<std::string, 2> v(100);
v.resize(0);
v.shrink_to_fit();
- EXPECT_EQ(v.capacity(), 2); // inlined capacity
+ EXPECT_EQ(v.capacity(), 2u); // inlined capacity
}
{
absl::InlinedVector<std::string, 2> v(100);
v.resize(1);
v.shrink_to_fit();
- EXPECT_EQ(v.capacity(), 2); // inlined capacity
+ EXPECT_EQ(v.capacity(), 2u); // inlined capacity
}
{
absl::InlinedVector<std::string, 2> v(100);
v.resize(2);
v.shrink_to_fit();
- EXPECT_EQ(v.capacity(), 2);
+ EXPECT_EQ(v.capacity(), 2u);
}
{
absl::InlinedVector<std::string, 2> v(100);
v.resize(3);
v.shrink_to_fit();
- EXPECT_EQ(v.capacity(), 3);
+ EXPECT_EQ(v.capacity(), 3u);
}
}
TEST(IntVec, Insert) {
- for (int len = 0; len < 20; len++) {
- for (int pos = 0; pos <= len; pos++) {
+ for (size_t len = 0; len < 20; len++) {
+ for (ptrdiff_t pos = 0; pos <= static_cast<ptrdiff_t>(len); pos++) {
{
// Single element
std::vector<int> std_v;
@@ -526,16 +652,16 @@
TEST(RefCountedVec, InsertConstructorDestructor) {
// Make sure the proper construction/destruction happen during insert
// operations.
- for (int len = 0; len < 20; len++) {
+ for (size_t len = 0; len < 20; len++) {
SCOPED_TRACE(len);
- for (int pos = 0; pos <= len; pos++) {
+ for (size_t pos = 0; pos <= len; pos++) {
SCOPED_TRACE(pos);
std::vector<int> counts(len, 0);
int inserted_count = 0;
RefCountedVec v;
- for (int i = 0; i < len; ++i) {
+ for (size_t i = 0; i < len; ++i) {
SCOPED_TRACE(i);
- v.push_back(RefCounted(i, &counts[i]));
+ v.push_back(RefCounted(static_cast<int>(i), &counts[i]));
}
EXPECT_THAT(counts, Each(Eq(1)));
@@ -552,20 +678,20 @@
}
TEST(IntVec, Resize) {
- for (int len = 0; len < 20; len++) {
+ for (size_t len = 0; len < 20; len++) {
IntVec v;
Fill(&v, len);
// Try resizing up and down by k elements
static const int kResizeElem = 1000000;
- for (int k = 0; k < 10; k++) {
+ for (size_t k = 0; k < 10; k++) {
// Enlarging resize
v.resize(len + k, kResizeElem);
EXPECT_EQ(len + k, v.size());
EXPECT_LE(len + k, v.capacity());
- for (int i = 0; i < len + k; i++) {
+ for (size_t i = 0; i < len + k; i++) {
if (i < len) {
- EXPECT_EQ(i, v[i]);
+ EXPECT_EQ(static_cast<int>(i), v[i]);
} else {
EXPECT_EQ(kResizeElem, v[i]);
}
@@ -575,26 +701,26 @@
v.resize(len, kResizeElem);
EXPECT_EQ(len, v.size());
EXPECT_LE(len, v.capacity());
- for (int i = 0; i < len; i++) {
- EXPECT_EQ(i, v[i]);
+ for (size_t i = 0; i < len; i++) {
+ EXPECT_EQ(static_cast<int>(i), v[i]);
}
}
}
}
TEST(IntVec, InitWithLength) {
- for (int len = 0; len < 20; len++) {
+ for (size_t len = 0; len < 20; len++) {
IntVec v(len, 7);
EXPECT_EQ(len, v.size());
EXPECT_LE(len, v.capacity());
- for (int i = 0; i < len; i++) {
+ for (size_t i = 0; i < len; i++) {
EXPECT_EQ(7, v[i]);
}
}
}
TEST(IntVec, CopyConstructorAndAssignment) {
- for (int len = 0; len < 20; len++) {
+ for (size_t len = 0; len < 20; len++) {
IntVec v;
Fill(&v, len);
EXPECT_EQ(len, v.size());
@@ -603,7 +729,7 @@
IntVec v2(v);
EXPECT_TRUE(v == v2) << PrintToString(v) << PrintToString(v2);
- for (int start_len = 0; start_len < 20; start_len++) {
+ for (size_t start_len = 0; start_len < 20; start_len++) {
IntVec v3;
Fill(&v3, start_len, 99); // Add dummy elements that should go away
v3 = v;
@@ -613,7 +739,7 @@
}
TEST(IntVec, AliasingCopyAssignment) {
- for (int len = 0; len < 20; ++len) {
+ for (size_t len = 0; len < 20; ++len) {
IntVec original;
Fill(&original, len);
IntVec dup = original;
@@ -623,9 +749,9 @@
}
TEST(IntVec, MoveConstructorAndAssignment) {
- for (int len = 0; len < 20; len++) {
+ for (size_t len = 0; len < 20; len++) {
IntVec v_in;
- const int inlined_capacity = v_in.capacity();
+ const size_t inlined_capacity = v_in.capacity();
Fill(&v_in, len);
EXPECT_EQ(len, v_in.size());
EXPECT_LE(len, v_in.capacity());
@@ -642,7 +768,7 @@
EXPECT_FALSE(v_out.data() == old_data);
}
}
- for (int start_len = 0; start_len < 20; start_len++) {
+ for (size_t start_len = 0; start_len < 20; start_len++) {
IntVec v_out;
Fill(&v_out, start_len, 99); // Add dummy elements that should go away
IntVec v_temp(v_in);
@@ -681,10 +807,10 @@
};
TEST(AliasingTest, Emplace) {
- for (int i = 2; i < 20; ++i) {
+ for (size_t i = 2; i < 20; ++i) {
absl::InlinedVector<NotTriviallyDestructible, 10> vec;
- for (int j = 0; j < i; ++j) {
- vec.push_back(NotTriviallyDestructible(j));
+ for (size_t j = 0; j < i; ++j) {
+ vec.push_back(NotTriviallyDestructible(static_cast<int>(j)));
}
vec.emplace(vec.begin(), vec[0]);
EXPECT_EQ(vec[0], vec[1]);
@@ -696,12 +822,12 @@
}
TEST(AliasingTest, InsertWithCount) {
- for (int i = 1; i < 20; ++i) {
+ for (size_t i = 1; i < 20; ++i) {
absl::InlinedVector<NotTriviallyDestructible, 10> vec;
- for (int j = 0; j < i; ++j) {
- vec.push_back(NotTriviallyDestructible(j));
+ for (size_t j = 0; j < i; ++j) {
+ vec.push_back(NotTriviallyDestructible(static_cast<int>(j)));
}
- for (int n = 0; n < 5; ++n) {
+ for (size_t n = 0; n < 5; ++n) {
// We use back where we can because it's guaranteed to become invalidated
vec.insert(vec.begin(), n, vec.back());
auto b = vec.begin();
@@ -737,7 +863,9 @@
// The union should be absorbing some of the allocation bookkeeping overhead
// in the larger vectors, leaving only the size_ field as overhead.
- struct T { void* val; };
+ struct T {
+ void* val;
+ };
size_t expected_overhead = sizeof(T);
EXPECT_EQ((2 * expected_overhead),
@@ -759,22 +887,22 @@
}
TEST(IntVec, Clear) {
- for (int len = 0; len < 20; len++) {
+ for (size_t len = 0; len < 20; len++) {
SCOPED_TRACE(len);
IntVec v;
Fill(&v, len);
v.clear();
- EXPECT_EQ(0, v.size());
+ EXPECT_EQ(0u, v.size());
EXPECT_EQ(v.begin(), v.end());
}
}
TEST(IntVec, Reserve) {
- for (int len = 0; len < 20; len++) {
+ for (size_t len = 0; len < 20; len++) {
IntVec v;
Fill(&v, len);
- for (int newlen = 0; newlen < 100; newlen++) {
+ for (size_t newlen = 0; newlen < 100; newlen++) {
const int* start_rep = v.data();
v.reserve(newlen);
const int* final_rep = v.data();
@@ -841,9 +969,9 @@
}
TEST(IntVec, Swap) {
- for (int l1 = 0; l1 < 20; l1++) {
+ for (size_t l1 = 0; l1 < 20; l1++) {
SCOPED_TRACE(l1);
- for (int l2 = 0; l2 < 20; l2++) {
+ for (size_t l2 = 0; l2 < 20; l2++) {
SCOPED_TRACE(l2);
IntVec a = Fill(l1, 0);
IntVec b = Fill(l2, 100);
@@ -853,13 +981,13 @@
}
EXPECT_EQ(l1, b.size());
EXPECT_EQ(l2, a.size());
- for (int i = 0; i < l1; i++) {
+ for (size_t i = 0; i < l1; i++) {
SCOPED_TRACE(i);
- EXPECT_EQ(i, b[i]);
+ EXPECT_EQ(static_cast<int>(i), b[i]);
}
- for (int i = 0; i < l2; i++) {
+ for (size_t i = 0; i < l2; i++) {
SCOPED_TRACE(i);
- EXPECT_EQ(100 + i, a[i]);
+ EXPECT_EQ(100 + static_cast<int>(i), a[i]);
}
}
}
@@ -868,46 +996,48 @@
TYPED_TEST_P(InstanceTest, Swap) {
using Instance = TypeParam;
using InstanceVec = absl::InlinedVector<Instance, 8>;
- for (int l1 = 0; l1 < 20; l1++) {
+ for (size_t l1 = 0; l1 < 20; l1++) {
SCOPED_TRACE(l1);
- for (int l2 = 0; l2 < 20; l2++) {
+ for (size_t l2 = 0; l2 < 20; l2++) {
SCOPED_TRACE(l2);
InstanceTracker tracker;
InstanceVec a, b;
const size_t inlined_capacity = a.capacity();
auto min_len = std::min(l1, l2);
auto max_len = std::max(l1, l2);
- for (int i = 0; i < l1; i++) a.push_back(Instance(i));
- for (int i = 0; i < l2; i++) b.push_back(Instance(100 + i));
- EXPECT_EQ(tracker.instances(), l1 + l2);
+ for (size_t i = 0; i < l1; i++)
+ a.push_back(Instance(static_cast<int>(i)));
+ for (size_t i = 0; i < l2; i++)
+ b.push_back(Instance(100 + static_cast<int>(i)));
+ EXPECT_EQ(tracker.instances(), static_cast<int>(l1 + l2));
tracker.ResetCopiesMovesSwaps();
{
using std::swap;
swap(a, b);
}
- EXPECT_EQ(tracker.instances(), l1 + l2);
+ EXPECT_EQ(tracker.instances(), static_cast<int>(l1 + l2));
if (a.size() > inlined_capacity && b.size() > inlined_capacity) {
EXPECT_EQ(tracker.swaps(), 0); // Allocations are swapped.
EXPECT_EQ(tracker.moves(), 0);
} else if (a.size() <= inlined_capacity && b.size() <= inlined_capacity) {
- EXPECT_EQ(tracker.swaps(), min_len);
+ EXPECT_EQ(tracker.swaps(), static_cast<int>(min_len));
EXPECT_EQ((tracker.moves() ? tracker.moves() : tracker.copies()),
- max_len - min_len);
+ static_cast<int>(max_len - min_len));
} else {
// One is allocated and the other isn't. The allocation is transferred
// without copying elements, and the inlined instances are copied/moved.
EXPECT_EQ(tracker.swaps(), 0);
EXPECT_EQ((tracker.moves() ? tracker.moves() : tracker.copies()),
- min_len);
+ static_cast<int>(min_len));
}
EXPECT_EQ(l1, b.size());
EXPECT_EQ(l2, a.size());
- for (int i = 0; i < l1; i++) {
- EXPECT_EQ(i, b[i].value());
+ for (size_t i = 0; i < l1; i++) {
+ EXPECT_EQ(static_cast<int>(i), b[i].value());
}
- for (int i = 0; i < l2; i++) {
- EXPECT_EQ(100 + i, a[i].value());
+ for (size_t i = 0; i < l2; i++) {
+ EXPECT_EQ(100 + static_cast<int>(i), a[i].value());
}
}
}
@@ -936,9 +1066,9 @@
a.clear();
b.clear();
- for (int i = 0; i < 100; i++) {
- a.push_back(i);
- b.push_back(i);
+ for (size_t i = 0; i < 100; i++) {
+ a.push_back(static_cast<int>(i));
+ b.push_back(static_cast<int>(i));
EXPECT_TRUE(a == b);
EXPECT_FALSE(a != b);
@@ -977,26 +1107,26 @@
using Instance = TypeParam;
using InstanceVec = absl::InlinedVector<Instance, 8>;
InstanceTracker tracker;
- for (int len = 0; len < 20; len++) {
+ for (size_t len = 0; len < 20; len++) {
SCOPED_TRACE(len);
tracker.ResetCopiesMovesSwaps();
InstanceVec v;
const size_t inlined_capacity = v.capacity();
- for (int i = 0; i < len; i++) {
- v.push_back(Instance(i));
+ for (size_t i = 0; i < len; i++) {
+ v.push_back(Instance(static_cast<int>(i)));
}
- EXPECT_EQ(tracker.instances(), len);
+ EXPECT_EQ(tracker.instances(), static_cast<int>(len));
EXPECT_GE(tracker.copies() + tracker.moves(),
- len); // More due to reallocation.
+ static_cast<int>(len)); // More due to reallocation.
tracker.ResetCopiesMovesSwaps();
// Enlarging resize() must construct some objects
tracker.ResetCopiesMovesSwaps();
v.resize(len + 10, Instance(100));
- EXPECT_EQ(tracker.instances(), len + 10);
+ EXPECT_EQ(tracker.instances(), static_cast<int>(len) + 10);
if (len <= inlined_capacity && len + 10 > inlined_capacity) {
- EXPECT_EQ(tracker.copies() + tracker.moves(), 10 + len);
+ EXPECT_EQ(tracker.copies() + tracker.moves(), 10 + static_cast<int>(len));
} else {
// Only specify a minimum number of copies + moves. We don't want to
// depend on the reallocation policy here.
@@ -1007,29 +1137,30 @@
// Shrinking resize() must destroy some objects
tracker.ResetCopiesMovesSwaps();
v.resize(len, Instance(100));
- EXPECT_EQ(tracker.instances(), len);
+ EXPECT_EQ(tracker.instances(), static_cast<int>(len));
EXPECT_EQ(tracker.copies(), 0);
EXPECT_EQ(tracker.moves(), 0);
// reserve() must not increase the number of initialized objects
SCOPED_TRACE("reserve");
v.reserve(len + 1000);
- EXPECT_EQ(tracker.instances(), len);
- EXPECT_EQ(tracker.copies() + tracker.moves(), len);
+ EXPECT_EQ(tracker.instances(), static_cast<int>(len));
+ EXPECT_EQ(tracker.copies() + tracker.moves(), static_cast<int>(len));
// pop_back() and erase() must destroy one object
if (len > 0) {
tracker.ResetCopiesMovesSwaps();
v.pop_back();
- EXPECT_EQ(tracker.instances(), len - 1);
+ EXPECT_EQ(tracker.instances(), static_cast<int>(len) - 1);
EXPECT_EQ(tracker.copies(), 0);
EXPECT_EQ(tracker.moves(), 0);
if (!v.empty()) {
tracker.ResetCopiesMovesSwaps();
v.erase(v.begin());
- EXPECT_EQ(tracker.instances(), len - 2);
- EXPECT_EQ(tracker.copies() + tracker.moves(), len - 2);
+ EXPECT_EQ(tracker.instances(), static_cast<int>(len) - 2);
+ EXPECT_EQ(tracker.copies() + tracker.moves(),
+ static_cast<int>(len) - 2);
}
}
@@ -1086,12 +1217,12 @@
tracker.ResetCopiesMovesSwaps();
{
InstanceVec v_copy(std::move(v));
- if (len > inlined_capacity) {
+ if (static_cast<size_t>(len) > inlined_capacity) {
// Allocation is moved as a whole.
EXPECT_EQ(tracker.instances(), len);
EXPECT_EQ(tracker.live_instances(), len);
// Tests an implementation detail, don't rely on this in your code.
- EXPECT_EQ(v.size(), 0); // NOLINT misc-use-after-move
+ EXPECT_EQ(v.size(), 0u); // NOLINT misc-use-after-move
EXPECT_EQ(tracker.copies(), 0);
EXPECT_EQ(tracker.moves(), 0);
} else {
@@ -1157,7 +1288,7 @@
tracker.ResetCopiesMovesSwaps();
InstanceVec longer, shorter;
- const int inlined_capacity = longer.capacity();
+ const size_t inlined_capacity = longer.capacity();
for (int i = 0; i < len; i++) {
longer.push_back(Instance(i));
shorter.push_back(Instance(i));
@@ -1176,7 +1307,7 @@
src_len = len;
longer = std::move(shorter);
}
- if (src_len > inlined_capacity) {
+ if (static_cast<size_t>(src_len) > inlined_capacity) {
// Allocation moved as a whole.
EXPECT_EQ(tracker.instances(), src_len);
EXPECT_EQ(tracker.live_instances(), src_len);
@@ -1201,6 +1332,8 @@
}
TEST(CountElemAssign, SimpleTypeWithInlineBacking) {
+ const size_t inlined_capacity = absl::InlinedVector<int, 2>().capacity();
+
for (size_t original_size = 0; original_size <= 5; ++original_size) {
SCOPED_TRACE(original_size);
// Original contents are [12345, 12345, ...]
@@ -1209,10 +1342,10 @@
absl::InlinedVector<int, 2> v(original_contents.begin(),
original_contents.end());
v.assign(2, 123);
- EXPECT_THAT(v, AllOf(SizeIs(2), ElementsAre(123, 123)));
- if (original_size <= 2) {
+ EXPECT_THAT(v, AllOf(SizeIs(2u), ElementsAre(123, 123)));
+ if (original_size <= inlined_capacity) {
// If the original had inline backing, it should stay inline.
- EXPECT_EQ(2, v.capacity());
+ EXPECT_EQ(v.capacity(), inlined_capacity);
}
}
}
@@ -1226,7 +1359,7 @@
absl::InlinedVector<int, 2> v(original_contents.begin(),
original_contents.end());
v.assign(3, 123);
- EXPECT_THAT(v, AllOf(SizeIs(3), ElementsAre(123, 123, 123)));
+ EXPECT_THAT(v, AllOf(SizeIs(3u), ElementsAre(123, 123, 123)));
EXPECT_LE(v.size(), v.capacity());
}
}
@@ -1241,10 +1374,10 @@
absl::InlinedVector<Instance, 2> v(original_contents.begin(),
original_contents.end());
v.assign(2, Instance(123));
- EXPECT_THAT(v, AllOf(SizeIs(2), ElementsAre(ValueIs(123), ValueIs(123))));
+ EXPECT_THAT(v, AllOf(SizeIs(2u), ElementsAre(ValueIs(123), ValueIs(123))));
if (original_size <= 2) {
// If the original had inline backing, it should stay inline.
- EXPECT_EQ(2, v.capacity());
+ EXPECT_EQ(2u, v.capacity());
}
}
}
@@ -1259,8 +1392,8 @@
absl::InlinedVector<Instance, 2> v(original_contents.begin(),
original_contents.end());
v.assign(3, Instance(123));
- EXPECT_THAT(v, AllOf(SizeIs(3), ElementsAre(ValueIs(123), ValueIs(123),
- ValueIs(123))));
+ EXPECT_THAT(v, AllOf(SizeIs(3u), ElementsAre(ValueIs(123), ValueIs(123),
+ ValueIs(123))));
EXPECT_LE(v.size(), v.capacity());
}
}
@@ -1275,16 +1408,17 @@
std::vector<int> source_v = {4, 5, 6};
// First try to fit in inline backing
absl::InlinedVector<int, 4> v(source_v.begin(), source_v.end());
- EXPECT_EQ(3, v.size());
- EXPECT_EQ(4, v.capacity()); // Indication that we're still on inlined storage
+ EXPECT_EQ(3u, v.size());
+ EXPECT_EQ(4u,
+ v.capacity()); // Indication that we're still on inlined storage
EXPECT_EQ(4, v[0]);
EXPECT_EQ(5, v[1]);
EXPECT_EQ(6, v[2]);
// Now, force a re-allocate
absl::InlinedVector<int, 2> realloc_v(source_v.begin(), source_v.end());
- EXPECT_EQ(3, realloc_v.size());
- EXPECT_LT(2, realloc_v.capacity());
+ EXPECT_EQ(3u, realloc_v.size());
+ EXPECT_LT(2u, realloc_v.capacity());
EXPECT_EQ(4, realloc_v[0]);
EXPECT_EQ(5, realloc_v[1]);
EXPECT_EQ(6, realloc_v[2]);
@@ -1299,8 +1433,8 @@
tracker.ResetCopiesMovesSwaps();
absl::InlinedVector<Instance, inlined_capacity> v(source_v.begin(),
source_v.end());
- EXPECT_EQ(2, v.size());
- EXPECT_LT(1, v.capacity());
+ EXPECT_EQ(2u, v.size());
+ EXPECT_LT(1u, v.capacity());
EXPECT_EQ(0, v[0].value());
EXPECT_EQ(1, v[1].value());
EXPECT_EQ(tracker.copies(), 2);
@@ -1352,6 +1486,8 @@
}
TEST(RangedAssign, SimpleType) {
+ const size_t inlined_capacity = absl::InlinedVector<int, 3>().capacity();
+
// Test for all combinations of original sizes (empty and non-empty inline,
// and out of line) and target sizes.
for (size_t original_size = 0; original_size <= 5; ++original_size) {
@@ -1365,7 +1501,7 @@
// New contents are [3, 4, ...]
std::vector<int> new_contents;
for (size_t i = 0; i < target_size; ++i) {
- new_contents.push_back(i + 3);
+ new_contents.push_back(static_cast<int>(i + 3));
}
absl::InlinedVector<int, 3> v(original_contents.begin(),
@@ -1374,9 +1510,10 @@
EXPECT_EQ(new_contents.size(), v.size());
EXPECT_LE(new_contents.size(), v.capacity());
- if (target_size <= 3 && original_size <= 3) {
+ if (target_size <= inlined_capacity &&
+ original_size <= inlined_capacity) {
// Storage should stay inline when target size is small.
- EXPECT_EQ(3, v.capacity());
+ EXPECT_EQ(v.capacity(), inlined_capacity);
}
EXPECT_THAT(v, ElementsAreArray(new_contents));
}
@@ -1409,7 +1546,7 @@
// TODO(bsamwel): Test with an input iterator.
std::vector<Instance> new_contents_in;
for (size_t i = 0; i < target_size; ++i) {
- new_contents_in.push_back(Instance(i + 3));
+ new_contents_in.push_back(Instance(static_cast<int>(i) + 3));
}
SourceContainer new_contents(new_contents_in.begin(),
new_contents_in.end());
@@ -1422,7 +1559,7 @@
EXPECT_LE(new_contents.size(), v.capacity());
if (target_size <= 3 && original_size <= 3) {
// Storage should stay inline when target size is small.
- EXPECT_EQ(3, v.capacity());
+ EXPECT_EQ(3u, v.capacity());
}
EXPECT_TRUE(std::equal(v.begin(), v.end(), new_contents.begin(),
InstanceValuesEqual<Instance>));
@@ -1446,12 +1583,12 @@
TEST(InitializerListConstructor, SimpleTypeWithInlineBacking) {
EXPECT_THAT((absl::InlinedVector<int, 4>{4, 5, 6}),
- AllOf(SizeIs(3), CapacityIs(4), ElementsAre(4, 5, 6)));
+ AllOf(SizeIs(3u), CapacityIs(4u), ElementsAre(4, 5, 6)));
}
TEST(InitializerListConstructor, SimpleTypeWithReallocationRequired) {
EXPECT_THAT((absl::InlinedVector<int, 2>{4, 5, 6}),
- AllOf(SizeIs(3), CapacityIs(Gt(2)), ElementsAre(4, 5, 6)));
+ AllOf(SizeIs(3u), CapacityIs(Gt(2u)), ElementsAre(4, 5, 6)));
}
TEST(InitializerListConstructor, DisparateTypesInList) {
@@ -1462,16 +1599,19 @@
}
TEST(InitializerListConstructor, ComplexTypeWithInlineBacking) {
- EXPECT_THAT((absl::InlinedVector<CopyableMovableInstance, 1>{
- CopyableMovableInstance(0)}),
- AllOf(SizeIs(1), CapacityIs(1), ElementsAre(ValueIs(0))));
+ const size_t inlined_capacity =
+ absl::InlinedVector<CopyableMovableInstance, 1>().capacity();
+ EXPECT_THAT(
+ (absl::InlinedVector<CopyableMovableInstance, 1>{
+ CopyableMovableInstance(0)}),
+ AllOf(SizeIs(1u), CapacityIs(inlined_capacity), ElementsAre(ValueIs(0))));
}
TEST(InitializerListConstructor, ComplexTypeWithReallocationRequired) {
- EXPECT_THAT(
- (absl::InlinedVector<CopyableMovableInstance, 1>{
- CopyableMovableInstance(0), CopyableMovableInstance(1)}),
- AllOf(SizeIs(2), CapacityIs(Gt(1)), ElementsAre(ValueIs(0), ValueIs(1))));
+ EXPECT_THAT((absl::InlinedVector<CopyableMovableInstance, 1>{
+ CopyableMovableInstance(0), CopyableMovableInstance(1)}),
+ AllOf(SizeIs(2u), CapacityIs(Gt(1u)),
+ ElementsAre(ValueIs(0), ValueIs(1))));
}
TEST(InitializerListAssign, SimpleTypeFitsInlineBacking) {
@@ -1481,14 +1621,14 @@
absl::InlinedVector<int, 2> v1(original_size, 12345);
const size_t original_capacity_v1 = v1.capacity();
v1.assign({3});
- EXPECT_THAT(
- v1, AllOf(SizeIs(1), CapacityIs(original_capacity_v1), ElementsAre(3)));
+ EXPECT_THAT(v1, AllOf(SizeIs(1u), CapacityIs(original_capacity_v1),
+ ElementsAre(3)));
absl::InlinedVector<int, 2> v2(original_size, 12345);
const size_t original_capacity_v2 = v2.capacity();
v2 = {3};
- EXPECT_THAT(
- v2, AllOf(SizeIs(1), CapacityIs(original_capacity_v2), ElementsAre(3)));
+ EXPECT_THAT(v2, AllOf(SizeIs(1u), CapacityIs(original_capacity_v2),
+ ElementsAre(3)));
}
}
@@ -1497,13 +1637,13 @@
SCOPED_TRACE(original_size);
absl::InlinedVector<int, 2> v1(original_size, 12345);
v1.assign({3, 4, 5});
- EXPECT_THAT(v1, AllOf(SizeIs(3), ElementsAre(3, 4, 5)));
- EXPECT_LE(3, v1.capacity());
+ EXPECT_THAT(v1, AllOf(SizeIs(3u), ElementsAre(3, 4, 5)));
+ EXPECT_LE(3u, v1.capacity());
absl::InlinedVector<int, 2> v2(original_size, 12345);
v2 = {3, 4, 5};
- EXPECT_THAT(v2, AllOf(SizeIs(3), ElementsAre(3, 4, 5)));
- EXPECT_LE(3, v2.capacity());
+ EXPECT_THAT(v2, AllOf(SizeIs(3u), ElementsAre(3, 4, 5)));
+ EXPECT_LE(3u, v2.capacity());
}
}
@@ -1532,7 +1672,7 @@
absl::InlinedVector<Instance, 2> v(original_size, Instance(12345));
const size_t original_capacity = v.capacity();
v.assign({Instance(3)});
- EXPECT_THAT(v, AllOf(SizeIs(1), CapacityIs(original_capacity),
+ EXPECT_THAT(v, AllOf(SizeIs(1u), CapacityIs(original_capacity),
ElementsAre(ValueIs(3))));
}
for (size_t original_size = 0; original_size <= 4; ++original_size) {
@@ -1540,28 +1680,53 @@
absl::InlinedVector<Instance, 2> v(original_size, Instance(12345));
v.assign({Instance(3), Instance(4), Instance(5)});
EXPECT_THAT(
- v, AllOf(SizeIs(3), ElementsAre(ValueIs(3), ValueIs(4), ValueIs(5))));
- EXPECT_LE(3, v.capacity());
+ v, AllOf(SizeIs(3u), ElementsAre(ValueIs(3), ValueIs(4), ValueIs(5))));
+ EXPECT_LE(3u, v.capacity());
}
}
-REGISTER_TYPED_TEST_CASE_P(InstanceTest, Swap, CountConstructorsDestructors,
- CountConstructorsDestructorsOnCopyConstruction,
- CountConstructorsDestructorsOnMoveConstruction,
- CountConstructorsDestructorsOnAssignment,
- CountConstructorsDestructorsOnMoveAssignment,
- CountElemAssignInlineBacking, RangedConstructor,
- RangedAssign, InitializerListAssign);
+REGISTER_TYPED_TEST_SUITE_P(InstanceTest, Swap, CountConstructorsDestructors,
+ CountConstructorsDestructorsOnCopyConstruction,
+ CountConstructorsDestructorsOnMoveConstruction,
+ CountConstructorsDestructorsOnAssignment,
+ CountConstructorsDestructorsOnMoveAssignment,
+ CountElemAssignInlineBacking, RangedConstructor,
+ RangedAssign, InitializerListAssign);
using InstanceTypes =
::testing::Types<CopyableOnlyInstance, CopyableMovableInstance>;
-INSTANTIATE_TYPED_TEST_CASE_P(InstanceTestOnTypes, InstanceTest, InstanceTypes);
+INSTANTIATE_TYPED_TEST_SUITE_P(InstanceTestOnTypes, InstanceTest,
+ InstanceTypes);
TEST(DynamicVec, DynamicVecCompiles) {
DynamicVec v;
(void)v;
}
+TEST(DynamicVec, CreateNonEmptyDynamicVec) {
+ DynamicVec v(1);
+ EXPECT_EQ(v.size(), 1u);
+}
+
+TEST(DynamicVec, EmplaceBack) {
+ DynamicVec v;
+ v.emplace_back(Dynamic{});
+ EXPECT_EQ(v.size(), 1u);
+}
+
+TEST(DynamicVec, EmplaceBackAfterHeapAllocation) {
+ DynamicVec v;
+ v.reserve(10);
+ v.emplace_back(Dynamic{});
+ EXPECT_EQ(v.size(), 1u);
+}
+
+TEST(DynamicVec, EmptyIteratorComparison) {
+ DynamicVec v;
+ EXPECT_EQ(v.begin(), v.end());
+ EXPECT_EQ(v.cbegin(), v.cend());
+}
+
TEST(AllocatorSupportTest, Constructors) {
using MyAlloc = CountingAllocator<int>;
using AllocVec = absl::InlinedVector<int, 4, MyAlloc>;
@@ -1586,54 +1751,54 @@
MyAlloc alloc(&allocated);
{
AllocVec ABSL_ATTRIBUTE_UNUSED v(ia, ia + 4, alloc);
- EXPECT_THAT(allocated, 0);
+ EXPECT_THAT(allocated, Eq(0));
}
- EXPECT_THAT(allocated, 0);
+ EXPECT_THAT(allocated, Eq(0));
{
AllocVec ABSL_ATTRIBUTE_UNUSED v(ia, ia + ABSL_ARRAYSIZE(ia), alloc);
- EXPECT_THAT(allocated, v.size() * sizeof(int));
+ EXPECT_THAT(allocated, Eq(static_cast<int64_t>(v.size() * sizeof(int))));
}
- EXPECT_THAT(allocated, 0);
+ EXPECT_THAT(allocated, Eq(0));
{
AllocVec v(4, 1, alloc);
- EXPECT_THAT(allocated, 0);
+ EXPECT_THAT(allocated, Eq(0));
int64_t allocated2 = 0;
MyAlloc alloc2(&allocated2);
AllocVec v2(v, alloc2);
- EXPECT_THAT(allocated2, 0);
+ EXPECT_THAT(allocated2, Eq(0));
int64_t allocated3 = 0;
MyAlloc alloc3(&allocated3);
AllocVec v3(std::move(v), alloc3);
- EXPECT_THAT(allocated3, 0);
+ EXPECT_THAT(allocated3, Eq(0));
}
EXPECT_THAT(allocated, 0);
{
AllocVec v(8, 2, alloc);
- EXPECT_THAT(allocated, v.size() * sizeof(int));
+ EXPECT_THAT(allocated, Eq(static_cast<int64_t>(v.size() * sizeof(int))));
int64_t allocated2 = 0;
MyAlloc alloc2(&allocated2);
AllocVec v2(v, alloc2);
- EXPECT_THAT(allocated2, v2.size() * sizeof(int));
+ EXPECT_THAT(allocated2, Eq(static_cast<int64_t>(v2.size() * sizeof(int))));
int64_t allocated3 = 0;
MyAlloc alloc3(&allocated3);
AllocVec v3(std::move(v), alloc3);
- EXPECT_THAT(allocated3, v3.size() * sizeof(int));
+ EXPECT_THAT(allocated3, Eq(static_cast<int64_t>(v3.size() * sizeof(int))));
}
EXPECT_EQ(allocated, 0);
{
// Test shrink_to_fit deallocations.
AllocVec v(8, 2, alloc);
- EXPECT_EQ(allocated, 8 * sizeof(int));
+ EXPECT_EQ(allocated, static_cast<int64_t>(8 * sizeof(int)));
v.resize(5);
- EXPECT_EQ(allocated, 8 * sizeof(int));
+ EXPECT_EQ(allocated, static_cast<int64_t>(8 * sizeof(int)));
v.shrink_to_fit();
- EXPECT_EQ(allocated, 5 * sizeof(int));
+ EXPECT_EQ(allocated, static_cast<int64_t>(5 * sizeof(int)));
v.resize(4);
- EXPECT_EQ(allocated, 5 * sizeof(int));
+ EXPECT_EQ(allocated, static_cast<int64_t>(5 * sizeof(int)));
v.shrink_to_fit();
EXPECT_EQ(allocated, 0);
}
@@ -1652,13 +1817,17 @@
AllocVec v1(ia1, ia1 + ABSL_ARRAYSIZE(ia1), a1);
AllocVec v2(ia2, ia2 + ABSL_ARRAYSIZE(ia2), a2);
EXPECT_LT(v1.capacity(), v2.capacity());
- EXPECT_THAT(allocated1, v1.capacity() * sizeof(int));
- EXPECT_THAT(allocated2, v2.capacity() * sizeof(int));
+ EXPECT_THAT(allocated1,
+ Eq(static_cast<int64_t>(v1.capacity() * sizeof(int))));
+ EXPECT_THAT(allocated2,
+ Eq(static_cast<int64_t>(v2.capacity() * sizeof(int))));
v1.swap(v2);
EXPECT_THAT(v1, ElementsAreArray(ia2));
EXPECT_THAT(v2, ElementsAreArray(ia1));
- EXPECT_THAT(allocated1, v2.capacity() * sizeof(int));
- EXPECT_THAT(allocated2, v1.capacity() * sizeof(int));
+ EXPECT_THAT(allocated1,
+ Eq(static_cast<int64_t>(v2.capacity() * sizeof(int))));
+ EXPECT_THAT(allocated2,
+ Eq(static_cast<int64_t>(v1.capacity() * sizeof(int))));
}
EXPECT_THAT(allocated1, 0);
EXPECT_THAT(allocated2, 0);
@@ -1676,13 +1845,15 @@
MyAlloc a2(&allocated2);
AllocVec v1(ia1, ia1 + ABSL_ARRAYSIZE(ia1), a1);
AllocVec v2(ia2, ia2 + ABSL_ARRAYSIZE(ia2), a2);
- EXPECT_THAT(allocated1, v1.capacity() * sizeof(int));
- EXPECT_THAT(allocated2, 0);
+ EXPECT_THAT(allocated1,
+ Eq(static_cast<int64_t>(v1.capacity() * sizeof(int))));
+ EXPECT_THAT(allocated2, Eq(0));
v1.swap(v2);
EXPECT_THAT(v1, ElementsAreArray(ia2));
EXPECT_THAT(v2, ElementsAreArray(ia1));
- EXPECT_THAT(allocated1, v2.capacity() * sizeof(int));
- EXPECT_THAT(allocated2, 0);
+ EXPECT_THAT(allocated1,
+ Eq(static_cast<int64_t>(v2.capacity() * sizeof(int))));
+ EXPECT_THAT(allocated2, Eq(0));
EXPECT_TRUE(v2.get_allocator() == a1);
EXPECT_TRUE(v1.get_allocator() == a2);
}
@@ -1744,7 +1915,7 @@
}
TEST(AllocatorSupportTest, SizeAllocConstructor) {
- constexpr int inlined_size = 4;
+ constexpr size_t inlined_size = 4;
using Alloc = CountingAllocator<int>;
using AllocVec = absl::InlinedVector<int, inlined_size, Alloc>;
@@ -1754,7 +1925,7 @@
auto v = AllocVec(len, Alloc(&allocated));
// Inline storage used; allocator should not be invoked
- EXPECT_THAT(allocated, 0);
+ EXPECT_THAT(allocated, Eq(0));
EXPECT_THAT(v, AllOf(SizeIs(len), Each(0)));
}
@@ -1764,7 +1935,7 @@
auto v = AllocVec(len, Alloc(&allocated));
// Out of line storage used; allocation of 8 elements expected
- EXPECT_THAT(allocated, len * sizeof(int));
+ EXPECT_THAT(allocated, Eq(static_cast<int64_t>(len * sizeof(int))));
EXPECT_THAT(v, AllOf(SizeIs(len), Each(0)));
}
}
@@ -1799,9 +1970,9 @@
// Generate a variety of vectors some of these are small enough for the inline
// space but are stored out of line.
- for (int i = 0; i < 10; ++i) {
+ for (size_t i = 0; i < 10; ++i) {
V v;
- for (int j = 0; j < i; ++j) {
+ for (int j = 0; j < static_cast<int>(i); ++j) {
v.push_back(j);
}
cases.push_back(v);
@@ -1812,4 +1983,226 @@
EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly(cases));
}
+class MoveConstructibleOnlyInstance
+ : public absl::test_internal::BaseCountedInstance {
+ public:
+ explicit MoveConstructibleOnlyInstance(int x) : BaseCountedInstance(x) {}
+ MoveConstructibleOnlyInstance(MoveConstructibleOnlyInstance&& other) =
+ default;
+ MoveConstructibleOnlyInstance& operator=(
+ MoveConstructibleOnlyInstance&& other) = delete;
+};
+
+MATCHER(HasValue, "") {
+ return ::testing::get<0>(arg).value() == ::testing::get<1>(arg);
+}
+
+TEST(NonAssignableMoveAssignmentTest, AllocatedToInline) {
+ using X = MoveConstructibleOnlyInstance;
+ InstanceTracker tracker;
+ absl::InlinedVector<X, 2> inlined;
+ inlined.emplace_back(1);
+ absl::InlinedVector<X, 2> allocated;
+ allocated.emplace_back(1);
+ allocated.emplace_back(2);
+ allocated.emplace_back(3);
+ tracker.ResetCopiesMovesSwaps();
+
+ inlined = std::move(allocated);
+ // passed ownership of the allocated storage
+ EXPECT_EQ(tracker.moves(), 0);
+ EXPECT_EQ(tracker.live_instances(), 3);
+
+ EXPECT_THAT(inlined, Pointwise(HasValue(), {1, 2, 3}));
+}
+
+TEST(NonAssignableMoveAssignmentTest, InlineToAllocated) {
+ using X = MoveConstructibleOnlyInstance;
+ InstanceTracker tracker;
+ absl::InlinedVector<X, 2> inlined;
+ inlined.emplace_back(1);
+ absl::InlinedVector<X, 2> allocated;
+ allocated.emplace_back(1);
+ allocated.emplace_back(2);
+ allocated.emplace_back(3);
+ tracker.ResetCopiesMovesSwaps();
+
+ allocated = std::move(inlined);
+ // Moved elements
+ EXPECT_EQ(tracker.moves(), 1);
+ EXPECT_EQ(tracker.live_instances(), 1);
+
+ EXPECT_THAT(allocated, Pointwise(HasValue(), {1}));
+}
+
+TEST(NonAssignableMoveAssignmentTest, InlineToInline) {
+ using X = MoveConstructibleOnlyInstance;
+ InstanceTracker tracker;
+ absl::InlinedVector<X, 2> inlined_a;
+ inlined_a.emplace_back(1);
+ absl::InlinedVector<X, 2> inlined_b;
+ inlined_b.emplace_back(1);
+ tracker.ResetCopiesMovesSwaps();
+
+ inlined_a = std::move(inlined_b);
+ // Moved elements
+ EXPECT_EQ(tracker.moves(), 1);
+ EXPECT_EQ(tracker.live_instances(), 1);
+
+ EXPECT_THAT(inlined_a, Pointwise(HasValue(), {1}));
+}
+
+TEST(NonAssignableMoveAssignmentTest, AllocatedToAllocated) {
+ using X = MoveConstructibleOnlyInstance;
+ InstanceTracker tracker;
+ absl::InlinedVector<X, 2> allocated_a;
+ allocated_a.emplace_back(1);
+ allocated_a.emplace_back(2);
+ allocated_a.emplace_back(3);
+ absl::InlinedVector<X, 2> allocated_b;
+ allocated_b.emplace_back(4);
+ allocated_b.emplace_back(5);
+ allocated_b.emplace_back(6);
+ allocated_b.emplace_back(7);
+ tracker.ResetCopiesMovesSwaps();
+
+ allocated_a = std::move(allocated_b);
+ // passed ownership of the allocated storage
+ EXPECT_EQ(tracker.moves(), 0);
+ EXPECT_EQ(tracker.live_instances(), 4);
+
+ EXPECT_THAT(allocated_a, Pointwise(HasValue(), {4, 5, 6, 7}));
+}
+
+TEST(NonAssignableMoveAssignmentTest, AssignThis) {
+ using X = MoveConstructibleOnlyInstance;
+ InstanceTracker tracker;
+ absl::InlinedVector<X, 2> v;
+ v.emplace_back(1);
+ v.emplace_back(2);
+ v.emplace_back(3);
+
+ tracker.ResetCopiesMovesSwaps();
+
+ // Obfuscated in order to pass -Wself-move.
+ v = std::move(*std::addressof(v));
+ // nothing happens
+ EXPECT_EQ(tracker.moves(), 0);
+ EXPECT_EQ(tracker.live_instances(), 3);
+
+ EXPECT_THAT(v, Pointwise(HasValue(), {1, 2, 3}));
+}
+
+class NonSwappableInstance : public absl::test_internal::BaseCountedInstance {
+ public:
+ explicit NonSwappableInstance(int x) : BaseCountedInstance(x) {}
+ NonSwappableInstance(const NonSwappableInstance& other) = default;
+ NonSwappableInstance& operator=(const NonSwappableInstance& other) = default;
+ NonSwappableInstance(NonSwappableInstance&& other) = default;
+ NonSwappableInstance& operator=(NonSwappableInstance&& other) = default;
+};
+
+void swap(NonSwappableInstance&, NonSwappableInstance&) = delete;
+
+TEST(NonSwappableSwapTest, InlineAndAllocatedTransferStorageAndMove) {
+ using X = NonSwappableInstance;
+ InstanceTracker tracker;
+ absl::InlinedVector<X, 2> inlined;
+ inlined.emplace_back(1);
+ absl::InlinedVector<X, 2> allocated;
+ allocated.emplace_back(1);
+ allocated.emplace_back(2);
+ allocated.emplace_back(3);
+ tracker.ResetCopiesMovesSwaps();
+
+ inlined.swap(allocated);
+ EXPECT_EQ(tracker.moves(), 1);
+ EXPECT_EQ(tracker.live_instances(), 4);
+
+ EXPECT_THAT(inlined, Pointwise(HasValue(), {1, 2, 3}));
+}
+
+TEST(NonSwappableSwapTest, InlineAndInlineMoveIndividualElements) {
+ using X = NonSwappableInstance;
+ InstanceTracker tracker;
+ absl::InlinedVector<X, 2> inlined_a;
+ inlined_a.emplace_back(1);
+ absl::InlinedVector<X, 2> inlined_b;
+ inlined_b.emplace_back(2);
+ tracker.ResetCopiesMovesSwaps();
+
+ inlined_a.swap(inlined_b);
+ EXPECT_EQ(tracker.moves(), 3);
+ EXPECT_EQ(tracker.live_instances(), 2);
+
+ EXPECT_THAT(inlined_a, Pointwise(HasValue(), {2}));
+ EXPECT_THAT(inlined_b, Pointwise(HasValue(), {1}));
+}
+
+TEST(NonSwappableSwapTest, AllocatedAndAllocatedOnlyTransferStorage) {
+ using X = NonSwappableInstance;
+ InstanceTracker tracker;
+ absl::InlinedVector<X, 2> allocated_a;
+ allocated_a.emplace_back(1);
+ allocated_a.emplace_back(2);
+ allocated_a.emplace_back(3);
+ absl::InlinedVector<X, 2> allocated_b;
+ allocated_b.emplace_back(4);
+ allocated_b.emplace_back(5);
+ allocated_b.emplace_back(6);
+ allocated_b.emplace_back(7);
+ tracker.ResetCopiesMovesSwaps();
+
+ allocated_a.swap(allocated_b);
+ EXPECT_EQ(tracker.moves(), 0);
+ EXPECT_EQ(tracker.live_instances(), 7);
+
+ EXPECT_THAT(allocated_a, Pointwise(HasValue(), {4, 5, 6, 7}));
+ EXPECT_THAT(allocated_b, Pointwise(HasValue(), {1, 2, 3}));
+}
+
+TEST(NonSwappableSwapTest, SwapThis) {
+ using X = NonSwappableInstance;
+ InstanceTracker tracker;
+ absl::InlinedVector<X, 2> v;
+ v.emplace_back(1);
+ v.emplace_back(2);
+ v.emplace_back(3);
+
+ tracker.ResetCopiesMovesSwaps();
+
+ v.swap(v);
+ EXPECT_EQ(tracker.moves(), 0);
+ EXPECT_EQ(tracker.live_instances(), 3);
+
+ EXPECT_THAT(v, Pointwise(HasValue(), {1, 2, 3}));
+}
+
+template <size_t N>
+using CharVec = absl::InlinedVector<char, N>;
+
+// Warning: This struct "simulates" the type `InlinedVector::Storage::Allocated`
+// to make reasonable expectations for inlined storage capacity optimization. If
+// implementation changes `Allocated`, then `MySpan` and tests that use it need
+// to be updated accordingly.
+template <typename T>
+struct MySpan {
+ T* data;
+ size_t size;
+};
+
+TEST(StorageTest, InlinedCapacityAutoIncrease) {
+ // The requested capacity is auto increased to `sizeof(MySpan<char>)`.
+ EXPECT_GT(CharVec<1>().capacity(), 1);
+ EXPECT_EQ(CharVec<1>().capacity(), sizeof(MySpan<char>));
+ EXPECT_EQ(CharVec<1>().capacity(), CharVec<2>().capacity());
+ EXPECT_EQ(sizeof(CharVec<1>), sizeof(CharVec<2>));
+
+ // The requested capacity is auto increased to
+ // `sizeof(MySpan<int>) / sizeof(int)`.
+ EXPECT_GT((absl::InlinedVector<int, 1>().capacity()), 1);
+ EXPECT_EQ((absl::InlinedVector<int, 1>().capacity()),
+ sizeof(MySpan<int>) / sizeof(int));
+}
+
} // anonymous namespace
diff --git a/absl/container/internal/btree.h b/absl/container/internal/btree.h
index f636c5f..91df57a 100644
--- a/absl/container/internal/btree.h
+++ b/absl/container/internal/btree.h
@@ -58,8 +58,10 @@
#include <type_traits>
#include <utility>
+#include "absl/base/internal/raw_logging.h"
#include "absl/base/macros.h"
#include "absl/container/internal/common.h"
+#include "absl/container/internal/common_policy_traits.h"
#include "absl/container/internal/compressed_tuple.h"
#include "absl/container/internal/container_memory.h"
#include "absl/container/internal/layout.h"
@@ -74,12 +76,31 @@
ABSL_NAMESPACE_BEGIN
namespace container_internal {
+#ifdef ABSL_BTREE_ENABLE_GENERATIONS
+#error ABSL_BTREE_ENABLE_GENERATIONS cannot be directly set
+#elif defined(ABSL_HAVE_ADDRESS_SANITIZER) || \
+ defined(ABSL_HAVE_HWADDRESS_SANITIZER) || \
+ defined(ABSL_HAVE_MEMORY_SANITIZER)
+// When compiled in sanitizer mode, we add generation integers to the nodes and
+// iterators. When iterators are used, we validate that the container has not
+// been mutated since the iterator was constructed.
+#define ABSL_BTREE_ENABLE_GENERATIONS
+#endif
+
+#ifdef ABSL_BTREE_ENABLE_GENERATIONS
+constexpr bool BtreeGenerationsEnabled() { return true; }
+#else
+constexpr bool BtreeGenerationsEnabled() { return false; }
+#endif
+
+template <typename Compare, typename T, typename U>
+using compare_result_t = absl::result_of_t<const Compare(const T &, const U &)>;
+
// A helper class that indicates if the Compare parameter is a key-compare-to
// comparator.
template <typename Compare, typename T>
using btree_is_key_compare_to =
- std::is_convertible<absl::result_of_t<Compare(const T &, const T &)>,
- absl::weak_ordering>;
+ std::is_convertible<compare_result_t<Compare, T, T>, absl::weak_ordering>;
struct StringBtreeDefaultLess {
using is_transparent = void;
@@ -87,7 +108,7 @@
StringBtreeDefaultLess() = default;
// Compatibility constructor.
- StringBtreeDefaultLess(std::less<std::string>) {} // NOLINT
+ StringBtreeDefaultLess(std::less<std::string>) {} // NOLINT
StringBtreeDefaultLess(std::less<absl::string_view>) {} // NOLINT
// Allow converting to std::less for use in key_comp()/value_comp().
@@ -119,7 +140,7 @@
StringBtreeDefaultGreater() = default;
- StringBtreeDefaultGreater(std::greater<std::string>) {} // NOLINT
+ StringBtreeDefaultGreater(std::greater<std::string>) {} // NOLINT
StringBtreeDefaultGreater(std::greater<absl::string_view>) {} // NOLINT
// Allow converting to std::greater for use in key_comp()/value_comp().
@@ -146,49 +167,140 @@
}
};
-// A helper class to convert a boolean comparison into a three-way "compare-to"
-// comparison that returns an `absl::weak_ordering`. This helper
-// class is specialized for less<std::string>, greater<std::string>,
-// less<string_view>, greater<string_view>, less<absl::Cord>, and
-// greater<absl::Cord>.
-//
-// key_compare_to_adapter is provided so that btree users
-// automatically get the more efficient compare-to code when using common
-// Abseil string types with common comparison functors.
-// These string-like specializations also turn on heterogeneous lookup by
-// default.
+// See below comments for checked_compare.
+template <typename Compare, bool is_class = std::is_class<Compare>::value>
+struct checked_compare_base : Compare {
+ using Compare::Compare;
+ explicit checked_compare_base(Compare c) : Compare(std::move(c)) {}
+ const Compare &comp() const { return *this; }
+};
template <typename Compare>
-struct key_compare_to_adapter {
- using type = Compare;
+struct checked_compare_base<Compare, false> {
+ explicit checked_compare_base(Compare c) : compare(std::move(c)) {}
+ const Compare &comp() const { return compare; }
+ Compare compare;
+};
+
+// A mechanism for opting out of checked_compare for use only in btree_test.cc.
+struct BtreeTestOnlyCheckedCompareOptOutBase {};
+
+// A helper class to adapt the specified comparator for two use cases:
+// (1) When using common Abseil string types with common comparison functors,
+// convert a boolean comparison into a three-way comparison that returns an
+// `absl::weak_ordering`. This helper class is specialized for
+// less<std::string>, greater<std::string>, less<string_view>,
+// greater<string_view>, less<absl::Cord>, and greater<absl::Cord>.
+// (2) Adapt the comparator to diagnose cases of non-strict-weak-ordering (see
+// https://en.cppreference.com/w/cpp/named_req/Compare) in debug mode. Whenever
+// a comparison is made, we will make assertions to verify that the comparator
+// is valid.
+template <typename Compare, typename Key>
+struct key_compare_adapter {
+ // Inherit from checked_compare_base to support function pointers and also
+ // keep empty-base-optimization (EBO) support for classes.
+ // Note: we can't use CompressedTuple here because that would interfere
+ // with the EBO for `btree::rightmost_`. `btree::rightmost_` is itself a
+ // CompressedTuple and nested `CompressedTuple`s don't support EBO.
+ // TODO(b/214288561): use CompressedTuple instead once it supports EBO for
+ // nested `CompressedTuple`s.
+ struct checked_compare : checked_compare_base<Compare> {
+ private:
+ using Base = typename checked_compare::checked_compare_base;
+ using Base::comp;
+
+ // If possible, returns whether `t` is equivalent to itself. We can only do
+ // this for `Key`s because we can't be sure that it's safe to call
+ // `comp()(k, k)` otherwise. Even if SFINAE allows it, there could be a
+ // compilation failure inside the implementation of the comparison operator.
+ bool is_self_equivalent(const Key &k) const {
+ // Note: this works for both boolean and three-way comparators.
+ return comp()(k, k) == 0;
+ }
+ // If we can't compare `t` with itself, returns true unconditionally.
+ template <typename T>
+ bool is_self_equivalent(const T &) const {
+ return true;
+ }
+
+ public:
+ using Base::Base;
+ checked_compare(Compare comp) : Base(std::move(comp)) {} // NOLINT
+
+ // Allow converting to Compare for use in key_comp()/value_comp().
+ explicit operator Compare() const { return comp(); }
+
+ template <typename T, typename U,
+ absl::enable_if_t<
+ std::is_same<bool, compare_result_t<Compare, T, U>>::value,
+ int> = 0>
+ bool operator()(const T &lhs, const U &rhs) const {
+ // NOTE: if any of these assertions fail, then the comparator does not
+ // establish a strict-weak-ordering (see
+ // https://en.cppreference.com/w/cpp/named_req/Compare).
+ assert(is_self_equivalent(lhs));
+ assert(is_self_equivalent(rhs));
+ const bool lhs_comp_rhs = comp()(lhs, rhs);
+ assert(!lhs_comp_rhs || !comp()(rhs, lhs));
+ return lhs_comp_rhs;
+ }
+
+ template <
+ typename T, typename U,
+ absl::enable_if_t<std::is_convertible<compare_result_t<Compare, T, U>,
+ absl::weak_ordering>::value,
+ int> = 0>
+ absl::weak_ordering operator()(const T &lhs, const U &rhs) const {
+ // NOTE: if any of these assertions fail, then the comparator does not
+ // establish a strict-weak-ordering (see
+ // https://en.cppreference.com/w/cpp/named_req/Compare).
+ assert(is_self_equivalent(lhs));
+ assert(is_self_equivalent(rhs));
+ const absl::weak_ordering lhs_comp_rhs = comp()(lhs, rhs);
+#ifndef NDEBUG
+ const absl::weak_ordering rhs_comp_lhs = comp()(rhs, lhs);
+ if (lhs_comp_rhs > 0) {
+ assert(rhs_comp_lhs < 0 && "lhs_comp_rhs > 0 -> rhs_comp_lhs < 0");
+ } else if (lhs_comp_rhs == 0) {
+ assert(rhs_comp_lhs == 0 && "lhs_comp_rhs == 0 -> rhs_comp_lhs == 0");
+ } else {
+ assert(rhs_comp_lhs > 0 && "lhs_comp_rhs < 0 -> rhs_comp_lhs > 0");
+ }
+#endif
+ return lhs_comp_rhs;
+ }
+ };
+ using type = absl::conditional_t<
+ std::is_base_of<BtreeTestOnlyCheckedCompareOptOutBase, Compare>::value,
+ Compare, checked_compare>;
};
template <>
-struct key_compare_to_adapter<std::less<std::string>> {
+struct key_compare_adapter<std::less<std::string>, std::string> {
using type = StringBtreeDefaultLess;
};
template <>
-struct key_compare_to_adapter<std::greater<std::string>> {
+struct key_compare_adapter<std::greater<std::string>, std::string> {
using type = StringBtreeDefaultGreater;
};
template <>
-struct key_compare_to_adapter<std::less<absl::string_view>> {
+struct key_compare_adapter<std::less<absl::string_view>, absl::string_view> {
using type = StringBtreeDefaultLess;
};
template <>
-struct key_compare_to_adapter<std::greater<absl::string_view>> {
+struct key_compare_adapter<std::greater<absl::string_view>, absl::string_view> {
using type = StringBtreeDefaultGreater;
};
template <>
-struct key_compare_to_adapter<std::less<absl::Cord>> {
+struct key_compare_adapter<std::less<absl::Cord>, absl::Cord> {
using type = StringBtreeDefaultLess;
};
template <>
-struct key_compare_to_adapter<std::greater<absl::Cord>> {
+struct key_compare_adapter<std::greater<absl::Cord>, absl::Cord> {
using type = StringBtreeDefaultGreater;
};
@@ -224,21 +336,63 @@
T, absl::void_t<typename T::absl_btree_prefer_linear_node_search>>
: T::absl_btree_prefer_linear_node_search {};
+template <typename Compare, typename Key>
+constexpr bool compare_has_valid_result_type() {
+ using compare_result_type = compare_result_t<Compare, Key, Key>;
+ return std::is_same<compare_result_type, bool>::value ||
+ std::is_convertible<compare_result_type, absl::weak_ordering>::value;
+}
+
+template <typename original_key_compare, typename value_type>
+class map_value_compare {
+ template <typename Params>
+ friend class btree;
+
+ // Note: this `protected` is part of the API of std::map::value_compare. See
+ // https://en.cppreference.com/w/cpp/container/map/value_compare.
+ protected:
+ explicit map_value_compare(original_key_compare c) : comp(std::move(c)) {}
+
+ original_key_compare comp; // NOLINT
+
+ public:
+ auto operator()(const value_type &lhs, const value_type &rhs) const
+ -> decltype(comp(lhs.first, rhs.first)) {
+ return comp(lhs.first, rhs.first);
+ }
+};
+
template <typename Key, typename Compare, typename Alloc, int TargetNodeSize,
- bool Multi, typename SlotPolicy>
-struct common_params {
+ bool IsMulti, bool IsMap, typename SlotPolicy>
+struct common_params : common_policy_traits<SlotPolicy> {
using original_key_compare = Compare;
// If Compare is a common comparator for a string-like type, then we adapt it
// to use heterogeneous lookup and to be a key-compare-to comparator.
- using key_compare = typename key_compare_to_adapter<Compare>::type;
+ // We also adapt the comparator to diagnose invalid comparators in debug mode.
+ // We disable this when `Compare` is invalid in a way that will cause
+ // adaptation to fail (having invalid return type) so that we can give a
+ // better compilation failure in static_assert_validation. If we don't do
+ // this, then there will be cascading compilation failures that are confusing
+ // for users.
+ using key_compare =
+ absl::conditional_t<!compare_has_valid_result_type<Compare, Key>(),
+ Compare,
+ typename key_compare_adapter<Compare, Key>::type>;
+
+ static constexpr bool kIsKeyCompareStringAdapted =
+ std::is_same<key_compare, StringBtreeDefaultLess>::value ||
+ std::is_same<key_compare, StringBtreeDefaultGreater>::value;
+ static constexpr bool kIsKeyCompareTransparent =
+ IsTransparent<original_key_compare>::value || kIsKeyCompareStringAdapted;
+
// A type which indicates if we have a key-compare-to functor or a plain old
// key-compare functor.
using is_key_compare_to = btree_is_key_compare_to<key_compare, Key>;
using allocator_type = Alloc;
using key_type = Key;
- using size_type = std::make_signed<size_t>::type;
+ using size_type = size_t;
using difference_type = ptrdiff_t;
using slot_policy = SlotPolicy;
@@ -250,6 +404,12 @@
using reference = value_type &;
using const_reference = const value_type &;
+ using value_compare =
+ absl::conditional_t<IsMap,
+ map_value_compare<original_key_compare, value_type>,
+ original_key_compare>;
+ using is_map_container = std::integral_constant<bool, IsMap>;
+
// For the given lookup key type, returns whether we can have multiple
// equivalent keys in the btree. If this is a multi-container, then we can.
// Otherwise, we can have multiple equivalent keys only if all of the
@@ -260,163 +420,26 @@
// that we know has the same equivalence classes for all lookup types.
template <typename LookupKey>
constexpr static bool can_have_multiple_equivalent_keys() {
- return Multi ||
- (IsTransparent<key_compare>::value &&
- !std::is_same<LookupKey, Key>::value &&
- !std::is_same<key_compare, StringBtreeDefaultLess>::value &&
- !std::is_same<key_compare, StringBtreeDefaultGreater>::value);
+ return IsMulti || (IsTransparent<key_compare>::value &&
+ !std::is_same<LookupKey, Key>::value &&
+ !kIsKeyCompareStringAdapted);
}
enum {
kTargetNodeSize = TargetNodeSize,
- // Upper bound for the available space for values. This is largest for leaf
+ // Upper bound for the available space for slots. This is largest for leaf
// nodes, which have overhead of at least a pointer + 4 bytes (for storing
// 3 field_types and an enum).
- kNodeValueSpace =
- TargetNodeSize - /*minimum overhead=*/(sizeof(void *) + 4),
+ kNodeSlotSpace = TargetNodeSize - /*minimum overhead=*/(sizeof(void *) + 4),
};
- // This is an integral type large enough to hold as many
- // ValueSize-values as will fit a node of TargetNodeSize bytes.
+ // This is an integral type large enough to hold as many slots as will fit a
+ // node of TargetNodeSize bytes.
using node_count_type =
- absl::conditional_t<(kNodeValueSpace / sizeof(value_type) >
+ absl::conditional_t<(kNodeSlotSpace / sizeof(slot_type) >
(std::numeric_limits<uint8_t>::max)()),
uint16_t, uint8_t>; // NOLINT
-
- // The following methods are necessary for passing this struct as PolicyTraits
- // for node_handle and/or are used within btree.
- static value_type &element(slot_type *slot) {
- return slot_policy::element(slot);
- }
- static const value_type &element(const slot_type *slot) {
- return slot_policy::element(slot);
- }
- template <class... Args>
- static void construct(Alloc *alloc, slot_type *slot, Args &&... args) {
- slot_policy::construct(alloc, slot, std::forward<Args>(args)...);
- }
- static void construct(Alloc *alloc, slot_type *slot, slot_type *other) {
- slot_policy::construct(alloc, slot, other);
- }
- static void destroy(Alloc *alloc, slot_type *slot) {
- slot_policy::destroy(alloc, slot);
- }
- static void transfer(Alloc *alloc, slot_type *new_slot, slot_type *old_slot) {
- construct(alloc, new_slot, old_slot);
- destroy(alloc, old_slot);
- }
- static void swap(Alloc *alloc, slot_type *a, slot_type *b) {
- slot_policy::swap(alloc, a, b);
- }
- static void move(Alloc *alloc, slot_type *src, slot_type *dest) {
- slot_policy::move(alloc, src, dest);
- }
-};
-
-// 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 Multi>
-struct map_params : common_params<Key, Compare, Alloc, TargetNodeSize, Multi,
- map_slot_policy<Key, Data>> {
- using super_type = typename map_params::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.
- using slot_policy = typename super_type::slot_policy;
- using slot_type = typename super_type::slot_type;
- using value_type = typename super_type::value_type;
- using init_type = typename super_type::init_type;
-
- using original_key_compare = typename super_type::original_key_compare;
- // Reference: https://en.cppreference.com/w/cpp/container/map/value_compare
- class value_compare {
- template <typename Params>
- friend class btree;
-
- protected:
- explicit value_compare(original_key_compare c) : comp(std::move(c)) {}
-
- original_key_compare comp; // NOLINT
-
- public:
- auto operator()(const value_type &lhs, const value_type &rhs) const
- -> decltype(comp(lhs.first, rhs.first)) {
- return comp(lhs.first, rhs.first);
- }
- };
- using is_map_container = std::true_type;
-
- template <typename V>
- static auto key(const V &value) -> decltype(value.first) {
- return value.first;
- }
- static const Key &key(const slot_type *s) { return slot_policy::key(s); }
- static const Key &key(slot_type *s) { return slot_policy::key(s); }
- // For use in node handle.
- static auto mutable_key(slot_type *s)
- -> decltype(slot_policy::mutable_key(s)) {
- return slot_policy::mutable_key(s);
- }
- static mapped_type &value(value_type *value) { return value->second; }
-};
-
-// This type implements the necessary functions from the
-// absl::container_internal::slot_type interface.
-template <typename Key>
-struct set_slot_policy {
- using slot_type = Key;
- using value_type = Key;
- using mutable_value_type = Key;
-
- static value_type &element(slot_type *slot) { return *slot; }
- static const value_type &element(const slot_type *slot) { return *slot; }
-
- template <typename Alloc, class... Args>
- static void construct(Alloc *alloc, slot_type *slot, Args &&... args) {
- absl::allocator_traits<Alloc>::construct(*alloc, slot,
- std::forward<Args>(args)...);
- }
-
- template <typename Alloc>
- static void construct(Alloc *alloc, slot_type *slot, slot_type *other) {
- absl::allocator_traits<Alloc>::construct(*alloc, slot, std::move(*other));
- }
-
- template <typename Alloc>
- static void destroy(Alloc *alloc, slot_type *slot) {
- absl::allocator_traits<Alloc>::destroy(*alloc, slot);
- }
-
- template <typename Alloc>
- static void swap(Alloc * /*alloc*/, slot_type *a, slot_type *b) {
- using std::swap;
- swap(*a, *b);
- }
-
- template <typename Alloc>
- static void move(Alloc * /*alloc*/, slot_type *src, slot_type *dest) {
- *dest = std::move(*src);
- }
-};
-
-// 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 Multi>
-struct set_params : common_params<Key, Compare, Alloc, TargetNodeSize, Multi,
- set_slot_policy<Key>> {
- using value_type = Key;
- using slot_type = typename set_params::common_params::slot_type;
- using value_compare =
- typename set_params::common_params::original_key_compare;
- using is_map_container = std::false_type;
-
- template <typename V>
- static const V &key(const V &value) { return value; }
- static const Key &key(const slot_type *slot) { return *slot; }
- static const Key &key(slot_type *slot) { return *slot; }
};
// An adapter class that converts a lower-bound compare into an upper-bound
@@ -453,8 +476,8 @@
template <typename V>
struct SearchResult<V, false> {
SearchResult() {}
- explicit SearchResult(V value) : value(value) {}
- SearchResult(V value, MatchKind /*match*/) : value(value) {}
+ explicit SearchResult(V v) : value(v) {}
+ SearchResult(V v, MatchKind /*match*/) : value(v) {}
V value;
@@ -471,6 +494,7 @@
using field_type = typename Params::node_count_type;
using allocator_type = typename Params::allocator_type;
using slot_type = typename Params::slot_type;
+ using original_key_compare = typename Params::original_key_compare;
public:
using params_type = Params;
@@ -492,21 +516,28 @@
// - Otherwise, choose binary.
// TODO(ezb): Might make sense to add condition(s) based on node-size.
using use_linear_search = std::integral_constant<
- bool,
- has_linear_node_search_preference<key_compare>::value
- ? prefers_linear_node_search<key_compare>::value
- : has_linear_node_search_preference<key_type>::value
+ bool, has_linear_node_search_preference<original_key_compare>::value
+ ? prefers_linear_node_search<original_key_compare>::value
+ : has_linear_node_search_preference<key_type>::value
? prefers_linear_node_search<key_type>::value
: std::is_arithmetic<key_type>::value &&
- (std::is_same<std::less<key_type>, key_compare>::value ||
+ (std::is_same<std::less<key_type>,
+ original_key_compare>::value ||
std::is_same<std::greater<key_type>,
- key_compare>::value)>;
+ original_key_compare>::value)>;
// This class is organized by absl::container_internal::Layout as if it had
// the following structure:
// // A pointer to the node's parent.
// btree_node *parent;
//
+ // // When ABSL_BTREE_ENABLE_GENERATIONS is defined, we also have a
+ // // generation integer in order to check that when iterators are
+ // // used, they haven't been invalidated already. Only the generation on
+ // // the root is used, but we have one on each node because whether a node
+ // // is root or not can change.
+ // uint32_t generation;
+ //
// // The position of the node in the node's parent.
// field_type position;
// // The index of the first populated value in `values`.
@@ -542,123 +573,132 @@
btree_node(btree_node const &) = delete;
btree_node &operator=(btree_node const &) = delete;
- // Public for EmptyNodeType.
- constexpr static size_type Alignment() {
- static_assert(LeafLayout(1).Alignment() == InternalLayout().Alignment(),
- "Alignment of all nodes must be equal.");
- return InternalLayout().Alignment();
- }
-
protected:
btree_node() = default;
private:
- using layout_type = absl::container_internal::Layout<btree_node *, field_type,
- slot_type, btree_node *>;
+ using layout_type =
+ absl::container_internal::Layout<btree_node *, uint32_t, field_type,
+ slot_type, btree_node *>;
constexpr static size_type SizeWithNSlots(size_type n) {
- return layout_type(/*parent*/ 1,
- /*position, start, finish, max_count*/ 4,
- /*slots*/ n,
- /*children*/ 0)
+ return layout_type(
+ /*parent*/ 1,
+ /*generation*/ BtreeGenerationsEnabled() ? 1 : 0,
+ /*position, start, finish, max_count*/ 4,
+ /*slots*/ n,
+ /*children*/ 0)
.AllocSize();
}
- // A lower bound for the overhead of fields other than values in a leaf node.
+ // A lower bound for the overhead of fields other than slots in a leaf node.
constexpr static size_type MinimumOverhead() {
- return SizeWithNSlots(1) - sizeof(value_type);
+ return SizeWithNSlots(1) - sizeof(slot_type);
}
// Compute how many values we can fit onto a leaf node taking into account
// padding.
- constexpr static size_type NodeTargetSlots(const int begin, const int end) {
+ constexpr static size_type NodeTargetSlots(const size_type begin,
+ const size_type end) {
return begin == end ? begin
- : SizeWithNSlots((begin + end) / 2 + 1) >
- params_type::kTargetNodeSize
- ? NodeTargetSlots(begin, (begin + end) / 2)
- : NodeTargetSlots((begin + end) / 2 + 1, end);
+ : SizeWithNSlots((begin + end) / 2 + 1) >
+ params_type::kTargetNodeSize
+ ? NodeTargetSlots(begin, (begin + end) / 2)
+ : NodeTargetSlots((begin + end) / 2 + 1, end);
}
- enum {
- kTargetNodeSize = params_type::kTargetNodeSize,
- kNodeTargetSlots = NodeTargetSlots(0, params_type::kTargetNodeSize),
+ constexpr static size_type kTargetNodeSize = params_type::kTargetNodeSize;
+ constexpr static size_type kNodeTargetSlots =
+ NodeTargetSlots(0, kTargetNodeSize);
- // We need a minimum of 3 slots per internal node in order to perform
- // splitting (1 value for the two nodes involved in the split and 1 value
- // propagated to the parent as the delimiter for the split). For performance
- // reasons, we don't allow 3 slots-per-node due to bad worst case occupancy
- // of 1/3 (for a node, not a b-tree).
- kMinNodeSlots = 4,
+ // We need a minimum of 3 slots per internal node in order to perform
+ // splitting (1 value for the two nodes involved in the split and 1 value
+ // propagated to the parent as the delimiter for the split). For performance
+ // reasons, we don't allow 3 slots-per-node due to bad worst case occupancy of
+ // 1/3 (for a node, not a b-tree).
+ constexpr static size_type kMinNodeSlots = 4;
- kNodeSlots =
- kNodeTargetSlots >= kMinNodeSlots ? kNodeTargetSlots : kMinNodeSlots,
+ constexpr static size_type kNodeSlots =
+ kNodeTargetSlots >= kMinNodeSlots ? kNodeTargetSlots : kMinNodeSlots;
- // The node is internal (i.e. is not a leaf node) if and only if `max_count`
- // has this value.
- kInternalNodeMaxCount = 0,
- };
+ // The node is internal (i.e. is not a leaf node) if and only if `max_count`
+ // has this value.
+ constexpr static field_type kInternalNodeMaxCount = 0;
+ constexpr static layout_type Layout(const size_type slot_count,
+ const size_type child_count) {
+ return layout_type(
+ /*parent*/ 1,
+ /*generation*/ BtreeGenerationsEnabled() ? 1 : 0,
+ /*position, start, finish, max_count*/ 4,
+ /*slots*/ slot_count,
+ /*children*/ child_count);
+ }
// Leaves can have less than kNodeSlots values.
- constexpr static layout_type LeafLayout(const int slot_count = kNodeSlots) {
- return layout_type(/*parent*/ 1,
- /*position, start, finish, max_count*/ 4,
- /*slots*/ slot_count,
- /*children*/ 0);
+ constexpr static layout_type LeafLayout(
+ const size_type slot_count = kNodeSlots) {
+ return Layout(slot_count, 0);
}
constexpr static layout_type InternalLayout() {
- return layout_type(/*parent*/ 1,
- /*position, start, finish, max_count*/ 4,
- /*slots*/ kNodeSlots,
- /*children*/ kNodeSlots + 1);
+ return Layout(kNodeSlots, kNodeSlots + 1);
}
- constexpr static size_type LeafSize(const int slot_count = kNodeSlots) {
+ constexpr static size_type LeafSize(const size_type slot_count = kNodeSlots) {
return LeafLayout(slot_count).AllocSize();
}
constexpr static size_type InternalSize() {
return InternalLayout().AllocSize();
}
+ constexpr static size_type Alignment() {
+ static_assert(LeafLayout(1).Alignment() == InternalLayout().Alignment(),
+ "Alignment of all nodes must be equal.");
+ return InternalLayout().Alignment();
+ }
+
// N is the index of the type in the Layout definition.
// ElementType<N> is the Nth type in the Layout definition.
template <size_type N>
inline typename layout_type::template ElementType<N> *GetField() {
// We assert that we don't read from values that aren't there.
- assert(N < 3 || !leaf());
+ assert(N < 4 || is_internal());
return InternalLayout().template Pointer<N>(reinterpret_cast<char *>(this));
}
template <size_type N>
inline const typename layout_type::template ElementType<N> *GetField() const {
- assert(N < 3 || !leaf());
+ assert(N < 4 || is_internal());
return InternalLayout().template Pointer<N>(
reinterpret_cast<const char *>(this));
}
void set_parent(btree_node *p) { *GetField<0>() = p; }
- field_type &mutable_finish() { return GetField<1>()[2]; }
- slot_type *slot(int i) { return &GetField<2>()[i]; }
+ field_type &mutable_finish() { return GetField<2>()[2]; }
+ slot_type *slot(size_type i) { return &GetField<3>()[i]; }
slot_type *start_slot() { return slot(start()); }
slot_type *finish_slot() { return slot(finish()); }
- const slot_type *slot(int i) const { return &GetField<2>()[i]; }
- void set_position(field_type v) { GetField<1>()[0] = v; }
- void set_start(field_type v) { GetField<1>()[1] = v; }
- void set_finish(field_type v) { GetField<1>()[2] = v; }
+ const slot_type *slot(size_type i) const { return &GetField<3>()[i]; }
+ void set_position(field_type v) { GetField<2>()[0] = v; }
+ void set_start(field_type v) { GetField<2>()[1] = v; }
+ void set_finish(field_type v) { GetField<2>()[2] = v; }
// This method is only called by the node init methods.
- void set_max_count(field_type v) { GetField<1>()[3] = v; }
+ void set_max_count(field_type v) { GetField<2>()[3] = v; }
public:
// Whether this is a leaf node or not. This value doesn't change after the
// node is created.
- bool leaf() const { return GetField<1>()[3] != kInternalNodeMaxCount; }
+ bool is_leaf() const { return GetField<2>()[3] != kInternalNodeMaxCount; }
+ // Whether this is an internal node or not. This value doesn't change after
+ // the node is created.
+ bool is_internal() const { return !is_leaf(); }
// Getter for the position of this node in its parent.
- field_type position() const { return GetField<1>()[0]; }
+ field_type position() const { return GetField<2>()[0]; }
// Getter for the offset of the first value in the `values` array.
field_type start() const {
- // TODO(ezb): when floating storage is implemented, return GetField<1>()[1];
- assert(GetField<1>()[1] == 0);
+ // TODO(ezb): when floating storage is implemented, return GetField<2>()[1];
+ assert(GetField<2>()[1] == 0);
return 0;
}
// Getter for the offset after the last value in the `values` array.
- field_type finish() const { return GetField<1>()[2]; }
+ field_type finish() const { return GetField<2>()[2]; }
// Getters for the number of values stored in this node.
field_type count() const {
@@ -668,7 +708,7 @@
field_type max_count() const {
// Internal nodes have max_count==kInternalNodeMaxCount.
// Leaf nodes have max_count in [1, kNodeSlots].
- const field_type max_count = GetField<1>()[3];
+ const field_type max_count = GetField<2>()[3];
return max_count == field_type{kInternalNodeMaxCount}
? field_type{kNodeSlots}
: max_count;
@@ -679,58 +719,86 @@
// Getter for whether the node is the root of the tree. The parent of the
// root of the tree is the leftmost node in the tree which is guaranteed to
// be a leaf.
- bool is_root() const { return parent()->leaf(); }
+ bool is_root() const { return parent()->is_leaf(); }
void make_root() {
assert(parent()->is_root());
+ set_generation(parent()->generation());
set_parent(parent()->parent());
}
+ // Gets the root node's generation integer, which is the one used by the tree.
+ uint32_t *get_root_generation() const {
+ assert(BtreeGenerationsEnabled());
+ const btree_node *curr = this;
+ for (; !curr->is_root(); curr = curr->parent()) continue;
+ return const_cast<uint32_t *>(&curr->GetField<1>()[0]);
+ }
+
+ // Returns the generation for iterator validation.
+ uint32_t generation() const {
+ return BtreeGenerationsEnabled() ? *get_root_generation() : 0;
+ }
+ // Updates generation. Should only be called on a root node or during node
+ // initialization.
+ void set_generation(uint32_t generation) {
+ if (BtreeGenerationsEnabled()) GetField<1>()[0] = generation;
+ }
+ // Updates the generation. We do this whenever the node is mutated.
+ void next_generation() {
+ if (BtreeGenerationsEnabled()) ++*get_root_generation();
+ }
+
// Getters for the key/value at position i in the node.
- const key_type &key(int i) const { return params_type::key(slot(i)); }
- reference value(int i) { return params_type::element(slot(i)); }
- const_reference value(int i) const { return params_type::element(slot(i)); }
+ const key_type &key(size_type i) const { return params_type::key(slot(i)); }
+ reference value(size_type i) { return params_type::element(slot(i)); }
+ const_reference value(size_type i) const {
+ return params_type::element(slot(i));
+ }
// Getters/setter for the child at position i in the node.
- btree_node *child(int i) const { return GetField<3>()[i]; }
+ btree_node *child(field_type i) const { return GetField<4>()[i]; }
btree_node *start_child() const { return child(start()); }
- btree_node *&mutable_child(int i) { return GetField<3>()[i]; }
- void clear_child(int i) {
+ btree_node *&mutable_child(field_type i) { return GetField<4>()[i]; }
+ void clear_child(field_type i) {
absl::container_internal::SanitizerPoisonObject(&mutable_child(i));
}
- void set_child(int i, btree_node *c) {
+ void set_child_noupdate_position(field_type i, btree_node *c) {
absl::container_internal::SanitizerUnpoisonObject(&mutable_child(i));
mutable_child(i) = c;
+ }
+ void set_child(field_type i, btree_node *c) {
+ set_child_noupdate_position(i, c);
c->set_position(i);
}
- void init_child(int i, btree_node *c) {
+ void init_child(field_type i, btree_node *c) {
set_child(i, c);
c->set_parent(this);
}
// Returns the position of the first value whose key is not less than k.
template <typename K>
- SearchResult<int, is_key_compare_to::value> lower_bound(
+ SearchResult<size_type, is_key_compare_to::value> lower_bound(
const K &k, const key_compare &comp) const {
return use_linear_search::value ? linear_search(k, comp)
: binary_search(k, comp);
}
// Returns the position of the first value whose key is greater than k.
template <typename K>
- int upper_bound(const K &k, const key_compare &comp) const {
+ size_type upper_bound(const K &k, const key_compare &comp) const {
auto upper_compare = upper_bound_adapter<key_compare>(comp);
return use_linear_search::value ? linear_search(k, upper_compare).value
: binary_search(k, upper_compare).value;
}
template <typename K, typename Compare>
- SearchResult<int, btree_is_key_compare_to<Compare, key_type>::value>
+ SearchResult<size_type, btree_is_key_compare_to<Compare, key_type>::value>
linear_search(const K &k, const Compare &comp) const {
return linear_search_impl(k, start(), finish(), comp,
btree_is_key_compare_to<Compare, key_type>());
}
template <typename K, typename Compare>
- SearchResult<int, btree_is_key_compare_to<Compare, key_type>::value>
+ SearchResult<size_type, btree_is_key_compare_to<Compare, key_type>::value>
binary_search(const K &k, const Compare &comp) const {
return binary_search_impl(k, start(), finish(), comp,
btree_is_key_compare_to<Compare, key_type>());
@@ -739,8 +807,8 @@
// Returns the position of the first value whose key is not less than k using
// linear search performed using plain compare.
template <typename K, typename Compare>
- SearchResult<int, false> linear_search_impl(
- const K &k, int s, const int e, const Compare &comp,
+ SearchResult<size_type, false> linear_search_impl(
+ const K &k, size_type s, const size_type e, const Compare &comp,
std::false_type /* IsCompareTo */) const {
while (s < e) {
if (!comp(key(s), k)) {
@@ -748,14 +816,14 @@
}
++s;
}
- return SearchResult<int, false>{s};
+ return SearchResult<size_type, false>{s};
}
// Returns the position of the first value whose key is not less than k using
// linear search performed using compare-to.
template <typename K, typename Compare>
- SearchResult<int, true> linear_search_impl(
- const K &k, int s, const int e, const Compare &comp,
+ SearchResult<size_type, true> linear_search_impl(
+ const K &k, size_type s, const size_type e, const Compare &comp,
std::true_type /* IsCompareTo */) const {
while (s < e) {
const absl::weak_ordering c = comp(key(s), k);
@@ -772,30 +840,30 @@
// Returns the position of the first value whose key is not less than k using
// binary search performed using plain compare.
template <typename K, typename Compare>
- SearchResult<int, false> binary_search_impl(
- const K &k, int s, int e, const Compare &comp,
+ SearchResult<size_type, false> binary_search_impl(
+ const K &k, size_type s, size_type e, const Compare &comp,
std::false_type /* IsCompareTo */) const {
while (s != e) {
- const int mid = (s + e) >> 1;
+ const size_type mid = (s + e) >> 1;
if (comp(key(mid), k)) {
s = mid + 1;
} else {
e = mid;
}
}
- return SearchResult<int, false>{s};
+ return SearchResult<size_type, false>{s};
}
// Returns the position of the first value whose key is not less than k using
// binary search performed using compare-to.
template <typename K, typename CompareTo>
- SearchResult<int, true> binary_search_impl(
- const K &k, int s, int e, const CompareTo &comp,
+ SearchResult<size_type, true> binary_search_impl(
+ const K &k, size_type s, size_type e, const CompareTo &comp,
std::true_type /* IsCompareTo */) const {
if (params_type::template can_have_multiple_equivalent_keys<K>()) {
MatchKind exact_match = MatchKind::kNe;
while (s != e) {
- const int mid = (s + e) >> 1;
+ const size_type mid = (s + e) >> 1;
const absl::weak_ordering c = comp(key(mid), k);
if (c < 0) {
s = mid + 1;
@@ -812,7 +880,7 @@
return {s, exact_match};
} else { // Can't have multiple equivalent keys.
while (s != e) {
- const int mid = (s + e) >> 1;
+ const size_type mid = (s + e) >> 1;
const absl::weak_ordering c = comp(key(mid), k);
if (c < 0) {
s = mid + 1;
@@ -826,10 +894,42 @@
}
}
+ // Returns whether key i is ordered correctly with respect to the other keys
+ // in the node. The motivation here is to detect comparators that violate
+ // transitivity. Note: we only do comparisons of keys on this node rather than
+ // the whole tree so that this is constant time.
+ template <typename Compare>
+ bool is_ordered_correctly(field_type i, const Compare &comp) const {
+ if (std::is_base_of<BtreeTestOnlyCheckedCompareOptOutBase,
+ Compare>::value ||
+ params_type::kIsKeyCompareStringAdapted) {
+ return true;
+ }
+
+ const auto compare = [&](field_type a, field_type b) {
+ const absl::weak_ordering cmp =
+ compare_internal::do_three_way_comparison(comp, key(a), key(b));
+ return cmp < 0 ? -1 : cmp > 0 ? 1 : 0;
+ };
+ int cmp = -1;
+ constexpr bool kCanHaveEquivKeys =
+ params_type::template can_have_multiple_equivalent_keys<key_type>();
+ for (field_type j = start(); j < finish(); ++j) {
+ if (j == i) {
+ if (cmp > 0) return false;
+ continue;
+ }
+ int new_cmp = compare(j, i);
+ if (new_cmp < cmp || (!kCanHaveEquivKeys && new_cmp == 0)) return false;
+ cmp = new_cmp;
+ }
+ return true;
+ }
+
// Emplaces a value at position i, shifting all existing values and
// children at positions >= i to the right by 1.
template <typename... Args>
- void emplace_value(size_type i, allocator_type *alloc, Args &&... args);
+ void emplace_value(field_type i, allocator_type *alloc, Args &&...args);
// Removes the values at positions [i, i + to_erase), shifting all existing
// values and children after that range to the left by to_erase. Clears all
@@ -837,9 +937,9 @@
void remove_values(field_type i, field_type to_erase, allocator_type *alloc);
// Rebalances a node with its right sibling.
- void rebalance_right_to_left(int to_move, btree_node *right,
+ void rebalance_right_to_left(field_type to_move, btree_node *right,
allocator_type *alloc);
- void rebalance_left_to_right(int to_move, btree_node *right,
+ void rebalance_left_to_right(field_type to_move, btree_node *right,
allocator_type *alloc);
// Splits a node, moving a portion of the node's values to its right sibling.
@@ -850,17 +950,19 @@
void merge(btree_node *src, allocator_type *alloc);
// Node allocation/deletion routines.
- void init_leaf(btree_node *parent, int max_count) {
+ void init_leaf(field_type position, field_type max_count,
+ btree_node *parent) {
+ set_generation(0);
set_parent(parent);
- set_position(0);
+ set_position(position);
set_start(0);
set_finish(0);
set_max_count(max_count);
absl::container_internal::SanitizerPoisonMemoryRegion(
start_slot(), max_count * sizeof(slot_type));
}
- void init_internal(btree_node *parent) {
- init_leaf(parent, kNodeSlots);
+ void init_internal(field_type position, btree_node *parent) {
+ init_leaf(position, kNodeSlots, parent);
// Set `max_count` to a sentinel value to indicate that this node is
// internal.
set_max_count(kInternalNodeMaxCount);
@@ -870,6 +972,7 @@
static void deallocate(const size_type size, btree_node *node,
allocator_type *alloc) {
+ absl::container_internal::SanitizerUnpoisonMemoryRegion(node, size);
absl::container_internal::Deallocate<Alignment()>(alloc, node, size);
}
@@ -878,16 +981,19 @@
private:
template <typename... Args>
- void value_init(const field_type i, allocator_type *alloc, Args &&... args) {
+ void value_init(const field_type i, allocator_type *alloc, Args &&...args) {
+ next_generation();
absl::container_internal::SanitizerUnpoisonObject(slot(i));
params_type::construct(alloc, slot(i), std::forward<Args>(args)...);
}
void value_destroy(const field_type i, allocator_type *alloc) {
+ next_generation();
params_type::destroy(alloc, slot(i));
absl::container_internal::SanitizerPoisonObject(slot(i));
}
void value_destroy_n(const field_type i, const field_type n,
allocator_type *alloc) {
+ next_generation();
for (slot_type *s = slot(i), *end = slot(i + n); s != end; ++s) {
params_type::destroy(alloc, s);
absl::container_internal::SanitizerPoisonObject(s);
@@ -903,6 +1009,7 @@
// Transfers value from slot `src_i` in `src_node` to slot `dest_i` in `this`.
void transfer(const size_type dest_i, const size_type src_i,
btree_node *src_node, allocator_type *alloc) {
+ next_generation();
transfer(slot(dest_i), src_node->slot(src_i), alloc);
}
@@ -911,6 +1018,7 @@
void transfer_n(const size_type n, const size_type dest_i,
const size_type src_i, btree_node *src_node,
allocator_type *alloc) {
+ next_generation();
for (slot_type *src = src_node->slot(src_i), *end = src + n,
*dest = slot(dest_i);
src != end; ++src, ++dest) {
@@ -923,23 +1031,83 @@
void transfer_n_backward(const size_type n, const size_type dest_i,
const size_type src_i, btree_node *src_node,
allocator_type *alloc) {
- for (slot_type *src = src_node->slot(src_i + n - 1), *end = src - n,
- *dest = slot(dest_i + n - 1);
+ next_generation();
+ for (slot_type *src = src_node->slot(src_i + n), *end = src - n,
+ *dest = slot(dest_i + n);
src != end; --src, --dest) {
- transfer(dest, src, alloc);
+ // If we modified the loop index calculations above to avoid the -1s here,
+ // it would result in UB in the computation of `end` (and possibly `src`
+ // as well, if n == 0), since slot() is effectively an array index and it
+ // is UB to compute the address of any out-of-bounds array element except
+ // for one-past-the-end.
+ transfer(dest - 1, src - 1, alloc);
}
}
template <typename P>
friend class btree;
template <typename N, typename R, typename P>
- friend struct btree_iterator;
+ friend class btree_iterator;
friend class BtreeNodePeer;
+ friend struct btree_access;
};
-template <typename Node, typename Reference, typename Pointer>
-struct btree_iterator {
+template <typename Node>
+bool AreNodesFromSameContainer(const Node *node_a, const Node *node_b) {
+ // If either node is null, then give up on checking whether they're from the
+ // same container. (If exactly one is null, then we'll trigger the
+ // default-constructed assert in Equals.)
+ if (node_a == nullptr || node_b == nullptr) return true;
+ while (!node_a->is_root()) node_a = node_a->parent();
+ while (!node_b->is_root()) node_b = node_b->parent();
+ return node_a == node_b;
+}
+
+class btree_iterator_generation_info_enabled {
+ public:
+ explicit btree_iterator_generation_info_enabled(uint32_t g)
+ : generation_(g) {}
+
+ // Updates the generation. For use internally right before we return an
+ // iterator to the user.
+ template <typename Node>
+ void update_generation(const Node *node) {
+ if (node != nullptr) generation_ = node->generation();
+ }
+ uint32_t generation() const { return generation_; }
+
+ template <typename Node>
+ void assert_valid_generation(const Node *node) const {
+ if (node != nullptr && node->generation() != generation_) {
+ ABSL_INTERNAL_LOG(
+ FATAL,
+ "Attempting to use an invalidated iterator. The corresponding b-tree "
+ "container has been mutated since this iterator was constructed.");
+ }
+ }
+
private:
+ // Used to check that the iterator hasn't been invalidated.
+ uint32_t generation_;
+};
+
+class btree_iterator_generation_info_disabled {
+ public:
+ explicit btree_iterator_generation_info_disabled(uint32_t) {}
+ static void update_generation(const void *) {}
+ static uint32_t generation() { return 0; }
+ static void assert_valid_generation(const void *) {}
+};
+
+#ifdef ABSL_BTREE_ENABLE_GENERATIONS
+using btree_iterator_generation_info = btree_iterator_generation_info_enabled;
+#else
+using btree_iterator_generation_info = btree_iterator_generation_info_disabled;
+#endif
+
+template <typename Node, typename Reference, typename Pointer>
+class btree_iterator : private btree_iterator_generation_info {
+ using field_type = typename Node::field_type;
using key_type = typename Node::key_type;
using size_type = typename Node::size_type;
using params_type = typename Node::params_type;
@@ -954,8 +1122,11 @@
using const_reference = typename params_type::const_reference;
using slot_type = typename params_type::slot_type;
- using iterator =
- btree_iterator<normal_node, normal_reference, normal_pointer>;
+ // In sets, all iterators are const.
+ using iterator = absl::conditional_t<
+ is_map_container::value,
+ btree_iterator<normal_node, normal_reference, normal_pointer>,
+ btree_iterator<normal_node, const_reference, const_pointer>>;
using const_iterator =
btree_iterator<const_node, const_reference, const_pointer>;
@@ -967,9 +1138,13 @@
using reference = Reference;
using iterator_category = std::bidirectional_iterator_tag;
- btree_iterator() : node(nullptr), position(-1) {}
- explicit btree_iterator(Node *n) : node(n), position(n->start()) {}
- btree_iterator(Node *n, int p) : node(n), position(p) {}
+ btree_iterator() : btree_iterator(nullptr, -1) {}
+ explicit btree_iterator(Node *n) : btree_iterator(n, n->start()) {}
+ btree_iterator(Node *n, int p)
+ : btree_iterator_generation_info(n != nullptr ? n->generation()
+ : ~uint32_t{}),
+ node_(n),
+ position_(p) {}
// NOTE: this SFINAE allows for implicit conversions from iterator to
// const_iterator, but it specifically avoids hiding the copy constructor so
@@ -980,58 +1155,43 @@
std::is_same<btree_iterator, const_iterator>::value,
int> = 0>
btree_iterator(const btree_iterator<N, R, P> other) // NOLINT
- : node(other.node), position(other.position) {}
+ : btree_iterator_generation_info(other),
+ node_(other.node_),
+ position_(other.position_) {}
- private:
- // This SFINAE allows explicit conversions from const_iterator to
- // iterator, but also avoids hiding the copy constructor.
- // NOTE: the const_cast is safe because this constructor is only called by
- // non-const methods and the container owns the nodes.
- template <typename N, typename R, typename P,
- absl::enable_if_t<
- std::is_same<btree_iterator<N, R, P>, const_iterator>::value &&
- std::is_same<btree_iterator, iterator>::value,
- int> = 0>
- explicit btree_iterator(const btree_iterator<N, R, P> other)
- : node(const_cast<node_type *>(other.node)), position(other.position) {}
-
- // Increment/decrement the iterator.
- void increment() {
- if (node->leaf() && ++position < node->finish()) {
- return;
- }
- increment_slow();
- }
- void increment_slow();
-
- void decrement() {
- if (node->leaf() && --position >= node->start()) {
- return;
- }
- decrement_slow();
- }
- void decrement_slow();
-
- public:
bool operator==(const iterator &other) const {
- return node == other.node && position == other.position;
+ return Equals(other);
}
bool operator==(const const_iterator &other) const {
- return node == other.node && position == other.position;
+ return Equals(other);
}
bool operator!=(const iterator &other) const {
- return node != other.node || position != other.position;
+ return !Equals(other);
}
bool operator!=(const const_iterator &other) const {
- return node != other.node || position != other.position;
+ return !Equals(other);
+ }
+
+ // Returns n such that n calls to ++other yields *this.
+ // Precondition: n exists.
+ difference_type operator-(const_iterator other) const {
+ if (node_ == other.node_) {
+ if (node_->is_leaf()) return position_ - other.position_;
+ if (position_ == other.position_) return 0;
+ }
+ return distance_slow(other);
}
// Accessors for the key/value the iterator is pointing at.
reference operator*() const {
- ABSL_HARDENING_ASSERT(node != nullptr);
- ABSL_HARDENING_ASSERT(node->start() <= position);
- ABSL_HARDENING_ASSERT(node->finish() > position);
- return node->value(position);
+ ABSL_HARDENING_ASSERT(node_ != nullptr);
+ assert_valid_generation(node_);
+ ABSL_HARDENING_ASSERT(position_ >= node_->start());
+ if (position_ >= node_->finish()) {
+ ABSL_HARDENING_ASSERT(!IsEndIterator() && "Dereferencing end() iterator");
+ ABSL_HARDENING_ASSERT(position_ < node_->finish());
+ }
+ return node_->value(static_cast<field_type>(position_));
}
pointer operator->() const { return &operator*(); }
@@ -1069,30 +1229,104 @@
friend class btree_multiset_container;
template <typename TreeType, typename CheckerType>
friend class base_checker;
+ friend struct btree_access;
- const key_type &key() const { return node->key(position); }
- slot_type *slot() { return node->slot(position); }
+ // This SFINAE allows explicit conversions from const_iterator to
+ // iterator, but also avoids hiding the copy constructor.
+ // NOTE: the const_cast is safe because this constructor is only called by
+ // non-const methods and the container owns the nodes.
+ template <typename N, typename R, typename P,
+ absl::enable_if_t<
+ std::is_same<btree_iterator<N, R, P>, const_iterator>::value &&
+ std::is_same<btree_iterator, iterator>::value,
+ int> = 0>
+ explicit btree_iterator(const btree_iterator<N, R, P> other)
+ : btree_iterator_generation_info(other.generation()),
+ node_(const_cast<node_type *>(other.node_)),
+ position_(other.position_) {}
+
+ bool Equals(const const_iterator other) const {
+ ABSL_HARDENING_ASSERT(((node_ == nullptr && other.node_ == nullptr) ||
+ (node_ != nullptr && other.node_ != nullptr)) &&
+ "Comparing default-constructed iterator with "
+ "non-default-constructed iterator.");
+ // Note: we use assert instead of ABSL_HARDENING_ASSERT here because this
+ // changes the complexity of Equals from O(1) to O(log(N) + log(M)) where
+ // N/M are sizes of the containers containing node_/other.node_.
+ assert(AreNodesFromSameContainer(node_, other.node_) &&
+ "Comparing iterators from different containers.");
+ assert_valid_generation(node_);
+ other.assert_valid_generation(other.node_);
+ return node_ == other.node_ && position_ == other.position_;
+ }
+
+ bool IsEndIterator() const {
+ if (position_ != node_->finish()) return false;
+ node_type *node = node_;
+ while (!node->is_root()) {
+ if (node->position() != node->parent()->finish()) return false;
+ node = node->parent();
+ }
+ return true;
+ }
+
+ // Returns n such that n calls to ++other yields *this.
+ // Precondition: n exists && (this->node_ != other.node_ ||
+ // !this->node_->is_leaf() || this->position_ != other.position_).
+ difference_type distance_slow(const_iterator other) const;
+
+ // Increment/decrement the iterator.
+ void increment() {
+ assert_valid_generation(node_);
+ if (node_->is_leaf() && ++position_ < node_->finish()) {
+ return;
+ }
+ increment_slow();
+ }
+ void increment_slow();
+
+ void decrement() {
+ assert_valid_generation(node_);
+ if (node_->is_leaf() && --position_ >= node_->start()) {
+ return;
+ }
+ decrement_slow();
+ }
+ void decrement_slow();
+
+ const key_type &key() const {
+ return node_->key(static_cast<size_type>(position_));
+ }
+ decltype(std::declval<Node *>()->slot(0)) slot() {
+ return node_->slot(static_cast<size_type>(position_));
+ }
+
+ void update_generation() {
+ btree_iterator_generation_info::update_generation(node_);
+ }
// The node in the tree the iterator is pointing at.
- Node *node;
+ Node *node_;
// The position within the node of the tree the iterator is pointing at.
// NOTE: this is an int rather than a field_type because iterators can point
// to invalid positions (such as -1) in certain circumstances.
- int position;
+ int position_;
};
template <typename Params>
class btree {
using node_type = btree_node<Params>;
using is_key_compare_to = typename Params::is_key_compare_to;
- using init_type = typename Params::init_type;
using field_type = typename node_type::field_type;
// We use a static empty node for the root/leftmost/rightmost of empty btrees
// in order to avoid branching in begin()/end().
- struct alignas(node_type::Alignment()) EmptyNodeType : node_type {
+ struct EmptyNodeType : node_type {
using field_type = typename node_type::field_type;
node_type *parent;
+#ifdef ABSL_BTREE_ENABLE_GENERATIONS
+ uint32_t generation = 0;
+#endif
field_type position = 0;
field_type start = 0;
field_type finish = 0;
@@ -1100,25 +1334,12 @@
// as a leaf node). max_count() is never called when the tree is empty.
field_type max_count = node_type::kInternalNodeMaxCount + 1;
-#ifdef _MSC_VER
- // MSVC has constexpr code generations bugs here.
- EmptyNodeType() : parent(this) {}
-#else
- constexpr EmptyNodeType(node_type *p) : parent(p) {}
-#endif
+ constexpr EmptyNodeType() : parent(this) {}
};
static node_type *EmptyNode() {
-#ifdef _MSC_VER
- static EmptyNodeType *empty_node = new EmptyNodeType;
- // This assert fails on some other construction methods.
- assert(empty_node->parent == empty_node);
- return empty_node;
-#else
- static constexpr EmptyNodeType empty_node(
- const_cast<EmptyNodeType *>(&empty_node));
+ alignas(node_type::Alignment()) static constexpr EmptyNodeType empty_node;
return const_cast<EmptyNodeType *>(&empty_node);
-#endif
}
enum : uint32_t {
@@ -1166,14 +1387,6 @@
using slot_type = typename Params::slot_type;
private:
- // For use in copy_or_move_values_in_order.
- const value_type &maybe_move_from_iterator(const_iterator it) { return *it; }
- value_type &&maybe_move_from_iterator(iterator it) {
- // This is a destructive operation on the other container so it's safe for
- // us to const_cast and move from the keys here even if it's a set.
- return std::move(const_cast<value_type &>(*it));
- }
-
// Copies or moves (depending on the template parameter) the values in
// other into this btree in their order in other. This btree must be empty
// before this method is called. This method is used in copy construction,
@@ -1186,7 +1399,7 @@
public:
btree(const key_compare &comp, const allocator_type &alloc)
- : root_(comp, alloc, EmptyNode()), rightmost_(EmptyNode()), size_(0) {}
+ : root_(EmptyNode()), rightmost_(comp, alloc, EmptyNode()), size_(0) {}
btree(const btree &other) : btree(other, other.allocator()) {}
btree(const btree &other, const allocator_type &alloc)
@@ -1194,10 +1407,10 @@
copy_or_move_values_in_order(other);
}
btree(btree &&other) noexcept
- : root_(std::move(other.root_)),
- rightmost_(absl::exchange(other.rightmost_, EmptyNode())),
- size_(absl::exchange(other.size_, 0)) {
- other.mutable_root() = EmptyNode();
+ : root_(absl::exchange(other.root_, EmptyNode())),
+ rightmost_(std::move(other.rightmost_)),
+ size_(absl::exchange(other.size_, 0u)) {
+ other.mutable_rightmost() = EmptyNode();
}
btree(btree &&other, const allocator_type &alloc)
: btree(other.key_comp(), alloc) {
@@ -1222,9 +1435,9 @@
iterator begin() { return iterator(leftmost()); }
const_iterator begin() const { return const_iterator(leftmost()); }
- iterator end() { return iterator(rightmost_, rightmost_->finish()); }
+ iterator end() { return iterator(rightmost(), rightmost()->finish()); }
const_iterator end() const {
- return const_iterator(rightmost_, rightmost_->finish());
+ return const_iterator(rightmost(), rightmost()->finish());
}
reverse_iterator rbegin() { return reverse_iterator(end()); }
const_reverse_iterator rbegin() const {
@@ -1275,7 +1488,7 @@
// Requirement: if `key` already exists in the btree, does not consume `args`.
// Requirement: `key` is never referenced after consuming `args`.
template <typename K, typename... Args>
- std::pair<iterator, bool> insert_unique(const K &key, Args &&... args);
+ std::pair<iterator, bool> insert_unique(const K &key, Args &&...args);
// Inserts with hint. Checks to see if the value should be placed immediately
// before `position` in the tree. If so, then the insertion will take
@@ -1284,9 +1497,8 @@
// Requirement: if `key` already exists in the btree, does not consume `args`.
// Requirement: `key` is never referenced after consuming `args`.
template <typename K, typename... Args>
- std::pair<iterator, bool> insert_hint_unique(iterator position,
- const K &key,
- Args &&... args);
+ std::pair<iterator, bool> insert_hint_unique(iterator position, const K &key,
+ Args &&...args);
// Insert a range of values into the btree.
// Note: the first overload avoids constructing a value_type if the key
@@ -1320,7 +1532,8 @@
// Insert a range of values into the btree.
template <typename InputIterator>
- void insert_iterator_multi(InputIterator b, InputIterator e);
+ void insert_iterator_multi(InputIterator b,
+ InputIterator e);
// Erase the specified iterator from the btree. The iterator must be valid
// (i.e. not equal to end()). Return an iterator pointing to the node after
@@ -1350,7 +1563,7 @@
void swap(btree &other);
const key_compare &key_comp() const noexcept {
- return root_.template get<0>();
+ return rightmost_.template get<0>();
}
template <typename K1, typename K2>
bool compare_keys(const K1 &a, const K2 &b) const {
@@ -1397,6 +1610,7 @@
}
// The total number of bytes used by the btree.
+ // TODO(b/169338300): update to support node_btree_*.
size_type bytes_used() const {
node_stats stats = internal_stats(root());
if (stats.leaf_nodes == 1 && stats.internal_nodes == 0) {
@@ -1412,8 +1626,7 @@
static double average_bytes_per_value() {
// The expected number of values per node with random insertion order is the
// average of the maximum and minimum numbers of values per node.
- const double expected_values_per_node =
- (kNodeSlots + kMinNodeValues) / 2.0;
+ const double expected_values_per_node = (kNodeSlots + kMinNodeValues) / 2.0;
return node_type::LeafSize() / expected_values_per_node;
}
@@ -1440,11 +1653,20 @@
allocator_type get_allocator() const { return allocator(); }
private:
+ friend struct btree_access;
+
// Internal accessor routines.
- node_type *root() { return root_.template get<2>(); }
- const node_type *root() const { return root_.template get<2>(); }
- node_type *&mutable_root() noexcept { return root_.template get<2>(); }
- key_compare *mutable_key_comp() noexcept { return &root_.template get<0>(); }
+ node_type *root() { return root_; }
+ const node_type *root() const { return root_; }
+ node_type *&mutable_root() noexcept { return root_; }
+ node_type *rightmost() { return rightmost_.template get<2>(); }
+ const node_type *rightmost() const { return rightmost_.template get<2>(); }
+ node_type *&mutable_rightmost() noexcept {
+ return rightmost_.template get<2>();
+ }
+ key_compare *mutable_key_comp() noexcept {
+ return &rightmost_.template get<0>();
+ }
// The leftmost node is stored as the parent of the root node.
node_type *leftmost() { return root()->parent(); }
@@ -1452,34 +1674,34 @@
// Allocator routines.
allocator_type *mutable_allocator() noexcept {
- return &root_.template get<1>();
+ return &rightmost_.template get<1>();
}
const allocator_type &allocator() const noexcept {
- return root_.template get<1>();
+ return rightmost_.template get<1>();
}
// Allocates a correctly aligned node of at least size bytes using the
// allocator.
- node_type *allocate(const size_type size) {
+ node_type *allocate(size_type size) {
return reinterpret_cast<node_type *>(
absl::container_internal::Allocate<node_type::Alignment()>(
mutable_allocator(), size));
}
// Node creation/deletion routines.
- node_type *new_internal_node(node_type *parent) {
+ node_type *new_internal_node(field_type position, node_type *parent) {
node_type *n = allocate(node_type::InternalSize());
- n->init_internal(parent);
+ n->init_internal(position, parent);
return n;
}
- node_type *new_leaf_node(node_type *parent) {
+ node_type *new_leaf_node(field_type position, node_type *parent) {
node_type *n = allocate(node_type::LeafSize());
- n->init_leaf(parent, kNodeSlots);
+ n->init_leaf(position, kNodeSlots, parent);
return n;
}
- node_type *new_leaf_root_node(const int max_count) {
+ node_type *new_leaf_root_node(field_type max_count) {
node_type *n = allocate(node_type::LeafSize(max_count));
- n->init_leaf(/*parent=*/n, max_count);
+ n->init_leaf(/*position=*/0, max_count, /*parent=*/n);
return n;
}
@@ -1503,22 +1725,21 @@
void try_shrink();
iterator internal_end(iterator iter) {
- return iter.node != nullptr ? iter : end();
+ return iter.node_ != nullptr ? iter : end();
}
const_iterator internal_end(const_iterator iter) const {
- return iter.node != nullptr ? iter : end();
+ return iter.node_ != nullptr ? iter : end();
}
// Emplaces a value into the btree immediately before iter. Requires that
// key(v) <= iter.key() and (--iter).key() <= key(v).
template <typename... Args>
- iterator internal_emplace(iterator iter, Args &&... args);
+ iterator internal_emplace(iterator iter, Args &&...args);
// Returns an iterator pointing to the first value >= the value "iter" is
// pointing at. Note that "iter" might be pointing to an invalid location such
- // as iter.position == iter.node->finish(). This routine simply moves iter up
- // in the tree to a valid location.
- // Requires: iter.node is non-null.
+ // as iter.position_ == iter.node_->finish(). This routine simply moves iter
+ // up in the tree to a valid location. Requires: iter.node_ is non-null.
template <typename IterType>
static IterType internal_last(IterType iter);
@@ -1546,15 +1767,15 @@
iterator internal_find(const K &key) const;
// Verifies the tree structure of node.
- int internal_verify(const node_type *node, const key_type *lo,
- const key_type *hi) const;
+ size_type internal_verify(const node_type *node, const key_type *lo,
+ const key_type *hi) const;
node_stats internal_stats(const node_type *node) const {
// The root can be a static empty node.
if (node == nullptr || (node == root() && empty())) {
return node_stats(0, 0);
}
- if (node->leaf()) {
+ if (node->is_leaf()) {
return node_stats(1, 0);
}
node_stats res(0, 1);
@@ -1564,15 +1785,14 @@
return res;
}
- // We use compressed tuple in order to save space because key_compare and
- // allocator_type are usually empty.
- absl::container_internal::CompressedTuple<key_compare, allocator_type,
- node_type *>
- root_;
+ node_type *root_;
// A pointer to the rightmost node. Note that the leftmost node is stored as
- // the root's parent.
- node_type *rightmost_;
+ // the root's parent. We use compressed tuple in order to save space because
+ // key_compare and allocator_type are usually empty.
+ absl::container_internal::CompressedTuple<key_compare, allocator_type,
+ node_type *>
+ rightmost_;
// Number of values.
size_type size_;
@@ -1582,9 +1802,9 @@
// btree_node methods
template <typename P>
template <typename... Args>
-inline void btree_node<P>::emplace_value(const size_type i,
+inline void btree_node<P>::emplace_value(const field_type i,
allocator_type *alloc,
- Args &&... args) {
+ Args &&...args) {
assert(i >= start());
assert(i <= finish());
// Shift old values to create space for new value and then construct it in
@@ -1593,11 +1813,11 @@
transfer_n_backward(finish() - i, /*dest_i=*/i + 1, /*src_i=*/i, this,
alloc);
}
- value_init(i, alloc, std::forward<Args>(args)...);
+ value_init(static_cast<field_type>(i), alloc, std::forward<Args>(args)...);
set_finish(finish() + 1);
- if (!leaf() && finish() > i + 1) {
- for (int j = finish(); j > i + 1; --j) {
+ if (is_internal() && finish() > i + 1) {
+ for (field_type j = finish(); j > i + 1; --j) {
set_child(j, child(j - 1));
}
clear_child(i + 1);
@@ -1614,13 +1834,13 @@
const field_type src_i = i + to_erase;
transfer_n(orig_finish - src_i, i, src_i, this, alloc);
- if (!leaf()) {
+ if (is_internal()) {
// Delete all children between begin and end.
- for (int j = 0; j < to_erase; ++j) {
+ for (field_type j = 0; j < to_erase; ++j) {
clear_and_delete(child(i + j + 1), alloc);
}
// Rotate children after end into new positions.
- for (int j = i + to_erase + 1; j <= orig_finish; ++j) {
+ for (field_type j = i + to_erase + 1; j <= orig_finish; ++j) {
set_child(j - to_erase, child(j));
clear_child(j);
}
@@ -1629,7 +1849,7 @@
}
template <typename P>
-void btree_node<P>::rebalance_right_to_left(const int to_move,
+void btree_node<P>::rebalance_right_to_left(field_type to_move,
btree_node *right,
allocator_type *alloc) {
assert(parent() == right->parent());
@@ -1651,12 +1871,12 @@
right->transfer_n(right->count() - to_move, right->start(),
right->start() + to_move, right, alloc);
- if (!leaf()) {
+ if (is_internal()) {
// Move the child pointers from the right to the left node.
- for (int i = 0; i < to_move; ++i) {
+ for (field_type i = 0; i < to_move; ++i) {
init_child(finish() + i + 1, right->child(i));
}
- for (int i = right->start(); i <= right->finish() - to_move; ++i) {
+ for (field_type i = right->start(); i <= right->finish() - to_move; ++i) {
assert(i + to_move <= right->max_count());
right->init_child(i, right->child(i + to_move));
right->clear_child(i + to_move);
@@ -1669,7 +1889,7 @@
}
template <typename P>
-void btree_node<P>::rebalance_left_to_right(const int to_move,
+void btree_node<P>::rebalance_left_to_right(field_type to_move,
btree_node *right,
allocator_type *alloc) {
assert(parent() == right->parent());
@@ -1698,13 +1918,13 @@
// 4) Move the new delimiting value to the parent from the left node.
parent()->transfer(position(), finish() - to_move, this, alloc);
- if (!leaf()) {
+ if (is_internal()) {
// Move the child pointers from the left to the right node.
- for (int i = right->finish(); i >= right->start(); --i) {
- right->init_child(i + to_move, right->child(i));
- right->clear_child(i);
+ for (field_type i = right->finish() + 1; i > right->start(); --i) {
+ right->init_child(i - 1 + to_move, right->child(i - 1));
+ right->clear_child(i - 1);
}
- for (int i = 1; i <= to_move; ++i) {
+ for (field_type i = 1; i <= to_move; ++i) {
right->init_child(i - 1, child(finish() - to_move + i));
clear_child(finish() - to_move + i);
}
@@ -1720,6 +1940,8 @@
allocator_type *alloc) {
assert(dest->count() == 0);
assert(max_count() == kNodeSlots);
+ assert(position() + 1 == dest->position());
+ assert(parent() == dest->parent());
// We bias the split based on the position being inserted. If we're
// inserting at the beginning of the left node then bias the split to put
@@ -1742,10 +1964,10 @@
--mutable_finish();
parent()->emplace_value(position(), alloc, finish_slot());
value_destroy(finish(), alloc);
- parent()->init_child(position() + 1, dest);
+ parent()->set_child_noupdate_position(position() + 1, dest);
- if (!leaf()) {
- for (int i = dest->start(), j = finish() + 1; i <= dest->finish();
+ if (is_internal()) {
+ for (field_type i = dest->start(), j = finish() + 1; i <= dest->finish();
++i, ++j) {
assert(child(j) != nullptr);
dest->init_child(i, child(j));
@@ -1765,9 +1987,10 @@
// Move the values from the right to the left node.
transfer_n(src->count(), finish() + 1, src->start(), src, alloc);
- if (!leaf()) {
+ if (is_internal()) {
// Move the child pointers from the right to the left node.
- for (int i = src->start(), j = finish() + 1; i <= src->finish(); ++i, ++j) {
+ for (field_type i = src->start(), j = finish() + 1; i <= src->finish();
+ ++i, ++j) {
init_child(j, src->child(i));
src->clear_child(i);
}
@@ -1783,7 +2006,7 @@
template <typename P>
void btree_node<P>::clear_and_delete(btree_node *node, allocator_type *alloc) {
- if (node->leaf()) {
+ if (node->is_leaf()) {
node->value_destroy_n(node->start(), node->count(), alloc);
deallocate(LeafSize(node->max_count()), node, alloc);
return;
@@ -1797,24 +2020,35 @@
btree_node *delete_root_parent = node->parent();
// Navigate to the leftmost leaf under node, and then delete upwards.
- while (!node->leaf()) node = node->start_child();
- // Use `int` because `pos` needs to be able to hold `kNodeSlots+1`, which
- // isn't guaranteed to be a valid `field_type`.
- int pos = node->position();
+ while (node->is_internal()) node = node->start_child();
+#ifdef ABSL_BTREE_ENABLE_GENERATIONS
+ // When generations are enabled, we delete the leftmost leaf last in case it's
+ // the parent of the root and we need to check whether it's a leaf before we
+ // can update the root's generation.
+ // TODO(ezb): if we change btree_node::is_root to check a bool inside the node
+ // instead of checking whether the parent is a leaf, we can remove this logic.
+ btree_node *leftmost_leaf = node;
+#endif
+ // Use `size_type` because `pos` needs to be able to hold `kNodeSlots+1`,
+ // which isn't guaranteed to be a valid `field_type`.
+ size_type pos = node->position();
btree_node *parent = node->parent();
for (;;) {
// In each iteration of the next loop, we delete one leaf node and go right.
assert(pos <= parent->finish());
do {
- node = parent->child(pos);
- if (!node->leaf()) {
+ node = parent->child(static_cast<field_type>(pos));
+ if (node->is_internal()) {
// Navigate to the leftmost leaf under node.
- while (!node->leaf()) node = node->start_child();
+ while (node->is_internal()) node = node->start_child();
pos = node->position();
parent = node->parent();
}
node->value_destroy_n(node->start(), node->count(), alloc);
- deallocate(LeafSize(node->max_count()), node, alloc);
+#ifdef ABSL_BTREE_ENABLE_GENERATIONS
+ if (leftmost_leaf != node)
+#endif
+ deallocate(LeafSize(node->max_count()), node, alloc);
++pos;
} while (pos <= parent->finish());
@@ -1826,7 +2060,12 @@
parent = node->parent();
node->value_destroy_n(node->start(), node->count(), alloc);
deallocate(InternalSize(), node, alloc);
- if (parent == delete_root_parent) return;
+ if (parent == delete_root_parent) {
+#ifdef ABSL_BTREE_ENABLE_GENERATIONS
+ deallocate(LeafSize(leftmost_leaf->max_count()), leftmost_leaf, alloc);
+#endif
+ return;
+ }
++pos;
} while (pos > parent->finish());
}
@@ -1834,51 +2073,109 @@
////
// btree_iterator methods
+
+// Note: the implementation here is based on btree_node::clear_and_delete.
+template <typename N, typename R, typename P>
+auto btree_iterator<N, R, P>::distance_slow(const_iterator other) const
+ -> difference_type {
+ const_iterator begin = other;
+ const_iterator end = *this;
+ assert(begin.node_ != end.node_ || !begin.node_->is_leaf() ||
+ begin.position_ != end.position_);
+
+ const node_type *node = begin.node_;
+ // We need to compensate for double counting if begin.node_ is a leaf node.
+ difference_type count = node->is_leaf() ? -begin.position_ : 0;
+
+ // First navigate to the leftmost leaf node past begin.
+ if (node->is_internal()) {
+ ++count;
+ node = node->child(begin.position_ + 1);
+ }
+ while (node->is_internal()) node = node->start_child();
+
+ // Use `size_type` because `pos` needs to be able to hold `kNodeSlots+1`,
+ // which isn't guaranteed to be a valid `field_type`.
+ size_type pos = node->position();
+ const node_type *parent = node->parent();
+ for (;;) {
+ // In each iteration of the next loop, we count one leaf node and go right.
+ assert(pos <= parent->finish());
+ do {
+ node = parent->child(static_cast<field_type>(pos));
+ if (node->is_internal()) {
+ // Navigate to the leftmost leaf under node.
+ while (node->is_internal()) node = node->start_child();
+ pos = node->position();
+ parent = node->parent();
+ }
+ if (node == end.node_) return count + end.position_;
+ if (parent == end.node_ && pos == static_cast<size_type>(end.position_))
+ return count + node->count();
+ // +1 is for the next internal node value.
+ count += node->count() + 1;
+ ++pos;
+ } while (pos <= parent->finish());
+
+ // Once we've counted all children of parent, go up/right.
+ assert(pos > parent->finish());
+ do {
+ node = parent;
+ pos = node->position();
+ parent = node->parent();
+ // -1 because we counted the value at end and shouldn't.
+ if (parent == end.node_ && pos == static_cast<size_type>(end.position_))
+ return count - 1;
+ ++pos;
+ } while (pos > parent->finish());
+ }
+}
+
template <typename N, typename R, typename P>
void btree_iterator<N, R, P>::increment_slow() {
- if (node->leaf()) {
- assert(position >= node->finish());
+ if (node_->is_leaf()) {
+ assert(position_ >= node_->finish());
btree_iterator save(*this);
- while (position == node->finish() && !node->is_root()) {
- assert(node->parent()->child(node->position()) == node);
- position = node->position();
- node = node->parent();
+ while (position_ == node_->finish() && !node_->is_root()) {
+ assert(node_->parent()->child(node_->position()) == node_);
+ position_ = node_->position();
+ node_ = node_->parent();
}
// TODO(ezb): assert we aren't incrementing end() instead of handling.
- if (position == node->finish()) {
+ if (position_ == node_->finish()) {
*this = save;
}
} else {
- assert(position < node->finish());
- node = node->child(position + 1);
- while (!node->leaf()) {
- node = node->start_child();
+ assert(position_ < node_->finish());
+ node_ = node_->child(static_cast<field_type>(position_ + 1));
+ while (node_->is_internal()) {
+ node_ = node_->start_child();
}
- position = node->start();
+ position_ = node_->start();
}
}
template <typename N, typename R, typename P>
void btree_iterator<N, R, P>::decrement_slow() {
- if (node->leaf()) {
- assert(position <= -1);
+ if (node_->is_leaf()) {
+ assert(position_ <= -1);
btree_iterator save(*this);
- while (position < node->start() && !node->is_root()) {
- assert(node->parent()->child(node->position()) == node);
- position = node->position() - 1;
- node = node->parent();
+ while (position_ < node_->start() && !node_->is_root()) {
+ assert(node_->parent()->child(node_->position()) == node_);
+ position_ = node_->position() - 1;
+ node_ = node_->parent();
}
// TODO(ezb): assert we aren't decrementing begin() instead of handling.
- if (position < node->start()) {
+ if (position_ < node_->start()) {
*this = save;
}
} else {
- assert(position >= node->start());
- node = node->child(position);
- while (!node->leaf()) {
- node = node->child(node->finish());
+ assert(position_ >= node_->start());
+ node_ = node_->child(static_cast<field_type>(position_));
+ while (node_->is_internal()) {
+ node_ = node_->child(node_->finish());
}
- position = node->finish() - 1;
+ position_ = node_->finish() - 1;
}
}
@@ -1896,12 +2193,12 @@
// values is the same order we'll store them in.
auto iter = other.begin();
if (iter == other.end()) return;
- insert_multi(maybe_move_from_iterator(iter));
+ insert_multi(iter.slot());
++iter;
for (; iter != other.end(); ++iter) {
// If the btree is not empty, we can just insert the new value at the end
// of the tree.
- internal_emplace(end(), maybe_move_from_iterator(iter));
+ internal_emplace(end(), iter.slot());
}
}
@@ -1911,7 +2208,7 @@
"Key comparison must be nothrow copy constructible");
static_assert(std::is_nothrow_copy_constructible<allocator_type>::value,
"Allocator must be nothrow copy constructible");
- static_assert(type_traits_internal::is_trivially_copyable<iterator>::value,
+ static_assert(std::is_trivially_copyable<iterator>::value,
"iterator not trivially copyable.");
// Note: We assert that kTargetValues, which is computed from
@@ -1921,15 +2218,12 @@
"target node size too large");
// Verify that key_compare returns an absl::{weak,strong}_ordering or bool.
- using compare_result_type =
- absl::result_of_t<key_compare(key_type, key_type)>;
static_assert(
- std::is_same<compare_result_type, bool>::value ||
- std::is_convertible<compare_result_type, absl::weak_ordering>::value,
+ compare_has_valid_result_type<key_compare, key_type>(),
"key comparison function must return absl::{weak,strong}_ordering or "
"bool.");
- // Test the assumption made in setting kNodeValueSpace.
+ // Test the assumption made in setting kNodeSlotSpace.
static_assert(node_type::MinimumOverhead() >= sizeof(void *) + 4,
"node space assumption incorrect");
@@ -1980,10 +2274,10 @@
template <typename P>
template <typename K, typename... Args>
-auto btree<P>::insert_unique(const K &key, Args &&... args)
+auto btree<P>::insert_unique(const K &key, Args &&...args)
-> std::pair<iterator, bool> {
if (empty()) {
- mutable_root() = rightmost_ = new_leaf_root_node(1);
+ mutable_root() = mutable_rightmost() = new_leaf_root_node(1);
}
SearchResult<iterator, is_key_compare_to::value> res = internal_locate(key);
@@ -1996,7 +2290,7 @@
}
} else {
iterator last = internal_last(iter);
- if (last.node && !compare_keys(key, last.key())) {
+ if (last.node_ && !compare_keys(key, last.key())) {
// The key already exists in the tree, do nothing.
return {last, false};
}
@@ -2007,7 +2301,7 @@
template <typename P>
template <typename K, typename... Args>
inline auto btree<P>::insert_hint_unique(iterator position, const K &key,
- Args &&... args)
+ Args &&...args)
-> std::pair<iterator, bool> {
if (!empty()) {
if (position == end() || compare_keys(key, position.key())) {
@@ -2041,8 +2335,11 @@
template <typename InputIterator>
void btree<P>::insert_iterator_unique(InputIterator b, InputIterator e, char) {
for (; b != e; ++b) {
- init_type value(*b);
- insert_hint_unique(end(), params_type::key(value), std::move(value));
+ // Use a node handle to manage a temp slot.
+ auto node_handle =
+ CommonAccess::Construct<node_handle_type>(get_allocator(), *b);
+ slot_type *slot = CommonAccess::GetSlot(node_handle);
+ insert_hint_unique(end(), params_type::key(slot), slot);
}
}
@@ -2050,11 +2347,11 @@
template <typename ValueType>
auto btree<P>::insert_multi(const key_type &key, ValueType &&v) -> iterator {
if (empty()) {
- mutable_root() = rightmost_ = new_leaf_root_node(1);
+ mutable_root() = mutable_rightmost() = new_leaf_root_node(1);
}
iterator iter = internal_upper_bound(key);
- if (iter.node == nullptr) {
+ if (iter.node_ == nullptr) {
iter = end();
}
return internal_emplace(iter, std::forward<ValueType>(v));
@@ -2113,16 +2410,16 @@
using std::swap;
if (absl::allocator_traits<
- allocator_type>::propagate_on_container_copy_assignment::value) {
- // Note: `root_` also contains the allocator and the key comparator.
+ allocator_type>::propagate_on_container_move_assignment::value) {
swap(root_, other.root_);
+ // Note: `rightmost_` also contains the allocator and the key comparator.
swap(rightmost_, other.rightmost_);
swap(size_, other.size_);
} else {
if (allocator() == other.allocator()) {
swap(mutable_root(), other.mutable_root());
swap(*mutable_key_comp(), *other.mutable_key_comp());
- swap(rightmost_, other.rightmost_);
+ swap(mutable_rightmost(), other.mutable_rightmost());
swap(size_, other.size_);
} else {
// We aren't allowed to propagate the allocator and the allocator is
@@ -2140,22 +2437,34 @@
template <typename P>
auto btree<P>::erase(iterator iter) -> iterator {
- bool internal_delete = false;
- if (!iter.node->leaf()) {
- // Deletion of a value on an internal node. First, move the largest value
- // from our left child here, then delete that position (in remove_values()
- // below). We can get to the largest value from our left child by
- // decrementing iter.
+ iter.node_->value_destroy(static_cast<field_type>(iter.position_),
+ mutable_allocator());
+ iter.update_generation();
+
+ const bool internal_delete = iter.node_->is_internal();
+ if (internal_delete) {
+ // Deletion of a value on an internal node. First, transfer the largest
+ // value from our left child here, then erase/rebalance from that position.
+ // We can get to the largest value from our left child by decrementing iter.
iterator internal_iter(iter);
--iter;
- assert(iter.node->leaf());
- params_type::move(mutable_allocator(), iter.node->slot(iter.position),
- internal_iter.node->slot(internal_iter.position));
- internal_delete = true;
+ assert(iter.node_->is_leaf());
+ internal_iter.node_->transfer(
+ static_cast<size_type>(internal_iter.position_),
+ static_cast<size_type>(iter.position_), iter.node_,
+ mutable_allocator());
+ } else {
+ // Shift values after erased position in leaf. In the internal case, we
+ // don't need to do this because the leaf position is the end of the node.
+ const field_type transfer_from =
+ static_cast<field_type>(iter.position_ + 1);
+ const field_type num_to_transfer = iter.node_->finish() - transfer_from;
+ iter.node_->transfer_n(num_to_transfer,
+ static_cast<size_type>(iter.position_),
+ transfer_from, iter.node_, mutable_allocator());
}
-
- // Delete the key from the leaf.
- iter.node->remove_values(iter.position, /*to_erase=*/1, mutable_allocator());
+ // Update node finish and container size.
+ iter.node_->set_finish(iter.node_->finish() - 1);
--size_;
// We want to return the next value after the one we just erased. If we
@@ -2163,7 +2472,7 @@
// value is ++(++iter). If we erased from a leaf node (internal_delete ==
// false) then the next value is ++iter. Note that ++iter may point to an
// internal node and the value in the internal node may move to a leaf node
- // (iter.node) when rebalancing is performed at the leaf level.
+ // (iter.node_) when rebalancing is performed at the leaf level.
iterator res = rebalance_after_delete(iter);
@@ -2180,14 +2489,14 @@
iterator res(iter);
bool first_iteration = true;
for (;;) {
- if (iter.node == root()) {
+ if (iter.node_ == root()) {
try_shrink();
if (empty()) {
return end();
}
break;
}
- if (iter.node->count() >= kMinNodeValues) {
+ if (iter.node_->count() >= kMinNodeValues) {
break;
}
bool merged = try_merge_or_rebalance(&iter);
@@ -2200,57 +2509,67 @@
if (!merged) {
break;
}
- iter.position = iter.node->position();
- iter.node = iter.node->parent();
+ iter.position_ = iter.node_->position();
+ iter.node_ = iter.node_->parent();
}
+ res.update_generation();
// Adjust our return value. If we're pointing at the end of a node, advance
// the iterator.
- if (res.position == res.node->finish()) {
- res.position = res.node->finish() - 1;
+ if (res.position_ == res.node_->finish()) {
+ res.position_ = res.node_->finish() - 1;
++res;
}
return res;
}
+// Note: we tried implementing this more efficiently by erasing all of the
+// elements in [begin, end) at once and then doing rebalancing once at the end
+// (rather than interleaving deletion and rebalancing), but that adds a lot of
+// complexity, which seems to outweigh the performance win.
template <typename P>
auto btree<P>::erase_range(iterator begin, iterator end)
-> std::pair<size_type, iterator> {
- difference_type count = std::distance(begin, end);
+ size_type count = static_cast<size_type>(end - begin);
assert(count >= 0);
if (count == 0) {
return {0, begin};
}
- if (count == size_) {
+ if (static_cast<size_type>(count) == size_) {
clear();
return {count, this->end()};
}
- if (begin.node == end.node) {
- assert(end.position > begin.position);
- begin.node->remove_values(begin.position, end.position - begin.position,
- mutable_allocator());
+ if (begin.node_ == end.node_) {
+ assert(end.position_ > begin.position_);
+ begin.node_->remove_values(
+ static_cast<field_type>(begin.position_),
+ static_cast<field_type>(end.position_ - begin.position_),
+ mutable_allocator());
size_ -= count;
return {count, rebalance_after_delete(begin)};
}
const size_type target_size = size_ - count;
while (size_ > target_size) {
- if (begin.node->leaf()) {
+ if (begin.node_->is_leaf()) {
const size_type remaining_to_erase = size_ - target_size;
- const size_type remaining_in_node = begin.node->finish() - begin.position;
- const size_type to_erase =
- (std::min)(remaining_to_erase, remaining_in_node);
- begin.node->remove_values(begin.position, to_erase, mutable_allocator());
+ const size_type remaining_in_node =
+ static_cast<size_type>(begin.node_->finish() - begin.position_);
+ const field_type to_erase = static_cast<field_type>(
+ (std::min)(remaining_to_erase, remaining_in_node));
+ begin.node_->remove_values(static_cast<field_type>(begin.position_),
+ to_erase, mutable_allocator());
size_ -= to_erase;
begin = rebalance_after_delete(begin);
} else {
begin = erase(begin);
}
}
+ begin.update_generation();
return {count, begin};
}
@@ -2259,8 +2578,7 @@
if (!empty()) {
node_type::clear_and_delete(root(), mutable_allocator());
}
- mutable_root() = EmptyNode();
- rightmost_ = EmptyNode();
+ mutable_root() = mutable_rightmost() = EmptyNode();
size_ = 0;
}
@@ -2269,15 +2587,15 @@
using std::swap;
if (absl::allocator_traits<
allocator_type>::propagate_on_container_swap::value) {
- // Note: `root_` also contains the allocator and the key comparator.
- swap(root_, other.root_);
+ // Note: `rightmost_` also contains the allocator and the key comparator.
+ swap(rightmost_, other.rightmost_);
} else {
// It's undefined behavior if the allocators are unequal here.
assert(allocator() == other.allocator());
- swap(mutable_root(), other.mutable_root());
+ swap(mutable_rightmost(), other.mutable_rightmost());
swap(*mutable_key_comp(), *other.mutable_key_comp());
}
- swap(rightmost_, other.rightmost_);
+ swap(mutable_root(), other.mutable_root());
swap(size_, other.size_);
}
@@ -2285,18 +2603,18 @@
void btree<P>::verify() const {
assert(root() != nullptr);
assert(leftmost() != nullptr);
- assert(rightmost_ != nullptr);
+ assert(rightmost() != nullptr);
assert(empty() || size() == internal_verify(root(), nullptr, nullptr));
- assert(leftmost() == (++const_iterator(root(), -1)).node);
- assert(rightmost_ == (--const_iterator(root(), root()->finish())).node);
- assert(leftmost()->leaf());
- assert(rightmost_->leaf());
+ assert(leftmost() == (++const_iterator(root(), -1)).node_);
+ assert(rightmost() == (--const_iterator(root(), root()->finish())).node_);
+ assert(leftmost()->is_leaf());
+ assert(rightmost()->is_leaf());
}
template <typename P>
void btree<P>::rebalance_or_split(iterator *iter) {
- node_type *&node = iter->node;
- int &insert_position = iter->position;
+ node_type *&node = iter->node_;
+ int &insert_position = iter->position_;
assert(node->count() == node->max_count());
assert(kNodeSlots == node->max_count());
@@ -2311,16 +2629,19 @@
// We bias rebalancing based on the position being inserted. If we're
// inserting at the end of the right node then we bias rebalancing to
// fill up the left node.
- int to_move = (kNodeSlots - left->count()) /
- (1 + (insert_position < static_cast<int>(kNodeSlots)));
- to_move = (std::max)(1, to_move);
+ field_type to_move =
+ (kNodeSlots - left->count()) /
+ (1 + (static_cast<field_type>(insert_position) < kNodeSlots));
+ to_move = (std::max)(field_type{1}, to_move);
- if (insert_position - to_move >= node->start() ||
- left->count() + to_move < static_cast<int>(kNodeSlots)) {
+ if (static_cast<field_type>(insert_position) - to_move >=
+ node->start() ||
+ left->count() + to_move < kNodeSlots) {
left->rebalance_right_to_left(to_move, node, mutable_allocator());
assert(node->max_count() - node->count() == to_move);
- insert_position = insert_position - to_move;
+ insert_position = static_cast<int>(
+ static_cast<field_type>(insert_position) - to_move);
if (insert_position < node->start()) {
insert_position = insert_position + left->count() + 1;
node = left;
@@ -2340,12 +2661,13 @@
// We bias rebalancing based on the position being inserted. If we're
// inserting at the beginning of the left node then we bias rebalancing
// to fill up the right node.
- int to_move = (static_cast<int>(kNodeSlots) - right->count()) /
- (1 + (insert_position > node->start()));
- to_move = (std::max)(1, to_move);
+ field_type to_move = (kNodeSlots - right->count()) /
+ (1 + (insert_position > node->start()));
+ to_move = (std::max)(field_type{1}, to_move);
- if (insert_position <= node->finish() - to_move ||
- right->count() + to_move < static_cast<int>(kNodeSlots)) {
+ if (static_cast<field_type>(insert_position) <=
+ node->finish() - to_move ||
+ right->count() + to_move < kNodeSlots) {
node->rebalance_left_to_right(to_move, right, mutable_allocator());
if (insert_position > node->finish()) {
@@ -2363,29 +2685,31 @@
// value.
assert(parent->max_count() == kNodeSlots);
if (parent->count() == kNodeSlots) {
- iterator parent_iter(node->parent(), node->position());
+ iterator parent_iter(parent, node->position());
rebalance_or_split(&parent_iter);
+ parent = node->parent();
}
} else {
// Rebalancing not possible because this is the root node.
// Create a new root node and set the current root node as the child of the
// new root.
- parent = new_internal_node(parent);
- parent->init_child(parent->start(), root());
+ parent = new_internal_node(/*position=*/0, parent);
+ parent->set_generation(root()->generation());
+ parent->init_child(parent->start(), node);
mutable_root() = parent;
// If the former root was a leaf node, then it's now the rightmost node.
- assert(!parent->start_child()->leaf() ||
- parent->start_child() == rightmost_);
+ assert(parent->start_child()->is_internal() ||
+ parent->start_child() == rightmost());
}
// Split the node.
node_type *split_node;
- if (node->leaf()) {
- split_node = new_leaf_node(parent);
+ if (node->is_leaf()) {
+ split_node = new_leaf_node(node->position() + 1, parent);
node->split(insert_position, split_node, mutable_allocator());
- if (rightmost_ == node) rightmost_ = split_node;
+ if (rightmost() == node) mutable_rightmost() = split_node;
} else {
- split_node = new_internal_node(parent);
+ split_node = new_internal_node(node->position() + 1, parent);
node->split(insert_position, split_node, mutable_allocator());
}
@@ -2398,55 +2722,57 @@
template <typename P>
void btree<P>::merge_nodes(node_type *left, node_type *right) {
left->merge(right, mutable_allocator());
- if (rightmost_ == right) rightmost_ = left;
+ if (rightmost() == right) mutable_rightmost() = left;
}
template <typename P>
bool btree<P>::try_merge_or_rebalance(iterator *iter) {
- node_type *parent = iter->node->parent();
- if (iter->node->position() > parent->start()) {
+ node_type *parent = iter->node_->parent();
+ if (iter->node_->position() > parent->start()) {
// Try merging with our left sibling.
- node_type *left = parent->child(iter->node->position() - 1);
+ node_type *left = parent->child(iter->node_->position() - 1);
assert(left->max_count() == kNodeSlots);
- if (1U + left->count() + iter->node->count() <= kNodeSlots) {
- iter->position += 1 + left->count();
- merge_nodes(left, iter->node);
- iter->node = left;
+ if (1U + left->count() + iter->node_->count() <= kNodeSlots) {
+ iter->position_ += 1 + left->count();
+ merge_nodes(left, iter->node_);
+ iter->node_ = left;
return true;
}
}
- if (iter->node->position() < parent->finish()) {
+ if (iter->node_->position() < parent->finish()) {
// Try merging with our right sibling.
- node_type *right = parent->child(iter->node->position() + 1);
+ node_type *right = parent->child(iter->node_->position() + 1);
assert(right->max_count() == kNodeSlots);
- if (1U + iter->node->count() + right->count() <= kNodeSlots) {
- merge_nodes(iter->node, right);
+ if (1U + iter->node_->count() + right->count() <= kNodeSlots) {
+ merge_nodes(iter->node_, right);
return true;
}
// Try rebalancing with our right sibling. We don't perform rebalancing if
- // we deleted the first element from iter->node and the node is not
+ // we deleted the first element from iter->node_ and the node is not
// empty. This is a small optimization for the common pattern of deleting
// from the front of the tree.
if (right->count() > kMinNodeValues &&
- (iter->node->count() == 0 || iter->position > iter->node->start())) {
- int to_move = (right->count() - iter->node->count()) / 2;
- to_move = (std::min)(to_move, right->count() - 1);
- iter->node->rebalance_right_to_left(to_move, right, mutable_allocator());
+ (iter->node_->count() == 0 || iter->position_ > iter->node_->start())) {
+ field_type to_move = (right->count() - iter->node_->count()) / 2;
+ to_move =
+ (std::min)(to_move, static_cast<field_type>(right->count() - 1));
+ iter->node_->rebalance_right_to_left(to_move, right, mutable_allocator());
return false;
}
}
- if (iter->node->position() > parent->start()) {
+ if (iter->node_->position() > parent->start()) {
// Try rebalancing with our left sibling. We don't perform rebalancing if
- // we deleted the last element from iter->node and the node is not
+ // we deleted the last element from iter->node_ and the node is not
// empty. This is a small optimization for the common pattern of deleting
// from the back of the tree.
- node_type *left = parent->child(iter->node->position() - 1);
+ node_type *left = parent->child(iter->node_->position() - 1);
if (left->count() > kMinNodeValues &&
- (iter->node->count() == 0 || iter->position < iter->node->finish())) {
- int to_move = (left->count() - iter->node->count()) / 2;
- to_move = (std::min)(to_move, left->count() - 1);
- left->rebalance_left_to_right(to_move, iter->node, mutable_allocator());
- iter->position += to_move;
+ (iter->node_->count() == 0 ||
+ iter->position_ < iter->node_->finish())) {
+ field_type to_move = (left->count() - iter->node_->count()) / 2;
+ to_move = (std::min)(to_move, static_cast<field_type>(left->count() - 1));
+ left->rebalance_left_to_right(to_move, iter->node_, mutable_allocator());
+ iter->position_ += to_move;
return false;
}
}
@@ -2460,9 +2786,9 @@
return;
}
// Deleted the last item on the root node, shrink the height of the tree.
- if (orig_root->leaf()) {
+ if (orig_root->is_leaf()) {
assert(size() == 0);
- mutable_root() = rightmost_ = EmptyNode();
+ mutable_root() = mutable_rightmost() = EmptyNode();
} else {
node_type *child = orig_root->start_child();
child->make_root();
@@ -2474,53 +2800,95 @@
template <typename P>
template <typename IterType>
inline IterType btree<P>::internal_last(IterType iter) {
- assert(iter.node != nullptr);
- while (iter.position == iter.node->finish()) {
- iter.position = iter.node->position();
- iter.node = iter.node->parent();
- if (iter.node->leaf()) {
- iter.node = nullptr;
+ assert(iter.node_ != nullptr);
+ while (iter.position_ == iter.node_->finish()) {
+ iter.position_ = iter.node_->position();
+ iter.node_ = iter.node_->parent();
+ if (iter.node_->is_leaf()) {
+ iter.node_ = nullptr;
break;
}
}
+ iter.update_generation();
return iter;
}
template <typename P>
template <typename... Args>
-inline auto btree<P>::internal_emplace(iterator iter, Args &&... args)
+inline auto btree<P>::internal_emplace(iterator iter, Args &&...args)
-> iterator {
- if (!iter.node->leaf()) {
+ if (iter.node_->is_internal()) {
// We can't insert on an internal node. Instead, we'll insert after the
// previous value which is guaranteed to be on a leaf node.
--iter;
- ++iter.position;
+ ++iter.position_;
}
- const field_type max_count = iter.node->max_count();
+ const field_type max_count = iter.node_->max_count();
allocator_type *alloc = mutable_allocator();
- if (iter.node->count() == max_count) {
+
+ const auto transfer_and_delete = [&](node_type *old_node,
+ node_type *new_node) {
+ new_node->transfer_n(old_node->count(), new_node->start(),
+ old_node->start(), old_node, alloc);
+ new_node->set_finish(old_node->finish());
+ old_node->set_finish(old_node->start());
+ new_node->set_generation(old_node->generation());
+ node_type::clear_and_delete(old_node, alloc);
+ };
+ const auto replace_leaf_root_node = [&](field_type new_node_size) {
+ assert(iter.node_ == root());
+ node_type *old_root = iter.node_;
+ node_type *new_root = iter.node_ = new_leaf_root_node(new_node_size);
+ transfer_and_delete(old_root, new_root);
+ mutable_root() = mutable_rightmost() = new_root;
+ };
+
+ bool replaced_node = false;
+ if (iter.node_->count() == max_count) {
// Make room in the leaf for the new item.
if (max_count < kNodeSlots) {
// Insertion into the root where the root is smaller than the full node
// size. Simply grow the size of the root node.
- assert(iter.node == root());
- iter.node =
- new_leaf_root_node((std::min<int>)(kNodeSlots, 2 * max_count));
- // Transfer the values from the old root to the new root.
- node_type *old_root = root();
- node_type *new_root = iter.node;
- new_root->transfer_n(old_root->count(), new_root->start(),
- old_root->start(), old_root, alloc);
- new_root->set_finish(old_root->finish());
- old_root->set_finish(old_root->start());
- node_type::clear_and_delete(old_root, alloc);
- mutable_root() = rightmost_ = new_root;
+ replace_leaf_root_node(static_cast<field_type>(
+ (std::min)(static_cast<int>(kNodeSlots), 2 * max_count)));
+ replaced_node = true;
} else {
rebalance_or_split(&iter);
}
}
- iter.node->emplace_value(iter.position, alloc, std::forward<Args>(args)...);
+ (void)replaced_node;
+#if defined(ABSL_HAVE_ADDRESS_SANITIZER) || \
+ defined(ABSL_HAVE_HWADDRESS_SANITIZER)
+ if (!replaced_node) {
+ assert(iter.node_->is_leaf());
+ if (iter.node_->is_root()) {
+ replace_leaf_root_node(max_count);
+ } else {
+ node_type *old_node = iter.node_;
+ const bool was_rightmost = rightmost() == old_node;
+ const bool was_leftmost = leftmost() == old_node;
+ node_type *parent = old_node->parent();
+ const field_type position = old_node->position();
+ node_type *new_node = iter.node_ = new_leaf_node(position, parent);
+ parent->set_child_noupdate_position(position, new_node);
+ transfer_and_delete(old_node, new_node);
+ if (was_rightmost) mutable_rightmost() = new_node;
+ // The leftmost node is stored as the parent of the root node.
+ if (was_leftmost) root()->set_parent(new_node);
+ }
+ }
+#endif
+ iter.node_->emplace_value(static_cast<field_type>(iter.position_), alloc,
+ std::forward<Args>(args)...);
+ assert(
+ iter.node_->is_ordered_correctly(static_cast<field_type>(iter.position_),
+ original_key_compare(key_comp())) &&
+ "If this assert fails, then either (1) the comparator may violate "
+ "transitivity, i.e. comp(a,b) && comp(b,c) -> comp(a,c) (see "
+ "https://en.cppreference.com/w/cpp/named_req/Compare), or (2) a "
+ "key may have been mutated after it was inserted into the tree.");
++size_;
+ iter.update_generation();
return iter;
}
@@ -2530,9 +2898,9 @@
-> SearchResult<iterator, is_key_compare_to::value> {
iterator iter(const_cast<node_type *>(root()));
for (;;) {
- SearchResult<int, is_key_compare_to::value> res =
- iter.node->lower_bound(key, key_comp());
- iter.position = res.value;
+ SearchResult<size_type, is_key_compare_to::value> res =
+ iter.node_->lower_bound(key, key_comp());
+ iter.position_ = static_cast<int>(res.value);
if (res.IsEq()) {
return {iter, MatchKind::kEq};
}
@@ -2540,10 +2908,10 @@
// down the tree if the keys are equal, but determining equality would
// require doing an extra comparison on each node on the way down, and we
// will need to go all the way to the leaf node in the expected case.
- if (iter.node->leaf()) {
+ if (iter.node_->is_leaf()) {
break;
}
- iter.node = iter.node->child(iter.position);
+ iter.node_ = iter.node_->child(static_cast<field_type>(iter.position_));
}
// Note: in the non-key-compare-to case, the key may actually be equivalent
// here (and the MatchKind::kNe is ignored).
@@ -2560,16 +2928,16 @@
return ret;
}
iterator iter(const_cast<node_type *>(root()));
- SearchResult<int, is_key_compare_to::value> res;
+ SearchResult<size_type, is_key_compare_to::value> res;
bool seen_eq = false;
for (;;) {
- res = iter.node->lower_bound(key, key_comp());
- iter.position = res.value;
- if (iter.node->leaf()) {
+ res = iter.node_->lower_bound(key, key_comp());
+ iter.position_ = static_cast<int>(res.value);
+ if (iter.node_->is_leaf()) {
break;
}
seen_eq = seen_eq || res.IsEq();
- iter.node = iter.node->child(iter.position);
+ iter.node_ = iter.node_->child(static_cast<field_type>(iter.position_));
}
if (res.IsEq()) return {iter, MatchKind::kEq};
return {internal_last(iter), seen_eq ? MatchKind::kEq : MatchKind::kNe};
@@ -2580,11 +2948,11 @@
auto btree<P>::internal_upper_bound(const K &key) const -> iterator {
iterator iter(const_cast<node_type *>(root()));
for (;;) {
- iter.position = iter.node->upper_bound(key, key_comp());
- if (iter.node->leaf()) {
+ iter.position_ = static_cast<int>(iter.node_->upper_bound(key, key_comp()));
+ if (iter.node_->is_leaf()) {
break;
}
- iter.node = iter.node->child(iter.position);
+ iter.node_ = iter.node_->child(static_cast<field_type>(iter.position_));
}
return internal_last(iter);
}
@@ -2599,7 +2967,7 @@
}
} else {
const iterator iter = internal_last(res.value);
- if (iter.node != nullptr && !compare_keys(key, iter.key())) {
+ if (iter.node_ != nullptr && !compare_keys(key, iter.key())) {
return iter;
}
}
@@ -2607,8 +2975,8 @@
}
template <typename P>
-int btree<P>::internal_verify(const node_type *node, const key_type *lo,
- const key_type *hi) const {
+typename btree<P>::size_type btree<P>::internal_verify(
+ const node_type *node, const key_type *lo, const key_type *hi) const {
assert(node->count() > 0);
assert(node->count() <= node->max_count());
if (lo) {
@@ -2620,9 +2988,9 @@
for (int i = node->start() + 1; i < node->finish(); ++i) {
assert(!compare_keys(node->key(i), node->key(i - 1)));
}
- int count = node->count();
- if (!node->leaf()) {
- for (int i = node->start(); i <= node->finish(); ++i) {
+ size_type count = node->count();
+ if (node->is_internal()) {
+ for (field_type i = node->start(); i <= node->finish(); ++i) {
assert(node->child(i) != nullptr);
assert(node->child(i)->parent() == node);
assert(node->child(i)->position() == i);
@@ -2634,6 +3002,50 @@
return count;
}
+struct btree_access {
+ template <typename BtreeContainer, typename Pred>
+ static auto erase_if(BtreeContainer &container, Pred pred) ->
+ typename BtreeContainer::size_type {
+ const auto initial_size = container.size();
+ auto &tree = container.tree_;
+ auto *alloc = tree.mutable_allocator();
+ for (auto it = container.begin(); it != container.end();) {
+ if (!pred(*it)) {
+ ++it;
+ continue;
+ }
+ auto *node = it.node_;
+ if (node->is_internal()) {
+ // Handle internal nodes normally.
+ it = container.erase(it);
+ continue;
+ }
+ // If this is a leaf node, then we do all the erases from this node
+ // at once before doing rebalancing.
+
+ // The current position to transfer slots to.
+ int to_pos = it.position_;
+ node->value_destroy(it.position_, alloc);
+ while (++it.position_ < node->finish()) {
+ it.update_generation();
+ if (pred(*it)) {
+ node->value_destroy(it.position_, alloc);
+ } else {
+ node->transfer(node->slot(to_pos++), node->slot(it.position_), alloc);
+ }
+ }
+ const int num_deleted = node->finish() - to_pos;
+ tree.size_ -= num_deleted;
+ node->set_finish(to_pos);
+ it.position_ = to_pos;
+ it = tree.rebalance_after_delete(it);
+ }
+ return initial_size - container.size();
+ }
+};
+
+#undef ABSL_BTREE_ENABLE_GENERATIONS
+
} // namespace container_internal
ABSL_NAMESPACE_END
} // namespace absl
diff --git a/absl/container/internal/btree_container.h b/absl/container/internal/btree_container.h
index a99668c..a68ce44 100644
--- a/absl/container/internal/btree_container.h
+++ b/absl/container/internal/btree_container.h
@@ -44,8 +44,8 @@
// transparent case.
template <class K>
using key_arg =
- typename KeyArg<IsTransparent<typename Tree::key_compare>::value>::
- template type<K, typename Tree::key_type>;
+ typename KeyArg<params_type::kIsKeyCompareTransparent>::template type<
+ K, typename Tree::key_type>;
public:
using key_type = typename Tree::key_type;
@@ -65,6 +65,11 @@
using const_reverse_iterator = typename Tree::const_reverse_iterator;
using node_type = typename Tree::node_handle_type;
+ struct extract_and_get_next_return_type {
+ node_type node;
+ iterator next;
+ };
+
// Constructors/assignments.
btree_container() : tree_(key_compare(), allocator_type()) {}
explicit btree_container(const key_compare &comp,
@@ -90,31 +95,50 @@
std::is_nothrow_move_assignable<Tree>::value) = default;
// Iterator routines.
- iterator begin() { return tree_.begin(); }
- const_iterator begin() const { return tree_.begin(); }
- const_iterator cbegin() const { return tree_.begin(); }
- iterator end() { return tree_.end(); }
- const_iterator end() const { return tree_.end(); }
- const_iterator cend() const { return tree_.end(); }
- reverse_iterator rbegin() { return tree_.rbegin(); }
- const_reverse_iterator rbegin() const { return tree_.rbegin(); }
- const_reverse_iterator crbegin() const { return tree_.rbegin(); }
- reverse_iterator rend() { return tree_.rend(); }
- const_reverse_iterator rend() const { return tree_.rend(); }
- const_reverse_iterator crend() const { return tree_.rend(); }
+ iterator begin() ABSL_ATTRIBUTE_LIFETIME_BOUND { return tree_.begin(); }
+ const_iterator begin() const ABSL_ATTRIBUTE_LIFETIME_BOUND {
+ return tree_.begin();
+ }
+ const_iterator cbegin() const ABSL_ATTRIBUTE_LIFETIME_BOUND {
+ return tree_.begin();
+ }
+ iterator end() ABSL_ATTRIBUTE_LIFETIME_BOUND { return tree_.end(); }
+ const_iterator end() const ABSL_ATTRIBUTE_LIFETIME_BOUND {
+ return tree_.end();
+ }
+ const_iterator cend() const ABSL_ATTRIBUTE_LIFETIME_BOUND {
+ return tree_.end();
+ }
+ reverse_iterator rbegin() ABSL_ATTRIBUTE_LIFETIME_BOUND {
+ return tree_.rbegin();
+ }
+ const_reverse_iterator rbegin() const ABSL_ATTRIBUTE_LIFETIME_BOUND {
+ return tree_.rbegin();
+ }
+ const_reverse_iterator crbegin() const ABSL_ATTRIBUTE_LIFETIME_BOUND {
+ return tree_.rbegin();
+ }
+ reverse_iterator rend() ABSL_ATTRIBUTE_LIFETIME_BOUND { return tree_.rend(); }
+ const_reverse_iterator rend() const ABSL_ATTRIBUTE_LIFETIME_BOUND {
+ return tree_.rend();
+ }
+ const_reverse_iterator crend() const ABSL_ATTRIBUTE_LIFETIME_BOUND {
+ return tree_.rend();
+ }
// Lookup routines.
template <typename K = key_type>
size_type count(const key_arg<K> &key) const {
auto equal_range = this->equal_range(key);
- return std::distance(equal_range.first, equal_range.second);
+ return equal_range.second - equal_range.first;
}
template <typename K = key_type>
- iterator find(const key_arg<K> &key) {
+ iterator find(const key_arg<K> &key) ABSL_ATTRIBUTE_LIFETIME_BOUND {
return tree_.find(key);
}
template <typename K = key_type>
- const_iterator find(const key_arg<K> &key) const {
+ const_iterator find(const key_arg<K> &key) const
+ ABSL_ATTRIBUTE_LIFETIME_BOUND {
return tree_.find(key);
}
template <typename K = key_type>
@@ -122,28 +146,31 @@
return find(key) != end();
}
template <typename K = key_type>
- iterator lower_bound(const key_arg<K> &key) {
+ iterator lower_bound(const key_arg<K> &key) ABSL_ATTRIBUTE_LIFETIME_BOUND {
return tree_.lower_bound(key);
}
template <typename K = key_type>
- const_iterator lower_bound(const key_arg<K> &key) const {
+ const_iterator lower_bound(const key_arg<K> &key) const
+ ABSL_ATTRIBUTE_LIFETIME_BOUND {
return tree_.lower_bound(key);
}
template <typename K = key_type>
- iterator upper_bound(const key_arg<K> &key) {
+ iterator upper_bound(const key_arg<K> &key) ABSL_ATTRIBUTE_LIFETIME_BOUND {
return tree_.upper_bound(key);
}
template <typename K = key_type>
- const_iterator upper_bound(const key_arg<K> &key) const {
+ const_iterator upper_bound(const key_arg<K> &key) const
+ ABSL_ATTRIBUTE_LIFETIME_BOUND {
return tree_.upper_bound(key);
}
template <typename K = key_type>
- std::pair<iterator, iterator> equal_range(const key_arg<K> &key) {
+ std::pair<iterator, iterator> equal_range(const key_arg<K> &key)
+ ABSL_ATTRIBUTE_LIFETIME_BOUND {
return tree_.equal_range(key);
}
template <typename K = key_type>
std::pair<const_iterator, const_iterator> equal_range(
- const key_arg<K> &key) const {
+ const key_arg<K> &key) const ABSL_ATTRIBUTE_LIFETIME_BOUND {
return tree_.equal_range(key);
}
@@ -153,9 +180,14 @@
// Erase the specified iterator from the btree. The iterator must be valid
// (i.e. not equal to end()). Return an iterator pointing to the node after
// the one that was erased (or end() if none exists).
- iterator erase(const_iterator iter) { return tree_.erase(iterator(iter)); }
- iterator erase(iterator iter) { return tree_.erase(iter); }
- iterator erase(const_iterator first, const_iterator last) {
+ iterator erase(const_iterator iter) ABSL_ATTRIBUTE_LIFETIME_BOUND {
+ return tree_.erase(iterator(iter));
+ }
+ iterator erase(iterator iter) ABSL_ATTRIBUTE_LIFETIME_BOUND {
+ return tree_.erase(iter);
+ }
+ iterator erase(const_iterator first,
+ const_iterator last) ABSL_ATTRIBUTE_LIFETIME_BOUND {
return tree_.erase_range(iterator(first), iterator(last)).second;
}
template <typename K = key_type>
@@ -165,10 +197,20 @@
}
// Extract routines.
+ extract_and_get_next_return_type extract_and_get_next(const_iterator position)
+ ABSL_ATTRIBUTE_LIFETIME_BOUND {
+ // Use Construct instead of Transfer because the rebalancing code will
+ // destroy the slot later.
+ // Note: we rely on erase() taking place after Construct().
+ return {CommonAccess::Construct<node_type>(get_allocator(),
+ iterator(position).slot()),
+ erase(position)};
+ }
node_type extract(iterator position) {
- // Use Move instead of Transfer, because the rebalancing code expects to
- // have a valid object to scribble metadata bits on top of.
- auto node = CommonAccess::Move<node_type>(get_allocator(), position.slot());
+ // Use Construct instead of Transfer because the rebalancing code will
+ // destroy the slot later.
+ auto node =
+ CommonAccess::Construct<node_type>(get_allocator(), position.slot());
erase(position);
return node;
}
@@ -228,6 +270,7 @@
}
protected:
+ friend struct btree_access;
Tree tree_;
};
@@ -282,32 +325,44 @@
: btree_set_container(init.begin(), init.end(), alloc) {}
// Insertion routines.
- std::pair<iterator, bool> insert(const value_type &v) {
+ std::pair<iterator, bool> insert(const value_type &v)
+ ABSL_ATTRIBUTE_LIFETIME_BOUND {
return this->tree_.insert_unique(params_type::key(v), v);
}
- std::pair<iterator, bool> insert(value_type &&v) {
+ std::pair<iterator, bool> insert(value_type &&v)
+ ABSL_ATTRIBUTE_LIFETIME_BOUND {
return this->tree_.insert_unique(params_type::key(v), std::move(v));
}
template <typename... Args>
- std::pair<iterator, bool> emplace(Args &&... args) {
- init_type v(std::forward<Args>(args)...);
- return this->tree_.insert_unique(params_type::key(v), std::move(v));
+ std::pair<iterator, bool> emplace(Args &&...args)
+ ABSL_ATTRIBUTE_LIFETIME_BOUND {
+ // Use a node handle to manage a temp slot.
+ auto node = CommonAccess::Construct<node_type>(this->get_allocator(),
+ std::forward<Args>(args)...);
+ auto *slot = CommonAccess::GetSlot(node);
+ return this->tree_.insert_unique(params_type::key(slot), slot);
}
- iterator insert(const_iterator hint, const value_type &v) {
+ iterator insert(const_iterator hint,
+ const value_type &v) ABSL_ATTRIBUTE_LIFETIME_BOUND {
return this->tree_
.insert_hint_unique(iterator(hint), params_type::key(v), v)
.first;
}
- iterator insert(const_iterator hint, value_type &&v) {
+ iterator insert(const_iterator hint,
+ value_type &&v) ABSL_ATTRIBUTE_LIFETIME_BOUND {
return this->tree_
.insert_hint_unique(iterator(hint), params_type::key(v), std::move(v))
.first;
}
template <typename... Args>
- iterator emplace_hint(const_iterator hint, Args &&... args) {
- init_type v(std::forward<Args>(args)...);
+ iterator emplace_hint(const_iterator hint,
+ Args &&...args) ABSL_ATTRIBUTE_LIFETIME_BOUND {
+ // Use a node handle to manage a temp slot.
+ auto node = CommonAccess::Construct<node_type>(this->get_allocator(),
+ std::forward<Args>(args)...);
+ auto *slot = CommonAccess::GetSlot(node);
return this->tree_
- .insert_hint_unique(iterator(hint), params_type::key(v), std::move(v))
+ .insert_hint_unique(iterator(hint), params_type::key(slot), slot)
.first;
}
template <typename InputIterator>
@@ -317,7 +372,7 @@
void insert(std::initializer_list<init_type> init) {
this->tree_.insert_iterator_unique(init.begin(), init.end(), 0);
}
- insert_return_type insert(node_type &&node) {
+ insert_return_type insert(node_type &&node) ABSL_ATTRIBUTE_LIFETIME_BOUND {
if (!node) return {this->end(), false, node_type()};
std::pair<iterator, bool> res =
this->tree_.insert_unique(params_type::key(CommonAccess::GetSlot(node)),
@@ -329,7 +384,8 @@
return {res.first, false, std::move(node)};
}
}
- iterator insert(const_iterator hint, node_type &&node) {
+ iterator insert(const_iterator hint,
+ node_type &&node) ABSL_ATTRIBUTE_LIFETIME_BOUND {
if (!node) return this->end();
std::pair<iterator, bool> res = this->tree_.insert_hint_unique(
iterator(hint), params_type::key(CommonAccess::GetSlot(node)),
@@ -412,37 +468,43 @@
// Note: the nullptr template arguments and extra `const M&` overloads allow
// for supporting bitfield arguments.
template <typename K = key_type, class M>
- std::pair<iterator, bool> insert_or_assign(const key_arg<K> &k,
- const M &obj) {
+ std::pair<iterator, bool> insert_or_assign(const key_arg<K> &k, const M &obj)
+ ABSL_ATTRIBUTE_LIFETIME_BOUND {
return insert_or_assign_impl(k, obj);
}
template <typename K = key_type, class M, K * = nullptr>
- std::pair<iterator, bool> insert_or_assign(key_arg<K> &&k, const M &obj) {
+ std::pair<iterator, bool> insert_or_assign(key_arg<K> &&k, const M &obj)
+ ABSL_ATTRIBUTE_LIFETIME_BOUND {
return insert_or_assign_impl(std::forward<K>(k), obj);
}
template <typename K = key_type, class M, M * = nullptr>
- std::pair<iterator, bool> insert_or_assign(const key_arg<K> &k, M &&obj) {
+ std::pair<iterator, bool> insert_or_assign(const key_arg<K> &k, M &&obj)
+ ABSL_ATTRIBUTE_LIFETIME_BOUND {
return insert_or_assign_impl(k, std::forward<M>(obj));
}
template <typename K = key_type, class M, K * = nullptr, M * = nullptr>
- std::pair<iterator, bool> insert_or_assign(key_arg<K> &&k, M &&obj) {
+ std::pair<iterator, bool> insert_or_assign(key_arg<K> &&k, M &&obj)
+ ABSL_ATTRIBUTE_LIFETIME_BOUND {
return insert_or_assign_impl(std::forward<K>(k), std::forward<M>(obj));
}
template <typename K = key_type, class M>
iterator insert_or_assign(const_iterator hint, const key_arg<K> &k,
- const M &obj) {
+ const M &obj) ABSL_ATTRIBUTE_LIFETIME_BOUND {
return insert_or_assign_hint_impl(hint, k, obj);
}
template <typename K = key_type, class M, K * = nullptr>
- iterator insert_or_assign(const_iterator hint, key_arg<K> &&k, const M &obj) {
+ iterator insert_or_assign(const_iterator hint, key_arg<K> &&k,
+ const M &obj) ABSL_ATTRIBUTE_LIFETIME_BOUND {
return insert_or_assign_hint_impl(hint, std::forward<K>(k), obj);
}
template <typename K = key_type, class M, M * = nullptr>
- iterator insert_or_assign(const_iterator hint, const key_arg<K> &k, M &&obj) {
+ iterator insert_or_assign(const_iterator hint, const key_arg<K> &k,
+ M &&obj) ABSL_ATTRIBUTE_LIFETIME_BOUND {
return insert_or_assign_hint_impl(hint, k, std::forward<M>(obj));
}
template <typename K = key_type, class M, K * = nullptr, M * = nullptr>
- iterator insert_or_assign(const_iterator hint, key_arg<K> &&k, M &&obj) {
+ iterator insert_or_assign(const_iterator hint, key_arg<K> &&k,
+ M &&obj) ABSL_ATTRIBUTE_LIFETIME_BOUND {
return insert_or_assign_hint_impl(hint, std::forward<K>(k),
std::forward<M>(obj));
}
@@ -450,44 +512,48 @@
template <typename K = key_type, typename... Args,
typename absl::enable_if_t<
!std::is_convertible<K, const_iterator>::value, int> = 0>
- std::pair<iterator, bool> try_emplace(const key_arg<K> &k, Args &&... args) {
+ std::pair<iterator, bool> try_emplace(const key_arg<K> &k, Args &&...args)
+ ABSL_ATTRIBUTE_LIFETIME_BOUND {
return try_emplace_impl(k, std::forward<Args>(args)...);
}
template <typename K = key_type, typename... Args,
typename absl::enable_if_t<
!std::is_convertible<K, const_iterator>::value, int> = 0>
- std::pair<iterator, bool> try_emplace(key_arg<K> &&k, Args &&... args) {
+ std::pair<iterator, bool> try_emplace(key_arg<K> &&k, Args &&...args)
+ ABSL_ATTRIBUTE_LIFETIME_BOUND {
return try_emplace_impl(std::forward<K>(k), std::forward<Args>(args)...);
}
template <typename K = key_type, typename... Args>
iterator try_emplace(const_iterator hint, const key_arg<K> &k,
- Args &&... args) {
+ Args &&...args) ABSL_ATTRIBUTE_LIFETIME_BOUND {
return try_emplace_hint_impl(hint, k, std::forward<Args>(args)...);
}
template <typename K = key_type, typename... Args>
- iterator try_emplace(const_iterator hint, key_arg<K> &&k, Args &&... args) {
+ iterator try_emplace(const_iterator hint, key_arg<K> &&k,
+ Args &&...args) ABSL_ATTRIBUTE_LIFETIME_BOUND {
return try_emplace_hint_impl(hint, std::forward<K>(k),
std::forward<Args>(args)...);
}
template <typename K = key_type>
- mapped_type &operator[](const key_arg<K> &k) {
+ mapped_type &operator[](const key_arg<K> &k) ABSL_ATTRIBUTE_LIFETIME_BOUND {
return try_emplace(k).first->second;
}
template <typename K = key_type>
- mapped_type &operator[](key_arg<K> &&k) {
+ mapped_type &operator[](key_arg<K> &&k) ABSL_ATTRIBUTE_LIFETIME_BOUND {
return try_emplace(std::forward<K>(k)).first->second;
}
template <typename K = key_type>
- mapped_type &at(const key_arg<K> &key) {
+ mapped_type &at(const key_arg<K> &key) ABSL_ATTRIBUTE_LIFETIME_BOUND {
auto it = this->find(key);
if (it == this->end())
base_internal::ThrowStdOutOfRange("absl::btree_map::at");
return it->second;
}
template <typename K = key_type>
- const mapped_type &at(const key_arg<K> &key) const {
+ const mapped_type &at(const key_arg<K> &key) const
+ ABSL_ATTRIBUTE_LIFETIME_BOUND {
auto it = this->find(key);
if (it == this->end())
base_internal::ThrowStdOutOfRange("absl::btree_map::at");
@@ -536,6 +602,7 @@
using params_type = typename Tree::params_type;
using init_type = typename params_type::init_type;
using is_key_compare_to = typename params_type::is_key_compare_to;
+ friend class BtreeNodePeer;
template <class K>
using key_arg = typename super_type::template key_arg<K>;
@@ -577,14 +644,18 @@
: btree_multiset_container(init.begin(), init.end(), alloc) {}
// Insertion routines.
- iterator insert(const value_type &v) { return this->tree_.insert_multi(v); }
- iterator insert(value_type &&v) {
+ iterator insert(const value_type &v) ABSL_ATTRIBUTE_LIFETIME_BOUND {
+ return this->tree_.insert_multi(v);
+ }
+ iterator insert(value_type &&v) ABSL_ATTRIBUTE_LIFETIME_BOUND {
return this->tree_.insert_multi(std::move(v));
}
- iterator insert(const_iterator hint, const value_type &v) {
+ iterator insert(const_iterator hint,
+ const value_type &v) ABSL_ATTRIBUTE_LIFETIME_BOUND {
return this->tree_.insert_hint_multi(iterator(hint), v);
}
- iterator insert(const_iterator hint, value_type &&v) {
+ iterator insert(const_iterator hint,
+ value_type &&v) ABSL_ATTRIBUTE_LIFETIME_BOUND {
return this->tree_.insert_hint_multi(iterator(hint), std::move(v));
}
template <typename InputIterator>
@@ -595,15 +666,22 @@
this->tree_.insert_iterator_multi(init.begin(), init.end());
}
template <typename... Args>
- iterator emplace(Args &&... args) {
- return this->tree_.insert_multi(init_type(std::forward<Args>(args)...));
+ iterator emplace(Args &&...args) ABSL_ATTRIBUTE_LIFETIME_BOUND {
+ // Use a node handle to manage a temp slot.
+ auto node = CommonAccess::Construct<node_type>(this->get_allocator(),
+ std::forward<Args>(args)...);
+ return this->tree_.insert_multi(CommonAccess::GetSlot(node));
}
template <typename... Args>
- iterator emplace_hint(const_iterator hint, Args &&... args) {
- return this->tree_.insert_hint_multi(
- iterator(hint), init_type(std::forward<Args>(args)...));
+ iterator emplace_hint(const_iterator hint,
+ Args &&...args) ABSL_ATTRIBUTE_LIFETIME_BOUND {
+ // Use a node handle to manage a temp slot.
+ auto node = CommonAccess::Construct<node_type>(this->get_allocator(),
+ std::forward<Args>(args)...);
+ return this->tree_.insert_hint_multi(iterator(hint),
+ CommonAccess::GetSlot(node));
}
- iterator insert(node_type &&node) {
+ iterator insert(node_type &&node) ABSL_ATTRIBUTE_LIFETIME_BOUND {
if (!node) return this->end();
iterator res =
this->tree_.insert_multi(params_type::key(CommonAccess::GetSlot(node)),
@@ -611,7 +689,8 @@
CommonAccess::Destroy(&node);
return res;
}
- iterator insert(const_iterator hint, node_type &&node) {
+ iterator insert(const_iterator hint,
+ node_type &&node) ABSL_ATTRIBUTE_LIFETIME_BOUND {
if (!node) return this->end();
iterator res = this->tree_.insert_hint_multi(
iterator(hint),
@@ -667,6 +746,7 @@
class btree_multimap_container : public btree_multiset_container<Tree> {
using super_type = btree_multiset_container<Tree>;
using params_type = typename Tree::params_type;
+ friend class BtreeNodePeer;
public:
using mapped_type = typename params_type::mapped_type;
diff --git a/absl/container/internal/common.h b/absl/container/internal/common.h
index 030e9d4..9239bb4 100644
--- a/absl/container/internal/common.h
+++ b/absl/container/internal/common.h
@@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#ifndef ABSL_CONTAINER_INTERNAL_CONTAINER_H_
-#define ABSL_CONTAINER_INTERNAL_CONTAINER_H_
+#ifndef ABSL_CONTAINER_INTERNAL_COMMON_H_
+#define ABSL_CONTAINER_INTERNAL_COMMON_H_
#include <cassert>
#include <type_traits>
@@ -84,10 +84,11 @@
PolicyTraits::transfer(alloc(), slot(), s);
}
- struct move_tag_t {};
- node_handle_base(move_tag_t, const allocator_type& a, slot_type* s)
+ struct construct_tag_t {};
+ template <typename... Args>
+ node_handle_base(construct_tag_t, const allocator_type& a, Args&&... args)
: alloc_(a) {
- PolicyTraits::construct(alloc(), slot(), s);
+ PolicyTraits::construct(alloc(), slot(), std::forward<Args>(args)...);
}
void destroy() {
@@ -186,8 +187,8 @@
}
template <typename T, typename... Args>
- static T Move(Args&&... args) {
- return T(typename T::move_tag_t{}, std::forward<Args>(args)...);
+ static T Construct(Args&&... args) {
+ return T(typename T::construct_tag_t{}, std::forward<Args>(args)...);
}
};
@@ -203,4 +204,4 @@
ABSL_NAMESPACE_END
} // namespace absl
-#endif // ABSL_CONTAINER_INTERNAL_CONTAINER_H_
+#endif // ABSL_CONTAINER_INTERNAL_COMMON_H_
diff --git a/absl/container/internal/common_policy_traits.h b/absl/container/internal/common_policy_traits.h
new file mode 100644
index 0000000..c521f61
--- /dev/null
+++ b/absl/container/internal/common_policy_traits.h
@@ -0,0 +1,143 @@
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef ABSL_CONTAINER_INTERNAL_COMMON_POLICY_TRAITS_H_
+#define ABSL_CONTAINER_INTERNAL_COMMON_POLICY_TRAITS_H_
+
+#include <cstddef>
+#include <cstring>
+#include <memory>
+#include <new>
+#include <type_traits>
+#include <utility>
+
+#include "absl/meta/type_traits.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace container_internal {
+
+// Defines how slots are initialized/destroyed/moved.
+template <class Policy, class = void>
+struct common_policy_traits {
+ // The actual object stored in the container.
+ using slot_type = typename Policy::slot_type;
+ using reference = decltype(Policy::element(std::declval<slot_type*>()));
+ using value_type = typename std::remove_reference<reference>::type;
+
+ // PRECONDITION: `slot` is UNINITIALIZED
+ // POSTCONDITION: `slot` is INITIALIZED
+ template <class Alloc, class... Args>
+ static void construct(Alloc* alloc, slot_type* slot, Args&&... args) {
+ Policy::construct(alloc, slot, std::forward<Args>(args)...);
+ }
+
+ // PRECONDITION: `slot` is INITIALIZED
+ // POSTCONDITION: `slot` is UNINITIALIZED
+ // Returns std::true_type in case destroy is trivial.
+ template <class Alloc>
+ static auto destroy(Alloc* alloc, slot_type* slot) {
+ return Policy::destroy(alloc, slot);
+ }
+
+ // Transfers the `old_slot` to `new_slot`. Any memory allocated by the
+ // allocator inside `old_slot` to `new_slot` can be transferred.
+ //
+ // OPTIONAL: defaults to:
+ //
+ // clone(new_slot, std::move(*old_slot));
+ // destroy(old_slot);
+ //
+ // PRECONDITION: `new_slot` is UNINITIALIZED and `old_slot` is INITIALIZED
+ // POSTCONDITION: `new_slot` is INITIALIZED and `old_slot` is
+ // UNINITIALIZED
+ template <class Alloc>
+ static void transfer(Alloc* alloc, slot_type* new_slot, slot_type* old_slot) {
+ transfer_impl(alloc, new_slot, old_slot, Rank2{});
+ }
+
+ // PRECONDITION: `slot` is INITIALIZED
+ // POSTCONDITION: `slot` is INITIALIZED
+ // Note: we use remove_const_t so that the two overloads have different args
+ // in the case of sets with explicitly const value_types.
+ template <class P = Policy>
+ static auto element(absl::remove_const_t<slot_type>* slot)
+ -> decltype(P::element(slot)) {
+ return P::element(slot);
+ }
+ template <class P = Policy>
+ static auto element(const slot_type* slot) -> decltype(P::element(slot)) {
+ return P::element(slot);
+ }
+
+ static constexpr bool transfer_uses_memcpy() {
+ return std::is_same<decltype(transfer_impl<std::allocator<char>>(
+ nullptr, nullptr, nullptr, Rank2{})),
+ std::true_type>::value;
+ }
+
+ // Returns true if destroy is trivial and can be omitted.
+ template <class Alloc>
+ static constexpr bool destroy_is_trivial() {
+ return std::is_same<decltype(destroy<Alloc>(nullptr, nullptr)),
+ std::true_type>::value;
+ }
+
+ private:
+ // Use go/ranked-overloads for dispatching.
+ struct Rank0 {};
+ struct Rank1 : Rank0 {};
+ struct Rank2 : Rank1 {};
+
+ // Use auto -> decltype as an enabler.
+ // P::transfer returns std::true_type if transfer uses memcpy (e.g. in
+ // node_slot_policy).
+ template <class Alloc, class P = Policy>
+ static auto transfer_impl(Alloc* alloc, slot_type* new_slot,
+ slot_type* old_slot,
+ Rank2) -> decltype(P::transfer(alloc, new_slot,
+ old_slot)) {
+ return P::transfer(alloc, new_slot, old_slot);
+ }
+#if defined(__cpp_lib_launder) && __cpp_lib_launder >= 201606
+ // This overload returns true_type for the trait below.
+ // The conditional_t is to make the enabler type dependent.
+ template <class Alloc,
+ typename = std::enable_if_t<absl::is_trivially_relocatable<
+ std::conditional_t<false, Alloc, value_type>>::value>>
+ static std::true_type transfer_impl(Alloc*, slot_type* new_slot,
+ slot_type* old_slot, Rank1) {
+ // TODO(b/247130232): remove casts after fixing warnings.
+ // TODO(b/251814870): remove casts after fixing warnings.
+ std::memcpy(
+ static_cast<void*>(std::launder(
+ const_cast<std::remove_const_t<value_type>*>(&element(new_slot)))),
+ static_cast<const void*>(&element(old_slot)), sizeof(value_type));
+ return {};
+ }
+#endif
+
+ template <class Alloc>
+ static void transfer_impl(Alloc* alloc, slot_type* new_slot,
+ slot_type* old_slot, Rank0) {
+ construct(alloc, new_slot, std::move(element(old_slot)));
+ destroy(alloc, old_slot);
+ }
+};
+
+} // namespace container_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_CONTAINER_INTERNAL_COMMON_POLICY_TRAITS_H_
diff --git a/absl/container/internal/common_policy_traits_test.cc b/absl/container/internal/common_policy_traits_test.cc
new file mode 100644
index 0000000..8d8f8ba
--- /dev/null
+++ b/absl/container/internal/common_policy_traits_test.cc
@@ -0,0 +1,157 @@
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/container/internal/common_policy_traits.h"
+
+#include <functional>
+#include <memory>
+#include <type_traits>
+#include <utility>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/base/config.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace container_internal {
+namespace {
+
+using ::testing::MockFunction;
+using ::testing::AnyNumber;
+using ::testing::ReturnRef;
+
+using Slot = int;
+
+struct PolicyWithoutOptionalOps {
+ using slot_type = Slot;
+ using key_type = Slot;
+ using init_type = Slot;
+
+ struct PolicyFunctions {
+ std::function<void(void*, Slot*, Slot)> construct;
+ std::function<void(void*, Slot*)> destroy;
+ std::function<Slot&(Slot*)> element;
+ };
+
+ static PolicyFunctions* functions() {
+ static PolicyFunctions* functions = new PolicyFunctions();
+ return functions;
+ }
+
+ static void construct(void* a, Slot* b, Slot c) {
+ functions()->construct(a, b, c);
+ }
+ static void destroy(void* a, Slot* b) { functions()->destroy(a, b); }
+ static Slot& element(Slot* b) { return functions()->element(b); }
+};
+
+struct PolicyWithOptionalOps : PolicyWithoutOptionalOps {
+ struct TransferFunctions {
+ std::function<void(void*, Slot*, Slot*)> transfer;
+ };
+
+ static TransferFunctions* transfer_fn() {
+ static TransferFunctions* transfer_fn = new TransferFunctions();
+ return transfer_fn;
+ }
+ static void transfer(void* a, Slot* b, Slot* c) {
+ transfer_fn()->transfer(a, b, c);
+ }
+};
+
+struct PolicyWithMemcpyTransferAndTrivialDestroy : PolicyWithoutOptionalOps {
+ static std::true_type transfer(void*, Slot*, Slot*) { return {}; }
+ static std::true_type destroy(void*, Slot*) { return {}; }
+};
+
+struct Test : ::testing::Test {
+ Test() {
+ PolicyWithoutOptionalOps::functions()->construct = [&](void* a1, Slot* a2,
+ Slot a3) {
+ construct.Call(a1, a2, std::move(a3));
+ };
+ PolicyWithoutOptionalOps::functions()->destroy = [&](void* a1, Slot* a2) {
+ destroy.Call(a1, a2);
+ };
+
+ PolicyWithoutOptionalOps::functions()->element = [&](Slot* a1) -> Slot& {
+ return element.Call(a1);
+ };
+
+ PolicyWithOptionalOps::transfer_fn()->transfer =
+ [&](void* a1, Slot* a2, Slot* a3) { return transfer.Call(a1, a2, a3); };
+ }
+
+ std::allocator<Slot> alloc;
+ int a = 53;
+
+ MockFunction<void(void*, Slot*, Slot)> construct;
+ MockFunction<void(void*, Slot*)> destroy;
+
+ MockFunction<Slot&(Slot*)> element;
+
+ MockFunction<void(void*, Slot*, Slot*)> transfer;
+};
+
+TEST_F(Test, construct) {
+ EXPECT_CALL(construct, Call(&alloc, &a, 53));
+ common_policy_traits<PolicyWithoutOptionalOps>::construct(&alloc, &a, 53);
+}
+
+TEST_F(Test, destroy) {
+ EXPECT_CALL(destroy, Call(&alloc, &a));
+ common_policy_traits<PolicyWithoutOptionalOps>::destroy(&alloc, &a);
+}
+
+TEST_F(Test, element) {
+ int b = 0;
+ EXPECT_CALL(element, Call(&a)).WillOnce(ReturnRef(b));
+ EXPECT_EQ(&b, &common_policy_traits<PolicyWithoutOptionalOps>::element(&a));
+}
+
+TEST_F(Test, without_transfer) {
+ int b = 42;
+ EXPECT_CALL(element, Call(&a)).Times(AnyNumber()).WillOnce(ReturnRef(a));
+ EXPECT_CALL(element, Call(&b)).WillOnce(ReturnRef(b));
+ EXPECT_CALL(construct, Call(&alloc, &a, b)).Times(AnyNumber());
+ EXPECT_CALL(destroy, Call(&alloc, &b)).Times(AnyNumber());
+ common_policy_traits<PolicyWithoutOptionalOps>::transfer(&alloc, &a, &b);
+}
+
+TEST_F(Test, with_transfer) {
+ int b = 42;
+ EXPECT_CALL(transfer, Call(&alloc, &a, &b));
+ common_policy_traits<PolicyWithOptionalOps>::transfer(&alloc, &a, &b);
+}
+
+TEST(TransferUsesMemcpy, Basic) {
+ EXPECT_FALSE(
+ common_policy_traits<PolicyWithOptionalOps>::transfer_uses_memcpy());
+ EXPECT_TRUE(
+ common_policy_traits<
+ PolicyWithMemcpyTransferAndTrivialDestroy>::transfer_uses_memcpy());
+}
+
+TEST(DestroyIsTrivial, Basic) {
+ EXPECT_FALSE(common_policy_traits<PolicyWithOptionalOps>::destroy_is_trivial<
+ std::allocator<char>>());
+ EXPECT_TRUE(common_policy_traits<PolicyWithMemcpyTransferAndTrivialDestroy>::
+ destroy_is_trivial<std::allocator<char>>());
+}
+
+} // namespace
+} // 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 5ebe164..59e70eb 100644
--- a/absl/container/internal/compressed_tuple.h
+++ b/absl/container/internal/compressed_tuple.h
@@ -64,19 +64,6 @@
template <typename D, size_t I>
using ElemT = typename Elem<D, I>::type;
-// Use the __is_final intrinsic if available. Where it's not available, classes
-// declared with the 'final' specifier cannot be used as CompressedTuple
-// elements.
-// TODO(sbenza): Replace this with std::is_final in C++14.
-template <typename T>
-constexpr bool IsFinal() {
-#if defined(__clang__) || defined(__GNUC__)
- return __is_final(T);
-#else
- return false;
-#endif
-}
-
// We can't use EBCO on other CompressedTuples because that would mean that we
// derive from multiple Storage<> instantiations with the same I parameter,
// and potentially from multiple identical Storage<> instantiations. So anytime
@@ -86,20 +73,15 @@
template <typename T>
constexpr bool ShouldUseBase() {
- return std::is_class<T>::value && std::is_empty<T>::value && !IsFinal<T>() &&
+ return std::is_class<T>::value && std::is_empty<T>::value &&
+ !std::is_final<T>::value &&
!std::is_base_of<uses_inheritance, T>::value;
}
// The storage class provides two specializations:
// - For empty classes, it stores T as a base class.
// - For everything else, it stores T as a member.
-template <typename T, size_t I,
-#if defined(_MSC_VER)
- bool UseBase =
- ShouldUseBase<typename std::enable_if<true, T>::type>()>
-#else
- bool UseBase = ShouldUseBase<T>()>
-#endif
+template <typename T, size_t I, bool UseBase = ShouldUseBase<T>()>
struct Storage {
T value;
constexpr Storage() = default;
diff --git a/absl/container/internal/compressed_tuple_test.cc b/absl/container/internal/compressed_tuple_test.cc
index 62a7483..da07baa 100644
--- a/absl/container/internal/compressed_tuple_test.cc
+++ b/absl/container/internal/compressed_tuple_test.cc
@@ -358,7 +358,6 @@
EXPECT_EQ(x2, 5);
EXPECT_EQ(x3, CallType::kConstRef);
-#if !defined(__GNUC__) || defined(__clang__) || __GNUC__ > 4
constexpr CompressedTuple<Empty<0>, TrivialStruct, int> trivial = {};
constexpr CallType trivial0 = trivial.get<0>().value();
constexpr int trivial1 = trivial.get<1>().value();
@@ -367,7 +366,6 @@
EXPECT_EQ(trivial0, CallType::kConstRef);
EXPECT_EQ(trivial1, 0);
EXPECT_EQ(trivial2, 0);
-#endif
constexpr CompressedTuple<Empty<0>, NonTrivialStruct, absl::optional<int>>
non_trivial = {};
@@ -403,6 +401,16 @@
}
#endif
+// TODO(b/214288561): enable this test.
+TEST(CompressedTupleTest, DISABLED_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));
+}
+
} // namespace
} // namespace container_internal
ABSL_NAMESPACE_END
diff --git a/absl/container/internal/container_memory.h b/absl/container/internal/container_memory.h
index e67529e..ba8e08a 100644
--- a/absl/container/internal/container_memory.h
+++ b/absl/container/internal/container_memory.h
@@ -17,6 +17,7 @@
#include <cassert>
#include <cstddef>
+#include <cstring>
#include <memory>
#include <new>
#include <tuple>
@@ -67,6 +68,18 @@
return p;
}
+// Returns true if the destruction of the value with given Allocator will be
+// trivial.
+template <class Allocator, class ValueType>
+constexpr auto IsDestructionTrivial() {
+ constexpr bool result =
+ std::is_trivially_destructible<ValueType>::value &&
+ std::is_same<typename absl::allocator_traits<
+ Allocator>::template rebind_alloc<char>,
+ std::allocator<char>>::value;
+ return std::integral_constant<bool, result>();
+}
+
// The pointer must have been previously obtained by calling
// Allocate<Alignment>(alloc, n).
template <size_t Alignment, class Alloc>
@@ -121,10 +134,10 @@
// Returns a tuple of references to the elements of the input tuple. T must be a
// tuple.
template <class T>
-auto TupleRef(T&& t) -> decltype(
- TupleRefImpl(std::forward<T>(t),
- absl::make_index_sequence<
- std::tuple_size<typename std::decay<T>::type>::value>())) {
+auto TupleRef(T&& t) -> decltype(TupleRefImpl(
+ std::forward<T>(t),
+ absl::make_index_sequence<
+ std::tuple_size<typename std::decay<T>::type>::value>())) {
return TupleRefImpl(
std::forward<T>(t),
absl::make_index_sequence<
@@ -155,8 +168,8 @@
// Constructs T using the args specified in the tuple and calls F with the
// constructed value.
template <class T, class Tuple, class F>
-decltype(std::declval<F>()(std::declval<T>())) WithConstructed(
- Tuple&& t, F&& f) {
+decltype(std::declval<F>()(std::declval<T>())) WithConstructed(Tuple&& t,
+ F&& f) {
return memory_internal::WithConstructedImpl<T>(
std::forward<Tuple>(t),
absl::make_index_sequence<
@@ -164,7 +177,7 @@
std::forward<F>(f));
}
-// Given arguments of an std::pair's consructor, PairArgs() returns a pair of
+// Given arguments of an std::pair's constructor, PairArgs() returns a pair of
// tuples with references to the passed arguments. The tuples contain
// constructor arguments for the first and the second elements of the pair.
//
@@ -174,7 +187,7 @@
//
// 2. auto a = PairArgs(args...);
// std::pair<F, S> p(std::piecewise_construct,
-// std::move(p.first), std::move(p.second));
+// std::move(a.first), std::move(a.second));
inline std::pair<std::tuple<>, std::tuple<>> PairArgs() { return {}; }
template <class F, class S>
std::pair<std::tuple<F&&>, std::tuple<S&&>> PairArgs(F&& f, S&& s) {
@@ -340,7 +353,8 @@
struct map_slot_policy {
using slot_type = map_slot_type<K, V>;
using value_type = std::pair<const K, V>;
- using mutable_value_type = std::pair<K, V>;
+ using mutable_value_type =
+ std::pair<absl::remove_const_t<K>, absl::remove_const_t<V>>;
private:
static void emplace(slot_type* slot) {
@@ -402,19 +416,42 @@
}
}
+ // Construct this slot by copying from another slot.
template <class Allocator>
- static void destroy(Allocator* alloc, slot_type* slot) {
+ static void construct(Allocator* alloc, slot_type* slot,
+ const slot_type* other) {
+ emplace(slot);
+ absl::allocator_traits<Allocator>::construct(*alloc, &slot->value,
+ other->value);
+ }
+
+ template <class Allocator>
+ static auto destroy(Allocator* alloc, slot_type* slot) {
if (kMutableKeys::value) {
absl::allocator_traits<Allocator>::destroy(*alloc, &slot->mutable_value);
} else {
absl::allocator_traits<Allocator>::destroy(*alloc, &slot->value);
}
+ return IsDestructionTrivial<Allocator, value_type>();
}
template <class Allocator>
- static void transfer(Allocator* alloc, slot_type* new_slot,
+ static auto transfer(Allocator* alloc, slot_type* new_slot,
slot_type* old_slot) {
+ auto is_relocatable =
+ typename absl::is_trivially_relocatable<value_type>::type();
+
emplace(new_slot);
+#if defined(__cpp_lib_launder) && __cpp_lib_launder >= 201606
+ if (is_relocatable) {
+ // TODO(b/247130232,b/251814870): remove casts after fixing warnings.
+ std::memcpy(static_cast<void*>(std::launder(&new_slot->value)),
+ static_cast<const void*>(&old_slot->value),
+ sizeof(value_type));
+ return is_relocatable;
+ }
+#endif
+
if (kMutableKeys::value) {
absl::allocator_traits<Allocator>::construct(
*alloc, &new_slot->mutable_value, std::move(old_slot->mutable_value));
@@ -423,36 +460,30 @@
std::move(old_slot->value));
}
destroy(alloc, old_slot);
- }
-
- template <class Allocator>
- static void swap(Allocator* alloc, slot_type* a, slot_type* b) {
- if (kMutableKeys::value) {
- using std::swap;
- swap(a->mutable_value, b->mutable_value);
- } else {
- value_type tmp = std::move(a->value);
- absl::allocator_traits<Allocator>::destroy(*alloc, &a->value);
- absl::allocator_traits<Allocator>::construct(*alloc, &a->value,
- std::move(b->value));
- absl::allocator_traits<Allocator>::destroy(*alloc, &b->value);
- absl::allocator_traits<Allocator>::construct(*alloc, &b->value,
- std::move(tmp));
- }
- }
-
- template <class Allocator>
- static void move(Allocator* alloc, slot_type* src, slot_type* dest) {
- if (kMutableKeys::value) {
- dest->mutable_value = std::move(src->mutable_value);
- } else {
- absl::allocator_traits<Allocator>::destroy(*alloc, &dest->value);
- absl::allocator_traits<Allocator>::construct(*alloc, &dest->value,
- std::move(src->value));
- }
+ return is_relocatable;
}
};
+// Type erased function for computing hash of the slot.
+using HashSlotFn = size_t (*)(const void* hash_fn, void* slot);
+
+// 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) {
+ const auto* f = static_cast<const Fn*>(fn);
+ return (*f)(*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) {
+ const auto* f = static_cast<const Fn*>(fn);
+ const T* slot = *static_cast<const T**>(slot_ptr);
+ return (*f)(*slot);
+}
+
} // namespace container_internal
ABSL_NAMESPACE_END
} // namespace absl
diff --git a/absl/container/internal/container_memory_test.cc b/absl/container/internal/container_memory_test.cc
index fb9c4dd..7e4357d 100644
--- a/absl/container/internal/container_memory_test.cc
+++ b/absl/container/internal/container_memory_test.cc
@@ -14,15 +14,20 @@
#include "absl/container/internal/container_memory.h"
+#include <cstddef>
#include <cstdint>
+#include <memory>
#include <tuple>
+#include <type_traits>
#include <typeindex>
#include <typeinfo>
#include <utility>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
+#include "absl/base/no_destructor.h"
#include "absl/container/internal/test_instance_tracker.h"
+#include "absl/meta/type_traits.h"
#include "absl/strings/string_view.h"
namespace absl {
@@ -54,7 +59,7 @@
}
std::map<std::type_index, int>& AllocationMap() {
- static auto* map = new std::map<std::type_index, int>;
+ static absl::NoDestructor<std::map<std::type_index, int>> map;
return *map;
}
@@ -219,8 +224,7 @@
ADD_FAILURE() << "Must not be called";
return 'A';
};
- EXPECT_STREQ("not decomposable",
- TryDecomposePair(f));
+ EXPECT_STREQ("not decomposable", TryDecomposePair(f));
EXPECT_STREQ("not decomposable",
TryDecomposePair(f, std::piecewise_construct, std::make_tuple(),
std::make_tuple(0.5)));
@@ -251,6 +255,63 @@
EXPECT_EQ(tracker.copies(), 0);
}
+TEST(MapSlotPolicy, TransferReturnsTrue) {
+ {
+ using slot_policy = map_slot_policy<int, float>;
+ EXPECT_TRUE(
+ (std::is_same<decltype(slot_policy::transfer<std::allocator<char>>(
+ nullptr, nullptr, nullptr)),
+ std::true_type>::value));
+ }
+ {
+ struct NonRelocatable {
+ NonRelocatable() = default;
+ NonRelocatable(NonRelocatable&&) {}
+ NonRelocatable& operator=(NonRelocatable&&) { return *this; }
+ void* self = nullptr;
+ };
+
+ EXPECT_FALSE(absl::is_trivially_relocatable<NonRelocatable>::value);
+ using slot_policy = map_slot_policy<int, NonRelocatable>;
+ EXPECT_TRUE(
+ (std::is_same<decltype(slot_policy::transfer<std::allocator<char>>(
+ nullptr, nullptr, nullptr)),
+ std::false_type>::value));
+ }
+}
+
+TEST(MapSlotPolicy, DestroyReturnsTrue) {
+ {
+ using slot_policy = map_slot_policy<int, float>;
+ EXPECT_TRUE(
+ (std::is_same<decltype(slot_policy::destroy<std::allocator<char>>(
+ nullptr, nullptr)),
+ std::true_type>::value));
+ }
+ {
+ EXPECT_FALSE(std::is_trivially_destructible<std::unique_ptr<int>>::value);
+ using slot_policy = map_slot_policy<int, std::unique_ptr<int>>;
+ EXPECT_TRUE(
+ (std::is_same<decltype(slot_policy::destroy<std::allocator<char>>(
+ nullptr, nullptr)),
+ std::false_type>::value));
+ }
+}
+
+TEST(ApplyTest, TypeErasedApplyToSlotFn) {
+ size_t x = 7;
+ auto fn = [](size_t v) { return v * 2; };
+ EXPECT_EQ((TypeErasedApplyToSlotFn<decltype(fn), size_t>(&fn, &x)), 14);
+}
+
+TEST(ApplyTest, TypeErasedDerefAndApplyToSlotFn) {
+ size_t x = 7;
+ auto fn = [](size_t v) { return v * 2; };
+ size_t* x_ptr = &x;
+ EXPECT_EQ(
+ (TypeErasedDerefAndApplyToSlotFn<decltype(fn), size_t>(&fn, &x_ptr)), 14);
+}
+
} // namespace
} // namespace container_internal
ABSL_NAMESPACE_END
diff --git a/absl/container/internal/counting_allocator.h b/absl/container/internal/counting_allocator.h
deleted file mode 100644
index 927cf08..0000000
--- a/absl/container/internal/counting_allocator.h
+++ /dev/null
@@ -1,114 +0,0 @@
-// Copyright 2018 The Abseil Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef ABSL_CONTAINER_INTERNAL_COUNTING_ALLOCATOR_H_
-#define ABSL_CONTAINER_INTERNAL_COUNTING_ALLOCATOR_H_
-
-#include <cstdint>
-#include <memory>
-
-#include "absl/base/config.h"
-
-namespace absl {
-ABSL_NAMESPACE_BEGIN
-namespace container_internal {
-
-// This is a stateful allocator, but the state lives outside of the
-// allocator (in whatever test is using the allocator). This is odd
-// but helps in tests where the allocator is propagated into nested
-// containers - that chain of allocators uses the same state and is
-// thus easier to query for aggregate allocation information.
-template <typename T>
-class CountingAllocator {
- public:
- using Allocator = std::allocator<T>;
- using AllocatorTraits = std::allocator_traits<Allocator>;
- using value_type = typename AllocatorTraits::value_type;
- using pointer = typename AllocatorTraits::pointer;
- using const_pointer = typename AllocatorTraits::const_pointer;
- using size_type = typename AllocatorTraits::size_type;
- using difference_type = typename AllocatorTraits::difference_type;
-
- CountingAllocator() = default;
- explicit CountingAllocator(int64_t* bytes_used) : bytes_used_(bytes_used) {}
- CountingAllocator(int64_t* bytes_used, int64_t* instance_count)
- : bytes_used_(bytes_used), instance_count_(instance_count) {}
-
- template <typename U>
- CountingAllocator(const CountingAllocator<U>& x)
- : bytes_used_(x.bytes_used_), instance_count_(x.instance_count_) {}
-
- pointer allocate(
- size_type n,
- typename AllocatorTraits::const_void_pointer hint = nullptr) {
- Allocator allocator;
- pointer ptr = AllocatorTraits::allocate(allocator, n, hint);
- if (bytes_used_ != nullptr) {
- *bytes_used_ += n * sizeof(T);
- }
- return ptr;
- }
-
- void deallocate(pointer p, size_type n) {
- Allocator allocator;
- AllocatorTraits::deallocate(allocator, p, n);
- if (bytes_used_ != nullptr) {
- *bytes_used_ -= n * sizeof(T);
- }
- }
-
- template <typename U, typename... Args>
- void construct(U* p, Args&&... args) {
- Allocator allocator;
- AllocatorTraits::construct(allocator, p, std::forward<Args>(args)...);
- if (instance_count_ != nullptr) {
- *instance_count_ += 1;
- }
- }
-
- template <typename U>
- void destroy(U* p) {
- Allocator allocator;
- AllocatorTraits::destroy(allocator, p);
- if (instance_count_ != nullptr) {
- *instance_count_ -= 1;
- }
- }
-
- template <typename U>
- class rebind {
- public:
- using other = CountingAllocator<U>;
- };
-
- friend bool operator==(const CountingAllocator& a,
- const CountingAllocator& b) {
- return a.bytes_used_ == b.bytes_used_ &&
- a.instance_count_ == b.instance_count_;
- }
-
- friend bool operator!=(const CountingAllocator& a,
- const CountingAllocator& b) {
- return !(a == b);
- }
-
- int64_t* bytes_used_ = nullptr;
- int64_t* instance_count_ = nullptr;
-};
-
-} // namespace container_internal
-ABSL_NAMESPACE_END
-} // namespace absl
-
-#endif // ABSL_CONTAINER_INTERNAL_COUNTING_ALLOCATOR_H_
diff --git a/absl/container/internal/hash_function_defaults.h b/absl/container/internal/hash_function_defaults.h
index 0683422..0f07bcf 100644
--- a/absl/container/internal/hash_function_defaults.h
+++ b/absl/container/internal/hash_function_defaults.h
@@ -45,17 +45,23 @@
#ifndef ABSL_CONTAINER_INTERNAL_HASH_FUNCTION_DEFAULTS_H_
#define ABSL_CONTAINER_INTERNAL_HASH_FUNCTION_DEFAULTS_H_
-#include <stdint.h>
#include <cstddef>
+#include <functional>
#include <memory>
#include <string>
#include <type_traits>
#include "absl/base/config.h"
+#include "absl/container/internal/common.h"
#include "absl/hash/hash.h"
+#include "absl/meta/type_traits.h"
#include "absl/strings/cord.h"
#include "absl/strings/string_view.h"
+#ifdef ABSL_HAVE_STD_STRING_VIEW
+#include <string_view>
+#endif
+
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace container_internal {
@@ -78,24 +84,26 @@
}
};
+struct StringEq {
+ using is_transparent = void;
+ bool operator()(absl::string_view lhs, absl::string_view rhs) const {
+ return lhs == rhs;
+ }
+ bool operator()(const absl::Cord& lhs, const absl::Cord& rhs) const {
+ return lhs == rhs;
+ }
+ bool operator()(const absl::Cord& lhs, absl::string_view rhs) const {
+ return lhs == rhs;
+ }
+ bool operator()(absl::string_view lhs, const absl::Cord& rhs) const {
+ return lhs == rhs;
+ }
+};
+
// Supports heterogeneous lookup for string-like elements.
struct StringHashEq {
using Hash = StringHash;
- struct Eq {
- using is_transparent = void;
- bool operator()(absl::string_view lhs, absl::string_view rhs) const {
- return lhs == rhs;
- }
- bool operator()(const absl::Cord& lhs, const absl::Cord& rhs) const {
- return lhs == rhs;
- }
- bool operator()(const absl::Cord& lhs, absl::string_view rhs) const {
- return lhs == rhs;
- }
- bool operator()(absl::string_view lhs, const absl::Cord& rhs) const {
- return lhs == rhs;
- }
- };
+ using Eq = StringEq;
};
template <>
@@ -105,6 +113,48 @@
template <>
struct HashEq<absl::Cord> : StringHashEq {};
+#ifdef ABSL_HAVE_STD_STRING_VIEW
+
+template <typename TChar>
+struct BasicStringHash {
+ using is_transparent = void;
+
+ size_t operator()(std::basic_string_view<TChar> v) const {
+ return absl::Hash<std::basic_string_view<TChar>>{}(v);
+ }
+};
+
+template <typename TChar>
+struct BasicStringEq {
+ using is_transparent = void;
+ bool operator()(std::basic_string_view<TChar> lhs,
+ std::basic_string_view<TChar> rhs) const {
+ return lhs == rhs;
+ }
+};
+
+// Supports heterogeneous lookup for w/u16/u32 string + string_view + char*.
+template <typename TChar>
+struct BasicStringHashEq {
+ using Hash = BasicStringHash<TChar>;
+ using Eq = BasicStringEq<TChar>;
+};
+
+template <>
+struct HashEq<std::wstring> : BasicStringHashEq<wchar_t> {};
+template <>
+struct HashEq<std::wstring_view> : BasicStringHashEq<wchar_t> {};
+template <>
+struct HashEq<std::u16string> : BasicStringHashEq<char16_t> {};
+template <>
+struct HashEq<std::u16string_view> : BasicStringHashEq<char16_t> {};
+template <>
+struct HashEq<std::u32string> : BasicStringHashEq<char32_t> {};
+template <>
+struct HashEq<std::u32string_view> : BasicStringHashEq<char32_t> {};
+
+#endif // ABSL_HAVE_STD_STRING_VIEW
+
// Supports heterogeneous lookup for pointers and smart pointers.
template <class T>
struct HashEq<T*> {
@@ -140,6 +190,71 @@
template <class T>
struct HashEq<std::shared_ptr<T>> : HashEq<T*> {};
+template <typename T, typename E = void>
+struct HasAbslContainerHash : std::false_type {};
+
+template <typename T>
+struct HasAbslContainerHash<T, absl::void_t<typename T::absl_container_hash>>
+ : std::true_type {};
+
+template <typename T, typename E = void>
+struct HasAbslContainerEq : std::false_type {};
+
+template <typename T>
+struct HasAbslContainerEq<T, absl::void_t<typename T::absl_container_eq>>
+ : std::true_type {};
+
+template <typename T, typename E = void>
+struct AbslContainerEq {
+ using type = std::equal_to<>;
+};
+
+template <typename T>
+struct AbslContainerEq<
+ T, typename std::enable_if_t<HasAbslContainerEq<T>::value>> {
+ using type = typename T::absl_container_eq;
+};
+
+template <typename T, typename E = void>
+struct AbslContainerHash {
+ using type = void;
+};
+
+template <typename T>
+struct AbslContainerHash<
+ T, typename std::enable_if_t<HasAbslContainerHash<T>::value>> {
+ using type = typename T::absl_container_hash;
+};
+
+// HashEq specialization for user types that provide `absl_container_hash` and
+// (optionally) `absl_container_eq`. This specialization allows user types to
+// provide heterogeneous lookup without requiring to explicitly specify Hash/Eq
+// type arguments in unordered Abseil containers.
+//
+// Both `absl_container_hash` and `absl_container_eq` should be transparent
+// (have inner is_transparent type). While there is no technical reason to
+// restrict to transparent-only types, there is also no feasible use case when
+// it shouldn't be transparent - it is easier to relax the requirement later if
+// such a case arises rather than restricting it.
+//
+// If type provides only `absl_container_hash` then `eq` part will be
+// `std::equal_to<void>`.
+//
+// User types are not allowed to provide only a `Eq` part as there is no
+// feasible use case for this behavior - if Hash should be a default one then Eq
+// should be an equivalent to the `std::equal_to<T>`.
+template <typename T>
+struct HashEq<T, typename std::enable_if_t<HasAbslContainerHash<T>::value>> {
+ using Hash = typename AbslContainerHash<T>::type;
+ using Eq = typename AbslContainerEq<T>::type;
+ static_assert(IsTransparent<Hash>::value,
+ "absl_container_hash must be transparent. To achieve it add a "
+ "`using is_transparent = void;` clause to this type.");
+ static_assert(IsTransparent<Eq>::value,
+ "absl_container_eq must be transparent. To achieve it add a "
+ "`using is_transparent = void;` clause to this type.");
+};
+
// This header's visibility is restricted. If you need to access the default
// hasher please use the container's ::hasher alias instead.
//
diff --git a/absl/container/internal/hash_function_defaults_test.cc b/absl/container/internal/hash_function_defaults_test.cc
index 59576b8..912d119 100644
--- a/absl/container/internal/hash_function_defaults_test.cc
+++ b/absl/container/internal/hash_function_defaults_test.cc
@@ -14,16 +14,24 @@
#include "absl/container/internal/hash_function_defaults.h"
+#include <cstddef>
#include <functional>
#include <type_traits>
#include <utility>
#include "gtest/gtest.h"
+#include "absl/container/flat_hash_map.h"
+#include "absl/container/flat_hash_set.h"
+#include "absl/hash/hash.h"
#include "absl/random/random.h"
#include "absl/strings/cord.h"
#include "absl/strings/cord_test_helpers.h"
#include "absl/strings/string_view.h"
+#ifdef ABSL_HAVE_STD_STRING_VIEW
+#include <string_view>
+#endif
+
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace container_internal {
@@ -109,6 +117,168 @@
EXPECT_NE(h, hash(std::string("b")));
}
+TEST(BasicStringViewTest, WStringEqWorks) {
+#ifndef ABSL_HAVE_STD_STRING_VIEW
+ GTEST_SKIP();
+#else
+ hash_default_eq<std::wstring> eq;
+ EXPECT_TRUE(eq(L"a", L"a"));
+ EXPECT_TRUE(eq(L"a", std::wstring_view(L"a")));
+ EXPECT_TRUE(eq(L"a", std::wstring(L"a")));
+ EXPECT_FALSE(eq(L"a", L"b"));
+ EXPECT_FALSE(eq(L"a", std::wstring_view(L"b")));
+ EXPECT_FALSE(eq(L"a", std::wstring(L"b")));
+#endif
+}
+
+TEST(BasicStringViewTest, WStringViewEqWorks) {
+#ifndef ABSL_HAVE_STD_STRING_VIEW
+ GTEST_SKIP();
+#else
+ hash_default_eq<std::wstring_view> eq;
+ EXPECT_TRUE(eq(L"a", L"a"));
+ EXPECT_TRUE(eq(L"a", std::wstring_view(L"a")));
+ EXPECT_TRUE(eq(L"a", std::wstring(L"a")));
+ EXPECT_FALSE(eq(L"a", L"b"));
+ EXPECT_FALSE(eq(L"a", std::wstring_view(L"b")));
+ EXPECT_FALSE(eq(L"a", std::wstring(L"b")));
+#endif
+}
+
+TEST(BasicStringViewTest, U16StringEqWorks) {
+#ifndef ABSL_HAVE_STD_STRING_VIEW
+ GTEST_SKIP();
+#else
+ hash_default_eq<std::u16string> eq;
+ EXPECT_TRUE(eq(u"a", u"a"));
+ EXPECT_TRUE(eq(u"a", std::u16string_view(u"a")));
+ EXPECT_TRUE(eq(u"a", std::u16string(u"a")));
+ EXPECT_FALSE(eq(u"a", u"b"));
+ EXPECT_FALSE(eq(u"a", std::u16string_view(u"b")));
+ EXPECT_FALSE(eq(u"a", std::u16string(u"b")));
+#endif
+}
+
+TEST(BasicStringViewTest, U16StringViewEqWorks) {
+#ifndef ABSL_HAVE_STD_STRING_VIEW
+ GTEST_SKIP();
+#else
+ hash_default_eq<std::u16string_view> eq;
+ EXPECT_TRUE(eq(u"a", u"a"));
+ EXPECT_TRUE(eq(u"a", std::u16string_view(u"a")));
+ EXPECT_TRUE(eq(u"a", std::u16string(u"a")));
+ EXPECT_FALSE(eq(u"a", u"b"));
+ EXPECT_FALSE(eq(u"a", std::u16string_view(u"b")));
+ EXPECT_FALSE(eq(u"a", std::u16string(u"b")));
+#endif
+}
+
+TEST(BasicStringViewTest, U32StringEqWorks) {
+#ifndef ABSL_HAVE_STD_STRING_VIEW
+ GTEST_SKIP();
+#else
+ hash_default_eq<std::u32string> eq;
+ EXPECT_TRUE(eq(U"a", U"a"));
+ EXPECT_TRUE(eq(U"a", std::u32string_view(U"a")));
+ EXPECT_TRUE(eq(U"a", std::u32string(U"a")));
+ EXPECT_FALSE(eq(U"a", U"b"));
+ EXPECT_FALSE(eq(U"a", std::u32string_view(U"b")));
+ EXPECT_FALSE(eq(U"a", std::u32string(U"b")));
+#endif
+}
+
+TEST(BasicStringViewTest, U32StringViewEqWorks) {
+#ifndef ABSL_HAVE_STD_STRING_VIEW
+ GTEST_SKIP();
+#else
+ hash_default_eq<std::u32string_view> eq;
+ EXPECT_TRUE(eq(U"a", U"a"));
+ EXPECT_TRUE(eq(U"a", std::u32string_view(U"a")));
+ EXPECT_TRUE(eq(U"a", std::u32string(U"a")));
+ EXPECT_FALSE(eq(U"a", U"b"));
+ EXPECT_FALSE(eq(U"a", std::u32string_view(U"b")));
+ EXPECT_FALSE(eq(U"a", std::u32string(U"b")));
+#endif
+}
+
+TEST(BasicStringViewTest, WStringHashWorks) {
+#ifndef ABSL_HAVE_STD_STRING_VIEW
+ GTEST_SKIP();
+#else
+ hash_default_hash<std::wstring> hash;
+ auto h = hash(L"a");
+ EXPECT_EQ(h, hash(std::wstring_view(L"a")));
+ EXPECT_EQ(h, hash(std::wstring(L"a")));
+ EXPECT_NE(h, hash(std::wstring_view(L"b")));
+ EXPECT_NE(h, hash(std::wstring(L"b")));
+#endif
+}
+
+TEST(BasicStringViewTest, WStringViewHashWorks) {
+#ifndef ABSL_HAVE_STD_STRING_VIEW
+ GTEST_SKIP();
+#else
+ hash_default_hash<std::wstring_view> hash;
+ auto h = hash(L"a");
+ EXPECT_EQ(h, hash(std::wstring_view(L"a")));
+ EXPECT_EQ(h, hash(std::wstring(L"a")));
+ EXPECT_NE(h, hash(std::wstring_view(L"b")));
+ EXPECT_NE(h, hash(std::wstring(L"b")));
+#endif
+}
+
+TEST(BasicStringViewTest, U16StringHashWorks) {
+#ifndef ABSL_HAVE_STD_STRING_VIEW
+ GTEST_SKIP();
+#else
+ hash_default_hash<std::u16string> hash;
+ auto h = hash(u"a");
+ EXPECT_EQ(h, hash(std::u16string_view(u"a")));
+ EXPECT_EQ(h, hash(std::u16string(u"a")));
+ EXPECT_NE(h, hash(std::u16string_view(u"b")));
+ EXPECT_NE(h, hash(std::u16string(u"b")));
+#endif
+}
+
+TEST(BasicStringViewTest, U16StringViewHashWorks) {
+#ifndef ABSL_HAVE_STD_STRING_VIEW
+ GTEST_SKIP();
+#else
+ hash_default_hash<std::u16string_view> hash;
+ auto h = hash(u"a");
+ EXPECT_EQ(h, hash(std::u16string_view(u"a")));
+ EXPECT_EQ(h, hash(std::u16string(u"a")));
+ EXPECT_NE(h, hash(std::u16string_view(u"b")));
+ EXPECT_NE(h, hash(std::u16string(u"b")));
+#endif
+}
+
+TEST(BasicStringViewTest, U32StringHashWorks) {
+#ifndef ABSL_HAVE_STD_STRING_VIEW
+ GTEST_SKIP();
+#else
+ hash_default_hash<std::u32string> hash;
+ auto h = hash(U"a");
+ EXPECT_EQ(h, hash(std::u32string_view(U"a")));
+ EXPECT_EQ(h, hash(std::u32string(U"a")));
+ EXPECT_NE(h, hash(std::u32string_view(U"b")));
+ EXPECT_NE(h, hash(std::u32string(U"b")));
+#endif
+}
+
+TEST(BasicStringViewTest, U32StringViewHashWorks) {
+#ifndef ABSL_HAVE_STD_STRING_VIEW
+ GTEST_SKIP();
+#else
+ hash_default_hash<std::u32string_view> hash;
+ auto h = hash(U"a");
+ EXPECT_EQ(h, hash(std::u32string_view(U"a")));
+ EXPECT_EQ(h, hash(std::u32string(U"a")));
+ EXPECT_NE(h, hash(std::u32string_view(U"b")));
+ EXPECT_NE(h, hash(std::u32string(U"b")));
+#endif
+}
+
struct NoDeleter {
template <class T>
void operator()(const T* ptr) const {}
@@ -310,26 +480,157 @@
hash_default_hash<typename T::first_type> hash;
};
-TYPED_TEST_CASE_P(StringLikeTest);
+TYPED_TEST_SUITE(StringLikeTest, StringTypesCartesianProduct);
-TYPED_TEST_P(StringLikeTest, Eq) {
+TYPED_TEST(StringLikeTest, Eq) {
EXPECT_TRUE(this->eq(this->a1, this->b1));
EXPECT_TRUE(this->eq(this->b1, this->a1));
}
-TYPED_TEST_P(StringLikeTest, NotEq) {
+TYPED_TEST(StringLikeTest, NotEq) {
EXPECT_FALSE(this->eq(this->a1, this->b2));
EXPECT_FALSE(this->eq(this->b2, this->a1));
}
-TYPED_TEST_P(StringLikeTest, HashEq) {
+TYPED_TEST(StringLikeTest, HashEq) {
EXPECT_EQ(this->hash(this->a1), this->hash(this->b1));
EXPECT_EQ(this->hash(this->a2), this->hash(this->b2));
// It would be a poor hash function which collides on these strings.
EXPECT_NE(this->hash(this->a1), this->hash(this->b2));
}
-TYPED_TEST_SUITE(StringLikeTest, StringTypesCartesianProduct);
+struct TypeWithAbslContainerHash {
+ struct absl_container_hash {
+ using is_transparent = void;
+
+ size_t operator()(const TypeWithAbslContainerHash& foo) const {
+ return absl::HashOf(foo.value);
+ }
+
+ // Extra overload to test that heterogeneity works for this hasher.
+ size_t operator()(int value) const { return absl::HashOf(value); }
+ };
+
+ friend bool operator==(const TypeWithAbslContainerHash& lhs,
+ const TypeWithAbslContainerHash& rhs) {
+ return lhs.value == rhs.value;
+ }
+
+ friend bool operator==(const TypeWithAbslContainerHash& lhs, int rhs) {
+ return lhs.value == rhs;
+ }
+
+ int value;
+ int noise;
+};
+
+struct TypeWithAbslContainerHashAndEq {
+ struct absl_container_hash {
+ using is_transparent = void;
+
+ size_t operator()(const TypeWithAbslContainerHashAndEq& foo) const {
+ return absl::HashOf(foo.value);
+ }
+
+ // Extra overload to test that heterogeneity works for this hasher.
+ size_t operator()(int value) const { return absl::HashOf(value); }
+ };
+
+ struct absl_container_eq {
+ using is_transparent = void;
+
+ bool operator()(const TypeWithAbslContainerHashAndEq& lhs,
+ const TypeWithAbslContainerHashAndEq& rhs) const {
+ return lhs.value == rhs.value;
+ }
+
+ // Extra overload to test that heterogeneity works for this eq.
+ bool operator()(const TypeWithAbslContainerHashAndEq& lhs, int rhs) const {
+ return lhs.value == rhs;
+ }
+ };
+
+ template <typename T>
+ bool operator==(T&& other) const = delete;
+
+ int value;
+ int noise;
+};
+
+using AbslContainerHashTypes =
+ Types<TypeWithAbslContainerHash, TypeWithAbslContainerHashAndEq>;
+
+template <typename T>
+using AbslContainerHashTest = ::testing::Test;
+
+TYPED_TEST_SUITE(AbslContainerHashTest, AbslContainerHashTypes);
+
+TYPED_TEST(AbslContainerHashTest, HasherWorks) {
+ hash_default_hash<TypeParam> hasher;
+
+ TypeParam foo1{/*value=*/1, /*noise=*/100};
+ TypeParam foo1_copy{/*value=*/1, /*noise=*/20};
+ TypeParam foo2{/*value=*/2, /*noise=*/100};
+
+ EXPECT_EQ(hasher(foo1), absl::HashOf(1));
+ EXPECT_EQ(hasher(foo2), absl::HashOf(2));
+ EXPECT_EQ(hasher(foo1), hasher(foo1_copy));
+
+ // Heterogeneity works.
+ EXPECT_EQ(hasher(foo1), hasher(1));
+ EXPECT_EQ(hasher(foo2), hasher(2));
+}
+
+TYPED_TEST(AbslContainerHashTest, EqWorks) {
+ hash_default_eq<TypeParam> eq;
+
+ TypeParam foo1{/*value=*/1, /*noise=*/100};
+ TypeParam foo1_copy{/*value=*/1, /*noise=*/20};
+ TypeParam foo2{/*value=*/2, /*noise=*/100};
+
+ EXPECT_TRUE(eq(foo1, foo1_copy));
+ EXPECT_FALSE(eq(foo1, foo2));
+
+ // Heterogeneity works.
+ EXPECT_TRUE(eq(foo1, 1));
+ EXPECT_FALSE(eq(foo1, 2));
+}
+
+TYPED_TEST(AbslContainerHashTest, HeterogeneityInMapWorks) {
+ absl::flat_hash_map<TypeParam, int> map;
+
+ TypeParam foo1{/*value=*/1, /*noise=*/100};
+ TypeParam foo1_copy{/*value=*/1, /*noise=*/20};
+ TypeParam foo2{/*value=*/2, /*noise=*/100};
+ TypeParam foo3{/*value=*/3, /*noise=*/100};
+
+ map[foo1] = 1;
+ map[foo2] = 2;
+
+ EXPECT_TRUE(map.contains(foo1_copy));
+ EXPECT_EQ(map.at(foo1_copy), 1);
+ EXPECT_TRUE(map.contains(1));
+ EXPECT_EQ(map.at(1), 1);
+ EXPECT_TRUE(map.contains(2));
+ EXPECT_EQ(map.at(2), 2);
+ EXPECT_FALSE(map.contains(foo3));
+ EXPECT_FALSE(map.contains(3));
+}
+
+TYPED_TEST(AbslContainerHashTest, HeterogeneityInSetWorks) {
+ absl::flat_hash_set<TypeParam> set;
+
+ TypeParam foo1{/*value=*/1, /*noise=*/100};
+ TypeParam foo1_copy{/*value=*/1, /*noise=*/20};
+ TypeParam foo2{/*value=*/2, /*noise=*/100};
+
+ set.insert(foo1);
+
+ EXPECT_TRUE(set.contains(foo1_copy));
+ EXPECT_TRUE(set.contains(1));
+ EXPECT_FALSE(set.contains(foo2));
+ EXPECT_FALSE(set.contains(2));
+}
} // namespace
} // namespace container_internal
@@ -337,7 +638,7 @@
} // namespace absl
enum Hash : size_t {
- kStd = 0x1, // std::hash
+ kStd = 0x1, // std::hash
#ifdef _MSC_VER
kExtension = kStd, // In MSVC, std::hash == ::hash
#else // _MSC_VER
diff --git a/absl/container/internal/hash_generator_testing.cc b/absl/container/internal/hash_generator_testing.cc
index 59cc5aa..e89dfdb 100644
--- a/absl/container/internal/hash_generator_testing.cc
+++ b/absl/container/internal/hash_generator_testing.cc
@@ -16,6 +16,8 @@
#include <deque>
+#include "absl/base/no_destructor.h"
+
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace container_internal {
@@ -41,11 +43,11 @@
} // namespace
std::mt19937_64* GetSharedRng() {
- static auto* rng = [] {
+ static absl::NoDestructor<std::mt19937_64> rng([] {
RandomDeviceSeedSeq seed_seq;
- return new std::mt19937_64(seed_seq);
- }();
- return rng;
+ return std::mt19937_64(seed_seq);
+ }());
+ return rng.get();
}
std::string Generator<std::string>::operator()() const {
@@ -59,7 +61,7 @@
}
absl::string_view Generator<absl::string_view>::operator()() const {
- static auto* arena = new std::deque<std::string>();
+ static absl::NoDestructor<std::deque<std::string>> arena;
// NOLINTNEXTLINE(runtime/int)
std::uniform_int_distribution<short> chars(0x20, 0x7E);
arena->emplace_back();
diff --git a/absl/container/internal/hash_policy_testing.h b/absl/container/internal/hash_policy_testing.h
index 01c40d2..66bb12e 100644
--- a/absl/container/internal/hash_policy_testing.h
+++ b/absl/container/internal/hash_policy_testing.h
@@ -174,8 +174,7 @@
// 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 ) || \
-( __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 9 ))
+#if defined(__GLIBCXX__) && __GLIBCXX__ <= 20140425
#define ABSL_UNORDERED_SUPPORTS_ALLOC_CTORS 0
#else
#define ABSL_UNORDERED_SUPPORTS_ALLOC_CTORS 1
diff --git a/absl/container/internal/hash_policy_traits.h b/absl/container/internal/hash_policy_traits.h
index 46c97b1..86ffd1b 100644
--- a/absl/container/internal/hash_policy_traits.h
+++ b/absl/container/internal/hash_policy_traits.h
@@ -21,6 +21,7 @@
#include <type_traits>
#include <utility>
+#include "absl/container/internal/common_policy_traits.h"
#include "absl/meta/type_traits.h"
namespace absl {
@@ -29,7 +30,7 @@
// Defines how slots are initialized/destroyed/moved.
template <class Policy, class = void>
-struct hash_policy_traits {
+struct hash_policy_traits : common_policy_traits<Policy> {
// The type of the keys stored in the hashtable.
using key_type = typename Policy::key_type;
@@ -87,43 +88,6 @@
// Defaults to false if not provided by the policy.
using constant_iterators = ConstantIteratorsImpl<>;
- // PRECONDITION: `slot` is UNINITIALIZED
- // POSTCONDITION: `slot` is INITIALIZED
- template <class Alloc, class... Args>
- static void construct(Alloc* alloc, slot_type* slot, Args&&... args) {
- Policy::construct(alloc, slot, std::forward<Args>(args)...);
- }
-
- // PRECONDITION: `slot` is INITIALIZED
- // POSTCONDITION: `slot` is UNINITIALIZED
- template <class Alloc>
- static void destroy(Alloc* alloc, slot_type* slot) {
- Policy::destroy(alloc, slot);
- }
-
- // Transfers the `old_slot` to `new_slot`. Any memory allocated by the
- // allocator inside `old_slot` to `new_slot` can be transferred.
- //
- // OPTIONAL: defaults to:
- //
- // clone(new_slot, std::move(*old_slot));
- // destroy(old_slot);
- //
- // PRECONDITION: `new_slot` is UNINITIALIZED and `old_slot` is INITIALIZED
- // POSTCONDITION: `new_slot` is INITIALIZED and `old_slot` is
- // UNINITIALIZED
- template <class Alloc>
- static void transfer(Alloc* alloc, slot_type* new_slot, slot_type* old_slot) {
- transfer_impl(alloc, new_slot, old_slot, 0);
- }
-
- // PRECONDITION: `slot` is INITIALIZED
- // POSTCONDITION: `slot` is INITIALIZED
- template <class P = Policy>
- static auto element(slot_type* slot) -> decltype(P::element(slot)) {
- return P::element(slot);
- }
-
// Returns the amount of memory owned by `slot`, exclusive of `sizeof(*slot)`.
//
// If `slot` is nullptr, returns the constant amount of memory owned by any
@@ -174,8 +138,8 @@
// Used for node handle manipulation.
template <class P = Policy>
static auto mutable_key(slot_type* slot)
- -> decltype(P::apply(ReturnKey(), element(slot))) {
- return P::apply(ReturnKey(), element(slot));
+ -> decltype(P::apply(ReturnKey(), hash_policy_traits::element(slot))) {
+ return P::apply(ReturnKey(), hash_policy_traits::element(slot));
}
// Returns the "value" (as opposed to the "key") portion of the element. Used
@@ -185,19 +149,39 @@
return P::value(elem);
}
- private:
- // Use auto -> decltype as an enabler.
- template <class Alloc, class P = Policy>
- static auto transfer_impl(Alloc* alloc, slot_type* new_slot,
- slot_type* old_slot, int)
- -> decltype((void)P::transfer(alloc, new_slot, old_slot)) {
- P::transfer(alloc, new_slot, old_slot);
+ using HashSlotFn = size_t (*)(const void* hash_fn, void* slot);
+
+ template <class Hash>
+ 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.
+#if defined(__GNUC__) && !defined(__clang__)
+#pragma GCC diagnostic push
+// 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>();
+#if defined(__GNUC__) && !defined(__clang__)
+#pragma GCC diagnostic pop
+#endif
}
- template <class Alloc>
- static void transfer_impl(Alloc* alloc, slot_type* new_slot,
- slot_type* old_slot, char) {
- construct(alloc, new_slot, std::move(element(old_slot)));
- destroy(alloc, old_slot);
+
+ 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)));
}
};
diff --git a/absl/container/internal/hash_policy_traits_test.cc b/absl/container/internal/hash_policy_traits_test.cc
index 6ef8b9e..2d2c7c2 100644
--- a/absl/container/internal/hash_policy_traits_test.cc
+++ b/absl/container/internal/hash_policy_traits_test.cc
@@ -14,12 +14,14 @@
#include "absl/container/internal/hash_policy_traits.h"
+#include <cstddef>
#include <functional>
#include <memory>
#include <new>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
+#include "absl/container/internal/container_memory.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
@@ -38,81 +40,36 @@
using key_type = Slot;
using init_type = Slot;
- static std::function<void(void*, Slot*, Slot)> construct;
- static std::function<void(void*, Slot*)> destroy;
-
static std::function<Slot&(Slot*)> element;
static int apply(int v) { return apply_impl(v); }
static std::function<int(int)> apply_impl;
static std::function<Slot&(Slot*)> value;
+
+ template <class Hash>
+ static constexpr HashSlotFn get_hash_slot_fn() {
+ return nullptr;
+ }
};
-std::function<void(void*, Slot*, Slot)> PolicyWithoutOptionalOps::construct;
-std::function<void(void*, Slot*)> PolicyWithoutOptionalOps::destroy;
-
-std::function<Slot&(Slot*)> PolicyWithoutOptionalOps::element;
std::function<int(int)> PolicyWithoutOptionalOps::apply_impl;
std::function<Slot&(Slot*)> PolicyWithoutOptionalOps::value;
-struct PolicyWithOptionalOps : PolicyWithoutOptionalOps {
- static std::function<void(void*, Slot*, Slot*)> transfer;
-};
-
-std::function<void(void*, Slot*, Slot*)> PolicyWithOptionalOps::transfer;
-
struct Test : ::testing::Test {
Test() {
- PolicyWithoutOptionalOps::construct = [&](void* a1, Slot* a2, Slot a3) {
- construct.Call(a1, a2, std::move(a3));
- };
- PolicyWithoutOptionalOps::destroy = [&](void* a1, Slot* a2) {
- destroy.Call(a1, a2);
- };
-
- PolicyWithoutOptionalOps::element = [&](Slot* a1) -> Slot& {
- return element.Call(a1);
- };
PolicyWithoutOptionalOps::apply_impl = [&](int a1) -> int {
return apply.Call(a1);
};
PolicyWithoutOptionalOps::value = [&](Slot* a1) -> Slot& {
return value.Call(a1);
};
-
- PolicyWithOptionalOps::transfer = [&](void* a1, Slot* a2, Slot* a3) {
- return transfer.Call(a1, a2, a3);
- };
}
std::allocator<int> alloc;
int a = 53;
-
- MockFunction<void(void*, Slot*, Slot)> construct;
- MockFunction<void(void*, Slot*)> destroy;
-
- MockFunction<Slot&(Slot*)> element;
MockFunction<int(int)> apply;
MockFunction<Slot&(Slot*)> value;
-
- MockFunction<void(void*, Slot*, Slot*)> transfer;
};
-TEST_F(Test, construct) {
- EXPECT_CALL(construct, Call(&alloc, &a, 53));
- hash_policy_traits<PolicyWithoutOptionalOps>::construct(&alloc, &a, 53);
-}
-
-TEST_F(Test, destroy) {
- EXPECT_CALL(destroy, Call(&alloc, &a));
- hash_policy_traits<PolicyWithoutOptionalOps>::destroy(&alloc, &a);
-}
-
-TEST_F(Test, element) {
- int b = 0;
- EXPECT_CALL(element, Call(&a)).WillOnce(ReturnRef(b));
- EXPECT_EQ(&b, &hash_policy_traits<PolicyWithoutOptionalOps>::element(&a));
-}
-
TEST_F(Test, apply) {
EXPECT_CALL(apply, Call(42)).WillOnce(Return(1337));
EXPECT_EQ(1337, (hash_policy_traits<PolicyWithoutOptionalOps>::apply(42)));
@@ -124,18 +81,61 @@
EXPECT_EQ(&b, &hash_policy_traits<PolicyWithoutOptionalOps>::value(&a));
}
-TEST_F(Test, without_transfer) {
- int b = 42;
- EXPECT_CALL(element, Call(&b)).WillOnce(::testing::ReturnRef(b));
- EXPECT_CALL(construct, Call(&alloc, &a, b));
- EXPECT_CALL(destroy, Call(&alloc, &b));
- hash_policy_traits<PolicyWithoutOptionalOps>::transfer(&alloc, &a, &b);
+struct Hash {
+ size_t operator()(Slot a) const { return static_cast<size_t>(a) * 5; }
+};
+
+struct PolicyNoHashFn {
+ using slot_type = Slot;
+ using key_type = Slot;
+ using init_type = Slot;
+
+ static size_t* apply_called_count;
+
+ static Slot& element(Slot* slot) { return *slot; }
+ template <typename Fn>
+ static size_t apply(const Fn& fn, int v) {
+ ++(*apply_called_count);
+ return fn(v);
+ }
+
+ template <class Hash>
+ static constexpr HashSlotFn get_hash_slot_fn() {
+ return nullptr;
+ }
+};
+
+size_t* PolicyNoHashFn::apply_called_count;
+
+struct PolicyCustomHashFn : PolicyNoHashFn {
+ template <class Hash>
+ static constexpr HashSlotFn get_hash_slot_fn() {
+ return &TypeErasedApplyToSlotFn<Hash, int>;
+ }
+};
+
+TEST(HashTest, PolicyNoHashFn_get_hash_slot_fn) {
+ size_t apply_called_count = 0;
+ PolicyNoHashFn::apply_called_count = &apply_called_count;
+
+ Hash hasher;
+ Slot value = 7;
+ auto* fn = hash_policy_traits<PolicyNoHashFn>::get_hash_slot_fn<Hash>();
+ EXPECT_NE(fn, nullptr);
+ EXPECT_EQ(fn(&hasher, &value), hasher(value));
+ EXPECT_EQ(apply_called_count, 1);
}
-TEST_F(Test, with_transfer) {
- int b = 42;
- EXPECT_CALL(transfer, Call(&alloc, &a, &b));
- hash_policy_traits<PolicyWithOptionalOps>::transfer(&alloc, &a, &b);
+TEST(HashTest, PolicyCustomHashFn_get_hash_slot_fn) {
+ size_t apply_called_count = 0;
+ PolicyNoHashFn::apply_called_count = &apply_called_count;
+
+ 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));
+ EXPECT_EQ(apply_called_count, 0);
}
} // namespace
diff --git a/absl/container/internal/hashtable_debug.h b/absl/container/internal/hashtable_debug.h
index 19d5212..c79c1a9 100644
--- a/absl/container/internal/hashtable_debug.h
+++ b/absl/container/internal/hashtable_debug.h
@@ -95,14 +95,6 @@
HashtableDebugAccess<C>::AllocatedByteSize(c);
}
-// Returns a tight lower bound for AllocatedByteSize(c) where `c` is of type `C`
-// and `c.size()` is equal to `num_elements`.
-template <typename C>
-size_t LowerBoundAllocatedByteSize(size_t num_elements) {
- return absl::container_internal::hashtable_debug_internal::
- HashtableDebugAccess<C>::LowerBoundAllocatedByteSize(num_elements);
-}
-
} // namespace container_internal
ABSL_NAMESPACE_END
} // namespace absl
diff --git a/absl/container/internal/hashtablez_sampler.cc b/absl/container/internal/hashtablez_sampler.cc
index 5a29bed..b21beef 100644
--- a/absl/container/internal/hashtablez_sampler.cc
+++ b/absl/container/internal/hashtablez_sampler.cc
@@ -14,6 +14,7 @@
#include "absl/container/internal/hashtablez_sampler.h"
+#include <algorithm>
#include <atomic>
#include <cassert>
#include <cmath>
@@ -21,49 +22,58 @@
#include <limits>
#include "absl/base/attributes.h"
-#include "absl/base/internal/exponential_biased.h"
-#include "absl/container/internal/have_sse.h"
+#include "absl/base/config.h"
+#include "absl/base/internal/raw_logging.h"
+#include "absl/base/no_destructor.h"
#include "absl/debugging/stacktrace.h"
#include "absl/memory/memory.h"
+#include "absl/profiling/internal/exponential_biased.h"
+#include "absl/profiling/internal/sample_recorder.h"
#include "absl/synchronization/mutex.h"
+#include "absl/time/clock.h"
+#include "absl/utility/utility.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace container_internal {
+
+#ifdef ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL
constexpr int HashtablezInfo::kMaxStackDepth;
+#endif
namespace {
ABSL_CONST_INIT std::atomic<bool> g_hashtablez_enabled{
false
};
ABSL_CONST_INIT std::atomic<int32_t> g_hashtablez_sample_parameter{1 << 10};
-ABSL_CONST_INIT std::atomic<int32_t> g_hashtablez_max_samples{1 << 20};
+std::atomic<HashtablezConfigListener> g_hashtablez_config_listener{nullptr};
#if defined(ABSL_INTERNAL_HASHTABLEZ_SAMPLE)
-ABSL_PER_THREAD_TLS_KEYWORD absl::base_internal::ExponentialBiased
+ABSL_PER_THREAD_TLS_KEYWORD absl::profiling_internal::ExponentialBiased
g_exponential_biased_generator;
#endif
+void TriggerHashtablezConfigListener() {
+ auto* listener = g_hashtablez_config_listener.load(std::memory_order_acquire);
+ if (listener != nullptr) listener();
+}
+
} // namespace
#if defined(ABSL_INTERNAL_HASHTABLEZ_SAMPLE)
-ABSL_PER_THREAD_TLS_KEYWORD int64_t global_next_sample = 0;
+ABSL_PER_THREAD_TLS_KEYWORD SamplingState global_next_sample = {0, 0};
#endif // defined(ABSL_INTERNAL_HASHTABLEZ_SAMPLE)
-HashtablezSampler& HashtablezSampler::Global() {
- static auto* sampler = new HashtablezSampler();
+HashtablezSampler& GlobalHashtablezSampler() {
+ static absl::NoDestructor<HashtablezSampler> sampler;
return *sampler;
}
-HashtablezSampler::DisposeCallback HashtablezSampler::SetDisposeCallback(
- DisposeCallback f) {
- return dispose_.exchange(f, std::memory_order_relaxed);
-}
-
-HashtablezInfo::HashtablezInfo() { PrepareForSampling(); }
+HashtablezInfo::HashtablezInfo() = default;
HashtablezInfo::~HashtablezInfo() = default;
-void HashtablezInfo::PrepareForSampling() {
+void HashtablezInfo::PrepareForSampling(int64_t stride,
+ size_t inline_element_size_value) {
capacity.store(0, std::memory_order_relaxed);
size.store(0, std::memory_order_relaxed);
num_erases.store(0, std::memory_order_relaxed);
@@ -73,100 +83,16 @@
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();
+ weight = stride;
// 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);
- dead = nullptr;
-}
-
-HashtablezSampler::HashtablezSampler()
- : dropped_samples_(0), size_estimate_(0), all_(nullptr), dispose_(nullptr) {
- absl::MutexLock l(&graveyard_.init_mu);
- graveyard_.dead = &graveyard_;
-}
-
-HashtablezSampler::~HashtablezSampler() {
- HashtablezInfo* s = all_.load(std::memory_order_acquire);
- while (s != nullptr) {
- HashtablezInfo* next = s->next;
- delete s;
- s = next;
- }
-}
-
-void HashtablezSampler::PushNew(HashtablezInfo* sample) {
- sample->next = all_.load(std::memory_order_relaxed);
- while (!all_.compare_exchange_weak(sample->next, sample,
- std::memory_order_release,
- std::memory_order_relaxed)) {
- }
-}
-
-void HashtablezSampler::PushDead(HashtablezInfo* sample) {
- if (auto* dispose = dispose_.load(std::memory_order_relaxed)) {
- dispose(*sample);
- }
-
- absl::MutexLock graveyard_lock(&graveyard_.init_mu);
- absl::MutexLock sample_lock(&sample->init_mu);
- sample->dead = graveyard_.dead;
- graveyard_.dead = sample;
-}
-
-HashtablezInfo* HashtablezSampler::PopDead() {
- absl::MutexLock graveyard_lock(&graveyard_.init_mu);
-
- // The list is circular, so eventually it collapses down to
- // graveyard_.dead == &graveyard_
- // when it is empty.
- HashtablezInfo* sample = graveyard_.dead;
- if (sample == &graveyard_) return nullptr;
-
- absl::MutexLock sample_lock(&sample->init_mu);
- graveyard_.dead = sample->dead;
- sample->PrepareForSampling();
- return sample;
-}
-
-HashtablezInfo* HashtablezSampler::Register() {
- int64_t size = size_estimate_.fetch_add(1, std::memory_order_relaxed);
- if (size > g_hashtablez_max_samples.load(std::memory_order_relaxed)) {
- size_estimate_.fetch_sub(1, std::memory_order_relaxed);
- dropped_samples_.fetch_add(1, std::memory_order_relaxed);
- return nullptr;
- }
-
- HashtablezInfo* sample = PopDead();
- if (sample == nullptr) {
- // Resurrection failed. Hire a new warlock.
- sample = new HashtablezInfo();
- PushNew(sample);
- }
-
- return sample;
-}
-
-void HashtablezSampler::Unregister(HashtablezInfo* sample) {
- PushDead(sample);
- size_estimate_.fetch_sub(1, std::memory_order_relaxed);
-}
-
-int64_t HashtablezSampler::Iterate(
- const std::function<void(const HashtablezInfo& stack)>& f) {
- HashtablezInfo* s = all_.load(std::memory_order_acquire);
- while (s != nullptr) {
- absl::MutexLock l(&s->init_mu);
- if (s->dead == nullptr) {
- f(*s);
- }
- s = s->next;
- }
-
- return dropped_samples_.load(std::memory_order_relaxed);
+ inline_element_size = inline_element_size_value;
}
static bool ShouldForceSampling() {
@@ -189,21 +115,32 @@
return state == kForce;
}
-HashtablezInfo* SampleSlow(int64_t* next_sample) {
+HashtablezInfo* SampleSlow(SamplingState& next_sample,
+ size_t inline_element_size) {
if (ABSL_PREDICT_FALSE(ShouldForceSampling())) {
- *next_sample = 1;
- return HashtablezSampler::Global().Register();
+ next_sample.next_sample = 1;
+ const int64_t old_stride = exchange(next_sample.sample_stride, 1);
+ HashtablezInfo* result =
+ GlobalHashtablezSampler().Register(old_stride, inline_element_size);
+ return result;
}
#if !defined(ABSL_INTERNAL_HASHTABLEZ_SAMPLE)
- *next_sample = std::numeric_limits<int64_t>::max();
+ next_sample = {
+ std::numeric_limits<int64_t>::max(),
+ std::numeric_limits<int64_t>::max(),
+ };
return nullptr;
#else
- bool first = *next_sample < 0;
- *next_sample = g_exponential_biased_generator.GetStride(
+ bool first = next_sample.next_sample < 0;
+
+ const int64_t next_stride = g_exponential_biased_generator.GetStride(
g_hashtablez_sample_parameter.load(std::memory_order_relaxed));
+
+ next_sample.next_sample = next_stride;
+ const int64_t old_stride = exchange(next_sample.sample_stride, next_stride);
// Small values of interval are equivalent to just sampling next time.
- ABSL_ASSERT(*next_sample >= 1);
+ ABSL_ASSERT(next_stride >= 1);
// g_hashtablez_enabled can be dynamically flipped, we need to set a threshold
// low enough that we will start sampling in a reasonable time, so we just use
@@ -213,16 +150,53 @@
// We will only be negative on our first count, so we should just retry in
// that case.
if (first) {
- if (ABSL_PREDICT_TRUE(--*next_sample > 0)) return nullptr;
- return SampleSlow(next_sample);
+ if (ABSL_PREDICT_TRUE(--next_sample.next_sample > 0)) return nullptr;
+ return SampleSlow(next_sample, inline_element_size);
}
- return HashtablezSampler::Global().Register();
+ return GlobalHashtablezSampler().Register(old_stride, inline_element_size);
#endif
}
void UnsampleSlow(HashtablezInfo* info) {
- HashtablezSampler::Global().Unregister(info);
+ GlobalHashtablezSampler().Unregister(info);
+}
+
+void RecordRehashSlow(HashtablezInfo* info, size_t total_probe_length) {
+#ifdef ABSL_INTERNAL_HAVE_SSE2
+ total_probe_length /= 16;
+#else
+ total_probe_length /= 8;
+#endif
+ info->total_probe_length.store(total_probe_length, std::memory_order_relaxed);
+ info->num_erases.store(0, std::memory_order_relaxed);
+ // There is only one concurrent writer, so `load` then `store` is sufficient
+ // instead of using `fetch_add`.
+ info->num_rehashes.store(
+ 1 + info->num_rehashes.load(std::memory_order_relaxed),
+ std::memory_order_relaxed);
+}
+
+void RecordReservationSlow(HashtablezInfo* info, size_t target_capacity) {
+ info->max_reserve.store(
+ (std::max)(info->max_reserve.load(std::memory_order_relaxed),
+ target_capacity),
+ std::memory_order_relaxed);
+}
+
+void RecordClearedReservationSlow(HashtablezInfo* info) {
+ info->max_reserve.store(0, std::memory_order_relaxed);
+}
+
+void RecordStorageChangedSlow(HashtablezInfo* info, size_t size,
+ size_t capacity) {
+ info->size.store(size, std::memory_order_relaxed);
+ info->capacity.store(capacity, std::memory_order_relaxed);
+ if (size == 0) {
+ // This is a clear, reset the total/num_erases too.
+ info->total_probe_length.store(0, std::memory_order_relaxed);
+ info->num_erases.store(0, std::memory_order_relaxed);
+ }
}
void RecordInsertSlow(HashtablezInfo* info, size_t hash,
@@ -230,7 +204,7 @@
// 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;
-#if ABSL_INTERNAL_RAW_HASH_SET_HAVE_SSE2
+#ifdef ABSL_INTERNAL_HAVE_SSE2
probe_length /= 16;
#else
probe_length /= 8;
@@ -247,11 +221,41 @@
info->size.fetch_add(1, std::memory_order_relaxed);
}
+void RecordEraseSlow(HashtablezInfo* info) {
+ info->size.fetch_sub(1, std::memory_order_relaxed);
+ // There is only one concurrent writer, so `load` then `store` is sufficient
+ // instead of using `fetch_add`.
+ info->num_erases.store(1 + info->num_erases.load(std::memory_order_relaxed),
+ std::memory_order_relaxed);
+}
+
+void SetHashtablezConfigListener(HashtablezConfigListener l) {
+ g_hashtablez_config_listener.store(l, std::memory_order_release);
+}
+
+bool IsHashtablezEnabled() {
+ return g_hashtablez_enabled.load(std::memory_order_acquire);
+}
+
void SetHashtablezEnabled(bool enabled) {
+ SetHashtablezEnabledInternal(enabled);
+ TriggerHashtablezConfigListener();
+}
+
+void SetHashtablezEnabledInternal(bool enabled) {
g_hashtablez_enabled.store(enabled, std::memory_order_release);
}
+int32_t GetHashtablezSampleParameter() {
+ return g_hashtablez_sample_parameter.load(std::memory_order_acquire);
+}
+
void SetHashtablezSampleParameter(int32_t rate) {
+ SetHashtablezSampleParameterInternal(rate);
+ TriggerHashtablezConfigListener();
+}
+
+void SetHashtablezSampleParameterInternal(int32_t rate) {
if (rate > 0) {
g_hashtablez_sample_parameter.store(rate, std::memory_order_release);
} else {
@@ -260,12 +264,20 @@
}
}
-void SetHashtablezMaxSamples(int32_t max) {
+size_t GetHashtablezMaxSamples() {
+ return GlobalHashtablezSampler().GetMaxSamples();
+}
+
+void SetHashtablezMaxSamples(size_t max) {
+ SetHashtablezMaxSamplesInternal(max);
+ TriggerHashtablezConfigListener();
+}
+
+void SetHashtablezMaxSamplesInternal(size_t max) {
if (max > 0) {
- g_hashtablez_max_samples.store(max, std::memory_order_release);
+ GlobalHashtablezSampler().SetMaxSamples(max);
} else {
- ABSL_RAW_LOG(ERROR, "Invalid hashtablez max samples: %lld",
- static_cast<long long>(max)); // NOLINT(runtime/int)
+ ABSL_RAW_LOG(ERROR, "Invalid hashtablez max samples: 0");
}
}
diff --git a/absl/container/internal/hashtablez_sampler.h b/absl/container/internal/hashtablez_sampler.h
index 85685f7..e41ee2d 100644
--- a/absl/container/internal/hashtablez_sampler.h
+++ b/absl/container/internal/hashtablez_sampler.h
@@ -44,9 +44,10 @@
#include <memory>
#include <vector>
+#include "absl/base/config.h"
#include "absl/base/internal/per_thread_tls.h"
#include "absl/base/optimization.h"
-#include "absl/container/internal/have_sse.h"
+#include "absl/profiling/internal/sample_recorder.h"
#include "absl/synchronization/mutex.h"
#include "absl/utility/utility.h"
@@ -57,7 +58,7 @@
// Stores information about a sampled hashtable. All mutations to this *must*
// be made through `Record*` functions below. All reads from this *must* only
// occur in the callback to `HashtablezSampler::Iterate`.
-struct HashtablezInfo {
+struct HashtablezInfo : public profiling_internal::Sample<HashtablezInfo> {
// Constructs the object but does not fill in any fields.
HashtablezInfo();
~HashtablezInfo();
@@ -66,7 +67,8 @@
// Puts the object into a clean state, fills in the logically `const` members,
// blocking for any readers that are currently sampling the object.
- void PrepareForSampling() ABSL_EXCLUSIVE_LOCKS_REQUIRED(init_mu);
+ void PrepareForSampling(int64_t stride, size_t inline_element_size_value)
+ ABSL_EXCLUSIVE_LOCKS_REQUIRED(init_mu);
// These fields are mutated by the various Record* APIs and need to be
// thread-safe.
@@ -79,65 +81,43 @@
std::atomic<size_t> hashes_bitwise_or;
std::atomic<size_t> hashes_bitwise_and;
std::atomic<size_t> hashes_bitwise_xor;
-
- // `HashtablezSampler` maintains intrusive linked lists for all samples. See
- // comments on `HashtablezSampler::all_` for details on these. `init_mu`
- // guards the ability to restore the sample to a pristine state. This
- // prevents races with sampling and resurrecting an object.
- absl::Mutex init_mu;
- HashtablezInfo* next;
- HashtablezInfo* dead ABSL_GUARDED_BY(init_mu);
+ std::atomic<size_t> max_reserve;
// All of the fields below are set by `PrepareForSampling`, they must not be
// mutated in `Record*` functions. They are logically `const` in that sense.
- // These are guarded by init_mu, but that is not externalized to clients, who
- // can only read them during `HashtablezSampler::Iterate` which will hold the
- // lock.
+ // These are guarded by init_mu, but that is not externalized to clients,
+ // which can read them only during `SampleRecorder::Iterate` which will hold
+ // the lock.
static constexpr int kMaxStackDepth = 64;
absl::Time create_time;
int32_t depth;
void* stack[kMaxStackDepth];
+ size_t inline_element_size; // How big is the slot?
};
-inline void RecordRehashSlow(HashtablezInfo* info, size_t total_probe_length) {
-#if ABSL_INTERNAL_RAW_HASH_SET_HAVE_SSE2
- total_probe_length /= 16;
-#else
- total_probe_length /= 8;
-#endif
- info->total_probe_length.store(total_probe_length, std::memory_order_relaxed);
- info->num_erases.store(0, std::memory_order_relaxed);
- // There is only one concurrent writer, so `load` then `store` is sufficient
- // instead of using `fetch_add`.
- info->num_rehashes.store(
- 1 + info->num_rehashes.load(std::memory_order_relaxed),
- std::memory_order_relaxed);
-}
+void RecordRehashSlow(HashtablezInfo* info, size_t total_probe_length);
-inline void RecordStorageChangedSlow(HashtablezInfo* info, size_t size,
- size_t capacity) {
- info->size.store(size, std::memory_order_relaxed);
- info->capacity.store(capacity, std::memory_order_relaxed);
- if (size == 0) {
- // This is a clear, reset the total/num_erases too.
- info->total_probe_length.store(0, std::memory_order_relaxed);
- info->num_erases.store(0, std::memory_order_relaxed);
- }
-}
+void RecordReservationSlow(HashtablezInfo* info, size_t target_capacity);
+
+void RecordClearedReservationSlow(HashtablezInfo* info);
+
+void RecordStorageChangedSlow(HashtablezInfo* info, size_t size,
+ size_t capacity);
void RecordInsertSlow(HashtablezInfo* info, size_t hash,
size_t distance_from_desired);
-inline void RecordEraseSlow(HashtablezInfo* info) {
- info->size.fetch_sub(1, std::memory_order_relaxed);
- // There is only one concurrent writer, so `load` then `store` is sufficient
- // instead of using `fetch_add`.
- info->num_erases.store(
- 1 + info->num_erases.load(std::memory_order_relaxed),
- std::memory_order_relaxed);
-}
+void RecordEraseSlow(HashtablezInfo* info);
-HashtablezInfo* SampleSlow(int64_t* next_sample);
+struct SamplingState {
+ int64_t next_sample;
+ // When we make a sampling decision, we record that distance so we can weight
+ // each sample.
+ int64_t sample_stride;
+};
+
+HashtablezInfo* SampleSlow(SamplingState& next_sample,
+ size_t inline_element_size);
void UnsampleSlow(HashtablezInfo* info);
#if defined(ABSL_INTERNAL_HASHTABLEZ_SAMPLE)
@@ -149,23 +129,15 @@
public:
explicit HashtablezInfoHandle() : info_(nullptr) {}
explicit HashtablezInfoHandle(HashtablezInfo* info) : info_(info) {}
- ~HashtablezInfoHandle() {
+
+ // We do not have a destructor. Caller is responsible for calling Unregister
+ // before destroying the handle.
+ void Unregister() {
if (ABSL_PREDICT_TRUE(info_ == nullptr)) return;
UnsampleSlow(info_);
}
- HashtablezInfoHandle(const HashtablezInfoHandle&) = delete;
- HashtablezInfoHandle& operator=(const HashtablezInfoHandle&) = delete;
-
- HashtablezInfoHandle(HashtablezInfoHandle&& o) noexcept
- : info_(absl::exchange(o.info_, nullptr)) {}
- HashtablezInfoHandle& operator=(HashtablezInfoHandle&& o) noexcept {
- if (ABSL_PREDICT_FALSE(info_ != nullptr)) {
- UnsampleSlow(info_);
- }
- info_ = absl::exchange(o.info_, nullptr);
- return *this;
- }
+ inline bool IsSampled() const { return ABSL_PREDICT_FALSE(info_ != nullptr); }
inline void RecordStorageChanged(size_t size, size_t capacity) {
if (ABSL_PREDICT_TRUE(info_ == nullptr)) return;
@@ -177,6 +149,16 @@
RecordRehashSlow(info_, total_probe_length);
}
+ inline void RecordReservation(size_t target_capacity) {
+ if (ABSL_PREDICT_TRUE(info_ == nullptr)) return;
+ RecordReservationSlow(info_, target_capacity);
+ }
+
+ inline void RecordClearedReservation() {
+ if (ABSL_PREDICT_TRUE(info_ == nullptr)) return;
+ RecordClearedReservationSlow(info_);
+ }
+
inline void RecordInsert(size_t hash, size_t distance_from_desired) {
if (ABSL_PREDICT_TRUE(info_ == nullptr)) return;
RecordInsertSlow(info_, hash, distance_from_desired);
@@ -204,8 +186,12 @@
explicit HashtablezInfoHandle() = default;
explicit HashtablezInfoHandle(std::nullptr_t) {}
+ inline void Unregister() {}
+ inline bool IsSampled() const { return false; }
inline void RecordStorageChanged(size_t /*size*/, size_t /*capacity*/) {}
inline void RecordRehash(size_t /*total_probe_length*/) {}
+ inline void RecordReservation(size_t /*target_capacity*/) {}
+ inline void RecordClearedReservation() {}
inline void RecordInsert(size_t /*hash*/, size_t /*distance_from_desired*/) {}
inline void RecordErase() {}
@@ -215,98 +201,47 @@
#endif // defined(ABSL_INTERNAL_HASHTABLEZ_SAMPLE)
#if defined(ABSL_INTERNAL_HASHTABLEZ_SAMPLE)
-extern ABSL_PER_THREAD_TLS_KEYWORD int64_t global_next_sample;
+extern ABSL_PER_THREAD_TLS_KEYWORD SamplingState global_next_sample;
#endif // defined(ABSL_INTERNAL_HASHTABLEZ_SAMPLE)
// Returns an RAII sampling handle that manages registration and unregistation
// with the global sampler.
-inline HashtablezInfoHandle Sample() {
+inline HashtablezInfoHandle Sample(
+ size_t inline_element_size ABSL_ATTRIBUTE_UNUSED) {
#if defined(ABSL_INTERNAL_HASHTABLEZ_SAMPLE)
- if (ABSL_PREDICT_TRUE(--global_next_sample > 0)) {
+ if (ABSL_PREDICT_TRUE(--global_next_sample.next_sample > 0)) {
return HashtablezInfoHandle(nullptr);
}
- return HashtablezInfoHandle(SampleSlow(&global_next_sample));
+ return HashtablezInfoHandle(
+ SampleSlow(global_next_sample, inline_element_size));
#else
return HashtablezInfoHandle(nullptr);
#endif // !ABSL_PER_THREAD_TLS
}
-// Holds samples and their associated stack traces with a soft limit of
-// `SetHashtablezMaxSamples()`.
-//
-// Thread safe.
-class HashtablezSampler {
- public:
- // Returns a global Sampler.
- static HashtablezSampler& Global();
+using HashtablezSampler =
+ ::absl::profiling_internal::SampleRecorder<HashtablezInfo>;
- HashtablezSampler();
- ~HashtablezSampler();
+// Returns a global Sampler.
+HashtablezSampler& GlobalHashtablezSampler();
- // Registers for sampling. Returns an opaque registration info.
- HashtablezInfo* Register();
-
- // Unregisters the sample.
- void Unregister(HashtablezInfo* sample);
-
- // The dispose callback will be called on all samples the moment they are
- // being unregistered. Only affects samples that are unregistered after the
- // callback has been set.
- // Returns the previous callback.
- using DisposeCallback = void (*)(const HashtablezInfo&);
- DisposeCallback SetDisposeCallback(DisposeCallback f);
-
- // Iterates over all the registered `StackInfo`s. Returning the number of
- // samples that have been dropped.
- int64_t Iterate(const std::function<void(const HashtablezInfo& stack)>& f);
-
- private:
- void PushNew(HashtablezInfo* sample);
- void PushDead(HashtablezInfo* sample);
- HashtablezInfo* PopDead();
-
- std::atomic<size_t> dropped_samples_;
- std::atomic<size_t> size_estimate_;
-
- // Intrusive lock free linked lists for tracking samples.
- //
- // `all_` records all samples (they are never removed from this list) and is
- // terminated with a `nullptr`.
- //
- // `graveyard_.dead` is a circular linked list. When it is empty,
- // `graveyard_.dead == &graveyard`. The list is circular so that
- // every item on it (even the last) has a non-null dead pointer. This allows
- // `Iterate` to determine if a given sample is live or dead using only
- // information on the sample itself.
- //
- // For example, nodes [A, B, C, D, E] with [A, C, E] alive and [B, D] dead
- // looks like this (G is the Graveyard):
- //
- // +---+ +---+ +---+ +---+ +---+
- // all -->| A |--->| B |--->| C |--->| D |--->| E |
- // | | | | | | | | | |
- // +---+ | | +->| |-+ | | +->| |-+ | |
- // | G | +---+ | +---+ | +---+ | +---+ | +---+
- // | | | | | |
- // | | --------+ +--------+ |
- // +---+ |
- // ^ |
- // +--------------------------------------+
- //
- std::atomic<HashtablezInfo*> all_;
- HashtablezInfo graveyard_;
-
- std::atomic<DisposeCallback> dispose_;
-};
+using HashtablezConfigListener = void (*)();
+void SetHashtablezConfigListener(HashtablezConfigListener l);
// Enables or disables sampling for Swiss tables.
+bool IsHashtablezEnabled();
void SetHashtablezEnabled(bool enabled);
+void SetHashtablezEnabledInternal(bool enabled);
// Sets the rate at which Swiss tables will be sampled.
+int32_t GetHashtablezSampleParameter();
void SetHashtablezSampleParameter(int32_t rate);
+void SetHashtablezSampleParameterInternal(int32_t rate);
// Sets a soft max for the number of samples that will be kept.
-void SetHashtablezMaxSamples(int32_t max);
+size_t GetHashtablezMaxSamples();
+void SetHashtablezMaxSamples(size_t max);
+void SetHashtablezMaxSamplesInternal(size_t max);
// Configuration override.
// This allows process-wide sampling without depending on order of
diff --git a/absl/container/internal/hashtablez_sampler_test.cc b/absl/container/internal/hashtablez_sampler_test.cc
index 5f4c83b..8ebb08d 100644
--- a/absl/container/internal/hashtablez_sampler_test.cc
+++ b/absl/container/internal/hashtablez_sampler_test.cc
@@ -21,7 +21,8 @@
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/base/attributes.h"
-#include "absl/container/internal/have_sse.h"
+#include "absl/base/config.h"
+#include "absl/profiling/internal/sample_recorder.h"
#include "absl/synchronization/blocking_counter.h"
#include "absl/synchronization/internal/thread_pool.h"
#include "absl/synchronization/mutex.h"
@@ -29,7 +30,7 @@
#include "absl/time/clock.h"
#include "absl/time/time.h"
-#if ABSL_INTERNAL_RAW_HASH_SET_HAVE_SSE2
+#ifdef ABSL_INTERNAL_HAVE_SSE2
constexpr int kProbeLength = 16;
#else
constexpr int kProbeLength = 8;
@@ -41,16 +42,11 @@
#if defined(ABSL_INTERNAL_HASHTABLEZ_SAMPLE)
class HashtablezInfoHandlePeer {
public:
- static bool IsSampled(const HashtablezInfoHandle& h) {
- return h.info_ != nullptr;
- }
-
static HashtablezInfo* GetInfo(HashtablezInfoHandle* h) { return h->info_; }
};
#else
class HashtablezInfoHandlePeer {
public:
- static bool IsSampled(const HashtablezInfoHandle&) { return false; }
static HashtablezInfo* GetInfo(HashtablezInfoHandle*) { return nullptr; }
};
#endif // defined(ABSL_INTERNAL_HASHTABLEZ_SAMPLE)
@@ -69,7 +65,9 @@
}
HashtablezInfo* Register(HashtablezSampler* s, size_t size) {
- auto* info = s->Register();
+ const int64_t test_stride = 123;
+ const size_t test_element_size = 17;
+ auto* info = s->Register(test_stride, test_element_size);
assert(info != nullptr);
info->size.store(size);
return info;
@@ -77,9 +75,11 @@
TEST(HashtablezInfoTest, PrepareForSampling) {
absl::Time test_start = absl::Now();
+ const int64_t test_stride = 123;
+ const size_t test_element_size = 17;
HashtablezInfo info;
absl::MutexLock l(&info.init_mu);
- info.PrepareForSampling();
+ info.PrepareForSampling(test_stride, test_element_size);
EXPECT_EQ(info.capacity.load(), 0);
EXPECT_EQ(info.size.load(), 0);
@@ -90,7 +90,10 @@
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);
+ EXPECT_EQ(info.inline_element_size, test_element_size);
info.capacity.store(1, std::memory_order_relaxed);
info.size.store(1, std::memory_order_relaxed);
@@ -100,9 +103,10 @@
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);
- info.PrepareForSampling();
+ info.PrepareForSampling(test_stride * 2, test_element_size);
EXPECT_EQ(info.capacity.load(), 0);
EXPECT_EQ(info.size.load(), 0);
EXPECT_EQ(info.num_erases.load(), 0);
@@ -112,13 +116,18 @@
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);
EXPECT_GE(info.create_time, test_start);
}
TEST(HashtablezInfoTest, RecordStorageChanged) {
HashtablezInfo info;
absl::MutexLock l(&info.init_mu);
- info.PrepareForSampling();
+ const int64_t test_stride = 21;
+ const size_t test_element_size = 19;
+ info.PrepareForSampling(test_stride, test_element_size);
RecordStorageChangedSlow(&info, 17, 47);
EXPECT_EQ(info.size.load(), 17);
EXPECT_EQ(info.capacity.load(), 47);
@@ -130,7 +139,9 @@
TEST(HashtablezInfoTest, RecordInsert) {
HashtablezInfo info;
absl::MutexLock l(&info.init_mu);
- info.PrepareForSampling();
+ const int64_t test_stride = 25;
+ const size_t test_element_size = 23;
+ info.PrepareForSampling(test_stride, test_element_size);
EXPECT_EQ(info.max_probe_length.load(), 0);
RecordInsertSlow(&info, 0x0000FF00, 6 * kProbeLength);
EXPECT_EQ(info.max_probe_length.load(), 6);
@@ -150,9 +161,11 @@
}
TEST(HashtablezInfoTest, RecordErase) {
+ const int64_t test_stride = 31;
+ const size_t test_element_size = 29;
HashtablezInfo info;
absl::MutexLock l(&info.init_mu);
- info.PrepareForSampling();
+ info.PrepareForSampling(test_stride, test_element_size);
EXPECT_EQ(info.num_erases.load(), 0);
EXPECT_EQ(info.size.load(), 0);
RecordInsertSlow(&info, 0x0000FF00, 6 * kProbeLength);
@@ -160,12 +173,15 @@
RecordEraseSlow(&info);
EXPECT_EQ(info.size.load(), 0);
EXPECT_EQ(info.num_erases.load(), 1);
+ EXPECT_EQ(info.inline_element_size, test_element_size);
}
TEST(HashtablezInfoTest, RecordRehash) {
+ const int64_t test_stride = 33;
+ const size_t test_element_size = 31;
HashtablezInfo info;
absl::MutexLock l(&info.init_mu);
- info.PrepareForSampling();
+ info.PrepareForSampling(test_stride, test_element_size);
RecordInsertSlow(&info, 0x1, 0);
RecordInsertSlow(&info, 0x2, kProbeLength);
RecordInsertSlow(&info, 0x4, kProbeLength);
@@ -184,45 +200,69 @@
EXPECT_EQ(info.total_probe_length.load(), 3);
EXPECT_EQ(info.num_erases.load(), 0);
EXPECT_EQ(info.num_rehashes.load(), 1);
+ EXPECT_EQ(info.inline_element_size, test_element_size);
+}
+
+TEST(HashtablezInfoTest, RecordReservation) {
+ HashtablezInfo info;
+ absl::MutexLock l(&info.init_mu);
+ const int64_t test_stride = 35;
+ const size_t test_element_size = 33;
+ info.PrepareForSampling(test_stride, test_element_size);
+ RecordReservationSlow(&info, 3);
+ EXPECT_EQ(info.max_reserve.load(), 3);
+
+ RecordReservationSlow(&info, 2);
+ // High watermark does not change
+ EXPECT_EQ(info.max_reserve.load(), 3);
+
+ RecordReservationSlow(&info, 10);
+ // High watermark does change
+ EXPECT_EQ(info.max_reserve.load(), 10);
}
#if defined(ABSL_INTERNAL_HASHTABLEZ_SAMPLE)
TEST(HashtablezSamplerTest, SmallSampleParameter) {
+ const size_t test_element_size = 31;
SetHashtablezEnabled(true);
SetHashtablezSampleParameter(100);
for (int i = 0; i < 1000; ++i) {
- int64_t next_sample = 0;
- HashtablezInfo* sample = SampleSlow(&next_sample);
- EXPECT_GT(next_sample, 0);
+ SamplingState next_sample = {0, 0};
+ HashtablezInfo* sample = SampleSlow(next_sample, test_element_size);
+ EXPECT_GT(next_sample.next_sample, 0);
+ EXPECT_EQ(next_sample.next_sample, next_sample.sample_stride);
EXPECT_NE(sample, nullptr);
UnsampleSlow(sample);
}
}
TEST(HashtablezSamplerTest, LargeSampleParameter) {
+ const size_t test_element_size = 31;
SetHashtablezEnabled(true);
SetHashtablezSampleParameter(std::numeric_limits<int32_t>::max());
for (int i = 0; i < 1000; ++i) {
- int64_t next_sample = 0;
- HashtablezInfo* sample = SampleSlow(&next_sample);
- EXPECT_GT(next_sample, 0);
+ SamplingState next_sample = {0, 0};
+ HashtablezInfo* sample = SampleSlow(next_sample, test_element_size);
+ EXPECT_GT(next_sample.next_sample, 0);
+ EXPECT_EQ(next_sample.next_sample, next_sample.sample_stride);
EXPECT_NE(sample, nullptr);
UnsampleSlow(sample);
}
}
TEST(HashtablezSamplerTest, Sample) {
+ const size_t test_element_size = 31;
SetHashtablezEnabled(true);
SetHashtablezSampleParameter(100);
int64_t num_sampled = 0;
int64_t total = 0;
double sample_rate = 0.0;
for (int i = 0; i < 1000000; ++i) {
- HashtablezInfoHandle h = Sample();
+ HashtablezInfoHandle h = Sample(test_element_size);
++total;
- if (HashtablezInfoHandlePeer::IsSampled(h)) {
+ if (h.IsSampled()) {
++num_sampled;
}
sample_rate = static_cast<double>(num_sampled) / total;
@@ -232,20 +272,24 @@
}
TEST(HashtablezSamplerTest, Handle) {
- auto& sampler = HashtablezSampler::Global();
- HashtablezInfoHandle h(sampler.Register());
+ auto& sampler = GlobalHashtablezSampler();
+ const int64_t test_stride = 41;
+ const size_t test_element_size = 39;
+ HashtablezInfoHandle h(sampler.Register(test_stride, test_element_size));
auto* info = HashtablezInfoHandlePeer::GetInfo(&h);
info->hashes_bitwise_and.store(0x12345678, std::memory_order_relaxed);
bool found = false;
sampler.Iterate([&](const HashtablezInfo& h) {
if (&h == info) {
+ EXPECT_EQ(h.weight, test_stride);
EXPECT_EQ(h.hashes_bitwise_and.load(), 0x12345678);
found = true;
}
});
EXPECT_TRUE(found);
+ h.Unregister();
h = HashtablezInfoHandle();
found = false;
sampler.Iterate([&](const HashtablezInfo& h) {
@@ -305,18 +349,20 @@
ThreadPool pool(10);
for (int i = 0; i < 10; ++i) {
- pool.Schedule([&sampler, &stop]() {
+ const int64_t sampling_stride = 11 + i % 3;
+ const size_t elt_size = 10 + i % 2;
+ pool.Schedule([&sampler, &stop, sampling_stride, elt_size]() {
std::random_device rd;
std::mt19937 gen(rd());
std::vector<HashtablezInfo*> infoz;
while (!stop.HasBeenNotified()) {
if (infoz.empty()) {
- infoz.push_back(sampler.Register());
+ infoz.push_back(sampler.Register(sampling_stride, elt_size));
}
switch (std::uniform_int_distribution<>(0, 2)(gen)) {
case 0: {
- infoz.push_back(sampler.Register());
+ infoz.push_back(sampler.Register(sampling_stride, elt_size));
break;
}
case 1: {
@@ -325,6 +371,7 @@
HashtablezInfo* info = infoz[p];
infoz[p] = infoz.back();
infoz.pop_back();
+ EXPECT_EQ(info->weight, sampling_stride);
sampler.Unregister(info);
break;
}
diff --git a/absl/container/internal/have_sse.h b/absl/container/internal/have_sse.h
deleted file mode 100644
index e75e1a1..0000000
--- a/absl/container/internal/have_sse.h
+++ /dev/null
@@ -1,50 +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.
-//
-// Shared config probing for SSE instructions used in Swiss tables.
-#ifndef ABSL_CONTAINER_INTERNAL_HAVE_SSE_H_
-#define ABSL_CONTAINER_INTERNAL_HAVE_SSE_H_
-
-#ifndef ABSL_INTERNAL_RAW_HASH_SET_HAVE_SSE2
-#if defined(__SSE2__) || \
- (defined(_MSC_VER) && \
- (defined(_M_X64) || (defined(_M_IX86) && _M_IX86_FP >= 2)))
-#define ABSL_INTERNAL_RAW_HASH_SET_HAVE_SSE2 1
-#else
-#define ABSL_INTERNAL_RAW_HASH_SET_HAVE_SSE2 0
-#endif
-#endif
-
-#ifndef ABSL_INTERNAL_RAW_HASH_SET_HAVE_SSSE3
-#ifdef __SSSE3__
-#define ABSL_INTERNAL_RAW_HASH_SET_HAVE_SSSE3 1
-#else
-#define ABSL_INTERNAL_RAW_HASH_SET_HAVE_SSSE3 0
-#endif
-#endif
-
-#if ABSL_INTERNAL_RAW_HASH_SET_HAVE_SSSE3 && \
- !ABSL_INTERNAL_RAW_HASH_SET_HAVE_SSE2
-#error "Bad configuration!"
-#endif
-
-#if ABSL_INTERNAL_RAW_HASH_SET_HAVE_SSE2
-#include <emmintrin.h>
-#endif
-
-#if ABSL_INTERNAL_RAW_HASH_SET_HAVE_SSSE3
-#include <tmmintrin.h>
-#endif
-
-#endif // ABSL_CONTAINER_INTERNAL_HAVE_SSE_H_
diff --git a/absl/container/internal/inlined_vector.h b/absl/container/internal/inlined_vector.h
index 49822af..a157532 100644
--- a/absl/container/internal/inlined_vector.h
+++ b/absl/container/internal/inlined_vector.h
@@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#ifndef ABSL_CONTAINER_INTERNAL_INLINED_VECTOR_INTERNAL_H_
-#define ABSL_CONTAINER_INTERNAL_INLINED_VECTOR_INTERNAL_H_
+#ifndef ABSL_CONTAINER_INTERNAL_INLINED_VECTOR_H_
+#define ABSL_CONTAINER_INTERNAL_INLINED_VECTOR_H_
#include <algorithm>
#include <cstddef>
@@ -21,8 +21,12 @@
#include <iterator>
#include <limits>
#include <memory>
+#include <new>
+#include <type_traits>
#include <utility>
+#include "absl/base/attributes.h"
+#include "absl/base/config.h"
#include "absl/base/macros.h"
#include "absl/container/internal/compressed_tuple.h"
#include "absl/memory/memory.h"
@@ -37,116 +41,142 @@
#if !defined(__clang__) && defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Warray-bounds"
-#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
#endif
+template <typename A>
+using AllocatorTraits = std::allocator_traits<A>;
+template <typename A>
+using ValueType = typename AllocatorTraits<A>::value_type;
+template <typename A>
+using SizeType = typename AllocatorTraits<A>::size_type;
+template <typename A>
+using Pointer = typename AllocatorTraits<A>::pointer;
+template <typename A>
+using ConstPointer = typename AllocatorTraits<A>::const_pointer;
+template <typename A>
+using SizeType = typename AllocatorTraits<A>::size_type;
+template <typename A>
+using DifferenceType = typename AllocatorTraits<A>::difference_type;
+template <typename A>
+using Reference = ValueType<A>&;
+template <typename A>
+using ConstReference = const ValueType<A>&;
+template <typename A>
+using Iterator = Pointer<A>;
+template <typename A>
+using ConstIterator = ConstPointer<A>;
+template <typename A>
+using ReverseIterator = typename std::reverse_iterator<Iterator<A>>;
+template <typename A>
+using ConstReverseIterator = typename std::reverse_iterator<ConstIterator<A>>;
+template <typename A>
+using MoveIterator = typename std::move_iterator<Iterator<A>>;
+
template <typename Iterator>
using IsAtLeastForwardIterator = std::is_convertible<
typename std::iterator_traits<Iterator>::iterator_category,
std::forward_iterator_tag>;
-template <typename AllocatorType,
- typename ValueType =
- typename absl::allocator_traits<AllocatorType>::value_type>
-using IsMemcpyOk =
- absl::conjunction<std::is_same<AllocatorType, std::allocator<ValueType>>,
- absl::is_trivially_copy_constructible<ValueType>,
- absl::is_trivially_copy_assignable<ValueType>,
- absl::is_trivially_destructible<ValueType>>;
+template <typename A>
+using IsMoveAssignOk = std::is_move_assignable<ValueType<A>>;
+template <typename A>
+using IsSwapOk = absl::type_traits_internal::IsSwappable<ValueType<A>>;
-template <typename AllocatorType, typename Pointer, typename SizeType>
-void DestroyElements(AllocatorType* alloc_ptr, Pointer destroy_first,
- SizeType destroy_size) {
- using AllocatorTraits = absl::allocator_traits<AllocatorType>;
+template <typename T>
+struct TypeIdentity {
+ using type = T;
+};
- if (destroy_first != nullptr) {
- for (auto i = destroy_size; i != 0;) {
+// Used for function arguments in template functions to prevent ADL by forcing
+// callers to explicitly specify the template parameter.
+template <typename T>
+using NoTypeDeduction = typename TypeIdentity<T>::type;
+
+template <typename A, bool IsTriviallyDestructible =
+ absl::is_trivially_destructible<ValueType<A>>::value>
+struct DestroyAdapter;
+
+template <typename A>
+struct DestroyAdapter<A, /* IsTriviallyDestructible */ false> {
+ static void DestroyElements(A& allocator, Pointer<A> destroy_first,
+ SizeType<A> destroy_size) {
+ for (SizeType<A> i = destroy_size; i != 0;) {
--i;
- AllocatorTraits::destroy(*alloc_ptr, destroy_first + i);
+ AllocatorTraits<A>::destroy(allocator, destroy_first + i);
}
-
-#if !defined(NDEBUG)
- {
- using ValueType = typename AllocatorTraits::value_type;
-
- // Overwrite unused memory with `0xab` so we can catch uninitialized
- // usage.
- //
- // Cast to `void*` to tell the compiler that we don't care that we might
- // be scribbling on a vtable pointer.
- void* memory_ptr = destroy_first;
- auto memory_size = destroy_size * sizeof(ValueType);
- std::memset(memory_ptr, 0xab, memory_size);
- }
-#endif // !defined(NDEBUG)
}
-}
+};
-// If kUseMemcpy is true, memcpy(dst, src, n); else do nothing.
-// Useful to avoid compiler warnings when memcpy() is used for T values
-// that are not trivially copyable in non-reachable code.
-template <bool kUseMemcpy>
-inline void MemcpyIfAllowed(void* dst, const void* src, size_t n);
+template <typename A>
+struct DestroyAdapter<A, /* IsTriviallyDestructible */ true> {
+ static void DestroyElements(A& allocator, Pointer<A> destroy_first,
+ SizeType<A> destroy_size) {
+ static_cast<void>(allocator);
+ static_cast<void>(destroy_first);
+ static_cast<void>(destroy_size);
+ }
+};
-// memcpy when allowed.
-template <>
-inline void MemcpyIfAllowed<true>(void* dst, const void* src, size_t n) {
- memcpy(dst, src, n);
-}
+template <typename A>
+struct Allocation {
+ Pointer<A> data = nullptr;
+ SizeType<A> capacity = 0;
+};
-// Do nothing for types that are not memcpy-able. This function is only
-// called from non-reachable branches.
-template <>
-inline void MemcpyIfAllowed<false>(void*, const void*, size_t) {}
+template <typename A,
+ bool IsOverAligned =
+ (alignof(ValueType<A>) > ABSL_INTERNAL_DEFAULT_NEW_ALIGNMENT)>
+struct MallocAdapter {
+ static Allocation<A> Allocate(A& allocator, SizeType<A> requested_capacity) {
+ return {AllocatorTraits<A>::allocate(allocator, requested_capacity),
+ requested_capacity};
+ }
-template <typename AllocatorType, typename Pointer, typename ValueAdapter,
- typename SizeType>
-void ConstructElements(AllocatorType* alloc_ptr, Pointer construct_first,
- ValueAdapter* values_ptr, SizeType construct_size) {
- for (SizeType i = 0; i < construct_size; ++i) {
- ABSL_INTERNAL_TRY {
- values_ptr->ConstructNext(alloc_ptr, construct_first + i);
- }
+ static void Deallocate(A& allocator, Pointer<A> pointer,
+ SizeType<A> capacity) {
+ AllocatorTraits<A>::deallocate(allocator, pointer, capacity);
+ }
+};
+
+template <typename A, typename ValueAdapter>
+void ConstructElements(NoTypeDeduction<A>& allocator,
+ Pointer<A> construct_first, ValueAdapter& values,
+ SizeType<A> construct_size) {
+ for (SizeType<A> i = 0; i < construct_size; ++i) {
+ ABSL_INTERNAL_TRY { values.ConstructNext(allocator, construct_first + i); }
ABSL_INTERNAL_CATCH_ANY {
- inlined_vector_internal::DestroyElements(alloc_ptr, construct_first, i);
+ DestroyAdapter<A>::DestroyElements(allocator, construct_first, i);
ABSL_INTERNAL_RETHROW;
}
}
}
-template <typename Pointer, typename ValueAdapter, typename SizeType>
-void AssignElements(Pointer assign_first, ValueAdapter* values_ptr,
- SizeType assign_size) {
- for (SizeType i = 0; i < assign_size; ++i) {
- values_ptr->AssignNext(assign_first + i);
+template <typename A, typename ValueAdapter>
+void AssignElements(Pointer<A> assign_first, ValueAdapter& values,
+ SizeType<A> assign_size) {
+ for (SizeType<A> i = 0; i < assign_size; ++i) {
+ values.AssignNext(assign_first + i);
}
}
-template <typename AllocatorType>
+template <typename A>
struct StorageView {
- using AllocatorTraits = absl::allocator_traits<AllocatorType>;
- using Pointer = typename AllocatorTraits::pointer;
- using SizeType = typename AllocatorTraits::size_type;
-
- Pointer data;
- SizeType size;
- SizeType capacity;
+ Pointer<A> data;
+ SizeType<A> size;
+ SizeType<A> capacity;
};
-template <typename AllocatorType, typename Iterator>
+template <typename A, typename Iterator>
class IteratorValueAdapter {
- using AllocatorTraits = absl::allocator_traits<AllocatorType>;
- using Pointer = typename AllocatorTraits::pointer;
-
public:
explicit IteratorValueAdapter(const Iterator& it) : it_(it) {}
- void ConstructNext(AllocatorType* alloc_ptr, Pointer construct_at) {
- AllocatorTraits::construct(*alloc_ptr, construct_at, *it_);
+ void ConstructNext(A& allocator, Pointer<A> construct_at) {
+ AllocatorTraits<A>::construct(allocator, construct_at, *it_);
++it_;
}
- void AssignNext(Pointer assign_at) {
+ void AssignNext(Pointer<A> assign_at) {
*assign_at = *it_;
++it_;
}
@@ -155,166 +185,161 @@
Iterator it_;
};
-template <typename AllocatorType>
+template <typename A>
class CopyValueAdapter {
- using AllocatorTraits = absl::allocator_traits<AllocatorType>;
- using ValueType = typename AllocatorTraits::value_type;
- using Pointer = typename AllocatorTraits::pointer;
- using ConstPointer = typename AllocatorTraits::const_pointer;
-
public:
- explicit CopyValueAdapter(const ValueType& v) : ptr_(std::addressof(v)) {}
+ explicit CopyValueAdapter(ConstPointer<A> p) : ptr_(p) {}
- void ConstructNext(AllocatorType* alloc_ptr, Pointer construct_at) {
- AllocatorTraits::construct(*alloc_ptr, construct_at, *ptr_);
+ void ConstructNext(A& allocator, Pointer<A> construct_at) {
+ AllocatorTraits<A>::construct(allocator, construct_at, *ptr_);
}
- void AssignNext(Pointer assign_at) { *assign_at = *ptr_; }
+ void AssignNext(Pointer<A> assign_at) { *assign_at = *ptr_; }
private:
- ConstPointer ptr_;
+ ConstPointer<A> ptr_;
};
-template <typename AllocatorType>
+template <typename A>
class DefaultValueAdapter {
- using AllocatorTraits = absl::allocator_traits<AllocatorType>;
- using ValueType = typename AllocatorTraits::value_type;
- using Pointer = typename AllocatorTraits::pointer;
-
public:
explicit DefaultValueAdapter() {}
- void ConstructNext(AllocatorType* alloc_ptr, Pointer construct_at) {
- AllocatorTraits::construct(*alloc_ptr, construct_at);
+ void ConstructNext(A& allocator, Pointer<A> construct_at) {
+ AllocatorTraits<A>::construct(allocator, construct_at);
}
- void AssignNext(Pointer assign_at) { *assign_at = ValueType(); }
+ void AssignNext(Pointer<A> assign_at) { *assign_at = ValueType<A>(); }
};
-template <typename AllocatorType>
+template <typename A>
class AllocationTransaction {
- using AllocatorTraits = absl::allocator_traits<AllocatorType>;
- using Pointer = typename AllocatorTraits::pointer;
- using SizeType = typename AllocatorTraits::size_type;
-
public:
- explicit AllocationTransaction(AllocatorType* alloc_ptr)
- : alloc_data_(*alloc_ptr, nullptr) {}
+ explicit AllocationTransaction(A& allocator)
+ : allocator_data_(allocator, nullptr), capacity_(0) {}
~AllocationTransaction() {
if (DidAllocate()) {
- AllocatorTraits::deallocate(GetAllocator(), GetData(), GetCapacity());
+ MallocAdapter<A>::Deallocate(GetAllocator(), GetData(), GetCapacity());
}
}
AllocationTransaction(const AllocationTransaction&) = delete;
void operator=(const AllocationTransaction&) = delete;
- AllocatorType& GetAllocator() { return alloc_data_.template get<0>(); }
- Pointer& GetData() { return alloc_data_.template get<1>(); }
- SizeType& GetCapacity() { return capacity_; }
+ A& GetAllocator() { return allocator_data_.template get<0>(); }
+ Pointer<A>& GetData() { return allocator_data_.template get<1>(); }
+ SizeType<A>& GetCapacity() { return capacity_; }
bool DidAllocate() { return GetData() != nullptr; }
- Pointer Allocate(SizeType capacity) {
- GetData() = AllocatorTraits::allocate(GetAllocator(), capacity);
- GetCapacity() = capacity;
- return GetData();
+
+ Pointer<A> Allocate(SizeType<A> requested_capacity) {
+ Allocation<A> result =
+ MallocAdapter<A>::Allocate(GetAllocator(), requested_capacity);
+ GetData() = result.data;
+ GetCapacity() = result.capacity;
+ return result.data;
}
+ ABSL_MUST_USE_RESULT Allocation<A> Release() && {
+ Allocation<A> result = {GetData(), GetCapacity()};
+ Reset();
+ return result;
+ }
+
+ private:
void Reset() {
GetData() = nullptr;
GetCapacity() = 0;
}
- private:
- container_internal::CompressedTuple<AllocatorType, Pointer> alloc_data_;
- SizeType capacity_ = 0;
+ container_internal::CompressedTuple<A, Pointer<A>> allocator_data_;
+ SizeType<A> capacity_;
};
-template <typename AllocatorType>
+template <typename A>
class ConstructionTransaction {
- using AllocatorTraits = absl::allocator_traits<AllocatorType>;
- using Pointer = typename AllocatorTraits::pointer;
- using SizeType = typename AllocatorTraits::size_type;
-
public:
- explicit ConstructionTransaction(AllocatorType* alloc_ptr)
- : alloc_data_(*alloc_ptr, nullptr) {}
+ explicit ConstructionTransaction(A& allocator)
+ : allocator_data_(allocator, nullptr), size_(0) {}
~ConstructionTransaction() {
if (DidConstruct()) {
- inlined_vector_internal::DestroyElements(std::addressof(GetAllocator()),
- GetData(), GetSize());
+ DestroyAdapter<A>::DestroyElements(GetAllocator(), GetData(), GetSize());
}
}
ConstructionTransaction(const ConstructionTransaction&) = delete;
void operator=(const ConstructionTransaction&) = delete;
- AllocatorType& GetAllocator() { return alloc_data_.template get<0>(); }
- Pointer& GetData() { return alloc_data_.template get<1>(); }
- SizeType& GetSize() { return size_; }
+ A& GetAllocator() { return allocator_data_.template get<0>(); }
+ Pointer<A>& GetData() { return allocator_data_.template get<1>(); }
+ SizeType<A>& GetSize() { return size_; }
bool DidConstruct() { return GetData() != nullptr; }
template <typename ValueAdapter>
- void Construct(Pointer data, ValueAdapter* values_ptr, SizeType size) {
- inlined_vector_internal::ConstructElements(std::addressof(GetAllocator()),
- data, values_ptr, size);
+ void Construct(Pointer<A> data, ValueAdapter& values, SizeType<A> size) {
+ ConstructElements<A>(GetAllocator(), data, values, size);
GetData() = data;
GetSize() = size;
}
- void Commit() {
+ void Commit() && {
GetData() = nullptr;
GetSize() = 0;
}
private:
- container_internal::CompressedTuple<AllocatorType, Pointer> alloc_data_;
- SizeType size_ = 0;
+ container_internal::CompressedTuple<A, Pointer<A>> allocator_data_;
+ SizeType<A> size_;
};
template <typename T, size_t N, typename A>
class Storage {
public:
- using AllocatorTraits = absl::allocator_traits<A>;
- using allocator_type = typename AllocatorTraits::allocator_type;
- using value_type = typename AllocatorTraits::value_type;
- using pointer = typename AllocatorTraits::pointer;
- using const_pointer = typename AllocatorTraits::const_pointer;
- using size_type = typename AllocatorTraits::size_type;
- using difference_type = typename AllocatorTraits::difference_type;
+ struct MemcpyPolicy {};
+ struct ElementwiseAssignPolicy {};
+ struct ElementwiseSwapPolicy {};
+ struct ElementwiseConstructPolicy {};
- using reference = value_type&;
- using const_reference = const value_type&;
- using RValueReference = value_type&&;
- using iterator = pointer;
- using const_iterator = const_pointer;
- using reverse_iterator = std::reverse_iterator<iterator>;
- using const_reverse_iterator = std::reverse_iterator<const_iterator>;
- using MoveIterator = std::move_iterator<iterator>;
- using IsMemcpyOk = inlined_vector_internal::IsMemcpyOk<allocator_type>;
+ using MoveAssignmentPolicy = absl::conditional_t<
+ // Fast path: if the value type can be trivially move assigned and
+ // destroyed, and we know the allocator doesn't do anything fancy, then
+ // it's safe for us to simply adopt the contents of the storage for
+ // `other` and remove its own reference to them. It's as if we had
+ // individually move-assigned each value and then destroyed the original.
+ absl::conjunction<absl::is_trivially_move_assignable<ValueType<A>>,
+ absl::is_trivially_destructible<ValueType<A>>,
+ std::is_same<A, std::allocator<ValueType<A>>>>::value,
+ MemcpyPolicy,
+ // Otherwise we use move assignment if possible. If not, we simulate
+ // move assignment using move construction.
+ //
+ // Note that this is in contrast to e.g. std::vector and std::optional,
+ // which are themselves not move-assignable when their contained type is
+ // not.
+ absl::conditional_t<IsMoveAssignOk<A>::value, ElementwiseAssignPolicy,
+ ElementwiseConstructPolicy>>;
- using StorageView = inlined_vector_internal::StorageView<allocator_type>;
+ // The policy to be used specifically when swapping inlined elements.
+ using SwapInlinedElementsPolicy = absl::conditional_t<
+ // Fast path: if the value type can be trivially relocated, and we
+ // know the allocator doesn't do anything fancy, then it's safe for us
+ // to simply swap the bytes in the inline storage. It's as if we had
+ // relocated the first vector's elements into temporary storage,
+ // relocated the second's elements into the (now-empty) first's,
+ // and then relocated from temporary storage into the second.
+ absl::conjunction<absl::is_trivially_relocatable<ValueType<A>>,
+ std::is_same<A, std::allocator<ValueType<A>>>>::value,
+ MemcpyPolicy,
+ absl::conditional_t<IsSwapOk<A>::value, ElementwiseSwapPolicy,
+ ElementwiseConstructPolicy>>;
- template <typename Iterator>
- using IteratorValueAdapter =
- inlined_vector_internal::IteratorValueAdapter<allocator_type, Iterator>;
- using CopyValueAdapter =
- inlined_vector_internal::CopyValueAdapter<allocator_type>;
- using DefaultValueAdapter =
- inlined_vector_internal::DefaultValueAdapter<allocator_type>;
-
- using AllocationTransaction =
- inlined_vector_internal::AllocationTransaction<allocator_type>;
- using ConstructionTransaction =
- inlined_vector_internal::ConstructionTransaction<allocator_type>;
-
- static size_type NextCapacity(size_type current_capacity) {
+ static SizeType<A> NextCapacity(SizeType<A> current_capacity) {
return current_capacity * 2;
}
- static size_type ComputeCapacity(size_type current_capacity,
- size_type requested_capacity) {
+ static SizeType<A> ComputeCapacity(SizeType<A> current_capacity,
+ SizeType<A> requested_capacity) {
return (std::max)(NextCapacity(current_capacity), requested_capacity);
}
@@ -322,72 +347,91 @@
// Storage Constructors and Destructor
// ---------------------------------------------------------------------------
- Storage() : metadata_(allocator_type(), /* size and is_allocated */ 0) {}
+ Storage() : metadata_(A(), /* size and is_allocated */ 0u) {}
- explicit Storage(const allocator_type& alloc)
- : metadata_(alloc, /* size and is_allocated */ 0) {}
+ explicit Storage(const A& allocator)
+ : metadata_(allocator, /* size and is_allocated */ 0u) {}
~Storage() {
+ // Fast path: if we are empty and not allocated, there's nothing to do.
if (GetSizeAndIsAllocated() == 0) {
- // Empty and not allocated; nothing to do.
- } else if (IsMemcpyOk::value) {
- // No destructors need to be run; just deallocate if necessary.
- DeallocateIfAllocated();
- } else {
- DestroyContents();
+ return;
}
+
+ // Fast path: if no destructors need to be run and we know the allocator
+ // doesn't do anything fancy, then all we need to do is deallocate (and
+ // maybe not even that).
+ if (absl::is_trivially_destructible<ValueType<A>>::value &&
+ std::is_same<A, std::allocator<ValueType<A>>>::value) {
+ DeallocateIfAllocated();
+ return;
+ }
+
+ DestroyContents();
}
// ---------------------------------------------------------------------------
// Storage Member Accessors
// ---------------------------------------------------------------------------
- size_type& GetSizeAndIsAllocated() { return metadata_.template get<1>(); }
+ SizeType<A>& GetSizeAndIsAllocated() { return metadata_.template get<1>(); }
- const size_type& GetSizeAndIsAllocated() const {
+ const SizeType<A>& GetSizeAndIsAllocated() const {
return metadata_.template get<1>();
}
- size_type GetSize() const { return GetSizeAndIsAllocated() >> 1; }
+ SizeType<A> GetSize() const { return GetSizeAndIsAllocated() >> 1; }
bool GetIsAllocated() const { return GetSizeAndIsAllocated() & 1; }
- pointer GetAllocatedData() { return data_.allocated.allocated_data; }
+ Pointer<A> GetAllocatedData() {
+ // GCC 12 has a false-positive -Wmaybe-uninitialized warning here.
+#if ABSL_INTERNAL_HAVE_MIN_GNUC_VERSION(12, 0)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
+#endif
+ return data_.allocated.allocated_data;
+#if ABSL_INTERNAL_HAVE_MIN_GNUC_VERSION(12, 0)
+#pragma GCC diagnostic pop
+#endif
+ }
- const_pointer GetAllocatedData() const {
+ ConstPointer<A> GetAllocatedData() const {
return data_.allocated.allocated_data;
}
- pointer GetInlinedData() {
- return reinterpret_cast<pointer>(
- std::addressof(data_.inlined.inlined_data[0]));
+ // ABSL_ATTRIBUTE_NO_SANITIZE_CFI is used because the memory pointed to may be
+ // uninitialized, a common pattern in allocate()+construct() APIs.
+ // https://clang.llvm.org/docs/ControlFlowIntegrity.html#bad-cast-checking
+ // NOTE: When this was written, LLVM documentation did not explicitly
+ // mention that casting `char*` and using `reinterpret_cast` qualifies
+ // as a bad cast.
+ ABSL_ATTRIBUTE_NO_SANITIZE_CFI Pointer<A> GetInlinedData() {
+ return reinterpret_cast<Pointer<A>>(data_.inlined.inlined_data);
}
- const_pointer GetInlinedData() const {
- return reinterpret_cast<const_pointer>(
- std::addressof(data_.inlined.inlined_data[0]));
+ ABSL_ATTRIBUTE_NO_SANITIZE_CFI ConstPointer<A> GetInlinedData() const {
+ return reinterpret_cast<ConstPointer<A>>(data_.inlined.inlined_data);
}
- size_type GetAllocatedCapacity() const {
+ SizeType<A> GetAllocatedCapacity() const {
return data_.allocated.allocated_capacity;
}
- size_type GetInlinedCapacity() const { return static_cast<size_type>(N); }
-
- StorageView MakeStorageView() {
- return GetIsAllocated()
- ? StorageView{GetAllocatedData(), GetSize(),
- GetAllocatedCapacity()}
- : StorageView{GetInlinedData(), GetSize(), GetInlinedCapacity()};
+ SizeType<A> GetInlinedCapacity() const {
+ return static_cast<SizeType<A>>(kOptimalInlinedSize);
}
- allocator_type* GetAllocPtr() {
- return std::addressof(metadata_.template get<0>());
+ StorageView<A> MakeStorageView() {
+ return GetIsAllocated() ? StorageView<A>{GetAllocatedData(), GetSize(),
+ GetAllocatedCapacity()}
+ : StorageView<A>{GetInlinedData(), GetSize(),
+ GetInlinedCapacity()};
}
- const allocator_type* GetAllocPtr() const {
- return std::addressof(metadata_.template get<0>());
- }
+ A& GetAllocator() { return metadata_.template get<0>(); }
+
+ const A& GetAllocator() const { return metadata_.template get<0>(); }
// ---------------------------------------------------------------------------
// Storage Member Mutators
@@ -396,74 +440,92 @@
ABSL_ATTRIBUTE_NOINLINE void InitFrom(const Storage& other);
template <typename ValueAdapter>
- void Initialize(ValueAdapter values, size_type new_size);
+ void Initialize(ValueAdapter values, SizeType<A> new_size);
template <typename ValueAdapter>
- void Assign(ValueAdapter values, size_type new_size);
+ void Assign(ValueAdapter values, SizeType<A> new_size);
template <typename ValueAdapter>
- void Resize(ValueAdapter values, size_type new_size);
+ void Resize(ValueAdapter values, SizeType<A> new_size);
template <typename ValueAdapter>
- iterator Insert(const_iterator pos, ValueAdapter values,
- size_type insert_count);
+ Iterator<A> Insert(ConstIterator<A> pos, ValueAdapter values,
+ SizeType<A> insert_count);
template <typename... Args>
- reference EmplaceBack(Args&&... args);
+ Reference<A> EmplaceBack(Args&&... args);
- iterator Erase(const_iterator from, const_iterator to);
+ Iterator<A> Erase(ConstIterator<A> from, ConstIterator<A> to);
- void Reserve(size_type requested_capacity);
+ void Reserve(SizeType<A> requested_capacity);
void ShrinkToFit();
void Swap(Storage* other_storage_ptr);
void SetIsAllocated() {
- GetSizeAndIsAllocated() |= static_cast<size_type>(1);
+ GetSizeAndIsAllocated() |= static_cast<SizeType<A>>(1);
}
void UnsetIsAllocated() {
- GetSizeAndIsAllocated() &= ((std::numeric_limits<size_type>::max)() - 1);
+ GetSizeAndIsAllocated() &= ((std::numeric_limits<SizeType<A>>::max)() - 1);
}
- void SetSize(size_type size) {
+ void SetSize(SizeType<A> size) {
GetSizeAndIsAllocated() =
- (size << 1) | static_cast<size_type>(GetIsAllocated());
+ (size << 1) | static_cast<SizeType<A>>(GetIsAllocated());
}
- void SetAllocatedSize(size_type size) {
- GetSizeAndIsAllocated() = (size << 1) | static_cast<size_type>(1);
+ void SetAllocatedSize(SizeType<A> size) {
+ GetSizeAndIsAllocated() = (size << 1) | static_cast<SizeType<A>>(1);
}
- void SetInlinedSize(size_type size) {
- GetSizeAndIsAllocated() = size << static_cast<size_type>(1);
+ void SetInlinedSize(SizeType<A> size) {
+ GetSizeAndIsAllocated() = size << static_cast<SizeType<A>>(1);
}
- void AddSize(size_type count) {
- GetSizeAndIsAllocated() += count << static_cast<size_type>(1);
+ void AddSize(SizeType<A> count) {
+ GetSizeAndIsAllocated() += count << static_cast<SizeType<A>>(1);
}
- void SubtractSize(size_type count) {
- assert(count <= GetSize());
+ void SubtractSize(SizeType<A> count) {
+ ABSL_HARDENING_ASSERT(count <= GetSize());
- GetSizeAndIsAllocated() -= count << static_cast<size_type>(1);
+ GetSizeAndIsAllocated() -= count << static_cast<SizeType<A>>(1);
}
- void SetAllocatedData(pointer data, size_type capacity) {
- data_.allocated.allocated_data = data;
- data_.allocated.allocated_capacity = capacity;
- }
-
- void AcquireAllocatedData(AllocationTransaction* allocation_tx_ptr) {
- SetAllocatedData(allocation_tx_ptr->GetData(),
- allocation_tx_ptr->GetCapacity());
-
- allocation_tx_ptr->Reset();
+ void SetAllocation(Allocation<A> allocation) {
+ data_.allocated.allocated_data = allocation.data;
+ data_.allocated.allocated_capacity = allocation.capacity;
}
void MemcpyFrom(const Storage& other_storage) {
- assert(IsMemcpyOk::value || other_storage.GetIsAllocated());
+ // Assumption check: it doesn't make sense to memcpy inlined elements unless
+ // we know the allocator doesn't do anything fancy, and one of the following
+ // holds:
+ //
+ // * The elements are trivially relocatable.
+ //
+ // * It's possible to trivially assign the elements and then destroy the
+ // source.
+ //
+ // * It's possible to trivially copy construct/assign the elements.
+ //
+ {
+ using V = ValueType<A>;
+ ABSL_HARDENING_ASSERT(
+ other_storage.GetIsAllocated() ||
+ (std::is_same<A, std::allocator<V>>::value &&
+ (
+ // First case above
+ absl::is_trivially_relocatable<V>::value ||
+ // Second case above
+ (absl::is_trivially_move_assignable<V>::value &&
+ absl::is_trivially_destructible<V>::value) ||
+ // Third case above
+ (absl::is_trivially_copy_constructible<V>::value ||
+ absl::is_trivially_copy_assignable<V>::value))));
+ }
GetSizeAndIsAllocated() = other_storage.GetSizeAndIsAllocated();
data_ = other_storage.data_;
@@ -471,24 +533,30 @@
void DeallocateIfAllocated() {
if (GetIsAllocated()) {
- AllocatorTraits::deallocate(*GetAllocPtr(), GetAllocatedData(),
- GetAllocatedCapacity());
+ MallocAdapter<A>::Deallocate(GetAllocator(), GetAllocatedData(),
+ GetAllocatedCapacity());
}
}
private:
ABSL_ATTRIBUTE_NOINLINE void DestroyContents();
- using Metadata =
- container_internal::CompressedTuple<allocator_type, size_type>;
+ using Metadata = container_internal::CompressedTuple<A, SizeType<A>>;
struct Allocated {
- pointer allocated_data;
- size_type allocated_capacity;
+ Pointer<A> allocated_data;
+ SizeType<A> allocated_capacity;
};
+ // `kOptimalInlinedSize` is an automatically adjusted inlined capacity of the
+ // `InlinedVector`. Sometimes, it is possible to increase the capacity (from
+ // the user requested `N`) without increasing the size of the `InlinedVector`.
+ static constexpr size_t kOptimalInlinedSize =
+ (std::max)(N, sizeof(Allocated) / sizeof(ValueType<A>));
+
struct Inlined {
- alignas(value_type) char inlined_data[sizeof(value_type[N])];
+ alignas(ValueType<A>) char inlined_data[sizeof(
+ ValueType<A>[kOptimalInlinedSize])];
};
union Data {
@@ -496,8 +564,15 @@
Inlined inlined;
};
+ void SwapN(ElementwiseSwapPolicy, Storage* other, SizeType<A> n);
+ void SwapN(ElementwiseConstructPolicy, Storage* other, SizeType<A> n);
+
+ void SwapInlinedElements(MemcpyPolicy, Storage* other);
+ template <typename NotMemcpyPolicy>
+ void SwapInlinedElements(NotMemcpyPolicy, Storage* other);
+
template <typename... Args>
- ABSL_ATTRIBUTE_NOINLINE reference EmplaceBackSlow(Args&&... args);
+ ABSL_ATTRIBUTE_NOINLINE Reference<A> EmplaceBackSlow(Args&&... args);
Metadata metadata_;
Data data_;
@@ -505,17 +580,17 @@
template <typename T, size_t N, typename A>
void Storage<T, N, A>::DestroyContents() {
- pointer data = GetIsAllocated() ? GetAllocatedData() : GetInlinedData();
- inlined_vector_internal::DestroyElements(GetAllocPtr(), data, GetSize());
+ Pointer<A> data = GetIsAllocated() ? GetAllocatedData() : GetInlinedData();
+ DestroyAdapter<A>::DestroyElements(GetAllocator(), data, GetSize());
DeallocateIfAllocated();
}
template <typename T, size_t N, typename A>
void Storage<T, N, A>::InitFrom(const Storage& other) {
- const auto n = other.GetSize();
- assert(n > 0); // Empty sources handled handled in caller.
- const_pointer src;
- pointer dst;
+ const SizeType<A> n = other.GetSize();
+ ABSL_HARDENING_ASSERT(n > 0); // Empty sources handled handled in caller.
+ ConstPointer<A> src;
+ Pointer<A> dst;
if (!other.GetIsAllocated()) {
dst = GetInlinedData();
src = other.GetInlinedData();
@@ -523,43 +598,54 @@
// Because this is only called from the `InlinedVector` constructors, it's
// safe to take on the allocation with size `0`. If `ConstructElements(...)`
// throws, deallocation will be automatically handled by `~Storage()`.
- size_type new_capacity = ComputeCapacity(GetInlinedCapacity(), n);
- dst = AllocatorTraits::allocate(*GetAllocPtr(), new_capacity);
- SetAllocatedData(dst, new_capacity);
+ SizeType<A> requested_capacity = ComputeCapacity(GetInlinedCapacity(), n);
+ Allocation<A> allocation =
+ MallocAdapter<A>::Allocate(GetAllocator(), requested_capacity);
+ SetAllocation(allocation);
+ dst = allocation.data;
src = other.GetAllocatedData();
}
- if (IsMemcpyOk::value) {
- MemcpyIfAllowed<IsMemcpyOk::value>(dst, src, sizeof(dst[0]) * n);
+
+ // Fast path: if the value type is trivially copy constructible and we know
+ // the allocator doesn't do anything fancy, then we know it is legal for us to
+ // simply memcpy the other vector's elements.
+ if (absl::is_trivially_copy_constructible<ValueType<A>>::value &&
+ std::is_same<A, std::allocator<ValueType<A>>>::value) {
+ std::memcpy(reinterpret_cast<char*>(dst),
+ reinterpret_cast<const char*>(src), n * sizeof(ValueType<A>));
} else {
- auto values = IteratorValueAdapter<const_pointer>(src);
- inlined_vector_internal::ConstructElements(GetAllocPtr(), dst, &values, n);
+ auto values = IteratorValueAdapter<A, ConstPointer<A>>(src);
+ ConstructElements<A>(GetAllocator(), dst, values, n);
}
+
GetSizeAndIsAllocated() = other.GetSizeAndIsAllocated();
}
template <typename T, size_t N, typename A>
template <typename ValueAdapter>
-auto Storage<T, N, A>::Initialize(ValueAdapter values, size_type new_size)
- -> void {
+auto Storage<T, N, A>::Initialize(ValueAdapter values,
+ SizeType<A> new_size) -> void {
// Only callable from constructors!
- assert(!GetIsAllocated());
- assert(GetSize() == 0);
+ ABSL_HARDENING_ASSERT(!GetIsAllocated());
+ ABSL_HARDENING_ASSERT(GetSize() == 0);
- pointer construct_data;
+ Pointer<A> construct_data;
if (new_size > GetInlinedCapacity()) {
// Because this is only called from the `InlinedVector` constructors, it's
// safe to take on the allocation with size `0`. If `ConstructElements(...)`
// throws, deallocation will be automatically handled by `~Storage()`.
- size_type new_capacity = ComputeCapacity(GetInlinedCapacity(), new_size);
- construct_data = AllocatorTraits::allocate(*GetAllocPtr(), new_capacity);
- SetAllocatedData(construct_data, new_capacity);
+ SizeType<A> requested_capacity =
+ ComputeCapacity(GetInlinedCapacity(), new_size);
+ Allocation<A> allocation =
+ MallocAdapter<A>::Allocate(GetAllocator(), requested_capacity);
+ construct_data = allocation.data;
+ SetAllocation(allocation);
SetIsAllocated();
} else {
construct_data = GetInlinedData();
}
- inlined_vector_internal::ConstructElements(GetAllocPtr(), construct_data,
- &values, new_size);
+ ConstructElements<A>(GetAllocator(), construct_data, values, new_size);
// Since the initial size was guaranteed to be `0` and the allocated bit is
// already correct for either case, *adding* `new_size` gives us the correct
@@ -569,18 +655,20 @@
template <typename T, size_t N, typename A>
template <typename ValueAdapter>
-auto Storage<T, N, A>::Assign(ValueAdapter values, size_type new_size) -> void {
- StorageView storage_view = MakeStorageView();
+auto Storage<T, N, A>::Assign(ValueAdapter values,
+ SizeType<A> new_size) -> void {
+ StorageView<A> storage_view = MakeStorageView();
- AllocationTransaction allocation_tx(GetAllocPtr());
+ AllocationTransaction<A> allocation_tx(GetAllocator());
- absl::Span<value_type> assign_loop;
- absl::Span<value_type> construct_loop;
- absl::Span<value_type> destroy_loop;
+ absl::Span<ValueType<A>> assign_loop;
+ absl::Span<ValueType<A>> construct_loop;
+ absl::Span<ValueType<A>> destroy_loop;
if (new_size > storage_view.capacity) {
- size_type new_capacity = ComputeCapacity(storage_view.capacity, new_size);
- construct_loop = {allocation_tx.Allocate(new_capacity), new_size};
+ SizeType<A> requested_capacity =
+ ComputeCapacity(storage_view.capacity, new_size);
+ construct_loop = {allocation_tx.Allocate(requested_capacity), new_size};
destroy_loop = {storage_view.data, storage_view.size};
} else if (new_size > storage_view.size) {
assign_loop = {storage_view.data, storage_view.size};
@@ -591,18 +679,17 @@
destroy_loop = {storage_view.data + new_size, storage_view.size - new_size};
}
- inlined_vector_internal::AssignElements(assign_loop.data(), &values,
- assign_loop.size());
+ AssignElements<A>(assign_loop.data(), values, assign_loop.size());
- inlined_vector_internal::ConstructElements(
- GetAllocPtr(), construct_loop.data(), &values, construct_loop.size());
+ ConstructElements<A>(GetAllocator(), construct_loop.data(), values,
+ construct_loop.size());
- inlined_vector_internal::DestroyElements(GetAllocPtr(), destroy_loop.data(),
- destroy_loop.size());
+ DestroyAdapter<A>::DestroyElements(GetAllocator(), destroy_loop.data(),
+ destroy_loop.size());
if (allocation_tx.DidAllocate()) {
DeallocateIfAllocated();
- AcquireAllocatedData(&allocation_tx);
+ SetAllocation(std::move(allocation_tx).Release());
SetIsAllocated();
}
@@ -611,42 +698,42 @@
template <typename T, size_t N, typename A>
template <typename ValueAdapter>
-auto Storage<T, N, A>::Resize(ValueAdapter values, size_type new_size) -> void {
- StorageView storage_view = MakeStorageView();
- auto* const base = storage_view.data;
- const size_type size = storage_view.size;
- auto* alloc = GetAllocPtr();
+auto Storage<T, N, A>::Resize(ValueAdapter values,
+ SizeType<A> new_size) -> void {
+ StorageView<A> storage_view = MakeStorageView();
+ Pointer<A> const base = storage_view.data;
+ const SizeType<A> size = storage_view.size;
+ A& alloc = GetAllocator();
if (new_size <= size) {
// Destroy extra old elements.
- inlined_vector_internal::DestroyElements(alloc, base + new_size,
- size - new_size);
+ DestroyAdapter<A>::DestroyElements(alloc, base + new_size, size - new_size);
} else if (new_size <= storage_view.capacity) {
// Construct new elements in place.
- inlined_vector_internal::ConstructElements(alloc, base + size, &values,
- new_size - size);
+ ConstructElements<A>(alloc, base + size, values, new_size - size);
} else {
// Steps:
// a. Allocate new backing store.
// b. Construct new elements in new backing store.
- // c. Move existing elements from old backing store to now.
+ // c. Move existing elements from old backing store to new backing store.
// d. Destroy all elements in old backing store.
// Use transactional wrappers for the first two steps so we can roll
// back if necessary due to exceptions.
- AllocationTransaction allocation_tx(alloc);
- size_type new_capacity = ComputeCapacity(storage_view.capacity, new_size);
- pointer new_data = allocation_tx.Allocate(new_capacity);
+ AllocationTransaction<A> allocation_tx(alloc);
+ SizeType<A> requested_capacity =
+ ComputeCapacity(storage_view.capacity, new_size);
+ Pointer<A> new_data = allocation_tx.Allocate(requested_capacity);
- ConstructionTransaction construction_tx(alloc);
- construction_tx.Construct(new_data + size, &values, new_size - size);
+ ConstructionTransaction<A> construction_tx(alloc);
+ construction_tx.Construct(new_data + size, values, new_size - size);
- IteratorValueAdapter<MoveIterator> move_values((MoveIterator(base)));
- inlined_vector_internal::ConstructElements(alloc, new_data, &move_values,
- size);
+ IteratorValueAdapter<A, MoveIterator<A>> move_values(
+ (MoveIterator<A>(base)));
+ ConstructElements<A>(alloc, new_data, move_values, size);
- inlined_vector_internal::DestroyElements(alloc, base, size);
- construction_tx.Commit();
+ DestroyAdapter<A>::DestroyElements(alloc, base, size);
+ std::move(construction_tx).Commit();
DeallocateIfAllocated();
- AcquireAllocatedData(&allocation_tx);
+ SetAllocation(std::move(allocation_tx).Release());
SetIsAllocated();
}
SetSize(new_size);
@@ -654,76 +741,77 @@
template <typename T, size_t N, typename A>
template <typename ValueAdapter>
-auto Storage<T, N, A>::Insert(const_iterator pos, ValueAdapter values,
- size_type insert_count) -> iterator {
- StorageView storage_view = MakeStorageView();
+auto Storage<T, N, A>::Insert(ConstIterator<A> pos, ValueAdapter values,
+ SizeType<A> insert_count) -> Iterator<A> {
+ StorageView<A> storage_view = MakeStorageView();
- size_type insert_index =
- std::distance(const_iterator(storage_view.data), pos);
- size_type insert_end_index = insert_index + insert_count;
- size_type new_size = storage_view.size + insert_count;
+ auto insert_index = static_cast<SizeType<A>>(
+ std::distance(ConstIterator<A>(storage_view.data), pos));
+ SizeType<A> insert_end_index = insert_index + insert_count;
+ SizeType<A> new_size = storage_view.size + insert_count;
if (new_size > storage_view.capacity) {
- AllocationTransaction allocation_tx(GetAllocPtr());
- ConstructionTransaction construction_tx(GetAllocPtr());
- ConstructionTransaction move_construciton_tx(GetAllocPtr());
+ AllocationTransaction<A> allocation_tx(GetAllocator());
+ ConstructionTransaction<A> construction_tx(GetAllocator());
+ ConstructionTransaction<A> move_construction_tx(GetAllocator());
- IteratorValueAdapter<MoveIterator> move_values(
- MoveIterator(storage_view.data));
+ IteratorValueAdapter<A, MoveIterator<A>> move_values(
+ MoveIterator<A>(storage_view.data));
- size_type new_capacity = ComputeCapacity(storage_view.capacity, new_size);
- pointer new_data = allocation_tx.Allocate(new_capacity);
+ SizeType<A> requested_capacity =
+ ComputeCapacity(storage_view.capacity, new_size);
+ Pointer<A> new_data = allocation_tx.Allocate(requested_capacity);
- construction_tx.Construct(new_data + insert_index, &values, insert_count);
+ construction_tx.Construct(new_data + insert_index, values, insert_count);
- move_construciton_tx.Construct(new_data, &move_values, insert_index);
+ move_construction_tx.Construct(new_data, move_values, insert_index);
- inlined_vector_internal::ConstructElements(
- GetAllocPtr(), new_data + insert_end_index, &move_values,
- storage_view.size - insert_index);
+ ConstructElements<A>(GetAllocator(), new_data + insert_end_index,
+ move_values, storage_view.size - insert_index);
- inlined_vector_internal::DestroyElements(GetAllocPtr(), storage_view.data,
- storage_view.size);
+ DestroyAdapter<A>::DestroyElements(GetAllocator(), storage_view.data,
+ storage_view.size);
- construction_tx.Commit();
- move_construciton_tx.Commit();
+ std::move(construction_tx).Commit();
+ std::move(move_construction_tx).Commit();
DeallocateIfAllocated();
- AcquireAllocatedData(&allocation_tx);
+ SetAllocation(std::move(allocation_tx).Release());
SetAllocatedSize(new_size);
- return iterator(new_data + insert_index);
+ return Iterator<A>(new_data + insert_index);
} else {
- size_type move_construction_destination_index =
+ SizeType<A> move_construction_destination_index =
(std::max)(insert_end_index, storage_view.size);
- ConstructionTransaction move_construction_tx(GetAllocPtr());
+ ConstructionTransaction<A> move_construction_tx(GetAllocator());
- IteratorValueAdapter<MoveIterator> move_construction_values(
- MoveIterator(storage_view.data +
- (move_construction_destination_index - insert_count)));
- absl::Span<value_type> move_construction = {
+ IteratorValueAdapter<A, MoveIterator<A>> move_construction_values(
+ MoveIterator<A>(storage_view.data +
+ (move_construction_destination_index - insert_count)));
+ absl::Span<ValueType<A>> move_construction = {
storage_view.data + move_construction_destination_index,
new_size - move_construction_destination_index};
- pointer move_assignment_values = storage_view.data + insert_index;
- absl::Span<value_type> move_assignment = {
+ Pointer<A> move_assignment_values = storage_view.data + insert_index;
+ absl::Span<ValueType<A>> move_assignment = {
storage_view.data + insert_end_index,
move_construction_destination_index - insert_end_index};
- absl::Span<value_type> insert_assignment = {move_assignment_values,
- move_construction.size()};
+ absl::Span<ValueType<A>> insert_assignment = {move_assignment_values,
+ move_construction.size()};
- absl::Span<value_type> insert_construction = {
+ absl::Span<ValueType<A>> insert_construction = {
insert_assignment.data() + insert_assignment.size(),
insert_count - insert_assignment.size()};
move_construction_tx.Construct(move_construction.data(),
- &move_construction_values,
+ move_construction_values,
move_construction.size());
- for (pointer destination = move_assignment.data() + move_assignment.size(),
- last_destination = move_assignment.data(),
- source = move_assignment_values + move_assignment.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;
@@ -731,30 +819,29 @@
*destination = std::move(*source);
}
- inlined_vector_internal::AssignElements(insert_assignment.data(), &values,
- insert_assignment.size());
+ AssignElements<A>(insert_assignment.data(), values,
+ insert_assignment.size());
- inlined_vector_internal::ConstructElements(
- GetAllocPtr(), insert_construction.data(), &values,
- insert_construction.size());
+ ConstructElements<A>(GetAllocator(), insert_construction.data(), values,
+ insert_construction.size());
- move_construction_tx.Commit();
+ std::move(move_construction_tx).Commit();
AddSize(insert_count);
- return iterator(storage_view.data + insert_index);
+ return Iterator<A>(storage_view.data + insert_index);
}
}
template <typename T, size_t N, typename A>
template <typename... Args>
-auto Storage<T, N, A>::EmplaceBack(Args&&... args) -> reference {
- StorageView storage_view = MakeStorageView();
- const auto n = storage_view.size;
+auto Storage<T, N, A>::EmplaceBack(Args&&... args) -> Reference<A> {
+ StorageView<A> storage_view = MakeStorageView();
+ const SizeType<A> n = storage_view.size;
if (ABSL_PREDICT_TRUE(n != storage_view.capacity)) {
// Fast path; new element fits.
- pointer last_ptr = storage_view.data + n;
- AllocatorTraits::construct(*GetAllocPtr(), last_ptr,
- std::forward<Args>(args)...);
+ Pointer<A> last_ptr = storage_view.data + n;
+ AllocatorTraits<A>::construct(GetAllocator(), last_ptr,
+ std::forward<Args>(args)...);
AddSize(1);
return *last_ptr;
}
@@ -764,130 +851,146 @@
template <typename T, size_t N, typename A>
template <typename... Args>
-auto Storage<T, N, A>::EmplaceBackSlow(Args&&... args) -> reference {
- StorageView storage_view = MakeStorageView();
- AllocationTransaction allocation_tx(GetAllocPtr());
- IteratorValueAdapter<MoveIterator> move_values(
- MoveIterator(storage_view.data));
- size_type new_capacity = NextCapacity(storage_view.capacity);
- pointer construct_data = allocation_tx.Allocate(new_capacity);
- pointer last_ptr = construct_data + storage_view.size;
+auto Storage<T, N, A>::EmplaceBackSlow(Args&&... args) -> Reference<A> {
+ StorageView<A> storage_view = MakeStorageView();
+ AllocationTransaction<A> allocation_tx(GetAllocator());
+ IteratorValueAdapter<A, MoveIterator<A>> move_values(
+ MoveIterator<A>(storage_view.data));
+ SizeType<A> requested_capacity = NextCapacity(storage_view.capacity);
+ Pointer<A> construct_data = allocation_tx.Allocate(requested_capacity);
+ Pointer<A> last_ptr = construct_data + storage_view.size;
// Construct new element.
- AllocatorTraits::construct(*GetAllocPtr(), last_ptr,
- std::forward<Args>(args)...);
+ AllocatorTraits<A>::construct(GetAllocator(), last_ptr,
+ std::forward<Args>(args)...);
// Move elements from old backing store to new backing store.
ABSL_INTERNAL_TRY {
- inlined_vector_internal::ConstructElements(
- GetAllocPtr(), allocation_tx.GetData(), &move_values,
- storage_view.size);
+ ConstructElements<A>(GetAllocator(), allocation_tx.GetData(), move_values,
+ storage_view.size);
}
ABSL_INTERNAL_CATCH_ANY {
- AllocatorTraits::destroy(*GetAllocPtr(), last_ptr);
+ AllocatorTraits<A>::destroy(GetAllocator(), last_ptr);
ABSL_INTERNAL_RETHROW;
}
// Destroy elements in old backing store.
- inlined_vector_internal::DestroyElements(GetAllocPtr(), storage_view.data,
- storage_view.size);
+ DestroyAdapter<A>::DestroyElements(GetAllocator(), storage_view.data,
+ storage_view.size);
DeallocateIfAllocated();
- AcquireAllocatedData(&allocation_tx);
+ SetAllocation(std::move(allocation_tx).Release());
SetIsAllocated();
AddSize(1);
return *last_ptr;
}
template <typename T, size_t N, typename A>
-auto Storage<T, N, A>::Erase(const_iterator from, const_iterator to)
- -> iterator {
- StorageView storage_view = MakeStorageView();
+auto Storage<T, N, A>::Erase(ConstIterator<A> from,
+ ConstIterator<A> to) -> Iterator<A> {
+ StorageView<A> storage_view = MakeStorageView();
- size_type erase_size = std::distance(from, to);
- size_type erase_index =
- std::distance(const_iterator(storage_view.data), from);
- size_type erase_end_index = erase_index + erase_size;
+ auto erase_size = static_cast<SizeType<A>>(std::distance(from, to));
+ auto erase_index = static_cast<SizeType<A>>(
+ std::distance(ConstIterator<A>(storage_view.data), from));
+ SizeType<A> erase_end_index = erase_index + erase_size;
- IteratorValueAdapter<MoveIterator> move_values(
- MoveIterator(storage_view.data + erase_end_index));
+ // Fast path: if the value type is trivially relocatable and we know
+ // the allocator doesn't do anything fancy, then we know it is legal for us to
+ // simply destroy the elements in the "erasure window" (which cannot throw)
+ // and then memcpy downward to close the window.
+ if (absl::is_trivially_relocatable<ValueType<A>>::value &&
+ std::is_nothrow_destructible<ValueType<A>>::value &&
+ std::is_same<A, std::allocator<ValueType<A>>>::value) {
+ DestroyAdapter<A>::DestroyElements(
+ GetAllocator(), storage_view.data + erase_index, erase_size);
+ std::memmove(
+ reinterpret_cast<char*>(storage_view.data + erase_index),
+ reinterpret_cast<const char*>(storage_view.data + erase_end_index),
+ (storage_view.size - erase_end_index) * sizeof(ValueType<A>));
+ } else {
+ IteratorValueAdapter<A, MoveIterator<A>> move_values(
+ MoveIterator<A>(storage_view.data + erase_end_index));
- inlined_vector_internal::AssignElements(storage_view.data + erase_index,
- &move_values,
- storage_view.size - erase_end_index);
+ AssignElements<A>(storage_view.data + erase_index, move_values,
+ storage_view.size - erase_end_index);
- inlined_vector_internal::DestroyElements(
- GetAllocPtr(), storage_view.data + (storage_view.size - erase_size),
- erase_size);
-
+ DestroyAdapter<A>::DestroyElements(
+ GetAllocator(), storage_view.data + (storage_view.size - erase_size),
+ erase_size);
+ }
SubtractSize(erase_size);
- return iterator(storage_view.data + erase_index);
+ return Iterator<A>(storage_view.data + erase_index);
}
template <typename T, size_t N, typename A>
-auto Storage<T, N, A>::Reserve(size_type requested_capacity) -> void {
- StorageView storage_view = MakeStorageView();
+auto Storage<T, N, A>::Reserve(SizeType<A> requested_capacity) -> void {
+ StorageView<A> storage_view = MakeStorageView();
if (ABSL_PREDICT_FALSE(requested_capacity <= storage_view.capacity)) return;
- AllocationTransaction allocation_tx(GetAllocPtr());
+ AllocationTransaction<A> allocation_tx(GetAllocator());
- IteratorValueAdapter<MoveIterator> move_values(
- MoveIterator(storage_view.data));
+ IteratorValueAdapter<A, MoveIterator<A>> move_values(
+ MoveIterator<A>(storage_view.data));
- size_type new_capacity =
+ SizeType<A> new_requested_capacity =
ComputeCapacity(storage_view.capacity, requested_capacity);
- pointer new_data = allocation_tx.Allocate(new_capacity);
+ Pointer<A> new_data = allocation_tx.Allocate(new_requested_capacity);
- inlined_vector_internal::ConstructElements(GetAllocPtr(), new_data,
- &move_values, storage_view.size);
+ ConstructElements<A>(GetAllocator(), new_data, move_values,
+ storage_view.size);
- inlined_vector_internal::DestroyElements(GetAllocPtr(), storage_view.data,
- storage_view.size);
+ DestroyAdapter<A>::DestroyElements(GetAllocator(), storage_view.data,
+ storage_view.size);
DeallocateIfAllocated();
- AcquireAllocatedData(&allocation_tx);
+ SetAllocation(std::move(allocation_tx).Release());
SetIsAllocated();
}
template <typename T, size_t N, typename A>
auto Storage<T, N, A>::ShrinkToFit() -> void {
// May only be called on allocated instances!
- assert(GetIsAllocated());
+ ABSL_HARDENING_ASSERT(GetIsAllocated());
- StorageView storage_view{GetAllocatedData(), GetSize(),
- GetAllocatedCapacity()};
+ StorageView<A> storage_view{GetAllocatedData(), GetSize(),
+ GetAllocatedCapacity()};
if (ABSL_PREDICT_FALSE(storage_view.size == storage_view.capacity)) return;
- AllocationTransaction allocation_tx(GetAllocPtr());
+ AllocationTransaction<A> allocation_tx(GetAllocator());
- IteratorValueAdapter<MoveIterator> move_values(
- MoveIterator(storage_view.data));
+ IteratorValueAdapter<A, MoveIterator<A>> move_values(
+ MoveIterator<A>(storage_view.data));
- pointer construct_data;
+ Pointer<A> construct_data;
if (storage_view.size > GetInlinedCapacity()) {
- size_type new_capacity = storage_view.size;
- construct_data = allocation_tx.Allocate(new_capacity);
+ SizeType<A> requested_capacity = storage_view.size;
+ construct_data = allocation_tx.Allocate(requested_capacity);
+ if (allocation_tx.GetCapacity() >= storage_view.capacity) {
+ // Already using the smallest available heap allocation.
+ return;
+ }
} else {
construct_data = GetInlinedData();
}
ABSL_INTERNAL_TRY {
- inlined_vector_internal::ConstructElements(GetAllocPtr(), construct_data,
- &move_values, storage_view.size);
+ ConstructElements<A>(GetAllocator(), construct_data, move_values,
+ storage_view.size);
}
ABSL_INTERNAL_CATCH_ANY {
- SetAllocatedData(storage_view.data, storage_view.capacity);
+ SetAllocation({storage_view.data, storage_view.capacity});
ABSL_INTERNAL_RETHROW;
}
- inlined_vector_internal::DestroyElements(GetAllocPtr(), storage_view.data,
- storage_view.size);
+ DestroyAdapter<A>::DestroyElements(GetAllocator(), storage_view.data,
+ storage_view.size);
- AllocatorTraits::deallocate(*GetAllocPtr(), storage_view.data,
- storage_view.capacity);
+ MallocAdapter<A>::Deallocate(GetAllocator(), storage_view.data,
+ storage_view.capacity);
if (allocation_tx.DidAllocate()) {
- AcquireAllocatedData(&allocation_tx);
+ SetAllocation(std::move(allocation_tx).Release());
} else {
UnsetIsAllocated();
}
@@ -896,67 +999,110 @@
template <typename T, size_t N, typename A>
auto Storage<T, N, A>::Swap(Storage* other_storage_ptr) -> void {
using std::swap;
- assert(this != other_storage_ptr);
+ ABSL_HARDENING_ASSERT(this != other_storage_ptr);
if (GetIsAllocated() && other_storage_ptr->GetIsAllocated()) {
swap(data_.allocated, other_storage_ptr->data_.allocated);
} else if (!GetIsAllocated() && !other_storage_ptr->GetIsAllocated()) {
- Storage* small_ptr = this;
- Storage* large_ptr = other_storage_ptr;
- if (small_ptr->GetSize() > large_ptr->GetSize()) swap(small_ptr, large_ptr);
-
- for (size_type i = 0; i < small_ptr->GetSize(); ++i) {
- swap(small_ptr->GetInlinedData()[i], large_ptr->GetInlinedData()[i]);
- }
-
- IteratorValueAdapter<MoveIterator> move_values(
- MoveIterator(large_ptr->GetInlinedData() + small_ptr->GetSize()));
-
- inlined_vector_internal::ConstructElements(
- large_ptr->GetAllocPtr(),
- small_ptr->GetInlinedData() + small_ptr->GetSize(), &move_values,
- large_ptr->GetSize() - small_ptr->GetSize());
-
- inlined_vector_internal::DestroyElements(
- large_ptr->GetAllocPtr(),
- large_ptr->GetInlinedData() + small_ptr->GetSize(),
- large_ptr->GetSize() - small_ptr->GetSize());
+ SwapInlinedElements(SwapInlinedElementsPolicy{}, other_storage_ptr);
} else {
Storage* allocated_ptr = this;
Storage* inlined_ptr = other_storage_ptr;
if (!allocated_ptr->GetIsAllocated()) swap(allocated_ptr, inlined_ptr);
- StorageView allocated_storage_view{allocated_ptr->GetAllocatedData(),
- allocated_ptr->GetSize(),
- allocated_ptr->GetAllocatedCapacity()};
+ StorageView<A> allocated_storage_view{
+ allocated_ptr->GetAllocatedData(), allocated_ptr->GetSize(),
+ allocated_ptr->GetAllocatedCapacity()};
- IteratorValueAdapter<MoveIterator> move_values(
- MoveIterator(inlined_ptr->GetInlinedData()));
+ IteratorValueAdapter<A, MoveIterator<A>> move_values(
+ MoveIterator<A>(inlined_ptr->GetInlinedData()));
ABSL_INTERNAL_TRY {
- inlined_vector_internal::ConstructElements(
- inlined_ptr->GetAllocPtr(), allocated_ptr->GetInlinedData(),
- &move_values, inlined_ptr->GetSize());
+ ConstructElements<A>(inlined_ptr->GetAllocator(),
+ allocated_ptr->GetInlinedData(), move_values,
+ inlined_ptr->GetSize());
}
ABSL_INTERNAL_CATCH_ANY {
- allocated_ptr->SetAllocatedData(allocated_storage_view.data,
- allocated_storage_view.capacity);
+ allocated_ptr->SetAllocation(Allocation<A>{
+ allocated_storage_view.data, allocated_storage_view.capacity});
ABSL_INTERNAL_RETHROW;
}
- inlined_vector_internal::DestroyElements(inlined_ptr->GetAllocPtr(),
- inlined_ptr->GetInlinedData(),
- inlined_ptr->GetSize());
+ DestroyAdapter<A>::DestroyElements(inlined_ptr->GetAllocator(),
+ inlined_ptr->GetInlinedData(),
+ inlined_ptr->GetSize());
- inlined_ptr->SetAllocatedData(allocated_storage_view.data,
- allocated_storage_view.capacity);
+ inlined_ptr->SetAllocation(Allocation<A>{allocated_storage_view.data,
+ allocated_storage_view.capacity});
}
swap(GetSizeAndIsAllocated(), other_storage_ptr->GetSizeAndIsAllocated());
- swap(*GetAllocPtr(), *other_storage_ptr->GetAllocPtr());
+ swap(GetAllocator(), other_storage_ptr->GetAllocator());
}
-// End ignore "array-bounds" and "maybe-uninitialized"
+template <typename T, size_t N, typename A>
+void Storage<T, N, A>::SwapN(ElementwiseSwapPolicy, Storage* other,
+ SizeType<A> n) {
+ std::swap_ranges(GetInlinedData(), GetInlinedData() + n,
+ other->GetInlinedData());
+}
+
+template <typename T, size_t N, typename A>
+void Storage<T, N, A>::SwapN(ElementwiseConstructPolicy, Storage* other,
+ SizeType<A> n) {
+ Pointer<A> a = GetInlinedData();
+ Pointer<A> b = other->GetInlinedData();
+ // see note on allocators in `SwapInlinedElements`.
+ A& allocator_a = GetAllocator();
+ A& allocator_b = other->GetAllocator();
+ for (SizeType<A> i = 0; i < n; ++i, ++a, ++b) {
+ ValueType<A> tmp(std::move(*a));
+
+ AllocatorTraits<A>::destroy(allocator_a, a);
+ AllocatorTraits<A>::construct(allocator_b, a, std::move(*b));
+
+ AllocatorTraits<A>::destroy(allocator_b, b);
+ AllocatorTraits<A>::construct(allocator_a, b, std::move(tmp));
+ }
+}
+
+template <typename T, size_t N, typename A>
+void Storage<T, N, A>::SwapInlinedElements(MemcpyPolicy, Storage* other) {
+ Data tmp = data_;
+ data_ = other->data_;
+ other->data_ = tmp;
+}
+
+template <typename T, size_t N, typename A>
+template <typename NotMemcpyPolicy>
+void Storage<T, N, A>::SwapInlinedElements(NotMemcpyPolicy policy,
+ Storage* other) {
+ // Note: `destroy` needs to use pre-swap allocator while `construct` -
+ // post-swap allocator. Allocators will be swapped later on outside of
+ // `SwapInlinedElements`.
+ Storage* small_ptr = this;
+ Storage* large_ptr = other;
+ if (small_ptr->GetSize() > large_ptr->GetSize()) {
+ std::swap(small_ptr, large_ptr);
+ }
+
+ auto small_size = small_ptr->GetSize();
+ auto diff = large_ptr->GetSize() - small_size;
+ SwapN(policy, other, small_size);
+
+ IteratorValueAdapter<A, MoveIterator<A>> move_values(
+ MoveIterator<A>(large_ptr->GetInlinedData() + small_size));
+
+ ConstructElements<A>(large_ptr->GetAllocator(),
+ small_ptr->GetInlinedData() + small_size, move_values,
+ diff);
+
+ DestroyAdapter<A>::DestroyElements(large_ptr->GetAllocator(),
+ large_ptr->GetInlinedData() + small_size,
+ diff);
+}
+
+// End ignore "array-bounds"
#if !defined(__clang__) && defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
@@ -965,4 +1111,4 @@
ABSL_NAMESPACE_END
} // namespace absl
-#endif // ABSL_CONTAINER_INTERNAL_INLINED_VECTOR_INTERNAL_H_
+#endif // ABSL_CONTAINER_INTERNAL_INLINED_VECTOR_H_
diff --git a/absl/container/internal/layout.h b/absl/container/internal/layout.h
index a59a243..a4ba610 100644
--- a/absl/container/internal/layout.h
+++ b/absl/container/internal/layout.h
@@ -55,7 +55,7 @@
// `Partial()` comes in handy when the array sizes are embedded into the
// allocation.
//
-// // size_t[1] containing N, size_t[1] containing M, double[N], int[M].
+// // size_t[0] containing N, size_t[1] containing M, double[N], int[M].
// using L = Layout<size_t, size_t, double, int>;
//
// unsigned char* Allocate(size_t n, size_t m) {
@@ -172,6 +172,7 @@
#include <utility>
#include "absl/base/config.h"
+#include "absl/debugging/internal/demangle.h"
#include "absl/meta/type_traits.h"
#include "absl/strings/str_cat.h"
#include "absl/types/span.h"
@@ -181,14 +182,6 @@
#include <sanitizer/asan_interface.h>
#endif
-#if defined(__GXX_RTTI)
-#define ABSL_INTERNAL_HAS_CXA_DEMANGLE
-#endif
-
-#ifdef ABSL_INTERNAL_HAS_CXA_DEMANGLE
-#include <cxxabi.h>
-#endif
-
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace container_internal {
@@ -294,19 +287,11 @@
template <class T>
std::string TypeName() {
std::string out;
- int status = 0;
- char* demangled = nullptr;
-#ifdef ABSL_INTERNAL_HAS_CXA_DEMANGLE
- demangled = abi::__cxa_demangle(typeid(T).name(), nullptr, nullptr, &status);
+#if ABSL_INTERNAL_HAS_RTTI
+ absl::StrAppend(&out, "<",
+ absl::debugging_internal::DemangleString(typeid(T).name()),
+ ">");
#endif
- if (status == 0 && demangled != nullptr) { // Demangling succeeded.
- absl::StrAppend(&out, "<", demangled, ">");
- free(demangled);
- } else {
-#if defined(__GXX_RTTI) || defined(_CPPRTTI)
- absl::StrAppend(&out, "<", typeid(T).name(), ">");
-#endif
- }
return out;
}
diff --git a/absl/container/internal/layout_benchmark.cc b/absl/container/internal/layout_benchmark.cc
index d8636e8..3af35e3 100644
--- a/absl/container/internal/layout_benchmark.cc
+++ b/absl/container/internal/layout_benchmark.cc
@@ -85,7 +85,7 @@
size_t m = 5;
size_t k = 7;
ABSL_RAW_CHECK(L::Partial(n, m, k).template Offset<3>() == Offset,
- "Inavlid offset");
+ "Invalid offset");
for (auto _ : state) {
DoNotOptimize(n);
DoNotOptimize(m);
diff --git a/absl/container/internal/layout_test.cc b/absl/container/internal/layout_test.cc
index 1d7158f..ae55cf7 100644
--- a/absl/container/internal/layout_test.cc
+++ b/absl/container/internal/layout_test.cc
@@ -19,15 +19,20 @@
#include <stddef.h>
#include <cstdint>
+#include <cstring>
+#include <initializer_list>
#include <memory>
-#include <sstream>
+#include <ostream>
+#include <string>
+#include <tuple>
#include <type_traits>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/base/config.h"
-#include "absl/base/internal/raw_logging.h"
+#include "absl/log/check.h"
#include "absl/types/span.h"
+#include "absl/utility/utility.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
@@ -38,7 +43,7 @@
using ::testing::ElementsAre;
size_t Distance(const void* from, const void* to) {
- ABSL_RAW_CHECK(from <= to, "Distance must be non-negative");
+ CHECK_LE(from, to) << "Distance must be non-negative";
return static_cast<const char*>(to) - static_cast<const char*>(from);
}
@@ -366,7 +371,7 @@
}
TEST(Layout, PointerByIndex) {
- alignas(max_align_t) const unsigned char p[100] = {};
+ alignas(max_align_t) const unsigned char p[100] = {0};
{
using L = Layout<int32_t>;
EXPECT_EQ(0, Distance(p, Type<const int32_t*>(L::Partial().Pointer<0>(p))));
@@ -447,7 +452,7 @@
}
TEST(Layout, PointerByType) {
- alignas(max_align_t) const unsigned char p[100] = {};
+ alignas(max_align_t) const unsigned char p[100] = {0};
{
using L = Layout<int32_t>;
EXPECT_EQ(
@@ -526,7 +531,7 @@
}
TEST(Layout, MutablePointerByIndex) {
- alignas(max_align_t) unsigned char p[100];
+ alignas(max_align_t) unsigned char p[100] = {0};
{
using L = Layout<int32_t>;
EXPECT_EQ(0, Distance(p, Type<int32_t*>(L::Partial().Pointer<0>(p))));
@@ -581,7 +586,7 @@
}
TEST(Layout, MutablePointerByType) {
- alignas(max_align_t) unsigned char p[100];
+ alignas(max_align_t) unsigned char p[100] = {0};
{
using L = Layout<int32_t>;
EXPECT_EQ(0, Distance(p, Type<int32_t*>(L::Partial().Pointer<int32_t>(p))));
@@ -647,7 +652,7 @@
}
TEST(Layout, Pointers) {
- alignas(max_align_t) const unsigned char p[100] = {};
+ alignas(max_align_t) const unsigned char p[100] = {0};
using L = Layout<int8_t, int8_t, Int128>;
{
const auto x = L::Partial();
@@ -683,7 +688,7 @@
}
TEST(Layout, MutablePointers) {
- alignas(max_align_t) unsigned char p[100];
+ alignas(max_align_t) unsigned char p[100] = {0};
using L = Layout<int8_t, int8_t, Int128>;
{
const auto x = L::Partial();
@@ -716,7 +721,7 @@
}
TEST(Layout, SliceByIndexSize) {
- alignas(max_align_t) const unsigned char p[100] = {};
+ alignas(max_align_t) const unsigned char p[100] = {0};
{
using L = Layout<int32_t>;
EXPECT_EQ(0, L::Partial(0).Slice<0>(p).size());
@@ -744,7 +749,7 @@
}
TEST(Layout, SliceByTypeSize) {
- alignas(max_align_t) const unsigned char p[100] = {};
+ alignas(max_align_t) const unsigned char p[100] = {0};
{
using L = Layout<int32_t>;
EXPECT_EQ(0, L::Partial(0).Slice<int32_t>(p).size());
@@ -766,7 +771,7 @@
}
TEST(Layout, MutableSliceByIndexSize) {
- alignas(max_align_t) unsigned char p[100];
+ alignas(max_align_t) unsigned char p[100] = {0};
{
using L = Layout<int32_t>;
EXPECT_EQ(0, L::Partial(0).Slice<0>(p).size());
@@ -794,7 +799,7 @@
}
TEST(Layout, MutableSliceByTypeSize) {
- alignas(max_align_t) unsigned char p[100];
+ alignas(max_align_t) unsigned char p[100] = {0};
{
using L = Layout<int32_t>;
EXPECT_EQ(0, L::Partial(0).Slice<int32_t>(p).size());
@@ -816,7 +821,7 @@
}
TEST(Layout, SliceByIndexData) {
- alignas(max_align_t) const unsigned char p[100] = {};
+ alignas(max_align_t) const unsigned char p[100] = {0};
{
using L = Layout<int32_t>;
EXPECT_EQ(
@@ -939,7 +944,7 @@
}
TEST(Layout, SliceByTypeData) {
- alignas(max_align_t) const unsigned char p[100] = {};
+ alignas(max_align_t) const unsigned char p[100] = {0};
{
using L = Layout<int32_t>;
EXPECT_EQ(
@@ -1037,7 +1042,7 @@
}
TEST(Layout, MutableSliceByIndexData) {
- alignas(max_align_t) unsigned char p[100];
+ alignas(max_align_t) unsigned char p[100] = {0};
{
using L = Layout<int32_t>;
EXPECT_EQ(
@@ -1122,7 +1127,7 @@
}
TEST(Layout, MutableSliceByTypeData) {
- alignas(max_align_t) unsigned char p[100];
+ alignas(max_align_t) unsigned char p[100] = {0};
{
using L = Layout<int32_t>;
EXPECT_EQ(
@@ -1268,7 +1273,7 @@
}
TEST(Layout, Slices) {
- alignas(max_align_t) const unsigned char p[100] = {};
+ alignas(max_align_t) const unsigned char p[100] = {0};
using L = Layout<int8_t, int8_t, Int128>;
{
const auto x = L::Partial();
@@ -1302,7 +1307,7 @@
}
TEST(Layout, MutableSlices) {
- alignas(max_align_t) unsigned char p[100] = {};
+ alignas(max_align_t) unsigned char p[100] = {0};
using L = Layout<int8_t, int8_t, Int128>;
{
const auto x = L::Partial();
@@ -1350,7 +1355,13 @@
TEST(Layout, OverAligned) {
constexpr size_t M = alignof(max_align_t);
constexpr Layout<unsigned char, Aligned<unsigned char, 2 * M>> x(1, 3);
+#ifdef __GNUC__
+ // Using __attribute__ ((aligned ())) instead of alignas to bypass a gcc bug:
+ // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=89357
+ __attribute__((aligned(2 * M))) unsigned char p[x.AllocSize()];
+#else
alignas(2 * M) unsigned char p[x.AllocSize()];
+#endif
EXPECT_EQ(2 * M + 3, x.AllocSize());
EXPECT_THAT(x.Pointers(p), Tuple(p + 0, p + 2 * M));
}
diff --git a/absl/container/internal/node_hash_policy.h b/absl/container/internal/node_slot_policy.h
similarity index 87%
rename from absl/container/internal/node_hash_policy.h
rename to absl/container/internal/node_slot_policy.h
index 4617162..3f1874d 100644
--- a/absl/container/internal/node_hash_policy.h
+++ b/absl/container/internal/node_slot_policy.h
@@ -30,8 +30,8 @@
// It may also optionally define `value()` and `apply()`. For documentation on
// these, see hash_policy_traits.h.
-#ifndef ABSL_CONTAINER_INTERNAL_NODE_HASH_POLICY_H_
-#define ABSL_CONTAINER_INTERNAL_NODE_HASH_POLICY_H_
+#ifndef ABSL_CONTAINER_INTERNAL_NODE_SLOT_POLICY_H_
+#define ABSL_CONTAINER_INTERNAL_NODE_SLOT_POLICY_H_
#include <cassert>
#include <cstddef>
@@ -46,7 +46,7 @@
namespace container_internal {
template <class Reference, class Policy>
-struct node_hash_policy {
+struct node_slot_policy {
static_assert(std::is_lvalue_reference<Reference>::value, "");
using slot_type = typename std::remove_cv<
@@ -62,9 +62,12 @@
Policy::delete_element(alloc, *slot);
}
+ // Returns true_type to indicate that transfer can use memcpy.
template <class Alloc>
- static void transfer(Alloc*, slot_type* new_slot, slot_type* old_slot) {
+ static std::true_type transfer(Alloc*, slot_type* new_slot,
+ slot_type* old_slot) {
*new_slot = *old_slot;
+ return {};
}
static size_t space_used(const slot_type* slot) {
@@ -89,4 +92,4 @@
ABSL_NAMESPACE_END
} // namespace absl
-#endif // ABSL_CONTAINER_INTERNAL_NODE_HASH_POLICY_H_
+#endif // ABSL_CONTAINER_INTERNAL_NODE_SLOT_POLICY_H_
diff --git a/absl/container/internal/node_hash_policy_test.cc b/absl/container/internal/node_slot_policy_test.cc
similarity index 89%
rename from absl/container/internal/node_hash_policy_test.cc
rename to absl/container/internal/node_slot_policy_test.cc
index 84aabba..d4ea919 100644
--- a/absl/container/internal/node_hash_policy_test.cc
+++ b/absl/container/internal/node_slot_policy_test.cc
@@ -12,12 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#include "absl/container/internal/node_hash_policy.h"
+#include "absl/container/internal/node_slot_policy.h"
#include <memory>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
+#include "absl/base/config.h"
#include "absl/container/internal/hash_policy_traits.h"
namespace absl {
@@ -27,7 +28,7 @@
using ::testing::Pointee;
-struct Policy : node_hash_policy<int&, Policy> {
+struct Policy : node_slot_policy<int&, Policy> {
using key_type = int;
using init_type = int;
@@ -61,6 +62,7 @@
int* b = &s;
NodePolicy::transfer(&alloc, &a, &b);
EXPECT_EQ(&s, a);
+ EXPECT_TRUE(NodePolicy::transfer_uses_memcpy());
}
} // namespace
diff --git a/absl/container/internal/raw_hash_map.h b/absl/container/internal/raw_hash_map.h
index c7df2ef..464bf23 100644
--- a/absl/container/internal/raw_hash_map.h
+++ b/absl/container/internal/raw_hash_map.h
@@ -19,6 +19,8 @@
#include <type_traits>
#include <utility>
+#include "absl/base/attributes.h"
+#include "absl/base/config.h"
#include "absl/base/internal/throw_delegate.h"
#include "absl/container/internal/container_memory.h"
#include "absl/container/internal/raw_hash_set.h" // IWYU pragma: export
@@ -71,43 +73,51 @@
// m.insert_or_assign(n, n);
template <class K = key_type, class V = mapped_type, K* = nullptr,
V* = nullptr>
- std::pair<iterator, bool> insert_or_assign(key_arg<K>&& k, V&& v) {
+ std::pair<iterator, bool> insert_or_assign(key_arg<K>&& k, V&& v)
+ ABSL_ATTRIBUTE_LIFETIME_BOUND {
return insert_or_assign_impl(std::forward<K>(k), std::forward<V>(v));
}
template <class K = key_type, class V = mapped_type, K* = nullptr>
- std::pair<iterator, bool> insert_or_assign(key_arg<K>&& k, const V& v) {
+ std::pair<iterator, bool> insert_or_assign(key_arg<K>&& k, const V& v)
+ ABSL_ATTRIBUTE_LIFETIME_BOUND {
return insert_or_assign_impl(std::forward<K>(k), v);
}
template <class K = key_type, class V = mapped_type, V* = nullptr>
- std::pair<iterator, bool> insert_or_assign(const key_arg<K>& k, V&& v) {
+ std::pair<iterator, bool> insert_or_assign(const key_arg<K>& k, V&& v)
+ ABSL_ATTRIBUTE_LIFETIME_BOUND {
return insert_or_assign_impl(k, std::forward<V>(v));
}
template <class K = key_type, class V = mapped_type>
- std::pair<iterator, bool> insert_or_assign(const key_arg<K>& k, const V& v) {
+ std::pair<iterator, bool> insert_or_assign(const key_arg<K>& k, const V& v)
+ ABSL_ATTRIBUTE_LIFETIME_BOUND {
return insert_or_assign_impl(k, v);
}
template <class K = key_type, class V = mapped_type, K* = nullptr,
V* = nullptr>
- iterator insert_or_assign(const_iterator, key_arg<K>&& k, V&& v) {
+ iterator insert_or_assign(const_iterator, key_arg<K>&& k,
+ V&& v) ABSL_ATTRIBUTE_LIFETIME_BOUND {
return insert_or_assign(std::forward<K>(k), std::forward<V>(v)).first;
}
template <class K = key_type, class V = mapped_type, K* = nullptr>
- iterator insert_or_assign(const_iterator, key_arg<K>&& k, const V& v) {
+ iterator insert_or_assign(const_iterator, key_arg<K>&& k,
+ const V& v) ABSL_ATTRIBUTE_LIFETIME_BOUND {
return insert_or_assign(std::forward<K>(k), v).first;
}
template <class K = key_type, class V = mapped_type, V* = nullptr>
- iterator insert_or_assign(const_iterator, const key_arg<K>& k, V&& v) {
+ iterator insert_or_assign(const_iterator, const key_arg<K>& k,
+ V&& v) ABSL_ATTRIBUTE_LIFETIME_BOUND {
return insert_or_assign(k, std::forward<V>(v)).first;
}
template <class K = key_type, class V = mapped_type>
- iterator insert_or_assign(const_iterator, const key_arg<K>& k, const V& v) {
+ iterator insert_or_assign(const_iterator, const key_arg<K>& k,
+ const V& v) ABSL_ATTRIBUTE_LIFETIME_BOUND {
return insert_or_assign(k, v).first;
}
@@ -118,29 +128,33 @@
typename std::enable_if<
!std::is_convertible<K, const_iterator>::value, int>::type = 0,
K* = nullptr>
- std::pair<iterator, bool> try_emplace(key_arg<K>&& k, Args&&... args) {
+ std::pair<iterator, bool> try_emplace(key_arg<K>&& k, Args&&... args)
+ ABSL_ATTRIBUTE_LIFETIME_BOUND {
return try_emplace_impl(std::forward<K>(k), std::forward<Args>(args)...);
}
template <class K = key_type, class... Args,
typename std::enable_if<
!std::is_convertible<K, const_iterator>::value, int>::type = 0>
- std::pair<iterator, bool> try_emplace(const key_arg<K>& k, Args&&... args) {
+ std::pair<iterator, bool> try_emplace(const key_arg<K>& k, Args&&... args)
+ ABSL_ATTRIBUTE_LIFETIME_BOUND {
return try_emplace_impl(k, std::forward<Args>(args)...);
}
template <class K = key_type, class... Args, K* = nullptr>
- iterator try_emplace(const_iterator, key_arg<K>&& k, Args&&... args) {
+ iterator try_emplace(const_iterator, key_arg<K>&& k,
+ Args&&... args) ABSL_ATTRIBUTE_LIFETIME_BOUND {
return try_emplace(std::forward<K>(k), std::forward<Args>(args)...).first;
}
template <class K = key_type, class... Args>
- iterator try_emplace(const_iterator, const key_arg<K>& k, Args&&... args) {
+ iterator try_emplace(const_iterator, const key_arg<K>& k,
+ Args&&... args) ABSL_ATTRIBUTE_LIFETIME_BOUND {
return try_emplace(k, std::forward<Args>(args)...).first;
}
template <class K = key_type, class P = Policy>
- MappedReference<P> at(const key_arg<K>& key) {
+ MappedReference<P> at(const key_arg<K>& key) ABSL_ATTRIBUTE_LIFETIME_BOUND {
auto it = this->find(key);
if (it == this->end()) {
base_internal::ThrowStdOutOfRange(
@@ -150,7 +164,8 @@
}
template <class K = key_type, class P = Policy>
- MappedConstReference<P> at(const key_arg<K>& key) const {
+ MappedConstReference<P> at(const key_arg<K>& key) const
+ ABSL_ATTRIBUTE_LIFETIME_BOUND {
auto it = this->find(key);
if (it == this->end()) {
base_internal::ThrowStdOutOfRange(
@@ -160,34 +175,47 @@
}
template <class K = key_type, class P = Policy, K* = nullptr>
- MappedReference<P> operator[](key_arg<K>&& key) {
- return Policy::value(&*try_emplace(std::forward<K>(key)).first);
+ MappedReference<P> operator[](key_arg<K>&& key)
+ ABSL_ATTRIBUTE_LIFETIME_BOUND {
+ // It is safe to use unchecked_deref here because try_emplace
+ // will always return an iterator pointing to a valid item in the table,
+ // since it inserts if nothing is found for the given key.
+ return Policy::value(
+ &this->unchecked_deref(try_emplace(std::forward<K>(key)).first));
}
template <class K = key_type, class P = Policy>
- MappedReference<P> operator[](const key_arg<K>& key) {
- return Policy::value(&*try_emplace(key).first);
+ MappedReference<P> operator[](const key_arg<K>& key)
+ ABSL_ATTRIBUTE_LIFETIME_BOUND {
+ // It is safe to use unchecked_deref here because try_emplace
+ // will always return an iterator pointing to a valid item in the table,
+ // since it inserts if nothing is found for the given key.
+ return Policy::value(&this->unchecked_deref(try_emplace(key).first));
}
private:
template <class K, class V>
- std::pair<iterator, bool> insert_or_assign_impl(K&& k, V&& v) {
+ std::pair<iterator, bool> insert_or_assign_impl(K&& k, V&& v)
+ ABSL_ATTRIBUTE_LIFETIME_BOUND {
auto res = this->find_or_prepare_insert(k);
- if (res.second)
+ if (res.second) {
this->emplace_at(res.first, std::forward<K>(k), std::forward<V>(v));
- else
- Policy::value(&*this->iterator_at(res.first)) = std::forward<V>(v);
- return {this->iterator_at(res.first), res.second};
+ } else {
+ Policy::value(&*res.first) = std::forward<V>(v);
+ }
+ return res;
}
template <class K = key_type, class... Args>
- std::pair<iterator, bool> try_emplace_impl(K&& k, Args&&... args) {
+ std::pair<iterator, bool> try_emplace_impl(K&& k, Args&&... args)
+ ABSL_ATTRIBUTE_LIFETIME_BOUND {
auto res = this->find_or_prepare_insert(k);
- if (res.second)
+ if (res.second) {
this->emplace_at(res.first, std::piecewise_construct,
std::forward_as_tuple(std::forward<K>(k)),
std::forward_as_tuple(std::forward<Args>(args)...));
- return {this->iterator_at(res.first), res.second};
+ }
+ return res;
}
};
diff --git a/absl/container/internal/raw_hash_set.cc b/absl/container/internal/raw_hash_set.cc
index a5dee6a..efb3d6f 100644
--- a/absl/container/internal/raw_hash_set.cc
+++ b/absl/container/internal/raw_hash_set.cc
@@ -15,20 +15,52 @@
#include "absl/container/internal/raw_hash_set.h"
#include <atomic>
+#include <cassert>
#include <cstddef>
+#include <cstdint>
+#include <cstring>
+#include "absl/base/attributes.h"
#include "absl/base/config.h"
+#include "absl/base/dynamic_annotations.h"
+#include "absl/container/internal/container_memory.h"
+#include "absl/hash/hash.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace container_internal {
+// We have space for `growth_left` before a single block of control bytes. A
+// single block of empty control bytes for tables without any slots allocated.
+// This enables removing a branch in the hot path of find(). In order to ensure
+// that the control bytes are aligned to 16, we have 16 bytes before the control
+// bytes even though growth_left only needs 8.
+constexpr ctrl_t ZeroCtrlT() { return static_cast<ctrl_t>(0); }
+alignas(16) ABSL_CONST_INIT ABSL_DLL const ctrl_t kEmptyGroup[32] = {
+ ZeroCtrlT(), ZeroCtrlT(), ZeroCtrlT(), ZeroCtrlT(),
+ ZeroCtrlT(), ZeroCtrlT(), ZeroCtrlT(), ZeroCtrlT(),
+ ZeroCtrlT(), ZeroCtrlT(), ZeroCtrlT(), ZeroCtrlT(),
+ ZeroCtrlT(), ZeroCtrlT(), ZeroCtrlT(), ZeroCtrlT(),
+ ctrl_t::kSentinel, ctrl_t::kEmpty, ctrl_t::kEmpty, ctrl_t::kEmpty,
+ ctrl_t::kEmpty, ctrl_t::kEmpty, ctrl_t::kEmpty, ctrl_t::kEmpty,
+ ctrl_t::kEmpty, ctrl_t::kEmpty, ctrl_t::kEmpty, ctrl_t::kEmpty,
+ ctrl_t::kEmpty, ctrl_t::kEmpty, ctrl_t::kEmpty, ctrl_t::kEmpty};
+
+#ifdef ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL
constexpr size_t Group::kWidth;
+#endif
+
+namespace {
// Returns "random" seed.
inline size_t RandomSeed() {
#ifdef ABSL_HAVE_THREAD_LOCAL
static thread_local size_t counter = 0;
+ // On Linux kernels >= 5.4 the MSAN runtime has a false-positive when
+ // accessing thread local storage data from loaded libraries
+ // (https://github.com/google/sanitizers/issues/1265), for this reason counter
+ // needs to be annotated as initialized.
+ ABSL_ANNOTATE_MEMORY_IS_INITIALIZED(&counter, sizeof(size_t));
size_t value = ++counter;
#else // ABSL_HAVE_THREAD_LOCAL
static std::atomic<size_t> counter(0);
@@ -37,10 +69,46 @@
return value ^ static_cast<size_t>(reinterpret_cast<uintptr_t>(&counter));
}
-bool ShouldInsertBackwards(size_t hash, const ctrl_t* ctrl) {
+bool ShouldRehashForBugDetection(const ctrl_t* ctrl, size_t capacity) {
+ // Note: we can't use the abseil-random library because abseil-random
+ // depends on swisstable. We want to return true with probability
+ // `min(1, RehashProbabilityConstant() / capacity())`. In order to do this,
+ // we probe based on a random hash and see if the offset is less than
+ // RehashProbabilityConstant().
+ return probe(ctrl, capacity, absl::HashOf(RandomSeed())).offset() <
+ RehashProbabilityConstant();
+}
+
+} // namespace
+
+GenerationType* EmptyGeneration() {
+ if (SwisstableGenerationsEnabled()) {
+ constexpr size_t kNumEmptyGenerations = 1024;
+ static constexpr GenerationType kEmptyGenerations[kNumEmptyGenerations]{};
+ return const_cast<GenerationType*>(
+ &kEmptyGenerations[RandomSeed() % kNumEmptyGenerations]);
+ }
+ return nullptr;
+}
+
+bool CommonFieldsGenerationInfoEnabled::
+ should_rehash_for_bug_detection_on_insert(const ctrl_t* ctrl,
+ size_t capacity) const {
+ if (reserved_growth_ == kReservedGrowthJustRanOut) return true;
+ if (reserved_growth_ > 0) return false;
+ return ShouldRehashForBugDetection(ctrl, capacity);
+}
+
+bool CommonFieldsGenerationInfoEnabled::should_rehash_for_bug_detection_on_move(
+ const ctrl_t* ctrl, size_t capacity) const {
+ return ShouldRehashForBugDetection(ctrl, capacity);
+}
+
+bool ShouldInsertBackwardsForDebug(size_t capacity, size_t hash,
+ const ctrl_t* ctrl) {
// To avoid problems with weak hashes and single bit tests, we use % 13.
// TODO(kfm,sbenza): revisit after we do unconditional mixing
- return (H1(hash, ctrl) ^ RandomSeed()) % 13 > 6;
+ return !is_small(capacity) && (H1(hash, ctrl) ^ RandomSeed()) % 13 > 6;
}
void ConvertDeletedToEmptyAndFullToDeleted(ctrl_t* ctrl, size_t capacity) {
@@ -53,6 +121,260 @@
std::memcpy(ctrl + capacity + 1, ctrl, NumClonedBytes());
ctrl[capacity] = ctrl_t::kSentinel;
}
+// Extern template instantiation for inline function.
+template FindInfo find_first_non_full(const CommonFields&, size_t);
+
+FindInfo find_first_non_full_outofline(const CommonFields& common,
+ size_t hash) {
+ return find_first_non_full(common, hash);
+}
+
+// Returns the address of the slot just after slot assuming each slot has the
+// specified size.
+static inline void* NextSlot(void* slot, size_t slot_size) {
+ return reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(slot) + slot_size);
+}
+
+// Returns the address of the slot just before slot assuming each slot has the
+// specified size.
+static inline void* PrevSlot(void* slot, size_t slot_size) {
+ return reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(slot) - slot_size);
+}
+
+void DropDeletesWithoutResize(CommonFields& common, const void* hash_fn,
+ const PolicyFunctions& policy, void* tmp_space) {
+ void* set = &common;
+ void* slot_array = common.slot_array();
+ const size_t capacity = common.capacity();
+ assert(IsValidCapacity(capacity));
+ assert(!is_small(capacity));
+ // Algorithm:
+ // - mark all DELETED slots as EMPTY
+ // - mark all FULL slots as DELETED
+ // - for each slot marked as DELETED
+ // hash = Hash(element)
+ // target = find_first_non_full(hash)
+ // if target is in the same group
+ // mark slot as FULL
+ // else if target is EMPTY
+ // transfer element to target
+ // mark slot as EMPTY
+ // mark target as FULL
+ // else if target is DELETED
+ // swap current element with target element
+ // mark target as FULL
+ // repeat procedure for current slot with moved from element (target)
+ ctrl_t* ctrl = common.control();
+ ConvertDeletedToEmptyAndFullToDeleted(ctrl, capacity);
+ auto hasher = policy.hash_slot;
+ auto transfer = policy.transfer;
+ const size_t slot_size = policy.slot_size;
+
+ size_t total_probe_length = 0;
+ void* slot_ptr = SlotAddress(slot_array, 0, slot_size);
+ for (size_t i = 0; i != capacity;
+ ++i, slot_ptr = NextSlot(slot_ptr, slot_size)) {
+ assert(slot_ptr == SlotAddress(slot_array, i, slot_size));
+ if (!IsDeleted(ctrl[i])) continue;
+ const size_t hash = (*hasher)(hash_fn, slot_ptr);
+ const FindInfo target = find_first_non_full(common, hash);
+ const size_t new_i = target.offset;
+ total_probe_length += target.probe_length;
+
+ // Verify if the old and new i fall within the same group wrt the hash.
+ // If they do, we don't need to move the object as it falls already in the
+ // best probe we can.
+ const size_t probe_offset = probe(common, hash).offset();
+ const auto probe_index = [probe_offset, capacity](size_t pos) {
+ return ((pos - probe_offset) & capacity) / Group::kWidth;
+ };
+
+ // Element doesn't move.
+ if (ABSL_PREDICT_TRUE(probe_index(new_i) == probe_index(i))) {
+ SetCtrl(common, i, H2(hash), slot_size);
+ continue;
+ }
+
+ void* new_slot_ptr = SlotAddress(slot_array, new_i, slot_size);
+ if (IsEmpty(ctrl[new_i])) {
+ // Transfer element to the empty spot.
+ // SetCtrl poisons/unpoisons the slots so we have to call it at the
+ // right time.
+ SetCtrl(common, new_i, H2(hash), slot_size);
+ (*transfer)(set, new_slot_ptr, slot_ptr);
+ SetCtrl(common, i, ctrl_t::kEmpty, slot_size);
+ } else {
+ assert(IsDeleted(ctrl[new_i]));
+ SetCtrl(common, new_i, H2(hash), slot_size);
+ // Until we are done rehashing, DELETED marks previously FULL slots.
+
+ // Swap i and new_i elements.
+ (*transfer)(set, tmp_space, new_slot_ptr);
+ (*transfer)(set, new_slot_ptr, slot_ptr);
+ (*transfer)(set, slot_ptr, tmp_space);
+
+ // repeat the processing of the ith slot
+ --i;
+ slot_ptr = PrevSlot(slot_ptr, slot_size);
+ }
+ }
+ ResetGrowthLeft(common);
+ common.infoz().RecordRehash(total_probe_length);
+}
+
+static bool WasNeverFull(CommonFields& c, size_t index) {
+ if (is_single_group(c.capacity())) {
+ return true;
+ }
+ const size_t index_before = (index - Group::kWidth) & c.capacity();
+ const auto empty_after = Group(c.control() + index).MaskEmpty();
+ const auto empty_before = Group(c.control() + index_before).MaskEmpty();
+
+ // We count how many consecutive non empties we have to the right and to the
+ // left of `it`. If the sum is >= kWidth then there is at least one probe
+ // window that might have seen a full group.
+ return empty_before && empty_after &&
+ static_cast<size_t>(empty_after.TrailingZeros()) +
+ empty_before.LeadingZeros() <
+ Group::kWidth;
+}
+
+void EraseMetaOnly(CommonFields& c, size_t index, size_t slot_size) {
+ assert(IsFull(c.control()[index]) && "erasing a dangling iterator");
+ c.decrement_size();
+ c.infoz().RecordErase();
+
+ if (WasNeverFull(c, index)) {
+ SetCtrl(c, index, ctrl_t::kEmpty, slot_size);
+ c.set_growth_left(c.growth_left() + 1);
+ return;
+ }
+
+ SetCtrl(c, index, ctrl_t::kDeleted, slot_size);
+}
+
+void ClearBackingArray(CommonFields& c, const PolicyFunctions& policy,
+ bool reuse) {
+ c.set_size(0);
+ if (reuse) {
+ ResetCtrl(c, policy.slot_size);
+ ResetGrowthLeft(c);
+ c.infoz().RecordStorageChanged(0, c.capacity());
+ } else {
+ // We need to record infoz before calling dealloc, which will unregister
+ // infoz.
+ c.infoz().RecordClearedReservation();
+ c.infoz().RecordStorageChanged(0, 0);
+ (*policy.dealloc)(c, policy);
+ c.set_control(EmptyGroup());
+ c.set_generation_ptr(EmptyGeneration());
+ c.set_slots(nullptr);
+ c.set_capacity(0);
+ }
+}
+
+void HashSetResizeHelper::GrowIntoSingleGroupShuffleControlBytes(
+ ctrl_t* new_ctrl, size_t new_capacity) const {
+ assert(is_single_group(new_capacity));
+ constexpr size_t kHalfWidth = Group::kWidth / 2;
+ assert(old_capacity_ < kHalfWidth);
+
+ const size_t half_old_capacity = old_capacity_ / 2;
+
+ // NOTE: operations are done with compile time known size = kHalfWidth.
+ // Compiler optimizes that into single ASM operation.
+
+ // Copy second half of bytes to the beginning.
+ // We potentially copy more bytes in order to have compile time known size.
+ // Mirrored bytes from the old_ctrl_ will also be copied.
+ // In case of old_capacity_ == 3, we will copy 1st element twice.
+ // Examples:
+ // old_ctrl = 0S0EEEEEEE...
+ // new_ctrl = S0EEEEEEEE...
+ //
+ // old_ctrl = 01S01EEEEE...
+ // new_ctrl = 1S01EEEEEE...
+ //
+ // old_ctrl = 0123456S0123456EE...
+ // new_ctrl = 456S0123?????????...
+ std::memcpy(new_ctrl, old_ctrl_ + half_old_capacity + 1, kHalfWidth);
+ // Clean up copied kSentinel from old_ctrl.
+ new_ctrl[half_old_capacity] = ctrl_t::kEmpty;
+
+ // Clean up damaged or uninitialized bytes.
+
+ // Clean bytes after the intended size of the copy.
+ // Example:
+ // new_ctrl = 1E01EEEEEEE????
+ // *new_ctrl= 1E0EEEEEEEE????
+ // position /
+ std::memset(new_ctrl + old_capacity_ + 1, static_cast<int8_t>(ctrl_t::kEmpty),
+ kHalfWidth);
+ // Clean non-mirrored bytes that are not initialized.
+ // For small old_capacity that may be inside of mirrored bytes zone.
+ // Examples:
+ // new_ctrl = 1E0EEEEEEEE??????????....
+ // *new_ctrl= 1E0EEEEEEEEEEEEE?????....
+ // position /
+ //
+ // new_ctrl = 456E0123???????????...
+ // *new_ctrl= 456E0123EEEEEEEE???...
+ // position /
+ std::memset(new_ctrl + kHalfWidth, static_cast<int8_t>(ctrl_t::kEmpty),
+ kHalfWidth);
+ // Clean last mirrored bytes that are not initialized
+ // and will not be overwritten by mirroring.
+ // Examples:
+ // new_ctrl = 1E0EEEEEEEEEEEEE????????
+ // *new_ctrl= 1E0EEEEEEEEEEEEEEEEEEEEE
+ // position S /
+ //
+ // new_ctrl = 456E0123EEEEEEEE???????????????
+ // *new_ctrl= 456E0123EEEEEEEE???????EEEEEEEE
+ // position S /
+ std::memset(new_ctrl + new_capacity + kHalfWidth,
+ static_cast<int8_t>(ctrl_t::kEmpty), kHalfWidth);
+
+ // Create mirrored bytes. old_capacity_ < kHalfWidth
+ // Example:
+ // new_ctrl = 456E0123EEEEEEEE???????EEEEEEEE
+ // *new_ctrl= 456E0123EEEEEEEE456E0123EEEEEEE
+ // position S/
+ ctrl_t g[kHalfWidth];
+ std::memcpy(g, new_ctrl, kHalfWidth);
+ std::memcpy(new_ctrl + new_capacity + 1, g, kHalfWidth);
+
+ // Finally set sentinel to its place.
+ new_ctrl[new_capacity] = ctrl_t::kSentinel;
+}
+
+void HashSetResizeHelper::GrowIntoSingleGroupShuffleTransferableSlots(
+ void* old_slots, void* new_slots, size_t slot_size) const {
+ assert(old_capacity_ > 0);
+ const size_t half_old_capacity = old_capacity_ / 2;
+
+ SanitizerUnpoisonMemoryRegion(old_slots, slot_size * old_capacity_);
+ std::memcpy(new_slots,
+ SlotAddress(old_slots, half_old_capacity + 1, slot_size),
+ slot_size * half_old_capacity);
+ std::memcpy(SlotAddress(new_slots, half_old_capacity + 1, slot_size),
+ old_slots, slot_size * (half_old_capacity + 1));
+}
+
+void HashSetResizeHelper::GrowSizeIntoSingleGroupTransferable(
+ CommonFields& c, void* old_slots, size_t slot_size) {
+ assert(old_capacity_ < Group::kWidth / 2);
+ assert(is_single_group(c.capacity()));
+ assert(IsGrowingIntoSingleGroupApplicable(old_capacity_, c.capacity()));
+
+ GrowIntoSingleGroupShuffleControlBytes(c.control(), c.capacity());
+ GrowIntoSingleGroupShuffleTransferableSlots(old_slots, c.slot_array(),
+ slot_size);
+
+ // We poison since GrowIntoSingleGroupShuffleTransferableSlots
+ // may leave empty slots unpoisoned.
+ PoisonSingleGroupEmptySlots(c, slot_size);
+}
} // namespace container_internal
ABSL_NAMESPACE_END
diff --git a/absl/container/internal/raw_hash_set.h b/absl/container/internal/raw_hash_set.h
index d778326..514f21a 100644
--- a/absl/container/internal/raw_hash_set.h
+++ b/absl/container/internal/raw_hash_set.h
@@ -53,48 +53,137 @@
//
// IMPLEMENTATION DETAILS
//
-// The table stores elements inline in a slot array. In addition to the slot
-// array the table maintains some control state per slot. The extra state is one
-// byte per slot and stores empty or deleted marks, or alternatively 7 bits from
-// the hash of an occupied slot. The table is split into logical groups of
-// slots, like so:
+// # Table Layout
+//
+// A raw_hash_set's backing array consists of control bytes followed by slots
+// that may or may not contain objects.
+//
+// The layout of the backing array, for `capacity` slots, is thus, as a
+// pseudo-struct:
+//
+// struct BackingArray {
+// // Sampling handler. This field isn't present when the sampling is
+// // disabled or this allocation hasn't been selected for sampling.
+// HashtablezInfoHandle infoz_;
+// // The number of elements we can insert before growing the capacity.
+// size_t growth_left;
+// // Control bytes for the "real" slots.
+// ctrl_t ctrl[capacity];
+// // Always `ctrl_t::kSentinel`. This is used by iterators to find when to
+// // stop and serves no other purpose.
+// ctrl_t sentinel;
+// // A copy of the first `kWidth - 1` elements of `ctrl`. This is used so
+// // that if a probe sequence picks a value near the end of `ctrl`,
+// // `Group` will have valid control bytes to look at.
+// ctrl_t clones[kWidth - 1];
+// // The actual slot data.
+// slot_type slots[capacity];
+// };
+//
+// The length of this array is computed by `RawHashSetLayout::alloc_size` below.
+//
+// Control bytes (`ctrl_t`) are bytes (collected into groups of a
+// platform-specific size) that define the state of the corresponding slot in
+// the slot array. Group manipulation is tightly optimized to be as efficient
+// as possible: SSE and friends on x86, clever bit operations on other arches.
//
// Group 1 Group 2 Group 3
// +---------------+---------------+---------------+
// | | | | | | | | | | | | | | | | | | | | | | | | |
// +---------------+---------------+---------------+
//
-// On lookup the hash is split into two parts:
-// - H2: 7 bits (those stored in the control bytes)
-// - H1: the rest of the bits
-// The groups are probed using H1. For each group the slots are matched to H2 in
-// parallel. Because H2 is 7 bits (128 states) and the number of slots per group
-// is low (8 or 16) in almost all cases a match in H2 is also a lookup hit.
+// Each control byte is either a special value for empty slots, deleted slots
+// (sometimes called *tombstones*), and a special end-of-table marker used by
+// iterators, or, if occupied, seven bits (H2) from the hash of the value in the
+// corresponding slot.
//
-// On insert, once the right group is found (as in lookup), its slots are
-// filled in order.
+// Storing control bytes in a separate array also has beneficial cache effects,
+// since more logical slots will fit into a cache line.
//
-// On erase a slot is cleared. In case the group did not have any empty slots
-// before the erase, the erased slot is marked as deleted.
+// # Hashing
//
-// Groups without empty slots (but maybe with deleted slots) extend the probe
-// sequence. The probing algorithm is quadratic. Given N the number of groups,
-// the probing function for the i'th probe is:
+// We compute two separate hashes, `H1` and `H2`, from the hash of an object.
+// `H1(hash(x))` is an index into `slots`, and essentially the starting point
+// for the probe sequence. `H2(hash(x))` is a 7-bit value used to filter out
+// objects that cannot possibly be the one we are looking for.
//
-// P(0) = H1 % N
+// # Table operations.
//
-// P(i) = (P(i - 1) + i) % N
+// The key operations are `insert`, `find`, and `erase`.
//
-// This probing function guarantees that after N probes, all the groups of the
-// table will be probed exactly once.
+// Since `insert` and `erase` are implemented in terms of `find`, we describe
+// `find` first. To `find` a value `x`, we compute `hash(x)`. From
+// `H1(hash(x))` and the capacity, we construct a `probe_seq` that visits every
+// group of slots in some interesting order.
+//
+// We now walk through these indices. At each index, we select the entire group
+// starting with that index and extract potential candidates: occupied slots
+// with a control byte equal to `H2(hash(x))`. If we find an empty slot in the
+// group, we stop and return an error. Each candidate slot `y` is compared with
+// `x`; if `x == y`, we are done and return `&y`; otherwise we continue to the
+// next probe index. Tombstones effectively behave like full slots that never
+// match the value we're looking for.
+//
+// The `H2` bits ensure when we compare a slot to an object with `==`, we are
+// likely to have actually found the object. That is, the chance is low that
+// `==` is called and returns `false`. Thus, when we search for an object, we
+// are unlikely to call `==` many times. This likelyhood can be analyzed as
+// follows (assuming that H2 is a random enough hash function).
+//
+// Let's assume that there are `k` "wrong" objects that must be examined in a
+// probe sequence. For example, when doing a `find` on an object that is in the
+// table, `k` is the number of objects between the start of the probe sequence
+// and the final found object (not including the final found object). The
+// expected number of objects with an H2 match is then `k/128`. Measurements
+// and analysis indicate that even at high load factors, `k` is less than 32,
+// meaning that the number of "false positive" comparisons we must perform is
+// less than 1/8 per `find`.
+
+// `insert` is implemented in terms of `unchecked_insert`, which inserts a
+// value presumed to not be in the table (violating this requirement will cause
+// the table to behave erratically). Given `x` and its hash `hash(x)`, to insert
+// it, we construct a `probe_seq` once again, and use it to find the first
+// group with an unoccupied (empty *or* deleted) slot. We place `x` into the
+// first such slot in the group and mark it as full with `x`'s H2.
+//
+// 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`.
+//
+// Below, `unchecked_insert` is partly implemented by `prepare_insert`, which
+// presents a viable, initialized slot pointee to the caller.
+//
+// `erase` is implemented in terms of `erase_at`, which takes an index to a
+// slot. Given an offset, we simply create a tombstone and destroy its contents.
+// If we can prove that the slot would not appear in a probe sequence, we can
+// make the slot as empty, instead. We can prove this by observing that if a
+// group has any empty slots, it has never been full (assuming we never create
+// an empty slot in a group with no empties, which this heuristic guarantees we
+// never do) and find would stop at this group anyways (since it does not probe
+// beyond groups with empties).
+//
+// `erase` is `erase_at` composed with `find`: if we
+// have a value `x`, we can perform a `find`, and then `erase_at` the resulting
+// slot.
+//
+// To iterate, we simply traverse the array, skipping empty and deleted slots
+// and stopping when we hit a `kSentinel`.
#ifndef ABSL_CONTAINER_INTERNAL_RAW_HASH_SET_H_
#define ABSL_CONTAINER_INTERNAL_RAW_HASH_SET_H_
#include <algorithm>
+#include <cassert>
#include <cmath>
+#include <cstddef>
#include <cstdint>
#include <cstring>
+#include <initializer_list>
#include <iterator>
#include <limits>
#include <memory>
@@ -102,26 +191,78 @@
#include <type_traits>
#include <utility>
+#include "absl/base/attributes.h"
+#include "absl/base/config.h"
#include "absl/base/internal/endian.h"
+#include "absl/base/internal/raw_logging.h"
+#include "absl/base/macros.h"
#include "absl/base/optimization.h"
+#include "absl/base/options.h"
#include "absl/base/port.h"
-#include "absl/container/internal/common.h"
+#include "absl/base/prefetch.h"
+#include "absl/container/internal/common.h" // IWYU pragma: export // for node_handle
#include "absl/container/internal/compressed_tuple.h"
#include "absl/container/internal/container_memory.h"
#include "absl/container/internal/hash_policy_traits.h"
#include "absl/container/internal/hashtable_debug_hooks.h"
#include "absl/container/internal/hashtablez_sampler.h"
-#include "absl/container/internal/have_sse.h"
-#include "absl/container/internal/layout.h"
#include "absl/memory/memory.h"
#include "absl/meta/type_traits.h"
#include "absl/numeric/bits.h"
#include "absl/utility/utility.h"
+#ifdef ABSL_INTERNAL_HAVE_SSE2
+#include <emmintrin.h>
+#endif
+
+#ifdef ABSL_INTERNAL_HAVE_SSSE3
+#include <tmmintrin.h>
+#endif
+
+#ifdef _MSC_VER
+#include <intrin.h>
+#endif
+
+#ifdef ABSL_INTERNAL_HAVE_ARM_NEON
+#include <arm_neon.h>
+#endif
+
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace container_internal {
+#ifdef ABSL_SWISSTABLE_ENABLE_GENERATIONS
+#error ABSL_SWISSTABLE_ENABLE_GENERATIONS cannot be directly set
+#elif defined(ABSL_HAVE_ADDRESS_SANITIZER) || \
+ defined(ABSL_HAVE_HWADDRESS_SANITIZER) || \
+ defined(ABSL_HAVE_MEMORY_SANITIZER)
+// When compiled in sanitizer mode, we add generation integers to the backing
+// array and iterators. In the backing array, we store the generation between
+// the control bytes and the slots. When iterators are dereferenced, we assert
+// that the container has not been mutated in a way that could cause iterator
+// invalidation since the iterator was initialized.
+#define ABSL_SWISSTABLE_ENABLE_GENERATIONS
+#endif
+
+// We use uint8_t so we don't need to worry about padding.
+using GenerationType = uint8_t;
+
+// A sentinel value for empty generations. Using 0 makes it easy to constexpr
+// initialize an array of this value.
+constexpr GenerationType SentinelEmptyGeneration() { return 0; }
+
+constexpr GenerationType NextGeneration(GenerationType generation) {
+ return ++generation == SentinelEmptyGeneration() ? ++generation : generation;
+}
+
+#ifdef ABSL_SWISSTABLE_ENABLE_GENERATIONS
+constexpr bool SwisstableGenerationsEnabled() { return true; }
+constexpr size_t NumGenerationBytes() { return sizeof(GenerationType); }
+#else
+constexpr bool SwisstableGenerationsEnabled() { return false; }
+constexpr size_t NumGenerationBytes() { return 0; }
+#endif
+
template <typename AllocType>
void SwapAlloc(AllocType& lhs, AllocType& rhs,
std::true_type /* propagate_on_container_swap */) {
@@ -129,17 +270,56 @@
swap(lhs, rhs);
}
template <typename AllocType>
-void SwapAlloc(AllocType& /*lhs*/, AllocType& /*rhs*/,
- std::false_type /* propagate_on_container_swap */) {}
+void SwapAlloc(AllocType& lhs, AllocType& rhs,
+ std::false_type /* propagate_on_container_swap */) {
+ (void)lhs;
+ (void)rhs;
+ assert(lhs == rhs &&
+ "It's UB to call swap with unequal non-propagating allocators.");
+}
+template <typename AllocType>
+void CopyAlloc(AllocType& lhs, AllocType& rhs,
+ std::true_type /* propagate_alloc */) {
+ lhs = rhs;
+}
+template <typename AllocType>
+void CopyAlloc(AllocType&, AllocType&, std::false_type /* propagate_alloc */) {}
+
+// The state for a probe sequence.
+//
+// Currently, the sequence is a triangular progression of the form
+//
+// p(i) := Width * (i^2 + i)/2 + hash (mod mask + 1)
+//
+// The use of `Width` ensures that each probe step does not overlap groups;
+// the sequence effectively outputs the addresses of *groups* (although not
+// necessarily aligned to any boundary). The `Group` machinery allows us
+// to check an entire group with minimal branching.
+//
+// Wrapping around at `mask + 1` is important, but not for the obvious reason.
+// As described above, the first few entries of the control byte array
+// are mirrored at the end of the array, which `Group` will find and use
+// for selecting candidates. However, when those candidates' slots are
+// actually inspected, there are no corresponding slots for the cloned bytes,
+// so we need to make sure we've treated those offsets as "wrapping around".
+//
+// It turns out that this probe sequence visits every group exactly once if the
+// number of groups is a power of two, since (i^2+i)/2 is a bijection in
+// Z/(2^m). See https://en.wikipedia.org/wiki/Quadratic_probing
template <size_t Width>
class probe_seq {
public:
+ // Creates a new probe sequence using `hash` as the initial value of the
+ // sequence and `mask` (usually the capacity of the table) as the mask to
+ // apply to each value in the progression.
probe_seq(size_t hash, size_t mask) {
assert(((mask + 1) & mask) == 0 && "not a mask");
mask_ = mask;
offset_ = hash & mask_;
}
+
+ // The offset within the table, i.e., the value `p(i)` above.
size_t offset() const { return offset_; }
size_t offset(size_t i) const { return (offset_ + i) & mask_; }
@@ -148,7 +328,7 @@
offset_ += index_;
offset_ &= mask_;
}
- // 0-based probe index. The i-th probe in the probe sequence.
+ // 0-based probe index, a multiple of `Width`.
size_t index() const { return index_; }
private:
@@ -172,9 +352,9 @@
template <class Policy, class Hash, class Eq, class... Ts>
struct IsDecomposable<
- absl::void_t<decltype(
- Policy::apply(RequireUsableKey<typename Policy::key_type, Hash, Eq>(),
- std::declval<Ts>()...))>,
+ absl::void_t<decltype(Policy::apply(
+ RequireUsableKey<typename Policy::key_type, Hash, Eq>(),
+ std::declval<Ts>()...))>,
Policy, Hash, Eq, Ts...> : std::true_type {};
// TODO(alkis): Switch to std::is_nothrow_swappable when gcc/clang supports it.
@@ -190,57 +370,101 @@
template <typename T>
uint32_t TrailingZeros(T x) {
- ABSL_INTERNAL_ASSUME(x != 0);
- return countr_zero(x);
+ ABSL_ASSUME(x != 0);
+ return static_cast<uint32_t>(countr_zero(x));
}
-// An abstraction over a bitmask. It provides an easy way to iterate through the
-// indexes of the set bits of a bitmask. When Shift=0 (platforms with SSE),
-// this is a true bitmask. On non-SSE, platforms the arithematic used to
-// emulate the SSE behavior works in bytes (Shift=3) and leaves each bytes as
-// either 0x00 or 0x80.
+// 8 bytes bitmask with most significant bit set for every byte.
+constexpr uint64_t kMsbs8Bytes = 0x8080808080808080ULL;
+
+// An abstract bitmask, such as that emitted by a SIMD instruction.
//
-// For example:
-// for (int i : BitMask<uint32_t, 16>(0x5)) -> yields 0, 2
-// for (int i : BitMask<uint64_t, 8, 3>(0x0000000080800000)) -> yields 2, 3
+// Specifically, this type implements a simple bitset whose representation is
+// controlled by `SignificantBits` and `Shift`. `SignificantBits` is the number
+// of abstract bits in the bitset, while `Shift` is the log-base-two of the
+// width of an abstract bit in the representation.
+// This mask provides operations for any number of real bits set in an abstract
+// bit. To add iteration on top of that, implementation must guarantee no more
+// than the most significant real bit is set in a set abstract bit.
template <class T, int SignificantBits, int Shift = 0>
-class BitMask {
- static_assert(std::is_unsigned<T>::value, "");
- static_assert(Shift == 0 || Shift == 3, "");
-
+class NonIterableBitMask {
public:
- // These are useful for unit tests (gunit).
- using value_type = int;
- using iterator = BitMask;
- using const_iterator = BitMask;
+ explicit NonIterableBitMask(T mask) : mask_(mask) {}
- explicit BitMask(T mask) : mask_(mask) {}
- BitMask& operator++() {
- mask_ &= (mask_ - 1);
- return *this;
- }
- explicit operator bool() const { return mask_ != 0; }
- int operator*() const { return LowestBitSet(); }
+ explicit operator bool() const { return this->mask_ != 0; }
+
+ // Returns the index of the lowest *abstract* bit set in `self`.
uint32_t LowestBitSet() const {
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);
}
- BitMask begin() const { return *this; }
- BitMask end() const { return BitMask(0); }
-
+ // Returns the number of trailing zero *abstract* bits.
uint32_t TrailingZeros() const {
return container_internal::TrailingZeros(mask_) >> Shift;
}
+ // Returns the number of leading zero *abstract* bits.
uint32_t LeadingZeros() const {
constexpr int total_significant_bits = SignificantBits << Shift;
constexpr int extra_bits = sizeof(T) * 8 - total_significant_bits;
- return countl_zero(mask_ << extra_bits) >> Shift;
+ return static_cast<uint32_t>(
+ countl_zero(static_cast<T>(mask_ << extra_bits))) >>
+ Shift;
}
+ T mask_;
+};
+
+// Mask that can be iterable
+//
+// For example, when `SignificantBits` is 16 and `Shift` is zero, this is just
+// an ordinary 16-bit bitset occupying the low 16 bits of `mask`. When
+// `SignificantBits` is 8 and `Shift` is 3, abstract bits are represented as
+// the bytes `0x00` and `0x80`, and it occupies all 64 bits of the bitmask.
+// If NullifyBitsOnIteration is true (only allowed for Shift == 3),
+// non zero abstract bit is allowed to have additional bits
+// (e.g., `0xff`, `0x83` and `0x9c` are ok, but `0x6f` is not).
+//
+// For example:
+// for (int i : BitMask<uint32_t, 16>(0b101)) -> yields 0, 2
+// for (int i : BitMask<uint64_t, 8, 3>(0x0000000080800000)) -> yields 2, 3
+template <class T, int SignificantBits, int Shift = 0,
+ bool NullifyBitsOnIteration = false>
+class BitMask : public NonIterableBitMask<T, SignificantBits, Shift> {
+ using Base = NonIterableBitMask<T, SignificantBits, Shift>;
+ static_assert(std::is_unsigned<T>::value, "");
+ static_assert(Shift == 0 || Shift == 3, "");
+ static_assert(!NullifyBitsOnIteration || Shift == 3, "");
+
+ public:
+ explicit BitMask(T mask) : Base(mask) {
+ if (Shift == 3 && !NullifyBitsOnIteration) {
+ assert(this->mask_ == (this->mask_ & kMsbs8Bytes));
+ }
+ }
+ // BitMask is an iterator over the indices of its abstract bits.
+ using value_type = int;
+ using iterator = BitMask;
+ using const_iterator = BitMask;
+
+ BitMask& operator++() {
+ if (Shift == 3 && NullifyBitsOnIteration) {
+ this->mask_ &= kMsbs8Bytes;
+ }
+ this->mask_ &= (this->mask_ - 1);
+ return *this;
+ }
+
+ uint32_t operator*() const { return Base::LowestBitSet(); }
+
+ BitMask begin() const { return *this; }
+ BitMask end() const { return BitMask(0); }
+
private:
friend bool operator==(const BitMask& a, const BitMask& b) {
return a.mask_ == b.mask_;
@@ -248,15 +472,27 @@
friend bool operator!=(const BitMask& a, const BitMask& b) {
return a.mask_ != b.mask_;
}
-
- T mask_;
};
using h2_t = uint8_t;
// The values here are selected for maximum performance. See the static asserts
-// below for details. We use an enum class so that when strict aliasing is
-// enabled, the compiler knows ctrl_t doesn't alias other types.
+// below for details.
+
+// A `ctrl_t` is a single control byte, which can have one of four
+// states: empty, deleted, full (which has an associated seven-bit h2_t value)
+// and the sentinel. They have the following bit patterns:
+//
+// empty: 1 0 0 0 0 0 0 0
+// deleted: 1 1 1 1 1 1 1 0
+// full: 0 h h h h h h h // h represents the hash bits.
+// sentinel: 1 1 1 1 1 1 1 1
+//
+// These values are specifically tuned for SSE-flavored SIMD.
+// The static_asserts below detail the source of these choices.
+//
+// We use an enum class so that when strict aliasing is enabled, the compiler
+// knows ctrl_t doesn't alias other types.
enum class ctrl_t : int8_t {
kEmpty = -128, // 0b10000000
kDeleted = -2, // 0b11111110
@@ -284,48 +520,111 @@
static_cast<int8_t>(ctrl_t::kSentinel) & 0x7F) != 0,
"ctrl_t::kEmpty and ctrl_t::kDeleted must share an unset bit that is not "
"shared by ctrl_t::kSentinel to make the scalar test for "
- "MatchEmptyOrDeleted() efficient");
+ "MaskEmptyOrDeleted() efficient");
static_assert(ctrl_t::kDeleted == static_cast<ctrl_t>(-2),
"ctrl_t::kDeleted must be -2 to make the implementation of "
"ConvertSpecialToEmptyAndFullToDeleted efficient");
-// A single block of empty control bytes for tables without any slots allocated.
-// This enables removing a branch in the hot path of find().
+// See definition comment for why this is size 32.
+ABSL_DLL extern const ctrl_t kEmptyGroup[32];
+
+// Returns a pointer to a control byte group that can be used by empty tables.
inline ctrl_t* EmptyGroup() {
- alignas(16) static constexpr ctrl_t empty_group[] = {
- 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};
- return const_cast<ctrl_t*>(empty_group);
+ // 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 generation to use for an empty hashtable.
+GenerationType* EmptyGeneration();
+
+// Returns whether `generation` is a generation for an empty hashtable that
+// could be returned by EmptyGeneration().
+inline bool IsEmptyGeneration(const GenerationType* generation) {
+ return *generation == SentinelEmptyGeneration();
}
// Mixes a randomly generated per-process seed with `hash` and `ctrl` to
// randomize insertion order within groups.
-bool ShouldInsertBackwards(size_t hash, const ctrl_t* ctrl);
+bool ShouldInsertBackwardsForDebug(size_t capacity, size_t hash,
+ const ctrl_t* ctrl);
-// Returns a hash seed.
+// Returns insert position for the given mask.
+// We want to add entropy even when ASLR is not enabled.
+// In debug build we will randomly insert in either the front or back of
+// the group.
+// TODO(kfm,sbenza): revisit after we do unconditional mixing
+template <class Mask>
+ABSL_ATTRIBUTE_ALWAYS_INLINE inline auto GetInsertionOffset(
+ Mask mask, ABSL_ATTRIBUTE_UNUSED size_t capacity,
+ ABSL_ATTRIBUTE_UNUSED size_t hash,
+ ABSL_ATTRIBUTE_UNUSED const ctrl_t* ctrl) {
+#if defined(NDEBUG)
+ return mask.LowestBitSet();
+#else
+ return ShouldInsertBackwardsForDebug(capacity, hash, ctrl)
+ ? mask.HighestBitSet()
+ : mask.LowestBitSet();
+#endif
+}
+
+// Returns a per-table, hash salt, which changes on resize. This gets mixed into
+// H1 to randomize iteration order per-table.
//
// The seed consists of the ctrl_ pointer, which adds enough entropy to ensure
// non-determinism of iteration order in most cases.
-inline size_t HashSeed(const ctrl_t* ctrl) {
+inline size_t PerTableSalt(const ctrl_t* ctrl) {
// The low bits of the pointer have little or no entropy because of
// alignment. We shift the pointer to try to use higher entropy bits. A
// good number seems to be 12 bits, because that aligns with page size.
return reinterpret_cast<uintptr_t>(ctrl) >> 12;
}
-
+// Extracts the H1 portion of a hash: 57 bits mixed with a per-table salt.
inline size_t H1(size_t hash, const ctrl_t* ctrl) {
- return (hash >> 7) ^ HashSeed(ctrl);
+ return (hash >> 7) ^ PerTableSalt(ctrl);
}
+
+// Extracts the H2 portion of a hash: the 7 bits not used for H1.
+//
+// These are used as an occupied control byte.
inline h2_t H2(size_t hash) { return hash & 0x7F; }
+// Helpers for checking the state of a control byte.
inline bool IsEmpty(ctrl_t c) { return c == ctrl_t::kEmpty; }
-inline bool IsFull(ctrl_t c) { return c >= static_cast<ctrl_t>(0); }
+inline bool IsFull(ctrl_t c) {
+ return static_cast<std::underlying_type_t<ctrl_t>>(c) >= 0;
+}
inline bool IsDeleted(ctrl_t c) { return c == ctrl_t::kDeleted; }
inline bool IsEmptyOrDeleted(ctrl_t c) { return c < ctrl_t::kSentinel; }
-#if ABSL_INTERNAL_RAW_HASH_SET_HAVE_SSE2
+#ifdef ABSL_INTERNAL_HAVE_SSE2
+// Quick reference guide for intrinsics used below:
+//
+// * __m128i: An XMM (128-bit) word.
+//
+// * _mm_setzero_si128: Returns a zero vector.
+// * _mm_set1_epi8: Returns a vector with the same i8 in each lane.
+//
+// * _mm_subs_epi8: Saturating-subtracts two i8 vectors.
+// * _mm_and_si128: Ands two i128s together.
+// * _mm_or_si128: Ors two i128s together.
+// * _mm_andnot_si128: And-nots two i128s together.
+//
+// * _mm_cmpeq_epi8: Component-wise compares two i8 vectors for equality,
+// filling each lane with 0x00 or 0xff.
+// * _mm_cmpgt_epi8: Same as above, but using > rather than ==.
+//
+// * _mm_loadu_si128: Performs an unaligned load of an i128.
+// * _mm_storeu_si128: Performs an unaligned store of an i128.
+//
+// * _mm_sign_epi8: Retains, negates, or zeroes each i8 lane of the first
+// argument if the corresponding lane of the second
+// argument is positive, negative, or zero, respectively.
+// * _mm_movemask_epi8: Selects the sign bit out of each i8 lane and produces a
+// bitmask consisting of those bits.
+// * _mm_shuffle_epi8: Selects i8s from the first argument, using the low
+// four bits of each i8 lane in the second argument as
+// indices.
// https://github.com/abseil/abseil-cpp/issues/209
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=87853
@@ -351,33 +650,53 @@
}
// Returns a bitmask representing the positions of slots that match hash.
- BitMask<uint32_t, kWidth> Match(h2_t hash) const {
- auto match = _mm_set1_epi8(hash);
- return BitMask<uint32_t, kWidth>(
- _mm_movemask_epi8(_mm_cmpeq_epi8(match, ctrl)));
+ BitMask<uint16_t, kWidth> Match(h2_t hash) const {
+ auto match = _mm_set1_epi8(static_cast<char>(hash));
+ BitMask<uint16_t, kWidth> result = BitMask<uint16_t, kWidth>(0);
+ result = BitMask<uint16_t, kWidth>(
+ static_cast<uint16_t>(_mm_movemask_epi8(_mm_cmpeq_epi8(match, ctrl))));
+ return result;
}
// Returns a bitmask representing the positions of empty slots.
- BitMask<uint32_t, kWidth> MatchEmpty() const {
-#if ABSL_INTERNAL_RAW_HASH_SET_HAVE_SSSE3
+ NonIterableBitMask<uint16_t, kWidth> MaskEmpty() const {
+#ifdef ABSL_INTERNAL_HAVE_SSSE3
// This only works because ctrl_t::kEmpty is -128.
- return BitMask<uint32_t, kWidth>(
- _mm_movemask_epi8(_mm_sign_epi8(ctrl, ctrl)));
+ return NonIterableBitMask<uint16_t, kWidth>(
+ static_cast<uint16_t>(_mm_movemask_epi8(_mm_sign_epi8(ctrl, ctrl))));
#else
- return Match(static_cast<h2_t>(ctrl_t::kEmpty));
+ auto match = _mm_set1_epi8(static_cast<char>(ctrl_t::kEmpty));
+ return NonIterableBitMask<uint16_t, kWidth>(
+ static_cast<uint16_t>(_mm_movemask_epi8(_mm_cmpeq_epi8(match, ctrl))));
#endif
}
+ // Returns a bitmask representing the positions of full slots.
+ // Note: for `is_small()` tables group may contain the "same" slot twice:
+ // original and mirrored.
+ BitMask<uint16_t, kWidth> MaskFull() const {
+ return BitMask<uint16_t, kWidth>(
+ static_cast<uint16_t>(_mm_movemask_epi8(ctrl) ^ 0xffff));
+ }
+
+ // Returns a bitmask representing the positions of non full slots.
+ // Note: this includes: kEmpty, kDeleted, kSentinel.
+ // It is useful in contexts when kSentinel is not present.
+ auto MaskNonFull() const {
+ return BitMask<uint16_t, kWidth>(
+ static_cast<uint16_t>(_mm_movemask_epi8(ctrl)));
+ }
+
// Returns a bitmask representing the positions of empty or deleted slots.
- BitMask<uint32_t, kWidth> MatchEmptyOrDeleted() const {
- auto special = _mm_set1_epi8(static_cast<int8_t>(ctrl_t::kSentinel));
- return BitMask<uint32_t, kWidth>(
- _mm_movemask_epi8(_mm_cmpgt_epi8_fixed(special, ctrl)));
+ NonIterableBitMask<uint16_t, kWidth> MaskEmptyOrDeleted() const {
+ auto special = _mm_set1_epi8(static_cast<char>(ctrl_t::kSentinel));
+ return NonIterableBitMask<uint16_t, kWidth>(static_cast<uint16_t>(
+ _mm_movemask_epi8(_mm_cmpgt_epi8_fixed(special, ctrl))));
}
// Returns the number of trailing empty or deleted elements in the group.
uint32_t CountLeadingEmptyOrDeleted() const {
- auto special = _mm_set1_epi8(static_cast<int8_t>(ctrl_t::kSentinel));
+ auto special = _mm_set1_epi8(static_cast<char>(ctrl_t::kSentinel));
return TrailingZeros(static_cast<uint32_t>(
_mm_movemask_epi8(_mm_cmpgt_epi8_fixed(special, ctrl)) + 1));
}
@@ -385,7 +704,7 @@
void ConvertSpecialToEmptyAndFullToDeleted(ctrl_t* dst) const {
auto msbs = _mm_set1_epi8(static_cast<char>(-128));
auto x126 = _mm_set1_epi8(126);
-#if ABSL_INTERNAL_RAW_HASH_SET_HAVE_SSSE3
+#ifdef ABSL_INTERNAL_HAVE_SSSE3
auto res = _mm_or_si128(_mm_shuffle_epi8(x126, ctrl), msbs);
#else
auto zero = _mm_setzero_si128();
@@ -399,6 +718,90 @@
};
#endif // ABSL_INTERNAL_RAW_HASH_SET_HAVE_SSE2
+#if defined(ABSL_INTERNAL_HAVE_ARM_NEON) && defined(ABSL_IS_LITTLE_ENDIAN)
+struct GroupAArch64Impl {
+ static constexpr size_t kWidth = 8;
+
+ explicit GroupAArch64Impl(const ctrl_t* pos) {
+ ctrl = vld1_u8(reinterpret_cast<const uint8_t*>(pos));
+ }
+
+ auto Match(h2_t hash) const {
+ uint8x8_t dup = vdup_n_u8(hash);
+ auto mask = vceq_u8(ctrl, dup);
+ return BitMask<uint64_t, kWidth, /*Shift=*/3,
+ /*NullifyBitsOnIteration=*/true>(
+ vget_lane_u64(vreinterpret_u64_u8(mask), 0));
+ }
+
+ NonIterableBitMask<uint64_t, kWidth, 3> MaskEmpty() const {
+ uint64_t mask =
+ vget_lane_u64(vreinterpret_u64_u8(vceq_s8(
+ vdup_n_s8(static_cast<int8_t>(ctrl_t::kEmpty)),
+ vreinterpret_s8_u8(ctrl))),
+ 0);
+ return NonIterableBitMask<uint64_t, kWidth, 3>(mask);
+ }
+
+ // Returns a bitmask representing the positions of full slots.
+ // Note: for `is_small()` tables group may contain the "same" slot twice:
+ // original and mirrored.
+ auto MaskFull() const {
+ uint64_t mask = vget_lane_u64(
+ vreinterpret_u64_u8(vcge_s8(vreinterpret_s8_u8(ctrl),
+ vdup_n_s8(static_cast<int8_t>(0)))),
+ 0);
+ return BitMask<uint64_t, kWidth, /*Shift=*/3,
+ /*NullifyBitsOnIteration=*/true>(mask);
+ }
+
+ // Returns a bitmask representing the positions of non full slots.
+ // Note: this includes: kEmpty, kDeleted, kSentinel.
+ // It is useful in contexts when kSentinel is not present.
+ auto MaskNonFull() const {
+ uint64_t mask = vget_lane_u64(
+ vreinterpret_u64_u8(vclt_s8(vreinterpret_s8_u8(ctrl),
+ vdup_n_s8(static_cast<int8_t>(0)))),
+ 0);
+ return BitMask<uint64_t, kWidth, /*Shift=*/3,
+ /*NullifyBitsOnIteration=*/true>(mask);
+ }
+
+ NonIterableBitMask<uint64_t, kWidth, 3> MaskEmptyOrDeleted() const {
+ uint64_t mask =
+ vget_lane_u64(vreinterpret_u64_u8(vcgt_s8(
+ vdup_n_s8(static_cast<int8_t>(ctrl_t::kSentinel)),
+ vreinterpret_s8_u8(ctrl))),
+ 0);
+ return NonIterableBitMask<uint64_t, kWidth, 3>(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;
+ }
+
+ void ConvertSpecialToEmptyAndFullToDeleted(ctrl_t* dst) const {
+ uint64_t mask = vget_lane_u64(vreinterpret_u64_u8(ctrl), 0);
+ constexpr uint64_t slsbs = 0x0202020202020202ULL;
+ constexpr uint64_t midbs = 0x7e7e7e7e7e7e7e7eULL;
+ auto x = slsbs & (mask >> 6);
+ auto res = (x + midbs) | kMsbs8Bytes;
+ little_endian::Store64(dst, res);
+ }
+
+ uint8x8_t ctrl;
+};
+#endif // ABSL_INTERNAL_HAVE_ARM_NEON && ABSL_IS_LITTLE_ENDIAN
+
struct GroupPortableImpl {
static constexpr size_t kWidth = 8;
@@ -419,31 +822,46 @@
// v = 0x1716151413121110
// hash = 0x12
// retval = (v - lsbs) & ~v & msbs = 0x0000000080800000
- constexpr uint64_t msbs = 0x8080808080808080ULL;
constexpr uint64_t lsbs = 0x0101010101010101ULL;
auto x = ctrl ^ (lsbs * hash);
- return BitMask<uint64_t, kWidth, 3>((x - lsbs) & ~x & msbs);
+ return BitMask<uint64_t, kWidth, 3>((x - lsbs) & ~x & kMsbs8Bytes);
}
- BitMask<uint64_t, kWidth, 3> MatchEmpty() const {
- constexpr uint64_t msbs = 0x8080808080808080ULL;
- return BitMask<uint64_t, kWidth, 3>((ctrl & (~ctrl << 6)) & msbs);
+ NonIterableBitMask<uint64_t, kWidth, 3> MaskEmpty() const {
+ return NonIterableBitMask<uint64_t, kWidth, 3>((ctrl & ~(ctrl << 6)) &
+ kMsbs8Bytes);
}
- BitMask<uint64_t, kWidth, 3> MatchEmptyOrDeleted() const {
- constexpr uint64_t msbs = 0x8080808080808080ULL;
- return BitMask<uint64_t, kWidth, 3>((ctrl & (~ctrl << 7)) & msbs);
+ // Returns a bitmask representing the positions of full slots.
+ // Note: for `is_small()` tables group may contain the "same" slot twice:
+ // original and mirrored.
+ BitMask<uint64_t, kWidth, 3> MaskFull() const {
+ return BitMask<uint64_t, kWidth, 3>((ctrl ^ kMsbs8Bytes) & kMsbs8Bytes);
+ }
+
+ // Returns a bitmask representing the positions of non full slots.
+ // Note: this includes: kEmpty, kDeleted, kSentinel.
+ // It is useful in contexts when kSentinel is not present.
+ auto MaskNonFull() const {
+ return BitMask<uint64_t, kWidth, 3>(ctrl & kMsbs8Bytes);
+ }
+
+ NonIterableBitMask<uint64_t, kWidth, 3> MaskEmptyOrDeleted() const {
+ return NonIterableBitMask<uint64_t, kWidth, 3>((ctrl & ~(ctrl << 7)) &
+ kMsbs8Bytes);
}
uint32_t CountLeadingEmptyOrDeleted() const {
- constexpr uint64_t gaps = 0x00FEFEFEFEFEFEFEULL;
- return (TrailingZeros(((~ctrl & (ctrl >> 7)) | gaps) + 1) + 7) >> 3;
+ // 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);
}
void ConvertSpecialToEmptyAndFullToDeleted(ctrl_t* dst) const {
- constexpr uint64_t msbs = 0x8080808080808080ULL;
constexpr uint64_t lsbs = 0x0101010101010101ULL;
- auto x = ctrl & msbs;
+ auto x = ctrl & kMsbs8Bytes;
auto res = (~x + (x >> 7)) & ~lsbs;
little_endian::Store64(dst, res);
}
@@ -451,32 +869,396 @@
uint64_t ctrl;
};
-#if ABSL_INTERNAL_RAW_HASH_SET_HAVE_SSE2
+#ifdef ABSL_INTERNAL_HAVE_SSE2
using Group = GroupSse2Impl;
+using GroupFullEmptyOrDeleted = GroupSse2Impl;
+#elif defined(ABSL_INTERNAL_HAVE_ARM_NEON) && defined(ABSL_IS_LITTLE_ENDIAN)
+using Group = GroupAArch64Impl;
+// For Aarch64, we use the portable implementation for counting and masking
+// full, empty or deleted group elements. This is to avoid the latency of moving
+// between data GPRs and Neon registers when it does not provide a benefit.
+// Using Neon is profitable when we call Match(), but is not when we don't,
+// which is the case when we do *EmptyOrDeleted and MaskFull operations.
+// It is difficult to make a similar approach beneficial on other architectures
+// such as x86 since they have much lower GPR <-> vector register transfer
+// latency and 16-wide Groups.
+using GroupFullEmptyOrDeleted = GroupPortableImpl;
#else
using Group = GroupPortableImpl;
+using GroupFullEmptyOrDeleted = GroupPortableImpl;
#endif
-// The number of cloned control bytes that we copy from the beginning to the
-// end of the control bytes array.
+// When there is an insertion with no reserved growth, we rehash with
+// probability `min(1, RehashProbabilityConstant() / capacity())`. Using a
+// constant divided by capacity ensures that inserting N elements is still O(N)
+// in the average case. Using the constant 16 means that we expect to rehash ~8
+// times more often than when generations are disabled. We are adding expected
+// rehash_probability * #insertions/capacity_growth = 16/capacity * ((7/8 -
+// 7/16) * capacity)/capacity_growth = ~7 extra rehashes per capacity growth.
+inline size_t RehashProbabilityConstant() { return 16; }
+
+class CommonFieldsGenerationInfoEnabled {
+ // A sentinel value for reserved_growth_ indicating that we just ran out of
+ // reserved growth on the last insertion. When reserve is called and then
+ // insertions take place, reserved_growth_'s state machine is N, ..., 1,
+ // kReservedGrowthJustRanOut, 0.
+ static constexpr size_t kReservedGrowthJustRanOut =
+ (std::numeric_limits<size_t>::max)();
+
+ public:
+ CommonFieldsGenerationInfoEnabled() = default;
+ CommonFieldsGenerationInfoEnabled(CommonFieldsGenerationInfoEnabled&& that)
+ : reserved_growth_(that.reserved_growth_),
+ reservation_size_(that.reservation_size_),
+ generation_(that.generation_) {
+ that.reserved_growth_ = 0;
+ that.reservation_size_ = 0;
+ that.generation_ = EmptyGeneration();
+ }
+ CommonFieldsGenerationInfoEnabled& operator=(
+ CommonFieldsGenerationInfoEnabled&&) = default;
+
+ // Whether we should rehash on insert in order to detect bugs of using invalid
+ // references. We rehash on the first insertion after reserved_growth_ reaches
+ // 0 after a call to reserve. We also do a rehash with low probability
+ // whenever reserved_growth_ is zero.
+ bool should_rehash_for_bug_detection_on_insert(const ctrl_t* ctrl,
+ size_t capacity) const;
+ // Similar to above, except that we don't depend on reserved_growth_.
+ bool should_rehash_for_bug_detection_on_move(const ctrl_t* ctrl,
+ size_t capacity) const;
+ void maybe_increment_generation_on_insert() {
+ if (reserved_growth_ == kReservedGrowthJustRanOut) reserved_growth_ = 0;
+
+ if (reserved_growth_ > 0) {
+ if (--reserved_growth_ == 0) reserved_growth_ = kReservedGrowthJustRanOut;
+ } else {
+ increment_generation();
+ }
+ }
+ void increment_generation() { *generation_ = NextGeneration(*generation_); }
+ void reset_reserved_growth(size_t reservation, size_t size) {
+ reserved_growth_ = reservation - size;
+ }
+ size_t reserved_growth() const { return reserved_growth_; }
+ void set_reserved_growth(size_t r) { reserved_growth_ = r; }
+ size_t reservation_size() const { return reservation_size_; }
+ void set_reservation_size(size_t r) { reservation_size_ = r; }
+ GenerationType generation() const { return *generation_; }
+ void set_generation(GenerationType g) { *generation_ = g; }
+ GenerationType* generation_ptr() const { return generation_; }
+ void set_generation_ptr(GenerationType* g) { generation_ = g; }
+
+ private:
+ // The number of insertions remaining that are guaranteed to not rehash due to
+ // a prior call to reserve. Note: we store reserved growth in addition to
+ // reservation size because calls to erase() decrease size_ but don't decrease
+ // reserved growth.
+ size_t reserved_growth_ = 0;
+ // The maximum argument to reserve() since the container was cleared. We need
+ // to keep track of this, in addition to reserved growth, because we reset
+ // reserved growth to this when erase(begin(), end()) is called.
+ size_t reservation_size_ = 0;
+ // Pointer to the generation counter, which is used to validate iterators and
+ // is stored in the backing array between the control bytes and the slots.
+ // Note that we can't store the generation inside the container itself and
+ // keep a pointer to the container in the iterators because iterators must
+ // remain valid when the container is moved.
+ // Note: we could derive this pointer from the control pointer, but it makes
+ // the code more complicated, and there's a benefit in having the sizes of
+ // raw_hash_set in sanitizer mode and non-sanitizer mode a bit more different,
+ // which is that tests are less likely to rely on the size remaining the same.
+ GenerationType* generation_ = EmptyGeneration();
+};
+
+class CommonFieldsGenerationInfoDisabled {
+ public:
+ CommonFieldsGenerationInfoDisabled() = default;
+ CommonFieldsGenerationInfoDisabled(CommonFieldsGenerationInfoDisabled&&) =
+ default;
+ CommonFieldsGenerationInfoDisabled& operator=(
+ CommonFieldsGenerationInfoDisabled&&) = default;
+
+ bool should_rehash_for_bug_detection_on_insert(const ctrl_t*, size_t) const {
+ return false;
+ }
+ bool should_rehash_for_bug_detection_on_move(const ctrl_t*, size_t) const {
+ return false;
+ }
+ void maybe_increment_generation_on_insert() {}
+ void increment_generation() {}
+ void reset_reserved_growth(size_t, size_t) {}
+ size_t reserved_growth() const { return 0; }
+ void set_reserved_growth(size_t) {}
+ size_t reservation_size() const { return 0; }
+ void set_reservation_size(size_t) {}
+ GenerationType generation() const { return 0; }
+ void set_generation(GenerationType) {}
+ GenerationType* generation_ptr() const { return nullptr; }
+ void set_generation_ptr(GenerationType*) {}
+};
+
+class HashSetIteratorGenerationInfoEnabled {
+ public:
+ HashSetIteratorGenerationInfoEnabled() = default;
+ explicit HashSetIteratorGenerationInfoEnabled(
+ const GenerationType* generation_ptr)
+ : generation_ptr_(generation_ptr), generation_(*generation_ptr) {}
+
+ GenerationType generation() const { return generation_; }
+ void reset_generation() { generation_ = *generation_ptr_; }
+ const GenerationType* generation_ptr() const { return generation_ptr_; }
+ void set_generation_ptr(const GenerationType* ptr) { generation_ptr_ = ptr; }
+
+ private:
+ const GenerationType* generation_ptr_ = EmptyGeneration();
+ GenerationType generation_ = *generation_ptr_;
+};
+
+class HashSetIteratorGenerationInfoDisabled {
+ public:
+ HashSetIteratorGenerationInfoDisabled() = default;
+ explicit HashSetIteratorGenerationInfoDisabled(const GenerationType*) {}
+
+ GenerationType generation() const { return 0; }
+ void reset_generation() {}
+ const GenerationType* generation_ptr() const { return nullptr; }
+ void set_generation_ptr(const GenerationType*) {}
+};
+
+#ifdef ABSL_SWISSTABLE_ENABLE_GENERATIONS
+using CommonFieldsGenerationInfo = CommonFieldsGenerationInfoEnabled;
+using HashSetIteratorGenerationInfo = HashSetIteratorGenerationInfoEnabled;
+#else
+using CommonFieldsGenerationInfo = CommonFieldsGenerationInfoDisabled;
+using HashSetIteratorGenerationInfo = HashSetIteratorGenerationInfoDisabled;
+#endif
+
+// Returns whether `n` is a valid capacity (i.e., number of slots).
+//
+// A valid capacity is a non-zero integer `2^m - 1`.
+inline bool IsValidCapacity(size_t n) { return ((n + 1) & n) == 0 && n > 0; }
+
+// Returns the number of "cloned control bytes".
+//
+// This is the number of control bytes that are present both at the beginning
+// of the control byte array and at the end, such that we can create a
+// `Group::kWidth`-width probe window starting from any control byte.
constexpr size_t NumClonedBytes() { return Group::kWidth - 1; }
+// Returns the number of control bytes including cloned.
+inline size_t NumControlBytes(size_t capacity) {
+ return capacity + 1 + NumClonedBytes();
+}
+
+// Computes the offset from the start of the backing allocation of control.
+// infoz and growth_left are stored at the beginning of the backing array.
+inline static size_t ControlOffset(bool has_infoz) {
+ return (has_infoz ? sizeof(HashtablezInfoHandle) : 0) + sizeof(size_t);
+}
+
+// Helper class for computing offsets and allocation size of hash set fields.
+class RawHashSetLayout {
+ public:
+ explicit RawHashSetLayout(size_t capacity, size_t slot_align, bool has_infoz)
+ : capacity_(capacity),
+ control_offset_(ControlOffset(has_infoz)),
+ generation_offset_(control_offset_ + NumControlBytes(capacity)),
+ slot_offset_(
+ (generation_offset_ + NumGenerationBytes() + slot_align - 1) &
+ (~slot_align + 1)) {
+ assert(IsValidCapacity(capacity));
+ }
+
+ // Returns the capacity of a table.
+ size_t capacity() const { return capacity_; }
+
+ // Returns precomputed offset from the start of the backing allocation of
+ // control.
+ size_t control_offset() const { return control_offset_; }
+
+ // Given the capacity of a table, computes the offset (from the start of the
+ // backing allocation) of the generation counter (if it exists).
+ size_t generation_offset() const { return generation_offset_; }
+
+ // Given the capacity of a table, computes the offset (from the start of the
+ // backing allocation) at which the slots begin.
+ size_t slot_offset() const { return slot_offset_; }
+
+ // Given the capacity of a table, computes the total size of the backing
+ // array.
+ size_t alloc_size(size_t slot_size) const {
+ return slot_offset_ + capacity_ * slot_size;
+ }
+
+ private:
+ size_t capacity_;
+ size_t control_offset_;
+ size_t generation_offset_;
+ size_t slot_offset_;
+};
+
+// CommonFields hold the fields in raw_hash_set that do not depend
+// on template parameters. This allows us to conveniently pass all
+// of this state to helper functions as a single argument.
+class CommonFields : public CommonFieldsGenerationInfo {
+ public:
+ CommonFields() = default;
+
+ // Not copyable
+ CommonFields(const CommonFields&) = delete;
+ CommonFields& operator=(const CommonFields&) = delete;
+
+ // Movable
+ CommonFields(CommonFields&& that) = default;
+ CommonFields& operator=(CommonFields&&) = default;
+
+ ctrl_t* control() const { return control_; }
+ void set_control(ctrl_t* c) { control_ = c; }
+ void* backing_array_start() const {
+ // growth_left (and maybe infoz) is stored before control bytes.
+ assert(reinterpret_cast<uintptr_t>(control()) % alignof(size_t) == 0);
+ return control() - ControlOffset(has_infoz());
+ }
+
+ // Note: we can't use slots() because Qt defines "slots" as a macro.
+ void* slot_array() const { return slots_; }
+ void set_slots(void* s) { slots_ = s; }
+
+ // The number of filled slots.
+ size_t size() const { return size_ >> HasInfozShift(); }
+ void set_size(size_t s) {
+ size_ = (s << HasInfozShift()) | (size_ & HasInfozMask());
+ }
+ void increment_size() {
+ assert(size() < capacity());
+ size_ += size_t{1} << HasInfozShift();
+ }
+ void decrement_size() {
+ assert(size() > 0);
+ size_ -= size_t{1} << HasInfozShift();
+ }
+
+ // The total number of available slots.
+ size_t capacity() const { return capacity_; }
+ void set_capacity(size_t c) {
+ assert(c == 0 || IsValidCapacity(c));
+ capacity_ = c;
+ }
+
+ // The number of slots we can still fill without needing to rehash.
+ // This is stored in the heap allocation before the control bytes.
+ size_t growth_left() const {
+ const size_t* gl_ptr = reinterpret_cast<size_t*>(control()) - 1;
+ assert(reinterpret_cast<uintptr_t>(gl_ptr) % alignof(size_t) == 0);
+ return *gl_ptr;
+ }
+ void set_growth_left(size_t gl) {
+ size_t* gl_ptr = reinterpret_cast<size_t*>(control()) - 1;
+ assert(reinterpret_cast<uintptr_t>(gl_ptr) % alignof(size_t) == 0);
+ *gl_ptr = gl;
+ }
+
+ bool has_infoz() const {
+ return ABSL_PREDICT_FALSE((size_ & HasInfozMask()) != 0);
+ }
+ void set_has_infoz(bool has_infoz) {
+ size_ = (size() << HasInfozShift()) | static_cast<size_t>(has_infoz);
+ }
+
+ HashtablezInfoHandle infoz() {
+ return has_infoz()
+ ? *reinterpret_cast<HashtablezInfoHandle*>(backing_array_start())
+ : HashtablezInfoHandle();
+ }
+ void set_infoz(HashtablezInfoHandle infoz) {
+ assert(has_infoz());
+ *reinterpret_cast<HashtablezInfoHandle*>(backing_array_start()) = infoz;
+ }
+
+ bool should_rehash_for_bug_detection_on_insert() const {
+ return CommonFieldsGenerationInfo::
+ should_rehash_for_bug_detection_on_insert(control(), capacity());
+ }
+ bool should_rehash_for_bug_detection_on_move() const {
+ return CommonFieldsGenerationInfo::
+ should_rehash_for_bug_detection_on_move(control(), capacity());
+ }
+ void maybe_increment_generation_on_move() {
+ if (capacity() == 0) return;
+ increment_generation();
+ }
+ void reset_reserved_growth(size_t reservation) {
+ CommonFieldsGenerationInfo::reset_reserved_growth(reservation, size());
+ }
+
+ // The size of the backing array allocation.
+ size_t alloc_size(size_t slot_size, size_t slot_align) const {
+ return RawHashSetLayout(capacity(), slot_align, has_infoz())
+ .alloc_size(slot_size);
+ }
+
+ // Returns the number of control bytes set to kDeleted. For testing only.
+ size_t TombstonesCount() const {
+ return static_cast<size_t>(
+ std::count(control(), control() + capacity(), ctrl_t::kDeleted));
+ }
+
+ private:
+ // We store the has_infoz bit in the lowest bit of size_.
+ static constexpr size_t HasInfozShift() { return 1; }
+ static constexpr size_t HasInfozMask() {
+ return (size_t{1} << HasInfozShift()) - 1;
+ }
+
+ // TODO(b/182800944): Investigate removing some of these fields:
+ // - control/slots can be derived from each other
+
+ // The control bytes (and, also, a pointer near to the base of the backing
+ // array).
+ //
+ // This contains `capacity + 1 + NumClonedBytes()` entries, even
+ // when the table is empty (hence EmptyGroup).
+ //
+ // Note that growth_left is stored immediately before this pointer.
+ ctrl_t* control_ = EmptyGroup();
+
+ // The beginning of the slots, located at `SlotOffset()` bytes after
+ // `control`. May be null for empty tables.
+ void* slots_ = nullptr;
+
+ // The number of slots in the backing array. This is always 2^N-1 for an
+ // integer N. NOTE: we tried experimenting with compressing the capacity and
+ // storing it together with size_: (a) using 6 bits to store the corresponding
+ // power (N in 2^N-1), and (b) storing 2^N as the most significant bit of
+ // size_ and storing size in the low bits. Both of these experiments were
+ // regressions, presumably because we need capacity to do find operations.
+ size_t capacity_ = 0;
+
+ // The size and also has one bit that stores whether we have infoz.
+ size_t size_ = 0;
+};
+
template <class Policy, class Hash, class Eq, class Alloc>
class raw_hash_set;
-inline bool IsValidCapacity(size_t n) { return ((n + 1) & n) == 0 && n > 0; }
+// Returns the next valid capacity after `n`.
+inline size_t NextCapacity(size_t n) {
+ assert(IsValidCapacity(n) || n == 0);
+ return n * 2 + 1;
+}
+// Applies the following mapping to every byte in the control array:
+// * kDeleted -> kEmpty
+// * kEmpty -> kEmpty
+// * _ -> kDeleted
// PRECONDITION:
// IsValidCapacity(capacity)
// ctrl[capacity] == ctrl_t::kSentinel
// ctrl[i] != ctrl_t::kSentinel for all i < capacity
-// Applies mapping for every byte in ctrl:
-// DELETED -> EMPTY
-// EMPTY -> EMPTY
-// FULL -> DELETED
void ConvertDeletedToEmptyAndFullToDeleted(ctrl_t* ctrl, size_t capacity);
-// Rounds up the capacity to the next power of 2 minus 1, with a minimum of 1.
+// Converts `n` into the next valid capacity, per `IsValidCapacity`.
inline size_t NormalizeCapacity(size_t n) {
return n ? ~size_t{} >> countl_zero(n) : 1;
}
@@ -489,8 +1271,8 @@
// never need to probe (the whole table fits in one group) so we don't need a
// load factor less than 1.
-// Given `capacity` of the table, returns the size (i.e. number of full slots)
-// at which we should grow the capacity.
+// Given `capacity`, applies the load factor; i.e., it returns the maximum
+// number of values we should put into the table before a resizing rehash.
inline size_t CapacityToGrowth(size_t capacity) {
assert(IsValidCapacity(capacity));
// `capacity*7/8`
@@ -500,8 +1282,12 @@
}
return capacity - capacity / 8;
}
-// From desired "growth" to a lowerbound of the necessary capacity.
-// Might not be a valid one and requires NormalizeCapacity().
+
+// Given `growth`, "unapplies" the load factor to find how large the capacity
+// should be to stay within the load factor.
+//
+// This might not be a valid capacity and `NormalizeCapacity()` should be
+// called on this.
inline size_t GrowthToLowerboundCapacity(size_t growth) {
// `growth*8/7`
if (Group::kWidth == 8 && growth == 7) {
@@ -527,16 +1313,157 @@
return 0;
}
-inline void AssertIsFull(ctrl_t* ctrl) {
- ABSL_HARDENING_ASSERT((ctrl != nullptr && IsFull(*ctrl)) &&
- "Invalid operation on iterator. The element might have "
- "been erased, or the table might have rehashed.");
+constexpr bool SwisstableDebugEnabled() {
+#if defined(ABSL_SWISSTABLE_ENABLE_GENERATIONS) || \
+ ABSL_OPTION_HARDENED == 1 || !defined(NDEBUG)
+ return true;
+#else
+ return false;
+#endif
}
-inline void AssertIsValid(ctrl_t* ctrl) {
- ABSL_HARDENING_ASSERT((ctrl == nullptr || IsFull(*ctrl)) &&
- "Invalid operation on iterator. The element might have "
- "been erased, or the table might have rehashed.");
+inline void AssertIsFull(const ctrl_t* ctrl, GenerationType generation,
+ const GenerationType* generation_ptr,
+ const char* operation) {
+ if (!SwisstableDebugEnabled()) return;
+ // `SwisstableDebugEnabled()` is also true for release builds with hardening
+ // enabled. To minimize their impact in those builds:
+ // - use `ABSL_PREDICT_FALSE()` to provide a compiler hint for code layout
+ // - use `ABSL_RAW_LOG()` with a format string to reduce code size and improve
+ // the chances that the hot paths will be inlined.
+ if (ABSL_PREDICT_FALSE(ctrl == nullptr)) {
+ ABSL_RAW_LOG(FATAL, "%s called on end() iterator.", operation);
+ }
+ if (ABSL_PREDICT_FALSE(ctrl == EmptyGroup())) {
+ ABSL_RAW_LOG(FATAL, "%s called on default-constructed iterator.",
+ operation);
+ }
+ if (SwisstableGenerationsEnabled()) {
+ if (ABSL_PREDICT_FALSE(generation != *generation_ptr)) {
+ ABSL_RAW_LOG(FATAL,
+ "%s called on invalid iterator. The table could have "
+ "rehashed or moved since this iterator was initialized.",
+ operation);
+ }
+ if (ABSL_PREDICT_FALSE(!IsFull(*ctrl))) {
+ ABSL_RAW_LOG(
+ FATAL,
+ "%s called on invalid iterator. The element was likely erased.",
+ operation);
+ }
+ } else {
+ if (ABSL_PREDICT_FALSE(!IsFull(*ctrl))) {
+ ABSL_RAW_LOG(
+ FATAL,
+ "%s called on invalid iterator. The element might have been erased "
+ "or the table might have rehashed. Consider running with "
+ "--config=asan to diagnose rehashing issues.",
+ operation);
+ }
+ }
+}
+
+// Note that for comparisons, null/end iterators are valid.
+inline void AssertIsValidForComparison(const ctrl_t* ctrl,
+ GenerationType generation,
+ const GenerationType* generation_ptr) {
+ if (!SwisstableDebugEnabled()) return;
+ const bool ctrl_is_valid_for_comparison =
+ ctrl == nullptr || ctrl == EmptyGroup() || IsFull(*ctrl);
+ if (SwisstableGenerationsEnabled()) {
+ if (ABSL_PREDICT_FALSE(generation != *generation_ptr)) {
+ ABSL_RAW_LOG(FATAL,
+ "Invalid iterator comparison. The table could have rehashed "
+ "or moved since this iterator was initialized.");
+ }
+ if (ABSL_PREDICT_FALSE(!ctrl_is_valid_for_comparison)) {
+ ABSL_RAW_LOG(
+ FATAL, "Invalid iterator comparison. The element was likely erased.");
+ }
+ } else {
+ ABSL_HARDENING_ASSERT(
+ ctrl_is_valid_for_comparison &&
+ "Invalid iterator comparison. The element might have been erased or "
+ "the table might have rehashed. Consider running with --config=asan to "
+ "diagnose rehashing issues.");
+ }
+}
+
+// If the two iterators come from the same container, then their pointers will
+// interleave such that ctrl_a <= ctrl_b < slot_a <= slot_b or vice/versa.
+// Note: we take slots by reference so that it's not UB if they're uninitialized
+// as long as we don't read them (when ctrl is null).
+inline bool AreItersFromSameContainer(const ctrl_t* ctrl_a,
+ const ctrl_t* ctrl_b,
+ const void* const& slot_a,
+ const void* const& slot_b) {
+ // If either control byte is null, then we can't tell.
+ if (ctrl_a == nullptr || ctrl_b == nullptr) return true;
+ const void* low_slot = slot_a;
+ const void* hi_slot = slot_b;
+ if (ctrl_a > ctrl_b) {
+ std::swap(ctrl_a, ctrl_b);
+ std::swap(low_slot, hi_slot);
+ }
+ return ctrl_b < low_slot && low_slot <= hi_slot;
+}
+
+// Asserts that two iterators come from the same container.
+// Note: we take slots by reference so that it's not UB if they're uninitialized
+// as long as we don't read them (when ctrl is null).
+inline void AssertSameContainer(const ctrl_t* ctrl_a, const ctrl_t* ctrl_b,
+ const void* const& slot_a,
+ const void* const& slot_b,
+ const GenerationType* generation_ptr_a,
+ const GenerationType* generation_ptr_b) {
+ if (!SwisstableDebugEnabled()) return;
+ // `SwisstableDebugEnabled()` is also true for release builds with hardening
+ // enabled. To minimize their impact in those builds:
+ // - use `ABSL_PREDICT_FALSE()` to provide a compiler hint for code layout
+ // - use `ABSL_RAW_LOG()` with a format string to reduce code size and improve
+ // the chances that the hot paths will be inlined.
+ const bool a_is_default = ctrl_a == EmptyGroup();
+ const bool b_is_default = ctrl_b == EmptyGroup();
+ if (ABSL_PREDICT_FALSE(a_is_default != b_is_default)) {
+ ABSL_RAW_LOG(
+ FATAL,
+ "Invalid iterator comparison. Comparing default-constructed iterator "
+ "with non-default-constructed iterator.");
+ }
+ if (a_is_default && b_is_default) return;
+
+ if (SwisstableGenerationsEnabled()) {
+ if (ABSL_PREDICT_TRUE(generation_ptr_a == generation_ptr_b)) return;
+ const bool a_is_empty = IsEmptyGeneration(generation_ptr_a);
+ const bool b_is_empty = IsEmptyGeneration(generation_ptr_b);
+ if (a_is_empty != b_is_empty) {
+ ABSL_RAW_LOG(FATAL,
+ "Invalid iterator comparison. Comparing iterator from a "
+ "non-empty hashtable with an iterator from an empty "
+ "hashtable.");
+ }
+ if (a_is_empty && b_is_empty) {
+ ABSL_RAW_LOG(FATAL,
+ "Invalid iterator comparison. Comparing iterators from "
+ "different empty hashtables.");
+ }
+ const bool a_is_end = ctrl_a == nullptr;
+ const bool b_is_end = ctrl_b == nullptr;
+ if (a_is_end || b_is_end) {
+ ABSL_RAW_LOG(FATAL,
+ "Invalid iterator comparison. Comparing iterator with an "
+ "end() iterator from a different hashtable.");
+ }
+ ABSL_RAW_LOG(FATAL,
+ "Invalid iterator comparison. Comparing non-end() iterators "
+ "from different hashtables.");
+ } else {
+ ABSL_HARDENING_ASSERT(
+ AreItersFromSameContainer(ctrl_a, ctrl_b, slot_a, slot_b) &&
+ "Invalid iterator comparison. The iterators may be from different "
+ "containers or the container might have rehashed or moved. Consider "
+ "running with --config=asan to diagnose issues.");
+ }
}
struct FindInfo {
@@ -544,91 +1471,471 @@
size_t probe_length;
};
-// The representation of the object has two modes:
-// - small: For capacities < kWidth-1
-// - large: For the rest.
+// Whether a table is "small". A small table fits entirely into a probing
+// group, i.e., has a capacity < `Group::kWidth`.
//
-// Differences:
-// - In small 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 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 are valid. The rest contain dummy ctrl_t::kEmpty values that do not
-// represent a real slot. This is important to take into account on
-// find_first_non_full(), where we never try ShouldInsertBackwards() for
-// small tables.
+// In small 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. This is important to take into account on
+// `find_first_non_full()`, where we never try
+// `ShouldInsertBackwards()` for small tables.
inline bool is_small(size_t capacity) { return capacity < Group::kWidth - 1; }
-inline probe_seq<Group::kWidth> probe(const ctrl_t* ctrl, size_t hash,
- size_t capacity) {
- return probe_seq<Group::kWidth>(H1(hash, ctrl), capacity);
+// Whether a table fits entirely into a probing group.
+// Arbitrary order of elements in such tables is correct.
+inline bool is_single_group(size_t capacity) {
+ return capacity <= Group::kWidth;
}
-// Probes the raw_hash_set with the probe sequence for hash and returns the
-// pointer to the first empty or deleted slot.
-// NOTE: this function must work with tables having both ctrl_t::kEmpty and
-// ctrl_t::kDeleted in one group. Such tables appears during
-// drop_deletes_without_resize.
+// Begins a probing operation on `common.control`, using `hash`.
+inline probe_seq<Group::kWidth> probe(const ctrl_t* ctrl, const size_t capacity,
+ size_t hash) {
+ return probe_seq<Group::kWidth>(H1(hash, ctrl), capacity);
+}
+inline probe_seq<Group::kWidth> probe(const CommonFields& common, size_t hash) {
+ return probe(common.control(), common.capacity(), hash);
+}
+
+// Probes an array of control bits using a probe sequence derived from `hash`,
+// and returns the offset corresponding to the first deleted or empty slot.
//
-// This function is very useful when insertions happen and:
-// - the input is already a set
-// - there are enough slots
-// - the element with the hash is not in the table
-inline FindInfo find_first_non_full(const ctrl_t* ctrl, size_t hash,
- size_t capacity) {
- auto seq = probe(ctrl, hash, capacity);
+// 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()`.
+template <typename = void>
+inline FindInfo find_first_non_full(const CommonFields& common, size_t hash) {
+ auto seq = probe(common, hash);
+ const ctrl_t* ctrl = common.control();
while (true) {
- Group g{ctrl + seq.offset()};
- auto mask = g.MatchEmptyOrDeleted();
+ GroupFullEmptyOrDeleted g{ctrl + seq.offset()};
+ auto mask = g.MaskEmptyOrDeleted();
if (mask) {
-#if !defined(NDEBUG)
- // We want to add entropy even when ASLR is not enabled.
- // In debug build we will randomly insert in either the front or back of
- // the group.
- // TODO(kfm,sbenza): revisit after we do unconditional mixing
- if (!is_small(capacity) && ShouldInsertBackwards(hash, ctrl)) {
- return {seq.offset(mask.HighestBitSet()), seq.index()};
- }
-#endif
- return {seq.offset(mask.LowestBitSet()), seq.index()};
+ return {
+ seq.offset(GetInsertionOffset(mask, common.capacity(), hash, ctrl)),
+ seq.index()};
}
seq.next();
- assert(seq.index() <= capacity && "full table!");
+ assert(seq.index() <= common.capacity() && "full table!");
}
}
-// Reset all ctrl bytes back to ctrl_t::kEmpty, except the sentinel.
-inline void ResetCtrl(size_t capacity, ctrl_t* ctrl, const void* slot,
- size_t slot_size) {
+// Extern template for inline function keep possibility of inlining.
+// When compiler decided to not inline, no symbols will be added to the
+// corresponding translation unit.
+extern template FindInfo find_first_non_full(const CommonFields&, size_t);
+
+// Non-inlined version of find_first_non_full for use in less
+// performance critical routines.
+FindInfo find_first_non_full_outofline(const CommonFields&, size_t);
+
+inline void ResetGrowthLeft(CommonFields& common) {
+ common.set_growth_left(CapacityToGrowth(common.capacity()) - common.size());
+}
+
+// Sets `ctrl` to `{kEmpty, kSentinel, ..., kEmpty}`, marking the entire
+// array as marked as empty.
+inline void ResetCtrl(CommonFields& common, size_t slot_size) {
+ const size_t capacity = common.capacity();
+ ctrl_t* ctrl = common.control();
std::memset(ctrl, static_cast<int8_t>(ctrl_t::kEmpty),
capacity + 1 + NumClonedBytes());
ctrl[capacity] = ctrl_t::kSentinel;
- SanitizerPoisonMemoryRegion(slot, slot_size * capacity);
+ SanitizerPoisonMemoryRegion(common.slot_array(), slot_size * capacity);
}
-// Sets the control byte, and if `i < NumClonedBytes()`, set the cloned byte
-// at the end too.
-inline void SetCtrl(size_t i, ctrl_t h, size_t capacity, ctrl_t* ctrl,
- const void* slot, size_t 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& common, size_t i, ctrl_t h,
+ size_t slot_size) {
+ const size_t capacity = common.capacity();
assert(i < capacity);
- auto* slot_i = static_cast<const char*>(slot) + i * slot_size;
+ auto* slot_i = static_cast<const char*>(common.slot_array()) + i * slot_size;
if (IsFull(h)) {
SanitizerUnpoisonMemoryRegion(slot_i, slot_size);
} else {
SanitizerPoisonMemoryRegion(slot_i, slot_size);
}
+ ctrl_t* ctrl = common.control();
ctrl[i] = h;
ctrl[((i - NumClonedBytes()) & capacity) + (NumClonedBytes() & capacity)] = h;
}
-inline void SetCtrl(size_t i, h2_t h, size_t capacity, ctrl_t* ctrl,
- const void* slot, size_t slot_size) {
- SetCtrl(i, static_cast<ctrl_t>(h), capacity, ctrl, slot, slot_size);
+// Overload for setting to an occupied `h2_t` rather than a special `ctrl_t`.
+inline void SetCtrl(const CommonFields& common, size_t i, h2_t h,
+ size_t slot_size) {
+ SetCtrl(common, i, static_cast<ctrl_t>(h), slot_size);
}
+// growth_left (which is a size_t) is stored with the backing array.
+constexpr size_t BackingArrayAlignment(size_t align_of_slot) {
+ return (std::max)(align_of_slot, alignof(size_t));
+}
+
+// Returns the address of the ith slot in slots where each slot occupies
+// slot_size.
+inline void* SlotAddress(void* slot_array, size_t slot, size_t slot_size) {
+ return reinterpret_cast<void*>(reinterpret_cast<char*>(slot_array) +
+ (slot * slot_size));
+}
+
+// Iterates over all full slots and calls `cb(const ctrl_t*, SlotType*)`.
+// NOTE: no erasure from this table allowed during Callback call.
+template <class SlotType, class Callback>
+ABSL_ATTRIBUTE_ALWAYS_INLINE inline void IterateOverFullSlots(
+ const CommonFields& c, SlotType* slot, Callback cb) {
+ const size_t cap = c.capacity();
+ const ctrl_t* ctrl = c.control();
+ if (is_small(cap)) {
+ // Mirrored/cloned control bytes in small 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
+ // GroupPortableImpl::MaskFull is more efficient for the
+ // capacity <= GroupPortableImpl::kWidth.
+ assert(cap <= GroupPortableImpl::kWidth &&
+ "unexpectedly large small capacity");
+ static_assert(Group::kWidth >= GroupPortableImpl::kWidth,
+ "unexpected group width");
+ // Group starts from kSentinel slot, so indices in the mask will
+ // be increased by 1.
+ const auto mask = GroupPortableImpl(ctrl + cap).MaskFull();
+ --ctrl;
+ --slot;
+ for (uint32_t i : mask) {
+ cb(ctrl + i, slot + i);
+ }
+ return;
+ }
+ size_t remaining = c.size();
+ while (remaining != 0) {
+ for (uint32_t i : GroupFullEmptyOrDeleted(ctrl).MaskFull()) {
+ cb(ctrl + i, slot + i);
+ --remaining;
+ }
+ slot += Group::kWidth;
+ ctrl += Group::kWidth;
+ }
+}
+
+// Helper class to perform resize of the hash set.
+//
+// It contains special optimizations for small group resizes.
+// See GrowIntoSingleGroupShuffleControlBytes for details.
+class HashSetResizeHelper {
+ public:
+ explicit HashSetResizeHelper(CommonFields& c)
+ : old_ctrl_(c.control()),
+ old_capacity_(c.capacity()),
+ had_infoz_(c.has_infoz()) {}
+
+ // Optimized for small groups version of `find_first_non_full`.
+ // Beneficial only right after calling `raw_hash_set::resize`.
+ // It is safe to call in case capacity is big or was not changed, but there
+ // will be no performance benefit.
+ // It has implicit assumption that `resize` will call
+ // `GrowSizeIntoSingleGroup*` in case `IsGrowingIntoSingleGroupApplicable`.
+ // Falls back to `find_first_non_full` in case of big groups, so it is
+ // safe to use after `rehash_and_grow_if_necessary`.
+ static FindInfo FindFirstNonFullAfterResize(const CommonFields& c,
+ size_t old_capacity,
+ size_t hash) {
+ if (!IsGrowingIntoSingleGroupApplicable(old_capacity, c.capacity())) {
+ return find_first_non_full(c, hash);
+ }
+ // Find a location for the new element non-deterministically.
+ // Note that any position is correct.
+ // It will located at `half_old_capacity` or one of the other
+ // empty slots with approximately 50% probability each.
+ size_t offset = probe(c, hash).offset();
+
+ // Note that we intentionally use unsigned int underflow.
+ if (offset - (old_capacity + 1) >= old_capacity) {
+ // Offset fall on kSentinel or into the mostly occupied first half.
+ offset = old_capacity / 2;
+ }
+ assert(IsEmpty(c.control()[offset]));
+ return FindInfo{offset, 0};
+ }
+
+ ctrl_t* old_ctrl() const { return old_ctrl_; }
+ size_t old_capacity() const { return old_capacity_; }
+
+ // Allocates a backing array for the hashtable.
+ // Reads `capacity` and updates all other fields based on the result of
+ // the allocation.
+ //
+ // It also may do the folowing actions:
+ // 1. initialize control bytes
+ // 2. initialize slots
+ // 3. deallocate old slots.
+ //
+ // We are bundling a lot of functionality
+ // in one ABSL_ATTRIBUTE_NOINLINE function in order to minimize binary code
+ // duplication in raw_hash_set<>::resize.
+ //
+ // `c.capacity()` must be nonzero.
+ // POSTCONDITIONS:
+ // 1. CommonFields is initialized.
+ //
+ // if IsGrowingIntoSingleGroupApplicable && TransferUsesMemcpy
+ // Both control bytes and slots are fully initialized.
+ // old_slots are deallocated.
+ // infoz.RecordRehash is called.
+ //
+ // if IsGrowingIntoSingleGroupApplicable && !TransferUsesMemcpy
+ // Control bytes are fully initialized.
+ // infoz.RecordRehash is called.
+ // GrowSizeIntoSingleGroup must be called to finish slots initialization.
+ //
+ // if !IsGrowingIntoSingleGroupApplicable
+ // Control bytes are initialized to empty table via ResetCtrl.
+ // raw_hash_set<>::resize must insert elements regularly.
+ // infoz.RecordRehash is called if old_capacity == 0.
+ //
+ // Returns IsGrowingIntoSingleGroupApplicable result to avoid recomputation.
+ template <typename Alloc, size_t SizeOfSlot, bool TransferUsesMemcpy,
+ size_t AlignOfSlot>
+ ABSL_ATTRIBUTE_NOINLINE bool InitializeSlots(CommonFields& c, void* old_slots,
+ Alloc alloc) {
+ assert(c.capacity());
+ // Folks with custom allocators often make unwarranted assumptions about the
+ // behavior of their classes vis-a-vis trivial destructability and what
+ // calls they will or won't make. Avoid sampling for people with custom
+ // allocators to get us out of this mess. This is not a hard guarantee but
+ // a workaround while we plan the exact guarantee we want to provide.
+ const size_t sample_size =
+ (std::is_same<Alloc, std::allocator<char>>::value &&
+ c.slot_array() == nullptr)
+ ? SizeOfSlot
+ : 0;
+ HashtablezInfoHandle infoz =
+ sample_size > 0 ? Sample(sample_size) : c.infoz();
+
+ const bool has_infoz = infoz.IsSampled();
+ RawHashSetLayout layout(c.capacity(), AlignOfSlot, has_infoz);
+ char* mem = static_cast<char*>(Allocate<BackingArrayAlignment(AlignOfSlot)>(
+ &alloc, layout.alloc_size(SizeOfSlot)));
+ const GenerationType old_generation = c.generation();
+ c.set_generation_ptr(
+ reinterpret_cast<GenerationType*>(mem + layout.generation_offset()));
+ c.set_generation(NextGeneration(old_generation));
+ c.set_control(reinterpret_cast<ctrl_t*>(mem + layout.control_offset()));
+ c.set_slots(mem + layout.slot_offset());
+ ResetGrowthLeft(c);
+
+ const bool grow_single_group =
+ IsGrowingIntoSingleGroupApplicable(old_capacity_, layout.capacity());
+ if (old_capacity_ != 0 && grow_single_group) {
+ if (TransferUsesMemcpy) {
+ GrowSizeIntoSingleGroupTransferable(c, old_slots, SizeOfSlot);
+ DeallocateOld<AlignOfSlot>(alloc, SizeOfSlot, old_slots);
+ } else {
+ GrowIntoSingleGroupShuffleControlBytes(c.control(), layout.capacity());
+ }
+ } else {
+ ResetCtrl(c, SizeOfSlot);
+ }
+
+ c.set_has_infoz(has_infoz);
+ if (has_infoz) {
+ infoz.RecordStorageChanged(c.size(), layout.capacity());
+ if (grow_single_group || old_capacity_ == 0) {
+ infoz.RecordRehash(0);
+ }
+ c.set_infoz(infoz);
+ }
+ return grow_single_group;
+ }
+
+ // Relocates slots into new single group consistent with
+ // GrowIntoSingleGroupShuffleControlBytes.
+ //
+ // PRECONDITIONS:
+ // 1. GrowIntoSingleGroupShuffleControlBytes was already called.
+ template <class PolicyTraits, class Alloc>
+ void GrowSizeIntoSingleGroup(CommonFields& c, Alloc& alloc_ref,
+ typename PolicyTraits::slot_type* old_slots) {
+ assert(old_capacity_ < Group::kWidth / 2);
+ assert(IsGrowingIntoSingleGroupApplicable(old_capacity_, c.capacity()));
+ using slot_type = typename PolicyTraits::slot_type;
+ assert(is_single_group(c.capacity()));
+
+ auto* new_slots = reinterpret_cast<slot_type*>(c.slot_array());
+
+ size_t shuffle_bit = old_capacity_ / 2 + 1;
+ for (size_t i = 0; i < old_capacity_; ++i) {
+ if (IsFull(old_ctrl_[i])) {
+ size_t new_i = i ^ shuffle_bit;
+ SanitizerUnpoisonMemoryRegion(new_slots + new_i, sizeof(slot_type));
+ PolicyTraits::transfer(&alloc_ref, new_slots + new_i, old_slots + i);
+ }
+ }
+ PoisonSingleGroupEmptySlots(c, sizeof(slot_type));
+ }
+
+ // Deallocates old backing array.
+ template <size_t AlignOfSlot, class CharAlloc>
+ void DeallocateOld(CharAlloc alloc_ref, size_t slot_size, void* old_slots) {
+ SanitizerUnpoisonMemoryRegion(old_slots, slot_size * old_capacity_);
+ auto layout = RawHashSetLayout(old_capacity_, AlignOfSlot, had_infoz_);
+ Deallocate<BackingArrayAlignment(AlignOfSlot)>(
+ &alloc_ref, old_ctrl_ - layout.control_offset(),
+ layout.alloc_size(slot_size));
+ }
+
+ private:
+ // Returns true if `GrowSizeIntoSingleGroup` can be used for resizing.
+ static bool IsGrowingIntoSingleGroupApplicable(size_t old_capacity,
+ size_t new_capacity) {
+ // NOTE that `old_capacity < new_capacity` in order to have
+ // `old_capacity < Group::kWidth / 2` to make faster copies of 8 bytes.
+ return is_single_group(new_capacity) && old_capacity < new_capacity;
+ }
+
+ // Relocates control bytes and slots into new single group for
+ // transferable objects.
+ // Must be called only if IsGrowingIntoSingleGroupApplicable returned true.
+ void GrowSizeIntoSingleGroupTransferable(CommonFields& c, void* old_slots,
+ size_t slot_size);
+
+ // Shuffle control bits deterministically to the next capacity.
+ // Returns offset for newly added element with given hash.
+ //
+ // PRECONDITIONs:
+ // 1. new_ctrl is allocated for new_capacity,
+ // but not initialized.
+ // 2. new_capacity is a single group.
+ //
+ // All elements are transferred into the first `old_capacity + 1` positions
+ // of the new_ctrl. Elements are rotated by `old_capacity_ / 2 + 1` positions
+ // in order to change an order and keep it non deterministic.
+ // Although rotation itself deterministic, position of the new added element
+ // will be based on `H1` and is not deterministic.
+ //
+ // Examples:
+ // S = kSentinel, E = kEmpty
+ //
+ // old_ctrl = SEEEEEEEE...
+ // new_ctrl = ESEEEEEEE...
+ //
+ // old_ctrl = 0SEEEEEEE...
+ // new_ctrl = E0ESE0EEE...
+ //
+ // old_ctrl = 012S012EEEEEEEEE...
+ // new_ctrl = 2E01EEES2E01EEE...
+ //
+ // old_ctrl = 0123456S0123456EEEEEEEEEEE...
+ // new_ctrl = 456E0123EEEEEES456E0123EEE...
+ void GrowIntoSingleGroupShuffleControlBytes(ctrl_t* new_ctrl,
+ size_t new_capacity) const;
+
+ // Shuffle trivially transferable slots in the way consistent with
+ // GrowIntoSingleGroupShuffleControlBytes.
+ //
+ // PRECONDITIONs:
+ // 1. old_capacity must be non-zero.
+ // 2. new_ctrl is fully initialized using
+ // GrowIntoSingleGroupShuffleControlBytes.
+ // 3. new_slots is allocated and *not* poisoned.
+ //
+ // POSTCONDITIONS:
+ // 1. new_slots are transferred from old_slots_ consistent with
+ // GrowIntoSingleGroupShuffleControlBytes.
+ // 2. Empty new_slots are *not* poisoned.
+ void GrowIntoSingleGroupShuffleTransferableSlots(void* old_slots,
+ void* new_slots,
+ size_t slot_size) const;
+
+ // Poison empty slots that were transferred using the deterministic algorithm
+ // described above.
+ // PRECONDITIONs:
+ // 1. new_ctrl is fully initialized using
+ // GrowIntoSingleGroupShuffleControlBytes.
+ // 2. new_slots is fully initialized consistent with
+ // GrowIntoSingleGroupShuffleControlBytes.
+ void PoisonSingleGroupEmptySlots(CommonFields& c, size_t slot_size) const {
+ // poison non full items
+ for (size_t i = 0; i < c.capacity(); ++i) {
+ if (!IsFull(c.control()[i])) {
+ SanitizerPoisonMemoryRegion(SlotAddress(c.slot_array(), i, slot_size),
+ slot_size);
+ }
+ }
+ }
+
+ ctrl_t* old_ctrl_;
+ size_t old_capacity_;
+ bool had_infoz_;
+};
+
+// PolicyFunctions bundles together some information for a particular
+// raw_hash_set<T, ...> instantiation. This information is passed to
+// type-erased functions that want to do small amounts of type-specific
+// work.
+struct PolicyFunctions {
+ size_t slot_size;
+
+ // Returns the hash of the pointed-to slot.
+ size_t (*hash_slot)(const void* hash_fn, void* slot);
+
+ // Transfer the contents of src_slot to dst_slot.
+ void (*transfer)(void* set, void* dst_slot, void* src_slot);
+
+ // Deallocate the backing store from common.
+ void (*dealloc)(CommonFields& common, const PolicyFunctions& policy);
+};
+
+// ClearBackingArray clears the backing array, either modifying it in place,
+// or creating a new one based on the value of "reuse".
+// REQUIRES: c.capacity > 0
+void ClearBackingArray(CommonFields& c, const PolicyFunctions& policy,
+ bool reuse);
+
+// Type-erased version of raw_hash_set::erase_meta_only.
+void EraseMetaOnly(CommonFields& c, size_t index, size_t slot_size);
+
+// Function to place in PolicyFunctions::dealloc for raw_hash_sets
+// that are using std::allocator. This allows us to share the same
+// function body for raw_hash_set instantiations that have the
+// same slot alignment.
+template <size_t AlignOfSlot>
+ABSL_ATTRIBUTE_NOINLINE void DeallocateStandard(CommonFields& common,
+ const PolicyFunctions& policy) {
+ // Unpoison before returning the memory to the allocator.
+ SanitizerUnpoisonMemoryRegion(common.slot_array(),
+ policy.slot_size * common.capacity());
+
+ std::allocator<char> alloc;
+ common.infoz().Unregister();
+ Deallocate<BackingArrayAlignment(AlignOfSlot)>(
+ &alloc, common.backing_array_start(),
+ common.alloc_size(policy.slot_size, AlignOfSlot));
+}
+
+// For trivially relocatable types we use memcpy directly. This allows us to
+// share the same function body for raw_hash_set instantiations that have the
+// same slot size as long as they are relocatable.
+template <size_t SizeOfSlot>
+ABSL_ATTRIBUTE_NOINLINE void TransferRelocatable(void*, void* dst, void* src) {
+ memcpy(dst, src, SizeOfSlot);
+}
+
+// Type-erased version of raw_hash_set::drop_deletes_without_resize.
+void DropDeletesWithoutResize(CommonFields& common, const void* hash_fn,
+ const PolicyFunctions& policy, void* tmp_space);
+
+// A SwissTable.
+//
// Policy: a policy defines how to perform different operations on
// the slots of the hashtable (see hash_policy_traits.h for the full interface
// of policy).
@@ -685,18 +1992,14 @@
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));
- using Layout = absl::container_internal::Layout<ctrl_t, slot_type>;
-
- static Layout MakeLayout(size_t capacity) {
- assert(IsValidCapacity(capacity));
- // The extra control bytes are for 1 sentinel byte followed by
- // NumClonedBytes() bytes that are cloned from the beginning.
- return Layout(capacity + 1 + NumClonedBytes(), capacity);
- }
-
using AllocTraits = absl::allocator_traits<allocator_type>;
using SlotAlloc = typename absl::allocator_traits<
allocator_type>::template rebind_alloc<slot_type>;
+ // People are often sloppy with the exact type of their allocator (sometimes
+ // it has an extra const or is missing the pair, but rebinds made it work
+ // anyway).
+ using CharAlloc =
+ typename absl::allocator_traits<Alloc>::template rebind_alloc<char>;
using SlotAllocTraits = typename absl::allocator_traits<
allocator_type>::template rebind_traits<slot_type>;
@@ -736,7 +2039,7 @@
static_assert(std::is_same<const_pointer, const value_type*>::value,
"Allocators with custom pointer types are not supported");
- class iterator {
+ class iterator : private HashSetIteratorGenerationInfo {
friend class raw_hash_set;
public:
@@ -752,19 +2055,23 @@
// PRECONDITION: not an end() iterator.
reference operator*() const {
- AssertIsFull(ctrl_);
- return PolicyTraits::element(slot_);
+ AssertIsFull(ctrl_, generation(), generation_ptr(), "operator*()");
+ return unchecked_deref();
}
// PRECONDITION: not an end() iterator.
- pointer operator->() const { return &operator*(); }
+ pointer operator->() const {
+ AssertIsFull(ctrl_, generation(), generation_ptr(), "operator->");
+ return &operator*();
+ }
// PRECONDITION: not an end() iterator.
iterator& operator++() {
- AssertIsFull(ctrl_);
+ AssertIsFull(ctrl_, generation(), generation_ptr(), "operator++");
++ctrl_;
++slot_;
skip_empty_or_deleted();
+ if (ABSL_PREDICT_FALSE(*ctrl_ == ctrl_t::kSentinel)) ctrl_ = nullptr;
return *this;
}
// PRECONDITION: not an end() iterator.
@@ -775,8 +2082,10 @@
}
friend bool operator==(const iterator& a, const iterator& b) {
- AssertIsValid(a.ctrl_);
- AssertIsValid(b.ctrl_);
+ AssertIsValidForComparison(a.ctrl_, a.generation(), a.generation_ptr());
+ AssertIsValidForComparison(b.ctrl_, b.generation(), b.generation_ptr());
+ AssertSameContainer(a.ctrl_, b.ctrl_, a.slot_, b.slot_,
+ a.generation_ptr(), b.generation_ptr());
return a.ctrl_ == b.ctrl_;
}
friend bool operator!=(const iterator& a, const iterator& b) {
@@ -784,31 +2093,58 @@
}
private:
- iterator(ctrl_t* ctrl, slot_type* slot) : ctrl_(ctrl), slot_(slot) {
+ iterator(ctrl_t* ctrl, slot_type* slot,
+ const GenerationType* generation_ptr)
+ : HashSetIteratorGenerationInfo(generation_ptr),
+ ctrl_(ctrl),
+ slot_(slot) {
// This assumption helps the compiler know that any non-end iterator is
// not equal to any end iterator.
- ABSL_INTERNAL_ASSUME(ctrl != nullptr);
+ ABSL_ASSUME(ctrl != nullptr);
}
+ // For end() iterators.
+ explicit iterator(const GenerationType* generation_ptr)
+ : HashSetIteratorGenerationInfo(generation_ptr), ctrl_(nullptr) {}
+ // 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 = Group{ctrl_}.CountLeadingEmptyOrDeleted();
+ uint32_t shift =
+ GroupFullEmptyOrDeleted{ctrl_}.CountLeadingEmptyOrDeleted();
ctrl_ += shift;
slot_ += shift;
}
- if (ABSL_PREDICT_FALSE(*ctrl_ == ctrl_t::kSentinel)) ctrl_ = nullptr;
}
- ctrl_t* ctrl_ = nullptr;
+ ctrl_t* control() const { return ctrl_; }
+ slot_type* slot() const { return slot_; }
+
+ // We use EmptyGroup() for default-constructed iterators so that they can
+ // be distinguished from end iterators, which have nullptr ctrl_.
+ ctrl_t* ctrl_ = EmptyGroup();
// To avoid uninitialized member warnings, put slot_ in an anonymous union.
// The member is not initialized on singleton and end iterators.
union {
slot_type* slot_;
};
+
+ // An equality check which skips ABSL Hardening iterator invalidation
+ // checks.
+ // Should be used when the lifetimes of the iterators are well-enough
+ // understood to prove that they cannot be invalid.
+ bool unchecked_equals(const iterator& b) { return ctrl_ == b.control(); }
+
+ // Dereferences the iterator without ABSL Hardening iterator invalidation
+ // checks.
+ reference unchecked_deref() const { return PolicyTraits::element(slot_); }
};
class const_iterator {
friend class raw_hash_set;
+ template <class Container, typename Enabler>
+ friend struct absl::container_internal::hashtable_debug_internal::
+ HashtableDebugAccess;
public:
using iterator_category = typename iterator::iterator_category;
@@ -817,9 +2153,9 @@
using pointer = typename raw_hash_set::const_pointer;
using difference_type = typename raw_hash_set::difference_type;
- const_iterator() {}
+ const_iterator() = default;
// Implicit construction from iterator.
- const_iterator(iterator i) : inner_(std::move(i)) {}
+ const_iterator(iterator i) : inner_(std::move(i)) {} // NOLINT
reference operator*() const { return *inner_; }
pointer operator->() const { return inner_.operator->(); }
@@ -838,28 +2174,37 @@
}
private:
- const_iterator(const ctrl_t* ctrl, const slot_type* slot)
- : inner_(const_cast<ctrl_t*>(ctrl), const_cast<slot_type*>(slot)) {}
+ const_iterator(const ctrl_t* ctrl, const slot_type* slot,
+ const GenerationType* gen)
+ : inner_(const_cast<ctrl_t*>(ctrl), const_cast<slot_type*>(slot), gen) {
+ }
+ 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>;
using insert_return_type = InsertReturnType<iterator, node_type>;
+ // Note: can't use `= default` due to non-default noexcept (causes
+ // problems for some compilers). NOLINTNEXTLINE
raw_hash_set() noexcept(
- std::is_nothrow_default_constructible<hasher>::value&&
- std::is_nothrow_default_constructible<key_equal>::value&&
- std::is_nothrow_default_constructible<allocator_type>::value) {}
+ std::is_nothrow_default_constructible<hasher>::value &&
+ std::is_nothrow_default_constructible<key_equal>::value &&
+ std::is_nothrow_default_constructible<allocator_type>::value) {}
- explicit raw_hash_set(size_t bucket_count, const hasher& hash = hasher(),
- const key_equal& eq = key_equal(),
- const allocator_type& alloc = allocator_type())
- : ctrl_(EmptyGroup()),
- settings_(0, HashtablezInfoHandle(), hash, eq, alloc) {
+ ABSL_ATTRIBUTE_NOINLINE explicit raw_hash_set(
+ size_t bucket_count, const hasher& hash = hasher(),
+ const key_equal& eq = key_equal(),
+ const allocator_type& alloc = allocator_type())
+ : settings_(CommonFields{}, hash, eq, alloc) {
if (bucket_count) {
- capacity_ = NormalizeCapacity(bucket_count);
- initialize_slots();
+ resize(NormalizeCapacity(bucket_count));
}
}
@@ -959,98 +2304,138 @@
that.alloc_ref())) {}
raw_hash_set(const raw_hash_set& that, const allocator_type& a)
- : raw_hash_set(0, that.hash_ref(), that.eq_ref(), a) {
- reserve(that.size());
- // Because the table is guaranteed to be empty, we can do something faster
- // than a full `insert`.
- for (const auto& v : that) {
- const size_t hash = PolicyTraits::apply(HashElement{hash_ref()}, v);
- auto target = find_first_non_full(ctrl_, hash, capacity_);
- SetCtrl(target.offset, H2(hash), capacity_, ctrl_, slots_,
- sizeof(slot_type));
- emplace_at(target.offset, v);
- infoz().RecordInsert(hash, target.probe_length);
+ : raw_hash_set(GrowthToLowerboundCapacity(that.size()), that.hash_ref(),
+ that.eq_ref(), a) {
+ const size_t size = that.size();
+ if (size == 0) {
+ return;
}
- size_ = that.size();
- growth_left() -= that.size();
+ const size_t cap = 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 if 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) ? (PerTableSalt(control()) | 1) : 0;
+ IterateOverFullSlots(
+ that.common(), that.slot_array(),
+ [&](const ctrl_t* that_ctrl,
+ slot_type* that_slot) ABSL_ATTRIBUTE_ALWAYS_INLINE {
+ 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 = PolicyTraits::apply(
+ HashElement{hash_ref()}, PolicyTraits::element(that_slot));
+ FindInfo target = find_first_non_full_outofline(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);
+ assert( // We rely that hash is not changed for small tables.
+ H2(PolicyTraits::apply(HashElement{hash_ref()},
+ PolicyTraits::element(that_slot))) == h2 &&
+ "hash function value changed unexpectedly during the copy");
+ SetCtrl(common(), offset, h2, sizeof(slot_type));
+ emplace_at(iterator_at(offset), PolicyTraits::element(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().set_size(size);
+ set_growth_left(growth_left() - size);
}
- raw_hash_set(raw_hash_set&& that) noexcept(
- std::is_nothrow_copy_constructible<hasher>::value&&
- std::is_nothrow_copy_constructible<key_equal>::value&&
- std::is_nothrow_copy_constructible<allocator_type>::value)
- : ctrl_(absl::exchange(that.ctrl_, EmptyGroup())),
- slots_(absl::exchange(that.slots_, nullptr)),
- size_(absl::exchange(that.size_, 0)),
- capacity_(absl::exchange(that.capacity_, 0)),
- // Hash, equality and allocator are copied instead of moved because
- // `that` must be left valid. If Hash is std::function<Key>, moving it
- // would create a nullptr functor that cannot be called.
- settings_(absl::exchange(that.growth_left(), 0),
- absl::exchange(that.infoz(), HashtablezInfoHandle()),
- that.hash_ref(), that.eq_ref(), that.alloc_ref()) {}
+ ABSL_ATTRIBUTE_NOINLINE raw_hash_set(raw_hash_set&& that) noexcept(
+ std::is_nothrow_copy_constructible<hasher>::value &&
+ std::is_nothrow_copy_constructible<key_equal>::value &&
+ std::is_nothrow_copy_constructible<allocator_type>::value)
+ : // Hash, equality and allocator are copied instead of moved because
+ // `that` must be left valid. If Hash is std::function<Key>, moving it
+ // would create a nullptr functor that cannot be called.
+ // TODO(b/296061262): move instead of copying hash/eq/alloc.
+ // Note: we avoid using exchange for better generated code.
+ settings_(std::move(that.common()), that.hash_ref(), that.eq_ref(),
+ that.alloc_ref()) {
+ that.common() = CommonFields{};
+ maybe_increment_generation_or_rehash_on_move();
+ }
raw_hash_set(raw_hash_set&& that, const allocator_type& a)
- : ctrl_(EmptyGroup()),
- slots_(nullptr),
- size_(0),
- capacity_(0),
- settings_(0, HashtablezInfoHandle(), that.hash_ref(), that.eq_ref(),
- a) {
+ : settings_(CommonFields{}, that.hash_ref(), that.eq_ref(), a) {
if (a == that.alloc_ref()) {
- std::swap(ctrl_, that.ctrl_);
- std::swap(slots_, that.slots_);
- std::swap(size_, that.size_);
- std::swap(capacity_, that.capacity_);
- std::swap(growth_left(), that.growth_left());
- std::swap(infoz(), that.infoz());
+ std::swap(common(), that.common());
+ maybe_increment_generation_or_rehash_on_move();
} else {
- reserve(that.size());
- // Note: this will copy elements of dense_set and unordered_set instead of
- // moving them. This can be fixed if it ever becomes an issue.
- for (auto& elem : that) insert(std::move(elem));
+ move_elements_allocs_unequal(std::move(that));
}
}
raw_hash_set& operator=(const raw_hash_set& that) {
- raw_hash_set tmp(that,
- AllocTraits::propagate_on_container_copy_assignment::value
- ? that.alloc_ref()
- : alloc_ref());
- swap(tmp);
- return *this;
+ if (ABSL_PREDICT_FALSE(this == &that)) return *this;
+ constexpr bool propagate_alloc =
+ AllocTraits::propagate_on_container_copy_assignment::value;
+ // TODO(ezb): maybe avoid allocating a new backing array if this->capacity()
+ // is an exact match for that.size(). If this->capacity() is too big, then
+ // it would make iteration very slow to reuse the allocation. Maybe we can
+ // do the same heuristic as clear() and reuse if it's small enough.
+ raw_hash_set tmp(that, propagate_alloc ? that.alloc_ref() : alloc_ref());
+ // NOLINTNEXTLINE: not returning *this for performance.
+ return assign_impl<propagate_alloc>(std::move(tmp));
}
raw_hash_set& operator=(raw_hash_set&& that) noexcept(
- absl::allocator_traits<allocator_type>::is_always_equal::value&&
- std::is_nothrow_move_assignable<hasher>::value&&
- std::is_nothrow_move_assignable<key_equal>::value) {
+ absl::allocator_traits<allocator_type>::is_always_equal::value &&
+ std::is_nothrow_move_assignable<hasher>::value &&
+ std::is_nothrow_move_assignable<key_equal>::value) {
// TODO(sbenza): We should only use the operations from the noexcept clause
// to make sure we actually adhere to that contract.
+ // NOLINTNEXTLINE: not returning *this for performance.
return move_assign(
std::move(that),
typename AllocTraits::propagate_on_container_move_assignment());
}
- ~raw_hash_set() { destroy_slots(); }
+ ~raw_hash_set() { destructor_impl(); }
- iterator begin() {
+ iterator begin() ABSL_ATTRIBUTE_LIFETIME_BOUND {
+ // TODO(b/324478958): Consider reverting if no impact.
+ if (ABSL_PREDICT_FALSE(empty())) return end();
auto it = iterator_at(0);
it.skip_empty_or_deleted();
+ assert(IsFull(*it.control()));
return it;
}
- iterator end() { return {}; }
+ iterator end() ABSL_ATTRIBUTE_LIFETIME_BOUND {
+ return iterator(common().generation_ptr());
+ }
- const_iterator begin() const {
+ const_iterator begin() const ABSL_ATTRIBUTE_LIFETIME_BOUND {
return const_cast<raw_hash_set*>(this)->begin();
}
- const_iterator end() const { return {}; }
- const_iterator cbegin() const { return begin(); }
- const_iterator cend() const { return end(); }
+ const_iterator end() const ABSL_ATTRIBUTE_LIFETIME_BOUND {
+ return iterator(common().generation_ptr());
+ }
+ const_iterator cbegin() const ABSL_ATTRIBUTE_LIFETIME_BOUND {
+ return begin();
+ }
+ const_iterator cend() const ABSL_ATTRIBUTE_LIFETIME_BOUND { return end(); }
bool empty() const { return !size(); }
- size_t size() const { return size_; }
- size_t capacity() const { return capacity_; }
+ size_t size() const { return common().size(); }
+ size_t capacity() const { return common().capacity(); }
size_t max_size() const { return (std::numeric_limits<size_t>::max)(); }
ABSL_ATTRIBUTE_REINITIALIZES void clear() {
@@ -1061,20 +2446,15 @@
// compared to destruction of the elements of the container. So we pick the
// largest bucket_count() threshold for which iteration is still fast and
// past that we simply deallocate the array.
- if (capacity_ > 127) {
+ const size_t cap = capacity();
+ if (cap == 0) {
+ // Already guaranteed to be empty; so nothing to do.
+ } else {
destroy_slots();
- } else if (capacity_) {
- for (size_t i = 0; i != capacity_; ++i) {
- if (IsFull(ctrl_[i])) {
- PolicyTraits::destroy(&alloc_ref(), slots_ + i);
- }
- }
- size_ = 0;
- ResetCtrl(capacity_, ctrl_, slots_, sizeof(slot_type));
- reset_growth_left();
+ ClearBackingArray(common(), GetPolicyFunctions(), /*reuse=*/cap < 128);
}
- assert(empty());
- infoz().RecordStorageChanged(0, capacity_);
+ common().set_reserved_growth(0);
+ common().set_reservation_size(0);
}
// This overload kicks in when the argument is an rvalue of insertable and
@@ -1084,11 +2464,10 @@
// m.insert(std::make_pair("abc", 42));
// TODO(cheshire): A type alias T2 is introduced as a workaround for the nvcc
// bug.
- template <class T, RequiresInsertable<T> = 0,
- class T2 = T,
+ template <class T, RequiresInsertable<T> = 0, class T2 = T,
typename std::enable_if<IsDecomposable<T2>::value, int>::type = 0,
T* = nullptr>
- std::pair<iterator, bool> insert(T&& value) {
+ std::pair<iterator, bool> insert(T&& value) ABSL_ATTRIBUTE_LIFETIME_BOUND {
return emplace(std::forward<T>(value));
}
@@ -1103,13 +2482,11 @@
// const char* p = "hello";
// s.insert(p);
//
- // TODO(romanp): Once we stop supporting gcc 5.1 and below, replace
- // RequiresInsertable<T> with RequiresInsertable<const T&>.
- // We are hitting this bug: https://godbolt.org/g/1Vht4f.
template <
- class T, RequiresInsertable<T> = 0,
+ class T, RequiresInsertable<const T&> = 0,
typename std::enable_if<IsDecomposable<const T&>::value, int>::type = 0>
- std::pair<iterator, bool> insert(const T& value) {
+ std::pair<iterator, bool> insert(const T& value)
+ ABSL_ATTRIBUTE_LIFETIME_BOUND {
return emplace(value);
}
@@ -1118,7 +2495,8 @@
//
// flat_hash_map<std::string, int> s;
// s.insert({"abc", 42});
- std::pair<iterator, bool> insert(init_type&& value) {
+ std::pair<iterator, bool> insert(init_type&& value)
+ ABSL_ATTRIBUTE_LIFETIME_BOUND {
return emplace(std::move(value));
}
@@ -1127,21 +2505,20 @@
template <class T, RequiresInsertable<T> = 0, class T2 = T,
typename std::enable_if<IsDecomposable<T2>::value, int>::type = 0,
T* = nullptr>
- iterator insert(const_iterator, T&& value) {
+ iterator insert(const_iterator, T&& value) ABSL_ATTRIBUTE_LIFETIME_BOUND {
return insert(std::forward<T>(value)).first;
}
- // TODO(romanp): Once we stop supporting gcc 5.1 and below, replace
- // RequiresInsertable<T> with RequiresInsertable<const T&>.
- // We are hitting this bug: https://godbolt.org/g/1Vht4f.
template <
- class T, RequiresInsertable<T> = 0,
+ class T, RequiresInsertable<const T&> = 0,
typename std::enable_if<IsDecomposable<const T&>::value, int>::type = 0>
- iterator insert(const_iterator, const T& value) {
+ iterator insert(const_iterator,
+ const T& value) ABSL_ATTRIBUTE_LIFETIME_BOUND {
return insert(value).first;
}
- iterator insert(const_iterator, init_type&& value) {
+ iterator insert(const_iterator,
+ init_type&& value) ABSL_ATTRIBUTE_LIFETIME_BOUND {
return insert(std::move(value)).first;
}
@@ -1159,7 +2536,7 @@
insert(ilist.begin(), ilist.end());
}
- insert_return_type insert(node_type&& node) {
+ insert_return_type insert(node_type&& node) ABSL_ATTRIBUTE_LIFETIME_BOUND {
if (!node) return {end(), false, node_type()};
const auto& elem = PolicyTraits::element(CommonAccess::GetSlot(node));
auto res = PolicyTraits::apply(
@@ -1173,7 +2550,8 @@
}
}
- iterator insert(const_iterator, node_type&& node) {
+ iterator insert(const_iterator,
+ node_type&& node) ABSL_ATTRIBUTE_LIFETIME_BOUND {
auto res = insert(std::move(node));
node = std::move(res.node);
return res.position;
@@ -1190,7 +2568,8 @@
// m.emplace("abc", "xyz");
template <class... Args, typename std::enable_if<
IsDecomposable<Args...>::value, int>::type = 0>
- std::pair<iterator, bool> emplace(Args&&... args) {
+ std::pair<iterator, bool> emplace(Args&&... args)
+ ABSL_ATTRIBUTE_LIFETIME_BOUND {
return PolicyTraits::apply(EmplaceDecomposable{*this},
std::forward<Args>(args)...);
}
@@ -1200,24 +2579,27 @@
// destroys.
template <class... Args, typename std::enable_if<
!IsDecomposable<Args...>::value, int>::type = 0>
- std::pair<iterator, bool> emplace(Args&&... args) {
+ std::pair<iterator, bool> emplace(Args&&... args)
+ ABSL_ATTRIBUTE_LIFETIME_BOUND {
alignas(slot_type) unsigned char raw[sizeof(slot_type)];
slot_type* slot = reinterpret_cast<slot_type*>(&raw);
- PolicyTraits::construct(&alloc_ref(), slot, std::forward<Args>(args)...);
+ construct(slot, std::forward<Args>(args)...);
const auto& elem = PolicyTraits::element(slot);
return PolicyTraits::apply(InsertSlot<true>{*this, std::move(*slot)}, elem);
}
template <class... Args>
- iterator emplace_hint(const_iterator, Args&&... args) {
+ iterator emplace_hint(const_iterator,
+ Args&&... args) ABSL_ATTRIBUTE_LIFETIME_BOUND {
return emplace(std::forward<Args>(args)...).first;
}
// Extension API: support for lazy emplace.
//
// Looks up key in the table. If found, returns the iterator to the element.
- // Otherwise calls `f` with one argument of type `raw_hash_set::constructor`.
+ // Otherwise calls `f` with one argument of type `raw_hash_set::constructor`,
+ // and returns an iterator to the new element.
//
// `f` must abide by several restrictions:
// - it MUST call `raw_hash_set::constructor` with arguments as if a
@@ -1260,14 +2642,15 @@
};
template <class K = key_type, class F>
- iterator lazy_emplace(const key_arg<K>& key, F&& f) {
+ iterator lazy_emplace(const key_arg<K>& key,
+ F&& f) ABSL_ATTRIBUTE_LIFETIME_BOUND {
auto res = find_or_prepare_insert(key);
if (res.second) {
- slot_type* slot = slots_ + res.first;
+ slot_type* slot = res.first.slot();
std::forward<F>(f)(constructor(&alloc_ref(), &slot));
assert(!slot);
}
- return iterator_at(res.first);
+ return res.first;
}
// Extension API: support for heterogeneous keys.
@@ -1305,12 +2688,25 @@
// 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) {
- AssertIsFull(it.ctrl_);
- PolicyTraits::destroy(&alloc_ref(), it.slot_);
+ AssertIsFull(it.control(), it.generation(), it.generation_ptr(), "erase()");
+ destroy(it.slot());
erase_meta_only(it);
}
- iterator erase(const_iterator first, const_iterator last) {
+ iterator erase(const_iterator first,
+ const_iterator last) ABSL_ATTRIBUTE_LIFETIME_BOUND {
+ // We check for empty first because ClearBackingArray requires that
+ // capacity() > 0 as a precondition.
+ if (empty()) return end();
+ if (first == begin() && last == end()) {
+ // TODO(ezb): we access control bytes in destroy_slots so it could make
+ // sense to combine destroy_slots and ClearBackingArray to avoid cache
+ // misses when the table is large. Note that we also do this in clear().
+ destroy_slots();
+ ClearBackingArray(common(), GetPolicyFunctions(), /*reuse=*/true);
+ common().set_reserved_growth(common().reservation_size());
+ return end();
+ }
while (first != last) {
erase(first++);
}
@@ -1324,8 +2720,8 @@
assert(this != &src);
for (auto it = src.begin(), e = src.end(); it != e;) {
auto next = std::next(it);
- if (PolicyTraits::apply(InsertSlot<false>{*this, std::move(*it.slot_)},
- PolicyTraits::element(it.slot_))
+ if (PolicyTraits::apply(InsertSlot<false>{*this, std::move(*it.slot())},
+ PolicyTraits::element(it.slot()))
.second) {
src.erase_meta_only(it);
}
@@ -1339,9 +2735,9 @@
}
node_type extract(const_iterator position) {
- AssertIsFull(position.inner_.ctrl_);
- auto node =
- CommonAccess::Transfer<node_type>(alloc_ref(), position.inner_.slot_);
+ AssertIsFull(position.control(), position.inner_.generation(),
+ position.inner_.generation_ptr(), "extract()");
+ auto node = CommonAccess::Transfer<node_type>(alloc_ref(), position.slot());
erase_meta_only(position);
return node;
}
@@ -1359,31 +2755,30 @@
IsNoThrowSwappable<allocator_type>(
typename AllocTraits::propagate_on_container_swap{})) {
using std::swap;
- swap(ctrl_, that.ctrl_);
- swap(slots_, that.slots_);
- swap(size_, that.size_);
- swap(capacity_, that.capacity_);
- swap(growth_left(), that.growth_left());
+ swap(common(), that.common());
swap(hash_ref(), that.hash_ref());
swap(eq_ref(), that.eq_ref());
- swap(infoz(), that.infoz());
SwapAlloc(alloc_ref(), that.alloc_ref(),
typename AllocTraits::propagate_on_container_swap{});
}
void rehash(size_t n) {
- if (n == 0 && capacity_ == 0) return;
- if (n == 0 && size_ == 0) {
- destroy_slots();
- infoz().RecordStorageChanged(0, 0);
+ if (n == 0 && capacity() == 0) return;
+ if (n == 0 && size() == 0) {
+ ClearBackingArray(common(), GetPolicyFunctions(), /*reuse=*/false);
return;
}
+
// bitor is a faster way of doing `max` here. We will round up to the next
// power-of-2-minus-1, so bitor is good enough.
auto m = NormalizeCapacity(n | GrowthToLowerboundCapacity(size()));
// n == 0 unconditionally rehashes as per the standard.
- if (n == 0 || m > capacity_) {
+ if (n == 0 || m > capacity()) {
resize(m);
+
+ // This is after resize, to ensure that we have completed the allocation
+ // and have potentially sampled the hashtable.
+ infoz().RecordReservation(n);
}
}
@@ -1391,7 +2786,13 @@
if (n > size() + growth_left()) {
size_t m = GrowthToLowerboundCapacity(n);
resize(NormalizeCapacity(m));
+
+ // This is after resize, to ensure that we have completed the allocation
+ // and have potentially sampled the hashtable.
+ infoz().RecordReservation(n);
}
+ common().reset_reserved_growth(n);
+ common().set_reservation_size(n);
}
// Extension API: support for heterogeneous keys.
@@ -1416,11 +2817,13 @@
template <class K = key_type>
void prefetch(const key_arg<K>& key) const {
(void)key;
-#if defined(__GNUC__)
- auto seq = probe(ctrl_, hash_ref()(key), capacity_);
- __builtin_prefetch(static_cast<const void*>(ctrl_ + seq.offset()));
- __builtin_prefetch(static_cast<const void*>(slots_ + seq.offset()));
-#endif // __GNUC__
+ // Avoid probing if we won't be able to prefetch the addresses received.
+#ifdef ABSL_HAVE_PREFETCH
+ prefetch_heap_block();
+ auto seq = probe(common(), hash_ref()(key));
+ PrefetchToLocalCache(control() + seq.offset());
+ PrefetchToLocalCache(slot_array() + seq.offset());
+#endif // ABSL_HAVE_PREFETCH
}
// The API of find() has two extensions.
@@ -1431,57 +2834,68 @@
// 2. The type of the key argument doesn't have to be key_type. This is so
// called heterogeneous key support.
template <class K = key_type>
- iterator find(const key_arg<K>& key, size_t hash) {
- auto seq = probe(ctrl_, hash, capacity_);
+ iterator find(const key_arg<K>& key,
+ size_t hash) ABSL_ATTRIBUTE_LIFETIME_BOUND {
+ auto seq = probe(common(), hash);
+ slot_type* slot_ptr = slot_array();
+ const ctrl_t* ctrl = control();
while (true) {
- Group g{ctrl_ + seq.offset()};
- for (int i : g.Match(H2(hash))) {
+ Group g{ctrl + seq.offset()};
+ for (uint32_t i : g.Match(H2(hash))) {
if (ABSL_PREDICT_TRUE(PolicyTraits::apply(
EqualElement<K>{key, eq_ref()},
- PolicyTraits::element(slots_ + seq.offset(i)))))
+ PolicyTraits::element(slot_ptr + seq.offset(i)))))
return iterator_at(seq.offset(i));
}
- if (ABSL_PREDICT_TRUE(g.MatchEmpty())) return end();
+ if (ABSL_PREDICT_TRUE(g.MaskEmpty())) return end();
seq.next();
- assert(seq.index() <= capacity_ && "full table!");
+ assert(seq.index() <= capacity() && "full table!");
}
}
template <class K = key_type>
- iterator find(const key_arg<K>& key) {
+ iterator find(const key_arg<K>& key) ABSL_ATTRIBUTE_LIFETIME_BOUND {
+ prefetch_heap_block();
return find(key, hash_ref()(key));
}
template <class K = key_type>
- const_iterator find(const key_arg<K>& key, size_t hash) const {
+ const_iterator find(const key_arg<K>& key,
+ size_t hash) const ABSL_ATTRIBUTE_LIFETIME_BOUND {
return const_cast<raw_hash_set*>(this)->find(key, hash);
}
template <class K = key_type>
- const_iterator find(const key_arg<K>& key) const {
- return find(key, hash_ref()(key));
+ const_iterator find(const key_arg<K>& key) const
+ ABSL_ATTRIBUTE_LIFETIME_BOUND {
+ return const_cast<raw_hash_set*>(this)->find(key);
}
template <class K = key_type>
bool contains(const key_arg<K>& key) const {
- return find(key) != end();
+ // Here neither the iterator returned by `find()` nor `end()` can be invalid
+ // outside of potential thread-safety issues.
+ // `find()`'s return value is constructed, used, and then destructed
+ // all in this context.
+ return !find(key).unchecked_equals(end());
}
template <class K = key_type>
- std::pair<iterator, iterator> equal_range(const key_arg<K>& key) {
+ std::pair<iterator, iterator> equal_range(const key_arg<K>& key)
+ ABSL_ATTRIBUTE_LIFETIME_BOUND {
auto it = find(key);
if (it != end()) return {it, std::next(it)};
return {it, it};
}
template <class K = key_type>
std::pair<const_iterator, const_iterator> equal_range(
- const key_arg<K>& key) const {
+ const key_arg<K>& key) const ABSL_ATTRIBUTE_LIFETIME_BOUND {
auto it = find(key);
if (it != end()) return {it, std::next(it)};
return {it, it};
}
- size_t bucket_count() const { return capacity_; }
+ size_t bucket_count() const { return capacity(); }
float load_factor() const {
- return capacity_ ? static_cast<double>(size()) / capacity_ : 0.0;
+ return capacity() ? static_cast<double>(size()) / capacity() : 0.0;
}
float max_load_factor() const { return 1.0f; }
void max_load_factor(float) {
@@ -1497,8 +2911,10 @@
const raw_hash_set* outer = &a;
const raw_hash_set* inner = &b;
if (outer->capacity() > inner->capacity()) std::swap(outer, inner);
- for (const value_type& elem : *outer)
- if (!inner->has_element(elem)) return false;
+ for (const value_type& elem : *outer) {
+ auto it = PolicyTraits::apply(FindElement{*inner}, elem);
+ if (it == inner->end() || !(*it == elem)) return false;
+ }
return true;
}
@@ -1506,6 +2922,14 @@
return !(a == b);
}
+ template <typename H>
+ friend typename std::enable_if<H::template is_hashable<value_type>::value,
+ H>::type
+ AbslHashValue(H h, const raw_hash_set& s) {
+ return H::combine(H::combine_unordered(std::move(h), s.begin(), s.end()),
+ s.size());
+ }
+
friend void swap(raw_hash_set& a,
raw_hash_set& b) noexcept(noexcept(a.swap(b))) {
a.swap(b);
@@ -1549,7 +2973,7 @@
if (res.second) {
s.emplace_at(res.first, std::forward<Args>(args)...);
}
- return {s.iterator_at(res.first), res.second};
+ return res;
}
raw_hash_set& s;
};
@@ -1560,193 +2984,143 @@
std::pair<iterator, bool> operator()(const K& key, Args&&...) && {
auto res = s.find_or_prepare_insert(key);
if (res.second) {
- PolicyTraits::transfer(&s.alloc_ref(), s.slots_ + res.first, &slot);
+ s.transfer(res.first.slot(), &slot);
} else if (do_destroy) {
- PolicyTraits::destroy(&s.alloc_ref(), &slot);
+ s.destroy(&slot);
}
- return {s.iterator_at(res.first), res.second};
+ return res;
}
raw_hash_set& s;
// Constructed slot. Either moved into place or destroyed.
slot_type&& slot;
};
- // "erases" the object from the container, except that it doesn't actually
- // destroy the object. It only updates all the metadata of the class.
- // This can be used in conjunction with Policy::transfer to move the object to
- // another place.
- void erase_meta_only(const_iterator it) {
- assert(IsFull(*it.inner_.ctrl_) && "erasing a dangling iterator");
- --size_;
- const size_t index = it.inner_.ctrl_ - ctrl_;
- const size_t index_before = (index - Group::kWidth) & capacity_;
- const auto empty_after = Group(it.inner_.ctrl_).MatchEmpty();
- const auto empty_before = Group(ctrl_ + index_before).MatchEmpty();
-
- // We count how many consecutive non empties we have to the right and to the
- // left of `it`. If the sum is >= kWidth then there is at least one probe
- // window that might have seen a full group.
- bool was_never_full =
- empty_before && empty_after &&
- static_cast<size_t>(empty_after.TrailingZeros() +
- empty_before.LeadingZeros()) < Group::kWidth;
-
- SetCtrl(index, was_never_full ? ctrl_t::kEmpty : ctrl_t::kDeleted,
- capacity_, ctrl_, slots_, sizeof(slot_type));
- growth_left() += was_never_full;
- infoz().RecordErase();
+ // TODO(b/303305702): re-enable reentrant validation.
+ template <typename... Args>
+ inline void construct(slot_type* slot, Args&&... args) {
+ PolicyTraits::construct(&alloc_ref(), slot, std::forward<Args>(args)...);
+ }
+ inline void destroy(slot_type* slot) {
+ PolicyTraits::destroy(&alloc_ref(), slot);
+ }
+ inline void transfer(slot_type* to, slot_type* from) {
+ PolicyTraits::transfer(&alloc_ref(), to, from);
}
- void initialize_slots() {
- assert(capacity_);
- // Folks with custom allocators often make unwarranted assumptions about the
- // behavior of their classes vis-a-vis trivial destructability and what
- // calls they will or wont make. Avoid sampling for people with custom
- // allocators to get us out of this mess. This is not a hard guarantee but
- // a workaround while we plan the exact guarantee we want to provide.
- //
- // People are often sloppy with the exact type of their allocator (sometimes
- // it has an extra const or is missing the pair, but rebinds made it work
- // anyway). To avoid the ambiguity, we work off SlotAlloc which we have
- // bound more carefully.
- if (std::is_same<SlotAlloc, std::allocator<slot_type>>::value &&
- slots_ == nullptr) {
- infoz() = Sample();
- }
-
- auto layout = MakeLayout(capacity_);
- char* mem = static_cast<char*>(
- Allocate<Layout::Alignment()>(&alloc_ref(), layout.AllocSize()));
- ctrl_ = layout.template Pointer<0>(mem);
- slots_ = layout.template Pointer<1>(mem);
- ResetCtrl(capacity_, ctrl_, slots_, sizeof(slot_type));
- reset_growth_left();
- infoz().RecordStorageChanged(size_, capacity_);
+ inline void destroy_slots() {
+ if (PolicyTraits::template destroy_is_trivial<Alloc>()) return;
+ IterateOverFullSlots(
+ common(), slot_array(),
+ [&](const ctrl_t*, slot_type* slot)
+ ABSL_ATTRIBUTE_ALWAYS_INLINE { this->destroy(slot); });
}
- void destroy_slots() {
- if (!capacity_) return;
- for (size_t i = 0; i != capacity_; ++i) {
- if (IsFull(ctrl_[i])) {
- PolicyTraits::destroy(&alloc_ref(), slots_ + i);
- }
- }
- auto layout = MakeLayout(capacity_);
+ inline void dealloc() {
+ assert(capacity() != 0);
// Unpoison before returning the memory to the allocator.
- SanitizerUnpoisonMemoryRegion(slots_, sizeof(slot_type) * capacity_);
- Deallocate<Layout::Alignment()>(&alloc_ref(), ctrl_, layout.AllocSize());
- ctrl_ = EmptyGroup();
- slots_ = nullptr;
- size_ = 0;
- capacity_ = 0;
- growth_left() = 0;
+ SanitizerUnpoisonMemoryRegion(slot_array(), sizeof(slot_type) * capacity());
+ infoz().Unregister();
+ Deallocate<BackingArrayAlignment(alignof(slot_type))>(
+ &alloc_ref(), common().backing_array_start(),
+ common().alloc_size(sizeof(slot_type), alignof(slot_type)));
}
- void resize(size_t new_capacity) {
+ inline void destructor_impl() {
+ if (capacity() == 0) return;
+ destroy_slots();
+ dealloc();
+ }
+
+ // Erases, but does not destroy, the value pointed to by `it`.
+ //
+ // This merely updates the pertinent control byte. This can be used in
+ // conjunction with Policy::transfer to move the object to another place.
+ void erase_meta_only(const_iterator it) {
+ EraseMetaOnly(common(), static_cast<size_t>(it.control() - control()),
+ sizeof(slot_type));
+ }
+
+ // Resizes table to the new capacity and move all elements to the new
+ // positions accordingly.
+ //
+ // Note that for better performance instead of
+ // find_first_non_full(common(), hash),
+ // HashSetResizeHelper::FindFirstNonFullAfterResize(
+ // common(), old_capacity, hash)
+ // can be called right after `resize`.
+ ABSL_ATTRIBUTE_NOINLINE void resize(size_t new_capacity) {
assert(IsValidCapacity(new_capacity));
- auto* old_ctrl = ctrl_;
- auto* old_slots = slots_;
- const size_t old_capacity = capacity_;
- capacity_ = new_capacity;
- initialize_slots();
+ HashSetResizeHelper resize_helper(common());
+ auto* old_slots = slot_array();
+ common().set_capacity(new_capacity);
+ // Note that `InitializeSlots` does different number initialization steps
+ // depending on the values of `transfer_uses_memcpy` and capacities.
+ // Refer to the comment in `InitializeSlots` for more details.
+ const bool grow_single_group =
+ resize_helper.InitializeSlots<CharAlloc, sizeof(slot_type),
+ PolicyTraits::transfer_uses_memcpy(),
+ alignof(slot_type)>(
+ common(), const_cast<std::remove_const_t<slot_type>*>(old_slots),
+ CharAlloc(alloc_ref()));
- size_t total_probe_length = 0;
- for (size_t i = 0; i != old_capacity; ++i) {
- if (IsFull(old_ctrl[i])) {
- size_t hash = PolicyTraits::apply(HashElement{hash_ref()},
- PolicyTraits::element(old_slots + i));
- auto target = find_first_non_full(ctrl_, hash, capacity_);
- size_t new_i = target.offset;
- total_probe_length += target.probe_length;
- SetCtrl(new_i, H2(hash), capacity_, ctrl_, slots_, sizeof(slot_type));
- PolicyTraits::transfer(&alloc_ref(), slots_ + new_i, old_slots + i);
+ if (resize_helper.old_capacity() == 0) {
+ // InitializeSlots did all the work including infoz().RecordRehash().
+ return;
+ }
+
+ if (grow_single_group) {
+ if (PolicyTraits::transfer_uses_memcpy()) {
+ // InitializeSlots did all the work.
+ return;
}
+ // We want GrowSizeIntoSingleGroup to be called here in order to make
+ // InitializeSlots not depend on PolicyTraits.
+ resize_helper.GrowSizeIntoSingleGroup<PolicyTraits>(common(), alloc_ref(),
+ old_slots);
+ } else {
+ // InitializeSlots prepares control bytes to correspond to empty table.
+ auto* new_slots = slot_array();
+ size_t total_probe_length = 0;
+ for (size_t i = 0; i != resize_helper.old_capacity(); ++i) {
+ if (IsFull(resize_helper.old_ctrl()[i])) {
+ size_t hash = PolicyTraits::apply(
+ HashElement{hash_ref()}, PolicyTraits::element(old_slots + i));
+ auto target = find_first_non_full(common(), hash);
+ size_t new_i = target.offset;
+ total_probe_length += target.probe_length;
+ SetCtrl(common(), new_i, H2(hash), sizeof(slot_type));
+ transfer(new_slots + new_i, old_slots + i);
+ }
+ }
+ infoz().RecordRehash(total_probe_length);
}
- if (old_capacity) {
- SanitizerUnpoisonMemoryRegion(old_slots,
- sizeof(slot_type) * old_capacity);
- auto layout = MakeLayout(old_capacity);
- Deallocate<Layout::Alignment()>(&alloc_ref(), old_ctrl,
- layout.AllocSize());
- }
- infoz().RecordRehash(total_probe_length);
+ resize_helper.DeallocateOld<alignof(slot_type)>(
+ CharAlloc(alloc_ref()), sizeof(slot_type),
+ const_cast<std::remove_const_t<slot_type>*>(old_slots));
}
- void drop_deletes_without_resize() ABSL_ATTRIBUTE_NOINLINE {
- assert(IsValidCapacity(capacity_));
- assert(!is_small(capacity_));
- // Algorithm:
- // - mark all DELETED slots as EMPTY
- // - mark all FULL slots as DELETED
- // - for each slot marked as DELETED
- // hash = Hash(element)
- // target = find_first_non_full(hash)
- // if target is in the same group
- // mark slot as FULL
- // else if target is EMPTY
- // transfer element to target
- // mark slot as EMPTY
- // mark target as FULL
- // else if target is DELETED
- // swap current element with target element
- // mark target as FULL
- // repeat procedure for current slot with moved from element (target)
- ConvertDeletedToEmptyAndFullToDeleted(ctrl_, capacity_);
- alignas(slot_type) unsigned char raw[sizeof(slot_type)];
- size_t total_probe_length = 0;
- slot_type* slot = reinterpret_cast<slot_type*>(&raw);
- for (size_t i = 0; i != capacity_; ++i) {
- if (!IsDeleted(ctrl_[i])) continue;
- const size_t hash = PolicyTraits::apply(
- HashElement{hash_ref()}, PolicyTraits::element(slots_ + i));
- const FindInfo target = find_first_non_full(ctrl_, hash, capacity_);
- const size_t new_i = target.offset;
- total_probe_length += target.probe_length;
-
- // Verify if the old and new i fall within the same group wrt the hash.
- // If they do, we don't need to move the object as it falls already in the
- // best probe we can.
- const size_t probe_offset = probe(ctrl_, hash, capacity_).offset();
- const auto probe_index = [probe_offset, this](size_t pos) {
- return ((pos - probe_offset) & capacity_) / Group::kWidth;
- };
-
- // Element doesn't move.
- if (ABSL_PREDICT_TRUE(probe_index(new_i) == probe_index(i))) {
- SetCtrl(i, H2(hash), capacity_, ctrl_, slots_, sizeof(slot_type));
- continue;
- }
- if (IsEmpty(ctrl_[new_i])) {
- // Transfer element to the empty spot.
- // SetCtrl poisons/unpoisons the slots so we have to call it at the
- // right time.
- SetCtrl(new_i, H2(hash), capacity_, ctrl_, slots_, sizeof(slot_type));
- PolicyTraits::transfer(&alloc_ref(), slots_ + new_i, slots_ + i);
- SetCtrl(i, ctrl_t::kEmpty, capacity_, ctrl_, slots_, sizeof(slot_type));
- } else {
- assert(IsDeleted(ctrl_[new_i]));
- SetCtrl(new_i, H2(hash), capacity_, ctrl_, slots_, sizeof(slot_type));
- // Until we are done rehashing, DELETED marks previously FULL slots.
- // Swap i and new_i elements.
- PolicyTraits::transfer(&alloc_ref(), slot, slots_ + i);
- PolicyTraits::transfer(&alloc_ref(), slots_ + i, slots_ + new_i);
- PolicyTraits::transfer(&alloc_ref(), slots_ + new_i, slot);
- --i; // repeat
- }
- }
- reset_growth_left();
- infoz().RecordRehash(total_probe_length);
+ // Prunes control bytes to remove as many tombstones as possible.
+ //
+ // See the comment on `rehash_and_grow_if_necessary()`.
+ inline void drop_deletes_without_resize() {
+ // Stack-allocate space for swapping elements.
+ alignas(slot_type) unsigned char tmp[sizeof(slot_type)];
+ DropDeletesWithoutResize(common(), &hash_ref(), GetPolicyFunctions(), tmp);
}
+ // Called whenever the table *might* need to conditionally grow.
+ //
+ // This function is an optimization opportunity to perform a rehash even when
+ // growth is unnecessary, because vacating tombstones is beneficial for
+ // performance in the long-run.
void rehash_and_grow_if_necessary() {
- if (capacity_ == 0) {
- resize(1);
- } else if (capacity_ > Group::kWidth &&
- // Do these calcuations in 64-bit to avoid overflow.
- size() * uint64_t{32} <= capacity_ * uint64_t{25}) {
+ const size_t cap = capacity();
+ if (cap > Group::kWidth &&
+ // Do these calculations in 64-bit to avoid overflow.
+ size() * uint64_t{32} <= cap * uint64_t{25}) {
// Squash DELETED without growing if there is enough capacity.
//
- // Rehash in place if the current size is <= 25/32 of capacity_.
+ // Rehash in place if the current size is <= 25/32 of capacity.
// Rationale for such a high factor: 1) drop_deletes_without_resize() is
// faster than resize, and 2) it takes quite a bit of work to add
// tombstones. In the worst case, seems to take approximately 4
@@ -1764,8 +3138,8 @@
//
// Here is output of an experiment using the BM_CacheInSteadyState
// benchmark running the old case (where we rehash-in-place only if we can
- // reclaim at least 7/16*capacity_) vs. this code (which rehashes in place
- // if we can recover 3/32*capacity_).
+ // reclaim at least 7/16*capacity) vs. this code (which rehashes in place
+ // if we can recover 3/32*capacity).
//
// Note that although in the worst-case number of rehashes jumped up from
// 15 to 190, but the number of operations per second is almost the same.
@@ -1788,70 +3162,125 @@
drop_deletes_without_resize();
} else {
// Otherwise grow the container.
- resize(capacity_ * 2 + 1);
+ resize(NextCapacity(cap));
}
}
- bool has_element(const value_type& elem) const {
- size_t hash = PolicyTraits::apply(HashElement{hash_ref()}, elem);
- auto seq = probe(ctrl_, hash, capacity_);
- while (true) {
- Group g{ctrl_ + seq.offset()};
- for (int i : g.Match(H2(hash))) {
- if (ABSL_PREDICT_TRUE(PolicyTraits::element(slots_ + seq.offset(i)) ==
- elem))
- return true;
- }
- if (ABSL_PREDICT_TRUE(g.MatchEmpty())) return false;
- seq.next();
- assert(seq.index() <= capacity_ && "full table!");
+ void maybe_increment_generation_or_rehash_on_move() {
+ common().maybe_increment_generation_on_move();
+ if (!empty() && common().should_rehash_for_bug_detection_on_move()) {
+ resize(capacity());
}
- return false;
}
- // TODO(alkis): Optimize this assuming *this and that don't overlap.
- raw_hash_set& move_assign(raw_hash_set&& that, std::true_type) {
- raw_hash_set tmp(std::move(that));
- swap(tmp);
+ template<bool propagate_alloc>
+ raw_hash_set& assign_impl(raw_hash_set&& that) {
+ // We don't bother checking for this/that aliasing. We just need to avoid
+ // breaking the invariants in that case.
+ destructor_impl();
+ common() = std::move(that.common());
+ // TODO(b/296061262): move instead of copying hash/eq/alloc.
+ hash_ref() = that.hash_ref();
+ eq_ref() = that.eq_ref();
+ CopyAlloc(alloc_ref(), that.alloc_ref(),
+ std::integral_constant<bool, propagate_alloc>());
+ that.common() = CommonFields{};
+ maybe_increment_generation_or_rehash_on_move();
return *this;
}
- raw_hash_set& move_assign(raw_hash_set&& that, std::false_type) {
- raw_hash_set tmp(std::move(that), alloc_ref());
- swap(tmp);
+
+ raw_hash_set& move_elements_allocs_unequal(raw_hash_set&& that) {
+ const size_t size = that.size();
+ if (size == 0) return *this;
+ reserve(size);
+ for (iterator it = that.begin(); it != that.end(); ++it) {
+ insert(std::move(PolicyTraits::element(it.slot())));
+ that.destroy(it.slot());
+ }
+ that.dealloc();
+ that.common() = CommonFields{};
+ maybe_increment_generation_or_rehash_on_move();
return *this;
}
+ raw_hash_set& move_assign(raw_hash_set&& that,
+ std::true_type /*propagate_alloc*/) {
+ return assign_impl<true>(std::move(that));
+ }
+ raw_hash_set& move_assign(raw_hash_set&& that,
+ std::false_type /*propagate_alloc*/) {
+ if (alloc_ref() == that.alloc_ref()) {
+ return assign_impl<false>(std::move(that));
+ }
+ // Aliasing can't happen here because allocs would compare equal above.
+ assert(this != &that);
+ destructor_impl();
+ // We can't take over that's memory so we need to move each element.
+ // While moving elements, this should have that's hash/eq so copy hash/eq
+ // before moving elements.
+ // TODO(b/296061262): move instead of copying hash/eq.
+ hash_ref() = that.hash_ref();
+ eq_ref() = that.eq_ref();
+ return move_elements_allocs_unequal(std::move(that));
+ }
+
protected:
+ // Attempts to find `key` in the table; if it isn't found, returns an iterator
+ // where the value can be inserted into, with the control byte already set to
+ // `key`'s H2. Returns a bool indicating whether an insertion can take place.
template <class K>
- std::pair<size_t, bool> find_or_prepare_insert(const K& key) {
+ std::pair<iterator, bool> find_or_prepare_insert(const K& key) {
+ prefetch_heap_block();
auto hash = hash_ref()(key);
- auto seq = probe(ctrl_, hash, capacity_);
+ auto seq = probe(common(), hash);
+ const ctrl_t* ctrl = control();
while (true) {
- Group g{ctrl_ + seq.offset()};
- for (int i : g.Match(H2(hash))) {
+ Group g{ctrl + seq.offset()};
+ for (uint32_t i : g.Match(H2(hash))) {
if (ABSL_PREDICT_TRUE(PolicyTraits::apply(
EqualElement<K>{key, eq_ref()},
- PolicyTraits::element(slots_ + seq.offset(i)))))
- return {seq.offset(i), false};
+ PolicyTraits::element(slot_array() + seq.offset(i)))))
+ return {iterator_at(seq.offset(i)), false};
}
- if (ABSL_PREDICT_TRUE(g.MatchEmpty())) break;
+ if (ABSL_PREDICT_TRUE(g.MaskEmpty())) break;
seq.next();
- assert(seq.index() <= capacity_ && "full table!");
+ assert(seq.index() <= capacity() && "full table!");
}
- return {prepare_insert(hash), true};
+ return {iterator_at(prepare_insert(hash)), true};
}
+ // Given the hash of a value not currently in the table, finds the next
+ // viable slot index to insert it at.
+ //
+ // REQUIRES: At least one non-full slot available.
size_t prepare_insert(size_t hash) ABSL_ATTRIBUTE_NOINLINE {
- auto target = find_first_non_full(ctrl_, hash, capacity_);
- if (ABSL_PREDICT_FALSE(growth_left() == 0 &&
- !IsDeleted(ctrl_[target.offset]))) {
- rehash_and_grow_if_necessary();
- target = find_first_non_full(ctrl_, hash, capacity_);
+ const bool rehash_for_bug_detection =
+ common().should_rehash_for_bug_detection_on_insert();
+ if (rehash_for_bug_detection) {
+ // Move to a different heap allocation in order to detect bugs.
+ const size_t cap = capacity();
+ resize(growth_left() > 0 ? cap : NextCapacity(cap));
}
- ++size_;
- growth_left() -= IsEmpty(ctrl_[target.offset]);
- SetCtrl(target.offset, H2(hash), capacity_, ctrl_, slots_,
- sizeof(slot_type));
+ FindInfo target;
+ if (!rehash_for_bug_detection && ABSL_PREDICT_FALSE(growth_left() == 0)) {
+ const size_t old_capacity = capacity();
+ rehash_and_grow_if_necessary();
+ // NOTE: It is safe to use `FindFirstNonFullAfterResize` after
+ // `rehash_and_grow_if_necessary`, whether capacity changes or not.
+ // `rehash_and_grow_if_necessary` may *not* call `resize`
+ // and perform `drop_deletes_without_resize` instead. But this
+ // could happen only on big tables and will not change capacity.
+ // For big tables `FindFirstNonFullAfterResize` will always
+ // fallback to normal `find_first_non_full`.
+ target = HashSetResizeHelper::FindFirstNonFullAfterResize(
+ common(), old_capacity, hash);
+ } else {
+ target = find_first_non_full(common(), hash);
+ }
+ common().increment_size();
+ set_growth_left(growth_left() - IsEmpty(control()[target.offset]));
+ SetCtrl(common(), target.offset, H2(hash), sizeof(slot_type));
+ common().maybe_increment_generation_on_insert();
infoz().RecordInsert(hash, target.probe_length);
return target.offset;
}
@@ -1860,66 +3289,123 @@
// after an unsuccessful find_or_prepare_insert() and before any other
// modifications happen in the raw_hash_set.
//
- // PRECONDITION: i is an index returned from find_or_prepare_insert(k), where
- // k is the key decomposed from `forward<Args>(args)...`, and the bool
- // returned by find_or_prepare_insert(k) was true.
+ // PRECONDITION: iter was returned from find_or_prepare_insert(k), where k is
+ // the key decomposed from `forward<Args>(args)...`, and the bool returned by
+ // find_or_prepare_insert(k) was true.
// POSTCONDITION: *m.iterator_at(i) == value_type(forward<Args>(args)...).
template <class... Args>
- void emplace_at(size_t i, Args&&... args) {
- PolicyTraits::construct(&alloc_ref(), slots_ + i,
- std::forward<Args>(args)...);
+ void emplace_at(iterator iter, Args&&... args) {
+ construct(iter.slot(), std::forward<Args>(args)...);
- assert(PolicyTraits::apply(FindElement{*this}, *iterator_at(i)) ==
- iterator_at(i) &&
+ assert(PolicyTraits::apply(FindElement{*this}, *iter) == iter &&
"constructed value does not match the lookup key");
}
- iterator iterator_at(size_t i) { return {ctrl_ + i, slots_ + i}; }
- const_iterator iterator_at(size_t i) const { return {ctrl_ + i, slots_ + i}; }
+ iterator iterator_at(size_t i) ABSL_ATTRIBUTE_LIFETIME_BOUND {
+ return {control() + i, slot_array() + i, common().generation_ptr()};
+ }
+ const_iterator iterator_at(size_t i) const ABSL_ATTRIBUTE_LIFETIME_BOUND {
+ return {control() + i, slot_array() + i, common().generation_ptr()};
+ }
+
+ reference unchecked_deref(iterator it) { return it.unchecked_deref(); }
private:
friend struct RawHashSetTestOnlyAccess;
- void reset_growth_left() {
- growth_left() = CapacityToGrowth(capacity()) - size_;
+ // The number of slots we can still fill without needing to rehash.
+ //
+ // This is stored separately due to tombstones: we do not include tombstones
+ // in the growth capacity, because we'd like to rehash when the table is
+ // otherwise filled with tombstones: otherwise, probe sequences might get
+ // unacceptably long without triggering a rehash. Callers can also force a
+ // rehash via the standard `rehash(0)`, which will recompute this value as a
+ // side-effect.
+ //
+ // See `CapacityToGrowth()`.
+ size_t growth_left() const { return common().growth_left(); }
+ void set_growth_left(size_t gl) { return common().set_growth_left(gl); }
+
+ // Prefetch the heap-allocated memory region to resolve potential TLB and
+ // cache misses. This is intended to overlap with execution of calculating the
+ // hash for a key.
+ void prefetch_heap_block() const {
+#if ABSL_HAVE_BUILTIN(__builtin_prefetch) || defined(__GNUC__)
+ __builtin_prefetch(control(), 0, 1);
+#endif
}
- size_t& growth_left() { return settings_.template get<0>(); }
+ CommonFields& common() { return settings_.template get<0>(); }
+ const CommonFields& common() const { return settings_.template get<0>(); }
- HashtablezInfoHandle& infoz() { return settings_.template get<1>(); }
+ ctrl_t* control() const { return common().control(); }
+ slot_type* slot_array() const {
+ return static_cast<slot_type*>(common().slot_array());
+ }
+ HashtablezInfoHandle infoz() { return common().infoz(); }
- hasher& hash_ref() { return settings_.template get<2>(); }
- const hasher& hash_ref() const { return settings_.template get<2>(); }
- key_equal& eq_ref() { return settings_.template get<3>(); }
- const key_equal& eq_ref() const { return settings_.template get<3>(); }
- allocator_type& alloc_ref() { return settings_.template get<4>(); }
+ hasher& hash_ref() { return settings_.template get<1>(); }
+ const hasher& hash_ref() const { return settings_.template get<1>(); }
+ key_equal& eq_ref() { return settings_.template get<2>(); }
+ const key_equal& eq_ref() const { return settings_.template get<2>(); }
+ allocator_type& alloc_ref() { return settings_.template get<3>(); }
const allocator_type& alloc_ref() const {
- return settings_.template get<4>();
+ return settings_.template get<3>();
}
- // TODO(alkis): Investigate removing some of these fields:
- // - ctrl/slots can be derived from each other
- // - size can be moved into the slot array
- ctrl_t* ctrl_ = EmptyGroup(); // [(capacity + 1 + NumClonedBytes()) * ctrl_t]
- slot_type* slots_ = nullptr; // [capacity * slot_type]
- size_t size_ = 0; // number of full slots
- size_t capacity_ = 0; // total number of slots
- absl::container_internal::CompressedTuple<size_t /* growth_left */,
- HashtablezInfoHandle, hasher,
- key_equal, allocator_type>
- settings_{0, HashtablezInfoHandle{}, hasher{}, key_equal{},
- allocator_type{}};
+ static void transfer_slot_fn(void* set, void* dst, void* src) {
+ auto* h = static_cast<raw_hash_set*>(set);
+ h->transfer(static_cast<slot_type*>(dst), static_cast<slot_type*>(src));
+ }
+ // Note: dealloc_fn will only be used if we have a non-standard allocator.
+ static void dealloc_fn(CommonFields& common, const PolicyFunctions&) {
+ auto* set = reinterpret_cast<raw_hash_set*>(&common);
+
+ // Unpoison before returning the memory to the allocator.
+ SanitizerUnpoisonMemoryRegion(common.slot_array(),
+ sizeof(slot_type) * common.capacity());
+
+ common.infoz().Unregister();
+ Deallocate<BackingArrayAlignment(alignof(slot_type))>(
+ &set->alloc_ref(), common.backing_array_start(),
+ common.alloc_size(sizeof(slot_type), alignof(slot_type)));
+ }
+
+ static const PolicyFunctions& GetPolicyFunctions() {
+ static constexpr PolicyFunctions value = {
+ sizeof(slot_type),
+ PolicyTraits::template get_hash_slot_fn<hasher>(),
+ PolicyTraits::transfer_uses_memcpy()
+ ? TransferRelocatable<sizeof(slot_type)>
+ : &raw_hash_set::transfer_slot_fn,
+ (std::is_same<SlotAlloc, std::allocator<slot_type>>::value
+ ? &DeallocateStandard<alignof(slot_type)>
+ : &raw_hash_set::dealloc_fn),
+ };
+ return value;
+ }
+
+ // Bundle together CommonFields plus other objects which might be empty.
+ // CompressedTuple will ensure that sizeof is not affected by any of the empty
+ // fields that occur after CommonFields.
+ absl::container_internal::CompressedTuple<CommonFields, hasher, key_equal,
+ allocator_type>
+ settings_{CommonFields{}, hasher{}, key_equal{}, allocator_type{}};
};
// Erases all elements that satisfy the predicate `pred` from the container `c`.
template <typename P, typename H, typename E, typename A, typename Predicate>
-void EraseIf(Predicate pred, raw_hash_set<P, H, E, A>* c) {
+typename raw_hash_set<P, H, E, A>::size_type EraseIf(
+ Predicate& pred, raw_hash_set<P, H, E, A>* c) {
+ const auto initial_size = c->size();
for (auto it = c->begin(), last = c->end(); it != last;) {
- auto copy_it = it++;
- if (pred(*copy_it)) {
- c->erase(copy_it);
+ if (pred(*it)) {
+ c->erase(it++);
+ } else {
+ ++it;
}
}
+ return initial_size - c->size();
}
namespace hashtable_debug_internal {
@@ -1932,53 +3418,39 @@
const typename Set::key_type& key) {
size_t num_probes = 0;
size_t hash = set.hash_ref()(key);
- auto seq = probe(set.ctrl_, hash, set.capacity_);
+ auto seq = probe(set.common(), hash);
+ const ctrl_t* ctrl = set.control();
while (true) {
- container_internal::Group g{set.ctrl_ + seq.offset()};
- for (int i : g.Match(container_internal::H2(hash))) {
+ container_internal::Group g{ctrl + seq.offset()};
+ for (uint32_t i : g.Match(container_internal::H2(hash))) {
if (Traits::apply(
typename Set::template EqualElement<typename Set::key_type>{
key, set.eq_ref()},
- Traits::element(set.slots_ + seq.offset(i))))
+ Traits::element(set.slot_array() + seq.offset(i))))
return num_probes;
++num_probes;
}
- if (g.MatchEmpty()) return num_probes;
+ if (g.MaskEmpty()) return num_probes;
seq.next();
++num_probes;
}
}
static size_t AllocatedByteSize(const Set& c) {
- size_t capacity = c.capacity_;
+ size_t capacity = c.capacity();
if (capacity == 0) return 0;
- auto layout = Set::MakeLayout(capacity);
- size_t m = layout.AllocSize();
+ size_t m = c.common().alloc_size(sizeof(Slot), alignof(Slot));
size_t per_slot = Traits::space_used(static_cast<const Slot*>(nullptr));
if (per_slot != ~size_t{}) {
m += per_slot * c.size();
} else {
- for (size_t i = 0; i != capacity; ++i) {
- if (container_internal::IsFull(c.ctrl_[i])) {
- m += Traits::space_used(c.slots_ + i);
- }
+ for (auto it = c.begin(); it != c.end(); ++it) {
+ m += Traits::space_used(it.slot());
}
}
return m;
}
-
- static size_t LowerBoundAllocatedByteSize(size_t size) {
- size_t capacity = GrowthToLowerboundCapacity(size);
- if (capacity == 0) return 0;
- auto layout = Set::MakeLayout(NormalizeCapacity(capacity));
- size_t m = layout.AllocSize();
- size_t per_slot = Traits::space_used(static_cast<const Slot*>(nullptr));
- if (per_slot != ~size_t{}) {
- m += per_slot * size;
- }
- return m;
- }
};
} // namespace hashtable_debug_internal
@@ -1986,4 +3458,6 @@
ABSL_NAMESPACE_END
} // namespace absl
+#undef ABSL_SWISSTABLE_ENABLE_GENERATIONS
+
#endif // ABSL_CONTAINER_INTERNAL_RAW_HASH_SET_H_
diff --git a/absl/container/internal/raw_hash_set_allocator_test.cc b/absl/container/internal/raw_hash_set_allocator_test.cc
index e73f53f..7e7a506 100644
--- a/absl/container/internal/raw_hash_set_allocator_test.cc
+++ b/absl/container/internal/raw_hash_set_allocator_test.cc
@@ -12,10 +12,20 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+#include <cstddef>
+#include <cstdint>
+#include <functional>
#include <limits>
-#include <scoped_allocator>
+#include <memory>
+#include <ostream>
+#include <set>
+#include <type_traits>
+#include <utility>
+#include "gmock/gmock.h"
#include "gtest/gtest.h"
+#include "absl/base/config.h"
+#include "absl/container/internal/container_memory.h"
#include "absl/container/internal/raw_hash_set.h"
#include "absl/container/internal/tracked.h"
@@ -23,6 +33,7 @@
ABSL_NAMESPACE_BEGIN
namespace container_internal {
namespace {
+using ::testing::AnyOf;
enum AllocSpec {
kPropagateOnCopy = 1,
@@ -123,7 +134,7 @@
};
struct Identity {
- int32_t operator()(int32_t v) const { return v; }
+ size_t operator()(int32_t v) const { return static_cast<size_t>(v); }
};
struct Policy {
@@ -168,6 +179,11 @@
}
static slot_type& element(slot_type* slot) { return *slot; }
+
+ template <class Hash>
+ static constexpr HashSlotFn get_hash_slot_fn() {
+ return nullptr;
+ }
};
template <int Spec>
@@ -276,34 +292,38 @@
}
TEST_F(PropagateOnAll, MoveConstructor) {
- auto it = t1.insert(0).first;
+ t1.insert(0);
Table u(std::move(t1));
- EXPECT_EQ(1, a1.num_allocs());
- EXPECT_EQ(0, it->num_moves());
+ auto it = u.begin();
+ EXPECT_THAT(a1.num_allocs(), AnyOf(1, 2));
+ EXPECT_THAT(it->num_moves(), AnyOf(0, 1));
EXPECT_EQ(0, it->num_copies());
}
TEST_F(NoPropagateOnMove, MoveConstructor) {
- auto it = t1.insert(0).first;
+ t1.insert(0);
Table u(std::move(t1));
- EXPECT_EQ(1, a1.num_allocs());
- EXPECT_EQ(0, it->num_moves());
+ auto it = u.begin();
+ EXPECT_THAT(a1.num_allocs(), AnyOf(1, 2));
+ EXPECT_THAT(it->num_moves(), AnyOf(0, 1));
EXPECT_EQ(0, it->num_copies());
}
TEST_F(PropagateOnAll, MoveConstructorWithSameAlloc) {
- auto it = t1.insert(0).first;
+ t1.insert(0);
Table u(std::move(t1), a1);
- EXPECT_EQ(1, a1.num_allocs());
- EXPECT_EQ(0, it->num_moves());
+ auto it = u.begin();
+ EXPECT_THAT(a1.num_allocs(), AnyOf(1, 2));
+ EXPECT_THAT(it->num_moves(), AnyOf(0, 1));
EXPECT_EQ(0, it->num_copies());
}
TEST_F(NoPropagateOnMove, MoveConstructorWithSameAlloc) {
- auto it = t1.insert(0).first;
+ t1.insert(0);
Table u(std::move(t1), a1);
- EXPECT_EQ(1, a1.num_allocs());
- EXPECT_EQ(0, it->num_moves());
+ auto it = u.begin();
+ EXPECT_THAT(a1.num_allocs(), AnyOf(1, 2));
+ EXPECT_THAT(it->num_moves(), AnyOf(0, 1));
EXPECT_EQ(0, it->num_copies());
}
@@ -313,8 +333,8 @@
it = u.find(0);
EXPECT_EQ(a2, u.get_allocator());
EXPECT_EQ(1, a1.num_allocs());
- EXPECT_EQ(1, a2.num_allocs());
- EXPECT_EQ(1, it->num_moves());
+ EXPECT_THAT(a2.num_allocs(), AnyOf(1, 2));
+ EXPECT_THAT(it->num_moves(), AnyOf(1, 2));
EXPECT_EQ(0, it->num_copies());
}
@@ -324,8 +344,8 @@
it = u.find(0);
EXPECT_EQ(a2, u.get_allocator());
EXPECT_EQ(1, a1.num_allocs());
- EXPECT_EQ(1, a2.num_allocs());
- EXPECT_EQ(1, it->num_moves());
+ EXPECT_THAT(a2.num_allocs(), AnyOf(1, 2));
+ EXPECT_THAT(it->num_moves(), AnyOf(1, 2));
EXPECT_EQ(0, it->num_copies());
}
@@ -333,8 +353,8 @@
auto it = t1.insert(0).first;
Table u(0, a1);
u = t1;
- EXPECT_EQ(2, a1.num_allocs());
- EXPECT_EQ(0, it->num_moves());
+ EXPECT_THAT(a1.num_allocs(), AnyOf(2, 3));
+ EXPECT_THAT(it->num_moves(), AnyOf(0, 1));
EXPECT_EQ(1, it->num_copies());
}
@@ -342,8 +362,8 @@
auto it = t1.insert(0).first;
Table u(0, a1);
u = t1;
- EXPECT_EQ(2, a1.num_allocs());
- EXPECT_EQ(0, it->num_moves());
+ EXPECT_THAT(a1.num_allocs(), AnyOf(2, 3));
+ EXPECT_THAT(it->num_moves(), AnyOf(0, 1));
EXPECT_EQ(1, it->num_copies());
}
@@ -352,9 +372,9 @@
Table u(0, a2);
u = t1;
EXPECT_EQ(a1, u.get_allocator());
- EXPECT_EQ(2, a1.num_allocs());
+ EXPECT_THAT(a1.num_allocs(), AnyOf(2, 3));
EXPECT_EQ(0, a2.num_allocs());
- EXPECT_EQ(0, it->num_moves());
+ EXPECT_THAT(it->num_moves(), AnyOf(0, 1));
EXPECT_EQ(1, it->num_copies());
}
@@ -364,51 +384,54 @@
u = t1;
EXPECT_EQ(a2, u.get_allocator());
EXPECT_EQ(1, a1.num_allocs());
- EXPECT_EQ(1, a2.num_allocs());
- EXPECT_EQ(0, it->num_moves());
+ EXPECT_THAT(a2.num_allocs(), AnyOf(1, 2));
+ EXPECT_THAT(it->num_moves(), AnyOf(0, 1));
EXPECT_EQ(1, it->num_copies());
}
TEST_F(PropagateOnAll, MoveAssignmentWithSameAlloc) {
- auto it = t1.insert(0).first;
+ t1.insert(0);
Table u(0, a1);
u = std::move(t1);
+ auto it = u.begin();
EXPECT_EQ(a1, u.get_allocator());
- EXPECT_EQ(1, a1.num_allocs());
- EXPECT_EQ(0, it->num_moves());
+ EXPECT_THAT(a1.num_allocs(), AnyOf(1, 2));
+ EXPECT_THAT(it->num_moves(), AnyOf(0, 1));
EXPECT_EQ(0, it->num_copies());
}
TEST_F(NoPropagateOnMove, MoveAssignmentWithSameAlloc) {
- auto it = t1.insert(0).first;
+ t1.insert(0);
Table u(0, a1);
u = std::move(t1);
+ auto it = u.begin();
EXPECT_EQ(a1, u.get_allocator());
- EXPECT_EQ(1, a1.num_allocs());
- EXPECT_EQ(0, it->num_moves());
+ EXPECT_THAT(a1.num_allocs(), AnyOf(1, 2));
+ EXPECT_THAT(it->num_moves(), AnyOf(0, 1));
EXPECT_EQ(0, it->num_copies());
}
TEST_F(PropagateOnAll, MoveAssignmentWithDifferentAlloc) {
- auto it = t1.insert(0).first;
+ t1.insert(0);
Table u(0, a2);
u = std::move(t1);
+ auto it = u.begin();
EXPECT_EQ(a1, u.get_allocator());
- EXPECT_EQ(1, a1.num_allocs());
+ EXPECT_THAT(a1.num_allocs(), AnyOf(1, 2));
EXPECT_EQ(0, a2.num_allocs());
- EXPECT_EQ(0, it->num_moves());
+ EXPECT_THAT(it->num_moves(), AnyOf(0, 1));
EXPECT_EQ(0, it->num_copies());
}
TEST_F(NoPropagateOnMove, MoveAssignmentWithDifferentAlloc) {
- auto it = t1.insert(0).first;
+ t1.insert(0);
Table u(0, a2);
u = std::move(t1);
- it = u.find(0);
+ auto it = u.find(0);
EXPECT_EQ(a2, u.get_allocator());
EXPECT_EQ(1, a1.num_allocs());
- EXPECT_EQ(1, a2.num_allocs());
- EXPECT_EQ(1, it->num_moves());
+ EXPECT_THAT(a2.num_allocs(), AnyOf(1, 2));
+ EXPECT_THAT(it->num_moves(), AnyOf(1, 2));
EXPECT_EQ(0, it->num_copies());
}
@@ -435,9 +458,6 @@
// types
using value_type = T;
- // traits
- using propagate_on_container_swap = std::false_type;
-
PAlloc() noexcept = default;
explicit PAlloc(size_t id) noexcept : id_(id) {}
PAlloc(const PAlloc&) noexcept = default;
@@ -466,21 +486,7 @@
size_t id_ = std::numeric_limits<size_t>::max();
};
-// This doesn't compile with GCC 5.4 and 5.5 due to a bug in noexcept handing.
-#if !defined(__GNUC__) || __GNUC__ != 5 || (__GNUC_MINOR__ != 4 && \
- __GNUC_MINOR__ != 5)
-TEST(NoPropagateOn, Swap) {
- using PA = PAlloc<char>;
- using Table = raw_hash_set<Policy, Identity, std::equal_to<int32_t>, PA>;
-
- Table t1(PA{1}), t2(PA{2});
- swap(t1, t2);
- EXPECT_EQ(t1.get_allocator(), PA(1));
- EXPECT_EQ(t2.get_allocator(), PA(2));
-}
-#endif
-
-TEST(NoPropagateOn, CopyConstruct) {
+TEST(NoPropagateDeletedAssignment, CopyConstruct) {
using PA = PAlloc<char>;
using Table = raw_hash_set<Policy, Identity, std::equal_to<int32_t>, PA>;
@@ -489,7 +495,7 @@
EXPECT_EQ(t2.get_allocator(), PA());
}
-TEST(NoPropagateOn, Assignment) {
+TEST(NoPropagateDeletedAssignment, CopyAssignment) {
using PA = PAlloc<char>;
using Table = raw_hash_set<Policy, Identity, std::equal_to<int32_t>, PA>;
@@ -499,6 +505,15 @@
EXPECT_EQ(t2.get_allocator(), PA(2));
}
+TEST(NoPropagateDeletedAssignment, MoveAssignment) {
+ using PA = PAlloc<char>;
+ using Table = raw_hash_set<Policy, Identity, std::equal_to<int32_t>, PA>;
+
+ Table t1(PA{1}), t2(PA{2});
+ t1 = std::move(t2);
+ EXPECT_EQ(t1.get_allocator(), PA(1));
+}
+
} // namespace
} // namespace container_internal
ABSL_NAMESPACE_END
diff --git a/absl/container/internal/raw_hash_set_benchmark.cc b/absl/container/internal/raw_hash_set_benchmark.cc
index c886d3a..0fa9c71 100644
--- a/absl/container/internal/raw_hash_set_benchmark.cc
+++ b/absl/container/internal/raw_hash_set_benchmark.cc
@@ -12,13 +12,21 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#include "absl/container/internal/raw_hash_set.h"
-
+#include <array>
+#include <cmath>
+#include <cstddef>
+#include <cstdint>
#include <numeric>
#include <random>
+#include <string>
+#include <tuple>
+#include <utility>
+#include <vector>
#include "absl/base/internal/raw_logging.h"
+#include "absl/container/internal/container_memory.h"
#include "absl/container/internal/hash_function_defaults.h"
+#include "absl/container/internal/raw_hash_set.h"
#include "absl/strings/str_format.h"
#include "benchmark/benchmark.h"
@@ -52,6 +60,11 @@
static auto apply(F&& f, int64_t x) -> decltype(std::forward<F>(f)(x, x)) {
return std::forward<F>(f)(x, x);
}
+
+ template <class Hash>
+ static constexpr HashSlotFn get_hash_slot_fn() {
+ return nullptr;
+ }
};
class StringPolicy {
@@ -110,6 +123,11 @@
return apply_impl(std::forward<F>(f),
PairArgs(std::forward<Args>(args)...));
}
+
+ template <class Hash>
+ static constexpr HashSlotFn get_hash_slot_fn() {
+ return nullptr;
+ }
};
struct StringHash : container_internal::hash_default_hash<absl::string_view> {
@@ -138,7 +156,7 @@
template <class RNG>
std::string operator()(RNG& rng) const {
std::string res;
- res.resize(12);
+ res.resize(size);
std::uniform_int_distribution<uint32_t> printable_ascii(0x20, 0x7E);
std::generate(res.begin(), res.end(), [&] { return printable_ascii(rng); });
return res;
@@ -201,41 +219,130 @@
}
BENCHMARK(BM_CacheInSteadyState)->Apply(CacheInSteadyStateArgs);
+void BM_EraseEmplace(benchmark::State& state) {
+ IntTable t;
+ int64_t size = state.range(0);
+ for (int64_t i = 0; i < size; ++i) {
+ t.emplace(i);
+ }
+ while (state.KeepRunningBatch(size)) {
+ for (int64_t i = 0; i < size; ++i) {
+ benchmark::DoNotOptimize(t);
+ t.erase(i);
+ t.emplace(i);
+ }
+ }
+}
+BENCHMARK(BM_EraseEmplace)->Arg(1)->Arg(2)->Arg(4)->Arg(8)->Arg(16)->Arg(100);
+
void BM_EndComparison(benchmark::State& state) {
+ StringTable t = {{"a", "a"}, {"b", "b"}};
+ auto it = t.begin();
+ for (auto i : state) {
+ benchmark::DoNotOptimize(t);
+ benchmark::DoNotOptimize(it);
+ benchmark::DoNotOptimize(it != t.end());
+ }
+}
+BENCHMARK(BM_EndComparison);
+
+void BM_Iteration(benchmark::State& state) {
std::random_device rd;
std::mt19937 rng(rd());
string_generator gen{12};
StringTable t;
- while (t.size() < state.range(0)) {
+
+ size_t capacity = state.range(0);
+ size_t size = state.range(1);
+ t.reserve(capacity);
+
+ while (t.size() < size) {
t.emplace(gen(rng), gen(rng));
}
- for (auto _ : state) {
+ for (auto i : state) {
+ benchmark::DoNotOptimize(t);
for (auto it = t.begin(); it != t.end(); ++it) {
- benchmark::DoNotOptimize(it);
- benchmark::DoNotOptimize(t);
- benchmark::DoNotOptimize(it != t.end());
+ benchmark::DoNotOptimize(*it);
}
}
}
-BENCHMARK(BM_EndComparison)->Arg(400);
-void BM_CopyCtor(benchmark::State& state) {
+BENCHMARK(BM_Iteration)
+ ->ArgPair(1, 1)
+ ->ArgPair(2, 2)
+ ->ArgPair(4, 4)
+ ->ArgPair(7, 7)
+ ->ArgPair(10, 10)
+ ->ArgPair(15, 15)
+ ->ArgPair(16, 16)
+ ->ArgPair(54, 54)
+ ->ArgPair(100, 100)
+ ->ArgPair(400, 400)
+ // empty
+ ->ArgPair(0, 0)
+ ->ArgPair(10, 0)
+ ->ArgPair(100, 0)
+ ->ArgPair(1000, 0)
+ ->ArgPair(10000, 0)
+ // sparse
+ ->ArgPair(100, 1)
+ ->ArgPair(1000, 10);
+
+void BM_CopyCtorSparseInt(benchmark::State& state) {
std::random_device rd;
std::mt19937 rng(rd());
IntTable t;
std::uniform_int_distribution<uint64_t> dist(0, ~uint64_t{});
- while (t.size() < state.range(0)) {
+ size_t size = state.range(0);
+ t.reserve(size * 10);
+ while (t.size() < size) {
t.emplace(dist(rng));
}
- for (auto _ : state) {
+ for (auto i : state) {
IntTable t2 = t;
benchmark::DoNotOptimize(t2);
}
}
-BENCHMARK(BM_CopyCtor)->Range(128, 4096);
+BENCHMARK(BM_CopyCtorSparseInt)->Range(1, 4096);
+
+void BM_CopyCtorInt(benchmark::State& state) {
+ std::random_device rd;
+ std::mt19937 rng(rd());
+ IntTable t;
+ std::uniform_int_distribution<uint64_t> dist(0, ~uint64_t{});
+
+ size_t size = state.range(0);
+ while (t.size() < size) {
+ t.emplace(dist(rng));
+ }
+
+ for (auto i : state) {
+ IntTable t2 = t;
+ benchmark::DoNotOptimize(t2);
+ }
+}
+BENCHMARK(BM_CopyCtorInt)->Range(0, 4096);
+
+void BM_CopyCtorString(benchmark::State& state) {
+ std::random_device rd;
+ std::mt19937 rng(rd());
+ StringTable t;
+ std::uniform_int_distribution<uint64_t> dist(0, ~uint64_t{});
+
+ size_t size = state.range(0);
+ while (t.size() < size) {
+ t.emplace(std::to_string(dist(rng)), std::to_string(dist(rng)));
+ }
+
+ for (auto i : state) {
+ StringTable t2 = t;
+ benchmark::DoNotOptimize(t2);
+ }
+}
+BENCHMARK(BM_CopyCtorString)->Range(0, 4096);
void BM_CopyAssign(benchmark::State& state) {
std::random_device rd;
@@ -292,28 +399,42 @@
BENCHMARK(BM_NoOpReserveStringTable);
void BM_ReserveIntTable(benchmark::State& state) {
- int reserve_size = state.range(0);
- for (auto _ : state) {
+ constexpr size_t kBatchSize = 1024;
+ size_t reserve_size = static_cast<size_t>(state.range(0));
+
+ std::vector<IntTable> tables;
+ while (state.KeepRunningBatch(kBatchSize)) {
state.PauseTiming();
- IntTable t;
+ tables.clear();
+ tables.resize(kBatchSize);
state.ResumeTiming();
- benchmark::DoNotOptimize(t);
- t.reserve(reserve_size);
+ for (auto& t : tables) {
+ benchmark::DoNotOptimize(t);
+ t.reserve(reserve_size);
+ benchmark::DoNotOptimize(t);
+ }
}
}
-BENCHMARK(BM_ReserveIntTable)->Range(128, 4096);
+BENCHMARK(BM_ReserveIntTable)->Range(1, 64);
void BM_ReserveStringTable(benchmark::State& state) {
- int reserve_size = state.range(0);
- for (auto _ : state) {
+ constexpr size_t kBatchSize = 1024;
+ size_t reserve_size = static_cast<size_t>(state.range(0));
+
+ std::vector<StringTable> tables;
+ while (state.KeepRunningBatch(kBatchSize)) {
state.PauseTiming();
- StringTable t;
+ tables.clear();
+ tables.resize(kBatchSize);
state.ResumeTiming();
- benchmark::DoNotOptimize(t);
- t.reserve(reserve_size);
+ for (auto& t : tables) {
+ benchmark::DoNotOptimize(t);
+ t.reserve(reserve_size);
+ benchmark::DoNotOptimize(t);
+ }
}
}
-BENCHMARK(BM_ReserveStringTable)->Range(128, 4096);
+BENCHMARK(BM_ReserveStringTable)->Range(1, 64);
// Like std::iota, except that ctrl_t doesn't support operator++.
template <typename CtrlIter>
@@ -330,33 +451,66 @@
h2_t h = 1;
for (auto _ : state) {
::benchmark::DoNotOptimize(h);
+ ::benchmark::DoNotOptimize(g);
::benchmark::DoNotOptimize(g.Match(h));
}
}
BENCHMARK(BM_Group_Match);
-void BM_Group_MatchEmpty(benchmark::State& state) {
- std::array<ctrl_t, Group::kWidth> group;
+void BM_GroupPortable_Match(benchmark::State& state) {
+ std::array<ctrl_t, GroupPortableImpl::kWidth> group;
Iota(group.begin(), group.end(), -4);
- Group g{group.data()};
- for (auto _ : state) ::benchmark::DoNotOptimize(g.MatchEmpty());
+ GroupPortableImpl g{group.data()};
+ h2_t h = 1;
+ for (auto _ : state) {
+ ::benchmark::DoNotOptimize(h);
+ ::benchmark::DoNotOptimize(g);
+ ::benchmark::DoNotOptimize(g.Match(h));
+ }
}
-BENCHMARK(BM_Group_MatchEmpty);
+BENCHMARK(BM_GroupPortable_Match);
-void BM_Group_MatchEmptyOrDeleted(benchmark::State& state) {
+void BM_Group_MaskEmpty(benchmark::State& state) {
std::array<ctrl_t, Group::kWidth> group;
Iota(group.begin(), group.end(), -4);
Group g{group.data()};
- for (auto _ : state) ::benchmark::DoNotOptimize(g.MatchEmptyOrDeleted());
+ for (auto _ : state) {
+ ::benchmark::DoNotOptimize(g);
+ ::benchmark::DoNotOptimize(g.MaskEmpty());
+ }
}
-BENCHMARK(BM_Group_MatchEmptyOrDeleted);
+BENCHMARK(BM_Group_MaskEmpty);
+
+void BM_Group_MaskEmptyOrDeleted(benchmark::State& state) {
+ std::array<ctrl_t, Group::kWidth> group;
+ Iota(group.begin(), group.end(), -4);
+ Group g{group.data()};
+ for (auto _ : state) {
+ ::benchmark::DoNotOptimize(g);
+ ::benchmark::DoNotOptimize(g.MaskEmptyOrDeleted());
+ }
+}
+BENCHMARK(BM_Group_MaskEmptyOrDeleted);
+
+void BM_Group_MaskNonFull(benchmark::State& state) {
+ std::array<ctrl_t, Group::kWidth> group;
+ Iota(group.begin(), group.end(), -4);
+ Group g{group.data()};
+ for (auto _ : state) {
+ ::benchmark::DoNotOptimize(g);
+ ::benchmark::DoNotOptimize(g.MaskNonFull());
+ }
+}
+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)
+ for (auto _ : state) {
+ ::benchmark::DoNotOptimize(g);
::benchmark::DoNotOptimize(g.CountLeadingEmptyOrDeleted());
+ }
}
BENCHMARK(BM_Group_CountLeadingEmptyOrDeleted);
@@ -364,10 +518,24 @@
std::array<ctrl_t, Group::kWidth> group;
Iota(group.begin(), group.end(), -2);
Group g{group.data()};
- for (auto _ : state) ::benchmark::DoNotOptimize(*g.MatchEmptyOrDeleted());
+ for (auto _ : state) {
+ ::benchmark::DoNotOptimize(g);
+ ::benchmark::DoNotOptimize(g.MaskEmptyOrDeleted().LowestBitSet());
+ }
}
BENCHMARK(BM_Group_MatchFirstEmptyOrDeleted);
+void BM_Group_MatchFirstNonFull(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.MaskNonFull().LowestBitSet());
+ }
+}
+BENCHMARK(BM_Group_MatchFirstNonFull);
+
void BM_DropDeletes(benchmark::State& state) {
constexpr size_t capacity = (1 << 20) - 1;
std::vector<ctrl_t> ctrl(capacity + 1 + Group::kWidth);
@@ -389,6 +557,24 @@
}
BENCHMARK(BM_DropDeletes);
+void BM_Resize(benchmark::State& state) {
+ // For now just measure a small cheap hash table since we
+ // are mostly interested in the overhead of type-erasure
+ // in resize().
+ constexpr int kElements = 64;
+ const int kCapacity = kElements * 2;
+
+ IntTable table;
+ for (int i = 0; i < kElements; i++) {
+ table.insert(i);
+ }
+ for (auto unused : state) {
+ table.rehash(0);
+ table.rehash(kCapacity);
+ }
+}
+BENCHMARK(BM_Resize);
+
} // namespace
} // namespace container_internal
ABSL_NAMESPACE_END
@@ -406,6 +592,12 @@
return table->find(key) != table->end();
}
+// This is useful because the find isn't inlined but the iterator comparison is.
+bool CodegenAbslRawHashSetStringFindNeEnd(
+ absl::container_internal::StringTable* table, const std::string& key) {
+ return table->find(key) != table->end();
+}
+
auto CodegenAbslRawHashSetInt64Insert(absl::container_internal::IntTable* table,
int64_t key)
-> decltype(table->insert(key)) {
@@ -425,7 +617,7 @@
int odr =
(::benchmark::DoNotOptimize(std::make_tuple(
&CodegenAbslRawHashSetInt64Find, &CodegenAbslRawHashSetInt64FindNeEnd,
- &CodegenAbslRawHashSetInt64Insert,
- &CodegenAbslRawHashSetInt64Contains,
+ &CodegenAbslRawHashSetStringFindNeEnd,
+ &CodegenAbslRawHashSetInt64Insert, &CodegenAbslRawHashSetInt64Contains,
&CodegenAbslRawHashSetInt64Iterate)),
1);
diff --git a/absl/container/internal/raw_hash_set_probe_benchmark.cc b/absl/container/internal/raw_hash_set_probe_benchmark.cc
index 7169a2e..8f36305 100644
--- a/absl/container/internal/raw_hash_set_probe_benchmark.cc
+++ b/absl/container/internal/raw_hash_set_probe_benchmark.cc
@@ -19,6 +19,7 @@
#include <regex> // NOLINT
#include <vector>
+#include "absl/base/no_destructor.h"
#include "absl/container/flat_hash_map.h"
#include "absl/container/internal/hash_function_defaults.h"
#include "absl/container/internal/hashtable_debug.h"
@@ -29,6 +30,7 @@
#include "absl/strings/str_format.h"
#include "absl/strings/string_view.h"
#include "absl/strings/strip.h"
+#include "absl/types/optional.h"
namespace {
@@ -68,10 +70,15 @@
-> decltype(std::forward<F>(f)(arg, arg)) {
return std::forward<F>(f)(arg, arg);
}
+
+ template <class Hash>
+ static constexpr auto get_hash_slot_fn() {
+ return nullptr;
+ }
};
absl::BitGen& GlobalBitGen() {
- static auto* value = new absl::BitGen;
+ static absl::NoDestructor<absl::BitGen> value;
return *value;
}
@@ -112,7 +119,7 @@
static constexpr size_t kRandomPool = 20;
static std::vector<T*>& GetPointers(size_t n) {
- static auto* m = new absl::flat_hash_map<size_t, std::vector<T*>>();
+ static absl::NoDestructor<absl::flat_hash_map<size_t, std::vector<T*>>> m;
return (*m)[n];
}
};
@@ -460,12 +467,12 @@
constexpr int kDistWidth = 16;
bool CanRunBenchmark(absl::string_view name) {
- static std::regex* const filter = []() -> std::regex* {
+ static const absl::NoDestructor<absl::optional<std::regex>> filter([] {
return benchmarks.empty() || benchmarks == "all"
- ? nullptr
- : new std::regex(std::string(benchmarks));
- }();
- return filter == nullptr || std::regex_search(std::string(name), *filter);
+ ? absl::nullopt
+ : absl::make_optional(std::regex(std::string(benchmarks)));
+ }());
+ return !filter->has_value() || std::regex_search(std::string(name), **filter);
}
struct Result {
diff --git a/absl/container/internal/raw_hash_set_test.cc b/absl/container/internal/raw_hash_set_test.cc
index 4fb31fa..109340c 100644
--- a/absl/container/internal/raw_hash_set_test.cc
+++ b/absl/container/internal/raw_hash_set_test.cc
@@ -14,28 +14,48 @@
#include "absl/container/internal/raw_hash_set.h"
+#include <algorithm>
+#include <array>
#include <atomic>
#include <cmath>
+#include <cstddef>
#include <cstdint>
#include <deque>
#include <functional>
+#include <iostream>
+#include <iterator>
+#include <list>
+#include <map>
#include <memory>
#include <numeric>
+#include <ostream>
#include <random>
#include <string>
+#include <tuple>
+#include <type_traits>
#include <unordered_map>
#include <unordered_set>
+#include <utility>
+#include <vector>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/base/attributes.h"
#include "absl/base/config.h"
#include "absl/base/internal/cycleclock.h"
-#include "absl/base/internal/raw_logging.h"
+#include "absl/base/prefetch.h"
+#include "absl/container/flat_hash_map.h"
+#include "absl/container/flat_hash_set.h"
#include "absl/container/internal/container_memory.h"
#include "absl/container/internal/hash_function_defaults.h"
#include "absl/container/internal/hash_policy_testing.h"
#include "absl/container/internal/hashtable_debug.h"
+#include "absl/container/internal/hashtablez_sampler.h"
+#include "absl/container/internal/test_allocator.h"
+#include "absl/hash/hash.h"
+#include "absl/log/log.h"
+#include "absl/memory/memory.h"
+#include "absl/meta/type_traits.h"
#include "absl/strings/string_view.h"
namespace absl {
@@ -44,14 +64,23 @@
struct RawHashSetTestOnlyAccess {
template <typename C>
- static auto GetSlots(const C& c) -> decltype(c.slots_) {
- return c.slots_;
+ static auto GetCommon(const C& c) -> decltype(c.common()) {
+ return c.common();
+ }
+ template <typename C>
+ static auto GetSlots(const C& c) -> decltype(c.slot_array()) {
+ return c.slot_array();
+ }
+ template <typename C>
+ static size_t CountTombstones(const C& c) {
+ return c.common().TombstonesCount();
}
};
namespace {
using ::testing::ElementsAre;
+using ::testing::ElementsAreArray;
using ::testing::Eq;
using ::testing::Ge;
using ::testing::Lt;
@@ -133,20 +162,66 @@
EXPECT_THAT((BitMask<uint8_t, 8>(0xAA)), ElementsAre(1, 3, 5, 7));
}
-TEST(BitMask, WithShift) {
+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 msbs = 0x8080808080808080ULL;
constexpr uint64_t lsbs = 0x0101010101010101ULL;
auto x = ctrl ^ (lsbs * hash);
- uint64_t mask = (x - lsbs) & ~x & msbs;
+ 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);
@@ -194,35 +269,77 @@
}
}
-TEST(Group, MatchEmpty) {
+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}.MatchEmpty(), ElementsAre(0, 4));
+ 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}.MatchEmpty(), ElementsAre(0));
+ 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, MatchEmptyOrDeleted) {
+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), CtrlT(3), CtrlT(1),
- CtrlT(1), CtrlT(1), CtrlT(1), CtrlT(1)};
- EXPECT_THAT(Group{group}.MatchEmptyOrDeleted(), ElementsAre(0, 2, 4));
+ 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}.MatchEmptyOrDeleted(), ElementsAre(0, 3));
+ EXPECT_THAT(Group{group}.MaskEmptyOrDeleted().LowestBitSet(), 0);
+ EXPECT_THAT(Group{group}.MaskEmptyOrDeleted().HighestBitSet(), 3);
} else {
FAIL() << "No test coverage for Group::kWidth==" << Group::kWidth;
}
@@ -271,13 +388,13 @@
std::vector<ctrl_t> f(Group::kWidth, empty);
f[Group::kWidth * 2 / 3] = full;
f[Group::kWidth / 2] = full;
- EXPECT_EQ(
- Group::kWidth / 2, Group{f.data()}.CountLeadingEmptyOrDeleted());
+ EXPECT_EQ(Group::kWidth / 2,
+ Group{f.data()}.CountLeadingEmptyOrDeleted());
}
}
}
-template <class T>
+template <class T, bool kTransferable = false>
struct ValuePolicy {
using slot_type = T;
using key_type = T;
@@ -295,10 +412,11 @@
}
template <class Allocator>
- static void transfer(Allocator* alloc, slot_type* new_slot,
- slot_type* old_slot) {
+ static std::integral_constant<bool, kTransferable> transfer(
+ Allocator* alloc, slot_type* new_slot, slot_type* old_slot) {
construct(alloc, new_slot, std::move(*old_slot));
destroy(alloc, old_slot);
+ return {};
}
static T& element(slot_type* slot) { return *slot; }
@@ -310,11 +428,18 @@
return absl::container_internal::DecomposeValue(
std::forward<F>(f), std::forward<Args>(args)...);
}
+
+ template <class Hash>
+ static constexpr HashSlotFn get_hash_slot_fn() {
+ return nullptr;
+ }
};
using IntPolicy = ValuePolicy<int64_t>;
using Uint8Policy = ValuePolicy<uint8_t>;
+using TranferableIntPolicy = ValuePolicy<int64_t, /*kTransferable=*/true>;
+
class StringPolicy {
template <class F, class K, class V,
class = typename std::enable_if<
@@ -334,7 +459,7 @@
struct ctor {};
template <class... Ts>
- slot_type(ctor, Ts&&... ts) : pair(std::forward<Ts>(ts)...) {}
+ explicit slot_type(ctor, Ts&&... ts) : pair(std::forward<Ts>(ts)...) {}
std::pair<std::string, std::string> pair;
};
@@ -371,6 +496,11 @@
return apply_impl(std::forward<F>(f),
PairArgs(std::forward<Args>(args)...));
}
+
+ template <class Hash>
+ static constexpr HashSlotFn get_hash_slot_fn() {
+ return nullptr;
+ }
};
struct StringHash : absl::Hash<absl::string_view> {
@@ -383,43 +513,72 @@
struct StringTable
: raw_hash_set<StringPolicy, StringHash, StringEq, std::allocator<int>> {
using Base = typename StringTable::raw_hash_set;
- StringTable() {}
+ StringTable() = default;
using Base::Base;
};
-struct IntTable
- : raw_hash_set<IntPolicy, container_internal::hash_default_hash<int64_t>,
- std::equal_to<int64_t>, std::allocator<int64_t>> {
- using Base = typename IntTable::raw_hash_set;
+template <typename T, bool kTransferable = false>
+struct ValueTable
+ : raw_hash_set<ValuePolicy<T, kTransferable>, hash_default_hash<T>,
+ std::equal_to<T>, std::allocator<T>> {
+ using Base = typename ValueTable::raw_hash_set;
using Base::Base;
};
-struct Uint8Table
- : raw_hash_set<Uint8Policy, container_internal::hash_default_hash<uint8_t>,
- std::equal_to<uint8_t>, std::allocator<uint8_t>> {
- using Base = typename Uint8Table::raw_hash_set;
- using Base::Base;
-};
+using IntTable = ValueTable<int64_t>;
+using Uint8Table = ValueTable<uint8_t>;
+
+using TransferableIntTable = ValueTable<int64_t, /*kTransferable=*/true>;
template <typename T>
struct CustomAlloc : std::allocator<T> {
- CustomAlloc() {}
+ CustomAlloc() = default;
template <typename U>
- CustomAlloc(const CustomAlloc<U>& other) {}
+ explicit CustomAlloc(const CustomAlloc<U>& /*other*/) {}
- template<class U> struct rebind {
+ template <class U>
+ struct rebind {
using other = CustomAlloc<U>;
};
};
struct CustomAllocIntTable
- : raw_hash_set<IntPolicy, container_internal::hash_default_hash<int64_t>,
+ : raw_hash_set<IntPolicy, hash_default_hash<int64_t>,
std::equal_to<int64_t>, CustomAlloc<int64_t>> {
using Base = typename CustomAllocIntTable::raw_hash_set;
using Base::Base;
};
+struct MinimumAlignmentUint8Table
+ : raw_hash_set<Uint8Policy, hash_default_hash<uint8_t>,
+ std::equal_to<uint8_t>, MinimumAlignmentAlloc<uint8_t>> {
+ using Base = typename MinimumAlignmentUint8Table::raw_hash_set;
+ using Base::Base;
+};
+
+// Allows for freezing the allocator to expect no further allocations.
+template <typename T>
+struct FreezableAlloc : std::allocator<T> {
+ explicit FreezableAlloc(bool* f) : frozen(f) {}
+
+ template <typename U>
+ explicit FreezableAlloc(const FreezableAlloc<U>& other)
+ : frozen(other.frozen) {}
+
+ template <class U>
+ struct rebind {
+ using other = FreezableAlloc<U>;
+ };
+
+ T* allocate(size_t n) {
+ EXPECT_FALSE(*frozen);
+ return std::allocator<T>::allocate(n);
+ }
+
+ bool* frozen;
+};
+
struct BadFastHash {
template <class T>
size_t operator()(const T&) const {
@@ -427,10 +586,17 @@
}
};
+struct BadHashFreezableIntTable
+ : raw_hash_set<IntPolicy, BadFastHash, std::equal_to<int64_t>,
+ FreezableAlloc<int64_t>> {
+ using Base = typename BadHashFreezableIntTable::raw_hash_set;
+ using Base::Base;
+};
+
struct BadTable : raw_hash_set<IntPolicy, BadFastHash, std::equal_to<int>,
std::allocator<int>> {
using Base = typename BadTable::raw_hash_set;
- BadTable() {}
+ BadTable() = default;
using Base::Base;
};
@@ -443,15 +609,6 @@
void* slots;
size_t size;
size_t capacity;
- size_t growth_left;
- void* infoz;
- };
- struct MockTableInfozDisabled {
- void* ctrl;
- void* slots;
- size_t size;
- size_t capacity;
- size_t growth_left;
};
struct StatelessHash {
size_t operator()(absl::string_view) const { return 0; }
@@ -460,27 +617,36 @@
size_t dummy;
};
- if (std::is_empty<HashtablezInfoHandle>::value) {
- EXPECT_EQ(sizeof(MockTableInfozDisabled),
- sizeof(raw_hash_set<StringPolicy, StatelessHash,
- std::equal_to<absl::string_view>,
- std::allocator<int>>));
+ struct GenerationData {
+ size_t reserved_growth;
+ size_t reservation_size;
+ GenerationType* generation;
+ };
- EXPECT_EQ(sizeof(MockTableInfozDisabled) + sizeof(StatefulHash),
- sizeof(raw_hash_set<StringPolicy, StatefulHash,
- std::equal_to<absl::string_view>,
- std::allocator<int>>));
- } else {
- EXPECT_EQ(sizeof(MockTable),
- sizeof(raw_hash_set<StringPolicy, StatelessHash,
- std::equal_to<absl::string_view>,
- std::allocator<int>>));
+// Ignore unreachable-code warning. Compiler thinks one branch of each ternary
+// conditional is unreachable.
+#if defined(__clang__)
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunreachable-code"
+#endif
+ constexpr size_t mock_size = sizeof(MockTable);
+ constexpr size_t generation_size =
+ SwisstableGenerationsEnabled() ? sizeof(GenerationData) : 0;
+#if defined(__clang__)
+#pragma clang diagnostic pop
+#endif
- EXPECT_EQ(sizeof(MockTable) + sizeof(StatefulHash),
- sizeof(raw_hash_set<StringPolicy, StatefulHash,
- std::equal_to<absl::string_view>,
- std::allocator<int>>));
- }
+ EXPECT_EQ(
+ mock_size + generation_size,
+ sizeof(
+ raw_hash_set<StringPolicy, StatelessHash,
+ std::equal_to<absl::string_view>, std::allocator<int>>));
+
+ EXPECT_EQ(
+ mock_size + sizeof(StatefulHash) + generation_size,
+ sizeof(
+ raw_hash_set<StringPolicy, StatefulHash,
+ std::equal_to<absl::string_view>, std::allocator<int>>));
}
TEST(Table, Empty) {
@@ -568,6 +734,23 @@
EXPECT_TRUE(t.empty());
}
+TEST(Table, EraseInSmallTables) {
+ for (int64_t size = 0; size < 64; ++size) {
+ IntTable t;
+ for (int64_t i = 0; i < size; ++i) {
+ t.insert(i);
+ }
+ for (int64_t i = 0; i < size; ++i) {
+ t.erase(i);
+ EXPECT_EQ(t.size(), size - i - 1);
+ for (int64_t j = i + 1; j < size; ++j) {
+ EXPECT_THAT(*t.find(j), j);
+ }
+ }
+ EXPECT_TRUE(t.empty());
+ }
+}
+
TEST(Table, InsertWithinCapacity) {
IntTable t;
t.reserve(10);
@@ -599,6 +782,69 @@
EXPECT_THAT(addr(0), original_addr_0);
}
+template <class TableType>
+class SmallTableResizeTest : public testing::Test {};
+
+TYPED_TEST_SUITE_P(SmallTableResizeTest);
+
+TYPED_TEST_P(SmallTableResizeTest, InsertIntoSmallTable) {
+ TypeParam t;
+ for (int i = 0; i < 32; ++i) {
+ t.insert(i);
+ ASSERT_EQ(t.size(), i + 1);
+ for (int j = 0; j < i + 1; ++j) {
+ EXPECT_TRUE(t.find(j) != t.end());
+ EXPECT_EQ(*t.find(j), j);
+ }
+ }
+}
+
+TYPED_TEST_P(SmallTableResizeTest, ResizeGrowSmallTables) {
+ for (size_t source_size = 0; source_size < 32; ++source_size) {
+ for (size_t target_size = source_size; target_size < 32; ++target_size) {
+ for (bool rehash : {false, true}) {
+ TypeParam t;
+ for (size_t i = 0; i < source_size; ++i) {
+ t.insert(static_cast<int>(i));
+ }
+ if (rehash) {
+ t.rehash(target_size);
+ } else {
+ t.reserve(target_size);
+ }
+ for (size_t i = 0; i < source_size; ++i) {
+ EXPECT_TRUE(t.find(static_cast<int>(i)) != t.end());
+ EXPECT_EQ(*t.find(static_cast<int>(i)), static_cast<int>(i));
+ }
+ }
+ }
+ }
+}
+
+TYPED_TEST_P(SmallTableResizeTest, ResizeReduceSmallTables) {
+ for (size_t source_size = 0; source_size < 32; ++source_size) {
+ for (size_t target_size = 0; target_size <= source_size; ++target_size) {
+ TypeParam t;
+ t.reserve(source_size);
+ size_t inserted_count = std::min<size_t>(source_size, 5);
+ for (size_t i = 0; i < inserted_count; ++i) {
+ t.insert(static_cast<int>(i));
+ }
+ t.rehash(target_size);
+ for (size_t i = 0; i < inserted_count; ++i) {
+ EXPECT_TRUE(t.find(static_cast<int>(i)) != t.end());
+ EXPECT_EQ(*t.find(static_cast<int>(i)), static_cast<int>(i));
+ }
+ }
+ }
+}
+
+REGISTER_TYPED_TEST_SUITE_P(SmallTableResizeTest, InsertIntoSmallTable,
+ ResizeGrowSmallTables, ResizeReduceSmallTables);
+using SmallTableTypes = ::testing::Types<IntTable, TransferableIntTable>;
+INSTANTIATE_TYPED_TEST_SUITE_P(InstanceSmallTableResizeTest,
+ SmallTableResizeTest, SmallTableTypes);
+
TEST(Table, LazyEmplace) {
StringTable t;
bool called = false;
@@ -711,6 +957,11 @@
static auto apply(F&& f, const T& x) -> decltype(std::forward<F>(f)(x, x)) {
return std::forward<F>(f)(x, x);
}
+
+ template <class Hash>
+ static constexpr HashSlotFn get_hash_slot_fn() {
+ return nullptr;
+ }
};
template <typename Hash, typename Eq>
@@ -838,6 +1089,10 @@
}
TEST(Table, Decompose) {
+ if (SwisstableGenerationsEnabled()) {
+ GTEST_SKIP() << "Generations being enabled causes extra rehashes.";
+ }
+
TestDecompose<DecomposeHash, DecomposeEq>(false);
struct TransparentHashIntOverload {
@@ -867,7 +1122,7 @@
}
struct Modulo1000Hash {
- size_t operator()(int x) const { return x % 1000; }
+ size_t operator()(int64_t x) const { return static_cast<size_t>(x) % 1000; }
};
struct Modulo1000HashTable
@@ -876,6 +1131,10 @@
// Test that rehash with no resize happen in case of many deleted slots.
TEST(Table, RehashWithNoResize) {
+ if (SwisstableGenerationsEnabled()) {
+ GTEST_SKIP() << "Generations being enabled causes extra rehashes.";
+ }
+
Modulo1000HashTable t;
// Adding the same length (and the same hash) strings
// to have at least kMinFullGroups groups
@@ -969,6 +1228,10 @@
}
TEST(Table, ClearBug) {
+ if (SwisstableGenerationsEnabled()) {
+ GTEST_SKIP() << "Generations being enabled causes extra rehashes.";
+ }
+
IntTable t;
constexpr size_t capacity = container_internal::Group::kWidth - 1;
constexpr size_t max_size = capacity / 2 + 1;
@@ -987,7 +1250,7 @@
// We are checking that original and second are close enough to each other
// that they are probably still in the same group. This is not strictly
// guaranteed.
- EXPECT_LT(std::abs(original - second),
+ EXPECT_LT(static_cast<size_t>(std::abs(original - second)),
capacity * sizeof(IntTable::value_type));
}
@@ -1005,7 +1268,7 @@
TEST(Table, EraseMaintainsValidIterator) {
IntTable t;
const int kNumElements = 100;
- for (int i = 0; i < kNumElements; i ++) {
+ for (int i = 0; i < kNumElements; i++) {
EXPECT_TRUE(t.emplace(i).second);
}
EXPECT_EQ(t.size(), kNumElements);
@@ -1021,6 +1284,14 @@
EXPECT_EQ(num_erase_calls, kNumElements);
}
+TEST(Table, EraseBeginEnd) {
+ IntTable t;
+ for (int i = 0; i < 10; ++i) t.insert(i);
+ EXPECT_EQ(t.size(), 10);
+ t.erase(t.begin(), t.end());
+ EXPECT_EQ(t.size(), 0);
+}
+
// Collect N bad keys by following algorithm:
// 1. Create an empty table and reserve it to 2 * N.
// 2. Insert N random elements.
@@ -1064,19 +1335,6 @@
// Ratios total_probe_length/size for every tested table.
std::vector<double> single_table_ratios;
- friend ProbeStats operator+(const ProbeStats& a, const ProbeStats& b) {
- ProbeStats res = a;
- res.all_probes_histogram.resize(std::max(res.all_probes_histogram.size(),
- b.all_probes_histogram.size()));
- std::transform(b.all_probes_histogram.begin(), b.all_probes_histogram.end(),
- res.all_probes_histogram.begin(),
- res.all_probes_histogram.begin(), std::plus<size_t>());
- res.single_table_ratios.insert(res.single_table_ratios.end(),
- b.single_table_ratios.begin(),
- b.single_table_ratios.end());
- return res;
- }
-
// Average ratio total_probe_length/size over tables.
double AvgRatio() const {
return std::accumulate(single_table_ratios.begin(),
@@ -1231,20 +1489,20 @@
switch (container_internal::Group::kWidth) {
case 8:
if (kRandomizesInserts) {
- return {0.05,
- 1.0,
- {{0.95, 0.5}},
- {{0.95, 0}, {0.99, 2}, {0.999, 4}, {0.9999, 10}}};
+ return {0.05,
+ 1.0,
+ {{0.95, 0.5}},
+ {{0.95, 0}, {0.99, 2}, {0.999, 4}, {0.9999, 10}}};
} else {
- return {0.05,
- 2.0,
- {{0.95, 0.1}},
- {{0.95, 0}, {0.99, 2}, {0.999, 4}, {0.9999, 10}}};
+ return {0.05,
+ 2.0,
+ {{0.95, 0.1}},
+ {{0.95, 0}, {0.99, 2}, {0.999, 4}, {0.9999, 10}}};
}
case 16:
if (kRandomizesInserts) {
return {0.1,
- 1.0,
+ 2.0,
{{0.95, 0.1}},
{{0.95, 0}, {0.99, 1}, {0.999, 8}, {0.9999, 15}}};
} else {
@@ -1254,10 +1512,11 @@
{{0.95, 0}, {0.99, 1}, {0.999, 4}, {0.9999, 10}}};
}
}
- ABSL_RAW_LOG(FATAL, "%s", "Unknown Group width");
+ LOG(FATAL) << "Unknown Group width";
return {};
}
+// TODO(b/80415403): Figure out why this test is so flaky, esp. on MSVC
TEST(Table, DISABLED_EnsureNonQuadraticTopNXorSeedByProbeSeqLength) {
ProbeStatsPerSize stats;
std::vector<size_t> sizes = {Group::kWidth << 5, Group::kWidth << 10};
@@ -1269,6 +1528,7 @@
for (size_t size : sizes) {
auto& stat = stats[size];
VerifyStats(size, expected, stat);
+ LOG(INFO) << size << " " << stat;
}
}
@@ -1283,7 +1543,7 @@
std::random_device rd;
std::mt19937 rng(rd());
auto linear_transform = [](size_t x, size_t y) { return x * 17 + y * 13; };
- std::uniform_int_distribution<size_t> dist(0, keys.size()-1);
+ std::uniform_int_distribution<size_t> dist(0, keys.size() - 1);
while (num_iters--) {
IntTable t1;
size_t num_keys = keys.size() / 10;
@@ -1330,17 +1590,17 @@
{{0.95, 0.3}},
{{0.95, 0}, {0.99, 1}, {0.999, 8}, {0.9999, 15}}};
} else {
- return {0.15,
- 0.5,
- {{0.95, 0.3}},
- {{0.95, 0}, {0.99, 3}, {0.999, 15}, {0.9999, 25}}};
+ return {0.4,
+ 0.6,
+ {{0.95, 0.5}},
+ {{0.95, 1}, {0.99, 14}, {0.999, 23}, {0.9999, 26}}};
}
case 16:
if (kRandomizesInserts) {
return {0.1,
0.4,
{{0.95, 0.3}},
- {{0.95, 0}, {0.99, 1}, {0.999, 8}, {0.9999, 15}}};
+ {{0.95, 1}, {0.99, 2}, {0.999, 9}, {0.9999, 15}}};
} else {
return {0.05,
0.2,
@@ -1348,10 +1608,11 @@
{{0.95, 0}, {0.99, 1}, {0.999, 6}, {0.9999, 10}}};
}
}
- ABSL_RAW_LOG(FATAL, "%s", "Unknown Group width");
+ LOG(FATAL) << "Unknown Group width";
return {};
}
+// TODO(b/80415403): Figure out why this test is so flaky.
TEST(Table, DISABLED_EnsureNonQuadraticTopNLinearTransformByProbeSeqLength) {
ProbeStatsPerSize stats;
std::vector<size_t> sizes = {Group::kWidth << 5, Group::kWidth << 10};
@@ -1363,6 +1624,7 @@
for (size_t size : sizes) {
auto& stat = stats[size];
VerifyStats(size, expected, stat);
+ LOG(INFO) << size << " " << stat;
}
}
@@ -1497,7 +1759,7 @@
TEST(Table, ConstructFromInitList) {
using P = std::pair<std::string, std::string>;
struct Q {
- operator P() const { return {}; }
+ operator P() const { return {}; } // NOLINT
};
StringTable t = {P(), Q(), {}, {{}, {}}};
}
@@ -1523,6 +1785,40 @@
}
}
+TEST(Table, CopyDifferentSizes) {
+ IntTable t;
+
+ for (int i = 0; i < 100; ++i) {
+ t.emplace(i);
+ IntTable c = t;
+ for (int j = 0; j <= i; ++j) {
+ ASSERT_TRUE(c.find(j) != c.end()) << "i=" << i << " j=" << j;
+ }
+ // Testing find miss to verify that table is not full.
+ ASSERT_TRUE(c.find(-1) == c.end());
+ }
+}
+
+TEST(Table, CopyDifferentCapacities) {
+ for (int cap = 1; cap < 100; cap = cap * 2 + 1) {
+ IntTable t;
+ t.reserve(static_cast<size_t>(cap));
+ for (int i = 0; i <= cap; ++i) {
+ t.emplace(i);
+ if (i != cap && i % 5 != 0) {
+ continue;
+ }
+ IntTable c = t;
+ for (int j = 0; j <= i; ++j) {
+ ASSERT_TRUE(c.find(j) != c.end())
+ << "cap=" << cap << " i=" << i << " j=" << j;
+ }
+ // Testing find miss to verify that table is not full.
+ ASSERT_TRUE(c.find(-1) == c.end());
+ }
+ }
+}
+
TEST(Table, CopyConstructWithAlloc) {
StringTable t;
t.emplace("a", "b");
@@ -1533,9 +1829,9 @@
}
struct ExplicitAllocIntTable
- : raw_hash_set<IntPolicy, container_internal::hash_default_hash<int64_t>,
+ : raw_hash_set<IntPolicy, hash_default_hash<int64_t>,
std::equal_to<int64_t>, Alloc<int64_t>> {
- ExplicitAllocIntTable() {}
+ ExplicitAllocIntTable() = default;
};
TEST(Table, AllocWithExplicitCtor) {
@@ -1611,6 +1907,14 @@
EXPECT_THAT(*u.find("a"), Pair("a", "b"));
}
+TEST(Table, MoveSelfAssign) {
+ StringTable t;
+ t.emplace("a", "b");
+ EXPECT_EQ(1, t.size());
+ t = std::move(*&t);
+ // As long as we don't crash, it's fine.
+}
+
TEST(Table, Equality) {
StringTable t;
std::vector<std::pair<std::string, std::string>> v = {{"a", "b"},
@@ -1789,11 +2093,9 @@
EXPECT_FALSE((VerifyResultOf<CallPrefetch, NonTransparentTable>()));
EXPECT_FALSE((VerifyResultOf<CallCount, NonTransparentTable>()));
- using TransparentTable = raw_hash_set<
- StringPolicy,
- absl::container_internal::hash_default_hash<absl::string_view>,
- absl::container_internal::hash_default_eq<absl::string_view>,
- std::allocator<int>>;
+ using TransparentTable =
+ raw_hash_set<StringPolicy, hash_default_hash<absl::string_view>,
+ hash_default_eq<absl::string_view>, std::allocator<int>>;
EXPECT_TRUE((VerifyResultOf<CallFind, TransparentTable>()));
EXPECT_TRUE((VerifyResultOf<CallErase, TransparentTable>()));
@@ -1802,7 +2104,6 @@
EXPECT_TRUE((VerifyResultOf<CallCount, TransparentTable>()));
}
-// TODO(alkis): Expand iterator tests.
TEST(Iterator, IsDefaultConstructible) {
StringTable::iterator i;
EXPECT_TRUE(i == StringTable::iterator());
@@ -1923,7 +2224,7 @@
EXPECT_FALSE(res.inserted);
EXPECT_THAT(*res.position, Pair(k0, ""));
EXPECT_TRUE(res.node);
- EXPECT_FALSE(node);
+ EXPECT_FALSE(node); // NOLINT(bugprone-use-after-move)
}
TEST(Nodes, HintInsert) {
@@ -1933,7 +2234,7 @@
auto it = t.insert(t.begin(), std::move(node));
EXPECT_THAT(t, UnorderedElementsAre(1, 2, 3));
EXPECT_EQ(*it, 1);
- EXPECT_FALSE(node);
+ EXPECT_FALSE(node); // NOLINT(bugprone-use-after-move)
node = t.extract(2);
EXPECT_THAT(t, UnorderedElementsAre(1, 3));
@@ -1943,7 +2244,7 @@
it = t.insert(t.begin(), std::move(node));
EXPECT_EQ(*it, 2);
// The node was not emptied by the insert call.
- EXPECT_TRUE(node);
+ EXPECT_TRUE(node); // NOLINT(bugprone-use-after-move)
}
IntTable MakeSimpleTable(size_t size) {
@@ -1982,20 +2283,34 @@
}
TEST(Table, IterationOrderChangesOnRehash) {
- std::vector<IntTable> garbage;
- for (int i = 0; i < 5000; ++i) {
- auto t = MakeSimpleTable(20);
- const auto reference = OrderOfIteration(t);
- // Force rehash to the same size.
- t.rehash(0);
- auto trial = OrderOfIteration(t);
- if (trial != reference) {
- // We are done.
- return;
+ // We test different sizes with many small numbers, because small table
+ // resize has a different codepath.
+ // Note: iteration order for size() <= 1 is always the same.
+ for (size_t size : std::vector<size_t>{2, 3, 6, 7, 12, 15, 20, 50}) {
+ for (size_t rehash_size : {
+ size_t{0}, // Force rehash is guaranteed.
+ size * 10 // Rehash to the larger capacity is guaranteed.
+ }) {
+ std::vector<IntTable> garbage;
+ bool ok = false;
+ for (int i = 0; i < 5000; ++i) {
+ auto t = MakeSimpleTable(size);
+ const auto reference = OrderOfIteration(t);
+ // Force rehash.
+ t.rehash(rehash_size);
+ auto trial = OrderOfIteration(t);
+ if (trial != reference) {
+ // We are done.
+ ok = true;
+ break;
+ }
+ garbage.push_back(std::move(t));
+ }
+ EXPECT_TRUE(ok)
+ << "Iteration order remained the same across many attempts " << size
+ << "->" << rehash_size << ".";
}
- garbage.push_back(std::move(t));
}
- FAIL() << "Iteration order remained the same across many attempts.";
}
// Verify that pointers are invalidated as soon as a second element is inserted.
@@ -2016,20 +2331,89 @@
EXPECT_NE(old_ptr, addr(0));
}
-// Confirm that we assert if we try to erase() end().
-TEST(TableDeathTest, EraseOfEndAsserts) {
- // Use an assert with side-effects to figure out if they are actually enabled.
- bool assert_enabled = false;
- assert([&]() {
- assert_enabled = true;
- return true;
- }());
- if (!assert_enabled) return;
+TEST(TableDeathTest, InvalidIteratorAsserts) {
+ if (!IsAssertEnabled() && !SwisstableGenerationsEnabled())
+ GTEST_SKIP() << "Assertions not enabled.";
IntTable t;
// Extra simple "regexp" as regexp support is highly varied across platforms.
- constexpr char kDeathMsg[] = "Invalid operation on iterator";
- EXPECT_DEATH_IF_SUPPORTED(t.erase(t.end()), kDeathMsg);
+ EXPECT_DEATH_IF_SUPPORTED(t.erase(t.end()),
+ "erase.* called on end.. iterator.");
+ typename IntTable::iterator iter;
+ EXPECT_DEATH_IF_SUPPORTED(
+ ++iter, "operator.* called on default-constructed iterator.");
+ t.insert(0);
+ iter = t.begin();
+ t.erase(iter);
+ const char* const kErasedDeathMessage =
+ SwisstableGenerationsEnabled()
+ ? "operator.* called on invalid iterator.*was likely erased"
+ : "operator.* called on invalid iterator.*might have been "
+ "erased.*config=asan";
+ EXPECT_DEATH_IF_SUPPORTED(++iter, kErasedDeathMessage);
+}
+
+// Invalid iterator use can trigger use-after-free in asan/hwasan,
+// use-of-uninitialized-value in msan, or invalidated iterator assertions.
+constexpr const char* kInvalidIteratorDeathMessage =
+ "use-after-free|use-of-uninitialized-value|invalidated "
+ "iterator|Invalid iterator|invalid iterator";
+
+// MSVC doesn't support | in regex.
+#if defined(_MSC_VER)
+constexpr bool kMsvc = true;
+#else
+constexpr bool kMsvc = false;
+#endif
+
+TEST(TableDeathTest, IteratorInvalidAssertsEqualityOperator) {
+ if (!IsAssertEnabled() && !SwisstableGenerationsEnabled())
+ GTEST_SKIP() << "Assertions not enabled.";
+
+ IntTable t;
+ t.insert(1);
+ t.insert(2);
+ t.insert(3);
+ auto iter1 = t.begin();
+ auto iter2 = std::next(iter1);
+ ASSERT_NE(iter1, t.end());
+ ASSERT_NE(iter2, t.end());
+ t.erase(iter1);
+ // Extra simple "regexp" as regexp support is highly varied across platforms.
+ const char* const kErasedDeathMessage =
+ SwisstableGenerationsEnabled()
+ ? "Invalid iterator comparison.*was likely erased"
+ : "Invalid iterator comparison.*might have been erased.*config=asan";
+ EXPECT_DEATH_IF_SUPPORTED(void(iter1 == iter2), kErasedDeathMessage);
+ EXPECT_DEATH_IF_SUPPORTED(void(iter2 != iter1), kErasedDeathMessage);
+ t.erase(iter2);
+ EXPECT_DEATH_IF_SUPPORTED(void(iter1 == iter2), kErasedDeathMessage);
+
+ IntTable t1, t2;
+ t1.insert(0);
+ t2.insert(0);
+ iter1 = t1.begin();
+ iter2 = t2.begin();
+ const char* const kContainerDiffDeathMessage =
+ SwisstableGenerationsEnabled()
+ ? "Invalid iterator comparison.*iterators from different hashtables"
+ : "Invalid iterator comparison.*may be from different "
+ ".*containers.*config=asan";
+ EXPECT_DEATH_IF_SUPPORTED(void(iter1 == iter2), kContainerDiffDeathMessage);
+ EXPECT_DEATH_IF_SUPPORTED(void(iter2 == iter1), kContainerDiffDeathMessage);
+
+ for (int i = 0; i < 10; ++i) t1.insert(i);
+ // There should have been a rehash in t1.
+ if (kMsvc) return; // MSVC doesn't support | in regex.
+
+ // NOTE(b/293887834): After rehashing, iterators will contain pointers to
+ // freed memory, which may be detected by ThreadSanitizer.
+ const char* const kRehashedDeathMessage =
+ SwisstableGenerationsEnabled()
+ ? kInvalidIteratorDeathMessage
+ : "Invalid iterator comparison.*might have rehashed.*config=asan"
+ "|ThreadSanitizer: heap-use-after-free";
+ EXPECT_DEATH_IF_SUPPORTED(void(iter1 == t1.begin()), kRehashedDeathMessage);
}
#if defined(ABSL_INTERNAL_HASHTABLEZ_SAMPLE)
@@ -2038,9 +2422,9 @@
SetHashtablezEnabled(true);
SetHashtablezSampleParameter(100);
- auto& sampler = HashtablezSampler::Global();
+ auto& sampler = GlobalHashtablezSampler();
size_t start_size = 0;
- std::unordered_set<const HashtablezInfo*> preexisting_info;
+ absl::flat_hash_set<const HashtablezInfo*> preexisting_info;
start_size += sampler.Iterate([&](const HashtablezInfo& info) {
preexisting_info.insert(&info);
++start_size;
@@ -2049,16 +2433,33 @@
std::vector<IntTable> tables;
for (int i = 0; i < 1000000; ++i) {
tables.emplace_back();
+
+ const bool do_reserve = (i % 10 > 5);
+ const bool do_rehash = !do_reserve && (i % 10 > 0);
+
+ if (do_reserve) {
+ // Don't reserve on all tables.
+ tables.back().reserve(10 * (i % 10));
+ }
+
tables.back().insert(1);
tables.back().insert(i % 5);
+
+ if (do_rehash) {
+ // Rehash some other tables.
+ tables.back().rehash(10 * (i % 10));
+ }
}
size_t end_size = 0;
- std::unordered_map<size_t, int> observed_checksums;
+ absl::flat_hash_map<size_t, int> observed_checksums;
+ absl::flat_hash_map<ssize_t, int> reservations;
end_size += sampler.Iterate([&](const HashtablezInfo& info) {
if (preexisting_info.count(&info) == 0) {
observed_checksums[info.hashes_bitwise_xor.load(
std::memory_order_relaxed)]++;
+ reservations[info.max_reserve.load(std::memory_order_relaxed)]++;
}
+ EXPECT_EQ(info.inline_element_size, sizeof(int64_t));
++end_size;
});
@@ -2068,6 +2469,15 @@
for (const auto& [_, count] : observed_checksums) {
EXPECT_NEAR((100 * count) / static_cast<double>(tables.size()), 0.2, 0.05);
}
+
+ EXPECT_EQ(reservations.size(), 10);
+ for (const auto& [reservation, count] : reservations) {
+ EXPECT_GE(reservation, 0);
+ EXPECT_LT(reservation, 100);
+
+ EXPECT_NEAR((100 * count) / static_cast<double>(tables.size()), 0.1, 0.05)
+ << reservation;
+ }
}
#endif // ABSL_INTERNAL_HASHTABLEZ_SAMPLE
@@ -2076,7 +2486,7 @@
SetHashtablezEnabled(true);
SetHashtablezSampleParameter(100);
- auto& sampler = HashtablezSampler::Global();
+ auto& sampler = GlobalHashtablezSampler();
size_t start_size = 0;
start_size += sampler.Iterate([&](const HashtablezInfo&) { ++start_size; });
@@ -2093,21 +2503,34 @@
}
#ifdef ABSL_HAVE_ADDRESS_SANITIZER
-TEST(Sanitizer, PoisoningUnused) {
- IntTable t;
- t.reserve(5);
- // Insert something to force an allocation.
- int64_t& v1 = *t.insert(0).first;
+template <class TableType>
+class SanitizerTest : public testing::Test {};
- // Make sure there is something to test.
- ASSERT_GT(t.capacity(), 1);
+TYPED_TEST_SUITE_P(SanitizerTest);
- int64_t* slots = RawHashSetTestOnlyAccess::GetSlots(t);
- for (size_t i = 0; i < t.capacity(); ++i) {
- EXPECT_EQ(slots + i != &v1, __asan_address_is_poisoned(slots + i));
+TYPED_TEST_P(SanitizerTest, PoisoningUnused) {
+ TypeParam t;
+ for (size_t reserve_size = 2; reserve_size < 1024;
+ reserve_size = reserve_size * 3 / 2) {
+ t.reserve(reserve_size);
+ // Insert something to force an allocation.
+ int64_t& v = *t.insert(0).first;
+
+ // Make sure there is something to test.
+ ASSERT_GT(t.capacity(), 1);
+
+ int64_t* slots = RawHashSetTestOnlyAccess::GetSlots(t);
+ for (size_t i = 0; i < t.capacity(); ++i) {
+ EXPECT_EQ(slots + i != &v, __asan_address_is_poisoned(slots + i)) << i;
+ }
}
}
+REGISTER_TYPED_TEST_SUITE_P(SanitizerTest, PoisoningUnused);
+using SanitizerTableTypes = ::testing::Types<IntTable, TransferableIntTable>;
+INSTANTIATE_TYPED_TEST_SUITE_P(InstanceSanitizerTest, SanitizerTest,
+ SanitizerTableTypes);
+
TEST(Sanitizer, PoisoningOnErase) {
IntTable t;
int64_t& v = *t.insert(0).first;
@@ -2118,11 +2541,17 @@
}
#endif // ABSL_HAVE_ADDRESS_SANITIZER
-TEST(Table, AlignOne) {
+template <typename T>
+class AlignOneTest : public ::testing::Test {};
+using AlignOneTestTypes =
+ ::testing::Types<Uint8Table, MinimumAlignmentUint8Table>;
+TYPED_TEST_SUITE(AlignOneTest, AlignOneTestTypes);
+
+TYPED_TEST(AlignOneTest, AlignOne) {
// We previously had a bug in which we were copying a control byte over the
// first slot when alignof(value_type) is 1. We test repeated
// insertions/erases and verify that the behavior is correct.
- Uint8Table t;
+ TypeParam t;
std::unordered_set<uint8_t> verifier; // NOLINT
// Do repeated insertions/erases from the table.
@@ -2148,6 +2577,281 @@
}
}
+TEST(Iterator, InvalidUseCrashesWithSanitizers) {
+ if (!SwisstableGenerationsEnabled()) GTEST_SKIP() << "Generations disabled.";
+ if (kMsvc) GTEST_SKIP() << "MSVC doesn't support | in regexp.";
+
+ IntTable t;
+ // Start with 1 element so that `it` is never an end iterator.
+ t.insert(-1);
+ for (int i = 0; i < 10; ++i) {
+ auto it = t.begin();
+ t.insert(i);
+ EXPECT_DEATH_IF_SUPPORTED(*it, kInvalidIteratorDeathMessage);
+ EXPECT_DEATH_IF_SUPPORTED(void(it == t.begin()),
+ kInvalidIteratorDeathMessage);
+ }
+}
+
+TEST(Iterator, InvalidUseWithReserveCrashesWithSanitizers) {
+ if (!SwisstableGenerationsEnabled()) GTEST_SKIP() << "Generations disabled.";
+ if (kMsvc) GTEST_SKIP() << "MSVC doesn't support | in regexp.";
+
+ IntTable t;
+ t.reserve(10);
+ t.insert(0);
+ auto it = t.begin();
+ // Reserved growth can't rehash.
+ for (int i = 1; i < 10; ++i) {
+ t.insert(i);
+ EXPECT_EQ(*it, 0);
+ }
+ // ptr will become invalidated on rehash.
+ const int64_t* ptr = &*it;
+ (void)ptr;
+
+ // erase decreases size but does not decrease reserved growth so the next
+ // insertion still invalidates iterators.
+ t.erase(0);
+ // The first insert after reserved growth is 0 is guaranteed to rehash when
+ // generations are enabled.
+ t.insert(10);
+ EXPECT_DEATH_IF_SUPPORTED(*it, kInvalidIteratorDeathMessage);
+ EXPECT_DEATH_IF_SUPPORTED(void(it == t.begin()),
+ kInvalidIteratorDeathMessage);
+#ifdef ABSL_HAVE_ADDRESS_SANITIZER
+ EXPECT_DEATH_IF_SUPPORTED(std::cout << *ptr, "heap-use-after-free");
+#endif
+}
+
+TEST(Iterator, InvalidUseWithMoveCrashesWithSanitizers) {
+ if (!SwisstableGenerationsEnabled()) GTEST_SKIP() << "Generations disabled.";
+ if (kMsvc) GTEST_SKIP() << "MSVC doesn't support | in regexp.";
+
+ IntTable t1, t2;
+ t1.insert(1);
+ auto it = t1.begin();
+ // ptr will become invalidated on rehash.
+ const int64_t* ptr = &*it;
+ (void)ptr;
+
+ t2 = std::move(t1);
+ EXPECT_DEATH_IF_SUPPORTED(*it, kInvalidIteratorDeathMessage);
+ EXPECT_DEATH_IF_SUPPORTED(void(it == t2.begin()),
+ kInvalidIteratorDeathMessage);
+#ifdef ABSL_HAVE_ADDRESS_SANITIZER
+ EXPECT_DEATH_IF_SUPPORTED(std::cout << *ptr, "heap-use-after-free");
+#endif
+}
+
+TEST(Table, ReservedGrowthUpdatesWhenTableDoesntGrow) {
+ IntTable t;
+ for (int i = 0; i < 8; ++i) t.insert(i);
+ // Want to insert twice without invalidating iterators so reserve.
+ const size_t cap = t.capacity();
+ t.reserve(t.size() + 2);
+ // We want to be testing the case in which the reserve doesn't grow the table.
+ ASSERT_EQ(cap, t.capacity());
+ auto it = t.find(0);
+ t.insert(100);
+ t.insert(200);
+ // `it` shouldn't have been invalidated.
+ EXPECT_EQ(*it, 0);
+}
+
+TEST(Table, EraseBeginEndResetsReservedGrowth) {
+ bool frozen = false;
+ BadHashFreezableIntTable t{FreezableAlloc<int64_t>(&frozen)};
+ t.reserve(100);
+ const size_t cap = t.capacity();
+ frozen = true; // no further allocs allowed
+
+ for (int i = 0; i < 10; ++i) {
+ // Create a long run (hash function returns constant).
+ for (int j = 0; j < 100; ++j) t.insert(j);
+ // Erase elements from the middle of the long run, which creates tombstones.
+ for (int j = 30; j < 60; ++j) t.erase(j);
+ EXPECT_EQ(t.size(), 70);
+ EXPECT_EQ(t.capacity(), cap);
+ ASSERT_EQ(RawHashSetTestOnlyAccess::CountTombstones(t), 30);
+
+ t.erase(t.begin(), t.end());
+
+ EXPECT_EQ(t.size(), 0);
+ EXPECT_EQ(t.capacity(), cap);
+ ASSERT_EQ(RawHashSetTestOnlyAccess::CountTombstones(t), 0);
+ }
+}
+
+TEST(Table, GenerationInfoResetsOnClear) {
+ if (!SwisstableGenerationsEnabled()) GTEST_SKIP() << "Generations disabled.";
+ if (kMsvc) GTEST_SKIP() << "MSVC doesn't support | in regexp.";
+
+ IntTable t;
+ for (int i = 0; i < 1000; ++i) t.insert(i);
+ t.reserve(t.size() + 100);
+
+ t.clear();
+
+ t.insert(0);
+ auto it = t.begin();
+ t.insert(1);
+ EXPECT_DEATH_IF_SUPPORTED(*it, kInvalidIteratorDeathMessage);
+}
+
+TEST(Table, InvalidReferenceUseCrashesWithSanitizers) {
+ if (!SwisstableGenerationsEnabled()) GTEST_SKIP() << "Generations disabled.";
+#ifdef ABSL_HAVE_MEMORY_SANITIZER
+ GTEST_SKIP() << "MSan fails to detect some of these rehashes.";
+#endif
+
+ IntTable t;
+ t.insert(0);
+ // Rehashing is guaranteed on every insertion while capacity is less than
+ // RehashProbabilityConstant().
+ int64_t i = 0;
+ while (t.capacity() <= RehashProbabilityConstant()) {
+ // ptr will become invalidated on rehash.
+ const int64_t* ptr = &*t.begin();
+ t.insert(++i);
+ EXPECT_DEATH_IF_SUPPORTED(std::cout << *ptr, "use-after-free") << i;
+ }
+}
+
+TEST(Iterator, InvalidComparisonDifferentTables) {
+ if (!SwisstableGenerationsEnabled()) GTEST_SKIP() << "Generations disabled.";
+
+ IntTable t1, t2;
+ IntTable::iterator default_constructed_iter;
+ // We randomly use one of N empty generations for generations from empty
+ // hashtables. In general, we won't always detect when iterators from
+ // different empty hashtables are compared, but in this test case, we
+ // should deterministically detect the error due to our randomness yielding
+ // consecutive random generations.
+ EXPECT_DEATH_IF_SUPPORTED(void(t1.end() == t2.end()),
+ "Invalid iterator comparison.*empty hashtables");
+ EXPECT_DEATH_IF_SUPPORTED(void(t1.end() == default_constructed_iter),
+ "Invalid iterator comparison.*default-constructed");
+ t1.insert(0);
+ EXPECT_DEATH_IF_SUPPORTED(void(t1.begin() == t2.end()),
+ "Invalid iterator comparison.*empty hashtable");
+ EXPECT_DEATH_IF_SUPPORTED(void(t1.begin() == default_constructed_iter),
+ "Invalid iterator comparison.*default-constructed");
+ t2.insert(0);
+ EXPECT_DEATH_IF_SUPPORTED(void(t1.begin() == t2.end()),
+ "Invalid iterator comparison.*end.. iterator");
+ EXPECT_DEATH_IF_SUPPORTED(void(t1.begin() == t2.begin()),
+ "Invalid iterator comparison.*non-end");
+}
+
+template <typename Alloc>
+using RawHashSetAlloc = raw_hash_set<IntPolicy, hash_default_hash<int64_t>,
+ std::equal_to<int64_t>, Alloc>;
+
+TEST(Table, AllocatorPropagation) { TestAllocPropagation<RawHashSetAlloc>(); }
+
+struct CountedHash {
+ size_t operator()(int64_t value) const {
+ ++count;
+ return static_cast<size_t>(value);
+ }
+ mutable int count = 0;
+};
+
+struct CountedHashIntTable
+ : raw_hash_set<IntPolicy, CountedHash, std::equal_to<int>,
+ std::allocator<int>> {
+ using Base = typename CountedHashIntTable::raw_hash_set;
+ using Base::Base;
+};
+
+TEST(Table, CountedHash) {
+ // Verify that raw_hash_set does not compute redundant hashes.
+#ifdef NDEBUG
+ constexpr bool kExpectMinimumHashes = true;
+#else
+ constexpr bool kExpectMinimumHashes = false;
+#endif
+ if (!kExpectMinimumHashes) {
+ GTEST_SKIP() << "Only run under NDEBUG: `assert` statements may cause "
+ "redundant hashing.";
+ }
+
+ using Table = CountedHashIntTable;
+ auto HashCount = [](const Table& t) { return t.hash_function().count; };
+ {
+ Table t;
+ EXPECT_EQ(HashCount(t), 0);
+ }
+ {
+ Table t;
+ t.insert(1);
+ EXPECT_EQ(HashCount(t), 1);
+ t.erase(1);
+ EXPECT_EQ(HashCount(t), 2);
+ }
+ {
+ Table t;
+ t.insert(3);
+ EXPECT_EQ(HashCount(t), 1);
+ auto node = t.extract(3);
+ EXPECT_EQ(HashCount(t), 2);
+ t.insert(std::move(node));
+ EXPECT_EQ(HashCount(t), 3);
+ }
+ {
+ Table t;
+ t.emplace(5);
+ EXPECT_EQ(HashCount(t), 1);
+ }
+ {
+ Table src;
+ src.insert(7);
+ Table dst;
+ dst.merge(src);
+ EXPECT_EQ(HashCount(dst), 1);
+ }
+}
+
+TEST(Table, IterateOverFullSlotsEmpty) {
+ IntTable t;
+ auto fail_if_any = [](const ctrl_t*, int64_t* i) {
+ FAIL() << "expected no slots " << i;
+ };
+ container_internal::IterateOverFullSlots(
+ RawHashSetTestOnlyAccess::GetCommon(t),
+ RawHashSetTestOnlyAccess::GetSlots(t), fail_if_any);
+ for (size_t i = 0; i < 256; ++i) {
+ t.reserve(i);
+ container_internal::IterateOverFullSlots(
+ RawHashSetTestOnlyAccess::GetCommon(t),
+ RawHashSetTestOnlyAccess::GetSlots(t), fail_if_any);
+ }
+}
+
+TEST(Table, IterateOverFullSlotsFull) {
+ IntTable t;
+
+ std::vector<int64_t> expected_slots;
+ for (int64_t i = 0; i < 128; ++i) {
+ t.insert(i);
+ expected_slots.push_back(i);
+
+ std::vector<int64_t> slots;
+ container_internal::IterateOverFullSlots(
+ RawHashSetTestOnlyAccess::GetCommon(t),
+ RawHashSetTestOnlyAccess::GetSlots(t),
+ [&t, &slots](const ctrl_t* ctrl, int64_t* i) {
+ ptrdiff_t ctrl_offset =
+ ctrl - RawHashSetTestOnlyAccess::GetCommon(t).control();
+ ptrdiff_t slot_offset = i - RawHashSetTestOnlyAccess::GetSlots(t);
+ ASSERT_EQ(ctrl_offset, slot_offset);
+ slots.push_back(*i);
+ });
+ EXPECT_THAT(slots, testing::UnorderedElementsAreArray(expected_slots));
+ }
+}
+
} // namespace
} // namespace container_internal
ABSL_NAMESPACE_END
diff --git a/absl/container/internal/test_allocator.h b/absl/container/internal/test_allocator.h
new file mode 100644
index 0000000..8e365a3
--- /dev/null
+++ b/absl/container/internal/test_allocator.h
@@ -0,0 +1,387 @@
+// Copyright 2018 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef ABSL_CONTAINER_INTERNAL_TEST_ALLOCATOR_H_
+#define ABSL_CONTAINER_INTERNAL_TEST_ALLOCATOR_H_
+
+#include <cassert>
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+#include <type_traits>
+
+#include "gtest/gtest.h"
+#include "absl/base/config.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace container_internal {
+
+// This is a stateful allocator, but the state lives outside of the
+// allocator (in whatever test is using the allocator). This is odd
+// but helps in tests where the allocator is propagated into nested
+// containers - that chain of allocators uses the same state and is
+// thus easier to query for aggregate allocation information.
+template <typename T>
+class CountingAllocator {
+ public:
+ using Allocator = std::allocator<T>;
+ using AllocatorTraits = std::allocator_traits<Allocator>;
+ using value_type = typename AllocatorTraits::value_type;
+ using pointer = typename AllocatorTraits::pointer;
+ using const_pointer = typename AllocatorTraits::const_pointer;
+ using size_type = typename AllocatorTraits::size_type;
+ using difference_type = typename AllocatorTraits::difference_type;
+
+ CountingAllocator() = default;
+ explicit CountingAllocator(int64_t* bytes_used) : bytes_used_(bytes_used) {}
+ CountingAllocator(int64_t* bytes_used, int64_t* instance_count)
+ : bytes_used_(bytes_used), instance_count_(instance_count) {}
+
+ template <typename U>
+ CountingAllocator(const CountingAllocator<U>& x)
+ : bytes_used_(x.bytes_used_), instance_count_(x.instance_count_) {}
+
+ pointer allocate(
+ size_type n,
+ typename AllocatorTraits::const_void_pointer hint = nullptr) {
+ Allocator allocator;
+ pointer ptr = AllocatorTraits::allocate(allocator, n, hint);
+ if (bytes_used_ != nullptr) {
+ *bytes_used_ += n * sizeof(T);
+ }
+ return ptr;
+ }
+
+ void deallocate(pointer p, size_type n) {
+ Allocator allocator;
+ AllocatorTraits::deallocate(allocator, p, n);
+ if (bytes_used_ != nullptr) {
+ *bytes_used_ -= n * sizeof(T);
+ }
+ }
+
+ template <typename U, typename... Args>
+ void construct(U* p, Args&&... args) {
+ Allocator allocator;
+ AllocatorTraits::construct(allocator, p, std::forward<Args>(args)...);
+ if (instance_count_ != nullptr) {
+ *instance_count_ += 1;
+ }
+ }
+
+ template <typename U>
+ void destroy(U* p) {
+ Allocator allocator;
+ // Ignore GCC warning bug.
+#if ABSL_INTERNAL_HAVE_MIN_GNUC_VERSION(12, 0)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wuse-after-free"
+#endif
+ AllocatorTraits::destroy(allocator, p);
+#if ABSL_INTERNAL_HAVE_MIN_GNUC_VERSION(12, 0)
+#pragma GCC diagnostic pop
+#endif
+ if (instance_count_ != nullptr) {
+ *instance_count_ -= 1;
+ }
+ }
+
+ template <typename U>
+ class rebind {
+ public:
+ using other = CountingAllocator<U>;
+ };
+
+ friend bool operator==(const CountingAllocator& a,
+ const CountingAllocator& b) {
+ return a.bytes_used_ == b.bytes_used_ &&
+ a.instance_count_ == b.instance_count_;
+ }
+
+ friend bool operator!=(const CountingAllocator& a,
+ const CountingAllocator& b) {
+ return !(a == b);
+ }
+
+ int64_t* bytes_used_ = nullptr;
+ int64_t* instance_count_ = nullptr;
+};
+
+template <typename T>
+struct CopyAssignPropagatingCountingAlloc : public CountingAllocator<T> {
+ using propagate_on_container_copy_assignment = std::true_type;
+
+ using Base = CountingAllocator<T>;
+ using Base::Base;
+
+ template <typename U>
+ explicit CopyAssignPropagatingCountingAlloc(
+ const CopyAssignPropagatingCountingAlloc<U>& other)
+ : Base(other.bytes_used_, other.instance_count_) {}
+
+ template <typename U>
+ struct rebind {
+ using other = CopyAssignPropagatingCountingAlloc<U>;
+ };
+};
+
+template <typename T>
+struct MoveAssignPropagatingCountingAlloc : public CountingAllocator<T> {
+ using propagate_on_container_move_assignment = std::true_type;
+
+ using Base = CountingAllocator<T>;
+ using Base::Base;
+
+ template <typename U>
+ explicit MoveAssignPropagatingCountingAlloc(
+ const MoveAssignPropagatingCountingAlloc<U>& other)
+ : Base(other.bytes_used_, other.instance_count_) {}
+
+ template <typename U>
+ struct rebind {
+ using other = MoveAssignPropagatingCountingAlloc<U>;
+ };
+};
+
+template <typename T>
+struct SwapPropagatingCountingAlloc : public CountingAllocator<T> {
+ using propagate_on_container_swap = std::true_type;
+
+ using Base = CountingAllocator<T>;
+ using Base::Base;
+
+ template <typename U>
+ explicit SwapPropagatingCountingAlloc(
+ const SwapPropagatingCountingAlloc<U>& other)
+ : Base(other.bytes_used_, other.instance_count_) {}
+
+ template <typename U>
+ struct rebind {
+ using other = SwapPropagatingCountingAlloc<U>;
+ };
+};
+
+// Tries to allocate memory at the minimum alignment even when the default
+// allocator uses a higher alignment.
+template <typename T>
+struct MinimumAlignmentAlloc : std::allocator<T> {
+ MinimumAlignmentAlloc() = default;
+
+ template <typename U>
+ explicit MinimumAlignmentAlloc(const MinimumAlignmentAlloc<U>& /*other*/) {}
+
+ template <class U>
+ struct rebind {
+ using other = MinimumAlignmentAlloc<U>;
+ };
+
+ T* allocate(size_t n) {
+ T* ptr = std::allocator<T>::allocate(n + 1);
+ char* cptr = reinterpret_cast<char*>(ptr);
+ cptr += alignof(T);
+ return reinterpret_cast<T*>(cptr);
+ }
+
+ void deallocate(T* ptr, size_t n) {
+ char* cptr = reinterpret_cast<char*>(ptr);
+ cptr -= alignof(T);
+ std::allocator<T>::deallocate(reinterpret_cast<T*>(cptr), n + 1);
+ }
+};
+
+inline bool IsAssertEnabled() {
+ // Use an assert with side-effects to figure out if they are actually enabled.
+ bool assert_enabled = false;
+ assert([&]() { // NOLINT
+ assert_enabled = true;
+ return true;
+ }());
+ return assert_enabled;
+}
+
+template <template <class Alloc> class Container>
+void TestCopyAssignAllocPropagation() {
+ int64_t bytes1 = 0, instances1 = 0, bytes2 = 0, instances2 = 0;
+ CopyAssignPropagatingCountingAlloc<int> allocator1(&bytes1, &instances1);
+ CopyAssignPropagatingCountingAlloc<int> allocator2(&bytes2, &instances2);
+
+ // Test propagating allocator_type.
+ {
+ Container<CopyAssignPropagatingCountingAlloc<int>> c1(allocator1);
+ Container<CopyAssignPropagatingCountingAlloc<int>> c2(allocator2);
+
+ for (int i = 0; i < 100; ++i) c1.insert(i);
+
+ EXPECT_NE(c2.get_allocator(), allocator1);
+ EXPECT_EQ(instances1, 100);
+ EXPECT_EQ(instances2, 0);
+
+ c2 = c1;
+
+ EXPECT_EQ(c2.get_allocator(), allocator1);
+ EXPECT_EQ(instances1, 200);
+ EXPECT_EQ(instances2, 0);
+ }
+ // Test non-propagating allocator_type with different allocators.
+ {
+ Container<CountingAllocator<int>> c1(allocator1), c2(allocator2);
+
+ for (int i = 0; i < 100; ++i) c1.insert(i);
+
+ EXPECT_EQ(c2.get_allocator(), allocator2);
+ EXPECT_EQ(instances1, 100);
+ EXPECT_EQ(instances2, 0);
+
+ c2 = c1;
+
+ EXPECT_EQ(c2.get_allocator(), allocator2);
+ EXPECT_EQ(instances1, 100);
+ EXPECT_EQ(instances2, 100);
+ }
+ EXPECT_EQ(bytes1, 0);
+ EXPECT_EQ(instances1, 0);
+ EXPECT_EQ(bytes2, 0);
+ EXPECT_EQ(instances2, 0);
+}
+
+template <template <class Alloc> class Container>
+void TestMoveAssignAllocPropagation() {
+ int64_t bytes1 = 0, instances1 = 0, bytes2 = 0, instances2 = 0;
+ MoveAssignPropagatingCountingAlloc<int> allocator1(&bytes1, &instances1);
+ MoveAssignPropagatingCountingAlloc<int> allocator2(&bytes2, &instances2);
+
+ // Test propagating allocator_type.
+ {
+ Container<MoveAssignPropagatingCountingAlloc<int>> c1(allocator1);
+ Container<MoveAssignPropagatingCountingAlloc<int>> c2(allocator2);
+
+ for (int i = 0; i < 100; ++i) c1.insert(i);
+
+ EXPECT_NE(c2.get_allocator(), allocator1);
+ EXPECT_EQ(instances1, 100);
+ EXPECT_EQ(instances2, 0);
+
+ c2 = std::move(c1);
+
+ EXPECT_EQ(c2.get_allocator(), allocator1);
+ EXPECT_EQ(instances1, 100);
+ EXPECT_EQ(instances2, 0);
+ }
+ // Test non-propagating allocator_type with equal allocators.
+ {
+ Container<CountingAllocator<int>> c1(allocator1), c2(allocator1);
+
+ for (int i = 0; i < 100; ++i) c1.insert(i);
+
+ EXPECT_EQ(c2.get_allocator(), allocator1);
+ EXPECT_EQ(instances1, 100);
+ EXPECT_EQ(instances2, 0);
+
+ c2 = std::move(c1);
+
+ EXPECT_EQ(c2.get_allocator(), allocator1);
+ EXPECT_EQ(instances1, 100);
+ EXPECT_EQ(instances2, 0);
+ }
+ // Test non-propagating allocator_type with different allocators.
+ {
+ Container<CountingAllocator<int>> c1(allocator1), c2(allocator2);
+
+ for (int i = 0; i < 100; ++i) c1.insert(i);
+
+ EXPECT_NE(c2.get_allocator(), allocator1);
+ EXPECT_EQ(instances1, 100);
+ EXPECT_EQ(instances2, 0);
+
+ c2 = std::move(c1);
+
+ EXPECT_EQ(c2.get_allocator(), allocator2);
+ EXPECT_LE(instances1, 100); // The values in c1 may or may not have been
+ // destroyed at this point.
+ EXPECT_EQ(instances2, 100);
+ }
+ EXPECT_EQ(bytes1, 0);
+ EXPECT_EQ(instances1, 0);
+ EXPECT_EQ(bytes2, 0);
+ EXPECT_EQ(instances2, 0);
+}
+
+template <template <class Alloc> class Container>
+void TestSwapAllocPropagation() {
+ int64_t bytes1 = 0, instances1 = 0, bytes2 = 0, instances2 = 0;
+ SwapPropagatingCountingAlloc<int> allocator1(&bytes1, &instances1);
+ SwapPropagatingCountingAlloc<int> allocator2(&bytes2, &instances2);
+
+ // Test propagating allocator_type.
+ {
+ Container<SwapPropagatingCountingAlloc<int>> c1(allocator1), c2(allocator2);
+
+ for (int i = 0; i < 100; ++i) c1.insert(i);
+
+ EXPECT_NE(c2.get_allocator(), allocator1);
+ EXPECT_EQ(instances1, 100);
+ EXPECT_EQ(instances2, 0);
+
+ c2.swap(c1);
+
+ EXPECT_EQ(c2.get_allocator(), allocator1);
+ EXPECT_EQ(instances1, 100);
+ EXPECT_EQ(instances2, 0);
+ }
+ // Test non-propagating allocator_type with equal allocators.
+ {
+ Container<CountingAllocator<int>> c1(allocator1), c2(allocator1);
+
+ for (int i = 0; i < 100; ++i) c1.insert(i);
+
+ EXPECT_EQ(c2.get_allocator(), allocator1);
+ EXPECT_EQ(instances1, 100);
+ EXPECT_EQ(instances2, 0);
+
+ c2.swap(c1);
+
+ EXPECT_EQ(c2.get_allocator(), allocator1);
+ EXPECT_EQ(instances1, 100);
+ EXPECT_EQ(instances2, 0);
+ }
+ // Test non-propagating allocator_type with different allocators.
+ {
+ Container<CountingAllocator<int>> c1(allocator1), c2(allocator2);
+
+ for (int i = 0; i < 100; ++i) c1.insert(i);
+
+ EXPECT_NE(c1.get_allocator(), c2.get_allocator());
+ if (IsAssertEnabled()) {
+ EXPECT_DEATH_IF_SUPPORTED(c2.swap(c1), "");
+ }
+ }
+ EXPECT_EQ(bytes1, 0);
+ EXPECT_EQ(instances1, 0);
+ EXPECT_EQ(bytes2, 0);
+ EXPECT_EQ(instances2, 0);
+}
+
+template <template <class Alloc> class Container>
+void TestAllocPropagation() {
+ TestCopyAssignAllocPropagation<Container>();
+ TestMoveAssignAllocPropagation<Container>();
+ TestSwapAllocPropagation<Container>();
+}
+
+} // namespace container_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_CONTAINER_INTERNAL_TEST_ALLOCATOR_H_
diff --git a/absl/container/internal/unordered_map_constructor_test.h b/absl/container/internal/unordered_map_constructor_test.h
index c1d20f3..7e84dc2 100644
--- a/absl/container/internal/unordered_map_constructor_test.h
+++ b/absl/container/internal/unordered_map_constructor_test.h
@@ -476,7 +476,7 @@
// containers in unspecified state (and in practice in causes memory-leak
// according to heap-checker!).
-REGISTER_TYPED_TEST_CASE_P(
+REGISTER_TYPED_TEST_SUITE_P(
ConstructorTest, NoArgs, BucketCount, BucketCountHash, BucketCountHashEqual,
BucketCountHashEqualAlloc, BucketCountAlloc, BucketCountHashAlloc, Alloc,
InputIteratorBucketHashEqualAlloc, InputIteratorBucketAlloc,
diff --git a/absl/container/internal/unordered_map_lookup_test.h b/absl/container/internal/unordered_map_lookup_test.h
index e76421e..3713cd9 100644
--- a/absl/container/internal/unordered_map_lookup_test.h
+++ b/absl/container/internal/unordered_map_lookup_test.h
@@ -107,8 +107,8 @@
}
}
-REGISTER_TYPED_TEST_CASE_P(LookupTest, At, OperatorBracket, Count, Find,
- EqualRange);
+REGISTER_TYPED_TEST_SUITE_P(LookupTest, At, OperatorBracket, Count, Find,
+ EqualRange);
} // namespace container_internal
ABSL_NAMESPACE_END
diff --git a/absl/container/internal/unordered_map_modifiers_test.h b/absl/container/internal/unordered_map_modifiers_test.h
index d354393..4d9ab30 100644
--- a/absl/container/internal/unordered_map_modifiers_test.h
+++ b/absl/container/internal/unordered_map_modifiers_test.h
@@ -297,11 +297,12 @@
// TODO(alkis): Write tests for extract.
// TODO(alkis): Write tests for merge.
-REGISTER_TYPED_TEST_CASE_P(ModifiersTest, Clear, Insert, InsertHint,
- InsertRange, InsertWithinCapacity,
- InsertRangeWithinCapacity, InsertOrAssign,
- InsertOrAssignHint, Emplace, EmplaceHint, TryEmplace,
- TryEmplaceHint, Erase, EraseRange, EraseKey, Swap);
+REGISTER_TYPED_TEST_SUITE_P(ModifiersTest, Clear, Insert, InsertHint,
+ InsertRange, InsertWithinCapacity,
+ InsertRangeWithinCapacity, InsertOrAssign,
+ InsertOrAssignHint, Emplace, EmplaceHint,
+ TryEmplace, TryEmplaceHint, Erase, EraseRange,
+ EraseKey, Swap);
template <typename Type>
struct is_unique_ptr : std::false_type {};
diff --git a/absl/container/internal/unordered_set_constructor_test.h b/absl/container/internal/unordered_set_constructor_test.h
index 41165b0..af1116e 100644
--- a/absl/container/internal/unordered_set_constructor_test.h
+++ b/absl/container/internal/unordered_set_constructor_test.h
@@ -478,7 +478,7 @@
EXPECT_THAT(keys(m), ::testing::UnorderedElementsAreArray(values));
}
-REGISTER_TYPED_TEST_CASE_P(
+REGISTER_TYPED_TEST_SUITE_P(
ConstructorTest, NoArgs, BucketCount, BucketCountHash, BucketCountHashEqual,
BucketCountHashEqualAlloc, BucketCountAlloc, BucketCountHashAlloc, Alloc,
InputIteratorBucketHashEqualAlloc, InputIteratorBucketAlloc,
diff --git a/absl/container/internal/unordered_set_lookup_test.h b/absl/container/internal/unordered_set_lookup_test.h
index 8f2f4b2..b35f766 100644
--- a/absl/container/internal/unordered_set_lookup_test.h
+++ b/absl/container/internal/unordered_set_lookup_test.h
@@ -82,7 +82,7 @@
}
}
-REGISTER_TYPED_TEST_CASE_P(LookupTest, Count, Find, EqualRange);
+REGISTER_TYPED_TEST_SUITE_P(LookupTest, Count, Find, EqualRange);
} // namespace container_internal
ABSL_NAMESPACE_END
diff --git a/absl/container/internal/unordered_set_modifiers_test.h b/absl/container/internal/unordered_set_modifiers_test.h
index 6e473e4..d8864bb 100644
--- a/absl/container/internal/unordered_set_modifiers_test.h
+++ b/absl/container/internal/unordered_set_modifiers_test.h
@@ -209,10 +209,10 @@
// TODO(alkis): Write tests for extract.
// TODO(alkis): Write tests for merge.
-REGISTER_TYPED_TEST_CASE_P(ModifiersTest, Clear, Insert, InsertHint,
- InsertRange, InsertWithinCapacity,
- InsertRangeWithinCapacity, Emplace, EmplaceHint,
- Erase, EraseRange, EraseKey, Swap);
+REGISTER_TYPED_TEST_SUITE_P(ModifiersTest, Clear, Insert, InsertHint,
+ InsertRange, InsertWithinCapacity,
+ InsertRangeWithinCapacity, Emplace, EmplaceHint,
+ Erase, EraseRange, EraseKey, Swap);
} // namespace container_internal
ABSL_NAMESPACE_END
diff --git a/absl/container/node_hash_map.h b/absl/container/node_hash_map.h
index 7a39f62..cb41543 100644
--- a/absl/container/node_hash_map.h
+++ b/absl/container/node_hash_map.h
@@ -36,14 +36,16 @@
#ifndef ABSL_CONTAINER_NODE_HASH_MAP_H_
#define ABSL_CONTAINER_NODE_HASH_MAP_H_
+#include <cstddef>
#include <tuple>
#include <type_traits>
#include <utility>
#include "absl/algorithm/container.h"
+#include "absl/base/macros.h"
#include "absl/container/internal/container_memory.h"
#include "absl/container/internal/hash_function_defaults.h" // IWYU pragma: export
-#include "absl/container/internal/node_hash_policy.h"
+#include "absl/container/internal/node_slot_policy.h"
#include "absl/container/internal/raw_hash_map.h" // IWYU pragma: export
#include "absl/memory/memory.h"
@@ -65,7 +67,7 @@
//
// * Supports heterogeneous lookup, through `find()`, `operator[]()` and
// `insert()`, provided that the map is provided a compatible heterogeneous
-// hashing function and equality operator.
+// hashing function and equality operator. See below for details.
// * Contains a `capacity()` member function indicating the number of element
// slots (open, deleted, and empty) within the hash map.
// * Returns `void` from the `erase(iterator)` overload.
@@ -77,6 +79,23 @@
// absl/hash/hash.h for information on extending Abseil hashing to user-defined
// types.
//
+// Using `absl::node_hash_map` at interface boundaries in dynamically loaded
+// libraries (e.g. .dll, .so) is unsupported due to way `absl::Hash` values may
+// be randomized across dynamically loaded libraries.
+//
+// To achieve heterogeneous lookup for custom types either `Hash` and `Eq` type
+// parameters can be used or `T` should have public inner types
+// `absl_container_hash` and (optionally) `absl_container_eq`. In either case,
+// `typename Hash::is_transparent` and `typename Eq::is_transparent` should be
+// well-formed. Both types are basically functors:
+// * `Hash` should support `size_t operator()(U val) const` that returns a hash
+// for the given `val`.
+// * `Eq` should support `bool operator()(U lhs, V rhs) const` that returns true
+// if `lhs` is equal to `rhs`.
+//
+// In most cases `T` needs only to provide the `absl_container_hash`. In this
+// case `std::equal_to<void>` will be used instead of `eq` part.
+//
// Example:
//
// // Create a node hash map of three strings (that map to strings)
@@ -221,7 +240,11 @@
// iterator erase(const_iterator first, const_iterator last):
//
// Erases the elements in the open interval [`first`, `last`), returning an
- // iterator pointing to `last`.
+ // iterator pointing to `last`. The special case of calling
+ // `erase(begin(), end())` resets the reserved growth such that if
+ // `reserve(N)` has previously been called and there has been no intervening
+ // call to `clear()`, then after calling `erase(begin(), end())`, it is safe
+ // to assume that inserting N elements will not cause a rehash.
//
// size_type erase(const key_type& key):
//
@@ -347,8 +370,8 @@
// `node_hash_map`.
//
// iterator try_emplace(const_iterator hint,
- // const init_type& k, Args&&... args):
- // iterator try_emplace(const_iterator hint, init_type&& k, Args&&... args):
+ // const key_type& k, Args&&... args):
+ // iterator try_emplace(const_iterator hint, key_type&& k, Args&&... args):
//
// Inserts (via copy or move) the element of the specified key into the
// `node_hash_map` using the position of `hint` as a non-binding suggestion
@@ -399,7 +422,7 @@
// for the past-the-end iterator, which is invalidated.
//
// `swap()` requires that the node hash map's hashing and key equivalence
- // functions be Swappable, and are exchaged using unqualified calls to
+ // functions be Swappable, and are exchanged using unqualified calls to
// non-member `swap()`. If the map's allocator has
// `std::allocator_traits<allocator_type>::propagate_on_container_swap::value`
// set to `true`, the allocators are also exchanged using an unqualified call
@@ -525,17 +548,19 @@
// erase_if(node_hash_map<>, Pred)
//
// Erases all elements that satisfy the predicate `pred` from the container `c`.
+// Returns the number of erased elements.
template <typename K, typename V, typename H, typename E, typename A,
typename Predicate>
-void erase_if(node_hash_map<K, V, H, E, A>& c, Predicate pred) {
- container_internal::EraseIf(pred, &c);
+typename node_hash_map<K, V, H, E, A>::size_type erase_if(
+ node_hash_map<K, V, H, E, A>& c, Predicate pred) {
+ return container_internal::EraseIf(pred, &c);
}
namespace container_internal {
template <class Key, class Value>
class NodeHashMapPolicy
- : public absl::container_internal::node_hash_policy<
+ : public absl::container_internal::node_slot_policy<
std::pair<const Key, Value>&, NodeHashMapPolicy<Key, Value>> {
using value_type = std::pair<const Key, Value>;
@@ -579,6 +604,13 @@
static Value& value(value_type* elem) { return elem->second; }
static const Value& value(const value_type* elem) { return elem->second; }
+
+ template <class Hash>
+ static constexpr HashSlotFn get_hash_slot_fn() {
+ return memory_internal::IsLayoutCompatible<Key, Value>::value
+ ? &TypeErasedDerefAndApplyToSlotFn<Hash, Key>
+ : nullptr;
+ }
};
} // namespace container_internal
diff --git a/absl/container/node_hash_map_test.cc b/absl/container/node_hash_map_test.cc
index 8f59a1e..9bcf470 100644
--- a/absl/container/node_hash_map_test.cc
+++ b/absl/container/node_hash_map_test.cc
@@ -223,33 +223,36 @@
// Erase all elements.
{
node_hash_map<int, int> s = {{1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}};
- erase_if(s, [](std::pair<const int, int>) { return true; });
+ EXPECT_EQ(erase_if(s, [](std::pair<const int, int>) { return true; }), 5);
EXPECT_THAT(s, IsEmpty());
}
// Erase no elements.
{
node_hash_map<int, int> s = {{1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}};
- erase_if(s, [](std::pair<const int, int>) { return false; });
+ EXPECT_EQ(erase_if(s, [](std::pair<const int, int>) { return false; }), 0);
EXPECT_THAT(s, UnorderedElementsAre(Pair(1, 1), Pair(2, 2), Pair(3, 3),
Pair(4, 4), Pair(5, 5)));
}
// Erase specific elements.
{
node_hash_map<int, int> s = {{1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}};
- erase_if(s,
- [](std::pair<const int, int> kvp) { return kvp.first % 2 == 1; });
+ EXPECT_EQ(erase_if(s,
+ [](std::pair<const int, int> kvp) {
+ return kvp.first % 2 == 1;
+ }),
+ 3);
EXPECT_THAT(s, UnorderedElementsAre(Pair(2, 2), Pair(4, 4)));
}
// Predicate is function reference.
{
node_hash_map<int, int> s = {{1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}};
- erase_if(s, FirstIsEven);
+ EXPECT_EQ(erase_if(s, FirstIsEven), 2);
EXPECT_THAT(s, UnorderedElementsAre(Pair(1, 1), Pair(3, 3), Pair(5, 5)));
}
// Predicate is function pointer.
{
node_hash_map<int, int> s = {{1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}};
- erase_if(s, &FirstIsEven);
+ EXPECT_EQ(erase_if(s, &FirstIsEven), 2);
EXPECT_THAT(s, UnorderedElementsAre(Pair(1, 1), Pair(3, 3), Pair(5, 5)));
}
}
@@ -269,6 +272,14 @@
}
#endif
+TEST(NodeHashMap, RecursiveTypeCompiles) {
+ struct RecursiveType {
+ node_hash_map<int, RecursiveType> m;
+ };
+ RecursiveType t;
+ t.m[0] = RecursiveType{};
+}
+
} // namespace
} // namespace container_internal
ABSL_NAMESPACE_END
diff --git a/absl/container/node_hash_set.h b/absl/container/node_hash_set.h
index 93b15f4..8cc4b62 100644
--- a/absl/container/node_hash_set.h
+++ b/absl/container/node_hash_set.h
@@ -35,11 +35,14 @@
#ifndef ABSL_CONTAINER_NODE_HASH_SET_H_
#define ABSL_CONTAINER_NODE_HASH_SET_H_
+#include <cstddef>
#include <type_traits>
#include "absl/algorithm/container.h"
+#include "absl/base/macros.h"
+#include "absl/container/internal/container_memory.h"
#include "absl/container/internal/hash_function_defaults.h" // IWYU pragma: export
-#include "absl/container/internal/node_hash_policy.h"
+#include "absl/container/internal/node_slot_policy.h"
#include "absl/container/internal/raw_hash_set.h" // IWYU pragma: export
#include "absl/memory/memory.h"
@@ -61,7 +64,7 @@
//
// * Supports heterogeneous lookup, through `find()`, `operator[]()` and
// `insert()`, provided that the set is provided a compatible heterogeneous
-// hashing function and equality operator.
+// hashing function and equality operator. See below for details.
// * Contains a `capacity()` member function indicating the number of element
// slots (open, deleted, and empty) within the hash set.
// * Returns `void` from the `erase(iterator)` overload.
@@ -73,6 +76,23 @@
// absl/hash/hash.h for information on extending Abseil hashing to user-defined
// types.
//
+// Using `absl::node_hash_set` at interface boundaries in dynamically loaded
+// libraries (e.g. .dll, .so) is unsupported due to way `absl::Hash` values may
+// be randomized across dynamically loaded libraries.
+//
+// To achieve heterogeneous lookup for custom types either `Hash` and `Eq` type
+// parameters can be used or `T` should have public inner types
+// `absl_container_hash` and (optionally) `absl_container_eq`. In either case,
+// `typename Hash::is_transparent` and `typename Eq::is_transparent` should be
+// well-formed. Both types are basically functors:
+// * `Hash` should support `size_t operator()(U val) const` that returns a hash
+// for the given `val`.
+// * `Eq` should support `bool operator()(U lhs, V rhs) const` that returns true
+// if `lhs` is equal to `rhs`.
+//
+// In most cases `T` needs only to provide the `absl_container_hash`. In this
+// case `std::equal_to<void>` will be used instead of `eq` part.
+//
// Example:
//
// // Create a node hash set of three strings
@@ -213,7 +233,11 @@
// iterator erase(const_iterator first, const_iterator last):
//
// Erases the elements in the open interval [`first`, `last`), returning an
- // iterator pointing to `last`.
+ // iterator pointing to `last`. The special case of calling
+ // `erase(begin(), end())` resets the reserved growth such that if
+ // `reserve(N)` has previously been called and there has been no intervening
+ // call to `clear()`, then after calling `erase(begin(), end())`, it is safe
+ // to assume that inserting N elements will not cause a rehash.
//
// size_type erase(const key_type& key):
//
@@ -329,7 +353,7 @@
// for the past-the-end iterator, which is invalidated.
//
// `swap()` requires that the node hash set's hashing and key equivalence
- // functions be Swappable, and are exchaged using unqualified calls to
+ // functions be Swappable, and are exchanged using unqualified calls to
// non-member `swap()`. If the set's allocator has
// `std::allocator_traits<allocator_type>::propagate_on_container_swap::value`
// set to `true`, the allocators are also exchanged using an unqualified call
@@ -433,16 +457,18 @@
// erase_if(node_hash_set<>, Pred)
//
// Erases all elements that satisfy the predicate `pred` from the container `c`.
+// Returns the number of erased elements.
template <typename T, typename H, typename E, typename A, typename Predicate>
-void erase_if(node_hash_set<T, H, E, A>& c, Predicate pred) {
- container_internal::EraseIf(pred, &c);
+typename node_hash_set<T, H, E, A>::size_type erase_if(
+ node_hash_set<T, H, E, A>& c, Predicate pred) {
+ return container_internal::EraseIf(pred, &c);
}
namespace container_internal {
template <class T>
struct NodeHashSetPolicy
- : absl::container_internal::node_hash_policy<T&, NodeHashSetPolicy<T>> {
+ : absl::container_internal::node_slot_policy<T&, NodeHashSetPolicy<T>> {
using key_type = T;
using init_type = T;
using constant_iterators = std::true_type;
@@ -476,6 +502,11 @@
}
static size_t element_space_used(const T*) { return sizeof(T); }
+
+ template <class Hash>
+ static constexpr HashSlotFn get_hash_slot_fn() {
+ return &TypeErasedDerefAndApplyToSlotFn<Hash, T>;
+ }
};
} // namespace container_internal
diff --git a/absl/container/node_hash_set_test.cc b/absl/container/node_hash_set_test.cc
index 7ddad20..98a8dbd 100644
--- a/absl/container/node_hash_set_test.cc
+++ b/absl/container/node_hash_set_test.cc
@@ -108,31 +108,31 @@
// Erase all elements.
{
node_hash_set<int> s = {1, 2, 3, 4, 5};
- erase_if(s, [](int) { return true; });
+ EXPECT_EQ(erase_if(s, [](int) { return true; }), 5);
EXPECT_THAT(s, IsEmpty());
}
// Erase no elements.
{
node_hash_set<int> s = {1, 2, 3, 4, 5};
- erase_if(s, [](int) { return false; });
+ EXPECT_EQ(erase_if(s, [](int) { return false; }), 0);
EXPECT_THAT(s, UnorderedElementsAre(1, 2, 3, 4, 5));
}
// Erase specific elements.
{
node_hash_set<int> s = {1, 2, 3, 4, 5};
- erase_if(s, [](int k) { return k % 2 == 1; });
+ EXPECT_EQ(erase_if(s, [](int k) { return k % 2 == 1; }), 3);
EXPECT_THAT(s, UnorderedElementsAre(2, 4));
}
// Predicate is function reference.
{
node_hash_set<int> s = {1, 2, 3, 4, 5};
- erase_if(s, IsEven);
+ EXPECT_EQ(erase_if(s, IsEven), 2);
EXPECT_THAT(s, UnorderedElementsAre(1, 3, 5));
}
// Predicate is function pointer.
{
node_hash_set<int> s = {1, 2, 3, 4, 5};
- erase_if(s, &IsEven);
+ EXPECT_EQ(erase_if(s, &IsEven), 2);
EXPECT_THAT(s, UnorderedElementsAre(1, 3, 5));
}
}
diff --git a/absl/container/sample_element_size_test.cc b/absl/container/sample_element_size_test.cc
new file mode 100644
index 0000000..b23626b
--- /dev/null
+++ b/absl/container/sample_element_size_test.cc
@@ -0,0 +1,114 @@
+// 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 "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/container/flat_hash_map.h"
+#include "absl/container/flat_hash_set.h"
+#include "absl/container/node_hash_map.h"
+#include "absl/container/node_hash_set.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace container_internal {
+namespace {
+
+#if defined(ABSL_INTERNAL_HASHTABLEZ_SAMPLE)
+// Create some tables of type `Table`, then look at all the new
+// `HashtablezInfo`s to make sure that the `inline_element_size ==
+// expected_element_size`. The `inline_element_size` is the amount of memory
+// allocated for each slot of a hash table, that is `sizeof(slot_type)`. Add
+// the new `HashtablezInfo`s to `preexisting_info`. Store all the new tables
+// into `tables`.
+template <class Table>
+void TestInlineElementSize(
+ HashtablezSampler& sampler,
+ // clang-tidy gives a false positive on this declaration. This unordered
+ // set cannot be flat_hash_set, however, since that would introduce a mutex
+ // deadlock.
+ std::unordered_set<const HashtablezInfo*>& preexisting_info, // NOLINT
+ std::vector<Table>& tables, const typename Table::value_type& elt,
+ size_t expected_element_size) {
+ for (int i = 0; i < 10; ++i) {
+ // We create a new table and must store it somewhere so that when we store
+ // a pointer to the resulting `HashtablezInfo` into `preexisting_info`
+ // that we aren't storing a dangling pointer.
+ tables.emplace_back();
+ // We must insert an element to get a hashtablez to instantiate.
+ tables.back().insert(elt);
+ }
+ size_t new_count = 0;
+ sampler.Iterate([&](const HashtablezInfo& info) {
+ if (preexisting_info.insert(&info).second) {
+ EXPECT_EQ(info.inline_element_size, expected_element_size);
+ ++new_count;
+ }
+ });
+ // Make sure we actually did get a new hashtablez.
+ EXPECT_GT(new_count, 0);
+}
+
+struct bigstruct {
+ char a[1000];
+ friend bool operator==(const bigstruct& x, const bigstruct& y) {
+ return memcmp(x.a, y.a, sizeof(x.a)) == 0;
+ }
+ template <typename H>
+ friend H AbslHashValue(H h, const bigstruct& c) {
+ return H::combine_contiguous(std::move(h), c.a, sizeof(c.a));
+ }
+};
+#endif
+
+TEST(FlatHashMap, SampleElementSize) {
+#if defined(ABSL_INTERNAL_HASHTABLEZ_SAMPLE)
+ // Enable sampling even if the prod default is off.
+ SetHashtablezEnabled(true);
+ SetHashtablezSampleParameter(1);
+
+ auto& sampler = GlobalHashtablezSampler();
+ std::vector<flat_hash_map<int, bigstruct>> flat_map_tables;
+ std::vector<flat_hash_set<bigstruct>> flat_set_tables;
+ std::vector<node_hash_map<int, bigstruct>> node_map_tables;
+ std::vector<node_hash_set<bigstruct>> node_set_tables;
+
+ // It takes thousands of new tables after changing the sampling parameters
+ // before you actually get some instrumentation. And if you must actually
+ // put something into those tables.
+ for (int i = 0; i < 10000; ++i) {
+ flat_map_tables.emplace_back();
+ flat_map_tables.back()[i] = bigstruct{};
+ }
+
+ // clang-tidy gives a false positive on this declaration. This unordered set
+ // cannot be a flat_hash_set, however, since that would introduce a mutex
+ // deadlock.
+ std::unordered_set<const HashtablezInfo*> preexisting_info; // NOLINT
+ sampler.Iterate(
+ [&](const HashtablezInfo& info) { preexisting_info.insert(&info); });
+ TestInlineElementSize(sampler, preexisting_info, flat_map_tables,
+ {0, bigstruct{}}, sizeof(int) + sizeof(bigstruct));
+ TestInlineElementSize(sampler, preexisting_info, node_map_tables,
+ {0, bigstruct{}}, sizeof(void*));
+ TestInlineElementSize(sampler, preexisting_info, flat_set_tables, //
+ bigstruct{}, sizeof(bigstruct));
+ TestInlineElementSize(sampler, preexisting_info, node_set_tables, //
+ bigstruct{}, sizeof(void*));
+#endif
+}
+
+} // namespace
+} // namespace container_internal
+ABSL_NAMESPACE_END
+} // namespace absl
diff --git a/absl/copts/AbseilConfigureCopts.cmake b/absl/copts/AbseilConfigureCopts.cmake
index 942ce90..3f737c8 100644
--- a/absl/copts/AbseilConfigureCopts.cmake
+++ b/absl/copts/AbseilConfigureCopts.cmake
@@ -1,8 +1,6 @@
# See absl/copts/copts.py and absl/copts/generate_copts.py
include(GENERATED_AbseilCopts)
-set(ABSL_LSAN_LINKOPTS "")
-set(ABSL_HAVE_LSAN OFF)
set(ABSL_DEFAULT_LINKOPTS "")
if (BUILD_SHARED_LIBS AND MSVC)
@@ -12,7 +10,49 @@
set(ABSL_BUILD_DLL FALSE)
endif()
-if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|amd64|AMD64")
+if(APPLE AND CMAKE_CXX_COMPILER_ID MATCHES [[Clang]])
+ # Some CMake targets (not known at the moment of processing) could be set to
+ # compile for multiple architectures as specified by the OSX_ARCHITECTURES
+ # property, which is target-specific. We should neither inspect nor rely on
+ # any CMake property or variable to detect an architecture, in particular:
+ #
+ # - CMAKE_OSX_ARCHITECTURES
+ # is just an initial value for OSX_ARCHITECTURES; set too early.
+ #
+ # - OSX_ARCHITECTURES
+ # is a per-target property; targets could be defined later, and their
+ # properties could be modified any time later.
+ #
+ # - CMAKE_SYSTEM_PROCESSOR
+ # does not reflect multiple architectures at all.
+ #
+ # When compiling for multiple architectures, a build system can invoke a
+ # compiler either
+ #
+ # - once: a single command line for multiple architectures (Ninja build)
+ # - twice: two command lines per each architecture (Xcode build system)
+ #
+ # If case of Xcode, it would be possible to set an Xcode-specific attributes
+ # like XCODE_ATTRIBUTE_OTHER_CPLUSPLUSFLAGS[arch=arm64] or similar.
+ #
+ # In both cases, the viable strategy is to pass all arguments at once, allowing
+ # the compiler to dispatch arch-specific arguments to a designated backend.
+ set(ABSL_RANDOM_RANDEN_COPTS "")
+ foreach(_arch IN ITEMS "x86_64" "arm64")
+ string(TOUPPER "${_arch}" _arch_uppercase)
+ string(REPLACE "X86_64" "X64" _arch_uppercase ${_arch_uppercase})
+ foreach(_flag IN LISTS ABSL_RANDOM_HWAES_${_arch_uppercase}_FLAGS)
+ list(APPEND ABSL_RANDOM_RANDEN_COPTS "-Xarch_${_arch}" "${_flag}")
+ endforeach()
+ endforeach()
+ # If a compiler happens to deal with an argument for a currently unused
+ # architecture, it will warn about an unused command line argument.
+ option(ABSL_RANDOM_RANDEN_COPTS_WARNING OFF
+ "Warn if one of ABSL_RANDOM_RANDEN_COPTS is unused")
+ if(ABSL_RANDOM_RANDEN_COPTS AND NOT ABSL_RANDOM_RANDEN_COPTS_WARNING)
+ list(APPEND ABSL_RANDOM_RANDEN_COPTS "-Wno-unused-command-line-argument")
+ endif()
+elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|amd64|AMD64")
if (MSVC)
set(ABSL_RANDOM_RANDEN_COPTS "${ABSL_RANDOM_HWAES_MSVC_X64_FLAGS}")
else()
@@ -27,34 +67,35 @@
message(WARNING "Value of CMAKE_SIZEOF_VOID_P (${CMAKE_SIZEOF_VOID_P}) is not supported.")
endif()
else()
- message(WARNING "Value of CMAKE_SYSTEM_PROCESSOR (${CMAKE_SYSTEM_PROCESSOR}) is unknown and cannot be used to set ABSL_RANDOM_RANDEN_COPTS")
set(ABSL_RANDOM_RANDEN_COPTS "")
endif()
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
set(ABSL_DEFAULT_COPTS "${ABSL_GCC_FLAGS}")
- set(ABSL_TEST_COPTS "${ABSL_GCC_FLAGS};${ABSL_GCC_TEST_FLAGS}")
+ set(ABSL_TEST_COPTS "${ABSL_GCC_TEST_FLAGS}")
elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang") # MATCHES so we get both Clang and AppleClang
if(MSVC)
# clang-cl is half MSVC, half LLVM
set(ABSL_DEFAULT_COPTS "${ABSL_CLANG_CL_FLAGS}")
- set(ABSL_TEST_COPTS "${ABSL_CLANG_CL_FLAGS};${ABSL_CLANG_CL_TEST_FLAGS}")
+ set(ABSL_TEST_COPTS "${ABSL_CLANG_CL_TEST_FLAGS}")
else()
set(ABSL_DEFAULT_COPTS "${ABSL_LLVM_FLAGS}")
- set(ABSL_TEST_COPTS "${ABSL_LLVM_FLAGS};${ABSL_LLVM_TEST_FLAGS}")
- if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
- # AppleClang doesn't have lsan
- # https://developer.apple.com/documentation/code_diagnostics
- if(NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 3.5)
- set(ABSL_LSAN_LINKOPTS "-fsanitize=leak")
- set(ABSL_HAVE_LSAN ON)
- endif()
- endif()
+ set(ABSL_TEST_COPTS "${ABSL_LLVM_TEST_FLAGS}")
+ endif()
+elseif(CMAKE_CXX_COMPILER_ID STREQUAL "IntelLLVM")
+ # IntelLLVM is similar to Clang, with some additional flags.
+ if(MSVC)
+ # clang-cl is half MSVC, half LLVM
+ set(ABSL_DEFAULT_COPTS "${ABSL_CLANG_CL_FLAGS}")
+ set(ABSL_TEST_COPTS "${ABSL_CLANG_CL_TEST_FLAGS}")
+ else()
+ set(ABSL_DEFAULT_COPTS "${ABSL_LLVM_FLAGS}")
+ set(ABSL_TEST_COPTS "${ABSL_LLVM_TEST_FLAGS}")
endif()
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
set(ABSL_DEFAULT_COPTS "${ABSL_MSVC_FLAGS}")
- set(ABSL_TEST_COPTS "${ABSL_MSVC_FLAGS};${ABSL_MSVC_TEST_FLAGS}")
+ set(ABSL_TEST_COPTS "${ABSL_MSVC_TEST_FLAGS}")
set(ABSL_DEFAULT_LINKOPTS "${ABSL_MSVC_LINKOPTS}")
else()
message(WARNING "Unknown compiler: ${CMAKE_CXX_COMPILER}. Building with no default flags")
diff --git a/absl/copts/GENERATED_AbseilCopts.cmake b/absl/copts/GENERATED_AbseilCopts.cmake
index 22a25eb..f90bff7 100644
--- a/absl/copts/GENERATED_AbseilCopts.cmake
+++ b/absl/copts/GENERATED_AbseilCopts.cmake
@@ -13,22 +13,27 @@
)
list(APPEND ABSL_CLANG_CL_TEST_FLAGS
- "-Wno-c99-extensions"
+ "/W3"
+ "/DNOMINMAX"
+ "/DWIN32_LEAN_AND_MEAN"
+ "/D_CRT_SECURE_NO_WARNINGS"
+ "/D_SCL_SECURE_NO_WARNINGS"
+ "/D_ENABLE_EXTENDED_ALIGNED_STORAGE"
"-Wno-deprecated-declarations"
- "-Wno-missing-noreturn"
+ "-Wno-implicit-int-conversion"
"-Wno-missing-prototypes"
"-Wno-missing-variable-declarations"
- "-Wno-null-conversion"
"-Wno-shadow"
- "-Wno-shift-sign-overflow"
+ "-Wno-shorten-64-to-32"
"-Wno-sign-compare"
+ "-Wno-sign-conversion"
+ "-Wno-unreachable-code-loop-increment"
"-Wno-unused-function"
"-Wno-unused-member-function"
"-Wno-unused-parameter"
"-Wno-unused-private-field"
"-Wno-unused-template"
"-Wno-used-but-marked-unused"
- "-Wno-zero-as-null-pointer-constant"
"-Wno-gnu-zero-variadic-macro-arguments"
)
@@ -51,9 +56,23 @@
)
list(APPEND ABSL_GCC_TEST_FLAGS
- "-Wno-conversion-null"
+ "-Wall"
+ "-Wextra"
+ "-Wcast-qual"
+ "-Wconversion-null"
+ "-Wformat-security"
+ "-Woverlength-strings"
+ "-Wpointer-arith"
+ "-Wundef"
+ "-Wunused-local-typedefs"
+ "-Wunused-result"
+ "-Wvarargs"
+ "-Wvla"
+ "-Wwrite-strings"
+ "-DNOMINMAX"
"-Wno-deprecated-declarations"
"-Wno-missing-declarations"
+ "-Wno-self-move"
"-Wno-sign-compare"
"-Wno-unused-function"
"-Wno-unused-parameter"
@@ -65,6 +84,46 @@
"-Wextra"
"-Wcast-qual"
"-Wconversion"
+ "-Wdead-code-aggressive"
+ "-Wfloat-overflow-conversion"
+ "-Wfloat-zero-conversion"
+ "-Wfor-loop-analysis"
+ "-Wformat-security"
+ "-Wgnu-redeclared-enum"
+ "-Winfinite-recursion"
+ "-Winvalid-constexpr"
+ "-Wliteral-conversion"
+ "-Wmissing-declarations"
+ "-Woverlength-strings"
+ "-Wpointer-arith"
+ "-Wself-assign"
+ "-Wshadow-all"
+ "-Wshorten-64-to-32"
+ "-Wsign-conversion"
+ "-Wstring-conversion"
+ "-Wtautological-overlap-compare"
+ "-Wtautological-unsigned-zero-compare"
+ "-Wundef"
+ "-Wuninitialized"
+ "-Wunreachable-code"
+ "-Wunused-comparison"
+ "-Wunused-local-typedefs"
+ "-Wunused-result"
+ "-Wvla"
+ "-Wwrite-strings"
+ "-Wno-float-conversion"
+ "-Wno-implicit-float-conversion"
+ "-Wno-implicit-int-float-conversion"
+ "-Wno-unknown-warning-option"
+ "-DNOMINMAX"
+)
+
+list(APPEND ABSL_LLVM_TEST_FLAGS
+ "-Wall"
+ "-Wextra"
+ "-Wcast-qual"
+ "-Wconversion"
+ "-Wdead-code-aggressive"
"-Wfloat-overflow-conversion"
"-Wfloat-zero-conversion"
"-Wfor-loop-analysis"
@@ -80,6 +139,7 @@
"-Wshadow-all"
"-Wstring-conversion"
"-Wtautological-overlap-compare"
+ "-Wtautological-unsigned-zero-compare"
"-Wundef"
"-Wuninitialized"
"-Wunreachable-code"
@@ -91,39 +151,28 @@
"-Wno-float-conversion"
"-Wno-implicit-float-conversion"
"-Wno-implicit-int-float-conversion"
- "-Wno-implicit-int-conversion"
- "-Wno-shorten-64-to-32"
- "-Wno-sign-conversion"
+ "-Wno-unknown-warning-option"
"-DNOMINMAX"
-)
-
-list(APPEND ABSL_LLVM_TEST_FLAGS
- "-Wno-c99-extensions"
"-Wno-deprecated-declarations"
- "-Wno-missing-noreturn"
+ "-Wno-implicit-int-conversion"
"-Wno-missing-prototypes"
"-Wno-missing-variable-declarations"
- "-Wno-null-conversion"
"-Wno-shadow"
- "-Wno-shift-sign-overflow"
+ "-Wno-shorten-64-to-32"
"-Wno-sign-compare"
+ "-Wno-sign-conversion"
+ "-Wno-unreachable-code-loop-increment"
"-Wno-unused-function"
"-Wno-unused-member-function"
"-Wno-unused-parameter"
"-Wno-unused-private-field"
"-Wno-unused-template"
"-Wno-used-but-marked-unused"
- "-Wno-zero-as-null-pointer-constant"
"-Wno-gnu-zero-variadic-macro-arguments"
)
list(APPEND ABSL_MSVC_FLAGS
"/W3"
- "/DNOMINMAX"
- "/DWIN32_LEAN_AND_MEAN"
- "/D_CRT_SECURE_NO_WARNINGS"
- "/D_SCL_SECURE_NO_WARNINGS"
- "/D_ENABLE_EXTENDED_ALIGNED_STORAGE"
"/bigobj"
"/wd4005"
"/wd4068"
@@ -132,6 +181,11 @@
"/wd4267"
"/wd4503"
"/wd4800"
+ "/DNOMINMAX"
+ "/DWIN32_LEAN_AND_MEAN"
+ "/D_CRT_SECURE_NO_WARNINGS"
+ "/D_SCL_SECURE_NO_WARNINGS"
+ "/D_ENABLE_EXTENDED_ALIGNED_STORAGE"
)
list(APPEND ABSL_MSVC_LINKOPTS
@@ -139,6 +193,20 @@
)
list(APPEND ABSL_MSVC_TEST_FLAGS
+ "/W3"
+ "/bigobj"
+ "/wd4005"
+ "/wd4068"
+ "/wd4180"
+ "/wd4244"
+ "/wd4267"
+ "/wd4503"
+ "/wd4800"
+ "/DNOMINMAX"
+ "/DWIN32_LEAN_AND_MEAN"
+ "/D_CRT_SECURE_NO_WARNINGS"
+ "/D_SCL_SECURE_NO_WARNINGS"
+ "/D_ENABLE_EXTENDED_ALIGNED_STORAGE"
"/wd4018"
"/wd4101"
"/wd4503"
diff --git a/absl/copts/GENERATED_copts.bzl b/absl/copts/GENERATED_copts.bzl
index 0c7a91b..3a65952 100644
--- a/absl/copts/GENERATED_copts.bzl
+++ b/absl/copts/GENERATED_copts.bzl
@@ -14,22 +14,27 @@
]
ABSL_CLANG_CL_TEST_FLAGS = [
- "-Wno-c99-extensions",
+ "/W3",
+ "/DNOMINMAX",
+ "/DWIN32_LEAN_AND_MEAN",
+ "/D_CRT_SECURE_NO_WARNINGS",
+ "/D_SCL_SECURE_NO_WARNINGS",
+ "/D_ENABLE_EXTENDED_ALIGNED_STORAGE",
"-Wno-deprecated-declarations",
- "-Wno-missing-noreturn",
+ "-Wno-implicit-int-conversion",
"-Wno-missing-prototypes",
"-Wno-missing-variable-declarations",
- "-Wno-null-conversion",
"-Wno-shadow",
- "-Wno-shift-sign-overflow",
+ "-Wno-shorten-64-to-32",
"-Wno-sign-compare",
+ "-Wno-sign-conversion",
+ "-Wno-unreachable-code-loop-increment",
"-Wno-unused-function",
"-Wno-unused-member-function",
"-Wno-unused-parameter",
"-Wno-unused-private-field",
"-Wno-unused-template",
"-Wno-used-but-marked-unused",
- "-Wno-zero-as-null-pointer-constant",
"-Wno-gnu-zero-variadic-macro-arguments",
]
@@ -52,9 +57,23 @@
]
ABSL_GCC_TEST_FLAGS = [
- "-Wno-conversion-null",
+ "-Wall",
+ "-Wextra",
+ "-Wcast-qual",
+ "-Wconversion-null",
+ "-Wformat-security",
+ "-Woverlength-strings",
+ "-Wpointer-arith",
+ "-Wundef",
+ "-Wunused-local-typedefs",
+ "-Wunused-result",
+ "-Wvarargs",
+ "-Wvla",
+ "-Wwrite-strings",
+ "-DNOMINMAX",
"-Wno-deprecated-declarations",
"-Wno-missing-declarations",
+ "-Wno-self-move",
"-Wno-sign-compare",
"-Wno-unused-function",
"-Wno-unused-parameter",
@@ -66,6 +85,46 @@
"-Wextra",
"-Wcast-qual",
"-Wconversion",
+ "-Wdead-code-aggressive",
+ "-Wfloat-overflow-conversion",
+ "-Wfloat-zero-conversion",
+ "-Wfor-loop-analysis",
+ "-Wformat-security",
+ "-Wgnu-redeclared-enum",
+ "-Winfinite-recursion",
+ "-Winvalid-constexpr",
+ "-Wliteral-conversion",
+ "-Wmissing-declarations",
+ "-Woverlength-strings",
+ "-Wpointer-arith",
+ "-Wself-assign",
+ "-Wshadow-all",
+ "-Wshorten-64-to-32",
+ "-Wsign-conversion",
+ "-Wstring-conversion",
+ "-Wtautological-overlap-compare",
+ "-Wtautological-unsigned-zero-compare",
+ "-Wundef",
+ "-Wuninitialized",
+ "-Wunreachable-code",
+ "-Wunused-comparison",
+ "-Wunused-local-typedefs",
+ "-Wunused-result",
+ "-Wvla",
+ "-Wwrite-strings",
+ "-Wno-float-conversion",
+ "-Wno-implicit-float-conversion",
+ "-Wno-implicit-int-float-conversion",
+ "-Wno-unknown-warning-option",
+ "-DNOMINMAX",
+]
+
+ABSL_LLVM_TEST_FLAGS = [
+ "-Wall",
+ "-Wextra",
+ "-Wcast-qual",
+ "-Wconversion",
+ "-Wdead-code-aggressive",
"-Wfloat-overflow-conversion",
"-Wfloat-zero-conversion",
"-Wfor-loop-analysis",
@@ -81,6 +140,7 @@
"-Wshadow-all",
"-Wstring-conversion",
"-Wtautological-overlap-compare",
+ "-Wtautological-unsigned-zero-compare",
"-Wundef",
"-Wuninitialized",
"-Wunreachable-code",
@@ -92,39 +152,28 @@
"-Wno-float-conversion",
"-Wno-implicit-float-conversion",
"-Wno-implicit-int-float-conversion",
- "-Wno-implicit-int-conversion",
- "-Wno-shorten-64-to-32",
- "-Wno-sign-conversion",
+ "-Wno-unknown-warning-option",
"-DNOMINMAX",
-]
-
-ABSL_LLVM_TEST_FLAGS = [
- "-Wno-c99-extensions",
"-Wno-deprecated-declarations",
- "-Wno-missing-noreturn",
+ "-Wno-implicit-int-conversion",
"-Wno-missing-prototypes",
"-Wno-missing-variable-declarations",
- "-Wno-null-conversion",
"-Wno-shadow",
- "-Wno-shift-sign-overflow",
+ "-Wno-shorten-64-to-32",
"-Wno-sign-compare",
+ "-Wno-sign-conversion",
+ "-Wno-unreachable-code-loop-increment",
"-Wno-unused-function",
"-Wno-unused-member-function",
"-Wno-unused-parameter",
"-Wno-unused-private-field",
"-Wno-unused-template",
"-Wno-used-but-marked-unused",
- "-Wno-zero-as-null-pointer-constant",
"-Wno-gnu-zero-variadic-macro-arguments",
]
ABSL_MSVC_FLAGS = [
"/W3",
- "/DNOMINMAX",
- "/DWIN32_LEAN_AND_MEAN",
- "/D_CRT_SECURE_NO_WARNINGS",
- "/D_SCL_SECURE_NO_WARNINGS",
- "/D_ENABLE_EXTENDED_ALIGNED_STORAGE",
"/bigobj",
"/wd4005",
"/wd4068",
@@ -133,6 +182,11 @@
"/wd4267",
"/wd4503",
"/wd4800",
+ "/DNOMINMAX",
+ "/DWIN32_LEAN_AND_MEAN",
+ "/D_CRT_SECURE_NO_WARNINGS",
+ "/D_SCL_SECURE_NO_WARNINGS",
+ "/D_ENABLE_EXTENDED_ALIGNED_STORAGE",
]
ABSL_MSVC_LINKOPTS = [
@@ -140,6 +194,20 @@
]
ABSL_MSVC_TEST_FLAGS = [
+ "/W3",
+ "/bigobj",
+ "/wd4005",
+ "/wd4068",
+ "/wd4180",
+ "/wd4244",
+ "/wd4267",
+ "/wd4503",
+ "/wd4800",
+ "/DNOMINMAX",
+ "/DWIN32_LEAN_AND_MEAN",
+ "/D_CRT_SECURE_NO_WARNINGS",
+ "/D_SCL_SECURE_NO_WARNINGS",
+ "/D_ENABLE_EXTENDED_ALIGNED_STORAGE",
"/wd4018",
"/wd4101",
"/wd4503",
diff --git a/absl/copts/configure_copts.bzl b/absl/copts/configure_copts.bzl
index 40d5849..ca5f26d 100644
--- a/absl/copts/configure_copts.bzl
+++ b/absl/copts/configure_copts.bzl
@@ -25,13 +25,15 @@
"//absl:msvc_compiler": ABSL_MSVC_FLAGS,
"//absl:clang-cl_compiler": ABSL_CLANG_CL_FLAGS,
"//absl:clang_compiler": ABSL_LLVM_FLAGS,
+ "//absl:gcc_compiler": ABSL_GCC_FLAGS,
"//conditions:default": ABSL_GCC_FLAGS,
})
-ABSL_TEST_COPTS = ABSL_DEFAULT_COPTS + select({
+ABSL_TEST_COPTS = select({
"//absl:msvc_compiler": ABSL_MSVC_TEST_FLAGS,
"//absl:clang-cl_compiler": ABSL_CLANG_CL_TEST_FLAGS,
"//absl:clang_compiler": ABSL_LLVM_TEST_FLAGS,
+ "//absl:gcc_compiler": ABSL_GCC_TEST_FLAGS,
"//conditions:default": ABSL_GCC_TEST_FLAGS,
})
diff --git a/absl/copts/copts.py b/absl/copts/copts.py
index 7268c68..946ceb8 100644
--- a/absl/copts/copts.py
+++ b/absl/copts/copts.py
@@ -11,32 +11,121 @@
AbseilConfigureCopts.cmake.
"""
-# /Wall with msvc includes unhelpful warnings such as C4711, C4710, ...
-MSVC_BIG_WARNING_FLAGS = [
- "/W3",
+ABSL_GCC_FLAGS = [
+ "-Wall",
+ "-Wextra",
+ "-Wcast-qual",
+ "-Wconversion-null",
+ "-Wformat-security",
+ "-Wmissing-declarations",
+ "-Woverlength-strings",
+ "-Wpointer-arith",
+ "-Wundef",
+ "-Wunused-local-typedefs",
+ "-Wunused-result",
+ "-Wvarargs",
+ "-Wvla", # variable-length array
+ "-Wwrite-strings",
+ # Don't define min and max macros (Build on Windows using gcc)
+ "-DNOMINMAX",
]
-LLVM_TEST_DISABLE_WARNINGS_FLAGS = [
- "-Wno-c99-extensions",
+ABSL_GCC_TEST_ADDITIONAL_FLAGS = [
"-Wno-deprecated-declarations",
- "-Wno-missing-noreturn",
+ "-Wno-missing-declarations",
+ "-Wno-self-move",
+ "-Wno-sign-compare",
+ "-Wno-unused-function",
+ "-Wno-unused-parameter",
+ "-Wno-unused-private-field",
+]
+
+ABSL_LLVM_FLAGS = [
+ "-Wall",
+ "-Wextra",
+ "-Wcast-qual",
+ "-Wconversion",
+ "-Wdead-code-aggressive",
+ "-Wfloat-overflow-conversion",
+ "-Wfloat-zero-conversion",
+ "-Wfor-loop-analysis",
+ "-Wformat-security",
+ "-Wgnu-redeclared-enum",
+ "-Winfinite-recursion",
+ "-Winvalid-constexpr",
+ "-Wliteral-conversion",
+ "-Wmissing-declarations",
+ "-Woverlength-strings",
+ "-Wpointer-arith",
+ "-Wself-assign",
+ "-Wshadow-all",
+ "-Wshorten-64-to-32",
+ "-Wsign-conversion",
+ "-Wstring-conversion",
+ "-Wtautological-overlap-compare",
+ "-Wtautological-unsigned-zero-compare",
+ "-Wundef",
+ "-Wuninitialized",
+ "-Wunreachable-code",
+ "-Wunused-comparison",
+ "-Wunused-local-typedefs",
+ "-Wunused-result",
+ "-Wvla",
+ "-Wwrite-strings",
+ # Warnings that are enabled by group warning flags like -Wall that we
+ # explicitly disable.
+ "-Wno-float-conversion",
+ "-Wno-implicit-float-conversion",
+ "-Wno-implicit-int-float-conversion",
+ # Disable warnings on unknown warning flags (when warning flags are
+ # unknown on older compiler versions)
+ "-Wno-unknown-warning-option",
+ # Don't define min and max macros (Build on Windows using clang)
+ "-DNOMINMAX",
+]
+
+ABSL_LLVM_TEST_ADDITIONAL_FLAGS = [
+ "-Wno-deprecated-declarations",
+ "-Wno-implicit-int-conversion",
"-Wno-missing-prototypes",
"-Wno-missing-variable-declarations",
- "-Wno-null-conversion",
"-Wno-shadow",
- "-Wno-shift-sign-overflow",
+ "-Wno-shorten-64-to-32",
"-Wno-sign-compare",
+ "-Wno-sign-conversion",
+ "-Wno-unreachable-code-loop-increment",
"-Wno-unused-function",
"-Wno-unused-member-function",
"-Wno-unused-parameter",
"-Wno-unused-private-field",
"-Wno-unused-template",
"-Wno-used-but-marked-unused",
- "-Wno-zero-as-null-pointer-constant",
# gtest depends on this GNU extension being offered.
"-Wno-gnu-zero-variadic-macro-arguments",
]
+# /Wall with msvc includes unhelpful warnings such as C4711, C4710, ...
+MSVC_BIG_WARNING_FLAGS = [
+ "/W3",
+]
+
+MSVC_WARNING_FLAGS = [
+ # Increase the number of sections available in object files
+ "/bigobj",
+ "/wd4005", # macro-redefinition
+ "/wd4068", # unknown pragma
+ # qualifier applied to function type has no meaning; ignored
+ "/wd4180",
+ # conversion from 'type1' to 'type2', possible loss of data
+ "/wd4244",
+ # conversion from 'size_t' to 'type', possible loss of data
+ "/wd4267",
+ # The decorated name was longer than the compiler limit
+ "/wd4503",
+ # forcing value to bool 'true' or 'false' (performance warning)
+ "/wd4800",
+]
+
MSVC_DEFINES = [
"/DNOMINMAX", # Don't define min and max macros (windows.h)
# Don't bloat namespace with incompatible winsock versions.
@@ -48,103 +137,43 @@
"/D_ENABLE_EXTENDED_ALIGNED_STORAGE",
]
+
+def GccStyleFilterAndCombine(default_flags, test_flags):
+ """Merges default_flags and test_flags for GCC and LLVM.
+
+ Args:
+ default_flags: A list of default compiler flags
+ test_flags: A list of flags that are only used in tests
+
+ Returns:
+ A combined list of default_flags and test_flags, but with all flags of the
+ form '-Wwarning' removed if test_flags contains a flag of the form
+ '-Wno-warning'
+ """
+ remove = set(["-W" + f[5:] for f in test_flags if f[:5] == "-Wno-"])
+ return [f for f in default_flags if f not in remove] + test_flags
+
COPT_VARS = {
- "ABSL_GCC_FLAGS": [
- "-Wall",
- "-Wextra",
- "-Wcast-qual",
- "-Wconversion-null",
- "-Wformat-security",
- "-Wmissing-declarations",
- "-Woverlength-strings",
- "-Wpointer-arith",
- "-Wundef",
- "-Wunused-local-typedefs",
- "-Wunused-result",
- "-Wvarargs",
- "-Wvla", # variable-length array
- "-Wwrite-strings",
- # Don't define min and max macros (Build on Windows using gcc)
- "-DNOMINMAX",
- ],
- "ABSL_GCC_TEST_FLAGS": [
- "-Wno-conversion-null",
- "-Wno-deprecated-declarations",
- "-Wno-missing-declarations",
- "-Wno-sign-compare",
- "-Wno-unused-function",
- "-Wno-unused-parameter",
- "-Wno-unused-private-field",
- ],
- "ABSL_LLVM_FLAGS": [
- "-Wall",
- "-Wextra",
- "-Wcast-qual",
- "-Wconversion",
- "-Wfloat-overflow-conversion",
- "-Wfloat-zero-conversion",
- "-Wfor-loop-analysis",
- "-Wformat-security",
- "-Wgnu-redeclared-enum",
- "-Winfinite-recursion",
- "-Winvalid-constexpr",
- "-Wliteral-conversion",
- "-Wmissing-declarations",
- "-Woverlength-strings",
- "-Wpointer-arith",
- "-Wself-assign",
- "-Wshadow-all",
- "-Wstring-conversion",
- "-Wtautological-overlap-compare",
- "-Wundef",
- "-Wuninitialized",
- "-Wunreachable-code",
- "-Wunused-comparison",
- "-Wunused-local-typedefs",
- "-Wunused-result",
- "-Wvla",
- "-Wwrite-strings",
- # Warnings that are enabled by group warning flags like -Wall that we
- # explicitly disable.
- "-Wno-float-conversion",
- "-Wno-implicit-float-conversion",
- "-Wno-implicit-int-float-conversion",
- "-Wno-implicit-int-conversion",
- "-Wno-shorten-64-to-32",
- "-Wno-sign-conversion",
- # Don't define min and max macros (Build on Windows using clang)
- "-DNOMINMAX",
- ],
- "ABSL_LLVM_TEST_FLAGS":
- LLVM_TEST_DISABLE_WARNINGS_FLAGS,
+ "ABSL_GCC_FLAGS": ABSL_GCC_FLAGS,
+ "ABSL_GCC_TEST_FLAGS": GccStyleFilterAndCombine(
+ ABSL_GCC_FLAGS, ABSL_GCC_TEST_ADDITIONAL_FLAGS),
+ "ABSL_LLVM_FLAGS": ABSL_LLVM_FLAGS,
+ "ABSL_LLVM_TEST_FLAGS": GccStyleFilterAndCombine(
+ ABSL_LLVM_FLAGS, ABSL_LLVM_TEST_ADDITIONAL_FLAGS),
"ABSL_CLANG_CL_FLAGS":
- (MSVC_BIG_WARNING_FLAGS + MSVC_DEFINES),
+ MSVC_BIG_WARNING_FLAGS + MSVC_DEFINES,
"ABSL_CLANG_CL_TEST_FLAGS":
- LLVM_TEST_DISABLE_WARNINGS_FLAGS,
+ MSVC_BIG_WARNING_FLAGS + MSVC_DEFINES + ABSL_LLVM_TEST_ADDITIONAL_FLAGS,
"ABSL_MSVC_FLAGS":
- MSVC_BIG_WARNING_FLAGS + MSVC_DEFINES + [
- # Increase the number of sections available in object files
- "/bigobj",
- "/wd4005", # macro-redefinition
- "/wd4068", # unknown pragma
- # qualifier applied to function type has no meaning; ignored
- "/wd4180",
- # conversion from 'type1' to 'type2', possible loss of data
- "/wd4244",
- # conversion from 'size_t' to 'type', possible loss of data
- "/wd4267",
- # The decorated name was longer than the compiler limit
- "/wd4503",
- # forcing value to bool 'true' or 'false' (performance warning)
- "/wd4800",
+ MSVC_BIG_WARNING_FLAGS + MSVC_WARNING_FLAGS + MSVC_DEFINES,
+ "ABSL_MSVC_TEST_FLAGS":
+ MSVC_BIG_WARNING_FLAGS + MSVC_WARNING_FLAGS + MSVC_DEFINES + [
+ "/wd4018", # signed/unsigned mismatch
+ "/wd4101", # unreferenced local variable
+ "/wd4503", # decorated name length exceeded, name was truncated
+ "/wd4996", # use of deprecated symbol
+ "/DNOMINMAX", # disable the min() and max() macros from <windows.h>
],
- "ABSL_MSVC_TEST_FLAGS": [
- "/wd4018", # signed/unsigned mismatch
- "/wd4101", # unreferenced local variable
- "/wd4503", # decorated name length exceeded, name was truncated
- "/wd4996", # use of deprecated symbol
- "/DNOMINMAX", # disable the min() and max() macros from <windows.h>
- ],
"ABSL_MSVC_LINKOPTS": [
# Object file doesn't export any previously undefined symbols
"-ignore:4221",
diff --git a/absl/crc/BUILD.bazel b/absl/crc/BUILD.bazel
new file mode 100644
index 0000000..9dc8181
--- /dev/null
+++ b/absl/crc/BUILD.bazel
@@ -0,0 +1,220 @@
+# Copyright 2022 The Abseil Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+load(
+ "//absl:copts/configure_copts.bzl",
+ "ABSL_DEFAULT_COPTS",
+ "ABSL_DEFAULT_LINKOPTS",
+ "ABSL_TEST_COPTS",
+)
+
+package(
+ default_visibility = ["//visibility:private"],
+ features = [
+ "header_modules",
+ "layering_check",
+ "parse_headers",
+ ],
+)
+
+licenses(["notice"])
+
+cc_library(
+ name = "cpu_detect",
+ srcs = [
+ "internal/cpu_detect.cc",
+ ],
+ hdrs = ["internal/cpu_detect.h"],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ visibility = ["//visibility:private"],
+ deps = [
+ "//absl/base",
+ "//absl/base:config",
+ ],
+)
+
+cc_library(
+ name = "crc_internal",
+ srcs = [
+ "internal/crc.cc",
+ "internal/crc_internal.h",
+ "internal/crc_x86_arm_combined.cc",
+ ],
+ hdrs = [
+ "internal/crc.h",
+ "internal/crc32_x86_arm_combined_simd.h",
+ ],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ visibility = ["//visibility:private"],
+ deps = [
+ ":cpu_detect",
+ "//absl/base:config",
+ "//absl/base:core_headers",
+ "//absl/base:endian",
+ "//absl/base:prefetch",
+ "//absl/base:raw_logging_internal",
+ "//absl/memory",
+ "//absl/numeric:bits",
+ ],
+)
+
+cc_library(
+ name = "crc32c",
+ srcs = [
+ "crc32c.cc",
+ "internal/crc32c_inline.h",
+ "internal/crc_memcpy_fallback.cc",
+ "internal/crc_memcpy_x86_arm_combined.cc",
+ "internal/crc_non_temporal_memcpy.cc",
+ ],
+ hdrs = [
+ "crc32c.h",
+ "internal/crc32c.h",
+ "internal/crc_memcpy.h",
+ ],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ visibility = ["//visibility:public"],
+ deps = [
+ ":cpu_detect",
+ ":crc_internal",
+ ":non_temporal_memcpy",
+ "//absl/base:config",
+ "//absl/base:core_headers",
+ "//absl/base:endian",
+ "//absl/base:prefetch",
+ "//absl/strings",
+ "//absl/strings:str_format",
+ ],
+)
+
+cc_test(
+ name = "crc32c_test",
+ srcs = ["crc32c_test.cc"],
+ copts = ABSL_TEST_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ visibility = ["//visibility:private"],
+ deps = [
+ ":crc32c",
+ "//absl/strings",
+ "//absl/strings:str_format",
+ "@com_google_googletest//:gtest",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_library(
+ name = "non_temporal_arm_intrinsics",
+ hdrs = ["internal/non_temporal_arm_intrinsics.h"],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ visibility = ["//visibility:private"],
+ deps = [
+ "//absl/base:config",
+ ],
+)
+
+cc_library(
+ name = "non_temporal_memcpy",
+ hdrs = ["internal/non_temporal_memcpy.h"],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ visibility = ["//visibility:private"],
+ deps = [
+ ":non_temporal_arm_intrinsics",
+ "//absl/base:config",
+ "//absl/base:core_headers",
+ ],
+)
+
+cc_test(
+ name = "crc_memcpy_test",
+ size = "large",
+ srcs = ["internal/crc_memcpy_test.cc"],
+ copts = ABSL_TEST_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ shard_count = 3,
+ visibility = ["//visibility:private"],
+ deps = [
+ ":crc32c",
+ "//absl/memory",
+ "//absl/random",
+ "//absl/random:distributions",
+ "//absl/strings",
+ "@com_google_googletest//:gtest",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "non_temporal_memcpy_test",
+ srcs = ["internal/non_temporal_memcpy_test.cc"],
+ copts = ABSL_TEST_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ visibility = ["//visibility:private"],
+ deps = [
+ ":non_temporal_memcpy",
+ "@com_google_googletest//:gtest",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_library(
+ name = "crc_cord_state",
+ srcs = ["internal/crc_cord_state.cc"],
+ hdrs = ["internal/crc_cord_state.h"],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ visibility = ["//absl/strings:__pkg__"],
+ deps = [
+ ":crc32c",
+ "//absl/base:config",
+ "//absl/base:no_destructor",
+ "//absl/numeric:bits",
+ ],
+)
+
+cc_test(
+ name = "crc_cord_state_test",
+ srcs = ["internal/crc_cord_state_test.cc"],
+ copts = ABSL_TEST_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ visibility = ["//visibility:private"],
+ deps = [
+ ":crc32c",
+ ":crc_cord_state",
+ "@com_google_googletest//:gtest",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_binary(
+ name = "crc32c_benchmark",
+ testonly = True,
+ srcs = ["crc32c_benchmark.cc"],
+ copts = ABSL_TEST_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ tags = [
+ "benchmark",
+ ],
+ visibility = ["//visibility:private"],
+ deps = [
+ ":crc32c",
+ "//absl/memory",
+ "//absl/strings",
+ "@com_github_google_benchmark//:benchmark_main",
+ ],
+)
diff --git a/absl/crc/CMakeLists.txt b/absl/crc/CMakeLists.txt
new file mode 100644
index 0000000..d52a1bc
--- /dev/null
+++ b/absl/crc/CMakeLists.txt
@@ -0,0 +1,176 @@
+# Copyright 2022 The Abseil Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Internal-only target, do not depend on directly.
+absl_cc_library(
+ NAME
+ crc_cpu_detect
+ HDRS
+ "internal/cpu_detect.h"
+ SRCS
+ "internal/cpu_detect.cc"
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ DEPS
+ absl::base
+ absl::config
+)
+
+# Internal-only target, do not depend on directly.
+absl_cc_library(
+ NAME
+ crc_internal
+ HDRS
+ "internal/crc.h"
+ "internal/crc32_x86_arm_combined_simd.h"
+ SRCS
+ "internal/crc.cc"
+ "internal/crc_internal.h"
+ "internal/crc_x86_arm_combined.cc"
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ DEPS
+ absl::crc_cpu_detect
+ absl::config
+ absl::core_headers
+ absl::endian
+ absl::prefetch
+ absl::raw_logging_internal
+ absl::memory
+ absl::bits
+)
+
+absl_cc_library(
+ NAME
+ crc32c
+ HDRS
+ "crc32c.h"
+ "internal/crc32c.h"
+ "internal/crc_memcpy.h"
+ SRCS
+ "crc32c.cc"
+ "internal/crc32c_inline.h"
+ "internal/crc_memcpy_fallback.cc"
+ "internal/crc_memcpy_x86_arm_combined.cc"
+ "internal/crc_non_temporal_memcpy.cc"
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ DEPS
+ absl::crc_cpu_detect
+ absl::crc_internal
+ absl::non_temporal_memcpy
+ absl::config
+ absl::core_headers
+ absl::endian
+ absl::prefetch
+ absl::str_format
+ absl::strings
+)
+
+absl_cc_test(
+ NAME
+ crc32c_test
+ SRCS
+ "crc32c_test.cc"
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ DEPS
+ absl::crc32c
+ absl::strings
+ absl::str_format
+ GTest::gtest_main
+)
+
+# Internal-only target, do not depend on directly.
+absl_cc_library(
+ NAME
+ non_temporal_arm_intrinsics
+ HDRS
+ "internal/non_temporal_arm_intrinsics.h"
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ DEPS
+ absl::config
+)
+
+# Internal-only target, do not depend on directly.
+absl_cc_library(
+ NAME
+ non_temporal_memcpy
+ HDRS
+ "internal/non_temporal_memcpy.h"
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ DEPS
+ absl::non_temporal_arm_intrinsics
+ absl::config
+ absl::core_headers
+)
+
+absl_cc_test(
+ NAME
+ crc_memcpy_test
+ SRCS
+ "internal/crc_memcpy_test.cc"
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ DEPS
+ absl::crc32c
+ absl::memory
+ absl::random_random
+ absl::random_distributions
+ absl::strings
+ GTest::gtest_main
+)
+
+absl_cc_test(
+ NAME
+ non_temporal_memcpy_test
+ SRCS
+ "internal/non_temporal_memcpy_test.cc"
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ DEPS
+ absl::non_temporal_memcpy
+ GTest::gtest_main
+)
+
+absl_cc_library(
+ NAME
+ crc_cord_state
+ HDRS
+ "internal/crc_cord_state.h"
+ SRCS
+ "internal/crc_cord_state.cc"
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ DEPS
+ absl::crc32c
+ absl::config
+ absl::strings
+ absl::no_destructor
+)
+
+absl_cc_test(
+ NAME
+ crc_cord_state_test
+ SRCS
+ "internal/crc_cord_state_test.cc"
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ DEPS
+ absl::crc_cord_state
+ absl::crc32c
+ GTest::gtest_main
+)
diff --git a/absl/crc/crc32c.cc b/absl/crc/crc32c.cc
new file mode 100644
index 0000000..468c1b3
--- /dev/null
+++ b/absl/crc/crc32c.cc
@@ -0,0 +1,99 @@
+// Copyright 2022 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/crc/crc32c.h"
+
+#include <cstdint>
+
+#include "absl/crc/internal/crc.h"
+#include "absl/crc/internal/crc32c.h"
+#include "absl/crc/internal/crc_memcpy.h"
+#include "absl/strings/string_view.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+
+namespace {
+
+const crc_internal::CRC* CrcEngine() {
+ static const crc_internal::CRC* engine = crc_internal::CRC::Crc32c();
+ return engine;
+}
+
+constexpr uint32_t kCRC32Xor = 0xffffffffU;
+
+} // namespace
+
+namespace crc_internal {
+
+crc32c_t UnextendCrc32cByZeroes(crc32c_t initial_crc, size_t length) {
+ uint32_t crc = static_cast<uint32_t>(initial_crc) ^ kCRC32Xor;
+ CrcEngine()->UnextendByZeroes(&crc, length);
+ return static_cast<crc32c_t>(crc ^ kCRC32Xor);
+}
+
+// Called by `absl::ExtendCrc32c()` on strings with size > 64 or when hardware
+// CRC32C support is missing.
+crc32c_t ExtendCrc32cInternal(crc32c_t initial_crc,
+ absl::string_view buf_to_add) {
+ uint32_t crc = static_cast<uint32_t>(initial_crc) ^ kCRC32Xor;
+ CrcEngine()->Extend(&crc, buf_to_add.data(), buf_to_add.size());
+ return static_cast<crc32c_t>(crc ^ kCRC32Xor);
+}
+
+} // namespace crc_internal
+
+crc32c_t ComputeCrc32c(absl::string_view buf) {
+ return ExtendCrc32c(crc32c_t{0}, buf);
+}
+
+crc32c_t ExtendCrc32cByZeroes(crc32c_t initial_crc, size_t length) {
+ uint32_t crc = static_cast<uint32_t>(initial_crc) ^ kCRC32Xor;
+ CrcEngine()->ExtendByZeroes(&crc, length);
+ return static_cast<crc32c_t>(crc ^ kCRC32Xor);
+}
+
+crc32c_t ConcatCrc32c(crc32c_t lhs_crc, crc32c_t rhs_crc, size_t rhs_len) {
+ uint32_t result = static_cast<uint32_t>(lhs_crc);
+ CrcEngine()->ExtendByZeroes(&result, rhs_len);
+ return crc32c_t{result ^ static_cast<uint32_t>(rhs_crc)};
+}
+
+crc32c_t RemoveCrc32cPrefix(crc32c_t crc_a, crc32c_t crc_ab, size_t length_b) {
+ return ConcatCrc32c(crc_a, crc_ab, length_b);
+}
+
+crc32c_t MemcpyCrc32c(void* dest, const void* src, size_t count,
+ crc32c_t initial_crc) {
+ return static_cast<crc32c_t>(
+ crc_internal::Crc32CAndCopy(dest, src, count, initial_crc, false));
+}
+
+// Remove a Suffix of given size from a buffer
+//
+// Given a CRC32C of an existing buffer, `full_string_crc`; the CRC32C of a
+// suffix of that buffer to remove, `suffix_crc`; and suffix buffer's length,
+// `suffix_len` return the CRC32C of the buffer with suffix removed
+//
+// This operation has a runtime cost of O(log(`suffix_len`))
+crc32c_t RemoveCrc32cSuffix(crc32c_t full_string_crc, crc32c_t suffix_crc,
+ size_t suffix_len) {
+ uint32_t result = static_cast<uint32_t>(full_string_crc) ^
+ static_cast<uint32_t>(suffix_crc);
+ CrcEngine()->UnextendByZeroes(&result, suffix_len);
+ return crc32c_t{result};
+}
+
+ABSL_NAMESPACE_END
+} // namespace absl
diff --git a/absl/crc/crc32c.h b/absl/crc/crc32c.h
new file mode 100644
index 0000000..362861e
--- /dev/null
+++ b/absl/crc/crc32c.h
@@ -0,0 +1,190 @@
+// Copyright 2022 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// -----------------------------------------------------------------------------
+// File: crc32c.h
+// -----------------------------------------------------------------------------
+//
+// This header file defines the API for computing CRC32C values as checksums
+// for arbitrary sequences of bytes provided as a string buffer.
+//
+// The API includes the basic functions for computing such CRC32C values and
+// some utility functions for performing more efficient mathematical
+// computations using an existing checksum.
+#ifndef ABSL_CRC_CRC32C_H_
+#define ABSL_CRC_CRC32C_H_
+
+#include <cstdint>
+#include <ostream>
+
+#include "absl/crc/internal/crc32c_inline.h"
+#include "absl/strings/str_format.h"
+#include "absl/strings/string_view.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+
+//-----------------------------------------------------------------------------
+// crc32c_t
+//-----------------------------------------------------------------------------
+
+// `crc32c_t` defines a strongly-typed integer for holding a CRC32C value.
+//
+// Some operators are intentionally omitted. Only equality operators are defined
+// so that `crc32c_t` can be directly compared. Methods for putting `crc32c_t`
+// directly into a set are omitted because this is bug-prone due to checksum
+// collisions. Use an explicit conversion to the `uint32_t` space for operations
+// that treat `crc32c_t` as an integer.
+class crc32c_t final {
+ public:
+ crc32c_t() = default;
+ constexpr explicit crc32c_t(uint32_t crc) : crc_(crc) {}
+
+ crc32c_t(const crc32c_t&) = default;
+ crc32c_t& operator=(const crc32c_t&) = default;
+
+ explicit operator uint32_t() const { return crc_; }
+
+ friend bool operator==(crc32c_t lhs, crc32c_t rhs) {
+ return static_cast<uint32_t>(lhs) == static_cast<uint32_t>(rhs);
+ }
+
+ friend bool operator!=(crc32c_t lhs, crc32c_t rhs) { return !(lhs == rhs); }
+
+ template <typename Sink>
+ friend void AbslStringify(Sink& sink, crc32c_t crc) {
+ absl::Format(&sink, "%08x", static_cast<uint32_t>(crc));
+ }
+
+ private:
+ uint32_t crc_;
+};
+
+
+namespace crc_internal {
+// Non-inline code path for `absl::ExtendCrc32c()`. Do not call directly.
+// Call `absl::ExtendCrc32c()` (defined below) instead.
+crc32c_t ExtendCrc32cInternal(crc32c_t initial_crc,
+ absl::string_view buf_to_add);
+} // namespace crc_internal
+
+// -----------------------------------------------------------------------------
+// CRC32C Computation Functions
+// -----------------------------------------------------------------------------
+
+// ComputeCrc32c()
+//
+// Returns the CRC32C value of the provided string.
+crc32c_t ComputeCrc32c(absl::string_view buf);
+
+// ExtendCrc32c()
+//
+// Computes a CRC32C value from an `initial_crc` CRC32C value including the
+// `buf_to_add` bytes of an additional buffer. Using this function is more
+// efficient than computing a CRC32C value for the combined buffer from
+// scratch.
+//
+// Note: `ExtendCrc32c` with an initial_crc of 0 is equivalent to
+// `ComputeCrc32c`.
+//
+// This operation has a runtime cost of O(`buf_to_add.size()`)
+inline crc32c_t ExtendCrc32c(crc32c_t initial_crc,
+ absl::string_view buf_to_add) {
+ // Approximately 75% of calls have size <= 64.
+ if (buf_to_add.size() <= 64) {
+ uint32_t crc = static_cast<uint32_t>(initial_crc);
+ if (crc_internal::ExtendCrc32cInline(&crc, buf_to_add.data(),
+ buf_to_add.size())) {
+ return crc32c_t{crc};
+ }
+ }
+ return crc_internal::ExtendCrc32cInternal(initial_crc, buf_to_add);
+}
+
+// ExtendCrc32cByZeroes()
+//
+// Computes a CRC32C value for a buffer with an `initial_crc` CRC32C value,
+// where `length` bytes with a value of 0 are appended to the buffer. Using this
+// function is more efficient than computing a CRC32C value for the combined
+// buffer from scratch.
+//
+// This operation has a runtime cost of O(log(`length`))
+crc32c_t ExtendCrc32cByZeroes(crc32c_t initial_crc, size_t length);
+
+// MemcpyCrc32c()
+//
+// Copies `src` to `dest` using `memcpy()` semantics, returning the CRC32C
+// value of the copied buffer.
+//
+// Using `MemcpyCrc32c()` is potentially faster than performing the `memcpy()`
+// and `ComputeCrc32c()` operations separately.
+crc32c_t MemcpyCrc32c(void* dest, const void* src, size_t count,
+ crc32c_t initial_crc = crc32c_t{0});
+
+// -----------------------------------------------------------------------------
+// CRC32C Arithmetic Functions
+// -----------------------------------------------------------------------------
+
+// The following functions perform arithmetic on CRC32C values, which are
+// generally more efficient than recalculating any given result's CRC32C value.
+
+// ConcatCrc32c()
+//
+// Calculates the CRC32C value of two buffers with known CRC32C values
+// concatenated together.
+//
+// Given a buffer with CRC32C value `crc1` and a buffer with
+// CRC32C value `crc2` and length, `crc2_length`, returns the CRC32C value of
+// the concatenation of these two buffers.
+//
+// This operation has a runtime cost of O(log(`crc2_length`)).
+crc32c_t ConcatCrc32c(crc32c_t crc1, crc32c_t crc2, size_t crc2_length);
+
+// RemoveCrc32cPrefix()
+//
+// Calculates the CRC32C value of an existing buffer with a series of bytes
+// (the prefix) removed from the beginning of that buffer.
+//
+// Given the CRC32C value of an existing buffer, `full_string_crc`; The CRC32C
+// value of a prefix of that buffer, `prefix_crc`; and the length of the buffer
+// with the prefix removed, `remaining_string_length` , return the CRC32C
+// value of the buffer with the prefix removed.
+//
+// This operation has a runtime cost of O(log(`remaining_string_length`)).
+crc32c_t RemoveCrc32cPrefix(crc32c_t prefix_crc, crc32c_t full_string_crc,
+ size_t remaining_string_length);
+// RemoveCrc32cSuffix()
+//
+// Calculates the CRC32C value of an existing buffer with a series of bytes
+// (the suffix) removed from the end of that buffer.
+//
+// Given a CRC32C value of an existing buffer `full_string_crc`, the CRC32C
+// value of the suffix to remove `suffix_crc`, and the length of that suffix
+// `suffix_len`, returns the CRC32C value of the buffer with suffix removed.
+//
+// This operation has a runtime cost of O(log(`suffix_len`))
+crc32c_t RemoveCrc32cSuffix(crc32c_t full_string_crc, crc32c_t suffix_crc,
+ size_t suffix_length);
+
+// operator<<
+//
+// Streams the CRC32C value `crc` to the stream `os`.
+inline std::ostream& operator<<(std::ostream& os, crc32c_t crc) {
+ return os << absl::StreamFormat("%08x", static_cast<uint32_t>(crc));
+}
+
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_CRC_CRC32C_H_
diff --git a/absl/crc/crc32c_benchmark.cc b/absl/crc/crc32c_benchmark.cc
new file mode 100644
index 0000000..3b46ef3
--- /dev/null
+++ b/absl/crc/crc32c_benchmark.cc
@@ -0,0 +1,183 @@
+// Copyright 2022 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <string>
+
+#include "absl/crc/crc32c.h"
+#include "absl/crc/internal/crc32c.h"
+#include "absl/memory/memory.h"
+#include "absl/strings/string_view.h"
+#include "benchmark/benchmark.h"
+
+namespace {
+
+std::string TestString(size_t len) {
+ std::string result;
+ result.reserve(len);
+ for (size_t i = 0; i < len; ++i) {
+ result.push_back(static_cast<char>(i % 256));
+ }
+ return result;
+}
+
+void BM_Calculate(benchmark::State& state) {
+ int len = state.range(0);
+ std::string data = TestString(len);
+ for (auto s : state) {
+ benchmark::DoNotOptimize(data);
+ absl::crc32c_t crc = absl::ComputeCrc32c(data);
+ benchmark::DoNotOptimize(crc);
+ }
+}
+BENCHMARK(BM_Calculate)->Arg(0)->Arg(1)->Arg(100)->Arg(10000)->Arg(500000);
+
+void BM_Extend(benchmark::State& state) {
+ int len = state.range(0);
+ std::string extension = TestString(len);
+ absl::crc32c_t base = absl::crc32c_t{0xC99465AA}; // CRC32C of "Hello World"
+ for (auto s : state) {
+ benchmark::DoNotOptimize(base);
+ benchmark::DoNotOptimize(extension);
+ absl::crc32c_t crc = absl::ExtendCrc32c(base, extension);
+ benchmark::DoNotOptimize(crc);
+ }
+}
+BENCHMARK(BM_Extend)->Arg(0)->Arg(1)->Arg(100)->Arg(10000)->Arg(500000)->Arg(
+ 100 * 1000 * 1000);
+
+// Make working set >> CPU cache size to benchmark prefetches better
+void BM_ExtendCacheMiss(benchmark::State& state) {
+ int len = state.range(0);
+ constexpr int total = 300 * 1000 * 1000;
+ std::string extension = TestString(total);
+ absl::crc32c_t base = absl::crc32c_t{0xC99465AA}; // CRC32C of "Hello World"
+ for (auto s : state) {
+ for (int i = 0; i < total; i += len * 2) {
+ benchmark::DoNotOptimize(base);
+ benchmark::DoNotOptimize(extension);
+ absl::crc32c_t crc =
+ absl::ExtendCrc32c(base, absl::string_view(&extension[i], len));
+ benchmark::DoNotOptimize(crc);
+ }
+ }
+ state.SetBytesProcessed(static_cast<int64_t>(state.iterations()) * total / 2);
+}
+BENCHMARK(BM_ExtendCacheMiss)->Arg(10)->Arg(100)->Arg(1000)->Arg(100000);
+
+void BM_ExtendByZeroes(benchmark::State& state) {
+ absl::crc32c_t base = absl::crc32c_t{0xC99465AA}; // CRC32C of "Hello World"
+ int num_zeroes = state.range(0);
+ for (auto s : state) {
+ benchmark::DoNotOptimize(base);
+ absl::crc32c_t crc = absl::ExtendCrc32cByZeroes(base, num_zeroes);
+ benchmark::DoNotOptimize(crc);
+ }
+}
+BENCHMARK(BM_ExtendByZeroes)
+ ->RangeMultiplier(10)
+ ->Range(1, 1000000)
+ ->RangeMultiplier(32)
+ ->Range(1, 1 << 20);
+
+void BM_UnextendByZeroes(benchmark::State& state) {
+ absl::crc32c_t base = absl::crc32c_t{0xdeadbeef};
+ int num_zeroes = state.range(0);
+ for (auto s : state) {
+ benchmark::DoNotOptimize(base);
+ absl::crc32c_t crc =
+ absl::crc_internal::UnextendCrc32cByZeroes(base, num_zeroes);
+ benchmark::DoNotOptimize(crc);
+ }
+}
+BENCHMARK(BM_UnextendByZeroes)
+ ->RangeMultiplier(10)
+ ->Range(1, 1000000)
+ ->RangeMultiplier(32)
+ ->Range(1, 1 << 20);
+
+void BM_Concat(benchmark::State& state) {
+ int string_b_len = state.range(0);
+ std::string string_b = TestString(string_b_len);
+
+ // CRC32C of "Hello World"
+ absl::crc32c_t crc_a = absl::crc32c_t{0xC99465AA};
+ absl::crc32c_t crc_b = absl::ComputeCrc32c(string_b);
+
+ for (auto s : state) {
+ benchmark::DoNotOptimize(crc_a);
+ benchmark::DoNotOptimize(crc_b);
+ benchmark::DoNotOptimize(string_b_len);
+ absl::crc32c_t crc_ab = absl::ConcatCrc32c(crc_a, crc_b, string_b_len);
+ benchmark::DoNotOptimize(crc_ab);
+ }
+}
+BENCHMARK(BM_Concat)
+ ->RangeMultiplier(10)
+ ->Range(1, 1000000)
+ ->RangeMultiplier(32)
+ ->Range(1, 1 << 20);
+
+void BM_Memcpy(benchmark::State& state) {
+ int string_len = state.range(0);
+
+ std::string source = TestString(string_len);
+ auto dest = absl::make_unique<char[]>(string_len);
+
+ for (auto s : state) {
+ benchmark::DoNotOptimize(source);
+ absl::crc32c_t crc =
+ absl::MemcpyCrc32c(dest.get(), source.data(), source.size());
+ benchmark::DoNotOptimize(crc);
+ benchmark::DoNotOptimize(dest);
+ benchmark::DoNotOptimize(dest.get());
+ benchmark::DoNotOptimize(dest[0]);
+ }
+
+ state.SetBytesProcessed(static_cast<int64_t>(state.iterations()) *
+ state.range(0));
+}
+BENCHMARK(BM_Memcpy)->Arg(0)->Arg(1)->Arg(100)->Arg(10000)->Arg(500000);
+
+void BM_RemoveSuffix(benchmark::State& state) {
+ int full_string_len = state.range(0);
+ int suffix_len = state.range(1);
+
+ std::string full_string = TestString(full_string_len);
+ std::string suffix = full_string.substr(
+ full_string_len - suffix_len, full_string_len);
+
+ absl::crc32c_t full_string_crc = absl::ComputeCrc32c(full_string);
+ absl::crc32c_t suffix_crc = absl::ComputeCrc32c(suffix);
+
+ for (auto s : state) {
+ benchmark::DoNotOptimize(full_string_crc);
+ benchmark::DoNotOptimize(suffix_crc);
+ benchmark::DoNotOptimize(suffix_len);
+ absl::crc32c_t crc = absl::RemoveCrc32cSuffix(full_string_crc, suffix_crc,
+ suffix_len);
+ benchmark::DoNotOptimize(crc);
+ }
+}
+BENCHMARK(BM_RemoveSuffix)
+ ->ArgPair(1, 1)
+ ->ArgPair(100, 10)
+ ->ArgPair(100, 100)
+ ->ArgPair(10000, 1)
+ ->ArgPair(10000, 100)
+ ->ArgPair(10000, 10000)
+ ->ArgPair(500000, 1)
+ ->ArgPair(500000, 100)
+ ->ArgPair(500000, 10000)
+ ->ArgPair(500000, 500000);
+} // namespace
diff --git a/absl/crc/crc32c_test.cc b/absl/crc/crc32c_test.cc
new file mode 100644
index 0000000..df0afb3
--- /dev/null
+++ b/absl/crc/crc32c_test.cc
@@ -0,0 +1,227 @@
+// Copyright 2022 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/crc/crc32c.h"
+
+#include <algorithm>
+#include <cstddef>
+#include <cstdint>
+#include <cstring>
+#include <sstream>
+#include <string>
+
+#include "gtest/gtest.h"
+#include "absl/crc/internal/crc32c.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/str_format.h"
+#include "absl/strings/string_view.h"
+
+namespace {
+
+TEST(CRC32C, RFC3720) {
+ // Test the results of the vectors from
+ // https://www.rfc-editor.org/rfc/rfc3720#appendix-B.4
+ char data[32];
+
+ // 32 bytes of ones.
+ memset(data, 0, sizeof(data));
+ EXPECT_EQ(absl::ComputeCrc32c(absl::string_view(data, sizeof(data))),
+ absl::crc32c_t{0x8a9136aa});
+
+ // 32 bytes of ones.
+ memset(data, 0xff, sizeof(data));
+ EXPECT_EQ(absl::ComputeCrc32c(absl::string_view(data, sizeof(data))),
+ absl::crc32c_t{0x62a8ab43});
+
+ // 32 incrementing bytes.
+ for (int i = 0; i < 32; ++i) data[i] = static_cast<char>(i);
+ EXPECT_EQ(absl::ComputeCrc32c(absl::string_view(data, sizeof(data))),
+ absl::crc32c_t{0x46dd794e});
+
+ // 32 decrementing bytes.
+ for (int i = 0; i < 32; ++i) data[i] = static_cast<char>(31 - i);
+ EXPECT_EQ(absl::ComputeCrc32c(absl::string_view(data, sizeof(data))),
+ absl::crc32c_t{0x113fdb5c});
+
+ // An iSCSI - SCSI Read (10) Command PDU.
+ constexpr uint8_t cmd[48] = {
+ 0x01, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00,
+ 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x18, 0x28, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ };
+ EXPECT_EQ(absl::ComputeCrc32c(absl::string_view(
+ reinterpret_cast<const char*>(cmd), sizeof(cmd))),
+ absl::crc32c_t{0xd9963a56});
+}
+
+std::string TestString(size_t len) {
+ std::string result;
+ result.reserve(len);
+ for (size_t i = 0; i < len; ++i) {
+ result.push_back(static_cast<char>(i % 256));
+ }
+ return result;
+}
+
+TEST(CRC32C, Compute) {
+ EXPECT_EQ(absl::ComputeCrc32c(""), absl::crc32c_t{0});
+ EXPECT_EQ(absl::ComputeCrc32c("hello world"), absl::crc32c_t{0xc99465aa});
+}
+
+TEST(CRC32C, Extend) {
+ uint32_t base = 0xC99465AA; // CRC32C of "Hello World"
+ std::string extension = "Extension String";
+
+ EXPECT_EQ(
+ absl::ExtendCrc32c(absl::crc32c_t{base}, extension),
+ absl::crc32c_t{0xD2F65090}); // CRC32C of "Hello WorldExtension String"
+}
+
+TEST(CRC32C, ExtendByZeroes) {
+ std::string base = "hello world";
+ absl::crc32c_t base_crc = absl::crc32c_t{0xc99465aa};
+
+ constexpr size_t kExtendByValues[] = {100, 10000, 100000};
+ for (const size_t extend_by : kExtendByValues) {
+ SCOPED_TRACE(extend_by);
+ absl::crc32c_t crc2 = absl::ExtendCrc32cByZeroes(base_crc, extend_by);
+ EXPECT_EQ(crc2, absl::ComputeCrc32c(base + std::string(extend_by, '\0')));
+ }
+}
+
+TEST(CRC32C, UnextendByZeroes) {
+ constexpr size_t kExtendByValues[] = {2, 200, 20000, 200000, 20000000};
+ constexpr size_t kUnextendByValues[] = {0, 100, 10000, 100000, 10000000};
+
+ for (auto seed_crc : {absl::crc32c_t{0}, absl::crc32c_t{0xc99465aa}}) {
+ SCOPED_TRACE(seed_crc);
+ for (const size_t size_1 : kExtendByValues) {
+ for (const size_t size_2 : kUnextendByValues) {
+ size_t extend_size = std::max(size_1, size_2);
+ size_t unextend_size = std::min(size_1, size_2);
+ SCOPED_TRACE(extend_size);
+ SCOPED_TRACE(unextend_size);
+
+ // Extending by A zeroes an unextending by B<A zeros should be identical
+ // to extending by A-B zeroes.
+ absl::crc32c_t crc1 = seed_crc;
+ crc1 = absl::ExtendCrc32cByZeroes(crc1, extend_size);
+ crc1 = absl::crc_internal::UnextendCrc32cByZeroes(crc1, unextend_size);
+
+ absl::crc32c_t crc2 = seed_crc;
+ crc2 = absl::ExtendCrc32cByZeroes(crc2, extend_size - unextend_size);
+
+ EXPECT_EQ(crc1, crc2);
+ }
+ }
+ }
+
+ constexpr size_t kSizes[] = {0, 1, 100, 10000};
+ for (const size_t size : kSizes) {
+ SCOPED_TRACE(size);
+ std::string string_before = TestString(size);
+ std::string string_after = string_before + std::string(size, '\0');
+
+ absl::crc32c_t crc_before = absl::ComputeCrc32c(string_before);
+ absl::crc32c_t crc_after = absl::ComputeCrc32c(string_after);
+
+ EXPECT_EQ(crc_before,
+ absl::crc_internal::UnextendCrc32cByZeroes(crc_after, size));
+ }
+}
+
+TEST(CRC32C, Concat) {
+ std::string hello = "Hello, ";
+ std::string world = "world!";
+ std::string hello_world = absl::StrCat(hello, world);
+
+ absl::crc32c_t crc_a = absl::ComputeCrc32c(hello);
+ absl::crc32c_t crc_b = absl::ComputeCrc32c(world);
+ absl::crc32c_t crc_ab = absl::ComputeCrc32c(hello_world);
+
+ EXPECT_EQ(absl::ConcatCrc32c(crc_a, crc_b, world.size()), crc_ab);
+}
+
+TEST(CRC32C, Memcpy) {
+ constexpr size_t kBytesSize[] = {0, 1, 20, 500, 100000};
+ for (size_t bytes : kBytesSize) {
+ SCOPED_TRACE(bytes);
+ std::string sample_string = TestString(bytes);
+ std::string target_buffer = std::string(bytes, '\0');
+
+ absl::crc32c_t memcpy_crc =
+ absl::MemcpyCrc32c(&(target_buffer[0]), sample_string.data(), bytes);
+ absl::crc32c_t compute_crc = absl::ComputeCrc32c(sample_string);
+
+ EXPECT_EQ(memcpy_crc, compute_crc);
+ EXPECT_EQ(sample_string, target_buffer);
+ }
+}
+
+TEST(CRC32C, RemovePrefix) {
+ std::string hello = "Hello, ";
+ std::string world = "world!";
+ std::string hello_world = absl::StrCat(hello, world);
+
+ absl::crc32c_t crc_a = absl::ComputeCrc32c(hello);
+ absl::crc32c_t crc_b = absl::ComputeCrc32c(world);
+ absl::crc32c_t crc_ab = absl::ComputeCrc32c(hello_world);
+
+ EXPECT_EQ(absl::RemoveCrc32cPrefix(crc_a, crc_ab, world.size()), crc_b);
+}
+
+TEST(CRC32C, RemoveSuffix) {
+ std::string hello = "Hello, ";
+ std::string world = "world!";
+ std::string hello_world = absl::StrCat(hello, world);
+
+ absl::crc32c_t crc_a = absl::ComputeCrc32c(hello);
+ absl::crc32c_t crc_b = absl::ComputeCrc32c(world);
+ absl::crc32c_t crc_ab = absl::ComputeCrc32c(hello_world);
+
+ EXPECT_EQ(absl::RemoveCrc32cSuffix(crc_ab, crc_b, world.size()), crc_a);
+}
+
+TEST(CRC32C, InsertionOperator) {
+ {
+ std::ostringstream buf;
+ buf << absl::crc32c_t{0xc99465aa};
+ EXPECT_EQ(buf.str(), "c99465aa");
+ }
+ {
+ std::ostringstream buf;
+ buf << absl::crc32c_t{0};
+ EXPECT_EQ(buf.str(), "00000000");
+ }
+ {
+ std::ostringstream buf;
+ buf << absl::crc32c_t{17};
+ EXPECT_EQ(buf.str(), "00000011");
+ }
+}
+
+TEST(CRC32C, AbslStringify) {
+ // StrFormat
+ EXPECT_EQ(absl::StrFormat("%v", absl::crc32c_t{0xc99465aa}), "c99465aa");
+ EXPECT_EQ(absl::StrFormat("%v", absl::crc32c_t{0}), "00000000");
+ EXPECT_EQ(absl::StrFormat("%v", absl::crc32c_t{17}), "00000011");
+
+ // StrCat
+ EXPECT_EQ(absl::StrCat(absl::crc32c_t{0xc99465aa}), "c99465aa");
+ EXPECT_EQ(absl::StrCat(absl::crc32c_t{0}), "00000000");
+ EXPECT_EQ(absl::StrCat(absl::crc32c_t{17}), "00000011");
+}
+
+} // namespace
diff --git a/absl/crc/internal/cpu_detect.cc b/absl/crc/internal/cpu_detect.cc
new file mode 100644
index 0000000..d7eedd1
--- /dev/null
+++ b/absl/crc/internal/cpu_detect.cc
@@ -0,0 +1,286 @@
+// Copyright 2022 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/crc/internal/cpu_detect.h"
+
+#include <cstdint>
+#include <string>
+
+#include "absl/base/config.h"
+
+#if defined(__aarch64__) && defined(__linux__)
+#include <asm/hwcap.h>
+#include <sys/auxv.h>
+#endif
+
+#if defined(_WIN32) || defined(_WIN64)
+#include <intrin.h>
+#endif
+
+#if defined(__x86_64__) || defined(_M_X64)
+#if ABSL_HAVE_BUILTIN(__cpuid)
+// MSVC-equivalent __cpuid intrinsic declaration for clang-like compilers
+// for non-Windows build environments.
+extern void __cpuid(int[4], int);
+#elif !defined(_WIN32) && !defined(_WIN64)
+// MSVC defines this function for us.
+// https://learn.microsoft.com/en-us/cpp/intrinsics/cpuid-cpuidex
+static void __cpuid(int cpu_info[4], int info_type) {
+ __asm__ volatile("cpuid \n\t"
+ : "=a"(cpu_info[0]), "=b"(cpu_info[1]), "=c"(cpu_info[2]),
+ "=d"(cpu_info[3])
+ : "a"(info_type), "c"(0));
+}
+#endif // !defined(_WIN32) && !defined(_WIN64)
+#endif // defined(__x86_64__) || defined(_M_X64)
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace crc_internal {
+
+#if defined(__x86_64__) || defined(_M_X64)
+
+namespace {
+
+enum class Vendor {
+ kUnknown,
+ kIntel,
+ kAmd,
+};
+
+Vendor GetVendor() {
+ // Get the vendor string (issue CPUID with eax = 0).
+ int cpu_info[4];
+ __cpuid(cpu_info, 0);
+
+ std::string vendor;
+ vendor.append(reinterpret_cast<char*>(&cpu_info[1]), 4);
+ vendor.append(reinterpret_cast<char*>(&cpu_info[3]), 4);
+ vendor.append(reinterpret_cast<char*>(&cpu_info[2]), 4);
+ if (vendor == "GenuineIntel") {
+ return Vendor::kIntel;
+ } else if (vendor == "AuthenticAMD") {
+ return Vendor::kAmd;
+ } else {
+ return Vendor::kUnknown;
+ }
+}
+
+CpuType GetIntelCpuType() {
+ // To get general information and extended features we send eax = 1 and
+ // ecx = 0 to cpuid. The response is returned in eax, ebx, ecx and edx.
+ // (See Intel 64 and IA-32 Architectures Software Developer's Manual
+ // Volume 2A: Instruction Set Reference, A-M CPUID).
+ // https://www.intel.com/content/www/us/en/architecture-and-technology/64-ia-32-architectures-software-developer-vol-2a-manual.html
+ // https://learn.microsoft.com/en-us/cpp/intrinsics/cpuid-cpuidex
+ int cpu_info[4];
+ __cpuid(cpu_info, 1);
+
+ // Response in eax bits as follows:
+ // 0-3 (stepping id)
+ // 4-7 (model number),
+ // 8-11 (family code),
+ // 12-13 (processor type),
+ // 16-19 (extended model)
+ // 20-27 (extended family)
+
+ int family = (cpu_info[0] >> 8) & 0x0f;
+ int model_num = (cpu_info[0] >> 4) & 0x0f;
+ int ext_family = (cpu_info[0] >> 20) & 0xff;
+ int ext_model_num = (cpu_info[0] >> 16) & 0x0f;
+
+ int brand_id = cpu_info[1] & 0xff;
+
+ // Process the extended family and model info if necessary
+ if (family == 0x0f) {
+ family += ext_family;
+ }
+
+ if (family == 0x0f || family == 0x6) {
+ model_num += (ext_model_num << 4);
+ }
+
+ switch (brand_id) {
+ case 0: // no brand ID, so parse CPU family/model
+ switch (family) {
+ case 6: // Most PentiumIII processors are in this category
+ switch (model_num) {
+ case 0x2c: // Westmere: Gulftown
+ return CpuType::kIntelWestmere;
+ case 0x2d: // Sandybridge
+ return CpuType::kIntelSandybridge;
+ case 0x3e: // Ivybridge
+ return CpuType::kIntelIvybridge;
+ case 0x3c: // Haswell (client)
+ case 0x3f: // Haswell
+ return CpuType::kIntelHaswell;
+ case 0x4f: // Broadwell
+ case 0x56: // BroadwellDE
+ return CpuType::kIntelBroadwell;
+ case 0x55: // Skylake Xeon
+ if ((cpu_info[0] & 0x0f) < 5) { // stepping < 5 is skylake
+ return CpuType::kIntelSkylakeXeon;
+ } else { // stepping >= 5 is cascadelake
+ return CpuType::kIntelCascadelakeXeon;
+ }
+ case 0x5e: // Skylake (client)
+ return CpuType::kIntelSkylake;
+ default:
+ return CpuType::kUnknown;
+ }
+ default:
+ return CpuType::kUnknown;
+ }
+ default:
+ return CpuType::kUnknown;
+ }
+}
+
+CpuType GetAmdCpuType() {
+ // To get general information and extended features we send eax = 1 and
+ // ecx = 0 to cpuid. The response is returned in eax, ebx, ecx and edx.
+ // (See Intel 64 and IA-32 Architectures Software Developer's Manual
+ // Volume 2A: Instruction Set Reference, A-M CPUID).
+ // https://learn.microsoft.com/en-us/cpp/intrinsics/cpuid-cpuidex
+ int cpu_info[4];
+ __cpuid(cpu_info, 1);
+
+ // Response in eax bits as follows:
+ // 0-3 (stepping id)
+ // 4-7 (model number),
+ // 8-11 (family code),
+ // 12-13 (processor type),
+ // 16-19 (extended model)
+ // 20-27 (extended family)
+
+ int family = (cpu_info[0] >> 8) & 0x0f;
+ int model_num = (cpu_info[0] >> 4) & 0x0f;
+ int ext_family = (cpu_info[0] >> 20) & 0xff;
+ int ext_model_num = (cpu_info[0] >> 16) & 0x0f;
+
+ if (family == 0x0f) {
+ family += ext_family;
+ model_num += (ext_model_num << 4);
+ }
+
+ switch (family) {
+ case 0x17:
+ switch (model_num) {
+ case 0x0: // Stepping Ax
+ case 0x1: // Stepping Bx
+ return CpuType::kAmdNaples;
+ case 0x30: // Stepping Ax
+ case 0x31: // Stepping Bx
+ return CpuType::kAmdRome;
+ default:
+ return CpuType::kUnknown;
+ }
+ break;
+ case 0x19:
+ switch (model_num) {
+ case 0x0: // Stepping Ax
+ case 0x1: // Stepping B0
+ return CpuType::kAmdMilan;
+ case 0x10: // Stepping A0
+ case 0x11: // Stepping B0
+ return CpuType::kAmdGenoa;
+ case 0x44: // Stepping A0
+ return CpuType::kAmdRyzenV3000;
+ default:
+ return CpuType::kUnknown;
+ }
+ break;
+ default:
+ return CpuType::kUnknown;
+ }
+}
+
+} // namespace
+
+CpuType GetCpuType() {
+ switch (GetVendor()) {
+ case Vendor::kIntel:
+ return GetIntelCpuType();
+ case Vendor::kAmd:
+ return GetAmdCpuType();
+ default:
+ return CpuType::kUnknown;
+ }
+}
+
+bool SupportsArmCRC32PMULL() { return false; }
+
+#elif defined(__aarch64__) && defined(__linux__)
+
+#ifndef HWCAP_CPUID
+#define HWCAP_CPUID (1 << 11)
+#endif
+
+#define ABSL_INTERNAL_AARCH64_ID_REG_READ(id, val) \
+ asm("mrs %0, " #id : "=r"(val))
+
+CpuType GetCpuType() {
+ // MIDR_EL1 is not visible to EL0, however the access will be emulated by
+ // linux if AT_HWCAP has HWCAP_CPUID set.
+ //
+ // This method will be unreliable on heterogeneous computing systems (ex:
+ // big.LITTLE) since the value of MIDR_EL1 will change based on the calling
+ // thread.
+ uint64_t hwcaps = getauxval(AT_HWCAP);
+ if (hwcaps & HWCAP_CPUID) {
+ uint64_t midr = 0;
+ ABSL_INTERNAL_AARCH64_ID_REG_READ(MIDR_EL1, midr);
+ uint32_t implementer = (midr >> 24) & 0xff;
+ uint32_t part_number = (midr >> 4) & 0xfff;
+ switch (implementer) {
+ case 0x41:
+ switch (part_number) {
+ case 0xd0c: return CpuType::kArmNeoverseN1;
+ case 0xd40: return CpuType::kArmNeoverseV1;
+ case 0xd49: return CpuType::kArmNeoverseN2;
+ case 0xd4f: return CpuType::kArmNeoverseV2;
+ default:
+ return CpuType::kUnknown;
+ }
+ break;
+ case 0xc0:
+ switch (part_number) {
+ case 0xac3: return CpuType::kAmpereSiryn;
+ default:
+ return CpuType::kUnknown;
+ }
+ break;
+ default:
+ return CpuType::kUnknown;
+ }
+ }
+ return CpuType::kUnknown;
+}
+
+bool SupportsArmCRC32PMULL() {
+ uint64_t hwcaps = getauxval(AT_HWCAP);
+ return (hwcaps & HWCAP_CRC32) && (hwcaps & HWCAP_PMULL);
+}
+
+#else
+
+CpuType GetCpuType() { return CpuType::kUnknown; }
+
+bool SupportsArmCRC32PMULL() { return false; }
+
+#endif
+
+} // namespace crc_internal
+ABSL_NAMESPACE_END
+} // namespace absl
diff --git a/absl/crc/internal/cpu_detect.h b/absl/crc/internal/cpu_detect.h
new file mode 100644
index 0000000..01e1959
--- /dev/null
+++ b/absl/crc/internal/cpu_detect.h
@@ -0,0 +1,63 @@
+// Copyright 2022 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef ABSL_CRC_INTERNAL_CPU_DETECT_H_
+#define ABSL_CRC_INTERNAL_CPU_DETECT_H_
+
+#include "absl/base/config.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace crc_internal {
+
+// Enumeration of architectures that we have special-case tuning parameters for.
+// This set may change over time.
+enum class CpuType {
+ kUnknown,
+ kIntelHaswell,
+ kAmdRome,
+ kAmdNaples,
+ kAmdMilan,
+ kAmdGenoa,
+ kAmdRyzenV3000,
+ kIntelCascadelakeXeon,
+ kIntelSkylakeXeon,
+ kIntelBroadwell,
+ kIntelSkylake,
+ kIntelIvybridge,
+ kIntelSandybridge,
+ kIntelWestmere,
+ kArmNeoverseN1,
+ kArmNeoverseV1,
+ kAmpereSiryn,
+ kArmNeoverseN2,
+ kArmNeoverseV2
+};
+
+// Returns the type of host CPU this code is running on. Returns kUnknown if
+// the host CPU is of unknown type, or if detection otherwise fails.
+CpuType GetCpuType();
+
+// Returns whether the host CPU supports the CPU features needed for our
+// accelerated implementations. The CpuTypes enumerated above apart from
+// kUnknown support the required features. On unknown CPUs, we can use
+// this to see if it's safe to use hardware acceleration, though without any
+// tuning.
+bool SupportsArmCRC32PMULL();
+
+} // namespace crc_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_CRC_INTERNAL_CPU_DETECT_H_
diff --git a/absl/crc/internal/crc.cc b/absl/crc/internal/crc.cc
new file mode 100644
index 0000000..22e91c5
--- /dev/null
+++ b/absl/crc/internal/crc.cc
@@ -0,0 +1,437 @@
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Implementation of CRCs (aka Rabin Fingerprints).
+// Treats the input as a polynomial with coefficients in Z(2),
+// and finds the remainder when divided by an irreducible polynomial
+// of the appropriate length.
+// It handles all CRC sizes from 8 to 128 bits.
+// It's somewhat complicated by having separate implementations optimized for
+// CRC's <=32 bits, <= 64 bits, and <= 128 bits.
+// The input string is prefixed with a "1" bit, and has "degree" "0" bits
+// appended to it before the remainder is found. This ensures that
+// short strings are scrambled somewhat and that strings consisting
+// of all nulls have a non-zero CRC.
+//
+// Uses the "interleaved word-by-word" method from
+// "Everything we know about CRC but afraid to forget" by Andrew Kadatch
+// and Bob Jenkins,
+// http://crcutil.googlecode.com/files/crc-doc.1.0.pdf
+//
+// The idea is to compute kStride CRCs simultaneously, allowing the
+// processor to more effectively use multiple execution units. Each of
+// the CRCs is calculated on one word of data followed by kStride - 1
+// words of zeroes; the CRC starting points are staggered by one word.
+// Assuming a stride of 4 with data words "ABCDABCDABCD", the first
+// CRC is over A000A000A, the second over 0B000B000B, and so on.
+// The CRC of the whole data is then calculated by properly aligning the
+// CRCs by appending zeroes until the data lengths agree then XORing
+// the CRCs.
+
+#include "absl/crc/internal/crc.h"
+
+#include <cstdint>
+
+#include "absl/base/internal/endian.h"
+#include "absl/base/internal/raw_logging.h"
+#include "absl/base/prefetch.h"
+#include "absl/crc/internal/crc_internal.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace crc_internal {
+
+namespace {
+
+// Constants
+#if defined(__i386__) || defined(__x86_64__)
+constexpr bool kNeedAlignedLoads = false;
+#else
+constexpr bool kNeedAlignedLoads = true;
+#endif
+
+// We express the number of zeroes as a number in base ZEROES_BASE. By
+// pre-computing the zero extensions for all possible components of such an
+// expression (numbers in a form a*ZEROES_BASE**b), we can calculate the
+// resulting extension by multiplying the extensions for individual components
+// using log_{ZEROES_BASE}(num_zeroes) polynomial multiplications. The tables of
+// zero extensions contain (ZEROES_BASE - 1) * (log_{ZEROES_BASE}(64)) entries.
+constexpr int ZEROES_BASE_LG = 4; // log_2(ZEROES_BASE)
+constexpr int ZEROES_BASE = (1 << ZEROES_BASE_LG); // must be a power of 2
+
+constexpr uint32_t kCrc32cPoly = 0x82f63b78;
+
+uint32_t ReverseBits(uint32_t bits) {
+ bits = (bits & 0xaaaaaaaau) >> 1 | (bits & 0x55555555u) << 1;
+ bits = (bits & 0xccccccccu) >> 2 | (bits & 0x33333333u) << 2;
+ bits = (bits & 0xf0f0f0f0u) >> 4 | (bits & 0x0f0f0f0fu) << 4;
+ return absl::gbswap_32(bits);
+}
+
+// Polynomial long multiplication mod the polynomial of degree 32.
+void PolyMultiply(uint32_t* val, uint32_t m, uint32_t poly) {
+ uint32_t l = *val;
+ uint32_t result = 0;
+ auto onebit = uint32_t{0x80000000u};
+ for (uint32_t one = onebit; one != 0; one >>= 1) {
+ if ((l & one) != 0) {
+ result ^= m;
+ }
+ if (m & 1) {
+ m = (m >> 1) ^ poly;
+ } else {
+ m >>= 1;
+ }
+ }
+ *val = result;
+}
+} // namespace
+
+void CRCImpl::FillWordTable(uint32_t poly, uint32_t last, int word_size,
+ Uint32By256* t) {
+ for (int j = 0; j != word_size; j++) { // for each byte of extension....
+ t[j][0] = 0; // a zero has no effect
+ for (int i = 128; i != 0; i >>= 1) { // fill in entries for powers of 2
+ if (j == 0 && i == 128) {
+ t[j][i] = last; // top bit in last byte is given
+ } else {
+ // each successive power of two is derived from the previous
+ // one, either in this table, or the last table
+ uint32_t pred;
+ if (i == 128) {
+ pred = t[j - 1][1];
+ } else {
+ pred = t[j][i << 1];
+ }
+ // Advance the CRC by one bit (multiply by X, and take remainder
+ // through one step of polynomial long division)
+ if (pred & 1) {
+ t[j][i] = (pred >> 1) ^ poly;
+ } else {
+ t[j][i] = pred >> 1;
+ }
+ }
+ }
+ // CRCs have the property that CRC(a xor b) == CRC(a) xor CRC(b)
+ // so we can make all the tables for non-powers of two by
+ // xoring previously created entries.
+ for (int i = 2; i != 256; i <<= 1) {
+ for (int k = i + 1; k != (i << 1); k++) {
+ t[j][k] = t[j][i] ^ t[j][k - i];
+ }
+ }
+ }
+}
+
+int CRCImpl::FillZeroesTable(uint32_t poly, Uint32By256* t) {
+ uint32_t inc = 1;
+ inc <<= 31;
+
+ // Extend by one zero bit. We know degree > 1 so (inc & 1) == 0.
+ inc >>= 1;
+
+ // Now extend by 2, 4, and 8 bits, so now `inc` is extended by one zero byte.
+ for (int i = 0; i < 3; ++i) {
+ PolyMultiply(&inc, inc, poly);
+ }
+
+ int j = 0;
+ for (uint64_t inc_len = 1; inc_len != 0; inc_len <<= ZEROES_BASE_LG) {
+ // Every entry in the table adds an additional inc_len zeroes.
+ uint32_t v = inc;
+ for (int a = 1; a != ZEROES_BASE; a++) {
+ t[0][j] = v;
+ PolyMultiply(&v, inc, poly);
+ j++;
+ }
+ inc = v;
+ }
+ ABSL_RAW_CHECK(j <= 256, "");
+ return j;
+}
+
+// Internal version of the "constructor".
+CRCImpl* CRCImpl::NewInternal() {
+ // Find an accelearated implementation first.
+ CRCImpl* result = TryNewCRC32AcceleratedX86ARMCombined();
+
+ // Fall back to generic implementions if no acceleration is available.
+ if (result == nullptr) {
+ result = new CRC32();
+ }
+
+ result->InitTables();
+
+ return result;
+}
+
+// The 32-bit implementation
+
+void CRC32::InitTables() {
+ // Compute the table for extending a CRC by one byte.
+ Uint32By256* t = new Uint32By256[4];
+ FillWordTable(kCrc32cPoly, kCrc32cPoly, 1, t);
+ for (int i = 0; i != 256; i++) {
+ this->table0_[i] = t[0][i];
+ }
+
+ // Construct a table for updating the CRC by 4 bytes data followed by
+ // 12 bytes of zeroes.
+ //
+ // Note: the data word size could be larger than the CRC size; it might
+ // be slightly faster to use a 64-bit data word, but doing so doubles the
+ // table size.
+ uint32_t last = kCrc32cPoly;
+ const size_t size = 12;
+ for (size_t i = 0; i < size; ++i) {
+ last = (last >> 8) ^ this->table0_[last & 0xff];
+ }
+ FillWordTable(kCrc32cPoly, last, 4, t);
+ for (size_t b = 0; b < 4; ++b) {
+ for (int i = 0; i < 256; ++i) {
+ this->table_[b][i] = t[b][i];
+ }
+ }
+
+ int j = FillZeroesTable(kCrc32cPoly, t);
+ ABSL_RAW_CHECK(j <= static_cast<int>(ABSL_ARRAYSIZE(this->zeroes_)), "");
+ for (int i = 0; i < j; i++) {
+ this->zeroes_[i] = t[0][i];
+ }
+
+ delete[] t;
+
+ // Build up tables for _reversing_ the operation of doing CRC operations on
+ // zero bytes.
+
+ // In C++, extending `crc` by a single zero bit is done by the following:
+ // (A) bool low_bit_set = (crc & 1);
+ // crc >>= 1;
+ // if (low_bit_set) crc ^= kCrc32cPoly;
+ //
+ // In particular note that the high bit of `crc` after this operation will be
+ // set if and only if the low bit of `crc` was set before it. This means that
+ // no information is lost, and the operation can be reversed, as follows:
+ // (B) bool high_bit_set = (crc & 0x80000000u);
+ // if (high_bit_set) crc ^= kCrc32cPoly;
+ // crc <<= 1;
+ // if (high_bit_set) crc ^= 1;
+ //
+ // Or, equivalently:
+ // (C) bool high_bit_set = (crc & 0x80000000u);
+ // crc <<= 1;
+ // if (high_bit_set) crc ^= ((kCrc32cPoly << 1) ^ 1);
+ //
+ // The last observation is, if we store our checksums in variable `rcrc`,
+ // with order of the bits reversed, the inverse operation becomes:
+ // (D) bool low_bit_set = (rcrc & 1);
+ // rcrc >>= 1;
+ // if (low_bit_set) rcrc ^= ReverseBits((kCrc32cPoly << 1) ^ 1)
+ //
+ // This is the same algorithm (A) that we started with, only with a different
+ // polynomial bit pattern. This means that by building up our tables with
+ // this alternate polynomial, we can apply the CRC algorithms to a
+ // bit-reversed CRC checksum to perform inverse zero-extension.
+
+ const uint32_t kCrc32cUnextendPoly =
+ ReverseBits(static_cast<uint32_t>((kCrc32cPoly << 1) ^ 1));
+ FillWordTable(kCrc32cUnextendPoly, kCrc32cUnextendPoly, 1, &reverse_table0_);
+
+ j = FillZeroesTable(kCrc32cUnextendPoly, &reverse_zeroes_);
+ ABSL_RAW_CHECK(j <= static_cast<int>(ABSL_ARRAYSIZE(this->reverse_zeroes_)),
+ "");
+}
+
+void CRC32::Extend(uint32_t* crc, const void* bytes, size_t length) const {
+ const uint8_t* p = static_cast<const uint8_t*>(bytes);
+ const uint8_t* e = p + length;
+ uint32_t l = *crc;
+
+ auto step_one_byte = [this, &p, &l]() {
+ int c = (l & 0xff) ^ *p++;
+ l = this->table0_[c] ^ (l >> 8);
+ };
+
+ if (kNeedAlignedLoads) {
+ // point x at first 4-byte aligned byte in string. this might be past the
+ // end of the string.
+ const uint8_t* x = RoundUp<4>(p);
+ if (x <= e) {
+ // Process bytes until finished or p is 4-byte aligned
+ while (p != x) {
+ step_one_byte();
+ }
+ }
+ }
+
+ const size_t kSwathSize = 16;
+ if (static_cast<size_t>(e - p) >= kSwathSize) {
+ // Load one swath of data into the operating buffers.
+ uint32_t buf0 = absl::little_endian::Load32(p) ^ l;
+ uint32_t buf1 = absl::little_endian::Load32(p + 4);
+ uint32_t buf2 = absl::little_endian::Load32(p + 8);
+ uint32_t buf3 = absl::little_endian::Load32(p + 12);
+ p += kSwathSize;
+
+ // Increment a CRC value by a "swath"; this combines the four bytes
+ // starting at `ptr` and twelve zero bytes, so that four CRCs can be
+ // built incrementally and combined at the end.
+ const auto step_swath = [this](uint32_t crc_in, const std::uint8_t* ptr) {
+ return absl::little_endian::Load32(ptr) ^
+ this->table_[3][crc_in & 0xff] ^
+ this->table_[2][(crc_in >> 8) & 0xff] ^
+ this->table_[1][(crc_in >> 16) & 0xff] ^
+ this->table_[0][crc_in >> 24];
+ };
+
+ // Run one CRC calculation step over all swaths in one 16-byte stride
+ const auto step_stride = [&]() {
+ buf0 = step_swath(buf0, p);
+ buf1 = step_swath(buf1, p + 4);
+ buf2 = step_swath(buf2, p + 8);
+ buf3 = step_swath(buf3, p + 12);
+ p += 16;
+ };
+
+ // Process kStride interleaved swaths through the data in parallel.
+ while ((e - p) > kPrefetchHorizon) {
+ PrefetchToLocalCacheNta(
+ reinterpret_cast<const void*>(p + kPrefetchHorizon));
+ // Process 64 bytes at a time
+ step_stride();
+ step_stride();
+ step_stride();
+ step_stride();
+ }
+ while (static_cast<size_t>(e - p) >= kSwathSize) {
+ step_stride();
+ }
+
+ // Now advance one word at a time as far as possible. This isn't worth
+ // doing if we have word-advance tables.
+ while (static_cast<size_t>(e - p) >= 4) {
+ buf0 = step_swath(buf0, p);
+ uint32_t tmp = buf0;
+ buf0 = buf1;
+ buf1 = buf2;
+ buf2 = buf3;
+ buf3 = tmp;
+ p += 4;
+ }
+
+ // Combine the results from the different swaths. This is just a CRC
+ // on the data values in the bufX words.
+ auto combine_one_word = [this](uint32_t crc_in, uint32_t w) {
+ w ^= crc_in;
+ for (size_t i = 0; i < 4; ++i) {
+ w = (w >> 8) ^ this->table0_[w & 0xff];
+ }
+ return w;
+ };
+
+ l = combine_one_word(0, buf0);
+ l = combine_one_word(l, buf1);
+ l = combine_one_word(l, buf2);
+ l = combine_one_word(l, buf3);
+ }
+
+ // Process the last few bytes
+ while (p != e) {
+ step_one_byte();
+ }
+
+ *crc = l;
+}
+
+void CRC32::ExtendByZeroesImpl(uint32_t* crc, size_t length,
+ const uint32_t zeroes_table[256],
+ const uint32_t poly_table[256]) {
+ if (length != 0) {
+ uint32_t l = *crc;
+ // For each ZEROES_BASE_LG bits in length
+ // (after the low-order bits have been removed)
+ // we lookup the appropriate polynomial in the zeroes_ array
+ // and do a polynomial long multiplication (mod the CRC polynomial)
+ // to extend the CRC by the appropriate number of bits.
+ for (int i = 0; length != 0;
+ i += ZEROES_BASE - 1, length >>= ZEROES_BASE_LG) {
+ int c = length & (ZEROES_BASE - 1); // pick next ZEROES_BASE_LG bits
+ if (c != 0) { // if they are not zero,
+ // multiply by entry in table
+ // Build a table to aid in multiplying 2 bits at a time.
+ // It takes too long to build tables for more bits.
+ uint64_t m = zeroes_table[c + i - 1];
+ m <<= 1;
+ uint64_t m2 = m << 1;
+ uint64_t mtab[4] = {0, m, m2, m2 ^ m};
+
+ // Do the multiply one byte at a time.
+ uint64_t result = 0;
+ for (int x = 0; x < 32; x += 8) {
+ // The carry-less multiply.
+ result ^= mtab[l & 3] ^ (mtab[(l >> 2) & 3] << 2) ^
+ (mtab[(l >> 4) & 3] << 4) ^ (mtab[(l >> 6) & 3] << 6);
+ l >>= 8;
+
+ // Reduce modulo the polynomial
+ result = (result >> 8) ^ poly_table[result & 0xff];
+ }
+ l = static_cast<uint32_t>(result);
+ }
+ }
+ *crc = l;
+ }
+}
+
+void CRC32::ExtendByZeroes(uint32_t* crc, size_t length) const {
+ return CRC32::ExtendByZeroesImpl(crc, length, zeroes_, table0_);
+}
+
+void CRC32::UnextendByZeroes(uint32_t* crc, size_t length) const {
+ // See the comment in CRC32::InitTables() for an explanation of the algorithm
+ // below.
+ *crc = ReverseBits(*crc);
+ ExtendByZeroesImpl(crc, length, reverse_zeroes_, reverse_table0_);
+ *crc = ReverseBits(*crc);
+}
+
+void CRC32::Scramble(uint32_t* crc) const {
+ // Rotate by near half the word size plus 1. See the scramble comment in
+ // crc_internal.h for an explanation.
+ constexpr int scramble_rotate = (32 / 2) + 1;
+ *crc = RotateRight<uint32_t>(static_cast<unsigned int>(*crc + kScrambleLo),
+ 32, scramble_rotate) &
+ MaskOfLength<uint32_t>(32);
+}
+
+void CRC32::Unscramble(uint32_t* crc) const {
+ constexpr int scramble_rotate = (32 / 2) + 1;
+ uint64_t rotated = RotateRight<uint32_t>(static_cast<unsigned int>(*crc), 32,
+ 32 - scramble_rotate);
+ *crc = (rotated - kScrambleLo) & MaskOfLength<uint32_t>(32);
+}
+
+// Constructor and destructor for base class CRC.
+CRC::~CRC() {}
+CRC::CRC() {}
+
+// The "constructor" for a CRC32C with a standard polynomial.
+CRC* CRC::Crc32c() {
+ static CRC* singleton = CRCImpl::NewInternal();
+ return singleton;
+}
+
+} // namespace crc_internal
+ABSL_NAMESPACE_END
+} // namespace absl
diff --git a/absl/crc/internal/crc.h b/absl/crc/internal/crc.h
new file mode 100644
index 0000000..4efdd03
--- /dev/null
+++ b/absl/crc/internal/crc.h
@@ -0,0 +1,83 @@
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef ABSL_CRC_INTERNAL_CRC_H_
+#define ABSL_CRC_INTERNAL_CRC_H_
+
+#include <cstdint>
+
+#include "absl/base/config.h"
+
+// This class implements CRCs (aka Rabin Fingerprints).
+// Treats the input as a polynomial with coefficients in Z(2),
+// and finds the remainder when divided by an primitive polynomial
+// of the appropriate length.
+
+// A polynomial is represented by the bit pattern formed by its coefficients,
+// but with the highest order bit not stored.
+// The highest degree coefficient is stored in the lowest numbered bit
+// in the lowest addressed byte. Thus, in what follows, the highest degree
+// coefficient that is stored is in the low order bit of "lo" or "*lo".
+
+// Hardware acceleration is used when available.
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace crc_internal {
+
+class CRC {
+ public:
+ virtual ~CRC();
+
+ // If "*crc" is the CRC of bytestring A, place the CRC of
+ // the bytestring formed from the concatenation of A and the "length"
+ // bytes at "bytes" into "*crc".
+ virtual void Extend(uint32_t* crc, const void* bytes,
+ size_t length) const = 0;
+
+ // Equivalent to Extend(crc, bytes, length) where "bytes"
+ // points to an array of "length" zero bytes.
+ virtual void ExtendByZeroes(uint32_t* crc, size_t length) const = 0;
+
+ // Inverse operation of ExtendByZeroes. If `crc` is the CRC value of a string
+ // ending in `length` zero bytes, this returns a CRC value of that string
+ // with those zero bytes removed.
+ virtual void UnextendByZeroes(uint32_t* crc, size_t length) const = 0;
+
+ // Apply a non-linear transformation to "*crc" so that
+ // it is safe to CRC the result with the same polynomial without
+ // any reduction of error-detection ability in the outer CRC.
+ // Unscramble() performs the inverse transformation.
+ // It is strongly recommended that CRCs be scrambled before storage or
+ // transmission, and unscrambled at the other end before further manipulation.
+ virtual void Scramble(uint32_t* crc) const = 0;
+ virtual void Unscramble(uint32_t* crc) const = 0;
+
+ // Crc32c() returns the singleton implementation of CRC for the CRC32C
+ // polynomial. Returns a handle that MUST NOT be destroyed with delete.
+ static CRC* Crc32c();
+
+ protected:
+ CRC(); // Clients may not call constructor; use Crc32c() instead.
+
+ private:
+ CRC(const CRC&) = delete;
+ CRC& operator=(const CRC&) = delete;
+};
+
+} // namespace crc_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_CRC_INTERNAL_CRC_H_
diff --git a/absl/crc/internal/crc32_x86_arm_combined_simd.h b/absl/crc/internal/crc32_x86_arm_combined_simd.h
new file mode 100644
index 0000000..1938a9b
--- /dev/null
+++ b/absl/crc/internal/crc32_x86_arm_combined_simd.h
@@ -0,0 +1,299 @@
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef ABSL_CRC_INTERNAL_CRC32_X86_ARM_COMBINED_SIMD_H_
+#define ABSL_CRC_INTERNAL_CRC32_X86_ARM_COMBINED_SIMD_H_
+
+#include <cstdint>
+
+#include "absl/base/config.h"
+
+// -------------------------------------------------------------------------
+// Many x86 and ARM machines have CRC acceleration hardware.
+// We can do a faster version of Extend() on such machines.
+// We define a translation layer for both x86 and ARM for the ease of use and
+// most performance gains.
+
+// This implementation requires 64-bit CRC instructions (part of SSE 4.2) and
+// PCLMULQDQ instructions. 32-bit builds with SSE 4.2 do exist, so the
+// __x86_64__ condition is necessary.
+#if defined(__x86_64__) && defined(__SSE4_2__) && defined(__PCLMUL__)
+
+#include <x86intrin.h>
+#define ABSL_CRC_INTERNAL_HAVE_X86_SIMD
+
+#elif defined(_MSC_VER) && !defined(__clang__) && defined(__AVX__) && \
+ defined(_M_AMD64)
+
+// MSVC AVX (/arch:AVX) implies SSE 4.2 and PCLMULQDQ.
+#include <intrin.h>
+#define ABSL_CRC_INTERNAL_HAVE_X86_SIMD
+
+#elif defined(__aarch64__) && defined(__LITTLE_ENDIAN__) && \
+ defined(__ARM_FEATURE_CRC32) && defined(ABSL_INTERNAL_HAVE_ARM_NEON) && \
+ defined(__ARM_FEATURE_CRYPTO)
+
+#include <arm_acle.h>
+#include <arm_neon.h>
+#define ABSL_CRC_INTERNAL_HAVE_ARM_SIMD
+
+#endif
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace crc_internal {
+
+#if defined(ABSL_CRC_INTERNAL_HAVE_ARM_SIMD) || \
+ defined(ABSL_CRC_INTERNAL_HAVE_X86_SIMD)
+
+#if defined(ABSL_CRC_INTERNAL_HAVE_ARM_SIMD)
+using V128 = uint64x2_t;
+#else
+// Note: Do not use __m128i_u, it is not portable.
+// Use V128_LoadU() perform an unaligned load from __m128i*.
+using V128 = __m128i;
+#endif
+
+// Starting with the initial value in |crc|, accumulates a CRC32 value for
+// unsigned integers of different sizes.
+uint32_t CRC32_u8(uint32_t crc, uint8_t v);
+
+uint32_t CRC32_u16(uint32_t crc, uint16_t v);
+
+uint32_t CRC32_u32(uint32_t crc, uint32_t v);
+
+uint32_t CRC32_u64(uint32_t crc, uint64_t v);
+
+// Loads 128 bits of integer data. |src| must be 16-byte aligned.
+V128 V128_Load(const V128* src);
+
+// Load 128 bits of integer data. |src| does not need to be aligned.
+V128 V128_LoadU(const V128* src);
+
+// Store 128 bits of integer data. |src| must be 16-byte aligned.
+void V128_Store(V128* dst, V128 data);
+
+// Polynomially multiplies the high 64 bits of |l| and |r|.
+V128 V128_PMulHi(const V128 l, const V128 r);
+
+// Polynomially multiplies the low 64 bits of |l| and |r|.
+V128 V128_PMulLow(const V128 l, const V128 r);
+
+// Polynomially multiplies the low 64 bits of |r| and high 64 bits of |l|.
+V128 V128_PMul01(const V128 l, const V128 r);
+
+// Polynomially multiplies the low 64 bits of |l| and high 64 bits of |r|.
+V128 V128_PMul10(const V128 l, const V128 r);
+
+// Produces a XOR operation of |l| and |r|.
+V128 V128_Xor(const V128 l, const V128 r);
+
+// Produces an AND operation of |l| and |r|.
+V128 V128_And(const V128 l, const V128 r);
+
+// Sets two 64 bit integers to one 128 bit vector. The order is reverse.
+// dst[63:0] := |r|
+// dst[127:64] := |l|
+V128 V128_From2x64(const uint64_t l, const uint64_t r);
+
+// Shift |l| right by |imm| bytes while shifting in zeros.
+template <int imm>
+V128 V128_ShiftRight(const V128 l);
+
+// Extracts a 32-bit integer from |l|, selected with |imm|.
+template <int imm>
+int V128_Extract32(const V128 l);
+
+// Extracts a 64-bit integer from |l|, selected with |imm|.
+template <int imm>
+uint64_t V128_Extract64(const V128 l);
+
+// Extracts the low 64 bits from V128.
+int64_t V128_Low64(const V128 l);
+
+// Left-shifts packed 64-bit integers in l by r.
+V128 V128_ShiftLeft64(const V128 l, const V128 r);
+
+#endif
+
+#if defined(ABSL_CRC_INTERNAL_HAVE_X86_SIMD)
+
+inline uint32_t CRC32_u8(uint32_t crc, uint8_t v) {
+ return _mm_crc32_u8(crc, v);
+}
+
+inline uint32_t CRC32_u16(uint32_t crc, uint16_t v) {
+ return _mm_crc32_u16(crc, v);
+}
+
+inline uint32_t CRC32_u32(uint32_t crc, uint32_t v) {
+ return _mm_crc32_u32(crc, v);
+}
+
+inline uint32_t CRC32_u64(uint32_t crc, uint64_t v) {
+ return static_cast<uint32_t>(_mm_crc32_u64(crc, v));
+}
+
+inline V128 V128_Load(const V128* src) { return _mm_load_si128(src); }
+
+inline V128 V128_LoadU(const V128* src) { return _mm_loadu_si128(src); }
+
+inline void V128_Store(V128* dst, V128 data) { _mm_store_si128(dst, data); }
+
+inline V128 V128_PMulHi(const V128 l, const V128 r) {
+ return _mm_clmulepi64_si128(l, r, 0x11);
+}
+
+inline V128 V128_PMulLow(const V128 l, const V128 r) {
+ return _mm_clmulepi64_si128(l, r, 0x00);
+}
+
+inline V128 V128_PMul01(const V128 l, const V128 r) {
+ return _mm_clmulepi64_si128(l, r, 0x01);
+}
+
+inline V128 V128_PMul10(const V128 l, const V128 r) {
+ return _mm_clmulepi64_si128(l, r, 0x10);
+}
+
+inline V128 V128_Xor(const V128 l, const V128 r) { return _mm_xor_si128(l, r); }
+
+inline V128 V128_And(const V128 l, const V128 r) { return _mm_and_si128(l, r); }
+
+inline V128 V128_From2x64(const uint64_t l, const uint64_t r) {
+ return _mm_set_epi64x(static_cast<int64_t>(l), static_cast<int64_t>(r));
+}
+
+template <int imm>
+inline V128 V128_ShiftRight(const V128 l) {
+ return _mm_srli_si128(l, imm);
+}
+
+template <int imm>
+inline int V128_Extract32(const V128 l) {
+ return _mm_extract_epi32(l, imm);
+}
+
+template <int imm>
+inline uint64_t V128_Extract64(const V128 l) {
+ return static_cast<uint64_t>(_mm_extract_epi64(l, imm));
+}
+
+inline int64_t V128_Low64(const V128 l) { return _mm_cvtsi128_si64(l); }
+
+inline V128 V128_ShiftLeft64(const V128 l, const V128 r) {
+ return _mm_sll_epi64(l, r);
+}
+
+#elif defined(ABSL_CRC_INTERNAL_HAVE_ARM_SIMD)
+
+inline uint32_t CRC32_u8(uint32_t crc, uint8_t v) { return __crc32cb(crc, v); }
+
+inline uint32_t CRC32_u16(uint32_t crc, uint16_t v) {
+ return __crc32ch(crc, v);
+}
+
+inline uint32_t CRC32_u32(uint32_t crc, uint32_t v) {
+ return __crc32cw(crc, v);
+}
+
+inline uint32_t CRC32_u64(uint32_t crc, uint64_t v) {
+ return __crc32cd(crc, v);
+}
+
+inline V128 V128_Load(const V128* src) {
+ return vld1q_u64(reinterpret_cast<const uint64_t*>(src));
+}
+
+inline V128 V128_LoadU(const V128* src) {
+ return vld1q_u64(reinterpret_cast<const uint64_t*>(src));
+}
+
+inline void V128_Store(V128* dst, V128 data) {
+ vst1q_u64(reinterpret_cast<uint64_t*>(dst), data);
+}
+
+// Using inline assembly as clang does not generate the pmull2 instruction and
+// performance drops by 15-20%.
+// TODO(b/193678732): Investigate why there is a slight performance hit when
+// using intrinsics instead of inline assembly.
+inline V128 V128_PMulHi(const V128 l, const V128 r) {
+ uint64x2_t res;
+ __asm__ __volatile__("pmull2 %0.1q, %1.2d, %2.2d \n\t"
+ : "=w"(res)
+ : "w"(l), "w"(r));
+ return res;
+}
+
+// TODO(b/193678732): Investigate why the compiler decides to move the constant
+// loop multiplicands from GPR to Neon registers every loop iteration.
+inline V128 V128_PMulLow(const V128 l, const V128 r) {
+ uint64x2_t res;
+ __asm__ __volatile__("pmull %0.1q, %1.1d, %2.1d \n\t"
+ : "=w"(res)
+ : "w"(l), "w"(r));
+ return res;
+}
+
+inline V128 V128_PMul01(const V128 l, const V128 r) {
+ return reinterpret_cast<V128>(vmull_p64(
+ reinterpret_cast<poly64_t>(vget_high_p64(vreinterpretq_p64_u64(l))),
+ reinterpret_cast<poly64_t>(vget_low_p64(vreinterpretq_p64_u64(r)))));
+}
+
+inline V128 V128_PMul10(const V128 l, const V128 r) {
+ return reinterpret_cast<V128>(vmull_p64(
+ reinterpret_cast<poly64_t>(vget_low_p64(vreinterpretq_p64_u64(l))),
+ reinterpret_cast<poly64_t>(vget_high_p64(vreinterpretq_p64_u64(r)))));
+}
+
+inline V128 V128_Xor(const V128 l, const V128 r) { return veorq_u64(l, r); }
+
+inline V128 V128_And(const V128 l, const V128 r) { return vandq_u64(l, r); }
+
+inline V128 V128_From2x64(const uint64_t l, const uint64_t r) {
+ return vcombine_u64(vcreate_u64(r), vcreate_u64(l));
+}
+
+template <int imm>
+inline V128 V128_ShiftRight(const V128 l) {
+ return vreinterpretq_u64_s8(
+ vextq_s8(vreinterpretq_s8_u64(l), vdupq_n_s8(0), imm));
+}
+
+template <int imm>
+inline int V128_Extract32(const V128 l) {
+ return vgetq_lane_s32(vreinterpretq_s32_u64(l), imm);
+}
+
+template <int imm>
+inline uint64_t V128_Extract64(const V128 l) {
+ return vgetq_lane_u64(l, imm);
+}
+
+inline int64_t V128_Low64(const V128 l) {
+ return vgetq_lane_s64(vreinterpretq_s64_u64(l), 0);
+}
+
+inline V128 V128_ShiftLeft64(const V128 l, const V128 r) {
+ return vshlq_u64(l, vreinterpretq_s64_u64(r));
+}
+
+#endif
+
+} // namespace crc_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_CRC_INTERNAL_CRC32_X86_ARM_COMBINED_SIMD_H_
diff --git a/absl/crc/internal/crc32c.h b/absl/crc/internal/crc32c.h
new file mode 100644
index 0000000..34027c5
--- /dev/null
+++ b/absl/crc/internal/crc32c.h
@@ -0,0 +1,39 @@
+// Copyright 2022 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef ABSL_CRC_INTERNAL_CRC32C_H_
+#define ABSL_CRC_INTERNAL_CRC32C_H_
+
+#include "absl/base/config.h"
+#include "absl/crc/crc32c.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace crc_internal {
+
+// Modifies a CRC32 value by removing `length` bytes with a value of 0 from
+// the end of the string.
+//
+// This is the inverse operation of ExtendCrc32cByZeroes().
+//
+// This operation has a runtime cost of O(log(`length`))
+//
+// Internal implementation detail, exposed for testing only.
+crc32c_t UnextendCrc32cByZeroes(crc32c_t initial_crc, size_t length);
+
+} // namespace crc_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_CRC_INTERNAL_CRC32C_H_
diff --git a/absl/crc/internal/crc32c_inline.h b/absl/crc/internal/crc32c_inline.h
new file mode 100644
index 0000000..6236c10
--- /dev/null
+++ b/absl/crc/internal/crc32c_inline.h
@@ -0,0 +1,72 @@
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef ABSL_CRC_INTERNAL_CRC32C_INLINE_H_
+#define ABSL_CRC_INTERNAL_CRC32C_INLINE_H_
+
+#include <cstdint>
+
+#include "absl/base/config.h"
+#include "absl/base/internal/endian.h"
+#include "absl/crc/internal/crc32_x86_arm_combined_simd.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace crc_internal {
+
+// CRC32C implementation optimized for small inputs.
+// Either computes crc and return true, or if there is
+// no hardware support does nothing and returns false.
+inline bool ExtendCrc32cInline(uint32_t* crc, const char* p, size_t n) {
+#if defined(ABSL_CRC_INTERNAL_HAVE_ARM_SIMD) || \
+ defined(ABSL_CRC_INTERNAL_HAVE_X86_SIMD)
+ constexpr uint32_t kCrc32Xor = 0xffffffffU;
+ *crc ^= kCrc32Xor;
+ if (n & 1) {
+ *crc = CRC32_u8(*crc, static_cast<uint8_t>(*p));
+ n--;
+ p++;
+ }
+ if (n & 2) {
+ *crc = CRC32_u16(*crc, absl::little_endian::Load16(p));
+ n -= 2;
+ p += 2;
+ }
+ if (n & 4) {
+ *crc = CRC32_u32(*crc, absl::little_endian::Load32(p));
+ n -= 4;
+ p += 4;
+ }
+ while (n) {
+ *crc = CRC32_u64(*crc, absl::little_endian::Load64(p));
+ n -= 8;
+ p += 8;
+ }
+ *crc ^= kCrc32Xor;
+ return true;
+#else
+ // No hardware support, signal the need to fallback.
+ static_cast<void>(crc);
+ static_cast<void>(p);
+ static_cast<void>(n);
+ return false;
+#endif // defined(ABSL_CRC_INTERNAL_HAVE_ARM_SIMD) ||
+ // defined(ABSL_CRC_INTERNAL_HAVE_X86_SIMD)
+}
+
+} // namespace crc_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_CRC_INTERNAL_CRC32C_INLINE_H_
diff --git a/absl/crc/internal/crc_cord_state.cc b/absl/crc/internal/crc_cord_state.cc
new file mode 100644
index 0000000..303a555
--- /dev/null
+++ b/absl/crc/internal/crc_cord_state.cc
@@ -0,0 +1,131 @@
+// Copyright 2022 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/crc/internal/crc_cord_state.h"
+
+#include <cassert>
+
+#include "absl/base/config.h"
+#include "absl/base/no_destructor.h"
+#include "absl/numeric/bits.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace crc_internal {
+
+CrcCordState::RefcountedRep* CrcCordState::RefSharedEmptyRep() {
+ static absl::NoDestructor<CrcCordState::RefcountedRep> empty;
+
+ assert(empty->count.load(std::memory_order_relaxed) >= 1);
+ assert(empty->rep.removed_prefix.length == 0);
+ assert(empty->rep.prefix_crc.empty());
+
+ Ref(empty.get());
+ return empty.get();
+}
+
+CrcCordState::CrcCordState() : refcounted_rep_(new RefcountedRep) {}
+
+CrcCordState::CrcCordState(const CrcCordState& other)
+ : refcounted_rep_(other.refcounted_rep_) {
+ Ref(refcounted_rep_);
+}
+
+CrcCordState::CrcCordState(CrcCordState&& other)
+ : refcounted_rep_(other.refcounted_rep_) {
+ // Make `other` valid for use after move.
+ other.refcounted_rep_ = RefSharedEmptyRep();
+}
+
+CrcCordState& CrcCordState::operator=(const CrcCordState& other) {
+ if (this != &other) {
+ Unref(refcounted_rep_);
+ refcounted_rep_ = other.refcounted_rep_;
+ Ref(refcounted_rep_);
+ }
+ return *this;
+}
+
+CrcCordState& CrcCordState::operator=(CrcCordState&& other) {
+ if (this != &other) {
+ Unref(refcounted_rep_);
+ refcounted_rep_ = other.refcounted_rep_;
+ // Make `other` valid for use after move.
+ other.refcounted_rep_ = RefSharedEmptyRep();
+ }
+ return *this;
+}
+
+CrcCordState::~CrcCordState() {
+ Unref(refcounted_rep_);
+}
+
+crc32c_t CrcCordState::Checksum() const {
+ if (rep().prefix_crc.empty()) {
+ return absl::crc32c_t{0};
+ }
+ if (IsNormalized()) {
+ return rep().prefix_crc.back().crc;
+ }
+ return absl::RemoveCrc32cPrefix(
+ rep().removed_prefix.crc, rep().prefix_crc.back().crc,
+ rep().prefix_crc.back().length - rep().removed_prefix.length);
+}
+
+CrcCordState::PrefixCrc CrcCordState::NormalizedPrefixCrcAtNthChunk(
+ size_t n) const {
+ assert(n < NumChunks());
+ if (IsNormalized()) {
+ return rep().prefix_crc[n];
+ }
+ size_t length = rep().prefix_crc[n].length - rep().removed_prefix.length;
+ return PrefixCrc(length,
+ absl::RemoveCrc32cPrefix(rep().removed_prefix.crc,
+ rep().prefix_crc[n].crc, length));
+}
+
+void CrcCordState::Normalize() {
+ if (IsNormalized() || rep().prefix_crc.empty()) {
+ return;
+ }
+
+ Rep* r = mutable_rep();
+ for (auto& prefix_crc : r->prefix_crc) {
+ size_t remaining = prefix_crc.length - r->removed_prefix.length;
+ prefix_crc.crc = absl::RemoveCrc32cPrefix(r->removed_prefix.crc,
+ prefix_crc.crc, remaining);
+ prefix_crc.length = remaining;
+ }
+ r->removed_prefix = PrefixCrc();
+}
+
+void CrcCordState::Poison() {
+ Rep* rep = mutable_rep();
+ if (NumChunks() > 0) {
+ for (auto& prefix_crc : rep->prefix_crc) {
+ // This is basically CRC32::Scramble().
+ uint32_t crc = static_cast<uint32_t>(prefix_crc.crc);
+ crc += 0x2e76e41b;
+ crc = absl::rotr(crc, 17);
+ prefix_crc.crc = crc32c_t{crc};
+ }
+ } else {
+ // Add a fake corrupt chunk.
+ rep->prefix_crc.emplace_back(0, crc32c_t{1});
+ }
+}
+
+} // namespace crc_internal
+ABSL_NAMESPACE_END
+} // namespace absl
diff --git a/absl/crc/internal/crc_cord_state.h b/absl/crc/internal/crc_cord_state.h
new file mode 100644
index 0000000..fbbb8c0
--- /dev/null
+++ b/absl/crc/internal/crc_cord_state.h
@@ -0,0 +1,159 @@
+// Copyright 2022 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef ABSL_CRC_INTERNAL_CRC_CORD_STATE_H_
+#define ABSL_CRC_INTERNAL_CRC_CORD_STATE_H_
+
+#include <atomic>
+#include <cstddef>
+#include <deque>
+
+#include "absl/base/config.h"
+#include "absl/crc/crc32c.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace crc_internal {
+
+// CrcCordState is a copy-on-write class that holds the chunked CRC32C data
+// that allows CrcCord to perform efficient substring operations. CrcCordState
+// is used as a member variable in CrcCord. When a CrcCord is converted to a
+// Cord, the CrcCordState is shallow-copied into the root node of the Cord. If
+// the converted Cord is modified outside of CrcCord, the CrcCordState is
+// discarded from the Cord. If the Cord is converted back to a CrcCord, and the
+// Cord is still carrying the CrcCordState in its root node, the CrcCord can
+// re-use the CrcCordState, making the construction of the CrcCord cheap.
+//
+// CrcCordState does not try to encapsulate the CRC32C state (CrcCord requires
+// knowledge of how CrcCordState represents the CRC32C state). It does
+// encapsulate the copy-on-write nature of the state.
+class CrcCordState {
+ public:
+ // Constructors.
+ CrcCordState();
+ CrcCordState(const CrcCordState&);
+ CrcCordState(CrcCordState&&);
+
+ // Destructor. Atomically unreferences the data.
+ ~CrcCordState();
+
+ // Copy and move operators.
+ CrcCordState& operator=(const CrcCordState&);
+ CrcCordState& operator=(CrcCordState&&);
+
+ // A (length, crc) pair.
+ struct PrefixCrc {
+ PrefixCrc() = default;
+ PrefixCrc(size_t length_arg, absl::crc32c_t crc_arg)
+ : length(length_arg), crc(crc_arg) {}
+
+ size_t length = 0;
+
+ // TODO(absl-team): Memory stomping often zeros out memory. If this struct
+ // gets overwritten, we could end up with {0, 0}, which is the correct CRC
+ // for a string of length 0. Consider storing a scrambled value and
+ // unscrambling it before verifying it.
+ absl::crc32c_t crc = absl::crc32c_t{0};
+ };
+
+ // The representation of the chunked CRC32C data.
+ struct Rep {
+ // `removed_prefix` is the crc and length of any prefix that has been
+ // removed from the Cord (for example, by calling
+ // `CrcCord::RemovePrefix()`). To get the checksum of any prefix of the
+ // cord, this value must be subtracted from `prefix_crc`. See `Checksum()`
+ // for an example.
+ //
+ // CrcCordState is said to be "normalized" if removed_prefix.length == 0.
+ PrefixCrc removed_prefix;
+
+ // A deque of (length, crc) pairs, representing length and crc of a prefix
+ // of the Cord, before removed_prefix has been subtracted. The lengths of
+ // the prefixes are stored in increasing order. If the Cord is not empty,
+ // the last value in deque is the contains the CRC32C of the entire Cord
+ // when removed_prefix is subtracted from it.
+ std::deque<PrefixCrc> prefix_crc;
+ };
+
+ // Returns a reference to the representation of the chunked CRC32C data.
+ const Rep& rep() const { return refcounted_rep_->rep; }
+
+ // Returns a mutable reference to the representation of the chunked CRC32C
+ // data. Calling this function will copy the data if another instance also
+ // holds a reference to the data, so it is important to call rep() instead if
+ // the data may not be mutated.
+ Rep* mutable_rep() {
+ if (refcounted_rep_->count.load(std::memory_order_acquire) != 1) {
+ RefcountedRep* copy = new RefcountedRep;
+ copy->rep = refcounted_rep_->rep;
+ Unref(refcounted_rep_);
+ refcounted_rep_ = copy;
+ }
+ return &refcounted_rep_->rep;
+ }
+
+ // Returns the CRC32C of the entire Cord.
+ absl::crc32c_t Checksum() const;
+
+ // Returns true if the chunked CRC32C cached is normalized.
+ bool IsNormalized() const { return rep().removed_prefix.length == 0; }
+
+ // Normalizes the chunked CRC32C checksum cache by subtracting any removed
+ // prefix from the chunks.
+ void Normalize();
+
+ // Returns the number of cached chunks.
+ size_t NumChunks() const { return rep().prefix_crc.size(); }
+
+ // Helper that returns the (length, crc) of the `n`-th cached chunked.
+ PrefixCrc NormalizedPrefixCrcAtNthChunk(size_t n) const;
+
+ // Poisons all chunks to so that Checksum() will likely be incorrect with high
+ // probability.
+ void Poison();
+
+ private:
+ struct RefcountedRep {
+ std::atomic<int32_t> count{1};
+ Rep rep;
+ };
+
+ // Adds a reference to the shared global empty `RefcountedRep`, and returns a
+ // pointer to the `RefcountedRep`. This is an optimization to avoid unneeded
+ // allocations when the allocation is unlikely to ever be used. The returned
+ // pointer can be `Unref()`ed when it is no longer needed. Since the returned
+ // instance will always have a reference counter greater than 1, attempts to
+ // modify it (by calling `mutable_rep()`) will create a new unshared copy.
+ static RefcountedRep* RefSharedEmptyRep();
+
+ static void Ref(RefcountedRep* r) {
+ assert(r != nullptr);
+ r->count.fetch_add(1, std::memory_order_relaxed);
+ }
+
+ static void Unref(RefcountedRep* r) {
+ assert(r != nullptr);
+ if (r->count.fetch_sub(1, std::memory_order_acq_rel) == 1) {
+ delete r;
+ }
+ }
+
+ RefcountedRep* refcounted_rep_;
+};
+
+} // namespace crc_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_CRC_INTERNAL_CRC_CORD_STATE_H_
diff --git a/absl/crc/internal/crc_cord_state_test.cc b/absl/crc/internal/crc_cord_state_test.cc
new file mode 100644
index 0000000..e2c8e3c
--- /dev/null
+++ b/absl/crc/internal/crc_cord_state_test.cc
@@ -0,0 +1,124 @@
+// Copyright 2022 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/crc/internal/crc_cord_state.h"
+
+#include <algorithm>
+#include <cstdint>
+#include <string>
+#include <utility>
+
+#include "gtest/gtest.h"
+#include "absl/crc/crc32c.h"
+
+namespace {
+
+TEST(CrcCordState, Default) {
+ absl::crc_internal::CrcCordState state;
+ EXPECT_TRUE(state.IsNormalized());
+ EXPECT_EQ(state.Checksum(), absl::crc32c_t{0});
+ state.Normalize();
+ EXPECT_EQ(state.Checksum(), absl::crc32c_t{0});
+}
+
+TEST(CrcCordState, Normalize) {
+ absl::crc_internal::CrcCordState state;
+ auto* rep = state.mutable_rep();
+ rep->prefix_crc.push_back(
+ absl::crc_internal::CrcCordState::PrefixCrc(1000, absl::crc32c_t{1000}));
+ rep->prefix_crc.push_back(
+ absl::crc_internal::CrcCordState::PrefixCrc(2000, absl::crc32c_t{2000}));
+ rep->removed_prefix =
+ absl::crc_internal::CrcCordState::PrefixCrc(500, absl::crc32c_t{500});
+
+ // The removed_prefix means state is not normalized.
+ EXPECT_FALSE(state.IsNormalized());
+
+ absl::crc32c_t crc = state.Checksum();
+ state.Normalize();
+ EXPECT_TRUE(state.IsNormalized());
+
+ // The checksum should not change as a result of calling Normalize().
+ EXPECT_EQ(state.Checksum(), crc);
+ EXPECT_EQ(rep->removed_prefix.length, 0);
+}
+
+TEST(CrcCordState, Copy) {
+ absl::crc_internal::CrcCordState state;
+ auto* rep = state.mutable_rep();
+ rep->prefix_crc.push_back(
+ absl::crc_internal::CrcCordState::PrefixCrc(1000, absl::crc32c_t{1000}));
+
+ absl::crc_internal::CrcCordState copy = state;
+
+ EXPECT_EQ(state.Checksum(), absl::crc32c_t{1000});
+ EXPECT_EQ(copy.Checksum(), absl::crc32c_t{1000});
+}
+
+TEST(CrcCordState, UnsharedSelfCopy) {
+ absl::crc_internal::CrcCordState state;
+ auto* rep = state.mutable_rep();
+ rep->prefix_crc.push_back(
+ absl::crc_internal::CrcCordState::PrefixCrc(1000, absl::crc32c_t{1000}));
+
+ const absl::crc_internal::CrcCordState& ref = state;
+ state = ref;
+
+ EXPECT_EQ(state.Checksum(), absl::crc32c_t{1000});
+}
+
+TEST(CrcCordState, Move) {
+ absl::crc_internal::CrcCordState state;
+ auto* rep = state.mutable_rep();
+ rep->prefix_crc.push_back(
+ absl::crc_internal::CrcCordState::PrefixCrc(1000, absl::crc32c_t{1000}));
+
+ absl::crc_internal::CrcCordState moved = std::move(state);
+ EXPECT_EQ(moved.Checksum(), absl::crc32c_t{1000});
+}
+
+TEST(CrcCordState, UnsharedSelfMove) {
+ absl::crc_internal::CrcCordState state;
+ auto* rep = state.mutable_rep();
+ rep->prefix_crc.push_back(
+ absl::crc_internal::CrcCordState::PrefixCrc(1000, absl::crc32c_t{1000}));
+
+ absl::crc_internal::CrcCordState& ref = state;
+ state = std::move(ref);
+
+ EXPECT_EQ(state.Checksum(), absl::crc32c_t{1000});
+}
+
+TEST(CrcCordState, PoisonDefault) {
+ absl::crc_internal::CrcCordState state;
+ state.Poison();
+ EXPECT_NE(state.Checksum(), absl::crc32c_t{0});
+}
+
+TEST(CrcCordState, PoisonData) {
+ absl::crc_internal::CrcCordState state;
+ auto* rep = state.mutable_rep();
+ rep->prefix_crc.push_back(
+ absl::crc_internal::CrcCordState::PrefixCrc(1000, absl::crc32c_t{1000}));
+ rep->prefix_crc.push_back(
+ absl::crc_internal::CrcCordState::PrefixCrc(2000, absl::crc32c_t{2000}));
+ rep->removed_prefix =
+ absl::crc_internal::CrcCordState::PrefixCrc(500, absl::crc32c_t{500});
+
+ absl::crc32c_t crc = state.Checksum();
+ state.Poison();
+ EXPECT_NE(state.Checksum(), crc);
+}
+
+} // namespace
diff --git a/absl/crc/internal/crc_internal.h b/absl/crc/internal/crc_internal.h
new file mode 100644
index 0000000..4d3582d
--- /dev/null
+++ b/absl/crc/internal/crc_internal.h
@@ -0,0 +1,177 @@
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef ABSL_CRC_INTERNAL_CRC_INTERNAL_H_
+#define ABSL_CRC_INTERNAL_CRC_INTERNAL_H_
+
+#include <cstdint>
+#include <memory>
+#include <vector>
+
+#include "absl/base/internal/raw_logging.h"
+#include "absl/crc/internal/crc.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+
+namespace crc_internal {
+
+// Prefetch constants used in some Extend() implementations
+constexpr int kPrefetchHorizon = ABSL_CACHELINE_SIZE * 4; // Prefetch this far
+// Shorter prefetch distance for smaller buffers
+constexpr int kPrefetchHorizonMedium = ABSL_CACHELINE_SIZE * 1;
+static_assert(kPrefetchHorizon >= 64, "CRCPrefetchHorizon less than loop len");
+
+// We require the Scramble() function:
+// - to be reversible (Unscramble() must exist)
+// - to be non-linear in the polynomial's Galois field (so the CRC of a
+// scrambled CRC is not linearly affected by the scrambled CRC, even if
+// using the same polynomial)
+// - not to be its own inverse. Preferably, if X=Scramble^N(X) and N!=0, then
+// N is large.
+// - to be fast.
+// - not to change once defined.
+// We introduce non-linearity in two ways:
+// Addition of a constant.
+// - The carries introduce non-linearity; we use bits of an irrational
+// (phi) to make it unlikely that we introduce no carries.
+// Rotate by a constant number of bits.
+// - We use floor(degree/2)+1, which does not divide the degree, and
+// splits the bits nearly evenly, which makes it less likely the
+// halves will be the same or one will be all zeroes.
+// We do both things to improve the chances of non-linearity in the face of
+// bit patterns with low numbers of bits set, while still being fast.
+// Below is the constant that we add. The bits are the first 128 bits of the
+// fractional part of phi, with a 1 ored into the bottom bit to maximize the
+// cycle length of repeated adds.
+constexpr uint64_t kScrambleHi = (static_cast<uint64_t>(0x4f1bbcdcU) << 32) |
+ static_cast<uint64_t>(0xbfa53e0aU);
+constexpr uint64_t kScrambleLo = (static_cast<uint64_t>(0xf9ce6030U) << 32) |
+ static_cast<uint64_t>(0x2e76e41bU);
+
+class CRCImpl : public CRC { // Implementation of the abstract class CRC
+ public:
+ using Uint32By256 = uint32_t[256];
+
+ CRCImpl() = default;
+ ~CRCImpl() override = default;
+
+ // The internal version of CRC::New().
+ static CRCImpl* NewInternal();
+
+ // Fill in a table for updating a CRC by one word of 'word_size' bytes
+ // [last_lo, last_hi] contains the answer if the last bit in the word
+ // is set.
+ static void FillWordTable(uint32_t poly, uint32_t last, int word_size,
+ Uint32By256* t);
+
+ // Build the table for extending by zeroes, returning the number of entries.
+ // For a in {1, 2, ..., ZEROES_BASE-1}, b in {0, 1, 2, 3, ...},
+ // entry j=a-1+(ZEROES_BASE-1)*b
+ // contains a polynomial Pi such that multiplying
+ // a CRC by Pi mod P, where P is the CRC polynomial, is equivalent to
+ // appending a*2**(ZEROES_BASE_LG*b) zero bytes to the original string.
+ static int FillZeroesTable(uint32_t poly, Uint32By256* t);
+
+ virtual void InitTables() = 0;
+
+ private:
+ CRCImpl(const CRCImpl&) = delete;
+ CRCImpl& operator=(const CRCImpl&) = delete;
+};
+
+// This is the 32-bit implementation. It handles all sizes from 8 to 32.
+class CRC32 : public CRCImpl {
+ public:
+ CRC32() = default;
+ ~CRC32() override = default;
+
+ void Extend(uint32_t* crc, const void* bytes, size_t length) const override;
+ void ExtendByZeroes(uint32_t* crc, size_t length) const override;
+ void Scramble(uint32_t* crc) const override;
+ void Unscramble(uint32_t* crc) const override;
+ void UnextendByZeroes(uint32_t* crc, size_t length) const override;
+
+ void InitTables() override;
+
+ private:
+ // Common implementation guts for ExtendByZeroes and UnextendByZeroes().
+ //
+ // zeroes_table is a table as returned by FillZeroesTable(), containing
+ // polynomials representing CRCs of strings-of-zeros of various lengths,
+ // and which can be combined by polynomial multiplication. poly_table is
+ // a table of CRC byte extension values. These tables are determined by
+ // the generator polynomial.
+ //
+ // These will be set to reverse_zeroes_ and reverse_table0_ for Unextend, and
+ // CRC32::zeroes_ and CRC32::table0_ for Extend.
+ static void ExtendByZeroesImpl(uint32_t* crc, size_t length,
+ const uint32_t zeroes_table[256],
+ const uint32_t poly_table[256]);
+
+ uint32_t table0_[256]; // table of byte extensions
+ uint32_t zeroes_[256]; // table of zero extensions
+
+ // table of 4-byte extensions shifted by 12 bytes of zeroes
+ uint32_t table_[4][256];
+
+ // Reverse lookup tables, using the alternate polynomial used by
+ // UnextendByZeroes().
+ uint32_t reverse_table0_[256]; // table of reverse byte extensions
+ uint32_t reverse_zeroes_[256]; // table of reverse zero extensions
+
+ CRC32(const CRC32&) = delete;
+ CRC32& operator=(const CRC32&) = delete;
+};
+
+// Helpers
+
+// Return a bit mask containing len 1-bits.
+// Requires 0 < len <= sizeof(T)
+template <typename T>
+T MaskOfLength(int len) {
+ // shift 2 by len-1 rather than 1 by len because shifts of wordsize
+ // are undefined.
+ return (T(2) << (len - 1)) - 1;
+}
+
+// Rotate low-order "width" bits of "in" right by "r" bits,
+// setting other bits in word to arbitrary values.
+template <typename T>
+T RotateRight(T in, int width, int r) {
+ return (in << (width - r)) | ((in >> r) & MaskOfLength<T>(width - r));
+}
+
+// RoundUp<N>(p) returns the lowest address >= p aligned to an N-byte
+// boundary. Requires that N is a power of 2.
+template <int alignment>
+const uint8_t* RoundUp(const uint8_t* p) {
+ static_assert((alignment & (alignment - 1)) == 0, "alignment is not 2^n");
+ constexpr uintptr_t mask = alignment - 1;
+ const uintptr_t as_uintptr = reinterpret_cast<uintptr_t>(p);
+ return reinterpret_cast<const uint8_t*>((as_uintptr + mask) & ~mask);
+}
+
+// Return a newly created CRC32AcceleratedX86ARMCombined if we can use Intel's
+// or ARM's CRC acceleration for a given polynomial. Return nullptr otherwise.
+CRCImpl* TryNewCRC32AcceleratedX86ARMCombined();
+
+// Return all possible hardware accelerated implementations. For testing only.
+std::vector<std::unique_ptr<CRCImpl>> NewCRC32AcceleratedX86ARMCombinedAll();
+
+} // namespace crc_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_CRC_INTERNAL_CRC_INTERNAL_H_
diff --git a/absl/crc/internal/crc_memcpy.h b/absl/crc/internal/crc_memcpy.h
new file mode 100644
index 0000000..a0fed65
--- /dev/null
+++ b/absl/crc/internal/crc_memcpy.h
@@ -0,0 +1,122 @@
+// Copyright 2022 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef ABSL_CRC_INTERNAL_CRC_MEMCPY_H_
+#define ABSL_CRC_INTERNAL_CRC_MEMCPY_H_
+
+#include <cstddef>
+#include <memory>
+
+#include "absl/base/config.h"
+#include "absl/crc/crc32c.h"
+#include "absl/crc/internal/crc32_x86_arm_combined_simd.h"
+
+// Defined if the class AcceleratedCrcMemcpyEngine exists.
+// TODO(b/299127771): Consider relaxing the pclmul requirement once the other
+// intrinsics are conditionally compiled without it.
+#if defined(ABSL_CRC_INTERNAL_HAVE_X86_SIMD)
+#define ABSL_INTERNAL_HAVE_X86_64_ACCELERATED_CRC_MEMCPY_ENGINE 1
+#elif defined(ABSL_CRC_INTERNAL_HAVE_ARM_SIMD)
+#define ABSL_INTERNAL_HAVE_ARM_ACCELERATED_CRC_MEMCPY_ENGINE 1
+#endif
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace crc_internal {
+
+class CrcMemcpyEngine {
+ public:
+ virtual ~CrcMemcpyEngine() = default;
+
+ virtual crc32c_t Compute(void* __restrict dst, const void* __restrict src,
+ std::size_t length, crc32c_t initial_crc) const = 0;
+
+ protected:
+ CrcMemcpyEngine() = default;
+};
+
+class CrcMemcpy {
+ public:
+ static crc32c_t CrcAndCopy(void* __restrict dst, const void* __restrict src,
+ std::size_t length,
+ crc32c_t initial_crc = crc32c_t{0},
+ bool non_temporal = false) {
+ static const ArchSpecificEngines engines = GetArchSpecificEngines();
+ auto* engine = non_temporal ? engines.non_temporal : engines.temporal;
+ return engine->Compute(dst, src, length, initial_crc);
+ }
+
+ // For testing only: get an architecture-specific engine for tests.
+ static std::unique_ptr<CrcMemcpyEngine> GetTestEngine(int vector,
+ int integer);
+
+ private:
+ struct ArchSpecificEngines {
+ CrcMemcpyEngine* temporal;
+ CrcMemcpyEngine* non_temporal;
+ };
+
+ static ArchSpecificEngines GetArchSpecificEngines();
+};
+
+// Fallback CRC-memcpy engine.
+class FallbackCrcMemcpyEngine : public CrcMemcpyEngine {
+ public:
+ FallbackCrcMemcpyEngine() = default;
+ FallbackCrcMemcpyEngine(const FallbackCrcMemcpyEngine&) = delete;
+ FallbackCrcMemcpyEngine operator=(const FallbackCrcMemcpyEngine&) = delete;
+
+ crc32c_t Compute(void* __restrict dst, const void* __restrict src,
+ std::size_t length, crc32c_t initial_crc) const override;
+};
+
+// CRC Non-Temporal-Memcpy engine.
+class CrcNonTemporalMemcpyEngine : public CrcMemcpyEngine {
+ public:
+ CrcNonTemporalMemcpyEngine() = default;
+ CrcNonTemporalMemcpyEngine(const CrcNonTemporalMemcpyEngine&) = delete;
+ CrcNonTemporalMemcpyEngine operator=(const CrcNonTemporalMemcpyEngine&) =
+ delete;
+
+ crc32c_t Compute(void* __restrict dst, const void* __restrict src,
+ std::size_t length, crc32c_t initial_crc) const override;
+};
+
+// CRC Non-Temporal-Memcpy AVX engine.
+class CrcNonTemporalMemcpyAVXEngine : public CrcMemcpyEngine {
+ public:
+ CrcNonTemporalMemcpyAVXEngine() = default;
+ CrcNonTemporalMemcpyAVXEngine(const CrcNonTemporalMemcpyAVXEngine&) = delete;
+ CrcNonTemporalMemcpyAVXEngine operator=(
+ const CrcNonTemporalMemcpyAVXEngine&) = delete;
+
+ crc32c_t Compute(void* __restrict dst, const void* __restrict src,
+ std::size_t length, crc32c_t initial_crc) const override;
+};
+
+// Copy source to destination and return the CRC32C of the data copied. If an
+// accelerated version is available, use the accelerated version, otherwise use
+// the generic fallback version.
+inline crc32c_t Crc32CAndCopy(void* __restrict dst, const void* __restrict src,
+ std::size_t length,
+ crc32c_t initial_crc = crc32c_t{0},
+ bool non_temporal = false) {
+ return CrcMemcpy::CrcAndCopy(dst, src, length, initial_crc, non_temporal);
+}
+
+} // namespace crc_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_CRC_INTERNAL_CRC_MEMCPY_H_
diff --git a/absl/crc/internal/crc_memcpy_fallback.cc b/absl/crc/internal/crc_memcpy_fallback.cc
new file mode 100644
index 0000000..0779550
--- /dev/null
+++ b/absl/crc/internal/crc_memcpy_fallback.cc
@@ -0,0 +1,77 @@
+// Copyright 2022 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <cstdint>
+#include <memory>
+
+#include "absl/base/config.h"
+#include "absl/crc/crc32c.h"
+#include "absl/crc/internal/crc_memcpy.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace crc_internal {
+
+absl::crc32c_t FallbackCrcMemcpyEngine::Compute(void* __restrict dst,
+ const void* __restrict src,
+ std::size_t length,
+ crc32c_t initial_crc) const {
+ constexpr size_t kBlockSize = 8192;
+ absl::crc32c_t crc = initial_crc;
+
+ const char* src_bytes = reinterpret_cast<const char*>(src);
+ char* dst_bytes = reinterpret_cast<char*>(dst);
+
+ // Copy + CRC loop - run 8k chunks until we are out of full chunks. CRC
+ // then copy was found to be slightly more efficient in our test cases.
+ std::size_t offset = 0;
+ for (; offset + kBlockSize < length; offset += kBlockSize) {
+ crc = absl::ExtendCrc32c(crc,
+ absl::string_view(src_bytes + offset, kBlockSize));
+ memcpy(dst_bytes + offset, src_bytes + offset, kBlockSize);
+ }
+
+ // Save some work if length is 0.
+ if (offset < length) {
+ std::size_t final_copy_size = length - offset;
+ crc = absl::ExtendCrc32c(
+ crc, absl::string_view(src_bytes + offset, final_copy_size));
+ memcpy(dst_bytes + offset, src_bytes + offset, final_copy_size);
+ }
+
+ return crc;
+}
+
+// Compile the following only if we don't have
+#if !defined(ABSL_INTERNAL_HAVE_X86_64_ACCELERATED_CRC_MEMCPY_ENGINE) && \
+ !defined(ABSL_INTERNAL_HAVE_ARM_ACCELERATED_CRC_MEMCPY_ENGINE)
+
+CrcMemcpy::ArchSpecificEngines CrcMemcpy::GetArchSpecificEngines() {
+ CrcMemcpy::ArchSpecificEngines engines;
+ engines.temporal = new FallbackCrcMemcpyEngine();
+ engines.non_temporal = new FallbackCrcMemcpyEngine();
+ return engines;
+}
+
+std::unique_ptr<CrcMemcpyEngine> CrcMemcpy::GetTestEngine(int /*vector*/,
+ int /*integer*/) {
+ return std::make_unique<FallbackCrcMemcpyEngine>();
+}
+
+#endif // !ABSL_INTERNAL_HAVE_X86_64_ACCELERATED_CRC_MEMCPY_ENGINE &&
+ // !ABSL_INTERNAL_HAVE_ARM_ACCELERATED_CRC_MEMCPY_ENGINE
+
+} // namespace crc_internal
+ABSL_NAMESPACE_END
+} // namespace absl
diff --git a/absl/crc/internal/crc_memcpy_test.cc b/absl/crc/internal/crc_memcpy_test.cc
new file mode 100644
index 0000000..045a1e5
--- /dev/null
+++ b/absl/crc/internal/crc_memcpy_test.cc
@@ -0,0 +1,177 @@
+// Copyright 2022 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/crc/internal/crc_memcpy.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <cstring>
+#include <limits>
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "gtest/gtest.h"
+#include "absl/crc/crc32c.h"
+#include "absl/memory/memory.h"
+#include "absl/random/distributions.h"
+#include "absl/random/random.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/string_view.h"
+
+namespace {
+
+enum CrcEngine {
+ ACCELERATED = 0,
+ NONTEMPORAL = 1,
+ FALLBACK = 2,
+};
+
+// Correctness tests:
+// - Every source/destination byte alignment 0-15, every size 0-511 bytes
+// - Arbitrarily aligned source, large size
+template <size_t max_size>
+class CrcMemcpyTest : public testing::Test {
+ protected:
+ CrcMemcpyTest() {
+ source_ = std::make_unique<char[]>(kSize);
+ destination_ = std::make_unique<char[]>(kSize);
+ }
+ static constexpr size_t kAlignment = 16;
+ static constexpr size_t kMaxCopySize = max_size;
+ static constexpr size_t kSize = kAlignment + kMaxCopySize;
+ std::unique_ptr<char[]> source_;
+ std::unique_ptr<char[]> destination_;
+
+ absl::BitGen gen_;
+};
+
+// Small test is slightly larger 4096 bytes to allow coverage of the "large"
+// copy function. The minimum size to exercise all code paths in that function
+// would be around 256 consecutive tests (getting every possible tail value
+// and 0-2 small copy loops after the main block), so testing from 4096-4500
+// will cover all of those code paths multiple times.
+typedef CrcMemcpyTest<4500> CrcSmallTest;
+typedef CrcMemcpyTest<(1 << 24)> CrcLargeTest;
+// Parametrize the small test so that it can be done with all configurations.
+template <typename ParamsT>
+class EngineParamTestTemplate : public CrcSmallTest,
+ public ::testing::WithParamInterface<ParamsT> {
+ protected:
+ EngineParamTestTemplate() {
+ if (GetParam().crc_engine_selector == FALLBACK) {
+ engine_ = std::make_unique<absl::crc_internal::FallbackCrcMemcpyEngine>();
+ } else if (GetParam().crc_engine_selector == NONTEMPORAL) {
+ engine_ =
+ std::make_unique<absl::crc_internal::CrcNonTemporalMemcpyEngine>();
+ } else {
+ engine_ = absl::crc_internal::CrcMemcpy::GetTestEngine(
+ GetParam().vector_lanes, GetParam().integer_lanes);
+ }
+ }
+
+ // Convenience method.
+ ParamsT GetParam() const {
+ return ::testing::WithParamInterface<ParamsT>::GetParam();
+ }
+
+ std::unique_ptr<absl::crc_internal::CrcMemcpyEngine> engine_;
+};
+struct TestParams {
+ CrcEngine crc_engine_selector = ACCELERATED;
+ int vector_lanes = 0;
+ int integer_lanes = 0;
+};
+using EngineParamTest = EngineParamTestTemplate<TestParams>;
+// SmallCorrectness is designed to exercise every possible set of code paths
+// in the memcpy code, not including the loop.
+TEST_P(EngineParamTest, SmallCorrectnessCheckSourceAlignment) {
+ constexpr size_t kTestSizes[] = {0, 100, 255, 512, 1024, 4000, kMaxCopySize};
+
+ for (size_t source_alignment = 0; source_alignment < kAlignment;
+ source_alignment++) {
+ for (auto size : kTestSizes) {
+ char* base_data = static_cast<char*>(source_.get()) + source_alignment;
+ for (size_t i = 0; i < size; i++) {
+ *(base_data + i) =
+ static_cast<char>(absl::Uniform<unsigned char>(gen_));
+ }
+ SCOPED_TRACE(absl::StrCat("engine=<", GetParam().vector_lanes, ",",
+ GetParam().integer_lanes, ">, ", "size=", size,
+ ", source_alignment=", source_alignment));
+ absl::crc32c_t initial_crc =
+ absl::crc32c_t{absl::Uniform<uint32_t>(gen_)};
+ absl::crc32c_t experiment_crc =
+ engine_->Compute(destination_.get(), source_.get() + source_alignment,
+ size, initial_crc);
+ // Check the memory region to make sure it is the same
+ int mem_comparison =
+ memcmp(destination_.get(), source_.get() + source_alignment, size);
+ SCOPED_TRACE(absl::StrCat("Error in memcpy of size: ", size,
+ " with source alignment: ", source_alignment));
+ ASSERT_EQ(mem_comparison, 0);
+ absl::crc32c_t baseline_crc = absl::ExtendCrc32c(
+ initial_crc,
+ absl::string_view(
+ static_cast<char*>(source_.get()) + source_alignment, size));
+ ASSERT_EQ(baseline_crc, experiment_crc);
+ }
+ }
+}
+
+TEST_P(EngineParamTest, SmallCorrectnessCheckDestAlignment) {
+ constexpr size_t kTestSizes[] = {0, 100, 255, 512, 1024, 4000, kMaxCopySize};
+
+ for (size_t dest_alignment = 0; dest_alignment < kAlignment;
+ dest_alignment++) {
+ for (auto size : kTestSizes) {
+ char* base_data = static_cast<char*>(source_.get());
+ for (size_t i = 0; i < size; i++) {
+ *(base_data + i) =
+ static_cast<char>(absl::Uniform<unsigned char>(gen_));
+ }
+ SCOPED_TRACE(absl::StrCat("engine=<", GetParam().vector_lanes, ",",
+ GetParam().integer_lanes, ">, ", "size=", size,
+ ", destination_alignment=", dest_alignment));
+ absl::crc32c_t initial_crc =
+ absl::crc32c_t{absl::Uniform<uint32_t>(gen_)};
+ absl::crc32c_t experiment_crc =
+ engine_->Compute(destination_.get() + dest_alignment, source_.get(),
+ size, initial_crc);
+ // Check the memory region to make sure it is the same
+ int mem_comparison =
+ memcmp(destination_.get() + dest_alignment, source_.get(), size);
+ SCOPED_TRACE(absl::StrCat("Error in memcpy of size: ", size,
+ " with dest alignment: ", dest_alignment));
+ ASSERT_EQ(mem_comparison, 0);
+ absl::crc32c_t baseline_crc = absl::ExtendCrc32c(
+ initial_crc,
+ absl::string_view(static_cast<char*>(source_.get()), size));
+ ASSERT_EQ(baseline_crc, experiment_crc);
+ }
+ }
+}
+
+INSTANTIATE_TEST_SUITE_P(EngineParamTest, EngineParamTest,
+ ::testing::Values(
+ // Tests for configurations that may occur in prod.
+ TestParams{ACCELERATED, 3, 0},
+ TestParams{ACCELERATED, 1, 2},
+ TestParams{ACCELERATED, 1, 0},
+ // Fallback test.
+ TestParams{FALLBACK, 0, 0},
+ // Non Temporal
+ TestParams{NONTEMPORAL, 0, 0}));
+
+} // namespace
diff --git a/absl/crc/internal/crc_memcpy_x86_arm_combined.cc b/absl/crc/internal/crc_memcpy_x86_arm_combined.cc
new file mode 100644
index 0000000..968e9ae
--- /dev/null
+++ b/absl/crc/internal/crc_memcpy_x86_arm_combined.cc
@@ -0,0 +1,450 @@
+// Copyright 2022 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Simultaneous memcopy and CRC-32C for x86-64 and ARM 64. Uses integer
+// registers because XMM registers do not support the CRC instruction (yet).
+// While copying, compute the running CRC of the data being copied.
+//
+// It is assumed that any CPU running this code has SSE4.2 instructions
+// available (for CRC32C). This file will do nothing if that is not true.
+//
+// The CRC instruction has a 3-byte latency, and we are stressing the ALU ports
+// here (unlike a traditional memcopy, which has almost no ALU use), so we will
+// need to copy in such a way that the CRC unit is used efficiently. We have two
+// regimes in this code:
+// 1. For operations of size < kCrcSmallSize, do the CRC then the memcpy
+// 2. For operations of size > kCrcSmallSize:
+// a) compute an initial CRC + copy on a small amount of data to align the
+// destination pointer on a 16-byte boundary.
+// b) Split the data into 3 main regions and a tail (smaller than 48 bytes)
+// c) Do the copy and CRC of the 3 main regions, interleaving (start with
+// full cache line copies for each region, then move to single 16 byte
+// pieces per region).
+// d) Combine the CRCs with CRC32C::Concat.
+// e) Copy the tail and extend the CRC with the CRC of the tail.
+// This method is not ideal for op sizes between ~1k and ~8k because CRC::Concat
+// takes a significant amount of time. A medium-sized approach could be added
+// using 3 CRCs over fixed-size blocks where the zero-extensions required for
+// CRC32C::Concat can be precomputed.
+
+#ifdef __SSE4_2__
+#include <immintrin.h>
+#endif
+
+#ifdef _MSC_VER
+#include <intrin.h>
+#endif
+
+#include <array>
+#include <cstddef>
+#include <cstdint>
+#include <cstring>
+#include <memory>
+
+#include "absl/base/config.h"
+#include "absl/base/optimization.h"
+#include "absl/base/prefetch.h"
+#include "absl/crc/crc32c.h"
+#include "absl/crc/internal/cpu_detect.h"
+#include "absl/crc/internal/crc32_x86_arm_combined_simd.h"
+#include "absl/crc/internal/crc_memcpy.h"
+#include "absl/strings/string_view.h"
+
+#if defined(ABSL_INTERNAL_HAVE_X86_64_ACCELERATED_CRC_MEMCPY_ENGINE) || \
+ defined(ABSL_INTERNAL_HAVE_ARM_ACCELERATED_CRC_MEMCPY_ENGINE)
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace crc_internal {
+
+namespace {
+
+inline crc32c_t ShortCrcCopy(char* dst, const char* src, std::size_t length,
+ crc32c_t crc) {
+ // Small copy: just go 1 byte at a time: being nice to the branch predictor
+ // is more important here than anything else
+ uint32_t crc_uint32 = static_cast<uint32_t>(crc);
+ for (std::size_t i = 0; i < length; i++) {
+ uint8_t data = *reinterpret_cast<const uint8_t*>(src);
+ crc_uint32 = CRC32_u8(crc_uint32, data);
+ *reinterpret_cast<uint8_t*>(dst) = data;
+ ++src;
+ ++dst;
+ }
+ return crc32c_t{crc_uint32};
+}
+
+constexpr size_t kIntLoadsPerVec = sizeof(V128) / sizeof(uint64_t);
+
+// Common function for copying the tails of multiple large regions.
+template <size_t vec_regions, size_t int_regions>
+inline void LargeTailCopy(crc32c_t* crcs, char** dst, const char** src,
+ size_t region_size, size_t copy_rounds) {
+ std::array<V128, vec_regions> data;
+ std::array<uint64_t, kIntLoadsPerVec * int_regions> int_data;
+
+ while (copy_rounds > 0) {
+ for (size_t i = 0; i < vec_regions; i++) {
+ size_t region = i;
+
+ auto* vsrc = reinterpret_cast<const V128*>(*src + region_size * region);
+ auto* vdst = reinterpret_cast<V128*>(*dst + region_size * region);
+
+ // Load the blocks, unaligned
+ data[i] = V128_LoadU(vsrc);
+
+ // Store the blocks, aligned
+ V128_Store(vdst, data[i]);
+
+ // Compute the running CRC
+ crcs[region] = crc32c_t{static_cast<uint32_t>(
+ CRC32_u64(static_cast<uint32_t>(crcs[region]),
+ static_cast<uint64_t>(V128_Extract64<0>(data[i]))))};
+ crcs[region] = crc32c_t{static_cast<uint32_t>(
+ CRC32_u64(static_cast<uint32_t>(crcs[region]),
+ static_cast<uint64_t>(V128_Extract64<1>(data[i]))))};
+ }
+
+ for (size_t i = 0; i < int_regions; i++) {
+ size_t region = vec_regions + i;
+
+ auto* usrc =
+ reinterpret_cast<const uint64_t*>(*src + region_size * region);
+ auto* udst = reinterpret_cast<uint64_t*>(*dst + region_size * region);
+
+ for (size_t j = 0; j < kIntLoadsPerVec; j++) {
+ size_t data_index = i * kIntLoadsPerVec + j;
+
+ int_data[data_index] = *(usrc + j);
+ crcs[region] = crc32c_t{static_cast<uint32_t>(CRC32_u64(
+ static_cast<uint32_t>(crcs[region]), int_data[data_index]))};
+
+ *(udst + j) = int_data[data_index];
+ }
+ }
+
+ // Increment pointers
+ *src += sizeof(V128);
+ *dst += sizeof(V128);
+ --copy_rounds;
+ }
+}
+
+} // namespace
+
+template <size_t vec_regions, size_t int_regions>
+class AcceleratedCrcMemcpyEngine : public CrcMemcpyEngine {
+ public:
+ AcceleratedCrcMemcpyEngine() = default;
+ AcceleratedCrcMemcpyEngine(const AcceleratedCrcMemcpyEngine&) = delete;
+ AcceleratedCrcMemcpyEngine operator=(const AcceleratedCrcMemcpyEngine&) =
+ delete;
+
+ crc32c_t Compute(void* __restrict dst, const void* __restrict src,
+ std::size_t length, crc32c_t initial_crc) const override;
+};
+
+template <size_t vec_regions, size_t int_regions>
+crc32c_t AcceleratedCrcMemcpyEngine<vec_regions, int_regions>::Compute(
+ void* __restrict dst, const void* __restrict src, std::size_t length,
+ crc32c_t initial_crc) const {
+ constexpr std::size_t kRegions = vec_regions + int_regions;
+ static_assert(kRegions > 0, "Must specify at least one region.");
+ constexpr uint32_t kCrcDataXor = uint32_t{0xffffffff};
+ constexpr std::size_t kBlockSize = sizeof(V128);
+ constexpr std::size_t kCopyRoundSize = kRegions * kBlockSize;
+
+ // Number of blocks per cacheline.
+ constexpr std::size_t kBlocksPerCacheLine = ABSL_CACHELINE_SIZE / kBlockSize;
+
+ char* dst_bytes = static_cast<char*>(dst);
+ const char* src_bytes = static_cast<const char*>(src);
+
+ // Make sure that one prefetch per big block is enough to cover the whole
+ // dataset, and we don't prefetch too much.
+ static_assert(ABSL_CACHELINE_SIZE % kBlockSize == 0,
+ "Cache lines are not divided evenly into blocks, may have "
+ "unintended behavior!");
+
+ // Experimentally-determined boundary between a small and large copy.
+ // Below this number, spin-up and concatenation of CRCs takes enough time that
+ // it kills the throughput gains of using 3 regions and wide vectors.
+ constexpr size_t kCrcSmallSize = 256;
+
+ // Experimentally-determined prefetch distance. Main loop copies will
+ // prefeth data 2 cache lines ahead.
+ constexpr std::size_t kPrefetchAhead = 2 * ABSL_CACHELINE_SIZE;
+
+ // Small-size CRC-memcpy : just do CRC + memcpy
+ if (length < kCrcSmallSize) {
+ crc32c_t crc =
+ ExtendCrc32c(initial_crc, absl::string_view(src_bytes, length));
+ memcpy(dst, src, length);
+ return crc;
+ }
+
+ // Start work on the CRC: undo the XOR from the previous calculation or set up
+ // the initial value of the CRC.
+ // initial_crc ^= kCrcDataXor;
+ initial_crc = crc32c_t{static_cast<uint32_t>(initial_crc) ^ kCrcDataXor};
+
+ // Do an initial alignment copy, so we can use aligned store instructions to
+ // the destination pointer. We align the destination pointer because the
+ // penalty for an unaligned load is small compared to the penalty of an
+ // unaligned store on modern CPUs.
+ std::size_t bytes_from_last_aligned =
+ reinterpret_cast<uintptr_t>(dst) & (kBlockSize - 1);
+ if (bytes_from_last_aligned != 0) {
+ std::size_t bytes_for_alignment = kBlockSize - bytes_from_last_aligned;
+
+ // Do the short-sized copy and CRC.
+ initial_crc =
+ ShortCrcCopy(dst_bytes, src_bytes, bytes_for_alignment, initial_crc);
+ src_bytes += bytes_for_alignment;
+ dst_bytes += bytes_for_alignment;
+ length -= bytes_for_alignment;
+ }
+
+ // We are going to do the copy and CRC in kRegions regions to make sure that
+ // we can saturate the CRC unit. The CRCs will be combined at the end of the
+ // run. Copying will use the SSE registers, and we will extract words from
+ // the SSE registers to add to the CRC. Initially, we run the loop one full
+ // cache line per region at a time, in order to insert prefetches.
+
+ // Initialize CRCs for kRegions regions.
+ crc32c_t crcs[kRegions];
+ crcs[0] = initial_crc;
+ for (size_t i = 1; i < kRegions; i++) {
+ crcs[i] = crc32c_t{kCrcDataXor};
+ }
+
+ // Find the number of rounds to copy and the region size. Also compute the
+ // tail size here.
+ size_t copy_rounds = length / kCopyRoundSize;
+
+ // Find the size of each region and the size of the tail.
+ const std::size_t region_size = copy_rounds * kBlockSize;
+ const std::size_t tail_size = length - (kRegions * region_size);
+
+ // Holding registers for data in each region.
+ std::array<V128, vec_regions> vec_data;
+ std::array<uint64_t, int_regions * kIntLoadsPerVec> int_data;
+
+ // Main loop.
+ while (copy_rounds > kBlocksPerCacheLine) {
+ // Prefetch kPrefetchAhead bytes ahead of each pointer.
+ for (size_t i = 0; i < kRegions; i++) {
+ absl::PrefetchToLocalCache(src_bytes + kPrefetchAhead + region_size * i);
+#ifdef ABSL_INTERNAL_HAVE_X86_64_ACCELERATED_CRC_MEMCPY_ENGINE
+ // TODO(b/297082454): investigate dropping prefetch on x86.
+ absl::PrefetchToLocalCache(dst_bytes + kPrefetchAhead + region_size * i);
+#endif
+ }
+
+ // Load and store data, computing CRC on the way.
+ for (size_t i = 0; i < kBlocksPerCacheLine; i++) {
+ // Copy and CRC the data for the CRC regions.
+ for (size_t j = 0; j < vec_regions; j++) {
+ // Cycle which regions get vector load/store and integer load/store, to
+ // engage prefetching logic around vector load/stores and save issue
+ // slots by using the integer registers.
+ size_t region = (j + i) % kRegions;
+
+ auto* vsrc =
+ reinterpret_cast<const V128*>(src_bytes + region_size * region);
+ auto* vdst = reinterpret_cast<V128*>(dst_bytes + region_size * region);
+
+ // Load and CRC data.
+ vec_data[j] = V128_LoadU(vsrc + i);
+ crcs[region] = crc32c_t{static_cast<uint32_t>(
+ CRC32_u64(static_cast<uint32_t>(crcs[region]),
+ static_cast<uint64_t>(V128_Extract64<0>(vec_data[j]))))};
+ crcs[region] = crc32c_t{static_cast<uint32_t>(
+ CRC32_u64(static_cast<uint32_t>(crcs[region]),
+ static_cast<uint64_t>(V128_Extract64<1>(vec_data[j]))))};
+
+ // Store the data.
+ V128_Store(vdst + i, vec_data[j]);
+ }
+
+ // Preload the partial CRCs for the CLMUL subregions.
+ for (size_t j = 0; j < int_regions; j++) {
+ // Cycle which regions get vector load/store and integer load/store, to
+ // engage prefetching logic around vector load/stores and save issue
+ // slots by using the integer registers.
+ size_t region = (j + vec_regions + i) % kRegions;
+
+ auto* usrc =
+ reinterpret_cast<const uint64_t*>(src_bytes + region_size * region);
+ auto* udst =
+ reinterpret_cast<uint64_t*>(dst_bytes + region_size * region);
+
+ for (size_t k = 0; k < kIntLoadsPerVec; k++) {
+ size_t data_index = j * kIntLoadsPerVec + k;
+
+ // Load and CRC the data.
+ int_data[data_index] = *(usrc + i * kIntLoadsPerVec + k);
+ crcs[region] = crc32c_t{static_cast<uint32_t>(CRC32_u64(
+ static_cast<uint32_t>(crcs[region]), int_data[data_index]))};
+
+ // Store the data.
+ *(udst + i * kIntLoadsPerVec + k) = int_data[data_index];
+ }
+ }
+ }
+
+ // Increment pointers
+ src_bytes += kBlockSize * kBlocksPerCacheLine;
+ dst_bytes += kBlockSize * kBlocksPerCacheLine;
+ copy_rounds -= kBlocksPerCacheLine;
+ }
+
+ // Copy and CRC the tails of each region.
+ LargeTailCopy<vec_regions, int_regions>(crcs, &dst_bytes, &src_bytes,
+ region_size, copy_rounds);
+
+ // Move the source and destination pointers to the end of the region
+ src_bytes += region_size * (kRegions - 1);
+ dst_bytes += region_size * (kRegions - 1);
+
+ // Copy and CRC the tail through the XMM registers.
+ std::size_t tail_blocks = tail_size / kBlockSize;
+ LargeTailCopy<0, 1>(&crcs[kRegions - 1], &dst_bytes, &src_bytes, 0,
+ tail_blocks);
+
+ // Final tail copy for under 16 bytes.
+ crcs[kRegions - 1] =
+ ShortCrcCopy(dst_bytes, src_bytes, tail_size - tail_blocks * kBlockSize,
+ crcs[kRegions - 1]);
+
+ if (kRegions == 1) {
+ // If there is only one region, finalize and return its CRC.
+ return crc32c_t{static_cast<uint32_t>(crcs[0]) ^ kCrcDataXor};
+ }
+
+ // Finalize the first CRCs: XOR the internal CRCs by the XOR mask to undo the
+ // XOR done before doing block copy + CRCs.
+ for (size_t i = 0; i + 1 < kRegions; i++) {
+ crcs[i] = crc32c_t{static_cast<uint32_t>(crcs[i]) ^ kCrcDataXor};
+ }
+
+ // Build a CRC of the first kRegions - 1 regions.
+ crc32c_t full_crc = crcs[0];
+ for (size_t i = 1; i + 1 < kRegions; i++) {
+ full_crc = ConcatCrc32c(full_crc, crcs[i], region_size);
+ }
+
+ // Finalize and concatenate the final CRC, then return.
+ crcs[kRegions - 1] =
+ crc32c_t{static_cast<uint32_t>(crcs[kRegions - 1]) ^ kCrcDataXor};
+ return ConcatCrc32c(full_crc, crcs[kRegions - 1], region_size + tail_size);
+}
+
+CrcMemcpy::ArchSpecificEngines CrcMemcpy::GetArchSpecificEngines() {
+#ifdef UNDEFINED_BEHAVIOR_SANITIZER
+ // UBSAN does not play nicely with unaligned loads (which we use a lot).
+ // Get the underlying architecture.
+ CpuType cpu_type = GetCpuType();
+ switch (cpu_type) {
+ case CpuType::kAmdRome:
+ case CpuType::kAmdNaples:
+ case CpuType::kAmdMilan:
+ case CpuType::kAmdGenoa:
+ case CpuType::kAmdRyzenV3000:
+ case CpuType::kIntelCascadelakeXeon:
+ case CpuType::kIntelSkylakeXeon:
+ case CpuType::kIntelSkylake:
+ case CpuType::kIntelBroadwell:
+ case CpuType::kIntelHaswell:
+ case CpuType::kIntelIvybridge:
+ return {
+ /*.temporal=*/new FallbackCrcMemcpyEngine(),
+ /*.non_temporal=*/new CrcNonTemporalMemcpyAVXEngine(),
+ };
+ // INTEL_SANDYBRIDGE performs better with SSE than AVX.
+ case CpuType::kIntelSandybridge:
+ return {
+ /*.temporal=*/new FallbackCrcMemcpyEngine(),
+ /*.non_temporal=*/new CrcNonTemporalMemcpyEngine(),
+ };
+ default:
+ return {/*.temporal=*/new FallbackCrcMemcpyEngine(),
+ /*.non_temporal=*/new FallbackCrcMemcpyEngine()};
+ }
+#else
+ // Get the underlying architecture.
+ CpuType cpu_type = GetCpuType();
+ switch (cpu_type) {
+ // On Zen 2, PEXTRQ uses 2 micro-ops, including one on the vector store port
+ // which data movement from the vector registers to the integer registers
+ // (where CRC32C happens) to crowd the same units as vector stores. As a
+ // result, using that path exclusively causes bottlenecking on this port.
+ // We can avoid this bottleneck by using the integer side of the CPU for
+ // most operations rather than the vector side. We keep a vector region to
+ // engage some of the prefetching logic in the cache hierarchy which seems
+ // to give vector instructions special treatment. These prefetch units see
+ // strided access to each region, and do the right thing.
+ case CpuType::kAmdRome:
+ case CpuType::kAmdNaples:
+ case CpuType::kAmdMilan:
+ case CpuType::kAmdGenoa:
+ case CpuType::kAmdRyzenV3000:
+ return {
+ /*.temporal=*/new AcceleratedCrcMemcpyEngine<1, 2>(),
+ /*.non_temporal=*/new CrcNonTemporalMemcpyAVXEngine(),
+ };
+ // PCLMULQDQ is slow and we don't have wide enough issue width to take
+ // advantage of it. For an unknown architecture, don't risk using CLMULs.
+ case CpuType::kIntelCascadelakeXeon:
+ case CpuType::kIntelSkylakeXeon:
+ case CpuType::kIntelSkylake:
+ case CpuType::kIntelBroadwell:
+ case CpuType::kIntelHaswell:
+ case CpuType::kIntelIvybridge:
+ return {
+ /*.temporal=*/new AcceleratedCrcMemcpyEngine<3, 0>(),
+ /*.non_temporal=*/new CrcNonTemporalMemcpyAVXEngine(),
+ };
+ // INTEL_SANDYBRIDGE performs better with SSE than AVX.
+ case CpuType::kIntelSandybridge:
+ return {
+ /*.temporal=*/new AcceleratedCrcMemcpyEngine<3, 0>(),
+ /*.non_temporal=*/new CrcNonTemporalMemcpyEngine(),
+ };
+ default:
+ return {/*.temporal=*/new FallbackCrcMemcpyEngine(),
+ /*.non_temporal=*/new FallbackCrcMemcpyEngine()};
+ }
+#endif // UNDEFINED_BEHAVIOR_SANITIZER
+}
+
+// For testing, allow the user to specify which engine they want.
+std::unique_ptr<CrcMemcpyEngine> CrcMemcpy::GetTestEngine(int vector,
+ int integer) {
+ if (vector == 3 && integer == 0) {
+ return std::make_unique<AcceleratedCrcMemcpyEngine<3, 0>>();
+ } else if (vector == 1 && integer == 2) {
+ return std::make_unique<AcceleratedCrcMemcpyEngine<1, 2>>();
+ } else if (vector == 1 && integer == 0) {
+ return std::make_unique<AcceleratedCrcMemcpyEngine<1, 0>>();
+ }
+ return nullptr;
+}
+
+} // namespace crc_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_INTERNAL_HAVE_X86_64_ACCELERATED_CRC_MEMCPY_ENGINE ||
+ // ABSL_INTERNAL_HAVE_ARM_ACCELERATED_CRC_MEMCPY_ENGINE
diff --git a/absl/crc/internal/crc_non_temporal_memcpy.cc b/absl/crc/internal/crc_non_temporal_memcpy.cc
new file mode 100644
index 0000000..adc867f
--- /dev/null
+++ b/absl/crc/internal/crc_non_temporal_memcpy.cc
@@ -0,0 +1,93 @@
+// Copyright 2022 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <cstdint>
+
+#include "absl/base/config.h"
+#include "absl/crc/crc32c.h"
+#include "absl/crc/internal/crc_memcpy.h"
+#include "absl/crc/internal/non_temporal_memcpy.h"
+#include "absl/strings/string_view.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace crc_internal {
+
+crc32c_t CrcNonTemporalMemcpyEngine::Compute(void* __restrict dst,
+ const void* __restrict src,
+ std::size_t length,
+ crc32c_t initial_crc) const {
+ constexpr size_t kBlockSize = 8192;
+ crc32c_t crc = initial_crc;
+
+ const char* src_bytes = reinterpret_cast<const char*>(src);
+ char* dst_bytes = reinterpret_cast<char*>(dst);
+
+ // Copy + CRC loop - run 8k chunks until we are out of full chunks.
+ std::size_t offset = 0;
+ for (; offset + kBlockSize < length; offset += kBlockSize) {
+ crc = absl::ExtendCrc32c(crc,
+ absl::string_view(src_bytes + offset, kBlockSize));
+ non_temporal_store_memcpy(dst_bytes + offset, src_bytes + offset,
+ kBlockSize);
+ }
+
+ // Save some work if length is 0.
+ if (offset < length) {
+ std::size_t final_copy_size = length - offset;
+ crc = ExtendCrc32c(crc,
+ absl::string_view(src_bytes + offset, final_copy_size));
+
+ non_temporal_store_memcpy(dst_bytes + offset, src_bytes + offset,
+ final_copy_size);
+ }
+
+ return crc;
+}
+
+crc32c_t CrcNonTemporalMemcpyAVXEngine::Compute(void* __restrict dst,
+ const void* __restrict src,
+ std::size_t length,
+ crc32c_t initial_crc) const {
+ constexpr size_t kBlockSize = 8192;
+ crc32c_t crc = initial_crc;
+
+ const char* src_bytes = reinterpret_cast<const char*>(src);
+ char* dst_bytes = reinterpret_cast<char*>(dst);
+
+ // Copy + CRC loop - run 8k chunks until we are out of full chunks.
+ std::size_t offset = 0;
+ for (; offset + kBlockSize < length; offset += kBlockSize) {
+ crc = ExtendCrc32c(crc, absl::string_view(src_bytes + offset, kBlockSize));
+
+ non_temporal_store_memcpy_avx(dst_bytes + offset, src_bytes + offset,
+ kBlockSize);
+ }
+
+ // Save some work if length is 0.
+ if (offset < length) {
+ std::size_t final_copy_size = length - offset;
+ crc = ExtendCrc32c(crc,
+ absl::string_view(src_bytes + offset, final_copy_size));
+
+ non_temporal_store_memcpy_avx(dst_bytes + offset, src_bytes + offset,
+ final_copy_size);
+ }
+
+ return crc;
+}
+
+} // namespace crc_internal
+ABSL_NAMESPACE_END
+} // namespace absl
diff --git a/absl/crc/internal/crc_x86_arm_combined.cc b/absl/crc/internal/crc_x86_arm_combined.cc
new file mode 100644
index 0000000..51eff4e
--- /dev/null
+++ b/absl/crc/internal/crc_x86_arm_combined.cc
@@ -0,0 +1,733 @@
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Hardware accelerated CRC32 computation on Intel and ARM architecture.
+
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+#include <vector>
+
+#include "absl/base/attributes.h"
+#include "absl/base/config.h"
+#include "absl/base/internal/endian.h"
+#include "absl/base/prefetch.h"
+#include "absl/crc/internal/cpu_detect.h"
+#include "absl/crc/internal/crc32_x86_arm_combined_simd.h"
+#include "absl/crc/internal/crc_internal.h"
+#include "absl/memory/memory.h"
+#include "absl/numeric/bits.h"
+
+#if defined(ABSL_CRC_INTERNAL_HAVE_ARM_SIMD) || \
+ defined(ABSL_CRC_INTERNAL_HAVE_X86_SIMD)
+#define ABSL_INTERNAL_CAN_USE_SIMD_CRC32C
+#endif
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace crc_internal {
+
+#if defined(ABSL_INTERNAL_CAN_USE_SIMD_CRC32C)
+
+// Implementation details not exported outside of file
+namespace {
+
+// Some machines have CRC acceleration hardware.
+// We can do a faster version of Extend() on such machines.
+class CRC32AcceleratedX86ARMCombined : public CRC32 {
+ public:
+ CRC32AcceleratedX86ARMCombined() {}
+ ~CRC32AcceleratedX86ARMCombined() override {}
+ void ExtendByZeroes(uint32_t* crc, size_t length) const override;
+ uint32_t ComputeZeroConstant(size_t length) const;
+
+ private:
+ CRC32AcceleratedX86ARMCombined(const CRC32AcceleratedX86ARMCombined&) =
+ delete;
+ CRC32AcceleratedX86ARMCombined& operator=(
+ const CRC32AcceleratedX86ARMCombined&) = delete;
+};
+
+// Constants for switching between algorithms.
+// Chosen by comparing speed at different powers of 2.
+constexpr size_t kSmallCutoff = 256;
+constexpr size_t kMediumCutoff = 2048;
+
+#define ABSL_INTERNAL_STEP1(crc) \
+ do { \
+ crc = CRC32_u8(static_cast<uint32_t>(crc), *p++); \
+ } while (0)
+#define ABSL_INTERNAL_STEP2(crc) \
+ do { \
+ crc = \
+ CRC32_u16(static_cast<uint32_t>(crc), absl::little_endian::Load16(p)); \
+ p += 2; \
+ } while (0)
+#define ABSL_INTERNAL_STEP4(crc) \
+ do { \
+ crc = \
+ CRC32_u32(static_cast<uint32_t>(crc), absl::little_endian::Load32(p)); \
+ p += 4; \
+ } while (0)
+#define ABSL_INTERNAL_STEP8(crc, data) \
+ do { \
+ crc = CRC32_u64(static_cast<uint32_t>(crc), \
+ absl::little_endian::Load64(data)); \
+ data += 8; \
+ } while (0)
+#define ABSL_INTERNAL_STEP8BY2(crc0, crc1, p0, p1) \
+ do { \
+ ABSL_INTERNAL_STEP8(crc0, p0); \
+ ABSL_INTERNAL_STEP8(crc1, p1); \
+ } while (0)
+#define ABSL_INTERNAL_STEP8BY3(crc0, crc1, crc2, p0, p1, p2) \
+ do { \
+ ABSL_INTERNAL_STEP8(crc0, p0); \
+ ABSL_INTERNAL_STEP8(crc1, p1); \
+ ABSL_INTERNAL_STEP8(crc2, p2); \
+ } while (0)
+
+namespace {
+
+uint32_t multiply(uint32_t a, uint32_t b) {
+ V128 shifts = V128_From2x64(0, 1);
+ V128 power = V128_From2x64(0, a);
+ V128 crc = V128_From2x64(0, b);
+ V128 res = V128_PMulLow(power, crc);
+
+ // Combine crc values
+ res = V128_ShiftLeft64(res, shifts);
+ return static_cast<uint32_t>(V128_Extract32<1>(res)) ^
+ CRC32_u32(0, static_cast<uint32_t>(V128_Low64(res)));
+}
+
+// Powers of crc32c polynomial, for faster ExtendByZeros.
+// Verified against folly:
+// folly/hash/detail/Crc32CombineDetail.cpp
+constexpr uint32_t kCRC32CPowers[] = {
+ 0x82f63b78, 0x6ea2d55c, 0x18b8ea18, 0x510ac59a, 0xb82be955, 0xb8fdb1e7,
+ 0x88e56f72, 0x74c360a4, 0xe4172b16, 0x0d65762a, 0x35d73a62, 0x28461564,
+ 0xbf455269, 0xe2ea32dc, 0xfe7740e6, 0xf946610b, 0x3c204f8f, 0x538586e3,
+ 0x59726915, 0x734d5309, 0xbc1ac763, 0x7d0722cc, 0xd289cabe, 0xe94ca9bc,
+ 0x05b74f3f, 0xa51e1f42, 0x40000000, 0x20000000, 0x08000000, 0x00800000,
+ 0x00008000, 0x82f63b78, 0x6ea2d55c, 0x18b8ea18, 0x510ac59a, 0xb82be955,
+ 0xb8fdb1e7, 0x88e56f72, 0x74c360a4, 0xe4172b16, 0x0d65762a, 0x35d73a62,
+ 0x28461564, 0xbf455269, 0xe2ea32dc, 0xfe7740e6, 0xf946610b, 0x3c204f8f,
+ 0x538586e3, 0x59726915, 0x734d5309, 0xbc1ac763, 0x7d0722cc, 0xd289cabe,
+ 0xe94ca9bc, 0x05b74f3f, 0xa51e1f42, 0x40000000, 0x20000000, 0x08000000,
+ 0x00800000, 0x00008000,
+};
+
+} // namespace
+
+// Compute a magic constant, so that multiplying by it is the same as
+// extending crc by length zeros.
+uint32_t CRC32AcceleratedX86ARMCombined::ComputeZeroConstant(
+ size_t length) const {
+ // Lowest 2 bits are handled separately in ExtendByZeroes
+ length >>= 2;
+
+ int index = absl::countr_zero(length);
+ uint32_t prev = kCRC32CPowers[index];
+ length &= length - 1;
+
+ while (length) {
+ // For each bit of length, extend by 2**n zeros.
+ index = absl::countr_zero(length);
+ prev = multiply(prev, kCRC32CPowers[index]);
+ length &= length - 1;
+ }
+ return prev;
+}
+
+void CRC32AcceleratedX86ARMCombined::ExtendByZeroes(uint32_t* crc,
+ size_t length) const {
+ uint32_t val = *crc;
+ // Don't bother with multiplication for small length.
+ switch (length & 3) {
+ case 0:
+ break;
+ case 1:
+ val = CRC32_u8(val, 0);
+ break;
+ case 2:
+ val = CRC32_u16(val, 0);
+ break;
+ case 3:
+ val = CRC32_u8(val, 0);
+ val = CRC32_u16(val, 0);
+ break;
+ }
+ if (length > 3) {
+ val = multiply(val, ComputeZeroConstant(length));
+ }
+ *crc = val;
+}
+
+// Taken from Intel paper "Fast CRC Computation for iSCSI Polynomial Using CRC32
+// Instruction"
+// https://www.intel.com/content/dam/www/public/us/en/documents/white-papers/crc-iscsi-polynomial-crc32-instruction-paper.pdf
+// We only need every 4th value, because we unroll loop by 4.
+constexpr uint64_t kClmulConstants[] = {
+ 0x09e4addf8, 0x0ba4fc28e, 0x00d3b6092, 0x09e4addf8, 0x0ab7aff2a,
+ 0x102f9b8a2, 0x0b9e02b86, 0x00d3b6092, 0x1bf2e8b8a, 0x18266e456,
+ 0x0d270f1a2, 0x0ab7aff2a, 0x11eef4f8e, 0x083348832, 0x0dd7e3b0c,
+ 0x0b9e02b86, 0x0271d9844, 0x1b331e26a, 0x06b749fb2, 0x1bf2e8b8a,
+ 0x0e6fc4e6a, 0x0ce7f39f4, 0x0d7a4825c, 0x0d270f1a2, 0x026f6a60a,
+ 0x12ed0daac, 0x068bce87a, 0x11eef4f8e, 0x1329d9f7e, 0x0b3e32c28,
+ 0x0170076fa, 0x0dd7e3b0c, 0x1fae1cc66, 0x010746f3c, 0x086d8e4d2,
+ 0x0271d9844, 0x0b3af077a, 0x093a5f730, 0x1d88abd4a, 0x06b749fb2,
+ 0x0c9c8b782, 0x0cec3662e, 0x1ddffc5d4, 0x0e6fc4e6a, 0x168763fa6,
+ 0x0b0cd4768, 0x19b1afbc4, 0x0d7a4825c, 0x123888b7a, 0x00167d312,
+ 0x133d7a042, 0x026f6a60a, 0x000bcf5f6, 0x19d34af3a, 0x1af900c24,
+ 0x068bce87a, 0x06d390dec, 0x16cba8aca, 0x1f16a3418, 0x1329d9f7e,
+ 0x19fb2a8b0, 0x02178513a, 0x1a0f717c4, 0x0170076fa,
+};
+
+enum class CutoffStrategy {
+ // Use 3 CRC streams to fold into 1.
+ Fold3,
+ // Unroll CRC instructions for 64 bytes.
+ Unroll64CRC,
+};
+
+// Base class for CRC32AcceleratedX86ARMCombinedMultipleStreams containing the
+// methods and data that don't need the template arguments.
+class CRC32AcceleratedX86ARMCombinedMultipleStreamsBase
+ : public CRC32AcceleratedX86ARMCombined {
+ protected:
+ // Update partialCRC with crc of 64 byte block. Calling FinalizePclmulStream
+ // would produce a single crc checksum, but it is expensive. PCLMULQDQ has a
+ // high latency, so we run 4 128-bit partial checksums that can be reduced to
+ // a single value by FinalizePclmulStream later. Computing crc for arbitrary
+ // polynomialas with PCLMULQDQ is described in Intel paper "Fast CRC
+ // Computation for Generic Polynomials Using PCLMULQDQ Instruction"
+ // https://www.intel.com/content/dam/www/public/us/en/documents/white-papers/fast-crc-computation-generic-polynomials-pclmulqdq-paper.pdf
+ // We are applying it to CRC32C polynomial.
+ ABSL_ATTRIBUTE_ALWAYS_INLINE void Process64BytesPclmul(
+ const uint8_t* p, V128* partialCRC) const {
+ V128 loopMultiplicands = V128_Load(reinterpret_cast<const V128*>(k1k2));
+
+ V128 partialCRC1 = partialCRC[0];
+ V128 partialCRC2 = partialCRC[1];
+ V128 partialCRC3 = partialCRC[2];
+ V128 partialCRC4 = partialCRC[3];
+
+ V128 tmp1 = V128_PMulHi(partialCRC1, loopMultiplicands);
+ V128 tmp2 = V128_PMulHi(partialCRC2, loopMultiplicands);
+ V128 tmp3 = V128_PMulHi(partialCRC3, loopMultiplicands);
+ V128 tmp4 = V128_PMulHi(partialCRC4, loopMultiplicands);
+ V128 data1 = V128_LoadU(reinterpret_cast<const V128*>(p + 16 * 0));
+ V128 data2 = V128_LoadU(reinterpret_cast<const V128*>(p + 16 * 1));
+ V128 data3 = V128_LoadU(reinterpret_cast<const V128*>(p + 16 * 2));
+ V128 data4 = V128_LoadU(reinterpret_cast<const V128*>(p + 16 * 3));
+ partialCRC1 = V128_PMulLow(partialCRC1, loopMultiplicands);
+ partialCRC2 = V128_PMulLow(partialCRC2, loopMultiplicands);
+ partialCRC3 = V128_PMulLow(partialCRC3, loopMultiplicands);
+ partialCRC4 = V128_PMulLow(partialCRC4, loopMultiplicands);
+ partialCRC1 = V128_Xor(tmp1, partialCRC1);
+ partialCRC2 = V128_Xor(tmp2, partialCRC2);
+ partialCRC3 = V128_Xor(tmp3, partialCRC3);
+ partialCRC4 = V128_Xor(tmp4, partialCRC4);
+ partialCRC1 = V128_Xor(partialCRC1, data1);
+ partialCRC2 = V128_Xor(partialCRC2, data2);
+ partialCRC3 = V128_Xor(partialCRC3, data3);
+ partialCRC4 = V128_Xor(partialCRC4, data4);
+ partialCRC[0] = partialCRC1;
+ partialCRC[1] = partialCRC2;
+ partialCRC[2] = partialCRC3;
+ partialCRC[3] = partialCRC4;
+ }
+
+ // Reduce partialCRC produced by Process64BytesPclmul into a single value,
+ // that represents crc checksum of all the processed bytes.
+ ABSL_ATTRIBUTE_ALWAYS_INLINE uint64_t
+ FinalizePclmulStream(V128* partialCRC) const {
+ V128 partialCRC1 = partialCRC[0];
+ V128 partialCRC2 = partialCRC[1];
+ V128 partialCRC3 = partialCRC[2];
+ V128 partialCRC4 = partialCRC[3];
+
+ // Combine 4 vectors of partial crc into a single vector.
+ V128 reductionMultiplicands =
+ V128_Load(reinterpret_cast<const V128*>(k5k6));
+
+ V128 low = V128_PMulLow(reductionMultiplicands, partialCRC1);
+ V128 high = V128_PMulHi(reductionMultiplicands, partialCRC1);
+
+ partialCRC1 = V128_Xor(low, high);
+ partialCRC1 = V128_Xor(partialCRC1, partialCRC2);
+
+ low = V128_PMulLow(reductionMultiplicands, partialCRC3);
+ high = V128_PMulHi(reductionMultiplicands, partialCRC3);
+
+ partialCRC3 = V128_Xor(low, high);
+ partialCRC3 = V128_Xor(partialCRC3, partialCRC4);
+
+ reductionMultiplicands = V128_Load(reinterpret_cast<const V128*>(k3k4));
+
+ low = V128_PMulLow(reductionMultiplicands, partialCRC1);
+ high = V128_PMulHi(reductionMultiplicands, partialCRC1);
+ V128 fullCRC = V128_Xor(low, high);
+ fullCRC = V128_Xor(fullCRC, partialCRC3);
+
+ // Reduce fullCRC into scalar value.
+ reductionMultiplicands = V128_Load(reinterpret_cast<const V128*>(k5k6));
+
+ V128 mask = V128_Load(reinterpret_cast<const V128*>(kMask));
+
+ V128 tmp = V128_PMul01(reductionMultiplicands, fullCRC);
+ fullCRC = V128_ShiftRight<8>(fullCRC);
+ fullCRC = V128_Xor(fullCRC, tmp);
+
+ reductionMultiplicands = V128_Load(reinterpret_cast<const V128*>(k7k0));
+
+ tmp = V128_ShiftRight<4>(fullCRC);
+ fullCRC = V128_And(fullCRC, mask);
+ fullCRC = V128_PMulLow(reductionMultiplicands, fullCRC);
+ fullCRC = V128_Xor(tmp, fullCRC);
+
+ reductionMultiplicands = V128_Load(reinterpret_cast<const V128*>(kPoly));
+
+ tmp = V128_And(fullCRC, mask);
+ tmp = V128_PMul01(reductionMultiplicands, tmp);
+ tmp = V128_And(tmp, mask);
+ tmp = V128_PMulLow(reductionMultiplicands, tmp);
+
+ fullCRC = V128_Xor(tmp, fullCRC);
+
+ return static_cast<uint64_t>(V128_Extract32<1>(fullCRC));
+ }
+
+ // Update crc with 64 bytes of data from p.
+ ABSL_ATTRIBUTE_ALWAYS_INLINE uint64_t Process64BytesCRC(const uint8_t* p,
+ uint64_t crc) const {
+ for (int i = 0; i < 8; i++) {
+ crc =
+ CRC32_u64(static_cast<uint32_t>(crc), absl::little_endian::Load64(p));
+ p += 8;
+ }
+ return crc;
+ }
+
+ // Generated by crc32c_x86_test --crc32c_generate_constants=true
+ // and verified against constants in linux kernel for S390:
+ // https://github.com/torvalds/linux/blob/master/arch/s390/crypto/crc32le-vx.S
+ alignas(16) static constexpr uint64_t k1k2[2] = {0x0740eef02, 0x09e4addf8};
+ alignas(16) static constexpr uint64_t k3k4[2] = {0x1384aa63a, 0x0ba4fc28e};
+ alignas(16) static constexpr uint64_t k5k6[2] = {0x0f20c0dfe, 0x14cd00bd6};
+ alignas(16) static constexpr uint64_t k7k0[2] = {0x0dd45aab8, 0x000000000};
+ alignas(16) static constexpr uint64_t kPoly[2] = {0x105ec76f0, 0x0dea713f1};
+ alignas(16) static constexpr uint32_t kMask[4] = {~0u, 0u, ~0u, 0u};
+
+ // Medium runs of bytes are broken into groups of kGroupsSmall blocks of same
+ // size. Each group is CRCed in parallel then combined at the end of the
+ // block.
+ static constexpr size_t kGroupsSmall = 3;
+ // For large runs we use up to kMaxStreams blocks computed with CRC
+ // instruction, and up to kMaxStreams blocks computed with PCLMULQDQ, which
+ // are combined in the end.
+ static constexpr size_t kMaxStreams = 3;
+};
+
+#ifdef ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL
+alignas(16) constexpr uint64_t
+ CRC32AcceleratedX86ARMCombinedMultipleStreamsBase::k1k2[2];
+alignas(16) constexpr uint64_t
+ CRC32AcceleratedX86ARMCombinedMultipleStreamsBase::k3k4[2];
+alignas(16) constexpr uint64_t
+ CRC32AcceleratedX86ARMCombinedMultipleStreamsBase::k5k6[2];
+alignas(16) constexpr uint64_t
+ CRC32AcceleratedX86ARMCombinedMultipleStreamsBase::k7k0[2];
+alignas(16) constexpr uint64_t
+ CRC32AcceleratedX86ARMCombinedMultipleStreamsBase::kPoly[2];
+alignas(16) constexpr uint32_t
+ CRC32AcceleratedX86ARMCombinedMultipleStreamsBase::kMask[4];
+constexpr size_t
+ CRC32AcceleratedX86ARMCombinedMultipleStreamsBase::kGroupsSmall;
+constexpr size_t CRC32AcceleratedX86ARMCombinedMultipleStreamsBase::kMaxStreams;
+#endif // ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL
+
+template <size_t num_crc_streams, size_t num_pclmul_streams,
+ CutoffStrategy strategy>
+class CRC32AcceleratedX86ARMCombinedMultipleStreams
+ : public CRC32AcceleratedX86ARMCombinedMultipleStreamsBase {
+ ABSL_ATTRIBUTE_HOT
+ void Extend(uint32_t* crc, const void* bytes, size_t length) const override {
+ static_assert(num_crc_streams >= 1 && num_crc_streams <= kMaxStreams,
+ "Invalid number of crc streams");
+ static_assert(num_pclmul_streams >= 0 && num_pclmul_streams <= kMaxStreams,
+ "Invalid number of pclmul streams");
+ const uint8_t* p = static_cast<const uint8_t*>(bytes);
+ const uint8_t* e = p + length;
+ uint32_t l = *crc;
+ uint64_t l64;
+
+ // We have dedicated instruction for 1,2,4 and 8 bytes.
+ if (length & 8) {
+ ABSL_INTERNAL_STEP8(l, p);
+ length &= ~size_t{8};
+ }
+ if (length & 4) {
+ ABSL_INTERNAL_STEP4(l);
+ length &= ~size_t{4};
+ }
+ if (length & 2) {
+ ABSL_INTERNAL_STEP2(l);
+ length &= ~size_t{2};
+ }
+ if (length & 1) {
+ ABSL_INTERNAL_STEP1(l);
+ length &= ~size_t{1};
+ }
+ if (length == 0) {
+ *crc = l;
+ return;
+ }
+ // length is now multiple of 16.
+
+ // For small blocks just run simple loop, because cost of combining multiple
+ // streams is significant.
+ if (strategy != CutoffStrategy::Unroll64CRC) {
+ if (length < kSmallCutoff) {
+ while (length >= 16) {
+ ABSL_INTERNAL_STEP8(l, p);
+ ABSL_INTERNAL_STEP8(l, p);
+ length -= 16;
+ }
+ *crc = l;
+ return;
+ }
+ }
+
+ // For medium blocks we run 3 crc streams and combine them as described in
+ // Intel paper above. Running 4th stream doesn't help, because crc
+ // instruction has latency 3 and throughput 1.
+ if (length < kMediumCutoff) {
+ l64 = l;
+ if (strategy == CutoffStrategy::Fold3) {
+ uint64_t l641 = 0;
+ uint64_t l642 = 0;
+ const size_t blockSize = 32;
+ size_t bs = static_cast<size_t>(e - p) / kGroupsSmall / blockSize;
+ const uint8_t* p1 = p + bs * blockSize;
+ const uint8_t* p2 = p1 + bs * blockSize;
+
+ for (size_t i = 0; i + 1 < bs; ++i) {
+ ABSL_INTERNAL_STEP8BY3(l64, l641, l642, p, p1, p2);
+ ABSL_INTERNAL_STEP8BY3(l64, l641, l642, p, p1, p2);
+ ABSL_INTERNAL_STEP8BY3(l64, l641, l642, p, p1, p2);
+ ABSL_INTERNAL_STEP8BY3(l64, l641, l642, p, p1, p2);
+ PrefetchToLocalCache(
+ reinterpret_cast<const char*>(p + kPrefetchHorizonMedium));
+ PrefetchToLocalCache(
+ reinterpret_cast<const char*>(p1 + kPrefetchHorizonMedium));
+ PrefetchToLocalCache(
+ reinterpret_cast<const char*>(p2 + kPrefetchHorizonMedium));
+ }
+ // Don't run crc on last 8 bytes.
+ ABSL_INTERNAL_STEP8BY3(l64, l641, l642, p, p1, p2);
+ ABSL_INTERNAL_STEP8BY3(l64, l641, l642, p, p1, p2);
+ ABSL_INTERNAL_STEP8BY3(l64, l641, l642, p, p1, p2);
+ ABSL_INTERNAL_STEP8BY2(l64, l641, p, p1);
+
+ V128 magic = *(reinterpret_cast<const V128*>(kClmulConstants) + bs - 1);
+
+ V128 tmp = V128_From2x64(0, l64);
+
+ V128 res1 = V128_PMulLow(tmp, magic);
+
+ tmp = V128_From2x64(0, l641);
+
+ V128 res2 = V128_PMul10(tmp, magic);
+ V128 x = V128_Xor(res1, res2);
+ l64 = static_cast<uint64_t>(V128_Low64(x)) ^
+ absl::little_endian::Load64(p2);
+ l64 = CRC32_u64(static_cast<uint32_t>(l642), l64);
+
+ p = p2 + 8;
+ } else if (strategy == CutoffStrategy::Unroll64CRC) {
+ while ((e - p) >= 64) {
+ l64 = Process64BytesCRC(p, l64);
+ p += 64;
+ }
+ }
+ } else {
+ // There is a lot of data, we can ignore combine costs and run all
+ // requested streams (num_crc_streams + num_pclmul_streams),
+ // using prefetch. CRC and PCLMULQDQ use different cpu execution units,
+ // so on some cpus it makes sense to execute both of them for different
+ // streams.
+
+ // Point x at first 8-byte aligned byte in string.
+ const uint8_t* x = RoundUp<8>(p);
+ // Process bytes until p is 8-byte aligned, if that isn't past the end.
+ while (p != x) {
+ ABSL_INTERNAL_STEP1(l);
+ }
+
+ size_t bs = static_cast<size_t>(e - p) /
+ (num_crc_streams + num_pclmul_streams) / 64;
+ const uint8_t* crc_streams[kMaxStreams];
+ const uint8_t* pclmul_streams[kMaxStreams];
+ // We are guaranteed to have at least one crc stream.
+ crc_streams[0] = p;
+ for (size_t i = 1; i < num_crc_streams; i++) {
+ crc_streams[i] = crc_streams[i - 1] + bs * 64;
+ }
+ pclmul_streams[0] = crc_streams[num_crc_streams - 1] + bs * 64;
+ for (size_t i = 1; i < num_pclmul_streams; i++) {
+ pclmul_streams[i] = pclmul_streams[i - 1] + bs * 64;
+ }
+
+ // Per stream crc sums.
+ uint64_t l64_crc[kMaxStreams] = {l};
+ uint64_t l64_pclmul[kMaxStreams] = {0};
+
+ // Peel first iteration, because PCLMULQDQ stream, needs setup.
+ for (size_t i = 0; i < num_crc_streams; i++) {
+ l64_crc[i] = Process64BytesCRC(crc_streams[i], l64_crc[i]);
+ crc_streams[i] += 16 * 4;
+ }
+
+ V128 partialCRC[kMaxStreams][4];
+ for (size_t i = 0; i < num_pclmul_streams; i++) {
+ partialCRC[i][0] = V128_LoadU(
+ reinterpret_cast<const V128*>(pclmul_streams[i] + 16 * 0));
+ partialCRC[i][1] = V128_LoadU(
+ reinterpret_cast<const V128*>(pclmul_streams[i] + 16 * 1));
+ partialCRC[i][2] = V128_LoadU(
+ reinterpret_cast<const V128*>(pclmul_streams[i] + 16 * 2));
+ partialCRC[i][3] = V128_LoadU(
+ reinterpret_cast<const V128*>(pclmul_streams[i] + 16 * 3));
+ pclmul_streams[i] += 16 * 4;
+ }
+
+ for (size_t i = 1; i < bs; i++) {
+ // Prefetch data for next iterations.
+ for (size_t j = 0; j < num_crc_streams; j++) {
+ PrefetchToLocalCache(
+ reinterpret_cast<const char*>(crc_streams[j] + kPrefetchHorizon));
+ }
+ for (size_t j = 0; j < num_pclmul_streams; j++) {
+ PrefetchToLocalCache(reinterpret_cast<const char*>(pclmul_streams[j] +
+ kPrefetchHorizon));
+ }
+
+ // We process each stream in 64 byte blocks. This can be written as
+ // for (int i = 0; i < num_pclmul_streams; i++) {
+ // Process64BytesPclmul(pclmul_streams[i], partialCRC[i]);
+ // pclmul_streams[i] += 16 * 4;
+ // }
+ // for (int i = 0; i < num_crc_streams; i++) {
+ // l64_crc[i] = Process64BytesCRC(crc_streams[i], l64_crc[i]);
+ // crc_streams[i] += 16*4;
+ // }
+ // But unrolling and interleaving PCLMULQDQ and CRC blocks manually
+ // gives ~2% performance boost.
+ l64_crc[0] = Process64BytesCRC(crc_streams[0], l64_crc[0]);
+ crc_streams[0] += 16 * 4;
+ if (num_pclmul_streams > 0) {
+ Process64BytesPclmul(pclmul_streams[0], partialCRC[0]);
+ pclmul_streams[0] += 16 * 4;
+ }
+ if (num_crc_streams > 1) {
+ l64_crc[1] = Process64BytesCRC(crc_streams[1], l64_crc[1]);
+ crc_streams[1] += 16 * 4;
+ }
+ if (num_pclmul_streams > 1) {
+ Process64BytesPclmul(pclmul_streams[1], partialCRC[1]);
+ pclmul_streams[1] += 16 * 4;
+ }
+ if (num_crc_streams > 2) {
+ l64_crc[2] = Process64BytesCRC(crc_streams[2], l64_crc[2]);
+ crc_streams[2] += 16 * 4;
+ }
+ if (num_pclmul_streams > 2) {
+ Process64BytesPclmul(pclmul_streams[2], partialCRC[2]);
+ pclmul_streams[2] += 16 * 4;
+ }
+ }
+
+ // PCLMULQDQ based streams require special final step;
+ // CRC based don't.
+ for (size_t i = 0; i < num_pclmul_streams; i++) {
+ l64_pclmul[i] = FinalizePclmulStream(partialCRC[i]);
+ }
+
+ // Combine all streams into single result.
+ uint32_t magic = ComputeZeroConstant(bs * 64);
+ l64 = l64_crc[0];
+ for (size_t i = 1; i < num_crc_streams; i++) {
+ l64 = multiply(static_cast<uint32_t>(l64), magic);
+ l64 ^= l64_crc[i];
+ }
+ for (size_t i = 0; i < num_pclmul_streams; i++) {
+ l64 = multiply(static_cast<uint32_t>(l64), magic);
+ l64 ^= l64_pclmul[i];
+ }
+
+ // Update p.
+ if (num_pclmul_streams > 0) {
+ p = pclmul_streams[num_pclmul_streams - 1];
+ } else {
+ p = crc_streams[num_crc_streams - 1];
+ }
+ }
+ l = static_cast<uint32_t>(l64);
+
+ while ((e - p) >= 16) {
+ ABSL_INTERNAL_STEP8(l, p);
+ ABSL_INTERNAL_STEP8(l, p);
+ }
+ // Process the last few bytes
+ while (p != e) {
+ ABSL_INTERNAL_STEP1(l);
+ }
+
+#undef ABSL_INTERNAL_STEP8BY3
+#undef ABSL_INTERNAL_STEP8BY2
+#undef ABSL_INTERNAL_STEP8
+#undef ABSL_INTERNAL_STEP4
+#undef ABSL_INTERNAL_STEP2
+#undef ABSL_INTERNAL_STEP1
+
+ *crc = l;
+ }
+};
+
+} // namespace
+
+// Intel processors with SSE4.2 have an instruction for one particular
+// 32-bit CRC polynomial: crc32c
+CRCImpl* TryNewCRC32AcceleratedX86ARMCombined() {
+ CpuType type = GetCpuType();
+ switch (type) {
+ case CpuType::kIntelHaswell:
+ case CpuType::kAmdRome:
+ case CpuType::kAmdNaples:
+ case CpuType::kAmdMilan:
+ return new CRC32AcceleratedX86ARMCombinedMultipleStreams<
+ 3, 1, CutoffStrategy::Fold3>();
+ // PCLMULQDQ is fast, use combined PCLMULQDQ + CRC implementation.
+ case CpuType::kIntelCascadelakeXeon:
+ case CpuType::kIntelSkylakeXeon:
+ case CpuType::kIntelBroadwell:
+ case CpuType::kIntelSkylake:
+ return new CRC32AcceleratedX86ARMCombinedMultipleStreams<
+ 3, 2, CutoffStrategy::Fold3>();
+ // PCLMULQDQ is slow, don't use it.
+ case CpuType::kIntelIvybridge:
+ case CpuType::kIntelSandybridge:
+ case CpuType::kIntelWestmere:
+ return new CRC32AcceleratedX86ARMCombinedMultipleStreams<
+ 3, 0, CutoffStrategy::Fold3>();
+ case CpuType::kArmNeoverseN1:
+ case CpuType::kArmNeoverseN2:
+ case CpuType::kArmNeoverseV1:
+ return new CRC32AcceleratedX86ARMCombinedMultipleStreams<
+ 1, 1, CutoffStrategy::Unroll64CRC>();
+ case CpuType::kAmpereSiryn:
+ return new CRC32AcceleratedX86ARMCombinedMultipleStreams<
+ 3, 2, CutoffStrategy::Fold3>();
+ case CpuType::kArmNeoverseV2:
+ return new CRC32AcceleratedX86ARMCombinedMultipleStreams<
+ 1, 2, CutoffStrategy::Unroll64CRC>();
+#if defined(__aarch64__)
+ default:
+ // Not all ARM processors support the needed instructions, so check here
+ // before trying to use an accelerated implementation.
+ if (SupportsArmCRC32PMULL()) {
+ return new CRC32AcceleratedX86ARMCombinedMultipleStreams<
+ 1, 1, CutoffStrategy::Unroll64CRC>();
+ } else {
+ return nullptr;
+ }
+#else
+ default:
+ // Something else, play it safe and assume slow PCLMULQDQ.
+ return new CRC32AcceleratedX86ARMCombinedMultipleStreams<
+ 3, 0, CutoffStrategy::Fold3>();
+#endif
+ }
+}
+
+std::vector<std::unique_ptr<CRCImpl>> NewCRC32AcceleratedX86ARMCombinedAll() {
+ auto ret = std::vector<std::unique_ptr<CRCImpl>>();
+ ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams<
+ 1, 0, CutoffStrategy::Fold3>>());
+ ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams<
+ 1, 1, CutoffStrategy::Fold3>>());
+ ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams<
+ 1, 2, CutoffStrategy::Fold3>>());
+ ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams<
+ 1, 3, CutoffStrategy::Fold3>>());
+ ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams<
+ 2, 0, CutoffStrategy::Fold3>>());
+ ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams<
+ 2, 1, CutoffStrategy::Fold3>>());
+ ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams<
+ 2, 2, CutoffStrategy::Fold3>>());
+ ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams<
+ 2, 3, CutoffStrategy::Fold3>>());
+ ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams<
+ 3, 0, CutoffStrategy::Fold3>>());
+ ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams<
+ 3, 1, CutoffStrategy::Fold3>>());
+ ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams<
+ 3, 2, CutoffStrategy::Fold3>>());
+ ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams<
+ 3, 3, CutoffStrategy::Fold3>>());
+ ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams<
+ 1, 0, CutoffStrategy::Unroll64CRC>>());
+ ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams<
+ 1, 1, CutoffStrategy::Unroll64CRC>>());
+ ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams<
+ 1, 2, CutoffStrategy::Unroll64CRC>>());
+ ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams<
+ 1, 3, CutoffStrategy::Unroll64CRC>>());
+ ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams<
+ 2, 0, CutoffStrategy::Unroll64CRC>>());
+ ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams<
+ 2, 1, CutoffStrategy::Unroll64CRC>>());
+ ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams<
+ 2, 2, CutoffStrategy::Unroll64CRC>>());
+ ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams<
+ 2, 3, CutoffStrategy::Unroll64CRC>>());
+ ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams<
+ 3, 0, CutoffStrategy::Unroll64CRC>>());
+ ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams<
+ 3, 1, CutoffStrategy::Unroll64CRC>>());
+ ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams<
+ 3, 2, CutoffStrategy::Unroll64CRC>>());
+ ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams<
+ 3, 3, CutoffStrategy::Unroll64CRC>>());
+
+ return ret;
+}
+
+#else // !ABSL_INTERNAL_CAN_USE_SIMD_CRC32C
+
+std::vector<std::unique_ptr<CRCImpl>> NewCRC32AcceleratedX86ARMCombinedAll() {
+ return std::vector<std::unique_ptr<CRCImpl>>();
+}
+
+// no hardware acceleration available
+CRCImpl* TryNewCRC32AcceleratedX86ARMCombined() { return nullptr; }
+
+#endif
+
+} // namespace crc_internal
+ABSL_NAMESPACE_END
+} // namespace absl
diff --git a/absl/crc/internal/non_temporal_arm_intrinsics.h b/absl/crc/internal/non_temporal_arm_intrinsics.h
new file mode 100644
index 0000000..9e5ccfc
--- /dev/null
+++ b/absl/crc/internal/non_temporal_arm_intrinsics.h
@@ -0,0 +1,79 @@
+// Copyright 2022 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef ABSL_CRC_INTERNAL_NON_TEMPORAL_ARM_INTRINSICS_H_
+#define ABSL_CRC_INTERNAL_NON_TEMPORAL_ARM_INTRINSICS_H_
+
+#include "absl/base/config.h"
+
+#ifdef __aarch64__
+#include <arm_neon.h>
+
+typedef int64x2_t __m128i; /* 128-bit vector containing integers */
+#define vreinterpretq_m128i_s32(x) vreinterpretq_s64_s32(x)
+#define vreinterpretq_s64_m128i(x) (x)
+
+// Guarantees that every preceding store is globally visible before any
+// subsequent store.
+// https://msdn.microsoft.com/en-us/library/5h2w73d1%28v=vs.90%29.aspx
+static inline __attribute__((always_inline)) void _mm_sfence(void) {
+ __sync_synchronize();
+}
+
+// Load 128-bits of integer data from unaligned memory into dst. This intrinsic
+// may perform better than _mm_loadu_si128 when the data crosses a cache line
+// boundary.
+//
+// dst[127:0] := MEM[mem_addr+127:mem_addr]
+//
+// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_lddqu_si128
+#define _mm_lddqu_si128 _mm_loadu_si128
+
+// Loads 128-bit value. :
+// https://msdn.microsoft.com/zh-cn/library/f4k12ae8(v=vs.90).aspx
+static inline __attribute__((always_inline)) __m128i _mm_loadu_si128(
+ const __m128i *p) {
+ return vreinterpretq_m128i_s32(vld1q_s32((const int32_t *)p));
+}
+
+// Stores the data in a to the address p without polluting the caches. If the
+// cache line containing address p is already in the cache, the cache will be
+// updated.
+// https://msdn.microsoft.com/en-us/library/ba08y07y%28v=vs.90%29.aspx
+static inline __attribute__((always_inline)) void _mm_stream_si128(__m128i *p,
+ __m128i a) {
+#if ABSL_HAVE_BUILTIN(__builtin_nontemporal_store)
+ __builtin_nontemporal_store(a, p);
+#else
+ vst1q_s64((int64_t *)p, vreinterpretq_s64_m128i(a));
+#endif
+}
+
+// Sets the 16 signed 8-bit integer values.
+// https://msdn.microsoft.com/en-us/library/x0cx8zd3(v=vs.90).aspx
+static inline __attribute__((always_inline)) __m128i _mm_set_epi8(
+ signed char b15, signed char b14, signed char b13, signed char b12,
+ signed char b11, signed char b10, signed char b9, signed char b8,
+ signed char b7, signed char b6, signed char b5, signed char b4,
+ signed char b3, signed char b2, signed char b1, signed char b0) {
+ int8_t __attribute__((aligned(16)))
+ data[16] = {(int8_t)b0, (int8_t)b1, (int8_t)b2, (int8_t)b3,
+ (int8_t)b4, (int8_t)b5, (int8_t)b6, (int8_t)b7,
+ (int8_t)b8, (int8_t)b9, (int8_t)b10, (int8_t)b11,
+ (int8_t)b12, (int8_t)b13, (int8_t)b14, (int8_t)b15};
+ return (__m128i)vld1q_s8(data);
+}
+#endif // __aarch64__
+
+#endif // ABSL_CRC_INTERNAL_NON_TEMPORAL_ARM_INTRINSICS_H_
diff --git a/absl/crc/internal/non_temporal_memcpy.h b/absl/crc/internal/non_temporal_memcpy.h
new file mode 100644
index 0000000..b3d94ba
--- /dev/null
+++ b/absl/crc/internal/non_temporal_memcpy.h
@@ -0,0 +1,180 @@
+// Copyright 2022 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef ABSL_CRC_INTERNAL_NON_TEMPORAL_MEMCPY_H_
+#define ABSL_CRC_INTERNAL_NON_TEMPORAL_MEMCPY_H_
+
+#ifdef _MSC_VER
+#include <intrin.h>
+#endif
+
+#ifdef __SSE__
+#include <xmmintrin.h>
+#endif
+
+#ifdef __SSE2__
+#include <emmintrin.h>
+#endif
+
+#ifdef __SSE3__
+#include <pmmintrin.h>
+#endif
+
+#ifdef __AVX__
+#include <immintrin.h>
+#endif
+
+#ifdef __aarch64__
+#include "absl/crc/internal/non_temporal_arm_intrinsics.h"
+#endif
+
+#include <algorithm>
+#include <cassert>
+#include <cstdint>
+#include <cstring>
+
+#include "absl/base/config.h"
+#include "absl/base/optimization.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace crc_internal {
+
+// This non-temporal memcpy does regular load and non-temporal store memory
+// copy. It is compatible to both 16-byte aligned and unaligned addresses. If
+// data at the destination is not immediately accessed, using non-temporal
+// memcpy can save 1 DRAM load of the destination cacheline.
+constexpr size_t kCacheLineSize = ABSL_CACHELINE_SIZE;
+
+// If the objects overlap, the behavior is undefined.
+inline void *non_temporal_store_memcpy(void *__restrict dst,
+ const void *__restrict src, size_t len) {
+#if defined(__SSE3__) || defined(__aarch64__) || \
+ (defined(_MSC_VER) && defined(__AVX__))
+ // This implementation requires SSE3.
+ // MSVC cannot target SSE3 directly, but when MSVC targets AVX,
+ // SSE3 support is implied.
+ uint8_t *d = reinterpret_cast<uint8_t *>(dst);
+ const uint8_t *s = reinterpret_cast<const uint8_t *>(src);
+
+ // memcpy() the misaligned header. At the end of this if block, <d> is
+ // aligned to a 64-byte cacheline boundary or <len> == 0.
+ if (reinterpret_cast<uintptr_t>(d) & (kCacheLineSize - 1)) {
+ uintptr_t bytes_before_alignment_boundary =
+ kCacheLineSize -
+ (reinterpret_cast<uintptr_t>(d) & (kCacheLineSize - 1));
+ size_t header_len = (std::min)(bytes_before_alignment_boundary, len);
+ assert(bytes_before_alignment_boundary < kCacheLineSize);
+ memcpy(d, s, header_len);
+ d += header_len;
+ s += header_len;
+ len -= header_len;
+ }
+
+ if (len >= kCacheLineSize) {
+ _mm_sfence();
+ __m128i *dst_cacheline = reinterpret_cast<__m128i *>(d);
+ const __m128i *src_cacheline = reinterpret_cast<const __m128i *>(s);
+ constexpr int kOpsPerCacheLine = kCacheLineSize / sizeof(__m128i);
+ size_t loops = len / kCacheLineSize;
+
+ while (len >= kCacheLineSize) {
+ __m128i temp1, temp2, temp3, temp4;
+ temp1 = _mm_lddqu_si128(src_cacheline + 0);
+ temp2 = _mm_lddqu_si128(src_cacheline + 1);
+ temp3 = _mm_lddqu_si128(src_cacheline + 2);
+ temp4 = _mm_lddqu_si128(src_cacheline + 3);
+ _mm_stream_si128(dst_cacheline + 0, temp1);
+ _mm_stream_si128(dst_cacheline + 1, temp2);
+ _mm_stream_si128(dst_cacheline + 2, temp3);
+ _mm_stream_si128(dst_cacheline + 3, temp4);
+ src_cacheline += kOpsPerCacheLine;
+ dst_cacheline += kOpsPerCacheLine;
+ len -= kCacheLineSize;
+ }
+ d += loops * kCacheLineSize;
+ s += loops * kCacheLineSize;
+ _mm_sfence();
+ }
+
+ // memcpy the tail.
+ if (len) {
+ memcpy(d, s, len);
+ }
+ return dst;
+#else
+ // Fallback to regular memcpy.
+ return memcpy(dst, src, len);
+#endif // __SSE3__ || __aarch64__ || (_MSC_VER && __AVX__)
+}
+
+inline void *non_temporal_store_memcpy_avx(void *__restrict dst,
+ const void *__restrict src,
+ size_t len) {
+#ifdef __AVX__
+ uint8_t *d = reinterpret_cast<uint8_t *>(dst);
+ const uint8_t *s = reinterpret_cast<const uint8_t *>(src);
+
+ // memcpy() the misaligned header. At the end of this if block, <d> is
+ // aligned to a 64-byte cacheline boundary or <len> == 0.
+ if (reinterpret_cast<uintptr_t>(d) & (kCacheLineSize - 1)) {
+ uintptr_t bytes_before_alignment_boundary =
+ kCacheLineSize -
+ (reinterpret_cast<uintptr_t>(d) & (kCacheLineSize - 1));
+ size_t header_len = (std::min)(bytes_before_alignment_boundary, len);
+ assert(bytes_before_alignment_boundary < kCacheLineSize);
+ memcpy(d, s, header_len);
+ d += header_len;
+ s += header_len;
+ len -= header_len;
+ }
+
+ if (len >= kCacheLineSize) {
+ _mm_sfence();
+ __m256i *dst_cacheline = reinterpret_cast<__m256i *>(d);
+ const __m256i *src_cacheline = reinterpret_cast<const __m256i *>(s);
+ constexpr int kOpsPerCacheLine = kCacheLineSize / sizeof(__m256i);
+ size_t loops = len / kCacheLineSize;
+
+ while (len >= kCacheLineSize) {
+ __m256i temp1, temp2;
+ temp1 = _mm256_lddqu_si256(src_cacheline + 0);
+ temp2 = _mm256_lddqu_si256(src_cacheline + 1);
+ _mm256_stream_si256(dst_cacheline + 0, temp1);
+ _mm256_stream_si256(dst_cacheline + 1, temp2);
+ src_cacheline += kOpsPerCacheLine;
+ dst_cacheline += kOpsPerCacheLine;
+ len -= kCacheLineSize;
+ }
+ d += loops * kCacheLineSize;
+ s += loops * kCacheLineSize;
+ _mm_sfence();
+ }
+
+ // memcpy the tail.
+ if (len) {
+ memcpy(d, s, len);
+ }
+ return dst;
+#else
+ // Fallback to regular memcpy when AVX is not available.
+ return memcpy(dst, src, len);
+#endif // __AVX__
+}
+
+} // namespace crc_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_CRC_INTERNAL_NON_TEMPORAL_MEMCPY_H_
diff --git a/absl/crc/internal/non_temporal_memcpy_test.cc b/absl/crc/internal/non_temporal_memcpy_test.cc
new file mode 100644
index 0000000..eb07a55
--- /dev/null
+++ b/absl/crc/internal/non_temporal_memcpy_test.cc
@@ -0,0 +1,88 @@
+// Copyright 2022 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/crc/internal/non_temporal_memcpy.h"
+
+#include <algorithm>
+#include <cstdint>
+#include <iostream>
+#include <vector>
+
+#include "gtest/gtest.h"
+
+namespace {
+
+struct TestParam {
+ size_t copy_size;
+ uint32_t src_offset;
+ uint32_t dst_offset;
+};
+
+class NonTemporalMemcpyTest : public testing::TestWithParam<TestParam> {
+ protected:
+ void SetUp() override {
+ // Make buf_size multiple of 16 bytes.
+ size_t buf_size = ((std::max(GetParam().src_offset, GetParam().dst_offset) +
+ GetParam().copy_size) +
+ 15) /
+ 16 * 16;
+ a_.resize(buf_size);
+ b_.resize(buf_size);
+ for (size_t i = 0; i < buf_size; i++) {
+ a_[i] = static_cast<uint8_t>(i % 256);
+ b_[i] = ~a_[i];
+ }
+ }
+
+ std::vector<uint8_t> a_, b_;
+};
+
+TEST_P(NonTemporalMemcpyTest, SSEEquality) {
+ uint8_t *src = a_.data() + GetParam().src_offset;
+ uint8_t *dst = b_.data() + GetParam().dst_offset;
+ absl::crc_internal::non_temporal_store_memcpy(dst, src, GetParam().copy_size);
+ for (size_t i = 0; i < GetParam().copy_size; i++) {
+ EXPECT_EQ(src[i], dst[i]);
+ }
+}
+
+TEST_P(NonTemporalMemcpyTest, AVXEquality) {
+ uint8_t* src = a_.data() + GetParam().src_offset;
+ uint8_t* dst = b_.data() + GetParam().dst_offset;
+
+ absl::crc_internal::non_temporal_store_memcpy_avx(dst, src,
+ GetParam().copy_size);
+ for (size_t i = 0; i < GetParam().copy_size; i++) {
+ EXPECT_EQ(src[i], dst[i]);
+ }
+}
+
+// 63B is smaller than one cacheline operation thus the non-temporal routine
+// will not be called.
+// 4352B is sufficient for testing 4092B data copy with room for offsets.
+constexpr TestParam params[] = {
+ {63, 0, 0}, {58, 5, 5}, {61, 2, 0}, {61, 0, 2},
+ {58, 5, 2}, {4096, 0, 0}, {4096, 0, 1}, {4096, 0, 2},
+ {4096, 0, 3}, {4096, 0, 4}, {4096, 0, 5}, {4096, 0, 6},
+ {4096, 0, 7}, {4096, 0, 8}, {4096, 0, 9}, {4096, 0, 10},
+ {4096, 0, 11}, {4096, 0, 12}, {4096, 0, 13}, {4096, 0, 14},
+ {4096, 0, 15}, {4096, 7, 7}, {4096, 3, 0}, {4096, 1, 0},
+ {4096, 9, 3}, {4096, 9, 11}, {8192, 0, 0}, {8192, 5, 2},
+ {1024768, 7, 11}, {1, 0, 0}, {1, 0, 1}, {1, 1, 0},
+ {1, 1, 1}};
+
+INSTANTIATE_TEST_SUITE_P(ParameterizedNonTemporalMemcpyTest,
+ NonTemporalMemcpyTest, testing::ValuesIn(params));
+
+} // namespace
diff --git a/absl/debugging/BUILD.bazel b/absl/debugging/BUILD.bazel
index a8c51a5..5baff7a 100644
--- a/absl/debugging/BUILD.bazel
+++ b/absl/debugging/BUILD.bazel
@@ -14,7 +14,6 @@
# limitations under the License.
#
-load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test")
load(
"//absl:copts/configure_copts.bzl",
"ABSL_DEFAULT_COPTS",
@@ -24,6 +23,11 @@
package(
default_visibility = ["//visibility:public"],
+ features = [
+ "header_modules",
+ "layering_check",
+ "parse_headers",
+ ],
)
licenses(["notice"])
@@ -37,6 +41,7 @@
"internal/stacktrace_emscripten-inl.inc",
"internal/stacktrace_generic-inl.inc",
"internal/stacktrace_powerpc-inl.inc",
+ "internal/stacktrace_riscv-inl.inc",
"internal/stacktrace_unimplemented-inl.inc",
"internal/stacktrace_win32-inl.inc",
"internal/stacktrace_x86-inl.inc",
@@ -49,6 +54,21 @@
":debugging_internal",
"//absl/base:config",
"//absl/base:core_headers",
+ "//absl/base:dynamic_annotations",
+ "//absl/base:raw_logging_internal",
+ ],
+)
+
+cc_test(
+ name = "stacktrace_test",
+ srcs = ["stacktrace_test.cc"],
+ copts = ABSL_TEST_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ ":stacktrace",
+ "//absl/base:core_headers",
+ "@com_google_googletest//:gtest",
+ "@com_google_googletest//:gtest_main",
],
)
@@ -70,6 +90,10 @@
linkopts = ABSL_DEFAULT_LINKOPTS + select({
"//absl:msvc_compiler": ["-DEFAULTLIB:dbghelp.lib"],
"//absl:clang-cl_compiler": ["-DEFAULTLIB:dbghelp.lib"],
+ "//absl:mingw_compiler": [
+ "-DEFAULTLIB:dbghelp.lib",
+ "-ldbghelp",
+ ],
"//conditions:default": [],
}),
deps = [
@@ -104,7 +128,8 @@
"//absl/base",
"//absl/base:config",
"//absl/base:core_headers",
- "//absl/base:raw_logging_internal",
+ "//absl/log",
+ "//absl/log:check",
"//absl/memory",
"//absl/strings",
"@com_google_googletest//:gtest",
@@ -121,7 +146,7 @@
],
copts = ABSL_DEFAULT_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
- visibility = ["//visibility:private"],
+ visibility = ["//absl/log/internal:__pkg__"],
deps = [
":stacktrace",
":symbolize",
@@ -143,7 +168,6 @@
"//absl/base",
"//absl/base:config",
"//absl/base:core_headers",
- "//absl/base:errno_saver",
"//absl/base:raw_logging_internal",
],
)
@@ -163,6 +187,7 @@
":stacktrace",
":symbolize",
"//absl/base:raw_logging_internal",
+ "//absl/log:check",
"//absl/strings",
"@com_google_googletest//:gtest",
],
@@ -182,6 +207,7 @@
],
copts = ABSL_DEFAULT_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
+ visibility = ["//visibility:private"],
deps = [
"//absl/base:config",
"//absl/base:core_headers",
@@ -196,6 +222,11 @@
srcs = ["internal/demangle.cc"],
hdrs = ["internal/demangle.h"],
copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ visibility = [
+ "//absl/container:__pkg__",
+ "//absl/debugging:__pkg__",
+ ],
deps = [
"//absl/base",
"//absl/base:config",
@@ -213,8 +244,9 @@
":stack_consumption",
"//absl/base:config",
"//absl/base:core_headers",
- "//absl/base:raw_logging_internal",
+ "//absl/log",
"//absl/memory",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -223,6 +255,7 @@
name = "leak_check",
srcs = ["leak_check.cc"],
hdrs = ["leak_check.h"],
+ copts = ABSL_DEFAULT_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
"//absl/base:config",
@@ -230,98 +263,35 @@
],
)
-# Adding a dependency to leak_check_disable will disable
-# sanitizer leak checking (asan/lsan) in a test without
-# the need to mess around with build features.
-cc_library(
- name = "leak_check_disable",
- srcs = ["leak_check_disable.cc"],
- linkopts = ABSL_DEFAULT_LINKOPTS,
- linkstatic = 1,
- deps = ["//absl/base:config"],
- alwayslink = 1,
-)
-
-# These targets exists for use in tests only, explicitly configuring the
-# LEAK_SANITIZER macro. It must be linked with -fsanitize=leak for lsan.
-ABSL_LSAN_LINKOPTS = select({
- "//absl:clang_compiler": ["-fsanitize=leak"],
- "//conditions:default": [],
-})
-
-cc_library(
- name = "leak_check_api_enabled_for_testing",
- testonly = 1,
- srcs = ["leak_check.cc"],
- hdrs = ["leak_check.h"],
- copts = select({
- "//absl:clang_compiler": ["-DLEAK_SANITIZER"],
- "//conditions:default": [],
- }),
- linkopts = ABSL_DEFAULT_LINKOPTS,
- visibility = ["//visibility:private"],
- deps = [
- "//absl/base:config",
- "//absl/base:core_headers",
- ],
-)
-
-cc_library(
- name = "leak_check_api_disabled_for_testing",
- testonly = 1,
- srcs = ["leak_check.cc"],
- hdrs = ["leak_check.h"],
- copts = ["-ULEAK_SANITIZER"],
- linkopts = ABSL_DEFAULT_LINKOPTS,
- visibility = ["//visibility:private"],
- deps = [
- "//absl/base:config",
- "//absl/base:core_headers",
- ],
-)
-
cc_test(
name = "leak_check_test",
srcs = ["leak_check_test.cc"],
- copts = select({
- "//absl:clang_compiler": ["-DABSL_EXPECT_LEAK_SANITIZER"],
- "//conditions:default": [],
- }),
- linkopts = ABSL_LSAN_LINKOPTS + ABSL_DEFAULT_LINKOPTS,
- tags = ["notsan"],
- deps = [
- ":leak_check_api_enabled_for_testing",
- "//absl/base",
- "@com_google_googletest//:gtest_main",
- ],
-)
-
-cc_test(
- name = "leak_check_no_lsan_test",
- srcs = ["leak_check_test.cc"],
- copts = ["-UABSL_EXPECT_LEAK_SANITIZER"],
+ copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
- tags = ["noasan"],
+ tags = ["notsan"],
deps = [
- ":leak_check_api_disabled_for_testing",
- "//absl/base", # for raw_logging
+ ":leak_check",
+ "//absl/base:config",
+ "//absl/log",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
-# Test that leak checking is skipped when lsan is enabled but
-# ":leak_check_disable" is linked in.
-#
-# This test should fail in the absence of a dependency on ":leak_check_disable"
-cc_test(
- name = "disabled_leak_check_test",
+# Binary that leaks memory and expects to fail on exit. This isn't a
+# test that expected to pass on its own; it exists to be called by a
+# script that checks exit status and output.
+# TODO(absl-team): Write a test to run this with a script that
+# verifies that it correctly fails.
+cc_binary(
+ name = "leak_check_fail_test_binary",
srcs = ["leak_check_fail_test.cc"],
- linkopts = ABSL_LSAN_LINKOPTS + ABSL_DEFAULT_LINKOPTS,
- tags = ["notsan"],
+ copts = ABSL_TEST_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
- ":leak_check_api_enabled_for_testing",
- ":leak_check_disable",
- "//absl/base",
+ ":leak_check",
+ "//absl/log",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -350,7 +320,23 @@
deps = [
":stack_consumption",
"//absl/base:core_headers",
- "//absl/base:raw_logging_internal",
+ "//absl/log",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
+
+cc_binary(
+ name = "stacktrace_benchmark",
+ testonly = 1,
+ srcs = ["stacktrace_benchmark.cc"],
+ copts = ABSL_TEST_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ tags = ["benchmark"],
+ deps = [
+ ":stacktrace",
+ "//absl/base:config",
+ "//absl/base:core_headers",
+ "@com_github_google_benchmark//:benchmark_main",
+ ],
+)
diff --git a/absl/debugging/CMakeLists.txt b/absl/debugging/CMakeLists.txt
index 760772f..65e2af8 100644
--- a/absl/debugging/CMakeLists.txt
+++ b/absl/debugging/CMakeLists.txt
@@ -14,6 +14,8 @@
# limitations under the License.
#
+find_library(EXECINFO_LIBRARY execinfo)
+
absl_cc_library(
NAME
stacktrace
@@ -25,6 +27,7 @@
"internal/stacktrace_emscripten-inl.inc"
"internal/stacktrace_generic-inl.inc"
"internal/stacktrace_powerpc-inl.inc"
+ "internal/stacktrace_riscv-inl.inc"
"internal/stacktrace_unimplemented-inl.inc"
"internal/stacktrace_win32-inl.inc"
"internal/stacktrace_x86-inl.inc"
@@ -32,13 +35,30 @@
"stacktrace.cc"
COPTS
${ABSL_DEFAULT_COPTS}
+ LINKOPTS
+ $<$<BOOL:${EXECINFO_LIBRARY}>:${EXECINFO_LIBRARY}>
DEPS
absl::debugging_internal
absl::config
absl::core_headers
+ absl::dynamic_annotations
+ absl::raw_logging_internal
PUBLIC
)
+absl_cc_test(
+ NAME
+ stacktrace_test
+ SRCS
+ "stacktrace_test.cc"
+ COPTS
+ ${ABSL_TEST_COPTS}
+ DEPS
+ absl::stacktrace
+ absl::core_headers
+ GTest::gmock_main
+)
+
absl_cc_library(
NAME
symbolize
@@ -56,7 +76,7 @@
${ABSL_DEFAULT_COPTS}
LINKOPTS
${ABSL_DEFAULT_LINKOPTS}
- $<$<BOOL:${MINGW}>:"dbghelp">
+ $<$<BOOL:${MINGW}>:-ldbghelp>
DEPS
absl::debugging_internal
absl::demangle_internal
@@ -81,17 +101,19 @@
LINKOPTS
$<$<BOOL:${MSVC}>:-DEBUG>
DEPS
- absl::stack_consumption
- absl::symbolize
absl::base
+ absl::check
absl::config
absl::core_headers
+ absl::log
absl::memory
- absl::raw_logging_internal
+ absl::stack_consumption
absl::strings
+ absl::symbolize
GTest::gmock
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
examine_stack
@@ -124,7 +146,6 @@
absl::base
absl::config
absl::core_headers
- absl::errno_saver
absl::raw_logging_internal
PUBLIC
)
@@ -137,6 +158,7 @@
COPTS
${ABSL_TEST_COPTS}
DEPS
+ absl::check
absl::failure_signal_handler
absl::stacktrace
absl::symbolize
@@ -146,6 +168,7 @@
GTest::gmock
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
debugging_internal
@@ -167,6 +190,7 @@
absl::raw_logging_internal
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
demangle_internal
@@ -194,8 +218,8 @@
absl::stack_consumption
absl::config
absl::core_headers
+ absl::log
absl::memory
- absl::raw_logging_internal
GTest::gmock_main
)
@@ -214,42 +238,6 @@
PUBLIC
)
-absl_cc_library(
- NAME
- leak_check_disable
- SRCS
- "leak_check_disable.cc"
- COPTS
- ${ABSL_DEFAULT_COPTS}
- PUBLIC
-)
-
-absl_cc_library(
- NAME
- leak_check_api_enabled_for_testing
- HDRS
- "leak_check.h"
- SRCS
- "leak_check.cc"
- COPTS
- ${ABSL_DEFAULT_COPTS}
- $<$<BOOL:${ABSL_HAVE_LSAN}>:-DLEAK_SANITIZER>
- TESTONLY
-)
-
-absl_cc_library(
- NAME
- leak_check_api_disabled_for_testing
- HDRS
- "leak_check.h"
- SRCS
- "leak_check.cc"
- COPTS
- ${ABSL_DEFAULT_COPTS}
- "-ULEAK_SANITIZER"
- TESTONLY
-)
-
absl_cc_test(
NAME
leak_check_test
@@ -257,46 +245,16 @@
"leak_check_test.cc"
COPTS
${ABSL_TEST_COPTS}
- "$<$<BOOL:${ABSL_HAVE_LSAN}>:-DABSL_EXPECT_LEAK_SANITIZER>"
LINKOPTS
- "${ABSL_LSAN_LINKOPTS}"
+ ${ABSL_DEFAULT_LINKOPTS}
DEPS
- absl::leak_check_api_enabled_for_testing
+ absl::leak_check
absl::base
+ absl::log
GTest::gmock_main
)
-absl_cc_test(
- NAME
- leak_check_no_lsan_test
- SRCS
- "leak_check_test.cc"
- COPTS
- ${ABSL_TEST_COPTS}
- "-UABSL_EXPECT_LEAK_SANITIZER"
- DEPS
- absl::leak_check_api_disabled_for_testing
- absl::base
- GTest::gmock_main
-)
-
-absl_cc_test(
- NAME
- disabled_leak_check_test
- SRCS
- "leak_check_fail_test.cc"
- COPTS
- ${ABSL_TEST_COPTS}
- LINKOPTS
- "${ABSL_LSAN_LINKOPTS}"
- DEPS
- absl::leak_check_api_enabled_for_testing
- absl::leak_check_disable
- absl::base
- absl::raw_logging_internal
- GTest::gmock_main
-)
-
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
stack_consumption
@@ -323,7 +281,7 @@
DEPS
absl::stack_consumption
absl::core_headers
- absl::raw_logging_internal
+ absl::log
GTest::gmock_main
)
diff --git a/absl/debugging/failure_signal_handler.cc b/absl/debugging/failure_signal_handler.cc
index 689e597..570d1e5 100644
--- a/absl/debugging/failure_signal_handler.cc
+++ b/absl/debugging/failure_signal_handler.cc
@@ -31,6 +31,13 @@
#ifdef ABSL_HAVE_MMAP
#include <sys/mman.h>
+#if defined(MAP_ANON) && !defined(MAP_ANONYMOUS)
+#define MAP_ANONYMOUS MAP_ANON
+#endif
+#endif
+
+#ifdef __linux__
+#include <sys/prctl.h>
#endif
#include <algorithm>
@@ -42,17 +49,18 @@
#include <ctime>
#include "absl/base/attributes.h"
-#include "absl/base/internal/errno_saver.h"
#include "absl/base/internal/raw_logging.h"
#include "absl/base/internal/sysinfo.h"
#include "absl/debugging/internal/examine_stack.h"
#include "absl/debugging/stacktrace.h"
-#ifndef _WIN32
+#if !defined(_WIN32) && !defined(__wasi__)
#define ABSL_HAVE_SIGACTION
// Apple WatchOS and TVOS don't allow sigaltstack
-#if !(defined(TARGET_OS_WATCH) && TARGET_OS_WATCH) && \
- !(defined(TARGET_OS_TV) && TARGET_OS_TV)
+// Apple macOS has sigaltstack, but using it makes backtrace() unusable.
+#if !(defined(TARGET_OS_OSX) && TARGET_OS_OSX) && \
+ !(defined(TARGET_OS_WATCH) && TARGET_OS_WATCH) && \
+ !(defined(TARGET_OS_TV) && TARGET_OS_TV) && !defined(__QNX__)
#define ABSL_HAVE_SIGALTSTACK
#endif
#endif
@@ -76,10 +84,10 @@
struct sigaction previous_action;
// StructSigaction is used to silence -Wmissing-field-initializers.
using StructSigaction = struct sigaction;
- #define FSD_PREVIOUS_INIT FailureSignalData::StructSigaction()
+#define FSD_PREVIOUS_INIT FailureSignalData::StructSigaction()
#else
void (*previous_handler)(int);
- #define FSD_PREVIOUS_INIT SIG_DFL
+#define FSD_PREVIOUS_INIT SIG_DFL
#endif
};
@@ -131,13 +139,14 @@
#ifdef ABSL_HAVE_SIGALTSTACK
static bool SetupAlternateStackOnce() {
-#if defined(__wasm__) || defined (__asjms__)
+#if defined(__wasm__) || defined(__asjms__)
const size_t page_mask = getpagesize() - 1;
#else
- const size_t page_mask = sysconf(_SC_PAGESIZE) - 1;
+ const size_t page_mask = static_cast<size_t>(sysconf(_SC_PAGESIZE)) - 1;
#endif
size_t stack_size =
- (std::max<size_t>(SIGSTKSZ, 65536) + page_mask) & ~page_mask;
+ (std::max(static_cast<size_t>(SIGSTKSZ), size_t{65536}) + page_mask) &
+ ~page_mask;
#if defined(ABSL_HAVE_ADDRESS_SANITIZER) || \
defined(ABSL_HAVE_MEMORY_SANITIZER) || defined(ABSL_HAVE_THREAD_SANITIZER)
// Account for sanitizer instrumentation requiring additional stack space.
@@ -152,9 +161,6 @@
#ifndef MAP_STACK
#define MAP_STACK 0
#endif
-#if defined(MAP_ANON) && !defined(MAP_ANONYMOUS)
-#define MAP_ANONYMOUS MAP_ANON
-#endif
sigstk.ss_sp = mmap(nullptr, sigstk.ss_size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);
if (sigstk.ss_sp == MAP_FAILED) {
@@ -170,6 +176,20 @@
if (sigaltstack(&sigstk, nullptr) != 0) {
ABSL_RAW_LOG(FATAL, "sigaltstack() failed with errno=%d", errno);
}
+
+#ifdef __linux__
+#if defined(PR_SET_VMA) && defined(PR_SET_VMA_ANON_NAME)
+ // Make a best-effort attempt to name the allocated region in
+ // /proc/$PID/smaps.
+ //
+ // The call to prctl() may fail if the kernel was not configured with the
+ // CONFIG_ANON_VMA_NAME kernel option. This is OK since the call is
+ // primarily a debugging aid.
+ prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, sigstk.ss_sp, sigstk.ss_size,
+ "absl-signalstack");
+#endif
+#endif // __linux__
+
return true;
}
@@ -216,11 +236,6 @@
#endif
-static void WriteToStderr(const char* data) {
- absl::base_internal::ErrnoSaver errno_saver;
- absl::raw_logging_internal::SafeWriteToStderr(data, strlen(data));
-}
-
static void WriteSignalMessage(int signo, int cpu,
void (*writerfn)(const char*)) {
char buf[96];
@@ -233,7 +248,7 @@
if (signal_string != nullptr && signal_string[0] != '\0') {
snprintf(buf, sizeof(buf), "*** %s received at time=%ld%s ***\n",
signal_string,
- static_cast<long>(time(nullptr)), // NOLINT(runtime/int)
+ static_cast<long>(time(nullptr)), // NOLINT(runtime/int)
on_cpu);
} else {
snprintf(buf, sizeof(buf), "*** Signal %d received at time=%ld%s ***\n",
@@ -291,12 +306,13 @@
// some platforms.
static void PortableSleepForSeconds(int seconds) {
#ifdef _WIN32
- Sleep(seconds * 1000);
+ Sleep(static_cast<DWORD>(seconds * 1000));
#else
struct timespec sleep_time;
sleep_time.tv_sec = seconds;
sleep_time.tv_nsec = 0;
- while (nanosleep(&sleep_time, &sleep_time) != 0 && errno == EINTR) {}
+ while (nanosleep(&sleep_time, &sleep_time) != 0 && errno == EINTR) {
+ }
#endif
}
@@ -306,9 +322,7 @@
// set amount of time. If AbslFailureSignalHandler() hangs for more than
// the alarm timeout, ImmediateAbortSignalHandler() will abort the
// program.
-static void ImmediateAbortSignalHandler(int) {
- RaiseToDefaultHandler(SIGABRT);
-}
+static void ImmediateAbortSignalHandler(int) { RaiseToDefaultHandler(SIGABRT); }
#endif
// absl::base_internal::GetTID() returns pid_t on most platforms, but
@@ -325,9 +339,9 @@
const GetTidType this_tid = absl::base_internal::GetTID();
GetTidType previous_failed_tid = 0;
- if (!failed_tid.compare_exchange_strong(
- previous_failed_tid, static_cast<intptr_t>(this_tid),
- std::memory_order_acq_rel, std::memory_order_relaxed)) {
+ if (!failed_tid.compare_exchange_strong(previous_failed_tid, this_tid,
+ std::memory_order_acq_rel,
+ std::memory_order_relaxed)) {
ABSL_RAW_LOG(
ERROR,
"Signal %d raised at PC=%p while already in AbslFailureSignalHandler()",
@@ -356,12 +370,15 @@
if (fsh_options.alarm_on_failure_secs > 0) {
alarm(0); // Cancel any existing alarms.
signal(SIGALRM, ImmediateAbortSignalHandler);
- alarm(fsh_options.alarm_on_failure_secs);
+ alarm(static_cast<unsigned int>(fsh_options.alarm_on_failure_secs));
}
#endif
// First write to stderr.
- WriteFailureInfo(signo, ucontext, my_cpu, WriteToStderr);
+ WriteFailureInfo(
+ signo, ucontext, my_cpu, +[](const char* data) {
+ absl::raw_log_internal::AsyncSignalSafeWriteError(data, strlen(data));
+ });
// Riskier code (because it is less likely to be async-signal-safe)
// goes after this point.
diff --git a/absl/debugging/failure_signal_handler.h b/absl/debugging/failure_signal_handler.h
index 500115c..4117fac 100644
--- a/absl/debugging/failure_signal_handler.h
+++ b/absl/debugging/failure_signal_handler.h
@@ -33,7 +33,7 @@
// }
//
// Any program that raises a fatal signal (such as `SIGSEGV`, `SIGILL`,
-// `SIGFPE`, `SIGABRT`, `SIGTERM`, `SIGBUG`, and `SIGTRAP`) will call the
+// `SIGFPE`, `SIGABRT`, `SIGTERM`, `SIGBUS`, and `SIGTRAP`) will call the
// installed failure signal handler and provide debugging information to stderr.
//
// Note that you should *not* install the Abseil failure signal handler more
@@ -62,7 +62,7 @@
// If true, try to run signal handlers on an alternate stack (if supported on
// the given platform). An alternate stack is useful for program crashes due
// to a stack overflow; by running on a alternate stack, the signal handler
- // may run even when normal stack space has been exausted. The downside of
+ // may run even when normal stack space has been exhausted. The downside of
// using an alternate stack is that extra memory for the alternate stack needs
// to be pre-allocated.
bool use_alternate_stack = true;
diff --git a/absl/debugging/failure_signal_handler_test.cc b/absl/debugging/failure_signal_handler_test.cc
index 6a62428..72816a3 100644
--- a/absl/debugging/failure_signal_handler_test.cc
+++ b/absl/debugging/failure_signal_handler_test.cc
@@ -22,11 +22,12 @@
#include <cstring>
#include <fstream>
-#include "gtest/gtest.h"
#include "gmock/gmock.h"
+#include "gtest/gtest.h"
#include "absl/base/internal/raw_logging.h"
#include "absl/debugging/stacktrace.h"
#include "absl/debugging/symbolize.h"
+#include "absl/log/check.h"
#include "absl/strings/match.h"
#include "absl/strings/str_cat.h"
@@ -87,7 +88,7 @@
// This function runs in a fork()ed process on most systems.
void InstallHandlerWithWriteToFileAndRaise(const char* file, int signo) {
error_file = fopen(file, "w");
- ABSL_RAW_CHECK(error_file != nullptr, "Failed create error_file");
+ CHECK_NE(error_file, nullptr) << "Failed create error_file";
absl::FailureSignalHandlerOptions options;
options.writerfn = WriteToErrorFile;
absl::InstallFailureSignalHandler(options);
diff --git a/absl/debugging/internal/address_is_readable.cc b/absl/debugging/internal/address_is_readable.cc
index 329c285..be17a10 100644
--- a/absl/debugging/internal/address_is_readable.cc
+++ b/absl/debugging/internal/address_is_readable.cc
@@ -30,16 +30,12 @@
ABSL_NAMESPACE_END
} // namespace absl
-#else
+#else // __linux__ && !__ANDROID__
-#include <fcntl.h>
-#include <sys/syscall.h>
+#include <stdint.h>
+#include <syscall.h>
#include <unistd.h>
-#include <atomic>
-#include <cerrno>
-#include <cstdint>
-
#include "absl/base/internal/errno_saver.h"
#include "absl/base/internal/raw_logging.h"
@@ -47,93 +43,56 @@
ABSL_NAMESPACE_BEGIN
namespace debugging_internal {
-// Pack a pid and two file descriptors into a 64-bit word,
-// using 16, 24, and 24 bits for each respectively.
-static uint64_t Pack(uint64_t pid, uint64_t read_fd, uint64_t write_fd) {
- ABSL_RAW_CHECK((read_fd >> 24) == 0 && (write_fd >> 24) == 0,
- "fd out of range");
- return (pid << 48) | ((read_fd & 0xffffff) << 24) | (write_fd & 0xffffff);
-}
-
-// Unpack x into a pid and two file descriptors, where x was created with
-// Pack().
-static void Unpack(uint64_t x, int *pid, int *read_fd, int *write_fd) {
- *pid = x >> 48;
- *read_fd = (x >> 24) & 0xffffff;
- *write_fd = x & 0xffffff;
-}
-
-// Return whether the byte at *addr is readable, without faulting.
-// Save and restores errno. Returns true on systems where
-// unimplemented.
-// This is a namespace-scoped variable for correct zero-initialization.
-static std::atomic<uint64_t> pid_and_fds; // initially 0, an invalid pid.
-
+// NOTE: be extra careful about adding any interposable function calls here
+// (such as open(), read(), etc.). These symbols may be interposed and will get
+// invoked in contexts they don't expect.
+//
+// NOTE: any new system calls here may also require sandbox reconfiguration.
+//
bool AddressIsReadable(const void *addr) {
+ // rt_sigprocmask below checks 8 contiguous bytes. If addr resides in the
+ // last 7 bytes of a page (unaligned), rt_sigprocmask would additionally
+ // check the readability of the next page, which is not desired. Align
+ // address on 8-byte boundary to check only the current page.
+ const uintptr_t u_addr = reinterpret_cast<uintptr_t>(addr) & ~uintptr_t{7};
+ addr = reinterpret_cast<const void *>(u_addr);
+
+ // rt_sigprocmask below will succeed for this input.
+ if (addr == nullptr) return false;
+
absl::base_internal::ErrnoSaver errno_saver;
- // We test whether a byte is readable by using write(). Normally, this would
- // be done via a cached file descriptor to /dev/null, but linux fails to
- // check whether the byte is readable when the destination is /dev/null, so
- // we use a cached pipe. We store the pid of the process that created the
- // pipe to handle the case where a process forks, and the child closes all
- // the file descriptors and then calls this routine. This is not perfect:
- // the child could use the routine, then close all file descriptors and then
- // use this routine again. But the likely use of this routine is when
- // crashing, to test the validity of pages when dumping the stack. Beware
- // that we may leak file descriptors, but we're unlikely to leak many.
- int bytes_written;
- int current_pid = getpid() & 0xffff; // we use only the low order 16 bits
- do { // until we do not get EBADF trying to use file descriptors
- int pid;
- int read_fd;
- int write_fd;
- uint64_t local_pid_and_fds = pid_and_fds.load(std::memory_order_acquire);
- Unpack(local_pid_and_fds, &pid, &read_fd, &write_fd);
- while (current_pid != pid) {
- int p[2];
- // new pipe
- if (pipe(p) != 0) {
- ABSL_RAW_LOG(FATAL, "Failed to create pipe, errno=%d", errno);
- }
- fcntl(p[0], F_SETFD, FD_CLOEXEC);
- fcntl(p[1], F_SETFD, FD_CLOEXEC);
- uint64_t new_pid_and_fds = Pack(current_pid, p[0], p[1]);
- if (pid_and_fds.compare_exchange_strong(
- local_pid_and_fds, new_pid_and_fds, std::memory_order_release,
- std::memory_order_relaxed)) {
- local_pid_and_fds = new_pid_and_fds; // fds exposed to other threads
- } else { // fds not exposed to other threads; we can close them.
- close(p[0]);
- close(p[1]);
- local_pid_and_fds = pid_and_fds.load(std::memory_order_acquire);
- }
- Unpack(local_pid_and_fds, &pid, &read_fd, &write_fd);
- }
- errno = 0;
- // Use syscall(SYS_write, ...) instead of write() to prevent ASAN
- // and other checkers from complaining about accesses to arbitrary
- // memory.
- do {
- bytes_written = syscall(SYS_write, write_fd, addr, 1);
- } while (bytes_written == -1 && errno == EINTR);
- if (bytes_written == 1) { // remove the byte from the pipe
- char c;
- while (read(read_fd, &c, 1) == -1 && errno == EINTR) {
- }
- }
- if (errno == EBADF) { // Descriptors invalid.
- // If pid_and_fds contains the problematic file descriptors we just used,
- // this call will forget them, and the loop will try again.
- pid_and_fds.compare_exchange_strong(local_pid_and_fds, 0,
- std::memory_order_release,
- std::memory_order_relaxed);
- }
- } while (errno == EBADF);
- return bytes_written == 1;
+
+ // Here we probe with some syscall which
+ // - accepts an 8-byte region of user memory as input
+ // - tests for EFAULT before other validation
+ // - has no problematic side-effects
+ //
+ // rt_sigprocmask(2) works for this. It copies sizeof(kernel_sigset_t)==8
+ // bytes from the address into the kernel memory before any validation.
+ //
+ // The call can never succeed, since the `how` parameter is not one of
+ // SIG_BLOCK, SIG_UNBLOCK, SIG_SETMASK.
+ //
+ // This strategy depends on Linux implementation details,
+ // so we rely on the test to alert us if it stops working.
+ //
+ // Some discarded past approaches:
+ // - msync() doesn't reject PROT_NONE regions
+ // - write() on /dev/null doesn't return EFAULT
+ // - write() on a pipe requires creating it and draining the writes
+ // - connect() works but is problematic for sandboxes and needs a valid
+ // file descriptor
+ //
+ // This can never succeed (invalid first argument to sigprocmask).
+ ABSL_RAW_CHECK(syscall(SYS_rt_sigprocmask, ~0, addr, nullptr,
+ /*sizeof(kernel_sigset_t)*/ 8) == -1,
+ "unexpected success");
+ ABSL_RAW_CHECK(errno == EFAULT || errno == EINVAL, "unexpected errno");
+ return errno != EFAULT;
}
} // namespace debugging_internal
ABSL_NAMESPACE_END
} // namespace absl
-#endif
+#endif // __linux__ && !__ANDROID__
diff --git a/absl/debugging/internal/demangle.cc b/absl/debugging/internal/demangle.cc
index 5cd5632..82420c8 100644
--- a/absl/debugging/internal/demangle.cc
+++ b/absl/debugging/internal/demangle.cc
@@ -21,7 +21,15 @@
#include <cstdint>
#include <cstdio>
+#include <cstdlib>
#include <limits>
+#include <string>
+
+#include "absl/base/config.h"
+
+#if ABSL_INTERNAL_HAS_CXA_DEMANGLE
+#include <cxxabi.h>
+#endif
namespace absl {
ABSL_NAMESPACE_BEGIN
@@ -151,12 +159,12 @@
// State needed for demangling. This struct is copied in almost every stack
// frame, so every byte counts.
typedef struct {
- int mangled_idx; // Cursor of mangled name.
- int out_cur_idx; // Cursor of output string.
- int prev_name_idx; // For constructors/destructors.
- signed int prev_name_length : 16; // For constructors/destructors.
- signed int nest_level : 15; // For nested names.
- unsigned int append : 1; // Append flag.
+ int mangled_idx; // Cursor of mangled name.
+ int out_cur_idx; // Cursor of output string.
+ int prev_name_idx; // For constructors/destructors.
+ unsigned int prev_name_length : 16; // For constructors/destructors.
+ signed int nest_level : 15; // For nested names.
+ unsigned int append : 1; // Append flag.
// Note: for some reason MSVC can't pack "bool append : 1" into the same int
// with the above two fields, so we use an int instead. Amusingly it can pack
// "signed bool" as expected, but relying on that to continue to be a legal
@@ -235,8 +243,8 @@
}
// Returns true if "str" has at least "n" characters remaining.
-static bool AtLeastNumCharsRemaining(const char *str, int n) {
- for (int i = 0; i < n; ++i) {
+static bool AtLeastNumCharsRemaining(const char *str, size_t n) {
+ for (size_t i = 0; i < n; ++i) {
if (str[i] == '\0') {
return false;
}
@@ -253,18 +261,20 @@
return prefix[i] == '\0'; // Consumed everything in "prefix".
}
-static void InitState(State *state, const char *mangled, char *out,
- int out_size) {
+static void InitState(State* state,
+ const char* mangled,
+ char* out,
+ size_t out_size) {
state->mangled_begin = mangled;
state->out = out;
- state->out_end_idx = out_size;
+ state->out_end_idx = static_cast<int>(out_size);
state->recursion_depth = 0;
state->steps = 0;
state->parse_state.mangled_idx = 0;
state->parse_state.out_cur_idx = 0;
state->parse_state.prev_name_idx = 0;
- state->parse_state.prev_name_length = -1;
+ state->parse_state.prev_name_length = 0;
state->parse_state.nest_level = -1;
state->parse_state.append = true;
}
@@ -356,8 +366,8 @@
// Append "str" at "out_cur_idx". If there is an overflow, out_cur_idx is
// set to out_end_idx+1. The output string is ensured to
// always terminate with '\0' as long as there is no overflow.
-static void Append(State *state, const char *const str, const int length) {
- for (int i = 0; i < length; ++i) {
+static void Append(State *state, const char *const str, const size_t length) {
+ for (size_t i = 0; i < length; ++i) {
if (state->parse_state.out_cur_idx + 1 <
state->out_end_idx) { // +1 for '\0'
state->out[state->parse_state.out_cur_idx++] = str[i];
@@ -420,7 +430,7 @@
// Append "str" with some tweaks, iff "append" state is true.
static void MaybeAppendWithLength(State *state, const char *const str,
- const int length) {
+ const size_t length) {
if (state->parse_state.append && length > 0) {
// Append a space if the output buffer ends with '<' and "str"
// starts with '<' to avoid <<<.
@@ -432,14 +442,14 @@
if (state->parse_state.out_cur_idx < state->out_end_idx &&
(IsAlpha(str[0]) || str[0] == '_')) {
state->parse_state.prev_name_idx = state->parse_state.out_cur_idx;
- state->parse_state.prev_name_length = length;
+ state->parse_state.prev_name_length = static_cast<unsigned int>(length);
}
Append(state, str, length);
}
}
// Appends a positive decimal number to the output if appending is enabled.
-static bool MaybeAppendDecimal(State *state, unsigned int val) {
+static bool MaybeAppendDecimal(State *state, int val) {
// Max {32-64}-bit unsigned int is 20 digits.
constexpr size_t kMaxLength = 20;
char buf[kMaxLength];
@@ -451,12 +461,12 @@
// one-past-the-end and manipulate one character before the pointer.
char *p = &buf[kMaxLength];
do { // val=0 is the only input that should write a leading zero digit.
- *--p = (val % 10) + '0';
+ *--p = static_cast<char>((val % 10) + '0');
val /= 10;
} while (p > buf && val != 0);
// 'p' landed on the last character we set. How convenient.
- Append(state, p, kMaxLength - (p - buf));
+ Append(state, p, kMaxLength - static_cast<size_t>(p - buf));
}
return true;
@@ -466,7 +476,7 @@
// Returns true so that it can be placed in "if" conditions.
static bool MaybeAppend(State *state, const char *const str) {
if (state->parse_state.append) {
- int length = StrLen(str);
+ size_t length = StrLen(str);
MaybeAppendWithLength(state, str, length);
}
return true;
@@ -521,10 +531,10 @@
// Returns true if the identifier of the given length pointed to by
// "mangled_cur" is anonymous namespace.
-static bool IdentifierIsAnonymousNamespace(State *state, int length) {
+static bool IdentifierIsAnonymousNamespace(State *state, size_t length) {
// Returns true if "anon_prefix" is a proper prefix of "mangled_cur".
static const char anon_prefix[] = "_GLOBAL__N_";
- return (length > static_cast<int>(sizeof(anon_prefix) - 1) &&
+ return (length > (sizeof(anon_prefix) - 1) &&
StrPrefix(RemainingInput(state), anon_prefix));
}
@@ -542,12 +552,13 @@
static bool ParseNumber(State *state, int *number_out);
static bool ParseFloatNumber(State *state);
static bool ParseSeqId(State *state);
-static bool ParseIdentifier(State *state, int length);
+static bool ParseIdentifier(State *state, size_t length);
static bool ParseOperatorName(State *state, int *arity);
static bool ParseSpecialName(State *state);
static bool ParseCallOffset(State *state);
static bool ParseNVOffset(State *state);
static bool ParseVOffset(State *state);
+static bool ParseAbiTags(State *state);
static bool ParseCtorDtorName(State *state);
static bool ParseDecltype(State *state);
static bool ParseType(State *state);
@@ -559,6 +570,7 @@
static bool ParseArrayType(State *state);
static bool ParsePointerToMemberType(State *state);
static bool ParseTemplateParam(State *state);
+static bool ParseTemplateParamDecl(State *state);
static bool ParseTemplateTemplateParam(State *state);
static bool ParseTemplateArgs(State *state);
static bool ParseTemplateArg(State *state);
@@ -567,6 +579,7 @@
static bool ParseExpression(State *state);
static bool ParseExprPrimary(State *state);
static bool ParseExprCastValue(State *state);
+static bool ParseQRequiresClauseExpr(State *state);
static bool ParseLocalName(State *state);
static bool ParseLocalNameSuffix(State *state);
static bool ParseDiscriminator(State *state);
@@ -601,7 +614,7 @@
//
// Reference:
// - Itanium C++ ABI
-// <https://mentorembedded.github.io/cxx-abi/abi.html#mangling>
+// <https://itanium-cxx-abi.github.io/cxx-abi/abi.html#mangling>
// <mangled-name> ::= _Z <encoding>
static bool ParseMangledName(State *state) {
@@ -611,22 +624,34 @@
}
// <encoding> ::= <(function) name> <bare-function-type>
+// [`Q` <requires-clause expr>]
// ::= <(data) name>
// ::= <special-name>
+//
+// NOTE: Based on http://shortn/_Hoq9qG83rx
static bool ParseEncoding(State *state) {
ComplexityGuard guard(state);
if (guard.IsTooComplex()) return false;
- // Implementing the first two productions together as <name>
- // [<bare-function-type>] avoids exponential blowup of backtracking.
+ // Since the first two productions both start with <name>, attempt
+ // to parse it only once to avoid exponential blowup of backtracking.
//
- // Since Optional(...) can't fail, there's no need to copy the state for
- // backtracking.
- if (ParseName(state) && Optional(ParseBareFunctionType(state))) {
+ // We're careful about exponential blowup because <encoding> recursively
+ // appears in other productions downstream of its first two productions,
+ // which means that every call to `ParseName` would possibly indirectly
+ // result in two calls to `ParseName` etc.
+ if (ParseName(state)) {
+ if (!ParseBareFunctionType(state)) {
+ return true; // <(data) name>
+ }
+
+ // Parsed: <(function) name> <bare-function-type>
+ // Pending: [`Q` <requires-clause expr>]
+ ParseQRequiresClauseExpr(state); // restores state on failure
return true;
}
if (ParseSpecialName(state)) {
- return true;
+ return true; // <special-name>
}
return false;
}
@@ -741,17 +766,42 @@
return true;
}
-// <unqualified-name> ::= <operator-name>
-// ::= <ctor-dtor-name>
-// ::= <source-name>
-// ::= <local-source-name> // GCC extension; see below.
-// ::= <unnamed-type-name>
+// <unqualified-name> ::= <operator-name> [<abi-tags>]
+// ::= <ctor-dtor-name> [<abi-tags>]
+// ::= <source-name> [<abi-tags>]
+// ::= <local-source-name> [<abi-tags>]
+// ::= <unnamed-type-name> [<abi-tags>]
+//
+// <local-source-name> is a GCC extension; see below.
static bool ParseUnqualifiedName(State *state) {
ComplexityGuard guard(state);
if (guard.IsTooComplex()) return false;
- return (ParseOperatorName(state, nullptr) || ParseCtorDtorName(state) ||
- ParseSourceName(state) || ParseLocalSourceName(state) ||
- ParseUnnamedTypeName(state));
+ if (ParseOperatorName(state, nullptr) || ParseCtorDtorName(state) ||
+ ParseSourceName(state) || ParseLocalSourceName(state) ||
+ ParseUnnamedTypeName(state)) {
+ return ParseAbiTags(state);
+ }
+ return false;
+}
+
+// <abi-tags> ::= <abi-tag> [<abi-tags>]
+// <abi-tag> ::= B <source-name>
+static bool ParseAbiTags(State *state) {
+ ComplexityGuard guard(state);
+ if (guard.IsTooComplex()) return false;
+
+ while (ParseOneCharToken(state, 'B')) {
+ ParseState copy = state->parse_state;
+ MaybeAppend(state, "[abi:");
+
+ if (!ParseSourceName(state)) {
+ state->parse_state = copy;
+ return false;
+ }
+ MaybeAppend(state, "]");
+ }
+
+ return true;
}
// <source-name> ::= <positive length number> <identifier>
@@ -760,7 +810,8 @@
if (guard.IsTooComplex()) return false;
ParseState copy = state->parse_state;
int length = -1;
- if (ParseNumber(state, &length) && ParseIdentifier(state, length)) {
+ if (ParseNumber(state, &length) &&
+ ParseIdentifier(state, static_cast<size_t>(length))) {
return true;
}
state->parse_state = copy;
@@ -838,7 +889,7 @@
uint64_t number = 0;
for (; *p != '\0'; ++p) {
if (IsDigit(*p)) {
- number = number * 10 + (*p - '0');
+ number = number * 10 + static_cast<uint64_t>(*p - '0');
} else {
break;
}
@@ -853,7 +904,7 @@
state->parse_state.mangled_idx += p - RemainingInput(state);
if (number_out != nullptr) {
// Note: possibly truncate "number".
- *number_out = number;
+ *number_out = static_cast<int>(number);
}
return true;
}
@@ -897,10 +948,10 @@
}
// <identifier> ::= <unqualified source code identifier> (of given length)
-static bool ParseIdentifier(State *state, int length) {
+static bool ParseIdentifier(State *state, size_t length) {
ComplexityGuard guard(state);
if (guard.IsTooComplex()) return false;
- if (length < 0 || !AtLeastNumCharsRemaining(RemainingInput(state), length)) {
+ if (!AtLeastNumCharsRemaining(RemainingInput(state), length)) {
return false;
}
if (IdentifierIsAnonymousNamespace(state, length)) {
@@ -1246,32 +1297,39 @@
}
// <builtin-type> ::= v, etc. # single-character builtin types
-// ::= u <source-name>
+// ::= u <source-name> [I <type> E]
// ::= Dd, etc. # two-character builtin types
//
// Not supported:
// ::= DF <number> _ # _FloatN (N bits)
//
+// NOTE: [I <type> E] is a vendor extension (http://shortn/_FrINpH1XC5).
static bool ParseBuiltinType(State *state) {
ComplexityGuard guard(state);
if (guard.IsTooComplex()) return false;
- const AbbrevPair *p;
- for (p = kBuiltinTypeList; p->abbrev != nullptr; ++p) {
+
+ for (const AbbrevPair *p = kBuiltinTypeList; p->abbrev != nullptr; ++p) {
// Guaranteed only 1- or 2-character strings in kBuiltinTypeList.
if (p->abbrev[1] == '\0') {
if (ParseOneCharToken(state, p->abbrev[0])) {
MaybeAppend(state, p->real_name);
- return true;
+ return true; // ::= v, etc. # single-character builtin types
}
} else if (p->abbrev[2] == '\0' && ParseTwoCharToken(state, p->abbrev)) {
MaybeAppend(state, p->real_name);
- return true;
+ return true; // ::= Dd, etc. # two-character builtin types
}
}
ParseState copy = state->parse_state;
if (ParseOneCharToken(state, 'u') && ParseSourceName(state)) {
- return true;
+ copy = state->parse_state;
+ if (ParseOneCharToken(state, 'I') && ParseType(state) &&
+ ParseOneCharToken(state, 'E')) {
+ return true; // ::= u <source-name> I <type> E
+ }
+ state->parse_state = copy;
+ return true; // ::= u <source-name>
}
state->parse_state = copy;
return false;
@@ -1376,21 +1434,83 @@
// <template-param> ::= T_
// ::= T <parameter-2 non-negative number> _
+// ::= TL <level-1> __
+// ::= TL <level-1> _ <parameter-2 non-negative number> _
static bool ParseTemplateParam(State *state) {
ComplexityGuard guard(state);
if (guard.IsTooComplex()) return false;
if (ParseTwoCharToken(state, "T_")) {
MaybeAppend(state, "?"); // We don't support template substitutions.
- return true;
+ return true; // ::= T_
}
ParseState copy = state->parse_state;
if (ParseOneCharToken(state, 'T') && ParseNumber(state, nullptr) &&
ParseOneCharToken(state, '_')) {
MaybeAppend(state, "?"); // We don't support template substitutions.
+ return true; // ::= T <parameter-2 non-negative number> _
+ }
+ state->parse_state = copy;
+
+ if (ParseTwoCharToken(state, "TL") && ParseNumber(state, nullptr)) {
+ if (ParseTwoCharToken(state, "__")) {
+ MaybeAppend(state, "?"); // We don't support template substitutions.
+ return true; // ::= TL <level-1> __
+ }
+
+ if (ParseOneCharToken(state, '_') && ParseNumber(state, nullptr) &&
+ ParseOneCharToken(state, '_')) {
+ MaybeAppend(state, "?"); // We don't support template substitutions.
+ return true; // ::= TL <level-1> _ <parameter-2 non-negative number> _
+ }
+ }
+ state->parse_state = copy;
+ return false;
+}
+
+// <template-param-decl>
+// ::= Ty # template type parameter
+// ::= Tk <concept name> [<template-args>] # constrained type parameter
+// ::= Tn <type> # template non-type parameter
+// ::= Tt <template-param-decl>* E # template template parameter
+// ::= Tp <template-param-decl> # template parameter pack
+//
+// NOTE: <concept name> is just a <name>: http://shortn/_MqJVyr0fc1
+// TODO(b/324066279): Implement optional suffix for `Tt`:
+// [Q <requires-clause expr>]
+static bool ParseTemplateParamDecl(State *state) {
+ ComplexityGuard guard(state);
+ if (guard.IsTooComplex()) return false;
+ ParseState copy = state->parse_state;
+
+ if (ParseTwoCharToken(state, "Ty")) {
return true;
}
state->parse_state = copy;
+
+ if (ParseTwoCharToken(state, "Tk") && ParseName(state) &&
+ Optional(ParseTemplateArgs(state))) {
+ return true;
+ }
+ state->parse_state = copy;
+
+ if (ParseTwoCharToken(state, "Tn") && ParseType(state)) {
+ return true;
+ }
+ state->parse_state = copy;
+
+ if (ParseTwoCharToken(state, "Tt") &&
+ ZeroOrMore(ParseTemplateParamDecl, state) &&
+ ParseOneCharToken(state, 'E')) {
+ return true;
+ }
+ state->parse_state = copy;
+
+ if (ParseTwoCharToken(state, "Tp") && ParseTemplateParamDecl(state)) {
+ return true;
+ }
+ state->parse_state = copy;
+
return false;
}
@@ -1404,13 +1524,14 @@
ParseSubstitution(state, /*accept_std=*/false));
}
-// <template-args> ::= I <template-arg>+ E
+// <template-args> ::= I <template-arg>+ [Q <requires-clause expr>] E
static bool ParseTemplateArgs(State *state) {
ComplexityGuard guard(state);
if (guard.IsTooComplex()) return false;
ParseState copy = state->parse_state;
DisableAppend(state);
if (ParseOneCharToken(state, 'I') && OneOrMore(ParseTemplateArg, state) &&
+ Optional(ParseQRequiresClauseExpr(state)) &&
ParseOneCharToken(state, 'E')) {
RestoreAppend(state, copy.append);
MaybeAppend(state, "<>");
@@ -1420,7 +1541,8 @@
return false;
}
-// <template-arg> ::= <type>
+// <template-arg> ::= <template-param-decl> <template-arg>
+// ::= <type>
// ::= <expr-primary>
// ::= J <template-arg>* E # argument pack
// ::= X <expression> E
@@ -1523,6 +1645,12 @@
return true;
}
state->parse_state = copy;
+
+ if (ParseTemplateParamDecl(state) && ParseTemplateArg(state)) {
+ return true;
+ }
+ state->parse_state = copy;
+
return false;
}
@@ -1617,6 +1745,7 @@
// ::= <2-ary operator-name> <expression> <expression>
// ::= <3-ary operator-name> <expression> <expression> <expression>
// ::= cl <expression>+ E
+// ::= cp <simple-id> <expression>* E # Clang-specific.
// ::= cv <type> <expression> # type (expression)
// ::= cv <type> _ <expression>* E # type (expr-list)
// ::= st <type>
@@ -1639,14 +1768,23 @@
return true;
}
- // Object/function call expression.
ParseState copy = state->parse_state;
+
+ // Object/function call expression.
if (ParseTwoCharToken(state, "cl") && OneOrMore(ParseExpression, state) &&
ParseOneCharToken(state, 'E')) {
return true;
}
state->parse_state = copy;
+ // Clang-specific "cp <simple-id> <expression>* E"
+ // https://clang.llvm.org/doxygen/ItaniumMangle_8cpp_source.html#l04338
+ if (ParseTwoCharToken(state, "cp") && ParseSimpleId(state) &&
+ ZeroOrMore(ParseExpression, state) && ParseOneCharToken(state, 'E')) {
+ return true;
+ }
+ state->parse_state = copy;
+
// Function-param expression (level 0).
if (ParseTwoCharToken(state, "fp") && Optional(ParseCVQualifiers(state)) &&
Optional(ParseNumber(state, nullptr)) && ParseOneCharToken(state, '_')) {
@@ -1809,6 +1947,40 @@
return false;
}
+// Parses `Q <requires-clause expr>`.
+// If parsing fails, applies backtracking to `state`.
+//
+// This function covers two symbols instead of one for convenience,
+// because in LLVM's Itanium ABI mangling grammar, <requires-clause expr>
+// always appears after Q.
+//
+// Does not emit the parsed `requires` clause to simplify the implementation.
+// In other words, these two functions' mangled names will demangle identically:
+//
+// template <typename T>
+// int foo(T) requires IsIntegral<T>;
+//
+// vs.
+//
+// template <typename T>
+// int foo(T);
+static bool ParseQRequiresClauseExpr(State *state) {
+ ComplexityGuard guard(state);
+ if (guard.IsTooComplex()) return false;
+ ParseState copy = state->parse_state;
+ DisableAppend(state);
+
+ // <requires-clause expr> is just an <expression>: http://shortn/_9E1Ul0rIM8
+ if (ParseOneCharToken(state, 'Q') && ParseExpression(state)) {
+ RestoreAppend(state, copy.append);
+ return true;
+ }
+
+ // also restores append
+ state->parse_state = copy;
+ return false;
+}
+
// <local-name> ::= Z <(function) encoding> E <(entity) name> [<discriminator>]
// ::= Z <(function) encoding> E s [<discriminator>]
//
@@ -1937,13 +2109,29 @@
}
// The demangler entry point.
-bool Demangle(const char *mangled, char *out, int out_size) {
+bool Demangle(const char* mangled, char* out, size_t out_size) {
State state;
InitState(&state, mangled, out, out_size);
return ParseTopLevelMangledName(&state) && !Overflowed(&state) &&
state.parse_state.out_cur_idx > 0;
}
+std::string DemangleString(const char* mangled) {
+ std::string out;
+ int status = 0;
+ char* demangled = nullptr;
+#if ABSL_INTERNAL_HAS_CXA_DEMANGLE
+ demangled = abi::__cxa_demangle(mangled, nullptr, nullptr, &status);
+#endif
+ if (status == 0 && demangled != nullptr) {
+ out.append(demangled);
+ free(demangled);
+ } else {
+ out.append(mangled);
+ }
+ return out;
+}
+
} // namespace debugging_internal
ABSL_NAMESPACE_END
} // namespace absl
diff --git a/absl/debugging/internal/demangle.h b/absl/debugging/internal/demangle.h
index c314d9b..146d115 100644
--- a/absl/debugging/internal/demangle.h
+++ b/absl/debugging/internal/demangle.h
@@ -12,13 +12,23 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-// An async-signal-safe and thread-safe demangler for Itanium C++ ABI
-// (aka G++ V3 ABI).
+#ifndef ABSL_DEBUGGING_INTERNAL_DEMANGLE_H_
+#define ABSL_DEBUGGING_INTERNAL_DEMANGLE_H_
+
+#include <string>
+#include "absl/base/config.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace debugging_internal {
+
+// Demangle `mangled`. On success, return true and write the
+// demangled symbol name to `out`. Otherwise, return false.
+// `out` is modified even if demangling is unsuccessful.
//
-// The demangler is implemented to be used in async signal handlers to
-// symbolize stack traces. We cannot use libstdc++'s
-// abi::__cxa_demangle() in such signal handlers since it's not async
-// signal safe (it uses malloc() internally).
+// This function provides an alternative to libstdc++'s abi::__cxa_demangle,
+// which is not async signal safe (it uses malloc internally). It's intended to
+// be used in async signal handlers to symbolize stack traces.
//
// Note that this demangler doesn't support full demangling. More
// specifically, it doesn't print types of function parameters and
@@ -30,39 +40,31 @@
//
// Example:
//
-// | Mangled Name | The Demangler | abi::__cxa_demangle()
-// |---------------|---------------|-----------------------
-// | _Z1fv | f() | f()
-// | _Z1fi | f() | f(int)
-// | _Z3foo3bar | foo() | foo(bar)
-// | _Z1fIiEvi | f<>() | void f<int>(int)
-// | _ZN1N1fE | N::f | N::f
-// | _ZN3Foo3BarEv | Foo::Bar() | Foo::Bar()
-// | _Zrm1XS_" | operator%() | operator%(X, X)
-// | _ZN3FooC1Ev | Foo::Foo() | Foo::Foo()
-// | _Z1fSs | f() | f(std::basic_string<char,
-// | | | std::char_traits<char>,
-// | | | std::allocator<char> >)
+// | Mangled Name | Demangle | DemangleString
+// |---------------|-------------|-----------------------
+// | _Z1fv | f() | f()
+// | _Z1fi | f() | f(int)
+// | _Z3foo3bar | foo() | foo(bar)
+// | _Z1fIiEvi | f<>() | void f<int>(int)
+// | _ZN1N1fE | N::f | N::f
+// | _ZN3Foo3BarEv | Foo::Bar() | Foo::Bar()
+// | _Zrm1XS_" | operator%() | operator%(X, X)
+// | _ZN3FooC1Ev | Foo::Foo() | Foo::Foo()
+// | _Z1fSs | f() | f(std::basic_string<char,
+// | | | std::char_traits<char>,
+// | | | std::allocator<char> >)
//
// See the unit test for more examples.
//
// Note: we might want to write demanglers for ABIs other than Itanium
// C++ ABI in the future.
+bool Demangle(const char* mangled, char* out, size_t out_size);
+
+// A wrapper around `abi::__cxa_demangle()`. On success, returns the demangled
+// name. On failure, returns the input mangled name.
//
-
-#ifndef ABSL_DEBUGGING_INTERNAL_DEMANGLE_H_
-#define ABSL_DEBUGGING_INTERNAL_DEMANGLE_H_
-
-#include "absl/base/config.h"
-
-namespace absl {
-ABSL_NAMESPACE_BEGIN
-namespace debugging_internal {
-
-// Demangle `mangled`. On success, return true and write the
-// demangled symbol name to `out`. Otherwise, return false.
-// `out` is modified even if demangling is unsuccessful.
-bool Demangle(const char *mangled, char *out, int out_size);
+// This function is not async-signal-safe.
+std::string DemangleString(const char* mangled);
} // namespace debugging_internal
ABSL_NAMESPACE_END
diff --git a/absl/debugging/internal/demangle_test.cc b/absl/debugging/internal/demangle_test.cc
index 6b14290..2771d4d 100644
--- a/absl/debugging/internal/demangle_test.cc
+++ b/absl/debugging/internal/demangle_test.cc
@@ -17,10 +17,11 @@
#include <cstdlib>
#include <string>
+#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/base/config.h"
-#include "absl/base/internal/raw_logging.h"
#include "absl/debugging/internal/stack_consumption.h"
+#include "absl/log/log.h"
#include "absl/memory/memory.h"
namespace absl {
@@ -28,17 +29,190 @@
namespace debugging_internal {
namespace {
-// A wrapper function for Demangle() to make the unit test simple.
-static const char *DemangleIt(const char * const mangled) {
- static char demangled[4096];
- if (Demangle(mangled, demangled, sizeof(demangled))) {
- return demangled;
- } else {
- return mangled;
- }
+using ::testing::ContainsRegex;
+
+TEST(Demangle, FunctionTemplate) {
+ char tmp[100];
+
+ // template <typename T>
+ // int foo(T);
+ //
+ // foo<int>(5);
+ ASSERT_TRUE(Demangle("_Z3fooIiEiT_", tmp, sizeof(tmp)));
+ EXPECT_STREQ(tmp, "foo<>()");
}
-// Test corner cases of bounary conditions.
+TEST(Demangle, FunctionTemplateWithNesting) {
+ char tmp[100];
+
+ // template <typename T>
+ // int foo(T);
+ //
+ // foo<Wrapper<int>>({ .value = 5 });
+ ASSERT_TRUE(Demangle("_Z3fooI7WrapperIiEEiT_", tmp, sizeof(tmp)));
+ EXPECT_STREQ(tmp, "foo<>()");
+}
+
+TEST(Demangle, FunctionTemplateWithNonTypeParamConstraint) {
+ char tmp[100];
+
+ // template <std::integral T>
+ // int foo(T);
+ //
+ // foo<int>(5);
+ ASSERT_TRUE(Demangle("_Z3fooITkSt8integraliEiT_", tmp, sizeof(tmp)));
+ EXPECT_STREQ(tmp, "foo<>()");
+}
+
+TEST(Demangle, FunctionTemplateWithFunctionRequiresClause) {
+ char tmp[100];
+
+ // template <typename T>
+ // int foo() requires std::integral<T>;
+ //
+ // foo<int>();
+ ASSERT_TRUE(Demangle("_Z3fooIiEivQsr3stdE8integralIT_E", tmp, sizeof(tmp)));
+ EXPECT_STREQ(tmp, "foo<>()");
+}
+
+TEST(Demangle, FunctionWithTemplateParamRequiresClause) {
+ char tmp[100];
+
+ // template <typename T>
+ // requires std::integral<T>
+ // int foo();
+ //
+ // foo<int>();
+ ASSERT_TRUE(Demangle("_Z3fooIiQsr3stdE8integralIT_EEiv", tmp, sizeof(tmp)));
+ EXPECT_STREQ(tmp, "foo<>()");
+}
+
+TEST(Demangle, FunctionWithTemplateParamAndFunctionRequiresClauses) {
+ char tmp[100];
+
+ // template <typename T>
+ // requires std::integral<T>
+ // int foo() requires std::integral<T>;
+ //
+ // foo<int>();
+ ASSERT_TRUE(Demangle("_Z3fooIiQsr3stdE8integralIT_EEivQsr3stdE8integralIS0_E",
+ tmp, sizeof(tmp)));
+ EXPECT_STREQ(tmp, "foo<>()");
+}
+
+TEST(Demangle, FunctionTemplateBacktracksOnMalformedRequiresClause) {
+ char tmp[100];
+
+ // template <typename T>
+ // int foo(T);
+ //
+ // foo<int>(5);
+ // Except there's an extra `Q` where the mangled requires clause would be.
+ ASSERT_FALSE(Demangle("_Z3fooIiQEiT_", tmp, sizeof(tmp)));
+}
+
+TEST(Demangle, FunctionTemplateWithAutoParam) {
+ char tmp[100];
+
+ // template <auto>
+ // void foo();
+ //
+ // foo<1>();
+ ASSERT_TRUE(Demangle("_Z3fooITnDaLi1EEvv", tmp, sizeof(tmp)));
+ EXPECT_STREQ(tmp, "foo<>()");
+}
+
+TEST(Demangle, FunctionTemplateWithNonTypeParamPack) {
+ char tmp[100];
+
+ // template <int&..., typename T>
+ // void foo(T);
+ //
+ // foo(2);
+ ASSERT_TRUE(Demangle("_Z3fooITpTnRiJEiEvT0_", tmp, sizeof(tmp)));
+ EXPECT_STREQ(tmp, "foo<>()");
+}
+
+TEST(Demangle, FunctionTemplateTemplateParamWithConstrainedArg) {
+ char tmp[100];
+
+ // template <typename T>
+ // concept True = true;
+ //
+ // template <typename T> requires True<T>
+ // struct Fooer {};
+ //
+ // template <template <typename T> typename>
+ // void foo() {}
+ //
+ // foo<Fooer>();
+ ASSERT_TRUE(Demangle("_Z3fooITtTyE5FooerEvv", tmp, sizeof(tmp)));
+ EXPECT_STREQ(tmp, "foo<>()");
+}
+
+TEST(Demangle, NonTemplateBuiltinType) {
+ char tmp[100];
+
+ // void foo(__my_builtin_type t);
+ //
+ // foo({});
+ ASSERT_TRUE(Demangle("_Z3foou17__my_builtin_type", tmp, sizeof(tmp)));
+ EXPECT_STREQ(tmp, "foo()");
+}
+
+TEST(Demangle, SingleArgTemplateBuiltinType) {
+ char tmp[100];
+
+ // template <typename T>
+ // __my_builtin_type<T> foo();
+ //
+ // foo<int>();
+ ASSERT_TRUE(Demangle("_Z3fooIiEu17__my_builtin_typeIT_Ev", tmp, sizeof(tmp)));
+ EXPECT_STREQ(tmp, "foo<>()");
+}
+
+TEST(Demangle, FailsOnTwoArgTemplateBuiltinType) {
+ char tmp[100];
+
+ // template <typename T, typename U>
+ // __my_builtin_type<T, U> foo();
+ //
+ // foo<int, char>();
+ ASSERT_FALSE(
+ Demangle("_Z3fooIicEu17__my_builtin_typeIT_T0_Ev", tmp, sizeof(tmp)));
+}
+
+TEST(Demangle, TemplateTemplateParamSubstitution) {
+ char tmp[100];
+
+ // template <typename T>
+ // concept True = true;
+ //
+ // template<std::integral T, T> struct Foolable {};
+ // template<template<typename T, T> typename> void foo() {}
+ //
+ // template void foo<Foolable>();
+ ASSERT_TRUE(Demangle("_Z3fooITtTyTnTL0__E8FoolableEvv", tmp, sizeof(tmp)));
+ EXPECT_STREQ(tmp, "foo<>()");
+}
+
+TEST(Demangle, TemplateParamSubstitutionWithGenericLambda) {
+ char tmp[100];
+
+ // template <typename>
+ // struct Fooer {
+ // template <typename>
+ // void foo(decltype([](auto x, auto y) {})) {}
+ // };
+ //
+ // Fooer<int> f;
+ // f.foo<int>({});
+ ASSERT_TRUE(
+ Demangle("_ZN5FooerIiE3fooIiEEvNS0_UlTL0__TL0_0_E_E", tmp, sizeof(tmp)));
+ EXPECT_STREQ(tmp, "Fooer<>::foo<>()");
+}
+
+// Test corner cases of boundary conditions.
TEST(Demangle, CornerCases) {
char tmp[10];
EXPECT_TRUE(Demangle("_Z6foobarv", tmp, sizeof(tmp)));
@@ -102,6 +276,30 @@
EXPECT_FALSE(Demangle("_ZL3Foov.isra.2.constprop.", tmp, sizeof(tmp)));
}
+// Test the GNU abi_tag extension.
+TEST(Demangle, AbiTags) {
+ char tmp[80];
+
+ // Mangled name generated via:
+ // struct [[gnu::abi_tag("abc")]] A{};
+ // A a;
+ EXPECT_TRUE(Demangle("_Z1aB3abc", tmp, sizeof(tmp)));
+ EXPECT_STREQ("a[abi:abc]", tmp);
+
+ // Mangled name generated via:
+ // struct B {
+ // B [[gnu::abi_tag("xyz")]] (){};
+ // };
+ // B b;
+ EXPECT_TRUE(Demangle("_ZN1BC2B3xyzEv", tmp, sizeof(tmp)));
+ EXPECT_STREQ("B::B[abi:xyz]()", tmp);
+
+ // Mangled name generated via:
+ // [[gnu::abi_tag("foo", "bar")]] void C() {}
+ EXPECT_TRUE(Demangle("_Z1CB3barB3foov", tmp, sizeof(tmp)));
+ EXPECT_STREQ("C[abi:bar][abi:foo]()", tmp);
+}
+
// Tests that verify that Demangle footprint is within some limit.
// They are not to be run under sanitizers as the sanitizers increase
// stack consumption by about 4x.
@@ -127,7 +325,7 @@
int *stack_consumed) {
g_mangled = mangled;
*stack_consumed = GetSignalHandlerStackConsumption(DemangleSignalHandler);
- ABSL_RAW_LOG(INFO, "Stack consumption of Demangle: %d", *stack_consumed);
+ LOG(INFO) << "Stack consumption of Demangle: " << *stack_consumed;
return g_demangle_result;
}
@@ -213,6 +411,25 @@
TestOnInput(data.c_str());
}
+struct Base {
+ virtual ~Base() = default;
+};
+
+struct Derived : public Base {};
+
+TEST(DemangleStringTest, SupportsSymbolNameReturnedByTypeId) {
+ EXPECT_EQ(DemangleString(typeid(int).name()), "int");
+ // We want to test that `DemangleString` can demangle the symbol names
+ // returned by `typeid`, but without hard-coding the actual demangled values
+ // (because they are platform-specific).
+ EXPECT_THAT(
+ DemangleString(typeid(Base).name()),
+ ContainsRegex("absl.*debugging_internal.*anonymous namespace.*::Base"));
+ EXPECT_THAT(DemangleString(typeid(Derived).name()),
+ ContainsRegex(
+ "absl.*debugging_internal.*anonymous namespace.*::Derived"));
+}
+
} // namespace
} // namespace debugging_internal
ABSL_NAMESPACE_END
diff --git a/absl/debugging/internal/elf_mem_image.cc b/absl/debugging/internal/elf_mem_image.cc
index 24cc013..42dcd3c 100644
--- a/absl/debugging/internal/elf_mem_image.cc
+++ b/absl/debugging/internal/elf_mem_image.cc
@@ -22,6 +22,7 @@
#include <string.h>
#include <cassert>
#include <cstddef>
+#include "absl/base/config.h"
#include "absl/base/internal/raw_logging.h"
// From binutils/include/elf/common.h (this doesn't appear to be documented
@@ -43,11 +44,11 @@
namespace {
-#if __WORDSIZE == 32
+#if __SIZEOF_POINTER__ == 4
const int kElfClass = ELFCLASS32;
int ElfBind(const ElfW(Sym) *symbol) { return ELF32_ST_BIND(symbol->st_info); }
int ElfType(const ElfW(Sym) *symbol) { return ELF32_ST_TYPE(symbol->st_info); }
-#elif __WORDSIZE == 64
+#elif __SIZEOF_POINTER__ == 8
const int kElfClass = ELFCLASS64;
int ElfBind(const ElfW(Sym) *symbol) { return ELF64_ST_BIND(symbol->st_info); }
int ElfType(const ElfW(Sym) *symbol) { return ELF64_ST_TYPE(symbol->st_info); }
@@ -90,7 +91,7 @@
return 0;
}
// See http://www.caldera.com/developers/gabi/latest/ch5.dynamic.html#hash
- return hash_[1];
+ return static_cast<int>(hash_[1]);
}
const ElfW(Sym) *ElfMemImage::GetDynsym(int index) const {
@@ -104,11 +105,9 @@
}
const ElfW(Phdr) *ElfMemImage::GetPhdr(int index) const {
- ABSL_RAW_CHECK(index < ehdr_->e_phnum, "index out of range");
- return GetTableElement<ElfW(Phdr)>(ehdr_,
- ehdr_->e_phoff,
- ehdr_->e_phentsize,
- index);
+ ABSL_RAW_CHECK(index >= 0 && index < ehdr_->e_phnum, "index out of range");
+ return GetTableElement<ElfW(Phdr)>(ehdr_, ehdr_->e_phoff, ehdr_->e_phentsize,
+ static_cast<size_t>(index));
}
const char *ElfMemImage::GetDynstr(ElfW(Word) offset) const {
@@ -158,7 +157,8 @@
hash_ = nullptr;
strsize_ = 0;
verdefnum_ = 0;
- link_base_ = ~0L; // Sentinel: PT_LOAD .p_vaddr can't possibly be this.
+ // Sentinel: PT_LOAD .p_vaddr can't possibly be this.
+ link_base_ = ~ElfW(Addr){0}; // NOLINT(readability/braces)
if (!base) {
return;
}
@@ -175,17 +175,17 @@
}
switch (base_as_char[EI_DATA]) {
case ELFDATA2LSB: {
- if (__LITTLE_ENDIAN != __BYTE_ORDER) {
- assert(false);
- return;
- }
+#ifndef ABSL_IS_LITTLE_ENDIAN
+ assert(false);
+ return;
+#endif
break;
}
case ELFDATA2MSB: {
- if (__BIG_ENDIAN != __BYTE_ORDER) {
- assert(false);
- return;
- }
+#ifndef ABSL_IS_BIG_ENDIAN
+ assert(false);
+ return;
+#endif
break;
}
default: {
@@ -217,11 +217,11 @@
}
ptrdiff_t relocation =
base_as_char - reinterpret_cast<const char *>(link_base_);
- ElfW(Dyn) *dynamic_entry =
- reinterpret_cast<ElfW(Dyn) *>(dynamic_program_header->p_vaddr +
- relocation);
+ ElfW(Dyn)* dynamic_entry = reinterpret_cast<ElfW(Dyn)*>(
+ static_cast<intptr_t>(dynamic_program_header->p_vaddr) + relocation);
for (; dynamic_entry->d_tag != DT_NULL; ++dynamic_entry) {
- const ElfW(Xword) value = dynamic_entry->d_un.d_val + relocation;
+ const auto value =
+ static_cast<intptr_t>(dynamic_entry->d_un.d_val) + relocation;
switch (dynamic_entry->d_tag) {
case DT_HASH:
hash_ = reinterpret_cast<ElfW(Word) *>(value);
@@ -239,10 +239,10 @@
verdef_ = reinterpret_cast<ElfW(Verdef) *>(value);
break;
case DT_VERDEFNUM:
- verdefnum_ = dynamic_entry->d_un.d_val;
+ verdefnum_ = static_cast<size_t>(dynamic_entry->d_un.d_val);
break;
case DT_STRSZ:
- strsize_ = dynamic_entry->d_un.d_val;
+ strsize_ = static_cast<size_t>(dynamic_entry->d_un.d_val);
break;
default:
// Unrecognized entries explicitly ignored.
@@ -350,7 +350,11 @@
const ElfW(Versym) *version_symbol = image->GetVersym(index_);
ABSL_RAW_CHECK(symbol && version_symbol, "");
const char *const symbol_name = image->GetDynstr(symbol->st_name);
+#if defined(__NetBSD__)
+ const int version_index = version_symbol->vs_vers & VERSYM_VERSION;
+#else
const ElfW(Versym) version_index = version_symbol[0] & VERSYM_VERSION;
+#endif
const ElfW(Verdef) *version_definition = nullptr;
const char *version_name = "";
if (symbol->st_shndx == SHN_UNDEF) {
diff --git a/absl/debugging/internal/elf_mem_image.h b/absl/debugging/internal/elf_mem_image.h
index 46bfade..e7fe6ab 100644
--- a/absl/debugging/internal/elf_mem_image.h
+++ b/absl/debugging/internal/elf_mem_image.h
@@ -31,8 +31,10 @@
#error ABSL_HAVE_ELF_MEM_IMAGE cannot be directly set
#endif
-#if defined(__ELF__) && defined(__GLIBC__) && !defined(__native_client__) && \
- !defined(__asmjs__) && !defined(__wasm__)
+#if defined(__ELF__) && !defined(__OpenBSD__) && !defined(__QNX__) && \
+ !defined(__native_client__) && !defined(__asmjs__) && \
+ !defined(__wasm__) && !defined(__HAIKU__) && !defined(__sun) && \
+ !defined(__VXWORKS__) && !defined(__hexagon__)
#define ABSL_HAVE_ELF_MEM_IMAGE 1
#endif
@@ -40,6 +42,10 @@
#include <link.h> // for ElfW
+#if defined(__FreeBSD__) && !defined(ElfW)
+#define ElfW(x) __ElfN(x)
+#endif
+
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace debugging_internal {
diff --git a/absl/debugging/internal/examine_stack.cc b/absl/debugging/internal/examine_stack.cc
index 589a3ef..3dd6ba1 100644
--- a/absl/debugging/internal/examine_stack.cc
+++ b/absl/debugging/internal/examine_stack.cc
@@ -20,7 +20,16 @@
#include <unistd.h>
#endif
-#ifdef __APPLE__
+#include "absl/base/config.h"
+
+#ifdef ABSL_HAVE_MMAP
+#include <sys/mman.h>
+#if defined(MAP_ANON) && !defined(MAP_ANONYMOUS)
+#define MAP_ANONYMOUS MAP_ANON
+#endif
+#endif
+
+#if defined(__linux__) || defined(__APPLE__)
#include <sys/ucontext.h>
#endif
@@ -37,10 +46,115 @@
ABSL_NAMESPACE_BEGIN
namespace debugging_internal {
+namespace {
+constexpr int kDefaultDumpStackFramesLimit = 64;
+// The %p field width for printf() functions is two characters per byte,
+// and two extra for the leading "0x".
+constexpr int kPrintfPointerFieldWidth = 2 + 2 * sizeof(void*);
+
+ABSL_CONST_INIT SymbolizeUrlEmitter debug_stack_trace_hook = nullptr;
+
+// Async-signal safe mmap allocator.
+void* Allocate(size_t num_bytes) {
+#ifdef ABSL_HAVE_MMAP
+ void* p = ::mmap(nullptr, num_bytes, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ return p == MAP_FAILED ? nullptr : p;
+#else
+ (void)num_bytes;
+ return nullptr;
+#endif // ABSL_HAVE_MMAP
+}
+
+void Deallocate(void* p, size_t size) {
+#ifdef ABSL_HAVE_MMAP
+ ::munmap(p, size);
+#else
+ (void)p;
+ (void)size;
+#endif // ABSL_HAVE_MMAP
+}
+
+// Print a program counter only.
+void DumpPC(OutputWriter* writer, void* writer_arg, void* const pc,
+ const char* const prefix) {
+ char buf[100];
+ snprintf(buf, sizeof(buf), "%s@ %*p\n", prefix, kPrintfPointerFieldWidth, pc);
+ writer(buf, writer_arg);
+}
+
+// Print a program counter and the corresponding stack frame size.
+void DumpPCAndFrameSize(OutputWriter* writer, void* writer_arg, void* const pc,
+ int framesize, const char* const prefix) {
+ char buf[100];
+ if (framesize <= 0) {
+ snprintf(buf, sizeof(buf), "%s@ %*p (unknown)\n", prefix,
+ kPrintfPointerFieldWidth, pc);
+ } else {
+ snprintf(buf, sizeof(buf), "%s@ %*p %9d\n", prefix,
+ kPrintfPointerFieldWidth, pc, framesize);
+ }
+ writer(buf, writer_arg);
+}
+
+// Print a program counter and the corresponding symbol.
+void DumpPCAndSymbol(OutputWriter* writer, void* writer_arg, void* const pc,
+ const char* const prefix) {
+ char tmp[1024];
+ const char* symbol = "(unknown)";
+ // Symbolizes the previous address of pc because pc may be in the
+ // next function. The overrun happens when the function ends with
+ // a call to a function annotated noreturn (e.g. CHECK).
+ // If symbolization of pc-1 fails, also try pc on the off-chance
+ // that we crashed on the first instruction of a function (that
+ // actually happens very often for e.g. __restore_rt).
+ const uintptr_t prev_pc = reinterpret_cast<uintptr_t>(pc) - 1;
+ if (absl::Symbolize(reinterpret_cast<const char*>(prev_pc), tmp,
+ sizeof(tmp)) ||
+ absl::Symbolize(pc, tmp, sizeof(tmp))) {
+ symbol = tmp;
+ }
+ char buf[1024];
+ snprintf(buf, sizeof(buf), "%s@ %*p %s\n", prefix, kPrintfPointerFieldWidth,
+ pc, symbol);
+ writer(buf, writer_arg);
+}
+
+// Print a program counter, its stack frame size, and its symbol name.
+// Note that there is a separate symbolize_pc argument. Return addresses may be
+// at the end of the function, and this allows the caller to back up from pc if
+// appropriate.
+void DumpPCAndFrameSizeAndSymbol(OutputWriter* writer, void* writer_arg,
+ void* const pc, void* const symbolize_pc,
+ int framesize, const char* const prefix) {
+ char tmp[1024];
+ const char* symbol = "(unknown)";
+ if (absl::Symbolize(symbolize_pc, tmp, sizeof(tmp))) {
+ symbol = tmp;
+ }
+ char buf[1024];
+ if (framesize <= 0) {
+ snprintf(buf, sizeof(buf), "%s@ %*p (unknown) %s\n", prefix,
+ kPrintfPointerFieldWidth, pc, symbol);
+ } else {
+ snprintf(buf, sizeof(buf), "%s@ %*p %9d %s\n", prefix,
+ kPrintfPointerFieldWidth, pc, framesize, symbol);
+ }
+ writer(buf, writer_arg);
+}
+
+} // namespace
+
+void RegisterDebugStackTraceHook(SymbolizeUrlEmitter hook) {
+ debug_stack_trace_hook = hook;
+}
+
+SymbolizeUrlEmitter GetDebugStackTraceHook() { return debug_stack_trace_hook; }
+
// Returns the program counter from signal context, nullptr if
// unknown. vuc is a ucontext_t*. We use void* to avoid the use of
// ucontext_t on non-POSIX systems.
-void* GetProgramCounter(void* vuc) {
+void* GetProgramCounter(void* const vuc) {
#ifdef __linux__
if (vuc != nullptr) {
ucontext_t* context = reinterpret_cast<ucontext_t*>(vuc);
@@ -82,6 +196,8 @@
return reinterpret_cast<void*>(context->uc_mcontext.gregs[16]);
#elif defined(__e2k__)
return reinterpret_cast<void*>(context->uc_mcontext.cr0_hi);
+#elif defined(__loongarch__)
+ return reinterpret_cast<void*>(context->uc_mcontext.__pc);
#else
#error "Undefined Architecture."
#endif
@@ -120,59 +236,17 @@
return nullptr;
}
-// The %p field width for printf() functions is two characters per byte,
-// and two extra for the leading "0x".
-static constexpr int kPrintfPointerFieldWidth = 2 + 2 * sizeof(void*);
-
-// Print a program counter, its stack frame size, and its symbol name.
-// Note that there is a separate symbolize_pc argument. Return addresses may be
-// at the end of the function, and this allows the caller to back up from pc if
-// appropriate.
-static void DumpPCAndFrameSizeAndSymbol(void (*writerfn)(const char*, void*),
- void* writerfn_arg, void* pc,
- void* symbolize_pc, int framesize,
- const char* const prefix) {
- char tmp[1024];
- const char* symbol = "(unknown)";
- if (absl::Symbolize(symbolize_pc, tmp, sizeof(tmp))) {
- symbol = tmp;
- }
- char buf[1024];
- if (framesize <= 0) {
- snprintf(buf, sizeof(buf), "%s@ %*p (unknown) %s\n", prefix,
- kPrintfPointerFieldWidth, pc, symbol);
- } else {
- snprintf(buf, sizeof(buf), "%s@ %*p %9d %s\n", prefix,
- kPrintfPointerFieldWidth, pc, framesize, symbol);
- }
- writerfn(buf, writerfn_arg);
-}
-
-// Print a program counter and the corresponding stack frame size.
-static void DumpPCAndFrameSize(void (*writerfn)(const char*, void*),
- void* writerfn_arg, void* pc, int framesize,
- const char* const prefix) {
- char buf[100];
- if (framesize <= 0) {
- snprintf(buf, sizeof(buf), "%s@ %*p (unknown)\n", prefix,
- kPrintfPointerFieldWidth, pc);
- } else {
- snprintf(buf, sizeof(buf), "%s@ %*p %9d\n", prefix,
- kPrintfPointerFieldWidth, pc, framesize);
- }
- writerfn(buf, writerfn_arg);
-}
-
-void DumpPCAndFrameSizesAndStackTrace(
- void* pc, void* const stack[], int frame_sizes[], int depth,
- int min_dropped_frames, bool symbolize_stacktrace,
- void (*writerfn)(const char*, void*), void* writerfn_arg) {
+void DumpPCAndFrameSizesAndStackTrace(void* const pc, void* const stack[],
+ int frame_sizes[], int depth,
+ int min_dropped_frames,
+ bool symbolize_stacktrace,
+ OutputWriter* writer, void* writer_arg) {
if (pc != nullptr) {
// We don't know the stack frame size for PC, use 0.
if (symbolize_stacktrace) {
- DumpPCAndFrameSizeAndSymbol(writerfn, writerfn_arg, pc, pc, 0, "PC: ");
+ DumpPCAndFrameSizeAndSymbol(writer, writer_arg, pc, pc, 0, "PC: ");
} else {
- DumpPCAndFrameSize(writerfn, writerfn_arg, pc, 0, "PC: ");
+ DumpPCAndFrameSize(writer, writer_arg, pc, 0, "PC: ");
}
}
for (int i = 0; i < depth; i++) {
@@ -182,22 +256,65 @@
// call to a function annotated noreturn (e.g. CHECK). Note that we don't
// do this for pc above, as the adjustment is only correct for return
// addresses.
- DumpPCAndFrameSizeAndSymbol(writerfn, writerfn_arg, stack[i],
+ DumpPCAndFrameSizeAndSymbol(writer, writer_arg, stack[i],
reinterpret_cast<char*>(stack[i]) - 1,
frame_sizes[i], " ");
} else {
- DumpPCAndFrameSize(writerfn, writerfn_arg, stack[i], frame_sizes[i],
- " ");
+ DumpPCAndFrameSize(writer, writer_arg, stack[i], frame_sizes[i], " ");
}
}
if (min_dropped_frames > 0) {
char buf[100];
snprintf(buf, sizeof(buf), " @ ... and at least %d more frames\n",
min_dropped_frames);
- writerfn(buf, writerfn_arg);
+ writer(buf, writer_arg);
}
}
+// Dump current stack trace as directed by writer.
+// Make sure this function is not inlined to avoid skipping too many top frames.
+ABSL_ATTRIBUTE_NOINLINE
+void DumpStackTrace(int min_dropped_frames, int max_num_frames,
+ bool symbolize_stacktrace, OutputWriter* writer,
+ void* writer_arg) {
+ // Print stack trace
+ void* stack_buf[kDefaultDumpStackFramesLimit];
+ void** stack = stack_buf;
+ int num_stack = kDefaultDumpStackFramesLimit;
+ size_t allocated_bytes = 0;
+
+ if (num_stack >= max_num_frames) {
+ // User requested fewer frames than we already have space for.
+ num_stack = max_num_frames;
+ } else {
+ const size_t needed_bytes =
+ static_cast<size_t>(max_num_frames) * sizeof(stack[0]);
+ void* p = Allocate(needed_bytes);
+ if (p != nullptr) { // We got the space.
+ num_stack = max_num_frames;
+ stack = reinterpret_cast<void**>(p);
+ allocated_bytes = needed_bytes;
+ }
+ }
+
+ int depth = absl::GetStackTrace(stack, num_stack, min_dropped_frames + 1);
+ for (int i = 0; i < depth; i++) {
+ if (symbolize_stacktrace) {
+ DumpPCAndSymbol(writer, writer_arg, stack[static_cast<size_t>(i)],
+ " ");
+ } else {
+ DumpPC(writer, writer_arg, stack[static_cast<size_t>(i)], " ");
+ }
+ }
+
+ auto hook = GetDebugStackTraceHook();
+ if (hook != nullptr) {
+ (*hook)(stack, depth, writer, writer_arg);
+ }
+
+ if (allocated_bytes != 0) Deallocate(stack, allocated_bytes);
+}
+
} // namespace debugging_internal
ABSL_NAMESPACE_END
} // namespace absl
diff --git a/absl/debugging/internal/examine_stack.h b/absl/debugging/internal/examine_stack.h
index 3933691..190af87 100644
--- a/absl/debugging/internal/examine_stack.h
+++ b/absl/debugging/internal/examine_stack.h
@@ -23,17 +23,39 @@
ABSL_NAMESPACE_BEGIN
namespace debugging_internal {
+// Type of function used for printing in stack trace dumping, etc.
+// We avoid closures to keep things simple.
+typedef void OutputWriter(const char*, void*);
+
+// RegisterDebugStackTraceHook() allows to register a single routine
+// `hook` that is called each time DumpStackTrace() is called.
+// `hook` may be called from a signal handler.
+typedef void (*SymbolizeUrlEmitter)(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();
+
// 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.
-void* GetProgramCounter(void* vuc);
+void* GetProgramCounter(void* const vuc);
-// Uses `writerfn` to dump the program counter, stack trace, and stack
+// Uses `writer` to dump the program counter, stack trace, and stack
// frame sizes.
-void DumpPCAndFrameSizesAndStackTrace(
- void* pc, void* const stack[], int frame_sizes[], int depth,
- int min_dropped_frames, bool symbolize_stacktrace,
- void (*writerfn)(const char*, void*), void* writerfn_arg);
+void DumpPCAndFrameSizesAndStackTrace(void* const pc, void* const stack[],
+ int frame_sizes[], int depth,
+ int min_dropped_frames,
+ bool symbolize_stacktrace,
+ OutputWriter* writer, void* writer_arg);
+
+// Dump current stack trace omitting the topmost `min_dropped_frames` stack
+// frames.
+void DumpStackTrace(int min_dropped_frames, int max_num_frames,
+ bool symbolize_stacktrace, OutputWriter* writer,
+ void* writer_arg);
} // namespace debugging_internal
ABSL_NAMESPACE_END
diff --git a/absl/debugging/internal/stack_consumption.cc b/absl/debugging/internal/stack_consumption.cc
index e3dd51c..b54a1b2 100644
--- a/absl/debugging/internal/stack_consumption.cc
+++ b/absl/debugging/internal/stack_consumption.cc
@@ -18,14 +18,17 @@
#ifdef ABSL_INTERNAL_HAVE_DEBUGGING_STACK_CONSUMPTION
#include <signal.h>
+#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
-#include <string.h>
-
#include "absl/base/attributes.h"
#include "absl/base/internal/raw_logging.h"
+#if defined(MAP_ANON) && !defined(MAP_ANONYMOUS)
+#define MAP_ANONYMOUS MAP_ANON
+#endif
+
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace debugging_internal {
@@ -43,7 +46,7 @@
// unspecified. Therefore, instead we hardcode the direction of the
// stack on platforms we know about.
#if defined(__i386__) || defined(__x86_64__) || defined(__ppc__) || \
- defined(__aarch64__)
+ defined(__aarch64__) || defined(__riscv)
constexpr bool kStackGrowsDown = true;
#else
#error Need to define kStackGrowsDown
@@ -162,7 +165,7 @@
// versions of musl have a bug that rejects ss_size==0. Work around this by
// setting ss_size to MINSIGSTKSZ, which should be ignored by the kernel
// when SS_DISABLE is set.
- old_sigstk.ss_size = MINSIGSTKSZ;
+ old_sigstk.ss_size = static_cast<size_t>(MINSIGSTKSZ);
}
ABSL_RAW_CHECK(sigaltstack(&old_sigstk, nullptr) == 0,
"sigaltstack() failed");
@@ -182,4 +185,22 @@
ABSL_NAMESPACE_END
} // namespace absl
+#else
+
+// https://github.com/abseil/abseil-cpp/issues/1465
+// CMake builds on Apple platforms error when libraries are empty.
+// Our CMake configuration can avoid this error on header-only libraries,
+// but since this library is conditionally empty, including a single
+// variable is an easy workaround.
+#ifdef __APPLE__
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace debugging_internal {
+extern const char kAvoidEmptyStackConsumptionLibraryWarning;
+const char kAvoidEmptyStackConsumptionLibraryWarning = 0;
+} // namespace debugging_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+#endif // __APPLE__
+
#endif // ABSL_INTERNAL_HAVE_DEBUGGING_STACK_CONSUMPTION
diff --git a/absl/debugging/internal/stack_consumption.h b/absl/debugging/internal/stack_consumption.h
index 2b5e715..f41b64c 100644
--- a/absl/debugging/internal/stack_consumption.h
+++ b/absl/debugging/internal/stack_consumption.h
@@ -26,7 +26,7 @@
#error ABSL_INTERNAL_HAVE_DEBUGGING_STACK_CONSUMPTION cannot be set directly
#elif !defined(__APPLE__) && !defined(_WIN32) && \
(defined(__i386__) || defined(__x86_64__) || defined(__ppc__) || \
- defined(__aarch64__))
+ defined(__aarch64__) || defined(__riscv))
#define ABSL_INTERNAL_HAVE_DEBUGGING_STACK_CONSUMPTION 1
namespace absl {
diff --git a/absl/debugging/internal/stack_consumption_test.cc b/absl/debugging/internal/stack_consumption_test.cc
index 80445bf..0255ac8 100644
--- a/absl/debugging/internal/stack_consumption_test.cc
+++ b/absl/debugging/internal/stack_consumption_test.cc
@@ -20,7 +20,7 @@
#include <string.h>
#include "gtest/gtest.h"
-#include "absl/base/internal/raw_logging.h"
+#include "absl/log/log.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
@@ -33,7 +33,7 @@
// Never true, but prevents compiler from optimizing buf out.
if (signo == 0) {
- ABSL_RAW_LOG(INFO, "%p", static_cast<void*>(buf));
+ LOG(INFO) << static_cast<void*>(buf);
}
}
diff --git a/absl/debugging/internal/stacktrace_aarch64-inl.inc b/absl/debugging/internal/stacktrace_aarch64-inl.inc
index f4859d7..1e07e04 100644
--- a/absl/debugging/internal/stacktrace_aarch64-inl.inc
+++ b/absl/debugging/internal/stacktrace_aarch64-inl.inc
@@ -4,6 +4,7 @@
// Generate stack tracer for aarch64
#if defined(__linux__)
+#include <signal.h>
#include <sys/mman.h>
#include <ucontext.h>
#include <unistd.h>
@@ -13,13 +14,18 @@
#include <cassert>
#include <cstdint>
#include <iostream>
+#include <limits>
#include "absl/base/attributes.h"
#include "absl/debugging/internal/address_is_readable.h"
#include "absl/debugging/internal/vdso_support.h" // a no-op on non-elf or non-glibc systems
#include "absl/debugging/stacktrace.h"
-static const uintptr_t kUnknownFrameSize = 0;
+static const size_t kUnknownFrameSize = 0;
+// Stack end to use when we don't know the actual stack end
+// (effectively just the end of address space).
+constexpr uintptr_t kUnknownStackEnd =
+ std::numeric_limits<size_t>::max() - sizeof(void *);
#if defined(__linux__)
// Returns the address of the VDSO __kernel_rt_sigreturn function, if present.
@@ -65,11 +71,26 @@
// Compute the size of a stack frame in [low..high). We assume that
// low < high. Return size of kUnknownFrameSize.
template<typename T>
-static inline uintptr_t ComputeStackFrameSize(const T* low,
- const T* high) {
+static size_t ComputeStackFrameSize(const T* low,
+ const T* high) {
const char* low_char_ptr = reinterpret_cast<const char *>(low);
const char* high_char_ptr = reinterpret_cast<const char *>(high);
- return low < high ? high_char_ptr - low_char_ptr : kUnknownFrameSize;
+ return low < high ? static_cast<size_t>(high_char_ptr - low_char_ptr)
+ : kUnknownFrameSize;
+}
+
+// Saves stack info that is expensive to calculate to avoid recalculating per frame.
+struct StackInfo {
+ uintptr_t stack_low;
+ uintptr_t stack_high;
+ uintptr_t sig_stack_low;
+ uintptr_t sig_stack_high;
+};
+
+static bool InsideSignalStack(void** ptr, const StackInfo* stack_info) {
+ uintptr_t comparable_ptr = reinterpret_cast<uintptr_t>(ptr);
+ return (comparable_ptr >= stack_info->sig_stack_low &&
+ comparable_ptr < stack_info->sig_stack_high);
}
// Given a pointer to a stack frame, locate and return the calling
@@ -78,10 +99,10 @@
// "STRICT_UNWINDING") to reduce the chance that a bad pointer is returned.
template<bool STRICT_UNWINDING, bool WITH_CONTEXT>
ABSL_ATTRIBUTE_NO_SANITIZE_ADDRESS // May read random elements from stack.
-ABSL_ATTRIBUTE_NO_SANITIZE_MEMORY // May read random elements from stack.
-static void **NextStackFrame(void **old_frame_pointer, const void *uc) {
+ABSL_ATTRIBUTE_NO_SANITIZE_MEMORY // May read random elements from stack.
+static void **NextStackFrame(void **old_frame_pointer, const void *uc,
+ const StackInfo *stack_info) {
void **new_frame_pointer = reinterpret_cast<void**>(*old_frame_pointer);
- bool check_frame_size = true;
#if defined(__linux__)
if (WITH_CONTEXT && uc != nullptr) {
@@ -93,41 +114,72 @@
void **const pre_signal_frame_pointer =
reinterpret_cast<void **>(ucv->uc_mcontext.regs[29]);
- // Check that alleged frame pointer is actually readable. This is to
- // prevent "double fault" in case we hit the first fault due to e.g.
- // stack corruption.
- if (!absl::debugging_internal::AddressIsReadable(
- pre_signal_frame_pointer))
- return nullptr;
-
- // Alleged frame pointer is readable, use it for further unwinding.
- new_frame_pointer = pre_signal_frame_pointer;
-
- // Skip frame size check if we return from a signal. We may be using a
- // an alternate stack for signals.
- check_frame_size = false;
- }
+ // The most recent signal always needs special handling to find the frame
+ // pointer, but a nested signal does not. If pre_signal_frame_pointer is
+ // earlier in the stack than the old_frame_pointer, then use it. If it is
+ // later, then we have already unwound through it and it needs no special
+ // handling.
+ if (pre_signal_frame_pointer >= old_frame_pointer) {
+ new_frame_pointer = pre_signal_frame_pointer;
+ }
}
#endif
- // aarch64 ABI requires stack pointer to be 16-byte-aligned.
- if ((reinterpret_cast<uintptr_t>(new_frame_pointer) & 15) != 0)
+ // The frame pointer should be 8-byte aligned.
+ if ((reinterpret_cast<uintptr_t>(new_frame_pointer) & 7) != 0)
return nullptr;
- // Check frame size. In strict mode, we assume frames to be under
- // 100,000 bytes. In non-strict mode, we relax the limit to 1MB.
- if (check_frame_size) {
- const uintptr_t max_size = STRICT_UNWINDING ? 100000 : 1000000;
- const uintptr_t frame_size =
+ // Check that alleged frame pointer is actually readable. This is to
+ // prevent "double fault" in case we hit the first fault due to e.g.
+ // stack corruption.
+ if (!absl::debugging_internal::AddressIsReadable(
+ new_frame_pointer))
+ return nullptr;
+ }
+
+ // Only check the size if both frames are in the same stack.
+ if (InsideSignalStack(new_frame_pointer, stack_info) ==
+ InsideSignalStack(old_frame_pointer, stack_info)) {
+ // Check frame size. In strict mode, we assume frames to be under
+ // 100,000 bytes. In non-strict mode, we relax the limit to 1MB.
+ const size_t max_size = STRICT_UNWINDING ? 100000 : 1000000;
+ const size_t frame_size =
ComputeStackFrameSize(old_frame_pointer, new_frame_pointer);
- if (frame_size == kUnknownFrameSize || frame_size > max_size)
- return nullptr;
+ if (frame_size == kUnknownFrameSize)
+ return nullptr;
+ // A very large frame may mean corrupt memory or an erroneous frame
+ // pointer. But also maybe just a plain-old large frame. Assume that if the
+ // frame is within a known stack, then it is valid.
+ if (frame_size > max_size) {
+ size_t stack_low = stack_info->stack_low;
+ size_t stack_high = stack_info->stack_high;
+ if (InsideSignalStack(new_frame_pointer, stack_info)) {
+ stack_low = stack_info->sig_stack_low;
+ stack_high = stack_info->sig_stack_high;
+ }
+ if (stack_high < kUnknownStackEnd &&
+ static_cast<size_t>(getpagesize()) < stack_low) {
+ const uintptr_t new_fp_u =
+ reinterpret_cast<uintptr_t>(new_frame_pointer);
+ // Stack bounds are known.
+ if (!(stack_low < new_fp_u && new_fp_u <= stack_high)) {
+ // new_frame_pointer is not within a known stack.
+ return nullptr;
+ }
+ } else {
+ // Stack bounds are unknown, prefer truncated stack to possible crash.
+ return nullptr;
+ }
+ }
}
return new_frame_pointer;
}
template <bool IS_STACK_FRAMES, bool IS_WITH_CONTEXT>
+// We count on the bottom frame being this one. See the comment
+// at prev_return_address
+ABSL_ATTRIBUTE_NOINLINE
ABSL_ATTRIBUTE_NO_SANITIZE_ADDRESS // May read random elements from stack.
ABSL_ATTRIBUTE_NO_SANITIZE_MEMORY // May read random elements from stack.
static int UnwindImpl(void** result, int* sizes, int max_depth, int skip_count,
@@ -137,51 +189,67 @@
#else
# error reading stack point not yet supported on this platform.
#endif
-
skip_count++; // Skip the frame for this function.
int n = 0;
+ // Assume that the first page is not stack.
+ StackInfo stack_info;
+ stack_info.stack_low = static_cast<uintptr_t>(getpagesize());
+ stack_info.stack_high = kUnknownStackEnd;
+ stack_info.sig_stack_low = stack_info.stack_low;
+ stack_info.sig_stack_high = kUnknownStackEnd;
+
// The frame pointer points to low address of a frame. The first 64-bit
// word of a frame points to the next frame up the call chain, which normally
// is just after the high address of the current frame. The second word of
- // a frame contains return adress of to the caller. To find a pc value
+ // a frame contains return address of to the caller. To find a pc value
// associated with the current frame, we need to go down a level in the call
// chain. So we remember return the address of the last frame seen. This
// does not work for the first stack frame, which belongs to UnwindImp() but
// we skip the frame for UnwindImp() anyway.
void* prev_return_address = nullptr;
+ // The nth frame size is the difference between the nth frame pointer and the
+ // the frame pointer below it in the call chain. There is no frame below the
+ // leaf frame, but this function is the leaf anyway, and we skip it.
+ void** prev_frame_pointer = nullptr;
- while (frame_pointer && n < max_depth) {
- // The absl::GetStackFrames routine is called when we are in some
- // informational context (the failure signal handler for example).
- // Use the non-strict unwinding rules to produce a stack trace
- // that is as complete as possible (even if it contains a few bogus
- // entries in some rare cases).
- void **next_frame_pointer =
- NextStackFrame<!IS_STACK_FRAMES, IS_WITH_CONTEXT>(frame_pointer, ucp);
-
+ while (frame_pointer && n < max_depth) {
if (skip_count > 0) {
skip_count--;
} else {
result[n] = prev_return_address;
if (IS_STACK_FRAMES) {
- sizes[n] = ComputeStackFrameSize(frame_pointer, next_frame_pointer);
+ sizes[n] = static_cast<int>(
+ ComputeStackFrameSize(prev_frame_pointer, frame_pointer));
}
n++;
}
prev_return_address = frame_pointer[1];
- frame_pointer = next_frame_pointer;
+ prev_frame_pointer = frame_pointer;
+ // The absl::GetStackFrames routine is called when we are in some
+ // informational context (the failure signal handler for example).
+ // Use the non-strict unwinding rules to produce a stack trace
+ // that is as complete as possible (even if it contains a few bogus
+ // entries in some rare cases).
+ frame_pointer = NextStackFrame<!IS_STACK_FRAMES, IS_WITH_CONTEXT>(
+ frame_pointer, ucp, &stack_info);
}
+
if (min_dropped_frames != nullptr) {
// Implementation detail: we clamp the max of frames we are willing to
// count, so as not to spend too much time in the loop below.
const int kMaxUnwind = 200;
- int j = 0;
- for (; frame_pointer != nullptr && j < kMaxUnwind; j++) {
- frame_pointer =
- NextStackFrame<!IS_STACK_FRAMES, IS_WITH_CONTEXT>(frame_pointer, ucp);
+ int num_dropped_frames = 0;
+ for (int j = 0; frame_pointer != nullptr && j < kMaxUnwind; j++) {
+ if (skip_count > 0) {
+ skip_count--;
+ } else {
+ num_dropped_frames++;
+ }
+ frame_pointer = NextStackFrame<!IS_STACK_FRAMES, IS_WITH_CONTEXT>(
+ frame_pointer, ucp, &stack_info);
}
- *min_dropped_frames = j;
+ *min_dropped_frames = num_dropped_frames;
}
return n;
}
diff --git a/absl/debugging/internal/stacktrace_arm-inl.inc b/absl/debugging/internal/stacktrace_arm-inl.inc
index 2a1bf2e..102a2a1 100644
--- a/absl/debugging/internal/stacktrace_arm-inl.inc
+++ b/absl/debugging/internal/stacktrace_arm-inl.inc
@@ -112,11 +112,16 @@
// Implementation detail: we clamp the max of frames we are willing to
// count, so as not to spend too much time in the loop below.
const int kMaxUnwind = 200;
- int j = 0;
- for (; sp != nullptr && j < kMaxUnwind; j++) {
+ int num_dropped_frames = 0;
+ for (int j = 0; sp != nullptr && j < kMaxUnwind; j++) {
+ if (skip_count > 0) {
+ skip_count--;
+ } else {
+ num_dropped_frames++;
+ }
sp = NextStackFrame<!IS_STACK_FRAMES>(sp);
}
- *min_dropped_frames = j;
+ *min_dropped_frames = num_dropped_frames;
}
return n;
}
diff --git a/absl/debugging/internal/stacktrace_config.h b/absl/debugging/internal/stacktrace_config.h
index 6cceef2..3929b1b 100644
--- a/absl/debugging/internal/stacktrace_config.h
+++ b/absl/debugging/internal/stacktrace_config.h
@@ -35,9 +35,10 @@
// Thread local support required for UnwindImpl.
#define ABSL_STACKTRACE_INL_HEADER \
"absl/debugging/internal/stacktrace_generic-inl.inc"
-#endif
+#endif // defined(ABSL_HAVE_THREAD_LOCAL)
-#elif defined(__EMSCRIPTEN__)
+// Emscripten stacktraces rely on JS. Do not use them in standalone mode.
+#elif defined(__EMSCRIPTEN__) && !defined(STANDALONE_WASM)
#define ABSL_STACKTRACE_INL_HEADER \
"absl/debugging/internal/stacktrace_emscripten-inl.inc"
@@ -55,7 +56,7 @@
// Note: When using glibc this may require -funwind-tables to function properly.
#define ABSL_STACKTRACE_INL_HEADER \
"absl/debugging/internal/stacktrace_generic-inl.inc"
-#endif
+#endif // __has_include(<execinfo.h>)
#elif defined(__i386__) || defined(__x86_64__)
#define ABSL_STACKTRACE_INL_HEADER \
"absl/debugging/internal/stacktrace_x86-inl.inc"
@@ -65,14 +66,18 @@
#elif defined(__aarch64__)
#define ABSL_STACKTRACE_INL_HEADER \
"absl/debugging/internal/stacktrace_aarch64-inl.inc"
+#elif defined(__riscv)
+#define ABSL_STACKTRACE_INL_HEADER \
+ "absl/debugging/internal/stacktrace_riscv-inl.inc"
#elif defined(__has_include)
#if __has_include(<execinfo.h>)
// Note: When using glibc this may require -funwind-tables to function properly.
#define ABSL_STACKTRACE_INL_HEADER \
"absl/debugging/internal/stacktrace_generic-inl.inc"
-#endif
-#endif
-#endif
+#endif // __has_include(<execinfo.h>)
+#endif // defined(__has_include)
+
+#endif // defined(__linux__) && !defined(__ANDROID__)
// Fallback to the empty implementation.
#if !defined(ABSL_STACKTRACE_INL_HEADER)
diff --git a/absl/debugging/internal/stacktrace_generic-inl.inc b/absl/debugging/internal/stacktrace_generic-inl.inc
index b2792a1..5fa169a 100644
--- a/absl/debugging/internal/stacktrace_generic-inl.inc
+++ b/absl/debugging/internal/stacktrace_generic-inl.inc
@@ -80,7 +80,7 @@
if (IS_STACK_FRAMES) {
// No implementation for finding out the stack frame sizes yet.
- memset(sizes, 0, sizeof(*sizes) * result_count);
+ memset(sizes, 0, sizeof(*sizes) * static_cast<size_t>(result_count));
}
if (min_dropped_frames != nullptr) {
if (size - skip_count - max_depth > 0) {
diff --git a/absl/debugging/internal/stacktrace_powerpc-inl.inc b/absl/debugging/internal/stacktrace_powerpc-inl.inc
index cf8c051..a49ed2f 100644
--- a/absl/debugging/internal/stacktrace_powerpc-inl.inc
+++ b/absl/debugging/internal/stacktrace_powerpc-inl.inc
@@ -57,7 +57,7 @@
// This check is in case the compiler doesn't define _CALL_SYSV.
return *(sp+1);
#else
-#error Need to specify the PPC ABI for your archiecture.
+#error Need to specify the PPC ABI for your architecture.
#endif
}
@@ -231,11 +231,16 @@
// Implementation detail: we clamp the max of frames we are willing to
// count, so as not to spend too much time in the loop below.
const int kMaxUnwind = 1000;
- int j = 0;
- for (; next_sp != nullptr && j < kMaxUnwind; j++) {
+ int num_dropped_frames = 0;
+ for (int j = 0; next_sp != nullptr && j < kMaxUnwind; j++) {
+ if (skip_count > 0) {
+ skip_count--;
+ } else {
+ num_dropped_frames++;
+ }
next_sp = NextStackFrame<!IS_STACK_FRAMES, IS_WITH_CONTEXT>(next_sp, ucp);
}
- *min_dropped_frames = j;
+ *min_dropped_frames = num_dropped_frames;
}
return n;
}
diff --git a/absl/debugging/internal/stacktrace_riscv-inl.inc b/absl/debugging/internal/stacktrace_riscv-inl.inc
new file mode 100644
index 0000000..20183fa
--- /dev/null
+++ b/absl/debugging/internal/stacktrace_riscv-inl.inc
@@ -0,0 +1,191 @@
+// Copyright 2021 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef ABSL_DEBUGGING_INTERNAL_STACKTRACE_RISCV_INL_H_
+#define ABSL_DEBUGGING_INTERNAL_STACKTRACE_RISCV_INL_H_
+
+// Generate stack trace for riscv
+
+#include <sys/ucontext.h>
+
+#include "absl/base/config.h"
+#if defined(__linux__)
+#include <sys/mman.h>
+#include <ucontext.h>
+#include <unistd.h>
+#endif
+
+#include <atomic>
+#include <cassert>
+#include <cstdint>
+#include <iostream>
+#include <limits>
+#include <utility>
+
+#include "absl/base/attributes.h"
+#include "absl/debugging/stacktrace.h"
+
+static const uintptr_t kUnknownFrameSize = 0;
+
+// Compute the size of a stack frame in [low..high). We assume that low < high.
+// Return size of kUnknownFrameSize.
+template <typename T>
+static inline uintptr_t ComputeStackFrameSize(const T *low, const T *high) {
+ const char *low_char_ptr = reinterpret_cast<const char *>(low);
+ const char *high_char_ptr = reinterpret_cast<const char *>(high);
+ return low < high ? high_char_ptr - low_char_ptr : kUnknownFrameSize;
+}
+
+// Given a pointer to a stack frame, locate and return the calling stackframe,
+// or return null if no stackframe can be found. Perform sanity checks (the
+// strictness of which is controlled by the boolean parameter
+// "STRICT_UNWINDING") to reduce the chance that a bad pointer is returned.
+template <bool STRICT_UNWINDING, bool WITH_CONTEXT>
+ABSL_ATTRIBUTE_NO_SANITIZE_ADDRESS // May read random elements from stack.
+ABSL_ATTRIBUTE_NO_SANITIZE_MEMORY // May read random elements from stack.
+static void ** NextStackFrame(void **old_frame_pointer, const void *uc,
+ const std::pair<size_t, size_t> range) {
+ // .
+ // .
+ // .
+ // +-> +----------------+
+ // | | return address |
+ // | | previous fp |
+ // | | ... |
+ // | +----------------+ <-+
+ // | | return address | |
+ // +---|- previous fp | |
+ // | ... | |
+ // $fp ->|----------------+ |
+ // | return address | |
+ // | previous fp -|---+
+ // $sp ->| ... |
+ // +----------------+
+ void **new_frame_pointer = reinterpret_cast<void **>(old_frame_pointer[-2]);
+ uintptr_t frame_pointer = reinterpret_cast<uintptr_t>(new_frame_pointer);
+
+ // The RISCV ELF psABI mandates that the stack pointer is always 16-byte
+ // aligned.
+ // TODO(#1236) this doesn't hold for ILP32E which only mandates a 4-byte
+ // alignment.
+ if (frame_pointer & 15)
+ return nullptr;
+
+ // If the new frame pointer matches the signal context, avoid terminating
+ // early to deal with alternate signal stacks.
+ if (WITH_CONTEXT)
+ if (const ucontext_t *ucv = static_cast<const ucontext_t *>(uc))
+ // RISCV ELF psABI has the frame pointer at x8/fp/s0.
+ // -- RISCV psABI Table 18.2
+ if (ucv->uc_mcontext.__gregs[8] == frame_pointer)
+ return new_frame_pointer;
+
+ // Check frame size. In strict mode, we assume frames to be under 100,000
+ // bytes. In non-strict mode, we relax the limit to 1MB.
+ const uintptr_t max_size = STRICT_UNWINDING ? 100000 : 1000000;
+ const uintptr_t frame_size =
+ ComputeStackFrameSize(old_frame_pointer, new_frame_pointer);
+ if (frame_size == kUnknownFrameSize) {
+ if (STRICT_UNWINDING)
+ return nullptr;
+
+ // In non-strict mode permit non-contiguous stacks (e.g. alternate signal
+ // frame handling).
+ if (reinterpret_cast<uintptr_t>(new_frame_pointer) < range.first ||
+ reinterpret_cast<uintptr_t>(new_frame_pointer) > range.second)
+ return nullptr;
+ }
+
+ if (frame_size > max_size)
+ return nullptr;
+
+ return new_frame_pointer;
+}
+
+template <bool IS_STACK_FRAMES, bool IS_WITH_CONTEXT>
+ABSL_ATTRIBUTE_NO_SANITIZE_ADDRESS // May read random elements from stack.
+ABSL_ATTRIBUTE_NO_SANITIZE_MEMORY // May read random elements from stack.
+static int UnwindImpl(void **result, int *sizes, int max_depth, int skip_count,
+ const void *ucp, int *min_dropped_frames) {
+ // The `frame_pointer` that is computed here points to the top of the frame.
+ // The two words preceding the address are the return address and the previous
+ // frame pointer.
+#if defined(__GNUC__)
+ void **frame_pointer = reinterpret_cast<void **>(__builtin_frame_address(0));
+#else
+#error reading stack pointer not yet supported on this platform
+#endif
+
+ std::pair<size_t, size_t> stack = {
+ // assume that the first page is not the stack.
+ static_cast<size_t>(sysconf(_SC_PAGESIZE)),
+ std::numeric_limits<size_t>::max() - sizeof(void *)
+ };
+
+ int n = 0;
+ void *return_address = nullptr;
+ while (frame_pointer && n < max_depth) {
+ return_address = frame_pointer[-1];
+
+ // The absl::GetStackFrames routine is called when we are in some
+ // informational context (the failure signal handler for example). Use the
+ // non-strict unwinding rules to produce a stack trace that is as complete
+ // as possible (even if it contains a few bogus entries in some rare cases).
+ void **next_frame_pointer =
+ NextStackFrame<!IS_STACK_FRAMES, IS_WITH_CONTEXT>(frame_pointer, ucp,
+ stack);
+
+ if (skip_count > 0) {
+ skip_count--;
+ } else {
+ result[n] = return_address;
+ if (IS_STACK_FRAMES) {
+ sizes[n] = ComputeStackFrameSize(frame_pointer, next_frame_pointer);
+ }
+ n++;
+ }
+
+ frame_pointer = next_frame_pointer;
+ }
+
+ if (min_dropped_frames != nullptr) {
+ // Implementation detail: we clamp the max of frames we are willing to
+ // count, so as not to spend too much time in the loop below.
+ const int kMaxUnwind = 200;
+ int num_dropped_frames = 0;
+ for (int j = 0; frame_pointer != nullptr && j < kMaxUnwind; j++) {
+ if (skip_count > 0) {
+ skip_count--;
+ } else {
+ num_dropped_frames++;
+ }
+ frame_pointer =
+ NextStackFrame<!IS_STACK_FRAMES, IS_WITH_CONTEXT>(frame_pointer, ucp,
+ stack);
+ }
+ *min_dropped_frames = num_dropped_frames;
+ }
+
+ return n;
+}
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace debugging_internal {
+bool StackTraceWorksForTest() { return true; }
+} // namespace debugging_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif
diff --git a/absl/debugging/internal/stacktrace_win32-inl.inc b/absl/debugging/internal/stacktrace_win32-inl.inc
index 1c666c8..ef2b973 100644
--- a/absl/debugging/internal/stacktrace_win32-inl.inc
+++ b/absl/debugging/internal/stacktrace_win32-inl.inc
@@ -63,11 +63,12 @@
template <bool IS_STACK_FRAMES, bool IS_WITH_CONTEXT>
static int UnwindImpl(void** result, int* sizes, int max_depth, int skip_count,
const void*, int* min_dropped_frames) {
- int n = 0;
- if (!RtlCaptureStackBackTrace_fn) {
- // can't find a stacktrace with no function to call
+ USHORT n = 0;
+ if (!RtlCaptureStackBackTrace_fn || skip_count < 0 || max_depth < 0) {
+ // can't get a stacktrace with no function/invalid args
} else {
- n = (int)RtlCaptureStackBackTrace_fn(skip_count + 2, max_depth, result, 0);
+ n = RtlCaptureStackBackTrace_fn(static_cast<ULONG>(skip_count) + 2,
+ static_cast<ULONG>(max_depth), result, 0);
}
if (IS_STACK_FRAMES) {
// No implementation for finding out the stack frame sizes yet.
diff --git a/absl/debugging/internal/stacktrace_x86-inl.inc b/absl/debugging/internal/stacktrace_x86-inl.inc
index 70f79df..1975ba7 100644
--- a/absl/debugging/internal/stacktrace_x86-inl.inc
+++ b/absl/debugging/internal/stacktrace_x86-inl.inc
@@ -27,20 +27,20 @@
#include <cassert>
#include <cstdint>
+#include <limits>
+#include "absl/base/attributes.h"
#include "absl/base/macros.h"
#include "absl/base/port.h"
#include "absl/debugging/internal/address_is_readable.h"
#include "absl/debugging/internal/vdso_support.h" // a no-op on non-elf or non-glibc systems
#include "absl/debugging/stacktrace.h"
-#include "absl/base/internal/raw_logging.h"
-
using absl::debugging_internal::AddressIsReadable;
#if defined(__linux__) && defined(__i386__)
// Count "push %reg" instructions in VDSO __kernel_vsyscall(),
-// preceeding "syscall" or "sysenter".
+// preceding "syscall" or "sysenter".
// If __kernel_vsyscall uses frame pointer, answer 0.
//
// kMaxBytes tells how many instruction bytes of __kernel_vsyscall
@@ -112,6 +112,10 @@
// Assume stack frames larger than 100,000 bytes are bogus.
static const int kMaxFrameBytes = 100000;
+// Stack end to use when we don't know the actual stack end
+// (effectively just the end of address space).
+constexpr uintptr_t kUnknownStackEnd =
+ std::numeric_limits<size_t>::max() - sizeof(void *);
// Returns the stack frame pointer from signal context, 0 if unknown.
// vuc is a ucontext_t *. We use void* to avoid the use
@@ -139,13 +143,14 @@
// TODO(bcmills): -momit-leaf-frame-pointer is currently the default
// behavior when building with clang. Talk to the C++ toolchain team about
// fixing that.
- if (bp >= sp && bp - sp <= kMaxFrameBytes) return bp;
+ if (bp >= sp && bp - sp <= kMaxFrameBytes)
+ return static_cast<uintptr_t>(bp);
// If bp isn't a plausible frame pointer, return the stack pointer instead.
// If we're lucky, it points to the start of a stack frame; otherwise, we'll
// get one frame of garbage in the stack trace and fail the sanity check on
// the next iteration.
- return sp;
+ return static_cast<uintptr_t>(sp);
}
#endif
return 0;
@@ -158,7 +163,8 @@
template <bool STRICT_UNWINDING, bool WITH_CONTEXT>
ABSL_ATTRIBUTE_NO_SANITIZE_ADDRESS // May read random elements from stack.
ABSL_ATTRIBUTE_NO_SANITIZE_MEMORY // May read random elements from stack.
-static void **NextStackFrame(void **old_fp, const void *uc) {
+static void **NextStackFrame(void **old_fp, const void *uc,
+ size_t stack_low, size_t stack_high) {
void **new_fp = (void **)*old_fp;
#if defined(__linux__) && defined(__i386__)
@@ -256,7 +262,37 @@
// With the stack growing downwards, older stack frame must be
// at a greater address that the current one.
if (new_fp_u <= old_fp_u) return nullptr;
- if (new_fp_u - old_fp_u > kMaxFrameBytes) return nullptr;
+
+ // If we get a very large frame size, it may be an indication that we
+ // guessed frame pointers incorrectly and now risk a paging fault
+ // dereferencing a wrong frame pointer. Or maybe not because large frames
+ // are possible as well. The main stack is assumed to be readable,
+ // so we assume the large frame is legit if we know the real stack bounds
+ // and are within the stack.
+ if (new_fp_u - old_fp_u > kMaxFrameBytes) {
+ if (stack_high < kUnknownStackEnd &&
+ static_cast<size_t>(getpagesize()) < stack_low) {
+ // Stack bounds are known.
+ if (!(stack_low < new_fp_u && new_fp_u <= stack_high)) {
+ // new_fp_u is not within the known stack.
+ return nullptr;
+ }
+ } else {
+ // Stack bounds are unknown, prefer truncated stack to possible crash.
+ return nullptr;
+ }
+ }
+ if (stack_low < old_fp_u && old_fp_u <= stack_high) {
+ // Old BP was in the expected stack region...
+ if (!(stack_low < new_fp_u && new_fp_u <= stack_high)) {
+ // ... but new BP is outside of expected stack region.
+ // It is most likely bogus.
+ return nullptr;
+ }
+ } else {
+ // We may be here if we are executing in a co-routine with a
+ // separate stack. We can't do safety checks in this case.
+ }
} else {
if (new_fp == nullptr) return nullptr; // skip AddressIsReadable() below
// In the non-strict mode, allow discontiguous stack frames.
@@ -296,20 +332,27 @@
int n = 0;
void **fp = reinterpret_cast<void **>(__builtin_frame_address(0));
+ // Assume that the first page is not stack.
+ size_t stack_low = static_cast<size_t>(getpagesize());
+ size_t stack_high = kUnknownStackEnd;
+
while (fp && n < max_depth) {
if (*(fp + 1) == reinterpret_cast<void *>(0)) {
// In 64-bit code, we often see a frame that
// points to itself and has a return address of 0.
break;
}
- void **next_fp = NextStackFrame<!IS_STACK_FRAMES, IS_WITH_CONTEXT>(fp, ucp);
+ void **next_fp = NextStackFrame<!IS_STACK_FRAMES, IS_WITH_CONTEXT>(
+ fp, ucp, stack_low, stack_high);
if (skip_count > 0) {
skip_count--;
} else {
result[n] = *(fp + 1);
if (IS_STACK_FRAMES) {
if (next_fp > fp) {
- sizes[n] = (uintptr_t)next_fp - (uintptr_t)fp;
+ sizes[n] = static_cast<int>(
+ reinterpret_cast<uintptr_t>(next_fp) -
+ reinterpret_cast<uintptr_t>(fp));
} else {
// A frame-size of 0 is used to indicate unknown frame size.
sizes[n] = 0;
@@ -323,11 +366,17 @@
// Implementation detail: we clamp the max of frames we are willing to
// count, so as not to spend too much time in the loop below.
const int kMaxUnwind = 1000;
- int j = 0;
- for (; fp != nullptr && j < kMaxUnwind; j++) {
- fp = NextStackFrame<!IS_STACK_FRAMES, IS_WITH_CONTEXT>(fp, ucp);
+ int num_dropped_frames = 0;
+ for (int j = 0; fp != nullptr && j < kMaxUnwind; j++) {
+ if (skip_count > 0) {
+ skip_count--;
+ } else {
+ num_dropped_frames++;
+ }
+ fp = NextStackFrame<!IS_STACK_FRAMES, IS_WITH_CONTEXT>(fp, ucp, stack_low,
+ stack_high);
}
- *min_dropped_frames = j;
+ *min_dropped_frames = num_dropped_frames;
}
return n;
}
diff --git a/absl/debugging/internal/symbolize.h b/absl/debugging/internal/symbolize.h
index 8c296f8..5593fde 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(__native_client__) \
+ && !defined(__asmjs__) && !defined(__wasm__)
#define ABSL_INTERNAL_HAVE_ELF_SYMBOLIZE 1
#include <elf.h>
@@ -115,7 +115,7 @@
// Remove all installed decorators. Returns true if successful, false if
// symbolization is currently in progress.
-bool RemoveAllSymbolDecorators(void);
+bool RemoveAllSymbolDecorators();
// Registers an address range to a file mapping.
//
diff --git a/absl/debugging/internal/vdso_support.cc b/absl/debugging/internal/vdso_support.cc
index 6be16d9..8a588ea 100644
--- a/absl/debugging/internal/vdso_support.cc
+++ b/absl/debugging/internal/vdso_support.cc
@@ -20,12 +20,25 @@
#ifdef ABSL_HAVE_VDSO_SUPPORT // defined in vdso_support.h
+#if !defined(__has_include)
+#define __has_include(header) 0
+#endif
+
#include <errno.h>
#include <fcntl.h>
+#if __has_include(<syscall.h>)
+#include <syscall.h>
+#elif __has_include(<sys/syscall.h>)
#include <sys/syscall.h>
+#endif
#include <unistd.h>
-#if __GLIBC_PREREQ(2, 16) // GLIBC-2.16 implements getauxval.
+#if !defined(__UCLIBC__) && defined(__GLIBC__) && \
+ (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 16))
+#define ABSL_HAVE_GETAUXVAL
+#endif
+
+#ifdef ABSL_HAVE_GETAUXVAL
#include <sys/auxv.h>
#endif
@@ -37,6 +50,17 @@
#define AT_SYSINFO_EHDR 33 // for crosstoolv10
#endif
+#if defined(__NetBSD__)
+using Elf32_auxv_t = Aux32Info;
+using Elf64_auxv_t = Aux64Info;
+#endif
+#if defined(__FreeBSD__)
+#if defined(__ELF_WORD_SIZE) && __ELF_WORD_SIZE == 64
+using Elf64_auxv_t = Elf64_Auxinfo;
+#endif
+using Elf32_auxv_t = Elf32_Auxinfo;
+#endif
+
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace debugging_internal {
@@ -45,7 +69,9 @@
std::atomic<const void *> VDSOSupport::vdso_base_(
debugging_internal::ElfMemImage::kInvalidBase);
-std::atomic<VDSOSupport::GetCpuFn> VDSOSupport::getcpu_fn_(&InitAndGetCPU);
+ABSL_CONST_INIT std::atomic<VDSOSupport::GetCpuFn> VDSOSupport::getcpu_fn_(
+ &InitAndGetCPU);
+
VDSOSupport::VDSOSupport()
// If vdso_base_ is still set to kInvalidBase, we got here
// before VDSOSupport::Init has been called. Call it now.
@@ -65,7 +91,7 @@
// the operation should be idempotent.
const void *VDSOSupport::Init() {
const auto kInvalidBase = debugging_internal::ElfMemImage::kInvalidBase;
-#if __GLIBC_PREREQ(2, 16)
+#ifdef ABSL_HAVE_GETAUXVAL
if (vdso_base_.load(std::memory_order_relaxed) == kInvalidBase) {
errno = 0;
const void *const sysinfo_ehdr =
@@ -74,7 +100,7 @@
vdso_base_.store(sysinfo_ehdr, std::memory_order_relaxed);
}
}
-#endif // __GLIBC_PREREQ(2, 16)
+#endif // ABSL_HAVE_GETAUXVAL
if (vdso_base_.load(std::memory_order_relaxed) == kInvalidBase) {
int fd = open("/proc/self/auxv", O_RDONLY);
if (fd == -1) {
@@ -86,8 +112,13 @@
ElfW(auxv_t) aux;
while (read(fd, &aux, sizeof(aux)) == sizeof(aux)) {
if (aux.a_type == AT_SYSINFO_EHDR) {
+#if defined(__NetBSD__)
+ vdso_base_.store(reinterpret_cast<void *>(aux.a_v),
+ std::memory_order_relaxed);
+#else
vdso_base_.store(reinterpret_cast<void *>(aux.a_un.a_val),
std::memory_order_relaxed);
+#endif
break;
}
}
@@ -162,8 +193,9 @@
ABSL_ATTRIBUTE_NO_SANITIZE_MEMORY
int GetCPU() {
unsigned cpu;
- int ret_code = (*VDSOSupport::getcpu_fn_)(&cpu, nullptr, nullptr);
- return ret_code == 0 ? cpu : ret_code;
+ long ret_code = // NOLINT(runtime/int)
+ (*VDSOSupport::getcpu_fn_)(&cpu, nullptr, nullptr);
+ return ret_code == 0 ? static_cast<int>(cpu) : static_cast<int>(ret_code);
}
} // namespace debugging_internal
diff --git a/absl/debugging/leak_check.cc b/absl/debugging/leak_check.cc
index 764ca0a..fdb8798 100644
--- a/absl/debugging/leak_check.cc
+++ b/absl/debugging/leak_check.cc
@@ -11,29 +11,19 @@
// 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.
-
+//
// Wrappers around lsan_interface functions.
-// When lsan is not linked in, these functions are not available,
-// therefore Abseil code which depends on these functions is conditioned on the
-// definition of LEAK_SANITIZER.
-#include "absl/base/attributes.h"
+//
+// These are always-available run-time functions manipulating the LeakSanitizer,
+// even when the lsan_interface (and LeakSanitizer) is not available. When
+// LeakSanitizer is not linked in, these functions become no-op stubs.
+
#include "absl/debugging/leak_check.h"
-#ifndef LEAK_SANITIZER
+#include "absl/base/attributes.h"
+#include "absl/base/config.h"
-namespace absl {
-ABSL_NAMESPACE_BEGIN
-bool HaveLeakSanitizer() { return false; }
-bool LeakCheckerIsActive() { return false; }
-void DoIgnoreLeak(const void*) { }
-void RegisterLivePointers(const void*, size_t) { }
-void UnRegisterLivePointers(const void*, size_t) { }
-LeakCheckDisabler::LeakCheckDisabler() { }
-LeakCheckDisabler::~LeakCheckDisabler() { }
-ABSL_NAMESPACE_END
-} // namespace absl
-
-#else
+#if defined(ABSL_HAVE_LEAK_SANITIZER)
#include <sanitizer/lsan_interface.h>
@@ -66,4 +56,18 @@
ABSL_NAMESPACE_END
} // namespace absl
-#endif // LEAK_SANITIZER
+#else // defined(ABSL_HAVE_LEAK_SANITIZER)
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+bool HaveLeakSanitizer() { return false; }
+bool LeakCheckerIsActive() { return false; }
+void DoIgnoreLeak(const void*) { }
+void RegisterLivePointers(const void*, size_t) { }
+void UnRegisterLivePointers(const void*, size_t) { }
+LeakCheckDisabler::LeakCheckDisabler() = default;
+LeakCheckDisabler::~LeakCheckDisabler() = default;
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // defined(ABSL_HAVE_LEAK_SANITIZER)
diff --git a/absl/debugging/leak_check.h b/absl/debugging/leak_check.h
index 5fc2b05..6bd7940 100644
--- a/absl/debugging/leak_check.h
+++ b/absl/debugging/leak_check.h
@@ -24,7 +24,24 @@
// Note: this leak checking API is not yet supported in MSVC.
// Leak checking is enabled by default in all ASan builds.
//
-// See https://github.com/google/sanitizers/wiki/AddressSanitizerLeakSanitizer
+// https://clang.llvm.org/docs/LeakSanitizer.html
+// https://github.com/google/sanitizers/wiki/AddressSanitizerLeakSanitizer
+//
+// GCC and Clang both automatically enable LeakSanitizer when AddressSanitizer
+// is enabled. To use the mode, simply pass `-fsanitize=address` to both the
+// compiler and linker. An example Bazel command could be
+//
+// $ bazel test --copt=-fsanitize=address --linkopt=-fsanitize=address ...
+//
+// GCC and Clang auto support a standalone LeakSanitizer mode (a mode which does
+// not also use AddressSanitizer). To use the mode, simply pass
+// `-fsanitize=leak` to both the compiler and linker. Since GCC does not
+// currently provide a way of detecting this mode at compile-time, GCC users
+// must also pass -DLEAK_SANITIZER to the compiler. An example Bazel command
+// could be
+//
+// $ bazel test --copt=-DLEAK_SANITIZER --copt=-fsanitize=leak
+// --linkopt=-fsanitize=leak ...
//
// -----------------------------------------------------------------------------
#ifndef ABSL_DEBUGGING_LEAK_CHECK_H_
diff --git a/absl/debugging/leak_check_fail_test.cc b/absl/debugging/leak_check_fail_test.cc
index c49b81a..46e9fb6 100644
--- a/absl/debugging/leak_check_fail_test.cc
+++ b/absl/debugging/leak_check_fail_test.cc
@@ -13,9 +13,10 @@
// limitations under the License.
#include <memory>
+
#include "gtest/gtest.h"
-#include "absl/base/internal/raw_logging.h"
#include "absl/debugging/leak_check.h"
+#include "absl/log/log.h"
namespace {
@@ -25,7 +26,7 @@
// failed exit code.
char* foo = strdup("lsan should complain about this leaked string");
- ABSL_RAW_LOG(INFO, "Should detect leaked string %s", foo);
+ LOG(INFO) << "Should detect leaked string " << foo;
}
TEST(LeakCheckTest, LeakMemoryAfterDisablerScope) {
@@ -34,8 +35,7 @@
// failed exit code.
{ absl::LeakCheckDisabler disabler; }
char* foo = strdup("lsan should also complain about this leaked string");
- ABSL_RAW_LOG(INFO, "Re-enabled leak detection.Should detect leaked string %s",
- foo);
+ LOG(INFO) << "Re-enabled leak detection.Should detect leaked string " << foo;
}
} // namespace
diff --git a/absl/debugging/leak_check_test.cc b/absl/debugging/leak_check_test.cc
index 9fcfc8e..6f0135e 100644
--- a/absl/debugging/leak_check_test.cc
+++ b/absl/debugging/leak_check_test.cc
@@ -15,30 +15,27 @@
#include <string>
#include "gtest/gtest.h"
-#include "absl/base/internal/raw_logging.h"
+#include "absl/base/config.h"
#include "absl/debugging/leak_check.h"
+#include "absl/log/log.h"
namespace {
-TEST(LeakCheckTest, DetectLeakSanitizer) {
-#ifdef ABSL_EXPECT_LEAK_SANITIZER
- EXPECT_TRUE(absl::HaveLeakSanitizer());
- EXPECT_TRUE(absl::LeakCheckerIsActive());
-#else
- EXPECT_FALSE(absl::HaveLeakSanitizer());
- EXPECT_FALSE(absl::LeakCheckerIsActive());
-#endif
-}
-
TEST(LeakCheckTest, IgnoreLeakSuppressesLeakedMemoryErrors) {
+ if (!absl::LeakCheckerIsActive()) {
+ GTEST_SKIP() << "LeakChecker is not active";
+ }
auto foo = absl::IgnoreLeak(new std::string("some ignored leaked string"));
- ABSL_RAW_LOG(INFO, "Ignoring leaked string %s", foo->c_str());
+ LOG(INFO) << "Ignoring leaked string " << foo;
}
TEST(LeakCheckTest, LeakCheckDisablerIgnoresLeak) {
+ if (!absl::LeakCheckerIsActive()) {
+ GTEST_SKIP() << "LeakChecker is not active";
+ }
absl::LeakCheckDisabler disabler;
auto foo = new std::string("some string leaked while checks are disabled");
- ABSL_RAW_LOG(INFO, "Ignoring leaked string %s", foo->c_str());
+ LOG(INFO) << "Ignoring leaked string " << foo;
}
} // namespace
diff --git a/absl/debugging/stacktrace.cc b/absl/debugging/stacktrace.cc
index 5358b94..ff8069f 100644
--- a/absl/debugging/stacktrace.cc
+++ b/absl/debugging/stacktrace.cc
@@ -52,6 +52,7 @@
# 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"
diff --git a/absl/debugging/stacktrace_benchmark.cc b/absl/debugging/stacktrace_benchmark.cc
new file mode 100644
index 0000000..9360baf
--- /dev/null
+++ b/absl/debugging/stacktrace_benchmark.cc
@@ -0,0 +1,55 @@
+// Copyright 2022 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/base/attributes.h"
+#include "absl/base/config.h"
+#include "absl/base/optimization.h"
+#include "absl/debugging/stacktrace.h"
+#include "benchmark/benchmark.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace {
+
+static constexpr int kMaxStackDepth = 100;
+static constexpr int kCacheSize = (1 << 16);
+void* pcs[kMaxStackDepth];
+
+ABSL_ATTRIBUTE_NOINLINE void func(benchmark::State& state, int x, int depth) {
+ if (x <= 0) {
+ // Touch a significant amount of memory so that the stack is likely to be
+ // not cached in the L1 cache.
+ state.PauseTiming();
+ int* arr = new int[kCacheSize];
+ for (int i = 0; i < kCacheSize; ++i) benchmark::DoNotOptimize(arr[i] = 100);
+ delete[] arr;
+ state.ResumeTiming();
+ benchmark::DoNotOptimize(absl::GetStackTrace(pcs, depth, 0));
+ return;
+ }
+ ABSL_BLOCK_TAIL_CALL_OPTIMIZATION();
+ func(state, --x, depth);
+}
+
+void BM_GetStackTrace(benchmark::State& state) {
+ int depth = state.range(0);
+ for (auto s : state) {
+ func(state, depth, depth);
+ }
+}
+
+BENCHMARK(BM_GetStackTrace)->DenseRange(10, kMaxStackDepth, 10);
+} // namespace
+ABSL_NAMESPACE_END
+} // namespace absl
diff --git a/absl/debugging/stacktrace_test.cc b/absl/debugging/stacktrace_test.cc
new file mode 100644
index 0000000..31f7723
--- /dev/null
+++ b/absl/debugging/stacktrace_test.cc
@@ -0,0 +1,47 @@
+// Copyright 2023 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/debugging/stacktrace.h"
+
+#include "gtest/gtest.h"
+#include "absl/base/macros.h"
+#include "absl/base/optimization.h"
+
+namespace {
+
+// This test is currently only known to pass on Linux x86_64/aarch64.
+#if defined(__linux__) && (defined(__x86_64__) || defined(__aarch64__))
+ABSL_ATTRIBUTE_NOINLINE void Unwind(void* p) {
+ ABSL_ATTRIBUTE_UNUSED static void* volatile sink = p;
+ constexpr int kSize = 16;
+ void* stack[kSize];
+ int frames[kSize];
+ absl::GetStackTrace(stack, kSize, 0);
+ absl::GetStackFrames(stack, frames, kSize, 0);
+}
+
+ABSL_ATTRIBUTE_NOINLINE void HugeFrame() {
+ char buffer[1 << 20];
+ Unwind(buffer);
+ ABSL_BLOCK_TAIL_CALL_OPTIMIZATION();
+}
+
+TEST(StackTrace, HugeFrame) {
+ // Ensure that the unwinder is not confused by very large stack frames.
+ HugeFrame();
+ ABSL_BLOCK_TAIL_CALL_OPTIMIZATION();
+}
+#endif
+
+} // namespace
diff --git a/absl/debugging/symbolize.cc b/absl/debugging/symbolize.cc
index f1abdfd..638d395 100644
--- a/absl/debugging/symbolize.cc
+++ b/absl/debugging/symbolize.cc
@@ -23,6 +23,11 @@
#endif
#endif
+// Emscripten symbolization relies on JS. Do not use them in standalone mode.
+#if defined(__EMSCRIPTEN__) && !defined(STANDALONE_WASM)
+#define ABSL_INTERNAL_HAVE_SYMBOLIZE_WASM
+#endif
+
#if defined(ABSL_INTERNAL_HAVE_ELF_SYMBOLIZE)
#include "absl/debugging/symbolize_elf.inc"
#elif defined(ABSL_INTERNAL_HAVE_SYMBOLIZE_WIN32)
@@ -31,7 +36,7 @@
#include "absl/debugging/symbolize_win32.inc"
#elif defined(__APPLE__)
#include "absl/debugging/symbolize_darwin.inc"
-#elif defined(__EMSCRIPTEN__)
+#elif defined(ABSL_INTERNAL_HAVE_SYMBOLIZE_WASM)
#include "absl/debugging/symbolize_emscripten.inc"
#else
#include "absl/debugging/symbolize_unimplemented.inc"
diff --git a/absl/debugging/symbolize_darwin.inc b/absl/debugging/symbolize_darwin.inc
index 443ce9e..cf63d19 100644
--- a/absl/debugging/symbolize_darwin.inc
+++ b/absl/debugging/symbolize_darwin.inc
@@ -83,13 +83,14 @@
memmove(out, tmp_buf, len + 1);
}
} else {
- strncpy(out, symbol.c_str(), out_size);
+ strncpy(out, symbol.c_str(), static_cast<size_t>(out_size));
}
if (out[out_size - 1] != '\0') {
// strncpy() does not '\0' terminate when it truncates.
static constexpr char kEllipsis[] = "...";
- int ellipsis_size = std::min<int>(sizeof(kEllipsis) - 1, out_size - 1);
+ size_t ellipsis_size =
+ std::min(sizeof(kEllipsis) - 1, static_cast<size_t>(out_size) - 1);
memcpy(out + out_size - ellipsis_size - 1, kEllipsis, ellipsis_size);
out[out_size - 1] = '\0';
}
diff --git a/absl/debugging/symbolize_elf.inc b/absl/debugging/symbolize_elf.inc
index 87dbd07..ae75cd4 100644
--- a/absl/debugging/symbolize_elf.inc
+++ b/absl/debugging/symbolize_elf.inc
@@ -77,6 +77,10 @@
#include "absl/debugging/internal/vdso_support.h"
#include "absl/strings/string_view.h"
+#if defined(__FreeBSD__) && !defined(ElfW)
+#define ElfW(x) __ElfN(x)
+#endif
+
namespace absl {
ABSL_NAMESPACE_BEGIN
@@ -201,7 +205,8 @@
// PT_LOAD program header describing executable code.
// Normally we expect just one, but SWIFT binaries have two.
- std::array<ElfW(Phdr), 2> phdr;
+ // CUDA binaries have 3 (see cr/473913254 description).
+ std::array<ElfW(Phdr), 4> phdr;
};
// Build 4-way associative cache for symbols. Within each cache line, symbols
@@ -248,21 +253,21 @@
public:
AddrMap() : size_(0), allocated_(0), obj_(nullptr) {}
~AddrMap() { base_internal::LowLevelAlloc::Free(obj_); }
- int Size() const { return size_; }
- ObjFile *At(int i) { return &obj_[i]; }
+ size_t Size() const { return size_; }
+ ObjFile *At(size_t i) { return &obj_[i]; }
ObjFile *Add();
void Clear();
private:
- int size_; // count of valid elements (<= allocated_)
- int allocated_; // count of allocated elements
- ObjFile *obj_; // array of allocated_ elements
+ size_t size_; // count of valid elements (<= allocated_)
+ size_t allocated_; // count of allocated elements
+ ObjFile *obj_; // array of allocated_ elements
AddrMap(const AddrMap &) = delete;
AddrMap &operator=(const AddrMap &) = delete;
};
void AddrMap::Clear() {
- for (int i = 0; i != size_; i++) {
+ for (size_t i = 0; i != size_; i++) {
At(i)->~ObjFile();
}
size_ = 0;
@@ -270,7 +275,7 @@
ObjFile *AddrMap::Add() {
if (size_ == allocated_) {
- int new_allocated = allocated_ * 2 + 50;
+ size_t new_allocated = allocated_ * 2 + 50;
ObjFile *new_obj_ =
static_cast<ObjFile *>(base_internal::LowLevelAlloc::AllocWithArena(
new_allocated * sizeof(*new_obj_), SigSafeArena()));
@@ -284,6 +289,30 @@
return new (&obj_[size_++]) ObjFile;
}
+class CachingFile {
+ public:
+ // Setup reader for fd that uses buf[0, buf_size-1] as a cache.
+ CachingFile(int fd, char *buf, size_t buf_size)
+ : fd_(fd),
+ cache_(buf),
+ cache_size_(buf_size),
+ cache_start_(0),
+ cache_limit_(0) {}
+
+ int fd() const { return fd_; }
+ ssize_t ReadFromOffset(void *buf, size_t count, off_t offset);
+ bool ReadFromOffsetExact(void *buf, size_t count, off_t offset);
+
+ private:
+ // Bytes [cache_start_, cache_limit_-1] from fd_ are stored in
+ // a prefix of cache_[0, cache_size_-1].
+ int fd_;
+ char *cache_;
+ size_t cache_size_;
+ off_t cache_start_;
+ off_t cache_limit_;
+};
+
// ---------------------------------------------------------------
enum FindSymbolResult { SYMBOL_NOT_FOUND = 1, SYMBOL_TRUNCATED, SYMBOL_FOUND };
@@ -296,7 +325,7 @@
private:
char *CopyString(const char *s) {
- int len = strlen(s);
+ size_t len = strlen(s);
char *dst = static_cast<char *>(
base_internal::LowLevelAlloc::AllocWithArena(len + 1, SigSafeArena()));
ABSL_RAW_CHECK(dst != nullptr, "out of memory");
@@ -317,13 +346,15 @@
FindSymbolResult GetSymbolFromObjectFile(const ObjFile &obj,
const void *const pc,
const ptrdiff_t relocation,
- char *out, int out_size,
- char *tmp_buf, int tmp_buf_size);
+ char *out, size_t out_size,
+ char *tmp_buf, size_t tmp_buf_size);
+ const char *GetUncachedSymbol(const void *pc);
enum {
SYMBOL_BUF_SIZE = 3072,
TMP_BUF_SIZE = 1024,
SYMBOL_CACHE_LINES = 128,
+ FILE_CACHE_SIZE = 8192,
};
AddrMap addr_map_;
@@ -332,6 +363,7 @@
bool addr_map_read_;
char symbol_buf_[SYMBOL_BUF_SIZE];
+ char file_cache_[FILE_CACHE_SIZE];
// tmp_buf_ will be used to store arrays of ElfW(Shdr) and ElfW(Sym)
// so we ensure that tmp_buf_ is properly aligned to store either.
@@ -348,11 +380,11 @@
} // namespace
-static int SymbolizerSize() {
+static size_t SymbolizerSize() {
#if defined(__wasm__) || defined(__asmjs__)
- int pagesize = getpagesize();
+ auto pagesize = static_cast<size_t>(getpagesize());
#else
- int pagesize = sysconf(_SC_PAGESIZE);
+ auto pagesize = static_cast<size_t>(sysconf(_SC_PAGESIZE));
#endif
return ((sizeof(Symbolizer) - 1) / pagesize + 1) * pagesize;
}
@@ -424,40 +456,64 @@
if (len == 0) { // Reached EOF.
break;
}
- num_bytes += len;
+ num_bytes += static_cast<size_t>(len);
}
SAFE_ASSERT(num_bytes <= count);
return static_cast<ssize_t>(num_bytes);
}
-// Read up to "count" bytes from "offset" in the file pointed by file
-// descriptor "fd" into the buffer starting at "buf". On success,
-// return the number of bytes read. Otherwise, return -1.
-static ssize_t ReadFromOffset(const int fd, void *buf, const size_t count,
- const off_t offset) {
- off_t off = lseek(fd, offset, SEEK_SET);
- if (off == (off_t)-1) {
- ABSL_RAW_LOG(WARNING, "lseek(%d, %ju, SEEK_SET) failed: errno=%d", fd,
- static_cast<uintmax_t>(offset), errno);
- return -1;
+// Read up to "count" bytes from "offset" into the buffer starting at "buf",
+// while handling short reads and EINTR. On success, return the number of bytes
+// read. Otherwise, return -1.
+ssize_t CachingFile::ReadFromOffset(void *buf, size_t count, off_t offset) {
+ char *dst = static_cast<char *>(buf);
+ size_t read = 0;
+ while (read < count) {
+ // Look in cache first.
+ if (offset >= cache_start_ && offset < cache_limit_) {
+ const char *hit_start = &cache_[offset - cache_start_];
+ const size_t n =
+ std::min(count - read, static_cast<size_t>(cache_limit_ - offset));
+ memcpy(dst, hit_start, n);
+ dst += n;
+ read += static_cast<size_t>(n);
+ offset += static_cast<off_t>(n);
+ continue;
+ }
+
+ cache_start_ = 0;
+ cache_limit_ = 0;
+ ssize_t n = pread(fd_, cache_, cache_size_, offset);
+ if (n < 0) {
+ if (errno == EINTR) {
+ continue;
+ }
+ ABSL_RAW_LOG(WARNING, "read failed: errno=%d", errno);
+ return -1;
+ }
+ if (n == 0) { // Reached EOF.
+ break;
+ }
+
+ cache_start_ = offset;
+ cache_limit_ = offset + static_cast<off_t>(n);
+ // Next iteration will copy from cache into dst.
}
- return ReadPersistent(fd, buf, count);
+ return static_cast<ssize_t>(read);
}
-// Try reading exactly "count" bytes from "offset" bytes in a file
-// pointed by "fd" into the buffer starting at "buf" while handling
-// short reads and EINTR. On success, return true. Otherwise, return
-// false.
-static bool ReadFromOffsetExact(const int fd, void *buf, const size_t count,
- const off_t offset) {
- ssize_t len = ReadFromOffset(fd, buf, count, offset);
+// Try reading exactly "count" bytes from "offset" bytes into the buffer
+// starting at "buf" while handling short reads and EINTR. On success, return
+// true. Otherwise, return false.
+bool CachingFile::ReadFromOffsetExact(void *buf, size_t count, off_t offset) {
+ ssize_t len = ReadFromOffset(buf, count, offset);
return len >= 0 && static_cast<size_t>(len) == count;
}
// Returns elf_header.e_type if the file pointed by fd is an ELF binary.
-static int FileGetElfType(const int fd) {
+static int FileGetElfType(CachingFile *file) {
ElfW(Ehdr) elf_header;
- if (!ReadFromOffsetExact(fd, &elf_header, sizeof(elf_header), 0)) {
+ if (!file->ReadFromOffsetExact(&elf_header, sizeof(elf_header), 0)) {
return -1;
}
if (memcmp(elf_header.e_ident, ELFMAG, SELFMAG) != 0) {
@@ -472,30 +528,38 @@
// To keep stack consumption low, we would like this function to not get
// inlined.
static ABSL_ATTRIBUTE_NOINLINE bool GetSectionHeaderByType(
- const int fd, ElfW(Half) sh_num, const off_t sh_offset, ElfW(Word) type,
- ElfW(Shdr) * out, char *tmp_buf, int tmp_buf_size) {
+ CachingFile *file, ElfW(Half) sh_num, const off_t sh_offset,
+ ElfW(Word) type, ElfW(Shdr) * out, char *tmp_buf, size_t tmp_buf_size) {
ElfW(Shdr) *buf = reinterpret_cast<ElfW(Shdr) *>(tmp_buf);
- const int buf_entries = tmp_buf_size / sizeof(buf[0]);
- const int buf_bytes = buf_entries * sizeof(buf[0]);
+ const size_t buf_entries = tmp_buf_size / sizeof(buf[0]);
+ const size_t buf_bytes = buf_entries * sizeof(buf[0]);
- for (int i = 0; i < sh_num;) {
- const ssize_t num_bytes_left = (sh_num - i) * sizeof(buf[0]);
- const ssize_t num_bytes_to_read =
+ for (size_t i = 0; static_cast<int>(i) < sh_num;) {
+ const size_t num_bytes_left =
+ (static_cast<size_t>(sh_num) - i) * sizeof(buf[0]);
+ const size_t num_bytes_to_read =
(buf_bytes > num_bytes_left) ? num_bytes_left : buf_bytes;
- const off_t offset = sh_offset + i * sizeof(buf[0]);
- const ssize_t len = ReadFromOffset(fd, buf, num_bytes_to_read, offset);
- if (len % sizeof(buf[0]) != 0) {
+ const off_t offset = sh_offset + static_cast<off_t>(i * sizeof(buf[0]));
+ const ssize_t len = file->ReadFromOffset(buf, num_bytes_to_read, offset);
+ if (len < 0) {
ABSL_RAW_LOG(
WARNING,
- "Reading %zd bytes from offset %ju returned %zd which is not a "
+ "Reading %zu bytes from offset %ju returned %zd which is negative.",
+ num_bytes_to_read, static_cast<intmax_t>(offset), len);
+ return false;
+ }
+ if (static_cast<size_t>(len) % sizeof(buf[0]) != 0) {
+ ABSL_RAW_LOG(
+ WARNING,
+ "Reading %zu bytes from offset %jd returned %zd which is not a "
"multiple of %zu.",
- num_bytes_to_read, static_cast<uintmax_t>(offset), len,
+ num_bytes_to_read, static_cast<intmax_t>(offset), len,
sizeof(buf[0]));
return false;
}
- const ssize_t num_headers_in_buf = len / sizeof(buf[0]);
+ const size_t num_headers_in_buf = static_cast<size_t>(len) / sizeof(buf[0]);
SAFE_ASSERT(num_headers_in_buf <= buf_entries);
- for (int j = 0; j < num_headers_in_buf; ++j) {
+ for (size_t j = 0; j < num_headers_in_buf; ++j) {
if (buf[j].sh_type == type) {
*out = buf[j];
return true;
@@ -510,40 +574,52 @@
// but there has (as yet) been no need for anything longer either.
const int kMaxSectionNameLen = 64;
+// Small cache to use for miscellaneous file reads.
+const int kSmallFileCacheSize = 100;
+
bool ForEachSection(int fd,
const std::function<bool(absl::string_view name,
const ElfW(Shdr) &)> &callback) {
+ char buf[kSmallFileCacheSize];
+ CachingFile file(fd, buf, sizeof(buf));
+
ElfW(Ehdr) elf_header;
- if (!ReadFromOffsetExact(fd, &elf_header, sizeof(elf_header), 0)) {
+ if (!file.ReadFromOffsetExact(&elf_header, sizeof(elf_header), 0)) {
+ return false;
+ }
+
+ // Technically it can be larger, but in practice this never happens.
+ if (elf_header.e_shentsize != sizeof(ElfW(Shdr))) {
return false;
}
ElfW(Shdr) shstrtab;
- off_t shstrtab_offset =
- (elf_header.e_shoff + elf_header.e_shentsize * elf_header.e_shstrndx);
- if (!ReadFromOffsetExact(fd, &shstrtab, sizeof(shstrtab), shstrtab_offset)) {
+ off_t shstrtab_offset = static_cast<off_t>(elf_header.e_shoff) +
+ elf_header.e_shentsize * elf_header.e_shstrndx;
+ if (!file.ReadFromOffsetExact(&shstrtab, sizeof(shstrtab), shstrtab_offset)) {
return false;
}
for (int i = 0; i < elf_header.e_shnum; ++i) {
ElfW(Shdr) out;
off_t section_header_offset =
- (elf_header.e_shoff + elf_header.e_shentsize * i);
- if (!ReadFromOffsetExact(fd, &out, sizeof(out), section_header_offset)) {
+ static_cast<off_t>(elf_header.e_shoff) + elf_header.e_shentsize * i;
+ if (!file.ReadFromOffsetExact(&out, sizeof(out), section_header_offset)) {
return false;
}
- off_t name_offset = shstrtab.sh_offset + out.sh_name;
+ off_t name_offset = static_cast<off_t>(shstrtab.sh_offset) + out.sh_name;
char header_name[kMaxSectionNameLen];
ssize_t n_read =
- ReadFromOffset(fd, &header_name, kMaxSectionNameLen, name_offset);
- if (n_read == -1) {
+ file.ReadFromOffset(&header_name, kMaxSectionNameLen, name_offset);
+ if (n_read < 0) {
return false;
} else if (n_read > kMaxSectionNameLen) {
// Long read?
return false;
}
- absl::string_view name(header_name, strnlen(header_name, n_read));
+ absl::string_view name(header_name,
+ strnlen(header_name, static_cast<size_t>(n_read)));
if (!callback(name, out)) {
break;
}
@@ -564,26 +640,33 @@
return false;
}
+ char buf[kSmallFileCacheSize];
+ CachingFile file(fd, buf, sizeof(buf));
ElfW(Ehdr) elf_header;
- if (!ReadFromOffsetExact(fd, &elf_header, sizeof(elf_header), 0)) {
+ if (!file.ReadFromOffsetExact(&elf_header, sizeof(elf_header), 0)) {
+ return false;
+ }
+
+ // Technically it can be larger, but in practice this never happens.
+ if (elf_header.e_shentsize != sizeof(ElfW(Shdr))) {
return false;
}
ElfW(Shdr) shstrtab;
- off_t shstrtab_offset =
- (elf_header.e_shoff + elf_header.e_shentsize * elf_header.e_shstrndx);
- if (!ReadFromOffsetExact(fd, &shstrtab, sizeof(shstrtab), shstrtab_offset)) {
+ off_t shstrtab_offset = static_cast<off_t>(elf_header.e_shoff) +
+ elf_header.e_shentsize * elf_header.e_shstrndx;
+ if (!file.ReadFromOffsetExact(&shstrtab, sizeof(shstrtab), shstrtab_offset)) {
return false;
}
for (int i = 0; i < elf_header.e_shnum; ++i) {
off_t section_header_offset =
- (elf_header.e_shoff + elf_header.e_shentsize * i);
- if (!ReadFromOffsetExact(fd, out, sizeof(*out), section_header_offset)) {
+ static_cast<off_t>(elf_header.e_shoff) + elf_header.e_shentsize * i;
+ if (!file.ReadFromOffsetExact(out, sizeof(*out), section_header_offset)) {
return false;
}
- off_t name_offset = shstrtab.sh_offset + out->sh_name;
- ssize_t n_read = ReadFromOffset(fd, &header_name, name_len, name_offset);
+ off_t name_offset = static_cast<off_t>(shstrtab.sh_offset) + out->sh_name;
+ ssize_t n_read = file.ReadFromOffset(&header_name, name_len, name_offset);
if (n_read < 0) {
return false;
} else if (static_cast<size_t>(n_read) != name_len) {
@@ -633,17 +716,19 @@
}
// Return true if an address is inside a section.
-static bool InSection(const void *address, const ElfW(Shdr) * section) {
- const char *start = reinterpret_cast<const char *>(section->sh_addr);
+static bool InSection(const void *address, ptrdiff_t relocation,
+ const ElfW(Shdr) * section) {
+ const char *start = reinterpret_cast<const char *>(
+ section->sh_addr + static_cast<ElfW(Addr)>(relocation));
size_t size = static_cast<size_t>(section->sh_size);
return start <= address && address < (start + size);
}
static const char *ComputeOffset(const char *base, ptrdiff_t offset) {
- // Note: cast to uintptr_t to avoid undefined behavior when base evaluates to
+ // Note: cast to intptr_t to avoid undefined behavior when base evaluates to
// zero and offset is non-zero.
- return reinterpret_cast<const char *>(
- reinterpret_cast<uintptr_t>(base) + offset);
+ return reinterpret_cast<const char *>(reinterpret_cast<intptr_t>(base) +
+ offset);
}
// Read a symbol table and look for the symbol containing the
@@ -656,42 +741,45 @@
// To keep stack consumption low, we would like this function to not get
// inlined.
static ABSL_ATTRIBUTE_NOINLINE FindSymbolResult FindSymbol(
- const void *const pc, const int fd, char *out, int out_size,
+ const void *const pc, CachingFile *file, char *out, size_t out_size,
ptrdiff_t relocation, const ElfW(Shdr) * strtab, const ElfW(Shdr) * symtab,
- const ElfW(Shdr) * opd, char *tmp_buf, int tmp_buf_size) {
+ const ElfW(Shdr) * opd, char *tmp_buf, size_t tmp_buf_size) {
if (symtab == nullptr) {
return SYMBOL_NOT_FOUND;
}
// Read multiple symbols at once to save read() calls.
ElfW(Sym) *buf = reinterpret_cast<ElfW(Sym) *>(tmp_buf);
- const int buf_entries = tmp_buf_size / sizeof(buf[0]);
+ const size_t buf_entries = tmp_buf_size / sizeof(buf[0]);
- const int num_symbols = symtab->sh_size / symtab->sh_entsize;
+ const size_t num_symbols = symtab->sh_size / symtab->sh_entsize;
// On platforms using an .opd section (PowerPC & IA64), a function symbol
// has the address of a function descriptor, which contains the real
// starting address. However, we do not always want to use the real
// starting address because we sometimes want to symbolize a function
// pointer into the .opd section, e.g. FindSymbol(&foo,...).
- const bool pc_in_opd =
- kPlatformUsesOPDSections && opd != nullptr && InSection(pc, opd);
+ const bool pc_in_opd = kPlatformUsesOPDSections && opd != nullptr &&
+ InSection(pc, relocation, opd);
const bool deref_function_descriptor_pointer =
kPlatformUsesOPDSections && opd != nullptr && !pc_in_opd;
ElfW(Sym) best_match;
SafeMemZero(&best_match, sizeof(best_match));
bool found_match = false;
- for (int i = 0; i < num_symbols;) {
- off_t offset = symtab->sh_offset + i * symtab->sh_entsize;
- const int num_remaining_symbols = num_symbols - i;
- const int entries_in_chunk = std::min(num_remaining_symbols, buf_entries);
- const int bytes_in_chunk = entries_in_chunk * sizeof(buf[0]);
- const ssize_t len = ReadFromOffset(fd, buf, bytes_in_chunk, offset);
- SAFE_ASSERT(len % sizeof(buf[0]) == 0);
- const ssize_t num_symbols_in_buf = len / sizeof(buf[0]);
+ for (size_t i = 0; i < num_symbols;) {
+ off_t offset =
+ static_cast<off_t>(symtab->sh_offset + i * symtab->sh_entsize);
+ const size_t num_remaining_symbols = num_symbols - i;
+ const size_t entries_in_chunk =
+ std::min(num_remaining_symbols, buf_entries);
+ const size_t bytes_in_chunk = entries_in_chunk * sizeof(buf[0]);
+ const ssize_t len = file->ReadFromOffset(buf, bytes_in_chunk, offset);
+ SAFE_ASSERT(len >= 0);
+ SAFE_ASSERT(static_cast<size_t>(len) % sizeof(buf[0]) == 0);
+ const size_t num_symbols_in_buf = static_cast<size_t>(len) / sizeof(buf[0]);
SAFE_ASSERT(num_symbols_in_buf <= entries_in_chunk);
- for (int j = 0; j < num_symbols_in_buf; ++j) {
+ for (size_t j = 0; j < num_symbols_in_buf; ++j) {
const ElfW(Sym) &symbol = buf[j];
// For a DSO, a symbol address is relocated by the loading address.
@@ -708,11 +796,11 @@
// about what encoding is being used; we just want the real start address
// of the function.
start_address = reinterpret_cast<const char *>(
- reinterpret_cast<uintptr_t>(start_address) & ~1);
+ reinterpret_cast<uintptr_t>(start_address) & ~1u);
#endif
if (deref_function_descriptor_pointer &&
- InSection(original_start_address, opd)) {
+ InSection(original_start_address, /*relocation=*/0, opd)) {
// The opd section is mapped into memory. Just dereference
// start_address to get the first double word, which points to the
// function entry.
@@ -721,7 +809,8 @@
// If pc is inside the .opd section, it points to a function descriptor.
const size_t size = pc_in_opd ? kFunctionDescriptorSize : symbol.st_size;
- const void *const end_address = ComputeOffset(start_address, size);
+ const void *const end_address =
+ ComputeOffset(start_address, static_cast<ptrdiff_t>(size));
if (symbol.st_value != 0 && // Skip null value symbols.
symbol.st_shndx != 0 && // Skip undefined symbols.
#ifdef STT_TLS
@@ -739,16 +828,18 @@
}
if (found_match) {
- const size_t off = strtab->sh_offset + best_match.st_name;
- const ssize_t n_read = ReadFromOffset(fd, out, out_size, off);
+ const off_t off =
+ static_cast<off_t>(strtab->sh_offset) + best_match.st_name;
+ const ssize_t n_read = file->ReadFromOffset(out, out_size, off);
if (n_read <= 0) {
// This should never happen.
ABSL_RAW_LOG(WARNING,
- "Unable to read from fd %d at offset %zu: n_read = %zd", fd,
- off, n_read);
+ "Unable to read from fd %d at offset %lld: n_read = %zd",
+ file->fd(), static_cast<long long>(off), n_read);
return SYMBOL_NOT_FOUND;
}
- ABSL_RAW_CHECK(n_read <= out_size, "ReadFromOffset read too much data.");
+ ABSL_RAW_CHECK(static_cast<size_t>(n_read) <= out_size,
+ "ReadFromOffset read too much data.");
// strtab->sh_offset points into .strtab-like section that contains
// NUL-terminated strings: '\0foo\0barbaz\0...".
@@ -756,7 +847,7 @@
// sh_offset+st_name points to the start of symbol name, but we don't know
// how long the symbol is, so we try to read as much as we have space for,
// and usually over-read (i.e. there is a NUL somewhere before n_read).
- if (memchr(out, '\0', n_read) == nullptr) {
+ if (memchr(out, '\0', static_cast<size_t>(n_read)) == nullptr) {
// Either out_size was too small (n_read == out_size and no NUL), or
// we tried to read past the EOF (n_read < out_size) and .strtab is
// corrupt (missing terminating NUL; should never happen for valid ELF).
@@ -774,7 +865,7 @@
// See FindSymbol() comment for description of return value.
FindSymbolResult Symbolizer::GetSymbolFromObjectFile(
const ObjFile &obj, const void *const pc, const ptrdiff_t relocation,
- char *out, int out_size, char *tmp_buf, int tmp_buf_size) {
+ char *out, size_t out_size, char *tmp_buf, size_t tmp_buf_size) {
ElfW(Shdr) symtab;
ElfW(Shdr) strtab;
ElfW(Shdr) opd;
@@ -794,20 +885,24 @@
}
}
+ CachingFile file(obj.fd, file_cache_, sizeof(file_cache_));
+
// Consult a regular symbol table, then fall back to the dynamic symbol table.
for (const auto symbol_table_type : {SHT_SYMTAB, SHT_DYNSYM}) {
- if (!GetSectionHeaderByType(obj.fd, obj.elf_header.e_shnum,
- obj.elf_header.e_shoff, symbol_table_type,
+ if (!GetSectionHeaderByType(&file, obj.elf_header.e_shnum,
+ static_cast<off_t>(obj.elf_header.e_shoff),
+ static_cast<ElfW(Word)>(symbol_table_type),
&symtab, tmp_buf, tmp_buf_size)) {
continue;
}
- if (!ReadFromOffsetExact(
- obj.fd, &strtab, sizeof(strtab),
- obj.elf_header.e_shoff + symtab.sh_link * sizeof(symtab))) {
+ if (!file.ReadFromOffsetExact(
+ &strtab, sizeof(strtab),
+ static_cast<off_t>(obj.elf_header.e_shoff +
+ symtab.sh_link * sizeof(symtab)))) {
continue;
}
const FindSymbolResult rc =
- FindSymbol(pc, obj.fd, out, out_size, relocation, &strtab, &symtab,
+ FindSymbol(pc, &file, out, out_size, relocation, &strtab, &symtab,
opd_ptr, tmp_buf, tmp_buf_size);
if (rc != SYMBOL_NOT_FOUND) {
return rc;
@@ -828,7 +923,7 @@
~FileDescriptor() {
if (fd_ >= 0) {
- NO_INTR(close(fd_));
+ close(fd_);
}
}
@@ -845,7 +940,7 @@
// and snprintf().
class LineReader {
public:
- explicit LineReader(int fd, char *buf, int buf_len)
+ explicit LineReader(int fd, char *buf, size_t buf_len)
: fd_(fd),
buf_len_(buf_len),
buf_(buf),
@@ -873,12 +968,12 @@
bol_ = eol_ + 1; // Advance to the next line in the buffer.
SAFE_ASSERT(bol_ <= eod_); // "bol_" can point to "eod_".
if (!HasCompleteLine()) {
- const int incomplete_line_length = eod_ - bol_;
+ const auto incomplete_line_length = static_cast<size_t>(eod_ - bol_);
// Move the trailing incomplete line to the beginning.
memmove(buf_, bol_, incomplete_line_length);
// Read text from file and append it.
char *const append_pos = buf_ + incomplete_line_length;
- const int capacity_left = buf_len_ - incomplete_line_length;
+ const size_t capacity_left = buf_len_ - incomplete_line_length;
const ssize_t num_bytes =
ReadPersistent(fd_, append_pos, capacity_left);
if (num_bytes <= 0) { // EOF or error.
@@ -901,7 +996,8 @@
private:
char *FindLineFeed() const {
- return reinterpret_cast<char *>(memchr(bol_, '\n', eod_ - bol_));
+ return reinterpret_cast<char *>(
+ memchr(bol_, '\n', static_cast<size_t>(eod_ - bol_)));
}
bool BufferIsEmpty() const { return buf_ == eod_; }
@@ -911,7 +1007,7 @@
}
const int fd_;
- const int buf_len_;
+ const size_t buf_len_;
char *const buf_;
char *bol_;
char *eol_;
@@ -929,7 +1025,8 @@
int ch = *p;
if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'F') ||
(ch >= 'a' && ch <= 'f')) {
- hex = (hex << 4) | (ch < 'A' ? ch - '0' : (ch & 0xF) + 9);
+ hex = (hex << 4) |
+ static_cast<uint64_t>(ch < 'A' ? ch - '0' : (ch & 0xF) + 9);
} else { // Encountered the first non-hex character.
break;
}
@@ -961,7 +1058,7 @@
static ABSL_ATTRIBUTE_NOINLINE bool ReadAddrMap(
bool (*callback)(const char *filename, const void *const start_addr,
const void *const end_addr, uint64_t offset, void *arg),
- void *arg, void *tmp_buf, int tmp_buf_size) {
+ void *arg, void *tmp_buf, size_t tmp_buf_size) {
// Use /proc/self/task/<pid>/maps instead of /proc/self/maps. The latter
// requires kernel to stop all threads, and is significantly slower when there
// are 1000s of threads.
@@ -1076,10 +1173,10 @@
}
}
- int lo = 0;
- int hi = addr_map_.Size();
+ size_t lo = 0;
+ size_t hi = addr_map_.Size();
while (lo < hi) {
- int mid = (lo + hi) / 2;
+ size_t mid = (lo + hi) / 2;
if (addr < addr_map_.At(mid)->end_addr) {
hi = mid;
} else {
@@ -1101,11 +1198,11 @@
}
void Symbolizer::ClearAddrMap() {
- for (int i = 0; i != addr_map_.Size(); i++) {
+ for (size_t i = 0; i != addr_map_.Size(); i++) {
ObjFile *o = addr_map_.At(i);
base_internal::LowLevelAlloc::Free(o->filename);
if (o->fd >= 0) {
- NO_INTR(close(o->fd));
+ close(o->fd);
}
}
addr_map_.Clear();
@@ -1121,7 +1218,7 @@
// Files are supposed to be added in the increasing address order. Make
// sure that's the case.
- int addr_map_size = impl->addr_map_.Size();
+ size_t addr_map_size = impl->addr_map_.Size();
if (addr_map_size != 0) {
ObjFile *old = impl->addr_map_.At(addr_map_size - 1);
if (old->end_addr > end_addr) {
@@ -1141,6 +1238,14 @@
reinterpret_cast<uintptr_t>(old->end_addr), old->filename);
}
return true;
+ } else if (old->end_addr == start_addr &&
+ reinterpret_cast<uintptr_t>(old->start_addr) - old->offset ==
+ reinterpret_cast<uintptr_t>(start_addr) - offset &&
+ strcmp(old->filename, filename) == 0) {
+ // Two contiguous map entries that span a contiguous region of the file,
+ // perhaps because some part of the file was mlock()ed. Combine them.
+ old->end_addr = end_addr;
+ return true;
}
}
ObjFile *obj = impl->addr_map_.Add();
@@ -1157,12 +1262,12 @@
// where the input symbol is demangled in-place.
// To keep stack consumption low, we would like this function to not
// get inlined.
-static ABSL_ATTRIBUTE_NOINLINE void DemangleInplace(char *out, int out_size,
+static ABSL_ATTRIBUTE_NOINLINE void DemangleInplace(char *out, size_t out_size,
char *tmp_buf,
- int tmp_buf_size) {
+ size_t tmp_buf_size) {
if (Demangle(out, tmp_buf, tmp_buf_size)) {
// Demangling succeeded. Copy to out if the space allows.
- int len = strlen(tmp_buf);
+ size_t len = strlen(tmp_buf);
if (len + 1 <= out_size) { // +1 for '\0'.
SAFE_ASSERT(len < tmp_buf_size);
memmove(out, tmp_buf, len + 1);
@@ -1205,7 +1310,8 @@
SymbolCacheLine *line = GetCacheLine(pc);
uint32_t max_age = 0;
- int oldest_index = -1;
+ size_t oldest_index = 0;
+ bool found_oldest_index = false;
for (size_t i = 0; i < ABSL_ARRAYSIZE(line->pc); ++i) {
if (line->pc[i] == nullptr) {
AgeSymbols(line);
@@ -1217,11 +1323,12 @@
if (line->age[i] >= max_age) {
max_age = line->age[i];
oldest_index = i;
+ found_oldest_index = true;
}
}
AgeSymbols(line);
- ABSL_RAW_CHECK(oldest_index >= 0, "Corrupt cache");
+ ABSL_RAW_CHECK(found_oldest_index, "Corrupt cache");
base_internal::LowLevelAlloc::Free(line->name[oldest_index]);
line->pc[oldest_index] = pc;
line->name[oldest_index] = CopyString(name);
@@ -1276,46 +1383,63 @@
ABSL_RAW_LOG(WARNING, "%s: open failed: errno=%d", obj->filename, errno);
return false;
}
- obj->elf_type = FileGetElfType(obj->fd);
+
+ char buf[kSmallFileCacheSize];
+ CachingFile file(obj->fd, buf, sizeof(buf));
+
+ obj->elf_type = FileGetElfType(&file);
if (obj->elf_type < 0) {
ABSL_RAW_LOG(WARNING, "%s: wrong elf type: %d", obj->filename,
obj->elf_type);
return false;
}
- if (!ReadFromOffsetExact(obj->fd, &obj->elf_header, sizeof(obj->elf_header),
- 0)) {
+ if (!file.ReadFromOffsetExact(&obj->elf_header, sizeof(obj->elf_header),
+ 0)) {
ABSL_RAW_LOG(WARNING, "%s: failed to read elf header", obj->filename);
return false;
}
const int phnum = obj->elf_header.e_phnum;
const int phentsize = obj->elf_header.e_phentsize;
- size_t phoff = obj->elf_header.e_phoff;
- size_t num_executable_load_segments = 0;
+ auto phoff = static_cast<off_t>(obj->elf_header.e_phoff);
+ size_t num_interesting_load_segments = 0;
for (int j = 0; j < phnum; j++) {
ElfW(Phdr) phdr;
- if (!ReadFromOffsetExact(obj->fd, &phdr, sizeof(phdr), phoff)) {
+ if (!file.ReadFromOffsetExact(&phdr, sizeof(phdr), phoff)) {
ABSL_RAW_LOG(WARNING, "%s: failed to read program header %d",
obj->filename, j);
return false;
}
phoff += phentsize;
- constexpr int rx = PF_X | PF_R;
- if (phdr.p_type != PT_LOAD || (phdr.p_flags & rx) != rx) {
- // Not a LOAD segment, or not executable code.
+
+#if defined(__powerpc__) && !(_CALL_ELF > 1)
+ // On the PowerPC ELF v1 ABI, function pointers actually point to function
+ // descriptors. These descriptors are stored in an .opd section, which is
+ // mapped read-only. We thus need to look at all readable segments, not
+ // just the executable ones.
+ constexpr int interesting = PF_R;
+#else
+ constexpr int interesting = PF_X | PF_R;
+#endif
+
+ if (phdr.p_type != PT_LOAD
+ || (phdr.p_flags & interesting) != interesting) {
+ // Not a LOAD segment, not executable code, and not a function
+ // descriptor.
continue;
}
- if (num_executable_load_segments < obj->phdr.size()) {
- memcpy(&obj->phdr[num_executable_load_segments++], &phdr, sizeof(phdr));
+ if (num_interesting_load_segments < obj->phdr.size()) {
+ memcpy(&obj->phdr[num_interesting_load_segments++], &phdr, sizeof(phdr));
} else {
- ABSL_RAW_LOG(WARNING, "%s: too many executable LOAD segments",
- obj->filename);
+ ABSL_RAW_LOG(
+ WARNING, "%s: too many interesting LOAD segments: %zu >= %zu",
+ obj->filename, num_interesting_load_segments, obj->phdr.size());
break;
}
}
- if (num_executable_load_segments == 0) {
- // This object has no "r-x" LOAD segments. That's unexpected.
- ABSL_RAW_LOG(WARNING, "%s: no executable LOAD segments", obj->filename);
+ if (num_interesting_load_segments == 0) {
+ // This object has no interesting LOAD segments. That's unexpected.
+ ABSL_RAW_LOG(WARNING, "%s: no interesting LOAD segments", obj->filename);
return false;
}
}
@@ -1329,13 +1453,7 @@
// they are called here as well.
// To keep stack consumption low, we would like this function to not
// get inlined.
-const char *Symbolizer::GetSymbol(const void *const pc) {
- const char *entry = FindSymbolInCache(pc);
- if (entry != nullptr) {
- return entry;
- }
- symbol_buf_[0] = '\0';
-
+const char *Symbolizer::GetUncachedSymbol(const void *pc) {
ObjFile *const obj = FindObjFile(pc, 1);
ptrdiff_t relocation = 0;
int fd = -1;
@@ -1347,10 +1465,10 @@
//
// For obj->offset > 0, adjust the relocation since a mapping at offset
// X in the file will have a start address of [true relocation]+X.
- relocation = start_addr - obj->offset;
+ relocation = static_cast<ptrdiff_t>(start_addr - obj->offset);
- // Note: some binaries have multiple "rx" LOAD segments. We must
- // find the right one.
+ // Note: some binaries have multiple LOAD segments that can contain
+ // function pointers. We must find the right one.
ElfW(Phdr) *phdr = nullptr;
for (size_t j = 0; j < obj->phdr.size(); j++) {
ElfW(Phdr) &p = obj->phdr[j];
@@ -1360,7 +1478,7 @@
ABSL_RAW_CHECK(p.p_type == PT_NULL, "unexpected p_type");
break;
}
- if (pc < reinterpret_cast<void *>(start_addr + p.p_memsz)) {
+ if (pc < reinterpret_cast<void *>(start_addr + p.p_vaddr + p.p_memsz)) {
phdr = &p;
break;
}
@@ -1423,6 +1541,42 @@
return InsertSymbolInCache(pc, symbol_buf_);
}
+const char *Symbolizer::GetSymbol(const void *pc) {
+ const char *entry = FindSymbolInCache(pc);
+ if (entry != nullptr) {
+ return entry;
+ }
+ symbol_buf_[0] = '\0';
+
+#ifdef __hppa__
+ {
+ // In some contexts (e.g., return addresses), PA-RISC uses the lowest two
+ // bits of the address to indicate the privilege level. Clear those bits
+ // before trying to symbolize.
+ const auto pc_bits = reinterpret_cast<uintptr_t>(pc);
+ const auto address = pc_bits & ~0x3;
+ entry = GetUncachedSymbol(reinterpret_cast<const void *>(address));
+ if (entry != nullptr) {
+ return entry;
+ }
+
+ // In some contexts, PA-RISC also uses bit 1 of the address to indicate that
+ // this is a cross-DSO function pointer. Such function pointers actually
+ // point to a procedure label, a struct whose first 32-bit (pointer) element
+ // actually points to the function text. With no symbol found for this
+ // address so far, try interpreting it as a cross-DSO function pointer and
+ // see how that goes.
+ if (pc_bits & 0x2) {
+ return GetUncachedSymbol(*reinterpret_cast<const void *const *>(address));
+ }
+
+ return nullptr;
+ }
+#else
+ return GetUncachedSymbol(pc);
+#endif
+}
+
bool RemoveAllSymbolDecorators(void) {
if (!g_decorators_mu.TryLock()) {
// Someone else is using decorators. Get out.
@@ -1486,7 +1640,7 @@
ret = false;
} else {
// TODO(ckennelly): Move this into a string copy routine.
- int len = strlen(filename);
+ size_t len = strlen(filename);
char *dst = static_cast<char *>(
base_internal::LowLevelAlloc::AllocWithArena(len + 1, SigSafeArena()));
ABSL_RAW_CHECK(dst != nullptr, "out of memory");
@@ -1542,16 +1696,17 @@
const char *name = s->GetSymbol(pc);
bool ok = false;
if (name != nullptr && out_size > 0) {
- strncpy(out, name, out_size);
+ strncpy(out, name, static_cast<size_t>(out_size));
ok = true;
- if (out[out_size - 1] != '\0') {
+ if (out[static_cast<size_t>(out_size) - 1] != '\0') {
// strncpy() does not '\0' terminate when it truncates. Do so, with
// trailing ellipsis.
static constexpr char kEllipsis[] = "...";
- int ellipsis_size =
- std::min(implicit_cast<int>(strlen(kEllipsis)), out_size - 1);
- memcpy(out + out_size - ellipsis_size - 1, kEllipsis, ellipsis_size);
- out[out_size - 1] = '\0';
+ size_t ellipsis_size =
+ std::min(strlen(kEllipsis), static_cast<size_t>(out_size) - 1);
+ memcpy(out + static_cast<size_t>(out_size) - ellipsis_size - 1, kEllipsis,
+ ellipsis_size);
+ out[static_cast<size_t>(out_size) - 1] = '\0';
}
}
debugging_internal::FreeSymbolizer(s);
diff --git a/absl/debugging/symbolize_emscripten.inc b/absl/debugging/symbolize_emscripten.inc
index c226c45..a0f344d 100644
--- a/absl/debugging/symbolize_emscripten.inc
+++ b/absl/debugging/symbolize_emscripten.inc
@@ -50,6 +50,9 @@
if (!HaveOffsetConverter()) {
return false;
}
+ if (pc == nullptr || out_size <= 0) {
+ return false;
+ }
const char* func_name = emscripten_pc_get_function(pc);
if (func_name == nullptr) {
return false;
diff --git a/absl/debugging/symbolize_test.cc b/absl/debugging/symbolize_test.cc
index c710a3d..d0feab2 100644
--- a/absl/debugging/symbolize_test.cc
+++ b/absl/debugging/symbolize_test.cc
@@ -14,6 +14,10 @@
#include "absl/debugging/symbolize.h"
+#ifdef __EMSCRIPTEN__
+#include <emscripten.h>
+#endif
+
#ifndef _WIN32
#include <fcntl.h>
#include <sys/mman.h>
@@ -29,12 +33,17 @@
#include "absl/base/casts.h"
#include "absl/base/config.h"
#include "absl/base/internal/per_thread_tls.h"
-#include "absl/base/internal/raw_logging.h"
#include "absl/base/optimization.h"
#include "absl/debugging/internal/stack_consumption.h"
+#include "absl/log/check.h"
+#include "absl/log/log.h"
#include "absl/memory/memory.h"
#include "absl/strings/string_view.h"
+#if defined(MAP_ANON) && !defined(MAP_ANONYMOUS)
+#define MAP_ANONYMOUS MAP_ANON
+#endif
+
using testing::Contains;
#ifdef _WIN32
@@ -81,21 +90,13 @@
return 0;
}
-int ABSL_ATTRIBUTE_SECTION_VARIABLE(.text.hot) hot_func() {
- return 0;
-}
+int ABSL_ATTRIBUTE_SECTION_VARIABLE(.text.hot) hot_func() { return 0; }
-int ABSL_ATTRIBUTE_SECTION_VARIABLE(.text.startup) startup_func() {
- return 0;
-}
+int ABSL_ATTRIBUTE_SECTION_VARIABLE(.text.startup) startup_func() { return 0; }
-int ABSL_ATTRIBUTE_SECTION_VARIABLE(.text.exit) exit_func() {
- return 0;
-}
+int ABSL_ATTRIBUTE_SECTION_VARIABLE(.text.exit) exit_func() { return 0; }
-int /*ABSL_ATTRIBUTE_SECTION_VARIABLE(.text)*/ regular_func() {
- return 0;
-}
+int /*ABSL_ATTRIBUTE_SECTION_VARIABLE(.text)*/ regular_func() { return 0; }
// Thread-local data may confuse the symbolizer, ensure that it does not.
// Variable sizes and order are important.
@@ -106,6 +107,8 @@
#endif
#if !defined(__EMSCRIPTEN__)
+static void *GetPCFromFnPtr(void *ptr) { return ptr; }
+
// Used below to hopefully inhibit some compiler/linker optimizations
// that may remove kHpageTextPadding, kPadding0, and kPadding1 from
// the binary.
@@ -114,7 +117,14 @@
// Force the binary to be large enough that a THP .text remap will succeed.
static constexpr size_t kHpageSize = 1 << 21;
const char kHpageTextPadding[kHpageSize * 4] ABSL_ATTRIBUTE_SECTION_VARIABLE(
- .text) = "";
+ .text) = "";
+
+#else
+static void *GetPCFromFnPtr(void *ptr) {
+ return EM_ASM_PTR(
+ { return wasmOffsetConverter.convert(wasmTable.get($0).name, 0); }, ptr);
+}
+
#endif // !defined(__EMSCRIPTEN__)
static char try_symbolize_buffer[4096];
@@ -124,15 +134,17 @@
// absl::Symbolize() returns false, otherwise returns try_symbolize_buffer with
// the result of absl::Symbolize().
static const char *TrySymbolizeWithLimit(void *pc, int limit) {
- ABSL_RAW_CHECK(limit <= sizeof(try_symbolize_buffer),
- "try_symbolize_buffer is too small");
+ CHECK_LE(limit, sizeof(try_symbolize_buffer))
+ << "try_symbolize_buffer is too small";
// Use the heap to facilitate heap and buffer sanitizer tools.
auto heap_buffer = absl::make_unique<char[]>(sizeof(try_symbolize_buffer));
bool found = absl::Symbolize(pc, heap_buffer.get(), limit);
if (found) {
- ABSL_RAW_CHECK(strnlen(heap_buffer.get(), limit) < limit,
- "absl::Symbolize() did not properly terminate the string");
+ CHECK_LT(static_cast<int>(
+ strnlen(heap_buffer.get(), static_cast<size_t>(limit))),
+ limit)
+ << "absl::Symbolize() did not properly terminate the string";
strncpy(try_symbolize_buffer, heap_buffer.get(),
sizeof(try_symbolize_buffer) - 1);
try_symbolize_buffer[sizeof(try_symbolize_buffer) - 1] = '\0';
@@ -155,21 +167,20 @@
#if defined(ABSL_HAVE_ATTRIBUTE_NOINLINE)
void *return_address = __builtin_return_address(0);
const char *symbol = TrySymbolize(return_address);
- ABSL_RAW_CHECK(symbol != nullptr, "TestWithReturnAddress failed");
- ABSL_RAW_CHECK(strcmp(symbol, "main") == 0, "TestWithReturnAddress failed");
+ CHECK_NE(symbol, nullptr) << "TestWithReturnAddress failed";
+ CHECK_STREQ(symbol, "main") << "TestWithReturnAddress failed";
std::cout << "TestWithReturnAddress passed" << std::endl;
#endif
}
-#ifndef ABSL_INTERNAL_HAVE_EMSCRIPTEN_SYMBOLIZE
-
TEST(Symbolize, Cached) {
// Compilers should give us pointers to them.
- EXPECT_STREQ("nonstatic_func", TrySymbolize((void *)(&nonstatic_func)));
-
+ EXPECT_STREQ("nonstatic_func",
+ TrySymbolize(GetPCFromFnPtr((void *)(&nonstatic_func))));
// The name of an internal linkage symbol is not specified; allow either a
// mangled or an unmangled name here.
- const char *static_func_symbol = TrySymbolize((void *)(&static_func));
+ const char *static_func_symbol =
+ TrySymbolize(GetPCFromFnPtr((void *)(&static_func)));
EXPECT_TRUE(strcmp("static_func", static_func_symbol) == 0 ||
strcmp("static_func()", static_func_symbol) == 0);
@@ -179,33 +190,50 @@
TEST(Symbolize, Truncation) {
constexpr char kNonStaticFunc[] = "nonstatic_func";
EXPECT_STREQ("nonstatic_func",
- TrySymbolizeWithLimit((void *)(&nonstatic_func),
+ TrySymbolizeWithLimit(GetPCFromFnPtr((void *)(&nonstatic_func)),
strlen(kNonStaticFunc) + 1));
EXPECT_STREQ("nonstatic_...",
- TrySymbolizeWithLimit((void *)(&nonstatic_func),
+ TrySymbolizeWithLimit(GetPCFromFnPtr((void *)(&nonstatic_func)),
strlen(kNonStaticFunc) + 0));
EXPECT_STREQ("nonstatic...",
- TrySymbolizeWithLimit((void *)(&nonstatic_func),
+ TrySymbolizeWithLimit(GetPCFromFnPtr((void *)(&nonstatic_func)),
strlen(kNonStaticFunc) - 1));
- EXPECT_STREQ("n...", TrySymbolizeWithLimit((void *)(&nonstatic_func), 5));
- EXPECT_STREQ("...", TrySymbolizeWithLimit((void *)(&nonstatic_func), 4));
- EXPECT_STREQ("..", TrySymbolizeWithLimit((void *)(&nonstatic_func), 3));
- EXPECT_STREQ(".", TrySymbolizeWithLimit((void *)(&nonstatic_func), 2));
- EXPECT_STREQ("", TrySymbolizeWithLimit((void *)(&nonstatic_func), 1));
- EXPECT_EQ(nullptr, TrySymbolizeWithLimit((void *)(&nonstatic_func), 0));
+ EXPECT_STREQ("n...", TrySymbolizeWithLimit(
+ GetPCFromFnPtr((void *)(&nonstatic_func)), 5));
+ EXPECT_STREQ("...", TrySymbolizeWithLimit(
+ GetPCFromFnPtr((void *)(&nonstatic_func)), 4));
+ EXPECT_STREQ("..", TrySymbolizeWithLimit(
+ GetPCFromFnPtr((void *)(&nonstatic_func)), 3));
+ EXPECT_STREQ(
+ ".", TrySymbolizeWithLimit(GetPCFromFnPtr((void *)(&nonstatic_func)), 2));
+ EXPECT_STREQ(
+ "", TrySymbolizeWithLimit(GetPCFromFnPtr((void *)(&nonstatic_func)), 1));
+ EXPECT_EQ(nullptr, TrySymbolizeWithLimit(
+ GetPCFromFnPtr((void *)(&nonstatic_func)), 0));
}
TEST(Symbolize, SymbolizeWithDemangling) {
Foo::func(100);
- EXPECT_STREQ("Foo::func()", TrySymbolize((void *)(&Foo::func)));
+#ifdef __EMSCRIPTEN__
+ // Emscripten's online symbolizer is more precise with arguments.
+ EXPECT_STREQ("Foo::func(int)",
+ TrySymbolize(GetPCFromFnPtr((void *)(&Foo::func))));
+#else
+ EXPECT_STREQ("Foo::func()",
+ TrySymbolize(GetPCFromFnPtr((void *)(&Foo::func))));
+#endif
}
TEST(Symbolize, SymbolizeSplitTextSections) {
- EXPECT_STREQ("unlikely_func()", TrySymbolize((void *)(&unlikely_func)));
- EXPECT_STREQ("hot_func()", TrySymbolize((void *)(&hot_func)));
- EXPECT_STREQ("startup_func()", TrySymbolize((void *)(&startup_func)));
- EXPECT_STREQ("exit_func()", TrySymbolize((void *)(&exit_func)));
- EXPECT_STREQ("regular_func()", TrySymbolize((void *)(®ular_func)));
+ EXPECT_STREQ("unlikely_func()",
+ TrySymbolize(GetPCFromFnPtr((void *)(&unlikely_func))));
+ EXPECT_STREQ("hot_func()", TrySymbolize(GetPCFromFnPtr((void *)(&hot_func))));
+ EXPECT_STREQ("startup_func()",
+ TrySymbolize(GetPCFromFnPtr((void *)(&startup_func))));
+ EXPECT_STREQ("exit_func()",
+ TrySymbolize(GetPCFromFnPtr((void *)(&exit_func))));
+ EXPECT_STREQ("regular_func()",
+ TrySymbolize(GetPCFromFnPtr((void *)(®ular_func))));
}
// Tests that verify that Symbolize stack footprint is within some limit.
@@ -275,15 +303,14 @@
#endif // ABSL_INTERNAL_HAVE_DEBUGGING_STACK_CONSUMPTION
-#ifndef ABSL_INTERNAL_HAVE_DARWIN_SYMBOLIZE
+#if !defined(ABSL_INTERNAL_HAVE_DARWIN_SYMBOLIZE) && \
+ !defined(ABSL_INTERNAL_HAVE_EMSCRIPTEN_SYMBOLIZE)
// Use a 64K page size for PPC.
const size_t kPageSize = 64 << 10;
// We place a read-only symbols into the .text section and verify that we can
// symbolize them and other symbols after remapping them.
-const char kPadding0[kPageSize * 4] ABSL_ATTRIBUTE_SECTION_VARIABLE(.text) =
- "";
-const char kPadding1[kPageSize * 4] ABSL_ATTRIBUTE_SECTION_VARIABLE(.text) =
- "";
+const char kPadding0[kPageSize * 4] ABSL_ATTRIBUTE_SECTION_VARIABLE(.text) = "";
+const char kPadding1[kPageSize * 4] ABSL_ATTRIBUTE_SECTION_VARIABLE(.text) = "";
static int FilterElfHeader(struct dl_phdr_info *info, size_t size, void *data) {
for (int i = 0; i < info->dlpi_phnum; i++) {
@@ -314,8 +341,8 @@
TEST(Symbolize, SymbolizeWithMultipleMaps) {
// Force kPadding0 and kPadding1 to be linked in.
if (volatile_bool) {
- ABSL_RAW_LOG(INFO, "%s", kPadding0);
- ABSL_RAW_LOG(INFO, "%s", kPadding1);
+ LOG(INFO) << kPadding0;
+ LOG(INFO) << kPadding1;
}
// Verify we can symbolize everything.
@@ -392,12 +419,14 @@
DummySymbolDecorator, &c_message),
0);
- char *address = reinterpret_cast<char *>(1);
- EXPECT_STREQ("abc", TrySymbolize(address++));
+ // Use addresses 4 and 8 here to ensure that we always use valid addresses
+ // even on systems that require instructions to be 32-bit aligned.
+ char *address = reinterpret_cast<char *>(4);
+ EXPECT_STREQ("abc", TrySymbolize(address));
EXPECT_TRUE(absl::debugging_internal::RemoveSymbolDecorator(ticket_b));
- EXPECT_STREQ("ac", TrySymbolize(address++));
+ EXPECT_STREQ("ac", TrySymbolize(address + 4));
// Cleanup: remove all remaining decorators so other stack traces don't
// get mystery "ac" decoration.
@@ -431,17 +460,17 @@
close(fd);
}
-#endif // !ABSL_INTERNAL_HAVE_DARWIN_SYMBOLIZE
-#endif // !ABSL_INTERNAL_HAVE_EMSCRIPTEN_SYMBOLIZE
+#endif // !ABSL_INTERNAL_HAVE_DARWIN_SYMBOLIZE &&
+ // !ABSL_INTERNAL_HAVE_EMSCRIPTEN_SYMBOLIZE
// x86 specific tests. Uses some inline assembler.
extern "C" {
inline void *ABSL_ATTRIBUTE_ALWAYS_INLINE inline_func() {
void *pc = nullptr;
#if defined(__i386__)
- __asm__ __volatile__("call 1f;\n 1: pop %[PC]" : [ PC ] "=r"(pc));
+ __asm__ __volatile__("call 1f;\n 1: pop %[PC]" : [PC] "=r"(pc));
#elif defined(__x86_64__)
- __asm__ __volatile__("leaq 0(%%rip),%[PC];\n" : [ PC ] "=r"(pc));
+ __asm__ __volatile__("leaq 0(%%rip),%[PC];\n" : [PC] "=r"(pc));
#endif
return pc;
}
@@ -449,9 +478,9 @@
void *ABSL_ATTRIBUTE_NOINLINE non_inline_func() {
void *pc = nullptr;
#if defined(__i386__)
- __asm__ __volatile__("call 1f;\n 1: pop %[PC]" : [ PC ] "=r"(pc));
+ __asm__ __volatile__("call 1f;\n 1: pop %[PC]" : [PC] "=r"(pc));
#elif defined(__x86_64__)
- __asm__ __volatile__("leaq 0(%%rip),%[PC];\n" : [ PC ] "=r"(pc));
+ __asm__ __volatile__("leaq 0(%%rip),%[PC];\n" : [PC] "=r"(pc));
#endif
return pc;
}
@@ -461,9 +490,9 @@
(defined(__i386__) || defined(__x86_64__))
void *pc = non_inline_func();
const char *symbol = TrySymbolize(pc);
- ABSL_RAW_CHECK(symbol != nullptr, "TestWithPCInsideNonInlineFunction failed");
- ABSL_RAW_CHECK(strcmp(symbol, "non_inline_func") == 0,
- "TestWithPCInsideNonInlineFunction failed");
+ CHECK_NE(symbol, nullptr) << "TestWithPCInsideNonInlineFunction failed";
+ CHECK_STREQ(symbol, "non_inline_func")
+ << "TestWithPCInsideNonInlineFunction failed";
std::cout << "TestWithPCInsideNonInlineFunction passed" << std::endl;
#endif
}
@@ -473,15 +502,15 @@
(defined(__i386__) || defined(__x86_64__))
void *pc = inline_func(); // Must be inlined.
const char *symbol = TrySymbolize(pc);
- ABSL_RAW_CHECK(symbol != nullptr, "TestWithPCInsideInlineFunction failed");
- ABSL_RAW_CHECK(strcmp(symbol, __FUNCTION__) == 0,
- "TestWithPCInsideInlineFunction failed");
+ CHECK_NE(symbol, nullptr) << "TestWithPCInsideInlineFunction failed";
+ CHECK_STREQ(symbol, __FUNCTION__) << "TestWithPCInsideInlineFunction failed";
std::cout << "TestWithPCInsideInlineFunction passed" << std::endl;
#endif
}
}
-#if defined(__arm__) && ABSL_HAVE_ATTRIBUTE(target)
+#if defined(__arm__) && ABSL_HAVE_ATTRIBUTE(target) && \
+ ((__ARM_ARCH >= 7) || !defined(__ARM_PCS_VFP))
// Test that we correctly identify bounds of Thumb functions on ARM.
//
// Thumb functions have the lowest-order bit set in their addresses in the ELF
@@ -500,6 +529,10 @@
// bit in the Thumb function's entry point. It will correctly compute the end of
// the Thumb function, it will find no overlap between the Thumb and ARM
// functions, and it will return the name of the ARM function.
+//
+// Unfortunately we cannot perform this test on armv6 or lower systems that use
+// the hard float ABI because gcc refuses to compile thumb functions on such
+// systems with a "sorry, unimplemented: Thumb-1 hard-float VFP ABI" error.
__attribute__((target("thumb"))) int ArmThumbOverlapThumb(int x) {
return x * x * x;
@@ -512,14 +545,14 @@
void ABSL_ATTRIBUTE_NOINLINE TestArmThumbOverlap() {
#if defined(ABSL_HAVE_ATTRIBUTE_NOINLINE)
const char *symbol = TrySymbolize((void *)&ArmThumbOverlapArm);
- ABSL_RAW_CHECK(symbol != nullptr, "TestArmThumbOverlap failed");
- ABSL_RAW_CHECK(strcmp("ArmThumbOverlapArm()", symbol) == 0,
- "TestArmThumbOverlap failed");
+ CHECK_NE(symbol, nullptr) << "TestArmThumbOverlap failed";
+ CHECK_STREQ("ArmThumbOverlapArm()", symbol) << "TestArmThumbOverlap failed";
std::cout << "TestArmThumbOverlap passed" << std::endl;
#endif
}
-#endif // defined(__arm__) && ABSL_HAVE_ATTRIBUTE(target)
+#endif // defined(__arm__) && ABSL_HAVE_ATTRIBUTE(target) && ((__ARM_ARCH >= 7)
+ // || !defined(__ARM_PCS_VFP))
#elif defined(_WIN32)
#if !defined(ABSL_CONSUME_DLL)
@@ -562,7 +595,7 @@
}
#endif // !defined(ABSL_CONSUME_DLL)
-#else // Symbolizer unimplemented
+#else // Symbolizer unimplemented
TEST(Symbolize, Unimplemented) {
char buf[64];
EXPECT_FALSE(absl::Symbolize((void *)(&nonstatic_func), buf, sizeof(buf)));
@@ -576,7 +609,7 @@
#if !defined(__EMSCRIPTEN__)
// Make sure kHpageTextPadding is linked into the binary.
if (volatile_bool) {
- ABSL_RAW_LOG(INFO, "%s", kHpageTextPadding);
+ LOG(INFO) << kHpageTextPadding;
}
#endif // !defined(__EMSCRIPTEN__)
@@ -589,12 +622,14 @@
absl::InitializeSymbolizer(argv[0]);
testing::InitGoogleTest(&argc, argv);
-#if defined(ABSL_INTERNAL_HAVE_ELF_SYMBOLIZE) || \
+#if defined(ABSL_INTERNAL_HAVE_ELF_SYMBOLIZE) || \
+ defined(ABSL_INTERNAL_HAVE_EMSCRIPTEN_SYMBOLIZE) || \
defined(ABSL_INTERNAL_HAVE_DARWIN_SYMBOLIZE)
TestWithPCInsideInlineFunction();
TestWithPCInsideNonInlineFunction();
TestWithReturnAddress();
-#if defined(__arm__) && ABSL_HAVE_ATTRIBUTE(target)
+#if defined(__arm__) && ABSL_HAVE_ATTRIBUTE(target) && \
+ ((__ARM_ARCH >= 7) || !defined(__ARM_PCS_VFP))
TestArmThumbOverlap();
#endif
#endif
diff --git a/absl/debugging/symbolize_win32.inc b/absl/debugging/symbolize_win32.inc
index c3df46f..53a099a 100644
--- a/absl/debugging/symbolize_win32.inc
+++ b/absl/debugging/symbolize_win32.inc
@@ -65,14 +65,15 @@
if (!SymFromAddr(process, reinterpret_cast<DWORD64>(pc), nullptr, symbol)) {
return false;
}
- strncpy(out, symbol->Name, out_size);
- if (out[out_size - 1] != '\0') {
+ const size_t out_size_t = static_cast<size_t>(out_size);
+ strncpy(out, symbol->Name, out_size_t);
+ if (out[out_size_t - 1] != '\0') {
// strncpy() does not '\0' terminate when it truncates.
static constexpr char kEllipsis[] = "...";
- int ellipsis_size =
- std::min<int>(sizeof(kEllipsis) - 1, out_size - 1);
- memcpy(out + out_size - ellipsis_size - 1, kEllipsis, ellipsis_size);
- out[out_size - 1] = '\0';
+ size_t ellipsis_size =
+ std::min(sizeof(kEllipsis) - 1, out_size_t - 1);
+ memcpy(out + out_size_t - ellipsis_size - 1, kEllipsis, ellipsis_size);
+ out[out_size_t - 1] = '\0';
}
return true;
}
diff --git a/absl/flags/BUILD.bazel b/absl/flags/BUILD.bazel
index c178b86..cc513a0 100644
--- a/absl/flags/BUILD.bazel
+++ b/absl/flags/BUILD.bazel
@@ -14,7 +14,6 @@
# limitations under the License.
#
-load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library", "cc_test")
load(
"//absl:copts/configure_copts.bzl",
"ABSL_DEFAULT_COPTS",
@@ -22,7 +21,14 @@
"ABSL_TEST_COPTS",
)
-package(default_visibility = ["//visibility:public"])
+package(
+ default_visibility = ["//visibility:public"],
+ features = [
+ "header_modules",
+ "layering_check",
+ "parse_headers",
+ ],
+)
licenses(["notice"])
@@ -54,6 +60,7 @@
linkopts = ABSL_DEFAULT_LINKOPTS,
visibility = [
"//absl/flags:__pkg__",
+ "//absl/log:__pkg__",
],
deps = [
":path_util",
@@ -99,8 +106,10 @@
"//absl/base:config",
"//absl/base:core_headers",
"//absl/base:log_severity",
+ "//absl/numeric:int128",
"//absl/strings",
"//absl/strings:str_format",
+ "//absl/types:optional",
],
)
@@ -114,6 +123,9 @@
],
copts = ABSL_DEFAULT_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
+ visibility = [
+ "//visibility:private",
+ ],
deps = [
"//absl/base:config",
"//absl/base:fast_type_id",
@@ -178,6 +190,7 @@
":private_handle_accessor",
"//absl/base:config",
"//absl/base:core_headers",
+ "//absl/base:no_destructor",
"//absl/container:flat_hash_map",
"//absl/strings",
"//absl/synchronization",
@@ -205,6 +218,7 @@
"//absl/base",
"//absl/base:config",
"//absl/base:core_headers",
+ "//absl/base:dynamic_annotations",
"//absl/memory",
"//absl/meta:type_traits",
"//absl/strings",
@@ -215,9 +229,6 @@
cc_library(
name = "flag",
- srcs = [
- "flag.cc",
- ],
hdrs = [
"declare.h",
"flag.h",
@@ -225,10 +236,10 @@
copts = ABSL_DEFAULT_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
+ ":commandlineflag",
":config",
":flag_internal",
":reflection",
- "//absl/base",
"//absl/base:config",
"//absl/base:core_headers",
"//absl/strings",
@@ -259,8 +270,8 @@
":reflection",
"//absl/base:config",
"//absl/base:core_headers",
- "//absl/container:flat_hash_map",
"//absl/strings",
+ "//absl/synchronization",
],
)
@@ -278,6 +289,7 @@
":usage_internal",
"//absl/base:config",
"//absl/base:core_headers",
+ "//absl/base:raw_logging_internal",
"//absl/strings",
"//absl/synchronization",
],
@@ -303,6 +315,7 @@
":reflection",
":usage",
":usage_internal",
+ "//absl/algorithm:container",
"//absl/base:config",
"//absl/base:core_headers",
"//absl/strings",
@@ -321,15 +334,22 @@
],
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
+ tags = [
+ "no_test:os:android",
+ "no_test:os:ios",
+ "no_test_android",
+ "no_test_ios",
+ "no_test_wasm",
+ ],
deps = [
":commandlineflag",
- ":commandlineflag_internal",
":config",
":flag",
":private_handle_accessor",
":reflection",
"//absl/memory",
"//absl/strings",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -344,6 +364,7 @@
linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
":config",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -357,6 +378,13 @@
],
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
+ tags = [
+ "no_test:os:android",
+ "no_test:os:ios",
+ "no_test_android",
+ "no_test_ios",
+ "no_test_wasm",
+ ],
deps = [
":config",
":flag",
@@ -365,15 +393,19 @@
":reflection",
"//absl/base:core_headers",
"//absl/base:malloc_internal",
+ "//absl/base:raw_logging_internal",
+ "//absl/numeric:int128",
"//absl/strings",
"//absl/time",
+ "//absl/types:optional",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
cc_binary(
name = "flag_benchmark",
- testonly = 1,
+ testonly = True,
srcs = [
"flag_benchmark.cc",
],
@@ -406,6 +438,7 @@
linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
":marshalling",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -413,20 +446,30 @@
cc_test(
name = "parse_test",
size = "small",
+ timeout = "moderate",
srcs = [
"parse_test.cc",
],
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
+ tags = [
+ "no_test:os:android",
+ "no_test:os:ios",
+ "no_test_android",
+ "no_test_ios",
+ "no_test_wasm",
+ ],
deps = [
+ ":config",
":flag",
":parse",
":reflection",
":usage_internal",
- "//absl/base:raw_logging_internal",
"//absl/base:scoped_set_env",
+ "//absl/log",
"//absl/strings",
"//absl/types:span",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -441,6 +484,7 @@
linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
":path_util",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -453,9 +497,11 @@
],
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
+ tags = ["no_test_wasm"],
deps = [
":program_name",
"//absl/strings",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -468,14 +514,20 @@
],
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
+ tags = [
+ "no_test:os:android",
+ "no_test:os:ios",
+ "no_test_android",
+ "no_test_ios",
+ "no_test_wasm",
+ ],
deps = [
- ":commandlineflag_internal",
+ ":config",
":flag",
- ":marshalling",
":reflection",
- ":usage_internal",
"//absl/memory",
"//absl/strings",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -490,11 +542,13 @@
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
shard_count = 31,
+ tags = ["no_test_wasm"],
deps = [
":flag_internal",
"//absl/base",
"//absl/container:fixed_array",
"//absl/time",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -512,6 +566,7 @@
":path_util",
":program_name",
"//absl/strings",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -524,6 +579,13 @@
],
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
+ tags = [
+ "no_test:os:android",
+ "no_test:os:ios",
+ "no_test_android",
+ "no_test_ios",
+ "no_test_wasm",
+ ],
deps = [
":config",
":flag",
diff --git a/absl/flags/CMakeLists.txt b/absl/flags/CMakeLists.txt
index 956f70f..48cc832 100644
--- a/absl/flags/CMakeLists.txt
+++ b/absl/flags/CMakeLists.txt
@@ -87,6 +87,8 @@
absl::config
absl::core_headers
absl::log_severity
+ absl::int128
+ absl::optional
absl::strings
absl::str_format
)
@@ -105,6 +107,7 @@
${ABSL_DEFAULT_LINKOPTS}
DEPS
absl::config
+ absl::dynamic_annotations
absl::fast_type_id
)
@@ -166,6 +169,7 @@
absl::strings
absl::synchronization
absl::flat_hash_map
+ absl::no_destructor
)
# Internal-only target, do not depend on directly.
@@ -197,8 +201,6 @@
absl_cc_library(
NAME
flags
- SRCS
- "flag.cc"
HDRS
"declare.h"
"flag.h"
@@ -212,7 +214,6 @@
absl::flags_config
absl::flags_internal
absl::flags_reflection
- absl::base
absl::core_headers
absl::strings
)
@@ -239,7 +240,6 @@
absl::flags_private_handle_accessor
absl::flags_program_name
absl::flags_reflection
- absl::flat_hash_map
absl::strings
absl::synchronization
)
@@ -259,6 +259,7 @@
absl::config
absl::core_headers
absl::flags_usage_internal
+ absl::raw_logging_internal
absl::strings
absl::synchronization
)
@@ -276,6 +277,7 @@
LINKOPTS
${ABSL_DEFAULT_LINKOPTS}
DEPS
+ absl::algorithm_container
absl::config
absl::core_headers
absl::flags_config
@@ -292,7 +294,7 @@
)
############################################################################
-# Unit tests in alpahabetical order.
+# Unit tests in alphabetical order.
absl_cc_test(
NAME
@@ -304,7 +306,6 @@
DEPS
absl::flags
absl::flags_commandlineflag
- absl::flags_commandlineflag_internal
absl::flags_config
absl::flags_private_handle_accessor
absl::flags_reflection
@@ -340,6 +341,9 @@
absl::flags_internal
absl::flags_marshalling
absl::flags_reflection
+ absl::int128
+ absl::optional
+ absl::raw_logging_internal
absl::strings
absl::time
GTest::gtest_main
@@ -366,10 +370,11 @@
${ABSL_TEST_COPTS}
DEPS
absl::flags
+ absl::flags_config
absl::flags_parse
absl::flags_reflection
absl::flags_usage_internal
- absl::raw_logging_internal
+ absl::log
absl::scoped_set_env
absl::span
absl::strings
@@ -409,8 +414,8 @@
COPTS
${ABSL_TEST_COPTS}
DEPS
- absl::flags_commandlineflag_internal
absl::flags
+ absl::flags_config
absl::flags_reflection
absl::flags_usage
absl::memory
@@ -463,5 +468,5 @@
absl::flags_reflection
absl::flags_usage
absl::strings
- GTest::gtest
+ GTest::gmock
)
diff --git a/absl/flags/commandlineflag.h b/absl/flags/commandlineflag.h
index f2fa089..c30aa60 100644
--- a/absl/flags/commandlineflag.h
+++ b/absl/flags/commandlineflag.h
@@ -186,7 +186,7 @@
// command line.
virtual bool IsSpecifiedOnCommandLine() const = 0;
- // Validates supplied value usign validator or parseflag routine
+ // Validates supplied value using validator or parseflag routine
virtual bool ValidateInputValue(absl::string_view value) const = 0;
// Checks that flags default value can be converted to string and back to the
diff --git a/absl/flags/commandlineflag_test.cc b/absl/flags/commandlineflag_test.cc
index 585db4b..54700cf 100644
--- a/absl/flags/commandlineflag_test.cc
+++ b/absl/flags/commandlineflag_test.cc
@@ -19,8 +19,8 @@
#include <string>
#include "gtest/gtest.h"
+#include "absl/flags/config.h"
#include "absl/flags/flag.h"
-#include "absl/flags/internal/commandlineflag.h"
#include "absl/flags/internal/private_handle_accessor.h"
#include "absl/flags/reflection.h"
#include "absl/flags/usage_config.h"
@@ -51,7 +51,12 @@
absl::SetFlagsUsageConfig(default_config);
}
- void SetUp() override { flag_saver_ = absl::make_unique<absl::FlagSaver>(); }
+ void SetUp() override {
+#if ABSL_FLAGS_STRIP_NAMES
+ GTEST_SKIP() << "This test requires flag names to be present";
+#endif
+ flag_saver_ = absl::make_unique<absl::FlagSaver>();
+ }
void TearDown() override { flag_saver_.reset(); }
private:
diff --git a/absl/flags/config.h b/absl/flags/config.h
index 5ab1f31..14c4235 100644
--- a/absl/flags/config.h
+++ b/absl/flags/config.h
@@ -45,14 +45,6 @@
#define ABSL_FLAGS_STRIP_HELP ABSL_FLAGS_STRIP_NAMES
#endif
-// ABSL_FLAGS_INTERNAL_HAS_RTTI macro is used for selecting if we can use RTTI
-// for flag type identification.
-#ifdef ABSL_FLAGS_INTERNAL_HAS_RTTI
-#error ABSL_FLAGS_INTERNAL_HAS_RTTI cannot be directly set
-#elif !defined(__GNUC__) || defined(__GXX_RTTI)
-#define ABSL_FLAGS_INTERNAL_HAS_RTTI 1
-#endif // !defined(__GNUC__) || defined(__GXX_RTTI)
-
// These macros represent the "source of truth" for the list of supported
// built-in types.
#define ABSL_FLAGS_INTERNAL_BUILTIN_TYPES(A) \
diff --git a/absl/flags/declare.h b/absl/flags/declare.h
index b9794d8..8d2a856 100644
--- a/absl/flags/declare.h
+++ b/absl/flags/declare.h
@@ -40,13 +40,8 @@
// Flag
//
// Forward declaration of the `absl::Flag` type for use in defining the macro.
-#if defined(_MSC_VER) && !defined(__clang__)
-template <typename T>
-class Flag;
-#else
template <typename T>
using Flag = flags_internal::Flag<T>;
-#endif
ABSL_NAMESPACE_END
} // namespace absl
@@ -60,6 +55,14 @@
// The ABSL_DECLARE_FLAG(type, name) macro expands to:
//
// extern absl::Flag<type> FLAGS_name;
-#define ABSL_DECLARE_FLAG(type, name) extern ::absl::Flag<type> FLAGS_##name
+#define ABSL_DECLARE_FLAG(type, name) ABSL_DECLARE_FLAG_INTERNAL(type, name)
+
+// Internal implementation of ABSL_DECLARE_FLAG to allow macro expansion of its
+// arguments. Clients must use ABSL_DECLARE_FLAG instead.
+#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 // ABSL_FLAGS_DECLARE_H_
diff --git a/absl/flags/flag.h b/absl/flags/flag.h
index 14209e7..a8e0e93 100644
--- a/absl/flags/flag.h
+++ b/absl/flags/flag.h
@@ -29,12 +29,14 @@
#ifndef ABSL_FLAGS_FLAG_H_
#define ABSL_FLAGS_FLAG_H_
+#include <cstdint>
#include <string>
#include <type_traits>
#include "absl/base/attributes.h"
#include "absl/base/config.h"
#include "absl/base/optimization.h"
+#include "absl/flags/commandlineflag.h"
#include "absl/flags/config.h"
#include "absl/flags/internal/flag.h"
#include "absl/flags/internal/registry.h"
@@ -67,106 +69,13 @@
// ABSL_FLAG(int, count, 0, "Count of items to process");
//
// No public methods of `absl::Flag<T>` are part of the Abseil Flags API.
-#if !defined(_MSC_VER) || defined(__clang__)
+//
+// For type support of Abseil Flags, see the marshalling.h header file, which
+// discusses supported standard types, optional flags, and additional Abseil
+// type support.
+
template <typename T>
using Flag = flags_internal::Flag<T>;
-#else
-// MSVC debug builds do not implement initialization with constexpr constructors
-// correctly. To work around this we add a level of indirection, so that the
-// class `absl::Flag` contains an `internal::Flag*` (instead of being an alias
-// to that class) and dynamically allocates an instance when necessary. We also
-// forward all calls to internal::Flag methods via trampoline methods. In this
-// setup the `absl::Flag` class does not have constructor and virtual methods,
-// all the data members are public and thus MSVC is able to initialize it at
-// link time. To deal with multiple threads accessing the flag for the first
-// time concurrently we use an atomic boolean indicating if flag object is
-// initialized. We also employ the double-checked locking pattern where the
-// second level of protection is a global Mutex, so if two threads attempt to
-// construct the flag concurrently only one wins.
-// This solution is based on a recomendation here:
-// https://developercommunity.visualstudio.com/content/problem/336946/class-with-constexpr-constructor-not-using-static.html?childToView=648454#comment-648454
-
-namespace flags_internal {
-absl::Mutex* GetGlobalConstructionGuard();
-} // namespace flags_internal
-
-template <typename T>
-class Flag {
- public:
- // No constructor and destructor to ensure this is an aggregate type.
- // Visual Studio 2015 still requires the constructor for class to be
- // constexpr initializable.
-#if _MSC_VER <= 1900
- constexpr Flag(const char* name, const char* filename,
- const flags_internal::HelpGenFunc help_gen,
- const flags_internal::FlagDfltGenFunc default_value_gen)
- : name_(name),
- filename_(filename),
- help_gen_(help_gen),
- default_value_gen_(default_value_gen),
- inited_(false),
- impl_(nullptr) {}
-#endif
-
- flags_internal::Flag<T>& GetImpl() const {
- if (!inited_.load(std::memory_order_acquire)) {
- absl::MutexLock l(flags_internal::GetGlobalConstructionGuard());
-
- if (inited_.load(std::memory_order_acquire)) {
- return *impl_;
- }
-
- impl_ = new flags_internal::Flag<T>(
- name_, filename_,
- {flags_internal::FlagHelpMsg(help_gen_),
- flags_internal::FlagHelpKind::kGenFunc},
- {flags_internal::FlagDefaultSrc(default_value_gen_),
- flags_internal::FlagDefaultKind::kGenFunc});
- inited_.store(true, std::memory_order_release);
- }
-
- return *impl_;
- }
-
- // Public methods of `absl::Flag<T>` are NOT part of the Abseil Flags API.
- // See https://abseil.io/docs/cpp/guides/flags
- bool IsRetired() const { return GetImpl().IsRetired(); }
- absl::string_view Name() const { return GetImpl().Name(); }
- std::string Help() const { return GetImpl().Help(); }
- bool IsModified() const { return GetImpl().IsModified(); }
- bool IsSpecifiedOnCommandLine() const {
- return GetImpl().IsSpecifiedOnCommandLine();
- }
- std::string Filename() const { return GetImpl().Filename(); }
- std::string DefaultValue() const { return GetImpl().DefaultValue(); }
- std::string CurrentValue() const { return GetImpl().CurrentValue(); }
- template <typename U>
- inline bool IsOfType() const {
- return GetImpl().template IsOfType<U>();
- }
- T Get() const {
- return flags_internal::FlagImplPeer::InvokeGet<T>(GetImpl());
- }
- void Set(const T& v) {
- flags_internal::FlagImplPeer::InvokeSet(GetImpl(), v);
- }
- void InvokeCallback() { GetImpl().InvokeCallback(); }
-
- const CommandLineFlag& Reflect() const {
- return flags_internal::FlagImplPeer::InvokeReflect(GetImpl());
- }
-
- // The data members are logically private, but they need to be public for
- // this to be an aggregate type.
- const char* name_;
- const char* filename_;
- const flags_internal::HelpGenFunc help_gen_;
- const flags_internal::FlagDfltGenFunc default_value_gen_;
-
- mutable std::atomic<bool> inited_;
- mutable flags_internal::Flag<T>* impl_;
-};
-#endif
// GetFlag()
//
@@ -286,18 +195,12 @@
// -----------------------------------------------------------------------------
// ABSL_FLAG_IMPL macro definition conditional on ABSL_FLAGS_STRIP_NAMES
-#if !defined(_MSC_VER) || defined(__clang__)
#define ABSL_FLAG_IMPL_FLAG_PTR(flag) flag
#define ABSL_FLAG_IMPL_HELP_ARG(name) \
absl::flags_internal::HelpArg<AbslFlagHelpGenFor##name>( \
FLAGS_help_storage_##name)
#define ABSL_FLAG_IMPL_DEFAULT_ARG(Type, name) \
absl::flags_internal::DefaultArg<Type, AbslFlagDefaultGenFor##name>(0)
-#else
-#define ABSL_FLAG_IMPL_FLAG_PTR(flag) flag.GetImpl()
-#define ABSL_FLAG_IMPL_HELP_ARG(name) &AbslFlagHelpGenFor##name::NonConst
-#define ABSL_FLAG_IMPL_DEFAULT_ARG(Type, name) &AbslFlagDefaultGenFor##name::Gen
-#endif
#if ABSL_FLAGS_STRIP_NAMES
#define ABSL_FLAG_IMPL_FLAGNAME(txt) ""
@@ -335,8 +238,8 @@
/* default value argument. That keeps temporaries alive */ \
/* long enough for NonConst to work correctly. */ \
static constexpr absl::string_view Value( \
- absl::string_view v = ABSL_FLAG_IMPL_FLAGHELP(txt)) { \
- return v; \
+ absl::string_view absl_flag_help = ABSL_FLAG_IMPL_FLAGHELP(txt)) { \
+ return absl_flag_help; \
} \
static std::string NonConst() { return std::string(Value()); } \
}; \
@@ -348,8 +251,8 @@
#define ABSL_FLAG_IMPL_DECLARE_DEF_VAL_WRAPPER(name, Type, default_value) \
struct AbslFlagDefaultGenFor##name { \
Type value = absl::flags_internal::InitDefaultValue<Type>(default_value); \
- static void Gen(void* p) { \
- new (p) Type(AbslFlagDefaultGenFor##name{}.value); \
+ static void Gen(void* absl_flag_default_loc) { \
+ new (absl_flag_default_loc) Type(AbslFlagDefaultGenFor##name{}.value); \
} \
};
@@ -359,6 +262,7 @@
// global name for FLAGS_no<flag_name> symbol, thus preventing the possibility
// of defining two flags with names foo and nofoo.
#define ABSL_FLAG_IMPL(Type, name, default_value, help) \
+ extern ::absl::Flag<Type> FLAGS_##name; \
namespace absl /* block flags in namespaces */ {} \
ABSL_FLAG_IMPL_DECLARE_DEF_VAL_WRAPPER(name, Type, default_value) \
ABSL_FLAG_IMPL_DECLARE_HELP_WRAPPER(name, help) \
diff --git a/absl/flags/flag_benchmark.cc b/absl/flags/flag_benchmark.cc
index fc572d9..758a6a5 100644
--- a/absl/flags/flag_benchmark.cc
+++ b/absl/flags/flag_benchmark.cc
@@ -241,10 +241,11 @@
} // namespace
+#ifdef __llvm__
+// To view disassembly use: gdb ${BINARY} -batch -ex "disassemble /s $FUNC"
#define InvokeGetFlag(T) \
T AbslInvokeGetFlag##T() { return absl::GetFlag(SINGLE_FLAG(T)); } \
int odr##T = (benchmark::DoNotOptimize(AbslInvokeGetFlag##T), 1);
BENCHMARKED_TYPES(InvokeGetFlag)
-
-// To veiw disassembly use: gdb ${BINARY} -batch -ex "disassemble /s $FUNC"
+#endif // __llvm__
diff --git a/absl/flags/flag_test.cc b/absl/flags/flag_test.cc
index ba81317..8bc7cdf 100644
--- a/absl/flags/flag_test.cc
+++ b/absl/flags/flag_test.cc
@@ -19,14 +19,13 @@
#include <stdint.h>
#include <atomic>
-#include <cmath>
-#include <new>
#include <string>
#include <thread> // NOLINT
#include <vector>
#include "gtest/gtest.h"
#include "absl/base/attributes.h"
+#include "absl/base/internal/raw_logging.h"
#include "absl/base/macros.h"
#include "absl/flags/config.h"
#include "absl/flags/declare.h"
@@ -34,12 +33,15 @@
#include "absl/flags/marshalling.h"
#include "absl/flags/reflection.h"
#include "absl/flags/usage_config.h"
+#include "absl/numeric/int128.h"
#include "absl/strings/match.h"
#include "absl/strings/numbers.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_split.h"
#include "absl/strings/string_view.h"
+#include "absl/time/clock.h"
#include "absl/time/time.h"
+#include "absl/types/optional.h"
ABSL_DECLARE_FLAG(int64_t, mistyped_int_flag);
ABSL_DECLARE_FLAG(std::vector<std::string>, mistyped_string_flag);
@@ -103,9 +105,9 @@
TEST_F(FlagTest, Traits) {
EXPECT_EQ(flags::StorageKind<int>(),
- flags::FlagValueStorageKind::kOneWordAtomic);
+ flags::FlagValueStorageKind::kValueAndInitBit);
EXPECT_EQ(flags::StorageKind<bool>(),
- flags::FlagValueStorageKind::kOneWordAtomic);
+ flags::FlagValueStorageKind::kValueAndInitBit);
EXPECT_EQ(flags::StorageKind<double>(),
flags::FlagValueStorageKind::kOneWordAtomic);
EXPECT_EQ(flags::StorageKind<int64_t>(),
@@ -127,6 +129,11 @@
flags::FlagValueStorageKind::kAlignedBuffer);
EXPECT_EQ(flags::StorageKind<std::vector<std::string>>(),
flags::FlagValueStorageKind::kAlignedBuffer);
+
+ EXPECT_EQ(flags::StorageKind<absl::int128>(),
+ flags::FlagValueStorageKind::kSequenceLocked);
+ EXPECT_EQ(flags::StorageKind<absl::uint128>(),
+ flags::FlagValueStorageKind::kSequenceLocked);
}
// --------------------------------------------------------------------
@@ -135,8 +142,9 @@
flags::FlagHelpKind::kLiteral};
using String = std::string;
+using int128 = absl::int128;
+using uint128 = absl::uint128;
-#if !defined(_MSC_VER) || defined(__clang__)
#define DEFINE_CONSTRUCTED_FLAG(T, dflt, dflt_kind) \
constexpr flags::FlagDefaultArg f1default##T{ \
flags::FlagDefaultSrc{dflt}, flags::FlagDefaultKind::dflt_kind}; \
@@ -149,16 +157,6 @@
flags::FlagDefaultKind::kGenFunc \
} \
}
-#else
-#define DEFINE_CONSTRUCTED_FLAG(T, dflt, dflt_kind) \
- constexpr flags::FlagDefaultArg f1default##T{ \
- flags::FlagDefaultSrc{dflt}, flags::FlagDefaultKind::dflt_kind}; \
- constexpr absl::Flag<T> f1##T{"f1", "file", &TestLiteralHelpMsg, \
- &TestMakeDflt<T>}; \
- ABSL_CONST_INIT absl::Flag<T> f2##T { \
- "f2", "file", &TestHelpMsg, &TestMakeDflt<T> \
- }
-#endif
DEFINE_CONSTRUCTED_FLAG(bool, true, kOneWord);
DEFINE_CONSTRUCTED_FLAG(int16_t, 1, kOneWord);
@@ -171,6 +169,8 @@
DEFINE_CONSTRUCTED_FLAG(double, 9.10, kOneWord);
DEFINE_CONSTRUCTED_FLAG(String, &TestMakeDflt<String>, kGenFunc);
DEFINE_CONSTRUCTED_FLAG(UDT, &TestMakeDflt<UDT>, kGenFunc);
+DEFINE_CONSTRUCTED_FLAG(int128, 13, kGenFunc);
+DEFINE_CONSTRUCTED_FLAG(uint128, 14, kGenFunc);
template <typename T>
bool TestConstructionFor(const absl::Flag<T>& f1, absl::Flag<T>& f2) {
@@ -202,6 +202,8 @@
TEST_CONSTRUCTED_FLAG(double);
TEST_CONSTRUCTED_FLAG(String);
TEST_CONSTRUCTED_FLAG(UDT);
+ TEST_CONSTRUCTED_FLAG(int128);
+ TEST_CONSTRUCTED_FLAG(uint128);
}
// --------------------------------------------------------------------
@@ -220,12 +222,15 @@
ABSL_DECLARE_FLAG(float, test_flag_10);
ABSL_DECLARE_FLAG(std::string, test_flag_11);
ABSL_DECLARE_FLAG(absl::Duration, test_flag_12);
+ABSL_DECLARE_FLAG(absl::int128, test_flag_13);
+ABSL_DECLARE_FLAG(absl::uint128, test_flag_14);
namespace {
-#if !ABSL_FLAGS_STRIP_NAMES
-
TEST_F(FlagTest, TestFlagDeclaration) {
+#if ABSL_FLAGS_STRIP_NAMES
+ GTEST_SKIP() << "This test requires flag names to be present";
+#endif
// test that we can access flag objects.
EXPECT_EQ(absl::GetFlagReflectionHandle(FLAGS_test_flag_01).Name(),
"test_flag_01");
@@ -251,13 +256,32 @@
"test_flag_11");
EXPECT_EQ(absl::GetFlagReflectionHandle(FLAGS_test_flag_12).Name(),
"test_flag_12");
+ EXPECT_EQ(absl::GetFlagReflectionHandle(FLAGS_test_flag_13).Name(),
+ "test_flag_13");
+ EXPECT_EQ(absl::GetFlagReflectionHandle(FLAGS_test_flag_14).Name(),
+ "test_flag_14");
}
-#endif // !ABSL_FLAGS_STRIP_NAMES
-
-// --------------------------------------------------------------------
} // namespace
+#if ABSL_FLAGS_STRIP_NAMES
+// The intent of this helper struct and an expression below is to make sure that
+// in the configuration where ABSL_FLAGS_STRIP_NAMES=1 registrar construction
+// (in cases of of no Tail calls like OnUpdate) is constexpr and thus can and
+// should be completely optimized away, thus avoiding the cost/overhead of
+// static initializers.
+struct VerifyConsteval {
+ friend consteval flags::FlagRegistrarEmpty operator+(
+ flags::FlagRegistrarEmpty, VerifyConsteval) {
+ return {};
+ }
+};
+
+ABSL_FLAG(int, test_registrar_const_init, 0, "") + VerifyConsteval();
+#endif
+
+// --------------------------------------------------------------------
+
ABSL_FLAG(bool, test_flag_01, true, "test flag 01");
ABSL_FLAG(int, test_flag_02, 1234, "test flag 02");
ABSL_FLAG(int16_t, test_flag_03, -34, "test flag 03");
@@ -270,11 +294,16 @@
ABSL_FLAG(float, test_flag_10, 1.234e12f, "test flag 10");
ABSL_FLAG(std::string, test_flag_11, "", "test flag 11");
ABSL_FLAG(absl::Duration, test_flag_12, absl::Minutes(10), "test flag 12");
+ABSL_FLAG(absl::int128, test_flag_13, absl::MakeInt128(-1, 0), "test flag 13");
+ABSL_FLAG(absl::uint128, test_flag_14, absl::MakeUint128(0, 0xFFFAAABBBCCCDDD),
+ "test flag 14");
namespace {
-#if !ABSL_FLAGS_STRIP_NAMES
TEST_F(FlagTest, TestFlagDefinition) {
+#if ABSL_FLAGS_STRIP_NAMES
+ GTEST_SKIP() << "This test requires flag names to be present";
+#endif
absl::string_view expected_file_name = "absl/flags/flag_test.cc";
EXPECT_EQ(absl::GetFlagReflectionHandle(FLAGS_test_flag_01).Name(),
@@ -384,8 +413,25 @@
absl::GetFlagReflectionHandle(FLAGS_test_flag_12).Filename(),
expected_file_name))
<< absl::GetFlagReflectionHandle(FLAGS_test_flag_12).Filename();
+
+ EXPECT_EQ(absl::GetFlagReflectionHandle(FLAGS_test_flag_13).Name(),
+ "test_flag_13");
+ EXPECT_EQ(absl::GetFlagReflectionHandle(FLAGS_test_flag_13).Help(),
+ "test flag 13");
+ EXPECT_TRUE(absl::EndsWith(
+ absl::GetFlagReflectionHandle(FLAGS_test_flag_13).Filename(),
+ expected_file_name))
+ << absl::GetFlagReflectionHandle(FLAGS_test_flag_13).Filename();
+
+ EXPECT_EQ(absl::GetFlagReflectionHandle(FLAGS_test_flag_14).Name(),
+ "test_flag_14");
+ EXPECT_EQ(absl::GetFlagReflectionHandle(FLAGS_test_flag_14).Help(),
+ "test flag 14");
+ EXPECT_TRUE(absl::EndsWith(
+ absl::GetFlagReflectionHandle(FLAGS_test_flag_14).Filename(),
+ expected_file_name))
+ << absl::GetFlagReflectionHandle(FLAGS_test_flag_14).Filename();
}
-#endif // !ABSL_FLAGS_STRIP_NAMES
// --------------------------------------------------------------------
@@ -414,6 +460,10 @@
"");
EXPECT_EQ(absl::GetFlagReflectionHandle(FLAGS_test_flag_12).DefaultValue(),
"10m");
+ EXPECT_EQ(absl::GetFlagReflectionHandle(FLAGS_test_flag_13).DefaultValue(),
+ "-18446744073709551616");
+ EXPECT_EQ(absl::GetFlagReflectionHandle(FLAGS_test_flag_14).DefaultValue(),
+ "1152827684197027293");
EXPECT_EQ(absl::GetFlagReflectionHandle(FLAGS_test_flag_01).CurrentValue(),
"true");
@@ -439,6 +489,10 @@
"");
EXPECT_EQ(absl::GetFlagReflectionHandle(FLAGS_test_flag_12).CurrentValue(),
"10m");
+ EXPECT_EQ(absl::GetFlagReflectionHandle(FLAGS_test_flag_13).CurrentValue(),
+ "-18446744073709551616");
+ EXPECT_EQ(absl::GetFlagReflectionHandle(FLAGS_test_flag_14).CurrentValue(),
+ "1152827684197027293");
EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_01), true);
EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_02), 1234);
@@ -452,6 +506,9 @@
EXPECT_NEAR(absl::GetFlag(FLAGS_test_flag_10), 1.234e12f, 1e5f);
EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_11), "");
EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_12), absl::Minutes(10));
+ EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_13), absl::MakeInt128(-1, 0));
+ EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_14),
+ absl::MakeUint128(0, 0xFFFAAABBBCCCDDD));
}
// --------------------------------------------------------------------
@@ -553,11 +610,21 @@
absl::SetFlag(&FLAGS_test_flag_12, absl::Seconds(110));
EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_12), absl::Seconds(110));
+
+ absl::SetFlag(&FLAGS_test_flag_13, absl::MakeInt128(-1, 0));
+ EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_13), absl::MakeInt128(-1, 0));
+
+ absl::SetFlag(&FLAGS_test_flag_14, absl::MakeUint128(0, 0xFFFAAABBBCCCDDD));
+ EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_14),
+ absl::MakeUint128(0, 0xFFFAAABBBCCCDDD));
}
// --------------------------------------------------------------------
TEST_F(FlagTest, TestGetViaReflection) {
+#if ABSL_FLAGS_STRIP_NAMES
+ GTEST_SKIP() << "This test requires flag names to be present";
+#endif
auto* handle = absl::FindCommandLineFlag("test_flag_01");
EXPECT_EQ(*handle->TryGet<bool>(), true);
handle = absl::FindCommandLineFlag("test_flag_02");
@@ -582,11 +649,19 @@
EXPECT_EQ(*handle->TryGet<std::string>(), "");
handle = absl::FindCommandLineFlag("test_flag_12");
EXPECT_EQ(*handle->TryGet<absl::Duration>(), absl::Minutes(10));
+ handle = absl::FindCommandLineFlag("test_flag_13");
+ EXPECT_EQ(*handle->TryGet<absl::int128>(), absl::MakeInt128(-1, 0));
+ handle = absl::FindCommandLineFlag("test_flag_14");
+ EXPECT_EQ(*handle->TryGet<absl::uint128>(),
+ absl::MakeUint128(0, 0xFFFAAABBBCCCDDD));
}
// --------------------------------------------------------------------
TEST_F(FlagTest, ConcurrentSetAndGet) {
+#if ABSL_FLAGS_STRIP_NAMES
+ GTEST_SKIP() << "This test requires flag names to be present";
+#endif
static constexpr int kNumThreads = 8;
// Two arbitrary durations. One thread will concurrently flip the flag
// between these two values, while the other threads read it and verify
@@ -724,6 +799,8 @@
namespace {
TEST_F(FlagTest, TestCustomUDT) {
+ EXPECT_EQ(flags::StorageKind<CustomUDT>(),
+ flags::FlagValueStorageKind::kOneWordAtomic);
EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_custom_udt), CustomUDT(1, 1));
absl::SetFlag(&FLAGS_test_flag_custom_udt, CustomUDT(2, 3));
EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_custom_udt), CustomUDT(2, 3));
@@ -732,10 +809,12 @@
// MSVC produces link error on the type mismatch.
// Linux does not have build errors and validations work as expected.
#if !defined(_WIN32) && GTEST_HAS_DEATH_TEST
-
using FlagDeathTest = FlagTest;
TEST_F(FlagDeathTest, TestTypeMismatchValidations) {
+#if ABSL_FLAGS_STRIP_NAMES
+ GTEST_SKIP() << "This test requires flag names to be present";
+#endif
#if !defined(NDEBUG)
EXPECT_DEATH_IF_SUPPORTED(
static_cast<void>(absl::GetFlag(FLAGS_mistyped_int_flag)),
@@ -852,7 +931,9 @@
ABSL_RETIRED_FLAG(int, old_int_flag, (int)std::sqrt(10), "old descr");
ABSL_RETIRED_FLAG(std::string, old_str_flag, "", absl::StrCat("old ", "descr"));
-bool initializaion_order_fiasco_test = [] {
+namespace {
+
+bool initialization_order_fiasco_test ABSL_ATTRIBUTE_UNUSED = [] {
// Iterate over all the flags during static initialization.
// This should not trigger ASan's initialization-order-fiasco.
auto* handle1 = absl::FindCommandLineFlag("flag_on_separate_file");
@@ -863,8 +944,6 @@
return true;
}();
-namespace {
-
TEST_F(FlagTest, TestRetiredFlagRegistration) {
auto* handle = absl::FindCommandLineFlag("old_bool_flag");
EXPECT_TRUE(handle->IsOfType<bool>());
@@ -944,3 +1023,213 @@
}
} // namespace
+
+// --------------------------------------------------------------------
+
+namespace {
+
+enum TestE { A = 1, B = 2, C = 3 };
+
+struct EnumWrapper {
+ EnumWrapper() : e(A) {}
+
+ TestE e;
+};
+
+bool AbslParseFlag(absl::string_view, EnumWrapper*, std::string*) {
+ return true;
+}
+std::string AbslUnparseFlag(const EnumWrapper&) { return ""; }
+
+} // namespace
+
+ABSL_FLAG(EnumWrapper, test_enum_wrapper_flag, {}, "help");
+
+TEST_F(FlagTest, TesTypeWrappingEnum) {
+ EnumWrapper value = absl::GetFlag(FLAGS_test_enum_wrapper_flag);
+ EXPECT_EQ(value.e, A);
+
+ value.e = B;
+ absl::SetFlag(&FLAGS_test_enum_wrapper_flag, value);
+ value = absl::GetFlag(FLAGS_test_enum_wrapper_flag);
+ EXPECT_EQ(value.e, B);
+}
+
+// This is a compile test to ensure macros are expanded within ABSL_FLAG and
+// ABSL_DECLARE_FLAG.
+#define FLAG_NAME_MACRO(name) prefix_##name
+ABSL_DECLARE_FLAG(int, FLAG_NAME_MACRO(test_macro_named_flag));
+ABSL_FLAG(int, FLAG_NAME_MACRO(test_macro_named_flag), 0,
+ "Testing macro expansion within ABSL_FLAG");
+
+TEST_F(FlagTest, MacroWithinAbslFlag) {
+ EXPECT_EQ(absl::GetFlag(FLAGS_prefix_test_macro_named_flag), 0);
+ absl::SetFlag(&FLAGS_prefix_test_macro_named_flag, 1);
+ EXPECT_EQ(absl::GetFlag(FLAGS_prefix_test_macro_named_flag), 1);
+}
+
+// --------------------------------------------------------------------
+
+ABSL_FLAG(absl::optional<bool>, optional_bool, absl::nullopt, "help");
+ABSL_FLAG(absl::optional<int>, optional_int, {}, "help");
+ABSL_FLAG(absl::optional<double>, optional_double, 9.3, "help");
+ABSL_FLAG(absl::optional<std::string>, optional_string, absl::nullopt, "help");
+ABSL_FLAG(absl::optional<absl::Duration>, optional_duration, absl::nullopt,
+ "help");
+ABSL_FLAG(absl::optional<absl::optional<int>>, optional_optional_int,
+ absl::nullopt, "help");
+#if defined(ABSL_HAVE_STD_OPTIONAL) && !defined(ABSL_USES_STD_OPTIONAL)
+ABSL_FLAG(std::optional<int64_t>, std_optional_int64, std::nullopt, "help");
+#endif
+
+namespace {
+
+TEST_F(FlagTest, TestOptionalBool) {
+ EXPECT_FALSE(absl::GetFlag(FLAGS_optional_bool).has_value());
+ EXPECT_EQ(absl::GetFlag(FLAGS_optional_bool), absl::nullopt);
+
+ absl::SetFlag(&FLAGS_optional_bool, false);
+ EXPECT_TRUE(absl::GetFlag(FLAGS_optional_bool).has_value());
+ EXPECT_EQ(absl::GetFlag(FLAGS_optional_bool), false);
+
+ absl::SetFlag(&FLAGS_optional_bool, true);
+ EXPECT_TRUE(absl::GetFlag(FLAGS_optional_bool).has_value());
+ EXPECT_EQ(absl::GetFlag(FLAGS_optional_bool), true);
+
+ absl::SetFlag(&FLAGS_optional_bool, absl::nullopt);
+ EXPECT_FALSE(absl::GetFlag(FLAGS_optional_bool).has_value());
+ EXPECT_EQ(absl::GetFlag(FLAGS_optional_bool), absl::nullopt);
+}
+
+// --------------------------------------------------------------------
+
+TEST_F(FlagTest, TestOptionalInt) {
+ EXPECT_FALSE(absl::GetFlag(FLAGS_optional_int).has_value());
+ EXPECT_EQ(absl::GetFlag(FLAGS_optional_int), absl::nullopt);
+
+ absl::SetFlag(&FLAGS_optional_int, 0);
+ EXPECT_TRUE(absl::GetFlag(FLAGS_optional_int).has_value());
+ EXPECT_EQ(absl::GetFlag(FLAGS_optional_int), 0);
+
+ absl::SetFlag(&FLAGS_optional_int, 10);
+ EXPECT_TRUE(absl::GetFlag(FLAGS_optional_int).has_value());
+ EXPECT_EQ(absl::GetFlag(FLAGS_optional_int), 10);
+
+ absl::SetFlag(&FLAGS_optional_int, absl::nullopt);
+ EXPECT_FALSE(absl::GetFlag(FLAGS_optional_int).has_value());
+ EXPECT_EQ(absl::GetFlag(FLAGS_optional_int), absl::nullopt);
+}
+
+// --------------------------------------------------------------------
+
+TEST_F(FlagTest, TestOptionalDouble) {
+ EXPECT_TRUE(absl::GetFlag(FLAGS_optional_double).has_value());
+ EXPECT_DOUBLE_EQ(*absl::GetFlag(FLAGS_optional_double), 9.3);
+
+ absl::SetFlag(&FLAGS_optional_double, 0.0);
+ EXPECT_TRUE(absl::GetFlag(FLAGS_optional_double).has_value());
+ EXPECT_EQ(absl::GetFlag(FLAGS_optional_double), 0.0);
+
+ absl::SetFlag(&FLAGS_optional_double, 1.234);
+ EXPECT_TRUE(absl::GetFlag(FLAGS_optional_double).has_value());
+ EXPECT_DOUBLE_EQ(*absl::GetFlag(FLAGS_optional_double), 1.234);
+
+ absl::SetFlag(&FLAGS_optional_double, absl::nullopt);
+ EXPECT_FALSE(absl::GetFlag(FLAGS_optional_double).has_value());
+ EXPECT_EQ(absl::GetFlag(FLAGS_optional_double), absl::nullopt);
+}
+
+// --------------------------------------------------------------------
+
+TEST_F(FlagTest, TestOptionalString) {
+ EXPECT_FALSE(absl::GetFlag(FLAGS_optional_string).has_value());
+ EXPECT_EQ(absl::GetFlag(FLAGS_optional_string), absl::nullopt);
+
+ // Setting optional string to "" leads to undefined behavior.
+
+ absl::SetFlag(&FLAGS_optional_string, " ");
+ EXPECT_TRUE(absl::GetFlag(FLAGS_optional_string).has_value());
+ EXPECT_EQ(absl::GetFlag(FLAGS_optional_string), " ");
+
+ absl::SetFlag(&FLAGS_optional_string, "QWERTY");
+ EXPECT_TRUE(absl::GetFlag(FLAGS_optional_string).has_value());
+ EXPECT_EQ(absl::GetFlag(FLAGS_optional_string), "QWERTY");
+
+ absl::SetFlag(&FLAGS_optional_string, absl::nullopt);
+ EXPECT_FALSE(absl::GetFlag(FLAGS_optional_string).has_value());
+ EXPECT_EQ(absl::GetFlag(FLAGS_optional_string), absl::nullopt);
+}
+
+// --------------------------------------------------------------------
+
+TEST_F(FlagTest, TestOptionalDuration) {
+ EXPECT_FALSE(absl::GetFlag(FLAGS_optional_duration).has_value());
+ EXPECT_EQ(absl::GetFlag(FLAGS_optional_duration), absl::nullopt);
+
+ absl::SetFlag(&FLAGS_optional_duration, absl::ZeroDuration());
+ EXPECT_TRUE(absl::GetFlag(FLAGS_optional_duration).has_value());
+ EXPECT_EQ(absl::GetFlag(FLAGS_optional_duration), absl::Seconds(0));
+
+ absl::SetFlag(&FLAGS_optional_duration, absl::Hours(3));
+ EXPECT_TRUE(absl::GetFlag(FLAGS_optional_duration).has_value());
+ EXPECT_EQ(absl::GetFlag(FLAGS_optional_duration), absl::Hours(3));
+
+ absl::SetFlag(&FLAGS_optional_duration, absl::nullopt);
+ EXPECT_FALSE(absl::GetFlag(FLAGS_optional_duration).has_value());
+ EXPECT_EQ(absl::GetFlag(FLAGS_optional_duration), absl::nullopt);
+}
+
+// --------------------------------------------------------------------
+
+TEST_F(FlagTest, TestOptionalOptional) {
+ EXPECT_FALSE(absl::GetFlag(FLAGS_optional_optional_int).has_value());
+ EXPECT_EQ(absl::GetFlag(FLAGS_optional_optional_int), absl::nullopt);
+
+ absl::optional<int> nullint{absl::nullopt};
+
+ absl::SetFlag(&FLAGS_optional_optional_int, nullint);
+ EXPECT_TRUE(absl::GetFlag(FLAGS_optional_optional_int).has_value());
+ EXPECT_NE(absl::GetFlag(FLAGS_optional_optional_int), nullint);
+ EXPECT_EQ(absl::GetFlag(FLAGS_optional_optional_int),
+ absl::optional<absl::optional<int>>{nullint});
+
+ absl::SetFlag(&FLAGS_optional_optional_int, 0);
+ EXPECT_TRUE(absl::GetFlag(FLAGS_optional_optional_int).has_value());
+ EXPECT_EQ(absl::GetFlag(FLAGS_optional_optional_int), 0);
+
+ absl::SetFlag(&FLAGS_optional_optional_int, absl::optional<int>{0});
+ EXPECT_TRUE(absl::GetFlag(FLAGS_optional_optional_int).has_value());
+ EXPECT_EQ(absl::GetFlag(FLAGS_optional_optional_int), 0);
+ EXPECT_EQ(absl::GetFlag(FLAGS_optional_optional_int), absl::optional<int>{0});
+
+ absl::SetFlag(&FLAGS_optional_optional_int, absl::nullopt);
+ EXPECT_FALSE(absl::GetFlag(FLAGS_optional_optional_int).has_value());
+ EXPECT_EQ(absl::GetFlag(FLAGS_optional_optional_int), absl::nullopt);
+}
+
+// --------------------------------------------------------------------
+
+#if defined(ABSL_HAVE_STD_OPTIONAL) && !defined(ABSL_USES_STD_OPTIONAL)
+
+TEST_F(FlagTest, TestStdOptional) {
+ EXPECT_FALSE(absl::GetFlag(FLAGS_std_optional_int64).has_value());
+ EXPECT_EQ(absl::GetFlag(FLAGS_std_optional_int64), std::nullopt);
+
+ absl::SetFlag(&FLAGS_std_optional_int64, 0);
+ EXPECT_TRUE(absl::GetFlag(FLAGS_std_optional_int64).has_value());
+ EXPECT_EQ(absl::GetFlag(FLAGS_std_optional_int64), 0);
+
+ absl::SetFlag(&FLAGS_std_optional_int64, 0xFFFFFFFFFF16);
+ EXPECT_TRUE(absl::GetFlag(FLAGS_std_optional_int64).has_value());
+ EXPECT_EQ(absl::GetFlag(FLAGS_std_optional_int64), 0xFFFFFFFFFF16);
+
+ absl::SetFlag(&FLAGS_std_optional_int64, std::nullopt);
+ EXPECT_FALSE(absl::GetFlag(FLAGS_std_optional_int64).has_value());
+ EXPECT_EQ(absl::GetFlag(FLAGS_std_optional_int64), std::nullopt);
+}
+
+// --------------------------------------------------------------------
+
+#endif
+
+} // namespace
diff --git a/absl/flags/internal/commandlineflag.cc b/absl/flags/internal/commandlineflag.cc
index 4482955..3c114d1 100644
--- a/absl/flags/internal/commandlineflag.cc
+++ b/absl/flags/internal/commandlineflag.cc
@@ -19,7 +19,7 @@
ABSL_NAMESPACE_BEGIN
namespace flags_internal {
-FlagStateInterface::~FlagStateInterface() {}
+FlagStateInterface::~FlagStateInterface() = default;
} // namespace flags_internal
ABSL_NAMESPACE_END
diff --git a/absl/flags/internal/flag.cc b/absl/flags/internal/flag.cc
index f83c1fe..65d0e58 100644
--- a/absl/flags/internal/flag.cc
+++ b/absl/flags/internal/flag.cc
@@ -30,6 +30,7 @@
#include "absl/base/call_once.h"
#include "absl/base/casts.h"
#include "absl/base/config.h"
+#include "absl/base/dynamic_annotations.h"
#include "absl/base/optimization.h"
#include "absl/flags/config.h"
#include "absl/flags/internal/commandlineflag.h"
@@ -145,12 +146,7 @@
auto def_kind = static_cast<FlagDefaultKind>(def_kind_);
switch (ValueStorageKind()) {
- case FlagValueStorageKind::kAlignedBuffer:
- // For this storage kind the default_value_ always points to gen_func
- // during initialization.
- assert(def_kind == FlagDefaultKind::kGenFunc);
- (*default_value_.gen_func)(AlignedBufferValue());
- break;
+ case FlagValueStorageKind::kValueAndInitBit:
case FlagValueStorageKind::kOneWordAtomic: {
alignas(int64_t) std::array<char, sizeof(int64_t)> buf{};
if (def_kind == FlagDefaultKind::kGenFunc) {
@@ -159,6 +155,14 @@
assert(def_kind != FlagDefaultKind::kDynamicValue);
std::memcpy(buf.data(), &default_value_, Sizeof(op_));
}
+ if (ValueStorageKind() == FlagValueStorageKind::kValueAndInitBit) {
+ // We presume here the memory layout of FlagValueAndInitBit struct.
+ uint8_t initialized = 1;
+ std::memcpy(buf.data() + Sizeof(op_), &initialized,
+ sizeof(initialized));
+ }
+ // Type can contain valid uninitialized bits, e.g. padding.
+ ABSL_ANNOTATE_MEMORY_IS_INITIALIZED(buf.data(), buf.size());
OneWordValue().store(absl::bit_cast<int64_t>(buf),
std::memory_order_release);
break;
@@ -170,6 +174,12 @@
(*default_value_.gen_func)(AtomicBufferValue());
break;
}
+ case FlagValueStorageKind::kAlignedBuffer:
+ // For this storage kind the default_value_ always points to gen_func
+ // during initialization.
+ assert(def_kind == FlagDefaultKind::kGenFunc);
+ (*default_value_.gen_func)(AlignedBufferValue());
+ break;
}
seq_lock_.MarkInitialized();
}
@@ -187,7 +197,7 @@
FlagFastTypeId lhs_type_id = flags_internal::FastTypeId(op_);
// `rhs_type_id` is the fast type id corresponding to the declaration
- // visibile at the call site. `lhs_type_id` is the fast type id
+ // visible at the call site. `lhs_type_id` is the fast type id
// corresponding to the type specified in flag definition. They must match
// for this operation to be well-defined.
if (ABSL_PREDICT_TRUE(lhs_type_id == rhs_type_id)) return;
@@ -198,7 +208,7 @@
if (lhs_runtime_type_id == rhs_runtime_type_id) return;
-#if defined(ABSL_FLAGS_INTERNAL_HAS_RTTI)
+#ifdef ABSL_INTERNAL_HAS_RTTI
if (*lhs_runtime_type_id == *rhs_runtime_type_id) return;
#endif
@@ -226,12 +236,10 @@
void FlagImpl::StoreValue(const void* src) {
switch (ValueStorageKind()) {
- case FlagValueStorageKind::kAlignedBuffer:
- Copy(op_, src, AlignedBufferValue());
- seq_lock_.IncrementModificationCount();
- break;
+ case FlagValueStorageKind::kValueAndInitBit:
case FlagValueStorageKind::kOneWordAtomic: {
- int64_t one_word_val = 0;
+ // Load the current value to avoid setting 'init' bit manually.
+ int64_t one_word_val = OneWordValue().load(std::memory_order_acquire);
std::memcpy(&one_word_val, src, Sizeof(op_));
OneWordValue().store(one_word_val, std::memory_order_release);
seq_lock_.IncrementModificationCount();
@@ -241,6 +249,10 @@
seq_lock_.Write(AtomicBufferValue(), src, Sizeof(op_));
break;
}
+ case FlagValueStorageKind::kAlignedBuffer:
+ Copy(op_, src, AlignedBufferValue());
+ seq_lock_.IncrementModificationCount();
+ break;
}
modified_ = true;
InvokeCallback();
@@ -280,10 +292,7 @@
std::string FlagImpl::CurrentValue() const {
auto* guard = DataGuard(); // Make sure flag initialized
switch (ValueStorageKind()) {
- case FlagValueStorageKind::kAlignedBuffer: {
- absl::MutexLock l(guard);
- return flags_internal::Unparse(op_, AlignedBufferValue());
- }
+ case FlagValueStorageKind::kValueAndInitBit:
case FlagValueStorageKind::kOneWordAtomic: {
const auto one_word_val =
absl::bit_cast<std::array<char, sizeof(int64_t)>>(
@@ -296,6 +305,10 @@
ReadSequenceLockedData(cloned.get());
return flags_internal::Unparse(op_, cloned.get());
}
+ case FlagValueStorageKind::kAlignedBuffer: {
+ absl::MutexLock l(guard);
+ return flags_internal::Unparse(op_, AlignedBufferValue());
+ }
}
return "";
@@ -341,11 +354,7 @@
bool modified = modified_;
bool on_command_line = on_command_line_;
switch (ValueStorageKind()) {
- case FlagValueStorageKind::kAlignedBuffer: {
- return absl::make_unique<FlagState>(
- *this, flags_internal::Clone(op_, AlignedBufferValue()), modified,
- on_command_line, ModificationCount());
- }
+ case FlagValueStorageKind::kValueAndInitBit:
case FlagValueStorageKind::kOneWordAtomic: {
return absl::make_unique<FlagState>(
*this, OneWordValue().load(std::memory_order_acquire), modified,
@@ -361,6 +370,11 @@
return absl::make_unique<FlagState>(*this, cloned, modified,
on_command_line, ModificationCount());
}
+ case FlagValueStorageKind::kAlignedBuffer: {
+ return absl::make_unique<FlagState>(
+ *this, flags_internal::Clone(op_, AlignedBufferValue()), modified,
+ on_command_line, ModificationCount());
+ }
}
return nullptr;
}
@@ -372,13 +386,14 @@
}
switch (ValueStorageKind()) {
- case FlagValueStorageKind::kAlignedBuffer:
- case FlagValueStorageKind::kSequenceLocked:
- StoreValue(flag_state.value_.heap_allocated);
- break;
+ case FlagValueStorageKind::kValueAndInitBit:
case FlagValueStorageKind::kOneWordAtomic:
StoreValue(&flag_state.value_.one_word);
break;
+ case FlagValueStorageKind::kSequenceLocked:
+ case FlagValueStorageKind::kAlignedBuffer:
+ StoreValue(flag_state.value_.heap_allocated);
+ break;
}
modified_ = flag_state.modified_;
@@ -391,7 +406,7 @@
StorageT* FlagImpl::OffsetValue() const {
char* p = reinterpret_cast<char*>(const_cast<FlagImpl*>(this));
// The offset is deduced via Flag value type specific op_.
- size_t offset = flags_internal::ValueOffset(op_);
+ ptrdiff_t offset = flags_internal::ValueOffset(op_);
return reinterpret_cast<StorageT*>(p + offset);
}
@@ -407,7 +422,8 @@
}
std::atomic<int64_t>& FlagImpl::OneWordValue() const {
- assert(ValueStorageKind() == FlagValueStorageKind::kOneWordAtomic);
+ assert(ValueStorageKind() == FlagValueStorageKind::kOneWordAtomic ||
+ ValueStorageKind() == FlagValueStorageKind::kValueAndInitBit);
return OffsetValue<FlagOneWordValue>()->value;
}
@@ -433,11 +449,7 @@
void FlagImpl::Read(void* dst) const {
auto* guard = DataGuard(); // Make sure flag initialized
switch (ValueStorageKind()) {
- case FlagValueStorageKind::kAlignedBuffer: {
- absl::MutexLock l(guard);
- flags_internal::CopyConstruct(op_, AlignedBufferValue(), dst);
- break;
- }
+ case FlagValueStorageKind::kValueAndInitBit:
case FlagValueStorageKind::kOneWordAtomic: {
const int64_t one_word_val =
OneWordValue().load(std::memory_order_acquire);
@@ -448,11 +460,33 @@
ReadSequenceLockedData(dst);
break;
}
+ case FlagValueStorageKind::kAlignedBuffer: {
+ absl::MutexLock l(guard);
+ flags_internal::CopyConstruct(op_, AlignedBufferValue(), dst);
+ break;
+ }
}
}
+int64_t FlagImpl::ReadOneWord() const {
+ assert(ValueStorageKind() == FlagValueStorageKind::kOneWordAtomic ||
+ ValueStorageKind() == FlagValueStorageKind::kValueAndInitBit);
+ 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
+ (void)guard;
+ return absl::bit_cast<FlagValueAndInitBit<bool>>(
+ OneWordValue().load(std::memory_order_acquire))
+ .value;
+}
+
void FlagImpl::ReadSequenceLockedData(void* dst) const {
- int size = Sizeof(op_);
+ size_t size = Sizeof(op_);
// Attempt to read using the sequence lock.
if (ABSL_PREDICT_TRUE(seq_lock_.TryRead(dst, AtomicBufferValue(), size))) {
return;
diff --git a/absl/flags/internal/flag.h b/absl/flags/internal/flag.h
index e6bade0..7b05638 100644
--- a/absl/flags/internal/flag.h
+++ b/absl/flags/internal/flag.h
@@ -22,13 +22,13 @@
#include <atomic>
#include <cstring>
#include <memory>
-#include <new>
#include <string>
#include <type_traits>
#include <typeinfo>
#include "absl/base/attributes.h"
#include "absl/base/call_once.h"
+#include "absl/base/casts.h"
#include "absl/base/config.h"
#include "absl/base/optimization.h"
#include "absl/base/thread_annotations.h"
@@ -53,13 +53,8 @@
class Flag;
} // namespace flags_internal
-#if defined(_MSC_VER) && !defined(__clang__)
-template <typename T>
-class Flag;
-#else
template <typename T>
using Flag = flags_internal::Flag<T>;
-#endif
template <typename T>
ABSL_MUST_USE_RESULT T GetFlag(const absl::Flag<T>& flag);
@@ -120,7 +115,7 @@
flags_internal::CopyConstruct(op, obj, res);
return res;
}
-// Returns true if parsing of input text is successfull.
+// Returns true if parsing of input text is successful.
inline bool Parse(FlagOpFn op, absl::string_view text, void* dst,
std::string* error) {
return op(FlagOp::kParse, &text, dst, error) != nullptr;
@@ -138,12 +133,12 @@
return static_cast<size_t>(reinterpret_cast<intptr_t>(
op(FlagOp::kSizeof, nullptr, nullptr, nullptr)));
}
-// Returns fast type id coresponding to the value type.
+// Returns fast type id corresponding to the value type.
inline FlagFastTypeId FastTypeId(FlagOpFn op) {
return reinterpret_cast<FlagFastTypeId>(
op(FlagOp::kFastTypeId, nullptr, nullptr, nullptr));
}
-// Returns fast type id coresponding to the value type.
+// Returns fast type id corresponding to the value type.
inline const std::type_info* RuntimeTypeId(FlagOpFn op) {
return reinterpret_cast<const std::type_info*>(
op(FlagOp::kRuntimeTypeId, nullptr, nullptr, nullptr));
@@ -162,7 +157,7 @@
// Returns an address of RTTI's typeid(T).
template <typename T>
inline const std::type_info* GenRuntimeTypeId() {
-#if defined(ABSL_FLAGS_INTERNAL_HAS_RTTI)
+#ifdef ABSL_INTERNAL_HAS_RTTI
return &typeid(T);
#else
return nullptr;
@@ -222,12 +217,12 @@
// first overload if possible. If help message is evaluatable on constexpr
// context We'll be able to make FixedCharArray out of it and we'll choose first
// overload. In this case the help message expression is immediately evaluated
-// and is used to construct the absl::Flag. No additionl code is generated by
+// and is used to construct the absl::Flag. No additional code is generated by
// ABSL_FLAG Otherwise SFINAE kicks in and first overload is dropped from the
// consideration, in which case the second overload will be used. The second
// overload does not attempt to evaluate the help message expression
-// immediately and instead delays the evaluation by returing the function
-// pointer (&T::NonConst) genering the help message when necessary. This is
+// immediately and instead delays the evaluation by returning the function
+// pointer (&T::NonConst) generating the help message when necessary. This is
// evaluatable in constexpr context, but the cost is an extra function being
// generated in the ABSL_FLAG code.
template <typename Gen, size_t N>
@@ -289,7 +284,7 @@
template <typename ValueT, typename GenT,
typename std::enable_if<std::is_integral<ValueT>::value, int>::type =
- (GenT{}, 0)>
+ ((void)GenT{}, 0)>
constexpr FlagDefaultArg DefaultArg(int) {
return {FlagDefaultSrc(GenT{}.value), FlagDefaultKind::kOneWord};
}
@@ -302,51 +297,77 @@
///////////////////////////////////////////////////////////////////////////////
// Flag current value auxiliary structs.
-constexpr int64_t UninitializedFlagValue() { return 0xababababababababll; }
+constexpr int64_t UninitializedFlagValue() {
+ return static_cast<int64_t>(0xababababababababll);
+}
template <typename T>
-using FlagUseOneWordStorage = std::integral_constant<
- bool, absl::type_traits_internal::is_trivially_copyable<T>::value &&
- (sizeof(T) <= 8)>;
+using FlagUseValueAndInitBitStorage =
+ std::integral_constant<bool, std::is_trivially_copyable<T>::value &&
+ std::is_default_constructible<T>::value &&
+ (sizeof(T) < 8)>;
+
+template <typename T>
+using FlagUseOneWordStorage =
+ std::integral_constant<bool, std::is_trivially_copyable<T>::value &&
+ (sizeof(T) <= 8)>;
template <class T>
-using FlagShouldUseSequenceLock = std::integral_constant<
- bool, absl::type_traits_internal::is_trivially_copyable<T>::value &&
- (sizeof(T) > 8)>;
+using FlagUseSequenceLockStorage =
+ std::integral_constant<bool, std::is_trivially_copyable<T>::value &&
+ (sizeof(T) > 8)>;
enum class FlagValueStorageKind : uint8_t {
- kAlignedBuffer = 0,
+ kValueAndInitBit = 0,
kOneWordAtomic = 1,
kSequenceLocked = 2,
+ kAlignedBuffer = 3,
};
template <typename T>
static constexpr FlagValueStorageKind StorageKind() {
- return FlagUseOneWordStorage<T>::value ? FlagValueStorageKind::kOneWordAtomic
- : FlagShouldUseSequenceLock<T>::value
+ return FlagUseValueAndInitBitStorage<T>::value
+ ? FlagValueStorageKind::kValueAndInitBit
+ : FlagUseOneWordStorage<T>::value
+ ? FlagValueStorageKind::kOneWordAtomic
+ : FlagUseSequenceLockStorage<T>::value
? FlagValueStorageKind::kSequenceLocked
: FlagValueStorageKind::kAlignedBuffer;
}
struct FlagOneWordValue {
- constexpr FlagOneWordValue() : value(UninitializedFlagValue()) {}
-
+ constexpr explicit FlagOneWordValue(int64_t v) : value(v) {}
std::atomic<int64_t> value;
};
+template <typename T>
+struct alignas(8) FlagValueAndInitBit {
+ T value;
+ // Use an int instead of a bool to guarantee that a non-zero value has
+ // a bit set.
+ uint8_t init;
+};
+
template <typename T,
FlagValueStorageKind Kind = flags_internal::StorageKind<T>()>
struct FlagValue;
template <typename T>
-struct FlagValue<T, FlagValueStorageKind::kAlignedBuffer> {
- bool Get(const SequenceLock&, T&) const { return false; }
-
- alignas(T) char value[sizeof(T)];
+struct FlagValue<T, FlagValueStorageKind::kValueAndInitBit> : FlagOneWordValue {
+ constexpr FlagValue() : FlagOneWordValue(0) {}
+ bool Get(const SequenceLock&, T& dst) const {
+ int64_t storage = value.load(std::memory_order_acquire);
+ if (ABSL_PREDICT_FALSE(storage == 0)) {
+ return false;
+ }
+ dst = absl::bit_cast<FlagValueAndInitBit<T>>(storage).value;
+ return true;
+ }
};
template <typename T>
struct FlagValue<T, FlagValueStorageKind::kOneWordAtomic> : FlagOneWordValue {
+ constexpr FlagValue() : FlagOneWordValue(UninitializedFlagValue()) {}
bool Get(const SequenceLock&, T& dst) const {
int64_t one_word_val = value.load(std::memory_order_acquire);
if (ABSL_PREDICT_FALSE(one_word_val == UninitializedFlagValue())) {
@@ -370,6 +391,13 @@
std::atomic<uint64_t>) std::atomic<uint64_t> value_words[kNumWords];
};
+template <typename T>
+struct FlagValue<T, FlagValueStorageKind::kAlignedBuffer> {
+ bool Get(const SequenceLock&, T&) const { return false; }
+
+ alignas(T) char value[sizeof(T)];
+};
+
///////////////////////////////////////////////////////////////////////////////
// Flag callback auxiliary structs.
@@ -415,7 +443,27 @@
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()) {
+ *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()) {
+ int64_t v = ReadOneWord();
+ std::memcpy(value, static_cast<const void*>(&v), sizeof(T));
+ }
+ template <typename T,
+ typename std::enable_if<flags_internal::StorageKind<T>() ==
+ FlagValueStorageKind::kValueAndInitBit,
+ int>::type = 0>
+ 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());
@@ -704,8 +752,8 @@
case FlagOp::kValueOffset: {
// Round sizeof(FlagImp) to a multiple of alignof(FlagValue<T>) to get the
// offset of the data.
- ptrdiff_t round_to = alignof(FlagValue<T>);
- ptrdiff_t offset =
+ size_t round_to = alignof(FlagValue<T>);
+ size_t offset =
(sizeof(FlagImpl) + round_to - 1) / round_to * round_to;
return reinterpret_cast<void*>(offset);
}
@@ -721,7 +769,8 @@
template <typename T, bool do_register>
class FlagRegistrar {
public:
- explicit FlagRegistrar(Flag<T>& flag, const char* filename) : flag_(flag) {
+ constexpr explicit FlagRegistrar(Flag<T>& flag, const char* filename)
+ : flag_(flag) {
if (do_register)
flags_internal::RegisterCommandLineFlag(flag_.impl_, filename);
}
@@ -734,7 +783,7 @@
// Make the registrar "die" gracefully as an empty struct on a line where
// registration happens. Registrar objects are intended to live only as
// temporary.
- operator FlagRegistrarEmpty() const { return {}; } // NOLINT
+ constexpr operator FlagRegistrarEmpty() const { return {}; } // NOLINT
private:
Flag<T>& flag_; // Flag being registered (not owned).
diff --git a/absl/flags/internal/parse.h b/absl/flags/internal/parse.h
index de706c8..10c531b 100644
--- a/absl/flags/internal/parse.h
+++ b/absl/flags/internal/parse.h
@@ -16,11 +16,14 @@
#ifndef ABSL_FLAGS_INTERNAL_PARSE_H_
#define ABSL_FLAGS_INTERNAL_PARSE_H_
+#include <iostream>
+#include <ostream>
#include <string>
#include <vector>
#include "absl/base/config.h"
#include "absl/flags/declare.h"
+#include "absl/flags/internal/usage.h"
#include "absl/strings/string_view.h"
ABSL_DECLARE_FLAG(std::vector<std::string>, flagfile);
@@ -32,7 +35,6 @@
ABSL_NAMESPACE_BEGIN
namespace flags_internal {
-enum class ArgvListAction { kRemoveParsedArgs, kKeepParsedArgs };
enum class UsageFlagsAction { kHandleUsage, kIgnoreUsage };
enum class OnUndefinedFlag {
kIgnoreUndefined,
@@ -40,10 +42,15 @@
kAbortIfUndefined
};
-std::vector<char*> ParseCommandLineImpl(int argc, char* argv[],
- ArgvListAction arg_list_act,
- UsageFlagsAction usage_flag_act,
- OnUndefinedFlag on_undef_flag);
+// This is not a public interface. This interface exists to expose the ability
+// to change help output stream in case of parsing errors. This is used by
+// internal unit tests to validate expected outputs.
+// When this was written, `EXPECT_EXIT` only supported matchers on stderr,
+// but not on stdout.
+std::vector<char*> ParseCommandLineImpl(
+ int argc, char* argv[], UsageFlagsAction usage_flag_action,
+ OnUndefinedFlag undef_flag_action,
+ std::ostream& error_help_output = std::cout);
// --------------------------------------------------------------------
// Inspect original command line
@@ -52,6 +59,10 @@
// command line or specified in flag file present on the original command line.
bool WasPresentOnCommandLine(absl::string_view flag_name);
+// Return existing flags similar to the parameter, in order to help in case of
+// misspellings.
+std::vector<std::string> GetMisspellingHints(absl::string_view flag);
+
} // namespace flags_internal
ABSL_NAMESPACE_END
} // namespace absl
diff --git a/absl/flags/internal/sequence_lock.h b/absl/flags/internal/sequence_lock.h
index 807b2a7..36318ab 100644
--- a/absl/flags/internal/sequence_lock.h
+++ b/absl/flags/internal/sequence_lock.h
@@ -49,7 +49,7 @@
// The memory reads and writes protected by this lock must use the provided
// `TryRead()` and `Write()` functions. These functions behave similarly to
// `memcpy()`, with one oddity: the protected data must be an array of
-// `std::atomic<int64>`. This is to comply with the C++ standard, which
+// `std::atomic<uint64>`. This is to comply with the C++ standard, which
// considers data races on non-atomic objects to be undefined behavior. See "Can
// Seqlocks Get Along With Programming Language Memory Models?"[1] by Hans J.
// Boehm for more details.
diff --git a/absl/flags/internal/usage.cc b/absl/flags/internal/usage.cc
index 949709e..8b169bc 100644
--- a/absl/flags/internal/usage.cc
+++ b/absl/flags/internal/usage.cc
@@ -17,14 +17,20 @@
#include <stdint.h>
+#include <algorithm>
+#include <cstdlib>
#include <functional>
+#include <iterator>
#include <map>
#include <ostream>
#include <string>
#include <utility>
#include <vector>
+#include "absl/base/attributes.h"
#include "absl/base/config.h"
+#include "absl/base/const_init.h"
+#include "absl/base/thread_annotations.h"
#include "absl/flags/commandlineflag.h"
#include "absl/flags/flag.h"
#include "absl/flags/internal/flag.h"
@@ -33,9 +39,12 @@
#include "absl/flags/internal/program_name.h"
#include "absl/flags/internal/registry.h"
#include "absl/flags/usage_config.h"
+#include "absl/strings/match.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_split.h"
#include "absl/strings/string_view.h"
+#include "absl/strings/strip.h"
+#include "absl/synchronization/mutex.h"
// Dummy global variables to prevent anyone else defining these.
bool FLAGS_help = false;
@@ -88,8 +97,16 @@
case '>':
out << ">";
break;
+ case '\n':
+ case '\v':
+ case '\f':
+ case '\t':
+ out << " ";
+ break;
default:
- out << c;
+ if (IsValidXmlCharacter(static_cast<unsigned char>(c))) {
+ out << c;
+ }
break;
}
}
@@ -98,6 +115,7 @@
}
private:
+ static bool IsValidXmlCharacter(unsigned char c) { return c >= 0x20; }
absl::string_view tag_;
absl::string_view txt_;
};
@@ -127,7 +145,7 @@
for (auto line : absl::StrSplit(str, absl::ByAnyChar("\n\r"))) {
if (!tokens.empty()) {
// Keep line separators in the input string.
- tokens.push_back("\n");
+ tokens.emplace_back("\n");
}
for (auto token :
absl::StrSplit(line, absl::ByAnyChar(" \t"), absl::SkipEmpty())) {
@@ -148,8 +166,7 @@
}
// Write the token, ending the string first if necessary/possible.
- if (!new_line &&
- (line_len_ + static_cast<int>(token.size()) >= max_line_len_)) {
+ if (!new_line && (line_len_ + token.size() >= max_line_len_)) {
EndLine();
new_line = true;
}
@@ -344,7 +361,7 @@
void FlagsHelp(std::ostream& out, absl::string_view filter, HelpFormat format,
absl::string_view program_usage_message) {
flags_internal::FlagKindFilter filter_cb = [&](absl::string_view filename) {
- return filter.empty() || filename.find(filter) != absl::string_view::npos;
+ return filter.empty() || absl::StrContains(filename, filter);
};
flags_internal::FlagsHelpImpl(out, filter_cb, format, program_usage_message);
}
@@ -352,8 +369,8 @@
// --------------------------------------------------------------------
// Checks all the 'usage' command line flags to see if any have been set.
// If so, handles them appropriately.
-int HandleUsageFlags(std::ostream& out,
- absl::string_view program_usage_message) {
+HelpMode HandleUsageFlags(std::ostream& out,
+ absl::string_view program_usage_message) {
switch (GetFlagsHelpMode()) {
case HelpMode::kNone:
break;
@@ -361,25 +378,24 @@
flags_internal::FlagsHelpImpl(
out, flags_internal::GetUsageConfig().contains_help_flags,
GetFlagsHelpFormat(), program_usage_message);
- return 1;
+ break;
case HelpMode::kShort:
flags_internal::FlagsHelpImpl(
out, flags_internal::GetUsageConfig().contains_helpshort_flags,
GetFlagsHelpFormat(), program_usage_message);
- return 1;
+ break;
case HelpMode::kFull:
flags_internal::FlagsHelp(out, "", GetFlagsHelpFormat(),
program_usage_message);
- return 1;
+ break;
case HelpMode::kPackage:
flags_internal::FlagsHelpImpl(
out, flags_internal::GetUsageConfig().contains_helppackage_flags,
GetFlagsHelpFormat(), program_usage_message);
-
- return 1;
+ break;
case HelpMode::kMatch: {
std::string substr = GetFlagsHelpMatchSubstr();
@@ -398,20 +414,19 @@
flags_internal::FlagsHelpImpl(
out, filter_cb, HelpFormat::kHumanReadable, program_usage_message);
}
-
- return 1;
+ break;
}
case HelpMode::kVersion:
if (flags_internal::GetUsageConfig().version_string)
out << flags_internal::GetUsageConfig().version_string();
// Unlike help, we may be asking for version in a script, so return 0
- return 0;
+ break;
case HelpMode::kOnlyCheckArgs:
- return 0;
+ break;
}
- return -1;
+ return GetFlagsHelpMode();
}
// --------------------------------------------------------------------
@@ -466,7 +481,7 @@
// function.
bool DeduceUsageFlags(absl::string_view name, absl::string_view value) {
if (absl::ConsumePrefix(&name, "help")) {
- if (name == "") {
+ if (name.empty()) {
if (value.empty()) {
SetFlagsHelpMode(HelpMode::kImportant);
} else {
@@ -519,6 +534,22 @@
return false;
}
+// --------------------------------------------------------------------
+
+void MaybeExit(HelpMode mode) {
+ switch (mode) {
+ case flags_internal::HelpMode::kNone:
+ return;
+ case flags_internal::HelpMode::kOnlyCheckArgs:
+ case flags_internal::HelpMode::kVersion:
+ std::exit(0);
+ default: // For all the other modes we exit with 1
+ std::exit(1);
+ }
+}
+
+// --------------------------------------------------------------------
+
} // namespace flags_internal
ABSL_NAMESPACE_END
} // namespace absl
diff --git a/absl/flags/internal/usage.h b/absl/flags/internal/usage.h
index c0bcac5..a96cbf3 100644
--- a/absl/flags/internal/usage.h
+++ b/absl/flags/internal/usage.h
@@ -17,11 +17,11 @@
#define ABSL_FLAGS_INTERNAL_USAGE_H_
#include <iosfwd>
+#include <ostream>
#include <string>
#include "absl/base/config.h"
#include "absl/flags/commandlineflag.h"
-#include "absl/flags/declare.h"
#include "absl/strings/string_view.h"
// --------------------------------------------------------------------
@@ -36,6 +36,18 @@
kHumanReadable,
};
+// The kind of usage help requested.
+enum class HelpMode {
+ kNone,
+ kImportant,
+ kShort,
+ kFull,
+ kPackage,
+ kMatch,
+ kVersion,
+ kOnlyCheckArgs
+};
+
// Streams the help message describing `flag` to `out`.
// The default value for `flag` is included in the output.
void FlagHelp(std::ostream& out, const CommandLineFlag& flag,
@@ -57,29 +69,19 @@
// If any of the 'usage' related command line flags (listed on the bottom of
// this file) has been set this routine produces corresponding help message in
-// the specified output stream and returns:
-// 0 - if "version" or "only_check_flags" flags were set and handled.
-// 1 - if some other 'usage' related flag was set and handled.
-// -1 - if no usage flags were set on a commmand line.
-// Non negative return values are expected to be used as an exit code for a
-// binary.
-int HandleUsageFlags(std::ostream& out,
- absl::string_view program_usage_message);
+// the specified output stream and returns HelpMode that was handled. Otherwise
+// it returns HelpMode::kNone.
+HelpMode HandleUsageFlags(std::ostream& out,
+ absl::string_view program_usage_message);
+
+// --------------------------------------------------------------------
+// Encapsulates the logic of exiting the binary depending on handled help mode.
+
+void MaybeExit(HelpMode mode);
// --------------------------------------------------------------------
// Globals representing usage reporting flags
-enum class HelpMode {
- kNone,
- kImportant,
- kShort,
- kFull,
- kPackage,
- kMatch,
- kVersion,
- kOnlyCheckArgs
-};
-
// Returns substring to filter help output (--help=substr argument)
std::string GetFlagsHelpMatchSubstr();
// Returns the requested help mode.
diff --git a/absl/flags/internal/usage_test.cc b/absl/flags/internal/usage_test.cc
index 044d71c..9b6d730 100644
--- a/absl/flags/internal/usage_test.cc
+++ b/absl/flags/internal/usage_test.cc
@@ -20,10 +20,11 @@
#include <sstream>
#include <string>
+#include "gmock/gmock.h"
#include "gtest/gtest.h"
+#include "absl/flags/config.h"
#include "absl/flags/flag.h"
#include "absl/flags/internal/parse.h"
-#include "absl/flags/internal/path_util.h"
#include "absl/flags/internal/program_name.h"
#include "absl/flags/reflection.h"
#include "absl/flags/usage.h"
@@ -39,6 +40,8 @@
"usage_reporting_test_flag_03 help message");
ABSL_FLAG(int64_t, usage_reporting_test_flag_04, 1000000000000004L,
"usage_reporting_test_flag_04 help message");
+ABSL_FLAG(std::string, usage_reporting_test_flag_07, "\r\n\f\v\a\b\t ",
+ "usage_reporting_test_flag_07 help \r\n\f\v\a\b\t ");
static const char kTestUsageMessage[] = "Custom usage message";
@@ -47,8 +50,10 @@
UDT(const UDT&) = default;
UDT& operator=(const UDT&) = default;
};
-bool AbslParseFlag(absl::string_view, UDT*, std::string*) { return true; }
-std::string AbslUnparseFlag(const UDT&) { return "UDT{}"; }
+static bool AbslParseFlag(absl::string_view, UDT*, std::string*) {
+ return true;
+}
+static std::string AbslUnparseFlag(const UDT&) { return "UDT{}"; }
ABSL_FLAG(UDT, usage_reporting_test_flag_05, {},
"usage_reporting_test_flag_05 help message");
@@ -93,6 +98,11 @@
flags::SetFlagsHelpMatchSubstr("");
flags::SetFlagsHelpFormat(flags::HelpFormat::kHumanReadable);
}
+ void SetUp() override {
+#if ABSL_FLAGS_STRIP_NAMES
+ GTEST_SKIP() << "This test requires flag names to be present";
+#endif
+ }
private:
absl::FlagSaver flag_saver_;
@@ -103,14 +113,19 @@
using UsageReportingDeathTest = UsageReportingTest;
TEST_F(UsageReportingDeathTest, TestSetProgramUsageMessage) {
+#if !defined(GTEST_HAS_ABSL) || !GTEST_HAS_ABSL
+ // Check for kTestUsageMessage set in main() below.
EXPECT_EQ(absl::ProgramUsageMessage(), kTestUsageMessage);
+#else
+ // Check for part of the usage message set by GoogleTest.
+ EXPECT_THAT(absl::ProgramUsageMessage(),
+ ::testing::HasSubstr(
+ "This program contains tests written using Google Test"));
+#endif
-#ifndef _WIN32
- // TODO(rogeeff): figure out why this does not work on Windows.
EXPECT_DEATH_IF_SUPPORTED(
absl::SetProgramUsageMessage("custom usage message"),
- ".*SetProgramUsageMessage\\(\\) called twice.*");
-#endif
+ ::testing::HasSubstr("SetProgramUsageMessage() called twice"));
}
// --------------------------------------------------------------------
@@ -196,8 +211,12 @@
Some more help.
Even more long long long long long long long long long long long long help
- message.); default: "";
+ message.); default: "";)"
+ "\n --usage_reporting_test_flag_07 (usage_reporting_test_flag_07 "
+ "help\n\n \f\v\a\b ); default: \"\r\n\f\v\a\b\t \";\n"
+
+ R"(
Try --helpfull to get a list of all flags or --help=substring shows help for
flags which include specified substring in either in the name, or description or
path.
@@ -248,7 +267,8 @@
TEST_F(UsageReportingTest, TestNoUsageFlags) {
std::stringstream test_buf;
- EXPECT_EQ(flags::HandleUsageFlags(test_buf, kTestUsageMessage), -1);
+ EXPECT_EQ(flags::HandleUsageFlags(test_buf, kTestUsageMessage),
+ flags::HelpMode::kNone);
}
// --------------------------------------------------------------------
@@ -257,9 +277,11 @@
flags::SetFlagsHelpMode(flags::HelpMode::kShort);
std::stringstream test_buf;
- EXPECT_EQ(flags::HandleUsageFlags(test_buf, kTestUsageMessage), 1);
- EXPECT_EQ(test_buf.str(),
- R"(usage_test: Custom usage message
+ EXPECT_EQ(flags::HandleUsageFlags(test_buf, kTestUsageMessage),
+ flags::HelpMode::kShort);
+ EXPECT_EQ(
+ test_buf.str(),
+ R"(usage_test: Custom usage message
Flags from absl/flags/internal/usage_test.cc:
--usage_reporting_test_flag_01 (usage_reporting_test_flag_01 help message);
@@ -276,8 +298,12 @@
Some more help.
Even more long long long long long long long long long long long long help
- message.); default: "";
+ message.); default: "";)"
+ "\n --usage_reporting_test_flag_07 (usage_reporting_test_flag_07 "
+ "help\n\n \f\v\a\b ); default: \"\r\n\f\v\a\b\t \";\n"
+
+ R"(
Try --helpfull to get a list of all flags or --help=substring shows help for
flags which include specified substring in either in the name, or description or
path.
@@ -290,9 +316,11 @@
flags::SetFlagsHelpMode(flags::HelpMode::kImportant);
std::stringstream test_buf;
- EXPECT_EQ(flags::HandleUsageFlags(test_buf, kTestUsageMessage), 1);
- EXPECT_EQ(test_buf.str(),
- R"(usage_test: Custom usage message
+ EXPECT_EQ(flags::HandleUsageFlags(test_buf, kTestUsageMessage),
+ flags::HelpMode::kImportant);
+ EXPECT_EQ(
+ test_buf.str(),
+ R"(usage_test: Custom usage message
Flags from absl/flags/internal/usage_test.cc:
--usage_reporting_test_flag_01 (usage_reporting_test_flag_01 help message);
@@ -309,8 +337,12 @@
Some more help.
Even more long long long long long long long long long long long long help
- message.); default: "";
+ message.); default: "";)"
+ "\n --usage_reporting_test_flag_07 (usage_reporting_test_flag_07 "
+ "help\n\n \f\v\a\b ); default: \"\r\n\f\v\a\b\t \";\n"
+
+ R"(
Try --helpfull to get a list of all flags or --help=substring shows help for
flags which include specified substring in either in the name, or description or
path.
@@ -324,7 +356,8 @@
flags::SetFlagsHelpMatchSubstr("usage_reporting_test_flag_06");
std::stringstream test_buf;
- EXPECT_EQ(flags::HandleUsageFlags(test_buf, kTestUsageMessage), 1);
+ EXPECT_EQ(flags::HandleUsageFlags(test_buf, kTestUsageMessage),
+ flags::HelpMode::kMatch);
EXPECT_EQ(test_buf.str(),
R"(usage_test: Custom usage message
@@ -348,9 +381,11 @@
flags::SetFlagsHelpMatchSubstr("test_flag");
std::stringstream test_buf;
- EXPECT_EQ(flags::HandleUsageFlags(test_buf, kTestUsageMessage), 1);
- EXPECT_EQ(test_buf.str(),
- R"(usage_test: Custom usage message
+ EXPECT_EQ(flags::HandleUsageFlags(test_buf, kTestUsageMessage),
+ flags::HelpMode::kMatch);
+ EXPECT_EQ(
+ test_buf.str(),
+ R"(usage_test: Custom usage message
Flags from absl/flags/internal/usage_test.cc:
--usage_reporting_test_flag_01 (usage_reporting_test_flag_01 help message);
@@ -367,8 +402,12 @@
Some more help.
Even more long long long long long long long long long long long long help
- message.); default: "";
+ message.); default: "";)"
+ "\n --usage_reporting_test_flag_07 (usage_reporting_test_flag_07 "
+ "help\n\n \f\v\a\b ); default: \"\r\n\f\v\a\b\t \";\n"
+
+ R"(
Try --helpfull to get a list of all flags or --help=substring shows help for
flags which include specified substring in either in the name, or description or
path.
@@ -381,9 +420,11 @@
flags::SetFlagsHelpMode(flags::HelpMode::kPackage);
std::stringstream test_buf;
- EXPECT_EQ(flags::HandleUsageFlags(test_buf, kTestUsageMessage), 1);
- EXPECT_EQ(test_buf.str(),
- R"(usage_test: Custom usage message
+ EXPECT_EQ(flags::HandleUsageFlags(test_buf, kTestUsageMessage),
+ flags::HelpMode::kPackage);
+ EXPECT_EQ(
+ test_buf.str(),
+ R"(usage_test: Custom usage message
Flags from absl/flags/internal/usage_test.cc:
--usage_reporting_test_flag_01 (usage_reporting_test_flag_01 help message);
@@ -400,8 +441,12 @@
Some more help.
Even more long long long long long long long long long long long long help
- message.); default: "";
+ message.); default: "";)"
+ "\n --usage_reporting_test_flag_07 (usage_reporting_test_flag_07 "
+ "help\n\n \f\v\a\b ); default: \"\r\n\f\v\a\b\t \";\n"
+
+ R"(
Try --helpfull to get a list of all flags or --help=substring shows help for
flags which include specified substring in either in the name, or description or
path.
@@ -414,7 +459,8 @@
flags::SetFlagsHelpMode(flags::HelpMode::kVersion);
std::stringstream test_buf;
- EXPECT_EQ(flags::HandleUsageFlags(test_buf, kTestUsageMessage), 0);
+ EXPECT_EQ(flags::HandleUsageFlags(test_buf, kTestUsageMessage),
+ flags::HelpMode::kVersion);
#ifndef NDEBUG
EXPECT_EQ(test_buf.str(), "usage_test\nDebug build (NDEBUG not #defined)\n");
#else
@@ -428,7 +474,8 @@
flags::SetFlagsHelpMode(flags::HelpMode::kOnlyCheckArgs);
std::stringstream test_buf;
- EXPECT_EQ(flags::HandleUsageFlags(test_buf, kTestUsageMessage), 0);
+ EXPECT_EQ(flags::HandleUsageFlags(test_buf, kTestUsageMessage),
+ flags::HelpMode::kOnlyCheckArgs);
EXPECT_EQ(test_buf.str(), "");
}
@@ -439,7 +486,8 @@
flags::SetFlagsHelpMatchSubstr("/bla-bla.");
std::stringstream test_buf_01;
- EXPECT_EQ(flags::HandleUsageFlags(test_buf_01, kTestUsageMessage), 1);
+ EXPECT_EQ(flags::HandleUsageFlags(test_buf_01, kTestUsageMessage),
+ flags::HelpMode::kMatch);
EXPECT_EQ(test_buf_01.str(),
R"(usage_test: Custom usage message
@@ -453,9 +501,11 @@
flags::SetFlagsHelpMatchSubstr("/usage_test.");
std::stringstream test_buf_02;
- EXPECT_EQ(flags::HandleUsageFlags(test_buf_02, kTestUsageMessage), 1);
- EXPECT_EQ(test_buf_02.str(),
- R"(usage_test: Custom usage message
+ EXPECT_EQ(flags::HandleUsageFlags(test_buf_02, kTestUsageMessage),
+ flags::HelpMode::kMatch);
+ EXPECT_EQ(
+ test_buf_02.str(),
+ R"(usage_test: Custom usage message
Flags from absl/flags/internal/usage_test.cc:
--usage_reporting_test_flag_01 (usage_reporting_test_flag_01 help message);
@@ -472,8 +522,12 @@
Some more help.
Even more long long long long long long long long long long long long help
- message.); default: "";
+ message.); default: "";)"
+ "\n --usage_reporting_test_flag_07 (usage_reporting_test_flag_07 "
+ "help\n\n \f\v\a\b ); default: \"\r\n\f\v\a\b\t \";\n"
+
+ R"(
Try --helpfull to get a list of all flags or --help=substring shows help for
flags which include specified substring in either in the name, or description or
path.
@@ -487,8 +541,10 @@
int main(int argc, char* argv[]) {
(void)absl::GetFlag(FLAGS_undefok); // Force linking of parse.cc
flags::SetProgramInvocationName("usage_test");
+#if !defined(GTEST_HAS_ABSL) || !GTEST_HAS_ABSL
+ // GoogleTest calls absl::SetProgramUsageMessage() already.
absl::SetProgramUsageMessage(kTestUsageMessage);
+#endif
::testing::InitGoogleTest(&argc, argv);
-
return RUN_ALL_TESTS();
}
diff --git a/absl/flags/marshalling.cc b/absl/flags/marshalling.cc
index 81f9ceb..ca4a130 100644
--- a/absl/flags/marshalling.cc
+++ b/absl/flags/marshalling.cc
@@ -19,6 +19,7 @@
#include <cmath>
#include <limits>
+#include <sstream>
#include <string>
#include <type_traits>
#include <vector>
@@ -26,6 +27,7 @@
#include "absl/base/config.h"
#include "absl/base/log_severity.h"
#include "absl/base/macros.h"
+#include "absl/numeric/int128.h"
#include "absl/strings/ascii.h"
#include "absl/strings/match.h"
#include "absl/strings/numbers.h"
@@ -68,8 +70,10 @@
// puts us in base 16. But leading 0 does not put us in base 8. It
// caused too many bugs when we had that behavior.
static int NumericBase(absl::string_view text) {
- const bool hex = (text.size() >= 2 && text[0] == '0' &&
- (text[1] == 'x' || text[1] == 'X'));
+ if (text.empty()) return 0;
+ size_t num_start = (text[0] == '-' || text[0] == '+') ? 1 : 0;
+ const bool hex = (text.size() >= num_start + 2 && text[num_start] == '0' &&
+ (text[num_start + 1] == 'x' || text[num_start + 1] == 'X'));
return hex ? 16 : 10;
}
@@ -125,6 +129,32 @@
return ParseFlagImpl(text, *dst);
}
+bool AbslParseFlag(absl::string_view text, absl::int128* dst, std::string*) {
+ text = absl::StripAsciiWhitespace(text);
+
+ // check hex
+ int base = NumericBase(text);
+ if (!absl::numbers_internal::safe_strto128_base(text, dst, base)) {
+ return false;
+ }
+
+ return base == 16 ? absl::SimpleHexAtoi(text, dst)
+ : absl::SimpleAtoi(text, dst);
+}
+
+bool AbslParseFlag(absl::string_view text, absl::uint128* dst, std::string*) {
+ text = absl::StripAsciiWhitespace(text);
+
+ // check hex
+ int base = NumericBase(text);
+ if (!absl::numbers_internal::safe_strtou128_base(text, dst, base)) {
+ return false;
+ }
+
+ return base == 16 ? absl::SimpleHexAtoi(text, dst)
+ : absl::SimpleAtoi(text, dst);
+}
+
// --------------------------------------------------------------------
// AbslParseFlag for floating point types.
@@ -171,6 +201,17 @@
std::string Unparse(unsigned long v) { return absl::StrCat(v); }
std::string Unparse(long long v) { return absl::StrCat(v); }
std::string Unparse(unsigned long long v) { return absl::StrCat(v); }
+std::string Unparse(absl::int128 v) {
+ std::stringstream ss;
+ ss << v;
+ return ss.str();
+}
+std::string Unparse(absl::uint128 v) {
+ std::stringstream ss;
+ ss << v;
+ return ss.str();
+}
+
template <typename T>
std::string UnparseFloatingPointVal(T v) {
// digits10 is guaranteed to roundtrip correctly in string -> value -> string
@@ -206,6 +247,14 @@
*err = "no value provided";
return false;
}
+ if (absl::EqualsIgnoreCase(text, "dfatal")) {
+ *dst = absl::kLogDebugFatal;
+ return true;
+ }
+ if (absl::EqualsIgnoreCase(text, "klogdebugfatal")) {
+ *dst = absl::kLogDebugFatal;
+ return true;
+ }
if (text.front() == 'k' || text.front() == 'K') text.remove_prefix(1);
if (absl::EqualsIgnoreCase(text, "info")) {
*dst = absl::LogSeverity::kInfo;
@@ -228,7 +277,8 @@
*dst = static_cast<absl::LogSeverity>(numeric_value);
return true;
}
- *err = "only integers and absl::LogSeverity enumerators are accepted";
+ *err =
+ "only integers, absl::LogSeverity enumerators, and DFATAL are accepted";
return false;
}
diff --git a/absl/flags/marshalling.h b/absl/flags/marshalling.h
index 7cbc136..301213a 100644
--- a/absl/flags/marshalling.h
+++ b/absl/flags/marshalling.h
@@ -33,6 +33,7 @@
// * `double`
// * `std::string`
// * `std::vector<std::string>`
+// * `std::optional<T>`
// * `absl::LogSeverity` (provided natively for layering reasons)
//
// Note that support for integral types is implemented using overloads for
@@ -65,6 +66,42 @@
// below.)
//
// -----------------------------------------------------------------------------
+// Optional Flags
+// -----------------------------------------------------------------------------
+//
+// The Abseil flags library supports flags of type `std::optional<T>` where
+// `T` is a type of one of the supported flags. We refer to this flag type as
+// an "optional flag." An optional flag is either "valueless", holding no value
+// of type `T` (indicating that the flag has not been set) or a value of type
+// `T`. The valueless state in C++ code is represented by a value of
+// `std::nullopt` for the optional flag.
+//
+// Using `std::nullopt` as an optional flag's default value allows you to check
+// whether such a flag was ever specified on the command line:
+//
+// if (absl::GetFlag(FLAGS_foo).has_value()) {
+// // flag was set on command line
+// } else {
+// // flag was not passed on command line
+// }
+//
+// Using an optional flag in this manner avoids common workarounds for
+// indicating such an unset flag (such as using sentinel values to indicate this
+// state).
+//
+// An optional flag also allows a developer to pass a flag in an "unset"
+// valueless state on the command line, allowing the flag to later be set in
+// binary logic. An optional flag's valueless state is indicated by the special
+// notation of passing the value as an empty string through the syntax `--flag=`
+// or `--flag ""`.
+//
+// $ binary_with_optional --flag_in_unset_state=
+// $ binary_with_optional --flag_in_unset_state ""
+//
+// Note: as a result of the above syntax requirements, an optional flag cannot
+// be set to a `T` of any value which unparses to the empty string.
+//
+// -----------------------------------------------------------------------------
// Adding Type Support for Abseil Flags
// -----------------------------------------------------------------------------
//
@@ -162,14 +199,28 @@
#ifndef ABSL_FLAGS_MARSHALLING_H_
#define ABSL_FLAGS_MARSHALLING_H_
+#include "absl/base/config.h"
+#include "absl/numeric/int128.h"
+
+#if defined(ABSL_HAVE_STD_OPTIONAL) && !defined(ABSL_USES_STD_OPTIONAL)
+#include <optional>
+#endif
#include <string>
#include <vector>
-#include "absl/base/config.h"
#include "absl/strings/string_view.h"
+#include "absl/types/optional.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
+
+// Forward declaration to be used inside composable flag parse/unparse
+// implementations
+template <typename T>
+inline bool ParseFlag(absl::string_view input, T* dst, std::string* error);
+template <typename T>
+inline std::string UnparseFlag(const T& v);
+
namespace flags_internal {
// Overloads of `AbslParseFlag()` and `AbslUnparseFlag()` for fundamental types.
@@ -183,12 +234,44 @@
bool AbslParseFlag(absl::string_view, long long*, std::string*); // NOLINT
bool AbslParseFlag(absl::string_view, unsigned long long*, // NOLINT
std::string*);
+bool AbslParseFlag(absl::string_view, absl::int128*, std::string*); // NOLINT
+bool AbslParseFlag(absl::string_view, absl::uint128*, std::string*); // NOLINT
bool AbslParseFlag(absl::string_view, float*, std::string*);
bool AbslParseFlag(absl::string_view, double*, std::string*);
bool AbslParseFlag(absl::string_view, std::string*, std::string*);
bool AbslParseFlag(absl::string_view, std::vector<std::string>*, std::string*);
template <typename T>
+bool AbslParseFlag(absl::string_view text, absl::optional<T>* f,
+ std::string* err) {
+ if (text.empty()) {
+ *f = absl::nullopt;
+ return true;
+ }
+ T value;
+ if (!absl::ParseFlag(text, &value, err)) return false;
+
+ *f = std::move(value);
+ return true;
+}
+
+#if defined(ABSL_HAVE_STD_OPTIONAL) && !defined(ABSL_USES_STD_OPTIONAL)
+template <typename T>
+bool AbslParseFlag(absl::string_view text, std::optional<T>* f,
+ std::string* err) {
+ if (text.empty()) {
+ *f = std::nullopt;
+ return true;
+ }
+ T value;
+ if (!absl::ParseFlag(text, &value, err)) return false;
+
+ *f = std::move(value);
+ return true;
+}
+#endif
+
+template <typename T>
bool InvokeParseFlag(absl::string_view input, T* dst, std::string* err) {
// Comment on next line provides a good compiler error message if T
// does not have AbslParseFlag(absl::string_view, T*, std::string*).
@@ -202,6 +285,18 @@
std::string AbslUnparseFlag(const std::vector<std::string>&);
template <typename T>
+std::string AbslUnparseFlag(const absl::optional<T>& f) {
+ return f.has_value() ? absl::UnparseFlag(*f) : "";
+}
+
+#if defined(ABSL_HAVE_STD_OPTIONAL) && !defined(ABSL_USES_STD_OPTIONAL)
+template <typename T>
+std::string AbslUnparseFlag(const std::optional<T>& f) {
+ return f.has_value() ? absl::UnparseFlag(*f) : "";
+}
+#endif
+
+template <typename T>
std::string Unparse(const T& v) {
// Comment on next line provides a good compiler error message if T does not
// have UnparseFlag.
@@ -218,6 +313,8 @@
std::string Unparse(unsigned long v); // NOLINT
std::string Unparse(long long v); // NOLINT
std::string Unparse(unsigned long long v); // NOLINT
+std::string Unparse(absl::int128 v);
+std::string Unparse(absl::uint128 v);
std::string Unparse(float v);
std::string Unparse(double v);
diff --git a/absl/flags/marshalling_test.cc b/absl/flags/marshalling_test.cc
index 4a64ce1..b0e055f 100644
--- a/absl/flags/marshalling_test.cc
+++ b/absl/flags/marshalling_test.cc
@@ -137,11 +137,10 @@
EXPECT_EQ(value, 16);
EXPECT_TRUE(absl::ParseFlag("0X234", &value, &err));
EXPECT_EQ(value, 564);
- // TODO(rogeeff): fix below validations
- EXPECT_FALSE(absl::ParseFlag("-0x7FFD", &value, &err));
- EXPECT_NE(value, -3);
- EXPECT_FALSE(absl::ParseFlag("+0x31", &value, &err));
- EXPECT_NE(value, 49);
+ EXPECT_TRUE(absl::ParseFlag("-0x7FFD", &value, &err));
+ EXPECT_EQ(value, -32765);
+ EXPECT_TRUE(absl::ParseFlag("+0x31", &value, &err));
+ EXPECT_EQ(value, 49);
// Whitespace handling
EXPECT_TRUE(absl::ParseFlag("10 ", &value, &err));
@@ -194,9 +193,8 @@
EXPECT_EQ(value, 16);
EXPECT_TRUE(absl::ParseFlag("0X234", &value, &err));
EXPECT_EQ(value, 564);
- // TODO(rogeeff): fix below validations
- EXPECT_FALSE(absl::ParseFlag("+0x31", &value, &err));
- EXPECT_NE(value, 49);
+ EXPECT_TRUE(absl::ParseFlag("+0x31", &value, &err));
+ EXPECT_EQ(value, 49);
// Whitespace handling
EXPECT_TRUE(absl::ParseFlag("10 ", &value, &err));
@@ -254,11 +252,11 @@
EXPECT_EQ(value, 16);
EXPECT_TRUE(absl::ParseFlag("0X234", &value, &err));
EXPECT_EQ(value, 564);
- // TODO(rogeeff): fix below validations
- EXPECT_FALSE(absl::ParseFlag("-0x7FFFFFFD", &value, &err));
- EXPECT_NE(value, -3);
- EXPECT_FALSE(absl::ParseFlag("+0x31", &value, &err));
- EXPECT_NE(value, 49);
+
+ EXPECT_TRUE(absl::ParseFlag("-0x7FFFFFFD", &value, &err));
+ EXPECT_EQ(value, -2147483645);
+ EXPECT_TRUE(absl::ParseFlag("+0x31", &value, &err));
+ EXPECT_EQ(value, 49);
// Whitespace handling
EXPECT_TRUE(absl::ParseFlag("10 ", &value, &err));
@@ -311,9 +309,8 @@
EXPECT_EQ(value, 564);
EXPECT_TRUE(absl::ParseFlag("0xFFFFFFFD", &value, &err));
EXPECT_EQ(value, 4294967293);
- // TODO(rogeeff): fix below validations
- EXPECT_FALSE(absl::ParseFlag("+0x31", &value, &err));
- EXPECT_NE(value, 49);
+ EXPECT_TRUE(absl::ParseFlag("+0x31", &value, &err));
+ EXPECT_EQ(value, 49);
// Whitespace handling
EXPECT_TRUE(absl::ParseFlag("10 ", &value, &err));
@@ -371,11 +368,12 @@
EXPECT_EQ(value, 16);
EXPECT_TRUE(absl::ParseFlag("0XFFFAAABBBCCCDDD", &value, &err));
EXPECT_EQ(value, 1152827684197027293);
- // TODO(rogeeff): fix below validation
- EXPECT_FALSE(absl::ParseFlag("-0x7FFFFFFFFFFFFFFE", &value, &err));
- EXPECT_NE(value, -2);
- EXPECT_FALSE(absl::ParseFlag("+0x31", &value, &err));
- EXPECT_NE(value, 49);
+ EXPECT_TRUE(absl::ParseFlag("-0x7FFFFFFFFFFFFFFE", &value, &err));
+ EXPECT_EQ(value, -9223372036854775806);
+ EXPECT_TRUE(absl::ParseFlag("-0x02", &value, &err));
+ EXPECT_EQ(value, -2);
+ EXPECT_TRUE(absl::ParseFlag("+0x31", &value, &err));
+ EXPECT_EQ(value, 49);
// Whitespace handling
EXPECT_TRUE(absl::ParseFlag("10 ", &value, &err));
@@ -428,9 +426,8 @@
EXPECT_EQ(value, 16);
EXPECT_TRUE(absl::ParseFlag("0XFFFF", &value, &err));
EXPECT_EQ(value, 65535);
- // TODO(rogeeff): fix below validation
- EXPECT_FALSE(absl::ParseFlag("+0x31", &value, &err));
- EXPECT_NE(value, 49);
+ EXPECT_TRUE(absl::ParseFlag("+0x31", &value, &err));
+ EXPECT_EQ(value, 49);
// Whitespace handling
EXPECT_TRUE(absl::ParseFlag("10 ", &value, &err));
@@ -455,6 +452,125 @@
// --------------------------------------------------------------------
+TEST(MarshallingTest, TestInt128Parsing) {
+ std::string err;
+ absl::int128 value;
+
+ // Decimal values.
+ EXPECT_TRUE(absl::ParseFlag("0", &value, &err));
+ EXPECT_EQ(value, 0);
+ EXPECT_TRUE(absl::ParseFlag("1", &value, &err));
+ EXPECT_EQ(value, 1);
+ EXPECT_TRUE(absl::ParseFlag("-1", &value, &err));
+ EXPECT_EQ(value, -1);
+ EXPECT_TRUE(absl::ParseFlag("123", &value, &err));
+ EXPECT_EQ(value, 123);
+ EXPECT_TRUE(absl::ParseFlag("-98765", &value, &err));
+ EXPECT_EQ(value, -98765);
+ EXPECT_TRUE(absl::ParseFlag("+3", &value, &err));
+ EXPECT_EQ(value, 3);
+
+ // Leading zero values.
+ EXPECT_TRUE(absl::ParseFlag("01", &value, &err));
+ EXPECT_EQ(value, 1);
+ EXPECT_TRUE(absl::ParseFlag("001", &value, &err));
+ EXPECT_EQ(value, 1);
+ EXPECT_TRUE(absl::ParseFlag("0000100", &value, &err));
+ EXPECT_EQ(value, 100);
+
+ // Hex values.
+ EXPECT_TRUE(absl::ParseFlag("0x10", &value, &err));
+ EXPECT_EQ(value, 16);
+ EXPECT_TRUE(absl::ParseFlag("0xFFFAAABBBCCCDDD", &value, &err));
+ EXPECT_EQ(value, 1152827684197027293);
+ EXPECT_TRUE(absl::ParseFlag("0xFFF0FFFFFFFFFFFFFFF", &value, &err));
+ EXPECT_EQ(value, absl::MakeInt128(0x000000000000fff, 0xFFFFFFFFFFFFFFF));
+
+ EXPECT_TRUE(absl::ParseFlag("-0x10000000000000000", &value, &err));
+ EXPECT_EQ(value, absl::MakeInt128(-1, 0));
+ EXPECT_TRUE(absl::ParseFlag("+0x31", &value, &err));
+ EXPECT_EQ(value, 49);
+
+ // Whitespace handling
+ EXPECT_TRUE(absl::ParseFlag("16 ", &value, &err));
+ EXPECT_EQ(value, 16);
+ EXPECT_TRUE(absl::ParseFlag(" 16", &value, &err));
+ EXPECT_EQ(value, 16);
+ EXPECT_TRUE(absl::ParseFlag(" 0100 ", &value, &err));
+ EXPECT_EQ(value, 100);
+ EXPECT_TRUE(absl::ParseFlag(" 0x7B ", &value, &err));
+ EXPECT_EQ(value, 123);
+
+ // Invalid values.
+ EXPECT_FALSE(absl::ParseFlag("", &value, &err));
+ EXPECT_FALSE(absl::ParseFlag(" ", &value, &err));
+ EXPECT_FALSE(absl::ParseFlag(" ", &value, &err));
+ EXPECT_FALSE(absl::ParseFlag("--1", &value, &err));
+ EXPECT_FALSE(absl::ParseFlag("\n", &value, &err));
+ EXPECT_FALSE(absl::ParseFlag("\t", &value, &err));
+ EXPECT_FALSE(absl::ParseFlag("2U", &value, &err));
+ EXPECT_FALSE(absl::ParseFlag("FFF", &value, &err));
+}
+
+// --------------------------------------------------------------------
+
+TEST(MarshallingTest, TestUint128Parsing) {
+ std::string err;
+ absl::uint128 value;
+
+ // Decimal values.
+ EXPECT_TRUE(absl::ParseFlag("0", &value, &err));
+ EXPECT_EQ(value, 0);
+ EXPECT_TRUE(absl::ParseFlag("1", &value, &err));
+ EXPECT_EQ(value, 1);
+ EXPECT_TRUE(absl::ParseFlag("123", &value, &err));
+ EXPECT_EQ(value, 123);
+ EXPECT_TRUE(absl::ParseFlag("+3", &value, &err));
+ EXPECT_EQ(value, 3);
+
+ // Leading zero values.
+ EXPECT_TRUE(absl::ParseFlag("01", &value, &err));
+ EXPECT_EQ(value, 1);
+ EXPECT_TRUE(absl::ParseFlag("001", &value, &err));
+ EXPECT_EQ(value, 1);
+ EXPECT_TRUE(absl::ParseFlag("0000100", &value, &err));
+ EXPECT_EQ(value, 100);
+
+ // Hex values.
+ EXPECT_TRUE(absl::ParseFlag("0x10", &value, &err));
+ EXPECT_EQ(value, 16);
+ EXPECT_TRUE(absl::ParseFlag("0xFFFAAABBBCCCDDD", &value, &err));
+ EXPECT_EQ(value, 1152827684197027293);
+ EXPECT_TRUE(absl::ParseFlag("0xFFF0FFFFFFFFFFFFFFF", &value, &err));
+ EXPECT_EQ(value, absl::MakeInt128(0x000000000000fff, 0xFFFFFFFFFFFFFFF));
+ EXPECT_TRUE(absl::ParseFlag("+0x31", &value, &err));
+ EXPECT_EQ(value, 49);
+
+ // Whitespace handling
+ EXPECT_TRUE(absl::ParseFlag("16 ", &value, &err));
+ EXPECT_EQ(value, 16);
+ EXPECT_TRUE(absl::ParseFlag(" 16", &value, &err));
+ EXPECT_EQ(value, 16);
+ EXPECT_TRUE(absl::ParseFlag(" 0100 ", &value, &err));
+ EXPECT_EQ(value, 100);
+ EXPECT_TRUE(absl::ParseFlag(" 0x7B ", &value, &err));
+ EXPECT_EQ(value, 123);
+
+ // Invalid values.
+ EXPECT_FALSE(absl::ParseFlag("", &value, &err));
+ EXPECT_FALSE(absl::ParseFlag(" ", &value, &err));
+ EXPECT_FALSE(absl::ParseFlag(" ", &value, &err));
+ EXPECT_FALSE(absl::ParseFlag("-1", &value, &err));
+ EXPECT_FALSE(absl::ParseFlag("--1", &value, &err));
+ EXPECT_FALSE(absl::ParseFlag("\n", &value, &err));
+ EXPECT_FALSE(absl::ParseFlag("\t", &value, &err));
+ EXPECT_FALSE(absl::ParseFlag("2U", &value, &err));
+ EXPECT_FALSE(absl::ParseFlag("FFF", &value, &err));
+ EXPECT_FALSE(absl::ParseFlag("-0x10000000000000000", &value, &err));
+}
+
+// --------------------------------------------------------------------
+
TEST(MarshallingTest, TestFloatParsing) {
std::string err;
float value;
@@ -659,6 +775,88 @@
// --------------------------------------------------------------------
+TEST(MarshallingTest, TestOptionalBoolParsing) {
+ std::string err;
+ absl::optional<bool> value;
+
+ EXPECT_TRUE(absl::ParseFlag("", &value, &err));
+ EXPECT_FALSE(value.has_value());
+
+ EXPECT_TRUE(absl::ParseFlag("true", &value, &err));
+ EXPECT_TRUE(value.has_value());
+ EXPECT_TRUE(*value);
+
+ EXPECT_TRUE(absl::ParseFlag("false", &value, &err));
+ EXPECT_TRUE(value.has_value());
+ EXPECT_FALSE(*value);
+
+ EXPECT_FALSE(absl::ParseFlag("nullopt", &value, &err));
+}
+
+// --------------------------------------------------------------------
+
+TEST(MarshallingTest, TestOptionalIntParsing) {
+ std::string err;
+ absl::optional<int> value;
+
+ EXPECT_TRUE(absl::ParseFlag("", &value, &err));
+ EXPECT_FALSE(value.has_value());
+
+ EXPECT_TRUE(absl::ParseFlag("10", &value, &err));
+ EXPECT_TRUE(value.has_value());
+ EXPECT_EQ(*value, 10);
+
+ EXPECT_TRUE(absl::ParseFlag("0x1F", &value, &err));
+ EXPECT_TRUE(value.has_value());
+ EXPECT_EQ(*value, 31);
+
+ EXPECT_FALSE(absl::ParseFlag("nullopt", &value, &err));
+}
+
+// --------------------------------------------------------------------
+
+TEST(MarshallingTest, TestOptionalDoubleParsing) {
+ std::string err;
+ absl::optional<double> value;
+
+ EXPECT_TRUE(absl::ParseFlag("", &value, &err));
+ EXPECT_FALSE(value.has_value());
+
+ EXPECT_TRUE(absl::ParseFlag("1.11", &value, &err));
+ EXPECT_TRUE(value.has_value());
+ EXPECT_EQ(*value, 1.11);
+
+ EXPECT_TRUE(absl::ParseFlag("-0.12", &value, &err));
+ EXPECT_TRUE(value.has_value());
+ EXPECT_EQ(*value, -0.12);
+
+ EXPECT_FALSE(absl::ParseFlag("nullopt", &value, &err));
+}
+
+// --------------------------------------------------------------------
+
+TEST(MarshallingTest, TestOptionalStringParsing) {
+ std::string err;
+ absl::optional<std::string> value;
+
+ EXPECT_TRUE(absl::ParseFlag("", &value, &err));
+ EXPECT_FALSE(value.has_value());
+
+ EXPECT_TRUE(absl::ParseFlag(" ", &value, &err));
+ EXPECT_TRUE(value.has_value());
+ EXPECT_EQ(*value, " ");
+
+ EXPECT_TRUE(absl::ParseFlag("aqswde", &value, &err));
+ EXPECT_TRUE(value.has_value());
+ EXPECT_EQ(*value, "aqswde");
+
+ EXPECT_TRUE(absl::ParseFlag("nullopt", &value, &err));
+ EXPECT_TRUE(value.has_value());
+ EXPECT_EQ(*value, "nullopt");
+}
+
+// --------------------------------------------------------------------
+
TEST(MarshallingTest, TestBoolUnparsing) {
EXPECT_EQ(absl::UnparseFlag(true), "true");
EXPECT_EQ(absl::UnparseFlag(false), "false");
@@ -762,6 +960,40 @@
// --------------------------------------------------------------------
+TEST(MarshallingTest, TestInt128Unparsing) {
+ absl::int128 value;
+
+ value = 1;
+ EXPECT_EQ(absl::UnparseFlag(value), "1");
+ value = 0;
+ EXPECT_EQ(absl::UnparseFlag(value), "0");
+ value = -1;
+ EXPECT_EQ(absl::UnparseFlag(value), "-1");
+ value = 123456789L;
+ EXPECT_EQ(absl::UnparseFlag(value), "123456789");
+ value = -987654321L;
+ EXPECT_EQ(absl::UnparseFlag(value), "-987654321");
+ value = 0x7FFFFFFFFFFFFFFF;
+ EXPECT_EQ(absl::UnparseFlag(value), "9223372036854775807");
+}
+
+// --------------------------------------------------------------------
+
+TEST(MarshallingTest, TestUint128Unparsing) {
+ absl::uint128 value;
+
+ value = 1;
+ EXPECT_EQ(absl::UnparseFlag(value), "1");
+ value = 0;
+ EXPECT_EQ(absl::UnparseFlag(value), "0");
+ value = 123456789L;
+ EXPECT_EQ(absl::UnparseFlag(value), "123456789");
+ value = absl::MakeUint128(0, 0xFFFFFFFFFFFFFFFF);
+ EXPECT_EQ(absl::UnparseFlag(value), "18446744073709551615");
+}
+
+// --------------------------------------------------------------------
+
TEST(MarshallingTest, TestFloatUnparsing) {
float value;
@@ -808,6 +1040,90 @@
// --------------------------------------------------------------------
+TEST(MarshallingTest, TestOptionalBoolUnparsing) {
+ absl::optional<bool> value;
+
+ EXPECT_EQ(absl::UnparseFlag(value), "");
+ value = true;
+ EXPECT_EQ(absl::UnparseFlag(value), "true");
+ value = false;
+ EXPECT_EQ(absl::UnparseFlag(value), "false");
+ value = absl::nullopt;
+ EXPECT_EQ(absl::UnparseFlag(value), "");
+}
+
+// --------------------------------------------------------------------
+
+TEST(MarshallingTest, TestOptionalIntUnparsing) {
+ absl::optional<int> value;
+
+ EXPECT_EQ(absl::UnparseFlag(value), "");
+ value = 0;
+ EXPECT_EQ(absl::UnparseFlag(value), "0");
+ value = -12;
+ EXPECT_EQ(absl::UnparseFlag(value), "-12");
+ value = absl::nullopt;
+ EXPECT_EQ(absl::UnparseFlag(value), "");
+}
+
+// --------------------------------------------------------------------
+
+TEST(MarshallingTest, TestOptionalDoubleUnparsing) {
+ absl::optional<double> value;
+
+ EXPECT_EQ(absl::UnparseFlag(value), "");
+ value = 1.;
+ EXPECT_EQ(absl::UnparseFlag(value), "1");
+ value = -1.23;
+ EXPECT_EQ(absl::UnparseFlag(value), "-1.23");
+ value = absl::nullopt;
+ EXPECT_EQ(absl::UnparseFlag(value), "");
+}
+
+// --------------------------------------------------------------------
+
+TEST(MarshallingTest, TestOptionalStringUnparsing) {
+ absl::optional<std::string> strvalue;
+ EXPECT_EQ(absl::UnparseFlag(strvalue), "");
+
+ strvalue = "asdfg";
+ EXPECT_EQ(absl::UnparseFlag(strvalue), "asdfg");
+
+ strvalue = " ";
+ EXPECT_EQ(absl::UnparseFlag(strvalue), " ");
+
+ strvalue = ""; // It is UB to set an optional string flag to ""
+ EXPECT_EQ(absl::UnparseFlag(strvalue), "");
+}
+
+// --------------------------------------------------------------------
+
+#if defined(ABSL_HAVE_STD_OPTIONAL) && !defined(ABSL_USES_STD_OPTIONAL)
+
+TEST(MarshallingTest, TestStdOptionalUnparsing) {
+ std::optional<std::string> strvalue;
+ EXPECT_EQ(absl::UnparseFlag(strvalue), "");
+
+ strvalue = "asdfg";
+ EXPECT_EQ(absl::UnparseFlag(strvalue), "asdfg");
+
+ strvalue = " ";
+ EXPECT_EQ(absl::UnparseFlag(strvalue), " ");
+
+ strvalue = ""; // It is UB to set an optional string flag to ""
+ EXPECT_EQ(absl::UnparseFlag(strvalue), "");
+
+ std::optional<int> intvalue;
+ EXPECT_EQ(absl::UnparseFlag(intvalue), "");
+
+ intvalue = 10;
+ EXPECT_EQ(absl::UnparseFlag(intvalue), "10");
+}
+
+// --------------------------------------------------------------------
+
+#endif
+
template <typename T>
void TestRoundtrip(T v) {
T new_v;
diff --git a/absl/flags/parse.cc b/absl/flags/parse.cc
index dd1a679..526b61d 100644
--- a/absl/flags/parse.cc
+++ b/absl/flags/parse.cc
@@ -18,9 +18,11 @@
#include <stdlib.h>
#include <algorithm>
+#include <cstdint>
+#include <cstdlib>
#include <fstream>
#include <iostream>
-#include <iterator>
+#include <ostream>
#include <string>
#include <tuple>
#include <utility>
@@ -30,6 +32,7 @@
#include <windows.h>
#endif
+#include "absl/algorithm/container.h"
#include "absl/base/attributes.h"
#include "absl/base/config.h"
#include "absl/base/const_init.h"
@@ -47,7 +50,9 @@
#include "absl/flags/usage.h"
#include "absl/flags/usage_config.h"
#include "absl/strings/ascii.h"
+#include "absl/strings/internal/damerau_levenshtein_distance.h"
#include "absl/strings/str_cat.h"
+#include "absl/strings/str_join.h"
#include "absl/strings/string_view.h"
#include "absl/strings/strip.h"
#include "absl/synchronization/mutex.h"
@@ -72,6 +77,11 @@
ABSL_CONST_INIT std::vector<const CommandLineFlag*>* specified_flags
ABSL_GUARDED_BY(specified_flags_guard) = nullptr;
+// Suggesting at most kMaxHints flags in case of misspellings.
+ABSL_CONST_INIT const size_t kMaxHints = 100;
+// Suggesting only flags which have a smaller distance than kMaxDistance.
+ABSL_CONST_INIT const size_t kMaxDistance = 3;
+
struct SpecifiedFlagsCompare {
bool operator()(const CommandLineFlag* a, const CommandLineFlag* b) const {
return a->Name() < b->Name();
@@ -89,6 +99,8 @@
ABSL_NAMESPACE_END
} // namespace absl
+// These flags influence how command line flags are parsed and are only intended
+// to be set on the command line. Avoid reading or setting them from C++ code.
ABSL_FLAG(std::vector<std::string>, flagfile, {},
"comma-separated list of files to load flags from")
.OnUpdate([]() {
@@ -138,6 +150,8 @@
absl::flags_internal::tryfromenv_needs_processing = true;
});
+// Rather than reading or setting --undefok from C++ code, please consider using
+// ABSL_RETIRED_FLAG instead.
ABSL_FLAG(std::vector<std::string>, undefok, {},
"comma-separated list of flag names that it is okay to specify "
"on the command line even if the program does not define a flag "
@@ -159,14 +173,14 @@
// Returns success status: true if parsing successful, false otherwise.
bool ReadFromFlagfile(const std::string& flag_file_name);
- int Size() const { return args_.size() - next_arg_; }
- int FrontIndex() const { return next_arg_; }
+ size_t Size() const { return args_.size() - next_arg_; }
+ size_t FrontIndex() const { return next_arg_; }
absl::string_view Front() const { return args_[next_arg_]; }
void PopFront() { next_arg_++; }
private:
std::vector<std::string> args_;
- int next_arg_;
+ size_t next_arg_;
};
bool ArgsList::ReadFromFlagfile(const std::string& flag_file_name) {
@@ -181,7 +195,7 @@
// This argument represents fake argv[0], which should be present in all arg
// lists.
- args_.push_back("");
+ args_.emplace_back("");
std::string line;
bool success = true;
@@ -203,7 +217,7 @@
break;
}
- args_.push_back(std::string(stripped));
+ args_.emplace_back(stripped);
continue;
}
@@ -269,7 +283,7 @@
return std::make_tuple("", "", false);
}
- auto equal_sign_pos = arg.find("=");
+ auto equal_sign_pos = arg.find('=');
absl::string_view flag_name = arg.substr(0, equal_sign_pos);
@@ -358,7 +372,7 @@
// This argument represents fake argv[0], which should be present in all arg
// lists.
- args.push_back("");
+ args.emplace_back("");
for (const auto& flag_name : flag_names) {
// Avoid infinite recursion.
@@ -407,7 +421,7 @@
// programmatically before invoking ParseCommandLine. Note that we do not
// actually process arguments specified in the flagfile, but instead
// create a secondary arguments list to be processed along with the rest
- // of the comamnd line arguments. Since we always the process most recently
+ // of the command line arguments. Since we always the process most recently
// created list of arguments first, this will result in flagfile argument
// being processed before any other argument in the command line. If
// FLAGS_flagfile contains more than one file name we create multiple new
@@ -590,12 +604,40 @@
return false;
}
+// --------------------------------------------------------------------
+
+void ReportUnrecognizedFlags(
+ const std::vector<UnrecognizedFlag>& unrecognized_flags,
+ bool report_as_fatal_error) {
+ for (const auto& unrecognized : unrecognized_flags) {
+ // Verify if flag_name has the "no" already removed
+ std::vector<std::string> misspelling_hints;
+ if (unrecognized.source == UnrecognizedFlag::kFromArgv) {
+ misspelling_hints =
+ flags_internal::GetMisspellingHints(unrecognized.flag_name);
+ }
+
+ if (misspelling_hints.empty()) {
+ flags_internal::ReportUsageError(
+ absl::StrCat("Unknown command line flag '", unrecognized.flag_name,
+ "'"),
+ report_as_fatal_error);
+ } else {
+ flags_internal::ReportUsageError(
+ absl::StrCat("Unknown command line flag '", unrecognized.flag_name,
+ "'. Did you mean: ",
+ absl::StrJoin(misspelling_hints, ", "), " ?"),
+ report_as_fatal_error);
+ }
+ }
+}
+
} // namespace
// --------------------------------------------------------------------
bool WasPresentOnCommandLine(absl::string_view flag_name) {
- absl::MutexLock l(&specified_flags_guard);
+ absl::ReaderMutexLock l(&specified_flags_guard);
ABSL_INTERNAL_CHECK(specified_flags != nullptr,
"ParseCommandLine is not invoked yet");
@@ -605,60 +647,144 @@
// --------------------------------------------------------------------
+struct BestHints {
+ explicit BestHints(uint8_t _max) : best_distance(_max + 1) {}
+ bool AddHint(absl::string_view hint, uint8_t distance) {
+ if (hints.size() >= kMaxHints) return false;
+ if (distance == best_distance) {
+ hints.emplace_back(hint);
+ }
+ if (distance < best_distance) {
+ best_distance = distance;
+ hints = std::vector<std::string>{std::string(hint)};
+ }
+ return true;
+ }
+
+ uint8_t best_distance;
+ std::vector<std::string> hints;
+};
+
+// Return the list of flags with the smallest Damerau-Levenshtein distance to
+// the given flag.
+std::vector<std::string> GetMisspellingHints(const absl::string_view flag) {
+ const size_t maxCutoff = std::min(flag.size() / 2 + 1, kMaxDistance);
+ auto undefok = absl::GetFlag(FLAGS_undefok);
+ BestHints best_hints(static_cast<uint8_t>(maxCutoff));
+ flags_internal::ForEachFlag([&](const CommandLineFlag& f) {
+ if (best_hints.hints.size() >= kMaxHints) return;
+ uint8_t distance = strings_internal::CappedDamerauLevenshteinDistance(
+ flag, f.Name(), best_hints.best_distance);
+ best_hints.AddHint(f.Name(), distance);
+ // For boolean flags, also calculate distance to the negated form.
+ if (f.IsOfType<bool>()) {
+ const std::string negated_flag = absl::StrCat("no", f.Name());
+ distance = strings_internal::CappedDamerauLevenshteinDistance(
+ flag, negated_flag, best_hints.best_distance);
+ best_hints.AddHint(negated_flag, distance);
+ }
+ });
+ // Finally calculate distance to flags in "undefok".
+ absl::c_for_each(undefok, [&](const absl::string_view f) {
+ if (best_hints.hints.size() >= kMaxHints) return;
+ uint8_t distance = strings_internal::CappedDamerauLevenshteinDistance(
+ flag, f, best_hints.best_distance);
+ best_hints.AddHint(absl::StrCat(f, " (undefok)"), distance);
+ });
+ return best_hints.hints;
+}
+
+// --------------------------------------------------------------------
+
std::vector<char*> ParseCommandLineImpl(int argc, char* argv[],
- ArgvListAction arg_list_act,
- UsageFlagsAction usage_flag_act,
- OnUndefinedFlag on_undef_flag) {
+ UsageFlagsAction usage_flag_action,
+ OnUndefinedFlag undef_flag_action,
+ std::ostream& error_help_output) {
+ std::vector<char*> positional_args;
+ std::vector<UnrecognizedFlag> unrecognized_flags;
+
+ auto help_mode = flags_internal::ParseAbseilFlagsOnlyImpl(
+ argc, argv, positional_args, unrecognized_flags, usage_flag_action);
+
+ if (undef_flag_action != OnUndefinedFlag::kIgnoreUndefined) {
+ flags_internal::ReportUnrecognizedFlags(
+ unrecognized_flags,
+ (undef_flag_action == OnUndefinedFlag::kAbortIfUndefined));
+
+ if (undef_flag_action == OnUndefinedFlag::kAbortIfUndefined) {
+ if (!unrecognized_flags.empty()) {
+ flags_internal::HandleUsageFlags(error_help_output,
+ ProgramUsageMessage()); std::exit(1);
+ }
+ }
+ }
+
+ flags_internal::MaybeExit(help_mode);
+
+ return positional_args;
+}
+
+// --------------------------------------------------------------------
+
+// This function handles all Abseil Flags and built-in usage flags and, if any
+// help mode was handled, it returns that help mode. The caller of this function
+// can decide to exit based on the returned help mode.
+// The caller may decide to handle unrecognized positional arguments and
+// unrecognized flags first before exiting.
+//
+// Returns:
+// * HelpMode::kFull if parsing errors were detected in recognized arguments
+// * The HelpMode that was handled in case when `usage_flag_action` is
+// UsageFlagsAction::kHandleUsage and a usage flag was specified on the
+// commandline
+// * Otherwise it returns HelpMode::kNone
+HelpMode ParseAbseilFlagsOnlyImpl(
+ int argc, char* argv[], std::vector<char*>& positional_args,
+ std::vector<UnrecognizedFlag>& unrecognized_flags,
+ UsageFlagsAction usage_flag_action) {
ABSL_INTERNAL_CHECK(argc > 0, "Missing argv[0]");
- // Once parsing has started we will not have more flag registrations.
- // If we did, they would be missing during parsing, which is a problem on
- // itself.
+ using flags_internal::ArgsList;
+ using flags_internal::specified_flags;
+
+ std::vector<std::string> flagfile_value;
+ std::vector<ArgsList> input_args;
+
+ // Once parsing has started we will not allow more flag registrations.
flags_internal::FinalizeRegistry();
// This routine does not return anything since we abort on failure.
- CheckDefaultValuesParsingRoundtrip();
+ flags_internal::CheckDefaultValuesParsingRoundtrip();
- std::vector<std::string> flagfile_value;
-
- std::vector<ArgsList> input_args;
input_args.push_back(ArgsList(argc, argv));
- std::vector<char*> output_args;
- std::vector<char*> positional_args;
- output_args.reserve(argc);
-
- // This is the list of undefined flags. The element of the list is the pair
- // consisting of boolean indicating if flag came from command line (vs from
- // some flag file we've read) and flag name.
- // TODO(rogeeff): Eliminate the first element in the pair after cleanup.
- std::vector<std::pair<bool, std::string>> undefined_flag_names;
-
// Set program invocation name if it is not set before.
- if (ProgramInvocationName() == "UNKNOWN") {
+ if (flags_internal::ProgramInvocationName() == "UNKNOWN") {
flags_internal::SetProgramInvocationName(argv[0]);
}
- output_args.push_back(argv[0]);
+ positional_args.push_back(argv[0]);
- absl::MutexLock l(&specified_flags_guard);
+ absl::MutexLock l(&flags_internal::specified_flags_guard);
if (specified_flags == nullptr) {
specified_flags = new std::vector<const CommandLineFlag*>;
} else {
specified_flags->clear();
}
- // Iterate through the list of the input arguments. First level are arguments
- // originated from argc/argv. Following levels are arguments originated from
- // recursive parsing of flagfile(s).
+ // Iterate through the list of the input arguments. First level are
+ // arguments originated from argc/argv. Following levels are arguments
+ // originated from recursive parsing of flagfile(s).
bool success = true;
while (!input_args.empty()) {
- // 10. First we process the built-in generator flags.
- success &= HandleGeneratorFlags(input_args, flagfile_value);
+ // First we process the built-in generator flags.
+ success &= flags_internal::HandleGeneratorFlags(input_args, flagfile_value);
- // 30. Select top-most (most recent) arguments list. If it is empty drop it
+ // Select top-most (most recent) arguments list. If it is empty drop it
// and re-try.
ArgsList& curr_list = input_args.back();
+ // Every ArgsList starts with real or fake program name, so we can always
+ // start by skipping it.
curr_list.PopFront();
if (curr_list.Size() == 0) {
@@ -666,13 +792,13 @@
continue;
}
- // 40. Pick up the front remaining argument in the current list. If current
- // stack of argument lists contains only one element - we are processing an
- // argument from the original argv.
+ // Handle the next argument in the current list. If the stack of argument
+ // lists contains only one element - we are processing an argument from
+ // the original argv.
absl::string_view arg(curr_list.Front());
bool arg_from_argv = input_args.size() == 1;
- // 50. If argument does not start with - or is just "-" - this is
+ // If argument does not start with '-' or is just "-" - this is
// positional argument.
if (!absl::ConsumePrefix(&arg, "-") || arg.empty()) {
ABSL_INTERNAL_CHECK(arg_from_argv,
@@ -682,12 +808,8 @@
continue;
}
- if (arg_from_argv && (arg_list_act == ArgvListAction::kKeepParsedArgs)) {
- output_args.push_back(argv[curr_list.FrontIndex()]);
- }
-
- // 60. Split the current argument on '=' to figure out the argument
- // name and value. If flag name is empty it means we've got "--". value
+ // Split the current argument on '=' to deduce the argument flag name and
+ // value. If flag name is empty it means we've got an "--" argument. Value
// can be empty either if there were no '=' in argument string at all or
// an argument looked like "--foo=". In a latter case is_empty_value is
// true.
@@ -695,10 +817,11 @@
absl::string_view value;
bool is_empty_value = false;
- std::tie(flag_name, value, is_empty_value) = SplitNameAndValue(arg);
+ std::tie(flag_name, value, is_empty_value) =
+ flags_internal::SplitNameAndValue(arg);
- // 70. "--" alone means what it does for GNU: stop flags parsing. We do
- // not support positional arguments in flagfiles, so we just drop them.
+ // Standalone "--" argument indicates that the rest of the arguments are
+ // positional. We do not support positional arguments in flagfiles.
if (flag_name.empty()) {
ABSL_INTERNAL_CHECK(arg_from_argv,
"Flagfile cannot contain positional argument");
@@ -707,43 +830,36 @@
break;
}
- // 80. Locate the flag based on flag name. Handle both --foo and --nofoo
+ // Locate the flag based on flag name. Handle both --foo and --nofoo.
CommandLineFlag* flag = nullptr;
bool is_negative = false;
- std::tie(flag, is_negative) = LocateFlag(flag_name);
+ std::tie(flag, is_negative) = flags_internal::LocateFlag(flag_name);
if (flag == nullptr) {
// Usage flags are not modeled as Abseil flags. Locate them separately.
if (flags_internal::DeduceUsageFlags(flag_name, value)) {
continue;
}
-
- if (on_undef_flag != OnUndefinedFlag::kIgnoreUndefined) {
- undefined_flag_names.emplace_back(arg_from_argv,
- std::string(flag_name));
- }
+ unrecognized_flags.emplace_back(arg_from_argv
+ ? UnrecognizedFlag::kFromArgv
+ : UnrecognizedFlag::kFromFlagfile,
+ flag_name);
continue;
}
- // 90. Deduce flag's value (from this or next argument)
- auto curr_index = curr_list.FrontIndex();
+ // Deduce flag's value (from this or next argument).
bool value_success = true;
- std::tie(value_success, value) =
- DeduceFlagValue(*flag, value, is_negative, is_empty_value, &curr_list);
+ std::tie(value_success, value) = flags_internal::DeduceFlagValue(
+ *flag, value, is_negative, is_empty_value, &curr_list);
success &= value_success;
- // If above call consumed an argument, it was a standalone value
- if (arg_from_argv && (arg_list_act == ArgvListAction::kKeepParsedArgs) &&
- (curr_index != curr_list.FrontIndex())) {
- output_args.push_back(argv[curr_list.FrontIndex()]);
- }
-
- // 100. Set the located flag to a new new value, unless it is retired.
- // Setting retired flag fails, but we ignoring it here while also reporting
- // access to retired flag.
+ // Set the located flag to a new value, unless it is retired. Setting
+ // retired flag fails, but we ignoring it here while also reporting access
+ // to retired flag.
std::string error;
if (!flags_internal::PrivateHandleAccessor::ParseFrom(
- *flag, value, SET_FLAGS_VALUE, kCommandLine, error)) {
+ *flag, value, flags_internal::SET_FLAGS_VALUE,
+ flags_internal::kCommandLine, error)) {
if (flag->IsRetired()) continue;
flags_internal::ReportUsageError(error, true);
@@ -753,69 +869,73 @@
}
}
- for (const auto& flag_name : undefined_flag_names) {
- if (CanIgnoreUndefinedFlag(flag_name.second)) continue;
-
- flags_internal::ReportUsageError(
- absl::StrCat("Unknown command line flag '", flag_name.second, "'"),
- true);
-
- success = false;
- }
-
-#if ABSL_FLAGS_STRIP_NAMES
- if (!success) {
- flags_internal::ReportUsageError(
- "NOTE: command line flags are disabled in this build", true);
- }
-#endif
-
- if (!success) {
- flags_internal::HandleUsageFlags(std::cout,
- ProgramUsageMessage());
- std::exit(1);
- }
-
- if (usage_flag_act == UsageFlagsAction::kHandleUsage) {
- int exit_code = flags_internal::HandleUsageFlags(
- std::cout, ProgramUsageMessage());
-
- if (exit_code != -1) {
- std::exit(exit_code);
- }
- }
-
- ResetGeneratorFlags(flagfile_value);
-
- // Reinstate positional args which were intermixed with flags in the arguments
- // list.
- for (auto arg : positional_args) {
- output_args.push_back(arg);
- }
+ flags_internal::ResetGeneratorFlags(flagfile_value);
// All the remaining arguments are positional.
if (!input_args.empty()) {
- for (int arg_index = input_args.back().FrontIndex(); arg_index < argc;
- ++arg_index) {
- output_args.push_back(argv[arg_index]);
+ for (size_t arg_index = input_args.back().FrontIndex();
+ arg_index < static_cast<size_t>(argc); ++arg_index) {
+ positional_args.push_back(argv[arg_index]);
}
}
// Trim and sort the vector.
specified_flags->shrink_to_fit();
std::sort(specified_flags->begin(), specified_flags->end(),
- SpecifiedFlagsCompare{});
- return output_args;
+ flags_internal::SpecifiedFlagsCompare{});
+
+ // Filter out unrecognized flags, which are ok to ignore.
+ std::vector<UnrecognizedFlag> filtered;
+ filtered.reserve(unrecognized_flags.size());
+ for (const auto& unrecognized : unrecognized_flags) {
+ if (flags_internal::CanIgnoreUndefinedFlag(unrecognized.flag_name))
+ continue;
+ filtered.push_back(unrecognized);
+ }
+
+ std::swap(unrecognized_flags, filtered);
+
+ if (!success) {
+#if ABSL_FLAGS_STRIP_NAMES
+ flags_internal::ReportUsageError(
+ "NOTE: command line flags are disabled in this build", true);
+#else
+ flags_internal::HandleUsageFlags(std::cerr, ProgramUsageMessage());
+#endif
+ return HelpMode::kFull; // We just need to make sure the exit with
+ // code 1.
+ }
+
+ return usage_flag_action == UsageFlagsAction::kHandleUsage
+ ? flags_internal::HandleUsageFlags(std::cout,
+ ProgramUsageMessage())
+ : HelpMode::kNone;
}
} // namespace flags_internal
+void ParseAbseilFlagsOnly(int argc, char* argv[],
+ std::vector<char*>& positional_args,
+ std::vector<UnrecognizedFlag>& unrecognized_flags) {
+ auto help_mode = flags_internal::ParseAbseilFlagsOnlyImpl(
+ argc, argv, positional_args, unrecognized_flags,
+ flags_internal::UsageFlagsAction::kHandleUsage);
+
+ flags_internal::MaybeExit(help_mode);
+}
+
+// --------------------------------------------------------------------
+
+void ReportUnrecognizedFlags(
+ const std::vector<UnrecognizedFlag>& unrecognized_flags) {
+ flags_internal::ReportUnrecognizedFlags(unrecognized_flags, true);
+}
+
// --------------------------------------------------------------------
std::vector<char*> ParseCommandLine(int argc, char* argv[]) {
return flags_internal::ParseCommandLineImpl(
- argc, argv, flags_internal::ArgvListAction::kRemoveParsedArgs,
- flags_internal::UsageFlagsAction::kHandleUsage,
+ argc, argv, flags_internal::UsageFlagsAction::kHandleUsage,
flags_internal::OnUndefinedFlag::kAbortIfUndefined);
}
diff --git a/absl/flags/parse.h b/absl/flags/parse.h
index 929de2c..f2a5cb1 100644
--- a/absl/flags/parse.h
+++ b/absl/flags/parse.h
@@ -23,6 +23,7 @@
#ifndef ABSL_FLAGS_PARSE_H_
#define ABSL_FLAGS_PARSE_H_
+#include <string>
#include <vector>
#include "absl/base/config.h"
@@ -31,27 +32,96 @@
namespace absl {
ABSL_NAMESPACE_BEGIN
+// This type represent information about an unrecognized flag in the command
+// line.
+struct UnrecognizedFlag {
+ enum Source { kFromArgv, kFromFlagfile };
+
+ explicit UnrecognizedFlag(Source s, absl::string_view f)
+ : source(s), flag_name(f) {}
+ // This field indicates where we found this flag: on the original command line
+ // or read in some flag file.
+ Source source;
+ // Name of the flag we did not recognize in --flag_name=value or --flag_name.
+ std::string flag_name;
+};
+
+inline bool operator==(const UnrecognizedFlag& lhs,
+ const UnrecognizedFlag& rhs) {
+ return lhs.source == rhs.source && lhs.flag_name == rhs.flag_name;
+}
+
+namespace flags_internal {
+
+HelpMode ParseAbseilFlagsOnlyImpl(
+ int argc, char* argv[], std::vector<char*>& positional_args,
+ std::vector<UnrecognizedFlag>& unrecognized_flags,
+ UsageFlagsAction usage_flag_action);
+
+} // namespace flags_internal
+
+// ParseAbseilFlagsOnly()
+//
+// Parses a list of command-line arguments, passed in the `argc` and `argv[]`
+// parameters, into a set of Abseil Flag values, returning any unparsed
+// arguments in `positional_args` and `unrecognized_flags` output parameters.
+//
+// This function classifies all the arguments (including content of the
+// flagfiles, if any) into one of the following groups:
+//
+// * arguments specified as "--flag=value" or "--flag value" that match
+// registered or built-in Abseil Flags. These are "Abseil Flag arguments."
+// * arguments specified as "--flag" that are unrecognized as Abseil Flags
+// * arguments that are not specified as "--flag" are positional arguments
+// * arguments that follow the flag-terminating delimiter (`--`) are also
+// treated as positional arguments regardless of their syntax.
+//
+// All of the deduced Abseil Flag arguments are then parsed into their
+// corresponding flag values. If any syntax errors are found in these arguments,
+// the binary exits with code 1.
+//
+// This function also handles Abseil Flags built-in usage flags (e.g. --help)
+// if any were present on the command line.
+//
+// All the remaining positional arguments including original program name
+// (argv[0]) are are returned in the `positional_args` output parameter.
+//
+// All unrecognized flags that are not otherwise ignored are returned in the
+// `unrecognized_flags` output parameter. Note that the special `undefok`
+// flag allows you to specify flags which can be safely ignored; `undefok`
+// specifies these flags as a comma-separated list. Any unrecognized flags
+// that appear within `undefok` will therefore be ignored and not included in
+// the `unrecognized_flag` output parameter.
+//
+void ParseAbseilFlagsOnly(int argc, char* argv[],
+ std::vector<char*>& positional_args,
+ std::vector<UnrecognizedFlag>& unrecognized_flags);
+
+// ReportUnrecognizedFlags()
+//
+// Reports an error to `stderr` for all non-ignored unrecognized flags in
+// the provided `unrecognized_flags` list.
+void ReportUnrecognizedFlags(
+ const std::vector<UnrecognizedFlag>& unrecognized_flags);
+
// ParseCommandLine()
//
-// Parses the set of command-line arguments passed in the `argc` (argument
-// count) and `argv[]` (argument vector) parameters from `main()`, assigning
-// values to any defined Abseil flags. (Any arguments passed after the
-// flag-terminating delimiter (`--`) are treated as positional arguments and
-// ignored.)
+// First parses Abseil Flags only from the command line according to the
+// description in `ParseAbseilFlagsOnly`. In addition this function handles
+// unrecognized and usage flags.
//
-// Any command-line flags (and arguments to those flags) are parsed into Abseil
-// Flag values, if those flags are defined. Any undefined flags will either
-// return an error, or be ignored if that flag is designated using `undefok` to
-// indicate "undefined is OK."
+// If any unrecognized flags are located they are reported using
+// `ReportUnrecognizedFlags`.
//
-// Any command-line positional arguments not part of any command-line flag (or
-// arguments to a flag) are returned in a vector, with the program invocation
-// name at position 0 of that vector. (Note that this includes positional
-// arguments after the flag-terminating delimiter `--`.)
+// If any errors detected during command line parsing, this routine reports a
+// usage message and aborts the program.
//
-// After all flags and flag arguments are parsed, this function looks for any
-// built-in usage flags (e.g. `--help`), and if any were specified, it reports
-// help messages and then exits the program.
+// If any built-in usage flags were specified on the command line (e.g.
+// `--help`), this function reports help messages and then gracefully exits the
+// program.
+//
+// This function returns all the remaining positional arguments collected by
+// `ParseAbseilFlagsOnly`.
std::vector<char*> ParseCommandLine(int argc, char* argv[]);
ABSL_NAMESPACE_END
diff --git a/absl/flags/parse_test.cc b/absl/flags/parse_test.cc
index 8dc91db..b3d4ce4 100644
--- a/absl/flags/parse_test.cc
+++ b/absl/flags/parse_test.cc
@@ -18,18 +18,19 @@
#include <stdlib.h>
#include <fstream>
+#include <iostream>
#include <string>
#include <vector>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
-#include "absl/base/internal/raw_logging.h"
#include "absl/base/internal/scoped_set_env.h"
-#include "absl/flags/declare.h"
+#include "absl/flags/config.h"
#include "absl/flags/flag.h"
#include "absl/flags/internal/parse.h"
#include "absl/flags/internal/usage.h"
#include "absl/flags/reflection.h"
+#include "absl/log/log.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "absl/strings/substitute.h"
@@ -39,6 +40,36 @@
#include <windows.h>
#endif
+// Define 125 similar flags to test kMaxHints for flag suggestions.
+#define FLAG_MULT(x) F3(x)
+#define TEST_FLAG_HEADER FLAG_HEADER_
+
+#define F(name) ABSL_FLAG(int, name, 0, "");
+
+#define F1(name) \
+ F(name##1); \
+ F(name##2); \
+ F(name##3); \
+ F(name##4); \
+ F(name##5);
+/**/
+#define F2(name) \
+ F1(name##1); \
+ F1(name##2); \
+ F1(name##3); \
+ F1(name##4); \
+ F1(name##5);
+/**/
+#define F3(name) \
+ F2(name##1); \
+ F2(name##2); \
+ F2(name##3); \
+ F2(name##4); \
+ F2(name##5);
+/**/
+
+FLAG_MULT(TEST_FLAG_HEADER)
+
namespace {
using absl::base_internal::ScopedSetEnv;
@@ -120,8 +151,7 @@
}
if (res->empty()) {
- ABSL_INTERNAL_LOG(FATAL,
- "Failed to make temporary directory for data files");
+ LOG(FATAL) << "Failed to make temporary directory for data files";
}
#ifdef _WIN32
@@ -168,7 +198,7 @@
// Builds flagfile flag in the flagfile_flag buffer and returns it. This
// function also creates a temporary flagfile based on FlagfileData input.
// We create a flagfile in a temporary directory with the name specified in
-// FlagfileData and populate it with lines specifed in FlagfileData. If $0 is
+// FlagfileData and populate it with lines specified in FlagfileData. If $0 is
// referenced in any of the lines in FlagfileData they are replaced with
// temporary directory location. This way we can test inclusion of one flagfile
// from another flagfile.
@@ -206,12 +236,20 @@
namespace {
namespace flags = absl::flags_internal;
+using testing::AllOf;
using testing::ElementsAreArray;
+using testing::HasSubstr;
class ParseTest : public testing::Test {
public:
~ParseTest() override { flags::SetFlagsHelpMode(flags::HelpMode::kNone); }
+ void SetUp() override {
+#if ABSL_FLAGS_STRIP_NAMES
+ GTEST_SKIP() << "This test requires flag names to be present";
+#endif
+ }
+
private:
absl::FlagSaver flag_saver_;
};
@@ -219,6 +257,38 @@
// --------------------------------------------------------------------
template <int N>
+flags::HelpMode InvokeParseAbslOnlyImpl(const char* (&in_argv)[N]) {
+ std::vector<char*> positional_args;
+ std::vector<absl::UnrecognizedFlag> unrecognized_flags;
+
+ return flags::ParseAbseilFlagsOnlyImpl(N, const_cast<char**>(in_argv),
+ positional_args, unrecognized_flags,
+ flags::UsageFlagsAction::kHandleUsage);
+}
+
+// --------------------------------------------------------------------
+
+template <int N>
+void InvokeParseAbslOnly(const char* (&in_argv)[N]) {
+ std::vector<char*> positional_args;
+ std::vector<absl::UnrecognizedFlag> unrecognized_flags;
+
+ absl::ParseAbseilFlagsOnly(2, const_cast<char**>(in_argv), positional_args,
+ unrecognized_flags);
+}
+
+// --------------------------------------------------------------------
+
+template <int N>
+std::vector<char*> InvokeParseCommandLineImpl(const char* (&in_argv)[N]) {
+ return flags::ParseCommandLineImpl(
+ N, const_cast<char**>(in_argv), flags::UsageFlagsAction::kHandleUsage,
+ flags::OnUndefinedFlag::kAbortIfUndefined, std::cerr);
+}
+
+// --------------------------------------------------------------------
+
+template <int N>
std::vector<char*> InvokeParse(const char* (&in_argv)[N]) {
return absl::ParseCommandLine(N, const_cast<char**>(in_argv));
}
@@ -565,6 +635,49 @@
// --------------------------------------------------------------------
+TEST_F(ParseDeathTest, TestFlagSuggestions) {
+ const char* in_args1[] = {
+ "testbin",
+ "--legacy_boo",
+ };
+ EXPECT_DEATH_IF_SUPPORTED(
+ InvokeParse(in_args1),
+ "Unknown command line flag 'legacy_boo'. Did you mean: legacy_bool ?");
+
+ const char* in_args2[] = {"testbin", "--foo", "--undefok=foo1"};
+ EXPECT_DEATH_IF_SUPPORTED(
+ InvokeParse(in_args2),
+ "Unknown command line flag 'foo'. Did you mean: foo1 \\(undefok\\)?");
+
+ const char* in_args3[] = {
+ "testbin",
+ "--nolegacy_ino",
+ };
+ EXPECT_DEATH_IF_SUPPORTED(InvokeParse(in_args3),
+ "Unknown command line flag 'nolegacy_ino'. Did "
+ "you mean: nolegacy_bool, legacy_int ?");
+}
+
+// --------------------------------------------------------------------
+
+TEST_F(ParseTest, GetHints) {
+ EXPECT_THAT(absl::flags_internal::GetMisspellingHints("legacy_boo"),
+ testing::ContainerEq(std::vector<std::string>{"legacy_bool"}));
+ EXPECT_THAT(absl::flags_internal::GetMisspellingHints("nolegacy_itn"),
+ testing::ContainerEq(std::vector<std::string>{"legacy_int"}));
+ EXPECT_THAT(absl::flags_internal::GetMisspellingHints("nolegacy_int1"),
+ testing::ContainerEq(std::vector<std::string>{"legacy_int"}));
+ EXPECT_THAT(absl::flags_internal::GetMisspellingHints("nolegacy_int"),
+ testing::ContainerEq(std::vector<std::string>{"legacy_int"}));
+ EXPECT_THAT(absl::flags_internal::GetMisspellingHints("nolegacy_ino"),
+ testing::ContainerEq(
+ std::vector<std::string>{"nolegacy_bool", "legacy_int"}));
+ EXPECT_THAT(
+ absl::flags_internal::GetMisspellingHints("FLAG_HEADER_000").size(), 100);
+}
+
+// --------------------------------------------------------------------
+
TEST_F(ParseTest, TestLegacyFlags) {
const char* in_args1[] = {
"testbin",
@@ -780,88 +893,13 @@
// --------------------------------------------------------------------
-TEST_F(ParseTest, TestKeepParsedArgs) {
- const char* in_args1[] = {
- "testbin", "arg1", "--bool_flag",
- "--int_flag=211", "arg2", "--double_flag=1.1",
- "--string_flag", "asd", "--",
- "arg3", "arg4",
- };
-
- auto out_args1 = InvokeParse(in_args1);
-
- EXPECT_THAT(
- out_args1,
- ElementsAreArray({absl::string_view("testbin"), absl::string_view("arg1"),
- absl::string_view("arg2"), absl::string_view("arg3"),
- absl::string_view("arg4")}));
-
- auto out_args2 = flags::ParseCommandLineImpl(
- 11, const_cast<char**>(in_args1), flags::ArgvListAction::kKeepParsedArgs,
- flags::UsageFlagsAction::kHandleUsage,
- flags::OnUndefinedFlag::kAbortIfUndefined);
-
- EXPECT_THAT(
- out_args2,
- ElementsAreArray({absl::string_view("testbin"),
- absl::string_view("--bool_flag"),
- absl::string_view("--int_flag=211"),
- absl::string_view("--double_flag=1.1"),
- absl::string_view("--string_flag"),
- absl::string_view("asd"), absl::string_view("--"),
- absl::string_view("arg1"), absl::string_view("arg2"),
- absl::string_view("arg3"), absl::string_view("arg4")}));
-}
-
-// --------------------------------------------------------------------
-
-TEST_F(ParseTest, TestIgnoreUndefinedFlags) {
- const char* in_args1[] = {
- "testbin",
- "arg1",
- "--undef_flag=aa",
- "--int_flag=21",
- };
-
- auto out_args1 = flags::ParseCommandLineImpl(
- 4, const_cast<char**>(in_args1), flags::ArgvListAction::kRemoveParsedArgs,
- flags::UsageFlagsAction::kHandleUsage,
- flags::OnUndefinedFlag::kIgnoreUndefined);
-
- EXPECT_THAT(out_args1, ElementsAreArray({absl::string_view("testbin"),
- absl::string_view("arg1")}));
-
- EXPECT_EQ(absl::GetFlag(FLAGS_int_flag), 21);
-
- const char* in_args2[] = {
- "testbin",
- "arg1",
- "--undef_flag=aa",
- "--string_flag=AA",
- };
-
- auto out_args2 = flags::ParseCommandLineImpl(
- 4, const_cast<char**>(in_args2), flags::ArgvListAction::kKeepParsedArgs,
- flags::UsageFlagsAction::kHandleUsage,
- flags::OnUndefinedFlag::kIgnoreUndefined);
-
- EXPECT_THAT(
- out_args2,
- ElementsAreArray(
- {absl::string_view("testbin"), absl::string_view("--undef_flag=aa"),
- absl::string_view("--string_flag=AA"), absl::string_view("arg1")}));
-
- EXPECT_EQ(absl::GetFlag(FLAGS_string_flag), "AA");
-}
-
-// --------------------------------------------------------------------
-
TEST_F(ParseDeathTest, TestSimpleHelpFlagHandling) {
const char* in_args1[] = {
"testbin",
"--help",
};
+ EXPECT_EQ(InvokeParseAbslOnlyImpl(in_args1), flags::HelpMode::kImportant);
EXPECT_EXIT(InvokeParse(in_args1), testing::ExitedWithCode(1), "");
const char* in_args2[] = {
@@ -870,39 +908,51 @@
"--int_flag=3",
};
- auto out_args2 = flags::ParseCommandLineImpl(
- 3, const_cast<char**>(in_args2), flags::ArgvListAction::kRemoveParsedArgs,
- flags::UsageFlagsAction::kIgnoreUsage,
- flags::OnUndefinedFlag::kAbortIfUndefined);
-
- EXPECT_EQ(flags::GetFlagsHelpMode(), flags::HelpMode::kImportant);
+ EXPECT_EQ(InvokeParseAbslOnlyImpl(in_args2), flags::HelpMode::kImportant);
EXPECT_EQ(absl::GetFlag(FLAGS_int_flag), 3);
+
+ const char* in_args3[] = {"testbin", "--help", "some_positional_arg"};
+
+ EXPECT_EQ(InvokeParseAbslOnlyImpl(in_args3), flags::HelpMode::kImportant);
}
// --------------------------------------------------------------------
-TEST_F(ParseDeathTest, TestSubstringHelpFlagHandling) {
+TEST_F(ParseTest, TestSubstringHelpFlagHandling) {
const char* in_args1[] = {
"testbin",
"--help=abcd",
};
- auto out_args1 = flags::ParseCommandLineImpl(
- 2, const_cast<char**>(in_args1), flags::ArgvListAction::kRemoveParsedArgs,
- flags::UsageFlagsAction::kIgnoreUsage,
- flags::OnUndefinedFlag::kAbortIfUndefined);
-
- EXPECT_EQ(flags::GetFlagsHelpMode(), flags::HelpMode::kMatch);
+ EXPECT_EQ(InvokeParseAbslOnlyImpl(in_args1), flags::HelpMode::kMatch);
EXPECT_EQ(flags::GetFlagsHelpMatchSubstr(), "abcd");
+}
- const char* in_args2[] = {"testbin", "--help", "some_positional_arg"};
+// --------------------------------------------------------------------
- auto out_args2 = flags::ParseCommandLineImpl(
- 3, const_cast<char**>(in_args2), flags::ArgvListAction::kRemoveParsedArgs,
- flags::UsageFlagsAction::kIgnoreUsage,
- flags::OnUndefinedFlag::kAbortIfUndefined);
+TEST_F(ParseDeathTest, TestVersionHandling) {
+ const char* in_args1[] = {
+ "testbin",
+ "--version",
+ };
- EXPECT_EQ(flags::GetFlagsHelpMode(), flags::HelpMode::kImportant);
+ EXPECT_EQ(InvokeParseAbslOnlyImpl(in_args1), flags::HelpMode::kVersion);
+}
+
+// --------------------------------------------------------------------
+
+TEST_F(ParseTest, TestCheckArgsHandling) {
+ const char* in_args1[] = {"testbin", "--only_check_args", "--int_flag=211"};
+
+ EXPECT_EQ(InvokeParseAbslOnlyImpl(in_args1), flags::HelpMode::kOnlyCheckArgs);
+ EXPECT_EXIT(InvokeParseAbslOnly(in_args1), testing::ExitedWithCode(0), "");
+ EXPECT_EXIT(InvokeParse(in_args1), testing::ExitedWithCode(0), "");
+
+ const char* in_args2[] = {"testbin", "--only_check_args", "--unknown_flag=a"};
+
+ EXPECT_EQ(InvokeParseAbslOnlyImpl(in_args2), flags::HelpMode::kOnlyCheckArgs);
+ EXPECT_EXIT(InvokeParseAbslOnly(in_args2), testing::ExitedWithCode(0), "");
+ EXPECT_EXIT(InvokeParse(in_args2), testing::ExitedWithCode(1), "");
}
// --------------------------------------------------------------------
@@ -927,4 +977,118 @@
// --------------------------------------------------------------------
+TEST_F(ParseTest, ParseAbseilFlagsOnlySuccess) {
+ const char* in_args[] = {
+ "testbin",
+ "arg1",
+ "--bool_flag",
+ "--int_flag=211",
+ "arg2",
+ "--double_flag=1.1",
+ "--undef_flag1",
+ "--undef_flag2=123",
+ "--string_flag",
+ "asd",
+ "--",
+ "--some_flag",
+ "arg4",
+ };
+
+ std::vector<char*> positional_args;
+ std::vector<absl::UnrecognizedFlag> unrecognized_flags;
+
+ absl::ParseAbseilFlagsOnly(13, const_cast<char**>(in_args), positional_args,
+ unrecognized_flags);
+ EXPECT_THAT(positional_args,
+ ElementsAreArray(
+ {absl::string_view("testbin"), absl::string_view("arg1"),
+ absl::string_view("arg2"), absl::string_view("--some_flag"),
+ absl::string_view("arg4")}));
+ EXPECT_THAT(unrecognized_flags,
+ ElementsAreArray(
+ {absl::UnrecognizedFlag(absl::UnrecognizedFlag::kFromArgv,
+ "undef_flag1"),
+ absl::UnrecognizedFlag(absl::UnrecognizedFlag::kFromArgv,
+ "undef_flag2")}));
+}
+
+// --------------------------------------------------------------------
+
+TEST_F(ParseDeathTest, ParseAbseilFlagsOnlyFailure) {
+ const char* in_args[] = {
+ "testbin",
+ "--int_flag=21.1",
+ };
+
+ EXPECT_DEATH_IF_SUPPORTED(
+ InvokeParseAbslOnly(in_args),
+ "Illegal value '21.1' specified for flag 'int_flag'");
+}
+
+// --------------------------------------------------------------------
+
+TEST_F(ParseTest, UndefOkFlagsAreIgnored) {
+ const char* in_args[] = {
+ "testbin", "--undef_flag1",
+ "--undef_flag2=123", "--undefok=undef_flag2",
+ "--undef_flag3", "value",
+ };
+
+ std::vector<char*> positional_args;
+ std::vector<absl::UnrecognizedFlag> unrecognized_flags;
+
+ absl::ParseAbseilFlagsOnly(6, const_cast<char**>(in_args), positional_args,
+ unrecognized_flags);
+ EXPECT_THAT(positional_args, ElementsAreArray({absl::string_view("testbin"),
+ absl::string_view("value")}));
+ EXPECT_THAT(unrecognized_flags,
+ ElementsAreArray(
+ {absl::UnrecognizedFlag(absl::UnrecognizedFlag::kFromArgv,
+ "undef_flag1"),
+ absl::UnrecognizedFlag(absl::UnrecognizedFlag::kFromArgv,
+ "undef_flag3")}));
+}
+
+// --------------------------------------------------------------------
+
+TEST_F(ParseTest, AllUndefOkFlagsAreIgnored) {
+ const char* in_args[] = {
+ "testbin",
+ "--undef_flag1",
+ "--undef_flag2=123",
+ "--undefok=undef_flag2,undef_flag1,undef_flag3",
+ "--undef_flag3",
+ "value",
+ "--",
+ "--undef_flag4",
+ };
+
+ std::vector<char*> positional_args;
+ std::vector<absl::UnrecognizedFlag> unrecognized_flags;
+
+ absl::ParseAbseilFlagsOnly(8, const_cast<char**>(in_args), positional_args,
+ unrecognized_flags);
+ EXPECT_THAT(positional_args,
+ ElementsAreArray({absl::string_view("testbin"),
+ absl::string_view("value"),
+ absl::string_view("--undef_flag4")}));
+ EXPECT_THAT(unrecognized_flags, testing::IsEmpty());
+}
+
+// --------------------------------------------------------------------
+
+TEST_F(ParseDeathTest, ExitOnUnrecognizedFlagPrintsHelp) {
+ const char* in_args[] = {
+ "testbin",
+ "--undef_flag1",
+ "--help=int_flag",
+ };
+
+ EXPECT_EXIT(InvokeParseCommandLineImpl(in_args), testing::ExitedWithCode(1),
+ AllOf(HasSubstr("Unknown command line flag 'undef_flag1'"),
+ HasSubstr("Try --helpfull to get a list of all flags")));
+}
+
+// --------------------------------------------------------------------
+
} // namespace
diff --git a/absl/flags/reflection.cc b/absl/flags/reflection.cc
index dbce403..841921a 100644
--- a/absl/flags/reflection.cc
+++ b/absl/flags/reflection.cc
@@ -21,6 +21,7 @@
#include <string>
#include "absl/base/config.h"
+#include "absl/base/no_destructor.h"
#include "absl/base/thread_annotations.h"
#include "absl/container/flat_hash_map.h"
#include "absl/flags/commandlineflag.h"
@@ -169,7 +170,7 @@
}
FlagRegistry& FlagRegistry::GlobalRegistry() {
- static FlagRegistry* global_registry = new FlagRegistry;
+ static absl::NoDestructor<FlagRegistry> global_registry;
return *global_registry;
}
diff --git a/absl/flags/reflection_test.cc b/absl/flags/reflection_test.cc
index 79cfa90..68abeda 100644
--- a/absl/flags/reflection_test.cc
+++ b/absl/flags/reflection_test.cc
@@ -20,10 +20,8 @@
#include "gmock/gmock.h"
#include "gtest/gtest.h"
-#include "absl/flags/declare.h"
+#include "absl/flags/config.h"
#include "absl/flags/flag.h"
-#include "absl/flags/internal/commandlineflag.h"
-#include "absl/flags/marshalling.h"
#include "absl/memory/memory.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_split.h"
@@ -36,7 +34,12 @@
class ReflectionTest : public testing::Test {
protected:
- void SetUp() override { flag_saver_ = absl::make_unique<absl::FlagSaver>(); }
+ void SetUp() override {
+#if ABSL_FLAGS_STRIP_NAMES
+ GTEST_SKIP() << "This test requires flag names to be present";
+#endif
+ flag_saver_ = absl::make_unique<absl::FlagSaver>();
+ }
void TearDown() override { flag_saver_.reset(); }
private:
diff --git a/absl/flags/usage.cc b/absl/flags/usage.cc
index 452f667..267a503 100644
--- a/absl/flags/usage.cc
+++ b/absl/flags/usage.cc
@@ -21,6 +21,7 @@
#include "absl/base/attributes.h"
#include "absl/base/config.h"
#include "absl/base/const_init.h"
+#include "absl/base/internal/raw_logging.h"
#include "absl/base/thread_annotations.h"
#include "absl/flags/internal/usage.h"
#include "absl/strings/string_view.h"
diff --git a/absl/functional/BUILD.bazel b/absl/functional/BUILD.bazel
index ebd9b99..1a18af2 100644
--- a/absl/functional/BUILD.bazel
+++ b/absl/functional/BUILD.bazel
@@ -14,7 +14,6 @@
# limitations under the License.
#
-load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test")
load(
"//absl:copts/configure_copts.bzl",
"ABSL_DEFAULT_COPTS",
@@ -22,11 +21,53 @@
"ABSL_TEST_COPTS",
)
-package(default_visibility = ["//visibility:public"])
+package(
+ default_visibility = ["//visibility:public"],
+ features = [
+ "header_modules",
+ "layering_check",
+ "parse_headers",
+ ],
+)
licenses(["notice"])
cc_library(
+ name = "any_invocable",
+ srcs = ["internal/any_invocable.h"],
+ hdrs = ["any_invocable.h"],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ "//absl/base:base_internal",
+ "//absl/base:config",
+ "//absl/base:core_headers",
+ "//absl/meta:type_traits",
+ "//absl/utility",
+ ],
+)
+
+cc_test(
+ name = "any_invocable_test",
+ srcs = [
+ "any_invocable_test.cc",
+ "internal/any_invocable.h",
+ ],
+ copts = ABSL_TEST_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ ":any_invocable",
+ "//absl/base:base_internal",
+ "//absl/base:config",
+ "//absl/base:core_headers",
+ "//absl/meta:type_traits",
+ "//absl/utility",
+ "@com_google_googletest//:gtest",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_library(
name = "bind_front",
srcs = ["internal/front_binder.h"],
hdrs = ["bind_front.h"],
@@ -48,6 +89,7 @@
deps = [
":bind_front",
"//absl/memory",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -59,7 +101,9 @@
copts = ABSL_DEFAULT_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
+ ":any_invocable",
"//absl/base:base_internal",
+ "//absl/base:core_headers",
"//absl/meta:type_traits",
],
)
@@ -70,24 +114,55 @@
srcs = ["function_ref_test.cc"],
copts = ABSL_TEST_COPTS,
deps = [
+ ":any_invocable",
":function_ref",
"//absl/container:test_instance_tracker",
"//absl/memory",
+ "@com_google_googletest//:gtest",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_library(
+ name = "overload",
+ hdrs = ["overload.h"],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ "//absl/base:config",
+ "//absl/meta:type_traits",
+ ],
+)
+
+cc_test(
+ name = "overload_test",
+ size = "small",
+ srcs = ["overload_test.cc"],
+ copts = ABSL_TEST_COPTS,
+ deps = [
+ ":overload",
+ "//absl/base:config",
+ "//absl/strings",
+ "//absl/strings:string_view",
+ "//absl/types:variant",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
cc_test(
- name = "function_ref_benchmark",
+ name = "function_type_benchmark",
srcs = [
- "function_ref_benchmark.cc",
+ "function_type_benchmark.cc",
],
copts = ABSL_TEST_COPTS,
tags = ["benchmark"],
visibility = ["//visibility:private"],
deps = [
+ ":any_invocable",
":function_ref",
"//absl/base:core_headers",
"@com_github_google_benchmark//:benchmark_main",
+ "@com_google_googletest//:gtest",
],
)
diff --git a/absl/functional/CMakeLists.txt b/absl/functional/CMakeLists.txt
index 3919e9a..602829c 100644
--- a/absl/functional/CMakeLists.txt
+++ b/absl/functional/CMakeLists.txt
@@ -16,6 +16,42 @@
absl_cc_library(
NAME
+ any_invocable
+ SRCS
+ "internal/any_invocable.h"
+ HDRS
+ "any_invocable.h"
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ DEPS
+ absl::base_internal
+ absl::config
+ absl::core_headers
+ absl::type_traits
+ absl::utility
+ PUBLIC
+)
+
+absl_cc_test(
+ NAME
+ any_invocable_test
+ SRCS
+ "any_invocable_test.cc"
+ "internal/any_invocable.h"
+ COPTS
+ ${ABSL_TEST_COPTS}
+ DEPS
+ absl::any_invocable
+ absl::base_internal
+ absl::config
+ absl::core_headers
+ absl::type_traits
+ absl::utility
+ GTest::gmock_main
+)
+
+absl_cc_library(
+ NAME
bind_front
SRCS
"internal/front_binder.h"
@@ -53,6 +89,8 @@
${ABSL_DEFAULT_COPTS}
DEPS
absl::base_internal
+ absl::core_headers
+ absl::any_invocable
absl::meta
PUBLIC
)
@@ -70,3 +108,27 @@
absl::test_instance_tracker
GTest::gmock_main
)
+
+absl_cc_library(
+ NAME
+ overload
+ HDRS
+ "overload.h"
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ DEPS
+ absl::meta
+ PUBLIC
+)
+
+absl_cc_test(
+ NAME
+ overload_test
+ SRCS
+ "overload_test.cc"
+ COPTS
+ ${ABSL_TEST_COPTS}
+ DEPS
+ absl::strings
+ GTest::gmock_main
+)
diff --git a/absl/functional/any_invocable.h b/absl/functional/any_invocable.h
new file mode 100644
index 0000000..68d8825
--- /dev/null
+++ b/absl/functional/any_invocable.h
@@ -0,0 +1,324 @@
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// -----------------------------------------------------------------------------
+// File: any_invocable.h
+// -----------------------------------------------------------------------------
+//
+// This header file defines an `absl::AnyInvocable` type that assumes ownership
+// and wraps an object of an invocable type. (Invocable types adhere to the
+// concept specified in https://en.cppreference.com/w/cpp/concepts/invocable.)
+//
+// In general, prefer `absl::AnyInvocable` when you need a type-erased
+// function parameter that needs to take ownership of the type.
+//
+// NOTE: `absl::AnyInvocable` is similar to the C++23 `std::move_only_function`
+// abstraction, but has a slightly different API and is not designed to be a
+// drop-in replacement or C++11-compatible backfill of that type.
+//
+// Credits to Matt Calabrese (https://github.com/mattcalabrese) for the original
+// implementation.
+
+#ifndef ABSL_FUNCTIONAL_ANY_INVOCABLE_H_
+#define ABSL_FUNCTIONAL_ANY_INVOCABLE_H_
+
+#include <cstddef>
+#include <initializer_list>
+#include <type_traits>
+#include <utility>
+
+#include "absl/base/config.h"
+#include "absl/functional/internal/any_invocable.h"
+#include "absl/meta/type_traits.h"
+#include "absl/utility/utility.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+
+// absl::AnyInvocable
+//
+// `absl::AnyInvocable` is a functional wrapper type, like `std::function`, that
+// assumes ownership of an invocable object. Unlike `std::function`, an
+// `absl::AnyInvocable` is more type-safe and provides the following additional
+// benefits:
+//
+// * Properly adheres to const correctness of the underlying type
+// * Is move-only so avoids concurrency problems with copied invocables and
+// unnecessary copies in general.
+// * Supports reference qualifiers allowing it to perform unique actions (noted
+// below).
+//
+// `absl::AnyInvocable` is a template, and an `absl::AnyInvocable` instantiation
+// may wrap any invocable object with a compatible function signature, e.g.
+// having arguments and return types convertible to types matching the
+// `absl::AnyInvocable` signature, and also matching any stated reference
+// qualifiers, as long as that type is moveable. It therefore provides broad
+// type erasure for functional objects.
+//
+// An `absl::AnyInvocable` is typically used as a type-erased function parameter
+// for accepting various functional objects:
+//
+// // Define a function taking an AnyInvocable parameter.
+// void my_func(absl::AnyInvocable<int()> f) {
+// ...
+// };
+//
+// // That function can accept any invocable type:
+//
+// // Accept a function reference. We don't need to move a reference.
+// int func1() { return 0; };
+// my_func(func1);
+//
+// // Accept a lambda. We use std::move here because otherwise my_func would
+// // copy the lambda.
+// auto lambda = []() { return 0; };
+// my_func(std::move(lambda));
+//
+// // Accept a function pointer. We don't need to move a function pointer.
+// func2 = &func1;
+// my_func(func2);
+//
+// // Accept an std::function by moving it. Note that the lambda is copyable
+// // (satisfying std::function requirements) and moveable (satisfying
+// // absl::AnyInvocable requirements).
+// std::function<int()> func6 = []() { return 0; };
+// my_func(std::move(func6));
+//
+// `AnyInvocable` also properly respects `const` qualifiers, reference
+// qualifiers, and the `noexcept` specification (only in C++ 17 and beyond) as
+// part of the user-specified function type (e.g.
+// `AnyInvocable<void()&& const noexcept>`). These qualifiers will be applied to
+// the `AnyInvocable` object's `operator()`, and the underlying invocable must
+// be compatible with those qualifiers.
+//
+// Comparison of const and non-const function types:
+//
+// // Store a closure inside of `func` with the function type `int()`.
+// // Note that we have made `func` itself `const`.
+// const AnyInvocable<int()> func = [](){ return 0; };
+//
+// func(); // Compile-error: the passed type `int()` isn't `const`.
+//
+// // Store a closure inside of `const_func` with the function type
+// // `int() const`.
+// // Note that we have also made `const_func` itself `const`.
+// const AnyInvocable<int() const> const_func = [](){ return 0; };
+//
+// const_func(); // Fine: `int() const` is `const`.
+//
+// In the above example, the call `func()` would have compiled if
+// `std::function` were used even though the types are not const compatible.
+// This is a bug, and using `absl::AnyInvocable` properly detects that bug.
+//
+// In addition to affecting the signature of `operator()`, the `const` and
+// reference qualifiers of the function type also appropriately constrain which
+// kinds of invocable objects you are allowed to place into the `AnyInvocable`
+// instance. If you specify a function type that is const-qualified, then
+// anything that you attempt to put into the `AnyInvocable` must be callable on
+// a `const` instance of that type.
+//
+// Constraint example:
+//
+// // Fine because the lambda is callable when `const`.
+// AnyInvocable<int() const> func = [=](){ return 0; };
+//
+// // This is a compile-error because the lambda isn't callable when `const`.
+// AnyInvocable<int() const> error = [=]() mutable { return 0; };
+//
+// An `&&` qualifier can be used to express that an `absl::AnyInvocable`
+// instance should be invoked at most once:
+//
+// // Invokes `continuation` with the logical result of an operation when
+// // that operation completes (common in asynchronous code).
+// void CallOnCompletion(AnyInvocable<void(int)&&> continuation) {
+// int result_of_foo = foo();
+//
+// // `std::move` is required because the `operator()` of `continuation` is
+// // rvalue-reference qualified.
+// std::move(continuation)(result_of_foo);
+// }
+//
+// Attempting to call `absl::AnyInvocable` multiple times in such a case
+// results in undefined behavior.
+template <class Sig>
+class AnyInvocable : private internal_any_invocable::Impl<Sig> {
+ private:
+ static_assert(
+ std::is_function<Sig>::value,
+ "The template argument of AnyInvocable must be a function type.");
+
+ using Impl = internal_any_invocable::Impl<Sig>;
+
+ public:
+ // The return type of Sig
+ using result_type = typename Impl::result_type;
+
+ // Constructors
+
+ // Constructs the `AnyInvocable` in an empty state.
+ AnyInvocable() noexcept = default;
+ AnyInvocable(std::nullptr_t) noexcept {} // NOLINT
+
+ // Constructs the `AnyInvocable` from an existing `AnyInvocable` by a move.
+ // Note that `f` is not guaranteed to be empty after move-construction,
+ // although it may be.
+ AnyInvocable(AnyInvocable&& /*f*/) noexcept = default;
+
+ // Constructs an `AnyInvocable` from an invocable object.
+ //
+ // Upon construction, `*this` is only empty if `f` is a function pointer or
+ // member pointer type and is null, or if `f` is an `AnyInvocable` that is
+ // empty.
+ template <class F, typename = absl::enable_if_t<
+ internal_any_invocable::CanConvert<Sig, F>::value>>
+ AnyInvocable(F&& f) // NOLINT
+ : Impl(internal_any_invocable::ConversionConstruct(),
+ std::forward<F>(f)) {}
+
+ // Constructs an `AnyInvocable` that holds an invocable object of type `T`,
+ // which is constructed in-place from the given arguments.
+ //
+ // Example:
+ //
+ // AnyInvocable<int(int)> func(
+ // absl::in_place_type<PossiblyImmovableType>, arg1, arg2);
+ //
+ template <class T, class... Args,
+ typename = absl::enable_if_t<
+ internal_any_invocable::CanEmplace<Sig, T, Args...>::value>>
+ explicit AnyInvocable(absl::in_place_type_t<T>, Args&&... args)
+ : Impl(absl::in_place_type<absl::decay_t<T>>,
+ std::forward<Args>(args)...) {
+ static_assert(std::is_same<T, absl::decay_t<T>>::value,
+ "The explicit template argument of in_place_type is required "
+ "to be an unqualified object type.");
+ }
+
+ // Overload of the above constructor to support list-initialization.
+ template <class T, class U, class... Args,
+ typename = absl::enable_if_t<internal_any_invocable::CanEmplace<
+ Sig, T, std::initializer_list<U>&, Args...>::value>>
+ explicit AnyInvocable(absl::in_place_type_t<T>,
+ std::initializer_list<U> ilist, Args&&... args)
+ : Impl(absl::in_place_type<absl::decay_t<T>>, ilist,
+ std::forward<Args>(args)...) {
+ static_assert(std::is_same<T, absl::decay_t<T>>::value,
+ "The explicit template argument of in_place_type is required "
+ "to be an unqualified object type.");
+ }
+
+ // Assignment Operators
+
+ // Assigns an `AnyInvocable` through move-assignment.
+ // Note that `f` is not guaranteed to be empty after move-assignment
+ // although it may be.
+ AnyInvocable& operator=(AnyInvocable&& /*f*/) noexcept = default;
+
+ // Assigns an `AnyInvocable` from a nullptr, clearing the `AnyInvocable`. If
+ // not empty, destroys the target, putting `*this` into an empty state.
+ AnyInvocable& operator=(std::nullptr_t) noexcept {
+ this->Clear();
+ return *this;
+ }
+
+ // Assigns an `AnyInvocable` from an existing `AnyInvocable` instance.
+ //
+ // Upon assignment, `*this` is only empty if `f` is a function pointer or
+ // member pointer type and is null, or if `f` is an `AnyInvocable` that is
+ // empty.
+ template <class F, typename = absl::enable_if_t<
+ internal_any_invocable::CanAssign<Sig, F>::value>>
+ AnyInvocable& operator=(F&& f) {
+ *this = AnyInvocable(std::forward<F>(f));
+ return *this;
+ }
+
+ // Assigns an `AnyInvocable` from a reference to an invocable object.
+ // Upon assignment, stores a reference to the invocable object in the
+ // `AnyInvocable` instance.
+ template <
+ class F,
+ typename = absl::enable_if_t<
+ internal_any_invocable::CanAssignReferenceWrapper<Sig, F>::value>>
+ AnyInvocable& operator=(std::reference_wrapper<F> f) noexcept {
+ *this = AnyInvocable(f);
+ return *this;
+ }
+
+ // Destructor
+
+ // If not empty, destroys the target.
+ ~AnyInvocable() = default;
+
+ // absl::AnyInvocable::swap()
+ //
+ // Exchanges the targets of `*this` and `other`.
+ void swap(AnyInvocable& other) noexcept { std::swap(*this, other); }
+
+ // absl::AnyInvocable::operator bool()
+ //
+ // Returns `true` if `*this` is not empty.
+ //
+ // WARNING: An `AnyInvocable` that wraps an empty `std::function` is not
+ // itself empty. This behavior is consistent with the standard equivalent
+ // `std::move_only_function`.
+ //
+ // In other words:
+ // std::function<void()> f; // empty
+ // absl::AnyInvocable<void()> a = std::move(f); // not empty
+ explicit operator bool() const noexcept { return this->HasValue(); }
+
+ // Invokes the target object of `*this`. `*this` must not be empty.
+ //
+ // Note: The signature of this function call operator is the same as the
+ // template parameter `Sig`.
+ using Impl::operator();
+
+ // Equality operators
+
+ // Returns `true` if `*this` is empty.
+ friend bool operator==(const AnyInvocable& f, std::nullptr_t) noexcept {
+ return !f.HasValue();
+ }
+
+ // Returns `true` if `*this` is empty.
+ friend bool operator==(std::nullptr_t, const AnyInvocable& f) noexcept {
+ return !f.HasValue();
+ }
+
+ // Returns `false` if `*this` is empty.
+ friend bool operator!=(const AnyInvocable& f, std::nullptr_t) noexcept {
+ return f.HasValue();
+ }
+
+ // Returns `false` if `*this` is empty.
+ friend bool operator!=(std::nullptr_t, const AnyInvocable& f) noexcept {
+ return f.HasValue();
+ }
+
+ // swap()
+ //
+ // Exchanges the targets of `f1` and `f2`.
+ friend void swap(AnyInvocable& f1, AnyInvocable& f2) noexcept { f1.swap(f2); }
+
+ private:
+ // Friending other instantiations is necessary for conversions.
+ template <bool /*SigIsNoexcept*/, class /*ReturnType*/, class... /*P*/>
+ friend class internal_any_invocable::CoreImpl;
+};
+
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_FUNCTIONAL_ANY_INVOCABLE_H_
diff --git a/absl/functional/any_invocable_test.cc b/absl/functional/any_invocable_test.cc
new file mode 100644
index 0000000..a740faa
--- /dev/null
+++ b/absl/functional/any_invocable_test.cc
@@ -0,0 +1,1719 @@
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/functional/any_invocable.h"
+
+#include <cstddef>
+#include <initializer_list>
+#include <memory>
+#include <numeric>
+#include <type_traits>
+
+#include "gtest/gtest.h"
+#include "absl/base/config.h"
+#include "absl/meta/type_traits.h"
+#include "absl/utility/utility.h"
+
+static_assert(absl::internal_any_invocable::kStorageSize >= sizeof(void*),
+ "These tests assume that the small object storage is at least "
+ "the size of a pointer.");
+
+namespace {
+
+// Helper macro used to avoid spelling `noexcept` in language versions older
+// than C++17, where it is not part of the type system, in order to avoid
+// compilation failures and internal compiler errors.
+#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 201703L
+#define ABSL_INTERNAL_NOEXCEPT_SPEC(noex) noexcept(noex)
+#else
+#define ABSL_INTERNAL_NOEXCEPT_SPEC(noex)
+#endif
+
+// A dummy type we use when passing qualifiers to metafunctions
+struct _ {};
+
+template <class T>
+struct Wrapper {
+ template <class U,
+ class = absl::enable_if_t<std::is_convertible<U, T>::value>>
+ Wrapper(U&&); // NOLINT
+};
+
+// This will cause a recursive trait instantiation if the SFINAE checks are
+// not ordered correctly for constructibility.
+static_assert(std::is_constructible<Wrapper<absl::AnyInvocable<void()>>,
+ Wrapper<absl::AnyInvocable<void()>>>::value,
+ "");
+
+// A metafunction that takes the cv and l-value reference qualifiers that were
+// associated with a function type (here passed via qualifiers of an object
+// type), and .
+template <class Qualifiers, class This>
+struct QualifiersForThisImpl {
+ static_assert(std::is_object<This>::value, "");
+ using type =
+ absl::conditional_t<std::is_const<Qualifiers>::value, const This, This>&;
+};
+
+template <class Qualifiers, class This>
+struct QualifiersForThisImpl<Qualifiers&, This>
+ : QualifiersForThisImpl<Qualifiers, This> {};
+
+template <class Qualifiers, class This>
+struct QualifiersForThisImpl<Qualifiers&&, This> {
+ static_assert(std::is_object<This>::value, "");
+ using type =
+ absl::conditional_t<std::is_const<Qualifiers>::value, const This, This>&&;
+};
+
+template <class Qualifiers, class This>
+using QualifiersForThis =
+ typename QualifiersForThisImpl<Qualifiers, This>::type;
+
+// A metafunction that takes the cv and l-value reference qualifier of T and
+// applies them to U's function type qualifiers.
+template <class T, class Fun>
+struct GiveQualifiersToFunImpl;
+
+template <class T, class R, class... P>
+struct GiveQualifiersToFunImpl<T, R(P...)> {
+ using type =
+ absl::conditional_t<std::is_const<T>::value, R(P...) const, R(P...)>;
+};
+
+template <class T, class R, class... P>
+struct GiveQualifiersToFunImpl<T&, R(P...)> {
+ using type =
+ absl::conditional_t<std::is_const<T>::value, R(P...) const&, R(P...)&>;
+};
+
+template <class T, class R, class... P>
+struct GiveQualifiersToFunImpl<T&&, R(P...)> {
+ using type =
+ absl::conditional_t<std::is_const<T>::value, R(P...) const&&, R(P...) &&>;
+};
+
+// If noexcept is a part of the type system, then provide the noexcept forms.
+#if defined(__cpp_noexcept_function_type)
+
+template <class T, class R, class... P>
+struct GiveQualifiersToFunImpl<T, R(P...) noexcept> {
+ using type = absl::conditional_t<std::is_const<T>::value,
+ R(P...) const noexcept, R(P...) noexcept>;
+};
+
+template <class T, class R, class... P>
+struct GiveQualifiersToFunImpl<T&, R(P...) noexcept> {
+ using type =
+ absl::conditional_t<std::is_const<T>::value, R(P...) const & noexcept,
+ R(P...) & noexcept>;
+};
+
+template <class T, class R, class... P>
+struct GiveQualifiersToFunImpl<T&&, R(P...) noexcept> {
+ using type =
+ absl::conditional_t<std::is_const<T>::value, R(P...) const && noexcept,
+ R(P...) && noexcept>;
+};
+
+#endif // defined(__cpp_noexcept_function_type)
+
+template <class T, class Fun>
+using GiveQualifiersToFun = typename GiveQualifiersToFunImpl<T, Fun>::type;
+
+// This is used in template parameters to decide whether or not to use a type
+// that fits in the small object optimization storage.
+enum class ObjSize { small, large };
+
+// A base type that is used with classes as a means to insert an
+// appropriately-sized dummy datamember when Size is ObjSize::large so that the
+// user's class type is guaranteed to not fit in small object storage.
+template <ObjSize Size>
+struct TypeErasedPadding;
+
+template <>
+struct TypeErasedPadding<ObjSize::small> {};
+
+template <>
+struct TypeErasedPadding<ObjSize::large> {
+ char dummy_data[absl::internal_any_invocable::kStorageSize + 1] = {};
+};
+
+struct Int {
+ Int(int v) noexcept : value(v) {} // NOLINT
+#ifndef _MSC_VER
+ Int(Int&&) noexcept {
+ // NOTE: Prior to C++17, this not being called requires optimizations to
+ // take place when performing the top-level invocation. In practice,
+ // most supported compilers perform this optimization prior to C++17.
+ std::abort();
+ }
+#else
+ Int(Int&& v) noexcept = default;
+#endif
+ operator int() && noexcept { return value; } // NOLINT
+
+ int MemberFunctionAdd(int const& b, int c) noexcept { // NOLINT
+ return value + b + c;
+ }
+
+ int value;
+};
+
+enum class Movable { no, yes, nothrow, trivial };
+
+enum class NothrowCall { no, yes };
+
+enum class Destructible { nothrow, trivial };
+
+enum class ObjAlign : std::size_t {
+ normal = absl::internal_any_invocable::kAlignment,
+ large = absl::internal_any_invocable::kAlignment * 2,
+};
+
+// A function-object template that has knobs for each property that can affect
+// how the object is stored in AnyInvocable.
+template <Movable Movability, Destructible Destructibility, class Qual,
+ NothrowCall CallExceptionSpec, ObjSize Size, ObjAlign Alignment>
+struct add;
+
+#define ABSL_INTERNALS_ADD(qual) \
+ template <NothrowCall CallExceptionSpec, ObjSize Size, ObjAlign Alignment> \
+ struct alignas(static_cast<std::size_t>(Alignment)) \
+ add<Movable::trivial, Destructible::trivial, _ qual, CallExceptionSpec, \
+ Size, Alignment> : TypeErasedPadding<Size> { \
+ explicit add(int state_init) : state(state_init) {} \
+ explicit add(std::initializer_list<int> state_init, int tail) \
+ : state(std::accumulate(std::begin(state_init), std::end(state_init), \
+ 0) + \
+ tail) {} \
+ add(add&& other) = default; /*NOLINT*/ \
+ Int operator()(int a, int b, int c) qual \
+ ABSL_INTERNAL_NOEXCEPT_SPEC(CallExceptionSpec == NothrowCall::yes) { \
+ return state + a + b + c; \
+ } \
+ int state; \
+ }; \
+ \
+ template <NothrowCall CallExceptionSpec, ObjSize Size, ObjAlign Alignment> \
+ struct alignas(static_cast<std::size_t>(Alignment)) \
+ add<Movable::trivial, Destructible::nothrow, _ qual, CallExceptionSpec, \
+ Size, Alignment> : TypeErasedPadding<Size> { \
+ explicit add(int state_init) : state(state_init) {} \
+ explicit add(std::initializer_list<int> state_init, int tail) \
+ : state(std::accumulate(std::begin(state_init), std::end(state_init), \
+ 0) + \
+ tail) {} \
+ ~add() noexcept {} \
+ add(add&& other) = default; /*NOLINT*/ \
+ Int operator()(int a, int b, int c) qual \
+ ABSL_INTERNAL_NOEXCEPT_SPEC(CallExceptionSpec == NothrowCall::yes) { \
+ return state + a + b + c; \
+ } \
+ int state; \
+ }
+
+// Explicitly specify an empty argument.
+// MSVC (at least up to _MSC_VER 1931, if not beyond) warns that
+// ABSL_INTERNALS_ADD() is an undefined zero-arg overload.
+#define ABSL_INTERNALS_NOARG
+ABSL_INTERNALS_ADD(ABSL_INTERNALS_NOARG);
+#undef ABSL_INTERNALS_NOARG
+
+ABSL_INTERNALS_ADD(const);
+ABSL_INTERNALS_ADD(&);
+ABSL_INTERNALS_ADD(const&);
+ABSL_INTERNALS_ADD(&&); // NOLINT
+ABSL_INTERNALS_ADD(const&&); // NOLINT
+
+#undef ABSL_INTERNALS_ADD
+
+template <Destructible Destructibility, class Qual,
+ NothrowCall CallExceptionSpec, ObjSize Size, ObjAlign Alignment>
+struct add<Movable::no, Destructibility, Qual, CallExceptionSpec, Size,
+ Alignment> : private add<Movable::trivial, Destructibility, Qual,
+ CallExceptionSpec, Size, Alignment> {
+ using Base = add<Movable::trivial, Destructibility, Qual, CallExceptionSpec,
+ Size, Alignment>;
+
+ explicit add(int state_init) : Base(state_init) {}
+
+ explicit add(std::initializer_list<int> state_init, int tail)
+ : Base(state_init, tail) {}
+
+ add(add&&) = delete;
+
+ using Base::operator();
+ using Base::state;
+};
+
+template <Destructible Destructibility, class Qual,
+ NothrowCall CallExceptionSpec, ObjSize Size, ObjAlign Alignment>
+struct add<Movable::yes, Destructibility, Qual, CallExceptionSpec, Size,
+ Alignment> : private add<Movable::trivial, Destructibility, Qual,
+ CallExceptionSpec, Size, Alignment> {
+ using Base = add<Movable::trivial, Destructibility, Qual, CallExceptionSpec,
+ Size, Alignment>;
+
+ explicit add(int state_init) : Base(state_init) {}
+
+ explicit add(std::initializer_list<int> state_init, int tail)
+ : Base(state_init, tail) {}
+
+ add(add&& other) noexcept(false) : Base(other.state) {} // NOLINT
+
+ using Base::operator();
+ using Base::state;
+};
+
+template <Destructible Destructibility, class Qual,
+ NothrowCall CallExceptionSpec, ObjSize Size, ObjAlign Alignment>
+struct add<Movable::nothrow, Destructibility, Qual, CallExceptionSpec, Size,
+ Alignment> : private add<Movable::trivial, Destructibility, Qual,
+ CallExceptionSpec, Size, Alignment> {
+ using Base = add<Movable::trivial, Destructibility, Qual, CallExceptionSpec,
+ Size, Alignment>;
+
+ explicit add(int state_init) : Base(state_init) {}
+
+ explicit add(std::initializer_list<int> state_init, int tail)
+ : Base(state_init, tail) {}
+
+ add(add&& other) noexcept : Base(other.state) {}
+
+ using Base::operator();
+ using Base::state;
+};
+
+// Actual non-member functions rather than function objects
+Int add_function(Int&& a, int b, int c) noexcept { return a.value + b + c; }
+
+Int mult_function(Int&& a, int b, int c) noexcept { return a.value * b * c; }
+
+Int square_function(Int const&& a) noexcept { return a.value * a.value; }
+
+template <class Sig>
+using AnyInvocable = absl::AnyInvocable<Sig>;
+
+// Instantiations of this template contains all of the compile-time parameters
+// for a given instantiation of the AnyInvocable test suite.
+template <Movable Movability, Destructible Destructibility, class Qual,
+ NothrowCall CallExceptionSpec, ObjSize Size, ObjAlign Alignment>
+struct TestParams {
+ static constexpr Movable kMovability = Movability;
+ static constexpr Destructible kDestructibility = Destructibility;
+ using Qualifiers = Qual;
+ static constexpr NothrowCall kCallExceptionSpec = CallExceptionSpec;
+ static constexpr bool kIsNoexcept = kCallExceptionSpec == NothrowCall::yes;
+ static constexpr bool kIsRvalueQualified =
+ std::is_rvalue_reference<Qual>::value;
+ static constexpr ObjSize kSize = Size;
+ static constexpr ObjAlign kAlignment = Alignment;
+
+ // These types are used when testing with member object pointer Invocables
+ using UnqualifiedUnaryFunType = int(Int const&&)
+ ABSL_INTERNAL_NOEXCEPT_SPEC(CallExceptionSpec == NothrowCall::yes);
+ using UnaryFunType = GiveQualifiersToFun<Qualifiers, UnqualifiedUnaryFunType>;
+ using MemObjPtrType = int(Int::*);
+ using UnaryAnyInvType = AnyInvocable<UnaryFunType>;
+ using UnaryThisParamType = QualifiersForThis<Qualifiers, UnaryAnyInvType>;
+
+ template <class T>
+ static UnaryThisParamType ToUnaryThisParam(T&& fun) {
+ return static_cast<UnaryThisParamType>(fun);
+ }
+
+ // This function type intentionally uses 3 "kinds" of parameter types.
+ // - A user-defined type
+ // - A reference type
+ // - A scalar type
+ //
+ // These were chosen because internal forwarding takes place on parameters
+ // differently depending based on type properties (scalars are forwarded by
+ // value).
+ using ResultType = Int;
+ using AnyInvocableFunTypeNotNoexcept = Int(Int, const int&, int);
+ using UnqualifiedFunType =
+ typename std::conditional<kIsNoexcept, Int(Int, const int&, int) noexcept,
+ Int(Int, const int&, int)>::type;
+ using FunType = GiveQualifiersToFun<Qualifiers, UnqualifiedFunType>;
+ using MemFunPtrType =
+ typename std::conditional<kIsNoexcept,
+ Int (Int::*)(const int&, int) noexcept,
+ Int (Int::*)(const int&, int)>::type;
+ using AnyInvType = AnyInvocable<FunType>;
+ using AddType = add<kMovability, kDestructibility, Qualifiers,
+ kCallExceptionSpec, kSize, kAlignment>;
+ using ThisParamType = QualifiersForThis<Qualifiers, AnyInvType>;
+
+ template <class T>
+ static ThisParamType ToThisParam(T&& fun) {
+ return static_cast<ThisParamType>(fun);
+ }
+
+ // These typedefs are used when testing void return type covariance.
+ using UnqualifiedVoidFunType =
+ typename std::conditional<kIsNoexcept,
+ void(Int, const int&, int) noexcept,
+ void(Int, const int&, int)>::type;
+ using VoidFunType = GiveQualifiersToFun<Qualifiers, UnqualifiedVoidFunType>;
+ using VoidAnyInvType = AnyInvocable<VoidFunType>;
+ using VoidThisParamType = QualifiersForThis<Qualifiers, VoidAnyInvType>;
+
+ template <class T>
+ static VoidThisParamType ToVoidThisParam(T&& fun) {
+ return static_cast<VoidThisParamType>(fun);
+ }
+
+ using CompatibleAnyInvocableFunType =
+ absl::conditional_t<std::is_rvalue_reference<Qual>::value,
+ GiveQualifiersToFun<const _&&, UnqualifiedFunType>,
+ GiveQualifiersToFun<const _&, UnqualifiedFunType>>;
+
+ using CompatibleAnyInvType = AnyInvocable<CompatibleAnyInvocableFunType>;
+
+ using IncompatibleInvocable =
+ absl::conditional_t<std::is_rvalue_reference<Qual>::value,
+ GiveQualifiersToFun<_&, UnqualifiedFunType>(_::*),
+ GiveQualifiersToFun<_&&, UnqualifiedFunType>(_::*)>;
+};
+
+// Given a member-pointer type, this metafunction yields the target type of the
+// pointer, not including the class-type. It is used to verify that the function
+// call operator of AnyInvocable has the proper signature, corresponding to the
+// function type that the user provided.
+template <class MemberPtrType>
+struct MemberTypeOfImpl;
+
+template <class Class, class T>
+struct MemberTypeOfImpl<T(Class::*)> {
+ using type = T;
+};
+
+template <class MemberPtrType>
+using MemberTypeOf = typename MemberTypeOfImpl<MemberPtrType>::type;
+
+template <class T, class = void>
+struct IsMemberSwappableImpl : std::false_type {
+ static constexpr bool kIsNothrow = false;
+};
+
+template <class T>
+struct IsMemberSwappableImpl<
+ T, absl::void_t<decltype(std::declval<T&>().swap(std::declval<T&>()))>>
+ : std::true_type {
+ static constexpr bool kIsNothrow =
+ noexcept(std::declval<T&>().swap(std::declval<T&>()));
+};
+
+template <class T>
+using IsMemberSwappable = IsMemberSwappableImpl<T>;
+
+template <class T>
+using IsNothrowMemberSwappable =
+ std::integral_constant<bool, IsMemberSwappableImpl<T>::kIsNothrow>;
+
+template <class T>
+class AnyInvTestBasic : public ::testing::Test {};
+
+TYPED_TEST_SUITE_P(AnyInvTestBasic);
+
+TYPED_TEST_P(AnyInvTestBasic, DefaultConstruction) {
+ using AnyInvType = typename TypeParam::AnyInvType;
+
+ AnyInvType fun;
+
+ EXPECT_FALSE(static_cast<bool>(fun));
+
+ EXPECT_TRUE(std::is_nothrow_default_constructible<AnyInvType>::value);
+}
+
+TYPED_TEST_P(AnyInvTestBasic, ConstructionNullptr) {
+ using AnyInvType = typename TypeParam::AnyInvType;
+
+ AnyInvType fun = nullptr;
+
+ EXPECT_FALSE(static_cast<bool>(fun));
+
+ EXPECT_TRUE(
+ (std::is_nothrow_constructible<AnyInvType, std::nullptr_t>::value));
+}
+
+TYPED_TEST_P(AnyInvTestBasic, ConstructionNullFunctionPtr) {
+ using AnyInvType = typename TypeParam::AnyInvType;
+ using UnqualifiedFunType = typename TypeParam::UnqualifiedFunType;
+
+ UnqualifiedFunType* const null_fun_ptr = nullptr;
+ AnyInvType fun = null_fun_ptr;
+
+ EXPECT_FALSE(static_cast<bool>(fun));
+}
+
+TYPED_TEST_P(AnyInvTestBasic, ConstructionNullMemberFunctionPtr) {
+ using AnyInvType = typename TypeParam::AnyInvType;
+ using MemFunPtrType = typename TypeParam::MemFunPtrType;
+
+ const MemFunPtrType null_mem_fun_ptr = nullptr;
+ AnyInvType fun = null_mem_fun_ptr;
+
+ EXPECT_FALSE(static_cast<bool>(fun));
+}
+
+TYPED_TEST_P(AnyInvTestBasic, ConstructionNullMemberObjectPtr) {
+ using UnaryAnyInvType = typename TypeParam::UnaryAnyInvType;
+ using MemObjPtrType = typename TypeParam::MemObjPtrType;
+
+ const MemObjPtrType null_mem_obj_ptr = nullptr;
+ UnaryAnyInvType fun = null_mem_obj_ptr;
+
+ EXPECT_FALSE(static_cast<bool>(fun));
+}
+
+TYPED_TEST_P(AnyInvTestBasic, ConstructionMemberFunctionPtr) {
+ using AnyInvType = typename TypeParam::AnyInvType;
+
+ AnyInvType fun = &Int::MemberFunctionAdd;
+
+ EXPECT_TRUE(static_cast<bool>(fun));
+ EXPECT_EQ(24, TypeParam::ToThisParam(fun)(7, 8, 9).value);
+}
+
+TYPED_TEST_P(AnyInvTestBasic, ConstructionMemberObjectPtr) {
+ using UnaryAnyInvType = typename TypeParam::UnaryAnyInvType;
+
+ UnaryAnyInvType fun = &Int::value;
+
+ EXPECT_TRUE(static_cast<bool>(fun));
+ EXPECT_EQ(13, TypeParam::ToUnaryThisParam(fun)(13));
+}
+
+TYPED_TEST_P(AnyInvTestBasic, ConstructionFunctionReferenceDecay) {
+ using AnyInvType = typename TypeParam::AnyInvType;
+
+ AnyInvType fun = add_function;
+
+ EXPECT_TRUE(static_cast<bool>(fun));
+ EXPECT_EQ(24, TypeParam::ToThisParam(fun)(7, 8, 9).value);
+}
+
+TYPED_TEST_P(AnyInvTestBasic, ConstructionCompatibleAnyInvocableEmpty) {
+ using AnyInvType = typename TypeParam::AnyInvType;
+ using CompatibleAnyInvType = typename TypeParam::CompatibleAnyInvType;
+
+ CompatibleAnyInvType other;
+ AnyInvType fun = std::move(other);
+
+ EXPECT_FALSE(static_cast<bool>(other)); // NOLINT
+ EXPECT_EQ(other, nullptr); // NOLINT
+ EXPECT_EQ(nullptr, other); // NOLINT
+
+ EXPECT_FALSE(static_cast<bool>(fun));
+}
+
+TYPED_TEST_P(AnyInvTestBasic, ConstructionCompatibleAnyInvocableNonempty) {
+ using AnyInvType = typename TypeParam::AnyInvType;
+ using CompatibleAnyInvType = typename TypeParam::CompatibleAnyInvType;
+
+ CompatibleAnyInvType other = &add_function;
+ AnyInvType fun = std::move(other);
+
+ EXPECT_FALSE(static_cast<bool>(other)); // NOLINT
+ EXPECT_EQ(other, nullptr); // NOLINT
+ EXPECT_EQ(nullptr, other); // NOLINT
+
+ EXPECT_TRUE(static_cast<bool>(fun));
+ EXPECT_EQ(24, TypeParam::ToThisParam(fun)(7, 8, 9).value);
+}
+
+TYPED_TEST_P(AnyInvTestBasic, ConversionToBool) {
+ using AnyInvType = typename TypeParam::AnyInvType;
+
+ {
+ AnyInvType fun;
+
+ // This tests contextually-convertible-to-bool.
+ EXPECT_FALSE(fun ? true : false); // NOLINT
+
+ // Make sure that the conversion is not implicit.
+ EXPECT_TRUE(
+ (std::is_nothrow_constructible<bool, const AnyInvType&>::value));
+ EXPECT_FALSE((std::is_convertible<const AnyInvType&, bool>::value));
+ }
+
+ {
+ AnyInvType fun = &add_function;
+
+ // This tests contextually-convertible-to-bool.
+ EXPECT_TRUE(fun ? true : false); // NOLINT
+ }
+}
+
+TYPED_TEST_P(AnyInvTestBasic, Invocation) {
+ using AnyInvType = typename TypeParam::AnyInvType;
+
+ using FunType = typename TypeParam::FunType;
+ using AnyInvCallType = MemberTypeOf<decltype(&AnyInvType::operator())>;
+
+ // Make sure the function call operator of AnyInvocable always has the
+ // type that was specified via the template argument.
+ EXPECT_TRUE((std::is_same<AnyInvCallType, FunType>::value));
+
+ AnyInvType fun = &add_function;
+
+ EXPECT_EQ(24, TypeParam::ToThisParam(fun)(7, 8, 9).value);
+}
+
+TYPED_TEST_P(AnyInvTestBasic, InPlaceConstruction) {
+ using AnyInvType = typename TypeParam::AnyInvType;
+ using AddType = typename TypeParam::AddType;
+
+ AnyInvType fun(absl::in_place_type<AddType>, 5);
+
+ EXPECT_TRUE(static_cast<bool>(fun));
+ EXPECT_EQ(29, TypeParam::ToThisParam(fun)(7, 8, 9).value);
+}
+
+TYPED_TEST_P(AnyInvTestBasic, InPlaceConstructionInitializerList) {
+ using AnyInvType = typename TypeParam::AnyInvType;
+ using AddType = typename TypeParam::AddType;
+
+ AnyInvType fun(absl::in_place_type<AddType>, {1, 2, 3, 4}, 5);
+
+ EXPECT_TRUE(static_cast<bool>(fun));
+ EXPECT_EQ(39, TypeParam::ToThisParam(fun)(7, 8, 9).value);
+}
+
+TYPED_TEST_P(AnyInvTestBasic, InPlaceNullFunPtrConstruction) {
+ using AnyInvType = typename TypeParam::AnyInvType;
+ using UnqualifiedFunType = typename TypeParam::UnqualifiedFunType;
+
+ AnyInvType fun(absl::in_place_type<UnqualifiedFunType*>, nullptr);
+
+ // In-place construction does not lead to empty.
+ EXPECT_TRUE(static_cast<bool>(fun));
+}
+
+TYPED_TEST_P(AnyInvTestBasic, InPlaceNullFunPtrConstructionValueInit) {
+ using AnyInvType = typename TypeParam::AnyInvType;
+ using UnqualifiedFunType = typename TypeParam::UnqualifiedFunType;
+
+ AnyInvType fun(absl::in_place_type<UnqualifiedFunType*>);
+
+ // In-place construction does not lead to empty.
+ EXPECT_TRUE(static_cast<bool>(fun));
+}
+
+TYPED_TEST_P(AnyInvTestBasic, InPlaceNullMemFunPtrConstruction) {
+ using AnyInvType = typename TypeParam::AnyInvType;
+ using MemFunPtrType = typename TypeParam::MemFunPtrType;
+
+ AnyInvType fun(absl::in_place_type<MemFunPtrType>, nullptr);
+
+ // In-place construction does not lead to empty.
+ EXPECT_TRUE(static_cast<bool>(fun));
+}
+
+TYPED_TEST_P(AnyInvTestBasic, InPlaceNullMemFunPtrConstructionValueInit) {
+ using AnyInvType = typename TypeParam::AnyInvType;
+ using MemFunPtrType = typename TypeParam::MemFunPtrType;
+
+ AnyInvType fun(absl::in_place_type<MemFunPtrType>);
+
+ // In-place construction does not lead to empty.
+ EXPECT_TRUE(static_cast<bool>(fun));
+}
+
+TYPED_TEST_P(AnyInvTestBasic, InPlaceNullMemObjPtrConstruction) {
+ using UnaryAnyInvType = typename TypeParam::UnaryAnyInvType;
+ using MemObjPtrType = typename TypeParam::MemObjPtrType;
+
+ UnaryAnyInvType fun(absl::in_place_type<MemObjPtrType>, nullptr);
+
+ // In-place construction does not lead to empty.
+ EXPECT_TRUE(static_cast<bool>(fun));
+}
+
+TYPED_TEST_P(AnyInvTestBasic, InPlaceNullMemObjPtrConstructionValueInit) {
+ using UnaryAnyInvType = typename TypeParam::UnaryAnyInvType;
+ using MemObjPtrType = typename TypeParam::MemObjPtrType;
+
+ UnaryAnyInvType fun(absl::in_place_type<MemObjPtrType>);
+
+ // In-place construction does not lead to empty.
+ EXPECT_TRUE(static_cast<bool>(fun));
+}
+
+TYPED_TEST_P(AnyInvTestBasic, InPlaceVoidCovarianceConstruction) {
+ using VoidAnyInvType = typename TypeParam::VoidAnyInvType;
+ using AddType = typename TypeParam::AddType;
+
+ VoidAnyInvType fun(absl::in_place_type<AddType>, 5);
+
+ EXPECT_TRUE(static_cast<bool>(fun));
+}
+
+TYPED_TEST_P(AnyInvTestBasic, MoveConstructionFromEmpty) {
+ using AnyInvType = typename TypeParam::AnyInvType;
+
+ AnyInvType source_fun;
+ AnyInvType fun(std::move(source_fun));
+
+ EXPECT_FALSE(static_cast<bool>(fun));
+
+ EXPECT_TRUE(std::is_nothrow_move_constructible<AnyInvType>::value);
+}
+
+TYPED_TEST_P(AnyInvTestBasic, MoveConstructionFromNonEmpty) {
+ using AnyInvType = typename TypeParam::AnyInvType;
+ using AddType = typename TypeParam::AddType;
+
+ AnyInvType source_fun(absl::in_place_type<AddType>, 5);
+ AnyInvType fun(std::move(source_fun));
+
+ EXPECT_TRUE(static_cast<bool>(fun));
+ EXPECT_EQ(29, TypeParam::ToThisParam(fun)(7, 8, 9).value);
+
+ EXPECT_TRUE(std::is_nothrow_move_constructible<AnyInvType>::value);
+}
+
+TYPED_TEST_P(AnyInvTestBasic, ComparisonWithNullptrEmpty) {
+ using AnyInvType = typename TypeParam::AnyInvType;
+
+ AnyInvType fun;
+
+ EXPECT_TRUE(fun == nullptr);
+ EXPECT_TRUE(nullptr == fun);
+
+ EXPECT_FALSE(fun != nullptr);
+ EXPECT_FALSE(nullptr != fun);
+}
+
+TYPED_TEST_P(AnyInvTestBasic, ComparisonWithNullptrNonempty) {
+ using AnyInvType = typename TypeParam::AnyInvType;
+ using AddType = typename TypeParam::AddType;
+
+ AnyInvType fun(absl::in_place_type<AddType>, 5);
+
+ EXPECT_FALSE(fun == nullptr);
+ EXPECT_FALSE(nullptr == fun);
+
+ EXPECT_TRUE(fun != nullptr);
+ EXPECT_TRUE(nullptr != fun);
+}
+
+TYPED_TEST_P(AnyInvTestBasic, ResultType) {
+ using AnyInvType = typename TypeParam::AnyInvType;
+ using ExpectedResultType = typename TypeParam::ResultType;
+
+ EXPECT_TRUE((std::is_same<typename AnyInvType::result_type,
+ ExpectedResultType>::value));
+}
+
+template <class T>
+class AnyInvTestCombinatoric : public ::testing::Test {};
+
+TYPED_TEST_SUITE_P(AnyInvTestCombinatoric);
+
+TYPED_TEST_P(AnyInvTestCombinatoric, MoveAssignEmptyEmptyLhsRhs) {
+ using AnyInvType = typename TypeParam::AnyInvType;
+
+ AnyInvType source_fun;
+ AnyInvType fun;
+
+ fun = std::move(source_fun);
+
+ EXPECT_FALSE(static_cast<bool>(fun));
+}
+
+TYPED_TEST_P(AnyInvTestCombinatoric, MoveAssignEmptyLhsNonemptyRhs) {
+ using AnyInvType = typename TypeParam::AnyInvType;
+ using AddType = typename TypeParam::AddType;
+
+ AnyInvType source_fun(absl::in_place_type<AddType>, 5);
+ AnyInvType fun;
+
+ fun = std::move(source_fun);
+
+ EXPECT_TRUE(static_cast<bool>(fun));
+ EXPECT_EQ(29, TypeParam::ToThisParam(fun)(7, 8, 9).value);
+}
+
+TYPED_TEST_P(AnyInvTestCombinatoric, MoveAssignNonemptyEmptyLhsRhs) {
+ using AnyInvType = typename TypeParam::AnyInvType;
+ using AddType = typename TypeParam::AddType;
+
+ AnyInvType source_fun;
+ AnyInvType fun(absl::in_place_type<AddType>, 5);
+
+ fun = std::move(source_fun);
+
+ EXPECT_FALSE(static_cast<bool>(fun));
+}
+
+TYPED_TEST_P(AnyInvTestCombinatoric, MoveAssignNonemptyLhsNonemptyRhs) {
+ using AnyInvType = typename TypeParam::AnyInvType;
+ using AddType = typename TypeParam::AddType;
+
+ AnyInvType source_fun(absl::in_place_type<AddType>, 5);
+ AnyInvType fun(absl::in_place_type<AddType>, 20);
+
+ fun = std::move(source_fun);
+
+ EXPECT_TRUE(static_cast<bool>(fun));
+ EXPECT_EQ(29, TypeParam::ToThisParam(fun)(7, 8, 9).value);
+}
+
+TYPED_TEST_P(AnyInvTestCombinatoric, SelfMoveAssignEmpty) {
+ using AnyInvType = typename TypeParam::AnyInvType;
+
+ AnyInvType source_fun;
+ source_fun = std::move(source_fun);
+
+ // This space intentionally left blank.
+}
+
+TYPED_TEST_P(AnyInvTestCombinatoric, SelfMoveAssignNonempty) {
+ using AnyInvType = typename TypeParam::AnyInvType;
+ using AddType = typename TypeParam::AddType;
+
+ AnyInvType source_fun(absl::in_place_type<AddType>, 5);
+ source_fun = std::move(source_fun);
+
+ // This space intentionally left blank.
+}
+
+TYPED_TEST_P(AnyInvTestCombinatoric, AssignNullptrEmptyLhs) {
+ using AnyInvType = typename TypeParam::AnyInvType;
+
+ AnyInvType fun;
+ fun = nullptr;
+
+ EXPECT_FALSE(static_cast<bool>(fun));
+}
+
+TYPED_TEST_P(AnyInvTestCombinatoric, AssignNullFunctionPtrEmptyLhs) {
+ using AnyInvType = typename TypeParam::AnyInvType;
+ using UnqualifiedFunType = typename TypeParam::UnqualifiedFunType;
+
+ UnqualifiedFunType* const null_fun_ptr = nullptr;
+ AnyInvType fun;
+ fun = null_fun_ptr;
+
+ EXPECT_FALSE(static_cast<bool>(fun));
+}
+
+TYPED_TEST_P(AnyInvTestCombinatoric, AssignNullMemberFunctionPtrEmptyLhs) {
+ using AnyInvType = typename TypeParam::AnyInvType;
+ using MemFunPtrType = typename TypeParam::MemFunPtrType;
+
+ const MemFunPtrType null_mem_fun_ptr = nullptr;
+ AnyInvType fun;
+ fun = null_mem_fun_ptr;
+
+ EXPECT_FALSE(static_cast<bool>(fun));
+}
+
+TYPED_TEST_P(AnyInvTestCombinatoric, AssignNullMemberObjectPtrEmptyLhs) {
+ using UnaryAnyInvType = typename TypeParam::UnaryAnyInvType;
+ using MemObjPtrType = typename TypeParam::MemObjPtrType;
+
+ const MemObjPtrType null_mem_obj_ptr = nullptr;
+ UnaryAnyInvType fun;
+ fun = null_mem_obj_ptr;
+
+ EXPECT_FALSE(static_cast<bool>(fun));
+}
+
+TYPED_TEST_P(AnyInvTestCombinatoric, AssignMemberFunctionPtrEmptyLhs) {
+ using AnyInvType = typename TypeParam::AnyInvType;
+
+ AnyInvType fun;
+ fun = &Int::MemberFunctionAdd;
+
+ EXPECT_TRUE(static_cast<bool>(fun));
+ EXPECT_EQ(24, TypeParam::ToThisParam(fun)(7, 8, 9).value);
+}
+
+TYPED_TEST_P(AnyInvTestCombinatoric, AssignMemberObjectPtrEmptyLhs) {
+ using UnaryAnyInvType = typename TypeParam::UnaryAnyInvType;
+
+ UnaryAnyInvType fun;
+ fun = &Int::value;
+
+ EXPECT_TRUE(static_cast<bool>(fun));
+ EXPECT_EQ(13, TypeParam::ToUnaryThisParam(fun)(13));
+}
+
+TYPED_TEST_P(AnyInvTestCombinatoric, AssignFunctionReferenceDecayEmptyLhs) {
+ using AnyInvType = typename TypeParam::AnyInvType;
+
+ AnyInvType fun;
+ fun = add_function;
+
+ EXPECT_TRUE(static_cast<bool>(fun));
+ EXPECT_EQ(24, TypeParam::ToThisParam(fun)(7, 8, 9).value);
+}
+
+TYPED_TEST_P(AnyInvTestCombinatoric,
+ AssignCompatibleAnyInvocableEmptyLhsEmptyRhs) {
+ using AnyInvType = typename TypeParam::AnyInvType;
+ using CompatibleAnyInvType = typename TypeParam::CompatibleAnyInvType;
+
+ CompatibleAnyInvType other;
+ AnyInvType fun;
+ fun = std::move(other);
+
+ EXPECT_FALSE(static_cast<bool>(other)); // NOLINT
+ EXPECT_EQ(other, nullptr); // NOLINT
+ EXPECT_EQ(nullptr, other); // NOLINT
+
+ EXPECT_FALSE(static_cast<bool>(fun));
+}
+
+TYPED_TEST_P(AnyInvTestCombinatoric,
+ AssignCompatibleAnyInvocableEmptyLhsNonemptyRhs) {
+ using AnyInvType = typename TypeParam::AnyInvType;
+ using CompatibleAnyInvType = typename TypeParam::CompatibleAnyInvType;
+
+ CompatibleAnyInvType other = &add_function;
+ AnyInvType fun;
+ fun = std::move(other);
+
+ EXPECT_FALSE(static_cast<bool>(other)); // NOLINT
+
+ EXPECT_TRUE(static_cast<bool>(fun));
+ EXPECT_EQ(24, TypeParam::ToThisParam(fun)(7, 8, 9).value);
+}
+
+TYPED_TEST_P(AnyInvTestCombinatoric, AssignNullptrNonemptyLhs) {
+ using AnyInvType = typename TypeParam::AnyInvType;
+
+ AnyInvType fun = &mult_function;
+ fun = nullptr;
+
+ EXPECT_FALSE(static_cast<bool>(fun));
+}
+
+TYPED_TEST_P(AnyInvTestCombinatoric, AssignNullFunctionPtrNonemptyLhs) {
+ using AnyInvType = typename TypeParam::AnyInvType;
+ using UnqualifiedFunType = typename TypeParam::UnqualifiedFunType;
+
+ UnqualifiedFunType* const null_fun_ptr = nullptr;
+ AnyInvType fun = &mult_function;
+ fun = null_fun_ptr;
+
+ EXPECT_FALSE(static_cast<bool>(fun));
+}
+
+TYPED_TEST_P(AnyInvTestCombinatoric, AssignNullMemberFunctionPtrNonemptyLhs) {
+ using AnyInvType = typename TypeParam::AnyInvType;
+ using MemFunPtrType = typename TypeParam::MemFunPtrType;
+
+ const MemFunPtrType null_mem_fun_ptr = nullptr;
+ AnyInvType fun = &mult_function;
+ fun = null_mem_fun_ptr;
+
+ EXPECT_FALSE(static_cast<bool>(fun));
+}
+
+TYPED_TEST_P(AnyInvTestCombinatoric, AssignNullMemberObjectPtrNonemptyLhs) {
+ using UnaryAnyInvType = typename TypeParam::UnaryAnyInvType;
+ using MemObjPtrType = typename TypeParam::MemObjPtrType;
+
+ const MemObjPtrType null_mem_obj_ptr = nullptr;
+ UnaryAnyInvType fun = &square_function;
+ fun = null_mem_obj_ptr;
+
+ EXPECT_FALSE(static_cast<bool>(fun));
+}
+
+TYPED_TEST_P(AnyInvTestCombinatoric, AssignMemberFunctionPtrNonemptyLhs) {
+ using AnyInvType = typename TypeParam::AnyInvType;
+
+ AnyInvType fun = &mult_function;
+ fun = &Int::MemberFunctionAdd;
+
+ EXPECT_TRUE(static_cast<bool>(fun));
+ EXPECT_EQ(24, TypeParam::ToThisParam(fun)(7, 8, 9).value);
+}
+
+TYPED_TEST_P(AnyInvTestCombinatoric, AssignMemberObjectPtrNonemptyLhs) {
+ using UnaryAnyInvType = typename TypeParam::UnaryAnyInvType;
+
+ UnaryAnyInvType fun = &square_function;
+ fun = &Int::value;
+
+ EXPECT_TRUE(static_cast<bool>(fun));
+ EXPECT_EQ(13, TypeParam::ToUnaryThisParam(fun)(13));
+}
+
+TYPED_TEST_P(AnyInvTestCombinatoric, AssignFunctionReferenceDecayNonemptyLhs) {
+ using AnyInvType = typename TypeParam::AnyInvType;
+
+ AnyInvType fun = &mult_function;
+ fun = add_function;
+
+ EXPECT_TRUE(static_cast<bool>(fun));
+ EXPECT_EQ(24, TypeParam::ToThisParam(fun)(7, 8, 9).value);
+}
+
+TYPED_TEST_P(AnyInvTestCombinatoric,
+ AssignCompatibleAnyInvocableNonemptyLhsEmptyRhs) {
+ using AnyInvType = typename TypeParam::AnyInvType;
+ using CompatibleAnyInvType = typename TypeParam::CompatibleAnyInvType;
+
+ CompatibleAnyInvType other;
+ AnyInvType fun = &mult_function;
+ fun = std::move(other);
+
+ EXPECT_FALSE(static_cast<bool>(other)); // NOLINT
+ EXPECT_EQ(other, nullptr); // NOLINT
+ EXPECT_EQ(nullptr, other); // NOLINT
+
+ EXPECT_FALSE(static_cast<bool>(fun));
+}
+
+TYPED_TEST_P(AnyInvTestCombinatoric,
+ AssignCompatibleAnyInvocableNonemptyLhsNonemptyRhs) {
+ using AnyInvType = typename TypeParam::AnyInvType;
+ using CompatibleAnyInvType = typename TypeParam::CompatibleAnyInvType;
+
+ CompatibleAnyInvType other = &add_function;
+ AnyInvType fun = &mult_function;
+ fun = std::move(other);
+
+ EXPECT_FALSE(static_cast<bool>(other)); // NOLINT
+
+ EXPECT_TRUE(static_cast<bool>(fun));
+ EXPECT_EQ(24, TypeParam::ToThisParam(fun)(7, 8, 9).value);
+}
+
+TYPED_TEST_P(AnyInvTestCombinatoric, SwapEmptyLhsEmptyRhs) {
+ using AnyInvType = typename TypeParam::AnyInvType;
+
+ // Swap idiom
+ {
+ AnyInvType fun;
+ AnyInvType other;
+
+ using std::swap;
+ swap(fun, other);
+
+ EXPECT_FALSE(static_cast<bool>(fun));
+ EXPECT_FALSE(static_cast<bool>(other));
+
+ EXPECT_TRUE(
+ absl::type_traits_internal::IsNothrowSwappable<AnyInvType>::value);
+ }
+
+ // Member swap
+ {
+ AnyInvType fun;
+ AnyInvType other;
+
+ fun.swap(other);
+
+ EXPECT_FALSE(static_cast<bool>(fun));
+ EXPECT_FALSE(static_cast<bool>(other));
+
+ EXPECT_TRUE(IsNothrowMemberSwappable<AnyInvType>::value);
+ }
+}
+
+TYPED_TEST_P(AnyInvTestCombinatoric, SwapEmptyLhsNonemptyRhs) {
+ using AnyInvType = typename TypeParam::AnyInvType;
+ using AddType = typename TypeParam::AddType;
+
+ // Swap idiom
+ {
+ AnyInvType fun;
+ AnyInvType other(absl::in_place_type<AddType>, 5);
+
+ using std::swap;
+ swap(fun, other);
+
+ EXPECT_TRUE(static_cast<bool>(fun));
+ EXPECT_FALSE(static_cast<bool>(other));
+
+ EXPECT_EQ(29, TypeParam::ToThisParam(fun)(7, 8, 9).value);
+
+ EXPECT_TRUE(
+ absl::type_traits_internal::IsNothrowSwappable<AnyInvType>::value);
+ }
+
+ // Member swap
+ {
+ AnyInvType fun;
+ AnyInvType other(absl::in_place_type<AddType>, 5);
+
+ fun.swap(other);
+
+ EXPECT_TRUE(static_cast<bool>(fun));
+ EXPECT_FALSE(static_cast<bool>(other));
+
+ EXPECT_EQ(29, TypeParam::ToThisParam(fun)(7, 8, 9).value);
+
+ EXPECT_TRUE(IsNothrowMemberSwappable<AnyInvType>::value);
+ }
+}
+
+TYPED_TEST_P(AnyInvTestCombinatoric, SwapNonemptyLhsEmptyRhs) {
+ using AnyInvType = typename TypeParam::AnyInvType;
+ using AddType = typename TypeParam::AddType;
+
+ // Swap idiom
+ {
+ AnyInvType fun(absl::in_place_type<AddType>, 5);
+ AnyInvType other;
+
+ using std::swap;
+ swap(fun, other);
+
+ EXPECT_FALSE(static_cast<bool>(fun));
+ EXPECT_TRUE(static_cast<bool>(other));
+
+ EXPECT_EQ(29, TypeParam::ToThisParam(other)(7, 8, 9).value);
+
+ EXPECT_TRUE(
+ absl::type_traits_internal::IsNothrowSwappable<AnyInvType>::value);
+ }
+
+ // Member swap
+ {
+ AnyInvType fun(absl::in_place_type<AddType>, 5);
+ AnyInvType other;
+
+ fun.swap(other);
+
+ EXPECT_FALSE(static_cast<bool>(fun));
+ EXPECT_TRUE(static_cast<bool>(other));
+
+ EXPECT_EQ(29, TypeParam::ToThisParam(other)(7, 8, 9).value);
+
+ EXPECT_TRUE(IsNothrowMemberSwappable<AnyInvType>::value);
+ }
+}
+
+TYPED_TEST_P(AnyInvTestCombinatoric, SwapNonemptyLhsNonemptyRhs) {
+ using AnyInvType = typename TypeParam::AnyInvType;
+ using AddType = typename TypeParam::AddType;
+
+ // Swap idiom
+ {
+ AnyInvType fun(absl::in_place_type<AddType>, 5);
+ AnyInvType other(absl::in_place_type<AddType>, 6);
+
+ using std::swap;
+ swap(fun, other);
+
+ EXPECT_TRUE(static_cast<bool>(fun));
+ EXPECT_TRUE(static_cast<bool>(other));
+
+ EXPECT_EQ(30, TypeParam::ToThisParam(fun)(7, 8, 9).value);
+ EXPECT_EQ(29, TypeParam::ToThisParam(other)(7, 8, 9).value);
+
+ EXPECT_TRUE(
+ absl::type_traits_internal::IsNothrowSwappable<AnyInvType>::value);
+ }
+
+ // Member swap
+ {
+ AnyInvType fun(absl::in_place_type<AddType>, 5);
+ AnyInvType other(absl::in_place_type<AddType>, 6);
+
+ fun.swap(other);
+
+ EXPECT_TRUE(static_cast<bool>(fun));
+ EXPECT_TRUE(static_cast<bool>(other));
+
+ EXPECT_EQ(30, TypeParam::ToThisParam(fun)(7, 8, 9).value);
+ EXPECT_EQ(29, TypeParam::ToThisParam(other)(7, 8, 9).value);
+
+ EXPECT_TRUE(IsNothrowMemberSwappable<AnyInvType>::value);
+ }
+}
+
+template <class T>
+class AnyInvTestMovable : public ::testing::Test {};
+
+TYPED_TEST_SUITE_P(AnyInvTestMovable);
+
+TYPED_TEST_P(AnyInvTestMovable, ConversionConstructionUserDefinedType) {
+ using AnyInvType = typename TypeParam::AnyInvType;
+ using AddType = typename TypeParam::AddType;
+
+ AnyInvType fun(AddType(5));
+
+ EXPECT_TRUE(static_cast<bool>(fun));
+ EXPECT_EQ(29, TypeParam::ToThisParam(fun)(7, 8, 9).value);
+}
+
+TYPED_TEST_P(AnyInvTestMovable, ConversionConstructionVoidCovariance) {
+ using VoidAnyInvType = typename TypeParam::VoidAnyInvType;
+ using AddType = typename TypeParam::AddType;
+
+ VoidAnyInvType fun(AddType(5));
+
+ EXPECT_TRUE(static_cast<bool>(fun));
+}
+
+TYPED_TEST_P(AnyInvTestMovable, ConversionAssignUserDefinedTypeEmptyLhs) {
+ using AnyInvType = typename TypeParam::AnyInvType;
+ using AddType = typename TypeParam::AddType;
+
+ AnyInvType fun;
+ fun = AddType(5);
+
+ EXPECT_TRUE(static_cast<bool>(fun));
+ EXPECT_EQ(29, TypeParam::ToThisParam(fun)(7, 8, 9).value);
+}
+
+TYPED_TEST_P(AnyInvTestMovable, ConversionAssignUserDefinedTypeNonemptyLhs) {
+ using AnyInvType = typename TypeParam::AnyInvType;
+ using AddType = typename TypeParam::AddType;
+
+ AnyInvType fun = &add_function;
+ fun = AddType(5);
+
+ EXPECT_TRUE(static_cast<bool>(fun));
+ EXPECT_EQ(29, TypeParam::ToThisParam(fun)(7, 8, 9).value);
+}
+
+TYPED_TEST_P(AnyInvTestMovable, ConversionAssignVoidCovariance) {
+ using VoidAnyInvType = typename TypeParam::VoidAnyInvType;
+ using AddType = typename TypeParam::AddType;
+
+ VoidAnyInvType fun;
+ fun = AddType(5);
+
+ EXPECT_TRUE(static_cast<bool>(fun));
+}
+
+template <class T>
+class AnyInvTestNoexceptFalse : public ::testing::Test {};
+
+TYPED_TEST_SUITE_P(AnyInvTestNoexceptFalse);
+
+TYPED_TEST_P(AnyInvTestNoexceptFalse, ConversionConstructionConstraints) {
+ using AnyInvType = typename TypeParam::AnyInvType;
+
+ EXPECT_TRUE((std::is_constructible<
+ AnyInvType,
+ typename TypeParam::AnyInvocableFunTypeNotNoexcept*>::value));
+ EXPECT_FALSE((
+ std::is_constructible<AnyInvType,
+ typename TypeParam::IncompatibleInvocable>::value));
+}
+
+TYPED_TEST_P(AnyInvTestNoexceptFalse, ConversionAssignConstraints) {
+ using AnyInvType = typename TypeParam::AnyInvType;
+
+ EXPECT_TRUE((std::is_assignable<
+ AnyInvType&,
+ typename TypeParam::AnyInvocableFunTypeNotNoexcept*>::value));
+ EXPECT_FALSE(
+ (std::is_assignable<AnyInvType&,
+ typename TypeParam::IncompatibleInvocable>::value));
+}
+
+template <class T>
+class AnyInvTestNoexceptTrue : public ::testing::Test {};
+
+TYPED_TEST_SUITE_P(AnyInvTestNoexceptTrue);
+
+TYPED_TEST_P(AnyInvTestNoexceptTrue, ConversionConstructionConstraints) {
+#if ABSL_INTERNAL_CPLUSPLUS_LANG < 201703L
+ GTEST_SKIP() << "Noexcept was not part of the type system before C++17.";
+#else
+ using AnyInvType = typename TypeParam::AnyInvType;
+
+ EXPECT_FALSE((std::is_constructible<
+ AnyInvType,
+ typename TypeParam::AnyInvocableFunTypeNotNoexcept*>::value));
+ EXPECT_FALSE((
+ std::is_constructible<AnyInvType,
+ typename TypeParam::IncompatibleInvocable>::value));
+#endif
+}
+
+TYPED_TEST_P(AnyInvTestNoexceptTrue, ConversionAssignConstraints) {
+#if ABSL_INTERNAL_CPLUSPLUS_LANG < 201703L
+ GTEST_SKIP() << "Noexcept was not part of the type system before C++17.";
+#else
+ using AnyInvType = typename TypeParam::AnyInvType;
+
+ EXPECT_FALSE((std::is_assignable<
+ AnyInvType&,
+ typename TypeParam::AnyInvocableFunTypeNotNoexcept*>::value));
+ EXPECT_FALSE(
+ (std::is_assignable<AnyInvType&,
+ typename TypeParam::IncompatibleInvocable>::value));
+#endif
+}
+
+template <class T>
+class AnyInvTestNonRvalue : public ::testing::Test {};
+
+TYPED_TEST_SUITE_P(AnyInvTestNonRvalue);
+
+TYPED_TEST_P(AnyInvTestNonRvalue, ConversionConstructionReferenceWrapper) {
+ using AnyInvType = typename TypeParam::AnyInvType;
+ using AddType = typename TypeParam::AddType;
+
+ AddType add(4);
+ AnyInvType fun = std::ref(add);
+ add.state = 5;
+
+ EXPECT_TRUE(static_cast<bool>(fun));
+ EXPECT_EQ(29, TypeParam::ToThisParam(fun)(7, 8, 9).value);
+
+ EXPECT_TRUE(static_cast<bool>(fun));
+ EXPECT_EQ(38, TypeParam::ToThisParam(fun)(10, 11, 12).value);
+}
+
+TYPED_TEST_P(AnyInvTestNonRvalue, NonMoveableResultType) {
+#if ABSL_INTERNAL_CPLUSPLUS_LANG < 201703L
+ GTEST_SKIP() << "Copy/move elision was not standard before C++17";
+#else
+ // Define a result type that cannot be copy- or move-constructed.
+ struct Result {
+ int x;
+
+ explicit Result(const int x_in) : x(x_in) {}
+ Result(Result&&) = delete;
+ };
+
+ static_assert(!std::is_move_constructible<Result>::value, "");
+ static_assert(!std::is_copy_constructible<Result>::value, "");
+
+ // Assumption check: it should nevertheless be possible to use functors that
+ // return a Result struct according to the language rules.
+ const auto return_17 = []() noexcept { return Result(17); };
+ EXPECT_EQ(17, return_17().x);
+
+ // Just like plain functors, it should work fine to use an AnyInvocable that
+ // returns the non-moveable type.
+ using UnqualifiedFun =
+ absl::conditional_t<TypeParam::kIsNoexcept, Result() noexcept, Result()>;
+
+ using Fun =
+ GiveQualifiersToFun<typename TypeParam::Qualifiers, UnqualifiedFun>;
+
+ AnyInvocable<Fun> any_inv(return_17);
+ EXPECT_EQ(17, any_inv().x);
+#endif
+}
+
+TYPED_TEST_P(AnyInvTestNonRvalue, ConversionAssignReferenceWrapperEmptyLhs) {
+ using AnyInvType = typename TypeParam::AnyInvType;
+ using AddType = typename TypeParam::AddType;
+
+ AddType add(4);
+ AnyInvType fun;
+ fun = std::ref(add);
+ add.state = 5;
+ EXPECT_TRUE(
+ (std::is_nothrow_assignable<AnyInvType&,
+ std::reference_wrapper<AddType>>::value));
+
+ EXPECT_TRUE(static_cast<bool>(fun));
+ EXPECT_EQ(29, TypeParam::ToThisParam(fun)(7, 8, 9).value);
+
+ EXPECT_TRUE(static_cast<bool>(fun));
+ EXPECT_EQ(38, TypeParam::ToThisParam(fun)(10, 11, 12).value);
+}
+
+TYPED_TEST_P(AnyInvTestNonRvalue, ConversionAssignReferenceWrapperNonemptyLhs) {
+ using AnyInvType = typename TypeParam::AnyInvType;
+ using AddType = typename TypeParam::AddType;
+
+ AddType add(4);
+ AnyInvType fun = &mult_function;
+ fun = std::ref(add);
+ add.state = 5;
+ EXPECT_TRUE(
+ (std::is_nothrow_assignable<AnyInvType&,
+ std::reference_wrapper<AddType>>::value));
+
+ EXPECT_TRUE(static_cast<bool>(fun));
+ EXPECT_EQ(29, TypeParam::ToThisParam(fun)(7, 8, 9).value);
+
+ EXPECT_TRUE(static_cast<bool>(fun));
+ EXPECT_EQ(38, TypeParam::ToThisParam(fun)(10, 11, 12).value);
+}
+
+template <class T>
+class AnyInvTestRvalue : public ::testing::Test {};
+
+TYPED_TEST_SUITE_P(AnyInvTestRvalue);
+
+TYPED_TEST_P(AnyInvTestRvalue, ConversionConstructionReferenceWrapper) {
+ using AnyInvType = typename TypeParam::AnyInvType;
+ using AddType = typename TypeParam::AddType;
+
+ EXPECT_FALSE((
+ std::is_convertible<std::reference_wrapper<AddType>, AnyInvType>::value));
+}
+
+TYPED_TEST_P(AnyInvTestRvalue, NonMoveableResultType) {
+#if ABSL_INTERNAL_CPLUSPLUS_LANG < 201703L
+ GTEST_SKIP() << "Copy/move elision was not standard before C++17";
+#else
+ // Define a result type that cannot be copy- or move-constructed.
+ struct Result {
+ int x;
+
+ explicit Result(const int x_in) : x(x_in) {}
+ Result(Result&&) = delete;
+ };
+
+ static_assert(!std::is_move_constructible<Result>::value, "");
+ static_assert(!std::is_copy_constructible<Result>::value, "");
+
+ // Assumption check: it should nevertheless be possible to use functors that
+ // return a Result struct according to the language rules.
+ const auto return_17 = []() noexcept { return Result(17); };
+ EXPECT_EQ(17, return_17().x);
+
+ // Just like plain functors, it should work fine to use an AnyInvocable that
+ // returns the non-moveable type.
+ using UnqualifiedFun =
+ absl::conditional_t<TypeParam::kIsNoexcept, Result() noexcept, Result()>;
+
+ using Fun =
+ GiveQualifiersToFun<typename TypeParam::Qualifiers, UnqualifiedFun>;
+
+ EXPECT_EQ(17, AnyInvocable<Fun>(return_17)().x);
+#endif
+}
+
+TYPED_TEST_P(AnyInvTestRvalue, ConversionAssignReferenceWrapper) {
+ using AnyInvType = typename TypeParam::AnyInvType;
+ using AddType = typename TypeParam::AddType;
+
+ EXPECT_FALSE((
+ std::is_assignable<AnyInvType&, std::reference_wrapper<AddType>>::value));
+}
+
+TYPED_TEST_P(AnyInvTestRvalue, NonConstCrashesOnSecondCall) {
+ using AnyInvType = typename TypeParam::AnyInvType;
+ using AddType = typename TypeParam::AddType;
+
+ AnyInvType fun(absl::in_place_type<AddType>, 5);
+
+ EXPECT_TRUE(static_cast<bool>(fun));
+ std::move(fun)(7, 8, 9);
+
+ // Ensure we're still valid
+ EXPECT_TRUE(static_cast<bool>(fun)); // NOLINT(bugprone-use-after-move)
+
+#if !defined(NDEBUG)
+ EXPECT_DEATH_IF_SUPPORTED(std::move(fun)(7, 8, 9), "");
+#endif
+}
+
+// Ensure that any qualifiers (in particular &&-qualifiers) do not affect
+// when the destructor is actually run.
+TYPED_TEST_P(AnyInvTestRvalue, QualifierIndependentObjectLifetime) {
+ using AnyInvType = typename TypeParam::AnyInvType;
+
+ auto refs = std::make_shared<std::nullptr_t>();
+ {
+ AnyInvType fun([refs](auto&&...) noexcept { return 0; });
+ EXPECT_GT(refs.use_count(), 1);
+
+ std::move(fun)(7, 8, 9);
+
+ // Ensure destructor hasn't run even if rref-qualified
+ EXPECT_GT(refs.use_count(), 1);
+ }
+ EXPECT_EQ(refs.use_count(), 1);
+}
+
+// NOTE: This test suite originally attempted to enumerate all possible
+// combinations of type properties but the build-time started getting too large.
+// Instead, it is now assumed that certain parameters are orthogonal and so
+// some combinations are elided.
+
+// A metafunction to form a TypeList of all cv and non-rvalue ref combinations,
+// coupled with all of the other explicitly specified parameters.
+template <Movable Mov, Destructible Dest, NothrowCall CallExceptionSpec,
+ ObjSize Size, ObjAlign Align>
+using NonRvalueQualifiedTestParams = ::testing::Types< //
+ TestParams<Mov, Dest, _, CallExceptionSpec, Size, Align>, //
+ TestParams<Mov, Dest, const _, CallExceptionSpec, Size, Align>, //
+ TestParams<Mov, Dest, _&, CallExceptionSpec, Size, Align>, //
+ TestParams<Mov, Dest, const _&, CallExceptionSpec, Size, Align>>;
+
+// A metafunction to form a TypeList of const and non-const rvalue ref
+// qualifiers, coupled with all of the other explicitly specified parameters.
+template <Movable Mov, Destructible Dest, NothrowCall CallExceptionSpec,
+ ObjSize Size, ObjAlign Align>
+using RvalueQualifiedTestParams = ::testing::Types<
+ TestParams<Mov, Dest, _&&, CallExceptionSpec, Size, Align>, //
+ TestParams<Mov, Dest, const _&&, CallExceptionSpec, Size, Align> //
+ >;
+
+// All qualifier combinations and a noexcept function type
+using TestParameterListNonRvalueQualifiersNothrowCall =
+ NonRvalueQualifiedTestParams<Movable::trivial, Destructible::trivial,
+ NothrowCall::yes, ObjSize::small,
+ ObjAlign::normal>;
+using TestParameterListRvalueQualifiersNothrowCall =
+ RvalueQualifiedTestParams<Movable::trivial, Destructible::trivial,
+ NothrowCall::yes, ObjSize::small,
+ ObjAlign::normal>;
+
+// All qualifier combinations and a non-noexcept function type
+using TestParameterListNonRvalueQualifiersCallMayThrow =
+ NonRvalueQualifiedTestParams<Movable::trivial, Destructible::trivial,
+ NothrowCall::no, ObjSize::small,
+ ObjAlign::normal>;
+using TestParameterListRvalueQualifiersCallMayThrow =
+ RvalueQualifiedTestParams<Movable::trivial, Destructible::trivial,
+ NothrowCall::no, ObjSize::small,
+ ObjAlign::normal>;
+
+// Lists of various cases that should lead to remote storage
+using TestParameterListRemoteMovable = ::testing::Types<
+ // "Normal" aligned types that are large and have trivial destructors
+ TestParams<Movable::trivial, Destructible::trivial, _, NothrowCall::no,
+ ObjSize::large, ObjAlign::normal>, //
+ TestParams<Movable::nothrow, Destructible::trivial, _, NothrowCall::no,
+ ObjSize::large, ObjAlign::normal>, //
+ TestParams<Movable::yes, Destructible::trivial, _, NothrowCall::no,
+ ObjSize::small, ObjAlign::normal>, //
+ TestParams<Movable::yes, Destructible::trivial, _, NothrowCall::no,
+ ObjSize::large, ObjAlign::normal>, //
+
+ // Same as above but with non-trivial destructors
+ TestParams<Movable::trivial, Destructible::nothrow, _, NothrowCall::no,
+ ObjSize::large, ObjAlign::normal>, //
+ TestParams<Movable::nothrow, Destructible::nothrow, _, NothrowCall::no,
+ ObjSize::large, ObjAlign::normal>, //
+ TestParams<Movable::yes, Destructible::nothrow, _, NothrowCall::no,
+ ObjSize::small, ObjAlign::normal>, //
+ TestParams<Movable::yes, Destructible::nothrow, _, NothrowCall::no,
+ ObjSize::large, ObjAlign::normal> //
+
+// Dynamic memory allocation for over-aligned data was introduced in C++17.
+// See https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0035r4.html
+#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 201703L
+ // Types that must use remote storage because of a large alignment.
+ ,
+ TestParams<Movable::trivial, Destructible::trivial, _, NothrowCall::no,
+ ObjSize::small, ObjAlign::large>, //
+ TestParams<Movable::nothrow, Destructible::trivial, _, NothrowCall::no,
+ ObjSize::small, ObjAlign::large>, //
+ TestParams<Movable::trivial, Destructible::nothrow, _, NothrowCall::no,
+ ObjSize::small, ObjAlign::large>, //
+ TestParams<Movable::nothrow, Destructible::nothrow, _, NothrowCall::no,
+ ObjSize::small, ObjAlign::large> //
+#endif
+ >;
+using TestParameterListRemoteNonMovable = ::testing::Types<
+ // "Normal" aligned types that are large and have trivial destructors
+ TestParams<Movable::no, Destructible::trivial, _, NothrowCall::no,
+ ObjSize::small, ObjAlign::normal>, //
+ TestParams<Movable::no, Destructible::trivial, _, NothrowCall::no,
+ ObjSize::large, ObjAlign::normal>, //
+ // Same as above but with non-trivial destructors
+ TestParams<Movable::no, Destructible::nothrow, _, NothrowCall::no,
+ ObjSize::small, ObjAlign::normal>, //
+ TestParams<Movable::no, Destructible::nothrow, _, NothrowCall::no,
+ ObjSize::large, ObjAlign::normal> //
+ >;
+
+// Parameters that lead to local storage
+using TestParameterListLocal = ::testing::Types<
+ // Types that meet the requirements and have trivial destructors
+ TestParams<Movable::trivial, Destructible::trivial, _, NothrowCall::no,
+ ObjSize::small, ObjAlign::normal>, //
+ TestParams<Movable::nothrow, Destructible::trivial, _, NothrowCall::no,
+ ObjSize::small, ObjAlign::normal>, //
+
+ // Same as above but with non-trivial destructors
+ TestParams<Movable::trivial, Destructible::trivial, _, NothrowCall::no,
+ ObjSize::small, ObjAlign::normal>, //
+ TestParams<Movable::nothrow, Destructible::trivial, _, NothrowCall::no,
+ ObjSize::small, ObjAlign::normal> //
+ >;
+
+// All of the tests that are run for every possible combination of types.
+REGISTER_TYPED_TEST_SUITE_P(
+ AnyInvTestBasic, DefaultConstruction, ConstructionNullptr,
+ ConstructionNullFunctionPtr, ConstructionNullMemberFunctionPtr,
+ ConstructionNullMemberObjectPtr, ConstructionMemberFunctionPtr,
+ ConstructionMemberObjectPtr, ConstructionFunctionReferenceDecay,
+ ConstructionCompatibleAnyInvocableEmpty,
+ ConstructionCompatibleAnyInvocableNonempty, InPlaceConstruction,
+ ConversionToBool, Invocation, InPlaceConstructionInitializerList,
+ InPlaceNullFunPtrConstruction, InPlaceNullFunPtrConstructionValueInit,
+ InPlaceNullMemFunPtrConstruction, InPlaceNullMemFunPtrConstructionValueInit,
+ InPlaceNullMemObjPtrConstruction, InPlaceNullMemObjPtrConstructionValueInit,
+ InPlaceVoidCovarianceConstruction, MoveConstructionFromEmpty,
+ MoveConstructionFromNonEmpty, ComparisonWithNullptrEmpty,
+ ComparisonWithNullptrNonempty, ResultType);
+
+INSTANTIATE_TYPED_TEST_SUITE_P(
+ NonRvalueCallMayThrow, AnyInvTestBasic,
+ TestParameterListNonRvalueQualifiersCallMayThrow);
+INSTANTIATE_TYPED_TEST_SUITE_P(RvalueCallMayThrow, AnyInvTestBasic,
+ TestParameterListRvalueQualifiersCallMayThrow);
+
+INSTANTIATE_TYPED_TEST_SUITE_P(RemoteMovable, AnyInvTestBasic,
+ TestParameterListRemoteMovable);
+INSTANTIATE_TYPED_TEST_SUITE_P(RemoteNonMovable, AnyInvTestBasic,
+ TestParameterListRemoteNonMovable);
+
+INSTANTIATE_TYPED_TEST_SUITE_P(Local, AnyInvTestBasic, TestParameterListLocal);
+
+INSTANTIATE_TYPED_TEST_SUITE_P(NonRvalueCallNothrow, AnyInvTestBasic,
+ TestParameterListNonRvalueQualifiersNothrowCall);
+INSTANTIATE_TYPED_TEST_SUITE_P(CallNothrowRvalue, AnyInvTestBasic,
+ TestParameterListRvalueQualifiersNothrowCall);
+
+// Tests for functions that take two operands.
+REGISTER_TYPED_TEST_SUITE_P(
+ AnyInvTestCombinatoric, MoveAssignEmptyEmptyLhsRhs,
+ MoveAssignEmptyLhsNonemptyRhs, MoveAssignNonemptyEmptyLhsRhs,
+ MoveAssignNonemptyLhsNonemptyRhs, SelfMoveAssignEmpty,
+ SelfMoveAssignNonempty, AssignNullptrEmptyLhs,
+ AssignNullFunctionPtrEmptyLhs, AssignNullMemberFunctionPtrEmptyLhs,
+ AssignNullMemberObjectPtrEmptyLhs, AssignMemberFunctionPtrEmptyLhs,
+ AssignMemberObjectPtrEmptyLhs, AssignFunctionReferenceDecayEmptyLhs,
+ AssignCompatibleAnyInvocableEmptyLhsEmptyRhs,
+ AssignCompatibleAnyInvocableEmptyLhsNonemptyRhs, AssignNullptrNonemptyLhs,
+ AssignNullFunctionPtrNonemptyLhs, AssignNullMemberFunctionPtrNonemptyLhs,
+ AssignNullMemberObjectPtrNonemptyLhs, AssignMemberFunctionPtrNonemptyLhs,
+ AssignMemberObjectPtrNonemptyLhs, AssignFunctionReferenceDecayNonemptyLhs,
+ AssignCompatibleAnyInvocableNonemptyLhsEmptyRhs,
+ AssignCompatibleAnyInvocableNonemptyLhsNonemptyRhs, SwapEmptyLhsEmptyRhs,
+ SwapEmptyLhsNonemptyRhs, SwapNonemptyLhsEmptyRhs,
+ SwapNonemptyLhsNonemptyRhs);
+
+INSTANTIATE_TYPED_TEST_SUITE_P(
+ NonRvalueCallMayThrow, AnyInvTestCombinatoric,
+ TestParameterListNonRvalueQualifiersCallMayThrow);
+INSTANTIATE_TYPED_TEST_SUITE_P(RvalueCallMayThrow, AnyInvTestCombinatoric,
+ TestParameterListRvalueQualifiersCallMayThrow);
+
+INSTANTIATE_TYPED_TEST_SUITE_P(RemoteMovable, AnyInvTestCombinatoric,
+ TestParameterListRemoteMovable);
+INSTANTIATE_TYPED_TEST_SUITE_P(RemoteNonMovable, AnyInvTestCombinatoric,
+ TestParameterListRemoteNonMovable);
+
+INSTANTIATE_TYPED_TEST_SUITE_P(Local, AnyInvTestCombinatoric,
+ TestParameterListLocal);
+
+INSTANTIATE_TYPED_TEST_SUITE_P(NonRvalueCallNothrow, AnyInvTestCombinatoric,
+ TestParameterListNonRvalueQualifiersNothrowCall);
+INSTANTIATE_TYPED_TEST_SUITE_P(RvalueCallNothrow, AnyInvTestCombinatoric,
+ TestParameterListRvalueQualifiersNothrowCall);
+
+REGISTER_TYPED_TEST_SUITE_P(AnyInvTestMovable,
+ ConversionConstructionUserDefinedType,
+ ConversionConstructionVoidCovariance,
+ ConversionAssignUserDefinedTypeEmptyLhs,
+ ConversionAssignUserDefinedTypeNonemptyLhs,
+ ConversionAssignVoidCovariance);
+
+INSTANTIATE_TYPED_TEST_SUITE_P(
+ NonRvalueCallMayThrow, AnyInvTestMovable,
+ TestParameterListNonRvalueQualifiersCallMayThrow);
+INSTANTIATE_TYPED_TEST_SUITE_P(RvalueCallMayThrow, AnyInvTestMovable,
+ TestParameterListRvalueQualifiersCallMayThrow);
+
+INSTANTIATE_TYPED_TEST_SUITE_P(RemoteMovable, AnyInvTestMovable,
+ TestParameterListRemoteMovable);
+
+INSTANTIATE_TYPED_TEST_SUITE_P(Local, AnyInvTestMovable,
+ TestParameterListLocal);
+
+INSTANTIATE_TYPED_TEST_SUITE_P(NonRvalueCallNothrow, AnyInvTestMovable,
+ TestParameterListNonRvalueQualifiersNothrowCall);
+INSTANTIATE_TYPED_TEST_SUITE_P(RvalueCallNothrow, AnyInvTestMovable,
+ TestParameterListRvalueQualifiersNothrowCall);
+
+REGISTER_TYPED_TEST_SUITE_P(AnyInvTestNoexceptFalse,
+ ConversionConstructionConstraints,
+ ConversionAssignConstraints);
+
+INSTANTIATE_TYPED_TEST_SUITE_P(
+ NonRvalueCallMayThrow, AnyInvTestNoexceptFalse,
+ TestParameterListNonRvalueQualifiersCallMayThrow);
+INSTANTIATE_TYPED_TEST_SUITE_P(RvalueCallMayThrow, AnyInvTestNoexceptFalse,
+ TestParameterListRvalueQualifiersCallMayThrow);
+
+INSTANTIATE_TYPED_TEST_SUITE_P(RemoteMovable, AnyInvTestNoexceptFalse,
+ TestParameterListRemoteMovable);
+INSTANTIATE_TYPED_TEST_SUITE_P(RemoteNonMovable, AnyInvTestNoexceptFalse,
+ TestParameterListRemoteNonMovable);
+
+INSTANTIATE_TYPED_TEST_SUITE_P(Local, AnyInvTestNoexceptFalse,
+ TestParameterListLocal);
+
+REGISTER_TYPED_TEST_SUITE_P(AnyInvTestNoexceptTrue,
+ ConversionConstructionConstraints,
+ ConversionAssignConstraints);
+
+INSTANTIATE_TYPED_TEST_SUITE_P(NonRvalueCallNothrow, AnyInvTestNoexceptTrue,
+ TestParameterListNonRvalueQualifiersNothrowCall);
+INSTANTIATE_TYPED_TEST_SUITE_P(RvalueCallNothrow, AnyInvTestNoexceptTrue,
+ TestParameterListRvalueQualifiersNothrowCall);
+
+REGISTER_TYPED_TEST_SUITE_P(AnyInvTestNonRvalue,
+ ConversionConstructionReferenceWrapper,
+ NonMoveableResultType,
+ ConversionAssignReferenceWrapperEmptyLhs,
+ ConversionAssignReferenceWrapperNonemptyLhs);
+
+INSTANTIATE_TYPED_TEST_SUITE_P(
+ NonRvalueCallMayThrow, AnyInvTestNonRvalue,
+ TestParameterListNonRvalueQualifiersCallMayThrow);
+
+INSTANTIATE_TYPED_TEST_SUITE_P(RemoteMovable, AnyInvTestNonRvalue,
+ TestParameterListRemoteMovable);
+INSTANTIATE_TYPED_TEST_SUITE_P(RemoteNonMovable, AnyInvTestNonRvalue,
+ TestParameterListRemoteNonMovable);
+
+INSTANTIATE_TYPED_TEST_SUITE_P(Local, AnyInvTestNonRvalue,
+ TestParameterListLocal);
+
+INSTANTIATE_TYPED_TEST_SUITE_P(NonRvalueCallNothrow, AnyInvTestNonRvalue,
+ TestParameterListNonRvalueQualifiersNothrowCall);
+
+REGISTER_TYPED_TEST_SUITE_P(AnyInvTestRvalue,
+ ConversionConstructionReferenceWrapper,
+ NonMoveableResultType,
+ ConversionAssignReferenceWrapper,
+ NonConstCrashesOnSecondCall,
+ QualifierIndependentObjectLifetime);
+
+INSTANTIATE_TYPED_TEST_SUITE_P(RvalueCallMayThrow, AnyInvTestRvalue,
+ TestParameterListRvalueQualifiersCallMayThrow);
+
+INSTANTIATE_TYPED_TEST_SUITE_P(CallNothrowRvalue, AnyInvTestRvalue,
+ TestParameterListRvalueQualifiersNothrowCall);
+
+// Minimal SFINAE testing for platforms where we can't run the tests, but we can
+// build binaries for.
+static_assert(
+ std::is_convertible<void (*)(), absl::AnyInvocable<void() &&>>::value, "");
+static_assert(!std::is_convertible<void*, absl::AnyInvocable<void() &&>>::value,
+ "");
+
+#undef ABSL_INTERNAL_NOEXCEPT_SPEC
+
+} // namespace
diff --git a/absl/functional/bind_front.h b/absl/functional/bind_front.h
index 5b47970..a956eb0 100644
--- a/absl/functional/bind_front.h
+++ b/absl/functional/bind_front.h
@@ -30,6 +30,10 @@
#ifndef ABSL_FUNCTIONAL_BIND_FRONT_H_
#define ABSL_FUNCTIONAL_BIND_FRONT_H_
+#if defined(__cpp_lib_bind_front) && __cpp_lib_bind_front >= 201907L
+#include <functional> // For std::bind_front.
+#endif // defined(__cpp_lib_bind_front) && __cpp_lib_bind_front >= 201907L
+
#include "absl/functional/internal/front_binder.h"
#include "absl/utility/utility.h"
@@ -42,11 +46,12 @@
//
// Like `std::bind()`, `absl::bind_front()` is implicitly convertible to
// `std::function`. In particular, it may be used as a simpler replacement for
-// `std::bind()` in most cases, as it does not require placeholders to be
+// `std::bind()` in most cases, as it does not require placeholders to be
// specified. More importantly, it provides more reliable correctness guarantees
// than `std::bind()`; while `std::bind()` will silently ignore passing more
// parameters than expected, for example, `absl::bind_front()` will report such
-// mis-uses as errors.
+// mis-uses as errors. In C++20, `absl::bind_front` is replaced by
+// `std::bind_front`.
//
// absl::bind_front(a...) can be seen as storing the results of
// std::make_tuple(a...).
@@ -170,6 +175,9 @@
// // Doesn't copy "hi".
// absl::bind_front(Print, absl::string_view(hi))("Chuk");
//
+#if defined(__cpp_lib_bind_front) && __cpp_lib_bind_front >= 201907L
+using std::bind_front;
+#else // defined(__cpp_lib_bind_front) && __cpp_lib_bind_front >= 201907L
template <class F, class... BoundArgs>
constexpr functional_internal::bind_front_t<F, BoundArgs...> bind_front(
F&& func, BoundArgs&&... args) {
@@ -177,6 +185,7 @@
absl::in_place, absl::forward<F>(func),
absl::forward<BoundArgs>(args)...);
}
+#endif // defined(__cpp_lib_bind_front) && __cpp_lib_bind_front >= 201907L
ABSL_NAMESPACE_END
} // namespace absl
diff --git a/absl/functional/function_ref.h b/absl/functional/function_ref.h
index 5790a65..96cece5 100644
--- a/absl/functional/function_ref.h
+++ b/absl/functional/function_ref.h
@@ -50,6 +50,7 @@
#include <functional>
#include <type_traits>
+#include "absl/base/attributes.h"
#include "absl/functional/internal/function_ref.h"
#include "absl/meta/type_traits.h"
@@ -65,10 +66,11 @@
// FunctionRef
//
-// An `absl::FunctionRef` is a lightweight wrapper to any invokable object with
+// An `absl::FunctionRef` is a lightweight wrapper to any invocable object with
// a compatible signature. Generally, an `absl::FunctionRef` should only be used
// as an argument type and should be preferred as an argument over a const
-// reference to a `std::function`.
+// reference to a `std::function`. `absl::FunctionRef` itself does not allocate,
+// although the wrapped invocable may.
//
// Example:
//
@@ -96,9 +98,10 @@
std::is_convertible<FR, R>::value>::type;
public:
- // Constructs a FunctionRef from any invokable type.
+ // Constructs a FunctionRef from any invocable type.
template <typename F, typename = EnableIfCompatible<const F&>>
- FunctionRef(const F& f) // NOLINT(runtime/explicit)
+ // NOLINTNEXTLINE(runtime/explicit)
+ FunctionRef(const F& f ABSL_ATTRIBUTE_LIFETIME_BOUND)
: invoker_(&absl::functional_internal::InvokeObject<F, R, Args...>) {
absl::functional_internal::AssertNonNull(f);
ptr_.obj = &f;
@@ -134,6 +137,14 @@
absl::functional_internal::Invoker<R, Args...> invoker_;
};
+// Allow const qualified function signatures. Since FunctionRef requires
+// constness anyway we can just make this a no-op.
+template <typename R, typename... Args>
+class FunctionRef<R(Args...) const> : public FunctionRef<R(Args...)> {
+ public:
+ using FunctionRef<R(Args...)>::FunctionRef;
+};
+
ABSL_NAMESPACE_END
} // namespace absl
diff --git a/absl/functional/function_ref_test.cc b/absl/functional/function_ref_test.cc
index 3aa5974..c021113 100644
--- a/absl/functional/function_ref_test.cc
+++ b/absl/functional/function_ref_test.cc
@@ -14,11 +14,13 @@
#include "absl/functional/function_ref.h"
+#include <functional>
#include <memory>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/container/internal/test_instance_tracker.h"
+#include "absl/functional/any_invocable.h"
#include "absl/memory/memory.h"
namespace absl {
@@ -45,6 +47,11 @@
EXPECT_EQ(1337, ref());
}
+TEST(FunctionRefTest, ConstFunction) {
+ FunctionRef<int() const> ref(Function);
+ EXPECT_EQ(1337, ref());
+}
+
int NoExceptFunction() noexcept { return 1337; }
// TODO(jdennett): Add a test for noexcept member functions.
@@ -156,6 +163,25 @@
EXPECT_DEBUG_DEATH({ FunctionRef<int(const S& s)> ref(mem_ptr); }, "");
}
+TEST(FunctionRef, NullStdFunctionAssertPasses) {
+ std::function<void()> function = []() {};
+ FunctionRef<void()> ref(function);
+}
+
+TEST(FunctionRef, NullStdFunctionAssertFails) {
+ std::function<void()> function = nullptr;
+ EXPECT_DEBUG_DEATH({ FunctionRef<void()> ref(function); }, "");
+}
+
+TEST(FunctionRef, NullAnyInvocableAssertPasses) {
+ AnyInvocable<void() const> invocable = []() {};
+ FunctionRef<void()> ref(invocable);
+}
+TEST(FunctionRef, NullAnyInvocableAssertFails) {
+ AnyInvocable<void() const> invocable = nullptr;
+ EXPECT_DEBUG_DEATH({ FunctionRef<void()> ref(invocable); }, "");
+}
+
#endif // GTEST_HAS_DEATH_TEST
TEST(FunctionRef, CopiesAndMovesPerPassByValue) {
@@ -236,7 +262,7 @@
"Reference types should be preserved");
// Make sure the address of an object received by reference is the same as the
- // addess of the object passed by the caller.
+ // address of the object passed by the caller.
{
LargeTrivial obj;
auto test = [&obj](LargeTrivial& input) { ASSERT_EQ(&input, &obj); };
@@ -252,6 +278,16 @@
}
}
+TEST(FunctionRef, ReferenceToIncompleteType) {
+ struct IncompleteType;
+ auto test = [](IncompleteType&) {};
+ absl::FunctionRef<void(IncompleteType&)> ref(test);
+
+ struct IncompleteType {};
+ IncompleteType obj;
+ ref(obj);
+}
+
} // namespace
ABSL_NAMESPACE_END
} // namespace absl
diff --git a/absl/functional/function_ref_benchmark.cc b/absl/functional/function_type_benchmark.cc
similarity index 78%
rename from absl/functional/function_ref_benchmark.cc
rename to absl/functional/function_type_benchmark.cc
index 045305b..03dc31d 100644
--- a/absl/functional/function_ref_benchmark.cc
+++ b/absl/functional/function_type_benchmark.cc
@@ -1,4 +1,4 @@
-// Copyright 2019 The Abseil Authors.
+// Copyright 2022 The Abseil Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -12,12 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#include "absl/functional/function_ref.h"
-
+#include <functional>
#include <memory>
+#include <string>
#include "benchmark/benchmark.h"
#include "absl/base/attributes.h"
+#include "absl/functional/any_invocable.h"
+#include "absl/functional/function_ref.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
@@ -61,6 +63,12 @@
}
BENCHMARK(BM_TrivialFunctionRef);
+void BM_TrivialAnyInvocable(benchmark::State& state) {
+ ConstructAndCallFunctionBenchmark<AnyInvocable<void()>>(state,
+ TrivialFunctor{});
+}
+BENCHMARK(BM_TrivialAnyInvocable);
+
void BM_LargeStdFunction(benchmark::State& state) {
ConstructAndCallFunctionBenchmark<std::function<void()>>(state,
LargeFunctor{});
@@ -72,6 +80,13 @@
}
BENCHMARK(BM_LargeFunctionRef);
+
+void BM_LargeAnyInvocable(benchmark::State& state) {
+ ConstructAndCallFunctionBenchmark<AnyInvocable<void()>>(state,
+ LargeFunctor{});
+}
+BENCHMARK(BM_LargeAnyInvocable);
+
void BM_FunPtrStdFunction(benchmark::State& state) {
ConstructAndCallFunctionBenchmark<std::function<void()>>(state, FreeFunction);
}
@@ -82,6 +97,11 @@
}
BENCHMARK(BM_FunPtrFunctionRef);
+void BM_FunPtrAnyInvocable(benchmark::State& state) {
+ ConstructAndCallFunctionBenchmark<AnyInvocable<void()>>(state, FreeFunction);
+}
+BENCHMARK(BM_FunPtrAnyInvocable);
+
// Doesn't include construction or copy overhead in the loop.
template <typename Function, typename Callable, typename... Args>
void CallFunctionBenchmark(benchmark::State& state, const Callable& c,
@@ -113,6 +133,12 @@
}
BENCHMARK(BM_TrivialArgsFunctionRef);
+void BM_TrivialArgsAnyInvocable(benchmark::State& state) {
+ CallFunctionBenchmark<AnyInvocable<void(int, int, int)>>(
+ state, FunctorWithTrivialArgs{}, 1, 2, 3);
+}
+BENCHMARK(BM_TrivialArgsAnyInvocable);
+
struct FunctorWithNonTrivialArgs {
void operator()(std::string a, std::string b, std::string c) const {
benchmark::DoNotOptimize(&a);
@@ -137,6 +163,14 @@
}
BENCHMARK(BM_NonTrivialArgsFunctionRef);
+void BM_NonTrivialArgsAnyInvocable(benchmark::State& state) {
+ std::string a, b, c;
+ CallFunctionBenchmark<
+ AnyInvocable<void(std::string, std::string, std::string)>>(
+ state, FunctorWithNonTrivialArgs{}, a, b, c);
+}
+BENCHMARK(BM_NonTrivialArgsAnyInvocable);
+
} // namespace
ABSL_NAMESPACE_END
} // namespace absl
diff --git a/absl/functional/internal/any_invocable.h b/absl/functional/internal/any_invocable.h
new file mode 100644
index 0000000..b04436d
--- /dev/null
+++ b/absl/functional/internal/any_invocable.h
@@ -0,0 +1,891 @@
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// Implementation details for `absl::AnyInvocable`
+
+#ifndef ABSL_FUNCTIONAL_INTERNAL_ANY_INVOCABLE_H_
+#define ABSL_FUNCTIONAL_INTERNAL_ANY_INVOCABLE_H_
+
+////////////////////////////////////////////////////////////////////////////////
+// //
+// This implementation of the proposed `any_invocable` uses an approach that //
+// chooses between local storage and remote storage for the contained target //
+// object based on the target object's size, alignment requirements, and //
+// whether or not it has a nothrow move constructor. Additional optimizations //
+// are performed when the object is a trivially copyable type [basic.types]. //
+// //
+// There are three datamembers per `AnyInvocable` instance //
+// //
+// 1) A union containing either //
+// - A pointer to the target object referred to via a void*, or //
+// - the target object, emplaced into a raw char buffer //
+// //
+// 2) A function pointer to a "manager" function operation that takes a //
+// discriminator and logically branches to either perform a move operation //
+// or destroy operation based on that discriminator. //
+// //
+// 3) A function pointer to an "invoker" function operation that invokes the //
+// target object, directly returning the result. //
+// //
+// When in the logically empty state, the manager function is an empty //
+// function and the invoker function is one that would be undefined-behavior //
+// to call. //
+// //
+// An additional optimization is performed when converting from one //
+// AnyInvocable to another where only the noexcept specification and/or the //
+// cv/ref qualifiers of the function type differ. In these cases, the //
+// conversion works by "moving the guts", similar to if they were the same //
+// exact type, as opposed to having to perform an additional layer of //
+// wrapping through remote storage. //
+// //
+////////////////////////////////////////////////////////////////////////////////
+
+// IWYU pragma: private, include "absl/functional/any_invocable.h"
+
+#include <cassert>
+#include <cstddef>
+#include <cstring>
+#include <exception>
+#include <functional>
+#include <initializer_list>
+#include <memory>
+#include <new>
+#include <type_traits>
+#include <utility>
+
+#include "absl/base/config.h"
+#include "absl/base/internal/invoke.h"
+#include "absl/base/macros.h"
+#include "absl/base/optimization.h"
+#include "absl/meta/type_traits.h"
+#include "absl/utility/utility.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+
+// Helper macro used to prevent spelling `noexcept` in language versions older
+// than C++17, where it is not part of the type system, in order to avoid
+// compilation failures and internal compiler errors.
+#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 201703L
+#define ABSL_INTERNAL_NOEXCEPT_SPEC(noex) noexcept(noex)
+#else
+#define ABSL_INTERNAL_NOEXCEPT_SPEC(noex)
+#endif
+
+// Defined in functional/any_invocable.h
+template <class Sig>
+class AnyInvocable;
+
+namespace internal_any_invocable {
+
+// Constants relating to the small-object-storage for AnyInvocable
+enum StorageProperty : std::size_t {
+ kAlignment = alignof(std::max_align_t), // The alignment of the storage
+ kStorageSize = sizeof(void*) * 2 // The size of the storage
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// A metafunction for checking if a type is an AnyInvocable instantiation.
+// This is used during conversion operations.
+template <class T>
+struct IsAnyInvocable : std::false_type {};
+
+template <class Sig>
+struct IsAnyInvocable<AnyInvocable<Sig>> : std::true_type {};
+//
+////////////////////////////////////////////////////////////////////////////////
+
+// A type trait that tells us whether or not a target function type should be
+// stored locally in the small object optimization storage
+template <class T>
+using IsStoredLocally = std::integral_constant<
+ bool, sizeof(T) <= kStorageSize && alignof(T) <= kAlignment &&
+ kAlignment % alignof(T) == 0 &&
+ std::is_nothrow_move_constructible<T>::value>;
+
+// An implementation of std::remove_cvref_t of C++20.
+template <class T>
+using RemoveCVRef =
+ typename std::remove_cv<typename std::remove_reference<T>::type>::type;
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// An implementation of the C++ standard INVOKE<R> pseudo-macro, operation is
+// equivalent to std::invoke except that it forces an implicit conversion to the
+// specified return type. If "R" is void, the function is executed and the
+// return value is simply ignored.
+template <class ReturnType, class F, class... P,
+ typename = absl::enable_if_t<std::is_void<ReturnType>::value>>
+void InvokeR(F&& f, P&&... args) {
+ absl::base_internal::invoke(std::forward<F>(f), std::forward<P>(args)...);
+}
+
+template <class ReturnType, class F, class... P,
+ absl::enable_if_t<!std::is_void<ReturnType>::value, int> = 0>
+ReturnType InvokeR(F&& f, P&&... args) {
+ // GCC 12 has a false-positive -Wmaybe-uninitialized warning here.
+#if ABSL_INTERNAL_HAVE_MIN_GNUC_VERSION(12, 0)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
+#endif
+ return absl::base_internal::invoke(std::forward<F>(f),
+ std::forward<P>(args)...);
+#if ABSL_INTERNAL_HAVE_MIN_GNUC_VERSION(12, 0)
+#pragma GCC diagnostic pop
+#endif
+}
+
+//
+////////////////////////////////////////////////////////////////////////////////
+
+////////////////////////////////////////////////////////////////////////////////
+///
+// A metafunction that takes a "T" corresponding to a parameter type of the
+// user's specified function type, and yields the parameter type to use for the
+// type-erased invoker. In order to prevent observable moves, this must be
+// either a reference or, if the type is trivial, the original parameter type
+// itself. Since the parameter type may be incomplete at the point that this
+// metafunction is used, we can only do this optimization for scalar types
+// rather than for any trivial type.
+template <typename T>
+T ForwardImpl(std::true_type);
+
+template <typename T>
+T&& ForwardImpl(std::false_type);
+
+// NOTE: We deliberately use an intermediate struct instead of a direct alias,
+// as a workaround for b/206991861 on MSVC versions < 1924.
+template <class T>
+struct ForwardedParameter {
+ using type = decltype((
+ ForwardImpl<T>)(std::integral_constant<bool,
+ std::is_scalar<T>::value>()));
+};
+
+template <class T>
+using ForwardedParameterType = typename ForwardedParameter<T>::type;
+//
+////////////////////////////////////////////////////////////////////////////////
+
+// A discriminator when calling the "manager" function that describes operation
+// type-erased operation should be invoked.
+//
+// "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 };
+
+// The portion of `AnyInvocable` state that contains either a pointer to the
+// target object or the object itself in local storage
+union TypeErasedState {
+ struct {
+ // A pointer to the type-erased object when remotely stored
+ void* target;
+ // The size of the object for `RemoteManagerTrivial`
+ std::size_t size;
+ } remote;
+
+ // Local-storage for the type-erased object when small and trivial enough
+ alignas(kAlignment) char storage[kStorageSize];
+};
+
+// A typed accessor for the object in `TypeErasedState` storage
+template <class T>
+T& ObjectInLocalStorage(TypeErasedState* const state) {
+ // We launder here because the storage may be reused with the same type.
+#if defined(__cpp_lib_launder) && __cpp_lib_launder >= 201606L
+ return *std::launder(reinterpret_cast<T*>(&state->storage));
+#elif ABSL_HAVE_BUILTIN(__builtin_launder)
+ return *__builtin_launder(reinterpret_cast<T*>(&state->storage));
+#else
+
+ // When `std::launder` or equivalent are not available, we rely on undefined
+ // behavior, which works as intended on Abseil's officially supported
+ // platforms as of Q2 2022.
+#if !defined(__clang__) && defined(__GNUC__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wstrict-aliasing"
+#endif
+ return *reinterpret_cast<T*>(&state->storage);
+#if !defined(__clang__) && defined(__GNUC__)
+#pragma GCC diagnostic pop
+#endif
+
+#endif
+}
+
+// The type for functions issuing lifetime-related operations: move and dispose
+// A pointer to such a function is contained in each `AnyInvocable` instance.
+// NOTE: When specifying `FunctionToCall::`dispose, the same state must be
+// passed as both "from" and "to".
+using ManagerType = void(FunctionToCall /*operation*/,
+ TypeErasedState* /*from*/, TypeErasedState* /*to*/)
+ ABSL_INTERNAL_NOEXCEPT_SPEC(true);
+
+// The type for functions issuing the actual invocation of the object
+// A pointer to such a function is contained in each AnyInvocable instance.
+template <bool SigIsNoexcept, class ReturnType, class... P>
+using InvokerType = ReturnType(TypeErasedState*, ForwardedParameterType<P>...)
+ ABSL_INTERNAL_NOEXCEPT_SPEC(SigIsNoexcept);
+
+// The manager that is used when AnyInvocable is empty
+inline void EmptyManager(FunctionToCall /*operation*/,
+ TypeErasedState* /*from*/,
+ TypeErasedState* /*to*/) noexcept {}
+
+// The manager that is used when a target function is in local storage and is
+// a trivially copyable type.
+inline void LocalManagerTrivial(FunctionToCall /*operation*/,
+ TypeErasedState* const from,
+ TypeErasedState* const to) noexcept {
+ // This single statement without branching handles both possible operations.
+ //
+ // For FunctionToCall::dispose, "from" and "to" point to the same state, and
+ // so this assignment logically would do nothing.
+ //
+ // Note: Correctness here relies on http://wg21.link/p0593, which has only
+ // become standard in C++20, though implementations do not break it in
+ // practice for earlier versions of C++.
+ //
+ // The correct way to do this without that paper is to first placement-new a
+ // default-constructed T in "to->storage" prior to the memmove, but doing so
+ // requires a different function to be created for each T that is stored
+ // locally, which can cause unnecessary bloat and be less cache friendly.
+ *to = *from;
+
+ // Note: Because the type is trivially copyable, the destructor does not need
+ // to be called ("trivially copyable" requires a trivial destructor).
+}
+
+// The manager that is used when a target function is in local storage and is
+// not a trivially copyable type.
+template <class T>
+void LocalManagerNontrivial(FunctionToCall operation,
+ TypeErasedState* const from,
+ TypeErasedState* const to) noexcept {
+ static_assert(IsStoredLocally<T>::value,
+ "Local storage must only be used for supported types.");
+ static_assert(!std::is_trivially_copyable<T>::value,
+ "Locally stored types must be trivially copyable.");
+
+ T& from_object = (ObjectInLocalStorage<T>)(from);
+
+ switch (operation) {
+ case FunctionToCall::relocate_from_to:
+ // NOTE: Requires that the left-hand operand is already empty.
+ ::new (static_cast<void*>(&to->storage)) T(std::move(from_object));
+ ABSL_FALLTHROUGH_INTENDED;
+ case FunctionToCall::dispose:
+ from_object.~T(); // Must not throw. // NOLINT
+ return;
+ }
+ ABSL_UNREACHABLE();
+}
+
+// The invoker that is used when a target function is in local storage
+// Note: QualTRef here is the target function type along with cv and reference
+// qualifiers that must be used when calling the function.
+template <bool SigIsNoexcept, class ReturnType, class QualTRef, class... P>
+ReturnType LocalInvoker(
+ TypeErasedState* const state,
+ ForwardedParameterType<P>... args) noexcept(SigIsNoexcept) {
+ using RawT = RemoveCVRef<QualTRef>;
+ static_assert(
+ IsStoredLocally<RawT>::value,
+ "Target object must be in local storage in order to be invoked from it.");
+
+ auto& f = (ObjectInLocalStorage<RawT>)(state);
+ return (InvokeR<ReturnType>)(static_cast<QualTRef>(f),
+ static_cast<ForwardedParameterType<P>>(args)...);
+}
+
+// The manager that is used when a target function is in remote storage and it
+// has a trivial destructor
+inline void RemoteManagerTrivial(FunctionToCall operation,
+ TypeErasedState* const from,
+ TypeErasedState* const to) noexcept {
+ switch (operation) {
+ case FunctionToCall::relocate_from_to:
+ // NOTE: Requires that the left-hand operand is already empty.
+ to->remote = from->remote;
+ return;
+ case FunctionToCall::dispose:
+#if defined(__cpp_sized_deallocation)
+ ::operator delete(from->remote.target, from->remote.size);
+#else // __cpp_sized_deallocation
+ ::operator delete(from->remote.target);
+#endif // __cpp_sized_deallocation
+ return;
+ }
+ ABSL_UNREACHABLE();
+}
+
+// The manager that is used when a target function is in remote storage and the
+// destructor of the type is not trivial
+template <class T>
+void RemoteManagerNontrivial(FunctionToCall operation,
+ TypeErasedState* const from,
+ TypeErasedState* const to) noexcept {
+ static_assert(!IsStoredLocally<T>::value,
+ "Remote storage must only be used for types that do not "
+ "qualify for local storage.");
+
+ switch (operation) {
+ case FunctionToCall::relocate_from_to:
+ // NOTE: Requires that the left-hand operand is already empty.
+ to->remote.target = from->remote.target;
+ return;
+ case FunctionToCall::dispose:
+ ::delete static_cast<T*>(from->remote.target); // Must not throw.
+ return;
+ }
+ ABSL_UNREACHABLE();
+}
+
+// The invoker that is used when a target function is in remote storage
+template <bool SigIsNoexcept, class ReturnType, class QualTRef, class... P>
+ReturnType RemoteInvoker(
+ TypeErasedState* const state,
+ ForwardedParameterType<P>... args) noexcept(SigIsNoexcept) {
+ using RawT = RemoveCVRef<QualTRef>;
+ static_assert(!IsStoredLocally<RawT>::value,
+ "Target object must be in remote storage in order to be "
+ "invoked from it.");
+
+ auto& f = *static_cast<RawT*>(state->remote.target);
+ return (InvokeR<ReturnType>)(static_cast<QualTRef>(f),
+ static_cast<ForwardedParameterType<P>>(args)...);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// A metafunction that checks if a type T is an instantiation of
+// absl::in_place_type_t (needed for constructor constraints of AnyInvocable).
+template <class T>
+struct IsInPlaceType : std::false_type {};
+
+template <class T>
+struct IsInPlaceType<absl::in_place_type_t<T>> : std::true_type {};
+//
+////////////////////////////////////////////////////////////////////////////////
+
+// A constructor name-tag used with CoreImpl (below) to request the
+// conversion-constructor. QualDecayedTRef is the decayed-type of the object to
+// wrap, along with the cv and reference qualifiers that must be applied when
+// performing an invocation of the wrapped object.
+template <class QualDecayedTRef>
+struct TypedConversionConstruct {};
+
+// A helper base class for all core operations of AnyInvocable. Most notably,
+// this class creates the function call operator and constraint-checkers so that
+// the top-level class does not have to be a series of partial specializations.
+//
+// Note: This definition exists (as opposed to being a declaration) so that if
+// the user of the top-level template accidentally passes a template argument
+// that is not a function type, they will get a static_assert in AnyInvocable's
+// class body rather than an error stating that Impl is not defined.
+template <class Sig>
+class Impl {}; // Note: This is partially-specialized later.
+
+// A std::unique_ptr deleter that deletes memory allocated via ::operator new.
+#if defined(__cpp_sized_deallocation)
+class TrivialDeleter {
+ public:
+ explicit TrivialDeleter(std::size_t size) : size_(size) {}
+
+ void operator()(void* target) const {
+ ::operator delete(target, size_);
+ }
+
+ private:
+ std::size_t size_;
+};
+#else // __cpp_sized_deallocation
+class TrivialDeleter {
+ public:
+ explicit TrivialDeleter(std::size_t) {}
+
+ void operator()(void* target) const { ::operator delete(target); }
+};
+#endif // __cpp_sized_deallocation
+
+template <bool SigIsNoexcept, class ReturnType, class... P>
+class CoreImpl;
+
+constexpr bool IsCompatibleConversion(void*, void*) { return false; }
+template <bool NoExceptSrc, bool NoExceptDest, class... T>
+constexpr bool IsCompatibleConversion(CoreImpl<NoExceptSrc, T...>*,
+ CoreImpl<NoExceptDest, T...>*) {
+ return !NoExceptDest || NoExceptSrc;
+}
+
+// A helper base class for all core operations of AnyInvocable that do not
+// depend on the cv/ref qualifiers of the function type.
+template <bool SigIsNoexcept, class ReturnType, class... P>
+class CoreImpl {
+ public:
+ using result_type = ReturnType;
+
+ CoreImpl() noexcept : manager_(EmptyManager), invoker_(nullptr) {}
+
+ enum class TargetType {
+ kPointer,
+ kCompatibleAnyInvocable,
+ kIncompatibleAnyInvocable,
+ kOther,
+ };
+
+ // Note: QualDecayedTRef here includes the cv-ref qualifiers associated with
+ // the invocation of the Invocable. The unqualified type is the target object
+ // type to be stored.
+ template <class QualDecayedTRef, class F>
+ explicit CoreImpl(TypedConversionConstruct<QualDecayedTRef>, F&& f) {
+ using DecayedT = RemoveCVRef<QualDecayedTRef>;
+
+ constexpr TargetType kTargetType =
+ (std::is_pointer<DecayedT>::value ||
+ std::is_member_pointer<DecayedT>::value)
+ ? TargetType::kPointer
+ : IsCompatibleAnyInvocable<DecayedT>::value
+ ? TargetType::kCompatibleAnyInvocable
+ : IsAnyInvocable<DecayedT>::value
+ ? TargetType::kIncompatibleAnyInvocable
+ : TargetType::kOther;
+ // NOTE: We only use integers instead of enums as template parameters in
+ // order to work around a bug on C++14 under MSVC 2017.
+ // See b/236131881.
+ Initialize<kTargetType, QualDecayedTRef>(std::forward<F>(f));
+ }
+
+ // Note: QualTRef here includes the cv-ref qualifiers associated with the
+ // invocation of the Invocable. The unqualified type is the target object
+ // type to be stored.
+ template <class QualTRef, class... Args>
+ explicit CoreImpl(absl::in_place_type_t<QualTRef>, Args&&... args) {
+ InitializeStorage<QualTRef>(std::forward<Args>(args)...);
+ }
+
+ CoreImpl(CoreImpl&& other) noexcept {
+ other.manager_(FunctionToCall::relocate_from_to, &other.state_, &state_);
+ manager_ = other.manager_;
+ invoker_ = other.invoker_;
+ other.manager_ = EmptyManager;
+ other.invoker_ = nullptr;
+ }
+
+ CoreImpl& operator=(CoreImpl&& other) noexcept {
+ // Put the left-hand operand in an empty state.
+ //
+ // Note: A full reset that leaves us with an object that has its invariants
+ // intact is necessary in order to handle self-move. This is required by
+ // types that are used with certain operations of the standard library, such
+ // as the default definition of std::swap when both operands target the same
+ // object.
+ Clear();
+
+ // Perform the actual move/destroy operation on the target function.
+ other.manager_(FunctionToCall::relocate_from_to, &other.state_, &state_);
+ manager_ = other.manager_;
+ invoker_ = other.invoker_;
+ other.manager_ = EmptyManager;
+ other.invoker_ = nullptr;
+
+ return *this;
+ }
+
+ ~CoreImpl() { manager_(FunctionToCall::dispose, &state_, &state_); }
+
+ // Check whether or not the AnyInvocable is in the empty state.
+ bool HasValue() const { return invoker_ != nullptr; }
+
+ // Effects: Puts the object into its empty state.
+ void Clear() {
+ manager_(FunctionToCall::dispose, &state_, &state_);
+ manager_ = EmptyManager;
+ invoker_ = nullptr;
+ }
+
+ template <TargetType target_type, class QualDecayedTRef, class F,
+ absl::enable_if_t<target_type == TargetType::kPointer, int> = 0>
+ void Initialize(F&& f) {
+// This condition handles types that decay into pointers, which includes
+// function references. Since function references cannot be null, GCC warns
+// against comparing their decayed form with nullptr.
+// Since this is template-heavy code, we prefer to disable these warnings
+// locally instead of adding yet another overload of this function.
+#if !defined(__clang__) && defined(__GNUC__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wpragmas"
+#pragma GCC diagnostic ignored "-Waddress"
+#pragma GCC diagnostic ignored "-Wnonnull-compare"
+#endif
+ if (static_cast<RemoveCVRef<QualDecayedTRef>>(f) == nullptr) {
+#if !defined(__clang__) && defined(__GNUC__)
+#pragma GCC diagnostic pop
+#endif
+ manager_ = EmptyManager;
+ invoker_ = nullptr;
+ return;
+ }
+ InitializeStorage<QualDecayedTRef>(std::forward<F>(f));
+ }
+
+ template <TargetType target_type, class QualDecayedTRef, class F,
+ absl::enable_if_t<
+ target_type == TargetType::kCompatibleAnyInvocable, int> = 0>
+ void Initialize(F&& f) {
+ // In this case we can "steal the guts" of the other AnyInvocable.
+ f.manager_(FunctionToCall::relocate_from_to, &f.state_, &state_);
+ manager_ = f.manager_;
+ invoker_ = f.invoker_;
+
+ f.manager_ = EmptyManager;
+ f.invoker_ = nullptr;
+ }
+
+ template <TargetType target_type, class QualDecayedTRef, class F,
+ absl::enable_if_t<
+ target_type == TargetType::kIncompatibleAnyInvocable, int> = 0>
+ void Initialize(F&& f) {
+ if (f.HasValue()) {
+ InitializeStorage<QualDecayedTRef>(std::forward<F>(f));
+ } else {
+ manager_ = EmptyManager;
+ invoker_ = nullptr;
+ }
+ }
+
+ template <TargetType target_type, class QualDecayedTRef, class F,
+ typename = absl::enable_if_t<target_type == TargetType::kOther>>
+ void Initialize(F&& f) {
+ InitializeStorage<QualDecayedTRef>(std::forward<F>(f));
+ }
+
+ // Use local (inline) storage for applicable target object types.
+ template <class QualTRef, class... Args,
+ typename = absl::enable_if_t<
+ IsStoredLocally<RemoveCVRef<QualTRef>>::value>>
+ void InitializeStorage(Args&&... args) {
+ using RawT = RemoveCVRef<QualTRef>;
+ ::new (static_cast<void*>(&state_.storage))
+ RawT(std::forward<Args>(args)...);
+
+ invoker_ = LocalInvoker<SigIsNoexcept, ReturnType, QualTRef, P...>;
+ // We can simplify our manager if we know the type is trivially copyable.
+ InitializeLocalManager<RawT>();
+ }
+
+ // Use remote storage for target objects that cannot be stored locally.
+ template <class QualTRef, class... Args,
+ absl::enable_if_t<!IsStoredLocally<RemoveCVRef<QualTRef>>::value,
+ int> = 0>
+ void InitializeStorage(Args&&... args) {
+ InitializeRemoteManager<RemoveCVRef<QualTRef>>(std::forward<Args>(args)...);
+ // This is set after everything else in case an exception is thrown in an
+ // earlier step of the initialization.
+ invoker_ = RemoteInvoker<SigIsNoexcept, ReturnType, QualTRef, P...>;
+ }
+
+ template <class T,
+ typename = absl::enable_if_t<std::is_trivially_copyable<T>::value>>
+ void InitializeLocalManager() {
+ manager_ = LocalManagerTrivial;
+ }
+
+ template <class T,
+ absl::enable_if_t<!std::is_trivially_copyable<T>::value, int> = 0>
+ void InitializeLocalManager() {
+ manager_ = LocalManagerNontrivial<T>;
+ }
+
+ template <class T>
+ using HasTrivialRemoteStorage =
+ std::integral_constant<bool, std::is_trivially_destructible<T>::value &&
+ alignof(T) <=
+ ABSL_INTERNAL_DEFAULT_NEW_ALIGNMENT>;
+
+ template <class T, class... Args,
+ typename = absl::enable_if_t<HasTrivialRemoteStorage<T>::value>>
+ void InitializeRemoteManager(Args&&... args) {
+ // unique_ptr is used for exception-safety in case construction throws.
+ std::unique_ptr<void, TrivialDeleter> uninitialized_target(
+ ::operator new(sizeof(T)), TrivialDeleter(sizeof(T)));
+ ::new (uninitialized_target.get()) T(std::forward<Args>(args)...);
+ state_.remote.target = uninitialized_target.release();
+ state_.remote.size = sizeof(T);
+ manager_ = RemoteManagerTrivial;
+ }
+
+ template <class T, class... Args,
+ absl::enable_if_t<!HasTrivialRemoteStorage<T>::value, int> = 0>
+ void InitializeRemoteManager(Args&&... args) {
+ state_.remote.target = ::new T(std::forward<Args>(args)...);
+ manager_ = RemoteManagerNontrivial<T>;
+ }
+
+ //////////////////////////////////////////////////////////////////////////////
+ //
+ // Type trait to determine if the template argument is an AnyInvocable whose
+ // function type is compatible enough with ours such that we can
+ // "move the guts" out of it when moving, rather than having to place a new
+ // object into remote storage.
+
+ template <typename Other>
+ struct IsCompatibleAnyInvocable {
+ static constexpr bool value = false;
+ };
+
+ template <typename Sig>
+ struct IsCompatibleAnyInvocable<AnyInvocable<Sig>> {
+ static constexpr bool value =
+ (IsCompatibleConversion)(static_cast<
+ typename AnyInvocable<Sig>::CoreImpl*>(
+ nullptr),
+ static_cast<CoreImpl*>(nullptr));
+ };
+
+ //
+ //////////////////////////////////////////////////////////////////////////////
+
+ TypeErasedState state_;
+ ManagerType* manager_;
+ InvokerType<SigIsNoexcept, ReturnType, P...>* invoker_;
+};
+
+// A constructor name-tag used with Impl to request the
+// conversion-constructor
+struct ConversionConstruct {};
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// A metafunction that is normally an identity metafunction except that when
+// given a std::reference_wrapper<T>, it yields T&. This is necessary because
+// currently std::reference_wrapper's operator() is not conditionally noexcept,
+// so when checking if such an Invocable is nothrow-invocable, we must pull out
+// the underlying type.
+template <class T>
+struct UnwrapStdReferenceWrapperImpl {
+ using type = T;
+};
+
+template <class T>
+struct UnwrapStdReferenceWrapperImpl<std::reference_wrapper<T>> {
+ using type = T&;
+};
+
+template <class T>
+using UnwrapStdReferenceWrapper =
+ typename UnwrapStdReferenceWrapperImpl<T>::type;
+//
+////////////////////////////////////////////////////////////////////////////////
+
+// An alias that always yields std::true_type (used with constraints) where
+// substitution failures happen when forming the template arguments.
+template <class... T>
+using TrueAlias =
+ std::integral_constant<bool, sizeof(absl::void_t<T...>*) != 0>;
+
+/*SFINAE constraints for the conversion-constructor.*/
+template <class Sig, class F,
+ class = absl::enable_if_t<
+ !std::is_same<RemoveCVRef<F>, AnyInvocable<Sig>>::value>>
+using CanConvert = TrueAlias<
+ absl::enable_if_t<!IsInPlaceType<RemoveCVRef<F>>::value>,
+ absl::enable_if_t<Impl<Sig>::template CallIsValid<F>::value>,
+ absl::enable_if_t<
+ Impl<Sig>::template CallIsNoexceptIfSigIsNoexcept<F>::value>,
+ absl::enable_if_t<std::is_constructible<absl::decay_t<F>, F>::value>>;
+
+/*SFINAE constraints for the std::in_place constructors.*/
+template <class Sig, class F, class... Args>
+using CanEmplace = TrueAlias<
+ absl::enable_if_t<Impl<Sig>::template CallIsValid<F>::value>,
+ absl::enable_if_t<
+ Impl<Sig>::template CallIsNoexceptIfSigIsNoexcept<F>::value>,
+ absl::enable_if_t<std::is_constructible<absl::decay_t<F>, Args...>::value>>;
+
+/*SFINAE constraints for the conversion-assign operator.*/
+template <class Sig, class F,
+ class = absl::enable_if_t<
+ !std::is_same<RemoveCVRef<F>, AnyInvocable<Sig>>::value>>
+using CanAssign = TrueAlias<
+ absl::enable_if_t<Impl<Sig>::template CallIsValid<F>::value>,
+ absl::enable_if_t<
+ Impl<Sig>::template CallIsNoexceptIfSigIsNoexcept<F>::value>,
+ absl::enable_if_t<std::is_constructible<absl::decay_t<F>, F>::value>>;
+
+/*SFINAE constraints for the reference-wrapper conversion-assign operator.*/
+template <class Sig, class F>
+using CanAssignReferenceWrapper = TrueAlias<
+ absl::enable_if_t<
+ Impl<Sig>::template CallIsValid<std::reference_wrapper<F>>::value>,
+ absl::enable_if_t<Impl<Sig>::template CallIsNoexceptIfSigIsNoexcept<
+ std::reference_wrapper<F>>::value>>;
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// The constraint for checking whether or not a call meets the noexcept
+// callability requirements. This is a preprocessor macro because specifying it
+// this way as opposed to a disjunction/branch can improve the user-side error
+// messages and avoids an instantiation of std::is_nothrow_invocable_r in the
+// cases where the user did not specify a noexcept function type.
+//
+#define ABSL_INTERNAL_ANY_INVOCABLE_NOEXCEPT_CONSTRAINT(inv_quals, noex) \
+ ABSL_INTERNAL_ANY_INVOCABLE_NOEXCEPT_CONSTRAINT_##noex(inv_quals)
+
+// The disjunction below is because we can't rely on std::is_nothrow_invocable_r
+// to give the right result when ReturnType is non-moveable in toolchains that
+// don't treat non-moveable result types correctly. For example this was the
+// case in libc++ before commit c3a24882 (2022-05).
+#define ABSL_INTERNAL_ANY_INVOCABLE_NOEXCEPT_CONSTRAINT_true(inv_quals) \
+ absl::enable_if_t<absl::disjunction< \
+ std::is_nothrow_invocable_r< \
+ ReturnType, UnwrapStdReferenceWrapper<absl::decay_t<F>> inv_quals, \
+ P...>, \
+ std::conjunction< \
+ std::is_nothrow_invocable< \
+ UnwrapStdReferenceWrapper<absl::decay_t<F>> inv_quals, P...>, \
+ std::is_same< \
+ ReturnType, \
+ absl::base_internal::invoke_result_t< \
+ UnwrapStdReferenceWrapper<absl::decay_t<F>> inv_quals, \
+ P...>>>>::value>
+
+#define ABSL_INTERNAL_ANY_INVOCABLE_NOEXCEPT_CONSTRAINT_false(inv_quals)
+//
+////////////////////////////////////////////////////////////////////////////////
+
+// A macro to generate partial specializations of Impl with the different
+// combinations of supported cv/reference qualifiers and noexcept specifier.
+//
+// Here, `cv` are the cv-qualifiers if any, `ref` is the ref-qualifier if any,
+// inv_quals is the reference type to be used when invoking the target, and
+// noex is "true" if the function type is noexcept, or false if it is not.
+//
+// The CallIsValid condition is more complicated than simply using
+// absl::base_internal::is_invocable_r because we can't rely on it to give the
+// right result when ReturnType is non-moveable in toolchains that don't treat
+// non-moveable result types correctly. For example this was the case in libc++
+// before commit c3a24882 (2022-05).
+#define ABSL_INTERNAL_ANY_INVOCABLE_IMPL_(cv, ref, inv_quals, noex) \
+ template <class ReturnType, class... P> \
+ class Impl<ReturnType(P...) cv ref ABSL_INTERNAL_NOEXCEPT_SPEC(noex)> \
+ : public CoreImpl<noex, ReturnType, P...> { \
+ public: \
+ /*The base class, which contains the datamembers and core operations*/ \
+ using Core = CoreImpl<noex, ReturnType, P...>; \
+ \
+ /*SFINAE constraint to check if F is invocable with the proper signature*/ \
+ template <class F> \
+ using CallIsValid = TrueAlias<absl::enable_if_t<absl::disjunction< \
+ absl::base_internal::is_invocable_r<ReturnType, \
+ absl::decay_t<F> inv_quals, P...>, \
+ std::is_same<ReturnType, \
+ absl::base_internal::invoke_result_t< \
+ absl::decay_t<F> inv_quals, P...>>>::value>>; \
+ \
+ /*SFINAE constraint to check if F is nothrow-invocable when necessary*/ \
+ template <class F> \
+ using CallIsNoexceptIfSigIsNoexcept = \
+ TrueAlias<ABSL_INTERNAL_ANY_INVOCABLE_NOEXCEPT_CONSTRAINT(inv_quals, \
+ noex)>; \
+ \
+ /*Put the AnyInvocable into an empty state.*/ \
+ Impl() = default; \
+ \
+ /*The implementation of a conversion-constructor from "f*/ \
+ /*This forwards to Core, attaching inv_quals so that the base class*/ \
+ /*knows how to properly type-erase the invocation.*/ \
+ template <class F> \
+ explicit Impl(ConversionConstruct, F&& f) \
+ : Core(TypedConversionConstruct< \
+ typename std::decay<F>::type inv_quals>(), \
+ std::forward<F>(f)) {} \
+ \
+ /*Forward along the in-place construction parameters.*/ \
+ template <class T, class... Args> \
+ explicit Impl(absl::in_place_type_t<T>, Args&&... args) \
+ : Core(absl::in_place_type<absl::decay_t<T> inv_quals>, \
+ std::forward<Args>(args)...) {} \
+ \
+ /*Raises a fatal error when the AnyInvocable is invoked after a move*/ \
+ static ReturnType InvokedAfterMove( \
+ TypeErasedState*, \
+ ForwardedParameterType<P>...) noexcept(noex) { \
+ ABSL_HARDENING_ASSERT(false && "AnyInvocable use-after-move"); \
+ std::terminate(); \
+ } \
+ \
+ InvokerType<noex, ReturnType, P...>* ExtractInvoker() cv { \
+ using QualifiedTestType = int cv ref; \
+ auto* invoker = this->invoker_; \
+ if (!std::is_const<QualifiedTestType>::value && \
+ std::is_rvalue_reference<QualifiedTestType>::value) { \
+ ABSL_ASSERT([this]() { \
+ /* We checked that this isn't const above, so const_cast is safe */ \
+ const_cast<Impl*>(this)->invoker_ = InvokedAfterMove; \
+ return this->HasValue(); \
+ }()); \
+ } \
+ return invoker; \
+ } \
+ \
+ /*The actual invocation operation with the proper signature*/ \
+ ReturnType operator()(P... args) cv ref noexcept(noex) { \
+ assert(this->invoker_ != nullptr); \
+ return this->ExtractInvoker()( \
+ const_cast<TypeErasedState*>(&this->state_), \
+ static_cast<ForwardedParameterType<P>>(args)...); \
+ } \
+ }
+
+// Define the `noexcept(true)` specialization only for C++17 and beyond, when
+// `noexcept` is part of the type system.
+#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 201703L
+// A convenience macro that defines specializations for the noexcept(true) and
+// noexcept(false) forms, given the other properties.
+#define ABSL_INTERNAL_ANY_INVOCABLE_IMPL(cv, ref, inv_quals) \
+ ABSL_INTERNAL_ANY_INVOCABLE_IMPL_(cv, ref, inv_quals, false); \
+ ABSL_INTERNAL_ANY_INVOCABLE_IMPL_(cv, ref, inv_quals, true)
+#else
+#define ABSL_INTERNAL_ANY_INVOCABLE_IMPL(cv, ref, inv_quals) \
+ ABSL_INTERNAL_ANY_INVOCABLE_IMPL_(cv, ref, inv_quals, false)
+#endif
+
+// Non-ref-qualified partial specializations
+ABSL_INTERNAL_ANY_INVOCABLE_IMPL(, , &);
+ABSL_INTERNAL_ANY_INVOCABLE_IMPL(const, , const&);
+
+// Lvalue-ref-qualified partial specializations
+ABSL_INTERNAL_ANY_INVOCABLE_IMPL(, &, &);
+ABSL_INTERNAL_ANY_INVOCABLE_IMPL(const, &, const&);
+
+// Rvalue-ref-qualified partial specializations
+ABSL_INTERNAL_ANY_INVOCABLE_IMPL(, &&, &&);
+ABSL_INTERNAL_ANY_INVOCABLE_IMPL(const, &&, const&&);
+
+// Undef the detail-only macros.
+#undef ABSL_INTERNAL_ANY_INVOCABLE_IMPL
+#undef ABSL_INTERNAL_ANY_INVOCABLE_IMPL_
+#undef ABSL_INTERNAL_ANY_INVOCABLE_NOEXCEPT_CONSTRAINT_false
+#undef ABSL_INTERNAL_ANY_INVOCABLE_NOEXCEPT_CONSTRAINT_true
+#undef ABSL_INTERNAL_ANY_INVOCABLE_NOEXCEPT_CONSTRAINT
+#undef ABSL_INTERNAL_NOEXCEPT_SPEC
+
+} // namespace internal_any_invocable
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_FUNCTIONAL_INTERNAL_ANY_INVOCABLE_H_
diff --git a/absl/functional/internal/function_ref.h b/absl/functional/internal/function_ref.h
index b5bb8b4..1cd34a3 100644
--- a/absl/functional/internal/function_ref.h
+++ b/absl/functional/internal/function_ref.h
@@ -20,6 +20,7 @@
#include <type_traits>
#include "absl/base/internal/invoke.h"
+#include "absl/functional/any_invocable.h"
#include "absl/meta/type_traits.h"
namespace absl {
@@ -40,18 +41,21 @@
// Chooses the best type for passing T as an argument.
// Attempt to be close to SystemV AMD64 ABI. Objects with trivial copy ctor are
// passed by value.
-template <typename T>
-constexpr bool PassByValue() {
- return !std::is_lvalue_reference<T>::value &&
- absl::is_trivially_copy_constructible<T>::value &&
- absl::is_trivially_copy_assignable<
- typename std::remove_cv<T>::type>::value &&
- std::is_trivially_destructible<T>::value &&
- sizeof(T) <= 2 * sizeof(void*);
-}
+template <typename T,
+ bool IsLValueReference = std::is_lvalue_reference<T>::value>
+struct PassByValue : std::false_type {};
template <typename T>
-struct ForwardT : std::conditional<PassByValue<T>(), T, T&&> {};
+struct PassByValue<T, /*IsLValueReference=*/false>
+ : std::integral_constant<bool,
+ absl::is_trivially_copy_constructible<T>::value &&
+ absl::is_trivially_copy_assignable<
+ typename std::remove_cv<T>::type>::value &&
+ std::is_trivially_destructible<T>::value &&
+ sizeof(T) <= 2 * sizeof(void*)> {};
+
+template <typename T>
+struct ForwardT : std::conditional<PassByValue<T>::value, T, T&&> {};
// An Invoker takes a pointer to the type-erased invokable object, followed by
// the arguments that the invokable object expects.
@@ -87,6 +91,12 @@
(void)f;
}
+template <typename Sig>
+void AssertNonNull(const AnyInvocable<Sig>& f) {
+ assert(f != nullptr);
+ (void)f;
+}
+
template <typename F>
void AssertNonNull(const F&) {}
diff --git a/absl/functional/overload.h b/absl/functional/overload.h
new file mode 100644
index 0000000..4651f14
--- /dev/null
+++ b/absl/functional/overload.h
@@ -0,0 +1,75 @@
+// Copyright 2023 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// -----------------------------------------------------------------------------
+// File: overload.h
+// -----------------------------------------------------------------------------
+//
+// `absl::Overload()` returns a functor that provides overloads based on the
+// functors passed to it.
+// Before using this function, consider whether named function overloads would
+// be a better design.
+// One use case for this is locally defining visitors for `std::visit` inside a
+// function using lambdas.
+
+// Example: Using `absl::Overload` to define a visitor for `std::variant`.
+//
+// std::variant<int, std::string, double> v(int{1});
+//
+// assert(std::visit(absl::Overload(
+// [](int) -> absl::string_view { return "int"; },
+// [](const std::string&) -> absl::string_view {
+// return "string";
+// },
+// [](double) -> absl::string_view { return "double"; }),
+// v) == "int");
+//
+// Note: This requires C++17.
+
+#ifndef ABSL_FUNCTIONAL_OVERLOAD_H_
+#define ABSL_FUNCTIONAL_OVERLOAD_H_
+
+#include "absl/base/config.h"
+#include "absl/meta/type_traits.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+
+#if defined(ABSL_INTERNAL_CPLUSPLUS_LANG) && \
+ ABSL_INTERNAL_CPLUSPLUS_LANG >= 201703L
+
+template <int&... ExplicitArgumentBarrier, typename... T>
+auto Overload(T&&... ts) {
+ struct OverloadImpl : absl::remove_cvref_t<T>... {
+ using absl::remove_cvref_t<T>::operator()...;
+ };
+ return OverloadImpl{std::forward<T>(ts)...};
+}
+#else
+namespace functional_internal {
+template <typename T>
+constexpr bool kDependentFalse = false;
+}
+
+template <typename Dependent = int, typename... T>
+auto Overload(T&&...) {
+ static_assert(functional_internal::kDependentFalse<Dependent>,
+ "Overload is only usable with C++17 or above.");
+}
+
+#endif
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_FUNCTIONAL_OVERLOAD_H_
diff --git a/absl/functional/overload_test.cc b/absl/functional/overload_test.cc
new file mode 100644
index 0000000..739c4c4
--- /dev/null
+++ b/absl/functional/overload_test.cc
@@ -0,0 +1,130 @@
+// Copyright 2023 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/functional/overload.h"
+
+#include <cstdint>
+#include <string>
+#include <type_traits>
+
+#include "absl/base/config.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/string_view.h"
+#include "absl/types/variant.h"
+
+#if defined(ABSL_INTERNAL_CPLUSPLUS_LANG) && \
+ ABSL_INTERNAL_CPLUSPLUS_LANG >= 201703L
+
+#include "gtest/gtest.h"
+
+namespace {
+
+TEST(OverloadTest, DispatchConsidersType) {
+ auto overloaded = absl::Overload(
+ [](int v) -> std::string { return absl::StrCat("int ", v); }, //
+ [](double v) -> std::string { return absl::StrCat("double ", v); }, //
+ [](const char* v) -> std::string { //
+ return absl::StrCat("const char* ", v); //
+ }, //
+ [](auto v) -> std::string { return absl::StrCat("auto ", v); } //
+ );
+ EXPECT_EQ("int 1", overloaded(1));
+ EXPECT_EQ("double 2.5", overloaded(2.5));
+ EXPECT_EQ("const char* hello", overloaded("hello"));
+ EXPECT_EQ("auto 1.5", overloaded(1.5f));
+}
+
+TEST(OverloadTest, DispatchConsidersNumberOfArguments) {
+ auto overloaded = absl::Overload( //
+ [](int a) { return a + 1; }, //
+ [](int a, int b) { return a * b; }, //
+ []() -> absl::string_view { return "none"; } //
+ );
+ EXPECT_EQ(3, overloaded(2));
+ EXPECT_EQ(21, overloaded(3, 7));
+ EXPECT_EQ("none", overloaded());
+}
+
+TEST(OverloadTest, SupportsConstantEvaluation) {
+ auto overloaded = absl::Overload( //
+ [](int a) { return a + 1; }, //
+ [](int a, int b) { return a * b; }, //
+ []() -> absl::string_view { return "none"; } //
+ );
+ static_assert(overloaded() == "none");
+ static_assert(overloaded(2) == 3);
+ static_assert(overloaded(3, 7) == 21);
+}
+
+TEST(OverloadTest, PropogatesDefaults) {
+ auto overloaded = absl::Overload( //
+ [](int a, int b = 5) { return a * b; }, //
+ [](double c) { return c; } //
+ );
+
+ EXPECT_EQ(21, overloaded(3, 7));
+ EXPECT_EQ(35, overloaded(7));
+ EXPECT_EQ(2.5, overloaded(2.5));
+}
+
+TEST(OverloadTest, AmbiguousWithDefaultsNotInvocable) {
+ auto overloaded = absl::Overload( //
+ [](int a, int b = 5) { return a * b; }, //
+ [](int c) { return c; } //
+ );
+ static_assert(!std::is_invocable_v<decltype(overloaded), int>);
+ static_assert(std::is_invocable_v<decltype(overloaded), int, int>);
+}
+
+TEST(OverloadTest, AmbiguousDuplicatesNotInvocable) {
+ auto overloaded = absl::Overload( //
+ [](int a) { return a; }, //
+ [](int c) { return c; } //
+ );
+ static_assert(!std::is_invocable_v<decltype(overloaded), int>);
+}
+
+TEST(OverloadTest, AmbiguousConversionNotInvocable) {
+ auto overloaded = absl::Overload( //
+ [](uint16_t a) { return a; }, //
+ [](uint64_t c) { return c; } //
+ );
+ static_assert(!std::is_invocable_v<decltype(overloaded), int>);
+}
+
+TEST(OverloadTest, DispatchConsidersSfinae) {
+ auto overloaded = absl::Overload( //
+ [](auto a) -> decltype(a + 1) { return a + 1; } //
+ );
+ static_assert(std::is_invocable_v<decltype(overloaded), int>);
+ static_assert(!std::is_invocable_v<decltype(overloaded), std::string>);
+}
+
+TEST(OverloadTest, VariantVisitDispatchesCorrectly) {
+ absl::variant<int, double, std::string> v(1);
+ auto overloaded = absl::Overload(
+ [](int) -> absl::string_view { return "int"; }, //
+ [](double) -> absl::string_view { return "double"; }, //
+ [](const std::string&) -> absl::string_view { return "string"; } //
+ );
+ EXPECT_EQ("int", absl::visit(overloaded, v));
+ v = 1.1;
+ EXPECT_EQ("double", absl::visit(overloaded, v));
+ v = "hello";
+ EXPECT_EQ("string", absl::visit(overloaded, v));
+}
+
+} // namespace
+
+#endif
diff --git a/absl/hash/BUILD.bazel b/absl/hash/BUILD.bazel
index 4b2c220..fe567e9 100644
--- a/absl/hash/BUILD.bazel
+++ b/absl/hash/BUILD.bazel
@@ -14,7 +14,6 @@
# limitations under the License.
#
-load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test")
load(
"//absl:copts/configure_copts.bzl",
"ABSL_DEFAULT_COPTS",
@@ -22,7 +21,14 @@
"ABSL_TEST_COPTS",
)
-package(default_visibility = ["//visibility:public"])
+package(
+ default_visibility = ["//visibility:public"],
+ features = [
+ "header_modules",
+ "layering_check",
+ "parse_headers",
+ ],
+)
licenses(["notice"])
@@ -37,12 +43,14 @@
linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
":city",
- ":wyhash",
+ ":low_level_hash",
"//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",
@@ -53,7 +61,7 @@
cc_library(
name = "hash_testing",
- testonly = 1,
+ testonly = True,
hdrs = ["hash_testing.h"],
linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
@@ -67,25 +75,60 @@
cc_test(
name = "hash_test",
- srcs = ["hash_test.cc"],
+ srcs = [
+ "hash_test.cc",
+ "internal/hash_test.h",
+ ],
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
":hash",
":hash_testing",
":spy_hash_state",
+ "//absl/base:config",
"//absl/base:core_headers",
+ "//absl/container:btree",
+ "//absl/container:flat_hash_map",
"//absl/container:flat_hash_set",
+ "//absl/container:node_hash_map",
+ "//absl/container:node_hash_set",
+ "//absl/memory",
"//absl/meta:type_traits",
"//absl/numeric:int128",
"//absl/strings:cord_test_helpers",
+ "//absl/strings:string_view",
+ "//absl/types:optional",
+ "//absl/types:variant",
+ "@com_google_googletest//:gtest",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "hash_instantiated_test",
+ srcs = [
+ "hash_instantiated_test.cc",
+ "internal/hash_test.h",
+ ],
+ copts = ABSL_TEST_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ ":hash",
+ ":hash_testing",
+ "//absl/base:config",
+ "//absl/container:btree",
+ "//absl/container:flat_hash_map",
+ "//absl/container:flat_hash_set",
+ "//absl/container:node_hash_map",
+ "//absl/container:node_hash_set",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
cc_binary(
name = "hash_benchmark",
- testonly = 1,
+ testonly = True,
srcs = ["hash_benchmark.cc"],
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
@@ -94,6 +137,7 @@
deps = [
":hash",
"//absl/base:core_headers",
+ "//absl/container:flat_hash_set",
"//absl/random",
"//absl/strings",
"//absl/strings:cord",
@@ -104,7 +148,7 @@
cc_library(
name = "spy_hash_state",
- testonly = 1,
+ testonly = True,
hdrs = ["internal/spy_hash_state.h"],
copts = ABSL_DEFAULT_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
@@ -138,33 +182,36 @@
linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
":city",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
cc_library(
- name = "wyhash",
- srcs = ["internal/wyhash.cc"],
- hdrs = ["internal/wyhash.h"],
+ 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:endian",
+ "//absl/base:prefetch",
"//absl/numeric:int128",
],
)
cc_test(
- name = "wyhash_test",
- srcs = ["internal/wyhash_test.cc"],
+ name = "low_level_hash_test",
+ srcs = ["internal/low_level_hash_test.cc"],
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
visibility = ["//visibility:private"],
deps = [
- ":wyhash",
+ ":low_level_hash",
"//absl/strings",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
diff --git a/absl/hash/CMakeLists.txt b/absl/hash/CMakeLists.txt
index c82f66f..99d6fa1 100644
--- a/absl/hash/CMakeLists.txt
+++ b/absl/hash/CMakeLists.txt
@@ -24,19 +24,21 @@
"internal/hash.h"
COPTS
${ABSL_DEFAULT_COPTS}
- DEPS
+ DEPS
+ absl::bits
absl::city
absl::config
absl::core_headers
absl::endian
absl::fixed_array
+ absl::function_ref
absl::meta
absl::int128
absl::strings
absl::optional
absl::variant
absl::utility
- absl::wyhash
+ absl::low_level_hash
PUBLIC
)
@@ -54,6 +56,7 @@
absl::variant
GTest::gmock
TESTONLY
+ PUBLIC
)
absl_cc_test(
@@ -61,20 +64,54 @@
hash_test
SRCS
"hash_test.cc"
+ "internal/hash_test.h"
COPTS
${ABSL_TEST_COPTS}
DEPS
+ absl::btree
absl::cord_test_helpers
+ absl::core_headers
+ absl::flat_hash_map
+ absl::flat_hash_set
absl::hash
absl::hash_testing
- absl::core_headers
- absl::flat_hash_set
- absl::spy_hash_state
- absl::meta
absl::int128
+ absl::memory
+ absl::meta
+ absl::node_hash_map
+ absl::node_hash_set
+ absl::optional
+ absl::spy_hash_state
+ absl::string_view
+ absl::variant
GTest::gmock_main
)
+absl_cc_test(
+ NAME
+ hash_instantiated_test
+ SRCS
+ "hash_instantiated_test.cc"
+ "internal/hash_test.h"
+ COPTS
+ ${ABSL_TEST_COPTS}
+ DEPS
+ absl::hash
+ absl::hash_testing
+ absl::config
+ absl::btree
+ absl::flat_hash_map
+ absl::flat_hash_set
+ absl::node_hash_map
+ absl::node_hash_set
+ GTest::gmock_main
+)
+
+# Internal-only target, do not depend on directly.
+#
+# Note: Even though external code should not depend on this target
+# directly, it must be marked PUBLIC since it is a dependency of
+# hash_testing.
absl_cc_library(
NAME
spy_hash_state
@@ -87,8 +124,10 @@
absl::strings
absl::str_format
TESTONLY
+ PUBLIC
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
city
@@ -116,30 +155,32 @@
GTest::gmock_main
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
- wyhash
+ low_level_hash
HDRS
- "internal/wyhash.h"
+ "internal/low_level_hash.h"
SRCS
- "internal/wyhash.cc"
+ "internal/low_level_hash.cc"
COPTS
${ABSL_DEFAULT_COPTS}
DEPS
absl::config
absl::endian
absl::int128
+ absl::prefetch
)
absl_cc_test(
NAME
- wyhash_test
+ low_level_hash_test
SRCS
- "internal/wyhash_test.cc"
+ "internal/low_level_hash_test.cc"
COPTS
${ABSL_TEST_COPTS}
DEPS
- absl::wyhash
+ absl::low_level_hash
absl::strings
GTest::gmock_main
)
diff --git a/absl/hash/hash.h b/absl/hash/hash.h
index 8282ea5..470cca4 100644
--- a/absl/hash/hash.h
+++ b/absl/hash/hash.h
@@ -26,9 +26,9 @@
// support Abseil hashing without requiring you to define a hashing
// algorithm.
// * `HashState`, a type-erased class which implements the manipulation of the
-// hash state (H) itself, contains member functions `combine()` and
-// `combine_contiguous()`, which you can use to contribute to an existing
-// hash state when hashing your types.
+// hash state (H) itself; contains member functions `combine()`,
+// `combine_contiguous()`, and `combine_unordered()`; and which you can use
+// to contribute to an existing hash state when hashing your types.
//
// Unlike `std::hash` or other hashing frameworks, the Abseil hashing framework
// provides most of its utility by abstracting away the hash algorithm (and its
@@ -40,6 +40,11 @@
// each process. E.g., `absl::Hash<int>{}(9)` in one process and
// `absl::Hash<int>{}(9)` in another process are likely to differ.
//
+// `absl::Hash` may also produce different values from different dynamically
+// loaded libraries. For this reason, `absl::Hash` values must never cross
+// boundaries in dynamically loaded libraries (including when used in types like
+// hash containers.)
+//
// `absl::Hash` is intended to strongly mix input bits with a target of passing
// an [Avalanche Test](https://en.wikipedia.org/wiki/Avalanche_effect).
//
@@ -74,7 +79,9 @@
#define ABSL_HASH_HASH_H_
#include <tuple>
+#include <utility>
+#include "absl/functional/function_ref.h"
#include "absl/hash/internal/hash.h"
namespace absl {
@@ -103,18 +110,34 @@
// * std::unique_ptr and std::shared_ptr
// * All string-like types including:
// * absl::Cord
-// * std::string
-// * std::string_view (as well as any instance of std::basic_string that
-// uses char and std::char_traits)
+// * std::string (as well as any instance of std::basic_string that
+// uses one of {char, wchar_t, char16_t, char32_t} and its associated
+// std::char_traits)
+// * std::string_view (as well as any instance of std::basic_string_view
+// that uses one of {char, wchar_t, char16_t, char32_t} and its associated
+// std::char_traits)
// * All the standard sequence containers (provided the elements are hashable)
-// * All the standard ordered associative containers (provided the elements are
+// * All the standard associative containers (provided the elements are
// hashable)
// * absl types such as the following:
// * absl::string_view
-// * absl::InlinedVector
-// * absl::FixedArray
// * absl::uint128
// * absl::Time, absl::Duration, and absl::TimeZone
+// * absl containers (provided the elements are hashable) such as the
+// following:
+// * absl::flat_hash_set, absl::node_hash_set, absl::btree_set
+// * absl::flat_hash_map, absl::node_hash_map, absl::btree_map
+// * absl::btree_multiset, absl::btree_multimap
+// * absl::InlinedVector
+// * absl::FixedArray
+//
+// When absl::Hash is used to hash an unordered container with a custom hash
+// functor, the elements are hashed using default absl::Hash semantics, not
+// the custom hash functor. This is consistent with the behavior of
+// operator==() on unordered containers, which compares elements pairwise with
+// operator==() rather than the custom equality functor. It is usually a
+// mistake to use either operator==() or absl::Hash on unordered collections
+// that use functors incompatible with operator==() equality.
//
// Note: the list above is not meant to be exhaustive. Additional type support
// may be added, in which case the above list will be updated.
@@ -153,7 +176,8 @@
// that are otherwise difficult to extend using `AbslHashValue()`. (See the
// `HashState` class below.)
//
-// The "hash state" concept contains two member functions for mixing hash state:
+// The "hash state" concept contains three member functions for mixing hash
+// state:
//
// * `H::combine(state, values...)`
//
@@ -187,6 +211,15 @@
// (it may perform internal optimizations). If you need this guarantee, use a
// loop instead.
//
+// * `H::combine_unordered(state, begin, end)`
+//
+// Combines a set of elements denoted by an iterator pair into a hash
+// state, returning the updated state. Note that the existing hash
+// state is move-only and must be passed by value.
+//
+// Unlike the other two methods, the hashing is order-independent.
+// This can be used to hash unordered collections.
+//
// -----------------------------------------------------------------------------
// Adding Type Support to `absl::Hash`
// -----------------------------------------------------------------------------
@@ -243,8 +276,9 @@
// classes, virtual functions, etc.). The type erasure adds overhead so it
// should be avoided unless necessary.
//
-// Note: This wrapper will only erase calls to:
+// Note: This wrapper will only erase calls to
// combine_contiguous(H, const unsigned char*, size_t)
+// RunCombineUnordered(H, CombinerF)
//
// All other calls will be handled internally and will not invoke overloads
// provided by the wrapped class.
@@ -318,6 +352,8 @@
private:
HashState() = default;
+ friend class HashState::HashStateBase;
+
template <typename T>
static void CombineContiguousImpl(void* p, const unsigned char* first,
size_t size) {
@@ -329,16 +365,57 @@
void Init(T* state) {
state_ = state;
combine_contiguous_ = &CombineContiguousImpl<T>;
+ run_combine_unordered_ = &RunCombineUnorderedImpl<T>;
+ }
+
+ template <typename HS>
+ struct CombineUnorderedInvoker {
+ template <typename T, typename ConsumerT>
+ void operator()(T inner_state, ConsumerT inner_cb) {
+ f(HashState::Create(&inner_state),
+ [&](HashState& inner_erased) { inner_cb(inner_erased.Real<T>()); });
+ }
+
+ absl::FunctionRef<void(HS, absl::FunctionRef<void(HS&)>)> f;
+ };
+
+ template <typename T>
+ static HashState RunCombineUnorderedImpl(
+ HashState state,
+ absl::FunctionRef<void(HashState, absl::FunctionRef<void(HashState&)>)>
+ f) {
+ // Note that this implementation assumes that inner_state and outer_state
+ // are the same type. This isn't true in the SpyHash case, but SpyHash
+ // types are move-convertible to each other, so this still works.
+ T& real_state = state.Real<T>();
+ real_state = T::RunCombineUnordered(
+ std::move(real_state), CombineUnorderedInvoker<HashState>{f});
+ return state;
+ }
+
+ template <typename CombinerT>
+ static HashState RunCombineUnordered(HashState state, CombinerT combiner) {
+ auto* run = state.run_combine_unordered_;
+ return run(std::move(state), std::ref(combiner));
}
// Do not erase an already erased state.
void Init(HashState* state) {
state_ = state->state_;
combine_contiguous_ = state->combine_contiguous_;
+ run_combine_unordered_ = state->run_combine_unordered_;
+ }
+
+ template <typename T>
+ T& Real() {
+ return *static_cast<T*>(state_);
}
void* state_;
void (*combine_contiguous_)(void*, const unsigned char*, size_t);
+ HashState (*run_combine_unordered_)(
+ HashState state,
+ absl::FunctionRef<void(HashState, absl::FunctionRef<void(HashState&)>)>);
};
ABSL_NAMESPACE_END
diff --git a/absl/hash/hash_benchmark.cc b/absl/hash/hash_benchmark.cc
index d498ac2..d18ea69 100644
--- a/absl/hash/hash_benchmark.cc
+++ b/absl/hash/hash_benchmark.cc
@@ -12,13 +12,20 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+#include <algorithm>
+#include <cassert>
+#include <cstddef>
+#include <cstdint>
+#include <cstring>
#include <string>
+#include <tuple>
#include <type_traits>
#include <typeindex>
#include <utility>
#include <vector>
#include "absl/base/attributes.h"
+#include "absl/container/flat_hash_set.h"
#include "absl/hash/hash.h"
#include "absl/random/random.h"
#include "absl/strings/cord.h"
@@ -78,12 +85,6 @@
}
};
-template <typename FuncType>
-inline FuncType* ODRUseFunction(FuncType* ptr) {
- volatile FuncType* dummy = ptr;
- return dummy;
-}
-
absl::Cord FlatCord(size_t size) {
absl::Cord result(std::string(size, 'a'));
result.Flatten();
@@ -107,6 +108,44 @@
return result;
}
+template <typename T>
+std::vector<T> Vector(size_t count) {
+ std::vector<T> result;
+ for (size_t v = 0; v < count; ++v) {
+ result.push_back(v);
+ }
+ return result;
+}
+
+// Bogus type that replicates an unorderd_set's bit mixing, but with
+// vector-speed iteration. This is intended to measure the overhead of unordered
+// hashing without counting the speed of unordered_set iteration.
+template <typename T>
+struct FastUnorderedSet {
+ explicit FastUnorderedSet(size_t count) {
+ for (size_t v = 0; v < count; ++v) {
+ values.push_back(v);
+ }
+ }
+ std::vector<T> values;
+
+ template <typename H>
+ friend H AbslHashValue(H h, const FastUnorderedSet& fus) {
+ return H::combine(H::combine_unordered(std::move(h), fus.values.begin(),
+ fus.values.end()),
+ fus.values.size());
+ }
+};
+
+template <typename T>
+absl::flat_hash_set<T> FlatHashSet(size_t count) {
+ absl::flat_hash_set<T> result;
+ for (size_t v = 0; v < count; ++v) {
+ result.insert(v);
+ }
+ return result;
+}
+
// Generates a benchmark and a codegen method for the provided types. The
// codegen method provides a well known entrypoint for dumping assembly.
#define MAKE_BENCHMARK(hash, name, ...) \
@@ -121,7 +160,7 @@
return hash<decltype(__VA_ARGS__)>{}(arg); \
} \
bool absl_hash_test_odr_use##hash##name = \
- ODRUseFunction(&Codegen##hash##name);
+ (benchmark::DoNotOptimize(&Codegen##hash##name), false);
MAKE_BENCHMARK(AbslHash, Int32, int32_t{});
MAKE_BENCHMARK(AbslHash, Int64, int64_t{});
@@ -145,10 +184,22 @@
MAKE_BENCHMARK(AbslHash, Cord_Flat_5000, FlatCord(5000));
MAKE_BENCHMARK(AbslHash, Cord_Fragmented_200, FragmentedCord(200));
MAKE_BENCHMARK(AbslHash, Cord_Fragmented_5000, FragmentedCord(5000));
-MAKE_BENCHMARK(AbslHash, VectorInt64_10, std::vector<int64_t>(10));
-MAKE_BENCHMARK(AbslHash, VectorInt64_100, std::vector<int64_t>(100));
-MAKE_BENCHMARK(AbslHash, VectorDouble_10, std::vector<double>(10, 1.1));
-MAKE_BENCHMARK(AbslHash, VectorDouble_100, std::vector<double>(100, 1.1));
+MAKE_BENCHMARK(AbslHash, VectorInt64_10, Vector<int64_t>(10));
+MAKE_BENCHMARK(AbslHash, VectorInt64_100, Vector<int64_t>(100));
+MAKE_BENCHMARK(AbslHash, VectorInt64_1000, Vector<int64_t>(1000));
+MAKE_BENCHMARK(AbslHash, VectorDouble_10, Vector<double>(10));
+MAKE_BENCHMARK(AbslHash, VectorDouble_100, Vector<double>(100));
+MAKE_BENCHMARK(AbslHash, VectorDouble_1000, Vector<double>(1000));
+MAKE_BENCHMARK(AbslHash, FlatHashSetInt64_10, FlatHashSet<int64_t>(10));
+MAKE_BENCHMARK(AbslHash, FlatHashSetInt64_100, FlatHashSet<int64_t>(100));
+MAKE_BENCHMARK(AbslHash, FlatHashSetInt64_1000, FlatHashSet<int64_t>(1000));
+MAKE_BENCHMARK(AbslHash, FlatHashSetDouble_10, FlatHashSet<double>(10));
+MAKE_BENCHMARK(AbslHash, FlatHashSetDouble_100, FlatHashSet<double>(100));
+MAKE_BENCHMARK(AbslHash, FlatHashSetDouble_1000, FlatHashSet<double>(1000));
+MAKE_BENCHMARK(AbslHash, FastUnorderedSetInt64_1000,
+ FastUnorderedSet<int64_t>(1000));
+MAKE_BENCHMARK(AbslHash, FastUnorderedSetDouble_1000,
+ FastUnorderedSet<double>(1000));
MAKE_BENCHMARK(AbslHash, PairStringString_0,
std::make_pair(std::string(), std::string()));
MAKE_BENCHMARK(AbslHash, PairStringString_10,
@@ -180,6 +231,24 @@
std::vector<double>(10, 1.1));
MAKE_BENCHMARK(TypeErasedAbslHash, VectorDouble_100,
std::vector<double>(100, 1.1));
+MAKE_BENCHMARK(TypeErasedAbslHash, VectorDouble_1000,
+ std::vector<double>(1000, 1.1));
+MAKE_BENCHMARK(TypeErasedAbslHash, FlatHashSetInt64_10,
+ FlatHashSet<int64_t>(10));
+MAKE_BENCHMARK(TypeErasedAbslHash, FlatHashSetInt64_100,
+ FlatHashSet<int64_t>(100));
+MAKE_BENCHMARK(TypeErasedAbslHash, FlatHashSetInt64_1000,
+ FlatHashSet<int64_t>(1000));
+MAKE_BENCHMARK(TypeErasedAbslHash, FlatHashSetDouble_10,
+ FlatHashSet<double>(10));
+MAKE_BENCHMARK(TypeErasedAbslHash, FlatHashSetDouble_100,
+ FlatHashSet<double>(100));
+MAKE_BENCHMARK(TypeErasedAbslHash, FlatHashSetDouble_1000,
+ FlatHashSet<double>(1000));
+MAKE_BENCHMARK(TypeErasedAbslHash, FastUnorderedSetInt64_1000,
+ FastUnorderedSet<int64_t>(1000));
+MAKE_BENCHMARK(TypeErasedAbslHash, FastUnorderedSetDouble_1000,
+ FastUnorderedSet<double>(1000));
// The latency benchmark attempts to model the speed of the hash function in
// production. When a hash function is used for hashtable lookups it is rarely
diff --git a/absl/hash/hash_instantiated_test.cc b/absl/hash/hash_instantiated_test.cc
new file mode 100644
index 0000000..e65de9c
--- /dev/null
+++ b/absl/hash/hash_instantiated_test.cc
@@ -0,0 +1,224 @@
+// Copyright 2018 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// This file contains a few select absl::Hash tests that, due to their reliance
+// on INSTANTIATE_TYPED_TEST_SUITE_P, require a large amount of memory to
+// compile. Put new tests in hash_test.cc, not this file.
+
+#include "absl/hash/hash.h"
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <deque>
+#include <forward_list>
+#include <initializer_list>
+#include <list>
+#include <map>
+#include <set>
+#include <string>
+#include <type_traits>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include "gtest/gtest.h"
+#include "absl/container/btree_map.h"
+#include "absl/container/btree_set.h"
+#include "absl/container/flat_hash_map.h"
+#include "absl/container/flat_hash_set.h"
+#include "absl/container/node_hash_map.h"
+#include "absl/container/node_hash_set.h"
+#include "absl/hash/hash_testing.h"
+#include "absl/hash/internal/hash_test.h"
+
+namespace {
+
+using ::absl::hash_test_internal::is_hashable;
+using ::absl::hash_test_internal::TypeErasedContainer;
+
+// Dummy type with unordered equality and hashing semantics. This preserves
+// input order internally, and is used below to ensure we get test coverage
+// for equal sequences with different iteraton orders.
+template <typename T>
+class UnorderedSequence {
+ public:
+ UnorderedSequence() = default;
+ template <typename TT>
+ UnorderedSequence(std::initializer_list<TT> l)
+ : values_(l.begin(), l.end()) {}
+ template <typename ForwardIterator,
+ typename std::enable_if<!std::is_integral<ForwardIterator>::value,
+ bool>::type = true>
+ UnorderedSequence(ForwardIterator begin, ForwardIterator end)
+ : values_(begin, end) {}
+ // one-argument constructor of value type T, to appease older toolchains that
+ // get confused by one-element initializer lists in some contexts
+ explicit UnorderedSequence(const T& v) : values_(&v, &v + 1) {}
+
+ using value_type = T;
+
+ size_t size() const { return values_.size(); }
+ typename std::vector<T>::const_iterator begin() const {
+ return values_.begin();
+ }
+ typename std::vector<T>::const_iterator end() const { return values_.end(); }
+
+ friend bool operator==(const UnorderedSequence& lhs,
+ const UnorderedSequence& rhs) {
+ return lhs.size() == rhs.size() &&
+ std::is_permutation(lhs.begin(), lhs.end(), rhs.begin());
+ }
+ friend bool operator!=(const UnorderedSequence& lhs,
+ const UnorderedSequence& rhs) {
+ return !(lhs == rhs);
+ }
+ template <typename H>
+ friend H AbslHashValue(H h, const UnorderedSequence& u) {
+ return H::combine(H::combine_unordered(std::move(h), u.begin(), u.end()),
+ u.size());
+ }
+
+ private:
+ std::vector<T> values_;
+};
+
+template <typename T>
+class HashValueSequenceTest : public testing::Test {};
+TYPED_TEST_SUITE_P(HashValueSequenceTest);
+
+TYPED_TEST_P(HashValueSequenceTest, BasicUsage) {
+ EXPECT_TRUE((is_hashable<TypeParam>::value));
+
+ using IntType = typename TypeParam::value_type;
+ auto a = static_cast<IntType>(0);
+ auto b = static_cast<IntType>(23);
+ auto c = static_cast<IntType>(42);
+
+ std::vector<TypeParam> exemplars = {
+ TypeParam(), TypeParam(), TypeParam{a, b, c},
+ TypeParam{a, c, b}, TypeParam{c, a, b}, TypeParam{a},
+ TypeParam{a, a}, TypeParam{a, a, a}, TypeParam{a, a, b},
+ TypeParam{a, b, a}, TypeParam{b, a, a}, TypeParam{a, b},
+ TypeParam{b, c}};
+ EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly(exemplars));
+}
+
+REGISTER_TYPED_TEST_SUITE_P(HashValueSequenceTest, BasicUsage);
+using IntSequenceTypes = testing::Types<
+ std::deque<int>, std::forward_list<int>, std::list<int>, std::vector<int>,
+ std::vector<bool>, TypeErasedContainer<std::vector<int>>, std::set<int>,
+ std::multiset<int>, UnorderedSequence<int>,
+ TypeErasedContainer<UnorderedSequence<int>>, std::unordered_set<int>,
+ std::unordered_multiset<int>, absl::flat_hash_set<int>,
+ absl::node_hash_set<int>, absl::btree_set<int>>;
+INSTANTIATE_TYPED_TEST_SUITE_P(My, HashValueSequenceTest, IntSequenceTypes);
+
+template <typename T>
+class HashValueNestedSequenceTest : public testing::Test {};
+TYPED_TEST_SUITE_P(HashValueNestedSequenceTest);
+
+TYPED_TEST_P(HashValueNestedSequenceTest, BasicUsage) {
+ using T = TypeParam;
+ using V = typename T::value_type;
+ std::vector<T> exemplars = {
+ // empty case
+ T{},
+ // sets of empty sets
+ T{V{}}, T{V{}, V{}}, T{V{}, V{}, V{}},
+ // multisets of different values
+ T{V{1}}, T{V{1, 1}, V{1, 1}}, T{V{1, 1, 1}, V{1, 1, 1}, V{1, 1, 1}},
+ // various orderings of same nested sets
+ T{V{}, V{1, 2}}, T{V{}, V{2, 1}}, T{V{1, 2}, V{}}, T{V{2, 1}, V{}},
+ // various orderings of various nested sets, case 2
+ T{V{1, 2}, V{3, 4}}, T{V{1, 2}, V{4, 3}}, T{V{1, 3}, V{2, 4}},
+ T{V{1, 3}, V{4, 2}}, T{V{1, 4}, V{2, 3}}, T{V{1, 4}, V{3, 2}},
+ T{V{2, 3}, V{1, 4}}, T{V{2, 3}, V{4, 1}}, T{V{2, 4}, V{1, 3}},
+ T{V{2, 4}, V{3, 1}}, T{V{3, 4}, V{1, 2}}, T{V{3, 4}, V{2, 1}}};
+ EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly(exemplars));
+}
+
+REGISTER_TYPED_TEST_SUITE_P(HashValueNestedSequenceTest, BasicUsage);
+template <typename T>
+using TypeErasedSet = TypeErasedContainer<UnorderedSequence<T>>;
+
+using NestedIntSequenceTypes = testing::Types<
+ std::vector<std::vector<int>>, std::vector<UnorderedSequence<int>>,
+ std::vector<TypeErasedSet<int>>, UnorderedSequence<std::vector<int>>,
+ UnorderedSequence<UnorderedSequence<int>>,
+ UnorderedSequence<TypeErasedSet<int>>, TypeErasedSet<std::vector<int>>,
+ TypeErasedSet<UnorderedSequence<int>>, TypeErasedSet<TypeErasedSet<int>>>;
+INSTANTIATE_TYPED_TEST_SUITE_P(My, HashValueNestedSequenceTest,
+ NestedIntSequenceTypes);
+
+template <typename T>
+class HashValueAssociativeMapTest : public testing::Test {};
+TYPED_TEST_SUITE_P(HashValueAssociativeMapTest);
+
+TYPED_TEST_P(HashValueAssociativeMapTest, BasicUsage) {
+ using M = TypeParam;
+ using V = typename M::value_type;
+ std::vector<M> exemplars{M{},
+ M{V{0, "foo"}},
+ M{V{1, "foo"}},
+ M{V{0, "bar"}},
+ M{V{1, "bar"}},
+ M{V{0, "foo"}, V{42, "bar"}},
+ M{V{42, "bar"}, V{0, "foo"}},
+ M{V{1, "foo"}, V{42, "bar"}},
+ M{V{1, "foo"}, V{43, "bar"}},
+ M{V{1, "foo"}, V{43, "baz"}}};
+ EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly(exemplars));
+}
+
+REGISTER_TYPED_TEST_SUITE_P(HashValueAssociativeMapTest, BasicUsage);
+using AssociativeMapTypes = testing::Types<
+ std::map<int, std::string>, std::unordered_map<int, std::string>,
+ absl::flat_hash_map<int, std::string>,
+ absl::node_hash_map<int, std::string>, absl::btree_map<int, std::string>,
+ UnorderedSequence<std::pair<const int, std::string>>>;
+INSTANTIATE_TYPED_TEST_SUITE_P(My, HashValueAssociativeMapTest,
+ AssociativeMapTypes);
+
+template <typename T>
+class HashValueAssociativeMultimapTest : public testing::Test {};
+TYPED_TEST_SUITE_P(HashValueAssociativeMultimapTest);
+
+TYPED_TEST_P(HashValueAssociativeMultimapTest, BasicUsage) {
+ using MM = TypeParam;
+ using V = typename MM::value_type;
+ std::vector<MM> exemplars{MM{},
+ MM{V{0, "foo"}},
+ MM{V{1, "foo"}},
+ MM{V{0, "bar"}},
+ MM{V{1, "bar"}},
+ MM{V{0, "foo"}, V{0, "bar"}},
+ MM{V{0, "bar"}, V{0, "foo"}},
+ MM{V{0, "foo"}, V{42, "bar"}},
+ MM{V{1, "foo"}, V{42, "bar"}},
+ MM{V{1, "foo"}, V{1, "foo"}, V{43, "bar"}},
+ MM{V{1, "foo"}, V{43, "bar"}, V{1, "foo"}},
+ MM{V{1, "foo"}, V{43, "baz"}}};
+ EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly(exemplars));
+}
+
+REGISTER_TYPED_TEST_SUITE_P(HashValueAssociativeMultimapTest, BasicUsage);
+using AssociativeMultimapTypes =
+ testing::Types<std::multimap<int, std::string>,
+ std::unordered_multimap<int, std::string>>;
+INSTANTIATE_TYPED_TEST_SUITE_P(My, HashValueAssociativeMultimapTest,
+ AssociativeMultimapTypes);
+
+} // namespace
diff --git a/absl/hash/hash_test.cc b/absl/hash/hash_test.cc
index b3ddebd..59fe8de 100644
--- a/absl/hash/hash_test.cc
+++ b/absl/hash/hash_test.cc
@@ -14,19 +14,19 @@
#include "absl/hash/hash.h"
+#include <algorithm>
#include <array>
#include <bitset>
+#include <cstddef>
+#include <cstdint>
+#include <cstdlib>
#include <cstring>
-#include <deque>
-#include <forward_list>
#include <functional>
-#include <iterator>
+#include <initializer_list>
+#include <ios>
#include <limits>
-#include <list>
-#include <map>
#include <memory>
-#include <numeric>
-#include <random>
+#include <ostream>
#include <set>
#include <string>
#include <tuple>
@@ -35,17 +35,36 @@
#include <utility>
#include <vector>
-#include "gmock/gmock.h"
#include "gtest/gtest.h"
+#include "absl/base/config.h"
#include "absl/container/flat_hash_set.h"
#include "absl/hash/hash_testing.h"
+#include "absl/hash/internal/hash_test.h"
#include "absl/hash/internal/spy_hash_state.h"
+#include "absl/memory/memory.h"
#include "absl/meta/type_traits.h"
-#include "absl/numeric/int128.h"
#include "absl/strings/cord_test_helpers.h"
+#include "absl/strings/string_view.h"
+#include "absl/types/optional.h"
+#include "absl/types/variant.h"
+
+#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 201703L
+#include <filesystem> // NOLINT
+#endif
+
+#ifdef ABSL_HAVE_STD_STRING_VIEW
+#include <string_view>
+#endif
namespace {
+using ::absl::hash_test_internal::is_hashable;
+using ::absl::hash_test_internal::TypeErasedContainer;
+using ::absl::hash_test_internal::TypeErasedValue;
+
+template <typename T>
+using TypeErasedVector = TypeErasedContainer<std::vector<T>>;
+
using absl::Hash;
using absl::hash_internal::SpyHashState;
@@ -59,11 +78,6 @@
return SpyHashState::combine(SpyHashState(), value);
}
-// Helper trait to verify if T is hashable. We use absl::Hash's poison status to
-// detect it.
-template <typename T>
-using is_hashable = std::is_default_constructible<absl::Hash<T>>;
-
TYPED_TEST_P(HashValueIntTest, BasicUsage) {
EXPECT_TRUE((is_hashable<TypeParam>::value));
@@ -81,10 +95,10 @@
absl::Hash<std::tuple<TypeParam>>{}(std::tuple<TypeParam>(n)));
}
-REGISTER_TYPED_TEST_CASE_P(HashValueIntTest, BasicUsage, FastPath);
+REGISTER_TYPED_TEST_SUITE_P(HashValueIntTest, BasicUsage, FastPath);
using IntTypes = testing::Types<unsigned char, char, int, int32_t, int64_t,
uint32_t, uint64_t, size_t>;
-INSTANTIATE_TYPED_TEST_CASE_P(My, HashValueIntTest, IntTypes);
+INSTANTIATE_TYPED_TEST_SUITE_P(My, HashValueIntTest, IntTypes);
enum LegacyEnum { kValue1, kValue2, kValue3 };
@@ -127,6 +141,8 @@
TEST(HashValueTest, Pointer) {
EXPECT_TRUE((is_hashable<int*>::value));
+ EXPECT_TRUE((is_hashable<int(*)(char, float)>::value));
+ EXPECT_TRUE((is_hashable<void(*)(int, int, ...)>::value));
int i;
int* ptr = &i;
@@ -162,10 +178,89 @@
// Limit the scope to the bits we would be using for Swisstable.
constexpr size_t kMask = (1 << (kLog2NumValues + 7)) - 1;
size_t stuck_bits = (~bits_or | bits_and) & kMask;
- EXPECT_EQ(stuck_bits, 0) << "0x" << std::hex << stuck_bits;
+ EXPECT_EQ(stuck_bits, 0u) << "0x" << std::hex << stuck_bits;
}
}
+TEST(HashValueTest, PointerToMember) {
+ struct Bass {
+ void q() {}
+ };
+
+ struct A : Bass {
+ virtual ~A() = default;
+ virtual void vfa() {}
+
+ static auto pq() -> void (A::*)() { return &A::q; }
+ };
+
+ struct B : Bass {
+ virtual ~B() = default;
+ virtual void vfb() {}
+
+ static auto pq() -> void (B::*)() { return &B::q; }
+ };
+
+ struct Foo : A, B {
+ void f1() {}
+ void f2() const {}
+
+ int g1() & { return 0; }
+ int g2() const & { return 0; }
+ int g3() && { return 0; }
+ int g4() const && { return 0; }
+
+ int h1() & { return 0; }
+ int h2() const & { return 0; }
+ int h3() && { return 0; }
+ int h4() const && { return 0; }
+
+ int a;
+ int b;
+
+ const int c = 11;
+ const int d = 22;
+ };
+
+ EXPECT_TRUE((is_hashable<float Foo::*>::value));
+ EXPECT_TRUE((is_hashable<double (Foo::*)(int, int)&&>::value));
+
+ EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly(
+ std::make_tuple(&Foo::a, &Foo::b, static_cast<int Foo::*>(nullptr))));
+
+ EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly(
+ std::make_tuple(&Foo::c, &Foo::d, static_cast<const int Foo::*>(nullptr),
+ &Foo::a, &Foo::b)));
+
+ EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly(std::make_tuple(
+ &Foo::f1, static_cast<void (Foo::*)()>(nullptr))));
+
+ EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly(std::make_tuple(
+ &Foo::f2, static_cast<void (Foo::*)() const>(nullptr))));
+
+ EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly(std::make_tuple(
+ &Foo::g1, &Foo::h1, static_cast<int (Foo::*)() &>(nullptr))));
+
+ EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly(std::make_tuple(
+ &Foo::g2, &Foo::h2, static_cast<int (Foo::*)() const &>(nullptr))));
+
+ EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly(std::make_tuple(
+ &Foo::g3, &Foo::h3, static_cast<int (Foo::*)() &&>(nullptr))));
+
+ EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly(std::make_tuple(
+ &Foo::g4, &Foo::h4, static_cast<int (Foo::*)() const &&>(nullptr))));
+
+ EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly(
+ std::make_tuple(static_cast<void (Foo::*)()>(&Foo::vfa),
+ static_cast<void (Foo::*)()>(&Foo::vfb),
+ static_cast<void (Foo::*)()>(nullptr))));
+
+ EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly(
+ std::make_tuple(static_cast<void (Foo::*)()>(Foo::A::pq()),
+ static_cast<void (Foo::*)()>(Foo::B::pq()),
+ static_cast<void (Foo::*)()>(nullptr))));
+}
+
TEST(HashValueTest, PairAndTuple) {
EXPECT_TRUE((is_hashable<std::pair<int, int>>::value));
EXPECT_TRUE((is_hashable<std::pair<const int&, const int&>>::value));
@@ -348,6 +443,84 @@
std::u32string(U"Iñtërnâtiônàlizætiøn"))));
}
+TEST(HashValueTest, WStringView) {
+#ifndef ABSL_HAVE_STD_STRING_VIEW
+ GTEST_SKIP();
+#else
+ EXPECT_TRUE((is_hashable<std::wstring_view>::value));
+
+ EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly(std::make_tuple(
+ std::wstring_view(), std::wstring_view(L"ABC"), std::wstring_view(L"ABC"),
+ std::wstring_view(L"Some other different string_view"),
+ std::wstring_view(L"Iñtërnâtiônàlizætiøn"))));
+#endif
+}
+
+TEST(HashValueTest, U16StringView) {
+#ifndef ABSL_HAVE_STD_STRING_VIEW
+ GTEST_SKIP();
+#else
+ EXPECT_TRUE((is_hashable<std::u16string_view>::value));
+
+ EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly(
+ std::make_tuple(std::u16string_view(), std::u16string_view(u"ABC"),
+ std::u16string_view(u"ABC"),
+ std::u16string_view(u"Some other different string_view"),
+ std::u16string_view(u"Iñtërnâtiônàlizætiøn"))));
+#endif
+}
+
+TEST(HashValueTest, U32StringView) {
+#ifndef ABSL_HAVE_STD_STRING_VIEW
+ GTEST_SKIP();
+#else
+ EXPECT_TRUE((is_hashable<std::u32string_view>::value));
+
+ EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly(
+ std::make_tuple(std::u32string_view(), std::u32string_view(U"ABC"),
+ std::u32string_view(U"ABC"),
+ std::u32string_view(U"Some other different string_view"),
+ std::u32string_view(U"Iñtërnâtiônàlizætiøn"))));
+#endif
+}
+
+TEST(HashValueTest, StdFilesystemPath) {
+#ifndef ABSL_INTERNAL_STD_FILESYSTEM_PATH_HASH_AVAILABLE
+ GTEST_SKIP() << "std::filesystem::path is unavailable on this platform";
+#else
+ EXPECT_TRUE((is_hashable<std::filesystem::path>::value));
+
+ // clang-format off
+ const auto kTestCases = std::make_tuple(
+ std::filesystem::path(),
+ std::filesystem::path("/"),
+#ifndef __GLIBCXX__
+ // libstdc++ has a known issue normalizing "//".
+ // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=106452
+ std::filesystem::path("//"),
+#endif
+ std::filesystem::path("/a/b"),
+ std::filesystem::path("/a//b"),
+ std::filesystem::path("a/b"),
+ std::filesystem::path("a/b/"),
+ std::filesystem::path("a//b"),
+ std::filesystem::path("a//b/"),
+ std::filesystem::path("c:/"),
+ std::filesystem::path("c:\\"),
+ std::filesystem::path("c:\\/"),
+ std::filesystem::path("c:\\//"),
+ std::filesystem::path("c://"),
+ std::filesystem::path("c://\\"),
+ std::filesystem::path("/e/p"),
+ std::filesystem::path("/s/../e/p"),
+ std::filesystem::path("e/p"),
+ std::filesystem::path("s/../e/p"));
+ // clang-format on
+
+ EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly(kTestCases));
+#endif
+}
+
TEST(HashValueTest, StdArray) {
EXPECT_TRUE((is_hashable<std::array<int, 3>>::value));
@@ -381,31 +554,6 @@
std::bitset<kNumBits>(bit_strings[5].c_str())}));
} // namespace
-template <typename T>
-class HashValueSequenceTest : public testing::Test {
-};
-TYPED_TEST_SUITE_P(HashValueSequenceTest);
-
-TYPED_TEST_P(HashValueSequenceTest, BasicUsage) {
- EXPECT_TRUE((is_hashable<TypeParam>::value));
-
- using ValueType = typename TypeParam::value_type;
- auto a = static_cast<ValueType>(0);
- auto b = static_cast<ValueType>(23);
- auto c = static_cast<ValueType>(42);
-
- EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly(
- std::make_tuple(TypeParam(), TypeParam{}, TypeParam{a, b, c},
- TypeParam{a, b}, TypeParam{b, c})));
-}
-
-REGISTER_TYPED_TEST_CASE_P(HashValueSequenceTest, BasicUsage);
-using IntSequenceTypes =
- testing::Types<std::deque<int>, std::forward_list<int>, std::list<int>,
- std::vector<int>, std::vector<bool>, std::set<int>,
- std::multiset<int>>;
-INSTANTIATE_TYPED_TEST_CASE_P(My, HashValueSequenceTest, IntSequenceTypes);
-
// Private type that only supports AbslHashValue to make sure our chosen hash
// implementation is recursive within absl::Hash.
// It uses std::abs() on the value to provide different bitwise representations
@@ -508,10 +656,10 @@
//
// This test is run on a buffer that is a multiple of the stride size, and one
// that isn't.
- for (size_t big_buffer_size : {1024 * 2 + 512, 1024 * 3}) {
+ for (size_t big_buffer_size : {1024u * 2 + 512u, 1024u * 3}) {
SCOPED_TRACE(big_buffer_size);
std::string big_buffer;
- for (int i = 0; i < big_buffer_size; ++i) {
+ for (size_t i = 0; i < big_buffer_size; ++i) {
// Arbitrary string
big_buffer.push_back(32 + (i * (i / 3)) % 64);
}
@@ -564,23 +712,6 @@
#endif
}
-TEST(HashValueTest, Maps) {
- EXPECT_TRUE((is_hashable<std::map<int, std::string>>::value));
-
- using M = std::map<int, std::string>;
- EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly(std::make_tuple(
- M{}, M{{0, "foo"}}, M{{1, "foo"}}, M{{0, "bar"}}, M{{1, "bar"}},
- M{{0, "foo"}, {42, "bar"}}, M{{1, "foo"}, {42, "bar"}},
- M{{1, "foo"}, {43, "bar"}}, M{{1, "foo"}, {43, "baz"}})));
-
- using MM = std::multimap<int, std::string>;
- EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly(std::make_tuple(
- MM{}, MM{{0, "foo"}}, MM{{1, "foo"}}, MM{{0, "bar"}}, MM{{1, "bar"}},
- MM{{0, "foo"}, {0, "bar"}}, MM{{0, "bar"}, {0, "foo"}},
- MM{{0, "foo"}, {42, "bar"}}, MM{{1, "foo"}, {42, "bar"}},
- MM{{1, "foo"}, {1, "foo"}, {43, "bar"}}, MM{{1, "foo"}, {43, "baz"}})));
-}
-
TEST(HashValueTest, ReferenceWrapper) {
EXPECT_TRUE(is_hashable<std::reference_wrapper<Private>>::value);
@@ -634,8 +765,8 @@
EXPECT_FALSE(absl::is_copy_assignable<absl::Hash<X>>::value);
EXPECT_FALSE(absl::is_move_assignable<absl::Hash<X>>::value);
EXPECT_FALSE(IsHashCallable<X>::value);
-#if !defined(__GNUC__) || __GNUC__ < 9
- // This doesn't compile on GCC 9.
+#if !defined(__GNUC__) || defined(__clang__)
+ // TODO(b/144368551): As of GCC 8.4 this does not compile.
EXPECT_FALSE(IsAggregateInitializable<absl::Hash<X>>::value);
#endif
}
@@ -818,10 +949,10 @@
Hash<CombineVariadic<TypeParam>>()({}));
}
-REGISTER_TYPED_TEST_CASE_P(HashIntTest, BasicUsage);
+REGISTER_TYPED_TEST_SUITE_P(HashIntTest, BasicUsage);
using IntTypes = testing::Types<unsigned char, char, int, int32_t, int64_t,
uint32_t, uint64_t, size_t>;
-INSTANTIATE_TYPED_TEST_CASE_P(My, HashIntTest, IntTypes);
+INSTANTIATE_TYPED_TEST_SUITE_P(My, HashIntTest, IntTypes);
struct StructWithPadding {
char c;
@@ -865,10 +996,10 @@
unsigned char buffer2[kNumStructs * sizeof(StructWithPadding)];
std::memset(buffer2, 255, sizeof(buffer2));
auto* s2 = reinterpret_cast<StructWithPadding*>(buffer2);
- for (int i = 0; i < kNumStructs; ++i) {
+ for (size_t i = 0; i < kNumStructs; ++i) {
SCOPED_TRACE(i);
- s1[i].c = s2[i].c = '0' + i;
- s1[i].i = s2[i].i = i;
+ s1[i].c = s2[i].c = static_cast<char>('0' + i);
+ s1[i].i = s2[i].i = static_cast<int>(i);
ASSERT_FALSE(memcmp(buffer1 + i * sizeof(StructWithPadding),
buffer2 + i * sizeof(StructWithPadding),
sizeof(StructWithPadding)) == 0)
@@ -928,29 +1059,23 @@
Hash<IntAndString>()(IntAndString{0, std::string(63, '0')});
}
-struct TypeErased {
- size_t n;
-
- template <typename H>
- friend H AbslHashValue(H hash_state, const TypeErased& v) {
- v.HashValue(absl::HashState::Create(&hash_state));
- return hash_state;
- }
-
- void HashValue(absl::HashState state) const {
- absl::HashState::combine(std::move(state), n);
- }
-};
-
TEST(HashTest, TypeErased) {
- EXPECT_TRUE((is_hashable<TypeErased>::value));
- EXPECT_TRUE((is_hashable<std::pair<TypeErased, int>>::value));
+ EXPECT_TRUE((is_hashable<TypeErasedValue<size_t>>::value));
+ EXPECT_TRUE((is_hashable<std::pair<TypeErasedValue<size_t>, int>>::value));
- EXPECT_EQ(SpyHash(TypeErased{7}), SpyHash(size_t{7}));
- EXPECT_NE(SpyHash(TypeErased{7}), SpyHash(size_t{13}));
+ EXPECT_EQ(SpyHash(TypeErasedValue<size_t>(7)), SpyHash(size_t{7}));
+ EXPECT_NE(SpyHash(TypeErasedValue<size_t>(7)), SpyHash(size_t{13}));
- EXPECT_EQ(SpyHash(std::make_pair(TypeErased{7}, 17)),
+ EXPECT_EQ(SpyHash(std::make_pair(TypeErasedValue<size_t>(7), 17)),
SpyHash(std::make_pair(size_t{7}, 17)));
+
+ absl::flat_hash_set<absl::flat_hash_set<int>> ss = {{1, 2}, {3, 4}};
+ TypeErasedContainer<absl::flat_hash_set<absl::flat_hash_set<int>>> es = {
+ absl::flat_hash_set<int>{1, 2}, {3, 4}};
+ absl::flat_hash_set<TypeErasedContainer<absl::flat_hash_set<int>>> se = {
+ {1, 2}, {3, 4}};
+ EXPECT_EQ(SpyHash(ss), SpyHash(es));
+ EXPECT_EQ(SpyHash(ss), SpyHash(se));
}
struct ValueWithBoolConversion {
@@ -962,7 +1087,9 @@
namespace std {
template <>
struct hash<ValueWithBoolConversion> {
- size_t operator()(ValueWithBoolConversion v) { return v.i; }
+ size_t operator()(ValueWithBoolConversion v) {
+ return static_cast<size_t>(v.i);
+ }
};
} // namespace std
@@ -975,14 +1102,24 @@
TEST(HashOf, MatchesHashForSingleArgument) {
std::string s = "forty two";
- int i = 42;
double d = 42.0;
std::tuple<int, int> t{4, 2};
+ int i = 42;
+ int neg_i = -42;
+ int16_t i16 = 42;
+ int16_t neg_i16 = -42;
+ int8_t i8 = 42;
+ int8_t neg_i8 = -42;
EXPECT_EQ(absl::HashOf(s), absl::Hash<std::string>{}(s));
- EXPECT_EQ(absl::HashOf(i), absl::Hash<int>{}(i));
EXPECT_EQ(absl::HashOf(d), absl::Hash<double>{}(d));
EXPECT_EQ(absl::HashOf(t), (absl::Hash<std::tuple<int, int>>{}(t)));
+ EXPECT_EQ(absl::HashOf(i), absl::Hash<int>{}(i));
+ EXPECT_EQ(absl::HashOf(neg_i), absl::Hash<int>{}(neg_i));
+ EXPECT_EQ(absl::HashOf(i16), absl::Hash<int16_t>{}(i16));
+ EXPECT_EQ(absl::HashOf(neg_i16), absl::Hash<int16_t>{}(neg_i16));
+ EXPECT_EQ(absl::HashOf(i8), absl::Hash<int8_t>{}(i8));
+ EXPECT_EQ(absl::HashOf(neg_i8), absl::Hash<int8_t>{}(neg_i8));
}
TEST(HashOf, MatchesHashOfTupleForMultipleArguments) {
diff --git a/absl/hash/internal/city.cc b/absl/hash/internal/city.cc
index 5460134..f0d3196 100644
--- a/absl/hash/internal/city.cc
+++ b/absl/hash/internal/city.cc
@@ -97,7 +97,7 @@
uint32_t d = Fetch32(s + (len >> 1));
uint32_t e = Fetch32(s);
uint32_t f = Fetch32(s + len - 4);
- uint32_t h = len;
+ uint32_t h = static_cast<uint32_t>(len);
return fmix(Mur(f, Mur(e, Mur(d, Mur(c, Mur(b, Mur(a, h)))))));
}
@@ -106,15 +106,15 @@
uint32_t b = 0;
uint32_t c = 9;
for (size_t i = 0; i < len; i++) {
- signed char v = s[i];
- b = b * c1 + v;
+ signed char v = static_cast<signed char>(s[i]);
+ b = b * c1 + static_cast<uint32_t>(v);
c ^= b;
}
- return fmix(Mur(b, Mur(len, c)));
+ return fmix(Mur(b, Mur(static_cast<uint32_t>(len), c)));
}
static uint32_t Hash32Len5to12(const char *s, size_t len) {
- uint32_t a = len, b = len * 5, c = 9, d = b;
+ uint32_t a = static_cast<uint32_t>(len), b = a * 5, c = 9, d = b;
a += Fetch32(s);
b += Fetch32(s + len - 4);
c += Fetch32(s + ((len >> 1) & 4));
@@ -129,7 +129,7 @@
}
// len > 24
- uint32_t h = len, g = c1 * len, f = g;
+ uint32_t h = static_cast<uint32_t>(len), g = c1 * h, f = g;
uint32_t a0 = Rotate32(Fetch32(s + len - 4) * c1, 17) * c2;
uint32_t a1 = Rotate32(Fetch32(s + len - 8) * c1, 17) * c2;
@@ -230,11 +230,11 @@
return HashLen16(len + (a << 3), Fetch32(s + len - 4), mul);
}
if (len > 0) {
- uint8_t a = s[0];
- uint8_t b = s[len >> 1];
- uint8_t c = s[len - 1];
+ uint8_t a = static_cast<uint8_t>(s[0]);
+ uint8_t b = static_cast<uint8_t>(s[len >> 1]);
+ uint8_t c = static_cast<uint8_t>(s[len - 1]);
uint32_t y = static_cast<uint32_t>(a) + (static_cast<uint32_t>(b) << 8);
- uint32_t z = len + (static_cast<uint32_t>(c) << 2);
+ uint32_t z = static_cast<uint32_t>(len) + (static_cast<uint32_t>(c) << 2);
return ShiftMix(y * k2 ^ z * k0) * k2;
}
return k2;
diff --git a/absl/hash/internal/city_test.cc b/absl/hash/internal/city_test.cc
index 251d381..1bbf02e 100644
--- a/absl/hash/internal/city_test.cc
+++ b/absl/hash/internal/city_test.cc
@@ -22,6 +22,7 @@
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace hash_internal {
+namespace {
static const uint64_t k0 = 0xc3a5c85c97cb3127ULL;
static const uint64_t kSeed0 = 1234567;
@@ -590,6 +591,7 @@
TestUnchanging(testdata[i], 0, kDataSize);
}
+} // namespace
} // namespace hash_internal
ABSL_NAMESPACE_END
} // namespace absl
diff --git a/absl/hash/internal/hash.cc b/absl/hash/internal/hash.cc
index 06f53a5..93906ef 100644
--- a/absl/hash/internal/hash.cc
+++ b/absl/hash/internal/hash.cc
@@ -21,9 +21,9 @@
uint64_t MixingHashState::CombineLargeContiguousImpl32(
uint64_t state, const unsigned char* first, size_t len) {
while (len >= PiecewiseChunkSize()) {
- state =
- Mix(state, absl::hash_internal::CityHash32(reinterpret_cast<const char*>(first),
- PiecewiseChunkSize()));
+ state = Mix(state,
+ hash_internal::CityHash32(reinterpret_cast<const char*>(first),
+ PiecewiseChunkSize()));
len -= PiecewiseChunkSize();
first += PiecewiseChunkSize();
}
@@ -46,21 +46,22 @@
ABSL_CONST_INIT const void* const MixingHashState::kSeed = &kSeed;
-// The salt array used by Wyhash. This array is NOT the mechanism used to make
-// absl::Hash non-deterministic between program invocations. See `Seed()` for
-// that mechanism.
+// The salt array used by LowLevelHash. This array is NOT the mechanism used to
+// make absl::Hash non-deterministic between program invocations. See `Seed()`
+// for that mechanism.
//
// Any random values are fine. These values are just digits from the decimal
// part of pi.
// https://en.wikipedia.org/wiki/Nothing-up-my-sleeve_number
-constexpr uint64_t kWyhashSalt[5] = {
+constexpr uint64_t kHashSalt[5] = {
uint64_t{0x243F6A8885A308D3}, uint64_t{0x13198A2E03707344},
uint64_t{0xA4093822299F31D0}, uint64_t{0x082EFA98EC4E6C89},
uint64_t{0x452821E638D01377},
};
-uint64_t MixingHashState::WyhashImpl(const unsigned char* data, size_t len) {
- return Wyhash(data, len, Seed(), kWyhashSalt);
+uint64_t MixingHashState::LowLevelHashImpl(const unsigned char* data,
+ size_t len) {
+ return LowLevelHashLenGt16(data, len, Seed(), kHashSalt);
}
} // namespace hash_internal
diff --git a/absl/hash/internal/hash.h b/absl/hash/internal/hash.h
index 90627e0..f4a94f9 100644
--- a/absl/hash/internal/hash.h
+++ b/absl/hash/internal/hash.h
@@ -19,9 +19,16 @@
#ifndef ABSL_HASH_INTERNAL_HASH_H_
#define ABSL_HASH_INTERNAL_HASH_H_
+#ifdef __APPLE__
+#include <Availability.h>
+#include <TargetConditionals.h>
+#endif
+
#include <algorithm>
#include <array>
+#include <bitset>
#include <cmath>
+#include <cstddef>
#include <cstring>
#include <deque>
#include <forward_list>
@@ -35,6 +42,8 @@
#include <string>
#include <tuple>
#include <type_traits>
+#include <unordered_map>
+#include <unordered_set>
#include <utility>
#include <vector>
@@ -42,17 +51,30 @@
#include "absl/base/internal/unaligned_access.h"
#include "absl/base/port.h"
#include "absl/container/fixed_array.h"
-#include "absl/hash/internal/wyhash.h"
+#include "absl/hash/internal/city.h"
+#include "absl/hash/internal/low_level_hash.h"
#include "absl/meta/type_traits.h"
+#include "absl/numeric/bits.h"
#include "absl/numeric/int128.h"
#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
#include "absl/types/variant.h"
#include "absl/utility/utility.h"
-#include "absl/hash/internal/city.h"
+
+#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 201703L && \
+ !defined(_LIBCPP_HAS_NO_FILESYSTEM_LIBRARY)
+#include <filesystem> // NOLINT
+#endif
+
+#ifdef ABSL_HAVE_STD_STRING_VIEW
+#include <string_view>
+#endif
namespace absl {
ABSL_NAMESPACE_BEGIN
+
+class HashState;
+
namespace hash_internal {
// Internal detail: Large buffers are hashed in smaller chunks. This function
@@ -114,24 +136,66 @@
size_t position_;
};
+// 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
//
-// A hash state object represents an intermediate state in the computation
-// of an unspecified hash algorithm. `HashStateBase` provides a CRTP style
-// base class for hash state implementations. Developers adding type support
-// for `absl::Hash` should not rely on any parts of the state object other than
-// the following member functions:
+// 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
+// algorithm. `HashStateBase` provides a CRTP style base class for hash state
+// implementations. Developers adding type support for `absl::Hash` should not
+// rely on any parts of the state object other than the following member
+// functions:
//
// * HashStateBase::combine()
// * HashStateBase::combine_contiguous()
+// * HashStateBase::combine_unordered()
//
-// A derived hash state class of type `H` must provide a static member function
+// A derived hash state class of type `H` must provide a public member function
// with a signature similar to the following:
//
// `static H combine_contiguous(H state, const unsigned char*, size_t)`.
//
+// It must also provide a private template method named RunCombineUnordered.
+//
+// A "consumer" is a 1-arg functor returning void. Its argument is a reference
+// to an inner hash state object, and it may be called multiple times. When
+// called, the functor consumes the entropy from the provided state object,
+// and resets that object to its empty state.
+//
+// A "combiner" is a stateless 2-arg functor returning void. Its arguments are
+// an inner hash state object and an ElementStateConsumer functor. A combiner
+// uses the provided inner hash state object to hash each element of the
+// container, passing the inner hash state object to the consumer after hashing
+// each element.
+//
+// Given these definitions, a derived hash state class of type H
+// must provide a private template method with a signature similar to the
+// following:
+//
+// `template <typename CombinerT>`
+// `static H RunCombineUnordered(H outer_state, CombinerT combiner)`
+//
+// This function is responsible for constructing the inner state object and
+// providing a consumer to the combiner. It uses side effects of the consumer
+// and combiner to mix the state of each element in an order-independent manner,
+// and uses this to return an updated value of `outer_state`.
+//
+// This inside-out approach generates efficient object code in the normal case,
+// but allows us to use stack storage to implement the absl::HashState type
+// erasure mechanism (avoiding heap allocations while hashing).
+//
// `HashStateBase` will provide a complete implementation for a hash state
-// object in terms of this method.
+// object in terms of these two methods.
//
// Example:
//
@@ -140,6 +204,10 @@
// static H combine_contiguous(H state, const unsigned char*, size_t);
// using MyHashState::HashStateBase::combine;
// using MyHashState::HashStateBase::combine_contiguous;
+// using MyHashState::HashStateBase::combine_unordered;
+// private:
+// template <typename CombinerT>
+// static H RunCombineUnordered(H state, CombinerT combiner);
// };
template <typename H>
class HashStateBase {
@@ -180,7 +248,30 @@
template <typename T>
static H combine_contiguous(H state, const T* data, size_t size);
+ template <typename I>
+ static H combine_unordered(H state, I begin, I end);
+
using AbslInternalPiecewiseCombiner = PiecewiseCombiner;
+
+ template <typename T>
+ using is_hashable = absl::hash_internal::is_hashable<T>;
+
+ private:
+ // Common implementation of the iteration step of a "combiner", as described
+ // above.
+ template <typename I>
+ struct CombineUnorderedCallback {
+ I begin;
+ I end;
+
+ template <typename InnerH, typename ElementStateConsumer>
+ void operator()(InnerH inner_state, ElementStateConsumer cb) {
+ for (; begin != end; ++begin) {
+ inner_state = H::combine(std::move(inner_state), *begin);
+ cb(inner_state);
+ }
+ }
+ };
};
// is_uniquely_represented
@@ -328,9 +419,23 @@
return H::combine(std::move(hash_state), category);
}
+// Without this overload, an array decays to a pointer and we hash that, which
+// is not likely to be what the caller intended.
+template <typename H, typename T, size_t N>
+H AbslHashValue(H hash_state, T (&)[N]) {
+ static_assert(
+ sizeof(T) == -1,
+ "Hashing C arrays is not allowed. For string literals, wrap the literal "
+ "in absl::string_view(). To hash the array contents, use "
+ "absl::MakeSpan() or make the array an std::array. To hash the array "
+ "address, use &array[0].");
+ return hash_state;
+}
+
// AbslHashValue() for hashing pointers
template <typename H, typename T>
-H AbslHashValue(H hash_state, T* ptr) {
+std::enable_if_t<std::is_pointer<T>::value, H> AbslHashValue(H hash_state,
+ T ptr) {
auto v = reinterpret_cast<uintptr_t>(ptr);
// Due to alignment, pointers tend to have low bits as zero, and the next few
// bits follow a pattern since they are also multiples of some base value.
@@ -345,17 +450,43 @@
return H::combine(std::move(hash_state), static_cast<void*>(nullptr));
}
+// AbslHashValue() for hashing pointers-to-member
+template <typename H, typename T, typename C>
+H AbslHashValue(H hash_state, T C::*ptr) {
+ auto salient_ptm_size = [](std::size_t n) -> std::size_t {
+#if defined(_MSC_VER)
+ // Pointers-to-member-function on MSVC consist of one pointer plus 0, 1, 2,
+ // or 3 ints. In 64-bit mode, they are 8-byte aligned and thus can contain
+ // padding (namely when they have 1 or 3 ints). The value below is a lower
+ // bound on the number of salient, non-padding bytes that we use for
+ // hashing.
+ if (alignof(T C::*) == alignof(int)) {
+ // No padding when all subobjects have the same size as the total
+ // alignment. This happens in 32-bit mode.
+ return n;
+ } else {
+ // Padding for 1 int (size 16) or 3 ints (size 24).
+ // With 2 ints, the size is 16 with no padding, which we pessimize.
+ return n == 24 ? 20 : n == 16 ? 12 : n;
+ }
+#else
+ // On other platforms, we assume that pointers-to-members do not have
+ // padding.
+#ifdef __cpp_lib_has_unique_object_representations
+ static_assert(std::has_unique_object_representations<T C::*>::value);
+#endif // __cpp_lib_has_unique_object_representations
+ return n;
+#endif
+ };
+ return H::combine_contiguous(std::move(hash_state),
+ reinterpret_cast<unsigned char*>(&ptr),
+ salient_ptm_size(sizeof ptr));
+}
+
// -----------------------------------------------------------------------------
// AbslHashValue for Composite Types
// -----------------------------------------------------------------------------
-// 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;
-
// AbslHashValue() for hashing pairs
template <typename H, typename T1, typename T2>
typename std::enable_if<is_hashable<T1>::value && is_hashable<T2>::value,
@@ -413,14 +544,15 @@
// the same character sequence. These types are:
//
// - `absl::Cord`
-// - `std::string` (and std::basic_string<char, std::char_traits<char>, A> for
-// any allocator A)
-// - `absl::string_view` and `std::string_view`
+// - `std::string` (and std::basic_string<T, std::char_traits<T>, A> for
+// any allocator A and any T in {char, wchar_t, char16_t, char32_t})
+// - `absl::string_view`, `std::string_view`, `std::wstring_view`,
+// `std::u16string_view`, and `std::u32_string_view`.
//
-// For simplicity, we currently support only `char` strings. This support may
-// be broadened, if necessary, but with some caution - this overload would
-// misbehave in cases where the traits' `eq()` member isn't equivalent to `==`
-// on the underlying character type.
+// For simplicity, we currently support only strings built on `char`, `wchar_t`,
+// `char16_t`, or `char32_t`. This support may be broadened, if necessary, but
+// with some caution - this overload would misbehave in cases where the traits'
+// `eq()` member isn't equivalent to `==` on the underlying character type.
template <typename H>
H AbslHashValue(H hash_state, absl::string_view str) {
return H::combine(
@@ -441,6 +573,44 @@
str.size());
}
+#ifdef ABSL_HAVE_STD_STRING_VIEW
+
+// Support std::wstring_view, std::u16string_view and std::u32string_view.
+template <typename Char, typename H,
+ typename = absl::enable_if_t<std::is_same<Char, wchar_t>::value ||
+ std::is_same<Char, char16_t>::value ||
+ std::is_same<Char, char32_t>::value>>
+H AbslHashValue(H hash_state, std::basic_string_view<Char> str) {
+ return H::combine(
+ H::combine_contiguous(std::move(hash_state), str.data(), str.size()),
+ str.size());
+}
+
+#endif // ABSL_HAVE_STD_STRING_VIEW
+
+#if defined(__cpp_lib_filesystem) && __cpp_lib_filesystem >= 201703L && \
+ !defined(_LIBCPP_HAS_NO_FILESYSTEM_LIBRARY) && \
+ (!defined(__ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__) || \
+ __ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__ >= 130000)
+
+#define ABSL_INTERNAL_STD_FILESYSTEM_PATH_HASH_AVAILABLE 1
+
+// Support std::filesystem::path. The SFINAE is required because some string
+// types are implicitly convertible to std::filesystem::path.
+template <typename Path, typename H,
+ typename = absl::enable_if_t<
+ std::is_same_v<Path, std::filesystem::path>>>
+H AbslHashValue(H hash_state, const Path& path) {
+ // This is implemented by deferring to the standard library to compute the
+ // hash. The standard library requires that for two paths, `p1 == p2`, then
+ // `hash_value(p1) == hash_value(p2)`. `AbslHashValue` has the same
+ // requirement. Since `operator==` does platform specific matching, deferring
+ // to the standard library is the simplest approach.
+ return H::combine(std::move(hash_state), std::filesystem::hash_value(path));
+}
+
+#endif // ABSL_INTERNAL_STD_FILESYSTEM_PATH_HASH_AVAILABLE
+
// -----------------------------------------------------------------------------
// AbslHashValue for Sequence Containers
// -----------------------------------------------------------------------------
@@ -489,8 +659,9 @@
// AbslHashValue for hashing std::vector
//
-// Do not use this for vector<bool>. It does not have a .data(), and a fallback
-// for std::hash<> is most likely faster.
+// Do not use this for vector<bool> on platforms that have a working
+// implementation of std::hash. It does not have a .data(), and a fallback for
+// std::hash<> is most likely faster.
template <typename H, typename T, typename Allocator>
typename std::enable_if<is_hashable<T>::value && !std::is_same<T, bool>::value,
H>::type
@@ -500,6 +671,44 @@
vector.size());
}
+// AbslHashValue special cases for hashing std::vector<bool>
+
+#if defined(ABSL_IS_BIG_ENDIAN) && \
+ (defined(__GLIBCXX__) || defined(__GLIBCPP__))
+
+// std::hash in libstdc++ does not work correctly with vector<bool> on Big
+// Endian platforms therefore we need to implement a custom AbslHashValue for
+// it. More details on the bug:
+// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=102531
+template <typename H, typename T, typename Allocator>
+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) {
+ typename H::AbslInternalPiecewiseCombiner combiner;
+ for (const auto& i : vector) {
+ unsigned char c = static_cast<unsigned char>(i);
+ hash_state = combiner.add_buffer(std::move(hash_state), &c, sizeof(c));
+ }
+ return H::combine(combiner.finalize(std::move(hash_state)), vector.size());
+}
+#else
+// When not working around the libstdc++ bug above, we still have to contend
+// with the fact that std::hash<vector<bool>> is often poor quality, hashing
+// directly on the internal words and on no other state. On these platforms,
+// vector<bool>{1, 1} and vector<bool>{1, 1, 0} hash to the same value.
+//
+// Mixing in the size (as we do in our other vector<> implementations) on top
+// of the library-provided hash implementation avoids this QOI issue.
+template <typename H, typename T, typename Allocator>
+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(std::move(hash_state),
+ std::hash<std::vector<T, Allocator>>{}(vector),
+ vector.size());
+}
+#endif
+
// -----------------------------------------------------------------------------
// AbslHashValue for Ordered Associative Containers
// -----------------------------------------------------------------------------
@@ -550,6 +759,55 @@
}
// -----------------------------------------------------------------------------
+// AbslHashValue for Unordered Associative Containers
+// -----------------------------------------------------------------------------
+
+// AbslHashValue for hashing std::unordered_set
+template <typename H, typename Key, typename Hash, typename KeyEqual,
+ typename Alloc>
+typename std::enable_if<is_hashable<Key>::value, H>::type AbslHashValue(
+ H hash_state, const std::unordered_set<Key, Hash, KeyEqual, Alloc>& s) {
+ return H::combine(
+ H::combine_unordered(std::move(hash_state), s.begin(), s.end()),
+ s.size());
+}
+
+// AbslHashValue for hashing std::unordered_multiset
+template <typename H, typename Key, typename Hash, typename KeyEqual,
+ typename Alloc>
+typename std::enable_if<is_hashable<Key>::value, H>::type AbslHashValue(
+ H hash_state,
+ const std::unordered_multiset<Key, Hash, KeyEqual, Alloc>& s) {
+ return H::combine(
+ H::combine_unordered(std::move(hash_state), s.begin(), s.end()),
+ s.size());
+}
+
+// AbslHashValue for hashing std::unordered_set
+template <typename H, typename Key, typename T, typename Hash,
+ typename KeyEqual, typename Alloc>
+typename std::enable_if<is_hashable<Key>::value && is_hashable<T>::value,
+ H>::type
+AbslHashValue(H hash_state,
+ const std::unordered_map<Key, T, Hash, KeyEqual, Alloc>& s) {
+ return H::combine(
+ H::combine_unordered(std::move(hash_state), s.begin(), s.end()),
+ s.size());
+}
+
+// AbslHashValue for hashing std::unordered_multiset
+template <typename H, typename Key, typename T, typename Hash,
+ typename KeyEqual, typename Alloc>
+typename std::enable_if<is_hashable<Key>::value && is_hashable<T>::value,
+ H>::type
+AbslHashValue(H hash_state,
+ const std::unordered_multimap<Key, T, Hash, KeyEqual, Alloc>& s) {
+ return H::combine(
+ H::combine_unordered(std::move(hash_state), s.begin(), s.end()),
+ s.size());
+}
+
+// -----------------------------------------------------------------------------
// AbslHashValue for Wrapper Types
// -----------------------------------------------------------------------------
@@ -592,9 +850,28 @@
// AbslHashValue for Other Types
// -----------------------------------------------------------------------------
-// AbslHashValue for hashing std::bitset is not defined, for the same reason as
-// for vector<bool> (see std::vector above): It does not expose the raw bytes,
-// and a fallback to std::hash<> is most likely faster.
+// AbslHashValue for hashing std::bitset is not defined on Little Endian
+// platforms, for the same reason as for vector<bool> (see std::vector above):
+// It does not expose the raw bytes, and a fallback to std::hash<> is most
+// likely faster.
+
+#if defined(ABSL_IS_BIG_ENDIAN) && \
+ (defined(__GLIBCXX__) || defined(__GLIBCPP__))
+// AbslHashValue for hashing std::bitset
+//
+// std::hash in libstdc++ does not work correctly with std::bitset on Big Endian
+// platforms therefore we need to implement a custom AbslHashValue for it. More
+// details on the bug: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=102531
+template <typename H, size_t N>
+H AbslHashValue(H hash_state, const std::bitset<N>& set) {
+ typename H::AbslInternalPiecewiseCombiner combiner;
+ for (size_t i = 0; i < N; i++) {
+ unsigned char c = static_cast<unsigned char>(set[i]);
+ hash_state = combiner.add_buffer(std::move(hash_state), &c, sizeof(c));
+ }
+ return H::combine(combiner.finalize(std::move(hash_state)), N);
+}
+#endif
// -----------------------------------------------------------------------------
@@ -725,8 +1002,8 @@
#endif // ABSL_HAVE_INTRINSIC_INT128
static constexpr uint64_t kMul =
- sizeof(size_t) == 4 ? uint64_t{0xcc9e2d51}
- : uint64_t{0x9ddfea08eb382d69};
+ sizeof(size_t) == 4 ? uint64_t{0xcc9e2d51}
+ : uint64_t{0x9ddfea08eb382d69};
template <typename T>
using IntegralFastPath =
@@ -759,7 +1036,8 @@
// The result should be the same as running the whole algorithm, but faster.
template <typename T, absl::enable_if_t<IntegralFastPath<T>::value, int> = 0>
static size_t hash(T value) {
- return static_cast<size_t>(Mix(Seed(), static_cast<uint64_t>(value)));
+ return static_cast<size_t>(
+ Mix(Seed(), static_cast<std::make_unsigned_t<T>>(value)));
}
// Overload of MixingHashState::hash()
@@ -773,6 +1051,31 @@
// move-only ensures that there is only one non-moved-from object.
MixingHashState() : state_(Seed()) {}
+ friend class MixingHashState::HashStateBase;
+
+ template <typename CombinerT>
+ static MixingHashState RunCombineUnordered(MixingHashState state,
+ CombinerT combiner) {
+ uint64_t unordered_state = 0;
+ combiner(MixingHashState{}, [&](MixingHashState& inner_state) {
+ // Add the hash state of the element to the running total, but mix the
+ // carry bit back into the low bit. This in intended to avoid losing
+ // entropy to overflow, especially when unordered_multisets contain
+ // multiple copies of the same value.
+ auto element_state = inner_state.state_;
+ unordered_state += element_state;
+ if (unordered_state < element_state) {
+ ++unordered_state;
+ }
+ inner_state = MixingHashState{};
+ });
+ return MixingHashState::combine(std::move(state), unordered_state);
+ }
+
+ // Allow the HashState type-erasure implementation to invoke
+ // RunCombinedUnordered() directly.
+ friend class absl::HashState;
+
// 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
@@ -818,7 +1121,7 @@
uint64_t most_significant = low_mem;
uint64_t least_significant = high_mem;
#endif
- return {least_significant, most_significant >> (128 - len * 8)};
+ return {least_significant, most_significant};
}
// Reads 4 to 8 bytes from p. Zero pads to fill uint64_t.
@@ -838,6 +1141,7 @@
// Reads 1 to 3 bytes from p. Zero pads to fill uint32_t.
static uint32_t Read1To3(const unsigned char* p, size_t len) {
+ // The trick used by this implementation is to avoid branches if possible.
unsigned char mem0 = p[0];
unsigned char mem1 = p[len / 2];
unsigned char mem2 = p[len - 1];
@@ -847,7 +1151,7 @@
unsigned char significant0 = mem0;
#else
unsigned char significant2 = mem0;
- unsigned char significant1 = mem1;
+ unsigned char significant1 = len == 2 ? mem0 : mem1;
unsigned char significant0 = mem2;
#endif
return static_cast<uint32_t>(significant0 | //
@@ -856,15 +1160,10 @@
}
ABSL_ATTRIBUTE_ALWAYS_INLINE static uint64_t Mix(uint64_t state, uint64_t v) {
-#if defined(__aarch64__)
- // On AArch64, calculating a 128-bit product is inefficient, because it
- // requires a sequence of two instructions to calculate the upper and lower
- // halves of the result.
- using MultType = uint64_t;
-#else
+ // Though the 128-bit product on AArch64 needs two instructions, it is
+ // still a good balance between speed and hash quality.
using MultType =
absl::conditional_t<sizeof(size_t) == 4, uint64_t, uint128>;
-#endif
// We do the addition in 64-bit space to make sure the 128-bit
// multiplication is fast. If we were to do it as MultType the compiler has
// to assume that the high word is non-zero and needs to perform 2
@@ -874,16 +1173,16 @@
return static_cast<uint64_t>(m ^ (m >> (sizeof(m) * 8 / 2)));
}
- // An extern to avoid bloat on a direct call to Wyhash() with fixed values for
- // both the seed and salt parameters.
- static uint64_t WyhashImpl(const unsigned char* data, size_t len);
+ // 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 WyhashImpl(data, len);
+ return LowLevelHashImpl(data, len);
#else
- return absl::hash_internal::CityHash64(reinterpret_cast<const char*>(data), len);
+ return hash_internal::CityHash64(reinterpret_cast<const char*>(data), len);
#endif
}
@@ -905,7 +1204,8 @@
// probably per-build and not per-process.
ABSL_ATTRIBUTE_ALWAYS_INLINE static uint64_t Seed() {
#if (!defined(__clang__) || __clang_major__ > 11) && \
- !defined(__apple_build_version__)
+ (!defined(__apple_build_version__) || \
+ __apple_build_version__ >= 19558921) // Xcode 12
return static_cast<uint64_t>(reinterpret_cast<uintptr_t>(&kSeed));
#else
// Workaround the absence of
@@ -929,7 +1229,7 @@
if (ABSL_PREDICT_FALSE(len > PiecewiseChunkSize())) {
return CombineLargeContiguousImpl32(state, first, len);
}
- v = absl::hash_internal::CityHash32(reinterpret_cast<const char*>(first), len);
+ v = hash_internal::CityHash32(reinterpret_cast<const char*>(first), len);
} else if (len >= 4) {
v = Read4To8(first, len);
} else if (len > 0) {
@@ -945,8 +1245,8 @@
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 Wyhash or CityHash depending on the platform, for
- // small ones we just use a multiplicative hash.
+ // For large values we use LowLevelHash or CityHash depending on the platform,
+ // for small ones we just use a multiplicative hash.
uint64_t v;
if (len > 16) {
if (ABSL_PREDICT_FALSE(len > PiecewiseChunkSize())) {
@@ -954,9 +1254,22 @@
}
v = Hash64(first, len);
} else if (len > 8) {
+ // This hash function was constructed by the ML-driven algorithm discovery
+ // using reinforcement learning. We fed the agent lots of inputs from
+ // microbenchmarks, SMHasher, low hamming distance from generated inputs and
+ // picked up the one that was good on micro and macrobenchmarks.
auto p = Read9To16(first, len);
- state = Mix(state, p.first);
- v = p.second;
+ uint64_t lo = p.first;
+ uint64_t hi = p.second;
+ // Rotation by 53 was found to be most often useful when discovering these
+ // hashing algorithms with ML techniques.
+ lo = absl::rotr(lo, 53);
+ state += kMul;
+ lo += state;
+ state ^= hi;
+ uint128 m = state;
+ m *= lo;
+ return static_cast<uint64_t>(m ^ (m >> 64));
} else if (len >= 4) {
v = Read4To8(first, len);
} else if (len > 0) {
@@ -1007,6 +1320,14 @@
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) {
+ return H::RunCombineUnordered(std::move(state),
+ CombineUnorderedCallback<I>{begin, end});
+}
+
// HashStateBase::PiecewiseCombiner::add_buffer()
template <typename H>
H PiecewiseCombiner::add_buffer(H state, const unsigned char* data,
diff --git a/absl/hash/internal/hash_test.h b/absl/hash/internal/hash_test.h
new file mode 100644
index 0000000..9963dc0
--- /dev/null
+++ b/absl/hash/internal/hash_test.h
@@ -0,0 +1,87 @@
+// Copyright 2023 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Common code shared between absl/hash/hash_test.cc and
+// absl/hash/hash_instantiated_test.cc.
+
+#ifndef ABSL_HASH_INTERNAL_HASH_TEST_H_
+#define ABSL_HASH_INTERNAL_HASH_TEST_H_
+
+#include <type_traits>
+#include <utility>
+
+#include "absl/base/config.h"
+#include "absl/hash/hash.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace hash_test_internal {
+
+// Utility wrapper of T for the purposes of testing the `AbslHash` type erasure
+// mechanism. `TypeErasedValue<T>` can be constructed with a `T`, and can
+// be compared and hashed. However, all hashing goes through the hashing
+// type-erasure framework.
+template <typename T>
+class TypeErasedValue {
+ public:
+ TypeErasedValue() = default;
+ TypeErasedValue(const TypeErasedValue&) = default;
+ TypeErasedValue(TypeErasedValue&&) = default;
+ explicit TypeErasedValue(const T& n) : n_(n) {}
+
+ template <typename H>
+ friend H AbslHashValue(H hash_state, const TypeErasedValue& v) {
+ v.HashValue(absl::HashState::Create(&hash_state));
+ return hash_state;
+ }
+
+ void HashValue(absl::HashState state) const {
+ absl::HashState::combine(std::move(state), n_);
+ }
+
+ bool operator==(const TypeErasedValue& rhs) const { return n_ == rhs.n_; }
+ bool operator!=(const TypeErasedValue& rhs) const { return !(*this == rhs); }
+
+ private:
+ T n_;
+};
+
+// A TypeErasedValue refinement, for containers. It exposes the wrapped
+// `value_type` and is constructible from an initializer list.
+template <typename T>
+class TypeErasedContainer : public TypeErasedValue<T> {
+ public:
+ using value_type = typename T::value_type;
+ TypeErasedContainer() = default;
+ TypeErasedContainer(const TypeErasedContainer&) = default;
+ TypeErasedContainer(TypeErasedContainer&&) = default;
+ explicit TypeErasedContainer(const T& n) : TypeErasedValue<T>(n) {}
+ TypeErasedContainer(std::initializer_list<value_type> init_list)
+ : TypeErasedContainer(T(init_list.begin(), init_list.end())) {}
+ // one-argument constructor of value type T, to appease older toolchains that
+ // get confused by one-element initializer lists in some contexts
+ explicit TypeErasedContainer(const value_type& v)
+ : TypeErasedContainer(T(&v, &v + 1)) {}
+};
+
+// Helper trait to verify if T is hashable. We use absl::Hash's poison status to
+// detect it.
+template <typename T>
+using is_hashable = std::is_default_constructible<absl::Hash<T>>;
+
+} // namespace hash_test_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_HASH_INTERNAL_HASH_TEST_H_
diff --git a/absl/hash/internal/low_level_hash.cc b/absl/hash/internal/low_level_hash.cc
new file mode 100644
index 0000000..43de672
--- /dev/null
+++ b/absl/hash/internal/low_level_hash.cc
@@ -0,0 +1,152 @@
+// 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 <cstddef>
+#include <cstdint>
+
+#include "absl/base/internal/unaligned_access.h"
+#include "absl/base/prefetch.h"
+#include "absl/numeric/int128.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace hash_internal {
+
+static uint64_t Mix(uint64_t v0, uint64_t v1) {
+ absl::uint128 p = v0;
+ p *= v1;
+ return absl::Uint128Low64(p) ^ absl::Uint128High64(p);
+}
+
+uint64_t LowLevelHashLenGt16(const void* data, size_t len, uint64_t seed,
+ const uint64_t salt[5]) {
+ // Prefetch the cacheline that data resides in.
+ PrefetchToLocalCache(data);
+ const uint8_t* ptr = static_cast<const uint8_t*>(data);
+ uint64_t starting_length = static_cast<uint64_t>(len);
+ uint64_t current_state = seed ^ salt[0];
+
+ 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 two separate hash states
+ // which we will then hash together.
+ 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) {
+ 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);
+ current_state = cs0 ^ cs1;
+
+ ptr += 32;
+ len -= 32;
+ }
+
+ // We now have a data `ptr` with at most 32 bytes and the current state
+ // of the hashing state machine stored in current_state.
+ if (len > 16) {
+ uint64_t a = absl::base_internal::UnalignedLoad64(ptr);
+ uint64_t b = absl::base_internal::UnalignedLoad64(ptr + 8);
+
+ current_state = Mix(a ^ salt[1], b ^ current_state);
+
+ ptr += 16;
+ len -= 16;
+ }
+
+ // We now have a data `ptr` with at least 1 and at most 16 bytes. But we can
+ // safely read from `ptr + len - 16`.
+ uint64_t a = absl::base_internal::UnalignedLoad64(ptr + len - 16);
+ uint64_t b = absl::base_internal::UnalignedLoad64(ptr + len - 8);
+
+ return Mix(a ^ salt[1] ^ starting_length, b ^ current_state);
+}
+
+uint64_t LowLevelHash(const void* data, size_t len, uint64_t seed,
+ const uint64_t salt[5]) {
+ if (len > 16) return LowLevelHashLenGt16(data, len, seed, salt);
+
+ // Prefetch the cacheline that data resides in.
+ PrefetchToLocalCache(data);
+ const uint8_t* ptr = static_cast<const uint8_t*>(data);
+ uint64_t starting_length = static_cast<uint64_t>(len);
+ uint64_t current_state = seed ^ salt[0];
+ if (len == 0) return current_state;
+
+ uint64_t a = 0;
+ uint64_t b = 0;
+
+ // We now have a data `ptr` with at least 1 and at most 16 bytes.
+ if (len > 8) {
+ // When we have at least 9 and at most 16 bytes, set A to the first 64
+ // bits of the input and B to the last 64 bits of the input. Yes, they
+ // will overlap in the middle if we are working with less than the full 16
+ // bytes.
+ a = absl::base_internal::UnalignedLoad64(ptr);
+ b = absl::base_internal::UnalignedLoad64(ptr + len - 8);
+ } else if (len > 3) {
+ // If we have at least 4 and at most 8 bytes, set A to the first 32
+ // bits and B to the last 32 bits.
+ a = absl::base_internal::UnalignedLoad32(ptr);
+ b = absl::base_internal::UnalignedLoad32(ptr + len - 4);
+ } else {
+ // If we have at least 1 and at most 3 bytes, read 2 bytes into A and the
+ // other byte into B, with some adjustments.
+ a = static_cast<uint64_t>((ptr[0] << 8) | ptr[len - 1]);
+ b = static_cast<uint64_t>(ptr[len >> 1]);
+ }
+
+ return Mix(a ^ salt[1] ^ starting_length, b ^ current_state);
+}
+
+} // 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
new file mode 100644
index 0000000..d460e35
--- /dev/null
+++ b/absl/hash/internal/low_level_hash.h
@@ -0,0 +1,54 @@
+// 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.
+//
+// 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 LowLevelHash(const void* data, size_t len, uint64_t seed,
+ const uint64_t salt[5]);
+
+// Same as above except the length must be greater than 16.
+uint64_t LowLevelHashLenGt16(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
new file mode 100644
index 0000000..ea781dd
--- /dev/null
+++ b/absl/hash/internal/low_level_hash_test.cc
@@ -0,0 +1,532 @@
+// 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 <cinttypes>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/strings/escaping.h"
+
+#define UPDATE_GOLDEN 0
+
+namespace {
+
+static const uint64_t kSalt[5] = {0xa0761d6478bd642f, 0xe7037ed1a0b428dbl,
+ 0x8ebc6af09c88c6e3, 0x589965cc75374cc3l,
+ 0x1d8e4e27c47d124f};
+
+TEST(LowLevelHashTest, VerifyGolden) {
+ constexpr size_t kNumGoldenOutputs = 134;
+ static struct {
+ absl::string_view base64_data;
+ uint64_t seed;
+ } cases[] = {
+ {"", uint64_t{0xec42b7ab404b8acb}},
+ {"ICAg", uint64_t{0}},
+ {"YWFhYQ==", uint64_t{0}},
+ {"AQID", uint64_t{0}},
+ {"AQIDBA==", uint64_t{0}},
+ {"dGhpcmRfcGFydHl8d3loYXNofDY0", uint64_t{0}},
+ {"Zw==", uint64_t{0xeeee074043a3ee0f}},
+ {"xmk=", uint64_t{0x857902089c393de}},
+ {"c1H/", uint64_t{0x993df040024ca3af}},
+ {"SuwpzQ==", uint64_t{0xc4e4c2acea740e96}},
+ {"uqvy++M=", uint64_t{0x6a214b3db872d0cf}},
+ {"RnzCVPgb", uint64_t{0x44343db6a89dba4d}},
+ {"6OeNdlouYw==", uint64_t{0x77b5d6d1ae1dd483}},
+ {"M5/JmmYyDbc=", uint64_t{0x89ab8ecb44d221f1}},
+ {"MVijWiVdBRdY", uint64_t{0x60244b17577ca81b}},
+ {"6V7Uq7LNxpu0VA==", uint64_t{0x59a08dcee0717067}},
+ {"EQ6CdEEhPdyHcOk=", uint64_t{0xf5f20db3ade57396}},
+ {"PqFB4fxnPgF+l+rc", uint64_t{0xbf8dee0751ad3efb}},
+ {"a5aPOFwq7LA7+zKvPA==", uint64_t{0x6b7a06b268d63e30}},
+ {"VOwY21wCGv5D+/qqOvs=", uint64_t{0xb8c37f0ae0f54c82}},
+ {"KdHmBTx8lHXYvmGJ+Vy7", uint64_t{0x9fcbed0c38e50eef}},
+ {"qJkPlbHr8bMF7/cA6aE65Q==", uint64_t{0x2af4bade1d8e3a1d}},
+ {"ygvL0EhHZL0fIx6oHHtkxRQ=", uint64_t{0x714e3aa912da2f2c}},
+ {"c1rFXkt5YztwZCQRngncqtSs", uint64_t{0xf5ee75e3cbb82c1c}},
+ {"8hsQrzszzeNQSEcVXLtvIhm6mw==", uint64_t{0x620e7007321b93b9}},
+ {"ffUL4RocfyP4KfikGxO1yk7omDI=", uint64_t{0xc08528cac2e551fc}},
+ {"OOB5TT00vF9Od/rLbAWshiErqhpV", uint64_t{0x6a1debf9cc3ad39}},
+ {"or5wtXM7BFzTNpSzr+Lw5J5PMhVJ/Q==", uint64_t{0x7e0a3c88111fc226}},
+ {"gk6pCHDUsoopVEiaCrzVDhioRKxb844=", uint64_t{0x1301fef15df39edb}},
+ {"TNctmwlC5QbEM6/No4R/La3UdkfeMhzs", uint64_t{0x64e181f3d5817ab}},
+ {"SsQw9iAjhWz7sgcE9OwLuSC6hsM+BfHs2Q==", uint64_t{0xafafc44961078ecb}},
+ {"ZzO3mVCj4xTT2TT3XqDyEKj2BZQBvrS8RHg=", uint64_t{0x4f7bb45549250094}},
+ {"+klp5iPQGtppan5MflEls0iEUzqU+zGZkDJX", uint64_t{0xa30061abaa2818c}},
+ {"RO6bvOnlJc8I9eniXlNgqtKy0IX6VNg16NRmgg==",
+ uint64_t{0xd902ee3e44a5705f}},
+ {"ZJjZqId1ZXBaij9igClE3nyliU5XWdNRrayGlYA=", uint64_t{0x316d36da516f583}},
+ {"7BfkhfGMDGbxfMB8uyL85GbaYQtjr2K8g7RpLzr/",
+ uint64_t{0x402d83f9f834f616}},
+ {"rycWk6wHH7htETQtje9PidS2YzXBx+Qkg2fY7ZYS7A==",
+ uint64_t{0x9c604164c016b72c}},
+ {"RTkC2OUK+J13CdGllsH0H5WqgspsSa6QzRZouqx6pvI=",
+ uint64_t{0x3f4507e01f9e73ba}},
+ {"tKjKmbLCNyrLCM9hycOAXm4DKNpM12oZ7dLTmUx5iwAi",
+ uint64_t{0xc3fe0d5be8d2c7c7}},
+ {"VprUGNH+5NnNRaORxgH/ySrZFQFDL+4VAodhfBNinmn8cg==",
+ uint64_t{0x531858a40bfa7ea1}},
+ {"gc1xZaY+q0nPcUvOOnWnT3bqfmT/geth/f7Dm2e/DemMfk4=",
+ uint64_t{0x86689478a7a7e8fa}},
+ {"Mr35fIxqx1ukPAL0su1yFuzzAU3wABCLZ8+ZUFsXn47UmAph",
+ uint64_t{0x4ec948b8e7f27288}},
+ {"A9G8pw2+m7+rDtWYAdbl8tb2fT7FFo4hLi2vAsa5Y8mKH3CX3g==",
+ uint64_t{0xce46c7213c10032}},
+ {"DFaJGishGwEHDdj9ixbCoaTjz9KS0phLNWHVVdFsM93CvPft3hM=",
+ uint64_t{0xf63e96ee6f32a8b6}},
+ {"7+Ugx+Kr3aRNgYgcUxru62YkTDt5Hqis+2po81hGBkcrJg4N0uuy",
+ uint64_t{0x1cfe85e65fc5225}},
+ {"H2w6O8BUKqu6Tvj2xxaecxEI2wRgIgqnTTG1WwOgDSINR13Nm4d4Vg==",
+ uint64_t{0x45c474f1cee1d2e8}},
+ {"1XBMnIbqD5jy65xTDaf6WtiwtdtQwv1dCVoqpeKj+7cTR1SaMWMyI04=",
+ uint64_t{0x6e024e14015f329c}},
+ {"znZbdXG2TSFrKHEuJc83gPncYpzXGbAebUpP0XxzH0rpe8BaMQ17nDbt",
+ uint64_t{0x760c40502103ae1c}},
+ {"ylu8Atu13j1StlcC1MRMJJXIl7USgDDS22HgVv0WQ8hx/8pNtaiKB17hCQ==",
+ uint64_t{0x17fd05c3c560c320}},
+ {"M6ZVVzsd7vAvbiACSYHioH/440dp4xG2mLlBnxgiqEvI/aIEGpD0Sf4VS0g=",
+ uint64_t{0x8b34200a6f8e90d9}},
+ {"li3oFSXLXI+ubUVGJ4blP6mNinGKLHWkvGruun85AhVn6iuMtocbZPVhqxzn",
+ uint64_t{0x6be89e50818bdf69}},
+ {"kFuQHuUCqBF3Tc3hO4dgdIp223ShaCoog48d5Do5zMqUXOh5XpGK1t5XtxnfGA==",
+ uint64_t{0xfb389773315b47d8}},
+ {"jWmOad0v0QhXVJd1OdGuBZtDYYS8wBVHlvOeTQx9ZZnm8wLEItPMeihj72E0nWY=",
+ uint64_t{0x4f2512a23f61efee}},
+ {"z+DHU52HaOQdW4JrZwDQAebEA6rm13Zg/9lPYA3txt3NjTBqFZlOMvTRnVzRbl23",
+ uint64_t{0x59ccd92fc16c6fda}},
+ {"MmBiGDfYeTayyJa/tVycg+rN7f9mPDFaDc+23j0TlW9094er0ADigsl4QX7V3gG/qw==",
+ uint64_t{0x25c5a7f5bd330919}},
+ {"774RK+9rOL4iFvs1q2qpo/JVc/I39buvNjqEFDtDvyoB0FXxPI2vXqOrk08VPfIHkmU=",
+ uint64_t{0x51df4174d34c97d7}},
+ {"+slatXiQ7/2lK0BkVUI1qzNxOOLP3I1iK6OfHaoxgqT63FpzbElwEXSwdsryq3UlHK0I",
+ uint64_t{0x80ce6d76f89cb57}},
+ {"64mVTbQ47dHjHlOHGS/hjJwr/"
+ "K2frCNpn87exOqMzNUVYiPKmhCbfS7vBUce5tO6Ec9osQ==",
+ uint64_t{0x20961c911965f684}},
+ {"fIsaG1r530SFrBqaDj1kqE0AJnvvK8MNEZbII2Yw1OK77v0V59xabIh0B5axaz/"
+ "+a2V5WpA=",
+ uint64_t{0x4e5b926ec83868e7}},
+ {"PGih0zDEOWCYGxuHGDFu9Ivbff/"
+ "iE7BNUq65tycTR2R76TerrXALRosnzaNYO5fjFhTi+CiS",
+ uint64_t{0x3927b30b922eecef}},
+ {"RnpA/"
+ "zJnEnnLjmICORByRVb9bCOgxF44p3VMiW10G7PvW7IhwsWajlP9kIwNA9FjAD2GoQHk2Q="
+ "=",
+ uint64_t{0xbd0291284a49b61c}},
+ {"qFklMceaTHqJpy2qavJE+EVBiNFOi6OxjOA3LeIcBop1K7w8xQi3TrDk+"
+ "BrWPRIbfprszSaPfrI=",
+ uint64_t{0x73a77c575bcc956}},
+ {"cLbfUtLl3EcQmITWoTskUR8da/VafRDYF/ylPYwk7/"
+ "zazk6ssyrzxMN3mmSyvrXR2yDGNZ3WDrTT",
+ uint64_t{0x766a0e2ade6d09a6}},
+ {"s/"
+ "Jf1+"
+ "FbsbCpXWPTUSeWyMH6e4CvTFvPE5Fs6Z8hvFITGyr0dtukHzkI84oviVLxhM1xMxrMAy1db"
+ "w==",
+ uint64_t{0x2599f4f905115869}},
+ {"FvyQ00+j7nmYZVQ8hI1Edxd0AWplhTfWuFGiu34AK5X8u2hLX1bE97sZM0CmeLe+"
+ "7LgoUT1fJ/axybE=",
+ uint64_t{0xd8256e5444d21e53}},
+ {"L8ncxMaYLBH3g9buPu8hfpWZNlOF7nvWLNv9IozH07uQsIBWSKxoPy8+"
+ "LW4tTuzC6CIWbRGRRD1sQV/4",
+ uint64_t{0xf664a91333fb8dfd}},
+ {"CDK0meI07yrgV2kQlZZ+"
+ "wuVqhc2NmzqeLH7bmcA6kchsRWFPeVF5Wqjjaj556ABeUoUr3yBmfU3kWOakkg==",
+ uint64_t{0x9625b859be372cd1}},
+ {"d23/vc5ONh/"
+ "HkMiq+gYk4gaCNYyuFKwUkvn46t+dfVcKfBTYykr4kdvAPNXGYLjM4u1YkAEFpJP+"
+ "nX7eOvs=",
+ uint64_t{0x7b99940782e29898}},
+ {"NUR3SRxBkxTSbtQORJpu/GdR6b/h6sSGfsMj/KFd99ahbh+9r7LSgSGmkGVB/"
+ "mGoT0pnMTQst7Lv2q6QN6Vm",
+ uint64_t{0x4fe12fa5383b51a8}},
+ {"2BOFlcI3Z0RYDtS9T9Ie9yJoXlOdigpPeeT+CRujb/"
+ "O39Ih5LPC9hP6RQk1kYESGyaLZZi3jtabHs7DiVx/VDg==",
+ uint64_t{0xe2ccb09ac0f5b4b6}},
+ {"FF2HQE1FxEvWBpg6Z9zAMH+Zlqx8S1JD/"
+ "wIlViL6ZDZY63alMDrxB0GJQahmAtjlm26RGLnjW7jmgQ4Ie3I+014=",
+ uint64_t{0x7d0a37adbd7b753b}},
+ {"tHmO7mqVL/PX11nZrz50Hc+M17Poj5lpnqHkEN+4bpMx/"
+ "YGbkrGOaYjoQjgmt1X2QyypK7xClFrjeWrCMdlVYtbW",
+ uint64_t{0xd3ae96ef9f7185f2}},
+ {"/WiHi9IQcxRImsudkA/KOTqGe8/"
+ "gXkhKIHkjddv5S9hi02M049dIK3EUyAEjkjpdGLUs+BN0QzPtZqjIYPOgwsYE9g==",
+ uint64_t{0x4fb88ea63f79a0d8}},
+ {"qds+1ExSnU11L4fTSDz/QE90g4Jh6ioqSh3KDOTOAo2pQGL1k/"
+ "9CCC7J23YF27dUTzrWsCQA2m4epXoCc3yPHb3xElA=",
+ uint64_t{0xed564e259bb5ebe9}},
+ {"8FVYHx40lSQPTHheh08Oq0/"
+ "pGm2OlG8BEf8ezvAxHuGGdgCkqpXIueJBF2mQJhTfDy5NncO8ntS7vaKs7sCNdDaNGOEi",
+ uint64_t{0x3e3256b60c428000}},
+ {"4ZoEIrJtstiCkeew3oRzmyJHVt/pAs2pj0HgHFrBPztbQ10NsQ/"
+ "lM6DM439QVxpznnBSiHMgMQJhER+70l72LqFTO1JiIQ==",
+ uint64_t{0xfb05bad59ec8705}},
+ {"hQPtaYI+wJyxXgwD5n8jGIKFKaFA/"
+ "P83KqCKZfPthnjwdOFysqEOYwAaZuaaiv4cDyi9TyS8hk5cEbNP/jrI7q6pYGBLbsM=",
+ uint64_t{0xafdc251dbf97b5f8}},
+ {"S4gpMSKzMD7CWPsSfLeYyhSpfWOntyuVZdX1xSBjiGvsspwOZcxNKCRIOqAA0moUfOh3I5+"
+ "juQV4rsqYElMD/gWfDGpsWZKQ",
+ uint64_t{0x10ec9c92ddb5dcbc}},
+ {"oswxop+"
+ "bthuDLT4j0PcoSKby4LhF47ZKg8K17xxHf74UsGCzTBbOz0MM8hQEGlyqDT1iUiAYnaPaUp"
+ "L2mRK0rcIUYA4qLt5uOw==",
+ uint64_t{0x9a767d5822c7dac4}},
+ {"0II/"
+ "697p+"
+ "BtLSjxj5989OXI004TogEb94VUnDzOVSgMXie72cuYRvTFNIBgtXlKfkiUjeqVpd4a+"
+ "n5bxNOD1TGrjQtzKU5r7obo=",
+ uint64_t{0xee46254080d6e2db}},
+ {"E84YZW2qipAlMPmctrg7TKlwLZ68l4L+c0xRDUfyyFrA4MAti0q9sHq3TDFviH0Y+"
+ "Kq3tEE5srWFA8LM9oomtmvm5PYxoaarWPLc",
+ uint64_t{0xbbb669588d8bf398}},
+ {"x3pa4HIElyZG0Nj7Vdy9IdJIR4izLmypXw5PCmZB5y68QQ4uRaVVi3UthsoJROvbjDJkP2D"
+ "Q6L/eN8pFeLFzNPKBYzcmuMOb5Ull7w==",
+ uint64_t{0xdc2afaa529beef44}},
+ {"jVDKGYIuWOP/"
+ "QKLdd2wi8B2VJA8Wh0c8PwrXJVM8FOGM3voPDVPyDJOU6QsBDPseoR8uuKd19OZ/"
+ "zAvSCB+zlf6upAsBlheUKgCfKww=",
+ uint64_t{0xf1f67391d45013a8}},
+ {"mkquunhmYe1aR2wmUz4vcvLEcKBoe6H+kjUok9VUn2+eTSkWs4oDDtJvNCWtY5efJwg/"
+ "j4PgjRYWtqnrCkhaqJaEvkkOwVfgMIwF3e+d",
+ uint64_t{0x16fce2b8c65a3429}},
+ {"fRelvKYonTQ+s+rnnvQw+JzGfFoPixtna0vzcSjiDqX5s2Kg2//"
+ "UGrK+AVCyMUhO98WoB1DDbrsOYSw2QzrcPe0+3ck9sePvb+Q/IRaHbw==",
+ uint64_t{0xf4b096699f49fe67}},
+ {"DUwXFJzagljo44QeJ7/"
+ "6ZKw4QXV18lhkYT2jglMr8WB3CHUU4vdsytvw6AKv42ZcG6fRkZkq9fpnmXy6xG0aO3WPT1"
+ "eHuyFirAlkW+zKtwg=",
+ uint64_t{0xca584c4bc8198682}},
+ {"cYmZCrOOBBongNTr7e4nYn52uQUy2mfe48s50JXx2AZ6cRAt/"
+ "xRHJ5QbEoEJOeOHsJyM4nbzwFm++SlT6gFZZHJpkXJ92JkR86uS/eV1hJUR",
+ uint64_t{0xed269fc3818b6aad}},
+ {"EXeHBDfhwzAKFhsMcH9+2RHwV+mJaN01+9oacF6vgm8mCXRd6jeN9U2oAb0of5c5cO4i+"
+ "Vb/LlHZSMI490SnHU0bejhSCC2gsC5d2K30ER3iNA==",
+ uint64_t{0x33f253cbb8fe66a8}},
+ {"FzkzRYoNjkxFhZDso94IHRZaJUP61nFYrh5MwDwv9FNoJ5jyNCY/"
+ "eazPZk+tbmzDyJIGw2h3GxaWZ9bSlsol/vK98SbkMKCQ/wbfrXRLcDzdd/8=",
+ uint64_t{0xd0b76b2c1523d99c}},
+ {"Re4aXISCMlYY/XsX7zkIFR04ta03u4zkL9dVbLXMa/q6hlY/CImVIIYRN3VKP4pnd0AUr/"
+ "ugkyt36JcstAInb4h9rpAGQ7GMVOgBniiMBZ/MGU7H",
+ uint64_t{0xfd28f0811a2a237f}},
+ {"ueLyMcqJXX+MhO4UApylCN9WlTQ+"
+ "ltJmItgG7vFUtqs2qNwBMjmAvr5u0sAKd8jpzV0dDPTwchbIeAW5zbtkA2NABJV6hFM48ib"
+ "4/J3A5mseA3cS8w==",
+ uint64_t{0x6261fb136482e84}},
+ {"6Si7Yi11L+jZMkwaN+GUuzXMrlvEqviEkGOilNq0h8TdQyYKuFXzkYc/"
+ "q74gP3pVCyiwz9KpVGMM9vfnq36riMHRknkmhQutxLZs5fbmOgEO69HglCU=",
+ uint64_t{0x458efc750bca7c3a}},
+ {"Q6AbOofGuTJOegPh9Clm/"
+ "9crtUMQqylKrTc1fhfJo1tqvpXxhU4k08kntL1RG7woRnFrVh2UoMrL1kjin+s9CanT+"
+ "y4hHwLqRranl9FjvxfVKm3yvg68",
+ uint64_t{0xa7e69ff84e5e7c27}},
+ {"ieQEbIPvqY2YfIjHnqfJiO1/MIVRk0RoaG/WWi3kFrfIGiNLCczYoklgaecHMm/"
+ "1sZ96AjO+a5stQfZbJQwS7Sc1ODABEdJKcTsxeW2hbh9A6CFzpowP1A==",
+ uint64_t{0x3c59bfd0c29efe9e}},
+ {"zQUv8hFB3zh2GGl3KTvCmnfzE+"
+ "SUgQPVaSVIELFX5H9cE3FuVFGmymkPQZJLAyzC90Cmi8GqYCvPqTuAAB//"
+ "XTJxy4bCcVArgZG9zJXpjowpNBfr3ngWrSE=",
+ uint64_t{0x10befacc6afd298d}},
+ {"US4hcC1+op5JKGC7eIs8CUgInjKWKlvKQkapulxW262E/"
+ "B2ye79QxOexf188u2mFwwe3WTISJHRZzS61IwljqAWAWoBAqkUnW8SHmIDwHUP31J0p5sGd"
+ "P47L",
+ uint64_t{0x41d5320b0a38efa7}},
+ {"9bHUWFna2LNaGF6fQLlkx1Hkt24nrkLE2CmFdWgTQV3FFbUe747SSqYw6ebpTa07MWSpWRP"
+ "sHesVo2B9tqHbe7eQmqYebPDFnNqrhSdZwFm9arLQVs+7a3Ic6A==",
+ uint64_t{0x58db1c7450fe17f3}},
+ {"Kb3DpHRUPhtyqgs3RuXjzA08jGb59hjKTOeFt1qhoINfYyfTt2buKhD6YVffRCPsgK9SeqZ"
+ "qRPJSyaqsa0ovyq1WnWW8jI/NhvAkZTVHUrX2pC+cD3OPYT05Dag=",
+ uint64_t{0x6098c055a335b7a6}},
+ {"gzxyMJIPlU+bJBwhFUCHSofZ/"
+ "319LxqMoqnt3+L6h2U2+ZXJCSsYpE80xmR0Ta77Jq54o92SMH87HV8dGOaCTuAYF+"
+ "lDL42SY1P316Cl0sZTS2ow3ZqwGbcPNs/1",
+ uint64_t{0x1bbacec67845a801}},
+ {"uR7V0TW+FGVMpsifnaBAQ3IGlr1wx5sKd7TChuqRe6OvUXTlD4hKWy8S+"
+ "8yyOw8lQabism19vOQxfmocEOW/"
+ "vzY0pEa87qHrAZy4s9fH2Bltu8vaOIe+agYohhYORQ==",
+ uint64_t{0xc419cfc7442190}},
+ {"1UR5eoo2aCwhacjZHaCh9bkOsITp6QunUxHQ2SfeHv0imHetzt/"
+ "Z70mhyWZBalv6eAx+YfWKCUib2SHDtz/"
+ "A2dc3hqUWX5VfAV7FQsghPUAtu6IiRatq4YSLpDvKZBQ=",
+ uint64_t{0xc95e510d94ba270c}},
+ {"opubR7H63BH7OtY+Avd7QyQ25UZ8kLBdFDsBTwZlY6gA/"
+ "u+x+"
+ "czC9AaZMgmQrUy15DH7YMGsvdXnviTtI4eVI4aF1H9Rl3NXMKZgwFOsdTfdcZeeHVRzBBKX"
+ "8jUfh1il",
+ uint64_t{0xff1ae05c98089c3f}},
+ {"DC0kXcSXtfQ9FbSRwirIn5tgPri0sbzHSa78aDZVDUKCMaBGyFU6BmrulywYX8yzvwprdLs"
+ "oOwTWN2wMjHlPDqrvVHNEjnmufRDblW+nSS+xtKNs3N5xsxXdv6JXDrAB/Q==",
+ uint64_t{0x90c02b8dceced493}},
+ {"BXRBk+3wEP3Lpm1y75wjoz+PgB0AMzLe8tQ1AYU2/"
+ "oqrQB2YMC6W+9QDbcOfkGbeH+b7IBkt/"
+ "gwCMw2HaQsRFEsurXtcQ3YwRuPz5XNaw5NAvrNa67Fm7eRzdE1+hWLKtA8=",
+ uint64_t{0x9f8a76697ab1aa36}},
+ {"RRBSvEGYnzR9E45Aps/+WSnpCo/X7gJLO4DRnUqFrJCV/kzWlusLE/"
+ "6ZU6RoUf2ROwcgEvUiXTGjLs7ts3t9SXnJHxC1KiOzxHdYLMhVvgNd3hVSAXODpKFSkVXND"
+ "55G2L1W",
+ uint64_t{0x6ba1bf3d811a531d}},
+ {"jeh6Qazxmdi57pa9S3XSnnZFIRrnc6s8QLrah5OX3SB/V2ErSPoEAumavzQPkdKF1/"
+ "SfvmdL+qgF1C+Yawy562QaFqwVGq7+tW0yxP8FStb56ZRgNI4IOmI30s1Ei7iops9Uuw==",
+ uint64_t{0x6a418974109c67b4}},
+ {"6QO5nnDrY2/"
+ "wrUXpltlKy2dSBcmK15fOY092CR7KxAjNfaY+"
+ "aAmtWbbzQk3MjBg03x39afSUN1fkrWACdyQKRaGxgwq6MGNxI6W+8DLWJBHzIXrntrE/"
+ "ml6fnNXEpxplWJ1vEs4=",
+ uint64_t{0x8472f1c2b3d230a3}},
+ {"0oPxeEHhqhcFuwonNfLd5jF3RNATGZS6NPoS0WklnzyokbTqcl4BeBkMn07+fDQv83j/"
+ "BpGUwcWO05f3+DYzocfnizpFjLJemFGsls3gxcBYxcbqWYev51tG3lN9EvRE+X9+Pwww",
+ uint64_t{0x5e06068f884e73a7}},
+ {"naSBSjtOKgAOg8XVbR5cHAW3Y+QL4Pb/JO9/"
+ "oy6L08wvVRZqo0BrssMwhzBP401Um7A4ppAupbQeJFdMrysY34AuSSNvtNUy5VxjNECwiNt"
+ "gwYHw7yakDUv8WvonctmnoSPKENegQg==",
+ uint64_t{0x55290b1a8f170f59}},
+ {"vPyl8DxVeRe1OpilKb9KNwpGkQRtA94UpAHetNh+"
+ "95V7nIW38v7PpzhnTWIml5kw3So1Si0TXtIUPIbsu32BNhoH7QwFvLM+"
+ "JACgSpc5e3RjsL6Qwxxi11npwxRmRUqATDeMUfRAjxg=",
+ uint64_t{0x5501cfd83dfe706a}},
+ {"QC9i2GjdTMuNC1xQJ74ngKfrlA4w3o58FhvNCltdIpuMhHP1YsDA78scQPLbZ3OCUgeQguY"
+ "f/vw6zAaVKSgwtaykqg5ka/4vhz4hYqWU5ficdXqClHl+zkWEY26slCNYOM5nnDlly8Cj",
+ uint64_t{0xe43ed13d13a66990}},
+ {"7CNIgQhAHX27nxI0HeB5oUTnTdgKpRDYDKwRcXfSFGP1XeT9nQF6WKCMjL1tBV6x7KuJ91G"
+ "Zz11F4c+8s+MfqEAEpd4FHzamrMNjGcjCyrVtU6y+7HscMVzr7Q/"
+ "ODLcPEFztFnwjvCjmHw==",
+ uint64_t{0xdf43bc375cf5283f}},
+ {"Qa/hC2RPXhANSospe+gUaPfjdK/yhQvfm4cCV6/pdvCYWPv8p1kMtKOX3h5/"
+ "8oZ31fsmx4Axphu5qXJokuhZKkBUJueuMpxRyXpwSWz2wELx5glxF7CM0Fn+"
+ "OevnkhUn5jsPlG2r5jYlVn8=",
+ uint64_t{0x8112b806d288d7b5}},
+ {"kUw/0z4l3a89jTwN5jpG0SHY5km/"
+ "IVhTjgM5xCiPRLncg40aqWrJ5vcF891AOq5hEpSq0bUCJUMFXgct7kvnys905HjerV7Vs1G"
+ "y84tgVJ70/2+pAZTsB/PzNOE/G6sOj4+GbTzkQu819OLB",
+ uint64_t{0xd52a18abb001cb46}},
+ {"VDdfSDbO8Tdj3T5W0XM3EI7iHh5xpIutiM6dvcJ/fhe23V/srFEkDy5iZf/"
+ "VnA9kfi2C79ENnFnbOReeuZW1b3MUXB9lgC6U4pOTuC+"
+ "jHK3Qnpyiqzj7h3ISJSuo2pob7vY6VHZo6Fn7exEqHg==",
+ uint64_t{0xe12b76a2433a1236}},
+ {"Ldfvy3ORdquM/R2fIkhH/ONi69mcP1AEJ6n/"
+ "oropwecAsLJzQSgezSY8bEiEs0VnFTBBsW+RtZY6tDj03fnb3amNUOq1b7jbqyQkL9hpl+"
+ "2Z2J8IaVSeownWl+bQcsR5/xRktIMckC5AtF4YHfU=",
+ uint64_t{0x175bf7319cf1fa00}},
+ {"BrbNpb42+"
+ "VzZAjJw6QLirXzhweCVRfwlczzZ0VX2xluskwBqyfnGovz5EuX79JJ31VNXa5hTkAyQat3l"
+ "YKRADTdAdwE5PqM1N7YaMqqsqoAAAeuYVXuk5eWCykYmClNdSspegwgCuT+403JigBzi",
+ uint64_t{0xd63d57b3f67525ae}},
+ {"gB3NGHJJvVcuPyF0ZSvHwnWSIfmaI7La24VMPQVoIIWF7Z74NltPZZpx2f+cocESM+"
+ "ILzQW9p+BC8x5IWz7N4Str2WLGKMdgmaBfNkEhSHQDU0IJEOnpUt0HmjhFaBlx0/"
+ "LTmhua+rQ6Wup8ezLwfg==",
+ uint64_t{0x933faea858832b73}},
+ {"hTKHlRxx6Pl4gjG+6ksvvj0CWFicUg3WrPdSJypDpq91LUWRni2KF6+"
+ "81ZoHBFhEBrCdogKqeK+hy9bLDnx7g6rAFUjtn1+cWzQ2YjiOpz4+"
+ "ROBB7lnwjyTGWzJD1rXtlso1g2qVH8XJVigC5M9AIxM=",
+ uint64_t{0x53d061e5f8e7c04f}},
+ {"IWQBelSQnhrr0F3BhUpXUIDauhX6f95Qp+A0diFXiUK7irwPG1oqBiqHyK/SH/"
+ "9S+"
+ "rln9DlFROAmeFdH0OCJi2tFm4afxYzJTFR4HnR4cG4x12JqHaZLQx6iiu6CE3rtWBVz99oA"
+ "wCZUOEXIsLU24o2Y",
+ uint64_t{0xdb4124556dd515e0}},
+ {"TKo+l+"
+ "1dOXdLvIrFqeLaHdm0HZnbcdEgOoLVcGRiCbAMR0j5pIFw8D36tefckAS1RCFOH5IgP8yiF"
+ "T0Gd0a2hI3+"
+ "fTKA7iK96NekxWeoeqzJyctc6QsoiyBlkZerRxs5RplrxoeNg29kKDTM0K94mnhD9g==",
+ uint64_t{0x4fb31a0dd681ee71}},
+ {"YU4e7G6EfQYvxCFoCrrT0EFgVLHFfOWRTJQJ5gxM3G2b+"
+ "1kJf9YPrpsxF6Xr6nYtS8reEEbDoZJYqnlk9lXSkVArm88Cqn6d25VCx3+"
+ "49MqC0trIlXtb7SXUUhwpJK16T0hJUfPH7s5cMZXc6YmmbFuBNPE=",
+ uint64_t{0x27cc72eefa138e4c}},
+ {"/I/"
+ "eImMwPo1U6wekNFD1Jxjk9XQVi1D+"
+ "FPdqcHifYXQuP5aScNQfxMAmaPR2XhuOQhADV5tTVbBKwCDCX4E3jcDNHzCiPvViZF1W27t"
+ "xaf2BbFQdwKrNCmrtzcluBFYu0XZfc7RU1RmxK/RtnF1qHsq/O4pp",
+ uint64_t{0x44bc2dfba4bd3ced}},
+ {"CJTT9WGcY2XykTdo8KodRIA29qsqY0iHzWZRjKHb9alwyJ7RZAE3V5Juv4MY3MeYEr1EPCC"
+ "MxO7yFXqT8XA8YTjaMp3bafRt17Pw8JC4iKJ1zN+WWKOESrj+"
+ "3aluGQqn8z1EzqY4PH7rLG575PYeWsP98BugdA==",
+ uint64_t{0x242da1e3a439bed8}},
+ {"ZlhyQwLhXQyIUEnMH/"
+ "AEW27vh9xrbNKJxpWGtrEmKhd+nFqAfbeNBQjW0SfG1YI0xQkQMHXjuTt4P/"
+ "EpZRtA47ibZDVS8TtaxwyBjuIDwqcN09eCtpC+Ls+"
+ "vWDTLmBeDM3u4hmzz4DQAYsLiZYSJcldg9Q3wszw=",
+ uint64_t{0xdc559c746e35c139}},
+ {"v2KU8y0sCrBghmnm8lzGJlwo6D6ObccAxCf10heoDtYLosk4ztTpLlpSFEyu23MLA1tJkcg"
+ "Rko04h19QMG0mOw/"
+ "wc93EXAweriBqXfvdaP85sZABwiKO+6rtS9pacRVpYYhHJeVTQ5NzrvBvi1huxAr+"
+ "xswhVMfL",
+ uint64_t{0xd0b0350275b9989}},
+ {"QhKlnIS6BuVCTQsnoE67E/"
+ "yrgogE8EwO7xLaEGei26m0gEU4OksefJgppDh3X0x0Cs78Dr9IHK5b977CmZlrTRmwhlP8p"
+ "M+UzXPNRNIZuN3ntOum/QhUWP8SGpirheXENWsXMQ/"
+ "nxtxakyEtrNkKk471Oov9juP8oQ==",
+ uint64_t{0xb04489e41d17730c}},
+ {"/ZRMgnoRt+Uo6fUPr9FqQvKX7syhgVqWu+"
+ "WUSsiQ68UlN0efSP6Eced5gJZL6tg9gcYJIkhjuQNITU0Q3TjVAnAcobgbJikCn6qZ6pRxK"
+ "BY4MTiAlfGD3T7R7hwJwx554MAy++Zb/YUFlnCaCJiwQMnowF7aQzwYFCo=",
+ uint64_t{0x2217285eb4572156}},
+ {"NB7tU5fNE8nI+SXGfipc7sRkhnSkUF1krjeo6k+8FITaAtdyz+"
+ "o7mONgXmGLulBPH9bEwyYhKNVY0L+njNQrZ9YC2aXsFD3PdZsxAFaBT3VXEzh+"
+ "NGBTjDASNL3mXyS8Yv1iThGfHoY7T4aR0NYGJ+k+pR6f+KrPC96M",
+ uint64_t{0x12c2e8e68aede73b}},
+ {"8T6wrqCtEO6/rwxF6lvMeyuigVOLwPipX/FULvwyu+1wa5sQGav/"
+ "2FsLHUVn6cGSi0LlFwLewGHPFJDLR0u4t7ZUyM//"
+ "x6da0sWgOa5hzDqjsVGmjxEHXiaXKW3i4iSZNuxoNbMQkIbVML+"
+ "DkYu9ND0O2swg4itGeVSzXA==",
+ uint64_t{0x4d612125bdc4fd00}},
+ {"Ntf1bMRdondtMv1CYr3G80iDJ4WSAlKy5H34XdGruQiCrnRGDBa+"
+ "eUi7vKp4gp3BBcVGl8eYSasVQQjn7MLvb3BjtXx6c/"
+ "bCL7JtpzQKaDnPr9GWRxpBXVxKREgMM7d8lm35EODv0w+"
+ "hQLfVSh8OGs7fsBb68nNWPLeeSOo=",
+ uint64_t{0x81826b553954464e}},
+ {"VsSAw72Ro6xks02kaiLuiTEIWBC5bgqr4WDnmP8vglXzAhixk7td926rm9jNimL+"
+ "kroPSygZ9gl63aF5DCPOACXmsbmhDrAQuUzoh9ZKhWgElLQsrqo1KIjWoZT5b5QfVUXY9lS"
+ "IBg3U75SqORoTPq7HalxxoIT5diWOcJQi",
+ uint64_t{0xc2e5d345dc0ddd2d}},
+ {"j+loZ+C87+"
+ "bJxNVebg94gU0mSLeDulcHs84tQT7BZM2rzDSLiCNxUedHr1ZWJ9ejTiBa0dqy2I2ABc++"
+ "xzOLcv+//YfibtjKtYggC6/3rv0XCc7xu6d/"
+ "O6xO+XOBhOWAQ+IHJVHf7wZnDxIXB8AUHsnjEISKj7823biqXjyP3g==",
+ uint64_t{0x3da6830a9e32631e}},
+ {"f3LlpcPElMkspNtDq5xXyWU62erEaKn7RWKlo540gR6mZsNpK1czV/"
+ "sOmqaq8XAQLEn68LKj6/"
+ "cFkJukxRzCa4OF1a7cCAXYFp9+wZDu0bw4y63qbpjhdCl8GO6Z2lkcXy7KOzbPE01ukg7+"
+ "gN+7uKpoohgAhIwpAKQXmX5xtd0=",
+ uint64_t{0xc9ae5c8759b4877a}},
+ };
+
+#if defined(ABSL_IS_BIG_ENDIAN)
+ constexpr uint64_t kGolden[kNumGoldenOutputs] = {
+ 0x4c34aacf38f6eee4, 0x88b1366815e50b88, 0x1a36bd0c6150fb9c,
+ 0xa783aba8a67366c7, 0x5e4a92123ae874f2, 0x0cc9ecf27067ee9a,
+ 0xbe77aa94940527f9, 0x7ea5c12f2669fe31, 0xa33eed8737d946b9,
+ 0x310aec5b1340bb36, 0x354e400861c5d8ff, 0x15be98166adcf42f,
+ 0xc51910b62a90ae51, 0x539d47fc7fdf6a1f, 0x3ebba9daa46eef93,
+ 0xd96bcd3a9113c17f, 0xc78eaf6256ded15a, 0x98902ed321c2f0d9,
+ 0x75a4ac96414b954a, 0x2cb90e00a39e307b, 0x46539574626c3637,
+ 0x186ec89a2be3ff45, 0x972a3bf7531519d2, 0xa14df0d25922364b,
+ 0xa351e19d22752109, 0x08bd311d8fed4f82, 0xea2b52ddc6af54f9,
+ 0x5f20549941338336, 0xd43b07422dc2782e, 0x377c68e2acda4835,
+ 0x1b31a0a663b1d7b3, 0x7388ba5d68058a1a, 0xe382794ea816f032,
+ 0xd4c3fe7889276ee0, 0x2833030545582ea9, 0x554d32a55e55df32,
+ 0x8d6d33d7e17b424d, 0xe51a193d03ae1e34, 0xabb6a80835bd66b3,
+ 0x0e4ba5293f9ce9b7, 0x1ebd8642cb762cdf, 0xcb54b555850888ee,
+ 0x1e4195e4717c701f, 0x6235a13937f6532a, 0xd460960741e845c0,
+ 0x2a72168a2d6af7b1, 0x6be38fbbfc5b17de, 0x4ee97cffa0d0fb39,
+ 0xfdf1119ad5e71a55, 0x0dff7f66b3070727, 0x812d791d6ed62744,
+ 0x60962919074b70b8, 0x956fa5c7d6872547, 0xee892daa58aae597,
+ 0xeeda546e998ee369, 0x454481f5eb9b1fa8, 0x1054394634c98b1b,
+ 0x55bb425415f591fb, 0x9601fa97416232c4, 0xd7a18506519daad7,
+ 0x90935cb5de039acf, 0xe64054c5146ed359, 0xe5b323fb1e866c09,
+ 0x10a472555f5ba1bc, 0xe3c0cd57d26e0972, 0x7ca3db7c121da3e8,
+ 0x7004a89c800bb466, 0x865f69c1a1ff7f39, 0xbe0edd48f0cf2b99,
+ 0x10e5e4ba3cc400f5, 0xafc2b91a220eef50, 0x6f04a259289b24f1,
+ 0x2179a8070e880ef0, 0xd6a9a3d023a740c2, 0x96e6d7954755d9b8,
+ 0xc8e4bddecce5af9f, 0x93941f0fbc724c92, 0xbef5fb15bf76a479,
+ 0x534dca8f5da86529, 0x70789790feec116b, 0x2a296e167eea1fe9,
+ 0x54cb1efd2a3ec7ea, 0x357b43897dfeb9f7, 0xd1eda89bc7ff89d3,
+ 0x434f2e10cbb83c98, 0xeec4cdac46ca69ce, 0xd46aafd52a303206,
+ 0x4bf05968ff50a5c9, 0x71c533747a6292df, 0xa40bd0d16a36118c,
+ 0x597b4ee310c395ab, 0xc5b3e3e386172583, 0x12ca0b32284e6c70,
+ 0xb48995fadcf35630, 0x0646368454cd217d, 0xa21c168e40d765b5,
+ 0x4260d3811337da30, 0xb72728a01cff78e4, 0x8586920947f4756f,
+ 0xc21e5f853cae7dc1, 0xf08c9533be9de285, 0x72df06653b4256d6,
+ 0xf7b7f937f8db1779, 0x976db27dd0418127, 0x9ce863b7bc3f9e00,
+ 0xebb679854fcf3a0a, 0x2ccebabbcf1afa99, 0x44201d6be451dac5,
+ 0xb4af71c0e9a537d1, 0xad8fe9bb33ed2681, 0xcb30128bb68df43b,
+ 0x154d8328903e8d07, 0x5844276dabeabdff, 0xd99017d7d36d930b,
+ 0xabb0b4774fb261ca, 0x0a43f075d62e67e0, 0x8df7b371355ada6b,
+ 0xf4c7a40d06513dcf, 0x257a3615955a0372, 0x987ac410bba74c06,
+ 0xa011a46f25a632a2, 0xa14384b963ddd995, 0xf51b6b8cf9d50ba7,
+ 0x3acdb91ee3abf18d, 0x34e799be08920e8c, 0x8766748a31304b36,
+ 0x0aa239d5d0092f2e, 0xadf473ed26628594, 0xc4094b798eb4b79b,
+ 0xe04ee5f33cd130f4, 0x85045d098c341d46, 0xf936cdf115a890ec,
+ 0x51d137b6d8d2eb4f, 0xd10738bb2fccc1ef,
+ };
+#else
+ constexpr uint64_t kGolden[kNumGoldenOutputs] = {
+ 0x4c34aacf38f6eee4, 0x88b1366815e50b88, 0x1a36bd0c6150fb9c,
+ 0xa783aba8a67366c7, 0xbc89ebdc622314e4, 0x632bc3cfcc7544d8,
+ 0xbe77aa94940527f9, 0x7ea5c12f2669fe31, 0xa33eed8737d946b9,
+ 0x74d832ea11fd18ab, 0x49c0487486246cdc, 0x3fdd986c87ddb0a0,
+ 0xac3fa52a64d7c09a, 0xbff0e330196e7ed2, 0x8c8138d3ad7d3cce,
+ 0x968c7d4b48e93778, 0xa04c78d3a421f529, 0x8854bc9c3c3c0241,
+ 0xcccfcdf5a41113fe, 0xe6fc63dc543d984d, 0x00a39ff89e903c05,
+ 0xaf7e9da25f9a26f9, 0x6e269a13d01a43df, 0x846d2300ce2ecdf8,
+ 0xe7ea8c8f08478260, 0x9a2db0d62f6232f3, 0x6f66c761d168c59f,
+ 0x55f9feacaae82043, 0x518084043700f614, 0xb0c8cfc11bead99f,
+ 0xe4a68fdab6359d80, 0x97b17caa8f92236e, 0x96edf5e8363643dc,
+ 0x9b3fbcd8d5b254cd, 0x22a263621d9b3a8b, 0xde90bf6f81800a6d,
+ 0x1b51cae38c2e9513, 0x689215b3c414ef21, 0x064dc85afae8f557,
+ 0xa2f3a8b51f408378, 0x6907c197ec1f6a3b, 0xfe83a42ef5c1cf13,
+ 0x9b8b1d8f7a20cc13, 0x1f1681d52ca895d0, 0xd7b1670bf28e0f96,
+ 0xb32f20f82d8b038a, 0x6a61d030fb2f5253, 0x8eb2bb0bc29ebb39,
+ 0x144f36f7a9eef95c, 0xe77aa47d29808d8c, 0xf14d34c1fc568bad,
+ 0x9796dcd4383f3c73, 0xa2f685fc1be7225b, 0xf3791295b16068b1,
+ 0xb6b8f63424618948, 0x8ac4fd587045db19, 0x7e2aec2c34feb72e,
+ 0x72e135a6910ccbb1, 0x661ff16f3c904e6f, 0xdf92cf9d67ca092d,
+ 0x98a9953d79722eef, 0xe0649ed2181d1707, 0xcd8b8478636a297b,
+ 0x9516258709c8471b, 0xc703b675b51f4394, 0xdb740eae020139f3,
+ 0x57d1499ac4212ff2, 0x355cc03713d43825, 0x0e71ac9b8b1e101e,
+ 0x8029fa72258ff559, 0xa2159726b4c16a50, 0x04e61582fba43007,
+ 0xdab25af835be8cce, 0x13510b1b184705ee, 0xabdbc9e53666fdeb,
+ 0x94a788fcb8173cef, 0x750d5e031286e722, 0x02559e72f4f5b497,
+ 0x7d6e0e5996a646fa, 0x66e871b73b014132, 0x2ec170083f8b784f,
+ 0x34ac9540cfce3fd9, 0x75c5622c6aad1295, 0xf799a6bb2651acc1,
+ 0x8f6bcd3145bdc452, 0xddd9d326eb584a04, 0x5411af1e3532f8dc,
+ 0xeb34722f2ad0f509, 0x835bc952a82298cc, 0xeb3839ff60ea92ad,
+ 0x70bddf1bcdc8a4bc, 0x4bfb3ee86fcde525, 0xc7b3b93b81dfa386,
+ 0xe66db544d57997e8, 0xf68a1b83fd363187, 0xe9b99bec615b171b,
+ 0x093fba04d04ad28a, 0xba6117ed4231a303, 0x594bef25f9d4e206,
+ 0x0a8cba60578b8f67, 0x88f6c7ca10b06019, 0x32a74082aef17b08,
+ 0xe758222f971e22df, 0x4af14ff4a593e51e, 0xdba651e16cb09044,
+ 0x3f3ac837d181eaac, 0xa5589a3f89610c01, 0xd409a7c3a18d5643,
+ 0x8a89444f82962f26, 0x22eb62a13b9771b9, 0xd3a617615256ddd8,
+ 0x7089b990c4bba297, 0x7d752893783eac4f, 0x1f2fcbb79372c915,
+ 0x67a4446b17eb9839, 0x70d11df5cae46788, 0x52621e1780b47d0f,
+ 0xcf63b93a6e590ee6, 0xb6bc96b58ee064b8, 0x2587f8d635ca9c75,
+ 0xc6bddd62ec5e5d01, 0x957398ad3009cdb7, 0x05b6890b20bcd0d3,
+ 0xbe6e965ff837222e, 0x47383a87d2b04b1a, 0x7d42207e6d8d7950,
+ 0x7e981ed12a7f4aa3, 0xdebb05b30769441a, 0xaac5d86f4ff76c49,
+ 0x384f195ca3248331, 0xec4c4b855e909ca1, 0x6a7eeb5a657d73d5,
+ 0x9efbebe2fa9c2791, 0x19e7fa0546900c4d,
+ };
+#endif
+
+#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));
+ uint64_t h = absl::hash_internal::LowLevelHash(str.data(), str.size(),
+ cases[i].seed, kSalt);
+ printf("0x%016" PRIx64 ", ", h);
+ if (i % 3 == 2) {
+ printf("\n");
+ }
+ }
+ printf("\n\n\n");
+ EXPECT_FALSE(true);
+#else
+ for (size_t i = 0; i < kNumGoldenOutputs; ++i) {
+ SCOPED_TRACE(::testing::Message()
+ << "i = " << i << "; input = " << cases[i].base64_data);
+ std::string str;
+ ASSERT_TRUE(absl::Base64Unescape(cases[i].base64_data, &str));
+ EXPECT_EQ(absl::hash_internal::LowLevelHash(str.data(), str.size(),
+ cases[i].seed, kSalt),
+ kGolden[i]);
+ }
+#endif
+}
+
+} // namespace
diff --git a/absl/hash/internal/spy_hash_state.h b/absl/hash/internal/spy_hash_state.h
index c083120..357c301 100644
--- a/absl/hash/internal/spy_hash_state.h
+++ b/absl/hash/internal/spy_hash_state.h
@@ -15,6 +15,7 @@
#ifndef ABSL_HASH_INTERNAL_SPY_HASH_STATE_H_
#define ABSL_HASH_INTERNAL_SPY_HASH_STATE_H_
+#include <algorithm>
#include <ostream>
#include <string>
#include <vector>
@@ -148,25 +149,43 @@
const unsigned char* begin,
size_t size) {
const size_t large_chunk_stride = PiecewiseChunkSize();
- if (size > large_chunk_stride) {
- // Combining a large contiguous buffer must have the same effect as
- // doing it piecewise by the stride length, followed by the (possibly
- // empty) remainder.
- while (size >= large_chunk_stride) {
- hash_state = SpyHashStateImpl::combine_contiguous(
- std::move(hash_state), begin, large_chunk_stride);
- begin += large_chunk_stride;
- size -= large_chunk_stride;
- }
+ // Combining a large contiguous buffer must have the same effect as
+ // doing it piecewise by the stride length, followed by the (possibly
+ // empty) remainder.
+ while (size > large_chunk_stride) {
+ hash_state = SpyHashStateImpl::combine_contiguous(
+ std::move(hash_state), begin, large_chunk_stride);
+ begin += large_chunk_stride;
+ size -= large_chunk_stride;
}
- hash_state.hash_representation_.emplace_back(
- reinterpret_cast<const char*>(begin), size);
+ if (size > 0) {
+ hash_state.hash_representation_.emplace_back(
+ reinterpret_cast<const char*>(begin), size);
+ }
return hash_state;
}
using SpyHashStateImpl::HashStateBase::combine_contiguous;
+ template <typename CombinerT>
+ static SpyHashStateImpl RunCombineUnordered(SpyHashStateImpl state,
+ CombinerT combiner) {
+ UnorderedCombinerCallback cb;
+
+ combiner(SpyHashStateImpl<void>{}, std::ref(cb));
+
+ std::sort(cb.element_hash_representations.begin(),
+ cb.element_hash_representations.end());
+ state.hash_representation_.insert(state.hash_representation_.end(),
+ cb.element_hash_representations.begin(),
+ cb.element_hash_representations.end());
+ if (cb.error && cb.error->has_value()) {
+ state.error_ = std::move(cb.error);
+ }
+ return state;
+ }
+
absl::optional<std::string> error() const {
if (moved_from_) {
return "Returned a moved-from instance of the hash state object.";
@@ -178,6 +197,22 @@
template <typename U>
friend class SpyHashStateImpl;
+ struct UnorderedCombinerCallback {
+ std::vector<std::string> element_hash_representations;
+ std::shared_ptr<absl::optional<std::string>> error;
+
+ // The inner spy can have a different type.
+ template <typename U>
+ void operator()(SpyHashStateImpl<U>& inner) {
+ element_hash_representations.push_back(
+ absl::StrJoin(inner.hash_representation_, ""));
+ if (inner.error_->has_value()) {
+ error = std::move(inner.error_);
+ }
+ inner = SpyHashStateImpl<void>{};
+ }
+ };
+
// This is true if SpyHashStateImpl<T> has been passed to a call of
// AbslHashValue with the wrong type. This detects that the user called
// AbslHashValue directly (because the hash state type does not match).
diff --git a/absl/hash/internal/wyhash.cc b/absl/hash/internal/wyhash.cc
deleted file mode 100644
index 642bde4..0000000
--- a/absl/hash/internal/wyhash.cc
+++ /dev/null
@@ -1,111 +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/wyhash.h"
-
-#include "absl/base/internal/unaligned_access.h"
-#include "absl/numeric/int128.h"
-
-namespace absl {
-ABSL_NAMESPACE_BEGIN
-namespace hash_internal {
-
-static uint64_t WyhashMix(uint64_t v0, uint64_t v1) {
- absl::uint128 p = v0;
- p *= v1;
- return absl::Uint128Low64(p) ^ absl::Uint128High64(p);
-}
-
-uint64_t Wyhash(const void* data, size_t len, uint64_t seed,
- const uint64_t salt[]) {
- const uint8_t* ptr = static_cast<const uint8_t*>(data);
- uint64_t starting_length = static_cast<uint64_t>(len);
- uint64_t current_state = seed ^ salt[0];
-
- 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 two separate hash states
- // which we will then hash together.
- uint64_t duplicated_state = current_state;
-
- do {
- 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);
-
- uint64_t cs0 = WyhashMix(a ^ salt[1], b ^ current_state);
- uint64_t cs1 = WyhashMix(c ^ salt[2], d ^ current_state);
- current_state = (cs0 ^ cs1);
-
- uint64_t ds0 = WyhashMix(e ^ salt[3], f ^ duplicated_state);
- uint64_t ds1 = WyhashMix(g ^ salt[4], h ^ duplicated_state);
- duplicated_state = (ds0 ^ ds1);
-
- ptr += 64;
- len -= 64;
- } while (len > 64);
-
- current_state = current_state ^ duplicated_state;
- }
-
- // We now have a data `ptr` with at most 64 bytes and the current state
- // of the hashing state machine stored in current_state.
- while (len > 16) {
- uint64_t a = absl::base_internal::UnalignedLoad64(ptr);
- uint64_t b = absl::base_internal::UnalignedLoad64(ptr + 8);
-
- current_state = WyhashMix(a ^ salt[1], b ^ current_state);
-
- ptr += 16;
- len -= 16;
- }
-
- // We now have a data `ptr` with at most 16 bytes.
- uint64_t a = 0;
- uint64_t b = 0;
- if (len > 8) {
- // When we have at least 9 and at most 16 bytes, set A to the first 64
- // bits of the input and B to the last 64 bits of the input. Yes, they will
- // overlap in the middle if we are working with less than the full 16
- // bytes.
- a = absl::base_internal::UnalignedLoad64(ptr);
- b = absl::base_internal::UnalignedLoad64(ptr + len - 8);
- } else if (len > 3) {
- // If we have at least 4 and at most 8 bytes, set A to the first 32
- // bits and B to the last 32 bits.
- a = absl::base_internal::UnalignedLoad32(ptr);
- b = absl::base_internal::UnalignedLoad32(ptr + len - 4);
- } else if (len > 0) {
- // If we have at least 1 and at most 3 bytes, read all of the provided
- // bits into A, with some adjustments.
- a = ((ptr[0] << 16) | (ptr[len >> 1] << 8) | ptr[len - 1]);
- b = 0;
- } else {
- a = 0;
- b = 0;
- }
-
- uint64_t w = WyhashMix(a ^ salt[1], b ^ current_state);
- uint64_t z = salt[1] ^ starting_length;
- return WyhashMix(w, z);
-}
-
-} // namespace hash_internal
-ABSL_NAMESPACE_END
-} // namespace absl
diff --git a/absl/hash/internal/wyhash.h b/absl/hash/internal/wyhash.h
deleted file mode 100644
index 2b534b4..0000000
--- a/absl/hash/internal/wyhash.h
+++ /dev/null
@@ -1,48 +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 the Wyhash
-// algorithm.
-//
-// Wyhash 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.
-
-#ifndef ABSL_HASH_INTERNAL_WYHASH_H_
-#define ABSL_HASH_INTERNAL_WYHASH_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.
-//
-// 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 Wyhash(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_WYHASH_H_
diff --git a/absl/hash/internal/wyhash_test.cc b/absl/hash/internal/wyhash_test.cc
deleted file mode 100644
index 9fb06d2..0000000
--- a/absl/hash/internal/wyhash_test.cc
+++ /dev/null
@@ -1,486 +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/wyhash.h"
-
-#include "absl/strings/escaping.h"
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-
-namespace {
-
-static const uint64_t kCurrentSeed = 0;
-static const uint64_t kSalt[5] = {0xa0761d6478bd642f, 0xe7037ed1a0b428dbl,
- 0x8ebc6af09c88c6e3, 0x589965cc75374cc3l,
- 0x1d8e4e27c47d124f};
-
-// Note: We don't account for endianness, so the values here are only correct if
-// you're also running on a little endian platform.
-
-TEST(WyhashTest, EmptyString) {
- const std::string s = "";
- EXPECT_EQ(
- absl::hash_internal::Wyhash(s.c_str(), s.length(), kCurrentSeed, kSalt),
- 4808886099364463827);
-}
-
-TEST(WyhashTest, Spaces) {
- const std::string s = " ";
- EXPECT_EQ(
- absl::hash_internal::Wyhash(s.c_str(), s.length(), kCurrentSeed, kSalt),
- 1686201463024549249);
-}
-
-TEST(WyhashTest, RepeatingString) {
- const std::string s = "aaaa";
- EXPECT_EQ(
- absl::hash_internal::Wyhash(s.c_str(), s.length(), kCurrentSeed, kSalt),
- 6646112255271966632);
-}
-
-TEST(WyhashTest, HexString) {
- const std::string small = "\x01\x02\x03";
- const std::string med = "\x01\x02\x03\x04";
-
- EXPECT_EQ(absl::hash_internal::Wyhash(small.c_str(), small.length(),
- kCurrentSeed, kSalt),
- 11989428023081740911ULL);
- EXPECT_EQ(absl::hash_internal::Wyhash(med.c_str(), med.length(), kCurrentSeed,
- kSalt),
- 9765997711188871556ULL);
-}
-
-TEST(WyhashTest, Words) {
- const std::string s = "third_party|wyhash|64";
- EXPECT_EQ(
- absl::hash_internal::Wyhash(s.c_str(), s.length(), kCurrentSeed, kSalt),
- 3702018632387611330);
-}
-
-TEST(WyhashTest, LongString) {
- const std::string s =
- "AbCdEfGhIjKlMnOpQrStUvWxYz0123456789AbCdEfGhIjKlMnOpQrStUvWxYz"
- "0123456789AbCdEfGhIjKlMnOpQrStUvWxYz0123456789AbCdEfGhIjKlMnOp"
- "QrStUvWxYz0123456789AbCdEfGhIjKlMnOpQrStUvWxYz0123456789AbCdEf"
- "GhIjKlMnOpQrStUvWxYz0123456789AbCdEfGhIjKlMnOpQrStUvWxYz012345"
- "6789AbCdEfGhIjKlMnOpQrStUvWxYz0123456789";
-
- EXPECT_EQ(
- absl::hash_internal::Wyhash(s.c_str(), s.length(), kCurrentSeed, kSalt),
- 9245411362605796064ULL);
-}
-
-TEST(WyhashTest, BigReference) {
- struct ExpectedResult {
- absl::string_view base64_data;
- uint64_t seed;
- uint64_t hash;
- } expected_results[] = {
- {"", uint64_t{0xec42b7ab404b8acb}, uint64_t{0xe5a40d39ab796423}},
- {"Zw==", uint64_t{0xeeee074043a3ee0f}, uint64_t{0xa6564b468248c683}},
- {"xmk=", uint64_t{0x857902089c393de}, uint64_t{0xef192f401b116e1c}},
- {"c1H/", uint64_t{0x993df040024ca3af}, uint64_t{0xbe8dc0c54617639d}},
- {"SuwpzQ==", uint64_t{0xc4e4c2acea740e96}, uint64_t{0x93d7f665b5521c8e}},
- {"uqvy++M=", uint64_t{0x6a214b3db872d0cf}, uint64_t{0x646d70bb42445f28}},
- {"RnzCVPgb", uint64_t{0x44343db6a89dba4d}, uint64_t{0x96a7b1e3cc9bd426}},
- {"6OeNdlouYw==", uint64_t{0x77b5d6d1ae1dd483},
- uint64_t{0x76020289ab0790c4}},
- {"M5/JmmYyDbc=", uint64_t{0x89ab8ecb44d221f1},
- uint64_t{0x39f842e4133b9b44}},
- {"MVijWiVdBRdY", uint64_t{0x60244b17577ca81b},
- uint64_t{0x2b8d7047be4bcaab}},
- {"6V7Uq7LNxpu0VA==", uint64_t{0x59a08dcee0717067},
- uint64_t{0x99628abef6716a97}},
- {"EQ6CdEEhPdyHcOk=", uint64_t{0xf5f20db3ade57396},
- uint64_t{0x4432e02ba42b2740}},
- {"PqFB4fxnPgF+l+rc", uint64_t{0xbf8dee0751ad3efb},
- uint64_t{0x74d810efcad7918a}},
- {"a5aPOFwq7LA7+zKvPA==", uint64_t{0x6b7a06b268d63e30},
- uint64_t{0x88c84e986002507f}},
- {"VOwY21wCGv5D+/qqOvs=", uint64_t{0xb8c37f0ae0f54c82},
- uint64_t{0x4f99acf193cf39b9}},
- {"KdHmBTx8lHXYvmGJ+Vy7", uint64_t{0x9fcbed0c38e50eef},
- uint64_t{0xd90e7a3655891e37}},
- {"qJkPlbHr8bMF7/cA6aE65Q==", uint64_t{0x2af4bade1d8e3a1d},
- uint64_t{0x3bb378b1d4df8fcf}},
- {"ygvL0EhHZL0fIx6oHHtkxRQ=", uint64_t{0x714e3aa912da2f2c},
- uint64_t{0xf78e94045c052d47}},
- {"c1rFXkt5YztwZCQRngncqtSs", uint64_t{0xf5ee75e3cbb82c1c},
- uint64_t{0x26da0b2130da6b40}},
- {"8hsQrzszzeNQSEcVXLtvIhm6mw==", uint64_t{0x620e7007321b93b9},
- uint64_t{0x30b4d426af8c6986}},
- {"ffUL4RocfyP4KfikGxO1yk7omDI=", uint64_t{0xc08528cac2e551fc},
- uint64_t{0x5413b4aaf3baaeae}},
- {"OOB5TT00vF9Od/rLbAWshiErqhpV", uint64_t{0x6a1debf9cc3ad39},
- uint64_t{0x756ab265370a1597}},
- {"or5wtXM7BFzTNpSzr+Lw5J5PMhVJ/Q==", uint64_t{0x7e0a3c88111fc226},
- uint64_t{0xdaf5f4b7d09814fb}},
- {"gk6pCHDUsoopVEiaCrzVDhioRKxb844=", uint64_t{0x1301fef15df39edb},
- uint64_t{0x8f874ae37742b75e}},
- {"TNctmwlC5QbEM6/No4R/La3UdkfeMhzs", uint64_t{0x64e181f3d5817ab},
- uint64_t{0x8fecd03956121ce8}},
- {"SsQw9iAjhWz7sgcE9OwLuSC6hsM+BfHs2Q==", uint64_t{0xafafc44961078ecb},
- uint64_t{0x229c292ea7a08285}},
- {"ZzO3mVCj4xTT2TT3XqDyEKj2BZQBvrS8RHg=", uint64_t{0x4f7bb45549250094},
- uint64_t{0xbb4bf0692d14bae}},
- {"+klp5iPQGtppan5MflEls0iEUzqU+zGZkDJX", uint64_t{0xa30061abaa2818c},
- uint64_t{0x207b24ca3bdac1db}},
- {"RO6bvOnlJc8I9eniXlNgqtKy0IX6VNg16NRmgg==", uint64_t{0xd902ee3e44a5705f},
- uint64_t{0x64f6cd6745d3825b}},
- {"ZJjZqId1ZXBaij9igClE3nyliU5XWdNRrayGlYA=", uint64_t{0x316d36da516f583},
- uint64_t{0xa2b2e1656b58df1e}},
- {"7BfkhfGMDGbxfMB8uyL85GbaYQtjr2K8g7RpLzr/", uint64_t{0x402d83f9f834f616},
- uint64_t{0xd01d30d9ee7a148}},
- {"rycWk6wHH7htETQtje9PidS2YzXBx+Qkg2fY7ZYS7A==",
- uint64_t{0x9c604164c016b72c}, uint64_t{0x1cb4cd00ab804e3b}},
- {"RTkC2OUK+J13CdGllsH0H5WqgspsSa6QzRZouqx6pvI=",
- uint64_t{0x3f4507e01f9e73ba}, uint64_t{0x4697f2637fd90999}},
- {"tKjKmbLCNyrLCM9hycOAXm4DKNpM12oZ7dLTmUx5iwAi",
- uint64_t{0xc3fe0d5be8d2c7c7}, uint64_t{0x8383a756b5688c07}},
- {"VprUGNH+5NnNRaORxgH/ySrZFQFDL+4VAodhfBNinmn8cg==",
- uint64_t{0x531858a40bfa7ea1}, uint64_t{0x695c29cb3696a975}},
- {"gc1xZaY+q0nPcUvOOnWnT3bqfmT/geth/f7Dm2e/DemMfk4=",
- uint64_t{0x86689478a7a7e8fa}, uint64_t{0xda2e5a5a5e971521}},
- {"Mr35fIxqx1ukPAL0su1yFuzzAU3wABCLZ8+ZUFsXn47UmAph",
- uint64_t{0x4ec948b8e7f27288}, uint64_t{0x7935d4befa056b2b}},
- {"A9G8pw2+m7+rDtWYAdbl8tb2fT7FFo4hLi2vAsa5Y8mKH3CX3g==",
- uint64_t{0xce46c7213c10032}, uint64_t{0x38dd541ca95420fe}},
- {"DFaJGishGwEHDdj9ixbCoaTjz9KS0phLNWHVVdFsM93CvPft3hM=",
- uint64_t{0xf63e96ee6f32a8b6}, uint64_t{0xcc06c7a4963f967f}},
- {"7+Ugx+Kr3aRNgYgcUxru62YkTDt5Hqis+2po81hGBkcrJg4N0uuy",
- uint64_t{0x1cfe85e65fc5225}, uint64_t{0xbf0f6f66e232fb20}},
- {"H2w6O8BUKqu6Tvj2xxaecxEI2wRgIgqnTTG1WwOgDSINR13Nm4d4Vg==",
- uint64_t{0x45c474f1cee1d2e8}, uint64_t{0xf7efb32d373fe71a}},
- {"1XBMnIbqD5jy65xTDaf6WtiwtdtQwv1dCVoqpeKj+7cTR1SaMWMyI04=",
- uint64_t{0x6e024e14015f329c}, uint64_t{0xe2e64634b1c12660}},
- {"znZbdXG2TSFrKHEuJc83gPncYpzXGbAebUpP0XxzH0rpe8BaMQ17nDbt",
- uint64_t{0x760c40502103ae1c}, uint64_t{0x285b8fd1638e306d}},
- {"ylu8Atu13j1StlcC1MRMJJXIl7USgDDS22HgVv0WQ8hx/8pNtaiKB17hCQ==",
- uint64_t{0x17fd05c3c560c320}, uint64_t{0x658e8a4e3b714d6c}},
- {"M6ZVVzsd7vAvbiACSYHioH/440dp4xG2mLlBnxgiqEvI/aIEGpD0Sf4VS0g=",
- uint64_t{0x8b34200a6f8e90d9}, uint64_t{0xf391fb968e0eb398}},
- {"li3oFSXLXI+ubUVGJ4blP6mNinGKLHWkvGruun85AhVn6iuMtocbZPVhqxzn",
- uint64_t{0x6be89e50818bdf69}, uint64_t{0x744a9ea0cc144bf2}},
- {"kFuQHuUCqBF3Tc3hO4dgdIp223ShaCoog48d5Do5zMqUXOh5XpGK1t5XtxnfGA==",
- uint64_t{0xfb389773315b47d8}, uint64_t{0x12636f2be11012f1}},
- {"jWmOad0v0QhXVJd1OdGuBZtDYYS8wBVHlvOeTQx9ZZnm8wLEItPMeihj72E0nWY=",
- uint64_t{0x4f2512a23f61efee}, uint64_t{0x29c57de825948f80}},
- {"z+DHU52HaOQdW4JrZwDQAebEA6rm13Zg/9lPYA3txt3NjTBqFZlOMvTRnVzRbl23",
- uint64_t{0x59ccd92fc16c6fda}, uint64_t{0x58c6f99ab0d1c021}},
- {"MmBiGDfYeTayyJa/tVycg+rN7f9mPDFaDc+23j0TlW9094er0ADigsl4QX7V3gG/qw==",
- uint64_t{0x25c5a7f5bd330919}, uint64_t{0x13e7b5a7b82fe3bb}},
- {"774RK+9rOL4iFvs1q2qpo/JVc/I39buvNjqEFDtDvyoB0FXxPI2vXqOrk08VPfIHkmU=",
- uint64_t{0x51df4174d34c97d7}, uint64_t{0x10fbc87901e02b63}},
- {"+slatXiQ7/2lK0BkVUI1qzNxOOLP3I1iK6OfHaoxgqT63FpzbElwEXSwdsryq3UlHK0I",
- uint64_t{0x80ce6d76f89cb57}, uint64_t{0xa24c9184901b748b}},
- {"64mVTbQ47dHjHlOHGS/hjJwr/"
- "K2frCNpn87exOqMzNUVYiPKmhCbfS7vBUce5tO6Ec9osQ==",
- uint64_t{0x20961c911965f684}, uint64_t{0xcac4fd4c5080e581}},
- {"fIsaG1r530SFrBqaDj1kqE0AJnvvK8MNEZbII2Yw1OK77v0V59xabIh0B5axaz/"
- "+a2V5WpA=",
- uint64_t{0x4e5b926ec83868e7}, uint64_t{0xc38bdb7483ba68e1}},
- {"PGih0zDEOWCYGxuHGDFu9Ivbff/"
- "iE7BNUq65tycTR2R76TerrXALRosnzaNYO5fjFhTi+CiS",
- uint64_t{0x3927b30b922eecef}, uint64_t{0xdb2a8069b2ceaffa}},
- {"RnpA/"
- "zJnEnnLjmICORByRVb9bCOgxF44p3VMiW10G7PvW7IhwsWajlP9kIwNA9FjAD2GoQHk2Q="
- "=",
- uint64_t{0xbd0291284a49b61c}, uint64_t{0xdf9fe91d0d1c7887}},
- {"qFklMceaTHqJpy2qavJE+EVBiNFOi6OxjOA3LeIcBop1K7w8xQi3TrDk+"
- "BrWPRIbfprszSaPfrI=",
- uint64_t{0x73a77c575bcc956}, uint64_t{0xe83f49e96e2e6a08}},
- {"cLbfUtLl3EcQmITWoTskUR8da/VafRDYF/ylPYwk7/"
- "zazk6ssyrzxMN3mmSyvrXR2yDGNZ3WDrTT",
- uint64_t{0x766a0e2ade6d09a6}, uint64_t{0xc69e61b62ca2b62}},
- {"s/"
- "Jf1+"
- "FbsbCpXWPTUSeWyMH6e4CvTFvPE5Fs6Z8hvFITGyr0dtukHzkI84oviVLxhM1xMxrMAy1db"
- "w==",
- uint64_t{0x2599f4f905115869}, uint64_t{0xb4a4f3f85f8298fe}},
- {"FvyQ00+j7nmYZVQ8hI1Edxd0AWplhTfWuFGiu34AK5X8u2hLX1bE97sZM0CmeLe+"
- "7LgoUT1fJ/axybE=",
- uint64_t{0xd8256e5444d21e53}, uint64_t{0x167a1b39e1e95f41}},
- {"L8ncxMaYLBH3g9buPu8hfpWZNlOF7nvWLNv9IozH07uQsIBWSKxoPy8+"
- "LW4tTuzC6CIWbRGRRD1sQV/4",
- uint64_t{0xf664a91333fb8dfd}, uint64_t{0xf8a2a5649855ee41}},
- {"CDK0meI07yrgV2kQlZZ+"
- "wuVqhc2NmzqeLH7bmcA6kchsRWFPeVF5Wqjjaj556ABeUoUr3yBmfU3kWOakkg==",
- uint64_t{0x9625b859be372cd1}, uint64_t{0x27992565b595c498}},
- {"d23/vc5ONh/"
- "HkMiq+gYk4gaCNYyuFKwUkvn46t+dfVcKfBTYykr4kdvAPNXGYLjM4u1YkAEFpJP+"
- "nX7eOvs=",
- uint64_t{0x7b99940782e29898}, uint64_t{0x3e08cca5b71f9346}},
- {"NUR3SRxBkxTSbtQORJpu/GdR6b/h6sSGfsMj/KFd99ahbh+9r7LSgSGmkGVB/"
- "mGoT0pnMTQst7Lv2q6QN6Vm",
- uint64_t{0x4fe12fa5383b51a8}, uint64_t{0xad406b10c770a6d2}},
- {"2BOFlcI3Z0RYDtS9T9Ie9yJoXlOdigpPeeT+CRujb/"
- "O39Ih5LPC9hP6RQk1kYESGyaLZZi3jtabHs7DiVx/VDg==",
- uint64_t{0xe2ccb09ac0f5b4b6}, uint64_t{0xd1713ce6e552bcf2}},
- {"FF2HQE1FxEvWBpg6Z9zAMH+Zlqx8S1JD/"
- "wIlViL6ZDZY63alMDrxB0GJQahmAtjlm26RGLnjW7jmgQ4Ie3I+014=",
- uint64_t{0x7d0a37adbd7b753b}, uint64_t{0x753b287194c73ad3}},
- {"tHmO7mqVL/PX11nZrz50Hc+M17Poj5lpnqHkEN+4bpMx/"
- "YGbkrGOaYjoQjgmt1X2QyypK7xClFrjeWrCMdlVYtbW",
- uint64_t{0xd3ae96ef9f7185f2}, uint64_t{0x5ae41a95f600af1c}},
- {"/WiHi9IQcxRImsudkA/KOTqGe8/"
- "gXkhKIHkjddv5S9hi02M049dIK3EUyAEjkjpdGLUs+BN0QzPtZqjIYPOgwsYE9g==",
- uint64_t{0x4fb88ea63f79a0d8}, uint64_t{0x4a61163b86a8bb4c}},
- {"qds+1ExSnU11L4fTSDz/QE90g4Jh6ioqSh3KDOTOAo2pQGL1k/"
- "9CCC7J23YF27dUTzrWsCQA2m4epXoCc3yPHb3xElA=",
- uint64_t{0xed564e259bb5ebe9}, uint64_t{0x42eeaa79e760c7e4}},
- {"8FVYHx40lSQPTHheh08Oq0/"
- "pGm2OlG8BEf8ezvAxHuGGdgCkqpXIueJBF2mQJhTfDy5NncO8ntS7vaKs7sCNdDaNGOEi",
- uint64_t{0x3e3256b60c428000}, uint64_t{0x698df622ef465b0a}},
- {"4ZoEIrJtstiCkeew3oRzmyJHVt/pAs2pj0HgHFrBPztbQ10NsQ/"
- "lM6DM439QVxpznnBSiHMgMQJhER+70l72LqFTO1JiIQ==",
- uint64_t{0xfb05bad59ec8705}, uint64_t{0x157583111e1a6026}},
- {"hQPtaYI+wJyxXgwD5n8jGIKFKaFA/"
- "P83KqCKZfPthnjwdOFysqEOYwAaZuaaiv4cDyi9TyS8hk5cEbNP/jrI7q6pYGBLbsM=",
- uint64_t{0xafdc251dbf97b5f8}, uint64_t{0xaa1388f078e793e0}},
- {"S4gpMSKzMD7CWPsSfLeYyhSpfWOntyuVZdX1xSBjiGvsspwOZcxNKCRIOqAA0moUfOh3I5+"
- "juQV4rsqYElMD/gWfDGpsWZKQ",
- uint64_t{0x10ec9c92ddb5dcbc}, uint64_t{0xf10d68d0f3309360}},
- {"oswxop+"
- "bthuDLT4j0PcoSKby4LhF47ZKg8K17xxHf74UsGCzTBbOz0MM8hQEGlyqDT1iUiAYnaPaUp"
- "L2mRK0rcIUYA4qLt5uOw==",
- uint64_t{0x9a767d5822c7dac4}, uint64_t{0x2af056184457a3de}},
- {"0II/"
- "697p+"
- "BtLSjxj5989OXI004TogEb94VUnDzOVSgMXie72cuYRvTFNIBgtXlKfkiUjeqVpd4a+"
- "n5bxNOD1TGrjQtzKU5r7obo=",
- uint64_t{0xee46254080d6e2db}, uint64_t{0x6d0058e1590b2489}},
- {"E84YZW2qipAlMPmctrg7TKlwLZ68l4L+c0xRDUfyyFrA4MAti0q9sHq3TDFviH0Y+"
- "Kq3tEE5srWFA8LM9oomtmvm5PYxoaarWPLc",
- uint64_t{0xbbb669588d8bf398}, uint64_t{0x638f287f68817f12}},
- {"x3pa4HIElyZG0Nj7Vdy9IdJIR4izLmypXw5PCmZB5y68QQ4uRaVVi3UthsoJROvbjDJkP2D"
- "Q6L/eN8pFeLFzNPKBYzcmuMOb5Ull7w==",
- uint64_t{0xdc2afaa529beef44}, uint64_t{0xc46b71fecefd5467}},
- {"jVDKGYIuWOP/"
- "QKLdd2wi8B2VJA8Wh0c8PwrXJVM8FOGM3voPDVPyDJOU6QsBDPseoR8uuKd19OZ/"
- "zAvSCB+zlf6upAsBlheUKgCfKww=",
- uint64_t{0xf1f67391d45013a8}, uint64_t{0x2c8e94679d964e0a}},
- {"mkquunhmYe1aR2wmUz4vcvLEcKBoe6H+kjUok9VUn2+eTSkWs4oDDtJvNCWtY5efJwg/"
- "j4PgjRYWtqnrCkhaqJaEvkkOwVfgMIwF3e+d",
- uint64_t{0x16fce2b8c65a3429}, uint64_t{0x8612b797ce22503a}},
- {"fRelvKYonTQ+s+rnnvQw+JzGfFoPixtna0vzcSjiDqX5s2Kg2//"
- "UGrK+AVCyMUhO98WoB1DDbrsOYSw2QzrcPe0+3ck9sePvb+Q/IRaHbw==",
- uint64_t{0xf4b096699f49fe67}, uint64_t{0x59f929babfba7170}},
- {"DUwXFJzagljo44QeJ7/"
- "6ZKw4QXV18lhkYT2jglMr8WB3CHUU4vdsytvw6AKv42ZcG6fRkZkq9fpnmXy6xG0aO3WPT1"
- "eHuyFirAlkW+zKtwg=",
- uint64_t{0xca584c4bc8198682}, uint64_t{0x9527556923fb49a0}},
- {"cYmZCrOOBBongNTr7e4nYn52uQUy2mfe48s50JXx2AZ6cRAt/"
- "xRHJ5QbEoEJOeOHsJyM4nbzwFm++SlT6gFZZHJpkXJ92JkR86uS/eV1hJUR",
- uint64_t{0xed269fc3818b6aad}, uint64_t{0x1039ab644f5e150b}},
- {"EXeHBDfhwzAKFhsMcH9+2RHwV+mJaN01+9oacF6vgm8mCXRd6jeN9U2oAb0of5c5cO4i+"
- "Vb/LlHZSMI490SnHU0bejhSCC2gsC5d2K30ER3iNA==",
- uint64_t{0x33f253cbb8fe66a8}, uint64_t{0x7816c83f3aa05e6d}},
- {"FzkzRYoNjkxFhZDso94IHRZaJUP61nFYrh5MwDwv9FNoJ5jyNCY/"
- "eazPZk+tbmzDyJIGw2h3GxaWZ9bSlsol/vK98SbkMKCQ/wbfrXRLcDzdd/8=",
- uint64_t{0xd0b76b2c1523d99c}, uint64_t{0xf51d2f564518c619}},
- {"Re4aXISCMlYY/XsX7zkIFR04ta03u4zkL9dVbLXMa/q6hlY/CImVIIYRN3VKP4pnd0AUr/"
- "ugkyt36JcstAInb4h9rpAGQ7GMVOgBniiMBZ/MGU7H",
- uint64_t{0xfd28f0811a2a237f}, uint64_t{0x67d494cff03ac004}},
- {"ueLyMcqJXX+MhO4UApylCN9WlTQ+"
- "ltJmItgG7vFUtqs2qNwBMjmAvr5u0sAKd8jpzV0dDPTwchbIeAW5zbtkA2NABJV6hFM48ib"
- "4/J3A5mseA3cS8w==",
- uint64_t{0x6261fb136482e84}, uint64_t{0x2802d636ced1cfbb}},
- {"6Si7Yi11L+jZMkwaN+GUuzXMrlvEqviEkGOilNq0h8TdQyYKuFXzkYc/"
- "q74gP3pVCyiwz9KpVGMM9vfnq36riMHRknkmhQutxLZs5fbmOgEO69HglCU=",
- uint64_t{0x458efc750bca7c3a}, uint64_t{0xf64e20bad771cb12}},
- {"Q6AbOofGuTJOegPh9Clm/"
- "9crtUMQqylKrTc1fhfJo1tqvpXxhU4k08kntL1RG7woRnFrVh2UoMrL1kjin+s9CanT+"
- "y4hHwLqRranl9FjvxfVKm3yvg68",
- uint64_t{0xa7e69ff84e5e7c27}, uint64_t{0xb9a6cf84a83e15e}},
- {"ieQEbIPvqY2YfIjHnqfJiO1/MIVRk0RoaG/WWi3kFrfIGiNLCczYoklgaecHMm/"
- "1sZ96AjO+a5stQfZbJQwS7Sc1ODABEdJKcTsxeW2hbh9A6CFzpowP1A==",
- uint64_t{0x3c59bfd0c29efe9e}, uint64_t{0x8da6630319609301}},
- {"zQUv8hFB3zh2GGl3KTvCmnfzE+"
- "SUgQPVaSVIELFX5H9cE3FuVFGmymkPQZJLAyzC90Cmi8GqYCvPqTuAAB//"
- "XTJxy4bCcVArgZG9zJXpjowpNBfr3ngWrSE=",
- uint64_t{0x10befacc6afd298d}, uint64_t{0x40946a86e2a996f3}},
- {"US4hcC1+op5JKGC7eIs8CUgInjKWKlvKQkapulxW262E/"
- "B2ye79QxOexf188u2mFwwe3WTISJHRZzS61IwljqAWAWoBAqkUnW8SHmIDwHUP31J0p5sGd"
- "P47L",
- uint64_t{0x41d5320b0a38efa7}, uint64_t{0xcab7f5997953fa76}},
- {"9bHUWFna2LNaGF6fQLlkx1Hkt24nrkLE2CmFdWgTQV3FFbUe747SSqYw6ebpTa07MWSpWRP"
- "sHesVo2B9tqHbe7eQmqYebPDFnNqrhSdZwFm9arLQVs+7a3Ic6A==",
- uint64_t{0x58db1c7450fe17f3}, uint64_t{0x39129ca0e04fc465}},
- {"Kb3DpHRUPhtyqgs3RuXjzA08jGb59hjKTOeFt1qhoINfYyfTt2buKhD6YVffRCPsgK9SeqZ"
- "qRPJSyaqsa0ovyq1WnWW8jI/NhvAkZTVHUrX2pC+cD3OPYT05Dag=",
- uint64_t{0x6098c055a335b7a6}, uint64_t{0x5238221fd685e1b8}},
- {"gzxyMJIPlU+bJBwhFUCHSofZ/"
- "319LxqMoqnt3+L6h2U2+ZXJCSsYpE80xmR0Ta77Jq54o92SMH87HV8dGOaCTuAYF+"
- "lDL42SY1P316Cl0sZTS2ow3ZqwGbcPNs/1",
- uint64_t{0x1bbacec67845a801}, uint64_t{0x175130c407dbcaab}},
- {"uR7V0TW+FGVMpsifnaBAQ3IGlr1wx5sKd7TChuqRe6OvUXTlD4hKWy8S+"
- "8yyOw8lQabism19vOQxfmocEOW/"
- "vzY0pEa87qHrAZy4s9fH2Bltu8vaOIe+agYohhYORQ==",
- uint64_t{0xc419cfc7442190}, uint64_t{0x2f20e7536c0b0df}},
- {"1UR5eoo2aCwhacjZHaCh9bkOsITp6QunUxHQ2SfeHv0imHetzt/"
- "Z70mhyWZBalv6eAx+YfWKCUib2SHDtz/"
- "A2dc3hqUWX5VfAV7FQsghPUAtu6IiRatq4YSLpDvKZBQ=",
- uint64_t{0xc95e510d94ba270c}, uint64_t{0x2742cb488a04ad56}},
- {"opubR7H63BH7OtY+Avd7QyQ25UZ8kLBdFDsBTwZlY6gA/"
- "u+x+"
- "czC9AaZMgmQrUy15DH7YMGsvdXnviTtI4eVI4aF1H9Rl3NXMKZgwFOsdTfdcZeeHVRzBBKX"
- "8jUfh1il",
- uint64_t{0xff1ae05c98089c3f}, uint64_t{0xd6afb593879ff93b}},
- {"DC0kXcSXtfQ9FbSRwirIn5tgPri0sbzHSa78aDZVDUKCMaBGyFU6BmrulywYX8yzvwprdLs"
- "oOwTWN2wMjHlPDqrvVHNEjnmufRDblW+nSS+xtKNs3N5xsxXdv6JXDrAB/Q==",
- uint64_t{0x90c02b8dceced493}, uint64_t{0xf50ad64caac0ca7f}},
- {"BXRBk+3wEP3Lpm1y75wjoz+PgB0AMzLe8tQ1AYU2/"
- "oqrQB2YMC6W+9QDbcOfkGbeH+b7IBkt/"
- "gwCMw2HaQsRFEsurXtcQ3YwRuPz5XNaw5NAvrNa67Fm7eRzdE1+hWLKtA8=",
- uint64_t{0x9f8a76697ab1aa36}, uint64_t{0x2ade95c4261364ae}},
- {"RRBSvEGYnzR9E45Aps/+WSnpCo/X7gJLO4DRnUqFrJCV/kzWlusLE/"
- "6ZU6RoUf2ROwcgEvUiXTGjLs7ts3t9SXnJHxC1KiOzxHdYLMhVvgNd3hVSAXODpKFSkVXND"
- "55G2L1W",
- uint64_t{0x6ba1bf3d811a531d}, uint64_t{0x5c4f3299faacd07a}},
- {"jeh6Qazxmdi57pa9S3XSnnZFIRrnc6s8QLrah5OX3SB/V2ErSPoEAumavzQPkdKF1/"
- "SfvmdL+qgF1C+Yawy562QaFqwVGq7+tW0yxP8FStb56ZRgNI4IOmI30s1Ei7iops9Uuw==",
- uint64_t{0x6a418974109c67b4}, uint64_t{0xfffe3bff0ae5e9bc}},
- {"6QO5nnDrY2/"
- "wrUXpltlKy2dSBcmK15fOY092CR7KxAjNfaY+"
- "aAmtWbbzQk3MjBg03x39afSUN1fkrWACdyQKRaGxgwq6MGNxI6W+8DLWJBHzIXrntrE/"
- "ml6fnNXEpxplWJ1vEs4=",
- uint64_t{0x8472f1c2b3d230a3}, uint64_t{0x1db785c0005166e4}},
- {"0oPxeEHhqhcFuwonNfLd5jF3RNATGZS6NPoS0WklnzyokbTqcl4BeBkMn07+fDQv83j/"
- "BpGUwcWO05f3+DYzocfnizpFjLJemFGsls3gxcBYxcbqWYev51tG3lN9EvRE+X9+Pwww",
- uint64_t{0x5e06068f884e73a7}, uint64_t{0xea000d962ad18418}},
- {"naSBSjtOKgAOg8XVbR5cHAW3Y+QL4Pb/JO9/"
- "oy6L08wvVRZqo0BrssMwhzBP401Um7A4ppAupbQeJFdMrysY34AuSSNvtNUy5VxjNECwiNt"
- "gwYHw7yakDUv8WvonctmnoSPKENegQg==",
- uint64_t{0x55290b1a8f170f59}, uint64_t{0xe42aef38359362d9}},
- {"vPyl8DxVeRe1OpilKb9KNwpGkQRtA94UpAHetNh+"
- "95V7nIW38v7PpzhnTWIml5kw3So1Si0TXtIUPIbsu32BNhoH7QwFvLM+"
- "JACgSpc5e3RjsL6Qwxxi11npwxRmRUqATDeMUfRAjxg=",
- uint64_t{0x5501cfd83dfe706a}, uint64_t{0xc8e95657348a3891}},
- {"QC9i2GjdTMuNC1xQJ74ngKfrlA4w3o58FhvNCltdIpuMhHP1YsDA78scQPLbZ3OCUgeQguY"
- "f/vw6zAaVKSgwtaykqg5ka/4vhz4hYqWU5ficdXqClHl+zkWEY26slCNYOM5nnDlly8Cj",
- uint64_t{0xe43ed13d13a66990}, uint64_t{0xc162eca864f238c6}},
- {"7CNIgQhAHX27nxI0HeB5oUTnTdgKpRDYDKwRcXfSFGP1XeT9nQF6WKCMjL1tBV6x7KuJ91G"
- "Zz11F4c+8s+MfqEAEpd4FHzamrMNjGcjCyrVtU6y+7HscMVzr7Q/"
- "ODLcPEFztFnwjvCjmHw==",
- uint64_t{0xdf43bc375cf5283f}, uint64_t{0xbe1fb373e20579ad}},
- {"Qa/hC2RPXhANSospe+gUaPfjdK/yhQvfm4cCV6/pdvCYWPv8p1kMtKOX3h5/"
- "8oZ31fsmx4Axphu5qXJokuhZKkBUJueuMpxRyXpwSWz2wELx5glxF7CM0Fn+"
- "OevnkhUn5jsPlG2r5jYlVn8=",
- uint64_t{0x8112b806d288d7b5}, uint64_t{0x628a1d4f40aa6ffd}},
- {"kUw/0z4l3a89jTwN5jpG0SHY5km/"
- "IVhTjgM5xCiPRLncg40aqWrJ5vcF891AOq5hEpSq0bUCJUMFXgct7kvnys905HjerV7Vs1G"
- "y84tgVJ70/2+pAZTsB/PzNOE/G6sOj4+GbTzkQu819OLB",
- uint64_t{0xd52a18abb001cb46}, uint64_t{0xa87bdb7456340f90}},
- {"VDdfSDbO8Tdj3T5W0XM3EI7iHh5xpIutiM6dvcJ/fhe23V/srFEkDy5iZf/"
- "VnA9kfi2C79ENnFnbOReeuZW1b3MUXB9lgC6U4pOTuC+"
- "jHK3Qnpyiqzj7h3ISJSuo2pob7vY6VHZo6Fn7exEqHg==",
- uint64_t{0xe12b76a2433a1236}, uint64_t{0x5960ef3ba982c801}},
- {"Ldfvy3ORdquM/R2fIkhH/ONi69mcP1AEJ6n/"
- "oropwecAsLJzQSgezSY8bEiEs0VnFTBBsW+RtZY6tDj03fnb3amNUOq1b7jbqyQkL9hpl+"
- "2Z2J8IaVSeownWl+bQcsR5/xRktIMckC5AtF4YHfU=",
- uint64_t{0x175bf7319cf1fa00}, uint64_t{0x5026586df9a431ec}},
- {"BrbNpb42+"
- "VzZAjJw6QLirXzhweCVRfwlczzZ0VX2xluskwBqyfnGovz5EuX79JJ31VNXa5hTkAyQat3l"
- "YKRADTdAdwE5PqM1N7YaMqqsqoAAAeuYVXuk5eWCykYmClNdSspegwgCuT+403JigBzi",
- uint64_t{0xd63d57b3f67525ae}, uint64_t{0xfe4b8a20fdf0840b}},
- {"gB3NGHJJvVcuPyF0ZSvHwnWSIfmaI7La24VMPQVoIIWF7Z74NltPZZpx2f+cocESM+"
- "ILzQW9p+BC8x5IWz7N4Str2WLGKMdgmaBfNkEhSHQDU0IJEOnpUt0HmjhFaBlx0/"
- "LTmhua+rQ6Wup8ezLwfg==",
- uint64_t{0x933faea858832b73}, uint64_t{0xdcb761867da7072f}},
- {"hTKHlRxx6Pl4gjG+6ksvvj0CWFicUg3WrPdSJypDpq91LUWRni2KF6+"
- "81ZoHBFhEBrCdogKqeK+hy9bLDnx7g6rAFUjtn1+cWzQ2YjiOpz4+"
- "ROBB7lnwjyTGWzJD1rXtlso1g2qVH8XJVigC5M9AIxM=",
- uint64_t{0x53d061e5f8e7c04f}, uint64_t{0xc10d4653667275b7}},
- {"IWQBelSQnhrr0F3BhUpXUIDauhX6f95Qp+A0diFXiUK7irwPG1oqBiqHyK/SH/"
- "9S+"
- "rln9DlFROAmeFdH0OCJi2tFm4afxYzJTFR4HnR4cG4x12JqHaZLQx6iiu6CE3rtWBVz99oA"
- "wCZUOEXIsLU24o2Y",
- uint64_t{0xdb4124556dd515e0}, uint64_t{0x727720deec13110b}},
- {"TKo+l+"
- "1dOXdLvIrFqeLaHdm0HZnbcdEgOoLVcGRiCbAMR0j5pIFw8D36tefckAS1RCFOH5IgP8yiF"
- "T0Gd0a2hI3+"
- "fTKA7iK96NekxWeoeqzJyctc6QsoiyBlkZerRxs5RplrxoeNg29kKDTM0K94mnhD9g==",
- uint64_t{0x4fb31a0dd681ee71}, uint64_t{0x710b009662858dc9}},
- {"YU4e7G6EfQYvxCFoCrrT0EFgVLHFfOWRTJQJ5gxM3G2b+"
- "1kJf9YPrpsxF6Xr6nYtS8reEEbDoZJYqnlk9lXSkVArm88Cqn6d25VCx3+"
- "49MqC0trIlXtb7SXUUhwpJK16T0hJUfPH7s5cMZXc6YmmbFuBNPE=",
- uint64_t{0x27cc72eefa138e4c}, uint64_t{0xfbf8f7a3ecac1eb7}},
- {"/I/"
- "eImMwPo1U6wekNFD1Jxjk9XQVi1D+"
- "FPdqcHifYXQuP5aScNQfxMAmaPR2XhuOQhADV5tTVbBKwCDCX4E3jcDNHzCiPvViZF1W27t"
- "xaf2BbFQdwKrNCmrtzcluBFYu0XZfc7RU1RmxK/RtnF1qHsq/O4pp",
- uint64_t{0x44bc2dfba4bd3ced}, uint64_t{0xb6fc4fcd0722e3df}},
- {"CJTT9WGcY2XykTdo8KodRIA29qsqY0iHzWZRjKHb9alwyJ7RZAE3V5Juv4MY3MeYEr1EPCC"
- "MxO7yFXqT8XA8YTjaMp3bafRt17Pw8JC4iKJ1zN+WWKOESrj+"
- "3aluGQqn8z1EzqY4PH7rLG575PYeWsP98BugdA==",
- uint64_t{0x242da1e3a439bed8}, uint64_t{0x7cb86dcc55104aac}},
- {"ZlhyQwLhXQyIUEnMH/"
- "AEW27vh9xrbNKJxpWGtrEmKhd+nFqAfbeNBQjW0SfG1YI0xQkQMHXjuTt4P/"
- "EpZRtA47ibZDVS8TtaxwyBjuIDwqcN09eCtpC+Ls+"
- "vWDTLmBeDM3u4hmzz4DQAYsLiZYSJcldg9Q3wszw=",
- uint64_t{0xdc559c746e35c139}, uint64_t{0x19e71e9b45c3a51e}},
- {"v2KU8y0sCrBghmnm8lzGJlwo6D6ObccAxCf10heoDtYLosk4ztTpLlpSFEyu23MLA1tJkcg"
- "Rko04h19QMG0mOw/"
- "wc93EXAweriBqXfvdaP85sZABwiKO+6rtS9pacRVpYYhHJeVTQ5NzrvBvi1huxAr+"
- "xswhVMfL",
- uint64_t{0xd0b0350275b9989}, uint64_t{0x51de38573c2bea48}},
- {"QhKlnIS6BuVCTQsnoE67E/"
- "yrgogE8EwO7xLaEGei26m0gEU4OksefJgppDh3X0x0Cs78Dr9IHK5b977CmZlrTRmwhlP8p"
- "M+UzXPNRNIZuN3ntOum/QhUWP8SGpirheXENWsXMQ/"
- "nxtxakyEtrNkKk471Oov9juP8oQ==",
- uint64_t{0xb04489e41d17730c}, uint64_t{0xa73ab6996d6df158}},
- {"/ZRMgnoRt+Uo6fUPr9FqQvKX7syhgVqWu+"
- "WUSsiQ68UlN0efSP6Eced5gJZL6tg9gcYJIkhjuQNITU0Q3TjVAnAcobgbJikCn6qZ6pRxK"
- "BY4MTiAlfGD3T7R7hwJwx554MAy++Zb/YUFlnCaCJiwQMnowF7aQzwYFCo=",
- uint64_t{0x2217285eb4572156}, uint64_t{0x55ef2b8c930817b2}},
- {"NB7tU5fNE8nI+SXGfipc7sRkhnSkUF1krjeo6k+8FITaAtdyz+"
- "o7mONgXmGLulBPH9bEwyYhKNVY0L+njNQrZ9YC2aXsFD3PdZsxAFaBT3VXEzh+"
- "NGBTjDASNL3mXyS8Yv1iThGfHoY7T4aR0NYGJ+k+pR6f+KrPC96M",
- uint64_t{0x12c2e8e68aede73b}, uint64_t{0xb2850bf5fae87157}},
- {"8T6wrqCtEO6/rwxF6lvMeyuigVOLwPipX/FULvwyu+1wa5sQGav/"
- "2FsLHUVn6cGSi0LlFwLewGHPFJDLR0u4t7ZUyM//"
- "x6da0sWgOa5hzDqjsVGmjxEHXiaXKW3i4iSZNuxoNbMQkIbVML+"
- "DkYu9ND0O2swg4itGeVSzXA==",
- uint64_t{0x4d612125bdc4fd00}, uint64_t{0xecf3de1acd04651f}},
- {"Ntf1bMRdondtMv1CYr3G80iDJ4WSAlKy5H34XdGruQiCrnRGDBa+"
- "eUi7vKp4gp3BBcVGl8eYSasVQQjn7MLvb3BjtXx6c/"
- "bCL7JtpzQKaDnPr9GWRxpBXVxKREgMM7d8lm35EODv0w+"
- "hQLfVSh8OGs7fsBb68nNWPLeeSOo=",
- uint64_t{0x81826b553954464e}, uint64_t{0xcc0a40552559ff32}},
- {"VsSAw72Ro6xks02kaiLuiTEIWBC5bgqr4WDnmP8vglXzAhixk7td926rm9jNimL+"
- "kroPSygZ9gl63aF5DCPOACXmsbmhDrAQuUzoh9ZKhWgElLQsrqo1KIjWoZT5b5QfVUXY9lS"
- "IBg3U75SqORoTPq7HalxxoIT5diWOcJQi",
- uint64_t{0xc2e5d345dc0ddd2d}, uint64_t{0xc385c374f20315b1}},
- {"j+loZ+C87+"
- "bJxNVebg94gU0mSLeDulcHs84tQT7BZM2rzDSLiCNxUedHr1ZWJ9ejTiBa0dqy2I2ABc++"
- "xzOLcv+//YfibtjKtYggC6/3rv0XCc7xu6d/"
- "O6xO+XOBhOWAQ+IHJVHf7wZnDxIXB8AUHsnjEISKj7823biqXjyP3g==",
- uint64_t{0x3da6830a9e32631e}, uint64_t{0xb90208a4c7234183}},
- {"f3LlpcPElMkspNtDq5xXyWU62erEaKn7RWKlo540gR6mZsNpK1czV/"
- "sOmqaq8XAQLEn68LKj6/"
- "cFkJukxRzCa4OF1a7cCAXYFp9+wZDu0bw4y63qbpjhdCl8GO6Z2lkcXy7KOzbPE01ukg7+"
- "gN+7uKpoohgAhIwpAKQXmX5xtd0=",
- uint64_t{0xc9ae5c8759b4877a}, uint64_t{0x58aa1ca7a4c075d9}},
- };
-
- for (const auto& expected_result : expected_results) {
- std::string str;
- ASSERT_TRUE(absl::Base64Unescape(expected_result.base64_data, &str));
- EXPECT_EQ(absl::hash_internal::Wyhash(str.data(), str.size(),
- expected_result.seed, kSalt),
- expected_result.hash);
- }
-}
-
-} // namespace
diff --git a/absl/log/BUILD.bazel b/absl/log/BUILD.bazel
new file mode 100644
index 0000000..7ff5143
--- /dev/null
+++ b/absl/log/BUILD.bazel
@@ -0,0 +1,678 @@
+#
+# Copyright 2022 The Abseil Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+load(
+ "//absl:copts/configure_copts.bzl",
+ "ABSL_DEFAULT_COPTS",
+ "ABSL_DEFAULT_LINKOPTS",
+ "ABSL_TEST_COPTS",
+)
+
+package(
+ default_visibility = ["//visibility:public"],
+ features = [
+ "header_modules",
+ "layering_check",
+ "parse_headers",
+ ],
+)
+
+licenses(["notice"])
+
+# Public targets
+
+cc_library(
+ name = "absl_check",
+ hdrs = ["absl_check.h"],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ "//absl/log/internal:check_impl",
+ ],
+)
+
+cc_library(
+ name = "absl_log",
+ hdrs = ["absl_log.h"],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ "//absl/log/internal:log_impl",
+ ],
+)
+
+cc_library(
+ name = "check",
+ hdrs = ["check.h"],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ "//absl/log/internal:check_impl",
+ "//absl/log/internal:check_op",
+ "//absl/log/internal:conditions",
+ "//absl/log/internal:log_message",
+ "//absl/log/internal:strip",
+ ],
+)
+
+cc_library(
+ name = "die_if_null",
+ srcs = ["die_if_null.cc"],
+ hdrs = ["die_if_null.h"],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ ":log",
+ "//absl/base:config",
+ "//absl/base:core_headers",
+ "//absl/strings",
+ ],
+)
+
+cc_library(
+ name = "flags",
+ srcs = ["flags.cc"],
+ hdrs = ["flags.h"],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ visibility = ["//visibility:public"],
+ deps = [
+ ":globals",
+ "//absl/base:config",
+ "//absl/base:core_headers",
+ "//absl/base:log_severity",
+ "//absl/flags:flag",
+ "//absl/flags:marshalling",
+ "//absl/log/internal:config",
+ "//absl/log/internal:flags",
+ "//absl/log/internal:vlog_config",
+ "//absl/strings",
+ ],
+ # Binaries which do not access these flags from C++ still want this library linked in.
+ alwayslink = True,
+)
+
+cc_library(
+ name = "globals",
+ srcs = ["globals.cc"],
+ hdrs = ["globals.h"],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ visibility = ["//visibility:public"],
+ deps = [
+ "//absl/base:atomic_hook",
+ "//absl/base:config",
+ "//absl/base:core_headers",
+ "//absl/base:log_severity",
+ "//absl/base:raw_logging_internal",
+ "//absl/hash",
+ "//absl/log/internal:vlog_config",
+ "//absl/strings",
+ ],
+)
+
+cc_library(
+ name = "initialize",
+ srcs = ["initialize.cc"],
+ hdrs = ["initialize.h"],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ visibility = ["//visibility:public"],
+ deps = [
+ ":globals",
+ "//absl/base:config",
+ "//absl/log/internal:globals",
+ "//absl/time",
+ ],
+)
+
+cc_library(
+ name = "log",
+ hdrs = ["log.h"],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ ":vlog_is_on",
+ "//absl/log/internal:log_impl",
+ ],
+)
+
+cc_library(
+ name = "log_entry",
+ srcs = ["log_entry.cc"],
+ hdrs = ["log_entry.h"],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ "//absl/base:config",
+ "//absl/base:core_headers",
+ "//absl/base:log_severity",
+ "//absl/log/internal:config",
+ "//absl/strings",
+ "//absl/time",
+ "//absl/types:span",
+ ],
+)
+
+cc_library(
+ name = "log_sink",
+ srcs = ["log_sink.cc"],
+ hdrs = ["log_sink.h"],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ ":log_entry",
+ "//absl/base:config",
+ ],
+)
+
+cc_library(
+ name = "log_sink_registry",
+ hdrs = ["log_sink_registry.h"],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ ":log_sink",
+ "//absl/base:config",
+ "//absl/log/internal:log_sink_set",
+ ],
+)
+
+cc_library(
+ name = "log_streamer",
+ hdrs = ["log_streamer.h"],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ ":absl_log",
+ "//absl/base:config",
+ "//absl/base:log_severity",
+ "//absl/strings",
+ "//absl/strings:internal",
+ "//absl/types:optional",
+ "//absl/utility",
+ ],
+)
+
+cc_library(
+ name = "scoped_mock_log",
+ testonly = True,
+ srcs = ["scoped_mock_log.cc"],
+ hdrs = ["scoped_mock_log.h"],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ ":log_entry",
+ ":log_sink",
+ ":log_sink_registry",
+ "//absl/base:config",
+ "//absl/base:log_severity",
+ "//absl/base:raw_logging_internal",
+ "//absl/strings",
+ "@com_google_googletest//:gtest",
+ ],
+)
+
+cc_library(
+ name = "structured",
+ hdrs = ["structured.h"],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ "//absl/base:config",
+ "//absl/log/internal:structured",
+ "//absl/strings",
+ ],
+)
+
+cc_library(
+ name = "absl_vlog_is_on",
+ hdrs = ["absl_vlog_is_on.h"],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ visibility = [
+ "//absl/log:__subpackages__",
+ ],
+ deps = [
+ "//absl/base:config",
+ "//absl/base:core_headers",
+ "//absl/log/internal:vlog_config",
+ "//absl/strings",
+ ],
+)
+
+cc_library(
+ name = "vlog_is_on",
+ hdrs = ["vlog_is_on.h"],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ visibility = [
+ "//absl/log:__subpackages__",
+ ],
+ deps = [
+ ":absl_vlog_is_on",
+ ],
+)
+
+# TODO(b/200695798): run this in TAP projects with -DABSL_MAX_VLOG_VERBOSITY={-100,100}
+cc_test(
+ name = "vlog_is_on_test",
+ size = "small",
+ srcs = [
+ "vlog_is_on_test.cc",
+ ],
+ copts = ABSL_TEST_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ ":flags",
+ ":globals",
+ ":log",
+ ":scoped_mock_log",
+ ":vlog_is_on",
+ "//absl/base:log_severity",
+ "//absl/flags:flag",
+ "//absl/types:optional",
+ "@com_google_googletest//:gtest",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+# Test targets
+
+cc_test(
+ name = "absl_check_test",
+ size = "small",
+ timeout = "moderate",
+ srcs = ["absl_check_test.cc"],
+ copts = ABSL_TEST_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ tags = [
+ "no_test:os:ios",
+ "no_test_ios",
+ "no_test_wasm",
+ ],
+ deps = [
+ ":absl_check",
+ ":check_test_impl",
+ "@com_google_googletest//:gtest",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "absl_log_basic_test",
+ size = "small",
+ srcs = ["absl_log_basic_test.cc"],
+ copts = ABSL_TEST_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ ":absl_log",
+ ":log_basic_test_impl",
+ "@com_google_googletest//:gtest",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "check_test",
+ size = "small",
+ timeout = "moderate",
+ srcs = ["check_test.cc"],
+ copts = ABSL_TEST_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ tags = [
+ "no_test:os:ios",
+ "no_test_ios",
+ "no_test_wasm",
+ ],
+ deps = [
+ ":check",
+ ":check_test_impl",
+ "@com_google_googletest//:gtest",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_library(
+ name = "check_test_impl",
+ testonly = True,
+ copts = ABSL_TEST_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ tags = [
+ "no_test:os:ios",
+ "no_test_ios",
+ "no_test_wasm",
+ ],
+ textual_hdrs = ["check_test_impl.inc"],
+ visibility = ["//visibility:private"],
+ deps = [
+ "//absl/base:config",
+ "//absl/base:core_headers",
+ "//absl/log/internal:test_helpers",
+ "//absl/status",
+ "@com_google_googletest//:gtest",
+ ],
+)
+
+cc_test(
+ name = "die_if_null_test",
+ size = "small",
+ srcs = ["die_if_null_test.cc"],
+ copts = ABSL_TEST_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ ":die_if_null",
+ "//absl/base:core_headers",
+ "//absl/log/internal:test_helpers",
+ "@com_google_googletest//:gtest",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "flags_test",
+ size = "small",
+ srcs = ["flags_test.cc"],
+ copts = ABSL_TEST_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ ":flags",
+ ":globals",
+ ":log",
+ ":scoped_mock_log",
+ "//absl/base:core_headers",
+ "//absl/base:log_severity",
+ "//absl/flags:flag",
+ "//absl/flags:reflection",
+ "//absl/log/internal:flags",
+ "//absl/log/internal:test_helpers",
+ "//absl/log/internal:test_matchers",
+ "//absl/strings",
+ "@com_google_googletest//:gtest",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "globals_test",
+ size = "small",
+ srcs = ["globals_test.cc"],
+ copts = ABSL_TEST_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ ":globals",
+ ":log",
+ ":scoped_mock_log",
+ "//absl/base:core_headers",
+ "//absl/base:log_severity",
+ "//absl/log/internal:globals",
+ "//absl/log/internal:test_helpers",
+ "@com_google_googletest//:gtest",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "log_basic_test",
+ size = "small",
+ srcs = ["log_basic_test.cc"],
+ copts = ABSL_TEST_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ ":log",
+ ":log_basic_test_impl",
+ "@com_google_googletest//:gtest",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_library(
+ name = "log_basic_test_impl",
+ testonly = True,
+ copts = ABSL_TEST_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ textual_hdrs = ["log_basic_test_impl.inc"],
+ visibility = ["//visibility:private"],
+ deps = [
+ "//absl/base",
+ "//absl/base:log_severity",
+ "//absl/log:globals",
+ "//absl/log:log_entry",
+ "//absl/log:scoped_mock_log",
+ "//absl/log/internal:test_actions",
+ "//absl/log/internal:test_helpers",
+ "//absl/log/internal:test_matchers",
+ "@com_google_googletest//:gtest",
+ ],
+)
+
+cc_test(
+ name = "log_entry_test",
+ size = "small",
+ srcs = ["log_entry_test.cc"],
+ copts = ABSL_TEST_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ ":log_entry",
+ "//absl/base:config",
+ "//absl/base:core_headers",
+ "//absl/base:log_severity",
+ "//absl/log/internal:append_truncated",
+ "//absl/log/internal:format",
+ "//absl/log/internal:test_helpers",
+ "//absl/strings",
+ "//absl/time",
+ "//absl/types:span",
+ "@com_google_googletest//:gtest",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "log_format_test",
+ size = "small",
+ srcs = ["log_format_test.cc"],
+ copts = ABSL_TEST_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ ":check",
+ ":log",
+ ":scoped_mock_log",
+ "//absl/log/internal:test_matchers",
+ "//absl/strings",
+ "//absl/strings:str_format",
+ "//absl/types:optional",
+ "@com_google_googletest//:gtest",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "log_macro_hygiene_test",
+ size = "small",
+ srcs = ["log_macro_hygiene_test.cc"],
+ copts = ABSL_TEST_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ ":log",
+ ":scoped_mock_log",
+ "//absl/base:core_headers",
+ "//absl/base:log_severity",
+ "@com_google_googletest//:gtest",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "log_sink_test",
+ size = "medium",
+ srcs = ["log_sink_test.cc"],
+ copts = ABSL_TEST_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ tags = [
+ "no_test:os:ios",
+ "no_test_ios",
+ "no_test_wasm",
+ ],
+ deps = [
+ ":log",
+ ":log_sink",
+ ":log_sink_registry",
+ ":scoped_mock_log",
+ "//absl/base:core_headers",
+ "//absl/log/internal:test_actions",
+ "//absl/log/internal:test_helpers",
+ "//absl/log/internal:test_matchers",
+ "//absl/strings",
+ "@com_google_googletest//:gtest",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "log_streamer_test",
+ size = "medium",
+ srcs = ["log_streamer_test.cc"],
+ copts = ABSL_TEST_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ ":log",
+ ":log_streamer",
+ ":scoped_mock_log",
+ "//absl/base",
+ "//absl/base:core_headers",
+ "//absl/base:log_severity",
+ "//absl/log/internal:test_actions",
+ "//absl/log/internal:test_helpers",
+ "//absl/log/internal:test_matchers",
+ "//absl/strings",
+ "@com_google_googletest//:gtest",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "log_modifier_methods_test",
+ size = "small",
+ srcs = ["log_modifier_methods_test.cc"],
+ copts = ABSL_TEST_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ ":log",
+ ":log_sink",
+ ":scoped_mock_log",
+ "//absl/log/internal:test_actions",
+ "//absl/log/internal:test_helpers",
+ "//absl/log/internal:test_matchers",
+ "//absl/strings",
+ "//absl/time",
+ "@com_google_googletest//:gtest",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "scoped_mock_log_test",
+ size = "small",
+ srcs = ["scoped_mock_log_test.cc"],
+ copts = ABSL_TEST_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ linkstatic = 1,
+ tags = [
+ "no_test:os:ios",
+ "no_test_ios",
+ "no_test_wasm",
+ ],
+ deps = [
+ ":globals",
+ ":log",
+ ":scoped_mock_log",
+ "//absl/base:core_headers",
+ "//absl/base:log_severity",
+ "//absl/log/internal:test_helpers",
+ "//absl/log/internal:test_matchers",
+ "//absl/memory",
+ "//absl/strings",
+ "//absl/synchronization",
+ "@com_google_googletest//:gtest",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "stripping_test",
+ size = "small",
+ srcs = ["stripping_test.cc"],
+ copts = ABSL_TEST_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ # This test requires all code live in the binary (instead of shared libraries)
+ # because we test for the existence of specific literals in the binary.
+ linkstatic = 1,
+ deps = [
+ ":check",
+ ":log",
+ "//absl/base:log_severity",
+ "//absl/base:strerror",
+ "//absl/flags:program_name",
+ "//absl/log/internal:test_helpers",
+ "//absl/status",
+ "//absl/strings",
+ "//absl/strings:str_format",
+ "@com_google_googletest//:gtest",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "structured_test",
+ size = "small",
+ srcs = ["structured_test.cc"],
+ copts = ABSL_TEST_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ ":log",
+ ":scoped_mock_log",
+ ":structured",
+ "//absl/base:core_headers",
+ "//absl/log/internal:test_helpers",
+ "//absl/log/internal:test_matchers",
+ "@com_google_googletest//:gtest",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "log_benchmark",
+ size = "small",
+ srcs = ["log_benchmark.cc"],
+ copts = ABSL_TEST_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ tags = ["benchmark"],
+ deps = [
+ ":check",
+ ":flags",
+ ":globals",
+ ":log",
+ ":log_entry",
+ ":log_sink",
+ ":log_sink_registry",
+ "//absl/base:core_headers",
+ "//absl/base:log_severity",
+ "//absl/flags:flag",
+ "//absl/log/internal:flags",
+ "@com_github_google_benchmark//:benchmark_main",
+ ],
+)
diff --git a/absl/log/CMakeLists.txt b/absl/log/CMakeLists.txt
new file mode 100644
index 0000000..a7d8b69
--- /dev/null
+++ b/absl/log/CMakeLists.txt
@@ -0,0 +1,1144 @@
+# Copyright 2022 The Abseil Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Internal targets
+absl_cc_library(
+ NAME
+ log_internal_check_impl
+ SRCS
+ HDRS
+ "internal/check_impl.h"
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ LINKOPTS
+ ${ABSL_DEFAULT_LINKOPTS}
+ DEPS
+ absl::core_headers
+ absl::log_internal_check_op
+ absl::log_internal_conditions
+ absl::log_internal_message
+ absl::log_internal_strip
+)
+
+absl_cc_library(
+ NAME
+ log_internal_check_op
+ SRCS
+ "internal/check_op.cc"
+ HDRS
+ "internal/check_op.h"
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ LINKOPTS
+ ${ABSL_DEFAULT_LINKOPTS}
+ DEPS
+ absl::config
+ absl::core_headers
+ absl::log_internal_nullguard
+ absl::log_internal_nullstream
+ absl::log_internal_strip
+ absl::strings
+)
+
+absl_cc_library(
+ NAME
+ log_internal_conditions
+ SRCS
+ "internal/conditions.cc"
+ HDRS
+ "internal/conditions.h"
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ LINKOPTS
+ ${ABSL_DEFAULT_LINKOPTS}
+ DEPS
+ absl::base
+ absl::config
+ absl::core_headers
+ absl::log_internal_voidify
+)
+
+absl_cc_library(
+ NAME
+ log_internal_config
+ SRCS
+ HDRS
+ "internal/config.h"
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ LINKOPTS
+ ${ABSL_DEFAULT_LINKOPTS}
+ DEPS
+ absl::config
+ absl::core_headers
+)
+
+absl_cc_library(
+ NAME
+ log_internal_flags
+ SRCS
+ HDRS
+ "internal/flags.h"
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ LINKOPTS
+ ${ABSL_DEFAULT_LINKOPTS}
+ DEPS
+ absl::flags
+)
+
+absl_cc_library(
+ NAME
+ log_internal_format
+ SRCS
+ "internal/log_format.cc"
+ HDRS
+ "internal/log_format.h"
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ LINKOPTS
+ ${ABSL_DEFAULT_LINKOPTS}
+ DEPS
+ absl::config
+ absl::core_headers
+ absl::log_internal_append_truncated
+ absl::log_internal_config
+ absl::log_internal_globals
+ absl::log_severity
+ absl::strings
+ absl::str_format
+ absl::time
+ absl::span
+)
+
+absl_cc_library(
+ NAME
+ log_internal_globals
+ SRCS
+ "internal/globals.cc"
+ HDRS
+ "internal/globals.h"
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ LINKOPTS
+ ${ABSL_DEFAULT_LINKOPTS}
+ DEPS
+ absl::config
+ absl::core_headers
+ absl::log_severity
+ absl::raw_logging_internal
+ absl::strings
+ absl::time
+)
+
+absl_cc_library(
+ NAME
+ log_internal_log_impl
+ SRCS
+ HDRS
+ "internal/log_impl.h"
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ LINKOPTS
+ ${ABSL_DEFAULT_LINKOPTS}
+ DEPS
+ absl::log_internal_conditions
+ absl::log_internal_message
+ absl::log_internal_strip
+ absl::absl_vlog_is_on
+)
+
+absl_cc_library(
+ NAME
+ log_internal_proto
+ SRCS
+ "internal/proto.cc"
+ HDRS
+ "internal/proto.h"
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ LINKOPTS
+ ${ABSL_DEFAULT_LINKOPTS}
+ DEPS
+ absl::base
+ absl::config
+ absl::core_headers
+ absl::strings
+ absl::span
+)
+
+absl_cc_library(
+ NAME
+ log_internal_message
+ SRCS
+ "internal/log_message.cc"
+ HDRS
+ "internal/log_message.h"
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ LINKOPTS
+ ${ABSL_DEFAULT_LINKOPTS}
+ DEPS
+ absl::base
+ absl::config
+ absl::core_headers
+ absl::errno_saver
+ absl::inlined_vector
+ absl::examine_stack
+ absl::log_internal_append_truncated
+ absl::log_internal_format
+ absl::log_internal_globals
+ absl::log_internal_proto
+ absl::log_internal_log_sink_set
+ absl::log_internal_nullguard
+ absl::log_globals
+ absl::log_entry
+ absl::log_severity
+ absl::log_sink
+ absl::log_sink_registry
+ absl::memory
+ absl::raw_logging_internal
+ absl::strings
+ absl::strerror
+ absl::time
+ absl::span
+)
+
+absl_cc_library(
+ NAME
+ log_internal_log_sink_set
+ SRCS
+ "internal/log_sink_set.cc"
+ HDRS
+ "internal/log_sink_set.h"
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ LINKOPTS
+ ${ABSL_DEFAULT_LINKOPTS}
+ $<$<BOOL:${ANDROID}>:-llog>
+ DEPS
+ absl::base
+ absl::cleanup
+ absl::config
+ absl::core_headers
+ absl::log_internal_config
+ absl::log_internal_globals
+ absl::log_globals
+ absl::log_entry
+ absl::log_severity
+ absl::log_sink
+ absl::no_destructor
+ absl::raw_logging_internal
+ absl::synchronization
+ absl::span
+ absl::strings
+)
+
+absl_cc_library(
+ NAME
+ log_internal_nullguard
+ SRCS
+ "internal/nullguard.cc"
+ HDRS
+ "internal/nullguard.h"
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ LINKOPTS
+ ${ABSL_DEFAULT_LINKOPTS}
+ DEPS
+ absl::config
+ absl::core_headers
+)
+
+absl_cc_library(
+ NAME
+ log_internal_nullstream
+ SRCS
+ HDRS
+ "internal/nullstream.h"
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ LINKOPTS
+ ${ABSL_DEFAULT_LINKOPTS}
+ DEPS
+ absl::config
+ absl::core_headers
+ absl::log_severity
+ absl::strings
+)
+
+absl_cc_library(
+ NAME
+ log_internal_strip
+ SRCS
+ HDRS
+ "internal/strip.h"
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ LINKOPTS
+ ${ABSL_DEFAULT_LINKOPTS}
+ DEPS
+ absl::log_internal_message
+ absl::log_internal_nullstream
+ absl::log_severity
+)
+
+absl_cc_library(
+ NAME
+ log_internal_test_actions
+ SRCS
+ "internal/test_actions.cc"
+ HDRS
+ "internal/test_actions.h"
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ LINKOPTS
+ ${ABSL_DEFAULT_LINKOPTS}
+ DEPS
+ absl::config
+ absl::core_headers
+ absl::log_entry
+ absl::log_severity
+ absl::strings
+ absl::time
+ TESTONLY
+)
+
+absl_cc_library(
+ NAME
+ log_internal_test_helpers
+ SRCS
+ "internal/test_helpers.cc"
+ HDRS
+ "internal/test_helpers.h"
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ LINKOPTS
+ ${ABSL_DEFAULT_LINKOPTS}
+ DEPS
+ absl::config
+ absl::log_globals
+ absl::log_initialize
+ absl::log_internal_globals
+ absl::log_severity
+ GTest::gtest
+ TESTONLY
+)
+
+absl_cc_library(
+ NAME
+ log_internal_test_matchers
+ SRCS
+ "internal/test_matchers.cc"
+ HDRS
+ "internal/test_matchers.h"
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ LINKOPTS
+ ${ABSL_DEFAULT_LINKOPTS}
+ DEPS
+ absl::config
+ absl::core_headers
+ absl::log_entry
+ absl::log_internal_test_helpers
+ absl::log_severity
+ absl::strings
+ absl::time
+ GTest::gtest
+ GTest::gmock
+ TESTONLY
+)
+
+absl_cc_library(
+ NAME
+ log_internal_voidify
+ SRCS
+ HDRS
+ "internal/voidify.h"
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ LINKOPTS
+ ${ABSL_DEFAULT_LINKOPTS}
+ DEPS
+ absl::config
+)
+
+absl_cc_library(
+ NAME
+ log_internal_append_truncated
+ SRCS
+ HDRS
+ "internal/append_truncated.h"
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ LINKOPTS
+ ${ABSL_DEFAULT_LINKOPTS}
+ DEPS
+ absl::config
+ absl::strings
+ absl::span
+)
+
+# Public targets
+absl_cc_library(
+ NAME
+ absl_check
+ SRCS
+ HDRS
+ "absl_check.h"
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ LINKOPTS
+ ${ABSL_DEFAULT_LINKOPTS}
+ DEPS
+ absl::log_internal_check_impl
+ PUBLIC
+)
+
+absl_cc_library(
+ NAME
+ absl_log
+ SRCS
+ HDRS
+ "absl_log.h"
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ LINKOPTS
+ ${ABSL_DEFAULT_LINKOPTS}
+ DEPS
+ absl::log_internal_log_impl
+ PUBLIC
+)
+
+absl_cc_library(
+ NAME
+ check
+ SRCS
+ HDRS
+ "check.h"
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ LINKOPTS
+ ${ABSL_DEFAULT_LINKOPTS}
+ DEPS
+ absl::log_internal_check_impl
+ absl::core_headers
+ absl::log_internal_check_op
+ absl::log_internal_conditions
+ absl::log_internal_message
+ absl::log_internal_strip
+ PUBLIC
+)
+
+absl_cc_library(
+ NAME
+ die_if_null
+ SRCS
+ "die_if_null.cc"
+ HDRS
+ "die_if_null.h"
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ LINKOPTS
+ ${ABSL_DEFAULT_LINKOPTS}
+ DEPS
+ absl::config
+ absl::core_headers
+ absl::log
+ absl::strings
+ PUBLIC
+)
+
+# Warning: Many linkers will strip the contents of this library because its
+# symbols are only used in a global constructor. A workaround is for clients
+# to link this using $<LINK_LIBRARY:WHOLE_ARCHIVE,absl::log_flags> instead of
+# the plain absl::log_flags.
+# TODO(b/320467376): Implement the equivalent of Bazel's alwayslink=True.
+absl_cc_library(
+ NAME
+ log_flags
+ SRCS
+ "flags.cc"
+ HDRS
+ "flags.h"
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ LINKOPTS
+ ${ABSL_DEFAULT_LINKOPTS}
+ DEPS
+ absl::config
+ absl::core_headers
+ absl::log_globals
+ absl::log_severity
+ absl::log_internal_config
+ absl::log_internal_flags
+ absl::flags
+ absl::flags_marshalling
+ absl::strings
+ absl::vlog_config_internal
+ PUBLIC
+)
+
+absl_cc_library(
+ NAME
+ log_globals
+ SRCS
+ "globals.cc"
+ HDRS
+ "globals.h"
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ LINKOPTS
+ ${ABSL_DEFAULT_LINKOPTS}
+ DEPS
+ absl::atomic_hook
+ absl::config
+ absl::core_headers
+ absl::hash
+ absl::log_severity
+ absl::raw_logging_internal
+ absl::strings
+ absl::vlog_config_internal
+)
+
+absl_cc_library(
+ NAME
+ log_initialize
+ SRCS
+ "initialize.cc"
+ HDRS
+ "initialize.h"
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ LINKOPTS
+ ${ABSL_DEFAULT_LINKOPTS}
+ DEPS
+ absl::config
+ absl::log_globals
+ absl::log_internal_globals
+ absl::time
+ PUBLIC
+)
+
+absl_cc_library(
+ NAME
+ log
+ SRCS
+ HDRS
+ "log.h"
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ LINKOPTS
+ ${ABSL_DEFAULT_LINKOPTS}
+ DEPS
+ absl::log_internal_log_impl
+ absl::vlog_is_on
+ PUBLIC
+)
+
+absl_cc_library(
+ NAME
+ log_entry
+ SRCS
+ "log_entry.cc"
+ HDRS
+ "log_entry.h"
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ LINKOPTS
+ ${ABSL_DEFAULT_LINKOPTS}
+ DEPS
+ absl::config
+ absl::core_headers
+ absl::log_internal_config
+ absl::log_severity
+ absl::span
+ absl::strings
+ absl::time
+ PUBLIC
+)
+
+absl_cc_library(
+ NAME
+ log_sink
+ SRCS
+ "log_sink.cc"
+ HDRS
+ "log_sink.h"
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ LINKOPTS
+ ${ABSL_DEFAULT_LINKOPTS}
+ DEPS
+ absl::config
+ absl::log_entry
+ PUBLIC
+)
+
+absl_cc_library(
+ NAME
+ log_sink_registry
+ SRCS
+ HDRS
+ "log_sink_registry.h"
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ LINKOPTS
+ ${ABSL_DEFAULT_LINKOPTS}
+ DEPS
+ absl::config
+ absl::log_sink
+ absl::log_internal_log_sink_set
+ PUBLIC
+)
+
+absl_cc_library(
+ NAME
+ log_streamer
+ SRCS
+ HDRS
+ "log_streamer.h"
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ LINKOPTS
+ ${ABSL_DEFAULT_LINKOPTS}
+ DEPS
+ absl::config
+ absl::absl_log
+ absl::log_severity
+ absl::optional
+ absl::strings
+ absl::strings_internal
+ absl::utility
+ PUBLIC
+)
+
+absl_cc_library(
+ NAME
+ scoped_mock_log
+ SRCS
+ "scoped_mock_log.cc"
+ HDRS
+ "scoped_mock_log.h"
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ LINKOPTS
+ ${ABSL_DEFAULT_LINKOPTS}
+ DEPS
+ absl::config
+ absl::log_entry
+ absl::log_severity
+ absl::log_sink
+ absl::log_sink_registry
+ absl::raw_logging_internal
+ absl::strings
+ GTest::gmock
+ GTest::gtest
+ PUBLIC
+ TESTONLY
+)
+
+absl_cc_library(
+ NAME
+ log_internal_structured
+ HDRS
+ "internal/structured.h"
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ LINKOPTS
+ ${ABSL_DEFAULT_LINKOPTS}
+ DEPS
+ absl::config
+ absl::log_internal_message
+ absl::strings
+)
+
+absl_cc_library(
+ NAME
+ log_structured
+ HDRS
+ "structured.h"
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ LINKOPTS
+ ${ABSL_DEFAULT_LINKOPTS}
+ DEPS
+ absl::config
+ absl::log_internal_structured
+ absl::strings
+ PUBLIC
+)
+
+absl_cc_library(
+ NAME
+ vlog_config_internal
+ SRCS
+ "internal/vlog_config.cc"
+ HDRS
+ "internal/vlog_config.h"
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ LINKOPTS
+ ${ABSL_DEFAULT_LINKOPTS}
+ DEPS
+ absl::base
+ absl::config
+ absl::core_headers
+ absl::log_internal_fnmatch
+ absl::memory
+ absl::no_destructor
+ absl::strings
+ absl::synchronization
+ absl::optional
+)
+
+absl_cc_library(
+ NAME
+ absl_vlog_is_on
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ LINKOPTS
+ ${ABSL_DEFAULT_LINKOPTS}
+ HDRS
+ "absl_vlog_is_on.h"
+ DEPS
+ absl::vlog_config_internal
+ absl::config
+ absl::core_headers
+ absl::strings
+)
+
+absl_cc_library(
+ NAME
+ vlog_is_on
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ LINKOPTS
+ ${ABSL_DEFAULT_LINKOPTS}
+ HDRS
+ "vlog_is_on.h"
+ DEPS
+ absl::absl_vlog_is_on
+)
+
+absl_cc_test(
+ NAME
+ vlog_is_on_test
+ SRCS
+ "vlog_is_on_test.cc"
+ COPTS
+ ${ABSL_TEST_COPTS}
+ LINKOPTS
+ ${ABSL_DEFAULT_LINKOPTS}
+ DEPS
+ absl::log
+ absl::log_flags
+ absl::log_globals
+ absl::scoped_mock_log
+ absl::vlog_is_on
+ absl::log_severity
+ absl::flags
+ absl::optional
+ GTest::gmock_main
+)
+
+absl_cc_library(
+ NAME
+ log_internal_fnmatch
+ SRCS
+ "internal/fnmatch.cc"
+ HDRS
+ "internal/fnmatch.h"
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ LINKOPTS
+ ${ABSL_DEFAULT_LINKOPTS}
+ DEPS
+ absl::config
+ absl::strings
+)
+
+# Test targets
+
+absl_cc_test(
+ NAME
+ absl_check_test
+ SRCS
+ "absl_check_test.cc"
+ "check_test_impl.inc"
+ COPTS
+ ${ABSL_TEST_COPTS}
+ LINKOPTS
+ ${ABSL_DEFAULT_LINKOPTS}
+ DEPS
+ absl::absl_check
+ absl::config
+ absl::core_headers
+ absl::log_internal_test_helpers
+ absl::status
+ GTest::gmock_main
+)
+
+absl_cc_test(
+ NAME
+ absl_log_basic_test
+ SRCS
+ "log_basic_test.cc"
+ "log_basic_test_impl.inc"
+ COPTS
+ ${ABSL_TEST_COPTS}
+ LINKOPTS
+ ${ABSL_DEFAULT_LINKOPTS}
+ DEPS
+ absl::base
+ absl::absl_log
+ absl::log_entry
+ absl::log_globals
+ absl::log_severity
+ absl::log_internal_test_actions
+ absl::log_internal_test_helpers
+ absl::log_internal_test_matchers
+ absl::scoped_mock_log
+ GTest::gmock_main
+)
+
+absl_cc_test(
+ NAME
+ check_test
+ SRCS
+ "check_test.cc"
+ "check_test_impl.inc"
+ COPTS
+ ${ABSL_TEST_COPTS}
+ LINKOPTS
+ ${ABSL_DEFAULT_LINKOPTS}
+ DEPS
+ absl::check
+ absl::config
+ absl::core_headers
+ absl::log_internal_test_helpers
+ absl::status
+ GTest::gmock_main
+)
+
+absl_cc_test(
+ NAME
+ die_if_null_test
+ SRCS
+ "die_if_null_test.cc"
+ COPTS
+ ${ABSL_TEST_COPTS}
+ LINKOPTS
+ ${ABSL_DEFAULT_LINKOPTS}
+ DEPS
+ absl::core_headers
+ absl::die_if_null
+ absl::log_internal_test_helpers
+ GTest::gtest_main
+)
+
+absl_cc_test(
+ NAME
+ log_basic_test
+ SRCS
+ "log_basic_test.cc"
+ "log_basic_test_impl.inc"
+ COPTS
+ ${ABSL_TEST_COPTS}
+ LINKOPTS
+ ${ABSL_DEFAULT_LINKOPTS}
+ DEPS
+ absl::base
+ absl::log
+ absl::log_entry
+ absl::log_globals
+ absl::log_severity
+ absl::log_internal_test_actions
+ absl::log_internal_test_helpers
+ absl::log_internal_test_matchers
+ absl::scoped_mock_log
+ GTest::gmock_main
+)
+
+absl_cc_test(
+ NAME
+ log_entry_test
+ SRCS
+ "log_entry_test.cc"
+ COPTS
+ ${ABSL_TEST_COPTS}
+ LINKOPTS
+ ${ABSL_DEFAULT_LINKOPTS}
+ DEPS
+ absl::config
+ absl::core_headers
+ absl::log_entry
+ absl::log_internal_append_truncated
+ absl::log_internal_format
+ absl::log_internal_globals
+ absl::log_internal_test_helpers
+ absl::log_severity
+ absl::span
+ absl::strings
+ absl::time
+ GTest::gmock_main
+)
+
+absl_cc_test(
+ NAME
+ log_flags_test
+ SRCS
+ "flags_test.cc"
+ COPTS
+ ${ABSL_TEST_COPTS}
+ LINKOPTS
+ ${ABSL_DEFAULT_LINKOPTS}
+ DEPS
+ absl::core_headers
+ absl::log
+ absl::log_flags
+ absl::log_globals
+ absl::log_internal_flags
+ absl::log_internal_test_helpers
+ absl::log_internal_test_matchers
+ absl::log_severity
+ absl::flags
+ absl::flags_reflection
+ absl::scoped_mock_log
+ absl::strings
+ GTest::gmock_main
+)
+
+absl_cc_test(
+ NAME
+ log_globals_test
+ SRCS
+ "globals_test.cc"
+ COPTS
+ ${ABSL_TEST_COPTS}
+ LINKOPTS
+ ${ABSL_DEFAULT_LINKOPTS}
+ DEPS
+ absl::core_headers
+ absl::log
+ absl::log_globals
+ absl::log_internal_globals
+ absl::log_internal_test_helpers
+ absl::log_severity
+ absl::scoped_mock_log
+ GTest::gmock_main
+)
+
+absl_cc_test(
+ NAME
+ log_format_test
+ SRCS
+ "log_format_test.cc"
+ COPTS
+ ${ABSL_TEST_COPTS}
+ LINKOPTS
+ ${ABSL_DEFAULT_LINKOPTS}
+ DEPS
+ absl::check
+ absl::log
+ absl::log_internal_test_matchers
+ absl::optional
+ absl::scoped_mock_log
+ absl::str_format
+ absl::strings
+ GTest::gmock_main
+)
+
+absl_cc_test(
+ NAME
+ log_macro_hygiene_test
+ SRCS
+ "log_macro_hygiene_test.cc"
+ COPTS
+ ${ABSL_TEST_COPTS}
+ LINKOPTS
+ ${ABSL_DEFAULT_LINKOPTS}
+ DEPS
+ absl::core_headers
+ absl::log
+ absl::log_severity
+ absl::scoped_mock_log
+ GTest::gmock_main
+)
+
+absl_cc_test(
+ NAME
+ log_sink_test
+ SRCS
+ "log_sink_test.cc"
+ COPTS
+ ${ABSL_TEST_COPTS}
+ LINKOPTS
+ ${ABSL_DEFAULT_LINKOPTS}
+ DEPS
+ absl::core_headers
+ absl::log
+ absl::log_internal_test_actions
+ absl::log_internal_test_helpers
+ absl::log_internal_test_matchers
+ absl::log_sink
+ absl::log_sink_registry
+ absl::log_severity
+ absl::scoped_mock_log
+ absl::strings
+ GTest::gmock_main
+)
+
+absl_cc_test(
+ NAME
+ log_streamer_test
+ SRCS
+ "log_streamer_test.cc"
+ COPTS
+ ${ABSL_TEST_COPTS}
+ LINKOPTS
+ ${ABSL_DEFAULT_LINKOPTS}
+ DEPS
+ absl::base
+ absl::core_headers
+ absl::log
+ absl::log_internal_test_actions
+ absl::log_internal_test_helpers
+ absl::log_internal_test_matchers
+ absl::log_streamer
+ absl::log_severity
+ absl::scoped_mock_log
+ absl::strings
+ GTest::gmock_main
+)
+
+absl_cc_test(
+ NAME
+ log_modifier_methods_test
+ SRCS
+ "log_modifier_methods_test.cc"
+ COPTS
+ ${ABSL_TEST_COPTS}
+ LINKOPTS
+ ${ABSL_DEFAULT_LINKOPTS}
+ DEPS
+ absl::log
+ absl::log_internal_test_actions
+ absl::log_internal_test_helpers
+ absl::log_internal_test_matchers
+ absl::log_sink
+ absl::scoped_mock_log
+ absl::strings
+ absl::time
+ GTest::gmock_main
+)
+
+absl_cc_test(
+ NAME
+ scoped_mock_log_test
+ SRCS
+ "scoped_mock_log_test.cc"
+ COPTS
+ ${ABSL_TEST_COPTS}
+ LINKOPTS
+ ${ABSL_DEFAULT_LINKOPTS}
+ DEPS
+ absl::core_headers
+ absl::log
+ absl::log_globals
+ absl::log_internal_globals
+ absl::log_internal_test_helpers
+ absl::log_internal_test_matchers
+ absl::log_severity
+ absl::memory
+ absl::scoped_mock_log
+ absl::strings
+ absl::synchronization
+ GTest::gmock
+ GTest::gtest_main
+)
+
+absl_cc_test(
+ NAME
+ log_internal_stderr_log_sink_test
+ SRCS
+ "internal/stderr_log_sink_test.cc"
+ COPTS
+ ${ABSL_TEST_COPTS}
+ LINKOPTS
+ ${ABSL_DEFAULT_LINKOPTS}
+ DEPS
+ absl::core_headers
+ absl::log
+ absl::log_globals
+ absl::log_internal_test_helpers
+ absl::log_severity
+ GTest::gmock_main
+)
+
+absl_cc_test(
+ NAME
+ log_stripping_test
+ SRCS
+ "stripping_test.cc"
+ COPTS
+ ${ABSL_TEST_COPTS}
+ LINKOPTS
+ ${ABSL_DEFAULT_LINKOPTS}
+ DEPS
+ absl::check
+ absl::flags_program_name
+ absl::log
+ absl::log_internal_test_helpers
+ absl::log_severity
+ absl::status
+ absl::strerror
+ absl::strings
+ absl::str_format
+ GTest::gmock_main
+)
+
+absl_cc_test(
+ NAME
+ log_structured_test
+ SRCS
+ "structured_test.cc"
+ COPTS
+ ${ABSL_TEST_COPTS}
+ LINKOPTS
+ ${ABSL_DEFAULT_LINKOPTS}
+ DEPS
+ absl::core_headers
+ absl::log
+ absl::log_internal_test_helpers
+ absl::log_internal_test_matchers
+ absl::log_structured
+ absl::scoped_mock_log
+ GTest::gmock_main
+)
+
+absl_cc_test(
+ NAME
+ internal_fnmatch_test
+ SRCS
+ "internal/fnmatch_test.cc"
+ COPTS
+ ${ABSL_TEST_COPTS}
+ LINKOPTS
+ ${ABSL_DEFAULT_LINKOPTS}
+ DEPS
+ absl::log_internal_fnmatch
+ GTest::gmock_main
+)
diff --git a/absl/log/absl_check.h b/absl/log/absl_check.h
new file mode 100644
index 0000000..1bb43bd
--- /dev/null
+++ b/absl/log/absl_check.h
@@ -0,0 +1,117 @@
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// -----------------------------------------------------------------------------
+// File: log/absl_check.h
+// -----------------------------------------------------------------------------
+//
+// This header declares a family of `ABSL_CHECK` macros as alternative spellings
+// for `CHECK` macros in `check.h`.
+//
+// Except for those whose names begin with `ABSL_DCHECK`, these macros are not
+// controlled by `NDEBUG` (cf. `assert`), so the check will be executed
+// regardless of compilation mode. `ABSL_CHECK` and friends are thus useful for
+// confirming invariants in situations where continuing to run would be worse
+// than terminating, e.g., due to risk of data corruption or security
+// compromise. It is also more robust and portable to deliberately terminate
+// at a particular place with a useful message and backtrace than to assume some
+// ultimately unspecified and unreliable crashing behavior (such as a
+// "segmentation fault").
+//
+// For full documentation of each macro, see comments in `check.h`, which has an
+// identical set of macros without the ABSL_* prefix.
+
+#ifndef ABSL_LOG_ABSL_CHECK_H_
+#define ABSL_LOG_ABSL_CHECK_H_
+
+#include "absl/log/internal/check_impl.h"
+
+#define ABSL_CHECK(condition) \
+ ABSL_LOG_INTERNAL_CHECK_IMPL((condition), #condition)
+#define ABSL_QCHECK(condition) \
+ ABSL_LOG_INTERNAL_QCHECK_IMPL((condition), #condition)
+#define ABSL_PCHECK(condition) \
+ ABSL_LOG_INTERNAL_PCHECK_IMPL((condition), #condition)
+#define ABSL_DCHECK(condition) \
+ ABSL_LOG_INTERNAL_DCHECK_IMPL((condition), #condition)
+
+#define ABSL_CHECK_EQ(val1, val2) \
+ ABSL_LOG_INTERNAL_CHECK_EQ_IMPL((val1), #val1, (val2), #val2)
+#define ABSL_CHECK_NE(val1, val2) \
+ ABSL_LOG_INTERNAL_CHECK_NE_IMPL((val1), #val1, (val2), #val2)
+#define ABSL_CHECK_LE(val1, val2) \
+ ABSL_LOG_INTERNAL_CHECK_LE_IMPL((val1), #val1, (val2), #val2)
+#define ABSL_CHECK_LT(val1, val2) \
+ ABSL_LOG_INTERNAL_CHECK_LT_IMPL((val1), #val1, (val2), #val2)
+#define ABSL_CHECK_GE(val1, val2) \
+ ABSL_LOG_INTERNAL_CHECK_GE_IMPL((val1), #val1, (val2), #val2)
+#define ABSL_CHECK_GT(val1, val2) \
+ ABSL_LOG_INTERNAL_CHECK_GT_IMPL((val1), #val1, (val2), #val2)
+#define ABSL_QCHECK_EQ(val1, val2) \
+ ABSL_LOG_INTERNAL_QCHECK_EQ_IMPL((val1), #val1, (val2), #val2)
+#define ABSL_QCHECK_NE(val1, val2) \
+ ABSL_LOG_INTERNAL_QCHECK_NE_IMPL((val1), #val1, (val2), #val2)
+#define ABSL_QCHECK_LE(val1, val2) \
+ ABSL_LOG_INTERNAL_QCHECK_LE_IMPL((val1), #val1, (val2), #val2)
+#define ABSL_QCHECK_LT(val1, val2) \
+ ABSL_LOG_INTERNAL_QCHECK_LT_IMPL((val1), #val1, (val2), #val2)
+#define ABSL_QCHECK_GE(val1, val2) \
+ ABSL_LOG_INTERNAL_QCHECK_GE_IMPL((val1), #val1, (val2), #val2)
+#define ABSL_QCHECK_GT(val1, val2) \
+ ABSL_LOG_INTERNAL_QCHECK_GT_IMPL((val1), #val1, (val2), #val2)
+#define ABSL_DCHECK_EQ(val1, val2) \
+ ABSL_LOG_INTERNAL_DCHECK_EQ_IMPL((val1), #val1, (val2), #val2)
+#define ABSL_DCHECK_NE(val1, val2) \
+ ABSL_LOG_INTERNAL_DCHECK_NE_IMPL((val1), #val1, (val2), #val2)
+#define ABSL_DCHECK_LE(val1, val2) \
+ ABSL_LOG_INTERNAL_DCHECK_LE_IMPL((val1), #val1, (val2), #val2)
+#define ABSL_DCHECK_LT(val1, val2) \
+ ABSL_LOG_INTERNAL_DCHECK_LT_IMPL((val1), #val1, (val2), #val2)
+#define ABSL_DCHECK_GE(val1, val2) \
+ ABSL_LOG_INTERNAL_DCHECK_GE_IMPL((val1), #val1, (val2), #val2)
+#define ABSL_DCHECK_GT(val1, val2) \
+ ABSL_LOG_INTERNAL_DCHECK_GT_IMPL((val1), #val1, (val2), #val2)
+
+#define ABSL_CHECK_OK(status) ABSL_LOG_INTERNAL_CHECK_OK_IMPL((status), #status)
+#define ABSL_QCHECK_OK(status) \
+ ABSL_LOG_INTERNAL_QCHECK_OK_IMPL((status), #status)
+#define ABSL_DCHECK_OK(status) \
+ ABSL_LOG_INTERNAL_DCHECK_OK_IMPL((status), #status)
+
+#define ABSL_CHECK_STREQ(s1, s2) \
+ ABSL_LOG_INTERNAL_CHECK_STREQ_IMPL((s1), #s1, (s2), #s2)
+#define ABSL_CHECK_STRNE(s1, s2) \
+ ABSL_LOG_INTERNAL_CHECK_STRNE_IMPL((s1), #s1, (s2), #s2)
+#define ABSL_CHECK_STRCASEEQ(s1, s2) \
+ ABSL_LOG_INTERNAL_CHECK_STRCASEEQ_IMPL((s1), #s1, (s2), #s2)
+#define ABSL_CHECK_STRCASENE(s1, s2) \
+ ABSL_LOG_INTERNAL_CHECK_STRCASENE_IMPL((s1), #s1, (s2), #s2)
+#define ABSL_QCHECK_STREQ(s1, s2) \
+ ABSL_LOG_INTERNAL_QCHECK_STREQ_IMPL((s1), #s1, (s2), #s2)
+#define ABSL_QCHECK_STRNE(s1, s2) \
+ ABSL_LOG_INTERNAL_QCHECK_STRNE_IMPL((s1), #s1, (s2), #s2)
+#define ABSL_QCHECK_STRCASEEQ(s1, s2) \
+ ABSL_LOG_INTERNAL_QCHECK_STRCASEEQ_IMPL((s1), #s1, (s2), #s2)
+#define ABSL_QCHECK_STRCASENE(s1, s2) \
+ ABSL_LOG_INTERNAL_QCHECK_STRCASENE_IMPL((s1), #s1, (s2), #s2)
+#define ABSL_DCHECK_STREQ(s1, s2) \
+ ABSL_LOG_INTERNAL_DCHECK_STREQ_IMPL((s1), #s1, (s2), #s2)
+#define ABSL_DCHECK_STRNE(s1, s2) \
+ ABSL_LOG_INTERNAL_DCHECK_STRNE_IMPL((s1), #s1, (s2), #s2)
+#define ABSL_DCHECK_STRCASEEQ(s1, s2) \
+ ABSL_LOG_INTERNAL_DCHECK_STRCASEEQ_IMPL((s1), #s1, (s2), #s2)
+#define ABSL_DCHECK_STRCASENE(s1, s2) \
+ ABSL_LOG_INTERNAL_DCHECK_STRCASENE_IMPL((s1), #s1, (s2), #s2)
+
+#endif // ABSL_LOG_ABSL_CHECK_H_
diff --git a/absl/log/absl_check_test.cc b/absl/log/absl_check_test.cc
new file mode 100644
index 0000000..d84940f
--- /dev/null
+++ b/absl/log/absl_check_test.cc
@@ -0,0 +1,58 @@
+//
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/log/absl_check.h"
+
+#define ABSL_TEST_CHECK ABSL_CHECK
+#define ABSL_TEST_CHECK_OK ABSL_CHECK_OK
+#define ABSL_TEST_CHECK_EQ ABSL_CHECK_EQ
+#define ABSL_TEST_CHECK_NE ABSL_CHECK_NE
+#define ABSL_TEST_CHECK_GE ABSL_CHECK_GE
+#define ABSL_TEST_CHECK_LE ABSL_CHECK_LE
+#define ABSL_TEST_CHECK_GT ABSL_CHECK_GT
+#define ABSL_TEST_CHECK_LT ABSL_CHECK_LT
+#define ABSL_TEST_CHECK_STREQ ABSL_CHECK_STREQ
+#define ABSL_TEST_CHECK_STRNE ABSL_CHECK_STRNE
+#define ABSL_TEST_CHECK_STRCASEEQ ABSL_CHECK_STRCASEEQ
+#define ABSL_TEST_CHECK_STRCASENE ABSL_CHECK_STRCASENE
+
+#define ABSL_TEST_DCHECK ABSL_DCHECK
+#define ABSL_TEST_DCHECK_OK ABSL_DCHECK_OK
+#define ABSL_TEST_DCHECK_EQ ABSL_DCHECK_EQ
+#define ABSL_TEST_DCHECK_NE ABSL_DCHECK_NE
+#define ABSL_TEST_DCHECK_GE ABSL_DCHECK_GE
+#define ABSL_TEST_DCHECK_LE ABSL_DCHECK_LE
+#define ABSL_TEST_DCHECK_GT ABSL_DCHECK_GT
+#define ABSL_TEST_DCHECK_LT ABSL_DCHECK_LT
+#define ABSL_TEST_DCHECK_STREQ ABSL_DCHECK_STREQ
+#define ABSL_TEST_DCHECK_STRNE ABSL_DCHECK_STRNE
+#define ABSL_TEST_DCHECK_STRCASEEQ ABSL_DCHECK_STRCASEEQ
+#define ABSL_TEST_DCHECK_STRCASENE ABSL_DCHECK_STRCASENE
+
+#define ABSL_TEST_QCHECK ABSL_QCHECK
+#define ABSL_TEST_QCHECK_OK ABSL_QCHECK_OK
+#define ABSL_TEST_QCHECK_EQ ABSL_QCHECK_EQ
+#define ABSL_TEST_QCHECK_NE ABSL_QCHECK_NE
+#define ABSL_TEST_QCHECK_GE ABSL_QCHECK_GE
+#define ABSL_TEST_QCHECK_LE ABSL_QCHECK_LE
+#define ABSL_TEST_QCHECK_GT ABSL_QCHECK_GT
+#define ABSL_TEST_QCHECK_LT ABSL_QCHECK_LT
+#define ABSL_TEST_QCHECK_STREQ ABSL_QCHECK_STREQ
+#define ABSL_TEST_QCHECK_STRNE ABSL_QCHECK_STRNE
+#define ABSL_TEST_QCHECK_STRCASEEQ ABSL_QCHECK_STRCASEEQ
+#define ABSL_TEST_QCHECK_STRCASENE ABSL_QCHECK_STRCASENE
+
+#include "gtest/gtest.h"
+#include "absl/log/check_test_impl.inc"
diff --git a/absl/log/absl_log.h b/absl/log/absl_log.h
new file mode 100644
index 0000000..0fd9ae3
--- /dev/null
+++ b/absl/log/absl_log.h
@@ -0,0 +1,115 @@
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// -----------------------------------------------------------------------------
+// File: log/absl_log.h
+// -----------------------------------------------------------------------------
+//
+// This header declares a family of `ABSL_LOG` macros as alternative spellings
+// for macros in `log.h`.
+//
+// Basic invocation looks like this:
+//
+// ABSL_LOG(INFO) << "Found " << num_cookies << " cookies";
+//
+// Most `ABSL_LOG` macros take a severity level argument. The severity levels
+// are `INFO`, `WARNING`, `ERROR`, and `FATAL`.
+//
+// For full documentation, see comments in `log.h`, which includes full
+// reference documentation on use of the equivalent `LOG` macro and has an
+// identical set of macros without the ABSL_* prefix.
+
+#ifndef ABSL_LOG_ABSL_LOG_H_
+#define ABSL_LOG_ABSL_LOG_H_
+
+#include "absl/log/internal/log_impl.h"
+
+#define ABSL_LOG(severity) ABSL_LOG_INTERNAL_LOG_IMPL(_##severity)
+#define ABSL_PLOG(severity) ABSL_LOG_INTERNAL_PLOG_IMPL(_##severity)
+#define ABSL_DLOG(severity) ABSL_LOG_INTERNAL_DLOG_IMPL(_##severity)
+
+#define ABSL_VLOG(verbose_level) ABSL_LOG_INTERNAL_VLOG_IMPL(verbose_level)
+#define ABSL_DVLOG(verbose_level) ABSL_LOG_INTERNAL_DVLOG_IMPL(verbose_level)
+
+#define ABSL_LOG_IF(severity, condition) \
+ ABSL_LOG_INTERNAL_LOG_IF_IMPL(_##severity, condition)
+#define ABSL_PLOG_IF(severity, condition) \
+ ABSL_LOG_INTERNAL_PLOG_IF_IMPL(_##severity, condition)
+#define ABSL_DLOG_IF(severity, condition) \
+ ABSL_LOG_INTERNAL_DLOG_IF_IMPL(_##severity, condition)
+
+#define ABSL_LOG_EVERY_N(severity, n) \
+ ABSL_LOG_INTERNAL_LOG_EVERY_N_IMPL(_##severity, n)
+#define ABSL_LOG_FIRST_N(severity, n) \
+ ABSL_LOG_INTERNAL_LOG_FIRST_N_IMPL(_##severity, n)
+#define ABSL_LOG_EVERY_POW_2(severity) \
+ ABSL_LOG_INTERNAL_LOG_EVERY_POW_2_IMPL(_##severity)
+#define ABSL_LOG_EVERY_N_SEC(severity, n_seconds) \
+ ABSL_LOG_INTERNAL_LOG_EVERY_N_SEC_IMPL(_##severity, n_seconds)
+
+#define ABSL_PLOG_EVERY_N(severity, n) \
+ ABSL_LOG_INTERNAL_PLOG_EVERY_N_IMPL(_##severity, n)
+#define ABSL_PLOG_FIRST_N(severity, n) \
+ ABSL_LOG_INTERNAL_PLOG_FIRST_N_IMPL(_##severity, n)
+#define ABSL_PLOG_EVERY_POW_2(severity) \
+ ABSL_LOG_INTERNAL_PLOG_EVERY_POW_2_IMPL(_##severity)
+#define ABSL_PLOG_EVERY_N_SEC(severity, n_seconds) \
+ ABSL_LOG_INTERNAL_PLOG_EVERY_N_SEC_IMPL(_##severity, n_seconds)
+
+#define ABSL_DLOG_EVERY_N(severity, n) \
+ ABSL_LOG_INTERNAL_DLOG_EVERY_N_IMPL(_##severity, n)
+#define ABSL_DLOG_FIRST_N(severity, n) \
+ ABSL_LOG_INTERNAL_DLOG_FIRST_N_IMPL(_##severity, n)
+#define ABSL_DLOG_EVERY_POW_2(severity) \
+ ABSL_LOG_INTERNAL_DLOG_EVERY_POW_2_IMPL(_##severity)
+#define ABSL_DLOG_EVERY_N_SEC(severity, n_seconds) \
+ ABSL_LOG_INTERNAL_DLOG_EVERY_N_SEC_IMPL(_##severity, n_seconds)
+
+#define ABSL_VLOG_EVERY_N(verbose_level, n) \
+ ABSL_LOG_INTERNAL_VLOG_EVERY_N_IMPL(verbose_level, n)
+#define ABSL_VLOG_FIRST_N(verbose_level, n) \
+ ABSL_LOG_INTERNAL_VLOG_FIRST_N_IMPL(verbose_level, n)
+#define ABSL_VLOG_EVERY_POW_2(verbose_level, n) \
+ ABSL_LOG_INTERNAL_VLOG_EVERY_POW_2_IMPL(verbose_level, n)
+#define ABSL_VLOG_EVERY_N_SEC(verbose_level, n) \
+ ABSL_LOG_INTERNAL_VLOG_EVERY_N_SEC_IMPL(verbose_level, n)
+
+#define ABSL_LOG_IF_EVERY_N(severity, condition, n) \
+ ABSL_LOG_INTERNAL_LOG_IF_EVERY_N_IMPL(_##severity, condition, n)
+#define ABSL_LOG_IF_FIRST_N(severity, condition, n) \
+ ABSL_LOG_INTERNAL_LOG_IF_FIRST_N_IMPL(_##severity, condition, n)
+#define ABSL_LOG_IF_EVERY_POW_2(severity, condition) \
+ ABSL_LOG_INTERNAL_LOG_IF_EVERY_POW_2_IMPL(_##severity, condition)
+#define ABSL_LOG_IF_EVERY_N_SEC(severity, condition, n_seconds) \
+ ABSL_LOG_INTERNAL_LOG_IF_EVERY_N_SEC_IMPL(_##severity, condition, n_seconds)
+
+#define ABSL_PLOG_IF_EVERY_N(severity, condition, n) \
+ ABSL_LOG_INTERNAL_PLOG_IF_EVERY_N_IMPL(_##severity, condition, n)
+#define ABSL_PLOG_IF_FIRST_N(severity, condition, n) \
+ ABSL_LOG_INTERNAL_PLOG_IF_FIRST_N_IMPL(_##severity, condition, n)
+#define ABSL_PLOG_IF_EVERY_POW_2(severity, condition) \
+ ABSL_LOG_INTERNAL_PLOG_IF_EVERY_POW_2_IMPL(_##severity, condition)
+#define ABSL_PLOG_IF_EVERY_N_SEC(severity, condition, n_seconds) \
+ ABSL_LOG_INTERNAL_PLOG_IF_EVERY_N_SEC_IMPL(_##severity, condition, n_seconds)
+
+#define ABSL_DLOG_IF_EVERY_N(severity, condition, n) \
+ ABSL_LOG_INTERNAL_DLOG_IF_EVERY_N_IMPL(_##severity, condition, n)
+#define ABSL_DLOG_IF_FIRST_N(severity, condition, n) \
+ ABSL_LOG_INTERNAL_DLOG_IF_FIRST_N_IMPL(_##severity, condition, n)
+#define ABSL_DLOG_IF_EVERY_POW_2(severity, condition) \
+ ABSL_LOG_INTERNAL_DLOG_IF_EVERY_POW_2_IMPL(_##severity, condition)
+#define ABSL_DLOG_IF_EVERY_N_SEC(severity, condition, n_seconds) \
+ ABSL_LOG_INTERNAL_DLOG_IF_EVERY_N_SEC_IMPL(_##severity, condition, n_seconds)
+
+#endif // ABSL_LOG_ABSL_LOG_H_
diff --git a/absl/debugging/leak_check_disable.cc b/absl/log/absl_log_basic_test.cc
similarity index 67%
copy from absl/debugging/leak_check_disable.cc
copy to absl/log/absl_log_basic_test.cc
index 924d6e3..3a4b83c 100644
--- a/absl/debugging/leak_check_disable.cc
+++ b/absl/log/absl_log_basic_test.cc
@@ -1,4 +1,5 @@
-// Copyright 2017 The Abseil Authors.
+//
+// Copyright 2022 The Abseil Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -12,9 +13,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-// Disable LeakSanitizer when this file is linked in.
-// This function overrides __lsan_is_turned_off from sanitizer/lsan_interface.h
-extern "C" int __lsan_is_turned_off();
-extern "C" int __lsan_is_turned_off() {
- return 1;
-}
+#include "absl/log/absl_log.h"
+
+#define ABSL_TEST_LOG ABSL_LOG
+
+#include "gtest/gtest.h"
+#include "absl/log/log_basic_test_impl.inc"
diff --git a/absl/log/absl_vlog_is_on.h b/absl/log/absl_vlog_is_on.h
new file mode 100644
index 0000000..29096b4
--- /dev/null
+++ b/absl/log/absl_vlog_is_on.h
@@ -0,0 +1,93 @@
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// -----------------------------------------------------------------------------
+// File: log/absl_vlog_is_on.h
+// -----------------------------------------------------------------------------
+//
+// This header defines the `ABSL_VLOG_IS_ON()` macro that controls the
+// variable-verbosity conditional logging.
+//
+// It's used by `VLOG` in log.h, or it can also be used directly like this:
+//
+// if (ABSL_VLOG_IS_ON(2)) {
+// foo_server.RecomputeStatisticsExpensive();
+// LOG(INFO) << foo_server.LastStatisticsAsString();
+// }
+//
+// Each source file has an effective verbosity level that's a non-negative
+// integer computed from the `--vmodule` and `--v` flags.
+// `ABSL_VLOG_IS_ON(n)` is true, and `VLOG(n)` logs, if that effective verbosity
+// level is greater than or equal to `n`.
+//
+// `--vmodule` takes a comma-delimited list of key=value pairs. Each key is a
+// pattern matched against filenames, and the values give the effective severity
+// level applied to matching files. '?' and '*' characters in patterns are
+// interpreted as single-character and zero-or-more-character wildcards.
+// Patterns including a slash character are matched against full pathnames,
+// while those without are matched against basenames only. One suffix (i.e. the
+// last . and everything after it) is stripped from each filename prior to
+// matching, as is the special suffix "-inl".
+//
+// Files are matched against globs in `--vmodule` in order, and the first match
+// determines the verbosity level.
+//
+// Files which do not match any pattern in `--vmodule` use the value of `--v` as
+// their effective verbosity level. The default is 0.
+//
+// SetVLOGLevel helper function is provided to do limited dynamic control over
+// V-logging by appending to `--vmodule`. Because these go at the beginning of
+// the list, they take priority over any globs previously added.
+//
+// Resetting --vmodule will override all previous modifications to `--vmodule`,
+// including via SetVLOGLevel.
+
+#ifndef ABSL_LOG_ABSL_VLOG_IS_ON_H_
+#define ABSL_LOG_ABSL_VLOG_IS_ON_H_
+
+#include "absl/base/attributes.h"
+#include "absl/base/config.h"
+#include "absl/log/internal/vlog_config.h" // IWYU pragma: export
+#include "absl/strings/string_view.h"
+
+// IWYU pragma: private, include "absl/log/log.h"
+
+// This is expanded at the callsite to allow the compiler to optimize
+// always-false cases out of the build.
+// An ABSL_MAX_VLOG_VERBOSITY of 2 means that VLOG(3) and above should never
+// log.
+#ifdef ABSL_MAX_VLOG_VERBOSITY
+#define ABSL_LOG_INTERNAL_MAX_LOG_VERBOSITY_CHECK(x) \
+ ((x) <= ABSL_MAX_VLOG_VERBOSITY)&&
+#else
+#define ABSL_LOG_INTERNAL_MAX_LOG_VERBOSITY_CHECK(x)
+#endif
+
+// Each ABSL_VLOG_IS_ON call site gets its own VLogSite that registers with the
+// global linked list of sites to asynchronously update its verbosity level on
+// changes to --v or --vmodule. The verbosity can also be set by manually
+// calling SetVLOGLevel.
+//
+// ABSL_VLOG_IS_ON is not async signal safe, but it is guaranteed not to
+// allocate new memory.
+#define ABSL_VLOG_IS_ON(verbose_level) \
+ (ABSL_LOG_INTERNAL_MAX_LOG_VERBOSITY_CHECK(verbose_level)[]() \
+ ->::absl::log_internal::VLogSite * \
+ { \
+ ABSL_CONST_INIT static ::absl::log_internal::VLogSite site(__FILE__); \
+ return &site; \
+ }() \
+ ->IsEnabled(verbose_level))
+
+#endif // ABSL_LOG_ABSL_VLOG_IS_ON_H_
diff --git a/absl/log/check.h b/absl/log/check.h
new file mode 100644
index 0000000..50f633d
--- /dev/null
+++ b/absl/log/check.h
@@ -0,0 +1,209 @@
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// -----------------------------------------------------------------------------
+// File: log/check.h
+// -----------------------------------------------------------------------------
+//
+// This header declares a family of `CHECK` macros.
+//
+// `CHECK` macros terminate the program with a fatal error if the specified
+// condition is not true.
+//
+// Except for those whose names begin with `DCHECK`, these macros are not
+// controlled by `NDEBUG` (cf. `assert`), so the check will be executed
+// regardless of compilation mode. `CHECK` and friends are thus useful for
+// confirming invariants in situations where continuing to run would be worse
+// than terminating, e.g., due to risk of data corruption or security
+// compromise. It is also more robust and portable to deliberately terminate
+// at a particular place with a useful message and backtrace than to assume some
+// ultimately unspecified and unreliable crashing behavior (such as a
+// "segmentation fault").
+
+#ifndef ABSL_LOG_CHECK_H_
+#define ABSL_LOG_CHECK_H_
+
+#include "absl/log/internal/check_impl.h"
+#include "absl/log/internal/check_op.h" // IWYU pragma: export
+#include "absl/log/internal/conditions.h" // IWYU pragma: export
+#include "absl/log/internal/log_message.h" // IWYU pragma: export
+#include "absl/log/internal/strip.h" // IWYU pragma: export
+
+// CHECK()
+//
+// `CHECK` terminates the program with a fatal error if `condition` is not true.
+//
+// The message may include additional information such as stack traces, when
+// available.
+//
+// Example:
+//
+// CHECK(!cheese.empty()) << "Out of Cheese";
+//
+// Might produce a message like:
+//
+// Check failed: !cheese.empty() Out of Cheese
+#define CHECK(condition) ABSL_LOG_INTERNAL_CHECK_IMPL((condition), #condition)
+
+// QCHECK()
+//
+// `QCHECK` behaves like `CHECK` but does not print a full stack trace and does
+// not run registered error handlers (as `QFATAL`). It is useful when the
+// problem is definitely unrelated to program flow, e.g. when validating user
+// input.
+#define QCHECK(condition) ABSL_LOG_INTERNAL_QCHECK_IMPL((condition), #condition)
+
+// PCHECK()
+//
+// `PCHECK` behaves like `CHECK` but appends a description of the current state
+// of `errno` to the failure message.
+//
+// Example:
+//
+// int fd = open("/var/empty/missing", O_RDONLY);
+// PCHECK(fd != -1) << "posix is difficult";
+//
+// Might produce a message like:
+//
+// Check failed: fd != -1 posix is difficult: No such file or directory [2]
+#define PCHECK(condition) ABSL_LOG_INTERNAL_PCHECK_IMPL((condition), #condition)
+
+// DCHECK()
+//
+// `DCHECK` behaves like `CHECK` in debug mode and does nothing otherwise (as
+// `DLOG`). Unlike with `CHECK` (but as with `assert`), it is not safe to rely
+// on evaluation of `condition`: when `NDEBUG` is enabled, DCHECK does not
+// evaluate the condition.
+#define DCHECK(condition) ABSL_LOG_INTERNAL_DCHECK_IMPL((condition), #condition)
+
+// `CHECK_EQ` and friends are syntactic sugar for `CHECK(x == y)` that
+// automatically output the expression being tested and the evaluated values on
+// either side.
+//
+// Example:
+//
+// int x = 3, y = 5;
+// CHECK_EQ(2 * x, y) << "oops!";
+//
+// Might produce a message like:
+//
+// Check failed: 2 * x == y (6 vs. 5) oops!
+//
+// The values must implement the appropriate comparison operator as well as
+// `operator<<(std::ostream&, ...)`. Care is taken to ensure that each
+// argument is evaluated exactly once, and that anything which is legal to pass
+// as a function argument is legal here. In particular, the arguments may be
+// temporary expressions which will end up being destroyed at the end of the
+// statement,
+//
+// Example:
+//
+// CHECK_EQ(std::string("abc")[1], 'b');
+//
+// WARNING: Passing `NULL` as an argument to `CHECK_EQ` and similar macros does
+// not compile. Use `nullptr` instead.
+#define CHECK_EQ(val1, val2) \
+ ABSL_LOG_INTERNAL_CHECK_EQ_IMPL((val1), #val1, (val2), #val2)
+#define CHECK_NE(val1, val2) \
+ ABSL_LOG_INTERNAL_CHECK_NE_IMPL((val1), #val1, (val2), #val2)
+#define CHECK_LE(val1, val2) \
+ ABSL_LOG_INTERNAL_CHECK_LE_IMPL((val1), #val1, (val2), #val2)
+#define CHECK_LT(val1, val2) \
+ ABSL_LOG_INTERNAL_CHECK_LT_IMPL((val1), #val1, (val2), #val2)
+#define CHECK_GE(val1, val2) \
+ ABSL_LOG_INTERNAL_CHECK_GE_IMPL((val1), #val1, (val2), #val2)
+#define CHECK_GT(val1, val2) \
+ ABSL_LOG_INTERNAL_CHECK_GT_IMPL((val1), #val1, (val2), #val2)
+#define QCHECK_EQ(val1, val2) \
+ ABSL_LOG_INTERNAL_QCHECK_EQ_IMPL((val1), #val1, (val2), #val2)
+#define QCHECK_NE(val1, val2) \
+ ABSL_LOG_INTERNAL_QCHECK_NE_IMPL((val1), #val1, (val2), #val2)
+#define QCHECK_LE(val1, val2) \
+ ABSL_LOG_INTERNAL_QCHECK_LE_IMPL((val1), #val1, (val2), #val2)
+#define QCHECK_LT(val1, val2) \
+ ABSL_LOG_INTERNAL_QCHECK_LT_IMPL((val1), #val1, (val2), #val2)
+#define QCHECK_GE(val1, val2) \
+ ABSL_LOG_INTERNAL_QCHECK_GE_IMPL((val1), #val1, (val2), #val2)
+#define QCHECK_GT(val1, val2) \
+ ABSL_LOG_INTERNAL_QCHECK_GT_IMPL((val1), #val1, (val2), #val2)
+#define DCHECK_EQ(val1, val2) \
+ ABSL_LOG_INTERNAL_DCHECK_EQ_IMPL((val1), #val1, (val2), #val2)
+#define DCHECK_NE(val1, val2) \
+ ABSL_LOG_INTERNAL_DCHECK_NE_IMPL((val1), #val1, (val2), #val2)
+#define DCHECK_LE(val1, val2) \
+ ABSL_LOG_INTERNAL_DCHECK_LE_IMPL((val1), #val1, (val2), #val2)
+#define DCHECK_LT(val1, val2) \
+ ABSL_LOG_INTERNAL_DCHECK_LT_IMPL((val1), #val1, (val2), #val2)
+#define DCHECK_GE(val1, val2) \
+ ABSL_LOG_INTERNAL_DCHECK_GE_IMPL((val1), #val1, (val2), #val2)
+#define DCHECK_GT(val1, val2) \
+ ABSL_LOG_INTERNAL_DCHECK_GT_IMPL((val1), #val1, (val2), #val2)
+
+// `CHECK_OK` and friends validate that the provided `absl::Status` or
+// `absl::StatusOr<T>` is OK. If it isn't, they print a failure message that
+// includes the actual status and terminate the program.
+//
+// As with all `DCHECK` variants, `DCHECK_OK` has no effect (not even
+// evaluating its argument) if `NDEBUG` is enabled.
+//
+// Example:
+//
+// CHECK_OK(FunctionReturnsStatus(x, y, z)) << "oops!";
+//
+// Might produce a message like:
+//
+// Check failed: FunctionReturnsStatus(x, y, z) is OK (ABORTED: timeout) oops!
+#define CHECK_OK(status) ABSL_LOG_INTERNAL_CHECK_OK_IMPL((status), #status)
+#define QCHECK_OK(status) ABSL_LOG_INTERNAL_QCHECK_OK_IMPL((status), #status)
+#define DCHECK_OK(status) ABSL_LOG_INTERNAL_DCHECK_OK_IMPL((status), #status)
+
+// `CHECK_STREQ` and friends provide `CHECK_EQ` functionality for C strings,
+// i.e., null-terminated char arrays. The `CASE` versions are case-insensitive.
+//
+// Example:
+//
+// CHECK_STREQ(argv[0], "./skynet");
+//
+// Note that both arguments may be temporary strings which are destroyed by the
+// compiler at the end of the current full expression.
+//
+// Example:
+//
+// CHECK_STREQ(Foo().c_str(), Bar().c_str());
+#define CHECK_STREQ(s1, s2) \
+ ABSL_LOG_INTERNAL_CHECK_STREQ_IMPL((s1), #s1, (s2), #s2)
+#define CHECK_STRNE(s1, s2) \
+ ABSL_LOG_INTERNAL_CHECK_STRNE_IMPL((s1), #s1, (s2), #s2)
+#define CHECK_STRCASEEQ(s1, s2) \
+ ABSL_LOG_INTERNAL_CHECK_STRCASEEQ_IMPL((s1), #s1, (s2), #s2)
+#define CHECK_STRCASENE(s1, s2) \
+ ABSL_LOG_INTERNAL_CHECK_STRCASENE_IMPL((s1), #s1, (s2), #s2)
+#define QCHECK_STREQ(s1, s2) \
+ ABSL_LOG_INTERNAL_QCHECK_STREQ_IMPL((s1), #s1, (s2), #s2)
+#define QCHECK_STRNE(s1, s2) \
+ ABSL_LOG_INTERNAL_QCHECK_STRNE_IMPL((s1), #s1, (s2), #s2)
+#define QCHECK_STRCASEEQ(s1, s2) \
+ ABSL_LOG_INTERNAL_QCHECK_STRCASEEQ_IMPL((s1), #s1, (s2), #s2)
+#define QCHECK_STRCASENE(s1, s2) \
+ ABSL_LOG_INTERNAL_QCHECK_STRCASENE_IMPL((s1), #s1, (s2), #s2)
+#define DCHECK_STREQ(s1, s2) \
+ ABSL_LOG_INTERNAL_DCHECK_STREQ_IMPL((s1), #s1, (s2), #s2)
+#define DCHECK_STRNE(s1, s2) \
+ ABSL_LOG_INTERNAL_DCHECK_STRNE_IMPL((s1), #s1, (s2), #s2)
+#define DCHECK_STRCASEEQ(s1, s2) \
+ ABSL_LOG_INTERNAL_DCHECK_STRCASEEQ_IMPL((s1), #s1, (s2), #s2)
+#define DCHECK_STRCASENE(s1, s2) \
+ ABSL_LOG_INTERNAL_DCHECK_STRCASENE_IMPL((s1), #s1, (s2), #s2)
+
+#endif // ABSL_LOG_CHECK_H_
diff --git a/absl/log/check_test.cc b/absl/log/check_test.cc
new file mode 100644
index 0000000..ef415bd
--- /dev/null
+++ b/absl/log/check_test.cc
@@ -0,0 +1,58 @@
+//
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/log/check.h"
+
+#define ABSL_TEST_CHECK CHECK
+#define ABSL_TEST_CHECK_OK CHECK_OK
+#define ABSL_TEST_CHECK_EQ CHECK_EQ
+#define ABSL_TEST_CHECK_NE CHECK_NE
+#define ABSL_TEST_CHECK_GE CHECK_GE
+#define ABSL_TEST_CHECK_LE CHECK_LE
+#define ABSL_TEST_CHECK_GT CHECK_GT
+#define ABSL_TEST_CHECK_LT CHECK_LT
+#define ABSL_TEST_CHECK_STREQ CHECK_STREQ
+#define ABSL_TEST_CHECK_STRNE CHECK_STRNE
+#define ABSL_TEST_CHECK_STRCASEEQ CHECK_STRCASEEQ
+#define ABSL_TEST_CHECK_STRCASENE CHECK_STRCASENE
+
+#define ABSL_TEST_DCHECK DCHECK
+#define ABSL_TEST_DCHECK_OK DCHECK_OK
+#define ABSL_TEST_DCHECK_EQ DCHECK_EQ
+#define ABSL_TEST_DCHECK_NE DCHECK_NE
+#define ABSL_TEST_DCHECK_GE DCHECK_GE
+#define ABSL_TEST_DCHECK_LE DCHECK_LE
+#define ABSL_TEST_DCHECK_GT DCHECK_GT
+#define ABSL_TEST_DCHECK_LT DCHECK_LT
+#define ABSL_TEST_DCHECK_STREQ DCHECK_STREQ
+#define ABSL_TEST_DCHECK_STRNE DCHECK_STRNE
+#define ABSL_TEST_DCHECK_STRCASEEQ DCHECK_STRCASEEQ
+#define ABSL_TEST_DCHECK_STRCASENE DCHECK_STRCASENE
+
+#define ABSL_TEST_QCHECK QCHECK
+#define ABSL_TEST_QCHECK_OK QCHECK_OK
+#define ABSL_TEST_QCHECK_EQ QCHECK_EQ
+#define ABSL_TEST_QCHECK_NE QCHECK_NE
+#define ABSL_TEST_QCHECK_GE QCHECK_GE
+#define ABSL_TEST_QCHECK_LE QCHECK_LE
+#define ABSL_TEST_QCHECK_GT QCHECK_GT
+#define ABSL_TEST_QCHECK_LT QCHECK_LT
+#define ABSL_TEST_QCHECK_STREQ QCHECK_STREQ
+#define ABSL_TEST_QCHECK_STRNE QCHECK_STRNE
+#define ABSL_TEST_QCHECK_STRCASEEQ QCHECK_STRCASEEQ
+#define ABSL_TEST_QCHECK_STRCASENE QCHECK_STRCASENE
+
+#include "gtest/gtest.h"
+#include "absl/log/check_test_impl.inc"
diff --git a/absl/log/check_test_impl.inc b/absl/log/check_test_impl.inc
new file mode 100644
index 0000000..d5c0aee
--- /dev/null
+++ b/absl/log/check_test_impl.inc
@@ -0,0 +1,528 @@
+//
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef ABSL_LOG_CHECK_TEST_IMPL_H_
+#define ABSL_LOG_CHECK_TEST_IMPL_H_
+
+// Verify that both sets of macros behave identically by parameterizing the
+// entire test file.
+#ifndef ABSL_TEST_CHECK
+#error ABSL_TEST_CHECK must be defined for these tests to work.
+#endif
+
+#include <ostream>
+#include <string>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/base/attributes.h"
+#include "absl/base/config.h"
+#include "absl/log/internal/test_helpers.h"
+#include "absl/status/status.h"
+
+// NOLINTBEGIN(misc-definitions-in-headers)
+
+namespace absl_log_internal {
+
+using ::testing::AllOf;
+using ::testing::HasSubstr;
+using ::testing::Not;
+
+auto* test_env ABSL_ATTRIBUTE_UNUSED = ::testing::AddGlobalTestEnvironment(
+ new absl::log_internal::LogTestEnvironment);
+
+#if GTEST_HAS_DEATH_TEST
+
+TEST(CHECKDeathTest, TestBasicValues) {
+ ABSL_TEST_CHECK(true);
+
+ EXPECT_DEATH(ABSL_TEST_CHECK(false), "Check failed: false");
+
+ int i = 2;
+ ABSL_TEST_CHECK(i != 3); // NOLINT
+}
+
+#endif // GTEST_HAS_DEATH_TEST
+
+TEST(CHECKTest, TestLogicExpressions) {
+ int i = 5;
+ ABSL_TEST_CHECK(i > 0 && i < 10);
+ ABSL_TEST_CHECK(i < 0 || i > 3);
+}
+
+#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 201703L
+ABSL_CONST_INIT const auto global_var_check = [](int i) {
+ ABSL_TEST_CHECK(i > 0); // NOLINT
+ return i + 1;
+}(3);
+
+ABSL_CONST_INIT const auto global_var = [](int i) {
+ ABSL_TEST_CHECK_GE(i, 0); // NOLINT
+ return i + 1;
+}(global_var_check);
+#endif // ABSL_INTERNAL_CPLUSPLUS_LANG
+
+TEST(CHECKTest, TestPlacementsInCompoundStatements) {
+ // check placement inside if/else clauses
+ if (true) ABSL_TEST_CHECK(true);
+
+ if (false)
+ ; // NOLINT
+ else
+ ABSL_TEST_CHECK(true);
+
+ switch (0)
+ case 0:
+ ABSL_TEST_CHECK(true); // NOLINT
+
+#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 201703L
+ constexpr auto var = [](int i) {
+ ABSL_TEST_CHECK(i > 0); // NOLINT
+ return i + 1;
+ }(global_var);
+ (void)var;
+#endif // ABSL_INTERNAL_CPLUSPLUS_LANG
+}
+
+TEST(CHECKTest, TestBoolConvertible) {
+ struct Tester {
+ } tester;
+ ABSL_TEST_CHECK([&]() { return &tester; }());
+}
+
+#if GTEST_HAS_DEATH_TEST
+
+TEST(CHECKDeathTest, TestChecksWithSideEffects) {
+ int var = 0;
+ ABSL_TEST_CHECK([&var]() {
+ ++var;
+ return true;
+ }());
+ EXPECT_EQ(var, 1);
+
+ EXPECT_DEATH(ABSL_TEST_CHECK([&var]() {
+ ++var;
+ return false;
+ }()) << var,
+ "Check failed: .* 2");
+}
+
+#endif // GTEST_HAS_DEATH_TEST
+
+template <int a, int b>
+constexpr int sum() {
+ return a + b;
+}
+#define MACRO_ONE 1
+#define TEMPLATE_SUM(a, b) sum<a, b>()
+#define CONCAT(a, b) a b
+#define IDENTITY(x) x
+
+TEST(CHECKTest, TestPassingMacroExpansion) {
+ ABSL_TEST_CHECK(IDENTITY(true));
+ ABSL_TEST_CHECK_EQ(TEMPLATE_SUM(MACRO_ONE, 2), 3);
+ ABSL_TEST_CHECK_STREQ(CONCAT("x", "y"), "xy");
+}
+
+#if GTEST_HAS_DEATH_TEST
+
+TEST(CHECKTest, TestMacroExpansionInMessage) {
+ auto MessageGen = []() { ABSL_TEST_CHECK(IDENTITY(false)); };
+ EXPECT_DEATH(MessageGen(), HasSubstr("IDENTITY(false)"));
+}
+
+TEST(CHECKTest, TestNestedMacroExpansionInMessage) {
+ EXPECT_DEATH(ABSL_TEST_CHECK(IDENTITY(false)), HasSubstr("IDENTITY(false)"));
+}
+
+TEST(CHECKTest, TestMacroExpansionCompare) {
+ EXPECT_DEATH(ABSL_TEST_CHECK_EQ(IDENTITY(false), IDENTITY(true)),
+ HasSubstr("IDENTITY(false) == IDENTITY(true)"));
+ EXPECT_DEATH(ABSL_TEST_CHECK_GT(IDENTITY(1), IDENTITY(2)),
+ HasSubstr("IDENTITY(1) > IDENTITY(2)"));
+}
+
+TEST(CHECKTest, TestMacroExpansionStrCompare) {
+ EXPECT_DEATH(ABSL_TEST_CHECK_STREQ(IDENTITY("x"), IDENTITY("y")),
+ HasSubstr("IDENTITY(\"x\") == IDENTITY(\"y\")"));
+ EXPECT_DEATH(ABSL_TEST_CHECK_STRCASENE(IDENTITY("a"), IDENTITY("A")),
+ HasSubstr("IDENTITY(\"a\") != IDENTITY(\"A\")"));
+}
+
+TEST(CHECKTest, TestMacroExpansionStatus) {
+ EXPECT_DEATH(
+ ABSL_TEST_CHECK_OK(IDENTITY(absl::FailedPreconditionError("message"))),
+ HasSubstr("IDENTITY(absl::FailedPreconditionError(\"message\"))"));
+}
+
+TEST(CHECKTest, TestMacroExpansionComma) {
+ EXPECT_DEATH(ABSL_TEST_CHECK(TEMPLATE_SUM(MACRO_ONE, 2) == 4),
+ HasSubstr("TEMPLATE_SUM(MACRO_ONE, 2) == 4"));
+}
+
+TEST(CHECKTest, TestMacroExpansionCommaCompare) {
+ EXPECT_DEATH(
+ ABSL_TEST_CHECK_EQ(TEMPLATE_SUM(2, MACRO_ONE), TEMPLATE_SUM(3, 2)),
+ HasSubstr("TEMPLATE_SUM(2, MACRO_ONE) == TEMPLATE_SUM(3, 2)"));
+ EXPECT_DEATH(
+ ABSL_TEST_CHECK_GT(TEMPLATE_SUM(2, MACRO_ONE), TEMPLATE_SUM(3, 2)),
+ HasSubstr("TEMPLATE_SUM(2, MACRO_ONE) > TEMPLATE_SUM(3, 2)"));
+}
+
+TEST(CHECKTest, TestMacroExpansionCommaStrCompare) {
+ EXPECT_DEATH(ABSL_TEST_CHECK_STREQ(CONCAT("x", "y"), "z"),
+ HasSubstr("CONCAT(\"x\", \"y\") == \"z\""));
+ EXPECT_DEATH(ABSL_TEST_CHECK_STRNE(CONCAT("x", "y"), "xy"),
+ HasSubstr("CONCAT(\"x\", \"y\") != \"xy\""));
+}
+
+#endif // GTEST_HAS_DEATH_TEST
+
+#undef TEMPLATE_SUM
+#undef CONCAT
+#undef MACRO
+#undef ONE
+
+#if GTEST_HAS_DEATH_TEST
+
+TEST(CHECKDeachTest, TestOrderOfInvocationsBetweenCheckAndMessage) {
+ int counter = 0;
+
+ auto GetStr = [&counter]() -> std::string {
+ return counter++ == 0 ? "" : "non-empty";
+ };
+
+ EXPECT_DEATH(ABSL_TEST_CHECK(!GetStr().empty()) << GetStr(),
+ HasSubstr("non-empty"));
+}
+
+TEST(CHECKTest, TestSecondaryFailure) {
+ auto FailingRoutine = []() {
+ ABSL_TEST_CHECK(false) << "Secondary";
+ return false;
+ };
+ EXPECT_DEATH(ABSL_TEST_CHECK(FailingRoutine()) << "Primary",
+ AllOf(HasSubstr("Secondary"), Not(HasSubstr("Primary"))));
+}
+
+TEST(CHECKTest, TestSecondaryFailureInMessage) {
+ auto MessageGen = []() {
+ ABSL_TEST_CHECK(false) << "Secondary";
+ return "Primary";
+ };
+ EXPECT_DEATH(ABSL_TEST_CHECK(false) << MessageGen(),
+ AllOf(HasSubstr("Secondary"), Not(HasSubstr("Primary"))));
+}
+
+#endif // GTEST_HAS_DEATH_TEST
+
+TEST(CHECKTest, TestBinaryChecksWithPrimitives) {
+ ABSL_TEST_CHECK_EQ(1, 1);
+ ABSL_TEST_CHECK_NE(1, 2);
+ ABSL_TEST_CHECK_GE(1, 1);
+ ABSL_TEST_CHECK_GE(2, 1);
+ ABSL_TEST_CHECK_LE(1, 1);
+ ABSL_TEST_CHECK_LE(1, 2);
+ ABSL_TEST_CHECK_GT(2, 1);
+ ABSL_TEST_CHECK_LT(1, 2);
+}
+
+// For testing using CHECK*() on anonymous enums.
+enum { CASE_A, CASE_B };
+
+TEST(CHECKTest, TestBinaryChecksWithEnumValues) {
+ // Tests using CHECK*() on anonymous enums.
+ ABSL_TEST_CHECK_EQ(CASE_A, CASE_A);
+ ABSL_TEST_CHECK_NE(CASE_A, CASE_B);
+ ABSL_TEST_CHECK_GE(CASE_A, CASE_A);
+ ABSL_TEST_CHECK_GE(CASE_B, CASE_A);
+ ABSL_TEST_CHECK_LE(CASE_A, CASE_A);
+ ABSL_TEST_CHECK_LE(CASE_A, CASE_B);
+ ABSL_TEST_CHECK_GT(CASE_B, CASE_A);
+ ABSL_TEST_CHECK_LT(CASE_A, CASE_B);
+}
+
+TEST(CHECKTest, TestBinaryChecksWithNullptr) {
+ const void* p_null = nullptr;
+ const void* p_not_null = &p_null;
+ ABSL_TEST_CHECK_EQ(p_null, nullptr);
+ ABSL_TEST_CHECK_EQ(nullptr, p_null);
+ ABSL_TEST_CHECK_NE(p_not_null, nullptr);
+ ABSL_TEST_CHECK_NE(nullptr, p_not_null);
+}
+
+#if GTEST_HAS_DEATH_TEST
+
+// Test logging of various char-typed values by failing CHECK*().
+TEST(CHECKDeathTest, TestComparingCharsValues) {
+ {
+ char a = ';';
+ char b = 'b';
+ EXPECT_DEATH(ABSL_TEST_CHECK_EQ(a, b),
+ "Check failed: a == b \\(';' vs. 'b'\\)");
+ b = 1;
+ EXPECT_DEATH(ABSL_TEST_CHECK_EQ(a, b),
+ "Check failed: a == b \\(';' vs. char value 1\\)");
+ }
+ {
+ signed char a = ';';
+ signed char b = 'b';
+ EXPECT_DEATH(ABSL_TEST_CHECK_EQ(a, b),
+ "Check failed: a == b \\(';' vs. 'b'\\)");
+ b = -128;
+ EXPECT_DEATH(ABSL_TEST_CHECK_EQ(a, b),
+ "Check failed: a == b \\(';' vs. signed char value -128\\)");
+ }
+ {
+ unsigned char a = ';';
+ unsigned char b = 'b';
+ EXPECT_DEATH(ABSL_TEST_CHECK_EQ(a, b),
+ "Check failed: a == b \\(';' vs. 'b'\\)");
+ b = 128;
+ EXPECT_DEATH(ABSL_TEST_CHECK_EQ(a, b),
+ "Check failed: a == b \\(';' vs. unsigned char value 128\\)");
+ }
+}
+
+TEST(CHECKDeathTest, TestNullValuesAreReportedCleanly) {
+ const char* a = nullptr;
+ const char* b = nullptr;
+ EXPECT_DEATH(ABSL_TEST_CHECK_NE(a, b),
+ "Check failed: a != b \\(\\(null\\) vs. \\(null\\)\\)");
+
+ a = "xx";
+ EXPECT_DEATH(ABSL_TEST_CHECK_EQ(a, b),
+ "Check failed: a == b \\(xx vs. \\(null\\)\\)");
+ EXPECT_DEATH(ABSL_TEST_CHECK_EQ(b, a),
+ "Check failed: b == a \\(\\(null\\) vs. xx\\)");
+
+ std::nullptr_t n{};
+ EXPECT_DEATH(ABSL_TEST_CHECK_NE(n, nullptr),
+ "Check failed: n != nullptr \\(\\(null\\) vs. \\(null\\)\\)");
+}
+
+#endif // GTEST_HAS_DEATH_TEST
+
+TEST(CHECKTest, TestSTREQ) {
+ ABSL_TEST_CHECK_STREQ("this", "this");
+ ABSL_TEST_CHECK_STREQ(nullptr, nullptr);
+ ABSL_TEST_CHECK_STRCASEEQ("this", "tHiS");
+ ABSL_TEST_CHECK_STRCASEEQ(nullptr, nullptr);
+ ABSL_TEST_CHECK_STRNE("this", "tHiS");
+ ABSL_TEST_CHECK_STRNE("this", nullptr);
+ ABSL_TEST_CHECK_STRCASENE("this", "that");
+ ABSL_TEST_CHECK_STRCASENE(nullptr, "that");
+ ABSL_TEST_CHECK_STREQ((std::string("a") + "b").c_str(), "ab");
+ ABSL_TEST_CHECK_STREQ(std::string("test").c_str(),
+ (std::string("te") + std::string("st")).c_str());
+}
+
+TEST(CHECKTest, TestComparisonPlacementsInCompoundStatements) {
+ // check placement inside if/else clauses
+ if (true) ABSL_TEST_CHECK_EQ(1, 1);
+ if (true) ABSL_TEST_CHECK_STREQ("c", "c");
+
+ if (false)
+ ; // NOLINT
+ else
+ ABSL_TEST_CHECK_LE(0, 1);
+
+ if (false)
+ ; // NOLINT
+ else
+ ABSL_TEST_CHECK_STRNE("a", "b");
+
+ switch (0)
+ case 0:
+ ABSL_TEST_CHECK_NE(1, 0);
+
+ switch (0)
+ case 0:
+ ABSL_TEST_CHECK_STRCASEEQ("A", "a");
+
+#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 201703L
+ constexpr auto var = [](int i) {
+ ABSL_TEST_CHECK_GT(i, 0);
+ return i + 1;
+ }(global_var);
+ (void)var;
+
+ // CHECK_STR... checks are not supported in constexpr routines.
+ // constexpr auto var2 = [](int i) {
+ // ABSL_TEST_CHECK_STRNE("c", "d");
+ // return i + 1;
+ // }(global_var);
+
+#if defined(__GNUC__)
+ int var3 = (({ ABSL_TEST_CHECK_LE(1, 2); }), global_var < 10) ? 1 : 0;
+ (void)var3;
+
+ int var4 = (({ ABSL_TEST_CHECK_STREQ("a", "a"); }), global_var < 10) ? 1 : 0;
+ (void)var4;
+#endif // __GNUC__
+#endif // ABSL_INTERNAL_CPLUSPLUS_LANG
+}
+
+TEST(CHECKTest, TestDCHECK) {
+#ifdef NDEBUG
+ ABSL_TEST_DCHECK(1 == 2) << " DCHECK's shouldn't be compiled in normal mode";
+#endif
+ ABSL_TEST_DCHECK(1 == 1); // NOLINT(readability/check)
+ ABSL_TEST_DCHECK_EQ(1, 1);
+ ABSL_TEST_DCHECK_NE(1, 2);
+ ABSL_TEST_DCHECK_GE(1, 1);
+ ABSL_TEST_DCHECK_GE(2, 1);
+ ABSL_TEST_DCHECK_LE(1, 1);
+ ABSL_TEST_DCHECK_LE(1, 2);
+ ABSL_TEST_DCHECK_GT(2, 1);
+ ABSL_TEST_DCHECK_LT(1, 2);
+
+ // Test DCHECK on std::nullptr_t
+ const void* p_null = nullptr;
+ const void* p_not_null = &p_null;
+ ABSL_TEST_DCHECK_EQ(p_null, nullptr);
+ ABSL_TEST_DCHECK_EQ(nullptr, p_null);
+ ABSL_TEST_DCHECK_NE(p_not_null, nullptr);
+ ABSL_TEST_DCHECK_NE(nullptr, p_not_null);
+}
+
+TEST(CHECKTest, TestQCHECK) {
+ // The tests that QCHECK does the same as CHECK
+ ABSL_TEST_QCHECK(1 == 1); // NOLINT(readability/check)
+ ABSL_TEST_QCHECK_EQ(1, 1);
+ ABSL_TEST_QCHECK_NE(1, 2);
+ ABSL_TEST_QCHECK_GE(1, 1);
+ ABSL_TEST_QCHECK_GE(2, 1);
+ ABSL_TEST_QCHECK_LE(1, 1);
+ ABSL_TEST_QCHECK_LE(1, 2);
+ ABSL_TEST_QCHECK_GT(2, 1);
+ ABSL_TEST_QCHECK_LT(1, 2);
+
+ // Tests using QCHECK*() on anonymous enums.
+ ABSL_TEST_QCHECK_EQ(CASE_A, CASE_A);
+ ABSL_TEST_QCHECK_NE(CASE_A, CASE_B);
+ ABSL_TEST_QCHECK_GE(CASE_A, CASE_A);
+ ABSL_TEST_QCHECK_GE(CASE_B, CASE_A);
+ ABSL_TEST_QCHECK_LE(CASE_A, CASE_A);
+ ABSL_TEST_QCHECK_LE(CASE_A, CASE_B);
+ ABSL_TEST_QCHECK_GT(CASE_B, CASE_A);
+ ABSL_TEST_QCHECK_LT(CASE_A, CASE_B);
+}
+
+TEST(CHECKTest, TestQCHECKPlacementsInCompoundStatements) {
+ // check placement inside if/else clauses
+ if (true) ABSL_TEST_QCHECK(true);
+
+ if (false)
+ ; // NOLINT
+ else
+ ABSL_TEST_QCHECK(true);
+
+ if (false)
+ ; // NOLINT
+ else
+ ABSL_TEST_QCHECK(true);
+
+ switch (0)
+ case 0:
+ ABSL_TEST_QCHECK(true);
+
+#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 201703L
+ constexpr auto var = [](int i) {
+ ABSL_TEST_QCHECK(i > 0); // NOLINT
+ return i + 1;
+ }(global_var);
+ (void)var;
+
+#if defined(__GNUC__)
+ int var2 = (({ ABSL_TEST_CHECK_LE(1, 2); }), global_var < 10) ? 1 : 0;
+ (void)var2;
+#endif // __GNUC__
+#endif // ABSL_INTERNAL_CPLUSPLUS_LANG
+}
+
+class ComparableType {
+ public:
+ explicit ComparableType(int v) : v_(v) {}
+
+ void MethodWithCheck(int i) {
+ ABSL_TEST_CHECK_EQ(*this, i);
+ ABSL_TEST_CHECK_EQ(i, *this);
+ }
+
+ int Get() const { return v_; }
+
+ private:
+ friend bool operator==(const ComparableType& lhs, const ComparableType& rhs) {
+ return lhs.v_ == rhs.v_;
+ }
+ friend bool operator!=(const ComparableType& lhs, const ComparableType& rhs) {
+ return lhs.v_ != rhs.v_;
+ }
+ friend bool operator<(const ComparableType& lhs, const ComparableType& rhs) {
+ return lhs.v_ < rhs.v_;
+ }
+ friend bool operator<=(const ComparableType& lhs, const ComparableType& rhs) {
+ return lhs.v_ <= rhs.v_;
+ }
+ friend bool operator>(const ComparableType& lhs, const ComparableType& rhs) {
+ return lhs.v_ > rhs.v_;
+ }
+ friend bool operator>=(const ComparableType& lhs, const ComparableType& rhs) {
+ return lhs.v_ >= rhs.v_;
+ }
+ friend bool operator==(const ComparableType& lhs, int rhs) {
+ return lhs.v_ == rhs;
+ }
+ friend bool operator==(int lhs, const ComparableType& rhs) {
+ return lhs == rhs.v_;
+ }
+
+ friend std::ostream& operator<<(std::ostream& out, const ComparableType& v) {
+ return out << "ComparableType{" << v.Get() << "}";
+ }
+
+ int v_;
+};
+
+TEST(CHECKTest, TestUserDefinedCompOp) {
+ ABSL_TEST_CHECK_EQ(ComparableType{0}, ComparableType{0});
+ ABSL_TEST_CHECK_NE(ComparableType{1}, ComparableType{2});
+ ABSL_TEST_CHECK_LT(ComparableType{1}, ComparableType{2});
+ ABSL_TEST_CHECK_LE(ComparableType{1}, ComparableType{2});
+ ABSL_TEST_CHECK_GT(ComparableType{2}, ComparableType{1});
+ ABSL_TEST_CHECK_GE(ComparableType{2}, ComparableType{2});
+}
+
+TEST(CHECKTest, TestCheckInMethod) {
+ ComparableType v{1};
+ v.MethodWithCheck(1);
+}
+
+TEST(CHECKDeathTest, TestUserDefinedStreaming) {
+ ComparableType v1{1};
+ ComparableType v2{2};
+
+ EXPECT_DEATH(
+ ABSL_TEST_CHECK_EQ(v1, v2),
+ HasSubstr(
+ "Check failed: v1 == v2 (ComparableType{1} vs. ComparableType{2})"));
+}
+
+} // namespace absl_log_internal
+
+// NOLINTEND(misc-definitions-in-headers)
+
+#endif // ABSL_LOG_CHECK_TEST_IMPL_H_
diff --git a/absl/flags/flag.cc b/absl/log/die_if_null.cc
similarity index 61%
rename from absl/flags/flag.cc
rename to absl/log/die_if_null.cc
index 531df12..19c6a28 100644
--- a/absl/flags/flag.cc
+++ b/absl/log/die_if_null.cc
@@ -1,5 +1,4 @@
-//
-// Copyright 2019 The Abseil Authors.
+// Copyright 2022 The Abseil Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -13,26 +12,21 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#include "absl/flags/flag.h"
+#include "absl/log/die_if_null.h"
#include "absl/base/config.h"
+#include "absl/log/log.h"
+#include "absl/strings/str_cat.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
+namespace log_internal {
-// This global mutex protects on-demand construction of flag objects in MSVC
-// builds.
-#if defined(_MSC_VER) && !defined(__clang__)
+void DieBecauseNull(const char* file, int line, const char* exprtext) {
+ LOG(FATAL).AtLocation(file, line)
+ << absl::StrCat("Check failed: '", exprtext, "' Must be non-null");
+}
-namespace flags_internal {
-
-ABSL_CONST_INIT static absl::Mutex construction_guard(absl::kConstInit);
-
-absl::Mutex* GetGlobalConstructionGuard() { return &construction_guard; }
-
-} // namespace flags_internal
-
-#endif
-
+} // namespace log_internal
ABSL_NAMESPACE_END
} // namespace absl
diff --git a/absl/log/die_if_null.h b/absl/log/die_if_null.h
new file mode 100644
index 0000000..127a9ac
--- /dev/null
+++ b/absl/log/die_if_null.h
@@ -0,0 +1,76 @@
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// -----------------------------------------------------------------------------
+// File: log/die_if_null.h
+// -----------------------------------------------------------------------------
+//
+// This header declares macro `ABSL_DIE_IF_NULL`.
+
+#ifndef ABSL_LOG_DIE_IF_NULL_H_
+#define ABSL_LOG_DIE_IF_NULL_H_
+
+#include <stdint.h>
+
+#include <utility>
+
+#include "absl/base/attributes.h"
+#include "absl/base/config.h"
+#include "absl/base/optimization.h"
+
+// ABSL_DIE_IF_NULL()
+//
+// `ABSL_DIE_IF_NULL` behaves as `CHECK_NE` against `nullptr` but *also*
+// "returns" its argument. It is useful in initializers where statements (like
+// `CHECK_NE`) can't be used. Outside initializers, prefer `CHECK` or
+// `CHECK_NE`. `ABSL_DIE_IF_NULL` works for both raw pointers and (compatible)
+// smart pointers including `std::unique_ptr` and `std::shared_ptr`; more
+// generally, it works for any type that can be compared to nullptr_t. For
+// types that aren't raw pointers, `ABSL_DIE_IF_NULL` returns a reference to
+// its argument, preserving the value category. Example:
+//
+// Foo() : bar_(ABSL_DIE_IF_NULL(MethodReturningUniquePtr())) {}
+//
+// Use `CHECK(ptr)` or `CHECK(ptr != nullptr)` if the returned pointer is
+// unused.
+#define ABSL_DIE_IF_NULL(val) \
+ ::absl::log_internal::DieIfNull(__FILE__, __LINE__, #val, (val))
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace log_internal {
+
+// Crashes the process after logging `exprtext` annotated at the `file` and
+// `line` location. Called when `ABSL_DIE_IF_NULL` fails. Calling this function
+// generates less code than its implementation would if inlined, for a slight
+// code size reduction each time `ABSL_DIE_IF_NULL` is called.
+ABSL_ATTRIBUTE_NORETURN ABSL_ATTRIBUTE_NOINLINE void DieBecauseNull(
+ const char* file, int line, const char* exprtext);
+
+// Helper for `ABSL_DIE_IF_NULL`.
+template <typename T>
+ABSL_MUST_USE_RESULT T DieIfNull(const char* file, int line,
+ const char* exprtext, T&& t) {
+ if (ABSL_PREDICT_FALSE(t == nullptr)) {
+ // Call a non-inline helper function for a small code size improvement.
+ DieBecauseNull(file, line, exprtext);
+ }
+ return std::forward<T>(t);
+}
+
+} // namespace log_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_LOG_DIE_IF_NULL_H_
diff --git a/absl/log/die_if_null_test.cc b/absl/log/die_if_null_test.cc
new file mode 100644
index 0000000..b0aab78
--- /dev/null
+++ b/absl/log/die_if_null_test.cc
@@ -0,0 +1,107 @@
+//
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/log/die_if_null.h"
+
+#include <stdint.h>
+
+#include <memory>
+#include <utility>
+
+#include "gtest/gtest.h"
+#include "absl/base/attributes.h"
+#include "absl/log/internal/test_helpers.h"
+
+namespace {
+
+auto* test_env ABSL_ATTRIBUTE_UNUSED = ::testing::AddGlobalTestEnvironment(
+ new absl::log_internal::LogTestEnvironment);
+
+// TODO(b/69907837): Revisit these tests with the goal of making them less
+// convoluted.
+TEST(AbslDieIfNull, Simple) {
+ int64_t t;
+ void* ptr = static_cast<void*>(&t);
+ void* ref = ABSL_DIE_IF_NULL(ptr);
+ ASSERT_EQ(ptr, ref);
+
+ char* t_as_char;
+ t_as_char = ABSL_DIE_IF_NULL(reinterpret_cast<char*>(&t));
+ (void)t_as_char;
+
+ unsigned char* t_as_uchar;
+ t_as_uchar = ABSL_DIE_IF_NULL(reinterpret_cast<unsigned char*>(&t));
+ (void)t_as_uchar;
+
+ int* t_as_int;
+ t_as_int = ABSL_DIE_IF_NULL(reinterpret_cast<int*>(&t));
+ (void)t_as_int;
+
+ int64_t* t_as_int64_t;
+ t_as_int64_t = ABSL_DIE_IF_NULL(reinterpret_cast<int64_t*>(&t));
+ (void)t_as_int64_t;
+
+ std::unique_ptr<int64_t> sptr(new int64_t);
+ EXPECT_EQ(sptr.get(), ABSL_DIE_IF_NULL(sptr).get());
+ ABSL_DIE_IF_NULL(sptr).reset();
+
+ int64_t* int_ptr = new int64_t();
+ EXPECT_EQ(int_ptr, ABSL_DIE_IF_NULL(std::unique_ptr<int64_t>(int_ptr)).get());
+}
+
+#if GTEST_HAS_DEATH_TEST
+TEST(DeathCheckAbslDieIfNull, Simple) {
+ void* ptr;
+ ASSERT_DEATH({ ptr = ABSL_DIE_IF_NULL(nullptr); }, "");
+ (void)ptr;
+
+ std::unique_ptr<int64_t> sptr;
+ ASSERT_DEATH(ptr = ABSL_DIE_IF_NULL(sptr).get(), "");
+}
+#endif
+
+// Ensures that ABSL_DIE_IF_NULL works with C++11's std::unique_ptr and
+// std::shared_ptr.
+TEST(AbslDieIfNull, DoesNotCompareSmartPointerToNULL) {
+ std::unique_ptr<int> up(new int);
+ EXPECT_EQ(&up, &ABSL_DIE_IF_NULL(up));
+ ABSL_DIE_IF_NULL(up).reset();
+
+ std::shared_ptr<int> sp(new int);
+ EXPECT_EQ(&sp, &ABSL_DIE_IF_NULL(sp));
+ ABSL_DIE_IF_NULL(sp).reset();
+}
+
+// Verifies that ABSL_DIE_IF_NULL returns an rvalue reference if its argument is
+// an rvalue reference.
+TEST(AbslDieIfNull, PreservesRValues) {
+ int64_t* ptr = new int64_t();
+ auto uptr = ABSL_DIE_IF_NULL(std::unique_ptr<int64_t>(ptr));
+ EXPECT_EQ(ptr, uptr.get());
+}
+
+// Verifies that ABSL_DIE_IF_NULL returns an lvalue if its argument is an
+// lvalue.
+TEST(AbslDieIfNull, PreservesLValues) {
+ int64_t array[2] = {0};
+ int64_t* a = array + 0;
+ int64_t* b = array + 1;
+ using std::swap;
+ swap(ABSL_DIE_IF_NULL(a), ABSL_DIE_IF_NULL(b));
+ EXPECT_EQ(array + 1, a);
+ EXPECT_EQ(array + 0, b);
+}
+
+} // namespace
diff --git a/absl/log/flags.cc b/absl/log/flags.cc
new file mode 100644
index 0000000..287b3e9
--- /dev/null
+++ b/absl/log/flags.cc
@@ -0,0 +1,143 @@
+//
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/log/internal/flags.h"
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <cstdlib>
+#include <string>
+
+#include "absl/base/attributes.h"
+#include "absl/base/config.h"
+#include "absl/base/log_severity.h"
+#include "absl/flags/flag.h"
+#include "absl/flags/marshalling.h"
+#include "absl/log/globals.h"
+#include "absl/log/internal/config.h"
+#include "absl/log/internal/vlog_config.h"
+#include "absl/strings/numbers.h"
+#include "absl/strings/string_view.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace log_internal {
+namespace {
+
+void SyncLoggingFlags() {
+ absl::SetFlag(&FLAGS_minloglevel, static_cast<int>(absl::MinLogLevel()));
+ absl::SetFlag(&FLAGS_log_prefix, absl::ShouldPrependLogPrefix());
+}
+
+bool RegisterSyncLoggingFlags() {
+ log_internal::SetLoggingGlobalsListener(&SyncLoggingFlags);
+ return true;
+}
+
+ABSL_ATTRIBUTE_UNUSED const bool unused = RegisterSyncLoggingFlags();
+
+template <typename T>
+T GetFromEnv(const char* varname, T dflt) {
+ const char* val = ::getenv(varname);
+ if (val != nullptr) {
+ std::string err;
+ ABSL_INTERNAL_CHECK(absl::ParseFlag(val, &dflt, &err), err.c_str());
+ }
+ return dflt;
+}
+
+constexpr absl::LogSeverityAtLeast StderrThresholdDefault() {
+ return absl::LogSeverityAtLeast::kError;
+}
+
+} // namespace
+} // namespace log_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+
+ABSL_FLAG(int, stderrthreshold,
+ static_cast<int>(absl::log_internal::StderrThresholdDefault()),
+ "Log messages at or above this threshold level are copied to stderr.")
+ .OnUpdate([] {
+ absl::log_internal::RawSetStderrThreshold(
+ static_cast<absl::LogSeverityAtLeast>(
+ absl::GetFlag(FLAGS_stderrthreshold)));
+ });
+
+ABSL_FLAG(int, minloglevel, static_cast<int>(absl::LogSeverityAtLeast::kInfo),
+ "Messages logged at a lower level than this don't actually "
+ "get logged anywhere")
+ .OnUpdate([] {
+ absl::log_internal::RawSetMinLogLevel(
+ static_cast<absl::LogSeverityAtLeast>(
+ absl::GetFlag(FLAGS_minloglevel)));
+ });
+
+ABSL_FLAG(std::string, log_backtrace_at, "",
+ "Emit a backtrace when logging at file:linenum.")
+ .OnUpdate([] {
+ const std::string log_backtrace_at =
+ absl::GetFlag(FLAGS_log_backtrace_at);
+ if (log_backtrace_at.empty()) {
+ absl::ClearLogBacktraceLocation();
+ return;
+ }
+
+ const size_t last_colon = log_backtrace_at.rfind(':');
+ if (last_colon == log_backtrace_at.npos) {
+ absl::ClearLogBacktraceLocation();
+ return;
+ }
+
+ const absl::string_view file =
+ absl::string_view(log_backtrace_at).substr(0, last_colon);
+ int line;
+ if (!absl::SimpleAtoi(
+ absl::string_view(log_backtrace_at).substr(last_colon + 1),
+ &line)) {
+ absl::ClearLogBacktraceLocation();
+ return;
+ }
+ absl::SetLogBacktraceLocation(file, line);
+ });
+
+ABSL_FLAG(bool, log_prefix, true,
+ "Prepend the log prefix to the start of each log line")
+ .OnUpdate([] {
+ absl::log_internal::RawEnableLogPrefix(absl::GetFlag(FLAGS_log_prefix));
+ });
+
+ABSL_FLAG(int, v, 0,
+ "Show all VLOG(m) messages for m <= this. Overridable by --vmodule.")
+ .OnUpdate([] {
+ absl::log_internal::UpdateGlobalVLogLevel(absl::GetFlag(FLAGS_v));
+ });
+
+ABSL_FLAG(
+ std::string, vmodule, "",
+ "per-module log verbosity level."
+ " Argument is a comma-separated list of <module name>=<log level>."
+ " <module name> is a glob pattern, matched against the filename base"
+ " (that is, name ignoring .cc/.h./-inl.h)."
+ " A pattern without slashes matches just the file name portion, otherwise"
+ " the whole file path below the workspace root"
+ " (still without .cc/.h./-inl.h) is matched."
+ " ? and * in the glob pattern match any single or sequence of characters"
+ " respectively including slashes."
+ " <log level> overrides any value given by --v.")
+ .OnUpdate([] {
+ absl::log_internal::UpdateVModule(absl::GetFlag(FLAGS_vmodule));
+ });
diff --git a/absl/log/flags.h b/absl/log/flags.h
new file mode 100644
index 0000000..146cfdd
--- /dev/null
+++ b/absl/log/flags.h
@@ -0,0 +1,43 @@
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// -----------------------------------------------------------------------------
+// File: log/flags.h
+// -----------------------------------------------------------------------------
+//
+
+#ifndef ABSL_LOG_FLAGS_H_
+#define ABSL_LOG_FLAGS_H_
+
+// The Abseil Logging library supports the following command line flags to
+// configure logging behavior at runtime:
+//
+// --stderrthreshold=<value>
+// Log messages at or above this threshold level are copied to stderr.
+//
+// --minloglevel=<value>
+// Messages logged at a lower level than this are discarded and don't actually
+// get logged anywhere.
+//
+// --log_backtrace_at=<file:linenum>
+// Emit a backtrace (stack trace) when logging at file:linenum.
+//
+// To use these commandline flags, the //absl/log:flags library must be
+// explicitly linked, and absl::ParseCommandLine() must be called before the
+// call to absl::InitializeLog().
+//
+// To configure the Log library programmatically, use the interfaces defined in
+// absl/log/globals.h.
+
+#endif // ABSL_LOG_FLAGS_H_
diff --git a/absl/log/flags_test.cc b/absl/log/flags_test.cc
new file mode 100644
index 0000000..1080ea1
--- /dev/null
+++ b/absl/log/flags_test.cc
@@ -0,0 +1,188 @@
+//
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/log/internal/flags.h"
+
+#include <string>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/base/attributes.h"
+#include "absl/base/log_severity.h"
+#include "absl/flags/flag.h"
+#include "absl/flags/reflection.h"
+#include "absl/log/globals.h"
+#include "absl/log/internal/test_helpers.h"
+#include "absl/log/internal/test_matchers.h"
+#include "absl/log/log.h"
+#include "absl/log/scoped_mock_log.h"
+#include "absl/strings/str_cat.h"
+
+namespace {
+using ::absl::log_internal::TextMessage;
+
+using ::testing::HasSubstr;
+using ::testing::Not;
+
+auto* test_env ABSL_ATTRIBUTE_UNUSED = ::testing::AddGlobalTestEnvironment(
+ new absl::log_internal::LogTestEnvironment);
+
+constexpr static absl::LogSeverityAtLeast DefaultStderrThreshold() {
+ return absl::LogSeverityAtLeast::kError;
+}
+
+class LogFlagsTest : public ::testing::Test {
+ protected:
+ absl::FlagSaver flag_saver_;
+};
+
+// This test is disabled because it adds order dependency to the test suite.
+// This order dependency is currently not fixable due to the way the
+// stderrthreshold global value is out of sync with the stderrthreshold flag.
+TEST_F(LogFlagsTest, DISABLED_StderrKnobsDefault) {
+ EXPECT_EQ(absl::StderrThreshold(), DefaultStderrThreshold());
+}
+
+TEST_F(LogFlagsTest, SetStderrThreshold) {
+ absl::SetFlag(&FLAGS_stderrthreshold,
+ static_cast<int>(absl::LogSeverityAtLeast::kInfo));
+
+ EXPECT_EQ(absl::StderrThreshold(), absl::LogSeverityAtLeast::kInfo);
+
+ absl::SetFlag(&FLAGS_stderrthreshold,
+ static_cast<int>(absl::LogSeverityAtLeast::kError));
+
+ EXPECT_EQ(absl::StderrThreshold(), absl::LogSeverityAtLeast::kError);
+}
+
+TEST_F(LogFlagsTest, SetMinLogLevel) {
+ absl::SetFlag(&FLAGS_minloglevel,
+ static_cast<int>(absl::LogSeverityAtLeast::kError));
+
+ EXPECT_EQ(absl::MinLogLevel(), absl::LogSeverityAtLeast::kError);
+
+ absl::log_internal::ScopedMinLogLevel scoped_min_log_level(
+ absl::LogSeverityAtLeast::kWarning);
+
+ EXPECT_EQ(absl::GetFlag(FLAGS_minloglevel),
+ static_cast<int>(absl::LogSeverityAtLeast::kWarning));
+}
+
+TEST_F(LogFlagsTest, PrependLogPrefix) {
+ absl::SetFlag(&FLAGS_log_prefix, false);
+
+ EXPECT_EQ(absl::ShouldPrependLogPrefix(), false);
+
+ absl::EnableLogPrefix(true);
+
+ EXPECT_EQ(absl::GetFlag(FLAGS_log_prefix), true);
+}
+
+TEST_F(LogFlagsTest, EmptyBacktraceAtFlag) {
+ absl::SetMinLogLevel(absl::LogSeverityAtLeast::kInfo);
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ EXPECT_CALL(test_sink, Send(TextMessage(Not(HasSubstr("(stacktrace:")))));
+
+ test_sink.StartCapturingLogs();
+ absl::SetFlag(&FLAGS_log_backtrace_at, "");
+ LOG(INFO) << "hello world";
+}
+
+TEST_F(LogFlagsTest, BacktraceAtNonsense) {
+ absl::SetMinLogLevel(absl::LogSeverityAtLeast::kInfo);
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ EXPECT_CALL(test_sink, Send(TextMessage(Not(HasSubstr("(stacktrace:")))));
+
+ test_sink.StartCapturingLogs();
+ absl::SetFlag(&FLAGS_log_backtrace_at, "gibberish");
+ LOG(INFO) << "hello world";
+}
+
+TEST_F(LogFlagsTest, BacktraceAtWrongFile) {
+ absl::SetMinLogLevel(absl::LogSeverityAtLeast::kInfo);
+ 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(TextMessage(Not(HasSubstr("(stacktrace:")))));
+
+ test_sink.StartCapturingLogs();
+ absl::SetFlag(&FLAGS_log_backtrace_at,
+ absl::StrCat("some_other_file.cc:", log_line));
+ do_log();
+}
+
+TEST_F(LogFlagsTest, BacktraceAtWrongLine) {
+ absl::SetMinLogLevel(absl::LogSeverityAtLeast::kInfo);
+ const int log_line = __LINE__ + 1;
+ auto do_log = [] { LOG(INFO) << "hello world"; };
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ EXPECT_CALL(test_sink, Send(TextMessage(Not(HasSubstr("(stacktrace:")))));
+
+ test_sink.StartCapturingLogs();
+ absl::SetFlag(&FLAGS_log_backtrace_at,
+ absl::StrCat("flags_test.cc:", log_line + 1));
+ do_log();
+}
+
+TEST_F(LogFlagsTest, BacktraceAtWholeFilename) {
+ absl::SetMinLogLevel(absl::LogSeverityAtLeast::kInfo);
+ 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(TextMessage(Not(HasSubstr("(stacktrace:")))));
+
+ test_sink.StartCapturingLogs();
+ absl::SetFlag(&FLAGS_log_backtrace_at, absl::StrCat(__FILE__, ":", log_line));
+ do_log();
+}
+
+TEST_F(LogFlagsTest, BacktraceAtNonmatchingSuffix) {
+ absl::SetMinLogLevel(absl::LogSeverityAtLeast::kInfo);
+ const int log_line = __LINE__ + 1;
+ auto do_log = [] { LOG(INFO) << "hello world"; };
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ EXPECT_CALL(test_sink, Send(TextMessage(Not(HasSubstr("(stacktrace:")))));
+
+ test_sink.StartCapturingLogs();
+ absl::SetFlag(&FLAGS_log_backtrace_at,
+ absl::StrCat("flags_test.cc:", log_line, "gibberish"));
+ do_log();
+}
+
+TEST_F(LogFlagsTest, LogsBacktrace) {
+ absl::SetMinLogLevel(absl::LogSeverityAtLeast::kInfo);
+ const int log_line = __LINE__ + 1;
+ auto do_log = [] { LOG(INFO) << "hello world"; };
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ testing::InSequence seq;
+ EXPECT_CALL(test_sink, Send(TextMessage(HasSubstr("(stacktrace:"))));
+ EXPECT_CALL(test_sink, Send(TextMessage(Not(HasSubstr("(stacktrace:")))));
+
+ test_sink.StartCapturingLogs();
+ absl::SetFlag(&FLAGS_log_backtrace_at,
+ absl::StrCat("flags_test.cc:", log_line));
+ do_log();
+ absl::SetFlag(&FLAGS_log_backtrace_at, "");
+ do_log();
+}
+
+} // namespace
diff --git a/absl/log/globals.cc b/absl/log/globals.cc
new file mode 100644
index 0000000..cc85438
--- /dev/null
+++ b/absl/log/globals.cc
@@ -0,0 +1,178 @@
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/log/globals.h"
+
+#include <atomic>
+#include <cstddef>
+#include <cstdint>
+#include <cstdlib>
+#include <cstring>
+#include <string>
+
+#include "absl/base/attributes.h"
+#include "absl/base/config.h"
+#include "absl/base/internal/atomic_hook.h"
+#include "absl/base/internal/raw_logging.h"
+#include "absl/base/log_severity.h"
+#include "absl/hash/hash.h"
+#include "absl/strings/string_view.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace {
+
+// These atomics represent logging library configuration.
+// Integer types are used instead of absl::LogSeverity to ensure that a
+// lock-free std::atomic is used when possible.
+ABSL_CONST_INIT std::atomic<int> min_log_level{
+ static_cast<int>(absl::LogSeverityAtLeast::kInfo)};
+ABSL_CONST_INIT std::atomic<int> stderrthreshold{
+ static_cast<int>(absl::LogSeverityAtLeast::kError)};
+// We evaluate this value as a hash comparison to avoid having to
+// hold a mutex or make a copy (to access the value of a string-typed flag) in
+// very hot codepath.
+ABSL_CONST_INIT std::atomic<size_t> log_backtrace_at_hash{0};
+ABSL_CONST_INIT std::atomic<bool> prepend_log_prefix{true};
+
+constexpr char kDefaultAndroidTag[] = "native";
+ABSL_CONST_INIT std::atomic<const char*> android_log_tag{kDefaultAndroidTag};
+
+ABSL_INTERNAL_ATOMIC_HOOK_ATTRIBUTES
+absl::base_internal::AtomicHook<log_internal::LoggingGlobalsListener>
+ logging_globals_listener;
+
+size_t HashSiteForLogBacktraceAt(absl::string_view file, int line) {
+ return absl::HashOf(file, line);
+}
+
+void TriggerLoggingGlobalsListener() {
+ auto* listener = logging_globals_listener.Load();
+ if (listener != nullptr) listener();
+}
+
+} // namespace
+
+namespace log_internal {
+
+void RawSetMinLogLevel(absl::LogSeverityAtLeast severity) {
+ min_log_level.store(static_cast<int>(severity), std::memory_order_release);
+}
+
+void RawSetStderrThreshold(absl::LogSeverityAtLeast severity) {
+ stderrthreshold.store(static_cast<int>(severity), std::memory_order_release);
+}
+
+void RawEnableLogPrefix(bool on_off) {
+ prepend_log_prefix.store(on_off, std::memory_order_release);
+}
+
+void SetLoggingGlobalsListener(LoggingGlobalsListener l) {
+ logging_globals_listener.Store(l);
+}
+
+} // namespace log_internal
+
+absl::LogSeverityAtLeast MinLogLevel() {
+ return static_cast<absl::LogSeverityAtLeast>(
+ min_log_level.load(std::memory_order_acquire));
+}
+
+void SetMinLogLevel(absl::LogSeverityAtLeast severity) {
+ log_internal::RawSetMinLogLevel(severity);
+ TriggerLoggingGlobalsListener();
+}
+
+namespace log_internal {
+
+ScopedMinLogLevel::ScopedMinLogLevel(absl::LogSeverityAtLeast severity)
+ : saved_severity_(absl::MinLogLevel()) {
+ absl::SetMinLogLevel(severity);
+}
+ScopedMinLogLevel::~ScopedMinLogLevel() {
+ absl::SetMinLogLevel(saved_severity_);
+}
+
+} // namespace log_internal
+
+absl::LogSeverityAtLeast StderrThreshold() {
+ return static_cast<absl::LogSeverityAtLeast>(
+ stderrthreshold.load(std::memory_order_acquire));
+}
+
+void SetStderrThreshold(absl::LogSeverityAtLeast severity) {
+ log_internal::RawSetStderrThreshold(severity);
+ TriggerLoggingGlobalsListener();
+}
+
+ScopedStderrThreshold::ScopedStderrThreshold(absl::LogSeverityAtLeast severity)
+ : saved_severity_(absl::StderrThreshold()) {
+ absl::SetStderrThreshold(severity);
+}
+
+ScopedStderrThreshold::~ScopedStderrThreshold() {
+ absl::SetStderrThreshold(saved_severity_);
+}
+
+namespace log_internal {
+
+const char* GetAndroidNativeTag() {
+ return android_log_tag.load(std::memory_order_acquire);
+}
+
+} // namespace log_internal
+
+void SetAndroidNativeTag(const char* tag) {
+ ABSL_CONST_INIT static std::atomic<const std::string*> user_log_tag(nullptr);
+ ABSL_INTERNAL_CHECK(tag, "tag must be non-null.");
+
+ const std::string* tag_str = new std::string(tag);
+ ABSL_INTERNAL_CHECK(
+ android_log_tag.exchange(tag_str->c_str(), std::memory_order_acq_rel) ==
+ kDefaultAndroidTag,
+ "SetAndroidNativeTag() must only be called once per process!");
+ user_log_tag.store(tag_str, std::memory_order_relaxed);
+}
+
+namespace log_internal {
+
+bool ShouldLogBacktraceAt(absl::string_view file, int line) {
+ const size_t flag_hash =
+ log_backtrace_at_hash.load(std::memory_order_relaxed);
+
+ return flag_hash != 0 && flag_hash == HashSiteForLogBacktraceAt(file, line);
+}
+
+} // namespace log_internal
+
+void SetLogBacktraceLocation(absl::string_view file, int line) {
+ log_backtrace_at_hash.store(HashSiteForLogBacktraceAt(file, line),
+ std::memory_order_relaxed);
+}
+
+void ClearLogBacktraceLocation() {
+ log_backtrace_at_hash.store(0, std::memory_order_relaxed);
+}
+
+bool ShouldPrependLogPrefix() {
+ return prepend_log_prefix.load(std::memory_order_acquire);
+}
+
+void EnableLogPrefix(bool on_off) {
+ log_internal::RawEnableLogPrefix(on_off);
+ TriggerLoggingGlobalsListener();
+}
+
+ABSL_NAMESPACE_END
+} // namespace absl
diff --git a/absl/log/globals.h b/absl/log/globals.h
new file mode 100644
index 0000000..b36e47e
--- /dev/null
+++ b/absl/log/globals.h
@@ -0,0 +1,218 @@
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// -----------------------------------------------------------------------------
+// File: log/globals.h
+// -----------------------------------------------------------------------------
+//
+// This header declares global logging library configuration knobs.
+
+#ifndef ABSL_LOG_GLOBALS_H_
+#define ABSL_LOG_GLOBALS_H_
+
+#include "absl/base/attributes.h"
+#include "absl/base/config.h"
+#include "absl/base/log_severity.h"
+#include "absl/log/internal/vlog_config.h"
+#include "absl/strings/string_view.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+
+//------------------------------------------------------------------------------
+// Minimum Log Level
+//------------------------------------------------------------------------------
+//
+// Messages logged at or above this severity are directed to all registered log
+// sinks or skipped otherwise. This parameter can also be modified using
+// command line flag --minloglevel.
+// See absl/base/log_severity.h for descriptions of severity levels.
+
+// MinLogLevel()
+//
+// Returns the value of the Minimum Log Level parameter.
+// This function is async-signal-safe.
+ABSL_MUST_USE_RESULT absl::LogSeverityAtLeast MinLogLevel();
+
+// SetMinLogLevel()
+//
+// Updates the value of Minimum Log Level parameter.
+// This function is async-signal-safe.
+void SetMinLogLevel(absl::LogSeverityAtLeast severity);
+
+namespace log_internal {
+
+// ScopedMinLogLevel
+//
+// RAII type used to temporarily update the Min Log Level parameter.
+class ScopedMinLogLevel final {
+ public:
+ explicit ScopedMinLogLevel(absl::LogSeverityAtLeast severity);
+ ScopedMinLogLevel(const ScopedMinLogLevel&) = delete;
+ ScopedMinLogLevel& operator=(const ScopedMinLogLevel&) = delete;
+ ~ScopedMinLogLevel();
+
+ private:
+ absl::LogSeverityAtLeast saved_severity_;
+};
+
+} // namespace log_internal
+
+//------------------------------------------------------------------------------
+// Stderr Threshold
+//------------------------------------------------------------------------------
+//
+// Messages logged at or above this level are directed to stderr in
+// addition to other registered log sinks. This parameter can also be modified
+// using command line flag --stderrthreshold.
+// See absl/base/log_severity.h for descriptions of severity levels.
+
+// StderrThreshold()
+//
+// Returns the value of the Stderr Threshold parameter.
+// This function is async-signal-safe.
+ABSL_MUST_USE_RESULT absl::LogSeverityAtLeast StderrThreshold();
+
+// SetStderrThreshold()
+//
+// Updates the Stderr Threshold parameter.
+// This function is async-signal-safe.
+void SetStderrThreshold(absl::LogSeverityAtLeast severity);
+inline void SetStderrThreshold(absl::LogSeverity severity) {
+ absl::SetStderrThreshold(static_cast<absl::LogSeverityAtLeast>(severity));
+}
+
+// ScopedStderrThreshold
+//
+// RAII type used to temporarily update the Stderr Threshold parameter.
+class ScopedStderrThreshold final {
+ public:
+ explicit ScopedStderrThreshold(absl::LogSeverityAtLeast severity);
+ ScopedStderrThreshold(const ScopedStderrThreshold&) = delete;
+ ScopedStderrThreshold& operator=(const ScopedStderrThreshold&) = delete;
+ ~ScopedStderrThreshold();
+
+ private:
+ absl::LogSeverityAtLeast saved_severity_;
+};
+
+//------------------------------------------------------------------------------
+// Log Backtrace At
+//------------------------------------------------------------------------------
+//
+// Users can request an existing `LOG` statement, specified by file and line
+// number, to also include a backtrace when logged.
+
+// ShouldLogBacktraceAt()
+//
+// Returns true if we should log a backtrace at the specified location.
+namespace log_internal {
+ABSL_MUST_USE_RESULT bool ShouldLogBacktraceAt(absl::string_view file,
+ int line);
+} // namespace log_internal
+
+// SetLogBacktraceLocation()
+//
+// Sets the location the backtrace should be logged at. If the specified
+// location isn't a `LOG` statement, the effect will be the same as
+// `ClearLogBacktraceLocation` (but less efficient).
+void SetLogBacktraceLocation(absl::string_view file, int line);
+
+// ClearLogBacktraceLocation()
+//
+// Clears the set location so that backtraces will no longer be logged at it.
+void ClearLogBacktraceLocation();
+
+//------------------------------------------------------------------------------
+// Prepend Log Prefix
+//------------------------------------------------------------------------------
+//
+// This option tells the logging library that every logged message
+// should include the prefix (severity, date, time, PID, etc.)
+
+// ShouldPrependLogPrefix()
+//
+// Returns the value of the Prepend Log Prefix option.
+// This function is async-signal-safe.
+ABSL_MUST_USE_RESULT bool ShouldPrependLogPrefix();
+
+// EnableLogPrefix()
+//
+// Updates the value of the Prepend Log Prefix option.
+// This function is async-signal-safe.
+void EnableLogPrefix(bool on_off);
+
+//------------------------------------------------------------------------------
+// Set Global VLOG Level
+//------------------------------------------------------------------------------
+//
+// Sets the global `(ABSL_)VLOG(_IS_ON)` level to `log_level`. This level is
+// applied to any sites whose filename doesn't match any `module_pattern`.
+// Returns the prior value.
+inline int SetGlobalVLogLevel(int log_level) {
+ return absl::log_internal::UpdateGlobalVLogLevel(log_level);
+}
+
+//------------------------------------------------------------------------------
+// Set VLOG Level
+//------------------------------------------------------------------------------
+//
+// Sets `(ABSL_)VLOG(_IS_ON)` level for `module_pattern` to `log_level`. This
+// allows programmatic control of what is normally set by the --vmodule flag.
+// Returns the level that previously applied to `module_pattern`.
+inline int SetVLogLevel(absl::string_view module_pattern, int log_level) {
+ return absl::log_internal::PrependVModule(module_pattern, log_level);
+}
+
+//------------------------------------------------------------------------------
+// Configure Android Native Log Tag
+//------------------------------------------------------------------------------
+//
+// The logging library forwards to the Android system log API when built for
+// Android. That API takes a string "tag" value in addition to a message and
+// severity level. The tag is used to identify the source of messages and to
+// filter them. This library uses the tag "native" by default.
+
+// SetAndroidNativeTag()
+//
+// Stores a copy of the string pointed to by `tag` and uses it as the Android
+// logging tag thereafter. `tag` must not be null.
+// This function must not be called more than once!
+void SetAndroidNativeTag(const char* tag);
+
+namespace log_internal {
+// GetAndroidNativeTag()
+//
+// Returns the configured Android logging tag.
+const char* GetAndroidNativeTag();
+} // namespace log_internal
+
+namespace log_internal {
+
+using LoggingGlobalsListener = void (*)();
+void SetLoggingGlobalsListener(LoggingGlobalsListener l);
+
+// Internal implementation for the setter routines. These are used
+// to break circular dependencies between flags and globals. Each "Raw"
+// routine corresponds to the non-"Raw" counterpart and used to set the
+// configuration parameter directly without calling back to the listener.
+void RawSetMinLogLevel(absl::LogSeverityAtLeast severity);
+void RawSetStderrThreshold(absl::LogSeverityAtLeast severity);
+void RawEnableLogPrefix(bool on_off);
+
+} // namespace log_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_LOG_GLOBALS_H_
diff --git a/absl/log/globals_test.cc b/absl/log/globals_test.cc
new file mode 100644
index 0000000..0dc54d5
--- /dev/null
+++ b/absl/log/globals_test.cc
@@ -0,0 +1,147 @@
+//
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/log/globals.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/base/attributes.h"
+#include "absl/base/log_severity.h"
+#include "absl/log/internal/globals.h"
+#include "absl/log/internal/test_helpers.h"
+#include "absl/log/log.h"
+#include "absl/log/scoped_mock_log.h"
+
+namespace {
+using ::testing::_;
+using ::testing::StrEq;
+
+auto* test_env ABSL_ATTRIBUTE_UNUSED = ::testing::AddGlobalTestEnvironment(
+ new absl::log_internal::LogTestEnvironment);
+
+constexpr static absl::LogSeverityAtLeast DefaultMinLogLevel() {
+ return absl::LogSeverityAtLeast::kInfo;
+}
+constexpr static absl::LogSeverityAtLeast DefaultStderrThreshold() {
+ return absl::LogSeverityAtLeast::kError;
+}
+
+TEST(TestGlobals, MinLogLevel) {
+ EXPECT_EQ(absl::MinLogLevel(), DefaultMinLogLevel());
+ absl::SetMinLogLevel(absl::LogSeverityAtLeast::kError);
+ EXPECT_EQ(absl::MinLogLevel(), absl::LogSeverityAtLeast::kError);
+ absl::SetMinLogLevel(DefaultMinLogLevel());
+}
+
+TEST(TestGlobals, ScopedMinLogLevel) {
+ EXPECT_EQ(absl::MinLogLevel(), DefaultMinLogLevel());
+ {
+ absl::log_internal::ScopedMinLogLevel scoped_stderr_threshold(
+ absl::LogSeverityAtLeast::kError);
+ EXPECT_EQ(absl::MinLogLevel(), absl::LogSeverityAtLeast::kError);
+ }
+ EXPECT_EQ(absl::MinLogLevel(), DefaultMinLogLevel());
+}
+
+TEST(TestGlobals, StderrThreshold) {
+ EXPECT_EQ(absl::StderrThreshold(), DefaultStderrThreshold());
+ absl::SetStderrThreshold(absl::LogSeverityAtLeast::kError);
+ EXPECT_EQ(absl::StderrThreshold(), absl::LogSeverityAtLeast::kError);
+ absl::SetStderrThreshold(DefaultStderrThreshold());
+}
+
+TEST(TestGlobals, ScopedStderrThreshold) {
+ EXPECT_EQ(absl::StderrThreshold(), DefaultStderrThreshold());
+ {
+ absl::ScopedStderrThreshold scoped_stderr_threshold(
+ absl::LogSeverityAtLeast::kError);
+ EXPECT_EQ(absl::StderrThreshold(), absl::LogSeverityAtLeast::kError);
+ }
+ EXPECT_EQ(absl::StderrThreshold(), DefaultStderrThreshold());
+}
+
+TEST(TestGlobals, LogBacktraceAt) {
+ EXPECT_FALSE(absl::log_internal::ShouldLogBacktraceAt("some_file.cc", 111));
+ absl::SetLogBacktraceLocation("some_file.cc", 111);
+ EXPECT_TRUE(absl::log_internal::ShouldLogBacktraceAt("some_file.cc", 111));
+ EXPECT_FALSE(
+ absl::log_internal::ShouldLogBacktraceAt("another_file.cc", 222));
+}
+
+TEST(TestGlobals, LogPrefix) {
+ EXPECT_TRUE(absl::ShouldPrependLogPrefix());
+ absl::EnableLogPrefix(false);
+ EXPECT_FALSE(absl::ShouldPrependLogPrefix());
+ absl::EnableLogPrefix(true);
+ EXPECT_TRUE(absl::ShouldPrependLogPrefix());
+}
+
+TEST(TestGlobals, SetGlobalVLogLevel) {
+ EXPECT_EQ(absl::SetGlobalVLogLevel(42), 0);
+ EXPECT_EQ(absl::SetGlobalVLogLevel(1337), 42);
+ // Restore the value since it affects the default unset module value for
+ // `SetVLogLevel()`.
+ EXPECT_EQ(absl::SetGlobalVLogLevel(0), 1337);
+}
+
+TEST(TestGlobals, SetVLogLevel) {
+ EXPECT_EQ(absl::SetVLogLevel("setvloglevel", 42), 0);
+ EXPECT_EQ(absl::SetVLogLevel("setvloglevel", 1337), 42);
+ EXPECT_EQ(absl::SetVLogLevel("othersetvloglevel", 50), 0);
+}
+
+TEST(TestGlobals, AndroidLogTag) {
+ // Verify invalid tags result in a check failure.
+ EXPECT_DEATH_IF_SUPPORTED(absl::SetAndroidNativeTag(nullptr), ".*");
+
+ // Verify valid tags applied.
+ EXPECT_THAT(absl::log_internal::GetAndroidNativeTag(), StrEq("native"));
+ absl::SetAndroidNativeTag("test_tag");
+ EXPECT_THAT(absl::log_internal::GetAndroidNativeTag(), StrEq("test_tag"));
+
+ // Verify that additional calls (more than 1) result in a check failure.
+ EXPECT_DEATH_IF_SUPPORTED(absl::SetAndroidNativeTag("test_tag_fail"), ".*");
+}
+
+TEST(TestExitOnDFatal, OffTest) {
+ // Turn off...
+ absl::log_internal::SetExitOnDFatal(false);
+ EXPECT_FALSE(absl::log_internal::ExitOnDFatal());
+
+ // We don't die.
+ {
+ absl::ScopedMockLog log(absl::MockLogDefault::kDisallowUnexpected);
+
+ // LOG(DFATAL) has severity FATAL if debugging, but is
+ // downgraded to ERROR if not debugging.
+ EXPECT_CALL(log, Log(absl::kLogDebugFatal, _, "This should not be fatal"));
+
+ log.StartCapturingLogs();
+ LOG(DFATAL) << "This should not be fatal";
+ }
+}
+
+#if GTEST_HAS_DEATH_TEST
+TEST(TestDeathWhileExitOnDFatal, OnTest) {
+ absl::log_internal::SetExitOnDFatal(true);
+ EXPECT_TRUE(absl::log_internal::ExitOnDFatal());
+
+ // Death comes on little cats' feet.
+ EXPECT_DEBUG_DEATH({ LOG(DFATAL) << "This should be fatal in debug mode"; },
+ "This should be fatal in debug mode");
+}
+#endif
+
+} // namespace
diff --git a/absl/log/initialize.cc b/absl/log/initialize.cc
new file mode 100644
index 0000000..ef5d314
--- /dev/null
+++ b/absl/log/initialize.cc
@@ -0,0 +1,38 @@
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/log/initialize.h"
+
+#include "absl/base/config.h"
+#include "absl/log/internal/globals.h"
+#include "absl/time/time.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+
+namespace {
+void InitializeLogImpl(absl::TimeZone time_zone) {
+ // This comes first since it is used by RAW_LOG.
+ absl::log_internal::SetTimeZone(time_zone);
+
+ // Note that initialization is complete, so logs can now be sent to their
+ // proper destinations rather than stderr.
+ log_internal::SetInitialized();
+}
+} // namespace
+
+void InitializeLog() { InitializeLogImpl(absl::LocalTimeZone()); }
+
+ABSL_NAMESPACE_END
+} // namespace absl
diff --git a/absl/log/initialize.h b/absl/log/initialize.h
new file mode 100644
index 0000000..f600eb6
--- /dev/null
+++ b/absl/log/initialize.h
@@ -0,0 +1,45 @@
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// -----------------------------------------------------------------------------
+// File: log/initialize.h
+// -----------------------------------------------------------------------------
+//
+// This header declares the Abseil Log initialization routine InitializeLog().
+
+#ifndef ABSL_LOG_INITIALIZE_H_
+#define ABSL_LOG_INITIALIZE_H_
+
+#include "absl/base/config.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+
+// InitializeLog()
+//
+// Initializes the Abseil logging library.
+//
+// Before this function is called, all log messages are directed only to stderr.
+// After initialization is finished, log messages are directed to all registered
+// `LogSink`s.
+//
+// It is an error to call this function twice.
+//
+// There is no corresponding function to shut down the logging library.
+void InitializeLog();
+
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_LOG_INITIALIZE_H_
diff --git a/absl/log/internal/BUILD.bazel b/absl/log/internal/BUILD.bazel
new file mode 100644
index 0000000..2a8c1a4
--- /dev/null
+++ b/absl/log/internal/BUILD.bazel
@@ -0,0 +1,471 @@
+#
+# Copyright 2022 The Abseil Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+load(
+ "//absl:copts/configure_copts.bzl",
+ "ABSL_DEFAULT_COPTS",
+ "ABSL_DEFAULT_LINKOPTS",
+ "ABSL_TEST_COPTS",
+)
+
+package(
+ default_visibility = [
+ "//absl/log:__pkg__",
+ ],
+ features = [
+ "header_modules",
+ "layering_check",
+ "parse_headers",
+ ],
+)
+
+licenses(["notice"])
+
+cc_library(
+ name = "check_impl",
+ hdrs = ["check_impl.h"],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ ":check_op",
+ ":conditions",
+ ":log_message",
+ ":strip",
+ "//absl/base:core_headers",
+ ],
+)
+
+cc_library(
+ name = "check_op",
+ srcs = ["check_op.cc"],
+ hdrs = ["check_op.h"],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ visibility = [
+ "//absl/log:__pkg__",
+ ],
+ deps = [
+ ":nullguard",
+ ":nullstream",
+ ":strip",
+ "//absl/base:config",
+ "//absl/base:core_headers",
+ "//absl/strings",
+ ],
+)
+
+cc_library(
+ name = "conditions",
+ srcs = ["conditions.cc"],
+ hdrs = ["conditions.h"],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ ":voidify",
+ "//absl/base",
+ "//absl/base:config",
+ "//absl/base:core_headers",
+ ],
+)
+
+cc_library(
+ name = "config",
+ hdrs = ["config.h"],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ visibility = [
+ "//absl/log:__pkg__",
+ ],
+ deps = [
+ "//absl/base:config",
+ "//absl/base:core_headers",
+ ],
+)
+
+cc_library(
+ name = "flags",
+ hdrs = ["flags.h"],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ "//absl/flags:flag",
+ ],
+)
+
+cc_library(
+ name = "format",
+ srcs = ["log_format.cc"],
+ hdrs = ["log_format.h"],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ ":append_truncated",
+ ":config",
+ ":globals",
+ "//absl/base:config",
+ "//absl/base:core_headers",
+ "//absl/base:log_severity",
+ "//absl/strings",
+ "//absl/strings:str_format",
+ "//absl/time",
+ "//absl/types:span",
+ ],
+)
+
+cc_library(
+ name = "globals",
+ srcs = ["globals.cc"],
+ hdrs = ["globals.h"],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ visibility = [
+ "//absl/log:__pkg__",
+ ],
+ deps = [
+ "//absl/base:config",
+ "//absl/base:core_headers",
+ "//absl/base:log_severity",
+ "//absl/base:raw_logging_internal",
+ "//absl/strings",
+ "//absl/time",
+ ],
+)
+
+cc_library(
+ name = "log_impl",
+ hdrs = ["log_impl.h"],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ ":conditions",
+ ":log_message",
+ ":strip",
+ "//absl/log:absl_vlog_is_on",
+ ],
+)
+
+cc_library(
+ name = "log_message",
+ srcs = ["log_message.cc"],
+ hdrs = ["log_message.h"],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ visibility = [
+ "//absl/log:__pkg__",
+ ],
+ deps = [
+ ":append_truncated",
+ ":format",
+ ":globals",
+ ":log_sink_set",
+ ":nullguard",
+ ":proto",
+ "//absl/base",
+ "//absl/base:config",
+ "//absl/base:core_headers",
+ "//absl/base:errno_saver",
+ "//absl/base:log_severity",
+ "//absl/base:raw_logging_internal",
+ "//absl/base:strerror",
+ "//absl/container:inlined_vector",
+ "//absl/debugging:examine_stack",
+ "//absl/log:globals",
+ "//absl/log:log_entry",
+ "//absl/log:log_sink",
+ "//absl/log:log_sink_registry",
+ "//absl/memory",
+ "//absl/strings",
+ "//absl/time",
+ "//absl/types:span",
+ ],
+)
+
+cc_library(
+ name = "append_truncated",
+ hdrs = ["append_truncated.h"],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ "//absl/base:config",
+ "//absl/strings",
+ "//absl/types:span",
+ ],
+)
+
+cc_library(
+ name = "log_sink_set",
+ srcs = ["log_sink_set.cc"],
+ hdrs = ["log_sink_set.h"],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS + select({
+ "//conditions:default": [],
+ "@platforms//os:android": ["-llog"],
+ }),
+ deps = [
+ ":config",
+ ":globals",
+ "//absl/base",
+ "//absl/base:config",
+ "//absl/base:core_headers",
+ "//absl/base:log_severity",
+ "//absl/base:no_destructor",
+ "//absl/base:raw_logging_internal",
+ "//absl/cleanup",
+ "//absl/log:globals",
+ "//absl/log:log_entry",
+ "//absl/log:log_sink",
+ "//absl/strings",
+ "//absl/synchronization",
+ "//absl/types:span",
+ ],
+)
+
+cc_library(
+ name = "nullguard",
+ srcs = ["nullguard.cc"],
+ hdrs = ["nullguard.h"],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ "//absl/base:config",
+ "//absl/base:core_headers",
+ ],
+)
+
+cc_library(
+ name = "nullstream",
+ hdrs = ["nullstream.h"],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ "//absl/base:config",
+ "//absl/base:core_headers",
+ "//absl/base:log_severity",
+ "//absl/strings",
+ ],
+)
+
+cc_library(
+ name = "strip",
+ hdrs = ["strip.h"],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ ":log_message",
+ ":nullstream",
+ "//absl/base:log_severity",
+ ],
+)
+
+cc_library(
+ name = "structured",
+ hdrs = ["structured.h"],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ ":log_message",
+ "//absl/base:config",
+ "//absl/strings",
+ ],
+)
+
+cc_library(
+ name = "test_actions",
+ testonly = True,
+ srcs = ["test_actions.cc"],
+ hdrs = ["test_actions.h"],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ "//absl/base:config",
+ "//absl/base:core_headers",
+ "//absl/base:log_severity",
+ "//absl/log:log_entry",
+ "//absl/strings",
+ "//absl/time",
+ ] + select({
+ "//absl:msvc_compiler": [],
+ "//conditions:default": [
+ ],
+ }),
+)
+
+cc_library(
+ name = "test_helpers",
+ testonly = True,
+ srcs = ["test_helpers.cc"],
+ hdrs = ["test_helpers.h"],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ ":globals",
+ "//absl/base:config",
+ "//absl/base:log_severity",
+ "//absl/log:globals",
+ "//absl/log:initialize",
+ "@com_google_googletest//:gtest",
+ ],
+)
+
+cc_library(
+ name = "test_matchers",
+ testonly = True,
+ srcs = ["test_matchers.cc"],
+ hdrs = ["test_matchers.h"],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ ":test_helpers",
+ "//absl/base:config",
+ "//absl/base:core_headers",
+ "//absl/base:log_severity",
+ "//absl/log:log_entry",
+ "//absl/strings",
+ "//absl/time",
+ "@com_google_googletest//:gtest",
+ ] + select({
+ "//absl:msvc_compiler": [],
+ "//conditions:default": [
+ ],
+ }),
+)
+
+cc_library(
+ name = "voidify",
+ hdrs = ["voidify.h"],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = ["//absl/base:config"],
+)
+
+cc_library(
+ name = "proto",
+ srcs = ["proto.cc"],
+ hdrs = ["proto.h"],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ "//absl/base",
+ "//absl/base:config",
+ "//absl/base:core_headers",
+ "//absl/strings",
+ "//absl/types:span",
+ ],
+)
+
+cc_library(
+ name = "fnmatch",
+ srcs = ["fnmatch.cc"],
+ hdrs = ["fnmatch.h"],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ "//absl/base:config",
+ "//absl/strings",
+ ],
+)
+
+cc_library(
+ name = "vlog_config",
+ srcs = ["vlog_config.cc"],
+ hdrs = ["vlog_config.h"],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ visibility = ["//absl/log:__subpackages__"],
+ deps = [
+ "//absl/base",
+ "//absl/base:config",
+ "//absl/base:core_headers",
+ "//absl/base:no_destructor",
+ "//absl/log/internal:fnmatch",
+ "//absl/memory",
+ "//absl/strings",
+ "//absl/synchronization",
+ "//absl/types:optional",
+ ],
+)
+
+cc_binary(
+ name = "vlog_config_benchmark",
+ testonly = True,
+ srcs = ["vlog_config_benchmark.cc"],
+ copts = ABSL_TEST_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ tags = [
+ "benchmark",
+ ],
+ visibility = ["//visibility:private"],
+ deps = [
+ ":vlog_config",
+ "//absl/base:config",
+ "//absl/base:core_headers",
+ "//absl/container:layout",
+ "//absl/memory",
+ "//absl/random:distributions",
+ "//absl/strings",
+ "@com_github_google_benchmark//:benchmark_main",
+ ],
+)
+
+# Test targets
+cc_test(
+ name = "stderr_log_sink_test",
+ size = "small",
+ srcs = ["stderr_log_sink_test.cc"],
+ copts = ABSL_TEST_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ tags = [
+ "no_test:os:android",
+ "no_test:os:ios",
+ "no_test_android",
+ "no_test_darwin_x86_64",
+ "no_test_ios",
+ "no_test_wasm",
+ ],
+ deps = [
+ ":test_helpers",
+ "//absl/base:core_headers",
+ "//absl/base:log_severity",
+ "//absl/log",
+ "//absl/log:globals",
+ "@com_google_googletest//:gtest",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "fnmatch_test",
+ srcs = ["fnmatch_test.cc"],
+ copts = ABSL_TEST_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ ":fnmatch",
+ "@com_google_googletest//:gtest",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "fnmatch_benchmark",
+ srcs = ["fnmatch_benchmark.cc"],
+ copts = ABSL_TEST_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ tags = ["benchmark"],
+ deps = [
+ ":fnmatch",
+ "@com_github_google_benchmark//:benchmark_main",
+ ],
+)
diff --git a/absl/log/internal/append_truncated.h b/absl/log/internal/append_truncated.h
new file mode 100644
index 0000000..f0e7912
--- /dev/null
+++ b/absl/log/internal/append_truncated.h
@@ -0,0 +1,47 @@
+// Copyright 2022 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef ABSL_LOG_INTERNAL_APPEND_TRUNCATED_H_
+#define ABSL_LOG_INTERNAL_APPEND_TRUNCATED_H_
+
+#include <cstddef>
+#include <cstring>
+
+#include "absl/base/config.h"
+#include "absl/strings/string_view.h"
+#include "absl/types/span.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace log_internal {
+// Copies into `dst` as many bytes of `src` as will fit, then truncates the
+// copied bytes from the front of `dst` and returns the number of bytes written.
+inline size_t AppendTruncated(absl::string_view src, absl::Span<char> &dst) {
+ if (src.size() > dst.size()) src = src.substr(0, dst.size());
+ memcpy(dst.data(), src.data(), src.size());
+ dst.remove_prefix(src.size());
+ return src.size();
+}
+// Likewise, but `n` copies of `c`.
+inline size_t AppendTruncated(char c, size_t n, absl::Span<char> &dst) {
+ if (n > dst.size()) n = dst.size();
+ memset(dst.data(), c, n);
+ dst.remove_prefix(n);
+ return n;
+}
+} // namespace log_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_LOG_INTERNAL_APPEND_TRUNCATED_H_
diff --git a/absl/log/internal/check_impl.h b/absl/log/internal/check_impl.h
new file mode 100644
index 0000000..00f25f8
--- /dev/null
+++ b/absl/log/internal/check_impl.h
@@ -0,0 +1,150 @@
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef ABSL_LOG_INTERNAL_CHECK_IMPL_H_
+#define ABSL_LOG_INTERNAL_CHECK_IMPL_H_
+
+#include "absl/base/optimization.h"
+#include "absl/log/internal/check_op.h"
+#include "absl/log/internal/conditions.h"
+#include "absl/log/internal/log_message.h"
+#include "absl/log/internal/strip.h"
+
+// CHECK
+#define ABSL_LOG_INTERNAL_CHECK_IMPL(condition, condition_text) \
+ ABSL_LOG_INTERNAL_CONDITION_FATAL(STATELESS, \
+ ABSL_PREDICT_FALSE(!(condition))) \
+ ABSL_LOG_INTERNAL_CHECK(condition_text).InternalStream()
+
+#define ABSL_LOG_INTERNAL_QCHECK_IMPL(condition, condition_text) \
+ ABSL_LOG_INTERNAL_CONDITION_QFATAL(STATELESS, \
+ ABSL_PREDICT_FALSE(!(condition))) \
+ ABSL_LOG_INTERNAL_QCHECK(condition_text).InternalStream()
+
+#define ABSL_LOG_INTERNAL_PCHECK_IMPL(condition, condition_text) \
+ ABSL_LOG_INTERNAL_CHECK_IMPL(condition, condition_text).WithPerror()
+
+#ifndef NDEBUG
+#define ABSL_LOG_INTERNAL_DCHECK_IMPL(condition, condition_text) \
+ ABSL_LOG_INTERNAL_CHECK_IMPL(condition, condition_text)
+#else
+#define ABSL_LOG_INTERNAL_DCHECK_IMPL(condition, condition_text) \
+ ABSL_LOG_INTERNAL_CHECK_IMPL(true || (condition), "true")
+#endif
+
+// CHECK_EQ
+#define ABSL_LOG_INTERNAL_CHECK_EQ_IMPL(val1, val1_text, val2, val2_text) \
+ ABSL_LOG_INTERNAL_CHECK_OP(Check_EQ, ==, val1, val1_text, val2, val2_text)
+#define ABSL_LOG_INTERNAL_CHECK_NE_IMPL(val1, val1_text, val2, val2_text) \
+ ABSL_LOG_INTERNAL_CHECK_OP(Check_NE, !=, val1, val1_text, val2, val2_text)
+#define ABSL_LOG_INTERNAL_CHECK_LE_IMPL(val1, val1_text, val2, val2_text) \
+ ABSL_LOG_INTERNAL_CHECK_OP(Check_LE, <=, val1, val1_text, val2, val2_text)
+#define ABSL_LOG_INTERNAL_CHECK_LT_IMPL(val1, val1_text, val2, val2_text) \
+ ABSL_LOG_INTERNAL_CHECK_OP(Check_LT, <, val1, val1_text, val2, val2_text)
+#define ABSL_LOG_INTERNAL_CHECK_GE_IMPL(val1, val1_text, val2, val2_text) \
+ ABSL_LOG_INTERNAL_CHECK_OP(Check_GE, >=, val1, val1_text, val2, val2_text)
+#define ABSL_LOG_INTERNAL_CHECK_GT_IMPL(val1, val1_text, val2, val2_text) \
+ ABSL_LOG_INTERNAL_CHECK_OP(Check_GT, >, val1, val1_text, val2, val2_text)
+#define ABSL_LOG_INTERNAL_QCHECK_EQ_IMPL(val1, val1_text, val2, val2_text) \
+ ABSL_LOG_INTERNAL_QCHECK_OP(Check_EQ, ==, val1, val1_text, val2, val2_text)
+#define ABSL_LOG_INTERNAL_QCHECK_NE_IMPL(val1, val1_text, val2, val2_text) \
+ ABSL_LOG_INTERNAL_QCHECK_OP(Check_NE, !=, val1, val1_text, val2, val2_text)
+#define ABSL_LOG_INTERNAL_QCHECK_LE_IMPL(val1, val1_text, val2, val2_text) \
+ ABSL_LOG_INTERNAL_QCHECK_OP(Check_LE, <=, val1, val1_text, val2, val2_text)
+#define ABSL_LOG_INTERNAL_QCHECK_LT_IMPL(val1, val1_text, val2, val2_text) \
+ ABSL_LOG_INTERNAL_QCHECK_OP(Check_LT, <, val1, val1_text, val2, val2_text)
+#define ABSL_LOG_INTERNAL_QCHECK_GE_IMPL(val1, val1_text, val2, val2_text) \
+ ABSL_LOG_INTERNAL_QCHECK_OP(Check_GE, >=, val1, val1_text, val2, val2_text)
+#define ABSL_LOG_INTERNAL_QCHECK_GT_IMPL(val1, val1_text, val2, val2_text) \
+ ABSL_LOG_INTERNAL_QCHECK_OP(Check_GT, >, val1, val1_text, val2, val2_text)
+#ifndef NDEBUG
+#define ABSL_LOG_INTERNAL_DCHECK_EQ_IMPL(val1, val1_text, val2, val2_text) \
+ ABSL_LOG_INTERNAL_CHECK_EQ_IMPL(val1, val1_text, val2, val2_text)
+#define ABSL_LOG_INTERNAL_DCHECK_NE_IMPL(val1, val1_text, val2, val2_text) \
+ ABSL_LOG_INTERNAL_CHECK_NE_IMPL(val1, val1_text, val2, val2_text)
+#define ABSL_LOG_INTERNAL_DCHECK_LE_IMPL(val1, val1_text, val2, val2_text) \
+ ABSL_LOG_INTERNAL_CHECK_LE_IMPL(val1, val1_text, val2, val2_text)
+#define ABSL_LOG_INTERNAL_DCHECK_LT_IMPL(val1, val1_text, val2, val2_text) \
+ ABSL_LOG_INTERNAL_CHECK_LT_IMPL(val1, val1_text, val2, val2_text)
+#define ABSL_LOG_INTERNAL_DCHECK_GE_IMPL(val1, val1_text, val2, val2_text) \
+ ABSL_LOG_INTERNAL_CHECK_GE_IMPL(val1, val1_text, val2, val2_text)
+#define ABSL_LOG_INTERNAL_DCHECK_GT_IMPL(val1, val1_text, val2, val2_text) \
+ ABSL_LOG_INTERNAL_CHECK_GT_IMPL(val1, val1_text, val2, val2_text)
+#else // ndef NDEBUG
+#define ABSL_LOG_INTERNAL_DCHECK_EQ_IMPL(val1, val1_text, val2, val2_text) \
+ ABSL_LOG_INTERNAL_DCHECK_NOP(val1, val2)
+#define ABSL_LOG_INTERNAL_DCHECK_NE_IMPL(val1, val1_text, val2, val2_text) \
+ ABSL_LOG_INTERNAL_DCHECK_NOP(val1, val2)
+#define ABSL_LOG_INTERNAL_DCHECK_LE_IMPL(val1, val1_text, val2, val2_text) \
+ ABSL_LOG_INTERNAL_DCHECK_NOP(val1, val2)
+#define ABSL_LOG_INTERNAL_DCHECK_LT_IMPL(val1, val1_text, val2, val2_text) \
+ ABSL_LOG_INTERNAL_DCHECK_NOP(val1, val2)
+#define ABSL_LOG_INTERNAL_DCHECK_GE_IMPL(val1, val1_text, val2, val2_text) \
+ ABSL_LOG_INTERNAL_DCHECK_NOP(val1, val2)
+#define ABSL_LOG_INTERNAL_DCHECK_GT_IMPL(val1, val1_text, val2, val2_text) \
+ ABSL_LOG_INTERNAL_DCHECK_NOP(val1, val2)
+#endif // def NDEBUG
+
+// CHECK_OK
+#define ABSL_LOG_INTERNAL_CHECK_OK_IMPL(status, status_text) \
+ ABSL_LOG_INTERNAL_CHECK_OK(status, status_text)
+#define ABSL_LOG_INTERNAL_QCHECK_OK_IMPL(status, status_text) \
+ ABSL_LOG_INTERNAL_QCHECK_OK(status, status_text)
+#ifndef NDEBUG
+#define ABSL_LOG_INTERNAL_DCHECK_OK_IMPL(status, status_text) \
+ ABSL_LOG_INTERNAL_CHECK_OK(status, status_text)
+#else
+#define ABSL_LOG_INTERNAL_DCHECK_OK_IMPL(status, status_text) \
+ ABSL_LOG_INTERNAL_DCHECK_NOP(status, nullptr)
+#endif
+
+// CHECK_STREQ
+#define ABSL_LOG_INTERNAL_CHECK_STREQ_IMPL(s1, s1_text, s2, s2_text) \
+ ABSL_LOG_INTERNAL_CHECK_STROP(strcmp, ==, true, s1, s1_text, s2, s2_text)
+#define ABSL_LOG_INTERNAL_CHECK_STRNE_IMPL(s1, s1_text, s2, s2_text) \
+ ABSL_LOG_INTERNAL_CHECK_STROP(strcmp, !=, false, s1, s1_text, s2, s2_text)
+#define ABSL_LOG_INTERNAL_CHECK_STRCASEEQ_IMPL(s1, s1_text, s2, s2_text) \
+ ABSL_LOG_INTERNAL_CHECK_STROP(strcasecmp, ==, true, s1, s1_text, s2, s2_text)
+#define ABSL_LOG_INTERNAL_CHECK_STRCASENE_IMPL(s1, s1_text, s2, s2_text) \
+ ABSL_LOG_INTERNAL_CHECK_STROP(strcasecmp, !=, false, s1, s1_text, s2, s2_text)
+#define ABSL_LOG_INTERNAL_QCHECK_STREQ_IMPL(s1, s1_text, s2, s2_text) \
+ ABSL_LOG_INTERNAL_QCHECK_STROP(strcmp, ==, true, s1, s1_text, s2, s2_text)
+#define ABSL_LOG_INTERNAL_QCHECK_STRNE_IMPL(s1, s1_text, s2, s2_text) \
+ ABSL_LOG_INTERNAL_QCHECK_STROP(strcmp, !=, false, s1, s1_text, s2, s2_text)
+#define ABSL_LOG_INTERNAL_QCHECK_STRCASEEQ_IMPL(s1, s1_text, s2, s2_text) \
+ ABSL_LOG_INTERNAL_QCHECK_STROP(strcasecmp, ==, true, s1, s1_text, s2, s2_text)
+#define ABSL_LOG_INTERNAL_QCHECK_STRCASENE_IMPL(s1, s1_text, s2, s2_text) \
+ ABSL_LOG_INTERNAL_QCHECK_STROP(strcasecmp, !=, false, s1, s1_text, s2, \
+ s2_text)
+#ifndef NDEBUG
+#define ABSL_LOG_INTERNAL_DCHECK_STREQ_IMPL(s1, s1_text, s2, s2_text) \
+ ABSL_LOG_INTERNAL_CHECK_STREQ_IMPL(s1, s1_text, s2, s2_text)
+#define ABSL_LOG_INTERNAL_DCHECK_STRCASEEQ_IMPL(s1, s1_text, s2, s2_text) \
+ ABSL_LOG_INTERNAL_CHECK_STRCASEEQ_IMPL(s1, s1_text, s2, s2_text)
+#define ABSL_LOG_INTERNAL_DCHECK_STRNE_IMPL(s1, s1_text, s2, s2_text) \
+ ABSL_LOG_INTERNAL_CHECK_STRNE_IMPL(s1, s1_text, s2, s2_text)
+#define ABSL_LOG_INTERNAL_DCHECK_STRCASENE_IMPL(s1, s1_text, s2, s2_text) \
+ ABSL_LOG_INTERNAL_CHECK_STRCASENE_IMPL(s1, s1_text, s2, s2_text)
+#else // ndef NDEBUG
+#define ABSL_LOG_INTERNAL_DCHECK_STREQ_IMPL(s1, s1_text, s2, s2_text) \
+ ABSL_LOG_INTERNAL_DCHECK_NOP(s1, s2)
+#define ABSL_LOG_INTERNAL_DCHECK_STRCASEEQ_IMPL(s1, s1_text, s2, s2_text) \
+ ABSL_LOG_INTERNAL_DCHECK_NOP(s1, s2)
+#define ABSL_LOG_INTERNAL_DCHECK_STRNE_IMPL(s1, s1_text, s2, s2_text) \
+ ABSL_LOG_INTERNAL_DCHECK_NOP(s1, s2)
+#define ABSL_LOG_INTERNAL_DCHECK_STRCASENE_IMPL(s1, s1_text, s2, s2_text) \
+ ABSL_LOG_INTERNAL_DCHECK_NOP(s1, s2)
+#endif // def NDEBUG
+
+#endif // ABSL_LOG_INTERNAL_CHECK_IMPL_H_
diff --git a/absl/log/internal/check_op.cc b/absl/log/internal/check_op.cc
new file mode 100644
index 0000000..f4b6764
--- /dev/null
+++ b/absl/log/internal/check_op.cc
@@ -0,0 +1,118 @@
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/log/internal/check_op.h"
+
+#include <string.h>
+
+#ifdef _MSC_VER
+#define strcasecmp _stricmp
+#else
+#include <strings.h> // for strcasecmp, but msvc does not have this header
+#endif
+
+#include <sstream>
+#include <string>
+
+#include "absl/base/config.h"
+#include "absl/strings/str_cat.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace log_internal {
+
+#define ABSL_LOGGING_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING(x) \
+ template std::string* MakeCheckOpString(x, x, const char*)
+ABSL_LOGGING_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING(bool);
+ABSL_LOGGING_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING(int64_t);
+ABSL_LOGGING_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING(uint64_t);
+ABSL_LOGGING_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING(float);
+ABSL_LOGGING_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING(double);
+ABSL_LOGGING_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING(char);
+ABSL_LOGGING_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING(unsigned char);
+ABSL_LOGGING_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING(const std::string&);
+ABSL_LOGGING_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING(const absl::string_view&);
+ABSL_LOGGING_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING(const char*);
+ABSL_LOGGING_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING(const signed char*);
+ABSL_LOGGING_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING(const unsigned char*);
+ABSL_LOGGING_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING(const void*);
+#undef ABSL_LOGGING_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING
+
+CheckOpMessageBuilder::CheckOpMessageBuilder(const char* exprtext) {
+ stream_ << exprtext << " (";
+}
+
+std::ostream& CheckOpMessageBuilder::ForVar2() {
+ stream_ << " vs. ";
+ return stream_;
+}
+
+std::string* CheckOpMessageBuilder::NewString() {
+ stream_ << ")";
+ return new std::string(stream_.str());
+}
+
+void MakeCheckOpValueString(std::ostream& os, const char v) {
+ if (v >= 32 && v <= 126) {
+ os << "'" << v << "'";
+ } else {
+ os << "char value " << int{v};
+ }
+}
+
+void MakeCheckOpValueString(std::ostream& os, const signed char v) {
+ if (v >= 32 && v <= 126) {
+ os << "'" << v << "'";
+ } else {
+ os << "signed char value " << int{v};
+ }
+}
+
+void MakeCheckOpValueString(std::ostream& os, const unsigned char v) {
+ if (v >= 32 && v <= 126) {
+ os << "'" << v << "'";
+ } else {
+ os << "unsigned char value " << int{v};
+ }
+}
+
+void MakeCheckOpValueString(std::ostream& os, const void* p) {
+ if (p == nullptr) {
+ os << "(null)";
+ } else {
+ os << p;
+ }
+}
+
+// Helper functions for string comparisons.
+#define DEFINE_CHECK_STROP_IMPL(name, func, expected) \
+ std::string* Check##func##expected##Impl(const char* s1, const char* s2, \
+ const char* exprtext) { \
+ bool equal = s1 == s2 || (s1 && s2 && !func(s1, s2)); \
+ if (equal == expected) { \
+ return nullptr; \
+ } else { \
+ return new std::string( \
+ absl::StrCat(exprtext, " (", s1, " vs. ", s2, ")")); \
+ } \
+ }
+DEFINE_CHECK_STROP_IMPL(CHECK_STREQ, strcmp, true)
+DEFINE_CHECK_STROP_IMPL(CHECK_STRNE, strcmp, false)
+DEFINE_CHECK_STROP_IMPL(CHECK_STRCASEEQ, strcasecmp, true)
+DEFINE_CHECK_STROP_IMPL(CHECK_STRCASENE, strcasecmp, false)
+#undef DEFINE_CHECK_STROP_IMPL
+
+} // namespace log_internal
+ABSL_NAMESPACE_END
+} // namespace absl
diff --git a/absl/log/internal/check_op.h b/absl/log/internal/check_op.h
new file mode 100644
index 0000000..11f0f40
--- /dev/null
+++ b/absl/log/internal/check_op.h
@@ -0,0 +1,420 @@
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// -----------------------------------------------------------------------------
+// File: log/internal/check_op.h
+// -----------------------------------------------------------------------------
+//
+// This file declares helpers routines and macros used to implement `CHECK`
+// macros.
+
+#ifndef ABSL_LOG_INTERNAL_CHECK_OP_H_
+#define ABSL_LOG_INTERNAL_CHECK_OP_H_
+
+#include <stdint.h>
+
+#include <ostream>
+#include <sstream>
+#include <string>
+#include <utility>
+
+#include "absl/base/attributes.h"
+#include "absl/base/config.h"
+#include "absl/base/optimization.h"
+#include "absl/log/internal/nullguard.h"
+#include "absl/log/internal/nullstream.h"
+#include "absl/log/internal/strip.h"
+
+// `ABSL_LOG_INTERNAL_STRIP_STRING_LITERAL` wraps string literals that
+// should be stripped when `ABSL_MIN_LOG_LEVEL` exceeds `kFatal`.
+#ifdef ABSL_MIN_LOG_LEVEL
+#define ABSL_LOG_INTERNAL_STRIP_STRING_LITERAL(literal) \
+ (::absl::LogSeverity::kFatal >= \
+ static_cast<::absl::LogSeverity>(ABSL_MIN_LOG_LEVEL) \
+ ? (literal) \
+ : "")
+#else
+#define ABSL_LOG_INTERNAL_STRIP_STRING_LITERAL(literal) (literal)
+#endif
+
+#ifdef NDEBUG
+// `NDEBUG` is defined, so `DCHECK_EQ(x, y)` and so on do nothing. However, we
+// still want the compiler to parse `x` and `y`, because we don't want to lose
+// potentially useful errors and warnings.
+#define ABSL_LOG_INTERNAL_DCHECK_NOP(x, y) \
+ while (false && ((void)(x), (void)(y), 0)) \
+ ::absl::log_internal::NullStream().InternalStream()
+#endif
+
+#define ABSL_LOG_INTERNAL_CHECK_OP(name, op, val1, val1_text, val2, val2_text) \
+ while ( \
+ ::std::string* absl_log_internal_check_op_result ABSL_ATTRIBUTE_UNUSED = \
+ ::absl::log_internal::name##Impl( \
+ ::absl::log_internal::GetReferenceableValue(val1), \
+ ::absl::log_internal::GetReferenceableValue(val2), \
+ ABSL_LOG_INTERNAL_STRIP_STRING_LITERAL(val1_text \
+ " " #op " " val2_text))) \
+ ABSL_LOG_INTERNAL_CONDITION_FATAL(STATELESS, true) \
+ ABSL_LOG_INTERNAL_CHECK(*absl_log_internal_check_op_result).InternalStream()
+#define ABSL_LOG_INTERNAL_QCHECK_OP(name, op, val1, val1_text, val2, \
+ val2_text) \
+ while (::std::string* absl_log_internal_qcheck_op_result = \
+ ::absl::log_internal::name##Impl( \
+ ::absl::log_internal::GetReferenceableValue(val1), \
+ ::absl::log_internal::GetReferenceableValue(val2), \
+ ABSL_LOG_INTERNAL_STRIP_STRING_LITERAL( \
+ val1_text " " #op " " val2_text))) \
+ ABSL_LOG_INTERNAL_CONDITION_QFATAL(STATELESS, true) \
+ ABSL_LOG_INTERNAL_QCHECK(*absl_log_internal_qcheck_op_result).InternalStream()
+#define ABSL_LOG_INTERNAL_CHECK_STROP(func, op, expected, s1, s1_text, s2, \
+ s2_text) \
+ while (::std::string* absl_log_internal_check_strop_result = \
+ ::absl::log_internal::Check##func##expected##Impl( \
+ (s1), (s2), \
+ ABSL_LOG_INTERNAL_STRIP_STRING_LITERAL(s1_text " " #op \
+ " " s2_text))) \
+ ABSL_LOG_INTERNAL_CONDITION_FATAL(STATELESS, true) \
+ ABSL_LOG_INTERNAL_CHECK(*absl_log_internal_check_strop_result) \
+ .InternalStream()
+#define ABSL_LOG_INTERNAL_QCHECK_STROP(func, op, expected, s1, s1_text, s2, \
+ s2_text) \
+ while (::std::string* absl_log_internal_qcheck_strop_result = \
+ ::absl::log_internal::Check##func##expected##Impl( \
+ (s1), (s2), \
+ ABSL_LOG_INTERNAL_STRIP_STRING_LITERAL(s1_text " " #op \
+ " " s2_text))) \
+ ABSL_LOG_INTERNAL_CONDITION_QFATAL(STATELESS, true) \
+ ABSL_LOG_INTERNAL_QCHECK(*absl_log_internal_qcheck_strop_result) \
+ .InternalStream()
+// This one is tricky:
+// * We must evaluate `val` exactly once, yet we need to do two things with it:
+// evaluate `.ok()` and (sometimes) `.ToString()`.
+// * `val` might be an `absl::Status` or some `absl::StatusOr<T>`.
+// * `val` might be e.g. `ATemporary().GetStatus()`, which may return a
+// reference to a member of `ATemporary` that is only valid until the end of
+// the full expression.
+// * We don't want this file to depend on `absl::Status` `#include`s or linkage,
+// nor do we want to move the definition to status and introduce a dependency
+// in the other direction. We can be assured that callers must already have a
+// `Status` and the necessary `#include`s and linkage.
+// * Callsites should be small and fast (at least when `val.ok()`): one branch,
+// minimal stack footprint.
+// * In particular, the string concat stuff should be out-of-line and emitted
+// in only one TU to save linker input size
+// * We want the `val.ok()` check inline so static analyzers and optimizers can
+// see it.
+// * As usual, no braces so we can stream into the expansion with `operator<<`.
+// * Also as usual, it must expand to a single (partial) statement with no
+// ambiguous-else problems.
+// * When stripped by `ABSL_MIN_LOG_LEVEL`, we must discard the `<expr> is OK`
+// string literal and abort without doing any streaming. We don't need to
+// strip the call to stringify the non-ok `Status` as long as we don't log it;
+// dropping the `Status`'s message text is out of scope.
+#define ABSL_LOG_INTERNAL_CHECK_OK(val, val_text) \
+ for (::std::pair<const ::absl::Status*, ::std::string*> \
+ absl_log_internal_check_ok_goo; \
+ 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_log_internal_check_ok_goo.second) \
+ .InternalStream()
+#define ABSL_LOG_INTERNAL_QCHECK_OK(val, val_text) \
+ for (::std::pair<const ::absl::Status*, ::std::string*> \
+ absl_log_internal_qcheck_ok_goo; \
+ absl_log_internal_qcheck_ok_goo.first = \
+ ::absl::log_internal::AsStatus(val), \
+ absl_log_internal_qcheck_ok_goo.second = \
+ ABSL_PREDICT_TRUE(absl_log_internal_qcheck_ok_goo.first->ok()) \
+ ? nullptr \
+ : ::absl::status_internal::MakeCheckFailString( \
+ absl_log_internal_qcheck_ok_goo.first, \
+ ABSL_LOG_INTERNAL_STRIP_STRING_LITERAL(val_text \
+ " is OK")), \
+ !ABSL_PREDICT_TRUE(absl_log_internal_qcheck_ok_goo.first->ok());) \
+ ABSL_LOG_INTERNAL_CONDITION_QFATAL(STATELESS, true) \
+ ABSL_LOG_INTERNAL_QCHECK(*absl_log_internal_qcheck_ok_goo.second) \
+ .InternalStream()
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+
+class Status;
+template <typename T>
+class StatusOr;
+
+namespace status_internal {
+ABSL_ATTRIBUTE_PURE_FUNCTION std::string* MakeCheckFailString(
+ const absl::Status* status, const char* prefix);
+} // namespace status_internal
+
+namespace log_internal {
+
+// Convert a Status or a StatusOr to its underlying status value.
+//
+// (This implementation does not require a dep on absl::Status to work.)
+inline const absl::Status* AsStatus(const absl::Status& s) { return &s; }
+template <typename T>
+const absl::Status* AsStatus(const absl::StatusOr<T>& s) {
+ return &s.status();
+}
+
+// A helper class for formatting `expr (V1 vs. V2)` in a `CHECK_XX` statement.
+// See `MakeCheckOpString` for sample usage.
+class CheckOpMessageBuilder final {
+ public:
+ // Inserts `exprtext` and ` (` to the stream.
+ explicit CheckOpMessageBuilder(const char* exprtext);
+ ~CheckOpMessageBuilder() = default;
+ // For inserting the first variable.
+ std::ostream& ForVar1() { return stream_; }
+ // For inserting the second variable (adds an intermediate ` vs. `).
+ std::ostream& ForVar2();
+ // Get the result (inserts the closing `)`).
+ std::string* NewString();
+
+ private:
+ std::ostringstream stream_;
+};
+
+// This formats a value for a failing `CHECK_XX` statement. Ordinarily, it uses
+// the definition for `operator<<`, with a few special cases below.
+template <typename T>
+inline void MakeCheckOpValueString(std::ostream& os, const T& v) {
+ os << log_internal::NullGuard<T>::Guard(v);
+}
+
+// Overloads for char types provide readable values for unprintable characters.
+void MakeCheckOpValueString(std::ostream& os, char v);
+void MakeCheckOpValueString(std::ostream& os, signed char v);
+void MakeCheckOpValueString(std::ostream& os, unsigned char v);
+void MakeCheckOpValueString(std::ostream& os, const void* p);
+
+namespace detect_specialization {
+
+// MakeCheckOpString is being specialized for every T and U pair that is being
+// passed to the CHECK_op macros. However, there is a lot of redundancy in these
+// specializations that creates unnecessary library and binary bloat.
+// The number of instantiations tends to be O(n^2) because we have two
+// independent inputs. This technique works by reducing `n`.
+//
+// Most user-defined types being passed to CHECK_op end up being printed as a
+// builtin type. For example, enums tend to be implicitly converted to its
+// underlying type when calling operator<<, and pointers are printed with the
+// `const void*` overload.
+// To reduce the number of instantiations we coerce these values before calling
+// MakeCheckOpString instead of inside it.
+//
+// To detect if this coercion is needed, we duplicate all the relevant
+// operator<< overloads as specified in the standard, just in a different
+// namespace. If the call to `stream << value` becomes ambiguous, it means that
+// one of these overloads is the one selected by overload resolution. We then
+// do overload resolution again just with our overload set to see which one gets
+// selected. That tells us which type to coerce to.
+// If the augmented call was not ambiguous, it means that none of these were
+// selected and we can't coerce the input.
+//
+// As a secondary step to reduce code duplication, we promote integral types to
+// their 64-bit variant. This does not change the printed value, but reduces the
+// number of instantiations even further. Promoting an integer is very cheap at
+// the call site.
+int64_t operator<<(std::ostream&, short value); // NOLINT
+int64_t operator<<(std::ostream&, unsigned short value); // NOLINT
+int64_t operator<<(std::ostream&, int value);
+int64_t operator<<(std::ostream&, unsigned int value);
+int64_t operator<<(std::ostream&, long value); // NOLINT
+uint64_t operator<<(std::ostream&, unsigned long value); // NOLINT
+int64_t operator<<(std::ostream&, long long value); // NOLINT
+uint64_t operator<<(std::ostream&, unsigned long long value); // NOLINT
+float operator<<(std::ostream&, float value);
+double operator<<(std::ostream&, double value);
+long double operator<<(std::ostream&, long double value);
+bool operator<<(std::ostream&, bool value);
+const void* operator<<(std::ostream&, const void* value);
+const void* operator<<(std::ostream&, std::nullptr_t);
+
+// These `char` overloads are specified like this in the standard, so we have to
+// write them exactly the same to ensure the call is ambiguous.
+// If we wrote it in a different way (eg taking std::ostream instead of the
+// template) then one call might have a higher rank than the other and it would
+// not be ambiguous.
+template <typename Traits>
+char operator<<(std::basic_ostream<char, Traits>&, char);
+template <typename Traits>
+signed char operator<<(std::basic_ostream<char, Traits>&, signed char);
+template <typename Traits>
+unsigned char operator<<(std::basic_ostream<char, Traits>&, unsigned char);
+template <typename Traits>
+const char* operator<<(std::basic_ostream<char, Traits>&, const char*);
+template <typename Traits>
+const signed char* operator<<(std::basic_ostream<char, Traits>&,
+ const signed char*);
+template <typename Traits>
+const unsigned char* operator<<(std::basic_ostream<char, Traits>&,
+ const unsigned char*);
+
+// This overload triggers when the call is not ambiguous.
+// It means that T is being printed with some overload not on this list.
+// We keep the value as `const T&`.
+template <typename T, typename = decltype(std::declval<std::ostream&>()
+ << std::declval<const T&>())>
+const T& Detect(int);
+
+// This overload triggers when the call is ambiguous.
+// It means that T is either one from this list or printed as one from this
+// list. Eg an enum that decays to `int` for printing.
+// We ask the overload set to give us the type we want to convert it to.
+template <typename T>
+decltype(detect_specialization::operator<<(std::declval<std::ostream&>(),
+ std::declval<const T&>()))
+Detect(char);
+
+} // namespace detect_specialization
+
+template <typename T>
+using CheckOpStreamType = decltype(detect_specialization::Detect<T>(0));
+
+// Build the error message string. Specify no inlining for code size.
+template <typename T1, typename T2>
+ABSL_ATTRIBUTE_RETURNS_NONNULL std::string* MakeCheckOpString(
+ T1 v1, T2 v2, const char* exprtext) ABSL_ATTRIBUTE_NOINLINE;
+
+template <typename T1, typename T2>
+std::string* MakeCheckOpString(T1 v1, T2 v2, const char* exprtext) {
+ CheckOpMessageBuilder comb(exprtext);
+ MakeCheckOpValueString(comb.ForVar1(), v1);
+ MakeCheckOpValueString(comb.ForVar2(), v2);
+ return comb.NewString();
+}
+
+// Add a few commonly used instantiations as extern to reduce size of objects
+// files.
+#define ABSL_LOG_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING_EXTERN(x) \
+ extern template std::string* MakeCheckOpString(x, x, const char*)
+ABSL_LOG_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING_EXTERN(bool);
+ABSL_LOG_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING_EXTERN(int64_t);
+ABSL_LOG_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING_EXTERN(uint64_t);
+ABSL_LOG_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING_EXTERN(float);
+ABSL_LOG_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING_EXTERN(double);
+ABSL_LOG_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING_EXTERN(char);
+ABSL_LOG_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING_EXTERN(unsigned char);
+ABSL_LOG_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING_EXTERN(const std::string&);
+ABSL_LOG_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING_EXTERN(const absl::string_view&);
+ABSL_LOG_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING_EXTERN(const char*);
+ABSL_LOG_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING_EXTERN(const signed char*);
+ABSL_LOG_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING_EXTERN(const unsigned char*);
+ABSL_LOG_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING_EXTERN(const void*);
+#undef ABSL_LOG_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING_EXTERN
+
+// `ABSL_LOG_INTERNAL_CHECK_OP_IMPL_RESULT` skips formatting the Check_OP result
+// string iff `ABSL_MIN_LOG_LEVEL` exceeds `kFatal`, instead returning an empty
+// string.
+#ifdef ABSL_MIN_LOG_LEVEL
+#define ABSL_LOG_INTERNAL_CHECK_OP_IMPL_RESULT(U1, U2, v1, v2, exprtext) \
+ ((::absl::LogSeverity::kFatal >= \
+ static_cast<::absl::LogSeverity>(ABSL_MIN_LOG_LEVEL)) \
+ ? MakeCheckOpString<U1, U2>(v1, v2, exprtext) \
+ : new std::string())
+#else
+#define ABSL_LOG_INTERNAL_CHECK_OP_IMPL_RESULT(U1, U2, v1, v2, exprtext) \
+ MakeCheckOpString<U1, U2>(v1, v2, exprtext)
+#endif
+
+// Helper functions for `ABSL_LOG_INTERNAL_CHECK_OP` macro family. The
+// `(int, int)` override works around the issue that the compiler will not
+// instantiate the template version of the function on values of unnamed enum
+// type.
+#define ABSL_LOG_INTERNAL_CHECK_OP_IMPL(name, op) \
+ template <typename T1, typename T2> \
+ inline constexpr ::std::string* name##Impl(const T1& v1, const T2& v2, \
+ const char* exprtext) { \
+ using U1 = CheckOpStreamType<T1>; \
+ using U2 = CheckOpStreamType<T2>; \
+ return ABSL_PREDICT_TRUE(v1 op v2) \
+ ? nullptr \
+ : ABSL_LOG_INTERNAL_CHECK_OP_IMPL_RESULT(U1, U2, v1, v2, \
+ exprtext); \
+ } \
+ inline constexpr ::std::string* name##Impl(int v1, int v2, \
+ const char* exprtext) { \
+ return name##Impl<int, int>(v1, v2, exprtext); \
+ }
+
+ABSL_LOG_INTERNAL_CHECK_OP_IMPL(Check_EQ, ==)
+ABSL_LOG_INTERNAL_CHECK_OP_IMPL(Check_NE, !=)
+ABSL_LOG_INTERNAL_CHECK_OP_IMPL(Check_LE, <=)
+ABSL_LOG_INTERNAL_CHECK_OP_IMPL(Check_LT, <)
+ABSL_LOG_INTERNAL_CHECK_OP_IMPL(Check_GE, >=)
+ABSL_LOG_INTERNAL_CHECK_OP_IMPL(Check_GT, >)
+#undef ABSL_LOG_INTERNAL_CHECK_OP_IMPL_RESULT
+#undef ABSL_LOG_INTERNAL_CHECK_OP_IMPL
+
+std::string* CheckstrcmptrueImpl(const char* s1, const char* s2,
+ const char* exprtext);
+std::string* CheckstrcmpfalseImpl(const char* s1, const char* s2,
+ const char* exprtext);
+std::string* CheckstrcasecmptrueImpl(const char* s1, const char* s2,
+ const char* exprtext);
+std::string* CheckstrcasecmpfalseImpl(const char* s1, const char* s2,
+ const char* exprtext);
+
+// `CHECK_EQ` and friends want to pass their arguments by reference, however
+// this winds up exposing lots of cases where people have defined and
+// initialized static const data members but never declared them (i.e. in a .cc
+// file), meaning they are not referenceable. This function avoids that problem
+// for integers (the most common cases) by overloading for every primitive
+// integer type, even the ones we discourage, and returning them by value.
+template <typename T>
+inline constexpr const T& GetReferenceableValue(const T& t) {
+ return t;
+}
+inline constexpr char GetReferenceableValue(char t) { return t; }
+inline constexpr unsigned char GetReferenceableValue(unsigned char t) {
+ return t;
+}
+inline constexpr signed char GetReferenceableValue(signed char t) { return t; }
+inline constexpr short GetReferenceableValue(short t) { return t; } // NOLINT
+inline constexpr unsigned short GetReferenceableValue( // NOLINT
+ unsigned short t) { // NOLINT
+ return t;
+}
+inline constexpr int GetReferenceableValue(int t) { return t; }
+inline constexpr unsigned int GetReferenceableValue(unsigned int t) {
+ return t;
+}
+inline constexpr long GetReferenceableValue(long t) { return t; } // NOLINT
+inline constexpr unsigned long GetReferenceableValue( // NOLINT
+ unsigned long t) { // NOLINT
+ return t;
+}
+inline constexpr long long GetReferenceableValue(long long t) { // NOLINT
+ return t;
+}
+inline constexpr unsigned long long GetReferenceableValue( // NOLINT
+ unsigned long long t) { // NOLINT
+ return t;
+}
+
+} // namespace log_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_LOG_INTERNAL_CHECK_OP_H_
diff --git a/absl/log/internal/conditions.cc b/absl/log/internal/conditions.cc
new file mode 100644
index 0000000..a9f4966
--- /dev/null
+++ b/absl/log/internal/conditions.cc
@@ -0,0 +1,83 @@
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/log/internal/conditions.h"
+
+#include <atomic>
+#include <cstdint>
+
+#include "absl/base/config.h"
+#include "absl/base/internal/cycleclock.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace log_internal {
+namespace {
+
+// The following code behaves like AtomicStatsCounter::LossyAdd() for
+// speed since it is fine to lose occasional updates.
+// Returns old value of *counter.
+uint32_t LossyIncrement(std::atomic<uint32_t>* counter) {
+ const uint32_t value = counter->load(std::memory_order_relaxed);
+ counter->store(value + 1, std::memory_order_relaxed);
+ return value;
+}
+
+} // namespace
+
+bool LogEveryNState::ShouldLog(int n) {
+ return n > 0 && (LossyIncrement(&counter_) % static_cast<uint32_t>(n)) == 0;
+}
+
+bool LogFirstNState::ShouldLog(int n) {
+ const uint32_t counter_value = counter_.load(std::memory_order_relaxed);
+ if (static_cast<int64_t>(counter_value) < n) {
+ counter_.store(counter_value + 1, std::memory_order_relaxed);
+ return true;
+ }
+ return false;
+}
+
+bool LogEveryPow2State::ShouldLog() {
+ const uint32_t new_value = LossyIncrement(&counter_) + 1;
+ return (new_value & (new_value - 1)) == 0;
+}
+
+bool LogEveryNSecState::ShouldLog(double seconds) {
+ using absl::base_internal::CycleClock;
+ LossyIncrement(&counter_);
+ const int64_t now_cycles = CycleClock::Now();
+ int64_t next_cycles = next_log_time_cycles_.load(std::memory_order_relaxed);
+#if defined(__myriad2__)
+ // myriad2 does not have 8-byte compare and exchange. Use a racy version that
+ // is "good enough" but will over-log in the face of concurrent logging.
+ if (now_cycles > next_cycles) {
+ next_log_time_cycles_.store(now_cycles + seconds * CycleClock::Frequency(),
+ std::memory_order_relaxed);
+ return true;
+ }
+ return false;
+#else
+ do {
+ if (now_cycles <= next_cycles) return false;
+ } while (!next_log_time_cycles_.compare_exchange_weak(
+ next_cycles, now_cycles + seconds * CycleClock::Frequency(),
+ std::memory_order_relaxed, std::memory_order_relaxed));
+ return true;
+#endif
+}
+
+} // namespace log_internal
+ABSL_NAMESPACE_END
+} // namespace absl
diff --git a/absl/log/internal/conditions.h b/absl/log/internal/conditions.h
new file mode 100644
index 0000000..645f3c2
--- /dev/null
+++ b/absl/log/internal/conditions.h
@@ -0,0 +1,239 @@
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// -----------------------------------------------------------------------------
+// File: log/internal/conditions.h
+// -----------------------------------------------------------------------------
+//
+// This file contains implementation of conditional log statements, like LOG_IF
+// including all the ABSL_LOG_INTERNAL_..._CONDITION_... macros and
+// various condition classes like LogEveryNState.
+
+#ifndef ABSL_LOG_INTERNAL_CONDITIONS_H_
+#define ABSL_LOG_INTERNAL_CONDITIONS_H_
+
+#if defined(_WIN32) || defined(__hexagon__)
+#include <cstdlib>
+#else
+#include <unistd.h>
+#endif
+#include <stdlib.h>
+
+#include <atomic>
+#include <cstdint>
+
+#include "absl/base/attributes.h"
+#include "absl/base/config.h"
+#include "absl/log/internal/voidify.h"
+
+// `ABSL_LOG_INTERNAL_CONDITION` prefixes another macro that expands to a
+// temporary `LogMessage` instantiation followed by zero or more streamed
+// expressions. This definition is tricky to read correctly. It evaluates to
+// either
+//
+// (void)0;
+//
+// or
+//
+// ::absl::log_internal::Voidify() &&
+// ::absl::log_internal::LogMessage(...) << "the user's message";
+//
+// If the condition is evaluable at compile time, as is often the case, it
+// compiles away to just one side or the other.
+//
+// Although this is not used anywhere a statement (e.g. `if`) could not go,
+// the ternary expression does a better job avoiding spurious diagnostics
+// (dangling else, missing switch case) and preserving noreturn semantics (e.g.
+// on `LOG(FATAL)`) without requiring braces.
+//
+// The `switch` ensures that this expansion is the beginning of a statement (as
+// opposed to an expression) and prevents shenanigans like
+// `AFunction(LOG(INFO))` and `decltype(LOG(INFO))`. The apparently-redundant
+// `default` case makes the condition more amenable to Clang dataflow analysis.
+#define ABSL_LOG_INTERNAL_STATELESS_CONDITION(condition) \
+ switch (0) \
+ case 0: \
+ default: \
+ !(condition) ? (void)0 : ::absl::log_internal::Voidify()&&
+
+// `ABSL_LOG_INTERNAL_STATEFUL_CONDITION` applies a condition like
+// `ABSL_LOG_INTERNAL_STATELESS_CONDITION` but adds to that a series of variable
+// declarations, including a local static object which stores the state needed
+// to implement the stateful macros like `LOG_EVERY_N`.
+//
+// `for`-loops are used to declare scoped variables without braces (to permit
+// streaming into the macro's expansion) and without the dangling-`else`
+// problems/diagnostics that come with `if`.
+//
+// Two more variables are declared in separate `for`-loops:
+//
+// * `COUNTER` implements a streamable token whose value when streamed is the
+// number of times execution has passed through the macro.
+// * A boolean flag is used to prevent any of the `for`-loops from ever actually
+// looping.
+#define ABSL_LOG_INTERNAL_STATEFUL_CONDITION(condition) \
+ for (bool absl_log_internal_stateful_condition_do_log(condition); \
+ absl_log_internal_stateful_condition_do_log; \
+ absl_log_internal_stateful_condition_do_log = false) \
+ ABSL_LOG_INTERNAL_STATEFUL_CONDITION_IMPL
+#define ABSL_LOG_INTERNAL_STATEFUL_CONDITION_IMPL(kind, ...) \
+ for (static ::absl::log_internal::Log##kind##State \
+ absl_log_internal_stateful_condition_state; \
+ absl_log_internal_stateful_condition_do_log && \
+ absl_log_internal_stateful_condition_state.ShouldLog(__VA_ARGS__); \
+ absl_log_internal_stateful_condition_do_log = false) \
+ for (const uint32_t COUNTER ABSL_ATTRIBUTE_UNUSED = \
+ absl_log_internal_stateful_condition_state.counter(); \
+ absl_log_internal_stateful_condition_do_log; \
+ absl_log_internal_stateful_condition_do_log = false)
+
+// `ABSL_LOG_INTERNAL_CONDITION_*` serve to combine any conditions from the
+// macro (e.g. `LOG_IF` or `VLOG`) with inherent conditions (e.g.
+// `ABSL_MIN_LOG_LEVEL`) into a single boolean expression. We could chain
+// ternary operators instead, however some versions of Clang sometimes issue
+// spurious diagnostics after such expressions due to a control flow analysis
+// bug.
+#ifdef ABSL_MIN_LOG_LEVEL
+#define ABSL_LOG_INTERNAL_CONDITION_INFO(type, condition) \
+ ABSL_LOG_INTERNAL_##type##_CONDITION( \
+ (condition) && ::absl::LogSeverity::kInfo >= \
+ static_cast<::absl::LogSeverity>(ABSL_MIN_LOG_LEVEL))
+#define ABSL_LOG_INTERNAL_CONDITION_WARNING(type, condition) \
+ ABSL_LOG_INTERNAL_##type##_CONDITION( \
+ (condition) && ::absl::LogSeverity::kWarning >= \
+ static_cast<::absl::LogSeverity>(ABSL_MIN_LOG_LEVEL))
+#define ABSL_LOG_INTERNAL_CONDITION_ERROR(type, condition) \
+ ABSL_LOG_INTERNAL_##type##_CONDITION( \
+ (condition) && ::absl::LogSeverity::kError >= \
+ static_cast<::absl::LogSeverity>(ABSL_MIN_LOG_LEVEL))
+// NOTE: Use ternary operators instead of short-circuiting to mitigate
+// https://bugs.llvm.org/show_bug.cgi?id=51928.
+#define ABSL_LOG_INTERNAL_CONDITION_FATAL(type, condition) \
+ ABSL_LOG_INTERNAL_##type##_CONDITION( \
+ ((condition) \
+ ? (::absl::LogSeverity::kFatal >= \
+ static_cast<::absl::LogSeverity>(ABSL_MIN_LOG_LEVEL) \
+ ? true \
+ : (::absl::log_internal::AbortQuietly(), false)) \
+ : false))
+// NOTE: Use ternary operators instead of short-circuiting to mitigate
+// https://bugs.llvm.org/show_bug.cgi?id=51928.
+#define ABSL_LOG_INTERNAL_CONDITION_QFATAL(type, condition) \
+ ABSL_LOG_INTERNAL_##type##_CONDITION( \
+ ((condition) \
+ ? (::absl::LogSeverity::kFatal >= \
+ static_cast<::absl::LogSeverity>(ABSL_MIN_LOG_LEVEL) \
+ ? true \
+ : (::absl::log_internal::ExitQuietly(), false)) \
+ : false))
+#define ABSL_LOG_INTERNAL_CONDITION_DFATAL(type, condition) \
+ ABSL_LOG_INTERNAL_##type##_CONDITION( \
+ (ABSL_ASSUME(absl::kLogDebugFatal == absl::LogSeverity::kError || \
+ absl::kLogDebugFatal == absl::LogSeverity::kFatal), \
+ (condition) && \
+ (::absl::kLogDebugFatal >= \
+ static_cast<::absl::LogSeverity>(ABSL_MIN_LOG_LEVEL) || \
+ (::absl::kLogDebugFatal == ::absl::LogSeverity::kFatal && \
+ (::absl::log_internal::AbortQuietly(), false)))))
+
+#define ABSL_LOG_INTERNAL_CONDITION_LEVEL(severity) \
+ for (int absl_log_internal_severity_loop = 1; \
+ absl_log_internal_severity_loop; absl_log_internal_severity_loop = 0) \
+ for (const absl::LogSeverity absl_log_internal_severity = \
+ ::absl::NormalizeLogSeverity(severity); \
+ absl_log_internal_severity_loop; absl_log_internal_severity_loop = 0) \
+ ABSL_LOG_INTERNAL_CONDITION_LEVEL_IMPL
+#define ABSL_LOG_INTERNAL_CONDITION_LEVEL_IMPL(type, condition) \
+ ABSL_LOG_INTERNAL_##type##_CONDITION(( \
+ (condition) && \
+ (absl_log_internal_severity >= \
+ static_cast<::absl::LogSeverity>(ABSL_MIN_LOG_LEVEL) || \
+ (absl_log_internal_severity == ::absl::LogSeverity::kFatal && \
+ (::absl::log_internal::AbortQuietly(), false)))))
+#else // ndef ABSL_MIN_LOG_LEVEL
+#define ABSL_LOG_INTERNAL_CONDITION_INFO(type, condition) \
+ ABSL_LOG_INTERNAL_##type##_CONDITION(condition)
+#define ABSL_LOG_INTERNAL_CONDITION_WARNING(type, condition) \
+ ABSL_LOG_INTERNAL_##type##_CONDITION(condition)
+#define ABSL_LOG_INTERNAL_CONDITION_ERROR(type, condition) \
+ ABSL_LOG_INTERNAL_##type##_CONDITION(condition)
+#define ABSL_LOG_INTERNAL_CONDITION_FATAL(type, condition) \
+ ABSL_LOG_INTERNAL_##type##_CONDITION(condition)
+#define ABSL_LOG_INTERNAL_CONDITION_QFATAL(type, condition) \
+ ABSL_LOG_INTERNAL_##type##_CONDITION(condition)
+#define ABSL_LOG_INTERNAL_CONDITION_DFATAL(type, condition) \
+ ABSL_LOG_INTERNAL_##type##_CONDITION(condition)
+#define ABSL_LOG_INTERNAL_CONDITION_LEVEL(severity) \
+ for (int absl_log_internal_severity_loop = 1; \
+ absl_log_internal_severity_loop; absl_log_internal_severity_loop = 0) \
+ for (const absl::LogSeverity absl_log_internal_severity = \
+ ::absl::NormalizeLogSeverity(severity); \
+ absl_log_internal_severity_loop; absl_log_internal_severity_loop = 0) \
+ ABSL_LOG_INTERNAL_CONDITION_LEVEL_IMPL
+#define ABSL_LOG_INTERNAL_CONDITION_LEVEL_IMPL(type, condition) \
+ ABSL_LOG_INTERNAL_##type##_CONDITION(condition)
+#endif // ndef ABSL_MIN_LOG_LEVEL
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace log_internal {
+
+// Stateful condition class name should be "Log" + name + "State".
+class LogEveryNState final {
+ public:
+ bool ShouldLog(int n);
+ uint32_t counter() { return counter_.load(std::memory_order_relaxed); }
+
+ private:
+ std::atomic<uint32_t> counter_{0};
+};
+
+class LogFirstNState final {
+ public:
+ bool ShouldLog(int n);
+ uint32_t counter() { return counter_.load(std::memory_order_relaxed); }
+
+ private:
+ std::atomic<uint32_t> counter_{0};
+};
+
+class LogEveryPow2State final {
+ public:
+ bool ShouldLog();
+ uint32_t counter() { return counter_.load(std::memory_order_relaxed); }
+
+ private:
+ std::atomic<uint32_t> counter_{0};
+};
+
+class LogEveryNSecState final {
+ public:
+ bool ShouldLog(double seconds);
+ uint32_t counter() { return counter_.load(std::memory_order_relaxed); }
+
+ private:
+ std::atomic<uint32_t> counter_{0};
+ // Cycle count according to CycleClock that we should next log at.
+ std::atomic<int64_t> next_log_time_cycles_{0};
+};
+
+// Helper routines to abort the application quietly
+
+ABSL_ATTRIBUTE_NORETURN inline void AbortQuietly() { abort(); }
+ABSL_ATTRIBUTE_NORETURN inline void ExitQuietly() { _exit(1); }
+} // namespace log_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_LOG_INTERNAL_CONDITIONS_H_
diff --git a/absl/log/internal/config.h b/absl/log/internal/config.h
new file mode 100644
index 0000000..379e9ab
--- /dev/null
+++ b/absl/log/internal/config.h
@@ -0,0 +1,45 @@
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// -----------------------------------------------------------------------------
+// File: log/internal/config.h
+// -----------------------------------------------------------------------------
+//
+
+#ifndef ABSL_LOG_INTERNAL_CONFIG_H_
+#define ABSL_LOG_INTERNAL_CONFIG_H_
+
+#include "absl/base/config.h"
+
+#ifdef _WIN32
+#include <cstdint>
+#else
+#include <sys/types.h>
+#endif
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace log_internal {
+
+#ifdef _WIN32
+using Tid = uint32_t;
+#else
+using Tid = pid_t;
+#endif
+
+} // namespace log_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_LOG_INTERNAL_CONFIG_H_
diff --git a/absl/log/internal/flags.h b/absl/log/internal/flags.h
new file mode 100644
index 0000000..c453978
--- /dev/null
+++ b/absl/log/internal/flags.h
@@ -0,0 +1,59 @@
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// -----------------------------------------------------------------------------
+// File: log/log_flags.h
+// -----------------------------------------------------------------------------
+//
+// This header declares set of flags which can be used to configure Abseil
+// Logging library behaviour at runtime.
+
+#ifndef ABSL_LOG_INTERNAL_FLAGS_H_
+#define ABSL_LOG_INTERNAL_FLAGS_H_
+
+#include <string>
+
+#include "absl/flags/declare.h"
+
+// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+// These flags should not be used in C++ code to access logging library
+// configuration knobs. Use interfaces defined in absl/log/globals.h
+// instead. It is still ok to use these flags on a command line.
+// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+// Log messages at this severity or above are sent to stderr in *addition* to
+// `LogSink`s. Defaults to `ERROR`. See log_severity.h for numeric values of
+// severity levels.
+ABSL_DECLARE_FLAG(int, stderrthreshold);
+
+// Log messages at this severity or above are logged; others are discarded.
+// Defaults to `INFO`, i.e. log all severities. See log_severity.h for numeric
+// values of severity levels.
+ABSL_DECLARE_FLAG(int, minloglevel);
+
+// If specified in the form file:linenum, any messages logged from a matching
+// location will also include a backtrace.
+ABSL_DECLARE_FLAG(std::string, log_backtrace_at);
+
+// If true, the log prefix (severity, date, time, PID, etc.) is prepended to
+// each message logged. Defaults to true.
+ABSL_DECLARE_FLAG(bool, log_prefix);
+
+// Global log verbosity level. Default is 0.
+ABSL_DECLARE_FLAG(int, v);
+
+// Per-module log verbosity level. By default is empty and is unused.
+ABSL_DECLARE_FLAG(std::string, vmodule);
+
+#endif // ABSL_LOG_INTERNAL_FLAGS_H_
diff --git a/absl/log/internal/fnmatch.cc b/absl/log/internal/fnmatch.cc
new file mode 100644
index 0000000..26e1e57
--- /dev/null
+++ b/absl/log/internal/fnmatch.cc
@@ -0,0 +1,73 @@
+// Copyright 2023 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/log/internal/fnmatch.h"
+
+#include <cstddef>
+
+#include "absl/base/config.h"
+#include "absl/strings/string_view.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace log_internal {
+bool FNMatch(absl::string_view pattern, absl::string_view str) {
+ bool in_wildcard_match = false;
+ while (true) {
+ if (pattern.empty()) {
+ // `pattern` is exhausted; succeed if all of `str` was consumed matching
+ // it.
+ return in_wildcard_match || str.empty();
+ }
+ if (str.empty()) {
+ // `str` is exhausted; succeed if `pattern` is empty or all '*'s.
+ return pattern.find_first_not_of('*') == pattern.npos;
+ }
+ switch (pattern.front()) {
+ case '*':
+ pattern.remove_prefix(1);
+ in_wildcard_match = true;
+ break;
+ case '?':
+ pattern.remove_prefix(1);
+ str.remove_prefix(1);
+ break;
+ default:
+ if (in_wildcard_match) {
+ absl::string_view fixed_portion = pattern;
+ const size_t end = fixed_portion.find_first_of("*?");
+ if (end != fixed_portion.npos) {
+ fixed_portion = fixed_portion.substr(0, end);
+ }
+ const size_t match = str.find(fixed_portion);
+ if (match == str.npos) {
+ return false;
+ }
+ pattern.remove_prefix(fixed_portion.size());
+ str.remove_prefix(match + fixed_portion.size());
+ in_wildcard_match = false;
+ } else {
+ if (pattern.front() != str.front()) {
+ return false;
+ }
+ pattern.remove_prefix(1);
+ str.remove_prefix(1);
+ }
+ break;
+ }
+ }
+}
+} // namespace log_internal
+ABSL_NAMESPACE_END
+} // namespace absl
diff --git a/absl/log/internal/fnmatch.h b/absl/log/internal/fnmatch.h
new file mode 100644
index 0000000..4ea147c
--- /dev/null
+++ b/absl/log/internal/fnmatch.h
@@ -0,0 +1,35 @@
+// Copyright 2023 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef ABSL_LOG_INTERNAL_FNMATCH_H_
+#define ABSL_LOG_INTERNAL_FNMATCH_H_
+
+#include "absl/base/config.h"
+#include "absl/strings/string_view.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace log_internal {
+// Like POSIX `fnmatch`, but:
+// * accepts `string_view`
+// * does not allocate any dynamic memory
+// * only supports * and ? wildcards and not bracket expressions [...]
+// * wildcards may match /
+// * no backslash-escaping
+bool FNMatch(absl::string_view pattern, absl::string_view str);
+} // namespace log_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_LOG_INTERNAL_FNMATCH_H_
diff --git a/absl/log/internal/fnmatch_benchmark.cc b/absl/log/internal/fnmatch_benchmark.cc
new file mode 100644
index 0000000..f062ba2
--- /dev/null
+++ b/absl/log/internal/fnmatch_benchmark.cc
@@ -0,0 +1,29 @@
+// Copyright 2023 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/log/internal/fnmatch.h"
+#include "benchmark/benchmark.h"
+
+namespace {
+void BM_FNMatch(benchmark::State& state) {
+ while (state.KeepRunning()) {
+ bool ret =
+ absl::log_internal::FNMatch("*?*asdf*?*we???asdf**asdf*we",
+ "QWERFASVWERASDFWEDFASDasdfQWERGFWASDERREWF"
+ "weHOOasdf@#$%TW#ZSERasdfQW#REGTZSERERwe");
+ benchmark::DoNotOptimize(ret);
+ }
+}
+BENCHMARK(BM_FNMatch);
+} // namespace
diff --git a/absl/log/internal/fnmatch_test.cc b/absl/log/internal/fnmatch_test.cc
new file mode 100644
index 0000000..e16a64e
--- /dev/null
+++ b/absl/log/internal/fnmatch_test.cc
@@ -0,0 +1,59 @@
+// Copyright 2023 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/log/internal/fnmatch.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace {
+using ::testing::IsFalse;
+using ::testing::IsTrue;
+
+TEST(FNMatchTest, Works) {
+ using absl::log_internal::FNMatch;
+ EXPECT_THAT(FNMatch("foo", "foo"), IsTrue());
+ EXPECT_THAT(FNMatch("foo", "bar"), IsFalse());
+ EXPECT_THAT(FNMatch("foo", "fo"), IsFalse());
+ EXPECT_THAT(FNMatch("foo", "foo2"), IsFalse());
+ EXPECT_THAT(FNMatch("bar/foo.ext", "bar/foo.ext"), IsTrue());
+ EXPECT_THAT(FNMatch("*ba*r/fo*o.ext*", "bar/foo.ext"), IsTrue());
+ EXPECT_THAT(FNMatch("bar/foo.ext", "bar/baz.ext"), IsFalse());
+ EXPECT_THAT(FNMatch("bar/foo.ext", "bar/foo"), IsFalse());
+ EXPECT_THAT(FNMatch("bar/foo.ext", "bar/foo.ext.zip"), IsFalse());
+ EXPECT_THAT(FNMatch("ba?/*.ext", "bar/foo.ext"), IsTrue());
+ EXPECT_THAT(FNMatch("ba?/*.ext", "baZ/FOO.ext"), IsTrue());
+ EXPECT_THAT(FNMatch("ba?/*.ext", "barr/foo.ext"), IsFalse());
+ EXPECT_THAT(FNMatch("ba?/*.ext", "bar/foo.ext2"), IsFalse());
+ EXPECT_THAT(FNMatch("ba?/*", "bar/foo.ext2"), IsTrue());
+ EXPECT_THAT(FNMatch("ba?/*", "bar/"), IsTrue());
+ EXPECT_THAT(FNMatch("ba?/?", "bar/"), IsFalse());
+ EXPECT_THAT(FNMatch("ba?/*", "bar"), IsFalse());
+ EXPECT_THAT(FNMatch("?x", "zx"), IsTrue());
+ EXPECT_THAT(FNMatch("*b", "aab"), IsTrue());
+ EXPECT_THAT(FNMatch("a*b", "aXb"), IsTrue());
+ EXPECT_THAT(FNMatch("", ""), IsTrue());
+ EXPECT_THAT(FNMatch("", "a"), IsFalse());
+ EXPECT_THAT(FNMatch("ab*", "ab"), IsTrue());
+ EXPECT_THAT(FNMatch("ab**", "ab"), IsTrue());
+ EXPECT_THAT(FNMatch("ab*?", "ab"), IsFalse());
+ EXPECT_THAT(FNMatch("*", "bbb"), IsTrue());
+ EXPECT_THAT(FNMatch("*", ""), IsTrue());
+ EXPECT_THAT(FNMatch("?", ""), IsFalse());
+ EXPECT_THAT(FNMatch("***", "**p"), IsTrue());
+ EXPECT_THAT(FNMatch("**", "*"), IsTrue());
+ EXPECT_THAT(FNMatch("*?", "*"), IsTrue());
+}
+
+} // namespace
diff --git a/absl/log/internal/globals.cc b/absl/log/internal/globals.cc
new file mode 100644
index 0000000..359858f
--- /dev/null
+++ b/absl/log/internal/globals.cc
@@ -0,0 +1,145 @@
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/log/internal/globals.h"
+
+#include <atomic>
+#include <cstdio>
+
+#if defined(__EMSCRIPTEN__)
+#include <emscripten/console.h>
+#endif
+
+#include "absl/base/attributes.h"
+#include "absl/base/config.h"
+#include "absl/base/internal/raw_logging.h"
+#include "absl/base/log_severity.h"
+#include "absl/strings/string_view.h"
+#include "absl/strings/strip.h"
+#include "absl/time/time.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace log_internal {
+
+namespace {
+// Keeps track of whether Logging initialization is finalized.
+// Log messages generated before that will go to stderr.
+ABSL_CONST_INIT std::atomic<bool> logging_initialized(false);
+
+// The TimeZone used for logging. This may only be set once.
+ABSL_CONST_INIT std::atomic<absl::TimeZone*> timezone_ptr{nullptr};
+
+// If true, the logging library will symbolize stack in fatal messages
+ABSL_CONST_INIT std::atomic<bool> symbolize_stack_trace(true);
+
+// Specifies maximum number of stack frames to report in fatal messages.
+ABSL_CONST_INIT std::atomic<int> max_frames_in_stack_trace(64);
+
+ABSL_CONST_INIT std::atomic<bool> exit_on_dfatal(true);
+ABSL_CONST_INIT std::atomic<bool> suppress_sigabort_trace(false);
+} // namespace
+
+bool IsInitialized() {
+ return logging_initialized.load(std::memory_order_acquire);
+}
+
+void SetInitialized() {
+ logging_initialized.store(true, std::memory_order_release);
+}
+
+void WriteToStderr(absl::string_view message, absl::LogSeverity severity) {
+ if (message.empty()) return;
+#if defined(__EMSCRIPTEN__)
+ // In WebAssembly, bypass filesystem emulation via fwrite.
+ // Skip a trailing newline character as emscripten_errn adds one itself.
+ const auto message_minus_newline = absl::StripSuffix(message, "\n");
+ // emscripten_errn was introduced in 3.1.41 but broken in standalone mode
+ // until 3.1.43.
+#if ABSL_INTERNAL_EMSCRIPTEN_VERSION >= 3001043
+ emscripten_errn(message_minus_newline.data(), message_minus_newline.size());
+#else
+ std::string null_terminated_message(message_minus_newline);
+ _emscripten_err(null_terminated_message.c_str());
+#endif
+#else
+ // Avoid using std::cerr from this module since we may get called during
+ // exit code, and cerr may be partially or fully destroyed by then.
+ std::fwrite(message.data(), message.size(), 1, stderr);
+#endif
+
+#if defined(_WIN64) || defined(_WIN32) || defined(_WIN16)
+ // C99 requires stderr to not be fully-buffered by default (7.19.3.7), but
+ // MS CRT buffers it anyway, so we must `fflush` to ensure the string hits
+ // the console/file before the program dies (and takes the libc buffers
+ // with it).
+ // https://docs.microsoft.com/en-us/cpp/c-runtime-library/stream-i-o
+ if (severity >= absl::LogSeverity::kWarning) {
+ std::fflush(stderr);
+ }
+#else
+ // Avoid unused parameter warning in this branch.
+ (void)severity;
+#endif
+}
+
+void SetTimeZone(absl::TimeZone tz) {
+ absl::TimeZone* expected = nullptr;
+ absl::TimeZone* new_tz = new absl::TimeZone(tz);
+ // timezone_ptr can only be set once, otherwise new_tz is leaked.
+ if (!timezone_ptr.compare_exchange_strong(expected, new_tz,
+ std::memory_order_release,
+ std::memory_order_relaxed)) {
+ ABSL_RAW_LOG(FATAL,
+ "absl::log_internal::SetTimeZone() has already been called");
+ }
+}
+
+const absl::TimeZone* TimeZone() {
+ return timezone_ptr.load(std::memory_order_acquire);
+}
+
+bool ShouldSymbolizeLogStackTrace() {
+ return symbolize_stack_trace.load(std::memory_order_acquire);
+}
+
+void EnableSymbolizeLogStackTrace(bool on_off) {
+ symbolize_stack_trace.store(on_off, std::memory_order_release);
+}
+
+int MaxFramesInLogStackTrace() {
+ return max_frames_in_stack_trace.load(std::memory_order_acquire);
+}
+
+void SetMaxFramesInLogStackTrace(int max_num_frames) {
+ max_frames_in_stack_trace.store(max_num_frames, std::memory_order_release);
+}
+
+bool ExitOnDFatal() { return exit_on_dfatal.load(std::memory_order_acquire); }
+
+void SetExitOnDFatal(bool on_off) {
+ exit_on_dfatal.store(on_off, std::memory_order_release);
+}
+
+bool SuppressSigabortTrace() {
+ return suppress_sigabort_trace.load(std::memory_order_acquire);
+}
+
+bool SetSuppressSigabortTrace(bool on_off) {
+ return suppress_sigabort_trace.exchange(on_off);
+}
+
+} // namespace log_internal
+ABSL_NAMESPACE_END
+} // namespace absl
diff --git a/absl/log/internal/globals.h b/absl/log/internal/globals.h
new file mode 100644
index 0000000..27bc0d0
--- /dev/null
+++ b/absl/log/internal/globals.h
@@ -0,0 +1,101 @@
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// -----------------------------------------------------------------------------
+// File: log/internal/globals.h
+// -----------------------------------------------------------------------------
+//
+// This header file contains various global objects and static helper routines
+// use in logging implementation.
+
+#ifndef ABSL_LOG_INTERNAL_GLOBALS_H_
+#define ABSL_LOG_INTERNAL_GLOBALS_H_
+
+#include "absl/base/config.h"
+#include "absl/base/log_severity.h"
+#include "absl/strings/string_view.h"
+#include "absl/time/time.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace log_internal {
+
+// IsInitialized returns true if the logging library is initialized.
+// This function is async-signal-safe
+bool IsInitialized();
+
+// SetLoggingInitialized is called once after logging initialization is done.
+void SetInitialized();
+
+// Unconditionally write a `message` to stderr. If `severity` exceeds kInfo
+// we also flush the stderr stream.
+void WriteToStderr(absl::string_view message, absl::LogSeverity severity);
+
+// Set the TimeZone used for human-friendly times (for example, the log message
+// prefix) printed by the logging library. This may only be called once.
+void SetTimeZone(absl::TimeZone tz);
+
+// Returns the TimeZone used for human-friendly times (for example, the log
+// message prefix) printed by the logging library Returns nullptr prior to
+// initialization.
+const absl::TimeZone* TimeZone();
+
+// Returns true if stack traces emitted by the logging library should be
+// symbolized. This function is async-signal-safe.
+bool ShouldSymbolizeLogStackTrace();
+
+// Enables or disables symbolization of stack traces emitted by the
+// logging library. This function is async-signal-safe.
+void EnableSymbolizeLogStackTrace(bool on_off);
+
+// Returns the maximum number of frames that appear in stack traces
+// emitted by the logging library. This function is async-signal-safe.
+int MaxFramesInLogStackTrace();
+
+// Sets the maximum number of frames that appear in stack traces emitted by
+// the logging library. This function is async-signal-safe.
+void SetMaxFramesInLogStackTrace(int max_num_frames);
+
+// Determines whether we exit the program for a LOG(DFATAL) message in
+// debug mode. It does this by skipping the call to Fail/FailQuietly.
+// This is intended for testing only.
+//
+// This can have some effects on LOG(FATAL) as well. Failure messages
+// are always allocated (rather than sharing a buffer), the crash
+// reason is not recorded, the "gwq" status message is not updated,
+// and the stack trace is not recorded. The LOG(FATAL) *will* still
+// exit the program. Since this function is used only in testing,
+// these differences are acceptable.
+//
+// Additionally, LOG(LEVEL(FATAL)) is indistinguishable from LOG(DFATAL) and
+// will not terminate the program if SetExitOnDFatal(false) has been called.
+bool ExitOnDFatal();
+
+// SetExitOnDFatal() sets the ExitOnDFatal() status
+void SetExitOnDFatal(bool on_off);
+
+// Determines if the logging library should suppress logging of stacktraces in
+// the `SIGABRT` handler, typically because we just logged a stacktrace as part
+// of `LOG(FATAL)` and are about to send ourselves a `SIGABRT` to end the
+// program.
+bool SuppressSigabortTrace();
+
+// Sets the SuppressSigabortTrace() status and returns the previous state.
+bool SetSuppressSigabortTrace(bool on_off);
+
+} // namespace log_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_LOG_INTERNAL_GLOBALS_H_
diff --git a/absl/log/internal/log_format.cc b/absl/log/internal/log_format.cc
new file mode 100644
index 0000000..23cef88
--- /dev/null
+++ b/absl/log/internal/log_format.cc
@@ -0,0 +1,205 @@
+//
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/log/internal/log_format.h"
+
+#include <string.h>
+
+#ifdef _MSC_VER
+#include <winsock2.h> // For timeval
+#else
+#include <sys/time.h>
+#endif
+
+#include <cstddef>
+#include <cstdint>
+#include <limits>
+#include <string>
+#include <type_traits>
+
+#include "absl/base/config.h"
+#include "absl/base/log_severity.h"
+#include "absl/base/optimization.h"
+#include "absl/log/internal/append_truncated.h"
+#include "absl/log/internal/config.h"
+#include "absl/log/internal/globals.h"
+#include "absl/strings/numbers.h"
+#include "absl/strings/str_format.h"
+#include "absl/strings/string_view.h"
+#include "absl/time/civil_time.h"
+#include "absl/time/time.h"
+#include "absl/types/span.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace log_internal {
+namespace {
+
+// This templated function avoids compiler warnings about tautological
+// comparisons when log_internal::Tid is unsigned. It can be replaced with a
+// constexpr if once the minimum C++ version Abseil supports is C++17.
+template <typename T>
+inline std::enable_if_t<!std::is_signed<T>::value>
+PutLeadingWhitespace(T tid, char*& p) {
+ if (tid < 10) *p++ = ' ';
+ if (tid < 100) *p++ = ' ';
+ if (tid < 1000) *p++ = ' ';
+ if (tid < 10000) *p++ = ' ';
+ if (tid < 100000) *p++ = ' ';
+ if (tid < 1000000) *p++ = ' ';
+}
+
+template <typename T>
+inline std::enable_if_t<std::is_signed<T>::value>
+PutLeadingWhitespace(T tid, char*& p) {
+ if (tid >= 0 && tid < 10) *p++ = ' ';
+ if (tid > -10 && tid < 100) *p++ = ' ';
+ if (tid > -100 && tid < 1000) *p++ = ' ';
+ if (tid > -1000 && tid < 10000) *p++ = ' ';
+ if (tid > -10000 && tid < 100000) *p++ = ' ';
+ if (tid > -100000 && tid < 1000000) *p++ = ' ';
+}
+
+// The fields before the filename are all fixed-width except for the thread ID,
+// which is of bounded width.
+size_t FormatBoundedFields(absl::LogSeverity severity, absl::Time timestamp,
+ log_internal::Tid tid, absl::Span<char>& buf) {
+ constexpr size_t kBoundedFieldsMaxLen =
+ sizeof("SMMDD HH:MM:SS.NNNNNN ") +
+ (1 + std::numeric_limits<log_internal::Tid>::digits10 + 1) - sizeof("");
+ if (ABSL_PREDICT_FALSE(buf.size() < kBoundedFieldsMaxLen)) {
+ // We don't bother trying to truncate these fields if the buffer is too
+ // short (or almost too short) because it would require doing a lot more
+ // length checking (slow) and it should never happen. A 15kB buffer should
+ // be enough for anyone. Instead we mark `buf` full without writing
+ // anything.
+ buf.remove_suffix(buf.size());
+ return 0;
+ }
+
+ // We can't call absl::LocalTime(), localtime_r(), or anything else here that
+ // isn't async-signal-safe. We can only use the time zone if it has already
+ // been loaded.
+ const absl::TimeZone* tz = absl::log_internal::TimeZone();
+ if (ABSL_PREDICT_FALSE(tz == nullptr)) {
+ // If a time zone hasn't been set yet because we are logging before the
+ // logging library has been initialized, we fallback to a simpler, slower
+ // method. Just report the raw Unix time in seconds. We cram this into the
+ // normal time format for the benefit of parsers.
+ auto tv = absl::ToTimeval(timestamp);
+ int snprintf_result = absl::SNPrintF(
+ buf.data(), buf.size(), "%c0000 00:00:%02d.%06d %7d ",
+ absl::LogSeverityName(severity)[0], static_cast<int>(tv.tv_sec),
+ static_cast<int>(tv.tv_usec), static_cast<int>(tid));
+ if (snprintf_result >= 0) {
+ buf.remove_prefix(static_cast<size_t>(snprintf_result));
+ return static_cast<size_t>(snprintf_result);
+ }
+ return 0;
+ }
+
+ char* p = buf.data();
+ *p++ = absl::LogSeverityName(severity)[0];
+ const absl::TimeZone::CivilInfo ci = tz->At(timestamp);
+ absl::numbers_internal::PutTwoDigits(static_cast<uint32_t>(ci.cs.month()), p);
+ p += 2;
+ absl::numbers_internal::PutTwoDigits(static_cast<uint32_t>(ci.cs.day()), p);
+ p += 2;
+ *p++ = ' ';
+ absl::numbers_internal::PutTwoDigits(static_cast<uint32_t>(ci.cs.hour()), p);
+ p += 2;
+ *p++ = ':';
+ absl::numbers_internal::PutTwoDigits(static_cast<uint32_t>(ci.cs.minute()),
+ p);
+ p += 2;
+ *p++ = ':';
+ absl::numbers_internal::PutTwoDigits(static_cast<uint32_t>(ci.cs.second()),
+ p);
+ p += 2;
+ *p++ = '.';
+ const int64_t usecs = absl::ToInt64Microseconds(ci.subsecond);
+ absl::numbers_internal::PutTwoDigits(static_cast<uint32_t>(usecs / 10000), p);
+ p += 2;
+ absl::numbers_internal::PutTwoDigits(static_cast<uint32_t>(usecs / 100 % 100),
+ p);
+ p += 2;
+ absl::numbers_internal::PutTwoDigits(static_cast<uint32_t>(usecs % 100), p);
+ p += 2;
+ *p++ = ' ';
+ PutLeadingWhitespace(tid, p);
+ p = absl::numbers_internal::FastIntToBuffer(tid, p);
+ *p++ = ' ';
+ const size_t bytes_formatted = static_cast<size_t>(p - buf.data());
+ buf.remove_prefix(bytes_formatted);
+ return bytes_formatted;
+}
+
+size_t FormatLineNumber(int line, absl::Span<char>& buf) {
+ constexpr size_t kLineFieldMaxLen =
+ sizeof(":] ") + (1 + std::numeric_limits<int>::digits10 + 1) - sizeof("");
+ if (ABSL_PREDICT_FALSE(buf.size() < kLineFieldMaxLen)) {
+ // As above, we don't bother trying to truncate this if the buffer is too
+ // short and it should never happen.
+ buf.remove_suffix(buf.size());
+ return 0;
+ }
+ char* p = buf.data();
+ *p++ = ':';
+ p = absl::numbers_internal::FastIntToBuffer(line, p);
+ *p++ = ']';
+ *p++ = ' ';
+ const size_t bytes_formatted = static_cast<size_t>(p - buf.data());
+ buf.remove_prefix(bytes_formatted);
+ return bytes_formatted;
+}
+
+} // namespace
+
+std::string FormatLogMessage(absl::LogSeverity severity,
+ absl::CivilSecond civil_second,
+ absl::Duration subsecond, log_internal::Tid tid,
+ absl::string_view basename, int line,
+ PrefixFormat format, absl::string_view message) {
+ return absl::StrFormat(
+ "%c%02d%02d %02d:%02d:%02d.%06d %7d %s:%d] %s%s",
+ absl::LogSeverityName(severity)[0], civil_second.month(),
+ civil_second.day(), civil_second.hour(), civil_second.minute(),
+ civil_second.second(), absl::ToInt64Microseconds(subsecond), tid,
+ basename, line, format == PrefixFormat::kRaw ? "RAW: " : "", message);
+}
+
+// This method is fairly hot, and the library always passes a huge `buf`, so we
+// save some bounds-checking cycles by not trying to do precise truncation.
+// Truncating at a field boundary is probably a better UX anyway.
+//
+// The prefix is written in three parts, each of which does a single
+// bounds-check and truncation:
+// 1. severity, timestamp, and thread ID
+// 2. filename
+// 3. line number and bracket
+size_t FormatLogPrefix(absl::LogSeverity severity, absl::Time timestamp,
+ log_internal::Tid tid, absl::string_view basename,
+ int line, PrefixFormat format, absl::Span<char>& buf) {
+ auto prefix_size = FormatBoundedFields(severity, timestamp, tid, buf);
+ prefix_size += log_internal::AppendTruncated(basename, buf);
+ prefix_size += FormatLineNumber(line, buf);
+ if (format == PrefixFormat::kRaw)
+ prefix_size += log_internal::AppendTruncated("RAW: ", buf);
+ return prefix_size;
+}
+
+} // namespace log_internal
+ABSL_NAMESPACE_END
+} // namespace absl
diff --git a/absl/log/internal/log_format.h b/absl/log/internal/log_format.h
new file mode 100644
index 0000000..95a45ed
--- /dev/null
+++ b/absl/log/internal/log_format.h
@@ -0,0 +1,78 @@
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// -----------------------------------------------------------------------------
+// File: log/internal/log_format.h
+// -----------------------------------------------------------------------------
+//
+// This file declares routines implementing formatting of log message and log
+// prefix.
+
+#ifndef ABSL_LOG_INTERNAL_LOG_FORMAT_H_
+#define ABSL_LOG_INTERNAL_LOG_FORMAT_H_
+
+#include <stddef.h>
+
+#include <string>
+
+#include "absl/base/config.h"
+#include "absl/base/log_severity.h"
+#include "absl/log/internal/config.h"
+#include "absl/strings/string_view.h"
+#include "absl/time/civil_time.h"
+#include "absl/time/time.h"
+#include "absl/types/span.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace log_internal {
+
+enum class PrefixFormat {
+ kNotRaw,
+ kRaw,
+};
+
+// Formats log message based on provided data.
+std::string FormatLogMessage(absl::LogSeverity severity,
+ absl::CivilSecond civil_second,
+ absl::Duration subsecond, log_internal::Tid tid,
+ absl::string_view basename, int line,
+ PrefixFormat format, absl::string_view message);
+
+// Formats various entry metadata into a text string meant for use as a
+// prefix on a log message string. Writes into `buf`, advances `buf` to point
+// at the remainder of the buffer (i.e. past any written bytes), and returns the
+// number of bytes written.
+//
+// In addition to calling `buf->remove_prefix()` (or the equivalent), this
+// function may also do `buf->remove_suffix(buf->size())` in cases where no more
+// bytes (i.e. no message data) should be written into the buffer. For example,
+// if the prefix ought to be:
+// I0926 09:00:00.000000 1234567 foo.cc:123]
+// `buf` is too small, the function might fill the whole buffer:
+// I0926 09:00:00.000000 1234
+// (note the apparrently incorrect thread ID), or it might write less:
+// I0926 09:00:00.000000
+// In this case, it might also empty `buf` prior to returning to prevent
+// message data from being written into the space where a reader would expect to
+// see a thread ID.
+size_t FormatLogPrefix(absl::LogSeverity severity, absl::Time timestamp,
+ log_internal::Tid tid, absl::string_view basename,
+ int line, PrefixFormat format, absl::Span<char>& buf);
+
+} // namespace log_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_LOG_INTERNAL_LOG_FORMAT_H_
diff --git a/absl/log/internal/log_impl.h b/absl/log/internal/log_impl.h
new file mode 100644
index 0000000..99de6db
--- /dev/null
+++ b/absl/log/internal/log_impl.h
@@ -0,0 +1,282 @@
+// Copyright 2022 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef ABSL_LOG_INTERNAL_LOG_IMPL_H_
+#define ABSL_LOG_INTERNAL_LOG_IMPL_H_
+
+#include "absl/log/absl_vlog_is_on.h"
+#include "absl/log/internal/conditions.h"
+#include "absl/log/internal/log_message.h"
+#include "absl/log/internal/strip.h"
+
+// ABSL_LOG()
+#define ABSL_LOG_INTERNAL_LOG_IMPL(severity) \
+ ABSL_LOG_INTERNAL_CONDITION##severity(STATELESS, true) \
+ ABSL_LOGGING_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() \
+ .WithPerror()
+
+// ABSL_DLOG()
+#ifndef NDEBUG
+#define ABSL_LOG_INTERNAL_DLOG_IMPL(severity) \
+ ABSL_LOG_INTERNAL_CONDITION##severity(STATELESS, true) \
+ ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream()
+#else
+#define ABSL_LOG_INTERNAL_DLOG_IMPL(severity) \
+ ABSL_LOG_INTERNAL_CONDITION##severity(STATELESS, false) \
+ ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream()
+#endif
+
+// The `switch` ensures that this expansion is the begnning of a statement (as
+// opposed to an expression). The use of both `case 0` and `default` is to
+// suppress a compiler warning.
+#define ABSL_LOG_INTERNAL_VLOG_IMPL(verbose_level) \
+ switch (const int absl_logging_internal_verbose_level = (verbose_level)) \
+ case 0: \
+ default: \
+ ABSL_LOG_INTERNAL_LOG_IF_IMPL( \
+ _INFO, ABSL_VLOG_IS_ON(absl_logging_internal_verbose_level)) \
+ .WithVerbosity(absl_logging_internal_verbose_level)
+
+#ifndef NDEBUG
+#define ABSL_LOG_INTERNAL_DVLOG_IMPL(verbose_level) \
+ switch (const int absl_logging_internal_verbose_level = (verbose_level)) \
+ case 0: \
+ default: \
+ ABSL_LOG_INTERNAL_LOG_IF_IMPL( \
+ _INFO, ABSL_VLOG_IS_ON(absl_logging_internal_verbose_level)) \
+ .WithVerbosity(absl_logging_internal_verbose_level)
+#else
+#define ABSL_LOG_INTERNAL_DVLOG_IMPL(verbose_level) \
+ switch (const int absl_logging_internal_verbose_level = (verbose_level)) \
+ case 0: \
+ default: \
+ ABSL_LOG_INTERNAL_LOG_IF_IMPL( \
+ _INFO, false && ABSL_VLOG_IS_ON(absl_logging_internal_verbose_level)) \
+ .WithVerbosity(absl_logging_internal_verbose_level)
+#endif
+
+#define ABSL_LOG_INTERNAL_LOG_IF_IMPL(severity, condition) \
+ ABSL_LOG_INTERNAL_CONDITION##severity(STATELESS, condition) \
+ ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream()
+#define ABSL_LOG_INTERNAL_PLOG_IF_IMPL(severity, condition) \
+ ABSL_LOG_INTERNAL_CONDITION##severity(STATELESS, condition) \
+ ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() \
+ .WithPerror()
+
+#ifndef NDEBUG
+#define ABSL_LOG_INTERNAL_DLOG_IF_IMPL(severity, condition) \
+ ABSL_LOG_INTERNAL_CONDITION##severity(STATELESS, condition) \
+ ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream()
+#else
+#define ABSL_LOG_INTERNAL_DLOG_IF_IMPL(severity, condition) \
+ ABSL_LOG_INTERNAL_CONDITION##severity(STATELESS, false && (condition)) \
+ ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream()
+#endif
+
+// ABSL_LOG_EVERY_N
+#define ABSL_LOG_INTERNAL_LOG_EVERY_N_IMPL(severity, n) \
+ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, true)(EveryN, n) \
+ ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream()
+
+// ABSL_LOG_FIRST_N
+#define ABSL_LOG_INTERNAL_LOG_FIRST_N_IMPL(severity, n) \
+ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, true)(FirstN, n) \
+ ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream()
+
+// ABSL_LOG_EVERY_POW_2
+#define ABSL_LOG_INTERNAL_LOG_EVERY_POW_2_IMPL(severity) \
+ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, true)(EveryPow2) \
+ ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream()
+
+// ABSL_LOG_EVERY_N_SEC
+#define ABSL_LOG_INTERNAL_LOG_EVERY_N_SEC_IMPL(severity, n_seconds) \
+ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, true)(EveryNSec, n_seconds) \
+ ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream()
+
+#define ABSL_LOG_INTERNAL_PLOG_EVERY_N_IMPL(severity, n) \
+ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, true)(EveryN, n) \
+ ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() \
+ .WithPerror()
+
+#define ABSL_LOG_INTERNAL_PLOG_FIRST_N_IMPL(severity, n) \
+ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, true)(FirstN, n) \
+ ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() \
+ .WithPerror()
+
+#define ABSL_LOG_INTERNAL_PLOG_EVERY_POW_2_IMPL(severity) \
+ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, true)(EveryPow2) \
+ ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() \
+ .WithPerror()
+
+#define ABSL_LOG_INTERNAL_PLOG_EVERY_N_SEC_IMPL(severity, n_seconds) \
+ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, true)(EveryNSec, n_seconds) \
+ ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() \
+ .WithPerror()
+
+#ifndef NDEBUG
+#define ABSL_LOG_INTERNAL_DLOG_EVERY_N_IMPL(severity, n) \
+ ABSL_LOG_INTERNAL_CONDITION_INFO(STATEFUL, true) \
+ (EveryN, n) ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream()
+
+#define ABSL_LOG_INTERNAL_DLOG_FIRST_N_IMPL(severity, n) \
+ ABSL_LOG_INTERNAL_CONDITION_INFO(STATEFUL, true) \
+ (FirstN, n) ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream()
+
+#define ABSL_LOG_INTERNAL_DLOG_EVERY_POW_2_IMPL(severity) \
+ ABSL_LOG_INTERNAL_CONDITION_INFO(STATEFUL, true) \
+ (EveryPow2) ABSL_LOGGING_INTERNAL_LOG##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_LOG##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_LOG##severity.InternalStream()
+
+#define ABSL_LOG_INTERNAL_DLOG_FIRST_N_IMPL(severity, n) \
+ ABSL_LOG_INTERNAL_CONDITION_INFO(STATEFUL, false) \
+ (FirstN, n) ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream()
+
+#define ABSL_LOG_INTERNAL_DLOG_EVERY_POW_2_IMPL(severity) \
+ ABSL_LOG_INTERNAL_CONDITION_INFO(STATEFUL, false) \
+ (EveryPow2) ABSL_LOGGING_INTERNAL_LOG##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_LOG##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_FIRST_N_IMPL(verbose_level, n) \
+ switch (const int absl_logging_internal_verbose_level = (verbose_level)) \
+ case 0: \
+ default: \
+ ABSL_LOG_INTERNAL_CONDITION_INFO( \
+ STATEFUL, ABSL_VLOG_IS_ON(absl_logging_internal_verbose_level)) \
+ (FirstN, n) ABSL_LOGGING_INTERNAL_LOG_INFO.InternalStream().WithVerbosity( \
+ absl_logging_internal_verbose_level)
+
+#define ABSL_LOG_INTERNAL_VLOG_EVERY_POW_2_IMPL(verbose_level) \
+ switch (const int absl_logging_internal_verbose_level = (verbose_level)) \
+ case 0: \
+ default: \
+ ABSL_LOG_INTERNAL_CONDITION_INFO( \
+ STATEFUL, ABSL_VLOG_IS_ON(absl_logging_internal_verbose_level)) \
+ (EveryPow2) ABSL_LOGGING_INTERNAL_LOG_INFO.InternalStream().WithVerbosity( \
+ absl_logging_internal_verbose_level)
+
+#define ABSL_LOG_INTERNAL_VLOG_EVERY_N_SEC_IMPL(verbose_level, n_seconds) \
+ switch (const int absl_logging_internal_verbose_level = (verbose_level)) \
+ case 0: \
+ default: \
+ ABSL_LOG_INTERNAL_CONDITION_INFO( \
+ STATEFUL, ABSL_VLOG_IS_ON(absl_logging_internal_verbose_level)) \
+ (EveryNSec, n_seconds) ABSL_LOGGING_INTERNAL_LOG_INFO.InternalStream() \
+ .WithVerbosity(absl_logging_internal_verbose_level)
+
+#define ABSL_LOG_INTERNAL_LOG_IF_EVERY_N_IMPL(severity, condition, n) \
+ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, condition)(EveryN, n) \
+ ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream()
+
+#define ABSL_LOG_INTERNAL_LOG_IF_FIRST_N_IMPL(severity, condition, n) \
+ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, condition)(FirstN, n) \
+ ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream()
+
+#define ABSL_LOG_INTERNAL_LOG_IF_EVERY_POW_2_IMPL(severity, condition) \
+ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, condition)(EveryPow2) \
+ ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream()
+
+#define ABSL_LOG_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_PLOG_IF_EVERY_N_IMPL(severity, condition, n) \
+ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, condition)(EveryN, n) \
+ ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() \
+ .WithPerror()
+
+#define ABSL_LOG_INTERNAL_PLOG_IF_FIRST_N_IMPL(severity, condition, n) \
+ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, condition)(FirstN, n) \
+ ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() \
+ .WithPerror()
+
+#define ABSL_LOG_INTERNAL_PLOG_IF_EVERY_POW_2_IMPL(severity, condition) \
+ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, condition)(EveryPow2) \
+ ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() \
+ .WithPerror()
+
+#define ABSL_LOG_INTERNAL_PLOG_IF_EVERY_N_SEC_IMPL(severity, condition, \
+ n_seconds) \
+ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, condition)(EveryNSec, \
+ n_seconds) \
+ ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() \
+ .WithPerror()
+
+#ifndef NDEBUG
+#define ABSL_LOG_INTERNAL_DLOG_IF_EVERY_N_IMPL(severity, condition, n) \
+ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, condition)(EveryN, n) \
+ ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream()
+
+#define ABSL_LOG_INTERNAL_DLOG_IF_FIRST_N_IMPL(severity, condition, n) \
+ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, condition)(FirstN, n) \
+ ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream()
+
+#define ABSL_LOG_INTERNAL_DLOG_IF_EVERY_POW_2_IMPL(severity, condition) \
+ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, condition)(EveryPow2) \
+ ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream()
+
+#define ABSL_LOG_INTERNAL_DLOG_IF_EVERY_N_SEC_IMPL(severity, condition, \
+ n_seconds) \
+ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, condition)(EveryNSec, \
+ n_seconds) \
+ ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream()
+
+#else // def NDEBUG
+#define ABSL_LOG_INTERNAL_DLOG_IF_EVERY_N_IMPL(severity, condition, n) \
+ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, false && (condition))( \
+ EveryN, n) ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream()
+
+#define ABSL_LOG_INTERNAL_DLOG_IF_FIRST_N_IMPL(severity, condition, n) \
+ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, false && (condition))( \
+ FirstN, n) ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream()
+
+#define ABSL_LOG_INTERNAL_DLOG_IF_EVERY_POW_2_IMPL(severity, condition) \
+ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, false && (condition))( \
+ EveryPow2) ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream()
+
+#define ABSL_LOG_INTERNAL_DLOG_IF_EVERY_N_SEC_IMPL(severity, condition, \
+ n_seconds) \
+ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, false && (condition))( \
+ EveryNSec, n_seconds) \
+ ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream()
+#endif // def NDEBUG
+
+#endif // ABSL_LOG_INTERNAL_LOG_IMPL_H_
diff --git a/absl/log/internal/log_message.cc b/absl/log/internal/log_message.cc
new file mode 100644
index 0000000..10ac245
--- /dev/null
+++ b/absl/log/internal/log_message.cc
@@ -0,0 +1,633 @@
+//
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/log/internal/log_message.h"
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifndef _WIN32
+#include <unistd.h>
+#endif
+
+#include <algorithm>
+#include <array>
+#include <atomic>
+#include <memory>
+#include <ostream>
+#include <string>
+#include <tuple>
+
+#include "absl/base/attributes.h"
+#include "absl/base/config.h"
+#include "absl/base/internal/raw_logging.h"
+#include "absl/base/internal/strerror.h"
+#include "absl/base/internal/sysinfo.h"
+#include "absl/base/log_severity.h"
+#include "absl/container/inlined_vector.h"
+#include "absl/debugging/internal/examine_stack.h"
+#include "absl/log/globals.h"
+#include "absl/log/internal/append_truncated.h"
+#include "absl/log/internal/globals.h"
+#include "absl/log/internal/log_format.h"
+#include "absl/log/internal/log_sink_set.h"
+#include "absl/log/internal/proto.h"
+#include "absl/log/log_entry.h"
+#include "absl/log/log_sink.h"
+#include "absl/log/log_sink_registry.h"
+#include "absl/memory/memory.h"
+#include "absl/strings/string_view.h"
+#include "absl/time/clock.h"
+#include "absl/time/time.h"
+#include "absl/types/span.h"
+
+extern "C" ABSL_ATTRIBUTE_WEAK void ABSL_INTERNAL_C_SYMBOL(
+ AbslInternalOnFatalLogMessage)(const absl::LogEntry&) {
+ // Default - Do nothing
+}
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace log_internal {
+
+namespace {
+// message `logging.proto.Event`
+enum EventTag : uint8_t {
+ kValue = 7,
+};
+
+// message `logging.proto.Value`
+enum ValueTag : uint8_t {
+ kString = 1,
+ kStringLiteral = 6,
+};
+
+// Decodes a `logging.proto.Value` from `buf` and writes a string representation
+// into `dst`. The string representation will be truncated if `dst` is not
+// large enough to hold it. Returns false if `dst` has size zero or one (i.e.
+// sufficient only for a nul-terminator) and no decoded data could be written.
+// This function may or may not write a nul-terminator into `dst`, and it may or
+// may not truncate the data it writes in order to do make space for that nul
+// terminator. In any case, `dst` will be advanced to point at the byte where
+// subsequent writes should begin.
+bool PrintValue(absl::Span<char>& dst, absl::Span<const char> buf) {
+ if (dst.size() <= 1) return false;
+ ProtoField field;
+ while (field.DecodeFrom(&buf)) {
+ switch (field.tag()) {
+ case ValueTag::kString:
+ case ValueTag::kStringLiteral:
+ if (field.type() == WireType::kLengthDelimited)
+ if (log_internal::AppendTruncated(field.string_value(), dst) <
+ field.string_value().size())
+ return false;
+ }
+ }
+ return true;
+}
+
+absl::string_view Basename(absl::string_view filepath) {
+#ifdef _WIN32
+ size_t path = filepath.find_last_of("/\\");
+#else
+ size_t path = filepath.find_last_of('/');
+#endif
+ if (path != filepath.npos) filepath.remove_prefix(path + 1);
+ return filepath;
+}
+
+void WriteToString(const char* data, void* str) {
+ reinterpret_cast<std::string*>(str)->append(data);
+}
+void WriteToStream(const char* data, void* os) {
+ auto* cast_os = static_cast<std::ostream*>(os);
+ *cast_os << data;
+}
+} // namespace
+
+struct LogMessage::LogMessageData final {
+ LogMessageData(const char* file, int line, absl::LogSeverity severity,
+ absl::Time timestamp);
+ LogMessageData(const LogMessageData&) = delete;
+ LogMessageData& operator=(const LogMessageData&) = delete;
+
+ // `LogEntry` sent to `LogSink`s; contains metadata.
+ absl::LogEntry entry;
+
+ // true => this was first fatal msg
+ bool first_fatal;
+ // true => all failures should be quiet
+ bool fail_quietly;
+ // true => PLOG was requested
+ bool is_perror;
+
+ // Extra `LogSink`s to log to, in addition to `global_sinks`.
+ absl::InlinedVector<absl::LogSink*, 16> extra_sinks;
+ // If true, log to `extra_sinks` but not to `global_sinks` or hardcoded
+ // non-sink targets (e.g. stderr, log files).
+ bool extra_sinks_only;
+
+ std::ostream manipulated; // ostream with IO manipulators applied
+
+ // A `logging.proto.Event` proto message is built into `encoded_buf`.
+ std::array<char, kLogMessageBufferSize> encoded_buf;
+ // `encoded_remaining` is the suffix of `encoded_buf` that has not been filled
+ // yet. If a datum to be encoded does not fit into `encoded_remaining` and
+ // cannot be truncated to fit, the size of `encoded_remaining` will be zeroed
+ // to prevent encoding of any further data. Note that in this case its data()
+ // pointer will not point past the end of `encoded_buf`.
+ absl::Span<char> encoded_remaining;
+
+ // A formatted string message is built in `string_buf`.
+ std::array<char, kLogMessageBufferSize> string_buf;
+
+ void FinalizeEncodingAndFormat();
+};
+
+LogMessage::LogMessageData::LogMessageData(const char* file, int line,
+ absl::LogSeverity severity,
+ absl::Time timestamp)
+ : extra_sinks_only(false),
+ manipulated(nullptr),
+ // This `absl::MakeSpan` silences spurious -Wuninitialized from GCC:
+ encoded_remaining(absl::MakeSpan(encoded_buf)) {
+ // Legacy defaults for LOG's ostream:
+ manipulated.setf(std::ios_base::showbase | std::ios_base::boolalpha);
+ entry.full_filename_ = file;
+ entry.base_filename_ = Basename(file);
+ entry.line_ = line;
+ entry.prefix_ = absl::ShouldPrependLogPrefix();
+ entry.severity_ = absl::NormalizeLogSeverity(severity);
+ entry.verbose_level_ = absl::LogEntry::kNoVerbosityLevel;
+ entry.timestamp_ = timestamp;
+ entry.tid_ = absl::base_internal::GetCachedTID();
+}
+
+void LogMessage::LogMessageData::FinalizeEncodingAndFormat() {
+ // Note that `encoded_remaining` may have zero size without pointing past the
+ // end of `encoded_buf`, so the difference between `data()` pointers is used
+ // to compute the size of `encoded_data`.
+ absl::Span<const char> encoded_data(
+ encoded_buf.data(),
+ static_cast<size_t>(encoded_remaining.data() - encoded_buf.data()));
+ // `string_remaining` is the suffix of `string_buf` that has not been filled
+ // yet.
+ absl::Span<char> string_remaining(string_buf);
+ // We may need to write a newline and nul-terminator at the end of the decoded
+ // string data. Rather than worry about whether those should overwrite the
+ // end of the string (if the buffer is full) or be appended, we avoid writing
+ // into the last two bytes so we always have space to append.
+ string_remaining.remove_suffix(2);
+ entry.prefix_len_ =
+ entry.prefix() ? log_internal::FormatLogPrefix(
+ entry.log_severity(), entry.timestamp(), entry.tid(),
+ entry.source_basename(), entry.source_line(),
+ log_internal::ThreadIsLoggingToLogSink()
+ ? PrefixFormat::kRaw
+ : PrefixFormat::kNotRaw,
+ string_remaining)
+ : 0;
+ // Decode data from `encoded_buf` until we run out of data or we run out of
+ // `string_remaining`.
+ ProtoField field;
+ while (field.DecodeFrom(&encoded_data)) {
+ switch (field.tag()) {
+ case EventTag::kValue:
+ if (field.type() != WireType::kLengthDelimited) continue;
+ if (PrintValue(string_remaining, field.bytes_value())) continue;
+ break;
+ }
+ break;
+ }
+ auto chars_written =
+ static_cast<size_t>(string_remaining.data() - string_buf.data());
+ string_buf[chars_written++] = '\n';
+ string_buf[chars_written++] = '\0';
+ entry.text_message_with_prefix_and_newline_and_nul_ =
+ absl::MakeSpan(string_buf).subspan(0, chars_written);
+}
+
+LogMessage::LogMessage(const char* file, int line, absl::LogSeverity severity)
+ : data_(absl::make_unique<LogMessageData>(file, line, severity,
+ absl::Now())) {
+ data_->first_fatal = false;
+ data_->is_perror = false;
+ data_->fail_quietly = false;
+
+ // This logs a backtrace even if the location is subsequently changed using
+ // AtLocation. This quirk, and the behavior when AtLocation is called twice,
+ // are fixable but probably not worth fixing.
+ LogBacktraceIfNeeded();
+}
+
+LogMessage::LogMessage(const char* file, int line, InfoTag)
+ : LogMessage(file, line, absl::LogSeverity::kInfo) {}
+LogMessage::LogMessage(const char* file, int line, WarningTag)
+ : LogMessage(file, line, absl::LogSeverity::kWarning) {}
+LogMessage::LogMessage(const char* file, int line, ErrorTag)
+ : LogMessage(file, line, absl::LogSeverity::kError) {}
+
+LogMessage::~LogMessage() {
+#ifdef ABSL_MIN_LOG_LEVEL
+ if (data_->entry.log_severity() <
+ static_cast<absl::LogSeverity>(ABSL_MIN_LOG_LEVEL) &&
+ data_->entry.log_severity() < absl::LogSeverity::kFatal) {
+ return;
+ }
+#endif
+ Flush();
+}
+
+LogMessage& LogMessage::AtLocation(absl::string_view file, int line) {
+ data_->entry.full_filename_ = file;
+ data_->entry.base_filename_ = Basename(file);
+ data_->entry.line_ = line;
+ LogBacktraceIfNeeded();
+ return *this;
+}
+
+LogMessage& LogMessage::NoPrefix() {
+ data_->entry.prefix_ = false;
+ return *this;
+}
+
+LogMessage& LogMessage::WithVerbosity(int verbose_level) {
+ if (verbose_level == absl::LogEntry::kNoVerbosityLevel) {
+ data_->entry.verbose_level_ = absl::LogEntry::kNoVerbosityLevel;
+ } else {
+ data_->entry.verbose_level_ = std::max(0, verbose_level);
+ }
+ return *this;
+}
+
+LogMessage& LogMessage::WithTimestamp(absl::Time timestamp) {
+ data_->entry.timestamp_ = timestamp;
+ return *this;
+}
+
+LogMessage& LogMessage::WithThreadID(absl::LogEntry::tid_t tid) {
+ data_->entry.tid_ = tid;
+ return *this;
+}
+
+LogMessage& LogMessage::WithMetadataFrom(const absl::LogEntry& entry) {
+ data_->entry.full_filename_ = entry.full_filename_;
+ data_->entry.base_filename_ = entry.base_filename_;
+ data_->entry.line_ = entry.line_;
+ data_->entry.prefix_ = entry.prefix_;
+ data_->entry.severity_ = entry.severity_;
+ data_->entry.verbose_level_ = entry.verbose_level_;
+ data_->entry.timestamp_ = entry.timestamp_;
+ data_->entry.tid_ = entry.tid_;
+ return *this;
+}
+
+LogMessage& LogMessage::WithPerror() {
+ data_->is_perror = true;
+ return *this;
+}
+
+LogMessage& LogMessage::ToSinkAlso(absl::LogSink* sink) {
+ ABSL_INTERNAL_CHECK(sink, "null LogSink*");
+ data_->extra_sinks.push_back(sink);
+ return *this;
+}
+
+LogMessage& LogMessage::ToSinkOnly(absl::LogSink* sink) {
+ ABSL_INTERNAL_CHECK(sink, "null LogSink*");
+ data_->extra_sinks.clear();
+ data_->extra_sinks.push_back(sink);
+ data_->extra_sinks_only = true;
+ return *this;
+}
+
+#ifdef __ELF__
+extern "C" void __gcov_dump() ABSL_ATTRIBUTE_WEAK;
+extern "C" void __gcov_flush() ABSL_ATTRIBUTE_WEAK;
+#endif
+
+void LogMessage::FailWithoutStackTrace() {
+ // Now suppress repeated trace logging:
+ log_internal::SetSuppressSigabortTrace(true);
+#if defined _DEBUG && defined COMPILER_MSVC
+ // When debugging on windows, avoid the obnoxious dialog.
+ __debugbreak();
+#endif
+
+#ifdef __ELF__
+ // For b/8737634, flush coverage if we are in coverage mode.
+ if (&__gcov_dump != nullptr) {
+ __gcov_dump();
+ } else if (&__gcov_flush != nullptr) {
+ __gcov_flush();
+ }
+#endif
+
+ abort();
+}
+
+void LogMessage::FailQuietly() {
+ // _exit. Calling abort() would trigger all sorts of death signal handlers
+ // and a detailed stack trace. Calling exit() would trigger the onexit
+ // handlers, including the heap-leak checker, which is guaranteed to fail in
+ // this case: we probably just new'ed the std::string that we logged.
+ // Anyway, if you're calling Fail or FailQuietly, you're trying to bail out
+ // of the program quickly, and it doesn't make much sense for FailQuietly to
+ // offer different guarantees about exit behavior than Fail does. (And as a
+ // consequence for QCHECK and CHECK to offer different exit behaviors)
+ _exit(1);
+}
+
+LogMessage& LogMessage::operator<<(const std::string& v) {
+ CopyToEncodedBuffer<StringType::kNotLiteral>(v);
+ return *this;
+}
+
+LogMessage& LogMessage::operator<<(absl::string_view v) {
+ CopyToEncodedBuffer<StringType::kNotLiteral>(v);
+ return *this;
+}
+LogMessage& LogMessage::operator<<(std::ostream& (*m)(std::ostream& os)) {
+ OstreamView view(*data_);
+ data_->manipulated << m;
+ return *this;
+}
+LogMessage& LogMessage::operator<<(std::ios_base& (*m)(std::ios_base& os)) {
+ OstreamView view(*data_);
+ data_->manipulated << m;
+ return *this;
+}
+template LogMessage& LogMessage::operator<<(const char& v);
+template LogMessage& LogMessage::operator<<(const signed char& v);
+template LogMessage& LogMessage::operator<<(const unsigned char& v);
+template LogMessage& LogMessage::operator<<(const short& v); // NOLINT
+template LogMessage& LogMessage::operator<<(const unsigned short& v); // NOLINT
+template LogMessage& LogMessage::operator<<(const int& v);
+template LogMessage& LogMessage::operator<<(const unsigned int& v);
+template LogMessage& LogMessage::operator<<(const long& v); // NOLINT
+template LogMessage& LogMessage::operator<<(const unsigned long& v); // NOLINT
+template LogMessage& LogMessage::operator<<(const long long& v); // NOLINT
+template LogMessage& LogMessage::operator<<(
+ const unsigned long long& v); // NOLINT
+template LogMessage& LogMessage::operator<<(void* const& v);
+template LogMessage& LogMessage::operator<<(const void* const& v);
+template LogMessage& LogMessage::operator<<(const float& v);
+template LogMessage& LogMessage::operator<<(const double& v);
+template LogMessage& LogMessage::operator<<(const bool& v);
+
+void LogMessage::Flush() {
+ if (data_->entry.log_severity() < absl::MinLogLevel()) return;
+
+ if (data_->is_perror) {
+ InternalStream() << ": " << absl::base_internal::StrError(errno_saver_())
+ << " [" << errno_saver_() << "]";
+ }
+
+ // Have we already seen a fatal message?
+ ABSL_CONST_INIT static std::atomic<bool> seen_fatal(false);
+ if (data_->entry.log_severity() == absl::LogSeverity::kFatal &&
+ absl::log_internal::ExitOnDFatal()) {
+ // Exactly one LOG(FATAL) message is responsible for aborting the process,
+ // even if multiple threads LOG(FATAL) concurrently.
+ bool expected_seen_fatal = false;
+ if (seen_fatal.compare_exchange_strong(expected_seen_fatal, true,
+ std::memory_order_relaxed)) {
+ data_->first_fatal = true;
+ }
+ }
+
+ data_->FinalizeEncodingAndFormat();
+ data_->entry.encoding_ =
+ absl::string_view(data_->encoded_buf.data(),
+ static_cast<size_t>(data_->encoded_remaining.data() -
+ data_->encoded_buf.data()));
+ SendToLog();
+}
+
+void LogMessage::SetFailQuietly() { data_->fail_quietly = true; }
+
+LogMessage::OstreamView::OstreamView(LogMessageData& message_data)
+ : data_(message_data), encoded_remaining_copy_(data_.encoded_remaining) {
+ // This constructor sets the `streambuf` up so that streaming into an attached
+ // ostream encodes string data in-place. To do that, we write appropriate
+ // headers into the buffer using a copy of the buffer view so that we can
+ // decide not to keep them later if nothing is ever streamed in. We don't
+ // know how much data we'll get, but we can use the size of the remaining
+ // buffer as an upper bound and fill in the right size once we know it.
+ message_start_ =
+ EncodeMessageStart(EventTag::kValue, encoded_remaining_copy_.size(),
+ &encoded_remaining_copy_);
+ string_start_ =
+ EncodeMessageStart(ValueTag::kString, encoded_remaining_copy_.size(),
+ &encoded_remaining_copy_);
+ setp(encoded_remaining_copy_.data(),
+ encoded_remaining_copy_.data() + encoded_remaining_copy_.size());
+ data_.manipulated.rdbuf(this);
+}
+
+LogMessage::OstreamView::~OstreamView() {
+ data_.manipulated.rdbuf(nullptr);
+ if (!string_start_.data()) {
+ // The second field header didn't fit. Whether the first one did or not, we
+ // shouldn't commit `encoded_remaining_copy_`, and we also need to zero the
+ // size of `data_->encoded_remaining` so that no more data are encoded.
+ data_.encoded_remaining.remove_suffix(data_.encoded_remaining.size());
+ return;
+ }
+ const absl::Span<const char> contents(pbase(),
+ static_cast<size_t>(pptr() - pbase()));
+ if (contents.empty()) return;
+ encoded_remaining_copy_.remove_prefix(contents.size());
+ EncodeMessageLength(string_start_, &encoded_remaining_copy_);
+ EncodeMessageLength(message_start_, &encoded_remaining_copy_);
+ data_.encoded_remaining = encoded_remaining_copy_;
+}
+
+std::ostream& LogMessage::OstreamView::stream() { return data_.manipulated; }
+
+bool LogMessage::IsFatal() const {
+ return data_->entry.log_severity() == absl::LogSeverity::kFatal &&
+ absl::log_internal::ExitOnDFatal();
+}
+
+void LogMessage::PrepareToDie() {
+ // If we log a FATAL message, flush all the log destinations, then toss
+ // a signal for others to catch. We leave the logs in a state that
+ // someone else can use them (as long as they flush afterwards)
+ if (data_->first_fatal) {
+ // Notify observers about the upcoming fatal error.
+ ABSL_INTERNAL_C_SYMBOL(AbslInternalOnFatalLogMessage)(data_->entry);
+ }
+
+ if (!data_->fail_quietly) {
+ // Log the message first before we start collecting stack trace.
+ log_internal::LogToSinks(data_->entry, absl::MakeSpan(data_->extra_sinks),
+ data_->extra_sinks_only);
+
+ // `DumpStackTrace` generates an empty string under MSVC.
+ // Adding the constant prefix here simplifies testing.
+ data_->entry.stacktrace_ = "*** Check failure stack trace: ***\n";
+ debugging_internal::DumpStackTrace(
+ 0, log_internal::MaxFramesInLogStackTrace(),
+ log_internal::ShouldSymbolizeLogStackTrace(), WriteToString,
+ &data_->entry.stacktrace_);
+ }
+}
+
+void LogMessage::Die() {
+ absl::FlushLogSinks();
+
+ if (data_->fail_quietly) {
+ FailQuietly();
+ } else {
+ FailWithoutStackTrace();
+ }
+}
+
+void LogMessage::SendToLog() {
+ if (IsFatal()) PrepareToDie();
+ // Also log to all registered sinks, even if OnlyLogToStderr() is set.
+ log_internal::LogToSinks(data_->entry, absl::MakeSpan(data_->extra_sinks),
+ data_->extra_sinks_only);
+ if (IsFatal()) Die();
+}
+
+void LogMessage::LogBacktraceIfNeeded() {
+ if (!absl::log_internal::IsInitialized()) return;
+
+ if (!absl::log_internal::ShouldLogBacktraceAt(data_->entry.source_basename(),
+ data_->entry.source_line()))
+ return;
+ OstreamView view(*data_);
+ view.stream() << " (stacktrace:\n";
+ debugging_internal::DumpStackTrace(
+ 1, log_internal::MaxFramesInLogStackTrace(),
+ log_internal::ShouldSymbolizeLogStackTrace(), WriteToStream,
+ &view.stream());
+ view.stream() << ") ";
+}
+
+// Encodes into `data_->encoded_remaining` a partial `logging.proto.Event`
+// containing the specified string data using a `Value` field appropriate to
+// `str_type`. Truncates `str` if necessary, but emits nothing and marks the
+// buffer full if even the field headers do not fit.
+template <LogMessage::StringType str_type>
+void LogMessage::CopyToEncodedBuffer(absl::string_view str) {
+ auto encoded_remaining_copy = data_->encoded_remaining;
+ auto start = EncodeMessageStart(
+ EventTag::kValue, BufferSizeFor(WireType::kLengthDelimited) + str.size(),
+ &encoded_remaining_copy);
+ // If the `logging.proto.Event.value` field header did not fit,
+ // `EncodeMessageStart` will have zeroed `encoded_remaining_copy`'s size and
+ // `EncodeStringTruncate` will fail too.
+ if (EncodeStringTruncate(str_type == StringType::kLiteral
+ ? ValueTag::kStringLiteral
+ : ValueTag::kString,
+ str, &encoded_remaining_copy)) {
+ // The string may have been truncated, but the field header fit.
+ EncodeMessageLength(start, &encoded_remaining_copy);
+ data_->encoded_remaining = encoded_remaining_copy;
+ } else {
+ // The field header(s) did not fit; zero `encoded_remaining` so we don't
+ // write anything else later.
+ data_->encoded_remaining.remove_suffix(data_->encoded_remaining.size());
+ }
+}
+template void LogMessage::CopyToEncodedBuffer<LogMessage::StringType::kLiteral>(
+ absl::string_view str);
+template void LogMessage::CopyToEncodedBuffer<
+ LogMessage::StringType::kNotLiteral>(absl::string_view str);
+template <LogMessage::StringType str_type>
+void LogMessage::CopyToEncodedBuffer(char ch, size_t num) {
+ auto encoded_remaining_copy = data_->encoded_remaining;
+ auto value_start = EncodeMessageStart(
+ EventTag::kValue, BufferSizeFor(WireType::kLengthDelimited) + num,
+ &encoded_remaining_copy);
+ auto str_start = EncodeMessageStart(str_type == StringType::kLiteral
+ ? ValueTag::kStringLiteral
+ : ValueTag::kString,
+ num, &encoded_remaining_copy);
+ if (str_start.data()) {
+ // The field headers fit.
+ log_internal::AppendTruncated(ch, num, encoded_remaining_copy);
+ EncodeMessageLength(str_start, &encoded_remaining_copy);
+ EncodeMessageLength(value_start, &encoded_remaining_copy);
+ data_->encoded_remaining = encoded_remaining_copy;
+ } else {
+ // The field header(s) did not fit; zero `encoded_remaining` so we don't
+ // write anything else later.
+ data_->encoded_remaining.remove_suffix(data_->encoded_remaining.size());
+ }
+}
+template void LogMessage::CopyToEncodedBuffer<LogMessage::StringType::kLiteral>(
+ char ch, size_t num);
+template void LogMessage::CopyToEncodedBuffer<
+ LogMessage::StringType::kNotLiteral>(char ch, size_t num);
+
+LogMessageFatal::LogMessageFatal(const char* file, int line)
+ : LogMessage(file, line, absl::LogSeverity::kFatal) {}
+
+LogMessageFatal::LogMessageFatal(const char* file, int line,
+ absl::string_view failure_msg)
+ : LogMessage(file, line, absl::LogSeverity::kFatal) {
+ *this << "Check failed: " << failure_msg << " ";
+}
+
+// ABSL_ATTRIBUTE_NORETURN doesn't seem to work on destructors with msvc, so
+// disable msvc's warning about the d'tor never returning.
+#if defined(_MSC_VER) && !defined(__clang__)
+#pragma warning(push)
+#pragma warning(disable : 4722)
+#endif
+LogMessageFatal::~LogMessageFatal() {
+ Flush();
+ FailWithoutStackTrace();
+}
+#if defined(_MSC_VER) && !defined(__clang__)
+#pragma warning(pop)
+#endif
+
+LogMessageQuietlyFatal::LogMessageQuietlyFatal(const char* file, int line)
+ : LogMessage(file, line, absl::LogSeverity::kFatal) {
+ SetFailQuietly();
+}
+
+LogMessageQuietlyFatal::LogMessageQuietlyFatal(const char* file, int line,
+ absl::string_view failure_msg)
+ : LogMessage(file, line, absl::LogSeverity::kFatal) {
+ SetFailQuietly();
+ *this << "Check failed: " << failure_msg << " ";
+}
+
+// ABSL_ATTRIBUTE_NORETURN doesn't seem to work on destructors with msvc, so
+// disable msvc's warning about the d'tor never returning.
+#if defined(_MSC_VER) && !defined(__clang__)
+#pragma warning(push)
+#pragma warning(disable : 4722)
+#endif
+LogMessageQuietlyFatal::~LogMessageQuietlyFatal() {
+ Flush();
+ FailQuietly();
+}
+#if defined(_MSC_VER) && !defined(__clang__)
+#pragma warning(pop)
+#endif
+
+} // namespace log_internal
+
+ABSL_NAMESPACE_END
+} // namespace absl
diff --git a/absl/log/internal/log_message.h b/absl/log/internal/log_message.h
new file mode 100644
index 0000000..4ecb8a1
--- /dev/null
+++ b/absl/log/internal/log_message.h
@@ -0,0 +1,375 @@
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// -----------------------------------------------------------------------------
+// File: log/internal/log_message.h
+// -----------------------------------------------------------------------------
+//
+// This file declares `class absl::log_internal::LogMessage`. This class more or
+// less represents a particular log message. LOG/CHECK macros create a
+// temporary instance of `LogMessage` and then stream values to it. At the end
+// of the LOG/CHECK statement, LogMessage instance goes out of scope and
+// `~LogMessage` directs the message to the registered log sinks.
+// Heap-allocation of `LogMessage` is unsupported. Construction outside of a
+// `LOG` macro is unsupported.
+
+#ifndef ABSL_LOG_INTERNAL_LOG_MESSAGE_H_
+#define ABSL_LOG_INTERNAL_LOG_MESSAGE_H_
+
+#include <ios>
+#include <memory>
+#include <ostream>
+#include <streambuf>
+#include <string>
+
+#include "absl/base/attributes.h"
+#include "absl/base/config.h"
+#include "absl/base/internal/errno_saver.h"
+#include "absl/base/log_severity.h"
+#include "absl/log/internal/nullguard.h"
+#include "absl/log/log_entry.h"
+#include "absl/log/log_sink.h"
+#include "absl/strings/has_absl_stringify.h"
+#include "absl/strings/string_view.h"
+#include "absl/time/time.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace log_internal {
+constexpr int kLogMessageBufferSize = 15000;
+
+class LogMessage {
+ public:
+ struct InfoTag {};
+ struct WarningTag {};
+ struct ErrorTag {};
+
+ // Used for `LOG`.
+ LogMessage(const char* file, int line,
+ absl::LogSeverity severity) ABSL_ATTRIBUTE_COLD;
+ // These constructors are slightly smaller/faster to call; the severity is
+ // curried into the function pointer.
+ LogMessage(const char* file, int line,
+ InfoTag) ABSL_ATTRIBUTE_COLD ABSL_ATTRIBUTE_NOINLINE;
+ LogMessage(const char* file, int line,
+ WarningTag) ABSL_ATTRIBUTE_COLD ABSL_ATTRIBUTE_NOINLINE;
+ LogMessage(const char* file, int line,
+ ErrorTag) ABSL_ATTRIBUTE_COLD ABSL_ATTRIBUTE_NOINLINE;
+ LogMessage(const LogMessage&) = delete;
+ LogMessage& operator=(const LogMessage&) = delete;
+ ~LogMessage() ABSL_ATTRIBUTE_COLD;
+
+ // Overrides the location inferred from the callsite. The string pointed to
+ // by `file` must be valid until the end of the statement.
+ LogMessage& AtLocation(absl::string_view file, int line);
+ // Omits the prefix from this line. The prefix includes metadata about the
+ // logged data such as source code location and timestamp.
+ LogMessage& NoPrefix();
+ // Sets the verbosity field of the logged message as if it was logged by
+ // `VLOG(verbose_level)`. Unlike `VLOG`, this method does not affect
+ // evaluation of the statement when the specified `verbose_level` has been
+ // disabled. The only effect is on `absl::LogSink` implementations which
+ // make use of the `absl::LogSink::verbosity()` value. The value
+ // `absl::LogEntry::kNoVerbosityLevel` can be specified to mark the message
+ // not verbose.
+ LogMessage& WithVerbosity(int verbose_level);
+ // Uses the specified timestamp instead of one collected in the constructor.
+ LogMessage& WithTimestamp(absl::Time timestamp);
+ // Uses the specified thread ID instead of one collected in the constructor.
+ LogMessage& WithThreadID(absl::LogEntry::tid_t tid);
+ // Copies all metadata (but no data) from the specified `absl::LogEntry`.
+ LogMessage& WithMetadataFrom(const absl::LogEntry& entry);
+ // Appends to the logged message a colon, a space, a textual description of
+ // the current value of `errno` (as by strerror(3)), and the numerical value
+ // of `errno`.
+ LogMessage& WithPerror();
+ // Sends this message to `*sink` in addition to whatever other sinks it would
+ // otherwise have been sent to. `sink` must not be null.
+ LogMessage& ToSinkAlso(absl::LogSink* sink);
+ // Sends this message to `*sink` and no others. `sink` must not be null.
+ LogMessage& ToSinkOnly(absl::LogSink* sink);
+
+ // Don't call this method from outside this library.
+ LogMessage& InternalStream() { return *this; }
+
+ // By-value overloads for small, common types let us overlook common failures
+ // to define globals and static data members (i.e. in a .cc file).
+ // clang-format off
+ // The CUDA toolchain cannot handle these <<<'s:
+ LogMessage& operator<<(char v) { return operator<< <char>(v); }
+ LogMessage& operator<<(signed char v) { return operator<< <signed char>(v); }
+ LogMessage& operator<<(unsigned char v) {
+ return operator<< <unsigned char>(v);
+ }
+ LogMessage& operator<<(signed short v) { // NOLINT
+ return operator<< <signed short>(v); // NOLINT
+ }
+ LogMessage& operator<<(signed int v) { return operator<< <signed int>(v); }
+ LogMessage& operator<<(signed long v) { // NOLINT
+ return operator<< <signed long>(v); // NOLINT
+ }
+ LogMessage& operator<<(signed long long v) { // NOLINT
+ return operator<< <signed long long>(v); // NOLINT
+ }
+ LogMessage& operator<<(unsigned short v) { // NOLINT
+ return operator<< <unsigned short>(v); // NOLINT
+ }
+ LogMessage& operator<<(unsigned int v) {
+ return operator<< <unsigned int>(v);
+ }
+ LogMessage& operator<<(unsigned long v) { // NOLINT
+ return operator<< <unsigned long>(v); // NOLINT
+ }
+ LogMessage& operator<<(unsigned long long v) { // NOLINT
+ return operator<< <unsigned long long>(v); // NOLINT
+ }
+ LogMessage& operator<<(void* v) { return operator<< <void*>(v); }
+ LogMessage& operator<<(const void* v) { return operator<< <const void*>(v); }
+ LogMessage& operator<<(float v) { return operator<< <float>(v); }
+ LogMessage& operator<<(double v) { return operator<< <double>(v); }
+ LogMessage& operator<<(bool v) { return operator<< <bool>(v); }
+ // clang-format on
+
+ // These overloads are more efficient since no `ostream` is involved.
+ LogMessage& operator<<(const std::string& v);
+ LogMessage& operator<<(absl::string_view v);
+
+ // Handle stream manipulators e.g. std::endl.
+ LogMessage& operator<<(std::ostream& (*m)(std::ostream& os));
+ LogMessage& operator<<(std::ios_base& (*m)(std::ios_base& os));
+
+ // Literal strings. This allows us to record C string literals as literals in
+ // the logging.proto.Value.
+ //
+ // Allow this overload to be inlined to prevent generating instantiations of
+ // this template for every value of `SIZE` encountered in each source code
+ // file. That significantly increases linker input sizes. Inlining is cheap
+ // because the argument to this overload is almost always a string literal so
+ // the call to `strlen` can be replaced at compile time. The overload for
+ // `char[]` below should not be inlined. The compiler typically does not have
+ // the string at compile time and cannot replace the call to `strlen` so
+ // inlining it increases the binary size. See the discussion on
+ // cl/107527369.
+ template <int SIZE>
+ LogMessage& operator<<(const char (&buf)[SIZE]);
+
+ // This prevents non-const `char[]` arrays from looking like literals.
+ template <int SIZE>
+ LogMessage& operator<<(char (&buf)[SIZE]) ABSL_ATTRIBUTE_NOINLINE;
+
+ // Types that support `AbslStringify()` are serialized that way.
+ template <typename T,
+ typename std::enable_if<absl::HasAbslStringify<T>::value,
+ int>::type = 0>
+ LogMessage& operator<<(const T& v) ABSL_ATTRIBUTE_NOINLINE;
+
+ // Types that don't support `AbslStringify()` but do support streaming into a
+ // `std::ostream&` are serialized that way.
+ template <typename T,
+ typename std::enable_if<!absl::HasAbslStringify<T>::value,
+ int>::type = 0>
+ LogMessage& operator<<(const T& v) ABSL_ATTRIBUTE_NOINLINE;
+
+ // Note: We explicitly do not support `operator<<` for non-const references
+ // because it breaks logging of non-integer bitfield types (i.e., enums).
+
+ protected:
+ // Call `abort()` or similar to perform `LOG(FATAL)` crash. It is assumed
+ // that the caller has already generated and written the trace as appropriate.
+ ABSL_ATTRIBUTE_NORETURN static void FailWithoutStackTrace();
+
+ // Similar to `FailWithoutStackTrace()`, but without `abort()`. Terminates
+ // the process with an error exit code.
+ ABSL_ATTRIBUTE_NORETURN static void FailQuietly();
+
+ // Dispatches the completed `absl::LogEntry` to applicable `absl::LogSink`s.
+ // This might as well be inlined into `~LogMessage` except that
+ // `~LogMessageFatal` needs to call it early.
+ void Flush();
+
+ // After this is called, failures are done as quiet as possible for this log
+ // message.
+ void SetFailQuietly();
+
+ private:
+ struct LogMessageData; // Opaque type containing message state
+ friend class AsLiteralImpl;
+ friend class StringifySink;
+
+ // This streambuf writes directly into the structured logging buffer so that
+ // arbitrary types can be encoded as string data (using
+ // `operator<<(std::ostream &, ...)` without any extra allocation or copying.
+ // Space is reserved before the data to store the length field, which is
+ // filled in by `~OstreamView`.
+ class OstreamView final : public std::streambuf {
+ public:
+ explicit OstreamView(LogMessageData& message_data);
+ ~OstreamView() override;
+ OstreamView(const OstreamView&) = delete;
+ OstreamView& operator=(const OstreamView&) = delete;
+ std::ostream& stream();
+
+ private:
+ LogMessageData& data_;
+ absl::Span<char> encoded_remaining_copy_;
+ absl::Span<char> message_start_;
+ absl::Span<char> string_start_;
+ };
+
+ enum class StringType {
+ kLiteral,
+ kNotLiteral,
+ };
+ template <StringType str_type>
+ void CopyToEncodedBuffer(absl::string_view str) ABSL_ATTRIBUTE_NOINLINE;
+ template <StringType str_type>
+ void CopyToEncodedBuffer(char ch, size_t num) ABSL_ATTRIBUTE_NOINLINE;
+
+ // Returns `true` if the message is fatal or enabled debug-fatal.
+ bool IsFatal() const;
+
+ // Records some tombstone-type data in anticipation of `Die`.
+ void PrepareToDie();
+ void Die();
+
+ void SendToLog();
+
+ // Checks `FLAGS_log_backtrace_at` and appends a backtrace if appropriate.
+ void LogBacktraceIfNeeded();
+
+ // This should be the first data member so that its initializer captures errno
+ // before any other initializers alter it (e.g. with calls to new) and so that
+ // no other destructors run afterward an alter it (e.g. with calls to delete).
+ absl::base_internal::ErrnoSaver errno_saver_;
+
+ // We keep the data in a separate struct so that each instance of `LogMessage`
+ // uses less stack space.
+ std::unique_ptr<LogMessageData> data_;
+};
+
+// Helper class so that `AbslStringify()` can modify the LogMessage.
+class StringifySink final {
+ public:
+ explicit StringifySink(LogMessage& message) : message_(message) {}
+
+ void Append(size_t count, char ch) {
+ message_.CopyToEncodedBuffer<LogMessage::StringType::kNotLiteral>(ch,
+ count);
+ }
+
+ void Append(absl::string_view v) {
+ message_.CopyToEncodedBuffer<LogMessage::StringType::kNotLiteral>(v);
+ }
+
+ // For types that implement `AbslStringify` using `absl::Format()`.
+ friend void AbslFormatFlush(StringifySink* sink, absl::string_view v) {
+ sink->Append(v);
+ }
+
+ private:
+ LogMessage& message_;
+};
+
+// Note: the following is declared `ABSL_ATTRIBUTE_NOINLINE`
+template <typename T,
+ typename std::enable_if<absl::HasAbslStringify<T>::value, int>::type>
+LogMessage& LogMessage::operator<<(const T& v) {
+ StringifySink sink(*this);
+ // Replace with public API.
+ AbslStringify(sink, v);
+ return *this;
+}
+
+// Note: the following is declared `ABSL_ATTRIBUTE_NOINLINE`
+template <typename T,
+ typename std::enable_if<!absl::HasAbslStringify<T>::value, int>::type>
+LogMessage& LogMessage::operator<<(const T& v) {
+ OstreamView view(*data_);
+ view.stream() << log_internal::NullGuard<T>().Guard(v);
+ return *this;
+}
+
+template <int SIZE>
+LogMessage& LogMessage::operator<<(const char (&buf)[SIZE]) {
+ CopyToEncodedBuffer<StringType::kLiteral>(buf);
+ return *this;
+}
+
+// Note: the following is declared `ABSL_ATTRIBUTE_NOINLINE`
+template <int SIZE>
+LogMessage& LogMessage::operator<<(char (&buf)[SIZE]) {
+ CopyToEncodedBuffer<StringType::kNotLiteral>(buf);
+ return *this;
+}
+// We instantiate these specializations in the library's TU to save space in
+// other TUs. Since the template is marked `ABSL_ATTRIBUTE_NOINLINE` we will be
+// emitting a function call either way.
+extern template LogMessage& LogMessage::operator<<(const char& v);
+extern template LogMessage& LogMessage::operator<<(const signed char& v);
+extern template LogMessage& LogMessage::operator<<(const unsigned char& v);
+extern template LogMessage& LogMessage::operator<<(const short& v); // NOLINT
+extern template LogMessage& LogMessage::operator<<(
+ const unsigned short& v); // NOLINT
+extern template LogMessage& LogMessage::operator<<(const int& v);
+extern template LogMessage& LogMessage::operator<<(
+ const unsigned int& v); // NOLINT
+extern template LogMessage& LogMessage::operator<<(const long& v); // NOLINT
+extern template LogMessage& LogMessage::operator<<(
+ const unsigned long& v); // NOLINT
+extern template LogMessage& LogMessage::operator<<(
+ const long long& v); // NOLINT
+extern template LogMessage& LogMessage::operator<<(
+ const unsigned long long& v); // NOLINT
+extern template LogMessage& LogMessage::operator<<(void* const& v);
+extern template LogMessage& LogMessage::operator<<(const void* const& v);
+extern template LogMessage& LogMessage::operator<<(const float& v);
+extern template LogMessage& LogMessage::operator<<(const double& v);
+extern template LogMessage& LogMessage::operator<<(const bool& v);
+
+extern template void LogMessage::CopyToEncodedBuffer<
+ LogMessage::StringType::kLiteral>(absl::string_view str);
+extern template void LogMessage::CopyToEncodedBuffer<
+ LogMessage::StringType::kNotLiteral>(absl::string_view str);
+extern template void
+LogMessage::CopyToEncodedBuffer<LogMessage::StringType::kLiteral>(char ch,
+ size_t num);
+extern template void LogMessage::CopyToEncodedBuffer<
+ LogMessage::StringType::kNotLiteral>(char ch, size_t num);
+
+// `LogMessageFatal` ensures the process will exit in failure after logging this
+// message.
+class LogMessageFatal final : public LogMessage {
+ public:
+ LogMessageFatal(const char* file, int line) ABSL_ATTRIBUTE_COLD;
+ LogMessageFatal(const char* file, int line,
+ absl::string_view failure_msg) ABSL_ATTRIBUTE_COLD;
+ ABSL_ATTRIBUTE_NORETURN ~LogMessageFatal();
+};
+
+class LogMessageQuietlyFatal final : public LogMessage {
+ public:
+ LogMessageQuietlyFatal(const char* file, int line) ABSL_ATTRIBUTE_COLD;
+ LogMessageQuietlyFatal(const char* file, int line,
+ absl::string_view failure_msg) ABSL_ATTRIBUTE_COLD;
+ ABSL_ATTRIBUTE_NORETURN ~LogMessageQuietlyFatal();
+};
+
+} // namespace log_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+
+extern "C" ABSL_ATTRIBUTE_WEAK void ABSL_INTERNAL_C_SYMBOL(
+ AbslInternalOnFatalLogMessage)(const absl::LogEntry&);
+
+#endif // ABSL_LOG_INTERNAL_LOG_MESSAGE_H_
diff --git a/absl/log/internal/log_sink_set.cc b/absl/log/internal/log_sink_set.cc
new file mode 100644
index 0000000..3d5c699
--- /dev/null
+++ b/absl/log/internal/log_sink_set.cc
@@ -0,0 +1,296 @@
+//
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/log/internal/log_sink_set.h"
+
+#ifndef ABSL_HAVE_THREAD_LOCAL
+#include <pthread.h>
+#endif
+
+#ifdef __ANDROID__
+#include <android/log.h>
+#endif
+
+#ifdef _WIN32
+#include <windows.h>
+#endif
+
+#include <algorithm>
+#include <vector>
+
+#include "absl/base/attributes.h"
+#include "absl/base/call_once.h"
+#include "absl/base/config.h"
+#include "absl/base/internal/raw_logging.h"
+#include "absl/base/log_severity.h"
+#include "absl/base/no_destructor.h"
+#include "absl/base/thread_annotations.h"
+#include "absl/cleanup/cleanup.h"
+#include "absl/log/globals.h"
+#include "absl/log/internal/config.h"
+#include "absl/log/internal/globals.h"
+#include "absl/log/log_entry.h"
+#include "absl/log/log_sink.h"
+#include "absl/strings/string_view.h"
+#include "absl/synchronization/mutex.h"
+#include "absl/types/span.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace log_internal {
+namespace {
+
+// Returns a mutable reference to a thread-local variable that should be true if
+// a globally-registered `LogSink`'s `Send()` is currently being invoked on this
+// thread.
+bool& ThreadIsLoggingStatus() {
+#ifdef ABSL_HAVE_THREAD_LOCAL
+ ABSL_CONST_INIT thread_local bool thread_is_logging = false;
+ return thread_is_logging;
+#else
+ ABSL_CONST_INIT static pthread_key_t thread_is_logging_key;
+ static const bool unused = [] {
+ if (pthread_key_create(&thread_is_logging_key, [](void* data) {
+ delete reinterpret_cast<bool*>(data);
+ })) {
+ perror("pthread_key_create failed!");
+ abort();
+ }
+ return true;
+ }();
+ (void)unused; // Fixes -wunused-variable warning
+ bool* thread_is_logging_ptr =
+ reinterpret_cast<bool*>(pthread_getspecific(thread_is_logging_key));
+
+ if (ABSL_PREDICT_FALSE(!thread_is_logging_ptr)) {
+ thread_is_logging_ptr = new bool{false};
+ if (pthread_setspecific(thread_is_logging_key, thread_is_logging_ptr)) {
+ perror("pthread_setspecific failed");
+ abort();
+ }
+ }
+ return *thread_is_logging_ptr;
+#endif
+}
+
+class StderrLogSink final : public LogSink {
+ public:
+ ~StderrLogSink() override = default;
+
+ void Send(const absl::LogEntry& entry) override {
+ if (entry.log_severity() < absl::StderrThreshold() &&
+ absl::log_internal::IsInitialized()) {
+ return;
+ }
+
+ ABSL_CONST_INIT static absl::once_flag warn_if_not_initialized;
+ absl::call_once(warn_if_not_initialized, []() {
+ if (absl::log_internal::IsInitialized()) return;
+ const char w[] =
+ "WARNING: All log messages before absl::InitializeLog() is called"
+ " are written to STDERR\n";
+ absl::log_internal::WriteToStderr(w, absl::LogSeverity::kWarning);
+ });
+
+ if (!entry.stacktrace().empty()) {
+ absl::log_internal::WriteToStderr(entry.stacktrace(),
+ entry.log_severity());
+ } else {
+ // TODO(b/226937039): do this outside else condition once we avoid
+ // ReprintFatalMessage
+ absl::log_internal::WriteToStderr(
+ entry.text_message_with_prefix_and_newline(), entry.log_severity());
+ }
+ }
+};
+
+#if defined(__ANDROID__)
+class AndroidLogSink final : public LogSink {
+ public:
+ ~AndroidLogSink() override = default;
+
+ void Send(const absl::LogEntry& entry) override {
+ const int level = AndroidLogLevel(entry);
+ const char* const tag = GetAndroidNativeTag();
+ __android_log_write(level, tag,
+ entry.text_message_with_prefix_and_newline_c_str());
+ if (entry.log_severity() == absl::LogSeverity::kFatal)
+ __android_log_write(ANDROID_LOG_FATAL, tag, "terminating.\n");
+ }
+
+ private:
+ static int AndroidLogLevel(const absl::LogEntry& entry) {
+ switch (entry.log_severity()) {
+ case absl::LogSeverity::kFatal:
+ return ANDROID_LOG_FATAL;
+ case absl::LogSeverity::kError:
+ return ANDROID_LOG_ERROR;
+ case absl::LogSeverity::kWarning:
+ return ANDROID_LOG_WARN;
+ default:
+ if (entry.verbosity() >= 2) return ANDROID_LOG_VERBOSE;
+ if (entry.verbosity() == 1) return ANDROID_LOG_DEBUG;
+ return ANDROID_LOG_INFO;
+ }
+ }
+};
+#endif // !defined(__ANDROID__)
+
+#if defined(_WIN32)
+class WindowsDebuggerLogSink final : public LogSink {
+ public:
+ ~WindowsDebuggerLogSink() override = default;
+
+ void Send(const absl::LogEntry& entry) override {
+ if (entry.log_severity() < absl::StderrThreshold() &&
+ absl::log_internal::IsInitialized()) {
+ return;
+ }
+ ::OutputDebugStringA(entry.text_message_with_prefix_and_newline_c_str());
+ }
+};
+#endif // !defined(_WIN32)
+
+class GlobalLogSinkSet final {
+ public:
+ GlobalLogSinkSet() {
+#if defined(__myriad2__) || defined(__Fuchsia__)
+ // myriad2 and Fuchsia do not log to stderr by default.
+#else
+ static absl::NoDestructor<StderrLogSink> stderr_log_sink;
+ AddLogSink(stderr_log_sink.get());
+#endif
+#ifdef __ANDROID__
+ static absl::NoDestructor<AndroidLogSink> android_log_sink;
+ AddLogSink(android_log_sink.get());
+#endif
+#if defined(_WIN32)
+ static absl::NoDestructor<WindowsDebuggerLogSink> debugger_log_sink;
+ AddLogSink(debugger_log_sink.get());
+#endif // !defined(_WIN32)
+ }
+
+ void LogToSinks(const absl::LogEntry& entry,
+ absl::Span<absl::LogSink*> extra_sinks, bool extra_sinks_only)
+ ABSL_LOCKS_EXCLUDED(guard_) {
+ SendToSinks(entry, extra_sinks);
+
+ if (!extra_sinks_only) {
+ if (ThreadIsLoggingToLogSink()) {
+ absl::log_internal::WriteToStderr(
+ entry.text_message_with_prefix_and_newline(), entry.log_severity());
+ } else {
+ absl::ReaderMutexLock global_sinks_lock(&guard_);
+ ThreadIsLoggingStatus() = true;
+ // Ensure the "thread is logging" status is reverted upon leaving the
+ // scope even in case of exceptions.
+ auto status_cleanup =
+ absl::MakeCleanup([] { ThreadIsLoggingStatus() = false; });
+ SendToSinks(entry, absl::MakeSpan(sinks_));
+ }
+ }
+ }
+
+ void AddLogSink(absl::LogSink* sink) ABSL_LOCKS_EXCLUDED(guard_) {
+ {
+ absl::WriterMutexLock global_sinks_lock(&guard_);
+ auto pos = std::find(sinks_.begin(), sinks_.end(), sink);
+ if (pos == sinks_.end()) {
+ sinks_.push_back(sink);
+ return;
+ }
+ }
+ ABSL_INTERNAL_LOG(FATAL, "Duplicate log sinks are not supported");
+ }
+
+ void RemoveLogSink(absl::LogSink* sink) ABSL_LOCKS_EXCLUDED(guard_) {
+ {
+ absl::WriterMutexLock global_sinks_lock(&guard_);
+ auto pos = std::find(sinks_.begin(), sinks_.end(), sink);
+ if (pos != sinks_.end()) {
+ sinks_.erase(pos);
+ return;
+ }
+ }
+ ABSL_INTERNAL_LOG(FATAL, "Mismatched log sink being removed");
+ }
+
+ void FlushLogSinks() ABSL_LOCKS_EXCLUDED(guard_) {
+ if (ThreadIsLoggingToLogSink()) {
+ // The thread_local condition demonstrates that we're already holding the
+ // lock in order to iterate over `sinks_` for dispatch. The thread-safety
+ // annotations don't know this, so we use `ABSL_NO_THREAD_SAFETY_ANALYSIS`
+ guard_.AssertReaderHeld();
+ FlushLogSinksLocked();
+ } else {
+ absl::ReaderMutexLock global_sinks_lock(&guard_);
+ // In case if LogSink::Flush overload decides to log
+ ThreadIsLoggingStatus() = true;
+ // Ensure the "thread is logging" status is reverted upon leaving the
+ // scope even in case of exceptions.
+ auto status_cleanup =
+ absl::MakeCleanup([] { ThreadIsLoggingStatus() = false; });
+ FlushLogSinksLocked();
+ }
+ }
+
+ private:
+ void FlushLogSinksLocked() ABSL_SHARED_LOCKS_REQUIRED(guard_) {
+ for (absl::LogSink* sink : sinks_) {
+ sink->Flush();
+ }
+ }
+
+ // Helper routine for LogToSinks.
+ static void SendToSinks(const absl::LogEntry& entry,
+ absl::Span<absl::LogSink*> sinks) {
+ for (absl::LogSink* sink : sinks) {
+ sink->Send(entry);
+ }
+ }
+
+ using LogSinksSet = std::vector<absl::LogSink*>;
+ absl::Mutex guard_;
+ LogSinksSet sinks_ ABSL_GUARDED_BY(guard_);
+};
+
+// Returns reference to the global LogSinks set.
+GlobalLogSinkSet& GlobalSinks() {
+ static absl::NoDestructor<GlobalLogSinkSet> global_sinks;
+ return *global_sinks;
+}
+
+} // namespace
+
+bool ThreadIsLoggingToLogSink() { return ThreadIsLoggingStatus(); }
+
+void LogToSinks(const absl::LogEntry& entry,
+ absl::Span<absl::LogSink*> extra_sinks, bool extra_sinks_only) {
+ log_internal::GlobalSinks().LogToSinks(entry, extra_sinks, extra_sinks_only);
+}
+
+void AddLogSink(absl::LogSink* sink) {
+ log_internal::GlobalSinks().AddLogSink(sink);
+}
+
+void RemoveLogSink(absl::LogSink* sink) {
+ log_internal::GlobalSinks().RemoveLogSink(sink);
+}
+
+void FlushLogSinks() { log_internal::GlobalSinks().FlushLogSinks(); }
+
+} // namespace log_internal
+ABSL_NAMESPACE_END
+} // namespace absl
diff --git a/absl/log/internal/log_sink_set.h b/absl/log/internal/log_sink_set.h
new file mode 100644
index 0000000..88ab073
--- /dev/null
+++ b/absl/log/internal/log_sink_set.h
@@ -0,0 +1,54 @@
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// -----------------------------------------------------------------------------
+// File: log/internal/log_sink_set.h
+// -----------------------------------------------------------------------------
+
+#ifndef ABSL_LOG_INTERNAL_LOG_SINK_SET_H_
+#define ABSL_LOG_INTERNAL_LOG_SINK_SET_H_
+
+#include "absl/base/config.h"
+#include "absl/log/log_entry.h"
+#include "absl/log/log_sink.h"
+#include "absl/types/span.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace log_internal {
+
+// Returns true if a globally-registered `LogSink`'s `Send()` is currently
+// being invoked on this thread.
+bool ThreadIsLoggingToLogSink();
+
+// This function may log to two sets of sinks:
+//
+// * If `extra_sinks_only` is true, it will dispatch only to `extra_sinks`.
+// `LogMessage::ToSinkAlso` and `LogMessage::ToSinkOnly` are used to attach
+// extra sinks to the entry.
+// * Otherwise it will also log to the global sinks set. This set is managed
+// by `absl::AddLogSink` and `absl::RemoveLogSink`.
+void LogToSinks(const absl::LogEntry& entry,
+ absl::Span<absl::LogSink*> extra_sinks, bool extra_sinks_only);
+
+// Implementation for operations with log sink set.
+void AddLogSink(absl::LogSink* sink);
+void RemoveLogSink(absl::LogSink* sink);
+void FlushLogSinks();
+
+} // namespace log_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_LOG_INTERNAL_LOG_SINK_SET_H_
diff --git a/absl/log/internal/nullguard.cc b/absl/log/internal/nullguard.cc
new file mode 100644
index 0000000..3296c01
--- /dev/null
+++ b/absl/log/internal/nullguard.cc
@@ -0,0 +1,35 @@
+// Copyright 2023 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/log/internal/nullguard.h"
+
+#include <array>
+
+#include "absl/base/attributes.h"
+#include "absl/base/config.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace log_internal {
+
+ABSL_CONST_INIT ABSL_DLL const std::array<char, 7> kCharNull{
+ {'(', 'n', 'u', 'l', 'l', ')', '\0'}};
+ABSL_CONST_INIT ABSL_DLL const std::array<signed char, 7> kSignedCharNull{
+ {'(', 'n', 'u', 'l', 'l', ')', '\0'}};
+ABSL_CONST_INIT ABSL_DLL const std::array<unsigned char, 7> kUnsignedCharNull{
+ {'(', 'n', 'u', 'l', 'l', ')', '\0'}};
+
+} // namespace log_internal
+ABSL_NAMESPACE_END
+} // namespace absl
diff --git a/absl/log/internal/nullguard.h b/absl/log/internal/nullguard.h
new file mode 100644
index 0000000..623943c
--- /dev/null
+++ b/absl/log/internal/nullguard.h
@@ -0,0 +1,88 @@
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// -----------------------------------------------------------------------------
+// File: log/internal/nullguard.h
+// -----------------------------------------------------------------------------
+//
+// NullGuard exists such that NullGuard<T>::Guard(v) returns v, unless passed a
+// nullptr_t, or a null char* or const char*, in which case it returns "(null)".
+// This allows streaming NullGuard<T>::Guard(v) to an output stream without
+// hitting undefined behavior for null values.
+
+#ifndef ABSL_LOG_INTERNAL_NULLGUARD_H_
+#define ABSL_LOG_INTERNAL_NULLGUARD_H_
+
+#include <array>
+#include <cstddef>
+
+#include "absl/base/attributes.h"
+#include "absl/base/config.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace log_internal {
+
+ABSL_CONST_INIT ABSL_DLL extern const std::array<char, 7> kCharNull;
+ABSL_CONST_INIT ABSL_DLL extern const std::array<signed char, 7>
+ kSignedCharNull;
+ABSL_CONST_INIT ABSL_DLL extern const std::array<unsigned char, 7>
+ kUnsignedCharNull;
+
+template <typename T>
+struct NullGuard final {
+ static const T& Guard(const T& v) { return v; }
+};
+template <>
+struct NullGuard<char*> final {
+ static const char* Guard(const char* v) { return v ? v : kCharNull.data(); }
+};
+template <>
+struct NullGuard<const char*> final {
+ static const char* Guard(const char* v) { return v ? v : kCharNull.data(); }
+};
+template <>
+struct NullGuard<signed char*> final {
+ static const signed char* Guard(const signed char* v) {
+ return v ? v : kSignedCharNull.data();
+ }
+};
+template <>
+struct NullGuard<const signed char*> final {
+ static const signed char* Guard(const signed char* v) {
+ return v ? v : kSignedCharNull.data();
+ }
+};
+template <>
+struct NullGuard<unsigned char*> final {
+ static const unsigned char* Guard(const unsigned char* v) {
+ return v ? v : kUnsignedCharNull.data();
+ }
+};
+template <>
+struct NullGuard<const unsigned char*> final {
+ static const unsigned char* Guard(const unsigned char* v) {
+ return v ? v : kUnsignedCharNull.data();
+ }
+};
+template <>
+struct NullGuard<std::nullptr_t> final {
+ static const char* Guard(const std::nullptr_t&) { return kCharNull.data(); }
+};
+
+} // namespace log_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_LOG_INTERNAL_NULLGUARD_H_
diff --git a/absl/log/internal/nullstream.h b/absl/log/internal/nullstream.h
new file mode 100644
index 0000000..9266852
--- /dev/null
+++ b/absl/log/internal/nullstream.h
@@ -0,0 +1,136 @@
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// -----------------------------------------------------------------------------
+// File: log/internal/nullstream.h
+// -----------------------------------------------------------------------------
+//
+// Classes `NullStream`, `NullStreamMaybeFatal ` and `NullStreamFatal`
+// implement a subset of the `LogMessage` API and are used instead when logging
+// of messages has been disabled.
+
+#ifndef ABSL_LOG_INTERNAL_NULLSTREAM_H_
+#define ABSL_LOG_INTERNAL_NULLSTREAM_H_
+
+#ifdef _WIN32
+#include <cstdlib>
+#else
+#include <unistd.h>
+#endif
+#include <ios>
+#include <ostream>
+
+#include "absl/base/attributes.h"
+#include "absl/base/config.h"
+#include "absl/base/log_severity.h"
+#include "absl/strings/string_view.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace log_internal {
+
+// A `NullStream` implements the API of `LogMessage` (a few methods and
+// `operator<<`) but does nothing. All methods are defined inline so the
+// compiler can eliminate the whole instance and discard anything that's
+// streamed in.
+class NullStream {
+ public:
+ NullStream& AtLocation(absl::string_view, int) { return *this; }
+ template <typename SourceLocationType>
+ NullStream& AtLocation(SourceLocationType) {
+ return *this;
+ }
+ NullStream& NoPrefix() { return *this; }
+ NullStream& WithVerbosity(int) { return *this; }
+ template <typename TimeType>
+ NullStream& WithTimestamp(TimeType) {
+ return *this;
+ }
+ template <typename Tid>
+ NullStream& WithThreadID(Tid) {
+ return *this;
+ }
+ template <typename LogEntryType>
+ NullStream& WithMetadataFrom(const LogEntryType&) {
+ return *this;
+ }
+ NullStream& WithPerror() { return *this; }
+ template <typename LogSinkType>
+ NullStream& ToSinkAlso(LogSinkType*) {
+ return *this;
+ }
+ template <typename LogSinkType>
+ NullStream& ToSinkOnly(LogSinkType*) {
+ return *this;
+ }
+ template <typename LogSinkType>
+ NullStream& OutputToSink(LogSinkType*, bool) {
+ return *this;
+ }
+ NullStream& InternalStream() { return *this; }
+};
+template <typename T>
+inline NullStream& operator<<(NullStream& str, const T&) {
+ return str;
+}
+inline NullStream& operator<<(NullStream& str,
+ std::ostream& (*)(std::ostream& os)) {
+ return str;
+}
+inline NullStream& operator<<(NullStream& str,
+ std::ios_base& (*)(std::ios_base& os)) {
+ return str;
+}
+
+// `NullStreamMaybeFatal` implements the process termination semantics of
+// `LogMessage`, which is used for `DFATAL` severity and expression-defined
+// severity e.g. `LOG(LEVEL(HowBadIsIt()))`. Like `LogMessage`, it terminates
+// the process when destroyed if the passed-in severity equals `FATAL`.
+class NullStreamMaybeFatal final : public NullStream {
+ public:
+ explicit NullStreamMaybeFatal(absl::LogSeverity severity)
+ : fatal_(severity == absl::LogSeverity::kFatal) {}
+ ~NullStreamMaybeFatal() {
+ if (fatal_) {
+ _exit(1);
+ }
+ }
+
+ private:
+ bool fatal_;
+};
+
+// `NullStreamFatal` implements the process termination semantics of
+// `LogMessageFatal`, which means it always terminates the process. `DFATAL`
+// and expression-defined severity use `NullStreamMaybeFatal` above.
+class NullStreamFatal final : public NullStream {
+ public:
+ NullStreamFatal() = default;
+ // ABSL_ATTRIBUTE_NORETURN doesn't seem to work on destructors with msvc, so
+ // disable msvc's warning about the d'tor never returning.
+#if defined(_MSC_VER) && !defined(__clang__)
+#pragma warning(push)
+#pragma warning(disable : 4722)
+#endif
+ ABSL_ATTRIBUTE_NORETURN ~NullStreamFatal() { _exit(1); }
+#if defined(_MSC_VER) && !defined(__clang__)
+#pragma warning(pop)
+#endif
+};
+
+} // namespace log_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_LOG_INTERNAL_GLOBALS_H_
diff --git a/absl/log/internal/proto.cc b/absl/log/internal/proto.cc
new file mode 100644
index 0000000..eb699ae
--- /dev/null
+++ b/absl/log/internal/proto.cc
@@ -0,0 +1,220 @@
+// Copyright 2020 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/log/internal/proto.h"
+
+#include <algorithm>
+#include <cassert>
+#include <cstddef>
+#include <cstdint>
+#include <cstring>
+
+#include "absl/base/attributes.h"
+#include "absl/base/config.h"
+#include "absl/types/span.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace log_internal {
+namespace {
+void EncodeRawVarint(uint64_t value, size_t size, absl::Span<char> *buf) {
+ for (size_t s = 0; s < size; s++) {
+ (*buf)[s] = static_cast<char>((value & 0x7f) | (s + 1 == size ? 0 : 0x80));
+ value >>= 7;
+ }
+ buf->remove_prefix(size);
+}
+constexpr uint64_t MakeTagType(uint64_t tag, WireType type) {
+ return tag << 3 | static_cast<uint64_t>(type);
+}
+} // namespace
+
+bool EncodeVarint(uint64_t tag, uint64_t value, absl::Span<char> *buf) {
+ const uint64_t tag_type = MakeTagType(tag, WireType::kVarint);
+ const size_t tag_type_size = VarintSize(tag_type);
+ const size_t value_size = VarintSize(value);
+ if (tag_type_size + value_size > buf->size()) {
+ buf->remove_suffix(buf->size());
+ return false;
+ }
+ EncodeRawVarint(tag_type, tag_type_size, buf);
+ EncodeRawVarint(value, value_size, buf);
+ return true;
+}
+
+bool Encode64Bit(uint64_t tag, uint64_t value, absl::Span<char> *buf) {
+ const uint64_t tag_type = MakeTagType(tag, WireType::k64Bit);
+ const size_t tag_type_size = VarintSize(tag_type);
+ if (tag_type_size + sizeof(value) > buf->size()) {
+ buf->remove_suffix(buf->size());
+ return false;
+ }
+ EncodeRawVarint(tag_type, tag_type_size, buf);
+ for (size_t s = 0; s < sizeof(value); s++) {
+ (*buf)[s] = static_cast<char>(value & 0xff);
+ value >>= 8;
+ }
+ buf->remove_prefix(sizeof(value));
+ return true;
+}
+
+bool Encode32Bit(uint64_t tag, uint32_t value, absl::Span<char> *buf) {
+ const uint64_t tag_type = MakeTagType(tag, WireType::k32Bit);
+ const size_t tag_type_size = VarintSize(tag_type);
+ if (tag_type_size + sizeof(value) > buf->size()) {
+ buf->remove_suffix(buf->size());
+ return false;
+ }
+ EncodeRawVarint(tag_type, tag_type_size, buf);
+ for (size_t s = 0; s < sizeof(value); s++) {
+ (*buf)[s] = static_cast<char>(value & 0xff);
+ value >>= 8;
+ }
+ buf->remove_prefix(sizeof(value));
+ return true;
+}
+
+bool EncodeBytes(uint64_t tag, absl::Span<const char> value,
+ absl::Span<char> *buf) {
+ const uint64_t tag_type = MakeTagType(tag, WireType::kLengthDelimited);
+ const size_t tag_type_size = VarintSize(tag_type);
+ uint64_t length = value.size();
+ const size_t length_size = VarintSize(length);
+ if (tag_type_size + length_size + value.size() > buf->size()) {
+ buf->remove_suffix(buf->size());
+ return false;
+ }
+ EncodeRawVarint(tag_type, tag_type_size, buf);
+ EncodeRawVarint(length, length_size, buf);
+ memcpy(buf->data(), value.data(), value.size());
+ buf->remove_prefix(value.size());
+ return true;
+}
+
+bool EncodeBytesTruncate(uint64_t tag, absl::Span<const char> value,
+ absl::Span<char> *buf) {
+ const uint64_t tag_type = MakeTagType(tag, WireType::kLengthDelimited);
+ const size_t tag_type_size = VarintSize(tag_type);
+ uint64_t length = value.size();
+ const size_t length_size =
+ VarintSize(std::min<uint64_t>(length, buf->size()));
+ if (tag_type_size + length_size <= buf->size() &&
+ tag_type_size + length_size + value.size() > buf->size()) {
+ value.remove_suffix(tag_type_size + length_size + value.size() -
+ buf->size());
+ length = value.size();
+ }
+ if (tag_type_size + length_size + value.size() > buf->size()) {
+ buf->remove_suffix(buf->size());
+ return false;
+ }
+ EncodeRawVarint(tag_type, tag_type_size, buf);
+ EncodeRawVarint(length, length_size, buf);
+ memcpy(buf->data(), value.data(), value.size());
+ buf->remove_prefix(value.size());
+ return true;
+}
+
+ABSL_MUST_USE_RESULT absl::Span<char> EncodeMessageStart(
+ uint64_t tag, uint64_t max_size, absl::Span<char> *buf) {
+ const uint64_t tag_type = MakeTagType(tag, WireType::kLengthDelimited);
+ const size_t tag_type_size = VarintSize(tag_type);
+ max_size = std::min<uint64_t>(max_size, buf->size());
+ const size_t length_size = VarintSize(max_size);
+ if (tag_type_size + length_size > buf->size()) {
+ buf->remove_suffix(buf->size());
+ return absl::Span<char>();
+ }
+ EncodeRawVarint(tag_type, tag_type_size, buf);
+ const absl::Span<char> ret = buf->subspan(0, length_size);
+ EncodeRawVarint(0, length_size, buf);
+ return ret;
+}
+
+void EncodeMessageLength(absl::Span<char> msg, const absl::Span<char> *buf) {
+ if (!msg.data()) return;
+ assert(buf->data() >= msg.data());
+ if (buf->data() < msg.data()) return;
+ EncodeRawVarint(
+ static_cast<uint64_t>(buf->data() - (msg.data() + msg.size())),
+ msg.size(), &msg);
+}
+
+namespace {
+uint64_t DecodeVarint(absl::Span<const char> *buf) {
+ uint64_t value = 0;
+ size_t s = 0;
+ while (s < buf->size()) {
+ value |= static_cast<uint64_t>(static_cast<unsigned char>((*buf)[s]) & 0x7f)
+ << 7 * s;
+ if (!((*buf)[s++] & 0x80)) break;
+ }
+ buf->remove_prefix(s);
+ return value;
+}
+
+uint64_t Decode64Bit(absl::Span<const char> *buf) {
+ uint64_t value = 0;
+ size_t s = 0;
+ while (s < buf->size()) {
+ value |= static_cast<uint64_t>(static_cast<unsigned char>((*buf)[s]))
+ << 8 * s;
+ if (++s == sizeof(value)) break;
+ }
+ buf->remove_prefix(s);
+ return value;
+}
+
+uint32_t Decode32Bit(absl::Span<const char> *buf) {
+ uint32_t value = 0;
+ size_t s = 0;
+ while (s < buf->size()) {
+ value |= static_cast<uint32_t>(static_cast<unsigned char>((*buf)[s]))
+ << 8 * s;
+ if (++s == sizeof(value)) break;
+ }
+ buf->remove_prefix(s);
+ return value;
+}
+} // namespace
+
+bool ProtoField::DecodeFrom(absl::Span<const char> *data) {
+ if (data->empty()) return false;
+ const uint64_t tag_type = DecodeVarint(data);
+ tag_ = tag_type >> 3;
+ type_ = static_cast<WireType>(tag_type & 0x07);
+ switch (type_) {
+ case WireType::kVarint:
+ value_ = DecodeVarint(data);
+ break;
+ case WireType::k64Bit:
+ value_ = Decode64Bit(data);
+ break;
+ case WireType::kLengthDelimited: {
+ value_ = DecodeVarint(data);
+ data_ = data->subspan(
+ 0, static_cast<size_t>(std::min<uint64_t>(value_, data->size())));
+ data->remove_prefix(data_.size());
+ break;
+ }
+ case WireType::k32Bit:
+ value_ = Decode32Bit(data);
+ break;
+ }
+ return true;
+}
+
+} // namespace log_internal
+ABSL_NAMESPACE_END
+} // namespace absl
diff --git a/absl/log/internal/proto.h b/absl/log/internal/proto.h
new file mode 100644
index 0000000..c8d14ac
--- /dev/null
+++ b/absl/log/internal/proto.h
@@ -0,0 +1,288 @@
+// Copyright 2020 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// -----------------------------------------------------------------------------
+// File: internal/proto.h
+// -----------------------------------------------------------------------------
+//
+// Declares functions for serializing and deserializing data to and from memory
+// buffers in protocol buffer wire format. This library takes no steps to
+// ensure that the encoded data matches with any message specification.
+
+#ifndef ABSL_LOG_INTERNAL_PROTO_H_
+#define ABSL_LOG_INTERNAL_PROTO_H_
+
+#include <cstddef>
+#include <cstdint>
+#include <limits>
+
+#include "absl/base/attributes.h"
+#include "absl/base/casts.h"
+#include "absl/base/config.h"
+#include "absl/strings/string_view.h"
+#include "absl/types/span.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace log_internal {
+
+// absl::Span<char> represents a view into the available space in a mutable
+// buffer during encoding. Encoding functions shrink the span as they go so
+// that the same view can be passed to a series of Encode functions. If the
+// data do not fit, nothing is encoded, the view is set to size zero (so that
+// all subsequent encode calls fail), and false is returned. Otherwise true is
+// returned.
+
+// In particular, attempting to encode a series of data into an insufficient
+// buffer has consistent and efficient behavior without any caller-side error
+// checking. Individual values will be encoded in their entirety or not at all
+// (unless one of the `Truncate` functions is used). Once a value is omitted
+// because it does not fit, no subsequent values will be encoded to preserve
+// ordering; the decoded sequence will be a prefix of the original sequence.
+
+// There are two ways to encode a message-typed field:
+//
+// * Construct its contents in a separate buffer and use `EncodeBytes` to copy
+// it into the primary buffer with type, tag, and length.
+// * Use `EncodeMessageStart` to write type and tag fields and reserve space for
+// the length field, then encode the contents directly into the buffer, then
+// use `EncodeMessageLength` to write the actual length into the reserved
+// bytes. This works fine if the actual length takes fewer bytes to encode
+// than were reserved, although you don't get your extra bytes back.
+// This approach will always produce a valid encoding, but your protocol may
+// require that the whole message field by omitted if the buffer is too small
+// to contain all desired subfields. In this case, operate on a copy of the
+// buffer view and assign back only if everything fit, i.e. if the last
+// `Encode` call returned true.
+
+// Encodes the specified integer as a varint field and returns true if it fits.
+// Used for int32_t, int64_t, uint32_t, uint64_t, bool, and enum field types.
+// Consumes up to kMaxVarintSize * 2 bytes (20).
+bool EncodeVarint(uint64_t tag, uint64_t value, absl::Span<char> *buf);
+inline bool EncodeVarint(uint64_t tag, int64_t value, absl::Span<char> *buf) {
+ return EncodeVarint(tag, static_cast<uint64_t>(value), buf);
+}
+inline bool EncodeVarint(uint64_t tag, uint32_t value, absl::Span<char> *buf) {
+ return EncodeVarint(tag, static_cast<uint64_t>(value), buf);
+}
+inline bool EncodeVarint(uint64_t tag, int32_t value, absl::Span<char> *buf) {
+ return EncodeVarint(tag, static_cast<uint64_t>(value), buf);
+}
+
+// Encodes the specified integer as a varint field using ZigZag encoding and
+// returns true if it fits.
+// Used for sint32 and sint64 field types.
+// Consumes up to kMaxVarintSize * 2 bytes (20).
+inline bool EncodeVarintZigZag(uint64_t tag, int64_t value,
+ absl::Span<char> *buf) {
+ if (value < 0)
+ return EncodeVarint(tag, 2 * static_cast<uint64_t>(-(value + 1)) + 1, buf);
+ return EncodeVarint(tag, 2 * static_cast<uint64_t>(value), buf);
+}
+
+// Encodes the specified integer as a 64-bit field and returns true if it fits.
+// Used for fixed64 and sfixed64 field types.
+// Consumes up to kMaxVarintSize + 8 bytes (18).
+bool Encode64Bit(uint64_t tag, uint64_t value, absl::Span<char> *buf);
+inline bool Encode64Bit(uint64_t tag, int64_t value, absl::Span<char> *buf) {
+ return Encode64Bit(tag, static_cast<uint64_t>(value), buf);
+}
+inline bool Encode64Bit(uint64_t tag, uint32_t value, absl::Span<char> *buf) {
+ return Encode64Bit(tag, static_cast<uint64_t>(value), buf);
+}
+inline bool Encode64Bit(uint64_t tag, int32_t value, absl::Span<char> *buf) {
+ return Encode64Bit(tag, static_cast<uint64_t>(value), buf);
+}
+
+// Encodes the specified double as a 64-bit field and returns true if it fits.
+// Used for double field type.
+// Consumes up to kMaxVarintSize + 8 bytes (18).
+inline bool EncodeDouble(uint64_t tag, double value, absl::Span<char> *buf) {
+ return Encode64Bit(tag, absl::bit_cast<uint64_t>(value), buf);
+}
+
+// Encodes the specified integer as a 32-bit field and returns true if it fits.
+// Used for fixed32 and sfixed32 field types.
+// Consumes up to kMaxVarintSize + 4 bytes (14).
+bool Encode32Bit(uint64_t tag, uint32_t value, absl::Span<char> *buf);
+inline bool Encode32Bit(uint64_t tag, int32_t value, absl::Span<char> *buf) {
+ return Encode32Bit(tag, static_cast<uint32_t>(value), buf);
+}
+
+// Encodes the specified float as a 32-bit field and returns true if it fits.
+// Used for float field type.
+// Consumes up to kMaxVarintSize + 4 bytes (14).
+inline bool EncodeFloat(uint64_t tag, float value, absl::Span<char> *buf) {
+ return Encode32Bit(tag, absl::bit_cast<uint32_t>(value), buf);
+}
+
+// Encodes the specified bytes as a length-delimited field and returns true if
+// they fit.
+// Used for string, bytes, message, and packed-repeated field type.
+// Consumes up to kMaxVarintSize * 2 + value.size() bytes (20 + value.size()).
+bool EncodeBytes(uint64_t tag, absl::Span<const char> value,
+ absl::Span<char> *buf);
+
+// Encodes as many of the specified bytes as will fit as a length-delimited
+// field and returns true as long as the field header (`tag_type` and `length`)
+// fits.
+// Used for string, bytes, message, and packed-repeated field type.
+// Consumes up to kMaxVarintSize * 2 + value.size() bytes (20 + value.size()).
+bool EncodeBytesTruncate(uint64_t tag, absl::Span<const char> value,
+ absl::Span<char> *buf);
+
+// Encodes the specified string as a length-delimited field and returns true if
+// it fits.
+// Used for string, bytes, message, and packed-repeated field type.
+// Consumes up to kMaxVarintSize * 2 + value.size() bytes (20 + value.size()).
+inline bool EncodeString(uint64_t tag, absl::string_view value,
+ absl::Span<char> *buf) {
+ return EncodeBytes(tag, value, buf);
+}
+
+// Encodes as much of the specified string as will fit as a length-delimited
+// field and returns true as long as the field header (`tag_type` and `length`)
+// fits.
+// Used for string, bytes, message, and packed-repeated field type.
+// Consumes up to kMaxVarintSize * 2 + value.size() bytes (20 + value.size()).
+inline bool EncodeStringTruncate(uint64_t tag, absl::string_view value,
+ absl::Span<char> *buf) {
+ return EncodeBytesTruncate(tag, value, buf);
+}
+
+// Encodes the header for a length-delimited field containing up to `max_size`
+// bytes or the number remaining in the buffer, whichever is less. If the
+// header fits, a non-nullptr `Span` is returned; this must be passed to
+// `EncodeMessageLength` after all contents are encoded to finalize the length
+// field. If the header does not fit, a nullptr `Span` is returned which is
+// safe to pass to `EncodeMessageLength` but need not be.
+// Used for string, bytes, message, and packed-repeated field type.
+// Consumes up to kMaxVarintSize * 2 bytes (20).
+ABSL_MUST_USE_RESULT absl::Span<char> EncodeMessageStart(uint64_t tag,
+ uint64_t max_size,
+ absl::Span<char> *buf);
+
+// Finalizes the length field in `msg` so that it encompasses all data encoded
+// since the call to `EncodeMessageStart` which returned `msg`. Does nothing if
+// `msg` is a `nullptr` `Span`.
+void EncodeMessageLength(absl::Span<char> msg, const absl::Span<char> *buf);
+
+enum class WireType : uint64_t {
+ kVarint = 0,
+ k64Bit = 1,
+ kLengthDelimited = 2,
+ k32Bit = 5,
+};
+
+constexpr size_t VarintSize(uint64_t value) {
+ return value < 128 ? 1 : 1 + VarintSize(value >> 7);
+}
+constexpr size_t MinVarintSize() {
+ return VarintSize((std::numeric_limits<uint64_t>::min)());
+}
+constexpr size_t MaxVarintSize() {
+ return VarintSize((std::numeric_limits<uint64_t>::max)());
+}
+
+constexpr uint64_t MaxVarintForSize(size_t size) {
+ return size >= 10 ? (std::numeric_limits<uint64_t>::max)()
+ : (static_cast<uint64_t>(1) << size * 7) - 1;
+}
+
+// `BufferSizeFor` returns a number of bytes guaranteed to be sufficient to
+// store encoded fields of the specified WireTypes regardless of tag numbers and
+// data values. This only makes sense for `WireType::kLengthDelimited` if you
+// add in the length of the contents yourself, e.g. for string and bytes fields
+// by adding the lengths of any encoded strings to the return value or for
+// submessage fields by enumerating the fields you may encode into their
+// contents.
+constexpr size_t BufferSizeFor() { return 0; }
+template <typename... T>
+constexpr size_t BufferSizeFor(WireType type, T... tail) {
+ // tag_type + data + ...
+ return MaxVarintSize() +
+ (type == WireType::kVarint ? MaxVarintSize() : //
+ type == WireType::k64Bit ? 8 : //
+ type == WireType::k32Bit ? 4 : MaxVarintSize()) + //
+ BufferSizeFor(tail...);
+}
+
+// absl::Span<const char> represents a view into the un-processed space in a
+// buffer during decoding. Decoding functions shrink the span as they go so
+// that the same view can be decoded iteratively until all data are processed.
+// In general, if the buffer is exhausted but additional bytes are expected by
+// the decoder, it will return values as if the additional bytes were zeros.
+// Length-delimited fields are an exception - if the encoded length field
+// indicates more data bytes than are available in the buffer, the `bytes_value`
+// and `string_value` accessors will return truncated views.
+
+class ProtoField final {
+ public:
+ // Consumes bytes from `data` and returns true if there were any bytes to
+ // decode.
+ bool DecodeFrom(absl::Span<const char> *data);
+ uint64_t tag() const { return tag_; }
+ WireType type() const { return type_; }
+
+ // These value accessors will return nonsense if the data were not encoded in
+ // the corresponding wiretype from the corresponding C++ (or other language)
+ // type.
+
+ double double_value() const { return absl::bit_cast<double>(value_); }
+ float float_value() const {
+ return absl::bit_cast<float>(static_cast<uint32_t>(value_));
+ }
+ int32_t int32_value() const { return static_cast<int32_t>(value_); }
+ int64_t int64_value() const { return static_cast<int64_t>(value_); }
+ int32_t sint32_value() const {
+ if (value_ % 2) return static_cast<int32_t>(0 - ((value_ - 1) / 2) - 1);
+ return static_cast<int32_t>(value_ / 2);
+ }
+ int64_t sint64_value() const {
+ if (value_ % 2) return 0 - ((value_ - 1) / 2) - 1;
+ return value_ / 2;
+ }
+ uint32_t uint32_value() const { return static_cast<uint32_t>(value_); }
+ uint64_t uint64_value() const { return value_; }
+ bool bool_value() const { return value_ != 0; }
+ // To decode an enum, call int32_value() and cast to the appropriate type.
+ // Note that the official C++ proto compiler treats enum fields with values
+ // that do not correspond to a defined enumerator as unknown fields.
+
+ // To decode fields within a submessage field, call
+ // `DecodeNextField(field.BytesValue())`.
+ absl::Span<const char> bytes_value() const { return data_; }
+ absl::string_view string_value() const {
+ const auto data = bytes_value();
+ return absl::string_view(data.data(), data.size());
+ }
+ // Returns the encoded length of a length-delimited field. This equals
+ // `bytes_value().size()` except when the latter has been truncated due to
+ // buffer underrun.
+ uint64_t encoded_length() const { return value_; }
+
+ private:
+ uint64_t tag_;
+ WireType type_;
+ // For `kTypeVarint`, `kType64Bit`, and `kType32Bit`, holds the decoded value.
+ // For `kTypeLengthDelimited`, holds the decoded length.
+ uint64_t value_;
+ absl::Span<const char> data_;
+};
+
+} // namespace log_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_LOG_INTERNAL_PROTO_H_
diff --git a/absl/log/internal/stderr_log_sink_test.cc b/absl/log/internal/stderr_log_sink_test.cc
new file mode 100644
index 0000000..763690d
--- /dev/null
+++ b/absl/log/internal/stderr_log_sink_test.cc
@@ -0,0 +1,105 @@
+//
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <stdlib.h>
+
+#include <string>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/base/attributes.h"
+#include "absl/base/log_severity.h"
+#include "absl/log/globals.h"
+#include "absl/log/internal/test_helpers.h"
+#include "absl/log/log.h"
+
+namespace {
+using ::testing::AllOf;
+using ::testing::HasSubstr;
+
+auto* test_env ABSL_ATTRIBUTE_UNUSED = ::testing::AddGlobalTestEnvironment(
+ new absl::log_internal::LogTestEnvironment);
+
+MATCHER_P2(HasSubstrTimes, substr, expected_count, "") {
+ int count = 0;
+ std::string::size_type pos = 0;
+ std::string needle(substr);
+ while ((pos = arg.find(needle, pos)) != std::string::npos) {
+ ++count;
+ pos += needle.size();
+ }
+
+ return count == expected_count;
+}
+
+TEST(StderrLogSinkDeathTest, InfoMessagesInStderr) {
+ EXPECT_DEATH_IF_SUPPORTED(
+ {
+ absl::SetStderrThreshold(absl::LogSeverityAtLeast::kInfo);
+ LOG(INFO) << "INFO message";
+ exit(1);
+ },
+ "INFO message");
+}
+
+TEST(StderrLogSinkDeathTest, WarningMessagesInStderr) {
+ EXPECT_DEATH_IF_SUPPORTED(
+ {
+ absl::SetStderrThreshold(absl::LogSeverityAtLeast::kInfo);
+ LOG(WARNING) << "WARNING message";
+ exit(1);
+ },
+ "WARNING message");
+}
+
+TEST(StderrLogSinkDeathTest, ErrorMessagesInStderr) {
+ EXPECT_DEATH_IF_SUPPORTED(
+ {
+ absl::SetStderrThreshold(absl::LogSeverityAtLeast::kInfo);
+ LOG(ERROR) << "ERROR message";
+ exit(1);
+ },
+ "ERROR message");
+}
+
+TEST(StderrLogSinkDeathTest, FatalMessagesInStderr) {
+ char message[] = "FATAL message";
+ char stacktrace[] = "*** Check failure stack trace: ***";
+
+ int expected_count = 1;
+
+ EXPECT_DEATH_IF_SUPPORTED(
+ {
+ absl::SetStderrThreshold(absl::LogSeverityAtLeast::kInfo);
+ LOG(FATAL) << message;
+ },
+ AllOf(HasSubstrTimes(message, expected_count), HasSubstr(stacktrace)));
+}
+
+TEST(StderrLogSinkDeathTest, SecondaryFatalMessagesInStderr) {
+ auto MessageGen = []() -> std::string {
+ LOG(FATAL) << "Internal failure";
+ return "External failure";
+ };
+
+ EXPECT_DEATH_IF_SUPPORTED(
+ {
+ absl::SetStderrThreshold(absl::LogSeverityAtLeast::kInfo);
+ LOG(FATAL) << MessageGen();
+ },
+ "Internal failure");
+}
+
+} // namespace
diff --git a/absl/log/internal/strip.h b/absl/log/internal/strip.h
new file mode 100644
index 0000000..f8d2786
--- /dev/null
+++ b/absl/log/internal/strip.h
@@ -0,0 +1,72 @@
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// -----------------------------------------------------------------------------
+// File: log/internal/strip.h
+// -----------------------------------------------------------------------------
+//
+
+#ifndef ABSL_LOG_INTERNAL_STRIP_H_
+#define ABSL_LOG_INTERNAL_STRIP_H_
+
+#include "absl/base/log_severity.h"
+#include "absl/log/internal/log_message.h"
+#include "absl/log/internal/nullstream.h"
+
+// `ABSL_LOGGING_INTERNAL_LOG_*` evaluates to a temporary `LogMessage` object or
+// to a related object with a compatible API but different behavior. This set
+// of defines comes in three flavors: vanilla, plus two variants that strip some
+// logging in subtly different ways for subtly different reasons (see below).
+#if defined(STRIP_LOG) && STRIP_LOG
+#define ABSL_LOGGING_INTERNAL_LOG_INFO ::absl::log_internal::NullStream()
+#define ABSL_LOGGING_INTERNAL_LOG_WARNING ::absl::log_internal::NullStream()
+#define ABSL_LOGGING_INTERNAL_LOG_ERROR ::absl::log_internal::NullStream()
+#define ABSL_LOGGING_INTERNAL_LOG_FATAL ::absl::log_internal::NullStreamFatal()
+#define ABSL_LOGGING_INTERNAL_LOG_QFATAL ::absl::log_internal::NullStreamFatal()
+#define ABSL_LOGGING_INTERNAL_LOG_DFATAL \
+ ::absl::log_internal::NullStreamMaybeFatal(::absl::kLogDebugFatal)
+#define ABSL_LOGGING_INTERNAL_LOG_LEVEL(severity) \
+ ::absl::log_internal::NullStreamMaybeFatal(absl_log_internal_severity)
+#define ABSL_LOG_INTERNAL_CHECK(failure_message) ABSL_LOGGING_INTERNAL_LOG_FATAL
+#define ABSL_LOG_INTERNAL_QCHECK(failure_message) \
+ ABSL_LOGGING_INTERNAL_LOG_QFATAL
+#else // !defined(STRIP_LOG) || !STRIP_LOG
+#define ABSL_LOGGING_INTERNAL_LOG_INFO \
+ ::absl::log_internal::LogMessage( \
+ __FILE__, __LINE__, ::absl::log_internal::LogMessage::InfoTag{})
+#define ABSL_LOGGING_INTERNAL_LOG_WARNING \
+ ::absl::log_internal::LogMessage( \
+ __FILE__, __LINE__, ::absl::log_internal::LogMessage::WarningTag{})
+#define ABSL_LOGGING_INTERNAL_LOG_ERROR \
+ ::absl::log_internal::LogMessage( \
+ __FILE__, __LINE__, ::absl::log_internal::LogMessage::ErrorTag{})
+#define ABSL_LOGGING_INTERNAL_LOG_FATAL \
+ ::absl::log_internal::LogMessageFatal(__FILE__, __LINE__)
+#define ABSL_LOGGING_INTERNAL_LOG_QFATAL \
+ ::absl::log_internal::LogMessageQuietlyFatal(__FILE__, __LINE__)
+#define ABSL_LOGGING_INTERNAL_LOG_DFATAL \
+ ::absl::log_internal::LogMessage(__FILE__, __LINE__, ::absl::kLogDebugFatal)
+#define ABSL_LOGGING_INTERNAL_LOG_LEVEL(severity) \
+ ::absl::log_internal::LogMessage(__FILE__, __LINE__, \
+ absl_log_internal_severity)
+// These special cases dispatch to special-case constructors that allow us to
+// avoid an extra function call and shrink non-LTO binaries by a percent or so.
+#define ABSL_LOG_INTERNAL_CHECK(failure_message) \
+ ::absl::log_internal::LogMessageFatal(__FILE__, __LINE__, failure_message)
+#define ABSL_LOG_INTERNAL_QCHECK(failure_message) \
+ ::absl::log_internal::LogMessageQuietlyFatal(__FILE__, __LINE__, \
+ failure_message)
+#endif // !defined(STRIP_LOG) || !STRIP_LOG
+
+#endif // ABSL_LOG_INTERNAL_STRIP_H_
diff --git a/absl/log/internal/structured.h b/absl/log/internal/structured.h
new file mode 100644
index 0000000..5223dbc
--- /dev/null
+++ b/absl/log/internal/structured.h
@@ -0,0 +1,58 @@
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// -----------------------------------------------------------------------------
+// File: log/internal/structured.h
+// -----------------------------------------------------------------------------
+
+#ifndef ABSL_LOG_INTERNAL_STRUCTURED_H_
+#define ABSL_LOG_INTERNAL_STRUCTURED_H_
+
+#include <ostream>
+
+#include "absl/base/config.h"
+#include "absl/log/internal/log_message.h"
+#include "absl/strings/string_view.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace log_internal {
+
+class ABSL_MUST_USE_RESULT AsLiteralImpl final {
+ public:
+ explicit AsLiteralImpl(absl::string_view str) : str_(str) {}
+ AsLiteralImpl(const AsLiteralImpl&) = default;
+ AsLiteralImpl& operator=(const AsLiteralImpl&) = default;
+
+ private:
+ absl::string_view str_;
+
+ friend std::ostream& operator<<(std::ostream& os, AsLiteralImpl as_literal) {
+ return os << as_literal.str_;
+ }
+ void AddToMessage(log_internal::LogMessage& m) {
+ m.CopyToEncodedBuffer<log_internal::LogMessage::StringType::kLiteral>(str_);
+ }
+ friend log_internal::LogMessage& operator<<(log_internal::LogMessage& m,
+ AsLiteralImpl as_literal) {
+ as_literal.AddToMessage(m);
+ return m;
+ }
+};
+
+} // namespace log_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_LOG_INTERNAL_STRUCTURED_H_
diff --git a/absl/log/internal/test_actions.cc b/absl/log/internal/test_actions.cc
new file mode 100644
index 0000000..bdfd637
--- /dev/null
+++ b/absl/log/internal/test_actions.cc
@@ -0,0 +1,75 @@
+//
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/log/internal/test_actions.h"
+
+#include <cassert>
+#include <iostream>
+#include <string>
+#include <type_traits>
+
+#include "absl/base/attributes.h"
+#include "absl/base/config.h"
+#include "absl/strings/escaping.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/string_view.h"
+#include "absl/time/time.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace log_internal {
+
+void WriteToStderrWithFilename::operator()(const absl::LogEntry& entry) const {
+ std::cerr << message << " (file: " << entry.source_filename() << ")\n";
+}
+
+void WriteEntryToStderr::operator()(const absl::LogEntry& entry) const {
+ if (!message.empty()) std::cerr << message << "\n";
+
+ const std::string source_filename = absl::CHexEscape(entry.source_filename());
+ const std::string source_basename = absl::CHexEscape(entry.source_basename());
+ const std::string text_message = absl::CHexEscape(entry.text_message());
+ const std::string encoded_message = absl::CHexEscape(entry.encoded_message());
+ std::string encoded_message_str;
+ std::cerr << "LogEntry{\n" //
+ << " source_filename: \"" << source_filename << "\"\n" //
+ << " source_basename: \"" << source_basename << "\"\n" //
+ << " source_line: " << entry.source_line() << "\n" //
+ << " prefix: " << (entry.prefix() ? "true\n" : "false\n") //
+ << " log_severity: " << entry.log_severity() << "\n" //
+ << " timestamp: " << entry.timestamp() << "\n" //
+ << " text_message: \"" << text_message << "\"\n" //
+ << " verbosity: " << entry.verbosity() << "\n" //
+ << " encoded_message (raw): \"" << encoded_message << "\"\n" //
+ << encoded_message_str //
+ << "}\n";
+}
+
+void WriteEntryToStderr::operator()(absl::LogSeverity severity,
+ absl::string_view filename,
+ absl::string_view log_message) const {
+ if (!message.empty()) std::cerr << message << "\n";
+ const std::string source_filename = absl::CHexEscape(filename);
+ const std::string text_message = absl::CHexEscape(log_message);
+ std::cerr << "LogEntry{\n" //
+ << " source_filename: \"" << source_filename << "\"\n" //
+ << " log_severity: " << severity << "\n" //
+ << " text_message: \"" << text_message << "\"\n" //
+ << "}\n";
+}
+
+} // namespace log_internal
+ABSL_NAMESPACE_END
+} // namespace absl
diff --git a/absl/log/internal/test_actions.h b/absl/log/internal/test_actions.h
new file mode 100644
index 0000000..649a050
--- /dev/null
+++ b/absl/log/internal/test_actions.h
@@ -0,0 +1,90 @@
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// -----------------------------------------------------------------------------
+// File: log/internal/test_actions.h
+// -----------------------------------------------------------------------------
+//
+// This file declares Googletest's actions used in the Abseil Logging library
+// unit tests.
+
+#ifndef ABSL_LOG_INTERNAL_TEST_ACTIONS_H_
+#define ABSL_LOG_INTERNAL_TEST_ACTIONS_H_
+
+#include <iostream>
+#include <ostream>
+#include <string>
+
+#include "absl/base/config.h"
+#include "absl/base/log_severity.h"
+#include "absl/log/log_entry.h"
+#include "absl/strings/string_view.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace log_internal {
+
+// These actions are used by the child process in a death test.
+//
+// Expectations set in the child cannot cause test failure in the parent
+// directly. Instead, the child can use these actions with
+// `EXPECT_CALL`/`WillOnce` and `ON_CALL`/`WillByDefault` (for unexpected calls)
+// to write messages to stderr that the parent can match against.
+struct WriteToStderr final {
+ explicit WriteToStderr(absl::string_view m) : message(m) {}
+ std::string message;
+
+ template <typename... Args>
+ void operator()(const Args&...) const {
+ std::cerr << message << std::endl;
+ }
+};
+
+struct WriteToStderrWithFilename final {
+ explicit WriteToStderrWithFilename(absl::string_view m) : message(m) {}
+
+ std::string message;
+
+ void operator()(const absl::LogEntry& entry) const;
+};
+
+struct WriteEntryToStderr final {
+ explicit WriteEntryToStderr(absl::string_view m) : message(m) {}
+
+ std::string message = "";
+
+ void operator()(const absl::LogEntry& entry) const;
+ void operator()(absl::LogSeverity, absl::string_view,
+ absl::string_view) const;
+};
+
+// See the documentation for `DeathTestValidateExpectations` above.
+// `DeathTestExpectedLogging` should be used once in a given death test, and the
+// applicable severity level is the one that should be passed to
+// `DeathTestValidateExpectations`.
+inline WriteEntryToStderr DeathTestExpectedLogging() {
+ return WriteEntryToStderr{"Mock received expected entry:"};
+}
+
+// `DeathTestUnexpectedLogging` should be used zero or more times to mark
+// messages that should not hit the logs as the process dies.
+inline WriteEntryToStderr DeathTestUnexpectedLogging() {
+ return WriteEntryToStderr{"Mock received unexpected entry:"};
+}
+
+} // namespace log_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_LOG_INTERNAL_TEST_ACTIONS_H_
diff --git a/absl/log/internal/test_helpers.cc b/absl/log/internal/test_helpers.cc
new file mode 100644
index 0000000..bfcc967
--- /dev/null
+++ b/absl/log/internal/test_helpers.cc
@@ -0,0 +1,82 @@
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+#include "absl/log/internal/test_helpers.h"
+
+#ifdef __Fuchsia__
+#include <zircon/syscalls.h>
+#endif
+
+#include "gtest/gtest.h"
+#include "absl/base/config.h"
+#include "absl/base/log_severity.h"
+#include "absl/log/globals.h"
+#include "absl/log/initialize.h"
+#include "absl/log/internal/globals.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace log_internal {
+
+// Returns false if the specified severity level is disabled by
+// `ABSL_MIN_LOG_LEVEL` or `absl::MinLogLevel()`.
+bool LoggingEnabledAt(absl::LogSeverity severity) {
+ return severity >= kAbslMinLogLevel && severity >= absl::MinLogLevel();
+}
+
+// -----------------------------------------------------------------------------
+// Googletest Death Test Predicates
+// -----------------------------------------------------------------------------
+
+#if GTEST_HAS_DEATH_TEST
+
+bool DiedOfFatal(int exit_status) {
+#if defined(_WIN32)
+ // Depending on NDEBUG and (configuration?) MSVC's abort either results
+ // in error code 3 (SIGABRT) or error code 0x80000003 (breakpoint
+ // triggered).
+ return ::testing::ExitedWithCode(3)(exit_status & 0x7fffffff);
+#elif defined(__Fuchsia__)
+ // The Fuchsia death test implementation kill()'s the process when it detects
+ // an exception, so it should exit with the corresponding code. See
+ // FuchsiaDeathTest::Wait().
+ return ::testing::ExitedWithCode(ZX_TASK_RETCODE_SYSCALL_KILL)(exit_status);
+#elif defined(__ANDROID__) && defined(__aarch64__)
+ // These are all run under a qemu config that eats died-due-to-signal exit
+ // statuses.
+ return true;
+#else
+ return ::testing::KilledBySignal(SIGABRT)(exit_status);
+#endif
+}
+
+bool DiedOfQFatal(int exit_status) {
+ return ::testing::ExitedWithCode(1)(exit_status);
+}
+
+#endif
+
+// -----------------------------------------------------------------------------
+// Helper for Log initialization in test
+// -----------------------------------------------------------------------------
+
+void LogTestEnvironment::SetUp() {
+ if (!absl::log_internal::IsInitialized()) {
+ absl::InitializeLog();
+ }
+}
+
+} // namespace log_internal
+ABSL_NAMESPACE_END
+} // namespace absl
diff --git a/absl/log/internal/test_helpers.h b/absl/log/internal/test_helpers.h
new file mode 100644
index 0000000..714bc7b
--- /dev/null
+++ b/absl/log/internal/test_helpers.h
@@ -0,0 +1,71 @@
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// -----------------------------------------------------------------------------
+// File: log/internal/test_helpers.h
+// -----------------------------------------------------------------------------
+//
+// This file declares testing helpers for the logging library.
+
+#ifndef ABSL_LOG_INTERNAL_TEST_HELPERS_H_
+#define ABSL_LOG_INTERNAL_TEST_HELPERS_H_
+
+#include "gtest/gtest.h"
+#include "absl/base/config.h"
+#include "absl/base/log_severity.h"
+#include "absl/log/globals.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace log_internal {
+
+// `ABSL_MIN_LOG_LEVEL` can't be used directly since it is not always defined.
+constexpr auto kAbslMinLogLevel =
+#ifdef ABSL_MIN_LOG_LEVEL
+ static_cast<absl::LogSeverityAtLeast>(ABSL_MIN_LOG_LEVEL);
+#else
+ absl::LogSeverityAtLeast::kInfo;
+#endif
+
+// Returns false if the specified severity level is disabled by
+// `ABSL_MIN_LOG_LEVEL` or `absl::MinLogLevel()`.
+bool LoggingEnabledAt(absl::LogSeverity severity);
+
+// -----------------------------------------------------------------------------
+// Googletest Death Test Predicates
+// -----------------------------------------------------------------------------
+
+#if GTEST_HAS_DEATH_TEST
+
+bool DiedOfFatal(int exit_status);
+bool DiedOfQFatal(int exit_status);
+
+#endif
+
+// -----------------------------------------------------------------------------
+// Helper for Log initialization in test
+// -----------------------------------------------------------------------------
+
+class LogTestEnvironment : public ::testing::Environment {
+ public:
+ ~LogTestEnvironment() override = default;
+
+ void SetUp() override;
+};
+
+} // namespace log_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_LOG_INTERNAL_TEST_HELPERS_H_
diff --git a/absl/log/internal/test_matchers.cc b/absl/log/internal/test_matchers.cc
new file mode 100644
index 0000000..8c6515c
--- /dev/null
+++ b/absl/log/internal/test_matchers.cc
@@ -0,0 +1,217 @@
+//
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/log/internal/test_matchers.h"
+
+#include <ostream>
+#include <sstream>
+#include <string>
+#include <type_traits>
+#include <utility>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/base/attributes.h"
+#include "absl/base/config.h"
+#include "absl/log/internal/test_helpers.h"
+#include "absl/strings/string_view.h"
+#include "absl/time/clock.h"
+#include "absl/time/time.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace log_internal {
+namespace {
+using ::testing::_;
+using ::testing::AllOf;
+using ::testing::Ge;
+using ::testing::HasSubstr;
+using ::testing::MakeMatcher;
+using ::testing::Matcher;
+using ::testing::MatcherInterface;
+using ::testing::MatchResultListener;
+using ::testing::Not;
+using ::testing::Property;
+using ::testing::ResultOf;
+using ::testing::Truly;
+
+class AsStringImpl final
+ : public MatcherInterface<absl::string_view> {
+ public:
+ explicit AsStringImpl(
+ const Matcher<const std::string&>& str_matcher)
+ : str_matcher_(str_matcher) {}
+ bool MatchAndExplain(
+ absl::string_view actual,
+ MatchResultListener* listener) const override {
+ return str_matcher_.MatchAndExplain(std::string(actual), listener);
+ }
+ void DescribeTo(std::ostream* os) const override {
+ return str_matcher_.DescribeTo(os);
+ }
+
+ void DescribeNegationTo(std::ostream* os) const override {
+ return str_matcher_.DescribeNegationTo(os);
+ }
+
+ private:
+ const Matcher<const std::string&> str_matcher_;
+};
+
+class MatchesOstreamImpl final
+ : public MatcherInterface<absl::string_view> {
+ public:
+ explicit MatchesOstreamImpl(std::string expected)
+ : expected_(std::move(expected)) {}
+ bool MatchAndExplain(absl::string_view actual,
+ MatchResultListener*) const override {
+ return actual == expected_;
+ }
+ void DescribeTo(std::ostream* os) const override {
+ *os << "matches the contents of the ostringstream, which are \""
+ << expected_ << "\"";
+ }
+
+ void DescribeNegationTo(std::ostream* os) const override {
+ *os << "does not match the contents of the ostringstream, which are \""
+ << expected_ << "\"";
+ }
+
+ private:
+ const std::string expected_;
+};
+} // namespace
+
+Matcher<absl::string_view> AsString(
+ const Matcher<const std::string&>& str_matcher) {
+ return MakeMatcher(new AsStringImpl(str_matcher));
+}
+
+Matcher<const absl::LogEntry&> SourceFilename(
+ const Matcher<absl::string_view>& source_filename) {
+ return Property("source_filename", &absl::LogEntry::source_filename,
+ source_filename);
+}
+
+Matcher<const absl::LogEntry&> SourceBasename(
+ const Matcher<absl::string_view>& source_basename) {
+ return Property("source_basename", &absl::LogEntry::source_basename,
+ source_basename);
+}
+
+Matcher<const absl::LogEntry&> SourceLine(
+ const Matcher<int>& source_line) {
+ return Property("source_line", &absl::LogEntry::source_line, source_line);
+}
+
+Matcher<const absl::LogEntry&> Prefix(
+ const Matcher<bool>& prefix) {
+ return Property("prefix", &absl::LogEntry::prefix, prefix);
+}
+
+Matcher<const absl::LogEntry&> LogSeverity(
+ const Matcher<absl::LogSeverity>& log_severity) {
+ return Property("log_severity", &absl::LogEntry::log_severity, log_severity);
+}
+
+Matcher<const absl::LogEntry&> Timestamp(
+ const Matcher<absl::Time>& timestamp) {
+ return Property("timestamp", &absl::LogEntry::timestamp, timestamp);
+}
+
+Matcher<const absl::LogEntry&> TimestampInMatchWindow() {
+ return Property("timestamp", &absl::LogEntry::timestamp,
+ AllOf(Ge(absl::Now()), Truly([](absl::Time arg) {
+ return arg <= absl::Now();
+ })));
+}
+
+Matcher<const absl::LogEntry&> ThreadID(
+ const Matcher<absl::LogEntry::tid_t>& tid) {
+ return Property("tid", &absl::LogEntry::tid, tid);
+}
+
+Matcher<const absl::LogEntry&> TextMessageWithPrefixAndNewline(
+ const Matcher<absl::string_view>&
+ text_message_with_prefix_and_newline) {
+ return Property("text_message_with_prefix_and_newline",
+ &absl::LogEntry::text_message_with_prefix_and_newline,
+ text_message_with_prefix_and_newline);
+}
+
+Matcher<const absl::LogEntry&> TextMessageWithPrefix(
+ const Matcher<absl::string_view>& text_message_with_prefix) {
+ return Property("text_message_with_prefix",
+ &absl::LogEntry::text_message_with_prefix,
+ text_message_with_prefix);
+}
+
+Matcher<const absl::LogEntry&> TextMessage(
+ const Matcher<absl::string_view>& text_message) {
+ return Property("text_message", &absl::LogEntry::text_message, text_message);
+}
+
+Matcher<const absl::LogEntry&> TextPrefix(
+ const Matcher<absl::string_view>& text_prefix) {
+ return ResultOf(
+ [](const absl::LogEntry& entry) {
+ absl::string_view msg = entry.text_message_with_prefix();
+ msg.remove_suffix(entry.text_message().size());
+ return msg;
+ },
+ text_prefix);
+}
+Matcher<const absl::LogEntry&> RawEncodedMessage(
+ const Matcher<absl::string_view>& raw_encoded_message) {
+ return Property("encoded_message", &absl::LogEntry::encoded_message,
+ raw_encoded_message);
+}
+
+Matcher<const absl::LogEntry&> Verbosity(
+ const Matcher<int>& verbosity) {
+ return Property("verbosity", &absl::LogEntry::verbosity, verbosity);
+}
+
+Matcher<const absl::LogEntry&> Stacktrace(
+ const Matcher<absl::string_view>& stacktrace) {
+ return Property("stacktrace", &absl::LogEntry::stacktrace, stacktrace);
+}
+
+Matcher<absl::string_view> MatchesOstream(
+ const std::ostringstream& stream) {
+ return MakeMatcher(new MatchesOstreamImpl(stream.str()));
+}
+
+// We need to validate what is and isn't logged as the process dies due to
+// `FATAL`, `QFATAL`, `CHECK`, etc., but assertions inside a death test
+// subprocess don't directly affect the pass/fail status of the parent process.
+// Instead, we use the mock actions `DeathTestExpectedLogging` and
+// `DeathTestUnexpectedLogging` to write specific phrases to `stderr` that we
+// can validate in the parent process using this matcher.
+Matcher<const std::string&> DeathTestValidateExpectations() {
+ if (log_internal::LoggingEnabledAt(absl::LogSeverity::kFatal)) {
+ return Matcher<const std::string&>(
+ AllOf(HasSubstr("Mock received expected entry"),
+ Not(HasSubstr("Mock received unexpected entry"))));
+ }
+ // If `FATAL` logging is disabled, neither message should have been written.
+ return Matcher<const std::string&>(
+ AllOf(Not(HasSubstr("Mock received expected entry")),
+ Not(HasSubstr("Mock received unexpected entry"))));
+}
+
+} // namespace log_internal
+ABSL_NAMESPACE_END
+} // namespace absl
diff --git a/absl/log/internal/test_matchers.h b/absl/log/internal/test_matchers.h
new file mode 100644
index 0000000..fc653a9
--- /dev/null
+++ b/absl/log/internal/test_matchers.h
@@ -0,0 +1,94 @@
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// -----------------------------------------------------------------------------
+// File: log/internal/test_matchers.h
+// -----------------------------------------------------------------------------
+//
+// This file declares Googletest's matchers used in the Abseil Logging library
+// unit tests.
+
+#ifndef ABSL_LOG_INTERNAL_TEST_MATCHERS_H_
+#define ABSL_LOG_INTERNAL_TEST_MATCHERS_H_
+
+#include <iosfwd>
+#include <sstream>
+#include <string>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/base/config.h"
+#include "absl/base/log_severity.h"
+#include "absl/log/internal/test_helpers.h"
+#include "absl/log/log_entry.h"
+#include "absl/strings/string_view.h"
+#include "absl/time/time.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace log_internal {
+// In some configurations, Googletest's string matchers (e.g.
+// `::testing::EndsWith`) need help to match `absl::string_view`.
+::testing::Matcher<absl::string_view> AsString(
+ const ::testing::Matcher<const std::string&>& str_matcher);
+
+// These matchers correspond to the components of `absl::LogEntry`.
+::testing::Matcher<const absl::LogEntry&> SourceFilename(
+ const ::testing::Matcher<absl::string_view>& source_filename);
+::testing::Matcher<const absl::LogEntry&> SourceBasename(
+ const ::testing::Matcher<absl::string_view>& source_basename);
+// Be careful with this one; multi-line statements using `__LINE__` evaluate
+// differently on different platforms. In particular, the MSVC implementation
+// of `EXPECT_DEATH` returns the line number of the macro expansion to all lines
+// within the code block that's expected to die.
+::testing::Matcher<const absl::LogEntry&> SourceLine(
+ const ::testing::Matcher<int>& source_line);
+::testing::Matcher<const absl::LogEntry&> Prefix(
+ const ::testing::Matcher<bool>& prefix);
+::testing::Matcher<const absl::LogEntry&> LogSeverity(
+ const ::testing::Matcher<absl::LogSeverity>& log_severity);
+::testing::Matcher<const absl::LogEntry&> Timestamp(
+ const ::testing::Matcher<absl::Time>& timestamp);
+// Matches if the `LogEntry`'s timestamp falls after the instantiation of this
+// matcher and before its execution, as is normal when used with EXPECT_CALL.
+::testing::Matcher<const absl::LogEntry&> TimestampInMatchWindow();
+::testing::Matcher<const absl::LogEntry&> ThreadID(
+ const ::testing::Matcher<absl::LogEntry::tid_t>&);
+::testing::Matcher<const absl::LogEntry&> TextMessageWithPrefixAndNewline(
+ const ::testing::Matcher<absl::string_view>&
+ text_message_with_prefix_and_newline);
+::testing::Matcher<const absl::LogEntry&> TextMessageWithPrefix(
+ const ::testing::Matcher<absl::string_view>& text_message_with_prefix);
+::testing::Matcher<const absl::LogEntry&> TextMessage(
+ const ::testing::Matcher<absl::string_view>& text_message);
+::testing::Matcher<const absl::LogEntry&> TextPrefix(
+ const ::testing::Matcher<absl::string_view>& text_prefix);
+::testing::Matcher<const absl::LogEntry&> Verbosity(
+ const ::testing::Matcher<int>& verbosity);
+::testing::Matcher<const absl::LogEntry&> Stacktrace(
+ const ::testing::Matcher<absl::string_view>& stacktrace);
+// Behaves as `Eq(stream.str())`, but produces better failure messages.
+::testing::Matcher<absl::string_view> MatchesOstream(
+ const std::ostringstream& stream);
+::testing::Matcher<const std::string&> DeathTestValidateExpectations();
+
+::testing::Matcher<const absl::LogEntry&> RawEncodedMessage(
+ const ::testing::Matcher<absl::string_view>& raw_encoded_message);
+#define ENCODED_MESSAGE(message_matcher) ::testing::_
+
+} // namespace log_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_LOG_INTERNAL_TEST_MATCHERS_H_
diff --git a/absl/log/internal/vlog_config.cc b/absl/log/internal/vlog_config.cc
new file mode 100644
index 0000000..b578850
--- /dev/null
+++ b/absl/log/internal/vlog_config.cc
@@ -0,0 +1,340 @@
+// Copyright 2022 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/log/internal/vlog_config.h"
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <atomic>
+#include <functional>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "absl/base/attributes.h"
+#include "absl/base/config.h"
+#include "absl/base/const_init.h"
+#include "absl/base/internal/spinlock.h"
+#include "absl/base/no_destructor.h"
+#include "absl/base/optimization.h"
+#include "absl/base/thread_annotations.h"
+#include "absl/log/internal/fnmatch.h"
+#include "absl/memory/memory.h"
+#include "absl/strings/numbers.h"
+#include "absl/strings/str_split.h"
+#include "absl/strings/string_view.h"
+#include "absl/strings/strip.h"
+#include "absl/synchronization/mutex.h"
+#include "absl/types/optional.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace log_internal {
+
+namespace {
+bool ModuleIsPath(absl::string_view module_pattern) {
+#ifdef _WIN32
+ return module_pattern.find_first_of("/\\") != module_pattern.npos;
+#else
+ return module_pattern.find('/') != module_pattern.npos;
+#endif
+}
+} // namespace
+
+bool VLogSite::SlowIsEnabled(int stale_v, int level) {
+ if (ABSL_PREDICT_TRUE(stale_v != kUninitialized)) {
+ // Because of the prerequisites to this function, we know that stale_v is
+ // either uninitialized or >= level. If it's not uninitialized, that means
+ // it must be >= level, thus we should log.
+ return true;
+ }
+ stale_v = log_internal::RegisterAndInitialize(this);
+ return ABSL_PREDICT_FALSE(stale_v >= level);
+}
+
+bool VLogSite::SlowIsEnabled0(int stale_v) { return SlowIsEnabled(stale_v, 0); }
+bool VLogSite::SlowIsEnabled1(int stale_v) { return SlowIsEnabled(stale_v, 1); }
+bool VLogSite::SlowIsEnabled2(int stale_v) { return SlowIsEnabled(stale_v, 2); }
+bool VLogSite::SlowIsEnabled3(int stale_v) { return SlowIsEnabled(stale_v, 3); }
+bool VLogSite::SlowIsEnabled4(int stale_v) { return SlowIsEnabled(stale_v, 4); }
+bool VLogSite::SlowIsEnabled5(int stale_v) { return SlowIsEnabled(stale_v, 5); }
+
+namespace {
+struct VModuleInfo final {
+ std::string module_pattern;
+ bool module_is_path; // i.e. it contains a path separator.
+ int vlog_level;
+
+ // Allocates memory.
+ VModuleInfo(absl::string_view module_pattern_arg, bool module_is_path_arg,
+ int vlog_level_arg)
+ : module_pattern(std::string(module_pattern_arg)),
+ module_is_path(module_is_path_arg),
+ vlog_level(vlog_level_arg) {}
+};
+
+// `mutex` guards all of the data structures that aren't lock-free.
+// To avoid problems with the heap checker which calls into `VLOG`, `mutex` must
+// be a `SpinLock` that prevents fiber scheduling instead of a `Mutex`.
+ABSL_CONST_INIT absl::base_internal::SpinLock mutex(
+ absl::kConstInit, absl::base_internal::SCHEDULE_KERNEL_ONLY);
+
+// `GetUpdateSitesMutex()` serializes updates to all of the sites (i.e. those in
+// `site_list_head`) themselves.
+absl::Mutex* GetUpdateSitesMutex() {
+ // Chromium requires no global destructors, so we can't use the
+ // absl::kConstInit idiom since absl::Mutex as a non-trivial destructor.
+ static absl::NoDestructor<absl::Mutex> update_sites_mutex ABSL_ACQUIRED_AFTER(
+ mutex);
+ return update_sites_mutex.get();
+}
+
+ABSL_CONST_INIT int global_v ABSL_GUARDED_BY(mutex) = 0;
+// `site_list_head` is the head of a singly-linked list. Traversal, insertion,
+// and reads are atomic, so no locks are required, but updates to existing
+// elements are guarded by `GetUpdateSitesMutex()`.
+ABSL_CONST_INIT std::atomic<VLogSite*> site_list_head{nullptr};
+ABSL_CONST_INIT std::vector<VModuleInfo>* vmodule_info ABSL_GUARDED_BY(mutex)
+ ABSL_PT_GUARDED_BY(mutex){nullptr};
+
+// Only used for lisp.
+ABSL_CONST_INIT std::vector<std::function<void()>>* update_callbacks
+ ABSL_GUARDED_BY(GetUpdateSitesMutex())
+ ABSL_PT_GUARDED_BY(GetUpdateSitesMutex()){nullptr};
+
+// Allocates memory.
+std::vector<VModuleInfo>& get_vmodule_info()
+ ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex) {
+ if (!vmodule_info) vmodule_info = new std::vector<VModuleInfo>;
+ return *vmodule_info;
+}
+
+// Does not allocate or take locks.
+int VLogLevel(absl::string_view file, const std::vector<VModuleInfo>* infos,
+ int current_global_v) {
+ // `infos` is null during a call to `VLOG` prior to setting `vmodule` (e.g. by
+ // parsing flags). We can't allocate in `VLOG`, so we treat null as empty
+ // here and press on.
+ if (!infos || infos->empty()) return current_global_v;
+ // Get basename for file
+ absl::string_view basename = file;
+ {
+ const size_t sep = basename.rfind('/');
+ if (sep != basename.npos) {
+ basename.remove_prefix(sep + 1);
+#ifdef _WIN32
+ } else {
+ const size_t sep = basename.rfind('\\');
+ if (sep != basename.npos) basename.remove_prefix(sep + 1);
+#endif
+ }
+ }
+
+ absl::string_view stem = file, stem_basename = basename;
+ {
+ const size_t sep = stem_basename.find('.');
+ if (sep != stem_basename.npos) {
+ stem.remove_suffix(stem_basename.size() - sep);
+ stem_basename.remove_suffix(stem_basename.size() - sep);
+ }
+ if (absl::ConsumeSuffix(&stem_basename, "-inl")) {
+ stem.remove_suffix(absl::string_view("-inl").size());
+ }
+ }
+ for (const auto& info : *infos) {
+ if (info.module_is_path) {
+ // If there are any slashes in the pattern, try to match the full
+ // name.
+ if (FNMatch(info.module_pattern, stem)) {
+ return info.vlog_level == kUseFlag ? current_global_v : info.vlog_level;
+ }
+ } else if (FNMatch(info.module_pattern, stem_basename)) {
+ return info.vlog_level == kUseFlag ? current_global_v : info.vlog_level;
+ }
+ }
+
+ return current_global_v;
+}
+
+// Allocates memory.
+int AppendVModuleLocked(absl::string_view module_pattern, int log_level)
+ ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex) {
+ for (const auto& info : get_vmodule_info()) {
+ if (FNMatch(info.module_pattern, module_pattern)) {
+ // This is a memory optimization to avoid storing patterns that will never
+ // match due to exit early semantics. Primarily optimized for our own unit
+ // tests.
+ return info.vlog_level;
+ }
+ }
+ bool module_is_path = ModuleIsPath(module_pattern);
+ get_vmodule_info().emplace_back(std::string(module_pattern), module_is_path,
+ log_level);
+ return global_v;
+}
+
+// Allocates memory.
+int PrependVModuleLocked(absl::string_view module_pattern, int log_level)
+ ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex) {
+ absl::optional<int> old_log_level;
+ for (const auto& info : get_vmodule_info()) {
+ if (FNMatch(info.module_pattern, module_pattern)) {
+ old_log_level = info.vlog_level;
+ break;
+ }
+ }
+ bool module_is_path = ModuleIsPath(module_pattern);
+ auto iter = get_vmodule_info().emplace(get_vmodule_info().cbegin(),
+ std::string(module_pattern),
+ module_is_path, log_level);
+
+ // This is a memory optimization to avoid storing patterns that will never
+ // match due to exit early semantics. Primarily optimized for our own unit
+ // tests.
+ get_vmodule_info().erase(
+ std::remove_if(++iter, get_vmodule_info().end(),
+ [module_pattern](const VModuleInfo& info) {
+ return FNMatch(info.module_pattern, module_pattern);
+ }),
+ get_vmodule_info().cend());
+ return old_log_level.value_or(global_v);
+}
+} // namespace
+
+int VLogLevel(absl::string_view file) ABSL_LOCKS_EXCLUDED(mutex) {
+ absl::base_internal::SpinLockHolder l(&mutex);
+ return VLogLevel(file, vmodule_info, global_v);
+}
+
+int RegisterAndInitialize(VLogSite* v) ABSL_LOCKS_EXCLUDED(mutex) {
+ // std::memory_order_seq_cst is overkill in this function, but given that this
+ // path is intended to be slow, it's not worth the brain power to relax that.
+ VLogSite* h = site_list_head.load(std::memory_order_seq_cst);
+
+ VLogSite* old = nullptr;
+ if (v->next_.compare_exchange_strong(old, h, std::memory_order_seq_cst,
+ std::memory_order_seq_cst)) {
+ // Multiple threads may attempt to register this site concurrently.
+ // By successfully setting `v->next` this thread commits to being *the*
+ // thread that installs `v` in the list.
+ while (!site_list_head.compare_exchange_weak(
+ h, v, std::memory_order_seq_cst, std::memory_order_seq_cst)) {
+ v->next_.store(h, std::memory_order_seq_cst);
+ }
+ }
+
+ int old_v = VLogSite::kUninitialized;
+ int new_v = VLogLevel(v->file_);
+ // No loop, if someone else set this, we should respect their evaluation of
+ // `VLogLevel`. This may mean we return a stale `v`, but `v` itself will
+ // always arrive at the freshest value. Otherwise, we could be writing a
+ // stale value and clobbering the fresher one.
+ if (v->v_.compare_exchange_strong(old_v, new_v, std::memory_order_seq_cst,
+ std::memory_order_seq_cst)) {
+ return new_v;
+ }
+ return old_v;
+}
+
+void UpdateVLogSites() ABSL_UNLOCK_FUNCTION(mutex)
+ ABSL_LOCKS_EXCLUDED(GetUpdateSitesMutex()) {
+ std::vector<VModuleInfo> infos = get_vmodule_info();
+ int current_global_v = global_v;
+ // We need to grab `GetUpdateSitesMutex()` before we release `mutex` to ensure
+ // that updates are not interleaved (resulting in an inconsistent final state)
+ // and to ensure that the final state in the sites matches the final state of
+ // `vmodule_info`. We unlock `mutex` to ensure that uninitialized sites don't
+ // have to wait on all updates in order to acquire `mutex` and initialize
+ // themselves.
+ absl::MutexLock ul(GetUpdateSitesMutex());
+ mutex.Unlock();
+ VLogSite* n = site_list_head.load(std::memory_order_seq_cst);
+ // Because sites are added to the list in the order they are executed, there
+ // tend to be clusters of entries with the same file.
+ const char* last_file = nullptr;
+ int last_file_level = 0;
+ while (n != nullptr) {
+ if (n->file_ != last_file) {
+ last_file = n->file_;
+ last_file_level = VLogLevel(n->file_, &infos, current_global_v);
+ }
+ n->v_.store(last_file_level, std::memory_order_seq_cst);
+ n = n->next_.load(std::memory_order_seq_cst);
+ }
+ if (update_callbacks) {
+ for (auto& cb : *update_callbacks) {
+ cb();
+ }
+ }
+}
+
+void UpdateVModule(absl::string_view vmodule)
+ ABSL_LOCKS_EXCLUDED(mutex, GetUpdateSitesMutex()) {
+ std::vector<std::pair<absl::string_view, int>> glob_levels;
+ for (absl::string_view glob_level : absl::StrSplit(vmodule, ',')) {
+ const size_t eq = glob_level.rfind('=');
+ if (eq == glob_level.npos) continue;
+ const absl::string_view glob = glob_level.substr(0, eq);
+ int level;
+ if (!absl::SimpleAtoi(glob_level.substr(eq + 1), &level)) continue;
+ glob_levels.emplace_back(glob, level);
+ }
+ mutex.Lock(); // Unlocked by UpdateVLogSites().
+ get_vmodule_info().clear();
+ for (const auto& it : glob_levels) {
+ const absl::string_view glob = it.first;
+ const int level = it.second;
+ AppendVModuleLocked(glob, level);
+ }
+ UpdateVLogSites();
+}
+
+int UpdateGlobalVLogLevel(int v)
+ ABSL_LOCKS_EXCLUDED(mutex, GetUpdateSitesMutex()) {
+ mutex.Lock(); // Unlocked by UpdateVLogSites().
+ const int old_global_v = global_v;
+ if (v == global_v) {
+ mutex.Unlock();
+ return old_global_v;
+ }
+ global_v = v;
+ UpdateVLogSites();
+ return old_global_v;
+}
+
+int PrependVModule(absl::string_view module_pattern, int log_level)
+ ABSL_LOCKS_EXCLUDED(mutex, GetUpdateSitesMutex()) {
+ mutex.Lock(); // Unlocked by UpdateVLogSites().
+ int old_v = PrependVModuleLocked(module_pattern, log_level);
+ UpdateVLogSites();
+ return old_v;
+}
+
+void OnVLogVerbosityUpdate(std::function<void()> cb)
+ ABSL_LOCKS_EXCLUDED(GetUpdateSitesMutex()) {
+ absl::MutexLock ul(GetUpdateSitesMutex());
+ if (!update_callbacks)
+ update_callbacks = new std::vector<std::function<void()>>;
+ update_callbacks->push_back(std::move(cb));
+}
+
+VLogSite* SetVModuleListHeadForTestOnly(VLogSite* v) {
+ return site_list_head.exchange(v, std::memory_order_seq_cst);
+}
+
+} // namespace log_internal
+ABSL_NAMESPACE_END
+} // namespace absl
diff --git a/absl/log/internal/vlog_config.h b/absl/log/internal/vlog_config.h
new file mode 100644
index 0000000..b6e322c
--- /dev/null
+++ b/absl/log/internal/vlog_config.h
@@ -0,0 +1,163 @@
+// Copyright 2022 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// -----------------------------------------------------------------------------
+// vlog_config.h
+// -----------------------------------------------------------------------------
+//
+// This header file defines `VLogSite`, a public primitive that represents
+// a callsite for the `VLOG` family of macros and related libraries.
+// It also declares and defines multiple internal utilities used to implement
+// `VLOG`, such as `VLogSiteManager`.
+
+#ifndef ABSL_LOG_INTERNAL_VLOG_CONFIG_H_
+#define ABSL_LOG_INTERNAL_VLOG_CONFIG_H_
+
+// IWYU pragma: private, include "absl/log/log.h"
+
+#include <atomic>
+#include <cstdint>
+#include <functional>
+#include <limits>
+#include <type_traits>
+
+#include "absl/base/attributes.h"
+#include "absl/base/config.h"
+#include "absl/base/optimization.h"
+#include "absl/base/thread_annotations.h"
+#include "absl/strings/string_view.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace log_internal {
+
+class SyntheticBinary;
+class VLogSite;
+
+int RegisterAndInitialize(VLogSite* v);
+void UpdateVLogSites();
+constexpr int kUseFlag = (std::numeric_limits<int16_t>::min)();
+
+// Represents a unique callsite for a `VLOG()` or `VLOG_IS_ON()` call.
+//
+// Libraries that provide `VLOG`-like functionality should use this to
+// efficiently handle --vmodule.
+//
+// VLogSite objects must not be destroyed until the program exits. Doing so will
+// probably yield nasty segfaults in VLogSiteManager::UpdateLogSites(). The
+// recommendation is to make all such objects function-local statics.
+class VLogSite final {
+ public:
+ // `f` must not be destroyed until the program exits.
+ explicit constexpr VLogSite(const char* f)
+ : file_(f), v_(kUninitialized), next_(nullptr) {}
+ VLogSite(const VLogSite&) = delete;
+ VLogSite& operator=(const VLogSite&) = delete;
+
+ // Inlining the function yields a ~3x performance improvement at the cost of a
+ // 1.5x code size increase at the call site.
+ // Takes locks but does not allocate memory.
+ ABSL_ATTRIBUTE_ALWAYS_INLINE
+ bool IsEnabled(int level) {
+ int stale_v = v_.load(std::memory_order_relaxed);
+ if (ABSL_PREDICT_TRUE(level > stale_v)) {
+ return false;
+ }
+
+ // We put everything other than the fast path, i.e. vlogging is initialized
+ // but not on, behind an out-of-line function to reduce code size.
+ // "level" is almost always a call-site constant, so we can save a bit
+ // of code space by special-casing for a few common levels.
+#if ABSL_HAVE_BUILTIN(__builtin_constant_p) || defined(__GNUC__)
+ if (__builtin_constant_p(level)) {
+ if (level == 0) return SlowIsEnabled0(stale_v);
+ if (level == 1) return SlowIsEnabled1(stale_v);
+ if (level == 2) return SlowIsEnabled2(stale_v);
+ if (level == 3) return SlowIsEnabled3(stale_v);
+ if (level == 4) return SlowIsEnabled4(stale_v);
+ if (level == 5) return SlowIsEnabled5(stale_v);
+ }
+#endif
+ return SlowIsEnabled(stale_v, level);
+ }
+
+ private:
+ friend int log_internal::RegisterAndInitialize(VLogSite* v);
+ friend void log_internal::UpdateVLogSites();
+ friend class log_internal::SyntheticBinary;
+ static constexpr int kUninitialized = (std::numeric_limits<int>::max)();
+
+ // SlowIsEnabled performs slower checks to determine whether a log site is
+ // enabled. Because it is expected to be called somewhat rarely
+ // (comparatively), it is not inlined to save on code size.
+ //
+ // Prerequisites to calling SlowIsEnabled:
+ // 1) stale_v is uninitialized OR
+ // 2) stale_v is initialized and >= level (meaning we must log).
+ // Takes locks but does not allocate memory.
+ ABSL_ATTRIBUTE_NOINLINE
+ bool SlowIsEnabled(int stale_v, int level);
+ ABSL_ATTRIBUTE_NOINLINE bool SlowIsEnabled0(int stale_v);
+ ABSL_ATTRIBUTE_NOINLINE bool SlowIsEnabled1(int stale_v);
+ ABSL_ATTRIBUTE_NOINLINE bool SlowIsEnabled2(int stale_v);
+ ABSL_ATTRIBUTE_NOINLINE bool SlowIsEnabled3(int stale_v);
+ ABSL_ATTRIBUTE_NOINLINE bool SlowIsEnabled4(int stale_v);
+ ABSL_ATTRIBUTE_NOINLINE bool SlowIsEnabled5(int stale_v);
+
+ // This object is too size-sensitive to use absl::string_view.
+ const char* const file_;
+ std::atomic<int> v_;
+ std::atomic<VLogSite*> next_;
+};
+static_assert(std::is_trivially_destructible<VLogSite>::value,
+ "VLogSite must be trivially destructible");
+
+// Returns the current verbose log level of `file`.
+// Does not allocate memory.
+int VLogLevel(absl::string_view file);
+
+// Registers a site `v` to get updated as `vmodule` and `v` change. Also
+// initializes the site based on their current values, and returns that result.
+// Does not allocate memory.
+int RegisterAndInitialize(VLogSite* v);
+
+// Allocates memory.
+void UpdateVLogSites();
+
+// Completely overwrites the saved value of `vmodule`.
+// Allocates memory.
+void UpdateVModule(absl::string_view vmodule);
+
+// Updates the global verbosity level to `v` and returns the prior value.
+// Allocates memory.
+int UpdateGlobalVLogLevel(int v);
+
+// Atomically prepends `module_pattern=log_level` to the start of vmodule.
+// Returns the prior value for `module_pattern` if there was an exact match and
+// `global_v` otherwise.
+// Allocates memory.
+int PrependVModule(absl::string_view module_pattern, int log_level);
+
+// Registers `on_update` to be called whenever `v` or `vmodule` change.
+// Allocates memory.
+void OnVLogVerbosityUpdate(std::function<void()> cb);
+
+// Does not allocate memory.
+VLogSite* SetVModuleListHeadForTestOnly(VLogSite* v);
+
+} // namespace log_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_LOG_INTERNAL_VLOG_CONFIG_H_
diff --git a/absl/log/internal/vlog_config_benchmark.cc b/absl/log/internal/vlog_config_benchmark.cc
new file mode 100644
index 0000000..9004e2e
--- /dev/null
+++ b/absl/log/internal/vlog_config_benchmark.cc
@@ -0,0 +1,187 @@
+// Copyright 2022 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <algorithm>
+#include <atomic>
+#include <cstddef>
+#include <cstring>
+#include <memory>
+#include <new>
+#include <random>
+#include <string>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+#include "absl/base/config.h"
+#include "absl/container/internal/layout.h"
+#include "absl/log/internal/vlog_config.h"
+#include "absl/memory/memory.h"
+#include "absl/random/distributions.h"
+#include "absl/strings/str_cat.h"
+#include "benchmark/benchmark.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace log_internal {
+// Performance of `UpdateLogSites` depends upon the number and organization of
+// `VLogSite`s in the program. We can synthesize some on the heap to mimic
+// their layout and linkage in static data.
+class SyntheticBinary {
+ public:
+ explicit SyntheticBinary(const size_t num_tus,
+ const size_t max_extra_static_data_bytes_per_tu,
+ const size_t max_sites_per_tu,
+ const int num_shuffles) {
+ per_tu_data_.reserve(num_tus);
+ auto sites = absl::make_unique<VLogSite *[]>(num_tus * max_sites_per_tu);
+ for (size_t i = 0; i < num_tus; i++) {
+ const std::string filename =
+ absl::StrCat("directory-", i / 100, "/subdirectory-", i % 100 / 10,
+ "/file-", i % 10, ".cc");
+ container_internal::Layout<char, VLogSite, char> layout(
+ filename.size() + 1,
+ absl::LogUniform<size_t>(bitgen_, 1, max_sites_per_tu),
+ absl::LogUniform<size_t>(bitgen_, 0,
+ max_extra_static_data_bytes_per_tu));
+ auto buf = absl::make_unique<char[]>(layout.AllocSize());
+ layout.PoisonPadding(buf.get());
+ memcpy(layout.Pointer<0>(buf.get()), filename.c_str(),
+ filename.size() + 1);
+ for (VLogSite &site : layout.Slice<1>(buf.get())) {
+ sites[num_sites_++] =
+ new (&site) VLogSite(layout.Pointer<0>(buf.get()));
+ // The last one is a dangling pointer but will be fixed below.
+ site.next_.store(&site + 1, std::memory_order_seq_cst);
+ }
+ num_extra_static_data_bytes_ += layout.Size<2>();
+ per_tu_data_.push_back(PerTU{layout, std::move(buf)});
+ }
+ // Now link the files together back-to-front into a circular list.
+ for (size_t i = 0; i < num_tus; i++) {
+ auto &tu = per_tu_data_[i];
+ auto &next_tu = per_tu_data_[(i + 1) % num_tus];
+ tu.layout.Slice<1>(tu.buf.get())
+ .back()
+ .next_.store(next_tu.layout.Pointer<1>(next_tu.buf.get()),
+ std::memory_order_seq_cst);
+ }
+ // Now do some shufflin'.
+ auto new_sites = absl::make_unique<VLogSite *[]>(num_sites_);
+ for (int shuffle_num = 0; shuffle_num < num_shuffles; shuffle_num++) {
+ // Each shuffle cuts the ring into three pieces and rearranges them.
+ const size_t cut_a = absl::Uniform(bitgen_, size_t{0}, num_sites_);
+ const size_t cut_b = absl::Uniform(bitgen_, size_t{0}, num_sites_);
+ const size_t cut_c = absl::Uniform(bitgen_, size_t{0}, num_sites_);
+ if (cut_a == cut_b || cut_b == cut_c || cut_a == cut_c) continue;
+ // The same cuts, sorted:
+ const size_t cut_1 = std::min({cut_a, cut_b, cut_c});
+ const size_t cut_3 = std::max({cut_a, cut_b, cut_c});
+ const size_t cut_2 = cut_a ^ cut_b ^ cut_c ^ cut_1 ^ cut_3;
+ VLogSite *const tmp = sites[cut_1]->next_.load(std::memory_order_seq_cst);
+ sites[cut_1]->next_.store(
+ sites[cut_2]->next_.load(std::memory_order_seq_cst),
+ std::memory_order_seq_cst);
+ sites[cut_2]->next_.store(
+ sites[cut_3]->next_.load(std::memory_order_seq_cst),
+ std::memory_order_seq_cst);
+ sites[cut_3]->next_.store(tmp, std::memory_order_seq_cst);
+ memcpy(&new_sites[0], &sites[0], sizeof(VLogSite *) * (cut_1 + 1));
+ memcpy(&new_sites[cut_1 + 1], &sites[cut_2 + 1],
+ sizeof(VLogSite *) * (cut_3 - cut_2));
+ memcpy(&new_sites[cut_1 + 1 + cut_3 - cut_2], &sites[cut_1 + 1],
+ sizeof(VLogSite *) * (cut_2 - cut_1));
+ memcpy(&new_sites[cut_3 + 1], &sites[cut_3 + 1],
+ sizeof(VLogSite *) * (num_sites_ - cut_3 - 1));
+ sites.swap(new_sites);
+ }
+ const char *last_file = nullptr;
+ for (size_t i = 0; i < num_sites_; i++) {
+ if (sites[i]->file_ == last_file) continue;
+ last_file = sites[i]->file_;
+ num_new_files_++;
+ }
+ absl::log_internal::SetVModuleListHeadForTestOnly(sites[0]);
+ sites[num_tus - 1]->next_.store(nullptr, std::memory_order_seq_cst);
+ }
+ ~SyntheticBinary() {
+ static_assert(std::is_trivially_destructible<VLogSite>::value, "");
+ absl::log_internal::SetVModuleListHeadForTestOnly(nullptr);
+ }
+
+ size_t num_sites() const { return num_sites_; }
+ size_t num_new_files() const { return num_new_files_; }
+ size_t num_extra_static_data_bytes() const {
+ return num_extra_static_data_bytes_;
+ }
+
+ private:
+ struct PerTU {
+ container_internal::Layout<char, VLogSite, char> layout;
+ std::unique_ptr<char[]> buf;
+ };
+
+ std::mt19937 bitgen_;
+ std::vector<PerTU> per_tu_data_;
+ size_t num_sites_ = 0;
+ size_t num_new_files_ = 0;
+ size_t num_extra_static_data_bytes_ = 0;
+};
+
+namespace {
+void BM_UpdateVModuleEmpty(benchmark::State& state) {
+ SyntheticBinary bin(static_cast<size_t>(state.range(0)), 10 * 1024 * 1024,
+ 256, state.range(1));
+ for (auto s : state) {
+ absl::log_internal::UpdateVModule("");
+ }
+ state.SetItemsProcessed(static_cast<int>(bin.num_new_files()));
+}
+BENCHMARK(BM_UpdateVModuleEmpty)
+ ->ArgPair(100, 200)
+ ->ArgPair(1000, 2000)
+ ->ArgPair(10000, 20000);
+
+void BM_UpdateVModuleShort(benchmark::State& state) {
+ SyntheticBinary bin(static_cast<size_t>(state.range(0)), 10 * 1024 * 1024,
+ 256, state.range(1));
+ for (auto s : state) {
+ absl::log_internal::UpdateVModule("directory2/*=4");
+ }
+ state.SetItemsProcessed(static_cast<int>(bin.num_new_files()));
+}
+BENCHMARK(BM_UpdateVModuleShort)
+ ->ArgPair(100, 200)
+ ->ArgPair(1000, 2000)
+ ->ArgPair(10000, 20000);
+
+void BM_UpdateVModuleLong(benchmark::State& state) {
+ SyntheticBinary bin(static_cast<size_t>(state.range(0)), 10 * 1024 * 1024,
+ 256, state.range(1));
+ for (auto s : state) {
+ absl::log_internal::UpdateVModule(
+ "d?r?c?o?y2/*=4,d?*r?*c?**o?*y1/*=2,d?*rc?**o?*y3/*=2,,"
+ "d?*r?*c?**o?*1/*=1,d?*r?**o?*y1/*=2,d?*r???***y1/*=7,"
+ "d?*r?**o?*y1/aaa=6");
+ }
+ state.SetItemsProcessed(static_cast<int>(bin.num_new_files()));
+}
+BENCHMARK(BM_UpdateVModuleLong)
+ ->ArgPair(100, 200)
+ ->ArgPair(1000, 2000)
+ ->ArgPair(10000, 20000);
+} // namespace
+} // namespace log_internal
+ABSL_NAMESPACE_END
+} // namespace absl
diff --git a/absl/log/internal/voidify.h b/absl/log/internal/voidify.h
new file mode 100644
index 0000000..8f62da2
--- /dev/null
+++ b/absl/log/internal/voidify.h
@@ -0,0 +1,44 @@
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// -----------------------------------------------------------------------------
+// File: log/internal/voidify.h
+// -----------------------------------------------------------------------------
+//
+// This class is used to explicitly ignore values in the conditional logging
+// macros. This avoids compiler warnings like "value computed is not used" and
+// "statement has no effect".
+
+#ifndef ABSL_LOG_INTERNAL_VOIDIFY_H_
+#define ABSL_LOG_INTERNAL_VOIDIFY_H_
+
+#include "absl/base/config.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace log_internal {
+
+class Voidify final {
+ public:
+ // This has to be an operator with a precedence lower than << but higher than
+ // ?:
+ template <typename T>
+ void operator&&(const T&) const&& {}
+};
+
+} // namespace log_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_LOG_INTERNAL_VOIDIFY_H_
diff --git a/absl/log/log.h b/absl/log/log.h
new file mode 100644
index 0000000..b721b08
--- /dev/null
+++ b/absl/log/log.h
@@ -0,0 +1,361 @@
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// -----------------------------------------------------------------------------
+// File: log/log.h
+// -----------------------------------------------------------------------------
+//
+// This header declares a family of LOG macros.
+//
+// Basic invocation looks like this:
+//
+// LOG(INFO) << "Found " << num_cookies << " cookies";
+//
+// Most `LOG` macros take a severity level argument. The severity levels are
+// `INFO`, `WARNING`, `ERROR`, and `FATAL`. They are defined
+// in absl/base/log_severity.h.
+// * The `FATAL` severity level terminates the program with a stack trace after
+// logging its message. Error handlers registered with `RunOnFailure`
+// (process_state.h) are run, but exit handlers registered with `atexit(3)`
+// are not.
+// * The `QFATAL` pseudo-severity level is equivalent to `FATAL` but triggers
+// quieter termination messages, e.g. without a full stack trace, and skips
+// running registered error handlers.
+// * The `DFATAL` pseudo-severity level is defined as `FATAL` in debug mode and
+// as `ERROR` otherwise.
+// Some preprocessor shenanigans are used to ensure that e.g. `LOG(INFO)` has
+// the same meaning even if a local symbol or preprocessor macro named `INFO` is
+// defined. To specify a severity level using an expression instead of a
+// literal, use `LEVEL(expr)`.
+// Example:
+//
+// LOG(LEVEL(stale ? absl::LogSeverity::kWarning : absl::LogSeverity::kInfo))
+// << "Cookies are " << days << " days old";
+
+// `LOG` macros evaluate to an unterminated statement. The value at the end of
+// the statement supports some chainable methods:
+//
+// * .AtLocation(absl::string_view file, int line)
+// .AtLocation(absl::SourceLocation loc)
+// Overrides the location inferred from the callsite. The string pointed to
+// by `file` must be valid until the end of the statement.
+// * .NoPrefix()
+// Omits the prefix from this line. The prefix includes metadata about the
+// logged data such as source code location and timestamp.
+// * .WithVerbosity(int verbose_level)
+// Sets the verbosity field of the logged message as if it was logged by
+// `VLOG(verbose_level)`. Unlike `VLOG`, this method does not affect
+// evaluation of the statement when the specified `verbose_level` has been
+// disabled. The only effect is on `LogSink` implementations which make use
+// of the `absl::LogSink::verbosity()` value. The value
+// `absl::LogEntry::kNoVerbosityLevel` can be specified to mark the message
+// not verbose.
+// * .WithTimestamp(absl::Time timestamp)
+// Uses the specified timestamp instead of one collected at the time of
+// execution.
+// * .WithThreadID(absl::LogEntry::tid_t tid)
+// Uses the specified thread ID instead of one collected at the time of
+// execution.
+// * .WithMetadataFrom(const absl::LogEntry &entry)
+// Copies all metadata (but no data) from the specified `absl::LogEntry`.
+// This can be used to change the severity of a message, but it has some
+// limitations:
+// * `ABSL_MIN_LOG_LEVEL` is evaluated against the severity passed into
+// `LOG` (or the implicit `FATAL` level of `CHECK`).
+// * `LOG(FATAL)` and `CHECK` terminate the process unconditionally, even if
+// the severity is changed later.
+// `.WithMetadataFrom(entry)` should almost always be used in combination
+// with `LOG(LEVEL(entry.log_severity()))`.
+// * .WithPerror()
+// Appends to the logged message a colon, a space, a textual description of
+// the current value of `errno` (as by `strerror(3)`), and the numerical
+// value of `errno`.
+// * .ToSinkAlso(absl::LogSink* sink)
+// Sends this message to `*sink` in addition to whatever other sinks it
+// would otherwise have been sent to. `sink` must not be null.
+// * .ToSinkOnly(absl::LogSink* sink)
+// Sends this message to `*sink` and no others. `sink` must not be null.
+//
+// No interfaces in this header are async-signal-safe; their use in signal
+// handlers is unsupported and may deadlock your program or eat your lunch.
+//
+// Many logging statements are inherently conditional. For example,
+// `LOG_IF(INFO, !foo)` does nothing if `foo` is true. Even seemingly
+// unconditional statements like `LOG(INFO)` might be disabled at
+// compile-time to minimize binary size or for security reasons.
+//
+// * Except for the condition in a `CHECK` or `QCHECK` statement, programs must
+// not rely on evaluation of expressions anywhere in logging statements for
+// correctness. For example, this is ok:
+//
+// CHECK((fp = fopen("config.ini", "r")) != nullptr);
+//
+// But this is probably not ok:
+//
+// LOG(INFO) << "Server status: " << StartServerAndReturnStatusString();
+//
+// The example below is bad too; the `i++` in the `LOG_IF` condition might
+// not be evaluated, resulting in an infinite loop:
+//
+// for (int i = 0; i < 1000000;)
+// LOG_IF(INFO, i++ % 1000 == 0) << "Still working...";
+//
+// * Except where otherwise noted, conditions which cause a statement not to log
+// also cause expressions not to be evaluated. Programs may rely on this for
+// performance reasons, e.g. by streaming the result of an expensive function
+// call into a `DLOG` or `LOG_EVERY_N` statement.
+// * Care has been taken to ensure that expressions are parsed by the compiler
+// even if they are never evaluated. This means that syntax errors will be
+// caught and variables will be considered used for the purposes of
+// unused-variable diagnostics. For example, this statement won't compile
+// even if `INFO`-level logging has been compiled out:
+//
+// int number_of_cakes = 40;
+// LOG(INFO) << "Number of cakes: " << number_of_cake; // Note the typo!
+//
+// Similarly, this won't produce unused-variable compiler diagnostics even
+// if `INFO`-level logging is compiled out:
+//
+// {
+// char fox_line1[] = "Hatee-hatee-hatee-ho!";
+// LOG_IF(ERROR, false) << "The fox says " << fox_line1;
+// char fox_line2[] = "A-oo-oo-oo-ooo!";
+// LOG(INFO) << "The fox also says " << fox_line2;
+// }
+//
+// This error-checking is not perfect; for example, symbols that have been
+// declared but not defined may not produce link errors if used in logging
+// statements that compile away.
+//
+// Expressions streamed into these macros are formatted using `operator<<` just
+// as they would be if streamed into a `std::ostream`, however it should be
+// noted that their actual type is unspecified.
+//
+// To implement a custom formatting operator for a type you own, there are two
+// options: `AbslStringify()` or `std::ostream& operator<<(std::ostream&, ...)`.
+// It is recommended that users make their types loggable through
+// `AbslStringify()` as it is a universal stringification extension that also
+// enables `absl::StrFormat` and `absl::StrCat` support. If both
+// `AbslStringify()` and `std::ostream& operator<<(std::ostream&, ...)` are
+// defined, `AbslStringify()` will be used.
+//
+// To use the `AbslStringify()` API, define a friend function template in your
+// type's namespace with the following signature:
+//
+// template <typename Sink>
+// void AbslStringify(Sink& sink, const UserDefinedType& value);
+//
+// `Sink` has the same interface as `absl::FormatSink`, but without
+// `PutPaddedString()`.
+//
+// Example:
+//
+// struct Point {
+// template <typename Sink>
+// friend void AbslStringify(Sink& sink, const Point& p) {
+// absl::Format(&sink, "(%v, %v)", p.x, p.y);
+// }
+//
+// int x;
+// int y;
+// };
+//
+// To use `std::ostream& operator<<(std::ostream&, ...)`, define
+// `std::ostream& operator<<(std::ostream&, ...)` in your type's namespace (for
+// ADL) just as you would to stream it to `std::cout`.
+//
+// Currently `AbslStringify()` ignores output manipulators but this is not
+// guaranteed behavior and may be subject to change in the future. If you would
+// like guaranteed behavior regarding output manipulators, please use
+// `std::ostream& operator<<(std::ostream&, ...)` to make custom types loggable
+// instead.
+//
+// Those macros that support streaming honor output manipulators and `fmtflag`
+// changes that output data (e.g. `std::ends`) or control formatting of data
+// (e.g. `std::hex` and `std::fixed`), however flushing such a stream is
+// ignored. The message produced by a log statement is sent to registered
+// `absl::LogSink` instances at the end of the statement; those sinks are
+// responsible for their own flushing (e.g. to disk) semantics.
+//
+// Flag settings are not carried over from one `LOG` statement to the next; this
+// is a bit different than e.g. `std::cout`:
+//
+// LOG(INFO) << std::hex << 0xdeadbeef; // logs "0xdeadbeef"
+// LOG(INFO) << 0xdeadbeef; // logs "3735928559"
+
+#ifndef ABSL_LOG_LOG_H_
+#define ABSL_LOG_LOG_H_
+
+#include "absl/log/internal/log_impl.h"
+#include "absl/log/vlog_is_on.h" // IWYU pragma: export
+
+// LOG()
+//
+// `LOG` takes a single argument which is a severity level. Data streamed in
+// comprise the logged message.
+// Example:
+//
+// LOG(INFO) << "Found " << num_cookies << " cookies";
+#define LOG(severity) ABSL_LOG_INTERNAL_LOG_IMPL(_##severity)
+
+// PLOG()
+//
+// `PLOG` behaves like `LOG` except that a description of the current state of
+// `errno` is appended to the streamed message.
+#define PLOG(severity) ABSL_LOG_INTERNAL_PLOG_IMPL(_##severity)
+
+// DLOG()
+//
+// `DLOG` behaves like `LOG` in debug mode (i.e. `#ifndef NDEBUG`). Otherwise
+// it compiles away and does nothing. Note that `DLOG(FATAL)` does not
+// terminate the program if `NDEBUG` is defined.
+#define DLOG(severity) ABSL_LOG_INTERNAL_DLOG_IMPL(_##severity)
+
+// `VLOG` uses numeric levels to provide verbose logging that can configured at
+// runtime, including at a per-module level. `VLOG` statements are logged at
+// `INFO` severity if they are logged at all; the numeric levels are on a
+// different scale than the proper severity levels. Positive levels are
+// disabled by default. Negative levels should not be used.
+// Example:
+//
+// VLOG(1) << "I print when you run the program with --v=1 or higher";
+// VLOG(2) << "I print when you run the program with --v=2 or higher";
+//
+// See vlog_is_on.h for further documentation, including the usage of the
+// --vmodule flag to log at different levels in different source files.
+#define VLOG(severity) ABSL_LOG_INTERNAL_VLOG_IMPL(severity)
+
+// `DVLOG` behaves like `VLOG` in debug mode (i.e. `#ifndef NDEBUG`).
+// Otherwise, it compiles away and does nothing.
+#define DVLOG(severity) ABSL_LOG_INTERNAL_DVLOG_IMPL(severity)
+
+// `LOG_IF` and friends add a second argument which specifies a condition. If
+// the condition is false, nothing is logged.
+// Example:
+//
+// LOG_IF(INFO, num_cookies > 10) << "Got lots of cookies";
+//
+// There is no `VLOG_IF` because the order of evaluation of the arguments is
+// ambiguous and the alternate spelling with an `if`-statement is trivial.
+#define LOG_IF(severity, condition) \
+ ABSL_LOG_INTERNAL_LOG_IF_IMPL(_##severity, condition)
+#define PLOG_IF(severity, condition) \
+ ABSL_LOG_INTERNAL_PLOG_IF_IMPL(_##severity, condition)
+#define DLOG_IF(severity, condition) \
+ ABSL_LOG_INTERNAL_DLOG_IF_IMPL(_##severity, condition)
+
+// LOG_EVERY_N
+//
+// An instance of `LOG_EVERY_N` increments a hidden zero-initialized counter
+// every time execution passes through it and logs the specified message when
+// the counter's value is a multiple of `n`, doing nothing otherwise. Each
+// instance has its own counter. The counter's value can be logged by streaming
+// the symbol `COUNTER`. `LOG_EVERY_N` is thread-safe.
+// Example:
+//
+// LOG_EVERY_N(WARNING, 1000) << "Got a packet with a bad CRC (" << COUNTER
+// << " total)";
+#define LOG_EVERY_N(severity, n) \
+ ABSL_LOG_INTERNAL_LOG_EVERY_N_IMPL(_##severity, n)
+
+// LOG_FIRST_N
+//
+// `LOG_FIRST_N` behaves like `LOG_EVERY_N` except that the specified message is
+// logged when the counter's value is less than `n`. `LOG_FIRST_N` is
+// thread-safe.
+#define LOG_FIRST_N(severity, n) \
+ ABSL_LOG_INTERNAL_LOG_FIRST_N_IMPL(_##severity, n)
+
+// LOG_EVERY_POW_2
+//
+// `LOG_EVERY_POW_2` behaves like `LOG_EVERY_N` except that the specified
+// message is logged when the counter's value is a power of 2.
+// `LOG_EVERY_POW_2` is thread-safe.
+#define LOG_EVERY_POW_2(severity) \
+ ABSL_LOG_INTERNAL_LOG_EVERY_POW_2_IMPL(_##severity)
+
+// LOG_EVERY_N_SEC
+//
+// An instance of `LOG_EVERY_N_SEC` uses a hidden state variable to log the
+// specified message at most once every `n_seconds`. A hidden counter of
+// executions (whether a message is logged or not) is also maintained and can be
+// logged by streaming the symbol `COUNTER`. `LOG_EVERY_N_SEC` is thread-safe.
+// Example:
+//
+// LOG_EVERY_N_SEC(INFO, 2.5) << "Got " << COUNTER << " cookies so far";
+#define LOG_EVERY_N_SEC(severity, n_seconds) \
+ ABSL_LOG_INTERNAL_LOG_EVERY_N_SEC_IMPL(_##severity, n_seconds)
+
+#define PLOG_EVERY_N(severity, n) \
+ ABSL_LOG_INTERNAL_PLOG_EVERY_N_IMPL(_##severity, n)
+#define PLOG_FIRST_N(severity, n) \
+ ABSL_LOG_INTERNAL_PLOG_FIRST_N_IMPL(_##severity, n)
+#define PLOG_EVERY_POW_2(severity) \
+ ABSL_LOG_INTERNAL_PLOG_EVERY_POW_2_IMPL(_##severity)
+#define PLOG_EVERY_N_SEC(severity, n_seconds) \
+ ABSL_LOG_INTERNAL_PLOG_EVERY_N_SEC_IMPL(_##severity, n_seconds)
+
+#define DLOG_EVERY_N(severity, n) \
+ ABSL_LOG_INTERNAL_DLOG_EVERY_N_IMPL(_##severity, n)
+#define DLOG_FIRST_N(severity, n) \
+ ABSL_LOG_INTERNAL_DLOG_FIRST_N_IMPL(_##severity, n)
+#define DLOG_EVERY_POW_2(severity) \
+ ABSL_LOG_INTERNAL_DLOG_EVERY_POW_2_IMPL(_##severity)
+#define DLOG_EVERY_N_SEC(severity, n_seconds) \
+ ABSL_LOG_INTERNAL_DLOG_EVERY_N_SEC_IMPL(_##severity, n_seconds)
+
+#define VLOG_EVERY_N(severity, n) \
+ ABSL_LOG_INTERNAL_VLOG_EVERY_N_IMPL(severity, n)
+#define VLOG_FIRST_N(severity, n) \
+ ABSL_LOG_INTERNAL_VLOG_FIRST_N_IMPL(severity, n)
+#define VLOG_EVERY_POW_2(severity) \
+ ABSL_LOG_INTERNAL_VLOG_EVERY_POW_2_IMPL(severity)
+#define VLOG_EVERY_N_SEC(severity, n_seconds) \
+ ABSL_LOG_INTERNAL_VLOG_EVERY_N_SEC_IMPL(severity, n_seconds)
+
+// `LOG_IF_EVERY_N` and friends behave as the corresponding `LOG_EVERY_N`
+// but neither increment a counter nor log a message if condition is false (as
+// `LOG_IF`).
+// Example:
+//
+// LOG_IF_EVERY_N(INFO, (size > 1024), 10) << "Got the " << COUNTER
+// << "th big cookie";
+#define LOG_IF_EVERY_N(severity, condition, n) \
+ ABSL_LOG_INTERNAL_LOG_IF_EVERY_N_IMPL(_##severity, condition, n)
+#define LOG_IF_FIRST_N(severity, condition, n) \
+ ABSL_LOG_INTERNAL_LOG_IF_FIRST_N_IMPL(_##severity, condition, n)
+#define LOG_IF_EVERY_POW_2(severity, condition) \
+ ABSL_LOG_INTERNAL_LOG_IF_EVERY_POW_2_IMPL(_##severity, condition)
+#define LOG_IF_EVERY_N_SEC(severity, condition, n_seconds) \
+ ABSL_LOG_INTERNAL_LOG_IF_EVERY_N_SEC_IMPL(_##severity, condition, n_seconds)
+
+#define PLOG_IF_EVERY_N(severity, condition, n) \
+ ABSL_LOG_INTERNAL_PLOG_IF_EVERY_N_IMPL(_##severity, condition, n)
+#define PLOG_IF_FIRST_N(severity, condition, n) \
+ ABSL_LOG_INTERNAL_PLOG_IF_FIRST_N_IMPL(_##severity, condition, n)
+#define PLOG_IF_EVERY_POW_2(severity, condition) \
+ ABSL_LOG_INTERNAL_PLOG_IF_EVERY_POW_2_IMPL(_##severity, condition)
+#define PLOG_IF_EVERY_N_SEC(severity, condition, n_seconds) \
+ ABSL_LOG_INTERNAL_PLOG_IF_EVERY_N_SEC_IMPL(_##severity, condition, n_seconds)
+
+#define DLOG_IF_EVERY_N(severity, condition, n) \
+ ABSL_LOG_INTERNAL_DLOG_IF_EVERY_N_IMPL(_##severity, condition, n)
+#define DLOG_IF_FIRST_N(severity, condition, n) \
+ ABSL_LOG_INTERNAL_DLOG_IF_FIRST_N_IMPL(_##severity, condition, n)
+#define DLOG_IF_EVERY_POW_2(severity, condition) \
+ ABSL_LOG_INTERNAL_DLOG_IF_EVERY_POW_2_IMPL(_##severity, condition)
+#define DLOG_IF_EVERY_N_SEC(severity, condition, n_seconds) \
+ ABSL_LOG_INTERNAL_DLOG_IF_EVERY_N_SEC_IMPL(_##severity, condition, n_seconds)
+
+#endif // ABSL_LOG_LOG_H_
diff --git a/absl/debugging/leak_check_disable.cc b/absl/log/log_basic_test.cc
similarity index 67%
rename from absl/debugging/leak_check_disable.cc
rename to absl/log/log_basic_test.cc
index 924d6e3..7fc7111 100644
--- a/absl/debugging/leak_check_disable.cc
+++ b/absl/log/log_basic_test.cc
@@ -1,4 +1,5 @@
-// Copyright 2017 The Abseil Authors.
+//
+// Copyright 2022 The Abseil Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -12,9 +13,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-// Disable LeakSanitizer when this file is linked in.
-// This function overrides __lsan_is_turned_off from sanitizer/lsan_interface.h
-extern "C" int __lsan_is_turned_off();
-extern "C" int __lsan_is_turned_off() {
- return 1;
-}
+#include "absl/log/log.h"
+
+#define ABSL_TEST_LOG LOG
+
+#include "gtest/gtest.h"
+#include "absl/log/log_basic_test_impl.inc"
diff --git a/absl/log/log_basic_test_impl.inc b/absl/log/log_basic_test_impl.inc
new file mode 100644
index 0000000..e2f3356
--- /dev/null
+++ b/absl/log/log_basic_test_impl.inc
@@ -0,0 +1,545 @@
+//
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// The testcases in this file are expected to pass or be skipped with any value
+// of ABSL_MIN_LOG_LEVEL
+
+#ifndef ABSL_LOG_LOG_BASIC_TEST_IMPL_H_
+#define ABSL_LOG_LOG_BASIC_TEST_IMPL_H_
+
+// Verify that both sets of macros behave identically by parameterizing the
+// entire test file.
+#ifndef ABSL_TEST_LOG
+#error ABSL_TEST_LOG must be defined for these tests to work.
+#endif
+
+#include <cerrno>
+#include <sstream>
+#include <string>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/base/internal/sysinfo.h"
+#include "absl/base/log_severity.h"
+#include "absl/log/globals.h"
+#include "absl/log/internal/test_actions.h"
+#include "absl/log/internal/test_helpers.h"
+#include "absl/log/internal/test_matchers.h"
+#include "absl/log/log_entry.h"
+#include "absl/log/scoped_mock_log.h"
+
+namespace absl_log_internal {
+#if GTEST_HAS_DEATH_TEST
+using ::absl::log_internal::DeathTestExpectedLogging;
+using ::absl::log_internal::DeathTestUnexpectedLogging;
+using ::absl::log_internal::DeathTestValidateExpectations;
+using ::absl::log_internal::DiedOfFatal;
+using ::absl::log_internal::DiedOfQFatal;
+#endif
+using ::absl::log_internal::LoggingEnabledAt;
+using ::absl::log_internal::LogSeverity;
+using ::absl::log_internal::Prefix;
+using ::absl::log_internal::SourceBasename;
+using ::absl::log_internal::SourceFilename;
+using ::absl::log_internal::SourceLine;
+using ::absl::log_internal::Stacktrace;
+using ::absl::log_internal::TextMessage;
+using ::absl::log_internal::ThreadID;
+using ::absl::log_internal::TimestampInMatchWindow;
+using ::absl::log_internal::Verbosity;
+using ::testing::AnyNumber;
+using ::testing::Eq;
+using ::testing::IsEmpty;
+using ::testing::IsTrue;
+
+class BasicLogTest : public testing::TestWithParam<absl::LogSeverityAtLeast> {};
+
+std::string ThresholdName(
+ testing::TestParamInfo<absl::LogSeverityAtLeast> severity) {
+ std::stringstream ostr;
+ ostr << severity.param;
+ return ostr.str().substr(
+ severity.param == absl::LogSeverityAtLeast::kInfinity ? 0 : 2);
+}
+
+INSTANTIATE_TEST_SUITE_P(WithParam, BasicLogTest,
+ testing::Values(absl::LogSeverityAtLeast::kInfo,
+ absl::LogSeverityAtLeast::kWarning,
+ absl::LogSeverityAtLeast::kError,
+ absl::LogSeverityAtLeast::kFatal,
+ absl::LogSeverityAtLeast::kInfinity),
+ ThresholdName);
+
+TEST_P(BasicLogTest, Info) {
+ absl::log_internal::ScopedMinLogLevel scoped_min_log_level(GetParam());
+
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const int log_line = __LINE__ + 1;
+ auto do_log = [] { ABSL_TEST_LOG(INFO) << "hello world"; };
+
+ if (LoggingEnabledAt(absl::LogSeverity::kInfo)) {
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(SourceFilename(Eq(__FILE__)),
+ SourceBasename(Eq("log_basic_test_impl.inc")),
+ SourceLine(Eq(log_line)), Prefix(IsTrue()),
+ LogSeverity(Eq(absl::LogSeverity::kInfo)),
+ TimestampInMatchWindow(),
+ ThreadID(Eq(absl::base_internal::GetTID())),
+ TextMessage(Eq("hello world")),
+ Verbosity(Eq(absl::LogEntry::kNoVerbosityLevel)),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value {
+ literal: "hello world"
+ })pb")),
+ Stacktrace(IsEmpty()))));
+ }
+
+ test_sink.StartCapturingLogs();
+ do_log();
+}
+
+TEST_P(BasicLogTest, Warning) {
+ absl::log_internal::ScopedMinLogLevel scoped_min_log_level(GetParam());
+
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const int log_line = __LINE__ + 1;
+ auto do_log = [] { ABSL_TEST_LOG(WARNING) << "hello world"; };
+
+ if (LoggingEnabledAt(absl::LogSeverity::kWarning)) {
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(SourceFilename(Eq(__FILE__)),
+ SourceBasename(Eq("log_basic_test_impl.inc")),
+ SourceLine(Eq(log_line)), Prefix(IsTrue()),
+ LogSeverity(Eq(absl::LogSeverity::kWarning)),
+ TimestampInMatchWindow(),
+ ThreadID(Eq(absl::base_internal::GetTID())),
+ TextMessage(Eq("hello world")),
+ Verbosity(Eq(absl::LogEntry::kNoVerbosityLevel)),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value {
+ literal: "hello world"
+ })pb")),
+ Stacktrace(IsEmpty()))));
+ }
+
+ test_sink.StartCapturingLogs();
+ do_log();
+}
+
+TEST_P(BasicLogTest, Error) {
+ absl::log_internal::ScopedMinLogLevel scoped_min_log_level(GetParam());
+
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const int log_line = __LINE__ + 1;
+ auto do_log = [] { ABSL_TEST_LOG(ERROR) << "hello world"; };
+
+ if (LoggingEnabledAt(absl::LogSeverity::kError)) {
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(SourceFilename(Eq(__FILE__)),
+ SourceBasename(Eq("log_basic_test_impl.inc")),
+ SourceLine(Eq(log_line)), Prefix(IsTrue()),
+ LogSeverity(Eq(absl::LogSeverity::kError)),
+ TimestampInMatchWindow(),
+ ThreadID(Eq(absl::base_internal::GetTID())),
+ TextMessage(Eq("hello world")),
+ Verbosity(Eq(absl::LogEntry::kNoVerbosityLevel)),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value {
+ literal: "hello world"
+ })pb")),
+ Stacktrace(IsEmpty()))));
+ }
+
+ test_sink.StartCapturingLogs();
+ do_log();
+}
+
+#if GTEST_HAS_DEATH_TEST
+using BasicLogDeathTest = BasicLogTest;
+
+INSTANTIATE_TEST_SUITE_P(WithParam, BasicLogDeathTest,
+ testing::Values(absl::LogSeverityAtLeast::kInfo,
+ absl::LogSeverityAtLeast::kFatal,
+ absl::LogSeverityAtLeast::kInfinity),
+ ThresholdName);
+
+TEST_P(BasicLogDeathTest, Fatal) {
+ absl::log_internal::ScopedMinLogLevel scoped_min_log_level(GetParam());
+
+ const int log_line = __LINE__ + 1;
+ auto do_log = [] { ABSL_TEST_LOG(FATAL) << "hello world"; };
+
+ EXPECT_EXIT(
+ {
+ absl::ScopedMockLog test_sink(
+ absl::MockLogDefault::kDisallowUnexpected);
+
+ EXPECT_CALL(test_sink, Send)
+ .Times(AnyNumber())
+ .WillRepeatedly(DeathTestUnexpectedLogging());
+
+ ::testing::InSequence s;
+
+ // Note the logic in DeathTestValidateExpectations() caters for the case
+ // of logging being disabled at FATAL level.
+
+ if (LoggingEnabledAt(absl::LogSeverity::kFatal)) {
+ // The first call without the stack trace.
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(SourceFilename(Eq(__FILE__)),
+ SourceBasename(Eq("log_basic_test_impl.inc")),
+ SourceLine(Eq(log_line)), Prefix(IsTrue()),
+ LogSeverity(Eq(absl::LogSeverity::kFatal)),
+ TimestampInMatchWindow(),
+ ThreadID(Eq(absl::base_internal::GetTID())),
+ TextMessage(Eq("hello world")),
+ Verbosity(Eq(absl::LogEntry::kNoVerbosityLevel)),
+ ENCODED_MESSAGE(EqualsProto(
+ R"pb(value { literal: "hello world" })pb")),
+ Stacktrace(IsEmpty()))))
+ .WillOnce(DeathTestExpectedLogging());
+
+ // The second call with the stack trace.
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(SourceFilename(Eq(__FILE__)),
+ SourceBasename(Eq("log_basic_test_impl.inc")),
+ SourceLine(Eq(log_line)), Prefix(IsTrue()),
+ LogSeverity(Eq(absl::LogSeverity::kFatal)),
+ TimestampInMatchWindow(),
+ ThreadID(Eq(absl::base_internal::GetTID())),
+ TextMessage(Eq("hello world")),
+ Verbosity(Eq(absl::LogEntry::kNoVerbosityLevel)),
+ ENCODED_MESSAGE(EqualsProto(
+ R"pb(value { literal: "hello world" })pb")),
+ Stacktrace(Not(IsEmpty())))))
+ .WillOnce(DeathTestExpectedLogging());
+ }
+
+ test_sink.StartCapturingLogs();
+ do_log();
+ },
+ DiedOfFatal, DeathTestValidateExpectations());
+}
+
+TEST_P(BasicLogDeathTest, QFatal) {
+ absl::log_internal::ScopedMinLogLevel scoped_min_log_level(GetParam());
+
+ const int log_line = __LINE__ + 1;
+ auto do_log = [] { ABSL_TEST_LOG(QFATAL) << "hello world"; };
+
+ EXPECT_EXIT(
+ {
+ absl::ScopedMockLog test_sink(
+ absl::MockLogDefault::kDisallowUnexpected);
+
+ EXPECT_CALL(test_sink, Send)
+ .Times(AnyNumber())
+ .WillRepeatedly(DeathTestUnexpectedLogging());
+
+ if (LoggingEnabledAt(absl::LogSeverity::kFatal)) {
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(SourceFilename(Eq(__FILE__)),
+ SourceBasename(Eq("log_basic_test_impl.inc")),
+ SourceLine(Eq(log_line)), Prefix(IsTrue()),
+ LogSeverity(Eq(absl::LogSeverity::kFatal)),
+ TimestampInMatchWindow(),
+ ThreadID(Eq(absl::base_internal::GetTID())),
+ TextMessage(Eq("hello world")),
+ Verbosity(Eq(absl::LogEntry::kNoVerbosityLevel)),
+ ENCODED_MESSAGE(EqualsProto(
+ R"pb(value { literal: "hello world" })pb")),
+ Stacktrace(IsEmpty()))))
+ .WillOnce(DeathTestExpectedLogging());
+ }
+
+ test_sink.StartCapturingLogs();
+ do_log();
+ },
+ DiedOfQFatal, DeathTestValidateExpectations());
+}
+#endif
+
+#ifdef NDEBUG
+TEST_P(BasicLogTest, DFatal) {
+ absl::log_internal::ScopedMinLogLevel scoped_min_log_level(GetParam());
+
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const int log_line = __LINE__ + 1;
+ auto do_log = [] { ABSL_TEST_LOG(DFATAL) << "hello world"; };
+
+ if (LoggingEnabledAt(absl::LogSeverity::kError)) {
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(SourceFilename(Eq(__FILE__)),
+ SourceBasename(Eq("log_basic_test_impl.inc")),
+ SourceLine(Eq(log_line)), Prefix(IsTrue()),
+ LogSeverity(Eq(absl::LogSeverity::kError)),
+ TimestampInMatchWindow(),
+ ThreadID(Eq(absl::base_internal::GetTID())),
+ TextMessage(Eq("hello world")),
+ Verbosity(Eq(absl::LogEntry::kNoVerbosityLevel)),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value {
+ literal: "hello world"
+ })pb")),
+ Stacktrace(IsEmpty()))));
+ }
+
+ test_sink.StartCapturingLogs();
+ do_log();
+}
+
+#elif GTEST_HAS_DEATH_TEST
+TEST_P(BasicLogDeathTest, DFatal) {
+ // TODO(b/242568884): re-enable once bug is fixed.
+ // absl::log_internal::ScopedMinLogLevel scoped_min_log_level(GetParam());
+
+ const int log_line = __LINE__ + 1;
+ auto do_log = [] { ABSL_TEST_LOG(DFATAL) << "hello world"; };
+
+ EXPECT_EXIT(
+ {
+ absl::ScopedMockLog test_sink(
+ absl::MockLogDefault::kDisallowUnexpected);
+
+ EXPECT_CALL(test_sink, Send)
+ .Times(AnyNumber())
+ .WillRepeatedly(DeathTestUnexpectedLogging());
+
+ ::testing::InSequence s;
+
+ if (LoggingEnabledAt(absl::LogSeverity::kFatal)) {
+ // The first call without the stack trace.
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(SourceFilename(Eq(__FILE__)),
+ SourceBasename(Eq("log_basic_test_impl.inc")),
+ SourceLine(Eq(log_line)), Prefix(IsTrue()),
+ LogSeverity(Eq(absl::LogSeverity::kFatal)),
+ TimestampInMatchWindow(),
+ ThreadID(Eq(absl::base_internal::GetTID())),
+ TextMessage(Eq("hello world")),
+ Verbosity(Eq(absl::LogEntry::kNoVerbosityLevel)),
+ ENCODED_MESSAGE(EqualsProto(
+ R"pb(value { literal: "hello world" })pb")),
+ Stacktrace(IsEmpty()))))
+ .WillOnce(DeathTestExpectedLogging());
+
+ // The second call with the stack trace.
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(SourceFilename(Eq(__FILE__)),
+ SourceBasename(Eq("log_basic_test_impl.inc")),
+ SourceLine(Eq(log_line)), Prefix(IsTrue()),
+ LogSeverity(Eq(absl::LogSeverity::kFatal)),
+ TimestampInMatchWindow(),
+ ThreadID(Eq(absl::base_internal::GetTID())),
+ TextMessage(Eq("hello world")),
+ Verbosity(Eq(absl::LogEntry::kNoVerbosityLevel)),
+ ENCODED_MESSAGE(EqualsProto(
+ R"pb(value { literal: "hello world" })pb")),
+ Stacktrace(Not(IsEmpty())))))
+ .WillOnce(DeathTestExpectedLogging());
+ }
+
+ test_sink.StartCapturingLogs();
+ do_log();
+ },
+ DiedOfFatal, DeathTestValidateExpectations());
+}
+#endif
+
+TEST_P(BasicLogTest, Level) {
+ absl::log_internal::ScopedMinLogLevel scoped_min_log_level(GetParam());
+
+ for (auto severity : {absl::LogSeverity::kInfo, absl::LogSeverity::kWarning,
+ absl::LogSeverity::kError}) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const int log_line = __LINE__ + 2;
+ auto do_log = [severity] {
+ ABSL_TEST_LOG(LEVEL(severity)) << "hello world";
+ };
+
+ if (LoggingEnabledAt(severity)) {
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(SourceFilename(Eq(__FILE__)),
+ SourceBasename(Eq("log_basic_test_impl.inc")),
+ SourceLine(Eq(log_line)), Prefix(IsTrue()),
+ LogSeverity(Eq(severity)), TimestampInMatchWindow(),
+ ThreadID(Eq(absl::base_internal::GetTID())),
+ TextMessage(Eq("hello world")),
+ Verbosity(Eq(absl::LogEntry::kNoVerbosityLevel)),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value {
+ literal: "hello world"
+ })pb")),
+ Stacktrace(IsEmpty()))));
+ }
+ test_sink.StartCapturingLogs();
+ do_log();
+ }
+}
+
+#if GTEST_HAS_DEATH_TEST
+TEST_P(BasicLogDeathTest, Level) {
+ // TODO(b/242568884): re-enable once bug is fixed.
+ // absl::log_internal::ScopedMinLogLevel scoped_min_log_level(GetParam());
+
+ // Ensure that `severity` is not a compile-time constant to prove that
+ // `LOG(LEVEL(severity))` works regardless:
+ auto volatile severity = absl::LogSeverity::kFatal;
+
+ const int log_line = __LINE__ + 1;
+ auto do_log = [severity] { ABSL_TEST_LOG(LEVEL(severity)) << "hello world"; };
+
+ EXPECT_EXIT(
+ {
+ absl::ScopedMockLog test_sink(
+ absl::MockLogDefault::kDisallowUnexpected);
+
+ EXPECT_CALL(test_sink, Send)
+ .Times(AnyNumber())
+ .WillRepeatedly(DeathTestUnexpectedLogging());
+
+ ::testing::InSequence s;
+
+ if (LoggingEnabledAt(absl::LogSeverity::kFatal)) {
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(SourceFilename(Eq(__FILE__)),
+ SourceBasename(Eq("log_basic_test_impl.inc")),
+ SourceLine(Eq(log_line)), Prefix(IsTrue()),
+ LogSeverity(Eq(absl::LogSeverity::kFatal)),
+ TimestampInMatchWindow(),
+ ThreadID(Eq(absl::base_internal::GetTID())),
+ TextMessage(Eq("hello world")),
+ Verbosity(Eq(absl::LogEntry::kNoVerbosityLevel)),
+ ENCODED_MESSAGE(EqualsProto(
+ R"pb(value { literal: "hello world" })pb")),
+ Stacktrace(IsEmpty()))))
+ .WillOnce(DeathTestExpectedLogging());
+
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(SourceFilename(Eq(__FILE__)),
+ SourceBasename(Eq("log_basic_test_impl.inc")),
+ SourceLine(Eq(log_line)), Prefix(IsTrue()),
+ LogSeverity(Eq(absl::LogSeverity::kFatal)),
+ TimestampInMatchWindow(),
+ ThreadID(Eq(absl::base_internal::GetTID())),
+ TextMessage(Eq("hello world")),
+ Verbosity(Eq(absl::LogEntry::kNoVerbosityLevel)),
+ ENCODED_MESSAGE(EqualsProto(
+ R"pb(value { literal: "hello world" })pb")),
+ Stacktrace(Not(IsEmpty())))))
+ .WillOnce(DeathTestExpectedLogging());
+ }
+
+ test_sink.StartCapturingLogs();
+ do_log();
+ },
+ DiedOfFatal, DeathTestValidateExpectations());
+}
+#endif
+
+TEST_P(BasicLogTest, LevelClampsNegativeValues) {
+ absl::log_internal::ScopedMinLogLevel scoped_min_log_level(GetParam());
+
+ if (!LoggingEnabledAt(absl::LogSeverity::kInfo)) {
+ GTEST_SKIP() << "This test cases required INFO log to be enabled";
+ return;
+ }
+
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ EXPECT_CALL(test_sink, Send(LogSeverity(Eq(absl::LogSeverity::kInfo))));
+
+ test_sink.StartCapturingLogs();
+ ABSL_TEST_LOG(LEVEL(-1)) << "hello world";
+}
+
+TEST_P(BasicLogTest, LevelClampsLargeValues) {
+ absl::log_internal::ScopedMinLogLevel scoped_min_log_level(GetParam());
+
+ if (!LoggingEnabledAt(absl::LogSeverity::kError)) {
+ GTEST_SKIP() << "This test cases required ERROR log to be enabled";
+ return;
+ }
+
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ EXPECT_CALL(test_sink, Send(LogSeverity(Eq(absl::LogSeverity::kError))));
+
+ test_sink.StartCapturingLogs();
+ ABSL_TEST_LOG(LEVEL(static_cast<int>(absl::LogSeverity::kFatal) + 1))
+ << "hello world";
+}
+
+TEST(ErrnoPreservationTest, InSeverityExpression) {
+ errno = 77;
+ int saved_errno;
+ ABSL_TEST_LOG(LEVEL((saved_errno = errno, absl::LogSeverity::kInfo)));
+ EXPECT_THAT(saved_errno, Eq(77));
+}
+
+TEST(ErrnoPreservationTest, InStreamedExpression) {
+ if (!LoggingEnabledAt(absl::LogSeverity::kInfo)) {
+ GTEST_SKIP() << "This test cases required INFO log to be enabled";
+ return;
+ }
+
+ errno = 77;
+ int saved_errno = 0;
+ ABSL_TEST_LOG(INFO) << (saved_errno = errno, "hello world");
+ EXPECT_THAT(saved_errno, Eq(77));
+}
+
+TEST(ErrnoPreservationTest, AfterStatement) {
+ errno = 77;
+ ABSL_TEST_LOG(INFO);
+ const int saved_errno = errno;
+ EXPECT_THAT(saved_errno, Eq(77));
+}
+
+// Tests that using a variable/parameter in a logging statement suppresses
+// unused-variable/parameter warnings.
+// -----------------------------------------------------------------------
+class UnusedVariableWarningCompileTest {
+ // These four don't prove anything unless `ABSL_MIN_LOG_LEVEL` is greater than
+ // `kInfo`.
+ static void LoggedVariable() {
+ const int x = 0;
+ ABSL_TEST_LOG(INFO) << x;
+ }
+ static void LoggedParameter(const int x) { ABSL_TEST_LOG(INFO) << x; }
+ static void SeverityVariable() {
+ const int x = 0;
+ ABSL_TEST_LOG(LEVEL(x)) << "hello world";
+ }
+ static void SeverityParameter(const int x) {
+ ABSL_TEST_LOG(LEVEL(x)) << "hello world";
+ }
+};
+
+} // namespace absl_log_internal
+
+#endif // ABSL_LOG_LOG_BASIC_TEST_IMPL_H_
diff --git a/absl/log/log_benchmark.cc b/absl/log/log_benchmark.cc
new file mode 100644
index 0000000..45d9a5d
--- /dev/null
+++ b/absl/log/log_benchmark.cc
@@ -0,0 +1,97 @@
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/base/attributes.h"
+#include "absl/base/log_severity.h"
+#include "absl/flags/flag.h"
+#include "absl/log/check.h"
+#include "absl/log/globals.h"
+#include "absl/log/log.h"
+#include "absl/log/log_entry.h"
+#include "absl/log/log_sink.h"
+#include "absl/log/log_sink_registry.h"
+#include "benchmark/benchmark.h"
+
+namespace {
+
+class NullLogSink : public absl::LogSink {
+ public:
+ NullLogSink() { absl::AddLogSink(this); }
+
+ ~NullLogSink() override { absl::RemoveLogSink(this); }
+
+ void Send(const absl::LogEntry&) override {}
+};
+
+constexpr int x = -1;
+
+void BM_SuccessfulBinaryCheck(benchmark::State& state) {
+ int n = 0;
+ while (state.KeepRunningBatch(8)) {
+ CHECK_GE(n, x);
+ CHECK_GE(n, x);
+ CHECK_GE(n, x);
+ CHECK_GE(n, x);
+ CHECK_GE(n, x);
+ CHECK_GE(n, x);
+ CHECK_GE(n, x);
+ CHECK_GE(n, x);
+ ++n;
+ }
+ benchmark::DoNotOptimize(n);
+}
+BENCHMARK(BM_SuccessfulBinaryCheck);
+
+static void BM_SuccessfulUnaryCheck(benchmark::State& state) {
+ int n = 0;
+ while (state.KeepRunningBatch(8)) {
+ CHECK(n >= x);
+ CHECK(n >= x);
+ CHECK(n >= x);
+ CHECK(n >= x);
+ CHECK(n >= x);
+ CHECK(n >= x);
+ CHECK(n >= x);
+ CHECK(n >= x);
+ ++n;
+ }
+ benchmark::DoNotOptimize(n);
+}
+BENCHMARK(BM_SuccessfulUnaryCheck);
+
+static void BM_DisabledLogOverhead(benchmark::State& state) {
+ absl::ScopedStderrThreshold disable_stderr_logging(
+ absl::LogSeverityAtLeast::kInfinity);
+ absl::log_internal::ScopedMinLogLevel scoped_min_log_level(
+ absl::LogSeverityAtLeast::kInfinity);
+ for (auto _ : state) {
+ LOG(INFO);
+ }
+}
+BENCHMARK(BM_DisabledLogOverhead);
+
+static void BM_EnabledLogOverhead(benchmark::State& state) {
+ absl::ScopedStderrThreshold stderr_logging(
+ absl::LogSeverityAtLeast::kInfinity);
+ absl::log_internal::ScopedMinLogLevel scoped_min_log_level(
+ absl::LogSeverityAtLeast::kInfo);
+ ABSL_ATTRIBUTE_UNUSED NullLogSink null_sink;
+ for (auto _ : state) {
+ LOG(INFO);
+ }
+}
+BENCHMARK(BM_EnabledLogOverhead);
+
+} // namespace
+
diff --git a/absl/log/log_entry.cc b/absl/log/log_entry.cc
new file mode 100644
index 0000000..fe58a57
--- /dev/null
+++ b/absl/log/log_entry.cc
@@ -0,0 +1,41 @@
+//
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/log/log_entry.h"
+
+#include "absl/base/config.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+
+#ifdef ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL
+constexpr int LogEntry::kNoVerbosityLevel;
+constexpr int LogEntry::kNoVerboseLevel;
+#endif
+
+// https://github.com/abseil/abseil-cpp/issues/1465
+// CMake builds on Apple platforms error when libraries are empty.
+// Our CMake configuration can avoid this error on header-only libraries,
+// but since this library is conditionally empty, including a single
+// variable is an easy workaround.
+#ifdef __APPLE__
+namespace log_internal {
+extern const char kAvoidEmptyLogEntryLibraryWarning;
+const char kAvoidEmptyLogEntryLibraryWarning = 0;
+} // namespace log_internal
+#endif // __APPLE__
+
+ABSL_NAMESPACE_END
+} // namespace absl
diff --git a/absl/log/log_entry.h b/absl/log/log_entry.h
new file mode 100644
index 0000000..7a55dfe
--- /dev/null
+++ b/absl/log/log_entry.h
@@ -0,0 +1,221 @@
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// -----------------------------------------------------------------------------
+// File: log/log_entry.h
+// -----------------------------------------------------------------------------
+//
+// This header declares `class absl::LogEntry`, which represents a log record as
+// passed to `LogSink::Send`. Data returned by pointer or by reference or by
+// `absl::string_view` must be copied if they are needed after the lifetime of
+// the `absl::LogEntry`.
+
+#ifndef ABSL_LOG_LOG_ENTRY_H_
+#define ABSL_LOG_LOG_ENTRY_H_
+
+#include <cstddef>
+#include <string>
+
+#include "absl/base/attributes.h"
+#include "absl/base/config.h"
+#include "absl/base/log_severity.h"
+#include "absl/log/internal/config.h"
+#include "absl/strings/string_view.h"
+#include "absl/time/time.h"
+#include "absl/types/span.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+
+namespace log_internal {
+// Test only friend.
+class LogEntryTestPeer;
+class LogMessage;
+} // namespace log_internal
+
+// LogEntry
+//
+// Represents a single entry in a log, i.e., one `LOG` statement or failed
+// `CHECK`.
+//
+// `LogEntry` is thread-compatible.
+class LogEntry final {
+ public:
+ using tid_t = log_internal::Tid;
+
+ // For non-verbose log entries, `verbosity()` returns `kNoVerbosityLevel`.
+ static constexpr int kNoVerbosityLevel = -1;
+ static constexpr int kNoVerboseLevel = -1; // TO BE removed
+
+ // Pass `LogEntry` by reference, and do not store it as its state does not
+ // outlive the call to `LogSink::Send()`.
+ LogEntry(const LogEntry&) = delete;
+ LogEntry& operator=(const LogEntry&) = delete;
+
+ // Source file and line where the log message occurred. Taken from `__FILE__`
+ // and `__LINE__` unless overridden by `LOG(...).AtLocation(...)`.
+ //
+ // Take special care not to use the values returned by `source_filename()` and
+ // `source_basename()` after the lifetime of the entry. This is always
+ // incorrect, but it will often work in practice because they usually point
+ // into a statically allocated character array obtained from `__FILE__`.
+ // Statements like `LOG(INFO).AtLocation(std::string(...), ...)` will expose
+ // the bug. If you need the data later, you must copy them.
+ absl::string_view source_filename() const ABSL_ATTRIBUTE_LIFETIME_BOUND {
+ return full_filename_;
+ }
+ absl::string_view source_basename() const ABSL_ATTRIBUTE_LIFETIME_BOUND {
+ return base_filename_;
+ }
+ int source_line() const { return line_; }
+
+ // LogEntry::prefix()
+ //
+ // True unless the metadata prefix was suppressed once by
+ // `LOG(...).NoPrefix()` or globally by `absl::EnableLogPrefix(false)`.
+ // Implies `text_message_with_prefix() == text_message()`.
+ bool prefix() const { return prefix_; }
+
+ // LogEntry::log_severity()
+ //
+ // Returns this entry's severity. For `LOG`, taken from the first argument;
+ // for `CHECK`, always `absl::LogSeverity::kFatal`.
+ absl::LogSeverity log_severity() const { return severity_; }
+
+ // LogEntry::verbosity()
+ //
+ // Returns this entry's verbosity, or `kNoVerbosityLevel` for a non-verbose
+ // entry. Taken from the argument to `VLOG` or from
+ // `LOG(...).WithVerbosity(...)`.
+ int verbosity() const { return verbose_level_; }
+
+ // LogEntry::timestamp()
+ //
+ // Returns the time at which this entry was written. Captured during
+ // evaluation of `LOG`, but can be overridden by
+ // `LOG(...).WithTimestamp(...)`.
+ //
+ // Take care not to rely on timestamps increasing monotonically, or even to
+ // rely on timestamps having any particular relationship with reality (since
+ // they can be overridden).
+ absl::Time timestamp() const { return timestamp_; }
+
+ // LogEntry::tid()
+ //
+ // Returns the ID of the thread that wrote this entry. Captured during
+ // evaluation of `LOG`, but can be overridden by `LOG(...).WithThreadID(...)`.
+ //
+ // Take care not to *rely* on reported thread IDs as they can be overridden as
+ // specified above.
+ tid_t tid() const { return tid_; }
+
+ // Text-formatted version of the log message. An underlying buffer holds
+ // these contiguous data:
+ //
+ // * A prefix formed by formatting metadata (timestamp, filename, line number,
+ // etc.)
+ // The prefix may be empty - see `LogEntry::prefix()` - and may rarely be
+ // truncated if the metadata are very long.
+ // * The streamed data
+ // The data may be empty if nothing was streamed, or may be truncated to fit
+ // the buffer.
+ // * A newline
+ // * A nul terminator
+ //
+ // The newline and nul terminator will be present even if the prefix and/or
+ // data are truncated.
+ //
+ // These methods give access to the most commonly useful substrings of the
+ // buffer's contents. Other combinations can be obtained with substring
+ // arithmetic.
+ //
+ // The buffer does not outlive the entry; if you need the data later, you must
+ // copy them.
+ absl::string_view text_message_with_prefix_and_newline() const
+ ABSL_ATTRIBUTE_LIFETIME_BOUND {
+ return absl::string_view(
+ text_message_with_prefix_and_newline_and_nul_.data(),
+ text_message_with_prefix_and_newline_and_nul_.size() - 1);
+ }
+ absl::string_view text_message_with_prefix() const
+ ABSL_ATTRIBUTE_LIFETIME_BOUND {
+ return absl::string_view(
+ text_message_with_prefix_and_newline_and_nul_.data(),
+ text_message_with_prefix_and_newline_and_nul_.size() - 2);
+ }
+ absl::string_view text_message_with_newline() const
+ ABSL_ATTRIBUTE_LIFETIME_BOUND {
+ return absl::string_view(
+ text_message_with_prefix_and_newline_and_nul_.data() + prefix_len_,
+ text_message_with_prefix_and_newline_and_nul_.size() - prefix_len_ - 1);
+ }
+ absl::string_view text_message() const ABSL_ATTRIBUTE_LIFETIME_BOUND {
+ return absl::string_view(
+ text_message_with_prefix_and_newline_and_nul_.data() + prefix_len_,
+ text_message_with_prefix_and_newline_and_nul_.size() - prefix_len_ - 2);
+ }
+ const char* text_message_with_prefix_and_newline_c_str() const
+ ABSL_ATTRIBUTE_LIFETIME_BOUND {
+ return text_message_with_prefix_and_newline_and_nul_.data();
+ }
+
+ // Returns a serialized protobuf holding the operands streamed into this
+ // log message. The message definition is not yet published.
+ //
+ // The buffer does not outlive the entry; if you need the data later, you must
+ // copy them.
+ absl::string_view encoded_message() const ABSL_ATTRIBUTE_LIFETIME_BOUND {
+ return encoding_;
+ }
+
+ // LogEntry::stacktrace()
+ //
+ // Optional stacktrace, e.g. for `FATAL` logs and failed `CHECK`s.
+ //
+ // Fatal entries are dispatched to each sink twice: first with all data and
+ // metadata but no stacktrace, and then with the stacktrace. This is done
+ // because stacktrace collection is sometimes slow and fallible, and it's
+ // critical to log enough information to diagnose the failure even if the
+ // stacktrace collection hangs.
+ //
+ // The buffer does not outlive the entry; if you need the data later, you must
+ // copy them.
+ absl::string_view stacktrace() const ABSL_ATTRIBUTE_LIFETIME_BOUND {
+ return stacktrace_;
+ }
+
+ private:
+ LogEntry() = default;
+
+ absl::string_view full_filename_;
+ absl::string_view base_filename_;
+ int line_;
+ bool prefix_;
+ absl::LogSeverity severity_;
+ int verbose_level_; // >=0 for `VLOG`, etc.; otherwise `kNoVerbosityLevel`.
+ absl::Time timestamp_;
+ tid_t tid_;
+ absl::Span<const char> text_message_with_prefix_and_newline_and_nul_;
+ size_t prefix_len_;
+ absl::string_view encoding_;
+ std::string stacktrace_;
+
+ friend class log_internal::LogEntryTestPeer;
+ friend class log_internal::LogMessage;
+};
+
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_LOG_LOG_ENTRY_H_
diff --git a/absl/log/log_entry_test.cc b/absl/log/log_entry_test.cc
new file mode 100644
index 0000000..d9bfa1f
--- /dev/null
+++ b/absl/log/log_entry_test.cc
@@ -0,0 +1,468 @@
+//
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/log/log_entry.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <cstring>
+#include <limits>
+#include <string>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/base/attributes.h"
+#include "absl/base/config.h"
+#include "absl/base/log_severity.h"
+#include "absl/log/internal/append_truncated.h"
+#include "absl/log/internal/log_format.h"
+#include "absl/log/internal/test_helpers.h"
+#include "absl/strings/numbers.h"
+#include "absl/strings/str_split.h"
+#include "absl/strings/string_view.h"
+#include "absl/time/civil_time.h"
+#include "absl/time/time.h"
+#include "absl/types/span.h"
+
+namespace {
+using ::absl::log_internal::LogEntryTestPeer;
+using ::testing::Eq;
+using ::testing::IsTrue;
+using ::testing::StartsWith;
+using ::testing::StrEq;
+
+auto* test_env ABSL_ATTRIBUTE_UNUSED = ::testing::AddGlobalTestEnvironment(
+ new absl::log_internal::LogTestEnvironment);
+} // namespace
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace log_internal {
+
+class LogEntryTestPeer {
+ public:
+ LogEntryTestPeer(absl::string_view base_filename, int line, bool prefix,
+ absl::LogSeverity severity, absl::string_view timestamp,
+ absl::LogEntry::tid_t tid, PrefixFormat format,
+ absl::string_view text_message)
+ : format_{format}, buf_(15000, '\0') {
+ entry_.base_filename_ = base_filename;
+ entry_.line_ = line;
+ entry_.prefix_ = prefix;
+ entry_.severity_ = severity;
+ std::string time_err;
+ EXPECT_THAT(
+ absl::ParseTime("%Y-%m-%d%ET%H:%M:%E*S", timestamp,
+ absl::LocalTimeZone(), &entry_.timestamp_, &time_err),
+ IsTrue())
+ << "Failed to parse time " << timestamp << ": " << time_err;
+ entry_.tid_ = tid;
+ std::pair<absl::string_view, std::string> timestamp_bits =
+ absl::StrSplit(timestamp, absl::ByChar('.'));
+ EXPECT_THAT(absl::ParseCivilTime(timestamp_bits.first, &ci_.cs), IsTrue())
+ << "Failed to parse time " << timestamp_bits.first;
+ timestamp_bits.second.resize(9, '0');
+ int64_t nanos = 0;
+ EXPECT_THAT(absl::SimpleAtoi(timestamp_bits.second, &nanos), IsTrue())
+ << "Failed to parse time " << timestamp_bits.first;
+ ci_.subsecond = absl::Nanoseconds(nanos);
+
+ absl::Span<char> view = absl::MakeSpan(buf_);
+ view.remove_suffix(2);
+ entry_.prefix_len_ =
+ entry_.prefix_
+ ? log_internal::FormatLogPrefix(
+ entry_.log_severity(), entry_.timestamp(), entry_.tid(),
+ entry_.source_basename(), entry_.source_line(), format_, view)
+ : 0;
+
+ EXPECT_THAT(entry_.prefix_len_,
+ Eq(static_cast<size_t>(view.data() - buf_.data())));
+ log_internal::AppendTruncated(text_message, view);
+ view = absl::Span<char>(view.data(), view.size() + 2);
+ view[0] = '\n';
+ view[1] = '\0';
+ view.remove_prefix(2);
+ buf_.resize(static_cast<size_t>(view.data() - buf_.data()));
+ entry_.text_message_with_prefix_and_newline_and_nul_ = absl::MakeSpan(buf_);
+ }
+ LogEntryTestPeer(const LogEntryTestPeer&) = delete;
+ LogEntryTestPeer& operator=(const LogEntryTestPeer&) = delete;
+
+ std::string FormatLogMessage() const {
+ return log_internal::FormatLogMessage(
+ entry_.log_severity(), ci_.cs, ci_.subsecond, entry_.tid(),
+ entry_.source_basename(), entry_.source_line(), format_,
+ entry_.text_message());
+ }
+ std::string FormatPrefixIntoSizedBuffer(size_t sz) {
+ std::string str(sz, '\0');
+ absl::Span<char> buf(&str[0], str.size());
+ const size_t prefix_size = log_internal::FormatLogPrefix(
+ entry_.log_severity(), entry_.timestamp(), entry_.tid(),
+ entry_.source_basename(), entry_.source_line(), format_, buf);
+ EXPECT_THAT(prefix_size, Eq(static_cast<size_t>(buf.data() - str.data())));
+ str.resize(prefix_size);
+ return str;
+ }
+ const absl::LogEntry& entry() const { return entry_; }
+
+ private:
+ absl::LogEntry entry_;
+ PrefixFormat format_;
+ absl::TimeZone::CivilInfo ci_;
+ std::vector<char> buf_;
+};
+
+} // namespace log_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+
+namespace {
+constexpr bool kUsePrefix = true, kNoPrefix = false;
+
+TEST(LogEntryTest, Baseline) {
+ LogEntryTestPeer entry("foo.cc", 1234, kUsePrefix, absl::LogSeverity::kInfo,
+ "2020-01-02T03:04:05.6789", 451,
+ absl::log_internal::PrefixFormat::kNotRaw,
+ "hello world");
+ EXPECT_THAT(entry.FormatLogMessage(),
+ Eq("I0102 03:04:05.678900 451 foo.cc:1234] hello world"));
+ EXPECT_THAT(entry.FormatPrefixIntoSizedBuffer(1000),
+ Eq("I0102 03:04:05.678900 451 foo.cc:1234] "));
+ for (size_t sz = strlen("I0102 03:04:05.678900 451 foo.cc:1234] ") + 20;
+ sz != std::numeric_limits<size_t>::max(); sz--)
+ EXPECT_THAT("I0102 03:04:05.678900 451 foo.cc:1234] ",
+ StartsWith(entry.FormatPrefixIntoSizedBuffer(sz)));
+
+ EXPECT_THAT(entry.entry().text_message_with_prefix_and_newline(),
+ Eq("I0102 03:04:05.678900 451 foo.cc:1234] hello world\n"));
+ EXPECT_THAT(
+ entry.entry().text_message_with_prefix_and_newline_c_str(),
+ StrEq("I0102 03:04:05.678900 451 foo.cc:1234] hello world\n"));
+ EXPECT_THAT(entry.entry().text_message_with_prefix(),
+ Eq("I0102 03:04:05.678900 451 foo.cc:1234] hello world"));
+ EXPECT_THAT(entry.entry().text_message(), Eq("hello world"));
+}
+
+TEST(LogEntryTest, NoPrefix) {
+ LogEntryTestPeer entry("foo.cc", 1234, kNoPrefix, absl::LogSeverity::kInfo,
+ "2020-01-02T03:04:05.6789", 451,
+ absl::log_internal::PrefixFormat::kNotRaw,
+ "hello world");
+ EXPECT_THAT(entry.FormatLogMessage(),
+ Eq("I0102 03:04:05.678900 451 foo.cc:1234] hello world"));
+ // These methods are not responsible for honoring `prefix()`.
+ EXPECT_THAT(entry.FormatPrefixIntoSizedBuffer(1000),
+ Eq("I0102 03:04:05.678900 451 foo.cc:1234] "));
+ for (size_t sz = strlen("I0102 03:04:05.678900 451 foo.cc:1234] ") + 20;
+ sz != std::numeric_limits<size_t>::max(); sz--)
+ EXPECT_THAT("I0102 03:04:05.678900 451 foo.cc:1234] ",
+ StartsWith(entry.FormatPrefixIntoSizedBuffer(sz)));
+
+ EXPECT_THAT(entry.entry().text_message_with_prefix_and_newline(),
+ Eq("hello world\n"));
+ EXPECT_THAT(entry.entry().text_message_with_prefix_and_newline_c_str(),
+ StrEq("hello world\n"));
+ EXPECT_THAT(entry.entry().text_message_with_prefix(), Eq("hello world"));
+ EXPECT_THAT(entry.entry().text_message(), Eq("hello world"));
+}
+
+TEST(LogEntryTest, EmptyFields) {
+ LogEntryTestPeer entry("", 0, kUsePrefix, absl::LogSeverity::kInfo,
+ "2020-01-02T03:04:05", 0,
+ absl::log_internal::PrefixFormat::kNotRaw, "");
+ const std::string format_message = entry.FormatLogMessage();
+ EXPECT_THAT(format_message, Eq("I0102 03:04:05.000000 0 :0] "));
+ EXPECT_THAT(entry.FormatPrefixIntoSizedBuffer(1000), Eq(format_message));
+ for (size_t sz = format_message.size() + 20;
+ sz != std::numeric_limits<size_t>::max(); sz--)
+ EXPECT_THAT(format_message,
+ StartsWith(entry.FormatPrefixIntoSizedBuffer(sz)));
+
+ EXPECT_THAT(entry.entry().text_message_with_prefix_and_newline(),
+ Eq("I0102 03:04:05.000000 0 :0] \n"));
+ EXPECT_THAT(entry.entry().text_message_with_prefix_and_newline_c_str(),
+ StrEq("I0102 03:04:05.000000 0 :0] \n"));
+ EXPECT_THAT(entry.entry().text_message_with_prefix(),
+ Eq("I0102 03:04:05.000000 0 :0] "));
+ EXPECT_THAT(entry.entry().text_message(), Eq(""));
+}
+
+TEST(LogEntryTest, NegativeFields) {
+ // When Abseil's minimum C++ version is C++17, this conditional can be
+ // converted to a constexpr if and the static_cast below removed.
+ if (std::is_signed<absl::LogEntry::tid_t>::value) {
+ LogEntryTestPeer entry(
+ "foo.cc", -1234, kUsePrefix, absl::LogSeverity::kInfo,
+ "2020-01-02T03:04:05.6789", static_cast<absl::LogEntry::tid_t>(-451),
+ absl::log_internal::PrefixFormat::kNotRaw, "hello world");
+ EXPECT_THAT(entry.FormatLogMessage(),
+ Eq("I0102 03:04:05.678900 -451 foo.cc:-1234] hello world"));
+ EXPECT_THAT(entry.FormatPrefixIntoSizedBuffer(1000),
+ Eq("I0102 03:04:05.678900 -451 foo.cc:-1234] "));
+ for (size_t sz =
+ strlen("I0102 03:04:05.678900 -451 foo.cc:-1234] ") + 20;
+ sz != std::numeric_limits<size_t>::max(); sz--)
+ EXPECT_THAT("I0102 03:04:05.678900 -451 foo.cc:-1234] ",
+ StartsWith(entry.FormatPrefixIntoSizedBuffer(sz)));
+
+ EXPECT_THAT(
+ entry.entry().text_message_with_prefix_and_newline(),
+ Eq("I0102 03:04:05.678900 -451 foo.cc:-1234] hello world\n"));
+ EXPECT_THAT(
+ entry.entry().text_message_with_prefix_and_newline_c_str(),
+ StrEq("I0102 03:04:05.678900 -451 foo.cc:-1234] hello world\n"));
+ EXPECT_THAT(entry.entry().text_message_with_prefix(),
+ Eq("I0102 03:04:05.678900 -451 foo.cc:-1234] hello world"));
+ EXPECT_THAT(entry.entry().text_message(), Eq("hello world"));
+ } else {
+ LogEntryTestPeer entry("foo.cc", -1234, kUsePrefix,
+ absl::LogSeverity::kInfo, "2020-01-02T03:04:05.6789",
+ 451, absl::log_internal::PrefixFormat::kNotRaw,
+ "hello world");
+ EXPECT_THAT(entry.FormatLogMessage(),
+ Eq("I0102 03:04:05.678900 451 foo.cc:-1234] hello world"));
+ EXPECT_THAT(entry.FormatPrefixIntoSizedBuffer(1000),
+ Eq("I0102 03:04:05.678900 451 foo.cc:-1234] "));
+ for (size_t sz =
+ strlen("I0102 03:04:05.678900 451 foo.cc:-1234] ") + 20;
+ sz != std::numeric_limits<size_t>::max(); sz--)
+ EXPECT_THAT("I0102 03:04:05.678900 451 foo.cc:-1234] ",
+ StartsWith(entry.FormatPrefixIntoSizedBuffer(sz)));
+
+ EXPECT_THAT(
+ entry.entry().text_message_with_prefix_and_newline(),
+ Eq("I0102 03:04:05.678900 451 foo.cc:-1234] hello world\n"));
+ EXPECT_THAT(
+ entry.entry().text_message_with_prefix_and_newline_c_str(),
+ StrEq("I0102 03:04:05.678900 451 foo.cc:-1234] hello world\n"));
+ EXPECT_THAT(entry.entry().text_message_with_prefix(),
+ Eq("I0102 03:04:05.678900 451 foo.cc:-1234] hello world"));
+ EXPECT_THAT(entry.entry().text_message(), Eq("hello world"));
+ }
+}
+
+TEST(LogEntryTest, LongFields) {
+ LogEntryTestPeer entry(
+ "I am the very model of a modern Major-General / "
+ "I've information vegetable, animal, and mineral.",
+ 2147483647, kUsePrefix, absl::LogSeverity::kInfo,
+ "2020-01-02T03:04:05.678967896789", 2147483647,
+ absl::log_internal::PrefixFormat::kNotRaw,
+ "I know the kings of England, and I quote the fights historical / "
+ "From Marathon to Waterloo, in order categorical.");
+ EXPECT_THAT(entry.FormatLogMessage(),
+ Eq("I0102 03:04:05.678967 2147483647 I am the very model of a "
+ "modern Major-General / I've information vegetable, animal, "
+ "and mineral.:2147483647] I know the kings of England, and I "
+ "quote the fights historical / From Marathon to Waterloo, in "
+ "order categorical."));
+ EXPECT_THAT(entry.FormatPrefixIntoSizedBuffer(1000),
+ Eq("I0102 03:04:05.678967 2147483647 I am the very model of a "
+ "modern Major-General / I've information vegetable, animal, "
+ "and mineral.:2147483647] "));
+ for (size_t sz =
+ strlen("I0102 03:04:05.678967 2147483647 I am the very model of a "
+ "modern Major-General / I've information vegetable, animal, "
+ "and mineral.:2147483647] ") +
+ 20;
+ sz != std::numeric_limits<size_t>::max(); sz--)
+ EXPECT_THAT(
+ "I0102 03:04:05.678967 2147483647 I am the very model of a "
+ "modern Major-General / I've information vegetable, animal, "
+ "and mineral.:2147483647] ",
+ StartsWith(entry.FormatPrefixIntoSizedBuffer(sz)));
+
+ EXPECT_THAT(entry.entry().text_message_with_prefix_and_newline(),
+ Eq("I0102 03:04:05.678967 2147483647 I am the very model of a "
+ "modern Major-General / I've information vegetable, animal, "
+ "and mineral.:2147483647] I know the kings of England, and I "
+ "quote the fights historical / From Marathon to Waterloo, in "
+ "order categorical.\n"));
+ EXPECT_THAT(
+ entry.entry().text_message_with_prefix_and_newline_c_str(),
+ StrEq("I0102 03:04:05.678967 2147483647 I am the very model of a "
+ "modern Major-General / I've information vegetable, animal, "
+ "and mineral.:2147483647] I know the kings of England, and I "
+ "quote the fights historical / From Marathon to Waterloo, in "
+ "order categorical.\n"));
+ EXPECT_THAT(entry.entry().text_message_with_prefix(),
+ Eq("I0102 03:04:05.678967 2147483647 I am the very model of a "
+ "modern Major-General / I've information vegetable, animal, "
+ "and mineral.:2147483647] I know the kings of England, and I "
+ "quote the fights historical / From Marathon to Waterloo, in "
+ "order categorical."));
+ EXPECT_THAT(
+ entry.entry().text_message(),
+ Eq("I know the kings of England, and I quote the fights historical / "
+ "From Marathon to Waterloo, in order categorical."));
+}
+
+TEST(LogEntryTest, LongNegativeFields) {
+ // When Abseil's minimum C++ version is C++17, this conditional can be
+ // converted to a constexpr if and the static_cast below removed.
+ if (std::is_signed<absl::LogEntry::tid_t>::value) {
+ LogEntryTestPeer entry(
+ "I am the very model of a modern Major-General / "
+ "I've information vegetable, animal, and mineral.",
+ -2147483647, kUsePrefix, absl::LogSeverity::kInfo,
+ "2020-01-02T03:04:05.678967896789",
+ static_cast<absl::LogEntry::tid_t>(-2147483647),
+ absl::log_internal::PrefixFormat::kNotRaw,
+ "I know the kings of England, and I quote the fights historical / "
+ "From Marathon to Waterloo, in order categorical.");
+ EXPECT_THAT(
+ entry.FormatLogMessage(),
+ Eq("I0102 03:04:05.678967 -2147483647 I am the very model of a "
+ "modern Major-General / I've information vegetable, animal, "
+ "and mineral.:-2147483647] I know the kings of England, and I "
+ "quote the fights historical / From Marathon to Waterloo, in "
+ "order categorical."));
+ EXPECT_THAT(entry.FormatPrefixIntoSizedBuffer(1000),
+ Eq("I0102 03:04:05.678967 -2147483647 I am the very model of a "
+ "modern Major-General / I've information vegetable, animal, "
+ "and mineral.:-2147483647] "));
+ for (size_t sz =
+ strlen(
+ "I0102 03:04:05.678967 -2147483647 I am the very model of a "
+ "modern Major-General / I've information vegetable, animal, "
+ "and mineral.:-2147483647] ") +
+ 20;
+ sz != std::numeric_limits<size_t>::max(); sz--)
+ EXPECT_THAT(
+ "I0102 03:04:05.678967 -2147483647 I am the very model of a "
+ "modern Major-General / I've information vegetable, animal, "
+ "and mineral.:-2147483647] ",
+ StartsWith(entry.FormatPrefixIntoSizedBuffer(sz)));
+
+ EXPECT_THAT(
+ entry.entry().text_message_with_prefix_and_newline(),
+ Eq("I0102 03:04:05.678967 -2147483647 I am the very model of a "
+ "modern Major-General / I've information vegetable, animal, "
+ "and mineral.:-2147483647] I know the kings of England, and I "
+ "quote the fights historical / From Marathon to Waterloo, in "
+ "order categorical.\n"));
+ EXPECT_THAT(
+ entry.entry().text_message_with_prefix_and_newline_c_str(),
+ StrEq("I0102 03:04:05.678967 -2147483647 I am the very model of a "
+ "modern Major-General / I've information vegetable, animal, "
+ "and mineral.:-2147483647] I know the kings of England, and I "
+ "quote the fights historical / From Marathon to Waterloo, in "
+ "order categorical.\n"));
+ EXPECT_THAT(
+ entry.entry().text_message_with_prefix(),
+ Eq("I0102 03:04:05.678967 -2147483647 I am the very model of a "
+ "modern Major-General / I've information vegetable, animal, "
+ "and mineral.:-2147483647] I know the kings of England, and I "
+ "quote the fights historical / From Marathon to Waterloo, in "
+ "order categorical."));
+ EXPECT_THAT(
+ entry.entry().text_message(),
+ Eq("I know the kings of England, and I quote the fights historical / "
+ "From Marathon to Waterloo, in order categorical."));
+ } else {
+ LogEntryTestPeer entry(
+ "I am the very model of a modern Major-General / "
+ "I've information vegetable, animal, and mineral.",
+ -2147483647, kUsePrefix, absl::LogSeverity::kInfo,
+ "2020-01-02T03:04:05.678967896789", 2147483647,
+ absl::log_internal::PrefixFormat::kNotRaw,
+ "I know the kings of England, and I quote the fights historical / "
+ "From Marathon to Waterloo, in order categorical.");
+ EXPECT_THAT(
+ entry.FormatLogMessage(),
+ Eq("I0102 03:04:05.678967 2147483647 I am the very model of a "
+ "modern Major-General / I've information vegetable, animal, "
+ "and mineral.:-2147483647] I know the kings of England, and I "
+ "quote the fights historical / From Marathon to Waterloo, in "
+ "order categorical."));
+ EXPECT_THAT(entry.FormatPrefixIntoSizedBuffer(1000),
+ Eq("I0102 03:04:05.678967 2147483647 I am the very model of a "
+ "modern Major-General / I've information vegetable, animal, "
+ "and mineral.:-2147483647] "));
+ for (size_t sz =
+ strlen(
+ "I0102 03:04:05.678967 2147483647 I am the very model of a "
+ "modern Major-General / I've information vegetable, animal, "
+ "and mineral.:-2147483647] ") +
+ 20;
+ sz != std::numeric_limits<size_t>::max(); sz--)
+ EXPECT_THAT(
+ "I0102 03:04:05.678967 2147483647 I am the very model of a "
+ "modern Major-General / I've information vegetable, animal, "
+ "and mineral.:-2147483647] ",
+ StartsWith(entry.FormatPrefixIntoSizedBuffer(sz)));
+
+ EXPECT_THAT(
+ entry.entry().text_message_with_prefix_and_newline(),
+ Eq("I0102 03:04:05.678967 2147483647 I am the very model of a "
+ "modern Major-General / I've information vegetable, animal, "
+ "and mineral.:-2147483647] I know the kings of England, and I "
+ "quote the fights historical / From Marathon to Waterloo, in "
+ "order categorical.\n"));
+ EXPECT_THAT(
+ entry.entry().text_message_with_prefix_and_newline_c_str(),
+ StrEq("I0102 03:04:05.678967 2147483647 I am the very model of a "
+ "modern Major-General / I've information vegetable, animal, "
+ "and mineral.:-2147483647] I know the kings of England, and I "
+ "quote the fights historical / From Marathon to Waterloo, in "
+ "order categorical.\n"));
+ EXPECT_THAT(
+ entry.entry().text_message_with_prefix(),
+ Eq("I0102 03:04:05.678967 2147483647 I am the very model of a "
+ "modern Major-General / I've information vegetable, animal, "
+ "and mineral.:-2147483647] I know the kings of England, and I "
+ "quote the fights historical / From Marathon to Waterloo, in "
+ "order categorical."));
+ EXPECT_THAT(
+ entry.entry().text_message(),
+ Eq("I know the kings of England, and I quote the fights historical / "
+ "From Marathon to Waterloo, in order categorical."));
+ }
+}
+
+TEST(LogEntryTest, Raw) {
+ LogEntryTestPeer entry("foo.cc", 1234, kUsePrefix, absl::LogSeverity::kInfo,
+ "2020-01-02T03:04:05.6789", 451,
+ absl::log_internal::PrefixFormat::kRaw, "hello world");
+ EXPECT_THAT(
+ entry.FormatLogMessage(),
+ Eq("I0102 03:04:05.678900 451 foo.cc:1234] RAW: hello world"));
+ EXPECT_THAT(entry.FormatPrefixIntoSizedBuffer(1000),
+ Eq("I0102 03:04:05.678900 451 foo.cc:1234] RAW: "));
+ for (size_t sz =
+ strlen("I0102 03:04:05.678900 451 foo.cc:1234] RAW: ") + 20;
+ sz != std::numeric_limits<size_t>::max(); sz--)
+ EXPECT_THAT("I0102 03:04:05.678900 451 foo.cc:1234] RAW: ",
+ StartsWith(entry.FormatPrefixIntoSizedBuffer(sz)));
+
+ EXPECT_THAT(
+ entry.entry().text_message_with_prefix_and_newline(),
+ Eq("I0102 03:04:05.678900 451 foo.cc:1234] RAW: hello world\n"));
+ EXPECT_THAT(
+ entry.entry().text_message_with_prefix_and_newline_c_str(),
+ StrEq("I0102 03:04:05.678900 451 foo.cc:1234] RAW: hello world\n"));
+ EXPECT_THAT(
+ entry.entry().text_message_with_prefix(),
+ Eq("I0102 03:04:05.678900 451 foo.cc:1234] RAW: hello world"));
+ EXPECT_THAT(entry.entry().text_message(), Eq("hello world"));
+}
+
+} // namespace
diff --git a/absl/log/log_format_test.cc b/absl/log/log_format_test.cc
new file mode 100644
index 0000000..dbad5d9
--- /dev/null
+++ b/absl/log/log_format_test.cc
@@ -0,0 +1,1872 @@
+//
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <math.h>
+
+#include <iomanip>
+#include <ios>
+#include <limits>
+#include <ostream>
+#include <sstream>
+#include <string>
+#include <type_traits>
+
+#ifdef __ANDROID__
+#include <android/api-level.h>
+#endif
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/log/check.h"
+#include "absl/log/internal/test_matchers.h"
+#include "absl/log/log.h"
+#include "absl/log/scoped_mock_log.h"
+#include "absl/strings/match.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/str_format.h"
+#include "absl/strings/string_view.h"
+#include "absl/types/optional.h"
+
+namespace {
+using ::absl::log_internal::AsString;
+using ::absl::log_internal::MatchesOstream;
+using ::absl::log_internal::RawEncodedMessage;
+using ::absl::log_internal::TextMessage;
+using ::absl::log_internal::TextPrefix;
+using ::testing::AllOf;
+using ::testing::AnyOf;
+using ::testing::Each;
+using ::testing::EndsWith;
+using ::testing::Eq;
+using ::testing::Ge;
+using ::testing::IsEmpty;
+using ::testing::Le;
+using ::testing::SizeIs;
+using ::testing::Types;
+
+// Some aspects of formatting streamed data (e.g. pointer handling) are
+// implementation-defined. Others are buggy in supported implementations.
+// These tests validate that the formatting matches that performed by a
+// `std::ostream` and also that the result is one of a list of expected formats.
+
+std::ostringstream ComparisonStream() {
+ std::ostringstream str;
+ str.setf(std::ios_base::showbase | std::ios_base::boolalpha |
+ std::ios_base::internal);
+ return str;
+}
+
+TEST(LogFormatTest, NoMessage) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const int log_line = __LINE__ + 1;
+ auto do_log = [] { LOG(INFO); };
+
+ EXPECT_CALL(test_sink,
+ Send(AllOf(TextMessage(MatchesOstream(ComparisonStream())),
+ TextPrefix(AsString(EndsWith(absl::StrCat(
+ " log_format_test.cc:", log_line, "] ")))),
+ TextMessage(IsEmpty()),
+ ENCODED_MESSAGE(EqualsProto(R"pb()pb")))));
+
+ test_sink.StartCapturingLogs();
+ do_log();
+}
+
+template <typename T>
+class CharLogFormatTest : public testing::Test {};
+using CharTypes = Types<char, signed char, unsigned char>;
+TYPED_TEST_SUITE(CharLogFormatTest, CharTypes);
+
+TYPED_TEST(CharLogFormatTest, Printable) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const TypeParam value = 'x';
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << value;
+
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(Eq("x")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "x" })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << value;
+}
+
+TYPED_TEST(CharLogFormatTest, Unprintable) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ constexpr auto value = static_cast<TypeParam>(0xeeu);
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << value;
+
+ EXPECT_CALL(
+ test_sink, Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(Eq("\xee")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value {
+ str: "\xee"
+ })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << value;
+}
+
+template <typename T>
+class UnsignedIntLogFormatTest : public testing::Test {};
+using UnsignedIntTypes = Types<unsigned short, unsigned int, // NOLINT
+ unsigned long, unsigned long long>; // NOLINT
+TYPED_TEST_SUITE(UnsignedIntLogFormatTest, UnsignedIntTypes);
+
+TYPED_TEST(UnsignedIntLogFormatTest, Positive) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const TypeParam value = 224;
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << value;
+
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(Eq("224")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "224" })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << value;
+}
+
+TYPED_TEST(UnsignedIntLogFormatTest, BitfieldPositive) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const struct {
+ TypeParam bits : 6;
+ } value{42};
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << value.bits;
+
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(Eq("42")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "42" })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << value.bits;
+}
+
+template <typename T>
+class SignedIntLogFormatTest : public testing::Test {};
+using SignedIntTypes =
+ Types<signed short, signed int, signed long, signed long long>; // NOLINT
+TYPED_TEST_SUITE(SignedIntLogFormatTest, SignedIntTypes);
+
+TYPED_TEST(SignedIntLogFormatTest, Positive) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const TypeParam value = 224;
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << value;
+
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(Eq("224")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "224" })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << value;
+}
+
+TYPED_TEST(SignedIntLogFormatTest, Negative) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const TypeParam value = -112;
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << value;
+
+ EXPECT_CALL(
+ test_sink, Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(Eq("-112")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value {
+ str: "-112"
+ })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << value;
+}
+
+TYPED_TEST(SignedIntLogFormatTest, BitfieldPositive) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const struct {
+ TypeParam bits : 6;
+ } value{21};
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << value.bits;
+
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(Eq("21")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "21" })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << value.bits;
+}
+
+TYPED_TEST(SignedIntLogFormatTest, BitfieldNegative) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const struct {
+ TypeParam bits : 6;
+ } value{-21};
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << value.bits;
+
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(Eq("-21")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "-21" })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << value.bits;
+}
+
+// Ignore these test cases on GCC due to "is too small to hold all values ..."
+// warning.
+#if !defined(__GNUC__) || defined(__clang__)
+// The implementation may choose a signed or unsigned integer type to represent
+// this enum, so it may be tested by either `UnsignedEnumLogFormatTest` or
+// `SignedEnumLogFormatTest`.
+enum MyUnsignedEnum {
+ MyUnsignedEnum_ZERO = 0,
+ MyUnsignedEnum_FORTY_TWO = 42,
+ MyUnsignedEnum_TWO_HUNDRED_TWENTY_FOUR = 224,
+};
+enum MyUnsignedIntEnum : unsigned int {
+ MyUnsignedIntEnum_ZERO = 0,
+ MyUnsignedIntEnum_FORTY_TWO = 42,
+ MyUnsignedIntEnum_TWO_HUNDRED_TWENTY_FOUR = 224,
+};
+
+template <typename T>
+class UnsignedEnumLogFormatTest : public testing::Test {};
+using UnsignedEnumTypes = std::conditional<
+ std::is_signed<std::underlying_type<MyUnsignedEnum>::type>::value,
+ Types<MyUnsignedIntEnum>, Types<MyUnsignedEnum, MyUnsignedIntEnum>>::type;
+TYPED_TEST_SUITE(UnsignedEnumLogFormatTest, UnsignedEnumTypes);
+
+TYPED_TEST(UnsignedEnumLogFormatTest, Positive) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const TypeParam value = static_cast<TypeParam>(224);
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << value;
+
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(Eq("224")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "224" })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << value;
+}
+
+TYPED_TEST(UnsignedEnumLogFormatTest, BitfieldPositive) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const struct {
+ TypeParam bits : 6;
+ } value{static_cast<TypeParam>(42)};
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << value.bits;
+
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(Eq("42")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "42" })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << value.bits;
+}
+
+enum MySignedEnum {
+ MySignedEnum_NEGATIVE_ONE_HUNDRED_TWELVE = -112,
+ MySignedEnum_NEGATIVE_TWENTY_ONE = -21,
+ MySignedEnum_ZERO = 0,
+ MySignedEnum_TWENTY_ONE = 21,
+ MySignedEnum_TWO_HUNDRED_TWENTY_FOUR = 224,
+};
+enum MySignedIntEnum : signed int {
+ MySignedIntEnum_NEGATIVE_ONE_HUNDRED_TWELVE = -112,
+ MySignedIntEnum_NEGATIVE_TWENTY_ONE = -21,
+ MySignedIntEnum_ZERO = 0,
+ MySignedIntEnum_TWENTY_ONE = 21,
+ MySignedIntEnum_TWO_HUNDRED_TWENTY_FOUR = 224,
+};
+
+template <typename T>
+class SignedEnumLogFormatTest : public testing::Test {};
+using SignedEnumTypes = std::conditional<
+ std::is_signed<std::underlying_type<MyUnsignedEnum>::type>::value,
+ Types<MyUnsignedEnum, MySignedEnum, MySignedIntEnum>,
+ Types<MySignedEnum, MySignedIntEnum>>::type;
+TYPED_TEST_SUITE(SignedEnumLogFormatTest, SignedEnumTypes);
+
+TYPED_TEST(SignedEnumLogFormatTest, Positive) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const TypeParam value = static_cast<TypeParam>(224);
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << value;
+
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(Eq("224")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "224" })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << value;
+}
+
+TYPED_TEST(SignedEnumLogFormatTest, Negative) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const TypeParam value = static_cast<TypeParam>(-112);
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << value;
+
+ EXPECT_CALL(
+ test_sink, Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(Eq("-112")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value {
+ str: "-112"
+ })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << value;
+}
+
+TYPED_TEST(SignedEnumLogFormatTest, BitfieldPositive) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const struct {
+ TypeParam bits : 6;
+ } value{static_cast<TypeParam>(21)};
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << value.bits;
+
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(Eq("21")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "21" })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << value.bits;
+}
+
+TYPED_TEST(SignedEnumLogFormatTest, BitfieldNegative) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const struct {
+ TypeParam bits : 6;
+ } value{static_cast<TypeParam>(-21)};
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << value.bits;
+
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(Eq("-21")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "-21" })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << value.bits;
+}
+#endif
+
+TEST(FloatLogFormatTest, Positive) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const float value = 6.02e23f;
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << value;
+
+ EXPECT_CALL(test_sink,
+ Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(Eq("6.02e+23")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value {
+ str: "6.02e+23"
+ })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << value;
+}
+
+TEST(FloatLogFormatTest, Negative) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const float value = -6.02e23f;
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << value;
+
+ EXPECT_CALL(test_sink,
+ Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(Eq("-6.02e+23")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value {
+ str: "-6.02e+23"
+ })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << value;
+}
+
+TEST(FloatLogFormatTest, NegativeExponent) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const float value = 6.02e-23f;
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << value;
+
+ EXPECT_CALL(test_sink,
+ Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(Eq("6.02e-23")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value {
+ str: "6.02e-23"
+ })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << value;
+}
+
+TEST(DoubleLogFormatTest, Positive) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const double value = 6.02e23;
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << value;
+
+ EXPECT_CALL(test_sink,
+ Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(Eq("6.02e+23")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value {
+ str: "6.02e+23"
+ })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << value;
+}
+
+TEST(DoubleLogFormatTest, Negative) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const double value = -6.02e23;
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << value;
+
+ EXPECT_CALL(test_sink,
+ Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(Eq("-6.02e+23")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value {
+ str: "-6.02e+23"
+ })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << value;
+}
+
+TEST(DoubleLogFormatTest, NegativeExponent) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const double value = 6.02e-23;
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << value;
+
+ EXPECT_CALL(test_sink,
+ Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(Eq("6.02e-23")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value {
+ str: "6.02e-23"
+ })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << value;
+}
+
+template <typename T>
+class FloatingPointLogFormatTest : public testing::Test {};
+using FloatingPointTypes = Types<float, double>;
+TYPED_TEST_SUITE(FloatingPointLogFormatTest, FloatingPointTypes);
+
+TYPED_TEST(FloatingPointLogFormatTest, Zero) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const TypeParam value = 0.0;
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << value;
+
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(Eq("0")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "0" })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << value;
+}
+
+TYPED_TEST(FloatingPointLogFormatTest, Integer) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const TypeParam value = 1.0;
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << value;
+
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(Eq("1")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "1" })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << value;
+}
+
+TYPED_TEST(FloatingPointLogFormatTest, Infinity) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const TypeParam value = std::numeric_limits<TypeParam>::infinity();
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << value;
+
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(AnyOf(Eq("inf"), Eq("Inf"))),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "inf" })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << value;
+}
+
+TYPED_TEST(FloatingPointLogFormatTest, NegativeInfinity) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const TypeParam value = -std::numeric_limits<TypeParam>::infinity();
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << value;
+
+ EXPECT_CALL(
+ test_sink, Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(AnyOf(Eq("-inf"), Eq("-Inf"))),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value {
+ str: "-inf"
+ })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << value;
+}
+
+TYPED_TEST(FloatingPointLogFormatTest, NaN) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const TypeParam value = std::numeric_limits<TypeParam>::quiet_NaN();
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << value;
+
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(AnyOf(Eq("nan"), Eq("NaN"))),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "nan" })pb")))));
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << value;
+}
+
+TYPED_TEST(FloatingPointLogFormatTest, NegativeNaN) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const TypeParam value =
+ std::copysign(std::numeric_limits<TypeParam>::quiet_NaN(), -1.0);
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << value;
+
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(
+ TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(AnyOf(Eq("-nan"), Eq("nan"), Eq("NaN"), Eq("-nan(ind)"))),
+ ENCODED_MESSAGE(
+ AnyOf(EqualsProto(R"pb(value { str: "-nan" })pb"),
+ EqualsProto(R"pb(value { str: "nan" })pb"),
+ EqualsProto(R"pb(value { str: "-nan(ind)" })pb"))))));
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << value;
+}
+
+template <typename T>
+class VoidPtrLogFormatTest : public testing::Test {};
+using VoidPtrTypes = Types<void *, const void *>;
+TYPED_TEST_SUITE(VoidPtrLogFormatTest, VoidPtrTypes);
+
+TYPED_TEST(VoidPtrLogFormatTest, Null) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const TypeParam value = nullptr;
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << value;
+
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(AnyOf(Eq("(nil)"), Eq("0"), Eq("0x0"),
+ Eq("00000000"), Eq("0000000000000000"))))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << value;
+}
+
+TYPED_TEST(VoidPtrLogFormatTest, NonNull) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const TypeParam value = reinterpret_cast<TypeParam>(0xdeadbeefULL);
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << value;
+
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(
+ TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(
+ AnyOf(Eq("0xdeadbeef"), Eq("DEADBEEF"), Eq("00000000DEADBEEF"))),
+ ENCODED_MESSAGE(AnyOf(
+ EqualsProto(R"pb(value { str: "0xdeadbeef" })pb"),
+ EqualsProto(R"pb(value { str: "00000000DEADBEEF" })pb"))))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << value;
+}
+
+template <typename T>
+class VolatilePtrLogFormatTest : public testing::Test {};
+using VolatilePtrTypes =
+ Types<volatile void*, const volatile void*, volatile char*,
+ const volatile char*, volatile signed char*,
+ const volatile signed char*, volatile unsigned char*,
+ const volatile unsigned char*>;
+TYPED_TEST_SUITE(VolatilePtrLogFormatTest, VolatilePtrTypes);
+
+TYPED_TEST(VolatilePtrLogFormatTest, Null) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const TypeParam value = nullptr;
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << value;
+
+ EXPECT_CALL(
+ test_sink, Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(Eq("false")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value {
+ str: "false"
+ })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << value;
+}
+
+TYPED_TEST(VolatilePtrLogFormatTest, NonNull) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const TypeParam value = reinterpret_cast<TypeParam>(0xdeadbeefLL);
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << value;
+
+ EXPECT_CALL(
+ test_sink, Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(Eq("true")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value {
+ str: "true"
+ })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << value;
+}
+
+template <typename T>
+class CharPtrLogFormatTest : public testing::Test {};
+using CharPtrTypes = Types<char, const char, signed char, const signed char,
+ unsigned char, const unsigned char>;
+TYPED_TEST_SUITE(CharPtrLogFormatTest, CharPtrTypes);
+
+TYPED_TEST(CharPtrLogFormatTest, Null) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ // Streaming `([cv] char *)nullptr` into a `std::ostream` is UB, and some C++
+ // standard library implementations choose to crash. We take measures to log
+ // something useful instead of crashing, even when that differs from the
+ // standard library in use (and thus the behavior of `std::ostream`).
+ TypeParam* const value = nullptr;
+
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(
+ // `MatchesOstream` deliberately omitted since we deliberately differ.
+ TextMessage(Eq("(null)")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "(null)" })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << value;
+}
+
+TYPED_TEST(CharPtrLogFormatTest, NonNull) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ TypeParam data[] = {'v', 'a', 'l', 'u', 'e', '\0'};
+ TypeParam* const value = data;
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << value;
+
+ EXPECT_CALL(
+ test_sink, Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(Eq("value")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value {
+ str: "value"
+ })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << value;
+}
+
+TEST(BoolLogFormatTest, True) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const bool value = true;
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << value;
+
+ EXPECT_CALL(
+ test_sink, Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(Eq("true")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value {
+ str: "true"
+ })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << value;
+}
+
+TEST(BoolLogFormatTest, False) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const bool value = false;
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << value;
+
+ EXPECT_CALL(
+ test_sink, Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(Eq("false")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value {
+ str: "false"
+ })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << value;
+}
+
+TEST(LogFormatTest, StringLiteral) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << "value";
+
+ EXPECT_CALL(test_sink,
+ Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(Eq("value")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value {
+ literal: "value"
+ })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << "value";
+}
+
+TEST(LogFormatTest, CharArray) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ char value[] = "value";
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << value;
+
+ EXPECT_CALL(
+ test_sink, Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(Eq("value")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value {
+ str: "value"
+ })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << value;
+}
+
+class CustomClass {};
+std::ostream& operator<<(std::ostream& os, const CustomClass&) {
+ return os << "CustomClass{}";
+}
+
+TEST(LogFormatTest, Custom) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ CustomClass value;
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << value;
+
+ EXPECT_CALL(test_sink,
+ Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(Eq("CustomClass{}")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value {
+ str: "CustomClass{}"
+ })pb")))));
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << value;
+}
+
+class CustomClassNonCopyable {
+ public:
+ CustomClassNonCopyable() = default;
+ CustomClassNonCopyable(const CustomClassNonCopyable&) = delete;
+ CustomClassNonCopyable& operator=(const CustomClassNonCopyable&) = delete;
+};
+std::ostream& operator<<(std::ostream& os, const CustomClassNonCopyable&) {
+ return os << "CustomClassNonCopyable{}";
+}
+
+TEST(LogFormatTest, CustomNonCopyable) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ CustomClassNonCopyable value;
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << value;
+
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(Eq("CustomClassNonCopyable{}")),
+ ENCODED_MESSAGE(EqualsProto(
+ R"pb(value { str: "CustomClassNonCopyable{}" })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << value;
+}
+
+struct Point {
+ template <typename Sink>
+ friend void AbslStringify(Sink& sink, const Point& p) {
+ absl::Format(&sink, "(%d, %d)", p.x, p.y);
+ }
+
+ int x = 10;
+ int y = 20;
+};
+
+TEST(LogFormatTest, AbslStringifyExample) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ Point p;
+
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(
+ TextMessage(Eq("(10, 20)")), TextMessage(Eq(absl::StrCat(p))),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "(10, 20)" })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << p;
+}
+
+struct PointWithAbslStringifiyAndOstream {
+ template <typename Sink>
+ friend void AbslStringify(Sink& sink,
+ const PointWithAbslStringifiyAndOstream& p) {
+ absl::Format(&sink, "(%d, %d)", p.x, p.y);
+ }
+
+ int x = 10;
+ int y = 20;
+};
+
+ABSL_ATTRIBUTE_UNUSED std::ostream& operator<<(
+ std::ostream& os, const PointWithAbslStringifiyAndOstream&) {
+ return os << "Default to AbslStringify()";
+}
+
+TEST(LogFormatTest, CustomWithAbslStringifyAndOstream) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ PointWithAbslStringifiyAndOstream p;
+
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(
+ TextMessage(Eq("(10, 20)")), TextMessage(Eq(absl::StrCat(p))),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "(10, 20)" })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << p;
+}
+
+struct PointStreamsNothing {
+ template <typename Sink>
+ friend void AbslStringify(Sink&, const PointStreamsNothing&) {}
+
+ int x = 10;
+ int y = 20;
+};
+
+TEST(LogFormatTest, AbslStringifyStreamsNothing) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ PointStreamsNothing p;
+
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(TextMessage(Eq("77")), TextMessage(Eq(absl::StrCat(p, 77))),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "77" })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << p << 77;
+}
+
+struct PointMultipleAppend {
+ template <typename Sink>
+ friend void AbslStringify(Sink& sink, const PointMultipleAppend& p) {
+ sink.Append("(");
+ sink.Append(absl::StrCat(p.x, ", ", p.y, ")"));
+ }
+
+ int x = 10;
+ int y = 20;
+};
+
+TEST(LogFormatTest, AbslStringifyMultipleAppend) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ PointMultipleAppend p;
+
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(
+ TextMessage(Eq("(10, 20)")), TextMessage(Eq(absl::StrCat(p))),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "(" }
+ value { str: "10, 20)" })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << p;
+}
+
+TEST(ManipulatorLogFormatTest, BoolAlphaTrue) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const bool value = true;
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << std::noboolalpha << value << " " //
+ << std::boolalpha << value << " " //
+ << std::noboolalpha << value;
+
+ EXPECT_CALL(test_sink,
+ Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(Eq("1 true 1")),
+ ENCODED_MESSAGE(EqualsProto(
+ R"pb(value { str: "1" }
+ value { literal: " " }
+ value { str: "true" }
+ value { literal: " " }
+ value { str: "1" })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << std::noboolalpha << value << " " //
+ << std::boolalpha << value << " " //
+ << std::noboolalpha << value;
+}
+
+TEST(ManipulatorLogFormatTest, BoolAlphaFalse) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const bool value = false;
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << std::noboolalpha << value << " " //
+ << std::boolalpha << value << " " //
+ << std::noboolalpha << value;
+
+ EXPECT_CALL(test_sink,
+ Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(Eq("0 false 0")),
+ ENCODED_MESSAGE(EqualsProto(
+ R"pb(value { str: "0" }
+ value { literal: " " }
+ value { str: "false" }
+ value { literal: " " }
+ value { str: "0" })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << std::noboolalpha << value << " " //
+ << std::boolalpha << value << " " //
+ << std::noboolalpha << value;
+}
+
+TEST(ManipulatorLogFormatTest, ShowPoint) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const double value = 77.0;
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << std::noshowpoint << value << " " //
+ << std::showpoint << value << " " //
+ << std::noshowpoint << value;
+
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(Eq("77 77.0000 77")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "77" }
+ value { literal: " " }
+ value { str: "77.0000" }
+ value { literal: " " }
+ value { str: "77" })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << std::noshowpoint << value << " " //
+ << std::showpoint << value << " " //
+ << std::noshowpoint << value;
+}
+
+TEST(ManipulatorLogFormatTest, ShowPos) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const int value = 77;
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << std::noshowpos << value << " " //
+ << std::showpos << value << " " //
+ << std::noshowpos << value;
+
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(Eq("77 +77 77")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "77" }
+ value { literal: " " }
+ value { str: "+77" }
+ value { literal: " " }
+ value { str: "77" })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << std::noshowpos << value << " " //
+ << std::showpos << value << " " //
+ << std::noshowpos << value;
+}
+
+TEST(ManipulatorLogFormatTest, UppercaseFloat) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const double value = 7.7e7;
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << std::nouppercase << value << " " //
+ << std::uppercase << value << " " //
+ << std::nouppercase << value;
+
+ EXPECT_CALL(test_sink,
+ Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(Eq("7.7e+07 7.7E+07 7.7e+07")),
+ ENCODED_MESSAGE(EqualsProto(
+ R"pb(value { str: "7.7e+07" }
+ value { literal: " " }
+ value { str: "7.7E+07" }
+ value { literal: " " }
+ value { str: "7.7e+07" })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << std::nouppercase << value << " " //
+ << std::uppercase << value << " " //
+ << std::nouppercase << value;
+}
+
+TEST(ManipulatorLogFormatTest, Hex) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const int value = 0x77;
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << std::hex << value;
+
+ EXPECT_CALL(
+ test_sink, Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(Eq("0x77")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value {
+ str: "0x77"
+ })pb")))));
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << std::hex << value;
+}
+
+TEST(ManipulatorLogFormatTest, Oct) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const int value = 077;
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << std::oct << value;
+
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(Eq("077")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "077" })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << std::oct << value;
+}
+
+TEST(ManipulatorLogFormatTest, Dec) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const int value = 77;
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << std::hex << std::dec << value;
+
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(Eq("77")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "77" })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << std::hex << std::dec << value;
+}
+
+TEST(ManipulatorLogFormatTest, ShowbaseHex) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const int value = 0x77;
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << std::hex //
+ << std::noshowbase << value << " " //
+ << std::showbase << value << " " //
+ << std::noshowbase << value;
+
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(Eq("77 0x77 77")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "77" }
+ value { literal: " " }
+ value { str: "0x77" }
+ value { literal: " " }
+ value { str: "77" })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << std::hex //
+ << std::noshowbase << value << " " //
+ << std::showbase << value << " " //
+ << std::noshowbase << value;
+}
+
+TEST(ManipulatorLogFormatTest, ShowbaseOct) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const int value = 077;
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << std::oct //
+ << std::noshowbase << value << " " //
+ << std::showbase << value << " " //
+ << std::noshowbase << value;
+
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(Eq("77 077 77")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "77" }
+ value { literal: " " }
+ value { str: "077" }
+ value { literal: " " }
+ value { str: "77" })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << std::oct //
+ << std::noshowbase << value << " " //
+ << std::showbase << value << " " //
+ << std::noshowbase << value;
+}
+
+TEST(ManipulatorLogFormatTest, UppercaseHex) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const int value = 0xbeef;
+ auto comparison_stream = ComparisonStream();
+ comparison_stream //
+ << std::hex //
+ << std::nouppercase << value << " " //
+ << std::uppercase << value << " " //
+ << std::nouppercase << value;
+
+ EXPECT_CALL(test_sink,
+ Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(Eq("0xbeef 0XBEEF 0xbeef")),
+ ENCODED_MESSAGE(EqualsProto(
+ R"pb(value { str: "0xbeef" }
+ value { literal: " " }
+ value { str: "0XBEEF" }
+ value { literal: " " }
+ value { str: "0xbeef" })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << std::hex //
+ << std::nouppercase << value << " " //
+ << std::uppercase << value << " " //
+ << std::nouppercase << value;
+}
+
+TEST(ManipulatorLogFormatTest, FixedFloat) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const double value = 7.7e7;
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << std::fixed << value;
+
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(Eq("77000000.000000")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value {
+ str: "77000000.000000"
+ })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << std::fixed << value;
+}
+
+TEST(ManipulatorLogFormatTest, ScientificFloat) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const double value = 7.7e7;
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << std::scientific << value;
+
+ EXPECT_CALL(test_sink,
+ Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(Eq("7.700000e+07")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value {
+ str: "7.700000e+07"
+ })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << std::scientific << value;
+}
+
+#if defined(__BIONIC__) && (!defined(__ANDROID_API__) || __ANDROID_API__ < 22)
+// Bionic doesn't support `%a` until API 22, so this prints 'a' even if the
+// C++ standard library implements it correctly (by forwarding to printf).
+#elif defined(__GLIBCXX__) && __cplusplus < 201402L
+// libstdc++ shipped C++11 support without `std::hexfloat`.
+#else
+TEST(ManipulatorLogFormatTest, FixedAndScientificFloat) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const double value = 7.7e7;
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << std::setiosflags(std::ios_base::scientific |
+ std::ios_base::fixed)
+ << value;
+
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(AnyOf(Eq("0x1.25bb50p+26"), Eq("0x1.25bb5p+26"),
+ Eq("0x1.25bb500000000p+26"))),
+ ENCODED_MESSAGE(
+ AnyOf(EqualsProto(R"pb(value { str: "0x1.25bb5p+26" })pb"),
+ EqualsProto(R"pb(value {
+ str: "0x1.25bb500000000p+26"
+ })pb"))))));
+
+ test_sink.StartCapturingLogs();
+
+ // This combination should mean the same thing as `std::hexfloat`.
+ LOG(INFO) << std::setiosflags(std::ios_base::scientific |
+ std::ios_base::fixed)
+ << value;
+}
+#endif
+
+#if defined(__BIONIC__) && (!defined(__ANDROID_API__) || __ANDROID_API__ < 22)
+// Bionic doesn't support `%a` until API 22, so this prints 'a' even if the C++
+// standard library supports `std::hexfloat` (by forwarding to printf).
+#elif defined(__GLIBCXX__) && __cplusplus < 201402L
+// libstdc++ shipped C++11 support without `std::hexfloat`.
+#else
+TEST(ManipulatorLogFormatTest, HexfloatFloat) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const double value = 7.7e7;
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << std::hexfloat << value;
+
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(AnyOf(Eq("0x1.25bb50p+26"), Eq("0x1.25bb5p+26"),
+ Eq("0x1.25bb500000000p+26"))),
+ ENCODED_MESSAGE(
+ AnyOf(EqualsProto(R"pb(value { str: "0x1.25bb5p+26" })pb"),
+ EqualsProto(R"pb(value {
+ str: "0x1.25bb500000000p+26"
+ })pb"))))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << std::hexfloat << value;
+}
+#endif
+
+TEST(ManipulatorLogFormatTest, DefaultFloatFloat) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const double value = 7.7e7;
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << std::hexfloat << std::defaultfloat << value;
+
+ EXPECT_CALL(test_sink,
+ Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(Eq("7.7e+07")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value {
+ str: "7.7e+07"
+ })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << std::hexfloat << std::defaultfloat << value;
+}
+
+TEST(ManipulatorLogFormatTest, Ends) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << std::ends;
+
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(Eq(absl::string_view("\0", 1))),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "\0" })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << std::ends;
+}
+
+TEST(ManipulatorLogFormatTest, Endl) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << std::endl;
+
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(
+ TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(Eq("\n")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "\n" })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << std::endl;
+}
+
+TEST(ManipulatorLogFormatTest, SetIosFlags) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const int value = 0x77;
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << std::resetiosflags(std::ios_base::basefield)
+ << std::setiosflags(std::ios_base::hex) << value << " " //
+ << std::resetiosflags(std::ios_base::basefield)
+ << std::setiosflags(std::ios_base::dec) << value;
+
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(
+ TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(Eq("0x77 119")),
+ // `std::setiosflags` and `std::resetiosflags` aren't manipulators.
+ // We're unable to distinguish their return type(s) from arbitrary
+ // user-defined types and thus don't suppress the empty str value.
+ ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "0x77" }
+ value { literal: " " }
+ value { str: "119" }
+ )pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << std::resetiosflags(std::ios_base::basefield)
+ << std::setiosflags(std::ios_base::hex) << value << " " //
+ << std::resetiosflags(std::ios_base::basefield)
+ << std::setiosflags(std::ios_base::dec) << value;
+}
+
+TEST(ManipulatorLogFormatTest, SetBase) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const int value = 0x77;
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << std::setbase(16) << value << " " //
+ << std::setbase(0) << value;
+
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(Eq("0x77 119")),
+ // `std::setbase` isn't a manipulator. We're unable to
+ // distinguish its return type from arbitrary user-defined
+ // types and thus don't suppress the empty str value.
+ ENCODED_MESSAGE(EqualsProto(
+ R"pb(value { str: "0x77" }
+ value { literal: " " }
+ value { str: "119" })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << std::setbase(16) << value << " " //
+ << std::setbase(0) << value;
+}
+
+TEST(ManipulatorLogFormatTest, SetPrecision) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const double value = 6.022140857e23;
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << std::setprecision(4) << value;
+
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(
+ TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(Eq("6.022e+23")),
+ // `std::setprecision` isn't a manipulator. We're unable to
+ // distinguish its return type from arbitrary user-defined
+ // types and thus don't suppress the empty str value.
+ ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "6.022e+23" })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << std::setprecision(4) << value;
+}
+
+TEST(ManipulatorLogFormatTest, SetPrecisionOverflow) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const double value = 6.022140857e23;
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << std::setprecision(200) << value;
+
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(Eq("602214085700000015187968")),
+ ENCODED_MESSAGE(EqualsProto(
+ R"pb(value { str: "602214085700000015187968" })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << std::setprecision(200) << value;
+}
+
+TEST(ManipulatorLogFormatTest, SetW) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const int value = 77;
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << std::setw(8) << value;
+
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(
+ TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(Eq(" 77")),
+ // `std::setw` isn't a manipulator. We're unable to
+ // distinguish its return type from arbitrary user-defined
+ // types and thus don't suppress the empty str value.
+ ENCODED_MESSAGE(EqualsProto(R"pb(value { str: " 77" })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << std::setw(8) << value;
+}
+
+TEST(ManipulatorLogFormatTest, Left) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const int value = -77;
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << std::left << std::setw(8) << value;
+
+ EXPECT_CALL(test_sink,
+ Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(Eq("-77 ")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value {
+ str: "-77 "
+ })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << std::left << std::setw(8) << value;
+}
+
+TEST(ManipulatorLogFormatTest, Right) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const int value = -77;
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << std::right << std::setw(8) << value;
+
+ EXPECT_CALL(test_sink,
+ Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(Eq(" -77")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value {
+ str: " -77"
+ })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << std::right << std::setw(8) << value;
+}
+
+TEST(ManipulatorLogFormatTest, Internal) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const int value = -77;
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << std::internal << std::setw(8) << value;
+
+ EXPECT_CALL(test_sink,
+ Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(Eq("- 77")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value {
+ str: "- 77"
+ })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << std::internal << std::setw(8) << value;
+}
+
+TEST(ManipulatorLogFormatTest, SetFill) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ const int value = 77;
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << std::setfill('0') << std::setw(8) << value;
+
+ EXPECT_CALL(test_sink,
+ Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(Eq("00000077")),
+ // `std::setfill` isn't a manipulator. We're
+ // unable to distinguish its return
+ // type from arbitrary user-defined types and
+ // thus don't suppress the empty str value.
+ ENCODED_MESSAGE(EqualsProto(R"pb(value {
+ str: "00000077"
+ })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << std::setfill('0') << std::setw(8) << value;
+}
+
+class FromCustomClass {};
+std::ostream& operator<<(std::ostream& os, const FromCustomClass&) {
+ return os << "FromCustomClass{}" << std::hex;
+}
+
+TEST(ManipulatorLogFormatTest, FromCustom) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ FromCustomClass value;
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << value << " " << 0x77;
+
+ EXPECT_CALL(test_sink,
+ Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(Eq("FromCustomClass{} 0x77")),
+ ENCODED_MESSAGE(EqualsProto(
+ R"pb(value { str: "FromCustomClass{}" }
+ value { literal: " " }
+ value { str: "0x77" })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << value << " " << 0x77;
+}
+
+class StreamsNothing {};
+std::ostream& operator<<(std::ostream& os, const StreamsNothing&) { return os; }
+
+TEST(ManipulatorLogFormatTest, CustomClassStreamsNothing) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ StreamsNothing value;
+ auto comparison_stream = ComparisonStream();
+ comparison_stream << value << 77;
+
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
+ TextMessage(Eq("77")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "77" })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << value << 77;
+}
+
+struct PointPercentV {
+ template <typename Sink>
+ friend void AbslStringify(Sink& sink, const PointPercentV& p) {
+ absl::Format(&sink, "(%v, %v)", p.x, p.y);
+ }
+
+ int x = 10;
+ int y = 20;
+};
+
+TEST(ManipulatorLogFormatTest, IOManipsDoNotAffectAbslStringify) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ PointPercentV p;
+
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(
+ TextMessage(Eq("(10, 20)")), TextMessage(Eq(absl::StrCat(p))),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "(10, 20)" })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << std::hex << p;
+}
+
+TEST(StructuredLoggingOverflowTest, TruncatesStrings) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ // This message is too long and should be truncated to some unspecified size
+ // no greater than the buffer size but not too much less either. It should be
+ // truncated rather than discarded.
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(
+ TextMessage(AllOf(
+ SizeIs(AllOf(Ge(absl::log_internal::kLogMessageBufferSize - 256),
+ Le(absl::log_internal::kLogMessageBufferSize))),
+ Each(Eq('x')))),
+ ENCODED_MESSAGE(HasOneStrThat(AllOf(
+ SizeIs(AllOf(Ge(absl::log_internal::kLogMessageBufferSize - 256),
+ Le(absl::log_internal::kLogMessageBufferSize))),
+ Each(Eq('x'))))))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << std::string(2 * absl::log_internal::kLogMessageBufferSize, 'x');
+}
+
+struct StringLike {
+ absl::string_view data;
+};
+std::ostream& operator<<(std::ostream& os, StringLike str) {
+ return os << str.data;
+}
+
+TEST(StructuredLoggingOverflowTest, TruncatesInsertionOperators) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ // This message is too long and should be truncated to some unspecified size
+ // no greater than the buffer size but not too much less either. It should be
+ // truncated rather than discarded.
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(
+ TextMessage(AllOf(
+ SizeIs(AllOf(Ge(absl::log_internal::kLogMessageBufferSize - 256),
+ Le(absl::log_internal::kLogMessageBufferSize))),
+ Each(Eq('x')))),
+ ENCODED_MESSAGE(HasOneStrThat(AllOf(
+ SizeIs(AllOf(Ge(absl::log_internal::kLogMessageBufferSize - 256),
+ Le(absl::log_internal::kLogMessageBufferSize))),
+ Each(Eq('x'))))))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << StringLike{
+ std::string(2 * absl::log_internal::kLogMessageBufferSize, 'x')};
+}
+
+// Returns the size of the largest string that will fit in a `LOG` message
+// buffer with no prefix.
+size_t MaxLogFieldLengthNoPrefix() {
+ class StringLengthExtractorSink : public absl::LogSink {
+ public:
+ void Send(const absl::LogEntry& entry) override {
+ CHECK(!size_.has_value());
+ CHECK_EQ(entry.text_message().find_first_not_of('x'),
+ absl::string_view::npos);
+ size_.emplace(entry.text_message().size());
+ }
+ size_t size() const {
+ CHECK(size_.has_value());
+ return *size_;
+ }
+
+ private:
+ absl::optional<size_t> size_;
+ } extractor_sink;
+ LOG(INFO).NoPrefix().ToSinkOnly(&extractor_sink)
+ << std::string(2 * absl::log_internal::kLogMessageBufferSize, 'x');
+ return extractor_sink.size();
+}
+
+TEST(StructuredLoggingOverflowTest, TruncatesStringsCleanly) {
+ const size_t longest_fit = MaxLogFieldLengthNoPrefix();
+ // To log a second value field, we need four bytes: two tag/type bytes and two
+ // sizes. To put any data in the field we need a fifth byte.
+ {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+ EXPECT_CALL(test_sink,
+ Send(AllOf(ENCODED_MESSAGE(HasOneStrThat(
+ AllOf(SizeIs(longest_fit), Each(Eq('x'))))),
+ RawEncodedMessage(AsString(EndsWith("x"))))));
+ test_sink.StartCapturingLogs();
+ // x fits exactly, no part of y fits.
+ LOG(INFO).NoPrefix() << std::string(longest_fit, 'x') << "y";
+ }
+ {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+ EXPECT_CALL(test_sink,
+ Send(AllOf(ENCODED_MESSAGE(HasOneStrThat(
+ AllOf(SizeIs(longest_fit - 1), Each(Eq('x'))))),
+ RawEncodedMessage(AsString(EndsWith("x"))))));
+ test_sink.StartCapturingLogs();
+ // x fits, one byte from y's header fits but shouldn't be visible.
+ LOG(INFO).NoPrefix() << std::string(longest_fit - 1, 'x') << "y";
+ }
+ {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+ EXPECT_CALL(test_sink,
+ Send(AllOf(ENCODED_MESSAGE(HasOneStrThat(
+ AllOf(SizeIs(longest_fit - 2), Each(Eq('x'))))),
+ RawEncodedMessage(AsString(EndsWith("x"))))));
+ test_sink.StartCapturingLogs();
+ // x fits, two bytes from y's header fit but shouldn't be visible.
+ LOG(INFO).NoPrefix() << std::string(longest_fit - 2, 'x') << "y";
+ }
+ {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+ EXPECT_CALL(test_sink,
+ Send(AllOf(ENCODED_MESSAGE(HasOneStrThat(
+ AllOf(SizeIs(longest_fit - 3), Each(Eq('x'))))),
+ RawEncodedMessage(AsString(EndsWith("x"))))));
+ test_sink.StartCapturingLogs();
+ // x fits, three bytes from y's header fit but shouldn't be visible.
+ LOG(INFO).NoPrefix() << std::string(longest_fit - 3, 'x') << "y";
+ }
+ {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+ EXPECT_CALL(test_sink,
+ Send(AllOf(ENCODED_MESSAGE(HasOneStrAndOneLiteralThat(
+ AllOf(SizeIs(longest_fit - 4), Each(Eq('x'))),
+ IsEmpty())),
+ RawEncodedMessage(Not(AsString(EndsWith("x")))))));
+ test_sink.StartCapturingLogs();
+ // x fits, all four bytes from y's header fit but no data bytes do, so we
+ // encode an empty string.
+ LOG(INFO).NoPrefix() << std::string(longest_fit - 4, 'x') << "y";
+ }
+ {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(ENCODED_MESSAGE(HasOneStrAndOneLiteralThat(
+ AllOf(SizeIs(longest_fit - 5), Each(Eq('x'))), Eq("y"))),
+ RawEncodedMessage(AsString(EndsWith("y"))))));
+ test_sink.StartCapturingLogs();
+ // x fits, y fits exactly.
+ LOG(INFO).NoPrefix() << std::string(longest_fit - 5, 'x') << "y";
+ }
+}
+
+TEST(StructuredLoggingOverflowTest, TruncatesInsertionOperatorsCleanly) {
+ const size_t longest_fit = MaxLogFieldLengthNoPrefix();
+ // To log a second value field, we need four bytes: two tag/type bytes and two
+ // sizes. To put any data in the field we need a fifth byte.
+ {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+ EXPECT_CALL(test_sink,
+ Send(AllOf(ENCODED_MESSAGE(HasOneStrThat(
+ AllOf(SizeIs(longest_fit), Each(Eq('x'))))),
+ RawEncodedMessage(AsString(EndsWith("x"))))));
+ test_sink.StartCapturingLogs();
+ // x fits exactly, no part of y fits.
+ LOG(INFO).NoPrefix() << std::string(longest_fit, 'x') << StringLike{"y"};
+ }
+ {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+ EXPECT_CALL(test_sink,
+ Send(AllOf(ENCODED_MESSAGE(HasOneStrThat(
+ AllOf(SizeIs(longest_fit - 1), Each(Eq('x'))))),
+ RawEncodedMessage(AsString(EndsWith("x"))))));
+ test_sink.StartCapturingLogs();
+ // x fits, one byte from y's header fits but shouldn't be visible.
+ LOG(INFO).NoPrefix() << std::string(longest_fit - 1, 'x')
+ << StringLike{"y"};
+ }
+ {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+ EXPECT_CALL(test_sink,
+ Send(AllOf(ENCODED_MESSAGE(HasOneStrThat(
+ AllOf(SizeIs(longest_fit - 2), Each(Eq('x'))))),
+ RawEncodedMessage(AsString(EndsWith("x"))))));
+ test_sink.StartCapturingLogs();
+ // x fits, two bytes from y's header fit but shouldn't be visible.
+ LOG(INFO).NoPrefix() << std::string(longest_fit - 2, 'x')
+ << StringLike{"y"};
+ }
+ {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+ EXPECT_CALL(test_sink,
+ Send(AllOf(ENCODED_MESSAGE(HasOneStrThat(
+ AllOf(SizeIs(longest_fit - 3), Each(Eq('x'))))),
+ RawEncodedMessage(AsString(EndsWith("x"))))));
+ test_sink.StartCapturingLogs();
+ // x fits, three bytes from y's header fit but shouldn't be visible.
+ LOG(INFO).NoPrefix() << std::string(longest_fit - 3, 'x')
+ << StringLike{"y"};
+ }
+ {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+ EXPECT_CALL(test_sink,
+ Send(AllOf(ENCODED_MESSAGE(HasOneStrThat(
+ AllOf(SizeIs(longest_fit - 4), Each(Eq('x'))))),
+ RawEncodedMessage(AsString(EndsWith("x"))))));
+ test_sink.StartCapturingLogs();
+ // x fits, all four bytes from y's header fit but no data bytes do. We
+ // don't encode an empty string here because every I/O manipulator hits this
+ // codepath and those shouldn't leave empty strings behind.
+ LOG(INFO).NoPrefix() << std::string(longest_fit - 4, 'x')
+ << StringLike{"y"};
+ }
+ {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(ENCODED_MESSAGE(HasTwoStrsThat(
+ AllOf(SizeIs(longest_fit - 5), Each(Eq('x'))), Eq("y"))),
+ RawEncodedMessage(AsString(EndsWith("y"))))));
+ test_sink.StartCapturingLogs();
+ // x fits, y fits exactly.
+ LOG(INFO).NoPrefix() << std::string(longest_fit - 5, 'x')
+ << StringLike{"y"};
+ }
+}
+
+} // namespace
diff --git a/absl/log/log_macro_hygiene_test.cc b/absl/log/log_macro_hygiene_test.cc
new file mode 100644
index 0000000..dad9389
--- /dev/null
+++ b/absl/log/log_macro_hygiene_test.cc
@@ -0,0 +1,187 @@
+//
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/base/attributes.h"
+#include "absl/base/log_severity.h"
+#include "absl/log/log.h"
+#include "absl/log/scoped_mock_log.h"
+
+namespace {
+using ::testing::_;
+using ::testing::Eq;
+
+namespace not_absl {
+
+class Dummy {
+ public:
+ Dummy() {}
+
+ private:
+ Dummy(const Dummy&) = delete;
+ Dummy& operator=(const Dummy&) = delete;
+};
+
+// This line tests that local definitions of INFO, WARNING, ERROR, and
+// etc don't shadow the global ones used by the logging macros. If
+// they do, the LOG() calls in the tests won't compile, catching the
+// bug.
+const Dummy INFO, WARNING, ERROR, FATAL, NUM_SEVERITIES;
+
+// These makes sure that the uses of same-named types in the
+// implementation of the logging macros are fully qualified.
+class string {};
+class vector {};
+class LogMessage {};
+class LogMessageFatal {};
+class LogMessageQuietlyFatal {};
+class LogMessageVoidify {};
+class LogSink {};
+class NullStream {};
+class NullStreamFatal {};
+
+} // namespace not_absl
+
+using namespace not_absl; // NOLINT
+
+// Tests for LOG(LEVEL(()).
+
+TEST(LogHygieneTest, WorksForQualifiedSeverity) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ ::testing::InSequence seq;
+ EXPECT_CALL(test_sink, Log(absl::LogSeverity::kInfo, _, "To INFO"));
+ EXPECT_CALL(test_sink, Log(absl::LogSeverity::kWarning, _, "To WARNING"));
+ EXPECT_CALL(test_sink, Log(absl::LogSeverity::kError, _, "To ERROR"));
+
+ test_sink.StartCapturingLogs();
+ // Note that LOG(LEVEL()) expects the severity as a run-time
+ // expression (as opposed to a compile-time constant). Hence we
+ // test that :: is allowed before INFO, etc.
+ LOG(LEVEL(absl::LogSeverity::kInfo)) << "To INFO";
+ LOG(LEVEL(absl::LogSeverity::kWarning)) << "To WARNING";
+ LOG(LEVEL(absl::LogSeverity::kError)) << "To ERROR";
+}
+
+TEST(LogHygieneTest, WorksWithAlternativeINFOSymbol) {
+ const double INFO ABSL_ATTRIBUTE_UNUSED = 7.77;
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ EXPECT_CALL(test_sink, Log(absl::LogSeverity::kInfo, _, "Hello world"));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << "Hello world";
+}
+
+TEST(LogHygieneTest, WorksWithAlternativeWARNINGSymbol) {
+ const double WARNING ABSL_ATTRIBUTE_UNUSED = 7.77;
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ EXPECT_CALL(test_sink, Log(absl::LogSeverity::kWarning, _, "Hello world"));
+
+ test_sink.StartCapturingLogs();
+ LOG(WARNING) << "Hello world";
+}
+
+TEST(LogHygieneTest, WorksWithAlternativeERRORSymbol) {
+ const double ERROR ABSL_ATTRIBUTE_UNUSED = 7.77;
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ EXPECT_CALL(test_sink, Log(absl::LogSeverity::kError, _, "Hello world"));
+
+ test_sink.StartCapturingLogs();
+ LOG(ERROR) << "Hello world";
+}
+
+TEST(LogHygieneTest, WorksWithAlternativeLEVELSymbol) {
+ const double LEVEL ABSL_ATTRIBUTE_UNUSED = 7.77;
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ EXPECT_CALL(test_sink, Log(absl::LogSeverity::kError, _, "Hello world"));
+
+ test_sink.StartCapturingLogs();
+ LOG(LEVEL(absl::LogSeverity::kError)) << "Hello world";
+}
+
+#define INFO Bogus
+#ifdef NDEBUG
+constexpr bool IsOptimized = false;
+#else
+constexpr bool IsOptimized = true;
+#endif
+
+TEST(LogHygieneTest, WorksWithINFODefined) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ EXPECT_CALL(test_sink, Log(absl::LogSeverity::kInfo, _, "Hello world"))
+ .Times(2 + (IsOptimized ? 2 : 0));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << "Hello world";
+ LOG_IF(INFO, true) << "Hello world";
+
+ DLOG(INFO) << "Hello world";
+ DLOG_IF(INFO, true) << "Hello world";
+}
+
+#undef INFO
+
+#define _INFO Bogus
+TEST(LogHygieneTest, WorksWithUnderscoreINFODefined) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ EXPECT_CALL(test_sink, Log(absl::LogSeverity::kInfo, _, "Hello world"))
+ .Times(2 + (IsOptimized ? 2 : 0));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << "Hello world";
+ LOG_IF(INFO, true) << "Hello world";
+
+ DLOG(INFO) << "Hello world";
+ DLOG_IF(INFO, true) << "Hello world";
+}
+#undef _INFO
+
+TEST(LogHygieneTest, ExpressionEvaluationInLEVELSeverity) {
+ auto i = static_cast<int>(absl::LogSeverity::kInfo);
+ LOG(LEVEL(++i)) << "hello world"; // NOLINT
+ EXPECT_THAT(i, Eq(static_cast<int>(absl::LogSeverity::kInfo) + 1));
+}
+
+TEST(LogHygieneTest, ExpressionEvaluationInStreamedMessage) {
+ int i = 0;
+ LOG(INFO) << ++i;
+ EXPECT_THAT(i, 1);
+ LOG_IF(INFO, false) << ++i;
+ EXPECT_THAT(i, 1);
+}
+
+// Tests that macros are usable in unbraced switch statements.
+// -----------------------------------------------------------
+
+class UnbracedSwitchCompileTest {
+ static void Log() {
+ switch (0) {
+ case 0:
+ LOG(INFO);
+ break;
+ default:
+ break;
+ }
+ }
+};
+
+} // namespace
diff --git a/absl/log/log_modifier_methods_test.cc b/absl/log/log_modifier_methods_test.cc
new file mode 100644
index 0000000..42e13b1
--- /dev/null
+++ b/absl/log/log_modifier_methods_test.cc
@@ -0,0 +1,233 @@
+//
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <errno.h>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/log/internal/test_actions.h"
+#include "absl/log/internal/test_helpers.h"
+#include "absl/log/internal/test_matchers.h"
+#include "absl/log/log.h"
+#include "absl/log/log_sink.h"
+#include "absl/log/scoped_mock_log.h"
+#include "absl/strings/match.h"
+#include "absl/strings/string_view.h"
+#include "absl/time/time.h"
+
+namespace {
+#if GTEST_HAS_DEATH_TEST
+using ::absl::log_internal::DeathTestExpectedLogging;
+using ::absl::log_internal::DeathTestUnexpectedLogging;
+using ::absl::log_internal::DeathTestValidateExpectations;
+using ::absl::log_internal::DiedOfQFatal;
+#endif
+using ::absl::log_internal::LogSeverity;
+using ::absl::log_internal::Prefix;
+using ::absl::log_internal::SourceBasename;
+using ::absl::log_internal::SourceFilename;
+using ::absl::log_internal::SourceLine;
+using ::absl::log_internal::Stacktrace;
+using ::absl::log_internal::TextMessage;
+using ::absl::log_internal::TextMessageWithPrefix;
+using ::absl::log_internal::TextMessageWithPrefixAndNewline;
+using ::absl::log_internal::TextPrefix;
+using ::absl::log_internal::ThreadID;
+using ::absl::log_internal::Timestamp;
+using ::absl::log_internal::Verbosity;
+
+using ::testing::AllOf;
+using ::testing::AnyNumber;
+using ::testing::AnyOf;
+using ::testing::Eq;
+using ::testing::IsEmpty;
+using ::testing::IsFalse;
+using ::testing::Truly;
+
+TEST(TailCallsModifiesTest, AtLocationFileLine) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(
+ // The metadata should change:
+ SourceFilename(Eq("/my/very/very/very_long_source_file.cc")),
+ SourceBasename(Eq("very_long_source_file.cc")), SourceLine(Eq(777)),
+ // The logged line should change too, even though the prefix must
+ // grow to fit the new metadata.
+ TextMessageWithPrefix(Truly([](absl::string_view msg) {
+ return absl::EndsWith(msg,
+ " very_long_source_file.cc:777] hello world");
+ })))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO).AtLocation("/my/very/very/very_long_source_file.cc", 777)
+ << "hello world";
+}
+
+TEST(TailCallsModifiesTest, NoPrefix) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ EXPECT_CALL(test_sink, Send(AllOf(Prefix(IsFalse()), TextPrefix(IsEmpty()),
+ TextMessageWithPrefix(Eq("hello world")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO).NoPrefix() << "hello world";
+}
+
+TEST(TailCallsModifiesTest, NoPrefixNoMessageNoShirtNoShoesNoService) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ EXPECT_CALL(test_sink,
+ Send(AllOf(Prefix(IsFalse()), TextPrefix(IsEmpty()),
+ TextMessageWithPrefix(IsEmpty()),
+ TextMessageWithPrefixAndNewline(Eq("\n")))));
+ test_sink.StartCapturingLogs();
+ LOG(INFO).NoPrefix();
+}
+
+TEST(TailCallsModifiesTest, WithVerbosity) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ EXPECT_CALL(test_sink, Send(Verbosity(Eq(2))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO).WithVerbosity(2) << "hello world";
+}
+
+TEST(TailCallsModifiesTest, WithVerbosityNoVerbosity) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ EXPECT_CALL(test_sink,
+ Send(Verbosity(Eq(absl::LogEntry::kNoVerbosityLevel))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO).WithVerbosity(2).WithVerbosity(absl::LogEntry::kNoVerbosityLevel)
+ << "hello world";
+}
+
+TEST(TailCallsModifiesTest, WithTimestamp) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ EXPECT_CALL(test_sink, Send(Timestamp(Eq(absl::UnixEpoch()))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO).WithTimestamp(absl::UnixEpoch()) << "hello world";
+}
+
+TEST(TailCallsModifiesTest, WithThreadID) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ EXPECT_CALL(test_sink,
+ Send(AllOf(ThreadID(Eq(absl::LogEntry::tid_t{1234})))));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO).WithThreadID(1234) << "hello world";
+}
+
+TEST(TailCallsModifiesTest, WithMetadataFrom) {
+ class ForwardingLogSink : public absl::LogSink {
+ public:
+ void Send(const absl::LogEntry &entry) override {
+ LOG(LEVEL(entry.log_severity())).WithMetadataFrom(entry)
+ << "forwarded: " << entry.text_message();
+ }
+ } forwarding_sink;
+
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(SourceFilename(Eq("fake/file")), SourceBasename(Eq("file")),
+ SourceLine(Eq(123)), Prefix(IsFalse()),
+ LogSeverity(Eq(absl::LogSeverity::kWarning)),
+ Timestamp(Eq(absl::UnixEpoch())),
+ ThreadID(Eq(absl::LogEntry::tid_t{456})),
+ TextMessage(Eq("forwarded: hello world")), Verbosity(Eq(7)),
+ ENCODED_MESSAGE(
+ EqualsProto(R"pb(value { literal: "forwarded: " }
+ value { str: "hello world" })pb")))));
+
+ test_sink.StartCapturingLogs();
+ LOG(WARNING)
+ .AtLocation("fake/file", 123)
+ .NoPrefix()
+ .WithTimestamp(absl::UnixEpoch())
+ .WithThreadID(456)
+ .WithVerbosity(7)
+ .ToSinkOnly(&forwarding_sink)
+ << "hello world";
+}
+
+TEST(TailCallsModifiesTest, WithPerror) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(TextMessage(AnyOf(Eq("hello world: Bad file number [9]"),
+ Eq("hello world: Bad file descriptor [9]"),
+ Eq("hello world: Bad file descriptor [8]"))),
+ ENCODED_MESSAGE(
+ AnyOf(EqualsProto(R"pb(value { literal: "hello world" }
+ value { literal: ": " }
+ value { str: "Bad file number" }
+ value { literal: " [" }
+ value { str: "9" }
+ value { literal: "]" })pb"),
+ EqualsProto(R"pb(value { literal: "hello world" }
+ value { literal: ": " }
+ value { str: "Bad file descriptor" }
+ value { literal: " [" }
+ value { str: "9" }
+ value { literal: "]" })pb"),
+ EqualsProto(R"pb(value { literal: "hello world" }
+ value { literal: ": " }
+ value { str: "Bad file descriptor" }
+ value { literal: " [" }
+ value { str: "8" }
+ value { literal: "]" })pb"))))));
+
+ test_sink.StartCapturingLogs();
+ errno = EBADF;
+ LOG(INFO).WithPerror() << "hello world";
+}
+
+#if GTEST_HAS_DEATH_TEST
+TEST(ModifierMethodDeathTest, ToSinkOnlyQFatal) {
+ EXPECT_EXIT(
+ {
+ absl::ScopedMockLog test_sink(
+ absl::MockLogDefault::kDisallowUnexpected);
+
+ auto do_log = [&test_sink] {
+ LOG(QFATAL).ToSinkOnly(&test_sink.UseAsLocalSink()) << "hello world";
+ };
+
+ EXPECT_CALL(test_sink, Send)
+ .Times(AnyNumber())
+ .WillRepeatedly(DeathTestUnexpectedLogging());
+
+ EXPECT_CALL(test_sink, Send(AllOf(TextMessage(Eq("hello world")),
+ Stacktrace(IsEmpty()))))
+ .WillOnce(DeathTestExpectedLogging());
+
+ test_sink.StartCapturingLogs();
+ do_log();
+ },
+ DiedOfQFatal, DeathTestValidateExpectations());
+}
+#endif
+
+} // namespace
diff --git a/absl/debugging/leak_check_disable.cc b/absl/log/log_sink.cc
similarity index 61%
copy from absl/debugging/leak_check_disable.cc
copy to absl/log/log_sink.cc
index 924d6e3..01d7ca8 100644
--- a/absl/debugging/leak_check_disable.cc
+++ b/absl/log/log_sink.cc
@@ -1,10 +1,10 @@
-// Copyright 2017 The Abseil Authors.
+// Copyright 2022 The Abseil Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
-// https://www.apache.org/licenses/LICENSE-2.0
+// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
@@ -12,9 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-// Disable LeakSanitizer when this file is linked in.
-// This function overrides __lsan_is_turned_off from sanitizer/lsan_interface.h
-extern "C" int __lsan_is_turned_off();
-extern "C" int __lsan_is_turned_off() {
- return 1;
-}
+#include "absl/log/log_sink.h"
+
+#include "absl/base/config.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+void LogSink::KeyFunction() const {}
+ABSL_NAMESPACE_END
+} // namespace absl
diff --git a/absl/log/log_sink.h b/absl/log/log_sink.h
new file mode 100644
index 0000000..2910070
--- /dev/null
+++ b/absl/log/log_sink.h
@@ -0,0 +1,71 @@
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// -----------------------------------------------------------------------------
+// File: log/log_sink.h
+// -----------------------------------------------------------------------------
+//
+// This header declares the interface class `absl::LogSink`.
+
+#ifndef ABSL_LOG_LOG_SINK_H_
+#define ABSL_LOG_LOG_SINK_H_
+
+#include "absl/base/config.h"
+#include "absl/log/log_entry.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+
+// absl::LogSink
+//
+// `absl::LogSink` is an interface which can be extended to intercept and
+// process particular messages (with `LOG.ToSinkOnly()` or
+// `LOG.ToSinkAlso()`) or all messages (if registered with
+// `absl::AddLogSink`). Implementations must not take any locks that might be
+// held by the `LOG` caller.
+class LogSink {
+ public:
+ virtual ~LogSink() = default;
+
+ // LogSink::Send()
+ //
+ // `Send` is called synchronously during the log statement. `Send` must be
+ // thread-safe.
+ //
+ // It is safe to use `LOG` within an implementation of `Send`. `ToSinkOnly`
+ // and `ToSinkAlso` are safe in general but can be used to create an infinite
+ // loop if you try.
+ virtual void Send(const absl::LogEntry& entry) = 0;
+
+ // LogSink::Flush()
+ //
+ // Sinks that buffer messages should override this method to flush the buffer
+ // and return. `Flush` must be thread-safe.
+ virtual void Flush() {}
+
+ protected:
+ LogSink() = default;
+ // Implementations may be copyable and/or movable.
+ LogSink(const LogSink&) = default;
+ LogSink& operator=(const LogSink&) = default;
+
+ private:
+ // https://lld.llvm.org/missingkeyfunction.html#missing-key-function
+ virtual void KeyFunction() const final; // NOLINT(readability/inheritance)
+};
+
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_LOG_LOG_SINK_H_
diff --git a/absl/log/log_sink_registry.h b/absl/log/log_sink_registry.h
new file mode 100644
index 0000000..bf76cce
--- /dev/null
+++ b/absl/log/log_sink_registry.h
@@ -0,0 +1,61 @@
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// -----------------------------------------------------------------------------
+// File: log/log_sink_registry.h
+// -----------------------------------------------------------------------------
+//
+// This header declares APIs to operate on global set of registered log sinks.
+
+#ifndef ABSL_LOG_LOG_SINK_REGISTRY_H_
+#define ABSL_LOG_LOG_SINK_REGISTRY_H_
+
+#include "absl/base/config.h"
+#include "absl/log/internal/log_sink_set.h"
+#include "absl/log/log_sink.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+
+// AddLogSink(), RemoveLogSink()
+//
+// Adds or removes a `absl::LogSink` as a consumer of logging data.
+//
+// These functions are thread-safe.
+//
+// It is an error to attempt to add a sink that's already registered or to
+// attempt to remove one that isn't.
+//
+// To avoid unbounded recursion, dispatch to registered `absl::LogSink`s is
+// disabled per-thread while running the `Send()` method of registered
+// `absl::LogSink`s. Affected messages are dispatched to a special internal
+// sink instead which writes them to `stderr`.
+//
+// Do not call these inside `absl::LogSink::Send`.
+inline void AddLogSink(absl::LogSink* sink) { log_internal::AddLogSink(sink); }
+inline void RemoveLogSink(absl::LogSink* sink) {
+ log_internal::RemoveLogSink(sink);
+}
+
+// FlushLogSinks()
+//
+// Calls `absl::LogSink::Flush` on all registered sinks.
+//
+// Do not call this inside `absl::LogSink::Send`.
+inline void FlushLogSinks() { log_internal::FlushLogSinks(); }
+
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_LOG_LOG_SINK_REGISTRY_H_
diff --git a/absl/log/log_sink_test.cc b/absl/log/log_sink_test.cc
new file mode 100644
index 0000000..fa74306
--- /dev/null
+++ b/absl/log/log_sink_test.cc
@@ -0,0 +1,418 @@
+//
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/log/log_sink.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/base/attributes.h"
+#include "absl/log/internal/test_actions.h"
+#include "absl/log/internal/test_helpers.h"
+#include "absl/log/internal/test_matchers.h"
+#include "absl/log/log.h"
+#include "absl/log/log_sink_registry.h"
+#include "absl/log/scoped_mock_log.h"
+#include "absl/strings/string_view.h"
+
+namespace {
+
+using ::absl::log_internal::DeathTestExpectedLogging;
+using ::absl::log_internal::DeathTestUnexpectedLogging;
+using ::absl::log_internal::DeathTestValidateExpectations;
+using ::absl::log_internal::DiedOfFatal;
+using ::testing::_;
+using ::testing::AnyNumber;
+using ::testing::HasSubstr;
+using ::testing::InSequence;
+
+auto* test_env ABSL_ATTRIBUTE_UNUSED = ::testing::AddGlobalTestEnvironment(
+ new absl::log_internal::LogTestEnvironment);
+
+// Tests for global log sink registration.
+// ---------------------------------------
+
+TEST(LogSinkRegistryTest, AddLogSink) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ InSequence s;
+ EXPECT_CALL(test_sink, Log(_, _, "hello world")).Times(0);
+ EXPECT_CALL(test_sink, Log(absl::LogSeverity::kInfo, __FILE__, "Test : 42"));
+ EXPECT_CALL(test_sink,
+ Log(absl::LogSeverity::kWarning, __FILE__, "Danger ahead"));
+ EXPECT_CALL(test_sink,
+ Log(absl::LogSeverity::kError, __FILE__, "This is an error"));
+
+ LOG(INFO) << "hello world";
+ test_sink.StartCapturingLogs();
+
+ LOG(INFO) << "Test : " << 42;
+ LOG(WARNING) << "Danger" << ' ' << "ahead";
+ LOG(ERROR) << "This is an error";
+
+ test_sink.StopCapturingLogs();
+ LOG(INFO) << "Goodby world";
+}
+
+TEST(LogSinkRegistryTest, MultipleLogSinks) {
+ absl::ScopedMockLog test_sink1(absl::MockLogDefault::kDisallowUnexpected);
+ absl::ScopedMockLog test_sink2(absl::MockLogDefault::kDisallowUnexpected);
+
+ ::testing::InSequence seq;
+ EXPECT_CALL(test_sink1, Log(absl::LogSeverity::kInfo, _, "First")).Times(1);
+ EXPECT_CALL(test_sink2, Log(absl::LogSeverity::kInfo, _, "First")).Times(0);
+
+ EXPECT_CALL(test_sink1, Log(absl::LogSeverity::kInfo, _, "Second")).Times(1);
+ EXPECT_CALL(test_sink2, Log(absl::LogSeverity::kInfo, _, "Second")).Times(1);
+
+ EXPECT_CALL(test_sink1, Log(absl::LogSeverity::kInfo, _, "Third")).Times(0);
+ EXPECT_CALL(test_sink2, Log(absl::LogSeverity::kInfo, _, "Third")).Times(1);
+
+ LOG(INFO) << "Before first";
+
+ test_sink1.StartCapturingLogs();
+ LOG(INFO) << "First";
+
+ test_sink2.StartCapturingLogs();
+ LOG(INFO) << "Second";
+
+ test_sink1.StopCapturingLogs();
+ LOG(INFO) << "Third";
+
+ test_sink2.StopCapturingLogs();
+ LOG(INFO) << "Fourth";
+}
+
+TEST(LogSinkRegistrationDeathTest, DuplicateSinkRegistration) {
+ ASSERT_DEATH_IF_SUPPORTED(
+ {
+ absl::ScopedMockLog sink;
+ sink.StartCapturingLogs();
+ absl::AddLogSink(&sink.UseAsLocalSink());
+ },
+ HasSubstr("Duplicate log sinks"));
+}
+
+TEST(LogSinkRegistrationDeathTest, MismatchSinkRemoval) {
+ ASSERT_DEATH_IF_SUPPORTED(
+ {
+ absl::ScopedMockLog sink;
+ absl::RemoveLogSink(&sink.UseAsLocalSink());
+ },
+ HasSubstr("Mismatched log sink"));
+}
+
+// Tests for log sink semantic.
+// ---------------------------------------
+
+TEST(LogSinkTest, FlushSinks) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ EXPECT_CALL(test_sink, Flush()).Times(2);
+
+ test_sink.StartCapturingLogs();
+
+ absl::FlushLogSinks();
+ absl::FlushLogSinks();
+}
+
+TEST(LogSinkDeathTest, DeathInSend) {
+ class FatalSendSink : public absl::LogSink {
+ public:
+ void Send(const absl::LogEntry&) override { LOG(FATAL) << "goodbye world"; }
+ };
+
+ FatalSendSink sink;
+ EXPECT_EXIT({ LOG(INFO).ToSinkAlso(&sink) << "hello world"; }, DiedOfFatal,
+ _);
+}
+
+// Tests for explicit log sink redirection.
+// ---------------------------------------
+
+TEST(LogSinkTest, ToSinkAlso) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+ absl::ScopedMockLog another_sink(absl::MockLogDefault::kDisallowUnexpected);
+ EXPECT_CALL(test_sink, Log(_, _, "hello world"));
+ EXPECT_CALL(another_sink, Log(_, _, "hello world"));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO).ToSinkAlso(&another_sink.UseAsLocalSink()) << "hello world";
+}
+
+TEST(LogSinkTest, ToSinkOnly) {
+ absl::ScopedMockLog another_sink(absl::MockLogDefault::kDisallowUnexpected);
+ EXPECT_CALL(another_sink, Log(_, _, "hello world"));
+ LOG(INFO).ToSinkOnly(&another_sink.UseAsLocalSink()) << "hello world";
+}
+
+TEST(LogSinkTest, ToManySinks) {
+ absl::ScopedMockLog sink1(absl::MockLogDefault::kDisallowUnexpected);
+ absl::ScopedMockLog sink2(absl::MockLogDefault::kDisallowUnexpected);
+ absl::ScopedMockLog sink3(absl::MockLogDefault::kDisallowUnexpected);
+ absl::ScopedMockLog sink4(absl::MockLogDefault::kDisallowUnexpected);
+ absl::ScopedMockLog sink5(absl::MockLogDefault::kDisallowUnexpected);
+
+ EXPECT_CALL(sink3, Log(_, _, "hello world"));
+ EXPECT_CALL(sink4, Log(_, _, "hello world"));
+ EXPECT_CALL(sink5, Log(_, _, "hello world"));
+
+ LOG(INFO)
+ .ToSinkAlso(&sink1.UseAsLocalSink())
+ .ToSinkAlso(&sink2.UseAsLocalSink())
+ .ToSinkOnly(&sink3.UseAsLocalSink())
+ .ToSinkAlso(&sink4.UseAsLocalSink())
+ .ToSinkAlso(&sink5.UseAsLocalSink())
+ << "hello world";
+}
+
+class ReentrancyTest : public ::testing::Test {
+ protected:
+ ReentrancyTest() = default;
+ enum class LogMode : int { kNormal, kToSinkAlso, kToSinkOnly };
+
+ class ReentrantSendLogSink : public absl::LogSink {
+ public:
+ explicit ReentrantSendLogSink(absl::LogSeverity severity,
+ absl::LogSink* sink, LogMode mode)
+ : severity_(severity), sink_(sink), mode_(mode) {}
+ explicit ReentrantSendLogSink(absl::LogSeverity severity)
+ : ReentrantSendLogSink(severity, nullptr, LogMode::kNormal) {}
+
+ void Send(const absl::LogEntry&) override {
+ switch (mode_) {
+ case LogMode::kNormal:
+ LOG(LEVEL(severity_)) << "The log is coming from *inside the sink*.";
+ break;
+ case LogMode::kToSinkAlso:
+ LOG(LEVEL(severity_)).ToSinkAlso(sink_)
+ << "The log is coming from *inside the sink*.";
+ break;
+ case LogMode::kToSinkOnly:
+ LOG(LEVEL(severity_)).ToSinkOnly(sink_)
+ << "The log is coming from *inside the sink*.";
+ break;
+ default:
+ LOG(FATAL) << "Invalid mode " << static_cast<int>(mode_);
+ }
+ }
+
+ private:
+ absl::LogSeverity severity_;
+ absl::LogSink* sink_;
+ LogMode mode_;
+ };
+
+ static absl::string_view LogAndReturn(absl::LogSeverity severity,
+ absl::string_view to_log,
+ absl::string_view to_return) {
+ LOG(LEVEL(severity)) << to_log;
+ return to_return;
+ }
+};
+
+TEST_F(ReentrancyTest, LogFunctionThatLogs) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ InSequence seq;
+ EXPECT_CALL(test_sink, Log(absl::LogSeverity::kInfo, _, "hello"));
+ EXPECT_CALL(test_sink, Log(absl::LogSeverity::kInfo, _, "world"));
+ EXPECT_CALL(test_sink, Log(absl::LogSeverity::kWarning, _, "danger"));
+ EXPECT_CALL(test_sink, Log(absl::LogSeverity::kInfo, _, "here"));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << LogAndReturn(absl::LogSeverity::kInfo, "hello", "world");
+ LOG(INFO) << LogAndReturn(absl::LogSeverity::kWarning, "danger", "here");
+}
+
+TEST_F(ReentrancyTest, RegisteredLogSinkThatLogsInSend) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+ ReentrantSendLogSink renentrant_sink(absl::LogSeverity::kInfo);
+ EXPECT_CALL(test_sink, Log(_, _, "hello world"));
+
+ test_sink.StartCapturingLogs();
+ absl::AddLogSink(&renentrant_sink);
+ LOG(INFO) << "hello world";
+ absl::RemoveLogSink(&renentrant_sink);
+}
+
+TEST_F(ReentrancyTest, AlsoLogSinkThatLogsInSend) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+ ReentrantSendLogSink reentrant_sink(absl::LogSeverity::kInfo);
+ EXPECT_CALL(test_sink, Log(_, _, "hello world"));
+ EXPECT_CALL(test_sink,
+ Log(_, _, "The log is coming from *inside the sink*."));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO).ToSinkAlso(&reentrant_sink) << "hello world";
+}
+
+TEST_F(ReentrancyTest, RegisteredAlsoLogSinkThatLogsInSend) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+ ReentrantSendLogSink reentrant_sink(absl::LogSeverity::kInfo);
+ EXPECT_CALL(test_sink, Log(_, _, "hello world"));
+ // We only call into the test_log sink once with this message, since the
+ // second time log statement is run we are in "ThreadIsLogging" mode and all
+ // the log statements are redirected into stderr.
+ EXPECT_CALL(test_sink,
+ Log(_, _, "The log is coming from *inside the sink*."));
+
+ test_sink.StartCapturingLogs();
+ absl::AddLogSink(&reentrant_sink);
+ LOG(INFO).ToSinkAlso(&reentrant_sink) << "hello world";
+ absl::RemoveLogSink(&reentrant_sink);
+}
+
+TEST_F(ReentrancyTest, OnlyLogSinkThatLogsInSend) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+ ReentrantSendLogSink reentrant_sink(absl::LogSeverity::kInfo);
+ EXPECT_CALL(test_sink,
+ Log(_, _, "The log is coming from *inside the sink*."));
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO).ToSinkOnly(&reentrant_sink) << "hello world";
+}
+
+TEST_F(ReentrancyTest, RegisteredOnlyLogSinkThatLogsInSend) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+ ReentrantSendLogSink reentrant_sink(absl::LogSeverity::kInfo);
+ EXPECT_CALL(test_sink,
+ Log(_, _, "The log is coming from *inside the sink*."));
+
+ test_sink.StartCapturingLogs();
+ absl::AddLogSink(&reentrant_sink);
+ LOG(INFO).ToSinkOnly(&reentrant_sink) << "hello world";
+ absl::RemoveLogSink(&reentrant_sink);
+}
+
+using ReentrancyDeathTest = ReentrancyTest;
+
+TEST_F(ReentrancyDeathTest, LogFunctionThatLogsFatal) {
+ EXPECT_EXIT(
+ {
+ absl::ScopedMockLog test_sink;
+
+ EXPECT_CALL(test_sink, Log)
+ .Times(AnyNumber())
+ .WillRepeatedly(DeathTestUnexpectedLogging());
+ EXPECT_CALL(test_sink, Log(_, _, "hello"))
+ .WillOnce(DeathTestExpectedLogging());
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO) << LogAndReturn(absl::LogSeverity::kFatal, "hello", "world");
+ },
+ DiedOfFatal, DeathTestValidateExpectations());
+}
+
+TEST_F(ReentrancyDeathTest, RegisteredLogSinkThatLogsFatalInSend) {
+ EXPECT_EXIT(
+ {
+ absl::ScopedMockLog test_sink;
+ ReentrantSendLogSink reentrant_sink(absl::LogSeverity::kFatal);
+ EXPECT_CALL(test_sink, Log)
+ .Times(AnyNumber())
+ .WillRepeatedly(DeathTestUnexpectedLogging());
+ EXPECT_CALL(test_sink, Log(_, _, "hello world"))
+ .WillOnce(DeathTestExpectedLogging());
+
+ test_sink.StartCapturingLogs();
+ absl::AddLogSink(&reentrant_sink);
+ LOG(INFO) << "hello world";
+ // No need to call RemoveLogSink - process is dead at this point.
+ },
+ DiedOfFatal, DeathTestValidateExpectations());
+}
+
+TEST_F(ReentrancyDeathTest, AlsoLogSinkThatLogsFatalInSend) {
+ EXPECT_EXIT(
+ {
+ absl::ScopedMockLog test_sink;
+ ReentrantSendLogSink reentrant_sink(absl::LogSeverity::kFatal);
+
+ EXPECT_CALL(test_sink, Log)
+ .Times(AnyNumber())
+ .WillRepeatedly(DeathTestUnexpectedLogging());
+ EXPECT_CALL(test_sink, Log(_, _, "hello world"))
+ .WillOnce(DeathTestExpectedLogging());
+ EXPECT_CALL(test_sink,
+ Log(_, _, "The log is coming from *inside the sink*."))
+ .WillOnce(DeathTestExpectedLogging());
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO).ToSinkAlso(&reentrant_sink) << "hello world";
+ },
+ DiedOfFatal, DeathTestValidateExpectations());
+}
+
+TEST_F(ReentrancyDeathTest, RegisteredAlsoLogSinkThatLogsFatalInSend) {
+ EXPECT_EXIT(
+ {
+ absl::ScopedMockLog test_sink;
+ ReentrantSendLogSink reentrant_sink(absl::LogSeverity::kFatal);
+ EXPECT_CALL(test_sink, Log)
+ .Times(AnyNumber())
+ .WillRepeatedly(DeathTestUnexpectedLogging());
+ EXPECT_CALL(test_sink, Log(_, _, "hello world"))
+ .WillOnce(DeathTestExpectedLogging());
+ EXPECT_CALL(test_sink,
+ Log(_, _, "The log is coming from *inside the sink*."))
+ .WillOnce(DeathTestExpectedLogging());
+
+ test_sink.StartCapturingLogs();
+ absl::AddLogSink(&reentrant_sink);
+ LOG(INFO).ToSinkAlso(&reentrant_sink) << "hello world";
+ // No need to call RemoveLogSink - process is dead at this point.
+ },
+ DiedOfFatal, DeathTestValidateExpectations());
+}
+
+TEST_F(ReentrancyDeathTest, OnlyLogSinkThatLogsFatalInSend) {
+ EXPECT_EXIT(
+ {
+ absl::ScopedMockLog test_sink;
+ ReentrantSendLogSink reentrant_sink(absl::LogSeverity::kFatal);
+ EXPECT_CALL(test_sink, Log)
+ .Times(AnyNumber())
+ .WillRepeatedly(DeathTestUnexpectedLogging());
+ EXPECT_CALL(test_sink,
+ Log(_, _, "The log is coming from *inside the sink*."))
+ .WillOnce(DeathTestExpectedLogging());
+
+ test_sink.StartCapturingLogs();
+ LOG(INFO).ToSinkOnly(&reentrant_sink) << "hello world";
+ },
+ DiedOfFatal, DeathTestValidateExpectations());
+}
+
+TEST_F(ReentrancyDeathTest, RegisteredOnlyLogSinkThatLogsFatalInSend) {
+ EXPECT_EXIT(
+ {
+ absl::ScopedMockLog test_sink;
+ ReentrantSendLogSink reentrant_sink(absl::LogSeverity::kFatal);
+ EXPECT_CALL(test_sink, Log)
+ .Times(AnyNumber())
+ .WillRepeatedly(DeathTestUnexpectedLogging());
+ EXPECT_CALL(test_sink,
+ Log(_, _, "The log is coming from *inside the sink*."))
+ .WillOnce(DeathTestExpectedLogging());
+
+ test_sink.StartCapturingLogs();
+ absl::AddLogSink(&reentrant_sink);
+ LOG(INFO).ToSinkOnly(&reentrant_sink) << "hello world";
+ // No need to call RemoveLogSink - process is dead at this point.
+ },
+ DiedOfFatal, DeathTestValidateExpectations());
+}
+
+} // namespace
diff --git a/absl/log/log_streamer.h b/absl/log/log_streamer.h
new file mode 100644
index 0000000..4ed2435
--- /dev/null
+++ b/absl/log/log_streamer.h
@@ -0,0 +1,181 @@
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// -----------------------------------------------------------------------------
+// File: log/log_streamer.h
+// -----------------------------------------------------------------------------
+//
+// This header declares the class `LogStreamer` and convenience functions to
+// construct LogStreamer objects with different associated log severity levels.
+
+#ifndef ABSL_LOG_LOG_STREAMER_H_
+#define ABSL_LOG_LOG_STREAMER_H_
+
+#include <ios>
+#include <memory>
+#include <ostream>
+#include <string>
+#include <utility>
+
+#include "absl/base/config.h"
+#include "absl/base/log_severity.h"
+#include "absl/log/absl_log.h"
+#include "absl/strings/internal/ostringstream.h"
+#include "absl/strings/string_view.h"
+#include "absl/types/optional.h"
+#include "absl/utility/utility.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+
+// LogStreamer
+//
+// Although you can stream into `LOG(INFO)`, you can't pass it into a function
+// that takes a `std::ostream` parameter. `LogStreamer::stream()` provides a
+// `std::ostream` that buffers everything that's streamed in. The buffer's
+// contents are logged as if by `LOG` when the `LogStreamer` is destroyed.
+// If nothing is streamed in, an empty message is logged. If the specified
+// severity is `absl::LogSeverity::kFatal`, the program will be terminated when
+// the `LogStreamer` is destroyed regardless of whether any data were streamed
+// in.
+//
+// Factory functions corresponding to the `absl::LogSeverity` enumerators
+// are provided for convenience; if the desired severity is variable, invoke the
+// constructor directly.
+//
+// LogStreamer is movable, but not copyable.
+//
+// Examples:
+//
+// ShaveYakAndWriteToStream(
+// yak, absl::LogInfoStreamer(__FILE__, __LINE__).stream());
+//
+// {
+// // This logs a single line containing data streamed by all three function
+// // calls.
+// absl::LogStreamer streamer(absl::LogSeverity::kInfo, __FILE__, __LINE__);
+// ShaveYakAndWriteToStream(yak1, streamer.stream());
+// streamer.stream() << " ";
+// ShaveYakAndWriteToStream(yak2, streamer.stream());
+// streamer.stream() << " ";
+// ShaveYakAndWriteToStreamPointer(yak3, &streamer.stream());
+// }
+class LogStreamer final {
+ public:
+ // LogStreamer::LogStreamer()
+ //
+ // Creates a LogStreamer with a given `severity` that will log a message
+ // attributed to the given `file` and `line`.
+ explicit LogStreamer(absl::LogSeverity severity, absl::string_view file,
+ int line)
+ : severity_(severity),
+ line_(line),
+ file_(file),
+ stream_(absl::in_place, &buf_) {
+ // To match `LOG`'s defaults:
+ stream_->setf(std::ios_base::showbase | std::ios_base::boolalpha);
+ }
+
+ // A moved-from `absl::LogStreamer` does not `LOG` when destroyed,
+ // and a program that streams into one has undefined behavior.
+ LogStreamer(LogStreamer&& that) noexcept
+ : severity_(that.severity_),
+ line_(that.line_),
+ file_(std::move(that.file_)),
+ buf_(std::move(that.buf_)),
+ stream_(std::move(that.stream_)) {
+ if (stream_.has_value()) stream_->str(&buf_);
+ that.stream_.reset();
+ }
+ LogStreamer& operator=(LogStreamer&& that) {
+ ABSL_LOG_IF(LEVEL(severity_), stream_).AtLocation(file_, line_) << buf_;
+ severity_ = that.severity_;
+ file_ = std::move(that.file_);
+ line_ = that.line_;
+ buf_ = std::move(that.buf_);
+ stream_ = std::move(that.stream_);
+ if (stream_.has_value()) stream_->str(&buf_);
+ that.stream_.reset();
+ return *this;
+ }
+
+ // LogStreamer::~LogStreamer()
+ //
+ // Logs this LogStreamer's buffered content as if by LOG.
+ ~LogStreamer() {
+ ABSL_LOG_IF(LEVEL(severity_), stream_.has_value()).AtLocation(file_, line_)
+ << buf_;
+ }
+
+ // LogStreamer::stream()
+ //
+ // Returns the `std::ostream` to use to write into this LogStreamer' internal
+ // buffer.
+ std::ostream& stream() { return *stream_; }
+
+ private:
+ absl::LogSeverity severity_;
+ int line_;
+ std::string file_;
+ std::string buf_;
+ // A disengaged `stream_` indicates a moved-from `LogStreamer` that should not
+ // `LOG` upon destruction.
+ absl::optional<absl::strings_internal::OStringStream> stream_;
+};
+
+// LogInfoStreamer()
+//
+// Returns a LogStreamer that writes at level LogSeverity::kInfo.
+inline LogStreamer LogInfoStreamer(absl::string_view file, int line) {
+ return absl::LogStreamer(absl::LogSeverity::kInfo, file, line);
+}
+
+// LogWarningStreamer()
+//
+// Returns a LogStreamer that writes at level LogSeverity::kWarning.
+inline LogStreamer LogWarningStreamer(absl::string_view file, int line) {
+ return absl::LogStreamer(absl::LogSeverity::kWarning, file, line);
+}
+
+// LogErrorStreamer()
+//
+// Returns a LogStreamer that writes at level LogSeverity::kError.
+inline LogStreamer LogErrorStreamer(absl::string_view file, int line) {
+ return absl::LogStreamer(absl::LogSeverity::kError, file, line);
+}
+
+// LogFatalStreamer()
+//
+// Returns a LogStreamer that writes at level LogSeverity::kFatal.
+//
+// The program will be terminated when this `LogStreamer` is destroyed,
+// regardless of whether any data were streamed in.
+inline LogStreamer LogFatalStreamer(absl::string_view file, int line) {
+ return absl::LogStreamer(absl::LogSeverity::kFatal, file, line);
+}
+
+// LogDebugFatalStreamer()
+//
+// Returns a LogStreamer that writes at level LogSeverity::kLogDebugFatal.
+//
+// In debug mode, the program will be terminated when this `LogStreamer` is
+// destroyed, regardless of whether any data were streamed in.
+inline LogStreamer LogDebugFatalStreamer(absl::string_view file, int line) {
+ return absl::LogStreamer(absl::kLogDebugFatal, file, line);
+}
+
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_LOG_LOG_STREAMER_H_
diff --git a/absl/log/log_streamer_test.cc b/absl/log/log_streamer_test.cc
new file mode 100644
index 0000000..40c7d48
--- /dev/null
+++ b/absl/log/log_streamer_test.cc
@@ -0,0 +1,416 @@
+//
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/log/log_streamer.h"
+
+#include <ios>
+#include <iostream>
+#include <utility>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/base/attributes.h"
+#include "absl/base/internal/sysinfo.h"
+#include "absl/base/log_severity.h"
+#include "absl/log/internal/test_actions.h"
+#include "absl/log/internal/test_helpers.h"
+#include "absl/log/internal/test_matchers.h"
+#include "absl/log/log.h"
+#include "absl/log/scoped_mock_log.h"
+#include "absl/strings/string_view.h"
+
+namespace {
+using ::absl::log_internal::DeathTestExpectedLogging;
+using ::absl::log_internal::DeathTestUnexpectedLogging;
+using ::absl::log_internal::DeathTestValidateExpectations;
+#if GTEST_HAS_DEATH_TEST
+using ::absl::log_internal::DiedOfFatal;
+#endif
+using ::absl::log_internal::LogSeverity;
+using ::absl::log_internal::Prefix;
+using ::absl::log_internal::SourceFilename;
+using ::absl::log_internal::SourceLine;
+using ::absl::log_internal::Stacktrace;
+using ::absl::log_internal::TextMessage;
+using ::absl::log_internal::ThreadID;
+using ::absl::log_internal::TimestampInMatchWindow;
+using ::testing::AnyNumber;
+using ::testing::Eq;
+using ::testing::HasSubstr;
+using ::testing::IsEmpty;
+using ::testing::IsTrue;
+
+auto* test_env ABSL_ATTRIBUTE_UNUSED = ::testing::AddGlobalTestEnvironment(
+ new absl::log_internal::LogTestEnvironment);
+
+void WriteToStream(absl::string_view data, std::ostream* os) {
+ *os << "WriteToStream: " << data;
+}
+void WriteToStreamRef(absl::string_view data, std::ostream& os) {
+ os << "WriteToStreamRef: " << data;
+}
+
+TEST(LogStreamerTest, LogInfoStreamer) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(SourceFilename(Eq("path/file.cc")), SourceLine(Eq(1234)),
+ Prefix(IsTrue()), LogSeverity(Eq(absl::LogSeverity::kInfo)),
+ TimestampInMatchWindow(),
+ ThreadID(Eq(absl::base_internal::GetTID())),
+ TextMessage(Eq("WriteToStream: foo")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value {
+ str: "WriteToStream: foo"
+ })pb")),
+ Stacktrace(IsEmpty()))));
+
+ test_sink.StartCapturingLogs();
+ WriteToStream("foo", &absl::LogInfoStreamer("path/file.cc", 1234).stream());
+}
+
+TEST(LogStreamerTest, LogWarningStreamer) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(SourceFilename(Eq("path/file.cc")), SourceLine(Eq(1234)),
+ Prefix(IsTrue()), LogSeverity(Eq(absl::LogSeverity::kWarning)),
+ TimestampInMatchWindow(),
+ ThreadID(Eq(absl::base_internal::GetTID())),
+ TextMessage(Eq("WriteToStream: foo")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value {
+ str: "WriteToStream: foo"
+ })pb")),
+ Stacktrace(IsEmpty()))));
+
+ test_sink.StartCapturingLogs();
+ WriteToStream("foo",
+ &absl::LogWarningStreamer("path/file.cc", 1234).stream());
+}
+
+TEST(LogStreamerTest, LogErrorStreamer) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(SourceFilename(Eq("path/file.cc")), SourceLine(Eq(1234)),
+ Prefix(IsTrue()), LogSeverity(Eq(absl::LogSeverity::kError)),
+ TimestampInMatchWindow(),
+ ThreadID(Eq(absl::base_internal::GetTID())),
+ TextMessage(Eq("WriteToStream: foo")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value {
+ str: "WriteToStream: foo"
+ })pb")),
+ Stacktrace(IsEmpty()))));
+
+ test_sink.StartCapturingLogs();
+ WriteToStream("foo", &absl::LogErrorStreamer("path/file.cc", 1234).stream());
+}
+
+#if GTEST_HAS_DEATH_TEST
+TEST(LogStreamerDeathTest, LogFatalStreamer) {
+ EXPECT_EXIT(
+ {
+ absl::ScopedMockLog test_sink;
+
+ EXPECT_CALL(test_sink, Send)
+ .Times(AnyNumber())
+ .WillRepeatedly(DeathTestUnexpectedLogging());
+
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(
+ SourceFilename(Eq("path/file.cc")), SourceLine(Eq(1234)),
+ Prefix(IsTrue()), LogSeverity(Eq(absl::LogSeverity::kFatal)),
+ TimestampInMatchWindow(),
+ ThreadID(Eq(absl::base_internal::GetTID())),
+ TextMessage(Eq("WriteToStream: foo")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value {
+ str: "WriteToStream: foo"
+ })pb")))))
+ .WillOnce(DeathTestExpectedLogging());
+
+ test_sink.StartCapturingLogs();
+ WriteToStream("foo",
+ &absl::LogFatalStreamer("path/file.cc", 1234).stream());
+ },
+ DiedOfFatal, DeathTestValidateExpectations());
+}
+#endif
+
+#ifdef NDEBUG
+TEST(LogStreamerTest, LogDebugFatalStreamer) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(SourceFilename(Eq("path/file.cc")), SourceLine(Eq(1234)),
+ Prefix(IsTrue()), LogSeverity(Eq(absl::LogSeverity::kError)),
+ TimestampInMatchWindow(),
+ ThreadID(Eq(absl::base_internal::GetTID())),
+ TextMessage(Eq("WriteToStream: foo")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value {
+ str: "WriteToStream: foo"
+ })pb")),
+ Stacktrace(IsEmpty()))));
+
+ test_sink.StartCapturingLogs();
+ WriteToStream("foo",
+ &absl::LogDebugFatalStreamer("path/file.cc", 1234).stream());
+}
+#elif GTEST_HAS_DEATH_TEST
+TEST(LogStreamerDeathTest, LogDebugFatalStreamer) {
+ EXPECT_EXIT(
+ {
+ absl::ScopedMockLog test_sink;
+
+ EXPECT_CALL(test_sink, Send)
+ .Times(AnyNumber())
+ .WillRepeatedly(DeathTestUnexpectedLogging());
+
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(
+ SourceFilename(Eq("path/file.cc")), SourceLine(Eq(1234)),
+ Prefix(IsTrue()), LogSeverity(Eq(absl::LogSeverity::kFatal)),
+ TimestampInMatchWindow(),
+ ThreadID(Eq(absl::base_internal::GetTID())),
+ TextMessage(Eq("WriteToStream: foo")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value {
+ str: "WriteToStream: foo"
+ })pb")))))
+ .WillOnce(DeathTestExpectedLogging());
+
+ test_sink.StartCapturingLogs();
+ WriteToStream(
+ "foo", &absl::LogDebugFatalStreamer("path/file.cc", 1234).stream());
+ },
+ DiedOfFatal, DeathTestValidateExpectations());
+}
+#endif
+
+TEST(LogStreamerTest, LogStreamer) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(SourceFilename(Eq("path/file.cc")), SourceLine(Eq(1234)),
+ Prefix(IsTrue()), LogSeverity(Eq(absl::LogSeverity::kError)),
+ TimestampInMatchWindow(),
+ ThreadID(Eq(absl::base_internal::GetTID())),
+ TextMessage(Eq("WriteToStream: foo")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value {
+ str: "WriteToStream: foo"
+ })pb")),
+ Stacktrace(IsEmpty()))));
+
+ test_sink.StartCapturingLogs();
+ WriteToStream(
+ "foo", &absl::LogStreamer(absl::LogSeverity::kError, "path/file.cc", 1234)
+ .stream());
+}
+
+#if GTEST_HAS_DEATH_TEST
+TEST(LogStreamerDeathTest, LogStreamer) {
+ EXPECT_EXIT(
+ {
+ absl::ScopedMockLog test_sink;
+
+ EXPECT_CALL(test_sink, Send)
+ .Times(AnyNumber())
+ .WillRepeatedly(DeathTestUnexpectedLogging());
+
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(
+ SourceFilename(Eq("path/file.cc")), SourceLine(Eq(1234)),
+ Prefix(IsTrue()), LogSeverity(Eq(absl::LogSeverity::kFatal)),
+ TimestampInMatchWindow(),
+ ThreadID(Eq(absl::base_internal::GetTID())),
+ TextMessage(Eq("WriteToStream: foo")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value {
+ str: "WriteToStream: foo"
+ })pb")))))
+ .WillOnce(DeathTestExpectedLogging());
+
+ test_sink.StartCapturingLogs();
+ WriteToStream("foo", &absl::LogStreamer(absl::LogSeverity::kFatal,
+ "path/file.cc", 1234)
+ .stream());
+ },
+ DiedOfFatal, DeathTestValidateExpectations());
+}
+#endif
+
+TEST(LogStreamerTest, PassedByReference) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(SourceFilename(Eq("path/file.cc")), SourceLine(Eq(1234)),
+ TextMessage(Eq("WriteToStreamRef: foo")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value {
+ str: "WriteToStreamRef: foo"
+ })pb")),
+ Stacktrace(IsEmpty()))));
+
+ test_sink.StartCapturingLogs();
+ WriteToStreamRef("foo", absl::LogInfoStreamer("path/file.cc", 1234).stream());
+}
+
+TEST(LogStreamerTest, StoredAsLocal) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ auto streamer = absl::LogInfoStreamer("path/file.cc", 1234);
+ WriteToStream("foo", &streamer.stream());
+ streamer.stream() << " ";
+ WriteToStreamRef("bar", streamer.stream());
+
+ // The call should happen when `streamer` goes out of scope; if it
+ // happened before this `EXPECT_CALL` the call would be unexpected and the
+ // test would fail.
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(SourceFilename(Eq("path/file.cc")), SourceLine(Eq(1234)),
+ TextMessage(Eq("WriteToStream: foo WriteToStreamRef: bar")),
+ ENCODED_MESSAGE(EqualsProto(
+ R"pb(value {
+ str: "WriteToStream: foo WriteToStreamRef: bar"
+ })pb")),
+ Stacktrace(IsEmpty()))));
+
+ test_sink.StartCapturingLogs();
+}
+
+#if GTEST_HAS_DEATH_TEST
+TEST(LogStreamerDeathTest, StoredAsLocal) {
+ EXPECT_EXIT(
+ {
+ // This is fatal when it goes out of scope, but not until then:
+ auto streamer = absl::LogFatalStreamer("path/file.cc", 1234);
+ std::cerr << "I'm still alive" << std::endl;
+ WriteToStream("foo", &streamer.stream());
+ },
+ DiedOfFatal, HasSubstr("I'm still alive"));
+}
+#endif
+
+TEST(LogStreamerTest, LogsEmptyLine) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ EXPECT_CALL(test_sink, Send(AllOf(SourceFilename(Eq("path/file.cc")),
+ SourceLine(Eq(1234)), TextMessage(Eq("")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value {
+ str: ""
+ })pb")),
+ Stacktrace(IsEmpty()))));
+
+ test_sink.StartCapturingLogs();
+ absl::LogInfoStreamer("path/file.cc", 1234);
+}
+
+#if GTEST_HAS_DEATH_TEST
+TEST(LogStreamerDeathTest, LogsEmptyLine) {
+ EXPECT_EXIT(
+ {
+ absl::ScopedMockLog test_sink;
+
+ EXPECT_CALL(test_sink, Log)
+ .Times(AnyNumber())
+ .WillRepeatedly(DeathTestUnexpectedLogging());
+
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(
+ SourceFilename(Eq("path/file.cc")), TextMessage(Eq("")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "" })pb")))))
+ .WillOnce(DeathTestExpectedLogging());
+
+ test_sink.StartCapturingLogs();
+ // This is fatal even though it's never used:
+ auto streamer = absl::LogFatalStreamer("path/file.cc", 1234);
+ },
+ DiedOfFatal, DeathTestValidateExpectations());
+}
+#endif
+
+TEST(LogStreamerTest, MoveConstruction) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(SourceFilename(Eq("path/file.cc")), SourceLine(Eq(1234)),
+ LogSeverity(Eq(absl::LogSeverity::kInfo)),
+ TextMessage(Eq("hello 0x10 world 0x10")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value {
+ str: "hello 0x10 world 0x10"
+ })pb")),
+ Stacktrace(IsEmpty()))));
+
+ test_sink.StartCapturingLogs();
+ auto streamer1 = absl::LogInfoStreamer("path/file.cc", 1234);
+ streamer1.stream() << "hello " << std::hex << 16;
+ absl::LogStreamer streamer2(std::move(streamer1));
+ streamer2.stream() << " world " << 16;
+}
+
+TEST(LogStreamerTest, MoveAssignment) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ testing::InSequence seq;
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(SourceFilename(Eq("path/file2.cc")), SourceLine(Eq(5678)),
+ LogSeverity(Eq(absl::LogSeverity::kWarning)),
+ TextMessage(Eq("something else")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value {
+ str: "something else"
+ })pb")),
+ Stacktrace(IsEmpty()))));
+ EXPECT_CALL(
+ test_sink,
+ Send(AllOf(SourceFilename(Eq("path/file.cc")), SourceLine(Eq(1234)),
+ LogSeverity(Eq(absl::LogSeverity::kInfo)),
+ TextMessage(Eq("hello 0x10 world 0x10")),
+ ENCODED_MESSAGE(EqualsProto(R"pb(value {
+ str: "hello 0x10 world 0x10"
+ })pb")),
+ Stacktrace(IsEmpty()))));
+
+ test_sink.StartCapturingLogs();
+ auto streamer1 = absl::LogInfoStreamer("path/file.cc", 1234);
+ streamer1.stream() << "hello " << std::hex << 16;
+ auto streamer2 = absl::LogWarningStreamer("path/file2.cc", 5678);
+ streamer2.stream() << "something else";
+ streamer2 = std::move(streamer1);
+ streamer2.stream() << " world " << 16;
+}
+
+TEST(LogStreamerTest, CorrectDefaultFlags) {
+ absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
+
+ // The `boolalpha` and `showbase` flags should be set by default, to match
+ // `LOG`.
+ EXPECT_CALL(test_sink, Send(AllOf(TextMessage(Eq("false0xdeadbeef")))))
+ .Times(2);
+
+ test_sink.StartCapturingLogs();
+ absl::LogInfoStreamer("path/file.cc", 1234).stream()
+ << false << std::hex << 0xdeadbeef;
+ LOG(INFO) << false << std::hex << 0xdeadbeef;
+}
+
+} // namespace
diff --git a/absl/log/scoped_mock_log.cc b/absl/log/scoped_mock_log.cc
new file mode 100644
index 0000000..39a0a52
--- /dev/null
+++ b/absl/log/scoped_mock_log.cc
@@ -0,0 +1,86 @@
+//
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/log/scoped_mock_log.h"
+
+#include <atomic>
+#include <string>
+
+#include "gmock/gmock.h"
+#include "absl/base/config.h"
+#include "absl/base/internal/raw_logging.h"
+#include "absl/log/log_entry.h"
+#include "absl/log/log_sink.h"
+#include "absl/log/log_sink_registry.h"
+#include "absl/strings/string_view.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+
+ScopedMockLog::ScopedMockLog(MockLogDefault default_exp)
+ : sink_(this), is_capturing_logs_(false), is_triggered_(false) {
+ if (default_exp == MockLogDefault::kIgnoreUnexpected) {
+ // Ignore all calls to Log we did not set expectations for.
+ EXPECT_CALL(*this, Log).Times(::testing::AnyNumber());
+ } else {
+ // Disallow all calls to Log we did not set expectations for.
+ EXPECT_CALL(*this, Log).Times(0);
+ }
+ // By default Send mock forwards to Log mock.
+ EXPECT_CALL(*this, Send)
+ .Times(::testing::AnyNumber())
+ .WillRepeatedly([this](const absl::LogEntry& entry) {
+ is_triggered_.store(true, std::memory_order_relaxed);
+ Log(entry.log_severity(), std::string(entry.source_filename()),
+ std::string(entry.text_message()));
+ });
+
+ // By default We ignore all Flush calls.
+ EXPECT_CALL(*this, Flush).Times(::testing::AnyNumber());
+}
+
+ScopedMockLog::~ScopedMockLog() {
+ ABSL_RAW_CHECK(is_triggered_.load(std::memory_order_relaxed),
+ "Did you forget to call StartCapturingLogs()?");
+
+ if (is_capturing_logs_) StopCapturingLogs();
+}
+
+void ScopedMockLog::StartCapturingLogs() {
+ ABSL_RAW_CHECK(!is_capturing_logs_,
+ "StartCapturingLogs() can be called only when the "
+ "absl::ScopedMockLog object is not capturing logs.");
+
+ is_capturing_logs_ = true;
+ is_triggered_.store(true, std::memory_order_relaxed);
+ absl::AddLogSink(&sink_);
+}
+
+void ScopedMockLog::StopCapturingLogs() {
+ ABSL_RAW_CHECK(is_capturing_logs_,
+ "StopCapturingLogs() can be called only when the "
+ "absl::ScopedMockLog object is capturing logs.");
+
+ is_capturing_logs_ = false;
+ absl::RemoveLogSink(&sink_);
+}
+
+absl::LogSink& ScopedMockLog::UseAsLocalSink() {
+ is_triggered_.store(true, std::memory_order_relaxed);
+ return sink_;
+}
+
+ABSL_NAMESPACE_END
+} // namespace absl
diff --git a/absl/log/scoped_mock_log.h b/absl/log/scoped_mock_log.h
new file mode 100644
index 0000000..399e604
--- /dev/null
+++ b/absl/log/scoped_mock_log.h
@@ -0,0 +1,197 @@
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// -----------------------------------------------------------------------------
+// File: log/scoped_mock_log.h
+// -----------------------------------------------------------------------------
+//
+// This header declares `class absl::ScopedMockLog`, for use in testing.
+
+#ifndef ABSL_LOG_SCOPED_MOCK_LOG_H_
+#define ABSL_LOG_SCOPED_MOCK_LOG_H_
+
+#include <atomic>
+#include <string>
+
+#include "gmock/gmock.h"
+#include "absl/base/config.h"
+#include "absl/base/log_severity.h"
+#include "absl/log/log_entry.h"
+#include "absl/log/log_sink.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+
+// MockLogDefault
+//
+// Controls how ScopedMockLog responds to unexpected calls by default.
+enum class MockLogDefault { kIgnoreUnexpected, kDisallowUnexpected };
+
+// ScopedMockLog
+//
+// ScopedMockLog is a LogSink that intercepts LOG() messages issued during its
+// lifespan.
+//
+// Using this together with GoogleTest, it's easy to test how a piece of code
+// calls LOG(). The typical usage, noting the distinction between
+// "uninteresting" and "unexpected", looks like this:
+//
+// using ::testing::_;
+// using ::testing::AnyNumber;
+// using ::testing::EndsWith;
+// using ::testing::kDoNotCaptureLogsYet;
+// using ::testing::Lt;
+//
+// TEST(FooTest, LogsCorrectly) {
+// // Simple robust setup, ignores unexpected logs.
+// absl::ScopedMockLog log;
+//
+// // We expect the WARNING "Something bad!" exactly twice.
+// EXPECT_CALL(log, Log(absl::LogSeverity::kWarning, _, "Something bad!"))
+// .Times(2);
+//
+// // But we want no messages from foo.cc.
+// EXPECT_CALL(log, Log(_, EndsWith("/foo.cc"), _)).Times(0);
+//
+// log.StartCapturingLogs(); // Call this after done setting expectations.
+// Foo(); // Exercises the code under test.
+// }
+//
+// TEST(BarTest, LogsExactlyCorrectly) {
+// // Strict checking, fails for unexpected logs.
+// absl::ScopedMockLog log(absl::MockLogDefault::kDisallowUnexpected);
+//
+// // ... but ignore low severity messages
+// EXPECT_CALL(log, Log(Lt(absl::LogSeverity::kWarning), _, _))
+// .Times(AnyNumber());
+//
+// // We expect the ERROR "Something bad!" exactly once.
+// EXPECT_CALL(log, Log(absl::LogSeverity::kError, EndsWith("/foo.cc"),
+// "Something bad!"))
+// .Times(1);
+//
+// log.StartCapturingLogs(); // Call this after done setting expectations.
+// Bar(); // Exercises the code under test.
+// }
+//
+// Note that in a multi-threaded environment, all LOG() messages from a single
+// thread will be handled in sequence, but that cannot be guaranteed for
+// messages from different threads. In fact, if the same or multiple
+// expectations are matched on two threads concurrently, their actions will be
+// executed concurrently as well and may interleave.
+class ScopedMockLog final {
+ public:
+ // ScopedMockLog::ScopedMockLog()
+ //
+ // Sets up the log and adds default expectations.
+ explicit ScopedMockLog(
+ MockLogDefault default_exp = MockLogDefault::kIgnoreUnexpected);
+ ScopedMockLog(const ScopedMockLog&) = delete;
+ ScopedMockLog& operator=(const ScopedMockLog&) = delete;
+
+ // ScopedMockLog::~ScopedMockLog()
+ //
+ // Stops intercepting logs and destroys this ScopedMockLog.
+ ~ScopedMockLog();
+
+ // ScopedMockLog::StartCapturingLogs()
+ //
+ // Starts log capturing if the object isn't already doing so. Otherwise
+ // crashes.
+ //
+ // Usually this method is called in the same thread that created this
+ // ScopedMockLog. It is the user's responsibility to not call this method if
+ // another thread may be calling it or StopCapturingLogs() at the same time.
+ // It is undefined behavior to add expectations while capturing logs is
+ // enabled.
+ void StartCapturingLogs();
+
+ // ScopedMockLog::StopCapturingLogs()
+ //
+ // Stops log capturing if the object is capturing logs. Otherwise crashes.
+ //
+ // Usually this method is called in the same thread that created this object.
+ // It is the user's responsibility to not call this method if another thread
+ // may be calling it or StartCapturingLogs() at the same time.
+ //
+ // It is UB to add expectations, while capturing logs is enabled.
+ void StopCapturingLogs();
+
+ // ScopedMockLog::UseAsLocalSink()
+ //
+ // Each `ScopedMockLog` is implemented with an `absl::LogSink`; this method
+ // returns a reference to that sink (e.g. for use with
+ // `LOG(...).ToSinkOnly()`) and marks the `ScopedMockLog` as having been used
+ // even if `StartCapturingLogs` is never called.
+ absl::LogSink& UseAsLocalSink();
+
+ // Implements the mock method:
+ //
+ // void Log(LogSeverity severity, absl::string_view file_path,
+ // absl::string_view message);
+ //
+ // The second argument to Log() is the full path of the source file in
+ // which the LOG() was issued.
+ //
+ // This is a shorthand form, which should be used by most users. Use the
+ // `Send` mock only if you want to add expectations for other log message
+ // attributes.
+ MOCK_METHOD(void, Log,
+ (absl::LogSeverity severity, const std::string& file_path,
+ const std::string& message));
+
+ // Implements the mock method:
+ //
+ // void Send(const absl::LogEntry& entry);
+ //
+ // This is the most generic form of mock that can be specified. Use this mock
+ // only if you want to add expectations for log message attributes different
+ // from the log message text, log message path and log message severity.
+ //
+ // If no expectations are specified for this mock, the default action is to
+ // forward the call to the `Log` mock.
+ MOCK_METHOD(void, Send, (const absl::LogEntry&));
+
+ // Implements the mock method:
+ //
+ // void Flush();
+ //
+ // Use this mock only if you want to add expectations for log flush calls.
+ MOCK_METHOD(void, Flush, ());
+
+ private:
+ class ForwardingSink final : public absl::LogSink {
+ public:
+ explicit ForwardingSink(ScopedMockLog* sml) : sml_(sml) {}
+ ForwardingSink(const ForwardingSink&) = delete;
+ ForwardingSink& operator=(const ForwardingSink&) = delete;
+ void Send(const absl::LogEntry& entry) override { sml_->Send(entry); }
+ void Flush() override { sml_->Flush(); }
+
+ private:
+ ScopedMockLog* sml_;
+ };
+
+ ForwardingSink sink_;
+ bool is_capturing_logs_;
+ // Until C++20, the default constructor leaves the underlying value wrapped in
+ // std::atomic uninitialized, so all constructors should be sure to initialize
+ // is_triggered_.
+ std::atomic<bool> is_triggered_;
+};
+
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_LOG_SCOPED_MOCK_LOG_H_
diff --git a/absl/log/scoped_mock_log_test.cc b/absl/log/scoped_mock_log_test.cc
new file mode 100644
index 0000000..4273693
--- /dev/null
+++ b/absl/log/scoped_mock_log_test.cc
@@ -0,0 +1,295 @@
+//
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/log/scoped_mock_log.h"
+
+#include <memory>
+#include <thread> // NOLINT(build/c++11)
+
+#include "gmock/gmock.h"
+#include "gtest/gtest-spi.h"
+#include "gtest/gtest.h"
+#include "absl/base/attributes.h"
+#include "absl/base/log_severity.h"
+#include "absl/log/globals.h"
+#include "absl/log/internal/test_helpers.h"
+#include "absl/log/internal/test_matchers.h"
+#include "absl/log/log.h"
+#include "absl/memory/memory.h"
+#include "absl/strings/match.h"
+#include "absl/strings/string_view.h"
+#include "absl/synchronization/barrier.h"
+#include "absl/synchronization/notification.h"
+
+namespace {
+
+using ::testing::_;
+using ::testing::AnyNumber;
+using ::testing::Eq;
+using ::testing::HasSubstr;
+using ::testing::InSequence;
+using ::testing::Lt;
+using ::testing::Truly;
+using absl::log_internal::SourceBasename;
+using absl::log_internal::SourceFilename;
+using absl::log_internal::SourceLine;
+using absl::log_internal::TextMessageWithPrefix;
+using absl::log_internal::ThreadID;
+
+auto* test_env ABSL_ATTRIBUTE_UNUSED = ::testing::AddGlobalTestEnvironment(
+ new absl::log_internal::LogTestEnvironment);
+
+#if GTEST_HAS_DEATH_TEST
+TEST(ScopedMockLogDeathTest,
+ StartCapturingLogsCannotBeCalledWhenAlreadyCapturing) {
+ EXPECT_DEATH(
+ {
+ absl::ScopedMockLog log;
+ log.StartCapturingLogs();
+ log.StartCapturingLogs();
+ },
+ "StartCapturingLogs");
+}
+
+TEST(ScopedMockLogDeathTest, StopCapturingLogsCannotBeCalledWhenNotCapturing) {
+ EXPECT_DEATH(
+ {
+ absl::ScopedMockLog log;
+ log.StopCapturingLogs();
+ },
+ "StopCapturingLogs");
+}
+
+TEST(ScopedMockLogDeathTest, FailsCheckIfStartCapturingLogsIsNeverCalled) {
+ EXPECT_DEATH({ absl::ScopedMockLog log; },
+ "Did you forget to call StartCapturingLogs");
+}
+#endif
+
+// Tests that ScopedMockLog intercepts LOG()s when it's alive.
+TEST(ScopedMockLogTest, LogMockCatchAndMatchStrictExpectations) {
+ absl::ScopedMockLog log;
+
+ // The following expectations must match in the order they appear.
+ InSequence s;
+ EXPECT_CALL(log,
+ Log(absl::LogSeverity::kWarning, HasSubstr(__FILE__), "Danger."));
+ EXPECT_CALL(log, Log(absl::LogSeverity::kInfo, _, "Working...")).Times(2);
+ EXPECT_CALL(log, Log(absl::LogSeverity::kError, _, "Bad!!"));
+
+ log.StartCapturingLogs();
+ LOG(WARNING) << "Danger.";
+ LOG(INFO) << "Working...";
+ LOG(INFO) << "Working...";
+ LOG(ERROR) << "Bad!!";
+}
+
+TEST(ScopedMockLogTest, LogMockCatchAndMatchSendExpectations) {
+ absl::ScopedMockLog log;
+
+ EXPECT_CALL(
+ log,
+ Send(AllOf(SourceFilename(Eq("/my/very/very/very_long_source_file.cc")),
+ SourceBasename(Eq("very_long_source_file.cc")),
+ SourceLine(Eq(777)), ThreadID(Eq(absl::LogEntry::tid_t{1234})),
+ TextMessageWithPrefix(Truly([](absl::string_view msg) {
+ return absl::EndsWith(
+ msg, " very_long_source_file.cc:777] Info message");
+ })))));
+
+ log.StartCapturingLogs();
+ LOG(INFO)
+ .AtLocation("/my/very/very/very_long_source_file.cc", 777)
+ .WithThreadID(1234)
+ << "Info message";
+}
+
+TEST(ScopedMockLogTest, ScopedMockLogCanBeNice) {
+ absl::ScopedMockLog log;
+
+ InSequence s;
+ EXPECT_CALL(log,
+ Log(absl::LogSeverity::kWarning, HasSubstr(__FILE__), "Danger."));
+ EXPECT_CALL(log, Log(absl::LogSeverity::kInfo, _, "Working...")).Times(2);
+ EXPECT_CALL(log, Log(absl::LogSeverity::kError, _, "Bad!!"));
+
+ log.StartCapturingLogs();
+
+ // Any number of these are OK.
+ LOG(INFO) << "Info message.";
+ // Any number of these are OK.
+ LOG(WARNING).AtLocation("SomeOtherFile.cc", 100) << "Danger ";
+
+ LOG(WARNING) << "Danger.";
+
+ // Any number of these are OK.
+ LOG(INFO) << "Info message.";
+ // Any number of these are OK.
+ LOG(WARNING).AtLocation("SomeOtherFile.cc", 100) << "Danger ";
+
+ LOG(INFO) << "Working...";
+
+ // Any number of these are OK.
+ LOG(INFO) << "Info message.";
+ // Any number of these are OK.
+ LOG(WARNING).AtLocation("SomeOtherFile.cc", 100) << "Danger ";
+
+ LOG(INFO) << "Working...";
+
+ // Any number of these are OK.
+ LOG(INFO) << "Info message.";
+ // Any number of these are OK.
+ LOG(WARNING).AtLocation("SomeOtherFile.cc", 100) << "Danger ";
+
+ LOG(ERROR) << "Bad!!";
+
+ // Any number of these are OK.
+ LOG(INFO) << "Info message.";
+ // Any number of these are OK.
+ LOG(WARNING).AtLocation("SomeOtherFile.cc", 100) << "Danger ";
+}
+
+// Tests that ScopedMockLog generates a test failure if a message is logged
+// that is not expected (here, that means ERROR or FATAL).
+TEST(ScopedMockLogTest, RejectsUnexpectedLogs) {
+ EXPECT_NONFATAL_FAILURE(
+ {
+ absl::ScopedMockLog log(absl::MockLogDefault::kDisallowUnexpected);
+ // Any INFO and WARNING messages are permitted.
+ EXPECT_CALL(log, Log(Lt(absl::LogSeverity::kError), _, _))
+ .Times(AnyNumber());
+ log.StartCapturingLogs();
+ LOG(INFO) << "Ignored";
+ LOG(WARNING) << "Ignored";
+ LOG(ERROR) << "Should not be ignored";
+ },
+ "Should not be ignored");
+}
+
+TEST(ScopedMockLogTest, CapturesLogsAfterStartCapturingLogs) {
+ absl::SetStderrThreshold(absl::LogSeverityAtLeast::kInfinity);
+ absl::ScopedMockLog log;
+
+ // The ScopedMockLog object shouldn't see these LOGs, as it hasn't
+ // started capturing LOGs yet.
+ LOG(INFO) << "Ignored info";
+ LOG(WARNING) << "Ignored warning";
+ LOG(ERROR) << "Ignored error";
+
+ EXPECT_CALL(log, Log(absl::LogSeverity::kInfo, _, "Expected info"));
+ log.StartCapturingLogs();
+
+ // Only this LOG will be seen by the ScopedMockLog.
+ LOG(INFO) << "Expected info";
+}
+
+TEST(ScopedMockLogTest, DoesNotCaptureLogsAfterStopCapturingLogs) {
+ absl::ScopedMockLog log;
+ EXPECT_CALL(log, Log(absl::LogSeverity::kInfo, _, "Expected info"));
+
+ log.StartCapturingLogs();
+
+ // This LOG should be seen by the ScopedMockLog.
+ LOG(INFO) << "Expected info";
+
+ log.StopCapturingLogs();
+
+ // The ScopedMockLog object shouldn't see these LOGs, as it has
+ // stopped capturing LOGs.
+ LOG(INFO) << "Ignored info";
+ LOG(WARNING) << "Ignored warning";
+ LOG(ERROR) << "Ignored error";
+}
+
+// Tests that all messages are intercepted regardless of issuing thread. The
+// purpose of this test is NOT to exercise thread-safety.
+TEST(ScopedMockLogTest, LogFromMultipleThreads) {
+ absl::ScopedMockLog log;
+
+ // We don't establish an order to expectations here, since the threads may
+ // execute their log statements in different order.
+ EXPECT_CALL(log, Log(absl::LogSeverity::kInfo, __FILE__, "Thread 1"));
+ EXPECT_CALL(log, Log(absl::LogSeverity::kInfo, __FILE__, "Thread 2"));
+
+ log.StartCapturingLogs();
+
+ absl::Barrier barrier(2);
+ std::thread thread1([&barrier]() {
+ barrier.Block();
+ LOG(INFO) << "Thread 1";
+ });
+ std::thread thread2([&barrier]() {
+ barrier.Block();
+ LOG(INFO) << "Thread 2";
+ });
+
+ thread1.join();
+ thread2.join();
+}
+
+// Tests that no sequence will be imposed on two LOG message expectations from
+// different threads. This test would actually deadlock if replaced to two LOG
+// statements from the same thread.
+TEST(ScopedMockLogTest, NoSequenceWithMultipleThreads) {
+ absl::ScopedMockLog log;
+
+ absl::Barrier barrier(2);
+ EXPECT_CALL(log, Log(absl::LogSeverity::kInfo, _, _))
+ .Times(2)
+ .WillRepeatedly([&barrier]() { barrier.Block(); });
+
+ log.StartCapturingLogs();
+
+ std::thread thread1([]() { LOG(INFO) << "Thread 1"; });
+ std::thread thread2([]() { LOG(INFO) << "Thread 2"; });
+
+ thread1.join();
+ thread2.join();
+}
+
+TEST(ScopedMockLogTsanTest,
+ ScopedMockLogCanBeDeletedWhenAnotherThreadIsLogging) {
+ auto log = absl::make_unique<absl::ScopedMockLog>();
+ EXPECT_CALL(*log, Log(absl::LogSeverity::kInfo, __FILE__, "Thread log"))
+ .Times(AnyNumber());
+
+ log->StartCapturingLogs();
+
+ absl::Notification logging_started;
+
+ std::thread thread([&logging_started]() {
+ for (int i = 0; i < 100; ++i) {
+ if (i == 50) logging_started.Notify();
+ LOG(INFO) << "Thread log";
+ }
+ });
+
+ logging_started.WaitForNotification();
+ log.reset();
+ thread.join();
+}
+
+TEST(ScopedMockLogTest, AsLocalSink) {
+ absl::ScopedMockLog log(absl::MockLogDefault::kDisallowUnexpected);
+
+ EXPECT_CALL(log, Log(_, _, "two"));
+ EXPECT_CALL(log, Log(_, _, "three"));
+
+ LOG(INFO) << "one";
+ LOG(INFO).ToSinkOnly(&log.UseAsLocalSink()) << "two";
+ LOG(INFO).ToSinkAlso(&log.UseAsLocalSink()) << "three";
+}
+
+} // namespace
diff --git a/absl/log/stripping_test.cc b/absl/log/stripping_test.cc
new file mode 100644
index 0000000..271fae1
--- /dev/null
+++ b/absl/log/stripping_test.cc
@@ -0,0 +1,502 @@
+//
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// Tests for stripping of literal strings.
+// ---------------------------------------
+//
+// When a `LOG` statement can be trivially proved at compile time to never fire,
+// e.g. due to `ABSL_MIN_LOG_LEVEL`, `NDEBUG`, or some explicit condition, data
+// streamed in can be dropped from the compiled program completely if they are
+// not used elsewhere. This most commonly affects string literals, which users
+// often want to strip to reduce binary size and/or redact information about
+// their program's internals (e.g. in a release build).
+//
+// These tests log strings and then validate whether they appear in the compiled
+// binary. This is done by opening the file corresponding to the running test
+// and running a simple string search on its contents. The strings to be logged
+// and searched for must be unique, and we must take care not to emit them into
+// the binary in any other place, e.g. when searching for them. The latter is
+// accomplished by computing them using base64; the source string appears in the
+// binary but the target string is computed at runtime.
+
+#include <stdio.h>
+
+#if defined(__MACH__)
+#include <mach-o/dyld.h>
+#elif defined(_WIN32)
+#include <Windows.h>
+#include <tchar.h>
+#endif
+
+#include <algorithm>
+#include <functional>
+#include <memory>
+#include <ostream>
+#include <string>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/base/internal/strerror.h"
+#include "absl/base/log_severity.h"
+#include "absl/flags/internal/program_name.h"
+#include "absl/log/check.h"
+#include "absl/log/internal/test_helpers.h"
+#include "absl/log/log.h"
+#include "absl/status/status.h"
+#include "absl/strings/escaping.h"
+#include "absl/strings/str_format.h"
+#include "absl/strings/string_view.h"
+
+// Set a flag that controls whether we actually execute fatal statements, but
+// prevent the compiler from optimizing it out.
+static volatile bool kReallyDie = false;
+
+namespace {
+using ::testing::_;
+using ::testing::Eq;
+using ::testing::NotNull;
+
+using absl::log_internal::kAbslMinLogLevel;
+
+std::string Base64UnescapeOrDie(absl::string_view data) {
+ std::string decoded;
+ CHECK(absl::Base64Unescape(data, &decoded));
+ return decoded;
+}
+
+// -----------------------------------------------------------------------------
+// A Googletest matcher which searches the running binary for a given string
+// -----------------------------------------------------------------------------
+
+// This matcher is used to validate that literal strings streamed into
+// `LOG` statements that ought to be compiled out (e.g. `LOG_IF(INFO, false)`)
+// do not appear in the binary.
+//
+// Note that passing the string to be sought directly to `FileHasSubstr()` all
+// but forces its inclusion in the binary regardless of the logging library's
+// behavior. For example:
+//
+// LOG_IF(INFO, false) << "you're the man now dog";
+// // This will always pass:
+// // EXPECT_THAT(fp, FileHasSubstr("you're the man now dog"));
+// // So use this instead:
+// EXPECT_THAT(fp, FileHasSubstr(
+// Base64UnescapeOrDie("eW91J3JlIHRoZSBtYW4gbm93IGRvZw==")));
+
+class FileHasSubstrMatcher final : public ::testing::MatcherInterface<FILE*> {
+ public:
+ explicit FileHasSubstrMatcher(absl::string_view needle) : needle_(needle) {}
+
+ bool MatchAndExplain(
+ FILE* fp, ::testing::MatchResultListener* listener) const override {
+ std::string buf(
+ std::max<std::string::size_type>(needle_.size() * 2, 163840000), '\0');
+ size_t buf_start_offset = 0; // The file offset of the byte at `buf[0]`.
+ size_t buf_data_size = 0; // The number of bytes of `buf` which contain
+ // data.
+
+ ::fseek(fp, 0, SEEK_SET);
+ while (true) {
+ // Fill the buffer to capacity or EOF:
+ while (buf_data_size < buf.size()) {
+ const size_t ret = fread(&buf[buf_data_size], sizeof(char),
+ buf.size() - buf_data_size, fp);
+ if (ret == 0) break;
+ buf_data_size += ret;
+ }
+ if (ferror(fp)) {
+ *listener << "error reading file";
+ return false;
+ }
+ const absl::string_view haystack(&buf[0], buf_data_size);
+ const auto off = haystack.find(needle_);
+ if (off != haystack.npos) {
+ *listener << "string found at offset " << buf_start_offset + off;
+ return true;
+ }
+ if (feof(fp)) {
+ *listener << "string not found";
+ return false;
+ }
+ // Copy the end of `buf` to the beginning so we catch matches that span
+ // buffer boundaries. `buf` and `buf_data_size` are always large enough
+ // that these ranges don't overlap.
+ memcpy(&buf[0], &buf[buf_data_size - needle_.size()], needle_.size());
+ buf_start_offset += buf_data_size - needle_.size();
+ buf_data_size = needle_.size();
+ }
+ }
+ void DescribeTo(std::ostream* os) const override {
+ *os << "contains the string \"" << needle_ << "\" (base64(\""
+ << Base64UnescapeOrDie(needle_) << "\"))";
+ }
+
+ void DescribeNegationTo(std::ostream* os) const override {
+ *os << "does not ";
+ DescribeTo(os);
+ }
+
+ private:
+ std::string needle_;
+};
+
+class StrippingTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+#ifndef NDEBUG
+ // Non-optimized builds don't necessarily eliminate dead code at all, so we
+ // don't attempt to validate stripping against such builds.
+ GTEST_SKIP() << "StrippingTests skipped since this build is not optimized";
+#elif defined(__EMSCRIPTEN__)
+ // These tests require a way to examine the running binary and look for
+ // strings; there's no portable way to do that.
+ GTEST_SKIP()
+ << "StrippingTests skipped since this platform is not optimized";
+#endif
+ }
+
+ // Opens this program's executable file. Returns `nullptr` and writes to
+ // `stderr` on failure.
+ std::unique_ptr<FILE, std::function<void(FILE*)>> OpenTestExecutable() {
+#if defined(__linux__)
+ std::unique_ptr<FILE, std::function<void(FILE*)>> fp(
+ fopen("/proc/self/exe", "rb"), [](FILE* fp) { fclose(fp); });
+ if (!fp) {
+ const std::string err = absl::base_internal::StrError(errno);
+ absl::FPrintF(stderr, "Failed to open /proc/self/exe: %s\n", err);
+ }
+ return fp;
+#elif defined(__Fuchsia__)
+ // TODO(b/242579714): We need to restore the test coverage on this platform.
+ std::unique_ptr<FILE, std::function<void(FILE*)>> fp(
+ fopen(absl::StrCat("/pkg/bin/",
+ absl::flags_internal::ShortProgramInvocationName())
+ .c_str(),
+ "rb"),
+ [](FILE* fp) { fclose(fp); });
+ if (!fp) {
+ const std::string err = absl::base_internal::StrError(errno);
+ absl::FPrintF(stderr, "Failed to open /pkg/bin/<binary name>: %s\n", err);
+ }
+ return fp;
+#elif defined(__MACH__)
+ uint32_t size = 0;
+ int ret = _NSGetExecutablePath(nullptr, &size);
+ if (ret != -1) {
+ absl::FPrintF(stderr,
+ "Failed to get executable path: "
+ "_NSGetExecutablePath(nullptr) returned %d\n",
+ ret);
+ return nullptr;
+ }
+ std::string path(size, '\0');
+ ret = _NSGetExecutablePath(&path[0], &size);
+ if (ret != 0) {
+ absl::FPrintF(
+ stderr,
+ "Failed to get executable path: _NSGetExecutablePath(buffer) "
+ "returned %d\n",
+ ret);
+ return nullptr;
+ }
+ std::unique_ptr<FILE, std::function<void(FILE*)>> fp(
+ fopen(path.c_str(), "rb"), [](FILE* fp) { fclose(fp); });
+ if (!fp) {
+ const std::string err = absl::base_internal::StrError(errno);
+ absl::FPrintF(stderr, "Failed to open executable at %s: %s\n", path, err);
+ }
+ return fp;
+#elif defined(_WIN32)
+ std::basic_string<TCHAR> path(4096, _T('\0'));
+ while (true) {
+ const uint32_t ret = ::GetModuleFileName(nullptr, &path[0],
+ static_cast<DWORD>(path.size()));
+ if (ret == 0) {
+ absl::FPrintF(
+ stderr,
+ "Failed to get executable path: GetModuleFileName(buffer) "
+ "returned 0\n");
+ return nullptr;
+ }
+ if (ret < path.size()) break;
+ path.resize(path.size() * 2, _T('\0'));
+ }
+ std::unique_ptr<FILE, std::function<void(FILE*)>> fp(
+ _tfopen(path.c_str(), _T("rb")), [](FILE* fp) { fclose(fp); });
+ if (!fp) absl::FPrintF(stderr, "Failed to open executable\n");
+ return fp;
+#else
+ absl::FPrintF(stderr,
+ "OpenTestExecutable() unimplemented on this platform\n");
+ return nullptr;
+#endif
+ }
+
+ ::testing::Matcher<FILE*> FileHasSubstr(absl::string_view needle) {
+ return MakeMatcher(new FileHasSubstrMatcher(needle));
+ }
+};
+
+// This tests whether out methodology for testing stripping works on this
+// platform by looking for one string that definitely ought to be there and one
+// that definitely ought not to. If this fails, none of the `StrippingTest`s
+// are going to produce meaningful results.
+TEST_F(StrippingTest, Control) {
+ constexpr char kEncodedPositiveControl[] =
+ "U3RyaXBwaW5nVGVzdC5Qb3NpdGl2ZUNvbnRyb2w=";
+ const std::string encoded_negative_control =
+ absl::Base64Escape("StrippingTest.NegativeControl");
+
+ // Verify this mainly so we can encode other strings and know definitely they
+ // won't encode to `kEncodedPositiveControl`.
+ EXPECT_THAT(Base64UnescapeOrDie("U3RyaXBwaW5nVGVzdC5Qb3NpdGl2ZUNvbnRyb2w="),
+ Eq("StrippingTest.PositiveControl"));
+
+ auto exe = OpenTestExecutable();
+ ASSERT_THAT(exe, NotNull());
+ EXPECT_THAT(exe.get(), FileHasSubstr(kEncodedPositiveControl));
+ EXPECT_THAT(exe.get(), Not(FileHasSubstr(encoded_negative_control)));
+}
+
+TEST_F(StrippingTest, Literal) {
+ // We need to load a copy of the needle string into memory (so we can search
+ // for it) without leaving it lying around in plaintext in the executable file
+ // as would happen if we used a literal. We might (or might not) leave it
+ // lying around later; that's what the tests are for!
+ const std::string needle = absl::Base64Escape("StrippingTest.Literal");
+ LOG(INFO) << "U3RyaXBwaW5nVGVzdC5MaXRlcmFs";
+ auto exe = OpenTestExecutable();
+ ASSERT_THAT(exe, NotNull());
+ if (absl::LogSeverity::kInfo >= kAbslMinLogLevel) {
+ EXPECT_THAT(exe.get(), FileHasSubstr(needle));
+ } else {
+ EXPECT_THAT(exe.get(), Not(FileHasSubstr(needle)));
+ }
+}
+
+TEST_F(StrippingTest, LiteralInExpression) {
+ // We need to load a copy of the needle string into memory (so we can search
+ // for it) without leaving it lying around in plaintext in the executable file
+ // as would happen if we used a literal. We might (or might not) leave it
+ // lying around later; that's what the tests are for!
+ const std::string needle =
+ absl::Base64Escape("StrippingTest.LiteralInExpression");
+ LOG(INFO) << absl::StrCat("secret: ",
+ "U3RyaXBwaW5nVGVzdC5MaXRlcmFsSW5FeHByZXNzaW9u");
+ std::unique_ptr<FILE, std::function<void(FILE*)>> exe = OpenTestExecutable();
+ ASSERT_THAT(exe, NotNull());
+ if (absl::LogSeverity::kInfo >= kAbslMinLogLevel) {
+ EXPECT_THAT(exe.get(), FileHasSubstr(needle));
+ } else {
+ EXPECT_THAT(exe.get(), Not(FileHasSubstr(needle)));
+ }
+}
+
+TEST_F(StrippingTest, Fatal) {
+ // We need to load a copy of the needle string into memory (so we can search
+ // for it) without leaving it lying around in plaintext in the executable file
+ // as would happen if we used a literal. We might (or might not) leave it
+ // lying around later; that's what the tests are for!
+ const std::string needle = absl::Base64Escape("StrippingTest.Fatal");
+ // We don't care if the LOG statement is actually executed, we're just
+ // checking that it's stripped.
+ if (kReallyDie) LOG(FATAL) << "U3RyaXBwaW5nVGVzdC5GYXRhbA==";
+
+ std::unique_ptr<FILE, std::function<void(FILE*)>> exe = OpenTestExecutable();
+ ASSERT_THAT(exe, NotNull());
+ if (absl::LogSeverity::kFatal >= kAbslMinLogLevel) {
+ EXPECT_THAT(exe.get(), FileHasSubstr(needle));
+ } else {
+ EXPECT_THAT(exe.get(), Not(FileHasSubstr(needle)));
+ }
+}
+
+TEST_F(StrippingTest, DFatal) {
+ // We need to load a copy of the needle string into memory (so we can search
+ // for it) without leaving it lying around in plaintext in the executable file
+ // as would happen if we used a literal. We might (or might not) leave it
+ // lying around later; that's what the tests are for!
+ const std::string needle = absl::Base64Escape("StrippingTest.DFatal");
+ // We don't care if the LOG statement is actually executed, we're just
+ // checking that it's stripped.
+ if (kReallyDie) LOG(DFATAL) << "U3RyaXBwaW5nVGVzdC5ERmF0YWw=";
+
+ std::unique_ptr<FILE, std::function<void(FILE*)>> exe = OpenTestExecutable();
+ ASSERT_THAT(exe, NotNull());
+ // `DFATAL` can be `ERROR` or `FATAL`, and a compile-time optimizer doesn't
+ // know which, because `absl::kLogDebugFatal` is declared `extern` and defined
+ // in another TU. Link-time optimization might do better. We have six cases:
+ // | `AMLL` is-> | `<=ERROR` | `FATAL` | `>FATAL` |
+ // | ------------------- | --------- | ------- | -------- |
+ // | `DFATAL` is `ERROR` | present | ? | stripped |
+ // | `DFATAL` is `FATAL` | present | present | stripped |
+
+ // These constexpr variables are used to suppress unreachable code warnings
+ // in the if-else statements below.
+
+ // "present" in the table above: `DFATAL` exceeds `ABSL_MIN_LOG_LEVEL`, so
+ // `DFATAL` statements should not be stripped (and they should be logged
+ // when executed, but that's a different testsuite).
+ constexpr bool kExpectPresent = absl::kLogDebugFatal >= kAbslMinLogLevel;
+
+ // "stripped" in the table above: even though the compiler may not know
+ // which value `DFATAL` has, it should be able to strip it since both
+ // possible values ought to be stripped.
+ constexpr bool kExpectStripped = kAbslMinLogLevel > absl::LogSeverity::kFatal;
+
+ if (kExpectPresent) {
+ EXPECT_THAT(exe.get(), FileHasSubstr(needle));
+ } else if (kExpectStripped) {
+ EXPECT_THAT(exe.get(), Not(FileHasSubstr(needle)));
+ } else {
+ // "?" in the table above; may or may not be stripped depending on whether
+ // any link-time optimization is done. Either outcome is ok.
+ }
+}
+
+TEST_F(StrippingTest, Level) {
+ const std::string needle = absl::Base64Escape("StrippingTest.Level");
+ volatile auto severity = absl::LogSeverity::kWarning;
+ // Ensure that `severity` is not a compile-time constant to prove that
+ // stripping works regardless:
+ LOG(LEVEL(severity)) << "U3RyaXBwaW5nVGVzdC5MZXZlbA==";
+ std::unique_ptr<FILE, std::function<void(FILE*)>> exe = OpenTestExecutable();
+ ASSERT_THAT(exe, NotNull());
+ if (absl::LogSeverity::kFatal >= kAbslMinLogLevel) {
+ // This can't be stripped at compile-time because it might evaluate to a
+ // level that shouldn't be stripped.
+ EXPECT_THAT(exe.get(), FileHasSubstr(needle));
+ } else {
+#if (defined(_MSC_VER) && !defined(__clang__)) || defined(__APPLE__)
+ // Dead code elimination misses this case.
+#else
+ // All levels should be stripped, so it doesn't matter what the severity
+ // winds up being.
+ EXPECT_THAT(exe.get(), Not(FileHasSubstr(needle)));
+#endif
+ }
+}
+
+TEST_F(StrippingTest, Check) {
+ // Here we also need a variable name with enough entropy that it's unlikely to
+ // appear in the binary by chance. `volatile` keeps the tautological
+ // comparison (and the rest of the `CHECK`) from being optimized away.
+ const std::string var_needle = absl::Base64Escape("StrippingTestCheckVar");
+ const std::string msg_needle = absl::Base64Escape("StrippingTest.Check");
+ volatile int U3RyaXBwaW5nVGVzdENoZWNrVmFy = 0xCAFE;
+ // We don't care if the CHECK is actually executed, just that stripping works.
+ // Hiding it behind `kReallyDie` works around some overly aggressive
+ // optimizations in older versions of MSVC.
+ if (kReallyDie) {
+ CHECK(U3RyaXBwaW5nVGVzdENoZWNrVmFy != U3RyaXBwaW5nVGVzdENoZWNrVmFy)
+ << "U3RyaXBwaW5nVGVzdC5DaGVjaw==";
+ }
+
+ std::unique_ptr<FILE, std::function<void(FILE*)>> exe = OpenTestExecutable();
+ ASSERT_THAT(exe, NotNull());
+ if (absl::LogSeverity::kFatal >= kAbslMinLogLevel) {
+ EXPECT_THAT(exe.get(), FileHasSubstr(var_needle));
+ EXPECT_THAT(exe.get(), FileHasSubstr(msg_needle));
+ } else {
+ EXPECT_THAT(exe.get(), Not(FileHasSubstr(var_needle)));
+ EXPECT_THAT(exe.get(), Not(FileHasSubstr(msg_needle)));
+ }
+}
+
+TEST_F(StrippingTest, CheckOp) {
+ // See `StrippingTest.Check` for some hairy implementation notes.
+ const std::string var_needle1 =
+ absl::Base64Escape("StrippingTestCheckOpVar1");
+ const std::string var_needle2 =
+ absl::Base64Escape("StrippingTestCheckOpVar2");
+ const std::string msg_needle = absl::Base64Escape("StrippingTest.CheckOp");
+ volatile int U3RyaXBwaW5nVGVzdENoZWNrT3BWYXIx = 0xFEED;
+ volatile int U3RyaXBwaW5nVGVzdENoZWNrT3BWYXIy = 0xCAFE;
+ if (kReallyDie) {
+ CHECK_EQ(U3RyaXBwaW5nVGVzdENoZWNrT3BWYXIx, U3RyaXBwaW5nVGVzdENoZWNrT3BWYXIy)
+ << "U3RyaXBwaW5nVGVzdC5DaGVja09w";
+ }
+
+ std::unique_ptr<FILE, std::function<void(FILE*)>> exe = OpenTestExecutable();
+ ASSERT_THAT(exe, NotNull());
+
+ if (absl::LogSeverity::kFatal >= kAbslMinLogLevel) {
+ EXPECT_THAT(exe.get(), FileHasSubstr(var_needle1));
+ EXPECT_THAT(exe.get(), FileHasSubstr(var_needle2));
+ EXPECT_THAT(exe.get(), FileHasSubstr(msg_needle));
+ } else {
+ EXPECT_THAT(exe.get(), Not(FileHasSubstr(var_needle1)));
+ EXPECT_THAT(exe.get(), Not(FileHasSubstr(var_needle2)));
+ EXPECT_THAT(exe.get(), Not(FileHasSubstr(msg_needle)));
+ }
+}
+
+TEST_F(StrippingTest, CheckStrOp) {
+ // See `StrippingTest.Check` for some hairy implementation notes.
+ const std::string var_needle1 =
+ absl::Base64Escape("StrippingTestCheckStrOpVar1");
+ const std::string var_needle2 =
+ absl::Base64Escape("StrippingTestCheckStrOpVar2");
+ const std::string msg_needle = absl::Base64Escape("StrippingTest.CheckStrOp");
+ const char *volatile U3RyaXBwaW5nVGVzdENoZWNrU3RyT3BWYXIx = "FEED";
+ const char *volatile U3RyaXBwaW5nVGVzdENoZWNrU3RyT3BWYXIy = "CAFE";
+ if (kReallyDie) {
+ CHECK_STREQ(U3RyaXBwaW5nVGVzdENoZWNrU3RyT3BWYXIx,
+ U3RyaXBwaW5nVGVzdENoZWNrU3RyT3BWYXIy)
+ << "U3RyaXBwaW5nVGVzdC5DaGVja1N0ck9w";
+ }
+
+ std::unique_ptr<FILE, std::function<void(FILE*)>> exe = OpenTestExecutable();
+ ASSERT_THAT(exe, NotNull());
+
+ if (absl::LogSeverity::kFatal >= kAbslMinLogLevel) {
+ EXPECT_THAT(exe.get(), FileHasSubstr(var_needle1));
+ EXPECT_THAT(exe.get(), FileHasSubstr(var_needle2));
+ EXPECT_THAT(exe.get(), FileHasSubstr(msg_needle));
+ } else {
+ EXPECT_THAT(exe.get(), Not(FileHasSubstr(var_needle1)));
+ EXPECT_THAT(exe.get(), Not(FileHasSubstr(var_needle2)));
+ EXPECT_THAT(exe.get(), Not(FileHasSubstr(msg_needle)));
+ }
+}
+
+TEST_F(StrippingTest, CheckOk) {
+ // See `StrippingTest.Check` for some hairy implementation notes.
+ const std::string var_needle = absl::Base64Escape("StrippingTestCheckOkVar1");
+ const std::string msg_needle = absl::Base64Escape("StrippingTest.CheckOk");
+ volatile bool x = false;
+ auto U3RyaXBwaW5nVGVzdENoZWNrT2tWYXIx = absl::OkStatus();
+ if (x) {
+ U3RyaXBwaW5nVGVzdENoZWNrT2tWYXIx =
+ absl::InvalidArgumentError("Stripping this is not my job!");
+ }
+ if (kReallyDie) {
+ CHECK_OK(U3RyaXBwaW5nVGVzdENoZWNrT2tWYXIx)
+ << "U3RyaXBwaW5nVGVzdC5DaGVja09r";
+ }
+
+ std::unique_ptr<FILE, std::function<void(FILE*)>> exe = OpenTestExecutable();
+ ASSERT_THAT(exe, NotNull());
+
+ if (absl::LogSeverity::kFatal >= kAbslMinLogLevel) {
+ EXPECT_THAT(exe.get(), FileHasSubstr(var_needle));
+ EXPECT_THAT(exe.get(), FileHasSubstr(msg_needle));
+ } else {
+ EXPECT_THAT(exe.get(), Not(FileHasSubstr(var_needle)));
+ EXPECT_THAT(exe.get(), Not(FileHasSubstr(msg_needle)));
+ }
+}
+
+} // namespace
diff --git a/absl/log/structured.h b/absl/log/structured.h
new file mode 100644
index 0000000..9ad69fb
--- /dev/null
+++ b/absl/log/structured.h
@@ -0,0 +1,70 @@
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// -----------------------------------------------------------------------------
+// File: log/structured.h
+// -----------------------------------------------------------------------------
+//
+// This header declares APIs supporting structured logging, allowing log
+// statements to be more easily parsed, especially by automated processes.
+//
+// When structured logging is in use, data streamed into a `LOG` statement are
+// encoded as `Value` fields in a `logging.proto.Event` protocol buffer message.
+// The individual data are exposed programmatically to `LogSink`s and to the
+// user via some log reading tools which are able to query the structured data
+// more usefully than would be possible if each message was a single opaque
+// string. These helpers allow user code to add additional structure to the
+// data they stream.
+
+#ifndef ABSL_LOG_STRUCTURED_H_
+#define ABSL_LOG_STRUCTURED_H_
+
+#include <ostream>
+
+#include "absl/base/config.h"
+#include "absl/log/internal/structured.h"
+#include "absl/strings/string_view.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+
+// LogAsLiteral()
+//
+// Annotates its argument as a string literal so that structured logging
+// captures it as a `literal` field instead of a `str` field (the default).
+// This does not affect the text representation, only the structure.
+//
+// Streaming `LogAsLiteral(s)` into a `std::ostream` behaves just like streaming
+// `s` directly.
+//
+// Using `LogAsLiteral()` is occasionally appropriate and useful when proxying
+// data logged from another system or another language. For example:
+//
+// void Logger::LogString(absl::string_view str, absl::LogSeverity severity,
+// const char *file, int line) {
+// LOG(LEVEL(severity)).AtLocation(file, line) << str;
+// }
+// void Logger::LogStringLiteral(absl::string_view str,
+// absl::LogSeverity severity, const char *file,
+// int line) {
+// LOG(LEVEL(severity)).AtLocation(file, line) << absl::LogAsLiteral(str);
+// }
+inline log_internal::AsLiteralImpl LogAsLiteral(absl::string_view s) {
+ return log_internal::AsLiteralImpl(s);
+}
+
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_LOG_STRUCTURED_H_
diff --git a/absl/log/structured_test.cc b/absl/log/structured_test.cc
new file mode 100644
index 0000000..490a35d
--- /dev/null
+++ b/absl/log/structured_test.cc
@@ -0,0 +1,63 @@
+//
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/log/structured.h"
+
+#include <ios>
+#include <sstream>
+#include <string>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/base/attributes.h"
+#include "absl/log/internal/test_helpers.h"
+#include "absl/log/internal/test_matchers.h"
+#include "absl/log/log.h"
+#include "absl/log/scoped_mock_log.h"
+
+namespace {
+using ::absl::log_internal::MatchesOstream;
+using ::absl::log_internal::TextMessage;
+using ::testing::Eq;
+
+auto *test_env ABSL_ATTRIBUTE_UNUSED = ::testing::AddGlobalTestEnvironment(
+ new absl::log_internal::LogTestEnvironment);
+
+// Abseil Logging library uses these by default, so we set them on the
+// `std::ostream` we compare against too.
+std::ios &LoggingDefaults(std::ios &str) {
+ str.setf(std::ios_base::showbase | std::ios_base::boolalpha |
+ std::ios_base::internal);
+ return str;
+}
+
+TEST(StreamingFormatTest, LogAsLiteral) {
+ std::ostringstream stream;
+ const std::string not_a_literal("hello world");
+ stream << LoggingDefaults << absl::LogAsLiteral(not_a_literal);
+
+ absl::ScopedMockLog sink;
+
+ EXPECT_CALL(sink,
+ Send(AllOf(TextMessage(MatchesOstream(stream)),
+ TextMessage(Eq("hello world")),
+ ENCODED_MESSAGE(EqualsProto(
+ R"pb(value { literal: "hello world" })pb")))));
+
+ sink.StartCapturingLogs();
+ LOG(INFO) << absl::LogAsLiteral(not_a_literal);
+}
+
+} // namespace
diff --git a/absl/log/vlog_is_on.h b/absl/log/vlog_is_on.h
new file mode 100644
index 0000000..7898651
--- /dev/null
+++ b/absl/log/vlog_is_on.h
@@ -0,0 +1,72 @@
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// -----------------------------------------------------------------------------
+// File: log/vlog_is_on.h
+// -----------------------------------------------------------------------------
+//
+// This header defines the `VLOG_IS_ON()` macro that controls the
+// variable-verbosity conditional logging.
+//
+// It's used by `VLOG` in log.h, or it can also be used directly like this:
+//
+// if (VLOG_IS_ON(2)) {
+// foo_server.RecomputeStatisticsExpensive();
+// LOG(INFO) << foo_server.LastStatisticsAsString();
+// }
+//
+// Each source file has an effective verbosity level that's a non-negative
+// integer computed from the `--vmodule` and `--v` flags.
+// `VLOG_IS_ON(n)` is true, and `VLOG(n)` logs, if that effective verbosity
+// level is greater than or equal to `n`.
+//
+// `--vmodule` takes a comma-delimited list of key=value pairs. Each key is a
+// pattern matched against filenames, and the values give the effective severity
+// level applied to matching files. '?' and '*' characters in patterns are
+// interpreted as single-character and zero-or-more-character wildcards.
+// Patterns including a slash character are matched against full pathnames,
+// while those without are matched against basenames only. One suffix (i.e. the
+// last . and everything after it) is stripped from each filename prior to
+// matching, as is the special suffix "-inl".
+//
+// Files are matched against globs in `--vmodule` in order, and the first match
+// determines the verbosity level.
+//
+// Files which do not match any pattern in `--vmodule` use the value of `--v` as
+// their effective verbosity level. The default is 0.
+//
+// SetVLOGLevel helper function is provided to do limited dynamic control over
+// V-logging by appending to `--vmodule`. Because these go at the beginning of
+// the list, they take priority over any globs previously added.
+//
+// Resetting --vmodule will override all previous modifications to `--vmodule`,
+// including via SetVLOGLevel.
+
+#ifndef ABSL_LOG_VLOG_IS_ON_H_
+#define ABSL_LOG_VLOG_IS_ON_H_
+
+#include "absl/log/absl_vlog_is_on.h" // IWYU pragma: export
+
+// IWYU pragma: private, include "absl/log/log.h"
+
+// Each VLOG_IS_ON call site gets its own VLogSite that registers with the
+// global linked list of sites to asynchronously update its verbosity level on
+// changes to --v or --vmodule. The verbosity can also be set by manually
+// calling SetVLOGLevel.
+//
+// VLOG_IS_ON is not async signal safe, but it is guaranteed not to allocate
+// new memory.
+#define VLOG_IS_ON(verbose_level) ABSL_VLOG_IS_ON(verbose_level)
+
+#endif // ABSL_LOG_VLOG_IS_ON_H_
diff --git a/absl/log/vlog_is_on_test.cc b/absl/log/vlog_is_on_test.cc
new file mode 100644
index 0000000..883d798
--- /dev/null
+++ b/absl/log/vlog_is_on_test.cc
@@ -0,0 +1,176 @@
+// Copyright 2023 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/log/vlog_is_on.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/base/log_severity.h"
+#include "absl/flags/flag.h"
+#include "absl/log/flags.h"
+#include "absl/log/globals.h"
+#include "absl/log/log.h"
+#include "absl/log/scoped_mock_log.h"
+#include "absl/types/optional.h"
+
+namespace {
+
+using ::testing::_;
+
+absl::optional<int> MaxLogVerbosity() {
+#ifdef ABSL_MAX_VLOG_VERBOSITY
+ return ABSL_MAX_VLOG_VERBOSITY;
+#else
+ return absl::nullopt;
+#endif
+}
+
+absl::optional<int> MinLogLevel() {
+#ifdef ABSL_MIN_LOG_LEVEL
+ return static_cast<int>(ABSL_MIN_LOG_LEVEL);
+#else
+ return absl::nullopt;
+#endif
+}
+
+TEST(VLogIsOn, GlobalWorksWithoutMaxVerbosityAndMinLogLevel) {
+ if (MaxLogVerbosity().has_value() || MinLogLevel().has_value()) {
+ GTEST_SKIP();
+ }
+
+ absl::SetGlobalVLogLevel(3);
+ absl::ScopedMockLog log(absl::MockLogDefault::kDisallowUnexpected);
+
+ EXPECT_CALL(log, Log(absl::LogSeverity::kInfo, _, "important"));
+
+ log.StartCapturingLogs();
+ VLOG(3) << "important";
+ VLOG(4) << "spam";
+}
+
+TEST(VLogIsOn, FileWorksWithoutMaxVerbosityAndMinLogLevel) {
+ if (MaxLogVerbosity().has_value() || MinLogLevel().has_value()) {
+ GTEST_SKIP();
+ }
+
+ absl::SetVLogLevel("vlog_is_on_test", 3);
+ absl::ScopedMockLog log(absl::MockLogDefault::kDisallowUnexpected);
+
+ EXPECT_CALL(log, Log(absl::LogSeverity::kInfo, _, "important"));
+
+ log.StartCapturingLogs();
+ VLOG(3) << "important";
+ VLOG(4) << "spam";
+}
+
+TEST(VLogIsOn, PatternWorksWithoutMaxVerbosityAndMinLogLevel) {
+ if (MaxLogVerbosity().has_value() || MinLogLevel().has_value()) {
+ GTEST_SKIP();
+ }
+
+ absl::SetVLogLevel("vlog_is_on*", 3);
+ absl::ScopedMockLog log(absl::MockLogDefault::kDisallowUnexpected);
+
+ EXPECT_CALL(log, Log(absl::LogSeverity::kInfo, _, "important"));
+
+ log.StartCapturingLogs();
+ VLOG(3) << "important";
+ VLOG(4) << "spam";
+}
+
+TEST(VLogIsOn, GlobalDoesNotFilterBelowMaxVerbosity) {
+ if (!MaxLogVerbosity().has_value() || *MaxLogVerbosity() < 2) {
+ GTEST_SKIP();
+ }
+
+ // Set an arbitrary high value to avoid filtering VLOGs in tests by default.
+ absl::SetGlobalVLogLevel(1000);
+ absl::ScopedMockLog log(absl::MockLogDefault::kDisallowUnexpected);
+
+ EXPECT_CALL(log, Log(absl::LogSeverity::kInfo, _, "asdf"));
+
+ log.StartCapturingLogs();
+ VLOG(2) << "asdf";
+}
+
+TEST(VLogIsOn, FileDoesNotFilterBelowMaxVerbosity) {
+ if (!MaxLogVerbosity().has_value() || *MaxLogVerbosity() < 2) {
+ GTEST_SKIP();
+ }
+
+ // Set an arbitrary high value to avoid filtering VLOGs in tests by default.
+ absl::SetVLogLevel("vlog_is_on_test", 1000);
+ absl::ScopedMockLog log(absl::MockLogDefault::kDisallowUnexpected);
+
+ EXPECT_CALL(log, Log(absl::LogSeverity::kInfo, _, "asdf"));
+
+ log.StartCapturingLogs();
+ VLOG(2) << "asdf";
+}
+
+TEST(VLogIsOn, PatternDoesNotFilterBelowMaxVerbosity) {
+ if (!MaxLogVerbosity().has_value() || *MaxLogVerbosity() < 2) {
+ GTEST_SKIP();
+ }
+
+ // Set an arbitrary high value to avoid filtering VLOGs in tests by default.
+ absl::SetVLogLevel("vlog_is_on*", 1000);
+ absl::ScopedMockLog log(absl::MockLogDefault::kDisallowUnexpected);
+
+ EXPECT_CALL(log, Log(absl::LogSeverity::kInfo, _, "asdf"));
+
+ log.StartCapturingLogs();
+ VLOG(2) << "asdf";
+}
+
+TEST(VLogIsOn, GlobalFiltersAboveMaxVerbosity) {
+ if (!MaxLogVerbosity().has_value() || *MaxLogVerbosity() >= 4) {
+ GTEST_SKIP();
+ }
+
+ // Set an arbitrary high value to avoid filtering VLOGs in tests by default.
+ absl::SetGlobalVLogLevel(1000);
+ absl::ScopedMockLog log(absl::MockLogDefault::kDisallowUnexpected);
+
+ log.StartCapturingLogs();
+ VLOG(4) << "dfgh";
+}
+
+TEST(VLogIsOn, FileFiltersAboveMaxVerbosity) {
+ if (!MaxLogVerbosity().has_value() || *MaxLogVerbosity() >= 4) {
+ GTEST_SKIP();
+ }
+
+ // Set an arbitrary high value to avoid filtering VLOGs in tests by default.
+ absl::SetVLogLevel("vlog_is_on_test", 1000);
+ absl::ScopedMockLog log(absl::MockLogDefault::kDisallowUnexpected);
+
+ log.StartCapturingLogs();
+ VLOG(4) << "dfgh";
+}
+
+TEST(VLogIsOn, PatternFiltersAboveMaxVerbosity) {
+ if (!MaxLogVerbosity().has_value() || *MaxLogVerbosity() >= 4) {
+ GTEST_SKIP();
+ }
+
+ // Set an arbitrary high value to avoid filtering VLOGs in tests by default.
+ absl::SetVLogLevel("vlog_is_on*", 1000);
+ absl::ScopedMockLog log(absl::MockLogDefault::kDisallowUnexpected);
+
+ log.StartCapturingLogs();
+ VLOG(4) << "dfgh";
+}
+
+} // namespace
diff --git a/absl/memory/BUILD.bazel b/absl/memory/BUILD.bazel
index d2824a0..4573f17 100644
--- a/absl/memory/BUILD.bazel
+++ b/absl/memory/BUILD.bazel
@@ -14,7 +14,6 @@
# limitations under the License.
#
-load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test")
load(
"//absl:copts/configure_copts.bzl",
"ABSL_DEFAULT_COPTS",
@@ -22,7 +21,14 @@
"ABSL_TEST_COPTS",
)
-package(default_visibility = ["//visibility:public"])
+package(
+ default_visibility = ["//visibility:public"],
+ features = [
+ "header_modules",
+ "layering_check",
+ "parse_headers",
+ ],
+)
licenses(["notice"])
@@ -30,6 +36,9 @@
name = "memory",
hdrs = ["memory.h"],
copts = ABSL_DEFAULT_COPTS,
+ defines = select({
+ "//conditions:default": [],
+ }),
linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
"//absl/base:core_headers",
@@ -45,21 +54,7 @@
deps = [
":memory",
"//absl/base:core_headers",
- "@com_google_googletest//:gtest_main",
- ],
-)
-
-cc_test(
- name = "memory_exception_safety_test",
- srcs = [
- "memory_exception_safety_test.cc",
- ],
- copts = ABSL_TEST_COPTS,
- linkopts = ABSL_DEFAULT_LINKOPTS,
- deps = [
- ":memory",
- "//absl/base:config",
- "//absl/base:exception_safety_testing",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
diff --git a/absl/memory/CMakeLists.txt b/absl/memory/CMakeLists.txt
index 9d50e1d..c5ed4b4 100644
--- a/absl/memory/CMakeLists.txt
+++ b/absl/memory/CMakeLists.txt
@@ -39,17 +39,3 @@
absl::core_headers
GTest::gmock_main
)
-
-absl_cc_test(
- NAME
- memory_exception_safety_test
- SRCS
- "memory_exception_safety_test.cc"
- COPTS
- ${ABSL_TEST_COPTS}
- DEPS
- absl::memory
- absl::config
- absl::exception_safety_testing
- GTest::gmock_main
-)
diff --git a/absl/memory/memory.h b/absl/memory/memory.h
index d633260..3508135 100644
--- a/absl/memory/memory.h
+++ b/absl/memory/memory.h
@@ -75,32 +75,6 @@
return std::unique_ptr<T>(ptr);
}
-namespace memory_internal {
-
-// Traits to select proper overload and return type for `absl::make_unique<>`.
-template <typename T>
-struct MakeUniqueResult {
- using scalar = std::unique_ptr<T>;
-};
-template <typename T>
-struct MakeUniqueResult<T[]> {
- using array = std::unique_ptr<T[]>;
-};
-template <typename T, size_t N>
-struct MakeUniqueResult<T[N]> {
- using invalid = void;
-};
-
-} // namespace memory_internal
-
-// gcc 4.8 has __cplusplus at 201301 but the libstdc++ shipped with it doesn't
-// define make_unique. Other supported compilers either just define __cplusplus
-// as 201103 but have make_unique (msvc), or have make_unique whenever
-// __cplusplus > 201103 (clang).
-#if (__cplusplus > 201103L || defined(_MSC_VER)) && \
- !(defined(__GLIBCXX__) && !defined(__cpp_lib_make_unique))
-using std::make_unique;
-#else
// -----------------------------------------------------------------------------
// Function Template: make_unique<T>()
// -----------------------------------------------------------------------------
@@ -109,82 +83,18 @@
// during the construction process. `absl::make_unique<>` also avoids redundant
// type declarations, by avoiding the need to explicitly use the `new` operator.
//
-// This implementation of `absl::make_unique<>` is designed for C++11 code and
-// will be replaced in C++14 by the equivalent `std::make_unique<>` abstraction.
-// `absl::make_unique<>` is designed to be 100% compatible with
-// `std::make_unique<>` so that the eventual migration will involve a simple
-// rename operation.
+// https://en.cppreference.com/w/cpp/memory/unique_ptr/make_unique
//
// For more background on why `std::unique_ptr<T>(new T(a,b))` is problematic,
// see Herb Sutter's explanation on
// (Exception-Safe Function Calls)[https://herbsutter.com/gotw/_102/].
// (In general, reviewers should treat `new T(a,b)` with scrutiny.)
//
-// Example usage:
-//
-// auto p = make_unique<X>(args...); // 'p' is a std::unique_ptr<X>
-// auto pa = make_unique<X[]>(5); // 'pa' is a std::unique_ptr<X[]>
-//
-// Three overloads of `absl::make_unique` are required:
-//
-// - For non-array T:
-//
-// Allocates a T with `new T(std::forward<Args> args...)`,
-// forwarding all `args` to T's constructor.
-// Returns a `std::unique_ptr<T>` owning that object.
-//
-// - For an array of unknown bounds T[]:
-//
-// `absl::make_unique<>` will allocate an array T of type U[] with
-// `new U[n]()` and return a `std::unique_ptr<U[]>` owning that array.
-//
-// Note that 'U[n]()' is different from 'U[n]', and elements will be
-// value-initialized. Note as well that `std::unique_ptr` will perform its
-// own destruction of the array elements upon leaving scope, even though
-// the array [] does not have a default destructor.
-//
-// NOTE: an array of unknown bounds T[] may still be (and often will be)
-// initialized to have a size, and will still use this overload. E.g:
-//
-// auto my_array = absl::make_unique<int[]>(10);
-//
-// - For an array of known bounds T[N]:
-//
-// `absl::make_unique<>` is deleted (like with `std::make_unique<>`) as
-// this overload is not useful.
-//
-// NOTE: an array of known bounds T[N] is not considered a useful
-// construction, and may cause undefined behavior in templates. E.g:
-//
-// auto my_array = absl::make_unique<int[10]>();
-//
-// In those cases, of course, you can still use the overload above and
-// simply initialize it to its desired size:
-//
-// auto my_array = absl::make_unique<int[]>(10);
-
-// `absl::make_unique` overload for non-array types.
-template <typename T, typename... Args>
-typename memory_internal::MakeUniqueResult<T>::scalar make_unique(
- Args&&... args) {
- return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
-}
-
-// `absl::make_unique` overload for an array T[] of unknown bounds.
-// The array allocation needs to use the `new T[size]` form and cannot take
-// element constructor arguments. The `std::unique_ptr` will manage destructing
-// these array elements.
-template <typename T>
-typename memory_internal::MakeUniqueResult<T>::array make_unique(size_t n) {
- return std::unique_ptr<T>(new typename absl::remove_extent_t<T>[n]());
-}
-
-// `absl::make_unique` overload for an array T[N] of known bounds.
-// This construction will be rejected.
-template <typename T, typename... Args>
-typename memory_internal::MakeUniqueResult<T>::invalid make_unique(
- Args&&... /* args */) = delete;
-#endif
+// Historical note: Abseil once provided a C++11 compatible implementation of
+// the C++14's `std::make_unique`. Now that C++11 support has been sunsetted,
+// `absl::make_unique` simply uses the STL-provided implementation. New code
+// should use `std::make_unique`.
+using std::make_unique;
// -----------------------------------------------------------------------------
// Function Template: RawPtr()
@@ -248,6 +158,26 @@
return std::weak_ptr<T>(ptr);
}
+// -----------------------------------------------------------------------------
+// Class Template: pointer_traits
+// -----------------------------------------------------------------------------
+//
+// Historical note: Abseil once provided an implementation of
+// `std::pointer_traits` for platforms that had not yet provided it. Those
+// platforms are no longer supported. New code should simply use
+// `std::pointer_traits`.
+using std::pointer_traits;
+
+// -----------------------------------------------------------------------------
+// Class Template: allocator_traits
+// -----------------------------------------------------------------------------
+//
+// Historical note: Abseil once provided an implementation of
+// `std::allocator_traits` for platforms that had not yet provided it. Those
+// platforms are no longer supported. New code should simply use
+// `std::allocator_traits`.
+using std::allocator_traits;
+
namespace memory_internal {
// ExtractOr<E, O, D>::type evaluates to E<O> if possible. Otherwise, D.
@@ -265,357 +195,6 @@
template <template <typename> class Extract, typename Obj, typename Default>
using ExtractOrT = typename ExtractOr<Extract, Obj, Default, void>::type;
-// Extractors for the features of allocators.
-template <typename T>
-using GetPointer = typename T::pointer;
-
-template <typename T>
-using GetConstPointer = typename T::const_pointer;
-
-template <typename T>
-using GetVoidPointer = typename T::void_pointer;
-
-template <typename T>
-using GetConstVoidPointer = typename T::const_void_pointer;
-
-template <typename T>
-using GetDifferenceType = typename T::difference_type;
-
-template <typename T>
-using GetSizeType = typename T::size_type;
-
-template <typename T>
-using GetPropagateOnContainerCopyAssignment =
- typename T::propagate_on_container_copy_assignment;
-
-template <typename T>
-using GetPropagateOnContainerMoveAssignment =
- typename T::propagate_on_container_move_assignment;
-
-template <typename T>
-using GetPropagateOnContainerSwap = typename T::propagate_on_container_swap;
-
-template <typename T>
-using GetIsAlwaysEqual = typename T::is_always_equal;
-
-template <typename T>
-struct GetFirstArg;
-
-template <template <typename...> class Class, typename T, typename... Args>
-struct GetFirstArg<Class<T, Args...>> {
- using type = T;
-};
-
-template <typename Ptr, typename = void>
-struct ElementType {
- using type = typename GetFirstArg<Ptr>::type;
-};
-
-template <typename T>
-struct ElementType<T, void_t<typename T::element_type>> {
- using type = typename T::element_type;
-};
-
-template <typename T, typename U>
-struct RebindFirstArg;
-
-template <template <typename...> class Class, typename T, typename... Args,
- typename U>
-struct RebindFirstArg<Class<T, Args...>, U> {
- using type = Class<U, Args...>;
-};
-
-template <typename T, typename U, typename = void>
-struct RebindPtr {
- using type = typename RebindFirstArg<T, U>::type;
-};
-
-template <typename T, typename U>
-struct RebindPtr<T, U, void_t<typename T::template rebind<U>>> {
- using type = typename T::template rebind<U>;
-};
-
-template <typename T, typename U>
-constexpr bool HasRebindAlloc(...) {
- return false;
-}
-
-template <typename T, typename U>
-constexpr bool HasRebindAlloc(typename T::template rebind<U>::other*) {
- return true;
-}
-
-template <typename T, typename U, bool = HasRebindAlloc<T, U>(nullptr)>
-struct RebindAlloc {
- using type = typename RebindFirstArg<T, U>::type;
-};
-
-template <typename T, typename U>
-struct RebindAlloc<T, U, true> {
- using type = typename T::template rebind<U>::other;
-};
-
-} // namespace memory_internal
-
-// -----------------------------------------------------------------------------
-// Class Template: pointer_traits
-// -----------------------------------------------------------------------------
-//
-// An implementation of C++11's std::pointer_traits.
-//
-// Provided for portability on toolchains that have a working C++11 compiler,
-// but the standard library is lacking in C++11 support. For example, some
-// version of the Android NDK.
-//
-
-template <typename Ptr>
-struct pointer_traits {
- using pointer = Ptr;
-
- // element_type:
- // Ptr::element_type if present. Otherwise T if Ptr is a template
- // instantiation Template<T, Args...>
- using element_type = typename memory_internal::ElementType<Ptr>::type;
-
- // difference_type:
- // Ptr::difference_type if present, otherwise std::ptrdiff_t
- using difference_type =
- memory_internal::ExtractOrT<memory_internal::GetDifferenceType, Ptr,
- std::ptrdiff_t>;
-
- // rebind:
- // Ptr::rebind<U> if exists, otherwise Template<U, Args...> if Ptr is a
- // template instantiation Template<T, Args...>
- template <typename U>
- using rebind = typename memory_internal::RebindPtr<Ptr, U>::type;
-
- // pointer_to:
- // Calls Ptr::pointer_to(r)
- static pointer pointer_to(element_type& r) { // NOLINT(runtime/references)
- return Ptr::pointer_to(r);
- }
-};
-
-// Specialization for T*.
-template <typename T>
-struct pointer_traits<T*> {
- using pointer = T*;
- using element_type = T;
- using difference_type = std::ptrdiff_t;
-
- template <typename U>
- using rebind = U*;
-
- // pointer_to:
- // Calls std::addressof(r)
- static pointer pointer_to(
- element_type& r) noexcept { // NOLINT(runtime/references)
- return std::addressof(r);
- }
-};
-
-// -----------------------------------------------------------------------------
-// Class Template: allocator_traits
-// -----------------------------------------------------------------------------
-//
-// A C++11 compatible implementation of C++17's std::allocator_traits.
-//
-#if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)
-using std::allocator_traits;
-#else // __cplusplus >= 201703L
-template <typename Alloc>
-struct allocator_traits {
- using allocator_type = Alloc;
-
- // value_type:
- // Alloc::value_type
- using value_type = typename Alloc::value_type;
-
- // pointer:
- // Alloc::pointer if present, otherwise value_type*
- using pointer = memory_internal::ExtractOrT<memory_internal::GetPointer,
- Alloc, value_type*>;
-
- // const_pointer:
- // Alloc::const_pointer if present, otherwise
- // absl::pointer_traits<pointer>::rebind<const value_type>
- using const_pointer =
- memory_internal::ExtractOrT<memory_internal::GetConstPointer, Alloc,
- typename absl::pointer_traits<pointer>::
- template rebind<const value_type>>;
-
- // void_pointer:
- // Alloc::void_pointer if present, otherwise
- // absl::pointer_traits<pointer>::rebind<void>
- using void_pointer = memory_internal::ExtractOrT<
- memory_internal::GetVoidPointer, Alloc,
- typename absl::pointer_traits<pointer>::template rebind<void>>;
-
- // const_void_pointer:
- // Alloc::const_void_pointer if present, otherwise
- // absl::pointer_traits<pointer>::rebind<const void>
- using const_void_pointer = memory_internal::ExtractOrT<
- memory_internal::GetConstVoidPointer, Alloc,
- typename absl::pointer_traits<pointer>::template rebind<const void>>;
-
- // difference_type:
- // Alloc::difference_type if present, otherwise
- // absl::pointer_traits<pointer>::difference_type
- using difference_type = memory_internal::ExtractOrT<
- memory_internal::GetDifferenceType, Alloc,
- typename absl::pointer_traits<pointer>::difference_type>;
-
- // size_type:
- // Alloc::size_type if present, otherwise
- // std::make_unsigned<difference_type>::type
- using size_type = memory_internal::ExtractOrT<
- memory_internal::GetSizeType, Alloc,
- typename std::make_unsigned<difference_type>::type>;
-
- // propagate_on_container_copy_assignment:
- // Alloc::propagate_on_container_copy_assignment if present, otherwise
- // std::false_type
- using propagate_on_container_copy_assignment = memory_internal::ExtractOrT<
- memory_internal::GetPropagateOnContainerCopyAssignment, Alloc,
- std::false_type>;
-
- // propagate_on_container_move_assignment:
- // Alloc::propagate_on_container_move_assignment if present, otherwise
- // std::false_type
- using propagate_on_container_move_assignment = memory_internal::ExtractOrT<
- memory_internal::GetPropagateOnContainerMoveAssignment, Alloc,
- std::false_type>;
-
- // propagate_on_container_swap:
- // Alloc::propagate_on_container_swap if present, otherwise std::false_type
- using propagate_on_container_swap =
- memory_internal::ExtractOrT<memory_internal::GetPropagateOnContainerSwap,
- Alloc, std::false_type>;
-
- // is_always_equal:
- // Alloc::is_always_equal if present, otherwise std::is_empty<Alloc>::type
- using is_always_equal =
- memory_internal::ExtractOrT<memory_internal::GetIsAlwaysEqual, Alloc,
- typename std::is_empty<Alloc>::type>;
-
- // rebind_alloc:
- // Alloc::rebind<T>::other if present, otherwise Alloc<T, Args> if this Alloc
- // is Alloc<U, Args>
- template <typename T>
- using rebind_alloc = typename memory_internal::RebindAlloc<Alloc, T>::type;
-
- // rebind_traits:
- // absl::allocator_traits<rebind_alloc<T>>
- template <typename T>
- using rebind_traits = absl::allocator_traits<rebind_alloc<T>>;
-
- // allocate(Alloc& a, size_type n):
- // Calls a.allocate(n)
- static pointer allocate(Alloc& a, // NOLINT(runtime/references)
- size_type n) {
- return a.allocate(n);
- }
-
- // allocate(Alloc& a, size_type n, const_void_pointer hint):
- // Calls a.allocate(n, hint) if possible.
- // If not possible, calls a.allocate(n)
- static pointer allocate(Alloc& a, size_type n, // NOLINT(runtime/references)
- const_void_pointer hint) {
- return allocate_impl(0, a, n, hint);
- }
-
- // deallocate(Alloc& a, pointer p, size_type n):
- // Calls a.deallocate(p, n)
- static void deallocate(Alloc& a, pointer p, // NOLINT(runtime/references)
- size_type n) {
- a.deallocate(p, n);
- }
-
- // construct(Alloc& a, T* p, Args&&... args):
- // Calls a.construct(p, std::forward<Args>(args)...) if possible.
- // If not possible, calls
- // ::new (static_cast<void*>(p)) T(std::forward<Args>(args)...)
- template <typename T, typename... Args>
- static void construct(Alloc& a, T* p, // NOLINT(runtime/references)
- Args&&... args) {
- construct_impl(0, a, p, std::forward<Args>(args)...);
- }
-
- // destroy(Alloc& a, T* p):
- // Calls a.destroy(p) if possible. If not possible, calls p->~T().
- template <typename T>
- static void destroy(Alloc& a, T* p) { // NOLINT(runtime/references)
- destroy_impl(0, a, p);
- }
-
- // max_size(const Alloc& a):
- // Returns a.max_size() if possible. If not possible, returns
- // std::numeric_limits<size_type>::max() / sizeof(value_type)
- static size_type max_size(const Alloc& a) { return max_size_impl(0, a); }
-
- // select_on_container_copy_construction(const Alloc& a):
- // Returns a.select_on_container_copy_construction() if possible.
- // If not possible, returns a.
- static Alloc select_on_container_copy_construction(const Alloc& a) {
- return select_on_container_copy_construction_impl(0, a);
- }
-
- private:
- template <typename A>
- static auto allocate_impl(int, A& a, // NOLINT(runtime/references)
- size_type n, const_void_pointer hint)
- -> decltype(a.allocate(n, hint)) {
- return a.allocate(n, hint);
- }
- static pointer allocate_impl(char, Alloc& a, // NOLINT(runtime/references)
- size_type n, const_void_pointer) {
- return a.allocate(n);
- }
-
- template <typename A, typename... Args>
- static auto construct_impl(int, A& a, // NOLINT(runtime/references)
- Args&&... args)
- -> decltype(a.construct(std::forward<Args>(args)...)) {
- a.construct(std::forward<Args>(args)...);
- }
-
- template <typename T, typename... Args>
- static void construct_impl(char, Alloc&, T* p, Args&&... args) {
- ::new (static_cast<void*>(p)) T(std::forward<Args>(args)...);
- }
-
- template <typename A, typename T>
- static auto destroy_impl(int, A& a, // NOLINT(runtime/references)
- T* p) -> decltype(a.destroy(p)) {
- a.destroy(p);
- }
- template <typename T>
- static void destroy_impl(char, Alloc&, T* p) {
- p->~T();
- }
-
- template <typename A>
- static auto max_size_impl(int, const A& a) -> decltype(a.max_size()) {
- return a.max_size();
- }
- static size_type max_size_impl(char, const Alloc&) {
- return (std::numeric_limits<size_type>::max)() / sizeof(value_type);
- }
-
- template <typename A>
- static auto select_on_container_copy_construction_impl(int, const A& a)
- -> decltype(a.select_on_container_copy_construction()) {
- return a.select_on_container_copy_construction();
- }
- static Alloc select_on_container_copy_construction_impl(char,
- const Alloc& a) {
- return a;
- }
-};
-#endif // __cplusplus >= 201703L
-
-namespace memory_internal {
-
// This template alias transforms Alloc::is_nothrow into a metafunction with
// Alloc as a parameter so it can be used with ExtractOrT<>.
template <typename Alloc>
diff --git a/absl/memory/memory_exception_safety_test.cc b/absl/memory/memory_exception_safety_test.cc
deleted file mode 100644
index 1df7261..0000000
--- a/absl/memory/memory_exception_safety_test.cc
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright 2018 The Abseil Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "absl/memory/memory.h"
-
-#include "absl/base/config.h"
-
-#ifdef ABSL_HAVE_EXCEPTIONS
-
-#include "gtest/gtest.h"
-#include "absl/base/internal/exception_safety_testing.h"
-
-namespace absl {
-ABSL_NAMESPACE_BEGIN
-namespace {
-
-constexpr int kLength = 50;
-using Thrower = testing::ThrowingValue<testing::TypeSpec::kEverythingThrows>;
-
-TEST(MakeUnique, CheckForLeaks) {
- constexpr int kValue = 321;
- auto tester = testing::MakeExceptionSafetyTester()
- .WithInitialValue(Thrower(kValue))
- // Ensures make_unique does not modify the input. The real
- // test, though, is ConstructorTracker checking for leaks.
- .WithContracts(testing::strong_guarantee);
-
- EXPECT_TRUE(tester.Test([](Thrower* thrower) {
- static_cast<void>(absl::make_unique<Thrower>(*thrower));
- }));
-
- EXPECT_TRUE(tester.Test([](Thrower* thrower) {
- static_cast<void>(absl::make_unique<Thrower>(std::move(*thrower)));
- }));
-
- // Test T[n] overload
- EXPECT_TRUE(tester.Test([&](Thrower*) {
- static_cast<void>(absl::make_unique<Thrower[]>(kLength));
- }));
-}
-
-} // namespace
-ABSL_NAMESPACE_END
-} // namespace absl
-
-#endif // ABSL_HAVE_EXCEPTIONS
diff --git a/absl/memory/memory_test.cc b/absl/memory/memory_test.cc
index 1990c7b..fafd3a4 100644
--- a/absl/memory/memory_test.cc
+++ b/absl/memory/memory_test.cc
@@ -63,12 +63,6 @@
}
EXPECT_EQ(0, DestructorVerifier::instance_count());
}
-TEST(MakeUniqueTest, Basic) {
- std::unique_ptr<std::string> p = absl::make_unique<std::string>();
- EXPECT_EQ("", *p);
- p = absl::make_unique<std::string>("hi");
- EXPECT_EQ("hi", *p);
-}
// InitializationVerifier fills in a pattern when allocated so we can
// distinguish between its default and value initialized states (without
@@ -93,65 +87,6 @@
int b;
};
-TEST(Initialization, MakeUnique) {
- auto p = absl::make_unique<InitializationVerifier>();
-
- EXPECT_EQ(0, p->a);
- EXPECT_EQ(0, p->b);
-}
-
-TEST(Initialization, MakeUniqueArray) {
- auto p = absl::make_unique<InitializationVerifier[]>(2);
-
- EXPECT_EQ(0, p[0].a);
- EXPECT_EQ(0, p[0].b);
- EXPECT_EQ(0, p[1].a);
- EXPECT_EQ(0, p[1].b);
-}
-
-struct MoveOnly {
- MoveOnly() = default;
- explicit MoveOnly(int i1) : ip1{new int{i1}} {}
- MoveOnly(int i1, int i2) : ip1{new int{i1}}, ip2{new int{i2}} {}
- std::unique_ptr<int> ip1;
- std::unique_ptr<int> ip2;
-};
-
-struct AcceptMoveOnly {
- explicit AcceptMoveOnly(MoveOnly m) : m_(std::move(m)) {}
- MoveOnly m_;
-};
-
-TEST(MakeUniqueTest, MoveOnlyTypeAndValue) {
- using ExpectedType = std::unique_ptr<MoveOnly>;
- {
- auto p = absl::make_unique<MoveOnly>();
- static_assert(std::is_same<decltype(p), ExpectedType>::value,
- "unexpected return type");
- EXPECT_TRUE(!p->ip1);
- EXPECT_TRUE(!p->ip2);
- }
- {
- auto p = absl::make_unique<MoveOnly>(1);
- static_assert(std::is_same<decltype(p), ExpectedType>::value,
- "unexpected return type");
- EXPECT_TRUE(p->ip1 && *p->ip1 == 1);
- EXPECT_TRUE(!p->ip2);
- }
- {
- auto p = absl::make_unique<MoveOnly>(1, 2);
- static_assert(std::is_same<decltype(p), ExpectedType>::value,
- "unexpected return type");
- EXPECT_TRUE(p->ip1 && *p->ip1 == 1);
- EXPECT_TRUE(p->ip2 && *p->ip2 == 2);
- }
-}
-
-TEST(MakeUniqueTest, AcceptMoveOnly) {
- auto p = absl::make_unique<AcceptMoveOnly>(MoveOnly());
- p = std::unique_ptr<AcceptMoveOnly>(new AcceptMoveOnly(MoveOnly()));
-}
-
struct ArrayWatch {
void* operator new[](size_t n) {
allocs().push_back(n);
@@ -164,38 +99,6 @@
}
};
-TEST(Make_UniqueTest, Array) {
- // Ensure state is clean before we start so that these tests
- // are order-agnostic.
- ArrayWatch::allocs().clear();
-
- auto p = absl::make_unique<ArrayWatch[]>(5);
- static_assert(std::is_same<decltype(p), std::unique_ptr<ArrayWatch[]>>::value,
- "unexpected return type");
- EXPECT_THAT(ArrayWatch::allocs(), ElementsAre(5 * sizeof(ArrayWatch)));
-}
-
-TEST(Make_UniqueTest, NotAmbiguousWithStdMakeUnique) {
- // Ensure that absl::make_unique is not ambiguous with std::make_unique.
- // In C++14 mode, the below call to make_unique has both types as candidates.
- struct TakesStdType {
- explicit TakesStdType(const std::vector<int>& vec) {}
- };
- using absl::make_unique;
- (void)make_unique<TakesStdType>(std::vector<int>());
-}
-
-#if 0
-// These tests shouldn't compile.
-TEST(MakeUniqueTestNC, AcceptMoveOnlyLvalue) {
- auto m = MoveOnly();
- auto p = absl::make_unique<AcceptMoveOnly>(m);
-}
-TEST(MakeUniqueTestNC, KnownBoundArray) {
- auto p = absl::make_unique<ArrayWatch[5]>();
-}
-#endif
-
TEST(RawPtrTest, RawPointer) {
int i = 5;
EXPECT_EQ(&i, absl::RawPtr(&i));
@@ -287,337 +190,6 @@
}
*/
-template <typename T>
-struct SmartPointer {
- using difference_type = char;
-};
-
-struct PointerWith {
- using element_type = int32_t;
- using difference_type = int16_t;
- template <typename U>
- using rebind = SmartPointer<U>;
-
- static PointerWith pointer_to(
- element_type& r) { // NOLINT(runtime/references)
- return PointerWith{&r};
- }
-
- element_type* ptr;
-};
-
-template <typename... Args>
-struct PointerWithout {};
-
-TEST(PointerTraits, Types) {
- using TraitsWith = absl::pointer_traits<PointerWith>;
- EXPECT_TRUE((std::is_same<TraitsWith::pointer, PointerWith>::value));
- EXPECT_TRUE((std::is_same<TraitsWith::element_type, int32_t>::value));
- EXPECT_TRUE((std::is_same<TraitsWith::difference_type, int16_t>::value));
- EXPECT_TRUE((
- std::is_same<TraitsWith::rebind<int64_t>, SmartPointer<int64_t>>::value));
-
- using TraitsWithout = absl::pointer_traits<PointerWithout<double, int>>;
- EXPECT_TRUE((std::is_same<TraitsWithout::pointer,
- PointerWithout<double, int>>::value));
- EXPECT_TRUE((std::is_same<TraitsWithout::element_type, double>::value));
- EXPECT_TRUE(
- (std::is_same<TraitsWithout ::difference_type, std::ptrdiff_t>::value));
- EXPECT_TRUE((std::is_same<TraitsWithout::rebind<int64_t>,
- PointerWithout<int64_t, int>>::value));
-
- using TraitsRawPtr = absl::pointer_traits<char*>;
- EXPECT_TRUE((std::is_same<TraitsRawPtr::pointer, char*>::value));
- EXPECT_TRUE((std::is_same<TraitsRawPtr::element_type, char>::value));
- EXPECT_TRUE(
- (std::is_same<TraitsRawPtr::difference_type, std::ptrdiff_t>::value));
- EXPECT_TRUE((std::is_same<TraitsRawPtr::rebind<int64_t>, int64_t*>::value));
-}
-
-TEST(PointerTraits, Functions) {
- int i;
- EXPECT_EQ(&i, absl::pointer_traits<PointerWith>::pointer_to(i).ptr);
- EXPECT_EQ(&i, absl::pointer_traits<int*>::pointer_to(i));
-}
-
-TEST(AllocatorTraits, Typedefs) {
- struct A {
- struct value_type {};
- };
- EXPECT_TRUE((
- std::is_same<A,
- typename absl::allocator_traits<A>::allocator_type>::value));
- EXPECT_TRUE(
- (std::is_same<A::value_type,
- typename absl::allocator_traits<A>::value_type>::value));
-
- struct X {};
- struct HasPointer {
- using value_type = X;
- using pointer = SmartPointer<X>;
- };
- EXPECT_TRUE((std::is_same<SmartPointer<X>, typename absl::allocator_traits<
- HasPointer>::pointer>::value));
- EXPECT_TRUE(
- (std::is_same<A::value_type*,
- typename absl::allocator_traits<A>::pointer>::value));
-
- EXPECT_TRUE(
- (std::is_same<
- SmartPointer<const X>,
- typename absl::allocator_traits<HasPointer>::const_pointer>::value));
- EXPECT_TRUE(
- (std::is_same<const A::value_type*,
- typename absl::allocator_traits<A>::const_pointer>::value));
-
- struct HasVoidPointer {
- using value_type = X;
- struct void_pointer {};
- };
-
- EXPECT_TRUE((std::is_same<HasVoidPointer::void_pointer,
- typename absl::allocator_traits<
- HasVoidPointer>::void_pointer>::value));
- EXPECT_TRUE(
- (std::is_same<SmartPointer<void>, typename absl::allocator_traits<
- HasPointer>::void_pointer>::value));
-
- struct HasConstVoidPointer {
- using value_type = X;
- struct const_void_pointer {};
- };
-
- EXPECT_TRUE(
- (std::is_same<HasConstVoidPointer::const_void_pointer,
- typename absl::allocator_traits<
- HasConstVoidPointer>::const_void_pointer>::value));
- EXPECT_TRUE((std::is_same<SmartPointer<const void>,
- typename absl::allocator_traits<
- HasPointer>::const_void_pointer>::value));
-
- struct HasDifferenceType {
- using value_type = X;
- using difference_type = int;
- };
- EXPECT_TRUE(
- (std::is_same<int, typename absl::allocator_traits<
- HasDifferenceType>::difference_type>::value));
- EXPECT_TRUE((std::is_same<char, typename absl::allocator_traits<
- HasPointer>::difference_type>::value));
-
- struct HasSizeType {
- using value_type = X;
- using size_type = unsigned int;
- };
- EXPECT_TRUE((std::is_same<unsigned int, typename absl::allocator_traits<
- HasSizeType>::size_type>::value));
- EXPECT_TRUE((std::is_same<unsigned char, typename absl::allocator_traits<
- HasPointer>::size_type>::value));
-
- struct HasPropagateOnCopy {
- using value_type = X;
- struct propagate_on_container_copy_assignment {};
- };
-
- EXPECT_TRUE(
- (std::is_same<HasPropagateOnCopy::propagate_on_container_copy_assignment,
- typename absl::allocator_traits<HasPropagateOnCopy>::
- propagate_on_container_copy_assignment>::value));
- EXPECT_TRUE(
- (std::is_same<std::false_type,
- typename absl::allocator_traits<
- A>::propagate_on_container_copy_assignment>::value));
-
- struct HasPropagateOnMove {
- using value_type = X;
- struct propagate_on_container_move_assignment {};
- };
-
- EXPECT_TRUE(
- (std::is_same<HasPropagateOnMove::propagate_on_container_move_assignment,
- typename absl::allocator_traits<HasPropagateOnMove>::
- propagate_on_container_move_assignment>::value));
- EXPECT_TRUE(
- (std::is_same<std::false_type,
- typename absl::allocator_traits<
- A>::propagate_on_container_move_assignment>::value));
-
- struct HasPropagateOnSwap {
- using value_type = X;
- struct propagate_on_container_swap {};
- };
-
- EXPECT_TRUE(
- (std::is_same<HasPropagateOnSwap::propagate_on_container_swap,
- typename absl::allocator_traits<HasPropagateOnSwap>::
- propagate_on_container_swap>::value));
- EXPECT_TRUE(
- (std::is_same<std::false_type, typename absl::allocator_traits<A>::
- propagate_on_container_swap>::value));
-
- struct HasIsAlwaysEqual {
- using value_type = X;
- struct is_always_equal {};
- };
-
- EXPECT_TRUE((std::is_same<HasIsAlwaysEqual::is_always_equal,
- typename absl::allocator_traits<
- HasIsAlwaysEqual>::is_always_equal>::value));
- EXPECT_TRUE((std::is_same<std::true_type, typename absl::allocator_traits<
- A>::is_always_equal>::value));
- struct NonEmpty {
- using value_type = X;
- int i;
- };
- EXPECT_TRUE(
- (std::is_same<std::false_type,
- absl::allocator_traits<NonEmpty>::is_always_equal>::value));
-}
-
-template <typename T>
-struct AllocWithPrivateInheritance : private std::allocator<T> {
- using value_type = T;
-};
-
-TEST(AllocatorTraits, RebindWithPrivateInheritance) {
- // Regression test for some versions of gcc that do not like the sfinae we
- // used in combination with private inheritance.
- EXPECT_TRUE(
- (std::is_same<AllocWithPrivateInheritance<int>,
- absl::allocator_traits<AllocWithPrivateInheritance<char>>::
- rebind_alloc<int>>::value));
-}
-
-template <typename T>
-struct Rebound {};
-
-struct AllocWithRebind {
- using value_type = int;
- template <typename T>
- struct rebind {
- using other = Rebound<T>;
- };
-};
-
-template <typename T, typename U>
-struct AllocWithoutRebind {
- using value_type = int;
-};
-
-TEST(AllocatorTraits, Rebind) {
- EXPECT_TRUE(
- (std::is_same<Rebound<int>,
- typename absl::allocator_traits<
- AllocWithRebind>::template rebind_alloc<int>>::value));
- EXPECT_TRUE(
- (std::is_same<absl::allocator_traits<Rebound<int>>,
- typename absl::allocator_traits<
- AllocWithRebind>::template rebind_traits<int>>::value));
-
- EXPECT_TRUE(
- (std::is_same<AllocWithoutRebind<double, char>,
- typename absl::allocator_traits<AllocWithoutRebind<
- int, char>>::template rebind_alloc<double>>::value));
- EXPECT_TRUE(
- (std::is_same<absl::allocator_traits<AllocWithoutRebind<double, char>>,
- typename absl::allocator_traits<AllocWithoutRebind<
- int, char>>::template rebind_traits<double>>::value));
-}
-
-struct TestValue {
- TestValue() {}
- explicit TestValue(int* trace) : trace(trace) { ++*trace; }
- ~TestValue() {
- if (trace) --*trace;
- }
- int* trace = nullptr;
-};
-
-struct MinimalMockAllocator {
- MinimalMockAllocator() : value(0) {}
- explicit MinimalMockAllocator(int value) : value(value) {}
- MinimalMockAllocator(const MinimalMockAllocator& other)
- : value(other.value) {}
- using value_type = TestValue;
- MOCK_METHOD(value_type*, allocate, (size_t));
- MOCK_METHOD(void, deallocate, (value_type*, size_t));
-
- int value;
-};
-
-TEST(AllocatorTraits, FunctionsMinimal) {
- int trace = 0;
- int hint;
- TestValue x(&trace);
- MinimalMockAllocator mock;
- using Traits = absl::allocator_traits<MinimalMockAllocator>;
- EXPECT_CALL(mock, allocate(7)).WillRepeatedly(Return(&x));
- EXPECT_CALL(mock, deallocate(&x, 7));
-
- EXPECT_EQ(&x, Traits::allocate(mock, 7));
- static_cast<void>(Traits::allocate(mock, 7, static_cast<const void*>(&hint)));
- EXPECT_EQ(&x, Traits::allocate(mock, 7, static_cast<const void*>(&hint)));
- Traits::deallocate(mock, &x, 7);
-
- EXPECT_EQ(1, trace);
- Traits::construct(mock, &x, &trace);
- EXPECT_EQ(2, trace);
- Traits::destroy(mock, &x);
- EXPECT_EQ(1, trace);
-
- EXPECT_EQ(std::numeric_limits<size_t>::max() / sizeof(TestValue),
- Traits::max_size(mock));
-
- EXPECT_EQ(0, mock.value);
- EXPECT_EQ(0, Traits::select_on_container_copy_construction(mock).value);
-}
-
-struct FullMockAllocator {
- FullMockAllocator() : value(0) {}
- explicit FullMockAllocator(int value) : value(value) {}
- FullMockAllocator(const FullMockAllocator& other) : value(other.value) {}
- using value_type = TestValue;
- MOCK_METHOD(value_type*, allocate, (size_t));
- MOCK_METHOD(value_type*, allocate, (size_t, const void*));
- MOCK_METHOD(void, construct, (value_type*, int*));
- MOCK_METHOD(void, destroy, (value_type*));
- MOCK_METHOD(size_t, max_size, (),
- (const));
- MOCK_METHOD(FullMockAllocator, select_on_container_copy_construction, (),
- (const));
-
- int value;
-};
-
-TEST(AllocatorTraits, FunctionsFull) {
- int trace = 0;
- int hint;
- TestValue x(&trace), y;
- FullMockAllocator mock;
- using Traits = absl::allocator_traits<FullMockAllocator>;
- EXPECT_CALL(mock, allocate(7)).WillRepeatedly(Return(&x));
- EXPECT_CALL(mock, allocate(13, &hint)).WillRepeatedly(Return(&y));
- EXPECT_CALL(mock, construct(&x, &trace));
- EXPECT_CALL(mock, destroy(&x));
- EXPECT_CALL(mock, max_size()).WillRepeatedly(Return(17));
- EXPECT_CALL(mock, select_on_container_copy_construction())
- .WillRepeatedly(Return(FullMockAllocator(23)));
-
- EXPECT_EQ(&x, Traits::allocate(mock, 7));
- EXPECT_EQ(&y, Traits::allocate(mock, 13, static_cast<const void*>(&hint)));
-
- EXPECT_EQ(1, trace);
- Traits::construct(mock, &x, &trace);
- EXPECT_EQ(1, trace);
- Traits::destroy(mock, &x);
- EXPECT_EQ(1, trace);
-
- EXPECT_EQ(17, Traits::max_size(mock));
-
- EXPECT_EQ(0, mock.value);
- EXPECT_EQ(23, Traits::select_on_container_copy_construction(mock).value);
-}
-
TEST(AllocatorNoThrowTest, DefaultAllocator) {
#if defined(ABSL_ALLOCATOR_NOTHROW) && ABSL_ALLOCATOR_NOTHROW
EXPECT_TRUE(absl::default_allocator_is_nothrow::value);
diff --git a/absl/meta/BUILD.bazel b/absl/meta/BUILD.bazel
index 5585fcc..cf5df9b 100644
--- a/absl/meta/BUILD.bazel
+++ b/absl/meta/BUILD.bazel
@@ -14,7 +14,6 @@
# limitations under the License.
#
-load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test")
load(
"//absl:copts/configure_copts.bzl",
"ABSL_DEFAULT_COPTS",
@@ -22,7 +21,14 @@
"ABSL_TEST_COPTS",
)
-package(default_visibility = ["//visibility:public"])
+package(
+ default_visibility = ["//visibility:public"],
+ features = [
+ "header_modules",
+ "layering_check",
+ "parse_headers",
+ ],
+)
licenses(["notice"])
@@ -33,6 +39,7 @@
linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
"//absl/base:config",
+ "//absl/base:core_headers",
],
)
@@ -43,6 +50,10 @@
linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
":type_traits",
+ "//absl/base:config",
+ "//absl/base:core_headers",
+ "//absl/time",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
diff --git a/absl/meta/CMakeLists.txt b/absl/meta/CMakeLists.txt
index 9de4bd3..d509114 100644
--- a/absl/meta/CMakeLists.txt
+++ b/absl/meta/CMakeLists.txt
@@ -23,6 +23,7 @@
${ABSL_DEFAULT_COPTS}
DEPS
absl::config
+ absl::core_headers
PUBLIC
)
@@ -34,6 +35,9 @@
COPTS
${ABSL_TEST_COPTS}
DEPS
+ absl::config
+ absl::time
+ absl::core_headers
absl::type_traits
GTest::gmock_main
)
diff --git a/absl/meta/type_traits.h b/absl/meta/type_traits.h
index e7c1239..ed5e608 100644
--- a/absl/meta/type_traits.h
+++ b/absl/meta/type_traits.h
@@ -35,89 +35,31 @@
#ifndef ABSL_META_TYPE_TRAITS_H_
#define ABSL_META_TYPE_TRAITS_H_
-#include <stddef.h>
+#include <cstddef>
#include <functional>
#include <type_traits>
+#include "absl/base/attributes.h"
#include "absl/base/config.h"
-// MSVC constructibility traits do not detect destructor properties and so our
-// implementations should not use them as a source-of-truth.
-#if defined(_MSC_VER) && !defined(__clang__) && !defined(__GNUC__)
-#define ABSL_META_INTERNAL_STD_CONSTRUCTION_TRAITS_DONT_CHECK_DESTRUCTION 1
-#endif
+// Defines the default alignment. `__STDCPP_DEFAULT_NEW_ALIGNMENT__` is a C++17
+// feature.
+#if defined(__STDCPP_DEFAULT_NEW_ALIGNMENT__)
+#define ABSL_INTERNAL_DEFAULT_NEW_ALIGNMENT __STDCPP_DEFAULT_NEW_ALIGNMENT__
+#else // defined(__STDCPP_DEFAULT_NEW_ALIGNMENT__)
+#define ABSL_INTERNAL_DEFAULT_NEW_ALIGNMENT alignof(std::max_align_t)
+#endif // defined(__STDCPP_DEFAULT_NEW_ALIGNMENT__)
namespace absl {
ABSL_NAMESPACE_BEGIN
-// Defined and documented later on in this file.
-template <typename T>
-struct is_trivially_destructible;
-
-// Defined and documented later on in this file.
-template <typename T>
-struct is_trivially_move_assignable;
-
namespace type_traits_internal {
-// Silence MSVC warnings about the destructor being defined as deleted.
-#if defined(_MSC_VER) && !defined(__GNUC__)
-#pragma warning(push)
-#pragma warning(disable : 4624)
-#endif // defined(_MSC_VER) && !defined(__GNUC__)
-
-template <class T>
-union SingleMemberUnion {
- T t;
-};
-
-// Restore the state of the destructor warning that was silenced above.
-#if defined(_MSC_VER) && !defined(__GNUC__)
-#pragma warning(pop)
-#endif // defined(_MSC_VER) && !defined(__GNUC__)
-
-template <class T>
-struct IsTriviallyMoveConstructibleObject
- : std::integral_constant<
- bool, std::is_move_constructible<
- type_traits_internal::SingleMemberUnion<T>>::value &&
- absl::is_trivially_destructible<T>::value> {};
-
-template <class T>
-struct IsTriviallyCopyConstructibleObject
- : std::integral_constant<
- bool, std::is_copy_constructible<
- type_traits_internal::SingleMemberUnion<T>>::value &&
- absl::is_trivially_destructible<T>::value> {};
-
-template <class T>
-struct IsTriviallyMoveAssignableReference : std::false_type {};
-
-template <class T>
-struct IsTriviallyMoveAssignableReference<T&>
- : absl::is_trivially_move_assignable<T>::type {};
-
-template <class T>
-struct IsTriviallyMoveAssignableReference<T&&>
- : absl::is_trivially_move_assignable<T>::type {};
-
template <typename... Ts>
struct VoidTImpl {
using type = void;
};
-// This trick to retrieve a default alignment is necessary for our
-// implementation of aligned_storage_t to be consistent with any implementation
-// of std::aligned_storage.
-template <size_t Len, typename T = std::aligned_storage<Len>>
-struct default_alignment_of_aligned_storage;
-
-template <size_t Len, size_t Align>
-struct default_alignment_of_aligned_storage<Len,
- std::aligned_storage<Len, Align>> {
- static constexpr size_t value = Align;
-};
-
////////////////////////////////
// Library Fundamentals V2 TS //
////////////////////////////////
@@ -161,39 +103,8 @@
struct is_detected_convertible
: is_detected_convertible_impl<void, To, Op, Args...>::type {};
-template <typename T>
-using IsCopyAssignableImpl =
- decltype(std::declval<T&>() = std::declval<const T&>());
-
-template <typename T>
-using IsMoveAssignableImpl = decltype(std::declval<T&>() = std::declval<T&&>());
-
} // namespace type_traits_internal
-// MSVC 19.20 has a regression that causes our workarounds to fail, but their
-// std forms now appear to be compliant.
-#if defined(_MSC_VER) && !defined(__clang__) && (_MSC_VER >= 1920)
-
-template <typename T>
-using is_copy_assignable = std::is_copy_assignable<T>;
-
-template <typename T>
-using is_move_assignable = std::is_move_assignable<T>;
-
-#else
-
-template <typename T>
-struct is_copy_assignable : type_traits_internal::is_detected<
- type_traits_internal::IsCopyAssignableImpl, T> {
-};
-
-template <typename T>
-struct is_move_assignable : type_traits_internal::is_detected<
- type_traits_internal::IsMoveAssignableImpl, T> {
-};
-
-#endif
-
// void_t()
//
// Ignores the type of any its arguments and returns `void`. In general, this
@@ -241,8 +152,8 @@
struct disjunction : std::false_type {};
template <typename T, typename... Ts>
-struct disjunction<T, Ts...> :
- std::conditional<T::value, T, disjunction<Ts...>>::type {};
+struct disjunction<T, Ts...>
+ : std::conditional<T::value, T, disjunction<Ts...>>::type {};
template <typename T>
struct disjunction<T> : T {};
@@ -274,230 +185,29 @@
bool, !(std::is_reference<T>::value ||
std::is_const<typename std::add_const<T>::type>::value)> {};
+// is_copy_assignable()
+// is_move_assignable()
// is_trivially_destructible()
-//
-// Determines whether the passed type `T` is trivially destructible.
-//
-// This metafunction is designed to be a drop-in replacement for the C++11
-// `std::is_trivially_destructible()` metafunction for platforms that have
-// incomplete C++11 support (such as libstdc++ 4.x). On any platforms that do
-// fully support C++11, we check whether this yields the same result as the std
-// implementation.
-//
-// NOTE: the extensions (__has_trivial_xxx) are implemented in gcc (version >=
-// 4.3) and clang. Since we are supporting libstdc++ > 4.7, they should always
-// be present. These extensions are documented at
-// https://gcc.gnu.org/onlinedocs/gcc/Type-Traits.html#Type-Traits.
-template <typename T>
-struct is_trivially_destructible
- : std::integral_constant<bool, __has_trivial_destructor(T) &&
- std::is_destructible<T>::value> {
-#ifdef ABSL_HAVE_STD_IS_TRIVIALLY_DESTRUCTIBLE
- private:
- static constexpr bool compliant = std::is_trivially_destructible<T>::value ==
- is_trivially_destructible::value;
- static_assert(compliant || std::is_trivially_destructible<T>::value,
- "Not compliant with std::is_trivially_destructible; "
- "Standard: false, Implementation: true");
- static_assert(compliant || !std::is_trivially_destructible<T>::value,
- "Not compliant with std::is_trivially_destructible; "
- "Standard: true, Implementation: false");
-#endif // ABSL_HAVE_STD_IS_TRIVIALLY_DESTRUCTIBLE
-};
-
// is_trivially_default_constructible()
-//
-// Determines whether the passed type `T` is trivially default constructible.
-//
-// This metafunction is designed to be a drop-in replacement for the C++11
-// `std::is_trivially_default_constructible()` metafunction for platforms that
-// have incomplete C++11 support (such as libstdc++ 4.x). On any platforms that
-// do fully support C++11, we check whether this yields the same result as the
-// std implementation.
-//
-// NOTE: according to the C++ standard, Section: 20.15.4.3 [meta.unary.prop]
-// "The predicate condition for a template specialization is_constructible<T,
-// Args...> shall be satisfied if and only if the following variable
-// definition would be well-formed for some invented variable t:
-//
-// T t(declval<Args>()...);
-//
-// is_trivially_constructible<T, Args...> additionally requires that the
-// variable definition does not call any operation that is not trivial.
-// For the purposes of this check, the call to std::declval is considered
-// trivial."
-//
-// Notes from https://en.cppreference.com/w/cpp/types/is_constructible:
-// In many implementations, is_nothrow_constructible also checks if the
-// destructor throws because it is effectively noexcept(T(arg)). Same
-// applies to is_trivially_constructible, which, in these implementations, also
-// requires that the destructor is trivial.
-// GCC bug 51452: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=51452
-// LWG issue 2116: http://cplusplus.github.io/LWG/lwg-active.html#2116.
-//
-// "T obj();" need to be well-formed and not call any nontrivial operation.
-// Nontrivially destructible types will cause the expression to be nontrivial.
-template <typename T>
-struct is_trivially_default_constructible
- : std::integral_constant<bool, __has_trivial_constructor(T) &&
- std::is_default_constructible<T>::value &&
- is_trivially_destructible<T>::value> {
-#if defined(ABSL_HAVE_STD_IS_TRIVIALLY_CONSTRUCTIBLE) && \
- !defined( \
- ABSL_META_INTERNAL_STD_CONSTRUCTION_TRAITS_DONT_CHECK_DESTRUCTION)
- private:
- static constexpr bool compliant =
- std::is_trivially_default_constructible<T>::value ==
- is_trivially_default_constructible::value;
- static_assert(compliant || std::is_trivially_default_constructible<T>::value,
- "Not compliant with std::is_trivially_default_constructible; "
- "Standard: false, Implementation: true");
- static_assert(compliant || !std::is_trivially_default_constructible<T>::value,
- "Not compliant with std::is_trivially_default_constructible; "
- "Standard: true, Implementation: false");
-#endif // ABSL_HAVE_STD_IS_TRIVIALLY_CONSTRUCTIBLE
-};
-
// is_trivially_move_constructible()
-//
-// Determines whether the passed type `T` is trivially move constructible.
-//
-// This metafunction is designed to be a drop-in replacement for the C++11
-// `std::is_trivially_move_constructible()` metafunction for platforms that have
-// incomplete C++11 support (such as libstdc++ 4.x). On any platforms that do
-// fully support C++11, we check whether this yields the same result as the std
-// implementation.
-//
-// NOTE: `T obj(declval<T>());` needs to be well-formed and not call any
-// nontrivial operation. Nontrivially destructible types will cause the
-// expression to be nontrivial.
-template <typename T>
-struct is_trivially_move_constructible
- : std::conditional<
- std::is_object<T>::value && !std::is_array<T>::value,
- type_traits_internal::IsTriviallyMoveConstructibleObject<T>,
- std::is_reference<T>>::type::type {
-#if defined(ABSL_HAVE_STD_IS_TRIVIALLY_CONSTRUCTIBLE) && \
- !defined( \
- ABSL_META_INTERNAL_STD_CONSTRUCTION_TRAITS_DONT_CHECK_DESTRUCTION)
- private:
- static constexpr bool compliant =
- std::is_trivially_move_constructible<T>::value ==
- is_trivially_move_constructible::value;
- static_assert(compliant || std::is_trivially_move_constructible<T>::value,
- "Not compliant with std::is_trivially_move_constructible; "
- "Standard: false, Implementation: true");
- static_assert(compliant || !std::is_trivially_move_constructible<T>::value,
- "Not compliant with std::is_trivially_move_constructible; "
- "Standard: true, Implementation: false");
-#endif // ABSL_HAVE_STD_IS_TRIVIALLY_CONSTRUCTIBLE
-};
-
// is_trivially_copy_constructible()
-//
-// Determines whether the passed type `T` is trivially copy constructible.
-//
-// This metafunction is designed to be a drop-in replacement for the C++11
-// `std::is_trivially_copy_constructible()` metafunction for platforms that have
-// incomplete C++11 support (such as libstdc++ 4.x). On any platforms that do
-// fully support C++11, we check whether this yields the same result as the std
-// implementation.
-//
-// NOTE: `T obj(declval<const T&>());` needs to be well-formed and not call any
-// nontrivial operation. Nontrivially destructible types will cause the
-// expression to be nontrivial.
-template <typename T>
-struct is_trivially_copy_constructible
- : std::conditional<
- std::is_object<T>::value && !std::is_array<T>::value,
- type_traits_internal::IsTriviallyCopyConstructibleObject<T>,
- std::is_lvalue_reference<T>>::type::type {
-#if defined(ABSL_HAVE_STD_IS_TRIVIALLY_CONSTRUCTIBLE) && \
- !defined( \
- ABSL_META_INTERNAL_STD_CONSTRUCTION_TRAITS_DONT_CHECK_DESTRUCTION)
- private:
- static constexpr bool compliant =
- std::is_trivially_copy_constructible<T>::value ==
- is_trivially_copy_constructible::value;
- static_assert(compliant || std::is_trivially_copy_constructible<T>::value,
- "Not compliant with std::is_trivially_copy_constructible; "
- "Standard: false, Implementation: true");
- static_assert(compliant || !std::is_trivially_copy_constructible<T>::value,
- "Not compliant with std::is_trivially_copy_constructible; "
- "Standard: true, Implementation: false");
-#endif // ABSL_HAVE_STD_IS_TRIVIALLY_CONSTRUCTIBLE
-};
-
// is_trivially_move_assignable()
-//
-// Determines whether the passed type `T` is trivially move assignable.
-//
-// This metafunction is designed to be a drop-in replacement for the C++11
-// `std::is_trivially_move_assignable()` metafunction for platforms that have
-// incomplete C++11 support (such as libstdc++ 4.x). On any platforms that do
-// fully support C++11, we check whether this yields the same result as the std
-// implementation.
-//
-// NOTE: `is_assignable<T, U>::value` is `true` if the expression
-// `declval<T>() = declval<U>()` is well-formed when treated as an unevaluated
-// operand. `is_trivially_assignable<T, U>` requires the assignment to call no
-// operation that is not trivial. `is_trivially_copy_assignable<T>` is simply
-// `is_trivially_assignable<T&, T>`.
-template <typename T>
-struct is_trivially_move_assignable
- : std::conditional<
- std::is_object<T>::value && !std::is_array<T>::value &&
- std::is_move_assignable<T>::value,
- std::is_move_assignable<type_traits_internal::SingleMemberUnion<T>>,
- type_traits_internal::IsTriviallyMoveAssignableReference<T>>::type::
- type {
-#ifdef ABSL_HAVE_STD_IS_TRIVIALLY_ASSIGNABLE
- private:
- static constexpr bool compliant =
- std::is_trivially_move_assignable<T>::value ==
- is_trivially_move_assignable::value;
- static_assert(compliant || std::is_trivially_move_assignable<T>::value,
- "Not compliant with std::is_trivially_move_assignable; "
- "Standard: false, Implementation: true");
- static_assert(compliant || !std::is_trivially_move_assignable<T>::value,
- "Not compliant with std::is_trivially_move_assignable; "
- "Standard: true, Implementation: false");
-#endif // ABSL_HAVE_STD_IS_TRIVIALLY_ASSIGNABLE
-};
-
// is_trivially_copy_assignable()
//
-// Determines whether the passed type `T` is trivially copy assignable.
+// Historical note: Abseil once provided implementations of these type traits
+// for platforms that lacked full support. New code should prefer to use the
+// std variants.
//
-// This metafunction is designed to be a drop-in replacement for the C++11
-// `std::is_trivially_copy_assignable()` metafunction for platforms that have
-// incomplete C++11 support (such as libstdc++ 4.x). On any platforms that do
-// fully support C++11, we check whether this yields the same result as the std
-// implementation.
-//
-// NOTE: `is_assignable<T, U>::value` is `true` if the expression
-// `declval<T>() = declval<U>()` is well-formed when treated as an unevaluated
-// operand. `is_trivially_assignable<T, U>` requires the assignment to call no
-// operation that is not trivial. `is_trivially_copy_assignable<T>` is simply
-// `is_trivially_assignable<T&, const T&>`.
-template <typename T>
-struct is_trivially_copy_assignable
- : std::integral_constant<
- bool, __has_trivial_assign(typename std::remove_reference<T>::type) &&
- absl::is_copy_assignable<T>::value> {
-#ifdef ABSL_HAVE_STD_IS_TRIVIALLY_ASSIGNABLE
- private:
- static constexpr bool compliant =
- std::is_trivially_copy_assignable<T>::value ==
- is_trivially_copy_assignable::value;
- static_assert(compliant || std::is_trivially_copy_assignable<T>::value,
- "Not compliant with std::is_trivially_copy_assignable; "
- "Standard: false, Implementation: true");
- static_assert(compliant || !std::is_trivially_copy_assignable<T>::value,
- "Not compliant with std::is_trivially_copy_assignable; "
- "Standard: true, Implementation: false");
-#endif // ABSL_HAVE_STD_IS_TRIVIALLY_ASSIGNABLE
-};
+// See the documentation for the STL <type_traits> header for more information:
+// https://en.cppreference.com/w/cpp/header/type_traits
+using std::is_copy_assignable;
+using std::is_move_assignable;
+using std::is_trivially_copy_assignable;
+using std::is_trivially_copy_constructible;
+using std::is_trivially_default_constructible;
+using std::is_trivially_destructible;
+using std::is_trivially_move_assignable;
+using std::is_trivially_move_constructible;
#if defined(__cpp_lib_remove_cvref) && __cpp_lib_remove_cvref >= 201711L
template <typename T>
@@ -520,49 +230,6 @@
using remove_cvref_t = typename remove_cvref<T>::type;
#endif
-namespace type_traits_internal {
-// is_trivially_copyable()
-//
-// Determines whether the passed type `T` is trivially copyable.
-//
-// This metafunction is designed to be a drop-in replacement for the C++11
-// `std::is_trivially_copyable()` metafunction for platforms that have
-// incomplete C++11 support (such as libstdc++ 4.x). We use the C++17 definition
-// of TriviallyCopyable.
-//
-// NOTE: `is_trivially_copyable<T>::value` is `true` if all of T's copy/move
-// constructors/assignment operators are trivial or deleted, T has at least
-// one non-deleted copy/move constructor/assignment operator, and T is trivially
-// destructible. Arrays of trivially copyable types are trivially copyable.
-//
-// We expose this metafunction only for internal use within absl.
-template <typename T>
-class is_trivially_copyable_impl {
- using ExtentsRemoved = typename std::remove_all_extents<T>::type;
- static constexpr bool kIsCopyOrMoveConstructible =
- std::is_copy_constructible<ExtentsRemoved>::value ||
- std::is_move_constructible<ExtentsRemoved>::value;
- static constexpr bool kIsCopyOrMoveAssignable =
- absl::is_copy_assignable<ExtentsRemoved>::value ||
- absl::is_move_assignable<ExtentsRemoved>::value;
-
- public:
- static constexpr bool kValue =
- (__has_trivial_copy(ExtentsRemoved) || !kIsCopyOrMoveConstructible) &&
- (__has_trivial_assign(ExtentsRemoved) || !kIsCopyOrMoveAssignable) &&
- (kIsCopyOrMoveConstructible || kIsCopyOrMoveAssignable) &&
- is_trivially_destructible<ExtentsRemoved>::value &&
- // We need to check for this explicitly because otherwise we'll say
- // references are trivial copyable when compiled by MSVC.
- !std::is_reference<ExtentsRemoved>::value;
-};
-
-template <typename T>
-struct is_trivially_copyable
- : std::integral_constant<
- bool, type_traits_internal::is_trivially_copyable_impl<T>::kValue> {};
-} // namespace type_traits_internal
-
// -----------------------------------------------------------------------------
// C++14 "_t" trait aliases
// -----------------------------------------------------------------------------
@@ -612,9 +279,26 @@
template <typename T>
using remove_all_extents_t = typename std::remove_all_extents<T>::type;
+ABSL_INTERNAL_DISABLE_DEPRECATED_DECLARATION_WARNING
+namespace type_traits_internal {
+// This trick to retrieve a default alignment is necessary for our
+// implementation of aligned_storage_t to be consistent with any
+// implementation of std::aligned_storage.
+template <size_t Len, typename T = std::aligned_storage<Len>>
+struct default_alignment_of_aligned_storage;
+
+template <size_t Len, size_t Align>
+struct default_alignment_of_aligned_storage<
+ Len, std::aligned_storage<Len, Align>> {
+ static constexpr size_t value = Align;
+};
+} // namespace type_traits_internal
+
+// TODO(b/260219225): std::aligned_storage(_t) is deprecated in C++23.
template <size_t Len, size_t Align = type_traits_internal::
default_alignment_of_aligned_storage<Len>::value>
using aligned_storage_t = typename std::aligned_storage<Len, Align>::type;
+ABSL_INTERNAL_RESTORE_DEPRECATED_DECLARATION_WARNING
template <typename T>
using decay_t = typename std::decay<T>::type;
@@ -631,21 +315,23 @@
template <typename T>
using underlying_type_t = typename std::underlying_type<T>::type;
-
namespace type_traits_internal {
-#if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)
+#if (defined(__cpp_lib_is_invocable) && __cpp_lib_is_invocable >= 201703L) || \
+ (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)
// std::result_of is deprecated (C++17) or removed (C++20)
-template<typename> struct result_of;
-template<typename F, typename... Args>
+template <typename>
+struct result_of;
+template <typename F, typename... Args>
struct result_of<F(Args...)> : std::invoke_result<F, Args...> {};
#else
-template<typename F> using result_of = std::result_of<F>;
+template <typename F>
+using result_of = std::result_of<F>;
#endif
} // namespace type_traits_internal
-template<typename F>
+template <typename F>
using result_of_t = typename type_traits_internal::result_of<F>::type;
namespace type_traits_internal {
@@ -778,10 +464,116 @@
// Make the swap-related traits/function accessible from this namespace.
using swap_internal::IsNothrowSwappable;
using swap_internal::IsSwappable;
-using swap_internal::Swap;
using swap_internal::StdSwapIsUnconstrained;
+using swap_internal::Swap;
} // namespace type_traits_internal
+
+// absl::is_trivially_relocatable<T>
+//
+// Detects whether a type is known to be "trivially relocatable" -- meaning it
+// can be relocated from one place to another as if by memcpy/memmove.
+// This implies that its object representation doesn't depend on its address,
+// and also none of its special member functions do anything strange.
+//
+// This trait is conservative. If it's true then the type is definitely
+// trivially relocatable, but if it's false then the type may or may not be. For
+// example, std::vector<int> is trivially relocatable on every known STL
+// implementation, but absl::is_trivially_relocatable<std::vector<int>> remains
+// false.
+//
+// Example:
+//
+// if constexpr (absl::is_trivially_relocatable<T>::value) {
+// memcpy(new_location, old_location, sizeof(T));
+// } else {
+// new(new_location) T(std::move(*old_location));
+// old_location->~T();
+// }
+//
+// Upstream documentation:
+//
+// https://clang.llvm.org/docs/LanguageExtensions.html#:~:text=__is_trivially_relocatable
+
+// If the compiler offers a builtin that tells us the answer, we can use that.
+// This covers all of the cases in the fallback below, plus types that opt in
+// using e.g. [[clang::trivial_abi]].
+//
+// Clang on Windows has the builtin, but it falsely claims types with a
+// user-provided destructor are trivial (http://b/275003464). So we opt out
+// there.
+//
+// TODO(b/275003464): remove the opt-out once the bug is fixed.
+//
+// Starting with Xcode 15, the Apple compiler will falsely say a type
+// with a user-provided move constructor is trivially relocatable
+// (b/324278148). We will opt out without a version check, due to
+// the fluidity of Apple versions.
+//
+// TODO(b/324278148): If all versions we use have the bug fixed, then
+// remove the condition.
+//
+// Clang on all platforms fails to detect that a type with a user-provided
+// move-assignment operator is not trivially relocatable. So in fact we
+// opt out of Clang altogether, for now.
+//
+// TODO(b/325479096): Remove the opt-out once Clang's behavior is fixed.
+//
+// According to https://github.com/abseil/abseil-cpp/issues/1479, this does not
+// work with NVCC either.
+#if ABSL_HAVE_BUILTIN(__is_trivially_relocatable) && \
+ (defined(__cpp_impl_trivially_relocatable) || \
+ (!defined(__clang__) && !defined(__APPLE__) && !defined(__NVCC__)))
+template <class T>
+struct is_trivially_relocatable
+ : std::integral_constant<bool, __is_trivially_relocatable(T)> {};
+#else
+// Otherwise we use a fallback that detects only those types we can feasibly
+// detect. Any type that is trivially copyable is by definition trivially
+// relocatable.
+template <class T>
+struct is_trivially_relocatable : std::is_trivially_copyable<T> {};
+#endif
+
+// absl::is_constant_evaluated()
+//
+// Detects whether the function call occurs within a constant-evaluated context.
+// Returns true if the evaluation of the call occurs within the evaluation of an
+// expression or conversion that is manifestly constant-evaluated; otherwise
+// returns false.
+//
+// This function is implemented in terms of `std::is_constant_evaluated` for
+// c++20 and up. For older c++ versions, the function is implemented in terms
+// of `__builtin_is_constant_evaluated` if available, otherwise the function
+// will fail to compile.
+//
+// Applications can inspect `ABSL_HAVE_CONSTANT_EVALUATED` at compile time
+// to check if this function is supported.
+//
+// Example:
+//
+// constexpr MyClass::MyClass(int param) {
+// #ifdef ABSL_HAVE_CONSTANT_EVALUATED
+// if (!absl::is_constant_evaluated()) {
+// ABSL_LOG(INFO) << "MyClass(" << param << ")";
+// }
+// #endif // ABSL_HAVE_CONSTANT_EVALUATED
+// }
+//
+// Upstream documentation:
+//
+// http://en.cppreference.com/w/cpp/types/is_constant_evaluated
+// http://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html#:~:text=__builtin_is_constant_evaluated
+//
+#if defined(ABSL_HAVE_CONSTANT_EVALUATED)
+constexpr bool is_constant_evaluated() noexcept {
+#ifdef __cpp_lib_is_constant_evaluated
+ return std::is_constant_evaluated();
+#elif ABSL_HAVE_BUILTIN(__builtin_is_constant_evaluated)
+ return __builtin_is_constant_evaluated();
+#endif
+}
+#endif // ABSL_HAVE_CONSTANT_EVALUATED
ABSL_NAMESPACE_END
} // namespace absl
diff --git a/absl/meta/type_traits_test.cc b/absl/meta/type_traits_test.cc
index 0ef5b66..25f5abb 100644
--- a/absl/meta/type_traits_test.cc
+++ b/absl/meta/type_traits_test.cc
@@ -21,6 +21,10 @@
#include <vector>
#include "gtest/gtest.h"
+#include "absl/base/attributes.h"
+#include "absl/base/config.h"
+#include "absl/time/clock.h"
+#include "absl/time/time.h"
namespace {
@@ -336,6 +340,7 @@
struct NonCopyableOrMovable {
NonCopyableOrMovable() = default;
+ virtual ~NonCopyableOrMovable() = default;
NonCopyableOrMovable(const NonCopyableOrMovable&) = delete;
NonCopyableOrMovable(NonCopyableOrMovable&&) = delete;
NonCopyableOrMovable& operator=(const NonCopyableOrMovable&) = delete;
@@ -347,29 +352,6 @@
virtual ~Base() {}
};
-// Old versions of libc++, around Clang 3.5 to 3.6, consider deleted destructors
-// as also being trivial. With the resolution of CWG 1928 and CWG 1734, this
-// is no longer considered true and has thus been amended.
-// Compiler Explorer: https://godbolt.org/g/zT59ZL
-// CWG issue 1734: http://open-std.org/JTC1/SC22/WG21/docs/cwg_defects.html#1734
-// CWG issue 1928: http://open-std.org/JTC1/SC22/WG21/docs/cwg_closed.html#1928
-#if !defined(_LIBCPP_VERSION) || _LIBCPP_VERSION >= 3700
-#define ABSL_TRIVIALLY_DESTRUCTIBLE_CONSIDER_DELETED_DESTRUCTOR_NOT_TRIVIAL 1
-#endif
-
-// As of the moment, GCC versions >5.1 have a problem compiling for
-// std::is_trivially_default_constructible<NontrivialDestructor[10]>, where
-// NontrivialDestructor is a struct with a custom nontrivial destructor. Note
-// that this problem only occurs for arrays of a known size, so something like
-// std::is_trivially_default_constructible<NontrivialDestructor[]> does not
-// have any problems.
-// Compiler Explorer: https://godbolt.org/g/dXRbdK
-// GCC bug 83689: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=83689
-#if defined(__clang__) || defined(_MSC_VER) || \
- (defined(__GNUC__) && __GNUC__ < 5)
-#define ABSL_GCC_BUG_TRIVIALLY_CONSTRUCTIBLE_ON_ARRAY_OF_NONTRIVIAL 1
-#endif
-
TEST(TypeTraitsTest, TestIsFunction) {
struct Callable {
void operator()() {}
@@ -380,568 +362,12 @@
EXPECT_TRUE(absl::is_function<void() noexcept>::value);
EXPECT_TRUE(absl::is_function<void(...) noexcept>::value);
- EXPECT_FALSE(absl::is_function<void(*)()>::value);
- EXPECT_FALSE(absl::is_function<void(&)()>::value);
+ EXPECT_FALSE(absl::is_function<void (*)()>::value);
+ EXPECT_FALSE(absl::is_function<void (&)()>::value);
EXPECT_FALSE(absl::is_function<int>::value);
EXPECT_FALSE(absl::is_function<Callable>::value);
}
-TEST(TypeTraitsTest, TestTrivialDestructor) {
- // Verify that arithmetic types and pointers have trivial destructors.
- EXPECT_TRUE(absl::is_trivially_destructible<bool>::value);
- EXPECT_TRUE(absl::is_trivially_destructible<char>::value);
- EXPECT_TRUE(absl::is_trivially_destructible<unsigned char>::value);
- EXPECT_TRUE(absl::is_trivially_destructible<signed char>::value);
- EXPECT_TRUE(absl::is_trivially_destructible<wchar_t>::value);
- EXPECT_TRUE(absl::is_trivially_destructible<int>::value);
- EXPECT_TRUE(absl::is_trivially_destructible<unsigned int>::value);
- EXPECT_TRUE(absl::is_trivially_destructible<int16_t>::value);
- EXPECT_TRUE(absl::is_trivially_destructible<uint16_t>::value);
- EXPECT_TRUE(absl::is_trivially_destructible<int64_t>::value);
- EXPECT_TRUE(absl::is_trivially_destructible<uint64_t>::value);
- EXPECT_TRUE(absl::is_trivially_destructible<float>::value);
- EXPECT_TRUE(absl::is_trivially_destructible<double>::value);
- EXPECT_TRUE(absl::is_trivially_destructible<long double>::value);
- EXPECT_TRUE(absl::is_trivially_destructible<std::string*>::value);
- EXPECT_TRUE(absl::is_trivially_destructible<Trivial*>::value);
- EXPECT_TRUE(absl::is_trivially_destructible<const std::string*>::value);
- EXPECT_TRUE(absl::is_trivially_destructible<const Trivial*>::value);
- EXPECT_TRUE(absl::is_trivially_destructible<std::string**>::value);
- EXPECT_TRUE(absl::is_trivially_destructible<Trivial**>::value);
-
- // classes with destructors
- EXPECT_TRUE(absl::is_trivially_destructible<Trivial>::value);
- EXPECT_TRUE(absl::is_trivially_destructible<TrivialDestructor>::value);
-
- // Verify that types with a nontrivial or deleted destructor
- // are marked as such.
- EXPECT_FALSE(absl::is_trivially_destructible<NontrivialDestructor>::value);
-#ifdef ABSL_TRIVIALLY_DESTRUCTIBLE_CONSIDER_DELETED_DESTRUCTOR_NOT_TRIVIAL
- EXPECT_FALSE(absl::is_trivially_destructible<DeletedDestructor>::value);
-#endif
-
- // simple_pair of such types is trivial
- EXPECT_TRUE((absl::is_trivially_destructible<simple_pair<int, int>>::value));
- EXPECT_TRUE((absl::is_trivially_destructible<
- simple_pair<Trivial, TrivialDestructor>>::value));
-
- // Verify that types without trivial destructors are correctly marked as such.
- EXPECT_FALSE(absl::is_trivially_destructible<std::string>::value);
- EXPECT_FALSE(absl::is_trivially_destructible<std::vector<int>>::value);
-
- // Verify that simple_pairs of types without trivial destructors
- // are not marked as trivial.
- EXPECT_FALSE((absl::is_trivially_destructible<
- simple_pair<int, std::string>>::value));
- EXPECT_FALSE((absl::is_trivially_destructible<
- simple_pair<std::string, int>>::value));
-
- // array of such types is trivial
- using int10 = int[10];
- EXPECT_TRUE(absl::is_trivially_destructible<int10>::value);
- using Trivial10 = Trivial[10];
- EXPECT_TRUE(absl::is_trivially_destructible<Trivial10>::value);
- using TrivialDestructor10 = TrivialDestructor[10];
- EXPECT_TRUE(absl::is_trivially_destructible<TrivialDestructor10>::value);
-
- // Conversely, the opposite also holds.
- using NontrivialDestructor10 = NontrivialDestructor[10];
- EXPECT_FALSE(absl::is_trivially_destructible<NontrivialDestructor10>::value);
-}
-
-TEST(TypeTraitsTest, TestTrivialDefaultCtor) {
- // arithmetic types and pointers have trivial default constructors.
- EXPECT_TRUE(absl::is_trivially_default_constructible<bool>::value);
- EXPECT_TRUE(absl::is_trivially_default_constructible<char>::value);
- EXPECT_TRUE(absl::is_trivially_default_constructible<unsigned char>::value);
- EXPECT_TRUE(absl::is_trivially_default_constructible<signed char>::value);
- EXPECT_TRUE(absl::is_trivially_default_constructible<wchar_t>::value);
- EXPECT_TRUE(absl::is_trivially_default_constructible<int>::value);
- EXPECT_TRUE(absl::is_trivially_default_constructible<unsigned int>::value);
- EXPECT_TRUE(absl::is_trivially_default_constructible<int16_t>::value);
- EXPECT_TRUE(absl::is_trivially_default_constructible<uint16_t>::value);
- EXPECT_TRUE(absl::is_trivially_default_constructible<int64_t>::value);
- EXPECT_TRUE(absl::is_trivially_default_constructible<uint64_t>::value);
- EXPECT_TRUE(absl::is_trivially_default_constructible<float>::value);
- EXPECT_TRUE(absl::is_trivially_default_constructible<double>::value);
- EXPECT_TRUE(absl::is_trivially_default_constructible<long double>::value);
- EXPECT_TRUE(absl::is_trivially_default_constructible<std::string*>::value);
- EXPECT_TRUE(absl::is_trivially_default_constructible<Trivial*>::value);
- EXPECT_TRUE(
- absl::is_trivially_default_constructible<const std::string*>::value);
- EXPECT_TRUE(absl::is_trivially_default_constructible<const Trivial*>::value);
- EXPECT_TRUE(absl::is_trivially_default_constructible<std::string**>::value);
- EXPECT_TRUE(absl::is_trivially_default_constructible<Trivial**>::value);
-
- // types with compiler generated default ctors
- EXPECT_TRUE(absl::is_trivially_default_constructible<Trivial>::value);
- EXPECT_TRUE(
- absl::is_trivially_default_constructible<TrivialDefaultCtor>::value);
-
- // Verify that types without them are not.
- EXPECT_FALSE(
- absl::is_trivially_default_constructible<NontrivialDefaultCtor>::value);
- EXPECT_FALSE(
- absl::is_trivially_default_constructible<DeletedDefaultCtor>::value);
-
- // types with nontrivial destructor are nontrivial
- EXPECT_FALSE(
- absl::is_trivially_default_constructible<NontrivialDestructor>::value);
-
- // types with vtables
- EXPECT_FALSE(absl::is_trivially_default_constructible<Base>::value);
-
- // Verify that simple_pair has trivial constructors where applicable.
- EXPECT_TRUE((absl::is_trivially_default_constructible<
- simple_pair<int, char*>>::value));
- EXPECT_TRUE((absl::is_trivially_default_constructible<
- simple_pair<int, Trivial>>::value));
- EXPECT_TRUE((absl::is_trivially_default_constructible<
- simple_pair<int, TrivialDefaultCtor>>::value));
-
- // Verify that types without trivial constructors are
- // correctly marked as such.
- EXPECT_FALSE(absl::is_trivially_default_constructible<std::string>::value);
- EXPECT_FALSE(
- absl::is_trivially_default_constructible<std::vector<int>>::value);
-
- // Verify that simple_pairs of types without trivial constructors
- // are not marked as trivial.
- EXPECT_FALSE((absl::is_trivially_default_constructible<
- simple_pair<int, std::string>>::value));
- EXPECT_FALSE((absl::is_trivially_default_constructible<
- simple_pair<std::string, int>>::value));
-
- // Verify that arrays of such types are trivially default constructible
- using int10 = int[10];
- EXPECT_TRUE(absl::is_trivially_default_constructible<int10>::value);
- using Trivial10 = Trivial[10];
- EXPECT_TRUE(absl::is_trivially_default_constructible<Trivial10>::value);
- using TrivialDefaultCtor10 = TrivialDefaultCtor[10];
- EXPECT_TRUE(
- absl::is_trivially_default_constructible<TrivialDefaultCtor10>::value);
-
- // Conversely, the opposite also holds.
-#ifdef ABSL_GCC_BUG_TRIVIALLY_CONSTRUCTIBLE_ON_ARRAY_OF_NONTRIVIAL
- using NontrivialDefaultCtor10 = NontrivialDefaultCtor[10];
- EXPECT_FALSE(
- absl::is_trivially_default_constructible<NontrivialDefaultCtor10>::value);
-#endif
-}
-
-// GCC prior to 7.4 had a bug in its trivially-constructible traits
-// (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80654).
-// This test makes sure that we do not depend on the trait in these cases when
-// implementing absl triviality traits.
-
-template <class T>
-struct BadConstructors {
- BadConstructors() { static_assert(T::value, ""); }
-
- BadConstructors(BadConstructors&&) { static_assert(T::value, ""); }
-
- BadConstructors(const BadConstructors&) { static_assert(T::value, ""); }
-};
-
-TEST(TypeTraitsTest, TestTrivialityBadConstructors) {
- using BadType = BadConstructors<int>;
-
- EXPECT_FALSE(absl::is_trivially_default_constructible<BadType>::value);
- EXPECT_FALSE(absl::is_trivially_move_constructible<BadType>::value);
- EXPECT_FALSE(absl::is_trivially_copy_constructible<BadType>::value);
-}
-
-TEST(TypeTraitsTest, TestTrivialMoveCtor) {
- // Verify that arithmetic types and pointers have trivial move
- // constructors.
- EXPECT_TRUE(absl::is_trivially_move_constructible<bool>::value);
- EXPECT_TRUE(absl::is_trivially_move_constructible<char>::value);
- EXPECT_TRUE(absl::is_trivially_move_constructible<unsigned char>::value);
- EXPECT_TRUE(absl::is_trivially_move_constructible<signed char>::value);
- EXPECT_TRUE(absl::is_trivially_move_constructible<wchar_t>::value);
- EXPECT_TRUE(absl::is_trivially_move_constructible<int>::value);
- EXPECT_TRUE(absl::is_trivially_move_constructible<unsigned int>::value);
- EXPECT_TRUE(absl::is_trivially_move_constructible<int16_t>::value);
- EXPECT_TRUE(absl::is_trivially_move_constructible<uint16_t>::value);
- EXPECT_TRUE(absl::is_trivially_move_constructible<int64_t>::value);
- EXPECT_TRUE(absl::is_trivially_move_constructible<uint64_t>::value);
- EXPECT_TRUE(absl::is_trivially_move_constructible<float>::value);
- EXPECT_TRUE(absl::is_trivially_move_constructible<double>::value);
- EXPECT_TRUE(absl::is_trivially_move_constructible<long double>::value);
- EXPECT_TRUE(absl::is_trivially_move_constructible<std::string*>::value);
- EXPECT_TRUE(absl::is_trivially_move_constructible<Trivial*>::value);
- EXPECT_TRUE(absl::is_trivially_move_constructible<const std::string*>::value);
- EXPECT_TRUE(absl::is_trivially_move_constructible<const Trivial*>::value);
- EXPECT_TRUE(absl::is_trivially_move_constructible<std::string**>::value);
- EXPECT_TRUE(absl::is_trivially_move_constructible<Trivial**>::value);
-
- // Reference types
- EXPECT_TRUE(absl::is_trivially_move_constructible<int&>::value);
- EXPECT_TRUE(absl::is_trivially_move_constructible<int&&>::value);
-
- // types with compiler generated move ctors
- EXPECT_TRUE(absl::is_trivially_move_constructible<Trivial>::value);
- EXPECT_TRUE(absl::is_trivially_move_constructible<TrivialMoveCtor>::value);
-
- // Verify that types without them (i.e. nontrivial or deleted) are not.
- EXPECT_FALSE(
- absl::is_trivially_move_constructible<NontrivialCopyCtor>::value);
- EXPECT_FALSE(absl::is_trivially_move_constructible<DeletedCopyCtor>::value);
- EXPECT_FALSE(
- absl::is_trivially_move_constructible<NonCopyableOrMovable>::value);
-
- // type with nontrivial destructor are nontrivial move construbtible
- EXPECT_FALSE(
- absl::is_trivially_move_constructible<NontrivialDestructor>::value);
-
- // types with vtables
- EXPECT_FALSE(absl::is_trivially_move_constructible<Base>::value);
-
- // Verify that simple_pair of such types is trivially move constructible
- EXPECT_TRUE(
- (absl::is_trivially_move_constructible<simple_pair<int, char*>>::value));
- EXPECT_TRUE((
- absl::is_trivially_move_constructible<simple_pair<int, Trivial>>::value));
- EXPECT_TRUE((absl::is_trivially_move_constructible<
- simple_pair<int, TrivialMoveCtor>>::value));
-
- // Verify that types without trivial move constructors are
- // correctly marked as such.
- EXPECT_FALSE(absl::is_trivially_move_constructible<std::string>::value);
- EXPECT_FALSE(absl::is_trivially_move_constructible<std::vector<int>>::value);
-
- // Verify that simple_pairs of types without trivial move constructors
- // are not marked as trivial.
- EXPECT_FALSE((absl::is_trivially_move_constructible<
- simple_pair<int, std::string>>::value));
- EXPECT_FALSE((absl::is_trivially_move_constructible<
- simple_pair<std::string, int>>::value));
-
- // Verify that arrays are not
- using int10 = int[10];
- EXPECT_FALSE(absl::is_trivially_move_constructible<int10>::value);
-}
-
-TEST(TypeTraitsTest, TestTrivialCopyCtor) {
- // Verify that arithmetic types and pointers have trivial copy
- // constructors.
- EXPECT_TRUE(absl::is_trivially_copy_constructible<bool>::value);
- EXPECT_TRUE(absl::is_trivially_copy_constructible<char>::value);
- EXPECT_TRUE(absl::is_trivially_copy_constructible<unsigned char>::value);
- EXPECT_TRUE(absl::is_trivially_copy_constructible<signed char>::value);
- EXPECT_TRUE(absl::is_trivially_copy_constructible<wchar_t>::value);
- EXPECT_TRUE(absl::is_trivially_copy_constructible<int>::value);
- EXPECT_TRUE(absl::is_trivially_copy_constructible<unsigned int>::value);
- EXPECT_TRUE(absl::is_trivially_copy_constructible<int16_t>::value);
- EXPECT_TRUE(absl::is_trivially_copy_constructible<uint16_t>::value);
- EXPECT_TRUE(absl::is_trivially_copy_constructible<int64_t>::value);
- EXPECT_TRUE(absl::is_trivially_copy_constructible<uint64_t>::value);
- EXPECT_TRUE(absl::is_trivially_copy_constructible<float>::value);
- EXPECT_TRUE(absl::is_trivially_copy_constructible<double>::value);
- EXPECT_TRUE(absl::is_trivially_copy_constructible<long double>::value);
- EXPECT_TRUE(absl::is_trivially_copy_constructible<std::string*>::value);
- EXPECT_TRUE(absl::is_trivially_copy_constructible<Trivial*>::value);
- EXPECT_TRUE(absl::is_trivially_copy_constructible<const std::string*>::value);
- EXPECT_TRUE(absl::is_trivially_copy_constructible<const Trivial*>::value);
- EXPECT_TRUE(absl::is_trivially_copy_constructible<std::string**>::value);
- EXPECT_TRUE(absl::is_trivially_copy_constructible<Trivial**>::value);
-
- // Reference types
- EXPECT_TRUE(absl::is_trivially_copy_constructible<int&>::value);
- EXPECT_FALSE(absl::is_trivially_copy_constructible<int&&>::value);
-
- // types with compiler generated copy ctors
- EXPECT_TRUE(absl::is_trivially_copy_constructible<Trivial>::value);
- EXPECT_TRUE(absl::is_trivially_copy_constructible<TrivialCopyCtor>::value);
-
- // Verify that types without them (i.e. nontrivial or deleted) are not.
- EXPECT_FALSE(
- absl::is_trivially_copy_constructible<NontrivialCopyCtor>::value);
- EXPECT_FALSE(absl::is_trivially_copy_constructible<DeletedCopyCtor>::value);
- EXPECT_FALSE(
- absl::is_trivially_copy_constructible<MovableNonCopyable>::value);
- EXPECT_FALSE(
- absl::is_trivially_copy_constructible<NonCopyableOrMovable>::value);
-
- // type with nontrivial destructor are nontrivial copy construbtible
- EXPECT_FALSE(
- absl::is_trivially_copy_constructible<NontrivialDestructor>::value);
-
- // types with vtables
- EXPECT_FALSE(absl::is_trivially_copy_constructible<Base>::value);
-
- // Verify that simple_pair of such types is trivially copy constructible
- EXPECT_TRUE(
- (absl::is_trivially_copy_constructible<simple_pair<int, char*>>::value));
- EXPECT_TRUE((
- absl::is_trivially_copy_constructible<simple_pair<int, Trivial>>::value));
- EXPECT_TRUE((absl::is_trivially_copy_constructible<
- simple_pair<int, TrivialCopyCtor>>::value));
-
- // Verify that types without trivial copy constructors are
- // correctly marked as such.
- EXPECT_FALSE(absl::is_trivially_copy_constructible<std::string>::value);
- EXPECT_FALSE(absl::is_trivially_copy_constructible<std::vector<int>>::value);
-
- // Verify that simple_pairs of types without trivial copy constructors
- // are not marked as trivial.
- EXPECT_FALSE((absl::is_trivially_copy_constructible<
- simple_pair<int, std::string>>::value));
- EXPECT_FALSE((absl::is_trivially_copy_constructible<
- simple_pair<std::string, int>>::value));
-
- // Verify that arrays are not
- using int10 = int[10];
- EXPECT_FALSE(absl::is_trivially_copy_constructible<int10>::value);
-}
-
-TEST(TypeTraitsTest, TestTrivialMoveAssign) {
- // Verify that arithmetic types and pointers have trivial move
- // assignment operators.
- EXPECT_TRUE(absl::is_trivially_move_assignable<bool>::value);
- EXPECT_TRUE(absl::is_trivially_move_assignable<char>::value);
- EXPECT_TRUE(absl::is_trivially_move_assignable<unsigned char>::value);
- EXPECT_TRUE(absl::is_trivially_move_assignable<signed char>::value);
- EXPECT_TRUE(absl::is_trivially_move_assignable<wchar_t>::value);
- EXPECT_TRUE(absl::is_trivially_move_assignable<int>::value);
- EXPECT_TRUE(absl::is_trivially_move_assignable<unsigned int>::value);
- EXPECT_TRUE(absl::is_trivially_move_assignable<int16_t>::value);
- EXPECT_TRUE(absl::is_trivially_move_assignable<uint16_t>::value);
- EXPECT_TRUE(absl::is_trivially_move_assignable<int64_t>::value);
- EXPECT_TRUE(absl::is_trivially_move_assignable<uint64_t>::value);
- EXPECT_TRUE(absl::is_trivially_move_assignable<float>::value);
- EXPECT_TRUE(absl::is_trivially_move_assignable<double>::value);
- EXPECT_TRUE(absl::is_trivially_move_assignable<long double>::value);
- EXPECT_TRUE(absl::is_trivially_move_assignable<std::string*>::value);
- EXPECT_TRUE(absl::is_trivially_move_assignable<Trivial*>::value);
- EXPECT_TRUE(absl::is_trivially_move_assignable<const std::string*>::value);
- EXPECT_TRUE(absl::is_trivially_move_assignable<const Trivial*>::value);
- EXPECT_TRUE(absl::is_trivially_move_assignable<std::string**>::value);
- EXPECT_TRUE(absl::is_trivially_move_assignable<Trivial**>::value);
-
- // const qualified types are not assignable
- EXPECT_FALSE(absl::is_trivially_move_assignable<const int>::value);
-
- // types with compiler generated move assignment
- EXPECT_TRUE(absl::is_trivially_move_assignable<Trivial>::value);
- EXPECT_TRUE(absl::is_trivially_move_assignable<TrivialMoveAssign>::value);
-
- // Verify that types without them (i.e. nontrivial or deleted) are not.
- EXPECT_FALSE(absl::is_trivially_move_assignable<NontrivialCopyAssign>::value);
- EXPECT_FALSE(absl::is_trivially_move_assignable<DeletedCopyAssign>::value);
- EXPECT_FALSE(absl::is_trivially_move_assignable<NonCopyableOrMovable>::value);
-
- // types with vtables
- EXPECT_FALSE(absl::is_trivially_move_assignable<Base>::value);
-
- // Verify that simple_pair is trivially assignable
- EXPECT_TRUE(
- (absl::is_trivially_move_assignable<simple_pair<int, char*>>::value));
- EXPECT_TRUE(
- (absl::is_trivially_move_assignable<simple_pair<int, Trivial>>::value));
- EXPECT_TRUE((absl::is_trivially_move_assignable<
- simple_pair<int, TrivialMoveAssign>>::value));
-
- // Verify that types not trivially move assignable are
- // correctly marked as such.
- EXPECT_FALSE(absl::is_trivially_move_assignable<std::string>::value);
- EXPECT_FALSE(absl::is_trivially_move_assignable<std::vector<int>>::value);
-
- // Verify that simple_pairs of types not trivially move assignable
- // are not marked as trivial.
- EXPECT_FALSE((absl::is_trivially_move_assignable<
- simple_pair<int, std::string>>::value));
- EXPECT_FALSE((absl::is_trivially_move_assignable<
- simple_pair<std::string, int>>::value));
-
- // Verify that arrays are not trivially move assignable
- using int10 = int[10];
- EXPECT_FALSE(absl::is_trivially_move_assignable<int10>::value);
-
- // Verify that references are handled correctly
- EXPECT_TRUE(absl::is_trivially_move_assignable<Trivial&&>::value);
- EXPECT_TRUE(absl::is_trivially_move_assignable<Trivial&>::value);
-}
-
-TEST(TypeTraitsTest, TestTrivialCopyAssign) {
- // Verify that arithmetic types and pointers have trivial copy
- // assignment operators.
- EXPECT_TRUE(absl::is_trivially_copy_assignable<bool>::value);
- EXPECT_TRUE(absl::is_trivially_copy_assignable<char>::value);
- EXPECT_TRUE(absl::is_trivially_copy_assignable<unsigned char>::value);
- EXPECT_TRUE(absl::is_trivially_copy_assignable<signed char>::value);
- EXPECT_TRUE(absl::is_trivially_copy_assignable<wchar_t>::value);
- EXPECT_TRUE(absl::is_trivially_copy_assignable<int>::value);
- EXPECT_TRUE(absl::is_trivially_copy_assignable<unsigned int>::value);
- EXPECT_TRUE(absl::is_trivially_copy_assignable<int16_t>::value);
- EXPECT_TRUE(absl::is_trivially_copy_assignable<uint16_t>::value);
- EXPECT_TRUE(absl::is_trivially_copy_assignable<int64_t>::value);
- EXPECT_TRUE(absl::is_trivially_copy_assignable<uint64_t>::value);
- EXPECT_TRUE(absl::is_trivially_copy_assignable<float>::value);
- EXPECT_TRUE(absl::is_trivially_copy_assignable<double>::value);
- EXPECT_TRUE(absl::is_trivially_copy_assignable<long double>::value);
- EXPECT_TRUE(absl::is_trivially_copy_assignable<std::string*>::value);
- EXPECT_TRUE(absl::is_trivially_copy_assignable<Trivial*>::value);
- EXPECT_TRUE(absl::is_trivially_copy_assignable<const std::string*>::value);
- EXPECT_TRUE(absl::is_trivially_copy_assignable<const Trivial*>::value);
- EXPECT_TRUE(absl::is_trivially_copy_assignable<std::string**>::value);
- EXPECT_TRUE(absl::is_trivially_copy_assignable<Trivial**>::value);
-
- // const qualified types are not assignable
- EXPECT_FALSE(absl::is_trivially_copy_assignable<const int>::value);
-
- // types with compiler generated copy assignment
- EXPECT_TRUE(absl::is_trivially_copy_assignable<Trivial>::value);
- EXPECT_TRUE(absl::is_trivially_copy_assignable<TrivialCopyAssign>::value);
-
- // Verify that types without them (i.e. nontrivial or deleted) are not.
- EXPECT_FALSE(absl::is_trivially_copy_assignable<NontrivialCopyAssign>::value);
- EXPECT_FALSE(absl::is_trivially_copy_assignable<DeletedCopyAssign>::value);
- EXPECT_FALSE(absl::is_trivially_copy_assignable<MovableNonCopyable>::value);
- EXPECT_FALSE(absl::is_trivially_copy_assignable<NonCopyableOrMovable>::value);
-
- // types with vtables
- EXPECT_FALSE(absl::is_trivially_copy_assignable<Base>::value);
-
- // Verify that simple_pair is trivially assignable
- EXPECT_TRUE(
- (absl::is_trivially_copy_assignable<simple_pair<int, char*>>::value));
- EXPECT_TRUE(
- (absl::is_trivially_copy_assignable<simple_pair<int, Trivial>>::value));
- EXPECT_TRUE((absl::is_trivially_copy_assignable<
- simple_pair<int, TrivialCopyAssign>>::value));
-
- // Verify that types not trivially copy assignable are
- // correctly marked as such.
- EXPECT_FALSE(absl::is_trivially_copy_assignable<std::string>::value);
- EXPECT_FALSE(absl::is_trivially_copy_assignable<std::vector<int>>::value);
-
- // Verify that simple_pairs of types not trivially copy assignable
- // are not marked as trivial.
- EXPECT_FALSE((absl::is_trivially_copy_assignable<
- simple_pair<int, std::string>>::value));
- EXPECT_FALSE((absl::is_trivially_copy_assignable<
- simple_pair<std::string, int>>::value));
-
- // Verify that arrays are not trivially copy assignable
- using int10 = int[10];
- EXPECT_FALSE(absl::is_trivially_copy_assignable<int10>::value);
-
- // Verify that references are handled correctly
- EXPECT_TRUE(absl::is_trivially_copy_assignable<Trivial&&>::value);
- EXPECT_TRUE(absl::is_trivially_copy_assignable<Trivial&>::value);
-}
-
-TEST(TypeTraitsTest, TestTriviallyCopyable) {
- // Verify that arithmetic types and pointers are trivially copyable.
- EXPECT_TRUE(absl::type_traits_internal::is_trivially_copyable<bool>::value);
- EXPECT_TRUE(absl::type_traits_internal::is_trivially_copyable<char>::value);
- EXPECT_TRUE(
- absl::type_traits_internal::is_trivially_copyable<unsigned char>::value);
- EXPECT_TRUE(
- absl::type_traits_internal::is_trivially_copyable<signed char>::value);
- EXPECT_TRUE(
- absl::type_traits_internal::is_trivially_copyable<wchar_t>::value);
- EXPECT_TRUE(absl::type_traits_internal::is_trivially_copyable<int>::value);
- EXPECT_TRUE(
- absl::type_traits_internal::is_trivially_copyable<unsigned int>::value);
- EXPECT_TRUE(
- absl::type_traits_internal::is_trivially_copyable<int16_t>::value);
- EXPECT_TRUE(
- absl::type_traits_internal::is_trivially_copyable<uint16_t>::value);
- EXPECT_TRUE(
- absl::type_traits_internal::is_trivially_copyable<int64_t>::value);
- EXPECT_TRUE(
- absl::type_traits_internal::is_trivially_copyable<uint64_t>::value);
- EXPECT_TRUE(absl::type_traits_internal::is_trivially_copyable<float>::value);
- EXPECT_TRUE(absl::type_traits_internal::is_trivially_copyable<double>::value);
- EXPECT_TRUE(
- absl::type_traits_internal::is_trivially_copyable<long double>::value);
- EXPECT_TRUE(
- absl::type_traits_internal::is_trivially_copyable<std::string*>::value);
- EXPECT_TRUE(
- absl::type_traits_internal::is_trivially_copyable<Trivial*>::value);
- EXPECT_TRUE(absl::type_traits_internal::is_trivially_copyable<
- const std::string*>::value);
- EXPECT_TRUE(
- absl::type_traits_internal::is_trivially_copyable<const Trivial*>::value);
- EXPECT_TRUE(
- absl::type_traits_internal::is_trivially_copyable<std::string**>::value);
- EXPECT_TRUE(
- absl::type_traits_internal::is_trivially_copyable<Trivial**>::value);
-
- // const qualified types are not assignable but are constructible
- EXPECT_TRUE(
- absl::type_traits_internal::is_trivially_copyable<const int>::value);
-
- // Trivial copy constructor/assignment and destructor.
- EXPECT_TRUE(
- absl::type_traits_internal::is_trivially_copyable<Trivial>::value);
- // Trivial copy assignment, but non-trivial copy constructor/destructor.
- EXPECT_FALSE(absl::type_traits_internal::is_trivially_copyable<
- TrivialCopyAssign>::value);
- // Trivial copy constructor, but non-trivial assignment.
- EXPECT_FALSE(absl::type_traits_internal::is_trivially_copyable<
- TrivialCopyCtor>::value);
-
- // Types with a non-trivial copy constructor/assignment
- EXPECT_FALSE(absl::type_traits_internal::is_trivially_copyable<
- NontrivialCopyCtor>::value);
- EXPECT_FALSE(absl::type_traits_internal::is_trivially_copyable<
- NontrivialCopyAssign>::value);
-
- // Types without copy constructor/assignment, but with move
- // MSVC disagrees with other compilers about this:
- // EXPECT_TRUE(absl::type_traits_internal::is_trivially_copyable<
- // MovableNonCopyable>::value);
-
- // Types without copy/move constructor/assignment
- EXPECT_FALSE(absl::type_traits_internal::is_trivially_copyable<
- NonCopyableOrMovable>::value);
-
- // No copy assign, but has trivial copy constructor.
- EXPECT_TRUE(absl::type_traits_internal::is_trivially_copyable<
- DeletedCopyAssign>::value);
-
- // types with vtables
- EXPECT_FALSE(absl::type_traits_internal::is_trivially_copyable<Base>::value);
-
- // Verify that simple_pair is trivially copyable if members are
- EXPECT_TRUE((absl::type_traits_internal::is_trivially_copyable<
- simple_pair<int, char*>>::value));
- EXPECT_TRUE((absl::type_traits_internal::is_trivially_copyable<
- simple_pair<int, Trivial>>::value));
-
- // Verify that types not trivially copyable are
- // correctly marked as such.
- EXPECT_FALSE(
- absl::type_traits_internal::is_trivially_copyable<std::string>::value);
- EXPECT_FALSE(absl::type_traits_internal::is_trivially_copyable<
- std::vector<int>>::value);
-
- // Verify that simple_pairs of types not trivially copyable
- // are not marked as trivial.
- EXPECT_FALSE((absl::type_traits_internal::is_trivially_copyable<
- simple_pair<int, std::string>>::value));
- EXPECT_FALSE((absl::type_traits_internal::is_trivially_copyable<
- simple_pair<std::string, int>>::value));
- EXPECT_FALSE((absl::type_traits_internal::is_trivially_copyable<
- simple_pair<int, TrivialCopyAssign>>::value));
-
- // Verify that arrays of trivially copyable types are trivially copyable
- using int10 = int[10];
- EXPECT_TRUE(absl::type_traits_internal::is_trivially_copyable<int10>::value);
- using int10x10 = int[10][10];
- EXPECT_TRUE(
- absl::type_traits_internal::is_trivially_copyable<int10x10>::value);
-
- // Verify that references are handled correctly
- EXPECT_FALSE(
- absl::type_traits_internal::is_trivially_copyable<Trivial&&>::value);
- EXPECT_FALSE(
- absl::type_traits_internal::is_trivially_copyable<Trivial&>::value);
-}
-
TEST(TypeTraitsTest, TestRemoveCVRef) {
EXPECT_TRUE(
(std::is_same<typename absl::remove_cvref<int>::type, int>::value));
@@ -956,8 +382,8 @@
// Does not remove const in this case.
EXPECT_TRUE((std::is_same<typename absl::remove_cvref<const int*>::type,
const int*>::value));
- EXPECT_TRUE((std::is_same<typename absl::remove_cvref<int[2]>::type,
- int[2]>::value));
+ EXPECT_TRUE(
+ (std::is_same<typename absl::remove_cvref<int[2]>::type, int[2]>::value));
EXPECT_TRUE((std::is_same<typename absl::remove_cvref<int(&)[2]>::type,
int[2]>::value));
EXPECT_TRUE((std::is_same<typename absl::remove_cvref<int(&&)[2]>::type,
@@ -1154,7 +580,7 @@
ABSL_INTERNAL_EXPECT_ALIAS_EQUIVALENCE(decay, int[][1]);
ABSL_INTERNAL_EXPECT_ALIAS_EQUIVALENCE(decay, int());
- ABSL_INTERNAL_EXPECT_ALIAS_EQUIVALENCE(decay, int(float)); // NOLINT
+ ABSL_INTERNAL_EXPECT_ALIAS_EQUIVALENCE(decay, int(float)); // NOLINT
ABSL_INTERNAL_EXPECT_ALIAS_EQUIVALENCE(decay, int(char, ...)); // NOLINT
}
@@ -1236,86 +662,9 @@
EXPECT_EQ(TypeEnum::D, GetTypeExt(Wrap<TypeD>()));
}
-template <typename T>
-bool TestCopyAssign() {
- return absl::is_copy_assignable<T>::value ==
- std::is_copy_assignable<T>::value;
-}
-
-TEST(TypeTraitsTest, IsCopyAssignable) {
- EXPECT_TRUE(TestCopyAssign<int>());
- EXPECT_TRUE(TestCopyAssign<int&>());
- EXPECT_TRUE(TestCopyAssign<int&&>());
-
- struct S {};
- EXPECT_TRUE(TestCopyAssign<S>());
- EXPECT_TRUE(TestCopyAssign<S&>());
- EXPECT_TRUE(TestCopyAssign<S&&>());
-
- class C {
- public:
- explicit C(C* c) : c_(c) {}
- ~C() { delete c_; }
-
- private:
- C* c_;
- };
- EXPECT_TRUE(TestCopyAssign<C>());
- EXPECT_TRUE(TestCopyAssign<C&>());
- EXPECT_TRUE(TestCopyAssign<C&&>());
-
- // Reason for ifndef: add_lvalue_reference<T> in libc++ breaks for these cases
-#ifndef _LIBCPP_VERSION
- EXPECT_TRUE(TestCopyAssign<int()>());
- EXPECT_TRUE(TestCopyAssign<int(int) const>());
- EXPECT_TRUE(TestCopyAssign<int(...) volatile&>());
- EXPECT_TRUE(TestCopyAssign<int(int, ...) const volatile&&>());
-#endif // _LIBCPP_VERSION
-}
-
-template <typename T>
-bool TestMoveAssign() {
- return absl::is_move_assignable<T>::value ==
- std::is_move_assignable<T>::value;
-}
-
-TEST(TypeTraitsTest, IsMoveAssignable) {
- EXPECT_TRUE(TestMoveAssign<int>());
- EXPECT_TRUE(TestMoveAssign<int&>());
- EXPECT_TRUE(TestMoveAssign<int&&>());
-
- struct S {};
- EXPECT_TRUE(TestMoveAssign<S>());
- EXPECT_TRUE(TestMoveAssign<S&>());
- EXPECT_TRUE(TestMoveAssign<S&&>());
-
- class C {
- public:
- explicit C(C* c) : c_(c) {}
- ~C() { delete c_; }
- void operator=(const C&) = delete;
- void operator=(C&&) = delete;
-
- private:
- C* c_;
- };
- EXPECT_TRUE(TestMoveAssign<C>());
- EXPECT_TRUE(TestMoveAssign<C&>());
- EXPECT_TRUE(TestMoveAssign<C&&>());
-
- // Reason for ifndef: add_lvalue_reference<T> in libc++ breaks for these cases
-#ifndef _LIBCPP_VERSION
- EXPECT_TRUE(TestMoveAssign<int()>());
- EXPECT_TRUE(TestMoveAssign<int(int) const>());
- EXPECT_TRUE(TestMoveAssign<int(...) volatile&>());
- EXPECT_TRUE(TestMoveAssign<int(int, ...) const volatile&&>());
-#endif // _LIBCPP_VERSION
-}
-
namespace adl_namespace {
-struct DeletedSwap {
-};
+struct DeletedSwap {};
void swap(DeletedSwap&, DeletedSwap&) = delete;
@@ -1393,4 +742,126 @@
EXPECT_TRUE(IsNothrowSwappable<adl_namespace::SpecialNoexceptSwap>::value);
}
+TEST(TriviallyRelocatable, PrimitiveTypes) {
+ static_assert(absl::is_trivially_relocatable<int>::value, "");
+ static_assert(absl::is_trivially_relocatable<char>::value, "");
+ static_assert(absl::is_trivially_relocatable<void*>::value, "");
+}
+
+// User-defined types can be trivially relocatable as long as they don't have a
+// user-provided move constructor or destructor.
+TEST(TriviallyRelocatable, UserDefinedTriviallyRelocatable) {
+ struct S {
+ int x;
+ int y;
+ };
+
+ static_assert(absl::is_trivially_relocatable<S>::value, "");
+}
+
+// A user-provided move constructor disqualifies a type from being trivially
+// relocatable.
+TEST(TriviallyRelocatable, UserProvidedMoveConstructor) {
+ struct S {
+ S(S&&) {} // NOLINT(modernize-use-equals-default)
+ };
+
+ static_assert(!absl::is_trivially_relocatable<S>::value, "");
+}
+
+// A user-provided copy constructor disqualifies a type from being trivially
+// relocatable.
+TEST(TriviallyRelocatable, UserProvidedCopyConstructor) {
+ struct S {
+ S(const S&) {} // NOLINT(modernize-use-equals-default)
+ };
+
+ static_assert(!absl::is_trivially_relocatable<S>::value, "");
+}
+
+// A user-provided copy assignment operator disqualifies a type from
+// being trivially relocatable.
+TEST(TriviallyRelocatable, UserProvidedCopyAssignment) {
+ struct S {
+ S(const S&) = default;
+ S& operator=(const S&) { // NOLINT(modernize-use-equals-default)
+ return *this;
+ }
+ };
+
+ static_assert(!absl::is_trivially_relocatable<S>::value, "");
+}
+
+// A user-provided move assignment operator disqualifies a type from
+// being trivially relocatable.
+TEST(TriviallyRelocatable, UserProvidedMoveAssignment) {
+ struct S {
+ S(S&&) = default;
+ S& operator=(S&&) { return *this; } // NOLINT(modernize-use-equals-default)
+ };
+
+ static_assert(!absl::is_trivially_relocatable<S>::value, "");
+}
+
+// A user-provided destructor disqualifies a type from being trivially
+// relocatable.
+TEST(TriviallyRelocatable, UserProvidedDestructor) {
+ struct S {
+ ~S() {} // NOLINT(modernize-use-equals-default)
+ };
+
+ static_assert(!absl::is_trivially_relocatable<S>::value, "");
+}
+
+// TODO(b/275003464): remove the opt-out for Clang on Windows once
+// __is_trivially_relocatable is used there again.
+// TODO(b/324278148): remove the opt-out for Apple once
+// __is_trivially_relocatable is fixed there.
+#if defined(ABSL_HAVE_ATTRIBUTE_TRIVIAL_ABI) && \
+ ABSL_HAVE_BUILTIN(__is_trivially_relocatable) && \
+ (defined(__cpp_impl_trivially_relocatable) || \
+ (!defined(__clang__) && !defined(__APPLE__) && !defined(__NVCC__)))
+// A type marked with the "trivial ABI" attribute is trivially relocatable even
+// if it has user-provided special members.
+TEST(TriviallyRelocatable, TrivialAbi) {
+ struct ABSL_ATTRIBUTE_TRIVIAL_ABI S {
+ S(S&&) {} // NOLINT(modernize-use-equals-default)
+ S(const S&) {} // NOLINT(modernize-use-equals-default)
+ void operator=(S&&) {}
+ void operator=(const S&) {}
+ ~S() {} // NOLINT(modernize-use-equals-default)
+ };
+
+ static_assert(absl::is_trivially_relocatable<S>::value, "");
+}
+#endif
+
+#ifdef ABSL_HAVE_CONSTANT_EVALUATED
+
+constexpr int64_t NegateIfConstantEvaluated(int64_t i) {
+ if (absl::is_constant_evaluated()) {
+ return -i;
+ } else {
+ return i;
+ }
+}
+
+#endif // ABSL_HAVE_CONSTANT_EVALUATED
+
+TEST(IsConstantEvaluated, is_constant_evaluated) {
+#ifdef ABSL_HAVE_CONSTANT_EVALUATED
+ constexpr int64_t constant = NegateIfConstantEvaluated(42);
+ EXPECT_EQ(constant, -42);
+
+ int64_t now = absl::ToUnixSeconds(absl::Now());
+ int64_t not_constant = NegateIfConstantEvaluated(now);
+ EXPECT_EQ(not_constant, now);
+
+ static int64_t const_init = NegateIfConstantEvaluated(42);
+ EXPECT_EQ(const_init, -42);
+#else
+ GTEST_SKIP() << "absl::is_constant_evaluated is not defined";
+#endif // ABSL_HAVE_CONSTANT_EVALUATED
+}
+
} // namespace
diff --git a/absl/numeric/BUILD.bazel b/absl/numeric/BUILD.bazel
index ea587bf..41c015d 100644
--- a/absl/numeric/BUILD.bazel
+++ b/absl/numeric/BUILD.bazel
@@ -12,7 +12,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test")
load(
"//absl:copts/configure_copts.bzl",
"ABSL_DEFAULT_COPTS",
@@ -20,7 +19,14 @@
"ABSL_TEST_COPTS",
)
-package(default_visibility = ["//visibility:public"])
+package(
+ default_visibility = ["//visibility:public"],
+ features = [
+ "header_modules",
+ "layering_check",
+ "parse_headers",
+ ],
+)
licenses(["notice"])
@@ -38,6 +44,21 @@
],
)
+cc_binary(
+ name = "bits_benchmark",
+ testonly = True,
+ srcs = ["bits_benchmark.cc"],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ ":bits",
+ "//absl/base:core_headers",
+ "//absl/random",
+ "@com_github_google_benchmark//:benchmark_main",
+ "@com_google_googletest//:gtest",
+ ],
+)
+
cc_test(
name = "bits_test",
size = "small",
@@ -49,6 +70,7 @@
deps = [
":bits",
"//absl/random",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -82,9 +104,10 @@
deps = [
":int128",
"//absl/base",
- "//absl/base:core_headers",
"//absl/hash:hash_testing",
"//absl/meta:type_traits",
+ "//absl/strings",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -99,6 +122,7 @@
":int128",
"//absl/base:config",
"@com_github_google_benchmark//:benchmark_main",
+ "@com_google_googletest//:gtest",
],
)
diff --git a/absl/numeric/CMakeLists.txt b/absl/numeric/CMakeLists.txt
index 26df5cf..7181b91 100644
--- a/absl/numeric/CMakeLists.txt
+++ b/absl/numeric/CMakeLists.txt
@@ -70,9 +70,9 @@
DEPS
absl::int128
absl::base
- absl::core_headers
absl::hash_testing
absl::type_traits
+ absl::strings
GTest::gmock_main
)
diff --git a/absl/numeric/bits.h b/absl/numeric/bits.h
index 52013ad..c76454c 100644
--- a/absl/numeric/bits.h
+++ b/absl/numeric/bits.h
@@ -38,20 +38,30 @@
#include <limits>
#include <type_traits>
-#if (defined(__cpp_lib_int_pow2) && __cpp_lib_int_pow2 >= 202002L) || \
- (defined(__cpp_lib_bitops) && __cpp_lib_bitops >= 201907L)
+#include "absl/base/config.h"
+
+#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 202002L
#include <bit>
#endif
#include "absl/base/attributes.h"
-#include "absl/base/config.h"
#include "absl/numeric/internal/bits.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
-#if !(defined(__cpp_lib_bitops) && __cpp_lib_bitops >= 201907L)
-// rotating
+// https://github.com/llvm/llvm-project/issues/64544
+// libc++ had the wrong signature for std::rotl and std::rotr
+// prior to libc++ 18.0.
+//
+#if (defined(__cpp_lib_bitops) && __cpp_lib_bitops >= 201907L) && \
+ (!defined(_LIBCPP_VERSION) || _LIBCPP_VERSION >= 180000)
+using std::rotl;
+using std::rotr;
+
+#else
+
+// Rotating functions
template <class T>
ABSL_MUST_USE_RESULT constexpr
typename std::enable_if<std::is_unsigned<T>::value, T>::type
@@ -66,6 +76,22 @@
return numeric_internal::RotateRight(x, s);
}
+#endif
+
+// https://github.com/llvm/llvm-project/issues/64544
+// libc++ had the wrong signature for std::rotl and std::rotr
+// prior to libc++ 18.0.
+//
+#if (defined(__cpp_lib_bitops) && __cpp_lib_bitops >= 201907L)
+
+using std::countl_one;
+using std::countl_zero;
+using std::countr_one;
+using std::countr_zero;
+using std::popcount;
+
+#else
+
// Counting functions
//
// While these functions are typically constexpr, on some platforms, they may
@@ -107,19 +133,18 @@
popcount(T x) noexcept {
return numeric_internal::Popcount(x);
}
-#else // defined(__cpp_lib_bitops) && __cpp_lib_bitops >= 201907L
-
-using std::countl_one;
-using std::countl_zero;
-using std::countr_one;
-using std::countr_zero;
-using std::popcount;
-using std::rotl;
-using std::rotr;
#endif
-#if !(defined(__cpp_lib_int_pow2) && __cpp_lib_int_pow2 >= 202002L)
+#if (defined(__cpp_lib_int_pow2) && __cpp_lib_int_pow2 >= 202002L)
+
+using std::bit_ceil;
+using std::bit_floor;
+using std::bit_width;
+using std::has_single_bit;
+
+#else
+
// Returns: true if x is an integral power of two; false otherwise.
template <class T>
constexpr inline typename std::enable_if<std::is_unsigned<T>::value, bool>::type
@@ -131,7 +156,7 @@
// fractional part discarded.
template <class T>
ABSL_INTERNAL_CONSTEXPR_CLZ inline
- typename std::enable_if<std::is_unsigned<T>::value, T>::type
+ typename std::enable_if<std::is_unsigned<T>::value, int>::type
bit_width(T x) noexcept {
return std::numeric_limits<T>::digits - countl_zero(x);
}
@@ -162,12 +187,6 @@
return has_single_bit(x) ? T{1} << (bit_width(x) - 1)
: numeric_internal::BitCeilNonPowerOf2(x);
}
-#else // defined(__cpp_lib_int_pow2) && __cpp_lib_int_pow2 >= 202002L
-
-using std::bit_ceil;
-using std::bit_floor;
-using std::bit_width;
-using std::has_single_bit;
#endif
diff --git a/absl/numeric/bits_benchmark.cc b/absl/numeric/bits_benchmark.cc
new file mode 100644
index 0000000..2c89afd
--- /dev/null
+++ b/absl/numeric/bits_benchmark.cc
@@ -0,0 +1,73 @@
+// Copyright 2022 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <cstdint>
+#include <vector>
+
+#include "benchmark/benchmark.h"
+#include "absl/base/optimization.h"
+#include "absl/numeric/bits.h"
+#include "absl/random/random.h"
+
+namespace absl {
+namespace {
+
+template <typename T>
+static void BM_bit_width(benchmark::State& state) {
+ const auto count = static_cast<size_t>(state.range(0));
+
+ absl::BitGen rng;
+ std::vector<T> values;
+ values.reserve(count);
+ for (size_t i = 0; i < count; ++i) {
+ values.push_back(absl::Uniform<T>(rng, 0, std::numeric_limits<T>::max()));
+ }
+
+ while (state.KeepRunningBatch(static_cast<int64_t>(count))) {
+ for (size_t i = 0; i < count; ++i) {
+ benchmark::DoNotOptimize(absl::bit_width(values[i]));
+ }
+ }
+}
+BENCHMARK_TEMPLATE(BM_bit_width, uint8_t)->Range(1, 1 << 20);
+BENCHMARK_TEMPLATE(BM_bit_width, uint16_t)->Range(1, 1 << 20);
+BENCHMARK_TEMPLATE(BM_bit_width, uint32_t)->Range(1, 1 << 20);
+BENCHMARK_TEMPLATE(BM_bit_width, uint64_t)->Range(1, 1 << 20);
+
+template <typename T>
+static void BM_bit_width_nonzero(benchmark::State& state) {
+ const auto count = static_cast<size_t>(state.range(0));
+
+ absl::BitGen rng;
+ std::vector<T> values;
+ values.reserve(count);
+ for (size_t i = 0; i < count; ++i) {
+ values.push_back(absl::Uniform<T>(rng, 1, std::numeric_limits<T>::max()));
+ }
+
+ while (state.KeepRunningBatch(static_cast<int64_t>(count))) {
+ for (size_t i = 0; i < count; ++i) {
+ const T value = values[i];
+ ABSL_ASSUME(value > 0);
+ benchmark::DoNotOptimize(absl::bit_width(value));
+ }
+ }
+}
+BENCHMARK_TEMPLATE(BM_bit_width_nonzero, uint8_t)->Range(1, 1 << 20);
+BENCHMARK_TEMPLATE(BM_bit_width_nonzero, uint16_t)->Range(1, 1 << 20);
+BENCHMARK_TEMPLATE(BM_bit_width_nonzero, uint32_t)->Range(1, 1 << 20);
+BENCHMARK_TEMPLATE(BM_bit_width_nonzero, uint64_t)->Range(1, 1 << 20);
+
+} // namespace
+} // namespace absl
diff --git a/absl/numeric/bits_test.cc b/absl/numeric/bits_test.cc
index 7c942aa..14955eb 100644
--- a/absl/numeric/bits_test.cc
+++ b/absl/numeric/bits_test.cc
@@ -15,6 +15,7 @@
#include "absl/numeric/bits.h"
#include <limits>
+#include <type_traits>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
@@ -24,6 +25,73 @@
ABSL_NAMESPACE_BEGIN
namespace {
+template <typename IntT>
+class IntegerTypesTest : public ::testing::Test {};
+
+using OneByteIntegerTypes = ::testing::Types<
+ unsigned char,
+ uint8_t
+ >;
+
+TYPED_TEST_SUITE(IntegerTypesTest, OneByteIntegerTypes);
+
+TYPED_TEST(IntegerTypesTest, HandlesTypes) {
+ using UIntType = TypeParam;
+
+ EXPECT_EQ(rotl(UIntType{0x12}, 0), uint8_t{0x12});
+ EXPECT_EQ(rotr(UIntType{0x12}, -4), uint8_t{0x21});
+ static_assert(rotl(UIntType{0x12}, 0) == uint8_t{0x12}, "");
+
+ static_assert(rotr(UIntType{0x12}, 0) == uint8_t{0x12}, "");
+ EXPECT_EQ(rotr(UIntType{0x12}, 0), uint8_t{0x12});
+
+#if ABSL_INTERNAL_HAS_CONSTEXPR_CLZ
+ static_assert(countl_zero(UIntType{}) == 8, "");
+ static_assert(countl_zero(static_cast<UIntType>(-1)) == 0, "");
+
+ static_assert(countl_one(UIntType{}) == 0, "");
+ static_assert(countl_one(static_cast<UIntType>(-1)) == 8, "");
+
+ static_assert(countr_zero(UIntType{}) == 8, "");
+ static_assert(countr_zero(static_cast<UIntType>(-1)) == 0, "");
+
+ static_assert(countr_one(UIntType{}) == 0, "");
+ static_assert(countr_one(static_cast<UIntType>(-1)) == 8, "");
+
+ static_assert(popcount(UIntType{}) == 0, "");
+ static_assert(popcount(UIntType{1}) == 1, "");
+ static_assert(popcount(static_cast<UIntType>(-1)) == 8, "");
+
+ static_assert(bit_width(UIntType{}) == 0, "");
+ static_assert(bit_width(UIntType{1}) == 1, "");
+ static_assert(bit_width(UIntType{3}) == 2, "");
+ static_assert(bit_width(static_cast<UIntType>(-1)) == 8, "");
+#endif
+
+ EXPECT_EQ(countl_zero(UIntType{}), 8);
+ EXPECT_EQ(countl_zero(static_cast<UIntType>(-1)), 0);
+
+ EXPECT_EQ(countl_one(UIntType{}), 0);
+ EXPECT_EQ(countl_one(static_cast<UIntType>(-1)), 8);
+
+ EXPECT_EQ(countr_zero(UIntType{}), 8);
+ EXPECT_EQ(countr_zero(static_cast<UIntType>(-1)), 0);
+
+ EXPECT_EQ(countr_one(UIntType{}), 0);
+ EXPECT_EQ(countr_one(static_cast<UIntType>(-1)), 8);
+
+ EXPECT_EQ(popcount(UIntType{}), 0);
+ EXPECT_EQ(popcount(UIntType{1}), 1);
+
+ EXPECT_FALSE(has_single_bit(UIntType{}));
+ EXPECT_FALSE(has_single_bit(static_cast<UIntType>(-1)));
+
+ EXPECT_EQ(bit_width(UIntType{}), 0);
+ EXPECT_EQ(bit_width(UIntType{1}), 1);
+ EXPECT_EQ(bit_width(UIntType{3}), 2);
+ EXPECT_EQ(bit_width(static_cast<UIntType>(-1)), 8);
+}
+
TEST(Rotate, Left) {
static_assert(rotl(uint8_t{0x12}, 0) == uint8_t{0x12}, "");
static_assert(rotl(uint16_t{0x1234}, 0) == uint16_t{0x1234}, "");
diff --git a/absl/numeric/int128.cc b/absl/numeric/int128.cc
index 5160df7..5d6c68d 100644
--- a/absl/numeric/int128.cc
+++ b/absl/numeric/int128.cc
@@ -29,9 +29,6 @@
namespace absl {
ABSL_NAMESPACE_BEGIN
-ABSL_DLL const uint128 kuint128max = MakeUint128(
- std::numeric_limits<uint64_t>::max(), std::numeric_limits<uint64_t>::max());
-
namespace {
// Returns the 0-based position of the last set bit (i.e., most significant bit)
@@ -42,11 +39,11 @@
// Returns: 2
inline ABSL_ATTRIBUTE_ALWAYS_INLINE int Fls128(uint128 n) {
if (uint64_t hi = Uint128High64(n)) {
- ABSL_INTERNAL_ASSUME(hi != 0);
+ ABSL_ASSUME(hi != 0);
return 127 - countl_zero(hi);
}
const uint64_t low = Uint128Low64(n);
- ABSL_INTERNAL_ASSUME(low != 0);
+ ABSL_ASSUME(low != 0);
return 63 - countl_zero(low);
}
@@ -111,7 +108,7 @@
return MakeUint128(0, static_cast<uint64_t>(v));
}
-#if defined(__clang__) && !defined(__SSE3__)
+#if defined(__clang__) && (__clang_major__ < 9) && !defined(__SSE3__)
// Workaround for clang bug: https://bugs.llvm.org/show_bug.cgi?id=38289
// Casting from long double to uint64_t is miscompiled and drops bits.
// It is more work, so only use when we need the workaround.
@@ -131,35 +128,28 @@
return (static_cast<uint128>(w0) << 100) | (static_cast<uint128>(w1) << 50) |
static_cast<uint128>(w2);
}
-#endif // __clang__ && !__SSE3__
+#endif // __clang__ && (__clang_major__ < 9) && !__SSE3__
} // namespace
uint128::uint128(float v) : uint128(MakeUint128FromFloat(v)) {}
uint128::uint128(double v) : uint128(MakeUint128FromFloat(v)) {}
uint128::uint128(long double v) : uint128(MakeUint128FromFloat(v)) {}
+#if !defined(ABSL_HAVE_INTRINSIC_INT128)
uint128 operator/(uint128 lhs, uint128 rhs) {
-#if defined(ABSL_HAVE_INTRINSIC_INT128)
- return static_cast<unsigned __int128>(lhs) /
- static_cast<unsigned __int128>(rhs);
-#else // ABSL_HAVE_INTRINSIC_INT128
uint128 quotient = 0;
uint128 remainder = 0;
DivModImpl(lhs, rhs, "ient, &remainder);
return quotient;
-#endif // ABSL_HAVE_INTRINSIC_INT128
}
+
uint128 operator%(uint128 lhs, uint128 rhs) {
-#if defined(ABSL_HAVE_INTRINSIC_INT128)
- return static_cast<unsigned __int128>(lhs) %
- static_cast<unsigned __int128>(rhs);
-#else // ABSL_HAVE_INTRINSIC_INT128
uint128 quotient = 0;
uint128 remainder = 0;
DivModImpl(lhs, rhs, "ient, &remainder);
return remainder;
-#endif // ABSL_HAVE_INTRINSIC_INT128
}
+#endif // !defined(ABSL_HAVE_INTRINSIC_INT128)
namespace {
@@ -209,6 +199,10 @@
} // namespace
+std::string uint128::ToString() const {
+ return Uint128ToFormattedString(*this, std::ios_base::dec);
+}
+
std::ostream& operator<<(std::ostream& os, uint128 v) {
std::ios_base::fmtflags flags = os.flags();
std::string rep = Uint128ToFormattedString(v, flags);
@@ -216,15 +210,16 @@
// Add the requisite padding.
std::streamsize width = os.width(0);
if (static_cast<size_t>(width) > rep.size()) {
+ const size_t count = static_cast<size_t>(width) - rep.size();
std::ios::fmtflags adjustfield = flags & std::ios::adjustfield;
if (adjustfield == std::ios::left) {
- rep.append(width - rep.size(), os.fill());
+ rep.append(count, os.fill());
} else if (adjustfield == std::ios::internal &&
(flags & std::ios::showbase) &&
(flags & std::ios::basefield) == std::ios::hex && v != 0) {
- rep.insert(2, width - rep.size(), os.fill());
+ rep.insert(size_t{2}, count, os.fill());
} else {
- rep.insert(0, width - rep.size(), os.fill());
+ rep.insert(size_t{0}, count, os.fill());
}
}
@@ -291,6 +286,14 @@
}
#endif // ABSL_HAVE_INTRINSIC_INT128
+std::string int128::ToString() const {
+ std::string rep;
+ if (Int128High64(*this) < 0) rep = "-";
+ rep.append(Uint128ToFormattedString(UnsignedAbsoluteValue(*this),
+ std::ios_base::dec));
+ return rep;
+}
+
std::ostream& operator<<(std::ostream& os, int128 v) {
std::ios_base::fmtflags flags = os.flags();
std::string rep;
@@ -313,22 +316,23 @@
// Add the requisite padding.
std::streamsize width = os.width(0);
if (static_cast<size_t>(width) > rep.size()) {
+ const size_t count = static_cast<size_t>(width) - rep.size();
switch (flags & std::ios::adjustfield) {
case std::ios::left:
- rep.append(width - rep.size(), os.fill());
+ rep.append(count, os.fill());
break;
case std::ios::internal:
if (print_as_decimal && (rep[0] == '+' || rep[0] == '-')) {
- rep.insert(1, width - rep.size(), os.fill());
+ rep.insert(size_t{1}, count, os.fill());
} else if ((flags & std::ios::basefield) == std::ios::hex &&
(flags & std::ios::showbase) && v != 0) {
- rep.insert(2, width - rep.size(), os.fill());
+ rep.insert(size_t{2}, count, os.fill());
} else {
- rep.insert(0, width - rep.size(), os.fill());
+ rep.insert(size_t{0}, count, os.fill());
}
break;
default: // std::ios::right
- rep.insert(0, width - rep.size(), os.fill());
+ rep.insert(size_t{0}, count, os.fill());
break;
}
}
@@ -339,6 +343,7 @@
ABSL_NAMESPACE_END
} // namespace absl
+#ifdef ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL
namespace std {
constexpr bool numeric_limits<absl::uint128>::is_specialized;
constexpr bool numeric_limits<absl::uint128>::is_signed;
@@ -388,3 +393,4 @@
constexpr bool numeric_limits<absl::int128>::traps;
constexpr bool numeric_limits<absl::int128>::tinyness_before;
} // namespace std
+#endif
diff --git a/absl/numeric/int128.h b/absl/numeric/int128.h
index 198aa19..a17d0e1 100644
--- a/absl/numeric/int128.h
+++ b/absl/numeric/int128.h
@@ -18,6 +18,10 @@
// -----------------------------------------------------------------------------
//
// This header file defines 128-bit integer types, `uint128` and `int128`.
+//
+// TODO(absl-team): This module is inconsistent as many inline `uint128` methods
+// are defined in this file, while many inline `int128` methods are defined in
+// the `int128_*_intrinsic.inc` files.
#ifndef ABSL_NUMERIC_INT128_H_
#define ABSL_NUMERIC_INT128_H_
@@ -28,6 +32,7 @@
#include <cstring>
#include <iosfwd>
#include <limits>
+#include <string>
#include <utility>
#include "absl/base/config.h"
@@ -40,7 +45,7 @@
// builtin type. We need to make sure not to define operator wchar_t()
// alongside operator unsigned short() in these instances.
#define ABSL_INTERNAL_WCHAR_T __wchar_t
-#if defined(_M_X64)
+#if defined(_M_X64) && !defined(_M_ARM64EC)
#include <intrin.h>
#pragma intrinsic(_umul128)
#endif // defined(_M_X64)
@@ -115,8 +120,8 @@
#ifdef ABSL_HAVE_INTRINSIC_INT128
constexpr uint128(__int128 v); // NOLINT(runtime/explicit)
constexpr uint128(unsigned __int128 v); // NOLINT(runtime/explicit)
-#endif // ABSL_HAVE_INTRINSIC_INT128
- constexpr uint128(int128 v); // NOLINT(runtime/explicit)
+#endif // ABSL_HAVE_INTRINSIC_INT128
+ constexpr uint128(int128 v); // NOLINT(runtime/explicit)
explicit uint128(float v);
explicit uint128(double v);
explicit uint128(long double v);
@@ -213,9 +218,17 @@
return H::combine(std::move(h), Uint128High64(v), Uint128Low64(v));
}
+ // Support for absl::StrCat() etc.
+ template <typename Sink>
+ friend void AbslStringify(Sink& sink, uint128 v) {
+ sink.Append(v.ToString());
+ }
+
private:
constexpr uint128(uint64_t high, uint64_t low);
+ std::string ToString() const;
+
// TODO(strel) Update implementation to use __int128 once all users of
// uint128 are fixed to not depend on alignof(uint128) == 8. Also add
// alignas(16) to class definition to keep alignment consistent across
@@ -231,11 +244,6 @@
#endif // byte order
};
-// Prefer to use the constexpr `Uint128Max()`.
-//
-// TODO(absl-team) deprecate kuint128max once migration tool is released.
-ABSL_DLL extern const uint128 kuint128max;
-
// allow uint128 to be logged
std::ostream& operator<<(std::ostream& os, uint128 v);
@@ -282,9 +290,9 @@
#endif // ABSL_HAVE_INTRINSIC_INT128
static constexpr bool tinyness_before = false;
- static constexpr absl::uint128 (min)() { return 0; }
+ static constexpr absl::uint128(min)() { return 0; }
static constexpr absl::uint128 lowest() { return 0; }
- static constexpr absl::uint128 (max)() { return absl::Uint128Max(); }
+ static constexpr absl::uint128(max)() { return absl::Uint128Max(); }
static constexpr absl::uint128 epsilon() { return 0; }
static constexpr absl::uint128 round_error() { return 0; }
static constexpr absl::uint128 infinity() { return 0; }
@@ -450,9 +458,17 @@
return H::combine(std::move(h), Int128High64(v), Int128Low64(v));
}
+ // Support for absl::StrCat() etc.
+ template <typename Sink>
+ friend void AbslStringify(Sink& sink, int128 v) {
+ sink.Append(v.ToString());
+ }
+
private:
constexpr int128(int64_t high, uint64_t low);
+ std::string ToString() const;
+
#if defined(ABSL_HAVE_INTRINSIC_INT128)
__int128 v_;
#else // ABSL_HAVE_INTRINSIC_INT128
@@ -517,9 +533,9 @@
#endif // ABSL_HAVE_INTRINSIC_INT128
static constexpr bool tinyness_before = false;
- static constexpr absl::int128 (min)() { return absl::Int128Min(); }
+ static constexpr absl::int128(min)() { return absl::Int128Min(); }
static constexpr absl::int128 lowest() { return absl::Int128Min(); }
- static constexpr absl::int128 (max)() { return absl::Int128Max(); }
+ static constexpr absl::int128(max)() { return absl::Int128Max(); }
static constexpr absl::int128 epsilon() { return 0; }
static constexpr absl::int128 round_error() { return 0; }
static constexpr absl::int128 infinity() { return 0; }
@@ -557,9 +573,7 @@
}
// NOLINTNEXTLINE(runtime/int)
-inline uint128& uint128::operator=(long long v) {
- return *this = uint128(v);
-}
+inline uint128& uint128::operator=(long long v) { return *this = uint128(v); }
// NOLINTNEXTLINE(runtime/int)
inline uint128& uint128::operator=(unsigned long long v) {
@@ -567,25 +581,21 @@
}
#ifdef ABSL_HAVE_INTRINSIC_INT128
-inline uint128& uint128::operator=(__int128 v) {
- return *this = uint128(v);
-}
+inline uint128& uint128::operator=(__int128 v) { return *this = uint128(v); }
inline uint128& uint128::operator=(unsigned __int128 v) {
return *this = uint128(v);
}
#endif // ABSL_HAVE_INTRINSIC_INT128
-inline uint128& uint128::operator=(int128 v) {
- return *this = uint128(v);
-}
+inline uint128& uint128::operator=(int128 v) { return *this = uint128(v); }
// Arithmetic operators.
-uint128 operator<<(uint128 lhs, int amount);
-uint128 operator>>(uint128 lhs, int amount);
-uint128 operator+(uint128 lhs, uint128 rhs);
-uint128 operator-(uint128 lhs, uint128 rhs);
+constexpr uint128 operator<<(uint128 lhs, int amount);
+constexpr uint128 operator>>(uint128 lhs, int amount);
+constexpr uint128 operator+(uint128 lhs, uint128 rhs);
+constexpr uint128 operator-(uint128 lhs, uint128 rhs);
uint128 operator*(uint128 lhs, uint128 rhs);
uint128 operator/(uint128 lhs, uint128 rhs);
uint128 operator%(uint128 lhs, uint128 rhs);
@@ -633,8 +643,7 @@
#if defined(ABSL_IS_LITTLE_ENDIAN)
-constexpr uint128::uint128(uint64_t high, uint64_t low)
- : lo_{low}, hi_{high} {}
+constexpr uint128::uint128(uint64_t high, uint64_t low) : lo_{low}, hi_{high} {}
constexpr uint128::uint128(int v)
: lo_{static_cast<uint64_t>(v)},
@@ -666,8 +675,7 @@
#elif defined(ABSL_IS_BIG_ENDIAN)
-constexpr uint128::uint128(uint64_t high, uint64_t low)
- : hi_{high}, lo_{low} {}
+constexpr uint128::uint128(uint64_t high, uint64_t low) : hi_{high}, lo_{low} {}
constexpr uint128::uint128(int v)
: hi_{v < 0 ? (std::numeric_limits<uint64_t>::max)() : 0},
@@ -782,16 +790,19 @@
// Comparison operators.
-inline bool operator==(uint128 lhs, uint128 rhs) {
+constexpr bool operator==(uint128 lhs, uint128 rhs) {
+#if defined(ABSL_HAVE_INTRINSIC_INT128)
+ return static_cast<unsigned __int128>(lhs) ==
+ static_cast<unsigned __int128>(rhs);
+#else
return (Uint128Low64(lhs) == Uint128Low64(rhs) &&
Uint128High64(lhs) == Uint128High64(rhs));
+#endif
}
-inline bool operator!=(uint128 lhs, uint128 rhs) {
- return !(lhs == rhs);
-}
+constexpr bool operator!=(uint128 lhs, uint128 rhs) { return !(lhs == rhs); }
-inline bool operator<(uint128 lhs, uint128 rhs) {
+constexpr bool operator<(uint128 lhs, uint128 rhs) {
#ifdef ABSL_HAVE_INTRINSIC_INT128
return static_cast<unsigned __int128>(lhs) <
static_cast<unsigned __int128>(rhs);
@@ -802,126 +813,165 @@
#endif
}
-inline bool operator>(uint128 lhs, uint128 rhs) { return rhs < lhs; }
+constexpr bool operator>(uint128 lhs, uint128 rhs) { return rhs < lhs; }
-inline bool operator<=(uint128 lhs, uint128 rhs) { return !(rhs < lhs); }
+constexpr bool operator<=(uint128 lhs, uint128 rhs) { return !(rhs < lhs); }
-inline bool operator>=(uint128 lhs, uint128 rhs) { return !(lhs < rhs); }
+constexpr bool operator>=(uint128 lhs, uint128 rhs) { return !(lhs < rhs); }
// Unary operators.
-constexpr inline uint128 operator+(uint128 val) {
- return val;
-}
+constexpr inline uint128 operator+(uint128 val) { return val; }
-constexpr inline int128 operator+(int128 val) {
- return val;
-}
+constexpr inline int128 operator+(int128 val) { return val; }
-inline uint128 operator-(uint128 val) {
- uint64_t hi = ~Uint128High64(val);
- uint64_t lo = ~Uint128Low64(val) + 1;
- if (lo == 0) ++hi; // carry
- return MakeUint128(hi, lo);
+constexpr uint128 operator-(uint128 val) {
+#if defined(ABSL_HAVE_INTRINSIC_INT128)
+ return -static_cast<unsigned __int128>(val);
+#else
+ return MakeUint128(
+ ~Uint128High64(val) + static_cast<unsigned long>(Uint128Low64(val) == 0),
+ ~Uint128Low64(val) + 1);
+#endif
}
constexpr inline bool operator!(uint128 val) {
+#if defined(ABSL_HAVE_INTRINSIC_INT128)
+ return !static_cast<unsigned __int128>(val);
+#else
return !Uint128High64(val) && !Uint128Low64(val);
+#endif
}
// Logical operators.
constexpr inline uint128 operator~(uint128 val) {
+#if defined(ABSL_HAVE_INTRINSIC_INT128)
+ return ~static_cast<unsigned __int128>(val);
+#else
return MakeUint128(~Uint128High64(val), ~Uint128Low64(val));
+#endif
}
constexpr inline uint128 operator|(uint128 lhs, uint128 rhs) {
+#if defined(ABSL_HAVE_INTRINSIC_INT128)
+ return static_cast<unsigned __int128>(lhs) |
+ static_cast<unsigned __int128>(rhs);
+#else
return MakeUint128(Uint128High64(lhs) | Uint128High64(rhs),
- Uint128Low64(lhs) | Uint128Low64(rhs));
+ Uint128Low64(lhs) | Uint128Low64(rhs));
+#endif
}
constexpr inline uint128 operator&(uint128 lhs, uint128 rhs) {
+#if defined(ABSL_HAVE_INTRINSIC_INT128)
+ return static_cast<unsigned __int128>(lhs) &
+ static_cast<unsigned __int128>(rhs);
+#else
return MakeUint128(Uint128High64(lhs) & Uint128High64(rhs),
- Uint128Low64(lhs) & Uint128Low64(rhs));
+ Uint128Low64(lhs) & Uint128Low64(rhs));
+#endif
}
constexpr inline uint128 operator^(uint128 lhs, uint128 rhs) {
+#if defined(ABSL_HAVE_INTRINSIC_INT128)
+ return static_cast<unsigned __int128>(lhs) ^
+ static_cast<unsigned __int128>(rhs);
+#else
return MakeUint128(Uint128High64(lhs) ^ Uint128High64(rhs),
- Uint128Low64(lhs) ^ Uint128Low64(rhs));
+ Uint128Low64(lhs) ^ Uint128Low64(rhs));
+#endif
}
inline uint128& uint128::operator|=(uint128 other) {
- hi_ |= other.hi_;
- lo_ |= other.lo_;
+ *this = *this | other;
return *this;
}
inline uint128& uint128::operator&=(uint128 other) {
- hi_ &= other.hi_;
- lo_ &= other.lo_;
+ *this = *this & other;
return *this;
}
inline uint128& uint128::operator^=(uint128 other) {
- hi_ ^= other.hi_;
- lo_ ^= other.lo_;
+ *this = *this ^ other;
return *this;
}
// Arithmetic operators.
-inline uint128 operator<<(uint128 lhs, int amount) {
+constexpr uint128 operator<<(uint128 lhs, int amount) {
#ifdef ABSL_HAVE_INTRINSIC_INT128
return static_cast<unsigned __int128>(lhs) << amount;
#else
// uint64_t shifts of >= 64 are undefined, so we will need some
// special-casing.
- if (amount < 64) {
- if (amount != 0) {
- return MakeUint128(
- (Uint128High64(lhs) << amount) | (Uint128Low64(lhs) >> (64 - amount)),
- Uint128Low64(lhs) << amount);
- }
- return lhs;
- }
- return MakeUint128(Uint128Low64(lhs) << (amount - 64), 0);
+ return amount >= 64 ? MakeUint128(Uint128Low64(lhs) << (amount - 64), 0)
+ : amount == 0 ? lhs
+ : MakeUint128((Uint128High64(lhs) << amount) |
+ (Uint128Low64(lhs) >> (64 - amount)),
+ Uint128Low64(lhs) << amount);
#endif
}
-inline uint128 operator>>(uint128 lhs, int amount) {
+constexpr uint128 operator>>(uint128 lhs, int amount) {
#ifdef ABSL_HAVE_INTRINSIC_INT128
return static_cast<unsigned __int128>(lhs) >> amount;
#else
// uint64_t shifts of >= 64 are undefined, so we will need some
// special-casing.
- if (amount < 64) {
- if (amount != 0) {
- return MakeUint128(Uint128High64(lhs) >> amount,
- (Uint128Low64(lhs) >> amount) |
- (Uint128High64(lhs) << (64 - amount)));
- }
- return lhs;
- }
- return MakeUint128(0, Uint128High64(lhs) >> (amount - 64));
+ return amount >= 64 ? MakeUint128(0, Uint128High64(lhs) >> (amount - 64))
+ : amount == 0 ? lhs
+ : MakeUint128(Uint128High64(lhs) >> amount,
+ (Uint128Low64(lhs) >> amount) |
+ (Uint128High64(lhs) << (64 - amount)));
#endif
}
-inline uint128 operator+(uint128 lhs, uint128 rhs) {
- uint128 result = MakeUint128(Uint128High64(lhs) + Uint128High64(rhs),
- Uint128Low64(lhs) + Uint128Low64(rhs));
- if (Uint128Low64(result) < Uint128Low64(lhs)) { // check for carry
- return MakeUint128(Uint128High64(result) + 1, Uint128Low64(result));
- }
- return result;
+#if !defined(ABSL_HAVE_INTRINSIC_INT128)
+namespace int128_internal {
+constexpr uint128 AddResult(uint128 result, uint128 lhs) {
+ // check for carry
+ return (Uint128Low64(result) < Uint128Low64(lhs))
+ ? MakeUint128(Uint128High64(result) + 1, Uint128Low64(result))
+ : result;
+}
+} // namespace int128_internal
+#endif
+
+constexpr uint128 operator+(uint128 lhs, uint128 rhs) {
+#if defined(ABSL_HAVE_INTRINSIC_INT128)
+ return static_cast<unsigned __int128>(lhs) +
+ static_cast<unsigned __int128>(rhs);
+#else
+ return int128_internal::AddResult(
+ MakeUint128(Uint128High64(lhs) + Uint128High64(rhs),
+ Uint128Low64(lhs) + Uint128Low64(rhs)),
+ lhs);
+#endif
}
-inline uint128 operator-(uint128 lhs, uint128 rhs) {
- uint128 result = MakeUint128(Uint128High64(lhs) - Uint128High64(rhs),
- Uint128Low64(lhs) - Uint128Low64(rhs));
- if (Uint128Low64(lhs) < Uint128Low64(rhs)) { // check for carry
- return MakeUint128(Uint128High64(result) - 1, Uint128Low64(result));
- }
- return result;
+#if !defined(ABSL_HAVE_INTRINSIC_INT128)
+namespace int128_internal {
+constexpr uint128 SubstructResult(uint128 result, uint128 lhs, uint128 rhs) {
+ // check for carry
+ return (Uint128Low64(lhs) < Uint128Low64(rhs))
+ ? MakeUint128(Uint128High64(result) - 1, Uint128Low64(result))
+ : result;
+}
+} // namespace int128_internal
+#endif
+
+constexpr uint128 operator-(uint128 lhs, uint128 rhs) {
+#if defined(ABSL_HAVE_INTRINSIC_INT128)
+ return static_cast<unsigned __int128>(lhs) -
+ static_cast<unsigned __int128>(rhs);
+#else
+ return int128_internal::SubstructResult(
+ MakeUint128(Uint128High64(lhs) - Uint128High64(rhs),
+ Uint128Low64(lhs) - Uint128Low64(rhs)),
+ lhs, rhs);
+#endif
}
inline uint128 operator*(uint128 lhs, uint128 rhs) {
@@ -930,7 +980,7 @@
// can be used for uint128 storage.
return static_cast<unsigned __int128>(lhs) *
static_cast<unsigned __int128>(rhs);
-#elif defined(_MSC_VER) && defined(_M_X64)
+#elif 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) +
@@ -951,6 +1001,18 @@
#endif // ABSL_HAVE_INTRINSIC128
}
+#if defined(ABSL_HAVE_INTRINSIC_INT128)
+inline uint128 operator/(uint128 lhs, uint128 rhs) {
+ return static_cast<unsigned __int128>(lhs) /
+ static_cast<unsigned __int128>(rhs);
+}
+
+inline uint128 operator%(uint128 lhs, uint128 rhs) {
+ return static_cast<unsigned __int128>(lhs) %
+ static_cast<unsigned __int128>(rhs);
+}
+#endif
+
// Increment/decrement operators.
inline uint128 uint128::operator++(int) {
@@ -980,27 +1042,19 @@
}
// Assignment from integer types.
-inline int128& int128::operator=(int v) {
- return *this = int128(v);
-}
+inline int128& int128::operator=(int v) { return *this = int128(v); }
-inline int128& int128::operator=(unsigned int v) {
- return *this = int128(v);
-}
+inline int128& int128::operator=(unsigned int v) { return *this = int128(v); }
inline int128& int128::operator=(long v) { // NOLINT(runtime/int)
return *this = int128(v);
}
// NOLINTNEXTLINE(runtime/int)
-inline int128& int128::operator=(unsigned long v) {
- return *this = int128(v);
-}
+inline int128& int128::operator=(unsigned long v) { return *this = int128(v); }
// NOLINTNEXTLINE(runtime/int)
-inline int128& int128::operator=(long long v) {
- return *this = int128(v);
-}
+inline int128& int128::operator=(long long v) { return *this = int128(v); }
// NOLINTNEXTLINE(runtime/int)
inline int128& int128::operator=(unsigned long long v) {
@@ -1008,17 +1062,17 @@
}
// Arithmetic operators.
-
-int128 operator+(int128 lhs, int128 rhs);
-int128 operator-(int128 lhs, int128 rhs);
+constexpr int128 operator-(int128 v);
+constexpr int128 operator+(int128 lhs, int128 rhs);
+constexpr int128 operator-(int128 lhs, int128 rhs);
int128 operator*(int128 lhs, int128 rhs);
int128 operator/(int128 lhs, int128 rhs);
int128 operator%(int128 lhs, int128 rhs);
-int128 operator|(int128 lhs, int128 rhs);
-int128 operator&(int128 lhs, int128 rhs);
-int128 operator^(int128 lhs, int128 rhs);
-int128 operator<<(int128 lhs, int amount);
-int128 operator>>(int128 lhs, int amount);
+constexpr int128 operator|(int128 lhs, int128 rhs);
+constexpr int128 operator&(int128 lhs, int128 rhs);
+constexpr int128 operator^(int128 lhs, int128 rhs);
+constexpr int128 operator<<(int128 lhs, int amount);
+constexpr int128 operator>>(int128 lhs, int amount);
inline int128& int128::operator+=(int128 other) {
*this = *this + other;
@@ -1070,6 +1124,9 @@
return *this;
}
+// Forward declaration for comparison operators.
+constexpr bool operator!=(int128 lhs, int128 rhs);
+
namespace int128_internal {
// Casts from unsigned to signed while preserving the underlying binary
diff --git a/absl/numeric/int128_have_intrinsic.inc b/absl/numeric/int128_have_intrinsic.inc
index d6c76dd..6f1ac64 100644
--- a/absl/numeric/int128_have_intrinsic.inc
+++ b/absl/numeric/int128_have_intrinsic.inc
@@ -155,16 +155,13 @@
#if defined(__clang__) && !defined(__ppc64__)
inline int128::operator float() const { return static_cast<float>(v_); }
-inline int128::operator double () const { return static_cast<double>(v_); }
+inline int128::operator double() const { return static_cast<double>(v_); }
inline int128::operator long double() const {
return static_cast<long double>(v_);
}
#else // Clang on PowerPC
-// Forward declaration for conversion operators to floating point types.
-int128 operator-(int128 v);
-bool operator!=(int128 lhs, int128 rhs);
inline int128::operator float() const {
// We must convert the absolute value and then negate as needed, because
@@ -199,51 +196,45 @@
// Comparison operators.
-inline bool operator==(int128 lhs, int128 rhs) {
+constexpr bool operator==(int128 lhs, int128 rhs) {
return static_cast<__int128>(lhs) == static_cast<__int128>(rhs);
}
-inline bool operator!=(int128 lhs, int128 rhs) {
+constexpr bool operator!=(int128 lhs, int128 rhs) {
return static_cast<__int128>(lhs) != static_cast<__int128>(rhs);
}
-inline bool operator<(int128 lhs, int128 rhs) {
+constexpr bool operator<(int128 lhs, int128 rhs) {
return static_cast<__int128>(lhs) < static_cast<__int128>(rhs);
}
-inline bool operator>(int128 lhs, int128 rhs) {
+constexpr bool operator>(int128 lhs, int128 rhs) {
return static_cast<__int128>(lhs) > static_cast<__int128>(rhs);
}
-inline bool operator<=(int128 lhs, int128 rhs) {
+constexpr bool operator<=(int128 lhs, int128 rhs) {
return static_cast<__int128>(lhs) <= static_cast<__int128>(rhs);
}
-inline bool operator>=(int128 lhs, int128 rhs) {
+constexpr bool operator>=(int128 lhs, int128 rhs) {
return static_cast<__int128>(lhs) >= static_cast<__int128>(rhs);
}
// Unary operators.
-inline int128 operator-(int128 v) {
- return -static_cast<__int128>(v);
-}
+constexpr int128 operator-(int128 v) { return -static_cast<__int128>(v); }
-inline bool operator!(int128 v) {
- return !static_cast<__int128>(v);
-}
+constexpr bool operator!(int128 v) { return !static_cast<__int128>(v); }
-inline int128 operator~(int128 val) {
- return ~static_cast<__int128>(val);
-}
+constexpr int128 operator~(int128 val) { return ~static_cast<__int128>(val); }
// Arithmetic operators.
-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);
}
@@ -281,22 +272,22 @@
return *this;
}
-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);
}
-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, int amount) {
+constexpr int128 operator<<(int128 lhs, int amount) {
return static_cast<__int128>(lhs) << amount;
}
-inline int128 operator>>(int128 lhs, int amount) {
+constexpr int128 operator>>(int128 lhs, int amount) {
return static_cast<__int128>(lhs) >> amount;
}
diff --git a/absl/numeric/int128_no_intrinsic.inc b/absl/numeric/int128_no_intrinsic.inc
index c753771..6f5d837 100644
--- a/absl/numeric/int128_no_intrinsic.inc
+++ b/absl/numeric/int128_no_intrinsic.inc
@@ -23,8 +23,7 @@
#if defined(ABSL_IS_LITTLE_ENDIAN)
-constexpr int128::int128(int64_t high, uint64_t low) :
- lo_(low), hi_(high) {}
+constexpr int128::int128(int64_t high, uint64_t low) : lo_(low), hi_(high) {}
constexpr int128::int128(int v)
: lo_{static_cast<uint64_t>(v)}, hi_{v < 0 ? ~int64_t{0} : 0} {}
@@ -44,8 +43,7 @@
#elif defined(ABSL_IS_BIG_ENDIAN)
-constexpr int128::int128(int64_t high, uint64_t low) :
- hi_{high}, lo_{low} {}
+constexpr int128::int128(int64_t high, uint64_t low) : hi_{high}, lo_{low} {}
constexpr int128::int128(int v)
: hi_{v < 0 ? ~int64_t{0} : 0}, lo_{static_cast<uint64_t>(v)} {}
@@ -134,10 +132,6 @@
return static_cast<unsigned long long>(lo_); // NOLINT(runtime/int)
}
-// Forward declaration for conversion operators to floating point types.
-int128 operator-(int128 v);
-bool operator!=(int128 lhs, int128 rhs);
-
inline 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
@@ -169,76 +163,80 @@
// Comparison operators.
-inline bool operator==(int128 lhs, int128 rhs) {
+constexpr bool operator==(int128 lhs, int128 rhs) {
return (Int128Low64(lhs) == Int128Low64(rhs) &&
Int128High64(lhs) == Int128High64(rhs));
}
-inline bool operator!=(int128 lhs, int128 rhs) {
- return !(lhs == rhs);
-}
+constexpr bool operator!=(int128 lhs, int128 rhs) { return !(lhs == rhs); }
-inline bool operator<(int128 lhs, int128 rhs) {
+constexpr bool operator<(int128 lhs, int128 rhs) {
return (Int128High64(lhs) == Int128High64(rhs))
? (Int128Low64(lhs) < Int128Low64(rhs))
: (Int128High64(lhs) < Int128High64(rhs));
}
-inline bool operator>(int128 lhs, int128 rhs) {
+constexpr bool operator>(int128 lhs, int128 rhs) {
return (Int128High64(lhs) == Int128High64(rhs))
? (Int128Low64(lhs) > Int128Low64(rhs))
: (Int128High64(lhs) > Int128High64(rhs));
}
-inline bool operator<=(int128 lhs, int128 rhs) {
- return !(lhs > rhs);
-}
+constexpr bool operator<=(int128 lhs, int128 rhs) { return !(lhs > rhs); }
-inline bool operator>=(int128 lhs, int128 rhs) {
- return !(lhs < rhs);
-}
+constexpr bool operator>=(int128 lhs, int128 rhs) { return !(lhs < rhs); }
// Unary operators.
-inline int128 operator-(int128 v) {
- int64_t hi = ~Int128High64(v);
- uint64_t lo = ~Int128Low64(v) + 1;
- if (lo == 0) ++hi; // carry
- return MakeInt128(hi, lo);
+constexpr int128 operator-(int128 v) {
+ return MakeInt128(~Int128High64(v) + (Int128Low64(v) == 0),
+ ~Int128Low64(v) + 1);
}
-inline bool operator!(int128 v) {
+constexpr bool operator!(int128 v) {
return !Int128Low64(v) && !Int128High64(v);
}
-inline int128 operator~(int128 val) {
+constexpr int128 operator~(int128 val) {
return MakeInt128(~Int128High64(val), ~Int128Low64(val));
}
// Arithmetic operators.
-inline int128 operator+(int128 lhs, int128 rhs) {
- int128 result = MakeInt128(Int128High64(lhs) + Int128High64(rhs),
- Int128Low64(lhs) + Int128Low64(rhs));
- if (Int128Low64(result) < Int128Low64(lhs)) { // check for carry
- return MakeInt128(Int128High64(result) + 1, Int128Low64(result));
- }
- return result;
+namespace int128_internal {
+constexpr int128 SignedAddResult(int128 result, int128 lhs) {
+ // check for carry
+ return (Int128Low64(result) < Int128Low64(lhs))
+ ? MakeInt128(Int128High64(result) + 1, Int128Low64(result))
+ : result;
+}
+} // namespace int128_internal
+constexpr int128 operator+(int128 lhs, int128 rhs) {
+ return int128_internal::SignedAddResult(
+ MakeInt128(Int128High64(lhs) + Int128High64(rhs),
+ Int128Low64(lhs) + Int128Low64(rhs)),
+ lhs);
}
-inline int128 operator-(int128 lhs, int128 rhs) {
- int128 result = MakeInt128(Int128High64(lhs) - Int128High64(rhs),
- Int128Low64(lhs) - Int128Low64(rhs));
- if (Int128Low64(lhs) < Int128Low64(rhs)) { // check for carry
- return MakeInt128(Int128High64(result) - 1, Int128Low64(result));
- }
- return result;
+namespace int128_internal {
+constexpr int128 SignedSubstructResult(int128 result, int128 lhs, int128 rhs) {
+ // check for carry
+ return (Int128Low64(lhs) < Int128Low64(rhs))
+ ? MakeInt128(Int128High64(result) - 1, Int128Low64(result))
+ : result;
+}
+} // namespace int128_internal
+constexpr int128 operator-(int128 lhs, int128 rhs) {
+ return int128_internal::SignedSubstructResult(
+ MakeInt128(Int128High64(lhs) - Int128High64(rhs),
+ Int128Low64(lhs) - Int128Low64(rhs)),
+ lhs, rhs);
}
inline int128 operator*(int128 lhs, int128 rhs) {
- uint128 result = uint128(lhs) * rhs;
- return MakeInt128(int128_internal::BitCastToSigned(Uint128High64(result)),
- Uint128Low64(result));
+ return MakeInt128(
+ int128_internal::BitCastToSigned(Uint128High64(uint128(lhs) * rhs)),
+ Uint128Low64(uint128(lhs) * rhs));
}
inline int128 int128::operator++(int) {
@@ -263,46 +261,68 @@
return *this;
}
-inline int128 operator|(int128 lhs, int128 rhs) {
+constexpr int128 operator|(int128 lhs, int128 rhs) {
return MakeInt128(Int128High64(lhs) | Int128High64(rhs),
Int128Low64(lhs) | Int128Low64(rhs));
}
-inline int128 operator&(int128 lhs, int128 rhs) {
+constexpr int128 operator&(int128 lhs, int128 rhs) {
return MakeInt128(Int128High64(lhs) & Int128High64(rhs),
Int128Low64(lhs) & Int128Low64(rhs));
}
-inline int128 operator^(int128 lhs, int128 rhs) {
+constexpr int128 operator^(int128 lhs, int128 rhs) {
return MakeInt128(Int128High64(lhs) ^ Int128High64(rhs),
Int128Low64(lhs) ^ Int128Low64(rhs));
}
-inline int128 operator<<(int128 lhs, int amount) {
- // uint64_t shifts of >= 64 are undefined, so we need some special-casing.
- if (amount < 64) {
- if (amount != 0) {
- return MakeInt128(
- (Int128High64(lhs) << amount) |
- static_cast<int64_t>(Int128Low64(lhs) >> (64 - amount)),
- Int128Low64(lhs) << amount);
- }
+constexpr int128 operator<<(int128 lhs, int amount) {
+ // int64_t shifts of >= 63 are undefined, so we need some special-casing.
+ assert(amount >= 0 && amount < 127);
+ if (amount <= 0) {
return lhs;
+ } else if (amount < 63) {
+ return MakeInt128(
+ (Int128High64(lhs) << amount) |
+ static_cast<int64_t>(Int128Low64(lhs) >> (64 - amount)),
+ Int128Low64(lhs) << amount);
+ } else if (amount == 63) {
+ return MakeInt128(((Int128High64(lhs) << 32) << 31) |
+ static_cast<int64_t>(Int128Low64(lhs) >> 1),
+ (Int128Low64(lhs) << 32) << 31);
+ } else if (amount == 127) {
+ return MakeInt128(static_cast<int64_t>(Int128Low64(lhs) << 63), 0);
+ } else if (amount > 127) {
+ return MakeInt128(0, 0);
+ } else {
+ // amount >= 64 && amount < 127
+ return MakeInt128(static_cast<int64_t>(Int128Low64(lhs) << (amount - 64)),
+ 0);
}
- return MakeInt128(static_cast<int64_t>(Int128Low64(lhs) << (amount - 64)), 0);
}
-inline int128 operator>>(int128 lhs, int amount) {
- // uint64_t shifts of >= 64 are undefined, so we need some special-casing.
- if (amount < 64) {
- if (amount != 0) {
- return MakeInt128(
- Int128High64(lhs) >> amount,
- (Int128Low64(lhs) >> amount) |
- (static_cast<uint64_t>(Int128High64(lhs)) << (64 - amount)));
- }
+constexpr int128 operator>>(int128 lhs, int amount) {
+ // int64_t shifts of >= 63 are undefined, so we need some special-casing.
+ assert(amount >= 0 && amount < 127);
+ if (amount <= 0) {
return lhs;
+ } else if (amount < 63) {
+ return MakeInt128(
+ Int128High64(lhs) >> amount,
+ Int128Low64(lhs) >> amount | static_cast<uint64_t>(Int128High64(lhs))
+ << (64 - amount));
+ } else if (amount == 63) {
+ return MakeInt128((Int128High64(lhs) >> 32) >> 31,
+ static_cast<uint64_t>(Int128High64(lhs) << 1) |
+ (Int128Low64(lhs) >> 32) >> 31);
+
+ } else if (amount >= 127) {
+ return MakeInt128((Int128High64(lhs) >> 32) >> 31,
+ static_cast<uint64_t>((Int128High64(lhs) >> 32) >> 31));
+ } else {
+ // amount >= 64 && amount < 127
+ return MakeInt128(
+ (Int128High64(lhs) >> 32) >> 31,
+ static_cast<uint64_t>(Int128High64(lhs) >> (amount - 64)));
}
- return MakeInt128(0,
- static_cast<uint64_t>(Int128High64(lhs) >> (amount - 64)));
}
diff --git a/absl/numeric/int128_stream_test.cc b/absl/numeric/int128_stream_test.cc
index 479ad66..1b058e1 100644
--- a/absl/numeric/int128_stream_test.cc
+++ b/absl/numeric/int128_stream_test.cc
@@ -18,6 +18,7 @@
#include <string>
#include "gtest/gtest.h"
+#include "absl/strings/str_cat.h"
namespace {
@@ -76,16 +77,6 @@
return msg.str();
}
-void CheckUint128Case(const Uint128TestCase& test_case) {
- std::ostringstream os;
- os.flags(test_case.flags);
- os.width(test_case.width);
- os.fill(kFill);
- os << test_case.value;
- SCOPED_TRACE(StreamFormatToString(test_case.flags, test_case.width));
- EXPECT_EQ(test_case.expected, os.str());
-}
-
constexpr std::ios::fmtflags kDec = std::ios::dec;
constexpr std::ios::fmtflags kOct = std::ios::oct;
constexpr std::ios::fmtflags kHex = std::ios::hex;
@@ -96,6 +87,19 @@
constexpr std::ios::fmtflags kBase = std::ios::showbase;
constexpr std::ios::fmtflags kPos = std::ios::showpos;
+void CheckUint128Case(const Uint128TestCase& test_case) {
+ if (test_case.flags == kDec && test_case.width == 0) {
+ EXPECT_EQ(absl::StrCat(test_case.value), test_case.expected);
+ }
+ std::ostringstream os;
+ os.flags(test_case.flags);
+ os.width(test_case.width);
+ os.fill(kFill);
+ os << test_case.value;
+ SCOPED_TRACE(StreamFormatToString(test_case.flags, test_case.width));
+ EXPECT_EQ(os.str(), test_case.expected);
+}
+
TEST(Uint128, OStreamValueTest) {
CheckUint128Case({1, kDec, /*width = */ 0, "1"});
CheckUint128Case({1, kOct, /*width = */ 0, "1"});
@@ -131,11 +135,11 @@
"2000000000000000000000000000000000000000000"});
CheckUint128Case({absl::MakeUint128(0x8000000000000000, 0), kHex,
/*width = */ 0, "80000000000000000000000000000000"});
- CheckUint128Case({absl::kuint128max, kDec, /*width = */ 0,
+ CheckUint128Case({absl::Uint128Max(), kDec, /*width = */ 0,
"340282366920938463463374607431768211455"});
- CheckUint128Case({absl::kuint128max, kOct, /*width = */ 0,
+ CheckUint128Case({absl::Uint128Max(), kOct, /*width = */ 0,
"3777777777777777777777777777777777777777777"});
- CheckUint128Case({absl::kuint128max, kHex, /*width = */ 0,
+ CheckUint128Case({absl::Uint128Max(), kHex, /*width = */ 0,
"ffffffffffffffffffffffffffffffff"});
}
@@ -155,13 +159,16 @@
};
void CheckInt128Case(const Int128TestCase& test_case) {
+ if (test_case.flags == kDec && test_case.width == 0) {
+ EXPECT_EQ(absl::StrCat(test_case.value), test_case.expected);
+ }
std::ostringstream os;
os.flags(test_case.flags);
os.width(test_case.width);
os.fill(kFill);
os << test_case.value;
SCOPED_TRACE(StreamFormatToString(test_case.flags, test_case.width));
- EXPECT_EQ(test_case.expected, os.str());
+ EXPECT_EQ(os.str(), test_case.expected);
}
TEST(Int128, OStreamValueTest) {
@@ -194,35 +201,33 @@
{absl::MakeInt128(1, 0), kHex, /*width = */ 0, "10000000000000000"});
CheckInt128Case({absl::MakeInt128(std::numeric_limits<int64_t>::max(),
std::numeric_limits<uint64_t>::max()),
- std::ios::dec, /*width = */ 0,
+ kDec, /*width = */ 0,
"170141183460469231731687303715884105727"});
CheckInt128Case({absl::MakeInt128(std::numeric_limits<int64_t>::max(),
std::numeric_limits<uint64_t>::max()),
- std::ios::oct, /*width = */ 0,
+ kOct, /*width = */ 0,
"1777777777777777777777777777777777777777777"});
CheckInt128Case({absl::MakeInt128(std::numeric_limits<int64_t>::max(),
std::numeric_limits<uint64_t>::max()),
- std::ios::hex, /*width = */ 0,
- "7fffffffffffffffffffffffffffffff"});
+ kHex, /*width = */ 0, "7fffffffffffffffffffffffffffffff"});
CheckInt128Case({absl::MakeInt128(std::numeric_limits<int64_t>::min(), 0),
- std::ios::dec, /*width = */ 0,
+ kDec, /*width = */ 0,
"-170141183460469231731687303715884105728"});
CheckInt128Case({absl::MakeInt128(std::numeric_limits<int64_t>::min(), 0),
- std::ios::oct, /*width = */ 0,
+ kOct, /*width = */ 0,
"2000000000000000000000000000000000000000000"});
CheckInt128Case({absl::MakeInt128(std::numeric_limits<int64_t>::min(), 0),
- std::ios::hex, /*width = */ 0,
- "80000000000000000000000000000000"});
- CheckInt128Case({-1, std::ios::dec, /*width = */ 0, "-1"});
- CheckInt128Case({-1, std::ios::oct, /*width = */ 0,
+ kHex, /*width = */ 0, "80000000000000000000000000000000"});
+ CheckInt128Case({-1, kDec, /*width = */ 0, "-1"});
+ CheckInt128Case({-1, kOct, /*width = */ 0,
"3777777777777777777777777777777777777777777"});
CheckInt128Case(
- {-1, std::ios::hex, /*width = */ 0, "ffffffffffffffffffffffffffffffff"});
- CheckInt128Case({-12345, std::ios::dec, /*width = */ 0, "-12345"});
- CheckInt128Case({-12345, std::ios::oct, /*width = */ 0,
+ {-1, kHex, /*width = */ 0, "ffffffffffffffffffffffffffffffff"});
+ CheckInt128Case({-12345, kDec, /*width = */ 0, "-12345"});
+ CheckInt128Case({-12345, kOct, /*width = */ 0,
"3777777777777777777777777777777777777747707"});
- CheckInt128Case({-12345, std::ios::hex, /*width = */ 0,
- "ffffffffffffffffffffffffffffcfc7"});
+ CheckInt128Case(
+ {-12345, kHex, /*width = */ 0, "ffffffffffffffffffffffffffffcfc7"});
}
std::vector<Int128TestCase> GetInt128FormatCases();
diff --git a/absl/numeric/int128_test.cc b/absl/numeric/int128_test.cc
index c445d89..f17a5f6 100644
--- a/absl/numeric/int128_test.cc
+++ b/absl/numeric/int128_test.cc
@@ -26,11 +26,7 @@
#include "absl/hash/hash_testing.h"
#include "absl/meta/type_traits.h"
-#if defined(_MSC_VER) && _MSC_VER == 1900
-// Disable "unary minus operator applied to unsigned type" warnings in Microsoft
-// Visual C++ 14 (2015).
-#pragma warning(disable:4146)
-#endif
+#define MAKE_INT128(HI, LO) absl::MakeInt128(static_cast<int64_t>(HI), LO)
namespace {
@@ -235,8 +231,24 @@
EXPECT_EQ(two, -((-one) - 1));
EXPECT_EQ(absl::Uint128Max(), -one);
EXPECT_EQ(zero, -zero);
+}
- EXPECT_EQ(absl::Uint128Max(), absl::kuint128max);
+TEST(Int128, RightShiftOfNegativeNumbers) {
+ absl::int128 minus_six = -6;
+ absl::int128 minus_three = -3;
+ absl::int128 minus_two = -2;
+ absl::int128 minus_one = -1;
+ if ((-6 >> 1) == -3) {
+ // Right shift is arithmetic (sign propagates)
+ EXPECT_EQ(minus_six >> 1, minus_three);
+ EXPECT_EQ(minus_six >> 2, minus_two);
+ EXPECT_EQ(minus_six >> 65, minus_one);
+ } else {
+ // Right shift is logical (zeros shifted in at MSB)
+ EXPECT_EQ(minus_six >> 1, absl::int128(absl::uint128(minus_six) >> 1));
+ EXPECT_EQ(minus_six >> 2, absl::int128(absl::uint128(minus_six) >> 2));
+ EXPECT_EQ(minus_six >> 65, absl::int128(absl::uint128(minus_six) >> 65));
+ }
}
TEST(Uint128, ConversionTests) {
@@ -265,8 +277,9 @@
EXPECT_EQ(from_precise_double, from_precise_ints);
EXPECT_DOUBLE_EQ(static_cast<double>(from_precise_ints), precise_double);
- double approx_double = 0xffffeeeeddddcccc * std::pow(2.0, 64.0) +
- 0xbbbbaaaa99998888;
+ double approx_double =
+ static_cast<double>(0xffffeeeeddddcccc) * std::pow(2.0, 64.0) +
+ static_cast<double>(0xbbbbaaaa99998888);
absl::uint128 from_approx_double(approx_double);
EXPECT_DOUBLE_EQ(static_cast<double>(from_approx_double), approx_double);
@@ -1227,6 +1240,27 @@
absl::MakeInt128(uint64_t{1} << j, 0) >>= (j - i));
}
}
+
+ // Manually calculated cases with shift count for positive (val1) and negative
+ // (val2) values
+ absl::int128 val1 = MAKE_INT128(0x123456789abcdef0, 0x123456789abcdef0);
+ absl::int128 val2 = MAKE_INT128(0xfedcba0987654321, 0xfedcba0987654321);
+
+ EXPECT_EQ(val1 << 63, MAKE_INT128(0x91a2b3c4d5e6f78, 0x0));
+ EXPECT_EQ(val1 << 64, MAKE_INT128(0x123456789abcdef0, 0x0));
+ EXPECT_EQ(val2 << 63, MAKE_INT128(0xff6e5d04c3b2a190, 0x8000000000000000));
+ EXPECT_EQ(val2 << 64, MAKE_INT128(0xfedcba0987654321, 0x0));
+
+ EXPECT_EQ(val1 << 126, MAKE_INT128(0x0, 0x0));
+ EXPECT_EQ(val2 << 126, MAKE_INT128(0x4000000000000000, 0x0));
+
+ EXPECT_EQ(val1 >> 63, MAKE_INT128(0x0, 0x2468acf13579bde0));
+ EXPECT_EQ(val1 >> 64, MAKE_INT128(0x0, 0x123456789abcdef0));
+ EXPECT_EQ(val2 >> 63, MAKE_INT128(0xffffffffffffffff, 0xfdb974130eca8643));
+ EXPECT_EQ(val2 >> 64, MAKE_INT128(0xffffffffffffffff, 0xfedcba0987654321));
+
+ EXPECT_EQ(val1 >> 126, MAKE_INT128(0x0, 0x0));
+ EXPECT_EQ(val2 >> 126, MAKE_INT128(0xffffffffffffffff, 0xffffffffffffffff));
}
TEST(Int128, NumericLimitsTest) {
diff --git a/absl/profiling/BUILD.bazel b/absl/profiling/BUILD.bazel
new file mode 100644
index 0000000..abe127e
--- /dev/null
+++ b/absl/profiling/BUILD.bazel
@@ -0,0 +1,140 @@
+# Copyright 2021 The Abseil Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+load(
+ "//absl:copts/configure_copts.bzl",
+ "ABSL_DEFAULT_COPTS",
+ "ABSL_DEFAULT_LINKOPTS",
+ "ABSL_TEST_COPTS",
+)
+
+package(
+ default_visibility = ["//visibility:private"],
+ features = [
+ "header_modules",
+ "layering_check",
+ "parse_headers",
+ ],
+)
+
+licenses(["notice"])
+
+cc_library(
+ name = "sample_recorder",
+ hdrs = ["internal/sample_recorder.h"],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ visibility = [
+ "//absl:__subpackages__",
+ ],
+ deps = [
+ "//absl/base:config",
+ "//absl/base:core_headers",
+ "//absl/synchronization",
+ "//absl/time",
+ ],
+)
+
+cc_test(
+ name = "sample_recorder_test",
+ srcs = ["internal/sample_recorder_test.cc"],
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ tags = [
+ "no_test_wasm",
+ ],
+ deps = [
+ ":sample_recorder",
+ "//absl/base:core_headers",
+ "//absl/synchronization",
+ "//absl/synchronization:thread_pool",
+ "//absl/time",
+ "@com_google_googletest//:gtest",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_library(
+ name = "exponential_biased",
+ srcs = ["internal/exponential_biased.cc"],
+ hdrs = ["internal/exponential_biased.h"],
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ visibility = [
+ "//absl:__subpackages__",
+ ],
+ deps = [
+ "//absl/base:config",
+ "//absl/base:core_headers",
+ ],
+)
+
+cc_test(
+ name = "exponential_biased_test",
+ size = "small",
+ srcs = ["internal/exponential_biased_test.cc"],
+ copts = ABSL_TEST_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ visibility = ["//visibility:private"],
+ deps = [
+ ":exponential_biased",
+ "//absl/strings",
+ "@com_google_googletest//:gtest",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_library(
+ name = "periodic_sampler",
+ srcs = ["internal/periodic_sampler.cc"],
+ hdrs = ["internal/periodic_sampler.h"],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ visibility = [
+ # TODO(b/304670045): remove after periodic_sampler moves to //spanner/common.
+ "//absl:__subpackages__",
+ ],
+ deps = [
+ ":exponential_biased",
+ "//absl/base:core_headers",
+ ],
+)
+
+cc_test(
+ name = "periodic_sampler_test",
+ size = "small",
+ srcs = ["internal/periodic_sampler_test.cc"],
+ copts = ABSL_TEST_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ visibility = ["//visibility:private"],
+ deps = [
+ ":periodic_sampler",
+ "//absl/base:core_headers",
+ "@com_google_googletest//:gtest",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_binary(
+ name = "periodic_sampler_benchmark",
+ testonly = True,
+ srcs = ["internal/periodic_sampler_benchmark.cc"],
+ copts = ABSL_TEST_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ tags = ["benchmark"],
+ visibility = ["//visibility:private"],
+ deps = [
+ ":periodic_sampler",
+ "//absl/base:core_headers",
+ "@com_github_google_benchmark//:benchmark_main",
+ ],
+)
diff --git a/absl/profiling/CMakeLists.txt b/absl/profiling/CMakeLists.txt
new file mode 100644
index 0000000..9b3a710
--- /dev/null
+++ b/absl/profiling/CMakeLists.txt
@@ -0,0 +1,93 @@
+# Copyright 2021 The Abseil Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+absl_cc_library(
+ NAME
+ sample_recorder
+ HDRS
+ "internal/sample_recorder.h"
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ DEPS
+ absl::base
+ absl::synchronization
+)
+
+absl_cc_test(
+ NAME
+ sample_recorder_test
+ SRCS
+ "internal/sample_recorder_test.cc"
+ COPTS
+ ${ABSL_TEST_COPTS}
+ DEPS
+ absl::sample_recorder
+ absl::time
+ GTest::gmock_main
+)
+
+absl_cc_library(
+ NAME
+ exponential_biased
+ SRCS
+ "internal/exponential_biased.cc"
+ HDRS
+ "internal/exponential_biased.h"
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ DEPS
+ absl::config
+ absl::core_headers
+)
+
+absl_cc_test(
+ NAME
+ exponential_biased_test
+ SRCS
+ "internal/exponential_biased_test.cc"
+ COPTS
+ ${ABSL_TEST_COPTS}
+ DEPS
+ absl::exponential_biased
+ absl::strings
+ GTest::gmock_main
+)
+
+absl_cc_library(
+ NAME
+ periodic_sampler
+ SRCS
+ "internal/periodic_sampler.cc"
+ HDRS
+ "internal/periodic_sampler.h"
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ DEPS
+ absl::core_headers
+ absl::exponential_biased
+)
+
+absl_cc_test(
+ NAME
+ periodic_sampler_test
+ SRCS
+ "internal/periodic_sampler_test.cc"
+ COPTS
+ ${ABSL_TEST_COPTS}
+ DEPS
+ absl::core_headers
+ absl::periodic_sampler
+ GTest::gmock_main
+)
+
diff --git a/absl/base/internal/exponential_biased.cc b/absl/profiling/internal/exponential_biased.cc
similarity index 96%
rename from absl/base/internal/exponential_biased.cc
rename to absl/profiling/internal/exponential_biased.cc
index 05aeea5..81d9a75 100644
--- a/absl/base/internal/exponential_biased.cc
+++ b/absl/profiling/internal/exponential_biased.cc
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#include "absl/base/internal/exponential_biased.h"
+#include "absl/profiling/internal/exponential_biased.h"
#include <stdint.h>
@@ -26,7 +26,7 @@
namespace absl {
ABSL_NAMESPACE_BEGIN
-namespace base_internal {
+namespace profiling_internal {
// The algorithm generates a random number between 0 and 1 and applies the
// inverse cumulative distribution function for an exponential. Specifically:
@@ -88,6 +88,6 @@
initialized_ = true;
}
-} // namespace base_internal
+} // namespace profiling_internal
ABSL_NAMESPACE_END
} // namespace absl
diff --git a/absl/base/internal/exponential_biased.h b/absl/profiling/internal/exponential_biased.h
similarity index 94%
rename from absl/base/internal/exponential_biased.h
rename to absl/profiling/internal/exponential_biased.h
index a81f10e..d31f778 100644
--- a/absl/base/internal/exponential_biased.h
+++ b/absl/profiling/internal/exponential_biased.h
@@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#ifndef ABSL_BASE_INTERNAL_EXPONENTIAL_BIASED_H_
-#define ABSL_BASE_INTERNAL_EXPONENTIAL_BIASED_H_
+#ifndef ABSL_PROFILING_INTERNAL_EXPONENTIAL_BIASED_H_
+#define ABSL_PROFILING_INTERNAL_EXPONENTIAL_BIASED_H_
#include <stdint.h>
@@ -22,7 +22,7 @@
namespace absl {
ABSL_NAMESPACE_BEGIN
-namespace base_internal {
+namespace profiling_internal {
// ExponentialBiased provides a small and fast random number generator for a
// rounded exponential distribution. This generator manages very little state,
@@ -123,8 +123,8 @@
return (prng_mult * rnd + prng_add) & prng_mod_mask;
}
-} // namespace base_internal
+} // namespace profiling_internal
ABSL_NAMESPACE_END
} // namespace absl
-#endif // ABSL_BASE_INTERNAL_EXPONENTIAL_BIASED_H_
+#endif // ABSL_PROFILING_INTERNAL_EXPONENTIAL_BIASED_H_
diff --git a/absl/base/internal/exponential_biased_test.cc b/absl/profiling/internal/exponential_biased_test.cc
similarity index 89%
rename from absl/base/internal/exponential_biased_test.cc
rename to absl/profiling/internal/exponential_biased_test.cc
index 075583c..ebfbcad 100644
--- a/absl/base/internal/exponential_biased_test.cc
+++ b/absl/profiling/internal/exponential_biased_test.cc
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#include "absl/base/internal/exponential_biased.h"
+#include "absl/profiling/internal/exponential_biased.h"
#include <stddef.h>
@@ -28,7 +28,8 @@
namespace absl {
ABSL_NAMESPACE_BEGIN
-namespace base_internal {
+namespace profiling_internal {
+namespace {
MATCHER_P2(IsBetween, a, b,
absl::StrCat(std::string(negation ? "isn't" : "is"), " between ", a,
@@ -93,13 +94,14 @@
}
double AndersonDarlingStatistic(const std::vector<double>& random_sample) {
- int n = random_sample.size();
+ size_t n = random_sample.size();
double ad_sum = 0;
- for (int i = 0; i < n; i++) {
+ for (size_t i = 0; i < n; i++) {
ad_sum += (2 * i + 1) *
std::log(random_sample[i] * (1 - random_sample[n - 1 - i]));
}
- double ad_statistic = -n - 1 / static_cast<double>(n) * ad_sum;
+ const auto n_as_double = static_cast<double>(n);
+ double ad_statistic = -n_as_double - 1 / n_as_double * ad_sum;
return ad_statistic;
}
@@ -110,14 +112,15 @@
// Marsaglia and Marsaglia for details.
double AndersonDarlingTest(const std::vector<double>& random_sample) {
double ad_statistic = AndersonDarlingStatistic(random_sample);
- double p = AndersonDarlingPValue(random_sample.size(), ad_statistic);
+ double p = AndersonDarlingPValue(static_cast<int>(random_sample.size()),
+ ad_statistic);
return p;
}
TEST(ExponentialBiasedTest, CoinTossDemoWithGetSkipCount) {
ExponentialBiased eb;
for (int runs = 0; runs < 10; ++runs) {
- for (int flips = eb.GetSkipCount(1); flips > 0; --flips) {
+ for (int64_t flips = eb.GetSkipCount(1); flips > 0; --flips) {
printf("head...");
}
printf("tail\n");
@@ -131,7 +134,7 @@
TEST(ExponentialBiasedTest, SampleDemoWithStride) {
ExponentialBiased eb;
- int stride = eb.GetStride(10);
+ int64_t stride = eb.GetStride(10);
int samples = 0;
for (int i = 0; i < 10000000; ++i) {
if (--stride == 0) {
@@ -146,7 +149,7 @@
// Testing that NextRandom generates uniform random numbers. Applies the
// Anderson-Darling test for uniformity
TEST(ExponentialBiasedTest, TestNextRandom) {
- for (auto n : std::vector<int>({
+ for (auto n : std::vector<size_t>({
10, // Check short-range correlation
100, 1000,
10000 // Make sure there's no systemic error
@@ -160,7 +163,7 @@
}
std::vector<uint64_t> int_random_sample(n);
// Collect samples
- for (int i = 0; i < n; i++) {
+ for (size_t i = 0; i < n; i++) {
int_random_sample[i] = x;
x = ExponentialBiased::NextRandom(x);
}
@@ -168,7 +171,7 @@
std::sort(int_random_sample.begin(), int_random_sample.end());
std::vector<double> random_sample(n);
// Convert them to uniform randoms (in the range [0,1])
- for (int i = 0; i < n; i++) {
+ for (size_t i = 0; i < n; i++) {
random_sample[i] =
static_cast<double>(int_random_sample[i]) / max_prng_value;
}
@@ -194,6 +197,7 @@
EXPECT_THAT(eb_stack.GetSkipCount(2), Ge(0));
}
-} // namespace base_internal
+} // namespace
+} // namespace profiling_internal
ABSL_NAMESPACE_END
} // namespace absl
diff --git a/absl/base/internal/periodic_sampler.cc b/absl/profiling/internal/periodic_sampler.cc
similarity index 88%
rename from absl/base/internal/periodic_sampler.cc
rename to absl/profiling/internal/periodic_sampler.cc
index 520dabb..a738a82 100644
--- a/absl/base/internal/periodic_sampler.cc
+++ b/absl/profiling/internal/periodic_sampler.cc
@@ -12,15 +12,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#include "absl/base/internal/periodic_sampler.h"
+#include "absl/profiling/internal/periodic_sampler.h"
#include <atomic>
-#include "absl/base/internal/exponential_biased.h"
+#include "absl/profiling/internal/exponential_biased.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
-namespace base_internal {
+namespace profiling_internal {
int64_t PeriodicSamplerBase::GetExponentialBiased(int period) noexcept {
return rng_.GetStride(period);
@@ -48,6 +48,6 @@
return true;
}
-} // namespace base_internal
+} // namespace profiling_internal
ABSL_NAMESPACE_END
} // namespace absl
diff --git a/absl/base/internal/periodic_sampler.h b/absl/profiling/internal/periodic_sampler.h
similarity index 95%
rename from absl/base/internal/periodic_sampler.h
rename to absl/profiling/internal/periodic_sampler.h
index f8a8679..54f0af4 100644
--- a/absl/base/internal/periodic_sampler.h
+++ b/absl/profiling/internal/periodic_sampler.h
@@ -12,19 +12,19 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#ifndef ABSL_BASE_INTERNAL_PERIODIC_SAMPLER_H_
-#define ABSL_BASE_INTERNAL_PERIODIC_SAMPLER_H_
+#ifndef ABSL_PROFILING_INTERNAL_PERIODIC_SAMPLER_H_
+#define ABSL_PROFILING_INTERNAL_PERIODIC_SAMPLER_H_
#include <stdint.h>
#include <atomic>
-#include "absl/base/internal/exponential_biased.h"
#include "absl/base/optimization.h"
+#include "absl/profiling/internal/exponential_biased.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
-namespace base_internal {
+namespace profiling_internal {
// PeriodicSamplerBase provides the basic period sampler implementation.
//
@@ -149,7 +149,7 @@
// ICC x64 (OK) : https://gcc.godbolt.org/z/ptTNfD
// MSVC x64 (OK) : https://gcc.godbolt.org/z/76j4-5
uint64_t stride_ = 0;
- ExponentialBiased rng_;
+ absl::profiling_internal::ExponentialBiased rng_;
};
inline bool PeriodicSamplerBase::SubtleMaybeSample() noexcept {
@@ -204,8 +204,8 @@
template <typename Tag, int default_period>
std::atomic<int> PeriodicSampler<Tag, default_period>::period_(default_period);
-} // namespace base_internal
+} // namespace profiling_internal
ABSL_NAMESPACE_END
} // namespace absl
-#endif // ABSL_BASE_INTERNAL_PERIODIC_SAMPLER_H_
+#endif // ABSL_PROFILING_INTERNAL_PERIODIC_SAMPLER_H_
diff --git a/absl/base/internal/periodic_sampler_benchmark.cc b/absl/profiling/internal/periodic_sampler_benchmark.cc
similarity index 94%
rename from absl/base/internal/periodic_sampler_benchmark.cc
rename to absl/profiling/internal/periodic_sampler_benchmark.cc
index 5ad469c..8f0e557 100644
--- a/absl/base/internal/periodic_sampler_benchmark.cc
+++ b/absl/profiling/internal/periodic_sampler_benchmark.cc
@@ -12,12 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+#include "absl/profiling/internal/periodic_sampler.h"
#include "benchmark/benchmark.h"
-#include "absl/base/internal/periodic_sampler.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
-namespace base_internal {
+namespace profiling_internal {
namespace {
template <typename Sampler>
@@ -74,6 +74,6 @@
BENCHMARK(BM_PeriodicSampler_Disabled);
} // namespace
-} // namespace base_internal
+} // namespace profiling_internal
ABSL_NAMESPACE_END
} // namespace absl
diff --git a/absl/base/internal/periodic_sampler_test.cc b/absl/profiling/internal/periodic_sampler_test.cc
similarity index 97%
rename from absl/base/internal/periodic_sampler_test.cc
rename to absl/profiling/internal/periodic_sampler_test.cc
index 3b301e3..ef986f3 100644
--- a/absl/base/internal/periodic_sampler_test.cc
+++ b/absl/profiling/internal/periodic_sampler_test.cc
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#include "absl/base/internal/periodic_sampler.h"
+#include "absl/profiling/internal/periodic_sampler.h"
#include <thread> // NOLINT(build/c++11)
@@ -23,7 +23,7 @@
namespace absl {
ABSL_NAMESPACE_BEGIN
-namespace base_internal {
+namespace profiling_internal {
namespace {
using testing::Eq;
@@ -172,6 +172,6 @@
}
} // namespace
-} // namespace base_internal
+} // namespace profiling_internal
ABSL_NAMESPACE_END
} // namespace absl
diff --git a/absl/profiling/internal/sample_recorder.h b/absl/profiling/internal/sample_recorder.h
new file mode 100644
index 0000000..371f6c4
--- /dev/null
+++ b/absl/profiling/internal/sample_recorder.h
@@ -0,0 +1,253 @@
+// 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.
+//
+// -----------------------------------------------------------------------------
+// File: sample_recorder.h
+// -----------------------------------------------------------------------------
+//
+// This header file defines a lock-free linked list for recording samples
+// collected from a random/stochastic process.
+//
+// This utility is internal-only. Use at your own risk.
+
+#ifndef ABSL_PROFILING_INTERNAL_SAMPLE_RECORDER_H_
+#define ABSL_PROFILING_INTERNAL_SAMPLE_RECORDER_H_
+
+#include <atomic>
+#include <cstddef>
+#include <functional>
+
+#include "absl/base/config.h"
+#include "absl/base/thread_annotations.h"
+#include "absl/synchronization/mutex.h"
+#include "absl/time/time.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace profiling_internal {
+
+// Sample<T> that has members required for linking samples in the linked list of
+// samples maintained by the SampleRecorder. Type T defines the sampled data.
+template <typename T>
+struct Sample {
+ // Guards the ability to restore the sample to a pristine state. This
+ // prevents races with sampling and resurrecting an object.
+ absl::Mutex init_mu;
+ T* next = nullptr;
+ T* dead ABSL_GUARDED_BY(init_mu) = nullptr;
+ int64_t weight; // How many sampling events were required to sample this one.
+};
+
+// Holds samples and their associated stack traces with a soft limit of
+// `SetHashtablezMaxSamples()`.
+//
+// Thread safe.
+template <typename T>
+class SampleRecorder {
+ public:
+ SampleRecorder();
+ ~SampleRecorder();
+
+ // Registers for sampling. Returns an opaque registration info.
+ template <typename... Targs>
+ T* Register(Targs&&... args);
+
+ // Unregisters the sample.
+ void Unregister(T* sample);
+
+ // The dispose callback will be called on all samples the moment they are
+ // being unregistered. Only affects samples that are unregistered after the
+ // callback has been set.
+ // Returns the previous callback.
+ using DisposeCallback = void (*)(const T&);
+ DisposeCallback SetDisposeCallback(DisposeCallback f);
+
+ // 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 GetMaxSamples() const;
+ void SetMaxSamples(size_t max);
+
+ private:
+ void PushNew(T* sample);
+ void PushDead(T* sample);
+ template <typename... Targs>
+ T* PopDead(Targs... args);
+
+ std::atomic<size_t> dropped_samples_;
+ std::atomic<size_t> size_estimate_;
+ std::atomic<size_t> max_samples_{1 << 20};
+
+ // Intrusive lock free linked lists for tracking samples.
+ //
+ // `all_` records all samples (they are never removed from this list) and is
+ // terminated with a `nullptr`.
+ //
+ // `graveyard_.dead` is a circular linked list. When it is empty,
+ // `graveyard_.dead == &graveyard`. The list is circular so that
+ // every item on it (even the last) has a non-null dead pointer. This allows
+ // `Iterate` to determine if a given sample is live or dead using only
+ // information on the sample itself.
+ //
+ // For example, nodes [A, B, C, D, E] with [A, C, E] alive and [B, D] dead
+ // looks like this (G is the Graveyard):
+ //
+ // +---+ +---+ +---+ +---+ +---+
+ // all -->| A |--->| B |--->| C |--->| D |--->| E |
+ // | | | | | | | | | |
+ // +---+ | | +->| |-+ | | +->| |-+ | |
+ // | G | +---+ | +---+ | +---+ | +---+ | +---+
+ // | | | | | |
+ // | | --------+ +--------+ |
+ // +---+ |
+ // ^ |
+ // +--------------------------------------+
+ //
+ std::atomic<T*> all_;
+ T graveyard_;
+
+ std::atomic<DisposeCallback> dispose_;
+};
+
+template <typename T>
+typename SampleRecorder<T>::DisposeCallback
+SampleRecorder<T>::SetDisposeCallback(DisposeCallback f) {
+ return dispose_.exchange(f, std::memory_order_relaxed);
+}
+
+template <typename T>
+SampleRecorder<T>::SampleRecorder()
+ : dropped_samples_(0), size_estimate_(0), all_(nullptr), dispose_(nullptr) {
+ absl::MutexLock l(&graveyard_.init_mu);
+ graveyard_.dead = &graveyard_;
+}
+
+template <typename T>
+SampleRecorder<T>::~SampleRecorder() {
+ T* s = all_.load(std::memory_order_acquire);
+ while (s != nullptr) {
+ T* next = s->next;
+ delete s;
+ s = next;
+ }
+}
+
+template <typename T>
+void SampleRecorder<T>::PushNew(T* sample) {
+ sample->next = all_.load(std::memory_order_relaxed);
+ while (!all_.compare_exchange_weak(sample->next, sample,
+ std::memory_order_release,
+ std::memory_order_relaxed)) {
+ }
+}
+
+template <typename T>
+void SampleRecorder<T>::PushDead(T* sample) {
+ if (auto* dispose = dispose_.load(std::memory_order_relaxed)) {
+ dispose(*sample);
+ }
+
+ absl::MutexLock graveyard_lock(&graveyard_.init_mu);
+ absl::MutexLock sample_lock(&sample->init_mu);
+ sample->dead = graveyard_.dead;
+ graveyard_.dead = sample;
+}
+
+template <typename T>
+template <typename... Targs>
+T* SampleRecorder<T>::PopDead(Targs... args) {
+ absl::MutexLock graveyard_lock(&graveyard_.init_mu);
+
+ // The list is circular, so eventually it collapses down to
+ // graveyard_.dead == &graveyard_
+ // when it is empty.
+ T* sample = graveyard_.dead;
+ if (sample == &graveyard_) return nullptr;
+
+ absl::MutexLock sample_lock(&sample->init_mu);
+ graveyard_.dead = sample->dead;
+ sample->dead = nullptr;
+ sample->PrepareForSampling(std::forward<Targs>(args)...);
+ return sample;
+}
+
+template <typename T>
+template <typename... Targs>
+T* SampleRecorder<T>::Register(Targs&&... args) {
+ size_t size = size_estimate_.fetch_add(1, std::memory_order_relaxed);
+ if (size > max_samples_.load(std::memory_order_relaxed)) {
+ size_estimate_.fetch_sub(1, std::memory_order_relaxed);
+ dropped_samples_.fetch_add(1, std::memory_order_relaxed);
+ return nullptr;
+ }
+
+ T* sample = PopDead(args...);
+ if (sample == nullptr) {
+ // Resurrection failed. Hire a new warlock.
+ sample = new T();
+ {
+ absl::MutexLock sample_lock(&sample->init_mu);
+ // If flag initialization happens to occur (perhaps in another thread)
+ // while in this block, it will lock `graveyard_` which is usually always
+ // locked before any sample. This will appear as a lock inversion.
+ // However, this code is run exactly once per sample, and this sample
+ // cannot be accessed until after it is returned from this method. This
+ // means that this lock state can never be recreated, so we can safely
+ // inform the deadlock detector to ignore it.
+ sample->init_mu.ForgetDeadlockInfo();
+ sample->PrepareForSampling(std::forward<Targs>(args)...);
+ }
+ PushNew(sample);
+ }
+
+ return sample;
+}
+
+template <typename T>
+void SampleRecorder<T>::Unregister(T* sample) {
+ PushDead(sample);
+ size_estimate_.fetch_sub(1, std::memory_order_relaxed);
+}
+
+template <typename T>
+int64_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);
+ if (s->dead == nullptr) {
+ f(*s);
+ }
+ s = s->next;
+ }
+
+ return dropped_samples_.load(std::memory_order_relaxed);
+}
+
+template <typename T>
+void SampleRecorder<T>::SetMaxSamples(size_t max) {
+ max_samples_.store(max, std::memory_order_release);
+}
+
+template <typename T>
+size_t SampleRecorder<T>::GetMaxSamples() const {
+ return max_samples_.load(std::memory_order_acquire);
+}
+
+} // namespace profiling_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_PROFILING_INTERNAL_SAMPLE_RECORDER_H_
diff --git a/absl/profiling/internal/sample_recorder_test.cc b/absl/profiling/internal/sample_recorder_test.cc
new file mode 100644
index 0000000..3373329
--- /dev/null
+++ b/absl/profiling/internal/sample_recorder_test.cc
@@ -0,0 +1,184 @@
+// Copyright 2018 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/profiling/internal/sample_recorder.h"
+
+#include <atomic>
+#include <random>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "absl/base/thread_annotations.h"
+#include "absl/synchronization/internal/thread_pool.h"
+#include "absl/synchronization/mutex.h"
+#include "absl/synchronization/notification.h"
+#include "absl/time/time.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace profiling_internal {
+
+namespace {
+using ::absl::synchronization_internal::ThreadPool;
+using ::testing::IsEmpty;
+using ::testing::UnorderedElementsAre;
+
+struct Info : public Sample<Info> {
+ public:
+ void PrepareForSampling(int64_t w) { weight = w; }
+ std::atomic<size_t> size;
+ absl::Time create_time;
+};
+
+std::vector<size_t> GetSizes(SampleRecorder<Info>* s) {
+ std::vector<size_t> res;
+ s->Iterate([&](const Info& info) {
+ res.push_back(info.size.load(std::memory_order_acquire));
+ });
+ return res;
+}
+
+std::vector<int64_t> GetWeights(SampleRecorder<Info>* s) {
+ std::vector<int64_t> res;
+ s->Iterate([&](const Info& info) { res.push_back(info.weight); });
+ return res;
+}
+
+Info* Register(SampleRecorder<Info>* s, int64_t weight, size_t size) {
+ auto* info = s->Register(weight);
+ assert(info != nullptr);
+ info->size.store(size);
+ return info;
+}
+
+TEST(SampleRecorderTest, Registration) {
+ SampleRecorder<Info> sampler;
+ auto* info1 = Register(&sampler, 31, 1);
+ EXPECT_THAT(GetSizes(&sampler), UnorderedElementsAre(1));
+ EXPECT_THAT(GetWeights(&sampler), UnorderedElementsAre(31));
+
+ auto* info2 = Register(&sampler, 32, 2);
+ EXPECT_THAT(GetSizes(&sampler), UnorderedElementsAre(1, 2));
+ info1->size.store(3);
+ EXPECT_THAT(GetSizes(&sampler), UnorderedElementsAre(3, 2));
+ EXPECT_THAT(GetWeights(&sampler), UnorderedElementsAre(31, 32));
+
+ sampler.Unregister(info1);
+ sampler.Unregister(info2);
+}
+
+TEST(SampleRecorderTest, Unregistration) {
+ SampleRecorder<Info> sampler;
+ std::vector<Info*> infos;
+ for (size_t i = 0; i < 3; ++i) {
+ infos.push_back(Register(&sampler, 33 + i, i));
+ }
+ EXPECT_THAT(GetSizes(&sampler), UnorderedElementsAre(0, 1, 2));
+ EXPECT_THAT(GetWeights(&sampler), UnorderedElementsAre(33, 34, 35));
+
+ sampler.Unregister(infos[1]);
+ EXPECT_THAT(GetSizes(&sampler), UnorderedElementsAre(0, 2));
+ EXPECT_THAT(GetWeights(&sampler), UnorderedElementsAre(33, 35));
+
+ infos.push_back(Register(&sampler, 36, 3));
+ infos.push_back(Register(&sampler, 37, 4));
+ EXPECT_THAT(GetSizes(&sampler), UnorderedElementsAre(0, 2, 3, 4));
+ EXPECT_THAT(GetWeights(&sampler), UnorderedElementsAre(33, 35, 36, 37));
+ sampler.Unregister(infos[3]);
+ EXPECT_THAT(GetSizes(&sampler), UnorderedElementsAre(0, 2, 4));
+ EXPECT_THAT(GetWeights(&sampler), UnorderedElementsAre(33, 35, 37));
+
+ sampler.Unregister(infos[0]);
+ sampler.Unregister(infos[2]);
+ sampler.Unregister(infos[4]);
+ EXPECT_THAT(GetSizes(&sampler), IsEmpty());
+}
+
+TEST(SampleRecorderTest, MultiThreaded) {
+ SampleRecorder<Info> sampler;
+ Notification stop;
+ ThreadPool pool(10);
+
+ for (int i = 0; i < 10; ++i) {
+ pool.Schedule([&sampler, &stop, i]() {
+ std::random_device rd;
+ std::mt19937 gen(rd());
+
+ std::vector<Info*> infoz;
+ while (!stop.HasBeenNotified()) {
+ if (infoz.empty()) {
+ infoz.push_back(sampler.Register(i));
+ }
+ switch (std::uniform_int_distribution<>(0, 2)(gen)) {
+ case 0: {
+ infoz.push_back(sampler.Register(i));
+ break;
+ }
+ case 1: {
+ size_t p =
+ std::uniform_int_distribution<>(0, infoz.size() - 1)(gen);
+ Info* info = infoz[p];
+ infoz[p] = infoz.back();
+ infoz.pop_back();
+ EXPECT_EQ(info->weight, i);
+ sampler.Unregister(info);
+ break;
+ }
+ case 2: {
+ absl::Duration oldest = absl::ZeroDuration();
+ sampler.Iterate([&](const Info& info) {
+ oldest = std::max(oldest, absl::Now() - info.create_time);
+ });
+ ASSERT_GE(oldest, absl::ZeroDuration());
+ break;
+ }
+ }
+ }
+ });
+ }
+ // The threads will hammer away. Give it a little bit of time for tsan to
+ // spot errors.
+ absl::SleepFor(absl::Seconds(3));
+ stop.Notify();
+}
+
+TEST(SampleRecorderTest, Callback) {
+ SampleRecorder<Info> sampler;
+
+ auto* info1 = Register(&sampler, 39, 1);
+ auto* info2 = Register(&sampler, 40, 2);
+
+ static const Info* expected;
+
+ auto callback = [](const Info& info) {
+ // We can't use `info` outside of this callback because the object will be
+ // disposed as soon as we return from here.
+ EXPECT_EQ(&info, expected);
+ };
+
+ // Set the callback.
+ EXPECT_EQ(sampler.SetDisposeCallback(callback), nullptr);
+ expected = info1;
+ sampler.Unregister(info1);
+
+ // Unset the callback.
+ EXPECT_EQ(callback, sampler.SetDisposeCallback(nullptr));
+ expected = nullptr; // no more calls.
+ sampler.Unregister(info2);
+}
+
+} // namespace
+} // namespace profiling_internal
+ABSL_NAMESPACE_END
+} // namespace absl
diff --git a/absl/random/BUILD.bazel b/absl/random/BUILD.bazel
index 66ffcbc..9ae3bc8 100644
--- a/absl/random/BUILD.bazel
+++ b/absl/random/BUILD.bazel
@@ -16,7 +16,6 @@
# ABSL random-number generation libraries.
-load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library", "cc_test")
load(
"//absl:copts/configure_copts.bzl",
"ABSL_DEFAULT_COPTS",
@@ -24,7 +23,14 @@
"ABSL_TEST_COPTS",
)
-package(default_visibility = ["//visibility:public"])
+package(
+ default_visibility = ["//visibility:public"],
+ features = [
+ "header_modules",
+ "layering_check",
+ "parse_headers",
+ ],
+)
licenses(["notice"])
@@ -101,11 +107,12 @@
linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
":seed_gen_exception",
- "//absl/container:inlined_vector",
- "//absl/random/internal:nonsecure_base",
+ "//absl/base:config",
+ "//absl/base:nullability",
"//absl/random/internal:pool_urbg",
"//absl/random/internal:salted_seed_seq",
"//absl/random/internal:seed_material",
+ "//absl/strings:string_view",
"//absl/types:span",
],
)
@@ -127,8 +134,9 @@
cc_library(
name = "mock_distributions",
- testonly = 1,
+ testonly = True,
hdrs = ["mock_distributions.h"],
+ linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
":distributions",
":mocking_bit_gen",
@@ -140,7 +148,7 @@
cc_library(
name = "mocking_bit_gen",
- testonly = 1,
+ testonly = True,
hdrs = [
"mocking_bit_gen.h",
],
@@ -172,6 +180,7 @@
":random",
"//absl/random/internal:pcg_engine",
"//absl/random/internal:sequence_urbg",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -184,16 +193,20 @@
copts = ABSL_TEST_COPTS,
flaky = 1,
linkopts = ABSL_DEFAULT_LINKOPTS,
+ tags = [
+ "no_test_wasm",
+ ],
deps = [
":distributions",
":random",
- "//absl/base:raw_logging_internal",
+ "//absl/log",
"//absl/numeric:representation",
"//absl/random/internal:distribution_test_util",
"//absl/random/internal:pcg_engine",
"//absl/random/internal:sequence_urbg",
"//absl/strings",
"//absl/strings:str_format",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -211,6 +224,7 @@
":distributions",
":random",
"//absl/random/internal:distribution_test_util",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -224,6 +238,7 @@
deps = [
":distributions",
":random",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -236,15 +251,19 @@
],
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
+ tags = [
+ "no_test_wasm", # Does not converge on WASM.
+ ],
deps = [
":distributions",
":random",
- "//absl/base:raw_logging_internal",
+ "//absl/log",
"//absl/random/internal:distribution_test_util",
"//absl/random/internal:pcg_engine",
"//absl/random/internal:sequence_urbg",
"//absl/strings",
"//absl/strings:str_format",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -260,11 +279,12 @@
deps = [
":distributions",
":random",
- "//absl/base:raw_logging_internal",
+ "//absl/log",
"//absl/random/internal:distribution_test_util",
"//absl/random/internal:pcg_engine",
"//absl/random/internal:sequence_urbg",
"//absl/strings",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -287,13 +307,14 @@
":distributions",
":random",
"//absl/base:core_headers",
- "//absl/base:raw_logging_internal",
"//absl/container:flat_hash_map",
+ "//absl/log",
"//absl/random/internal:distribution_test_util",
"//absl/random/internal:pcg_engine",
"//absl/random/internal:sequence_urbg",
"//absl/strings",
"//absl/strings:str_format",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -301,6 +322,7 @@
cc_test(
name = "exponential_distribution_test",
size = "small",
+ timeout = "moderate",
srcs = ["exponential_distribution_test.cc"],
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
@@ -308,13 +330,14 @@
":distributions",
":random",
"//absl/base:core_headers",
- "//absl/base:raw_logging_internal",
+ "//absl/log",
"//absl/numeric:representation",
"//absl/random/internal:distribution_test_util",
"//absl/random/internal:pcg_engine",
"//absl/random/internal:sequence_urbg",
"//absl/strings",
"//absl/strings:str_format",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -332,12 +355,13 @@
":distributions",
":random",
"//absl/base:core_headers",
- "//absl/base:raw_logging_internal",
+ "//absl/log",
"//absl/numeric:representation",
"//absl/random/internal:distribution_test_util",
"//absl/random/internal:sequence_urbg",
"//absl/strings",
"//absl/strings:str_format",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -354,11 +378,12 @@
deps = [
":distributions",
":random",
- "//absl/base:raw_logging_internal",
+ "//absl/log",
"//absl/random/internal:distribution_test_util",
"//absl/random/internal:pcg_engine",
"//absl/random/internal:sequence_urbg",
"//absl/strings",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -379,12 +404,13 @@
deps = [
":distributions",
":random",
- "//absl/base:raw_logging_internal",
+ "//absl/log",
"//absl/numeric:representation",
"//absl/random/internal:distribution_test_util",
"//absl/random/internal:pcg_engine",
"//absl/random/internal:sequence_urbg",
"//absl/strings",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -400,11 +426,12 @@
deps = [
":distributions",
":random",
- "//absl/base:raw_logging_internal",
+ "//absl/log",
"//absl/random/internal:distribution_test_util",
"//absl/random/internal:pcg_engine",
"//absl/random/internal:sequence_urbg",
"//absl/strings",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -420,6 +447,7 @@
":random",
"//absl/base:fast_type_id",
"//absl/random/internal:sequence_urbg",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -430,11 +458,15 @@
srcs = ["mocking_bit_gen_test.cc"],
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
+ tags = [
+ "no_test_wasm",
+ ],
deps = [
":bit_gen_ref",
":mock_distributions",
":mocking_bit_gen",
":random",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -445,10 +477,14 @@
srcs = ["mock_distributions_test.cc"],
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
+ tags = [
+ "no_test_wasm",
+ ],
deps = [
":mock_distributions",
":mocking_bit_gen",
":random",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -459,8 +495,12 @@
srcs = ["examples_test.cc"],
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
+ tags = [
+ "no_test_wasm",
+ ],
deps = [
":random",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -475,32 +515,21 @@
":random",
":seed_sequences",
"//absl/random/internal:nonsecure_base",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
-BENCHMARK_TAGS = [
- "benchmark",
- "no_test_android_arm",
- "no_test_android_arm64",
- "no_test_android_x86",
- "no_test_darwin_x86_64",
- "no_test_ios_x86_64",
- "no_test_loonix",
- "no_test_msvc_x64",
- "no_test_wasm",
-]
-
# Benchmarks for various methods / test utilities
cc_binary(
name = "benchmarks",
- testonly = 1,
+ testonly = True,
srcs = [
"benchmarks.cc",
],
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
- tags = BENCHMARK_TAGS,
+ tags = ["benchmark"],
deps = [
":distributions",
":random",
diff --git a/absl/random/CMakeLists.txt b/absl/random/CMakeLists.txt
index 9d1c67f..d30b43f 100644
--- a/absl/random/CMakeLists.txt
+++ b/absl/random/CMakeLists.txt
@@ -121,6 +121,7 @@
absl::variant
GTest::gmock
GTest::gtest
+ PUBLIC
TESTONLY
)
@@ -222,13 +223,15 @@
LINKOPTS
${ABSL_DEFAULT_LINKOPTS}
DEPS
+ absl::config
absl::inlined_vector
- absl::random_internal_nonsecure_base
+ absl::nullability
absl::random_internal_pool_urbg
absl::random_internal_salted_seed_seq
absl::random_internal_seed_material
absl::random_seed_gen_exception
absl::span
+ absl::string_view
)
absl_cc_test(
@@ -259,13 +262,13 @@
LINKOPTS
${ABSL_DEFAULT_LINKOPTS}
DEPS
+ absl::log
absl::numeric_representation
absl::random_distributions
absl::random_random
absl::random_internal_distribution_test_util
absl::random_internal_sequence_urbg
absl::random_internal_pcg_engine
- absl::raw_logging_internal
absl::strings
absl::str_format
GTest::gmock
@@ -298,6 +301,7 @@
${ABSL_TEST_COPTS}
LINKOPTS
${ABSL_DEFAULT_LINKOPTS}
+ DEPS
absl::random_distributions
absl::random_random
absl::raw_logging_internal
@@ -314,12 +318,13 @@
${ABSL_TEST_COPTS}
LINKOPTS
${ABSL_DEFAULT_LINKOPTS}
+ DEPS
+ absl::log
absl::random_distributions
absl::random_internal_distribution_test_util
absl::random_internal_pcg_engine
absl::random_internal_sequence_urbg
absl::random_random
- absl::raw_logging_internal
absl::strings
absl::str_format
GTest::gmock
@@ -336,12 +341,12 @@
LINKOPTS
${ABSL_DEFAULT_LINKOPTS}
DEPS
+ absl::log
absl::random_distributions
absl::random_internal_distribution_test_util
absl::random_internal_pcg_engine
absl::random_internal_sequence_urbg
absl::random_random
- absl::raw_logging_internal
absl::strings
GTest::gmock
GTest::gtest_main
@@ -361,10 +366,10 @@
absl::random_random
absl::core_headers
absl::flat_hash_map
+ absl::log
absl::random_internal_distribution_test_util
absl::random_internal_pcg_engine
absl::random_internal_sequence_urbg
- absl::raw_logging_internal
absl::strings
absl::str_format
GTest::gmock
@@ -382,13 +387,13 @@
${ABSL_DEFAULT_LINKOPTS}
DEPS
absl::core_headers
+ absl::log
absl::numeric_representation
absl::random_distributions
absl::random_internal_distribution_test_util
absl::random_internal_pcg_engine
absl::random_internal_sequence_urbg
absl::random_random
- absl::raw_logging_internal
absl::strings
absl::str_format
GTest::gmock
@@ -406,12 +411,12 @@
${ABSL_DEFAULT_LINKOPTS}
DEPS
absl::core_headers
+ absl::log
absl::numeric_representation
absl::random_distributions
absl::random_internal_distribution_test_util
absl::random_internal_sequence_urbg
absl::random_random
- absl::raw_logging_internal
absl::strings
absl::str_format
GTest::gmock
@@ -428,12 +433,12 @@
LINKOPTS
${ABSL_DEFAULT_LINKOPTS}
DEPS
+ absl::log
absl::random_distributions
absl::random_internal_distribution_test_util
absl::random_internal_pcg_engine
absl::random_internal_sequence_urbg
absl::random_random
- absl::raw_logging_internal
absl::strings
GTest::gmock
GTest::gtest_main
@@ -449,6 +454,7 @@
LINKOPTS
${ABSL_DEFAULT_LINKOPTS}
DEPS
+ absl::log
absl::numeric_representation
absl::random_distributions
absl::random_internal_distribution_test_util
@@ -470,12 +476,12 @@
LINKOPTS
${ABSL_DEFAULT_LINKOPTS}
DEPS
+ absl::log
absl::random_distributions
absl::random_internal_distribution_test_util
absl::random_internal_pcg_engine
absl::random_internal_sequence_urbg
absl::random_random
- absl::raw_logging_internal
absl::strings
GTest::gmock
GTest::gtest_main
@@ -568,7 +574,7 @@
${ABSL_DEFAULT_COPTS}
LINKOPTS
${ABSL_DEFAULT_LINKOPTS}
- $<$<BOOL:${MINGW}>:"bcrypt">
+ $<$<BOOL:${MINGW}>:-lbcrypt>
DEPS
absl::core_headers
absl::optional
@@ -726,7 +732,7 @@
${ABSL_DEFAULT_LINKOPTS}
DEPS
absl::core_headers
- absl::optional
+ absl::inlined_vector
absl::random_internal_pool_urbg
absl::random_internal_salted_seed_seq
absl::random_internal_seed_material
@@ -1089,9 +1095,9 @@
LINKOPTS
${ABSL_DEFAULT_LINKOPTS}
DEPS
+ absl::log
absl::random_internal_explicit_seed_seq
absl::random_internal_randen_engine
- absl::raw_logging_internal
absl::strings
absl::time
GTest::gmock
@@ -1141,10 +1147,10 @@
LINKOPTS
${ABSL_DEFAULT_LINKOPTS}
DEPS
+ absl::log
absl::random_internal_platform
absl::random_internal_randen_hwaes
absl::random_internal_randen_hwaes_impl
- absl::raw_logging_internal
absl::str_format
GTest::gmock
GTest::gtest
@@ -1210,5 +1216,6 @@
absl::random_internal_wide_multiply
absl::bits
absl::int128
+ GTest::gmock
GTest::gtest_main
)
diff --git a/absl/random/benchmarks.cc b/absl/random/benchmarks.cc
index 87bbb98..0900e81 100644
--- a/absl/random/benchmarks.cc
+++ b/absl/random/benchmarks.cc
@@ -62,7 +62,7 @@
public:
using result_type = uint32_t;
- PrecompiledSeedSeq() {}
+ PrecompiledSeedSeq() = default;
template <typename Iterator>
PrecompiledSeedSeq(Iterator begin, Iterator end) {}
diff --git a/absl/random/bernoulli_distribution.h b/absl/random/bernoulli_distribution.h
index 25bd0d5..d81b6ae 100644
--- a/absl/random/bernoulli_distribution.h
+++ b/absl/random/bernoulli_distribution.h
@@ -138,16 +138,16 @@
// 64 bits.
//
// Second, `c` is constructed by first casting explicitly to a signed
- // integer and then converting implicitly to an unsigned integer of the same
+ // integer and then casting explicitly to an unsigned integer of the same
// size. This is done because the hardware conversion instructions produce
// signed integers from double; if taken as a uint64_t the conversion would
// be wrong for doubles greater than 2^63 (not relevant in this use-case).
// If converted directly to an unsigned integer, the compiler would end up
// emitting code to handle such large values that are not relevant due to
// the known bounds on `c`. To avoid these extra instructions this
- // implementation converts first to the signed type and then use the
- // implicit conversion to unsigned (which is a no-op).
- const uint64_t c = static_cast<int64_t>(p * kP32);
+ // implementation converts first to the signed type and then convert to
+ // unsigned (which is a no-op).
+ const uint64_t c = static_cast<uint64_t>(static_cast<int64_t>(p * kP32));
const uint32_t v = fast_u32(g);
// FAST PATH: this path fails with probability 1/2^32. Note that simply
// returning v <= c would approximate P very well (up to an absolute error
diff --git a/absl/random/beta_distribution_test.cc b/absl/random/beta_distribution_test.cc
index d980c96..c93b2a3 100644
--- a/absl/random/beta_distribution_test.cc
+++ b/absl/random/beta_distribution_test.cc
@@ -28,7 +28,7 @@
#include "gmock/gmock.h"
#include "gtest/gtest.h"
-#include "absl/base/internal/raw_logging.h"
+#include "absl/log/log.h"
#include "absl/numeric/internal/representation.h"
#include "absl/random/internal/chi_square.h"
#include "absl/random/internal/distribution_test_util.h"
@@ -45,16 +45,26 @@
template <typename IntType>
class BetaDistributionInterfaceTest : public ::testing::Test {};
-// double-double arithmetic is not supported well by either GCC or Clang; see
-// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=99048,
-// https://bugs.llvm.org/show_bug.cgi?id=49131, and
-// https://bugs.llvm.org/show_bug.cgi?id=49132. Don't bother running these tests
-// with double doubles until compiler support is better.
-using RealTypes =
- std::conditional<absl::numeric_internal::IsDoubleDouble(),
- ::testing::Types<float, double>,
- ::testing::Types<float, double, long double>>::type;
-TYPED_TEST_CASE(BetaDistributionInterfaceTest, RealTypes);
+constexpr bool ShouldExerciseLongDoubleTests() {
+ // long double arithmetic is not supported well by either GCC or Clang on
+ // most platforms specifically not when implemented in terms of double-double;
+ // see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=99048,
+ // https://bugs.llvm.org/show_bug.cgi?id=49131, and
+ // https://bugs.llvm.org/show_bug.cgi?id=49132.
+ // So a conservative choice here is to disable long-double tests pretty much
+ // everywhere except on x64 but only if long double is not implemented as
+ // double-double.
+#if defined(__i686__) && defined(__x86_64__)
+ return !absl::numeric_internal::IsDoubleDouble();
+#else
+ return false;
+#endif
+}
+
+using RealTypes = std::conditional<ShouldExerciseLongDoubleTests(),
+ ::testing::Types<float, double, long double>,
+ ::testing::Types<float, double>>::type;
+TYPED_TEST_SUITE(BetaDistributionInterfaceTest, RealTypes);
TYPED_TEST(BetaDistributionInterfaceTest, SerializeTest) {
// The threshold for whether std::exp(1/a) is finite.
@@ -97,8 +107,8 @@
};
for (TypeParam alpha : kValues) {
for (TypeParam beta : kValues) {
- ABSL_INTERNAL_LOG(
- INFO, absl::StrFormat("Smoke test for Beta(%a, %a)", alpha, beta));
+ LOG(INFO) << absl::StreamFormat("Smoke test for Beta(%a, %a)", alpha,
+ beta);
param_type param(alpha, beta);
absl::beta_distribution<TypeParam> before(alpha, beta);
@@ -317,15 +327,13 @@
absl::random_internal::Near("z", z_mean, 0.0, max_err) &&
absl::random_internal::Near("z_variance", z_variance, 0.0, max_err);
if (!pass) {
- ABSL_INTERNAL_LOG(
- INFO,
- absl::StrFormat(
- "Beta(%f, %f), "
- "mean: sample %f, expect %f, which is %f stddevs away, "
- "variance: sample %f, expect %f, which is %f stddevs away.",
- alpha_, beta_, m.mean, Mean(),
- std::abs(m.mean - Mean()) / mean_stddev, m.variance, Variance(),
- std::abs(m.variance - Variance()) / variance_stddev));
+ LOG(INFO) << "Beta(" << alpha_ << ", " << beta_ << "), mean: sample "
+ << m.mean << ", expect " << Mean() << ", which is "
+ << std::abs(m.mean - Mean()) / mean_stddev
+ << " stddevs away, variance: sample " << m.variance << ", expect "
+ << Variance() << ", which is "
+ << std::abs(m.variance - Variance()) / variance_stddev
+ << " stddevs away.";
}
return pass;
}
@@ -386,18 +394,15 @@
const bool pass =
(absl::random_internal::ChiSquarePValue(chi_square, dof) >= p);
if (!pass) {
- for (int i = 0; i < cutoffs.size(); i++) {
- ABSL_INTERNAL_LOG(
- INFO, absl::StrFormat("cutoff[%d] = %f, actual count %d, expected %d",
- i, cutoffs[i], counts[i],
- static_cast<int>(expected[i])));
+ for (size_t i = 0; i < cutoffs.size(); i++) {
+ LOG(INFO) << "cutoff[" << i << "] = " << cutoffs[i] << ", actual count "
+ << counts[i] << ", expected " << static_cast<int>(expected[i]);
}
- ABSL_INTERNAL_LOG(
- INFO, absl::StrFormat(
- "Beta(%f, %f) %s %f, p = %f", alpha_, beta_,
- absl::random_internal::kChiSquared, chi_square,
- absl::random_internal::ChiSquarePValue(chi_square, dof)));
+ LOG(INFO) << "Beta(" << alpha_ << ", " << beta_ << ") "
+ << absl::random_internal::kChiSquared << " " << chi_square
+ << ", p = "
+ << absl::random_internal::ChiSquarePValue(chi_square, dof);
}
return pass;
}
@@ -431,13 +436,13 @@
return absl::StrReplaceAll(name, {{"+", "_"}, {"-", "_"}, {".", "_"}});
}
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
TestSampleStatisticsCombinations, BetaDistributionTest,
::testing::Combine(::testing::Values(0.1, 0.2, 0.9, 1.1, 2.5, 10.0, 123.4),
::testing::Values(0.1, 0.2, 0.9, 1.1, 2.5, 10.0, 123.4)),
ParamName);
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
TestSampleStatistics_SelectedPairs, BetaDistributionTest,
::testing::Values(std::make_pair(0.5, 1000), std::make_pair(1000, 0.5),
std::make_pair(900, 1000), std::make_pair(10000, 20000),
diff --git a/absl/random/bit_gen_ref.h b/absl/random/bit_gen_ref.h
index 9555460..2160a8d 100644
--- a/absl/random/bit_gen_ref.h
+++ b/absl/random/bit_gen_ref.h
@@ -24,6 +24,11 @@
#ifndef ABSL_RANDOM_BIT_GEN_REF_H_
#define ABSL_RANDOM_BIT_GEN_REF_H_
+#include <limits>
+#include <type_traits>
+#include <utility>
+
+#include "absl/base/attributes.h"
#include "absl/base/internal/fast_type_id.h"
#include "absl/base/macros.h"
#include "absl/meta/type_traits.h"
@@ -110,7 +115,7 @@
(!std::is_same<URBG, BitGenRef>::value &&
random_internal::is_urbg<URBG>::value &&
!HasInvokeMock<URBG>::value)>* = nullptr>
- BitGenRef(URBG& gen) // NOLINT
+ BitGenRef(URBG& gen ABSL_ATTRIBUTE_LIFETIME_BOUND) // NOLINT
: t_erased_gen_ptr_(reinterpret_cast<uintptr_t>(&gen)),
mock_call_(NotAMock),
generate_impl_fn_(ImplFn<URBG>) {}
@@ -119,7 +124,7 @@
typename absl::enable_if_t<(!std::is_same<URBG, BitGenRef>::value &&
random_internal::is_urbg<URBG>::value &&
HasInvokeMock<URBG>::value)>* = nullptr>
- BitGenRef(URBG& gen) // NOLINT
+ BitGenRef(URBG& gen ABSL_ATTRIBUTE_LIFETIME_BOUND) // NOLINT
: t_erased_gen_ptr_(reinterpret_cast<uintptr_t>(&gen)),
mock_call_(&MockCall<URBG>),
generate_impl_fn_(ImplFn<URBG>) {}
diff --git a/absl/random/discrete_distribution_test.cc b/absl/random/discrete_distribution_test.cc
index 415b14c..32405ea 100644
--- a/absl/random/discrete_distribution_test.cc
+++ b/absl/random/discrete_distribution_test.cc
@@ -26,7 +26,7 @@
#include "gmock/gmock.h"
#include "gtest/gtest.h"
-#include "absl/base/internal/raw_logging.h"
+#include "absl/log/log.h"
#include "absl/random/internal/chi_square.h"
#include "absl/random/internal/distribution_test_util.h"
#include "absl/random/internal/pcg_engine.h"
@@ -146,7 +146,7 @@
using absl::random_internal::kChiSquared;
constexpr size_t kTrials = 10000;
- constexpr int kBuckets = 50; // inclusive, so actally +1
+ constexpr int kBuckets = 50; // inclusive, so actually +1
// 1-in-100000 threshold, but remember, there are about 8 tests
// in this file. And the test could fail for other reasons.
@@ -194,7 +194,7 @@
absl::StrAppend(&msg, kChiSquared, " p-value ", p_value, "\n");
absl::StrAppend(&msg, "High ", kChiSquared, " value: ", chi_square, " > ",
kThreshold);
- ABSL_RAW_LOG(INFO, "%s", msg.c_str());
+ LOG(INFO) << msg;
FAIL() << msg;
}
}
diff --git a/absl/random/distributions.h b/absl/random/distributions.h
index 31c7969..4e3b332 100644
--- a/absl/random/distributions.h
+++ b/absl/random/distributions.h
@@ -362,7 +362,7 @@
// If `lo` is nonzero then this distribution is shifted to the desired interval,
// so LogUniform(lo, hi, b) is equivalent to LogUniform(0, hi-lo, b)+lo.
//
-// See http://ecolego.facilia.se/ecolego/show/Log-Uniform%20Distribution
+// See https://en.wikipedia.org/wiki/Log-normal_distribution
//
// Example:
//
@@ -373,7 +373,7 @@
template <typename IntType, typename URBG>
IntType LogUniform(URBG&& urbg, // NOLINT(runtime/references)
IntType lo, IntType hi, IntType base = 2) {
- static_assert(std::is_integral<IntType>::value,
+ static_assert(random_internal::IsIntegral<IntType>::value,
"Template-argument 'IntType' must be an integral type, in "
"absl::LogUniform<IntType, URBG>(...)");
@@ -403,7 +403,7 @@
template <typename IntType, typename URBG>
IntType Poisson(URBG&& urbg, // NOLINT(runtime/references)
double mean = 1.0) {
- static_assert(std::is_integral<IntType>::value,
+ static_assert(random_internal::IsIntegral<IntType>::value,
"Template-argument 'IntType' must be an integral type, in "
"absl::Poisson<IntType, URBG>(...)");
@@ -435,7 +435,7 @@
IntType Zipf(URBG&& urbg, // NOLINT(runtime/references)
IntType hi = (std::numeric_limits<IntType>::max)(), double q = 2.0,
double v = 1.0) {
- static_assert(std::is_integral<IntType>::value,
+ static_assert(random_internal::IsIntegral<IntType>::value,
"Template-argument 'IntType' must be an integral type, in "
"absl::Zipf<IntType, URBG>(...)");
diff --git a/absl/random/distributions_test.cc b/absl/random/distributions_test.cc
index d3a5dd7..5321a11 100644
--- a/absl/random/distributions_test.cc
+++ b/absl/random/distributions_test.cc
@@ -220,6 +220,7 @@
absl::Uniform<uint16_t>(gen);
absl::Uniform<uint32_t>(gen);
absl::Uniform<uint64_t>(gen);
+ absl::Uniform<absl::uint128>(gen);
}
TEST_F(RandomDistributionsTest, UniformNonsenseRanges) {
diff --git a/absl/random/exponential_distribution_test.cc b/absl/random/exponential_distribution_test.cc
index 81a5d17..fb9a0d1 100644
--- a/absl/random/exponential_distribution_test.cc
+++ b/absl/random/exponential_distribution_test.cc
@@ -29,8 +29,8 @@
#include "gmock/gmock.h"
#include "gtest/gtest.h"
-#include "absl/base/internal/raw_logging.h"
#include "absl/base/macros.h"
+#include "absl/log/log.h"
#include "absl/numeric/internal/representation.h"
#include "absl/random/internal/chi_square.h"
#include "absl/random/internal/distribution_test_util.h"
@@ -58,7 +58,7 @@
std::conditional<absl::numeric_internal::IsDoubleDouble(),
::testing::Types<float, double>,
::testing::Types<float, double, long double>>::type;
-TYPED_TEST_CASE(ExponentialDistributionTypedTest, RealTypes);
+TYPED_TEST_SUITE(ExponentialDistributionTypedTest, RealTypes);
TYPED_TEST(ExponentialDistributionTypedTest, SerializeTest) {
using param_type =
@@ -115,9 +115,8 @@
if (sample < sample_min) sample_min = sample;
}
if (!std::is_same<TypeParam, long double>::value) {
- ABSL_INTERNAL_LOG(INFO,
- absl::StrFormat("Range {%f}: %f, %f, lambda=%f", lambda,
- sample_min, sample_max, lambda));
+ LOG(INFO) << "Range {" << lambda << "}: " << sample_min << ", "
+ << sample_max << ", lambda=" << lambda;
}
std::stringstream ss;
@@ -219,17 +218,16 @@
const bool pass = absl::random_internal::Near("z", z, 0.0, max_err);
if (!pass) {
- ABSL_INTERNAL_LOG(
- INFO, absl::StrFormat("p=%f max_err=%f\n"
- " lambda=%f\n"
- " mean=%f vs. %f\n"
- " stddev=%f vs. %f\n"
- " skewness=%f vs. %f\n"
- " kurtosis=%f vs. %f\n"
- " z=%f vs. 0",
- p, max_err, lambda(), m.mean, mean(),
- std::sqrt(m.variance), stddev(), m.skewness,
- skew(), m.kurtosis, kurtosis(), z));
+ // clang-format off
+ LOG(INFO)
+ << "p=" << p << " max_err=" << max_err << "\n"
+ " lambda=" << lambda() << "\n"
+ " mean=" << m.mean << " vs. " << mean() << "\n"
+ " stddev=" << std::sqrt(m.variance) << " vs. " << stddev() << "\n"
+ " skewness=" << m.skewness << " vs. " << skew() << "\n"
+ " kurtosis=" << m.kurtosis << " vs. " << kurtosis() << "\n"
+ " z=" << z << " vs. 0";
+ // clang-format on
}
return pass;
}
@@ -274,16 +272,16 @@
double p = absl::random_internal::ChiSquarePValue(chi_square, dof);
if (chi_square > threshold) {
- for (int i = 0; i < cutoffs.size(); i++) {
- ABSL_INTERNAL_LOG(
- INFO, absl::StrFormat("%d : (%f) = %d", i, cutoffs[i], counts[i]));
+ for (size_t i = 0; i < cutoffs.size(); i++) {
+ LOG(INFO) << i << " : (" << cutoffs[i] << ") = " << counts[i];
}
- ABSL_INTERNAL_LOG(INFO,
- absl::StrCat("lambda ", lambda(), "\n", //
- " expected ", expected, "\n", //
- kChiSquared, " ", chi_square, " (", p, ")\n",
- kChiSquared, " @ 0.98 = ", threshold));
+ // clang-format off
+ LOG(INFO) << "lambda " << lambda() << "\n"
+ " expected " << expected << "\n"
+ << kChiSquared << " " << chi_square << " (" << p << ")\n"
+ << kChiSquared << " @ 0.98 = " << threshold;
+ // clang-format on
}
return p;
}
@@ -343,8 +341,8 @@
return absl::StrReplaceAll(name, {{"+", "_"}, {"-", "_"}, {".", "_"}});
}
-INSTANTIATE_TEST_CASE_P(All, ExponentialDistributionTests,
- ::testing::ValuesIn(GenParams()), ParamName);
+INSTANTIATE_TEST_SUITE_P(All, ExponentialDistributionTests,
+ ::testing::ValuesIn(GenParams()), ParamName);
// NOTE: absl::exponential_distribution is not guaranteed to be stable.
TEST(ExponentialDistributionTest, StabilityTest) {
diff --git a/absl/random/gaussian_distribution_test.cc b/absl/random/gaussian_distribution_test.cc
index c0bac2b..bad3476 100644
--- a/absl/random/gaussian_distribution_test.cc
+++ b/absl/random/gaussian_distribution_test.cc
@@ -26,8 +26,8 @@
#include "gmock/gmock.h"
#include "gtest/gtest.h"
-#include "absl/base/internal/raw_logging.h"
#include "absl/base/macros.h"
+#include "absl/log/log.h"
#include "absl/numeric/internal/representation.h"
#include "absl/random/internal/chi_square.h"
#include "absl/random/internal/distribution_test_util.h"
@@ -54,7 +54,7 @@
std::conditional<absl::numeric_internal::IsDoubleDouble(),
::testing::Types<float, double>,
::testing::Types<float, double, long double>>::type;
-TYPED_TEST_CASE(GaussianDistributionInterfaceTest, RealTypes);
+TYPED_TEST_SUITE(GaussianDistributionInterfaceTest, RealTypes);
TYPED_TEST(GaussianDistributionInterfaceTest, SerializeTest) {
using param_type =
@@ -116,9 +116,8 @@
EXPECT_LE(sample, before.max()) << before;
}
if (!std::is_same<TypeParam, long double>::value) {
- ABSL_INTERNAL_LOG(
- INFO, absl::StrFormat("Range{%f, %f}: %f, %f", mean, stddev,
- sample_min, sample_max));
+ LOG(INFO) << "Range{" << mean << ", " << stddev << "}: " << sample_min
+ << ", " << sample_max;
}
std::stringstream ss;
@@ -240,17 +239,16 @@
(std::pow(m.skewness, 2.0) + std::pow(m.kurtosis - 3.0, 2.0) / 4.0);
if (!pass || jb > 9.21) {
- ABSL_INTERNAL_LOG(
- INFO, absl::StrFormat("p=%f max_err=%f\n"
- " mean=%f vs. %f\n"
- " stddev=%f vs. %f\n"
- " skewness=%f vs. %f\n"
- " kurtosis=%f vs. %f\n"
- " z=%f vs. 0\n"
- " jb=%f vs. 9.21",
- p, max_err, m.mean, mean(), std::sqrt(m.variance),
- stddev(), m.skewness, skew(), m.kurtosis,
- kurtosis(), z, jb));
+ // clang-format off
+ LOG(INFO)
+ << "p=" << p << " max_err=" << max_err << "\n"
+ " mean=" << m.mean << " vs. " << mean() << "\n"
+ " stddev=" << std::sqrt(m.variance) << " vs. " << stddev() << "\n"
+ " skewness=" << m.skewness << " vs. " << skew() << "\n"
+ " kurtosis=" << m.kurtosis << " vs. " << kurtosis() << "\n"
+ " z=" << z << " vs. 0\n"
+ " jb=" << jb << " vs. 9.21";
+ // clang-format on
}
return pass;
}
@@ -297,16 +295,16 @@
// Log if the chi_square value is above the threshold.
if (chi_square > threshold) {
- for (int i = 0; i < cutoffs.size(); i++) {
- ABSL_INTERNAL_LOG(
- INFO, absl::StrFormat("%d : (%f) = %d", i, cutoffs[i], counts[i]));
+ for (size_t i = 0; i < cutoffs.size(); i++) {
+ LOG(INFO) << i << " : (" << cutoffs[i] << ") = " << counts[i];
}
- ABSL_INTERNAL_LOG(
- INFO, absl::StrCat("mean=", mean(), " stddev=", stddev(), "\n", //
- " expected ", expected, "\n", //
- kChiSquared, " ", chi_square, " (", p, ")\n", //
- kChiSquared, " @ 0.98 = ", threshold));
+ // clang-format off
+ LOG(INFO) << "mean=" << mean() << " stddev=" << stddev() << "\n"
+ " expected " << expected << "\n"
+ << kChiSquared << " " << chi_square << " (" << p << ")\n"
+ << kChiSquared << " @ 0.98 = " << threshold;
+ // clang-format on
}
return p;
}
diff --git a/absl/random/generators_test.cc b/absl/random/generators_test.cc
index 41725f1..2009130 100644
--- a/absl/random/generators_test.cc
+++ b/absl/random/generators_test.cc
@@ -49,7 +49,7 @@
// (a, b) semantics, inferred types.
absl::Uniform(absl::IntervalOpenOpen, *gen, 0, 1.0); // Promoted to double
- // Explict overriding of types.
+ // Explicit overriding of types.
absl::Uniform<int>(*gen, 0, 100);
absl::Uniform<int8_t>(*gen, 0, 100);
absl::Uniform<int16_t>(*gen, 0, 100);
@@ -107,6 +107,8 @@
absl::Poisson<int64_t>(*gen);
absl::Poisson<uint64_t>(*gen);
absl::Poisson<uint64_t>(URBG());
+ absl::Poisson<absl::int128>(*gen);
+ absl::Poisson<absl::uint128>(*gen);
}
template <typename URBG>
@@ -115,6 +117,7 @@
absl::Bernoulli(*gen, 0.5);
}
+
template <typename URBG>
void TestZipf(URBG* gen) {
absl::Zipf<int>(*gen, 100);
@@ -126,6 +129,8 @@
absl::Zipf<int64_t>(*gen, 1 << 10);
absl::Zipf<uint64_t>(*gen, 1 << 10);
absl::Zipf<uint64_t>(URBG(), 1 << 10);
+ absl::Zipf<absl::int128>(*gen, 1 << 10);
+ absl::Zipf<absl::uint128>(*gen, 1 << 10);
}
template <typename URBG>
@@ -146,6 +151,8 @@
absl::LogUniform<int64_t>(*gen, 0, 1 << 10);
absl::LogUniform<uint64_t>(*gen, 0, 1 << 10);
absl::LogUniform<uint64_t>(URBG(), 0, 1 << 10);
+ absl::LogUniform<absl::int128>(*gen, 0, 1 << 10);
+ absl::LogUniform<absl::uint128>(*gen, 0, 1 << 10);
}
template <typename URBG>
diff --git a/absl/random/internal/BUILD.bazel b/absl/random/internal/BUILD.bazel
index 8420b5c..69fb5f2 100644
--- a/absl/random/internal/BUILD.bazel
+++ b/absl/random/internal/BUILD.bazel
@@ -14,8 +14,6 @@
# limitations under the License.
#
-load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library", "cc_test")
-
# Internal-only implementation classes for Abseil Random
load(
"//absl:copts/configure_copts.bzl",
@@ -26,9 +24,18 @@
"absl_random_randen_copts_init",
)
-package(default_visibility = [
+default_package_visibility = [
"//absl/random:__pkg__",
-])
+]
+
+package(
+ default_visibility = default_package_visibility,
+ features = [
+ "header_modules",
+ "layering_check",
+ "parse_headers",
+ ],
+)
licenses(["notice"])
@@ -37,7 +44,11 @@
hdrs = ["traits.h"],
copts = ABSL_DEFAULT_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
- deps = ["//absl/base:config"],
+ deps = [
+ "//absl/base:config",
+ "//absl/numeric:bits",
+ "//absl/numeric:int128",
+ ],
)
cc_library(
@@ -60,6 +71,7 @@
copts = ABSL_DEFAULT_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
+ ":traits",
"//absl/base:config",
"//absl/meta:type_traits",
],
@@ -77,6 +89,10 @@
linkopts = ABSL_DEFAULT_LINKOPTS + select({
"//absl:msvc_compiler": ["-DEFAULTLIB:bcrypt.lib"],
"//absl:clang-cl_compiler": ["-DEFAULTLIB:bcrypt.lib"],
+ "//absl:mingw_compiler": [
+ "-DEFAULTLIB:bcrypt.lib",
+ "-lbcrypt",
+ ],
"//conditions:default": [],
}),
deps = [
@@ -121,7 +137,7 @@
cc_library(
name = "explicit_seed_seq",
- testonly = 1,
+ testonly = True,
hdrs = [
"explicit_seed_seq.h",
],
@@ -135,7 +151,7 @@
cc_library(
name = "sequence_urbg",
- testonly = 1,
+ testonly = True,
hdrs = [
"sequence_urbg.h",
],
@@ -219,8 +235,8 @@
":salted_seed_seq",
":seed_material",
"//absl/base:core_headers",
+ "//absl/container:inlined_vector",
"//absl/meta:type_traits",
- "//absl/types:optional",
"//absl/types:span",
],
)
@@ -245,6 +261,8 @@
hdrs = ["randen_engine.h"],
copts = ABSL_DEFAULT_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
+ visibility = default_package_visibility + [
+ ],
deps = [
":iostream_state_saver",
":randen",
@@ -357,7 +375,7 @@
cc_library(
name = "distribution_test_util",
- testonly = 1,
+ testonly = True,
srcs = [
"chi_square.cc",
"distribution_test_util.cc",
@@ -386,7 +404,7 @@
"no_test_darwin_x86_64",
"no_test_ios_x86_64",
"no_test_loonix",
- "no_test_msvc_x64",
+ "no_test_lexan",
"no_test_wasm",
]
@@ -398,6 +416,7 @@
linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
":traits",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -414,6 +433,7 @@
":generate_real",
"//absl/flags:flag",
"//absl/numeric:bits",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -426,6 +446,7 @@
linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
":distribution_test_util",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -438,6 +459,7 @@
linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
":fastmath",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -451,6 +473,7 @@
deps = [
":explicit_seed_seq",
"//absl/random:seed_sequences",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -463,6 +486,7 @@
linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
":salted_seed_seq",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -478,6 +502,7 @@
deps = [
":distribution_test_util",
"//absl/base:core_headers",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -492,6 +517,7 @@
linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
":fast_uniform_bits",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -499,6 +525,7 @@
cc_library(
name = "mock_helpers",
hdrs = ["mock_helpers.h"],
+ linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
"//absl/base:fast_type_id",
"//absl/types:optional",
@@ -507,8 +534,9 @@
cc_library(
name = "mock_overload_set",
- testonly = 1,
+ testonly = True,
hdrs = ["mock_overload_set.h"],
+ linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
":mock_helpers",
"//absl/random:mocking_bit_gen",
@@ -530,6 +558,7 @@
"//absl/random:distributions",
"//absl/random:seed_sequences",
"//absl/strings",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -542,6 +571,7 @@
linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
":seed_material",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -558,6 +588,7 @@
":pool_urbg",
"//absl/meta:type_traits",
"//absl/types:span",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -573,6 +604,7 @@
":explicit_seed_seq",
":pcg_engine",
"//absl/time",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -588,9 +620,10 @@
deps = [
":explicit_seed_seq",
":randen_engine",
- "//absl/base:raw_logging_internal",
+ "//absl/log",
"//absl/strings",
"//absl/time",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -604,6 +637,7 @@
deps = [
":randen",
"//absl/meta:type_traits",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -618,6 +652,7 @@
":platform",
":randen_slow",
"//absl/base:endian",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -626,14 +661,14 @@
name = "randen_hwaes_test",
size = "small",
srcs = ["randen_hwaes_test.cc"],
- copts = ABSL_TEST_COPTS,
+ copts = ABSL_TEST_COPTS + ABSL_RANDOM_RANDEN_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
tags = ABSL_RANDOM_NONPORTABLE_TAGS,
deps = [
":platform",
":randen_hwaes",
":randen_hwaes_impl", # build_cleaner: keep
- "//absl/base:raw_logging_internal",
+ "//absl/log",
"//absl/strings:str_format",
"@com_google_googletest//:gtest",
],
@@ -649,6 +684,7 @@
":wide_multiply",
"//absl/numeric:bits",
"//absl/numeric:int128",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -676,6 +712,7 @@
":traits",
"//absl/base:config",
"//absl/meta:type_traits",
+ "//absl/numeric:int128",
],
)
@@ -689,11 +726,14 @@
"benchmark",
"no_test_ios_x86_64",
"no_test_loonix", # Crashing.
+ "no_test_wasm",
],
deps = [
":nanobenchmark",
- "//absl/base:raw_logging_internal",
+ "//absl/log",
+ "//absl/log:check",
"//absl/strings",
+ "//absl/strings:str_format",
],
)
@@ -725,6 +765,7 @@
linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
":iostream_state_saver",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -737,6 +778,7 @@
linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
":uniform_helper",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
diff --git a/absl/random/internal/chi_square.cc b/absl/random/internal/chi_square.cc
index 640d48c..fbe0173 100644
--- a/absl/random/internal/chi_square.cc
+++ b/absl/random/internal/chi_square.cc
@@ -125,7 +125,8 @@
const double variance = 2.0 / (9 * dof);
// Cannot use this method if the variance is 0.
if (variance != 0) {
- return std::pow(z * std::sqrt(variance) + mean, 3.0) * dof;
+ double term = z * std::sqrt(variance) + mean;
+ return dof * (term * term * term);
}
}
diff --git a/absl/random/internal/distribution_caller.h b/absl/random/internal/distribution_caller.h
index fc81b78..0f162a4 100644
--- a/absl/random/internal/distribution_caller.h
+++ b/absl/random/internal/distribution_caller.h
@@ -18,6 +18,7 @@
#define ABSL_RANDOM_INTERNAL_DISTRIBUTION_CALLER_H_
#include <utility>
+#include <type_traits>
#include "absl/base/config.h"
#include "absl/base/internal/fast_type_id.h"
@@ -32,6 +33,8 @@
// to intercept such calls.
template <typename URBG>
struct DistributionCaller {
+ static_assert(!std::is_pointer<URBG>::value,
+ "You must pass a reference, not a pointer.");
// SFINAE to detect whether the URBG type includes a member matching
// bool InvokeMock(base_internal::FastTypeIdType, void*, void*).
//
diff --git a/absl/random/internal/distribution_test_util.cc b/absl/random/internal/distribution_test_util.cc
index e900565..9fa37bd 100644
--- a/absl/random/internal/distribution_test_util.cc
+++ b/absl/random/internal/distribution_test_util.cc
@@ -213,7 +213,7 @@
double result = 1.;
int ns = static_cast<int>(q + xc * psq);
- // Use the soper reduction forumla.
+ // Use the soper reduction formula.
double rx = (ns == 0) ? x : x / xc;
double temp = q - ai;
for (;;) {
@@ -236,7 +236,7 @@
}
}
- // NOTE: See also TOMS Alogrithm 708.
+ // NOTE: See also TOMS Algorithm 708.
// http://www.netlib.org/toms/index.html
//
// NOTE: The NWSC library also includes BRATIO / ISUBX (p87)
@@ -247,7 +247,7 @@
// https://www.jstor.org/stable/2346798?read-now=1&seq=4#page_scan_tab_contents
// https://www.jstor.org/stable/2346887?seq=1#page_scan_tab_contents
//
-// XINBTA(p, q, beta, alhpa)
+// XINBTA(p, q, beta, alpha)
// p: the value of the parameter p.
// q: the value of the parameter q.
// beta: the value of ln B(p, q)
diff --git a/absl/random/internal/explicit_seed_seq.h b/absl/random/internal/explicit_seed_seq.h
index e3aa31a..25f7915 100644
--- a/absl/random/internal/explicit_seed_seq.h
+++ b/absl/random/internal/explicit_seed_seq.h
@@ -74,7 +74,7 @@
template <typename OutIterator>
void generate(OutIterator begin, OutIterator end) {
for (size_t index = 0; begin != end; begin++) {
- *begin = state_.empty() ? 0 : little_endian::FromHost32(state_[index++]);
+ *begin = state_.empty() ? 0 : state_[index++];
if (index >= state_.size()) {
index = 0;
}
diff --git a/absl/random/internal/explicit_seed_seq_test.cc b/absl/random/internal/explicit_seed_seq_test.cc
index a55ad73..e36d5fa 100644
--- a/absl/random/internal/explicit_seed_seq_test.cc
+++ b/absl/random/internal/explicit_seed_seq_test.cc
@@ -24,6 +24,8 @@
namespace {
+using ::absl::random_internal::ExplicitSeedSeq;
+
template <typename Sseq>
bool ConformsToInterface() {
// Check that the SeedSequence can be default-constructed.
@@ -64,14 +66,14 @@
EXPECT_TRUE(ConformsToInterface<std::seed_seq>());
// Abseil classes
- EXPECT_TRUE(ConformsToInterface<absl::random_internal::ExplicitSeedSeq>());
+ EXPECT_TRUE(ConformsToInterface<ExplicitSeedSeq>());
}
TEST(ExplicitSeedSeq, DefaultConstructorGeneratesZeros) {
const size_t kNumBlocks = 128;
uint32_t outputs[kNumBlocks];
- absl::random_internal::ExplicitSeedSeq seq;
+ ExplicitSeedSeq seq;
seq.generate(outputs, &outputs[kNumBlocks]);
for (uint32_t& seed : outputs) {
@@ -87,8 +89,7 @@
for (uint32_t& seed : seed_material) {
seed = urandom();
}
- absl::random_internal::ExplicitSeedSeq seq(seed_material,
- &seed_material[kNumBlocks]);
+ ExplicitSeedSeq seq(seed_material, &seed_material[kNumBlocks]);
// Check that output is same as seed-material provided to constructor.
{
@@ -133,17 +134,14 @@
for (uint32_t& entry : entropy) {
entry = urandom();
}
- absl::random_internal::ExplicitSeedSeq seq_from_entropy(std::begin(entropy),
- std::end(entropy));
+ ExplicitSeedSeq seq_from_entropy(std::begin(entropy), std::end(entropy));
// Copy constructor.
{
- absl::random_internal::ExplicitSeedSeq seq_copy(seq_from_entropy);
+ ExplicitSeedSeq seq_copy(seq_from_entropy);
EXPECT_EQ(seq_copy.size(), seq_from_entropy.size());
- std::vector<uint32_t> seeds_1;
- seeds_1.resize(1000, 0);
- std::vector<uint32_t> seeds_2;
- seeds_2.resize(1000, 1);
+ std::vector<uint32_t> seeds_1(1000, 0);
+ std::vector<uint32_t> seeds_2(1000, 1);
seq_from_entropy.generate(seeds_1.begin(), seeds_1.end());
seq_copy.generate(seeds_2.begin(), seeds_2.end());
@@ -155,13 +153,10 @@
for (uint32_t& entry : entropy) {
entry = urandom();
}
- absl::random_internal::ExplicitSeedSeq another_seq(std::begin(entropy),
- std::end(entropy));
+ ExplicitSeedSeq another_seq(std::begin(entropy), std::end(entropy));
- std::vector<uint32_t> seeds_1;
- seeds_1.resize(1000, 0);
- std::vector<uint32_t> seeds_2;
- seeds_2.resize(1000, 0);
+ std::vector<uint32_t> seeds_1(1000, 0);
+ std::vector<uint32_t> seeds_2(1000, 0);
seq_from_entropy.generate(seeds_1.begin(), seeds_1.end());
another_seq.generate(seeds_2.begin(), seeds_2.end());
@@ -170,7 +165,15 @@
EXPECT_THAT(seeds_1, Not(Pointwise(Eq(), seeds_2)));
// Apply the assignment-operator.
+ // GCC 12 has a false-positive -Wstringop-overflow warning here.
+#if ABSL_INTERNAL_HAVE_MIN_GNUC_VERSION(12, 0)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wstringop-overflow"
+#endif
another_seq = seq_from_entropy;
+#if ABSL_INTERNAL_HAVE_MIN_GNUC_VERSION(12, 0)
+#pragma GCC diagnostic pop
+#endif
// Re-generate seeds.
seq_from_entropy.generate(seeds_1.begin(), seeds_1.end());
@@ -182,15 +185,13 @@
// Move constructor.
{
// Get seeds from seed-sequence constructed from entropy.
- std::vector<uint32_t> seeds_1;
- seeds_1.resize(1000, 0);
+ std::vector<uint32_t> seeds_1(1000, 0);
seq_from_entropy.generate(seeds_1.begin(), seeds_1.end());
// Apply move-constructor move the sequence to another instance.
absl::random_internal::ExplicitSeedSeq moved_seq(
std::move(seq_from_entropy));
- std::vector<uint32_t> seeds_2;
- seeds_2.resize(1000, 1);
+ std::vector<uint32_t> seeds_2(1000, 1);
moved_seq.generate(seeds_2.begin(), seeds_2.end());
// Verify that seeds produced by moved-instance are the same as original.
EXPECT_THAT(seeds_1, Pointwise(Eq(), seeds_2));
@@ -202,3 +203,35 @@
EXPECT_THAT(seeds_1, Each(Eq(0)));
}
}
+
+TEST(ExplicitSeedSeq, StdURBGGoldenTests) {
+ // Verify that for std::- URBG instances the results are stable across
+ // platforms (these should have deterministic output).
+ {
+ ExplicitSeedSeq seed_sequence{12, 34, 56};
+ std::minstd_rand rng(seed_sequence);
+
+ std::minstd_rand::result_type values[4] = {rng(), rng(), rng(), rng()};
+ EXPECT_THAT(values,
+ testing::ElementsAre(579252, 43785881, 464353103, 1501811174));
+ }
+
+ {
+ ExplicitSeedSeq seed_sequence{12, 34, 56};
+ std::mt19937 rng(seed_sequence);
+
+ std::mt19937::result_type values[4] = {rng(), rng(), rng(), rng()};
+ EXPECT_THAT(values, testing::ElementsAre(138416803, 151130212, 33817739,
+ 138416803));
+ }
+
+ {
+ ExplicitSeedSeq seed_sequence{12, 34, 56};
+ std::mt19937_64 rng(seed_sequence);
+
+ std::mt19937_64::result_type values[4] = {rng(), rng(), rng(), rng()};
+ EXPECT_THAT(values,
+ testing::ElementsAre(19738651785169348, 1464811352364190456,
+ 18054685302720800, 19738651785169348));
+ }
+}
diff --git a/absl/random/internal/fast_uniform_bits.h b/absl/random/internal/fast_uniform_bits.h
index 425aaf7..83ee5c0 100644
--- a/absl/random/internal/fast_uniform_bits.h
+++ b/absl/random/internal/fast_uniform_bits.h
@@ -22,6 +22,7 @@
#include "absl/base/config.h"
#include "absl/meta/type_traits.h"
+#include "absl/random/internal/traits.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
@@ -56,9 +57,10 @@
// `PowerOfTwoVariate(urbg)`.
template <typename URBG>
constexpr size_t NumBits() {
- return RangeSize<URBG>() == 0
- ? std::numeric_limits<typename URBG::result_type>::digits
- : IntegerLog2(RangeSize<URBG>());
+ return static_cast<size_t>(
+ RangeSize<URBG>() == 0
+ ? std::numeric_limits<typename URBG::result_type>::digits
+ : IntegerLog2(RangeSize<URBG>()));
}
// Given a shift value `n`, constructs a mask with exactly the low `n` bits set.
@@ -98,7 +100,7 @@
result_type operator()(URBG& g); // NOLINT(runtime/references)
private:
- static_assert(std::is_unsigned<UIntType>::value,
+ static_assert(IsUnsigned<UIntType>::value,
"Class-template FastUniformBits<> must be parameterized using "
"an unsigned type.");
@@ -150,7 +152,8 @@
result_type r = static_cast<result_type>(g() - kMin);
for (size_t n = 1; n < kIters; ++n) {
- r = (r << kShift) + static_cast<result_type>(g() - kMin);
+ r = static_cast<result_type>(r << kShift) +
+ static_cast<result_type>(g() - kMin);
}
return r;
}
diff --git a/absl/random/internal/fast_uniform_bits_test.cc b/absl/random/internal/fast_uniform_bits_test.cc
index cee702d..34c2520 100644
--- a/absl/random/internal/fast_uniform_bits_test.cc
+++ b/absl/random/internal/fast_uniform_bits_test.cc
@@ -167,7 +167,7 @@
FakeUrbg<uint64_t, 0, (std::numeric_limits<uint64_t>::max)()>>()));
}
-// The constants need to be choosen so that an infinite rejection loop doesn't
+// The constants need to be chosen so that an infinite rejection loop doesn't
// happen...
using Urng1_5bit = FakeUrbg<uint8_t, 0, 2, 0>; // ~1.5 bits (range 3)
using Urng4bits = FakeUrbg<uint8_t, 1, 0x10, 2>;
diff --git a/absl/random/internal/generate_real.h b/absl/random/internal/generate_real.h
index 4f62873..9a6f400 100644
--- a/absl/random/internal/generate_real.h
+++ b/absl/random/internal/generate_real.h
@@ -50,10 +50,10 @@
// inputs, otherwise it never returns 0.
//
// When a value in U(0,1) is required, use:
-// Uniform64ToReal<double, PositiveValueT, true>;
+// GenerateRealFromBits<double, PositiveValueT, true>;
//
// When a value in U(-1,1) is required, use:
-// Uniform64ToReal<double, SignedValueT, false>;
+// GenerateRealFromBits<double, SignedValueT, false>;
//
// This generates more distinct values than the mathematical equivalent
// `U(0, 1) * 2.0 - 1.0`.
@@ -78,7 +78,7 @@
"GenerateRealFromBits must be parameterized by either float or double.");
static_assert(sizeof(uint_type) == sizeof(real_type),
- "Mismatched unsinged and real types.");
+ "Mismatched unsigned and real types.");
static_assert((std::numeric_limits<real_type>::is_iec559 &&
std::numeric_limits<real_type>::radix == 2),
@@ -127,10 +127,8 @@
// Construct the 32-bit or 64-bit IEEE 754 floating-point value from
// the individual fields: sign, exp, mantissa(bits).
- uint_type val =
- (std::is_same<SignedTag, GeneratePositiveTag>::value ? 0u : sign) |
- (static_cast<uint_type>(exp) << kExp) |
- (static_cast<uint_type>(bits) & kMask);
+ uint_type val = sign | (static_cast<uint_type>(exp) << kExp) |
+ (static_cast<uint_type>(bits) & kMask);
// bit_cast to the output-type
real_type result;
diff --git a/absl/random/internal/iostream_state_saver_test.cc b/absl/random/internal/iostream_state_saver_test.cc
index 6e66266..ea9d2af 100644
--- a/absl/random/internal/iostream_state_saver_test.cc
+++ b/absl/random/internal/iostream_state_saver_test.cc
@@ -345,8 +345,9 @@
}
// Avoid undefined behavior (overflow/underflow).
- if (dd <= std::numeric_limits<int64_t>::max() &&
- dd >= std::numeric_limits<int64_t>::lowest()) {
+ if (dd <= static_cast<long double>(std::numeric_limits<int64_t>::max()) &&
+ dd >=
+ static_cast<long double>(std::numeric_limits<int64_t>::lowest())) {
int64_t x = static_cast<int64_t>(dd);
EXPECT_EQ(x, StreamRoundTrip<int64_t>(x));
}
diff --git a/absl/random/internal/mock_helpers.h b/absl/random/internal/mock_helpers.h
index 9d6ab21..a7a97bf 100644
--- a/absl/random/internal/mock_helpers.h
+++ b/absl/random/internal/mock_helpers.h
@@ -18,6 +18,7 @@
#include <tuple>
#include <type_traits>
+#include <utility>
#include "absl/base/internal/fast_type_id.h"
#include "absl/types/optional.h"
@@ -100,7 +101,7 @@
template <typename KeyT, typename URBG, typename... Args>
static auto MaybeInvokeMock(URBG* urbg, Args&&... args)
-> absl::optional<typename KeySignature<KeyT>::result_type> {
- // Use function overloading to dispatch to the implemenation since
+ // Use function overloading to dispatch to the implementation since
// more modern patterns (e.g. require + constexpr) are not supported in all
// compiler configurations.
return InvokeMockImpl<KeyT, typename KeySignature<KeyT>::result_type,
diff --git a/absl/random/internal/nanobenchmark.cc b/absl/random/internal/nanobenchmark.cc
index c918181..0f31a7d 100644
--- a/absl/random/internal/nanobenchmark.cc
+++ b/absl/random/internal/nanobenchmark.cc
@@ -361,7 +361,7 @@
// Write that many copies of each unique value to the array.
T* ABSL_RANDOM_INTERNAL_RESTRICT p = values;
for (const auto& value_count : unique) {
- std::fill(p, p + value_count.second, value_count.first);
+ std::fill_n(p, value_count.second, value_count.first);
p += value_count.second;
}
ABSL_RAW_CHECK(p == values + num_values, "Did not produce enough output");
diff --git a/absl/random/internal/nanobenchmark_test.cc b/absl/random/internal/nanobenchmark_test.cc
index f1571e2..d4f1028 100644
--- a/absl/random/internal/nanobenchmark_test.cc
+++ b/absl/random/internal/nanobenchmark_test.cc
@@ -14,8 +14,10 @@
#include "absl/random/internal/nanobenchmark.h"
-#include "absl/base/internal/raw_logging.h"
+#include "absl/log/check.h"
+#include "absl/log/log.h"
#include "absl/strings/numbers.h"
+#include "absl/strings/str_format.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
@@ -36,16 +38,16 @@
params.max_evals = 6; // avoid test timeout
const size_t num_results = Measure(&Div, nullptr, inputs, N, results, params);
if (num_results == 0) {
- ABSL_RAW_LOG(
- WARNING,
- "WARNING: Measurement failed, should not happen when using "
- "PinThreadToCPU unless the region to measure takes > 1 second.\n");
+ LOG(WARNING)
+ << "WARNING: Measurement failed, should not happen when using "
+ "PinThreadToCPU unless the region to measure takes > 1 second.";
return;
}
for (size_t i = 0; i < num_results; ++i) {
- ABSL_RAW_LOG(INFO, "%5zu: %6.2f ticks; MAD=%4.2f%%\n", results[i].input,
- results[i].ticks, results[i].variability * 100.0);
- ABSL_RAW_CHECK(results[i].ticks != 0.0f, "Zero duration");
+ LOG(INFO) << absl::StreamFormat("%5u: %6.2f ticks; MAD=%4.2f%%\n",
+ results[i].input, results[i].ticks,
+ results[i].variability * 100.0);
+ CHECK_NE(results[i].ticks, 0.0f) << "Zero duration";
}
}
@@ -54,7 +56,7 @@
int cpu = -1;
if (argc == 2) {
if (!absl::SimpleAtoi(argv[1], &cpu)) {
- ABSL_RAW_LOG(FATAL, "The optional argument must be a CPU number >= 0.\n");
+ LOG(FATAL) << "The optional argument must be a CPU number >= 0.";
}
}
PinThreadToCPU(cpu);
diff --git a/absl/random/internal/nonsecure_base.h b/absl/random/internal/nonsecure_base.h
index 730fa2e..c3b8033 100644
--- a/absl/random/internal/nonsecure_base.h
+++ b/absl/random/internal/nonsecure_base.h
@@ -17,28 +17,82 @@
#include <algorithm>
#include <cstdint>
-#include <iostream>
#include <iterator>
-#include <random>
-#include <string>
#include <type_traits>
+#include <utility>
#include <vector>
#include "absl/base/macros.h"
+#include "absl/container/inlined_vector.h"
#include "absl/meta/type_traits.h"
#include "absl/random/internal/pool_urbg.h"
#include "absl/random/internal/salted_seed_seq.h"
#include "absl/random/internal/seed_material.h"
-#include "absl/types/optional.h"
#include "absl/types/span.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace random_internal {
+// RandenPoolSeedSeq is a custom seed sequence type where generate() fills the
+// provided buffer via the RandenPool entropy source.
+class RandenPoolSeedSeq {
+ private:
+ struct ContiguousTag {};
+ struct BufferTag {};
+
+ // Generate random unsigned values directly into the buffer.
+ template <typename Contiguous>
+ void generate_impl(ContiguousTag, Contiguous begin, Contiguous end) {
+ const size_t n = static_cast<size_t>(std::distance(begin, end));
+ auto* a = &(*begin);
+ RandenPool<uint8_t>::Fill(
+ absl::MakeSpan(reinterpret_cast<uint8_t*>(a), sizeof(*a) * n));
+ }
+
+ // Construct a buffer of size n and fill it with values, then copy
+ // those values into the seed iterators.
+ template <typename RandomAccessIterator>
+ void generate_impl(BufferTag, RandomAccessIterator begin,
+ RandomAccessIterator end) {
+ const size_t n = std::distance(begin, end);
+ absl::InlinedVector<uint32_t, 8> data(n, 0);
+ RandenPool<uint32_t>::Fill(absl::MakeSpan(data.begin(), data.end()));
+ std::copy(std::begin(data), std::end(data), begin);
+ }
+
+ public:
+ using result_type = uint32_t;
+
+ size_t size() { return 0; }
+
+ template <typename OutIterator>
+ void param(OutIterator) const {}
+
+ template <typename RandomAccessIterator>
+ void generate(RandomAccessIterator begin, RandomAccessIterator end) {
+ // RandomAccessIterator must be assignable from uint32_t
+ if (begin != end) {
+ using U = typename std::iterator_traits<RandomAccessIterator>::value_type;
+ // ContiguousTag indicates the common case of a known contiguous buffer,
+ // which allows directly filling the buffer. In C++20,
+ // std::contiguous_iterator_tag provides a mechanism for testing this
+ // capability, however until Abseil's support requirements allow us to
+ // assume C++20, limit checks to a few common cases.
+ using TagType = absl::conditional_t<
+ (std::is_pointer<RandomAccessIterator>::value ||
+ std::is_same<RandomAccessIterator,
+ typename std::vector<U>::iterator>::value),
+ ContiguousTag, BufferTag>;
+
+ generate_impl(TagType{}, begin, end);
+ }
+ }
+};
+
// Each instance of NonsecureURBGBase<URBG> will be seeded by variates produced
// by a thread-unique URBG-instance.
-template <typename URBG>
+template <typename URBG, typename Seeder = RandenPoolSeedSeq>
class NonsecureURBGBase {
public:
using result_type = typename URBG::result_type;
@@ -85,49 +139,6 @@
}
private:
- // Seeder is a custom seed sequence type where generate() fills the provided
- // buffer via the RandenPool entropy source.
- struct Seeder {
- using result_type = uint32_t;
-
- size_t size() { return 0; }
-
- template <typename OutIterator>
- void param(OutIterator) const {}
-
- template <typename RandomAccessIterator>
- void generate(RandomAccessIterator begin, RandomAccessIterator end) {
- if (begin != end) {
- // begin, end must be random access iterators assignable from uint32_t.
- generate_impl(
- std::integral_constant<bool, sizeof(*begin) == sizeof(uint32_t)>{},
- begin, end);
- }
- }
-
- // Commonly, generate is invoked with a pointer to a buffer which
- // can be cast to a uint32_t.
- template <typename RandomAccessIterator>
- void generate_impl(std::integral_constant<bool, true>,
- RandomAccessIterator begin, RandomAccessIterator end) {
- auto buffer = absl::MakeSpan(begin, end);
- auto target = absl::MakeSpan(reinterpret_cast<uint32_t*>(buffer.data()),
- buffer.size());
- RandenPool<uint32_t>::Fill(target);
- }
-
- // The non-uint32_t case should be uncommon, and involves an extra copy,
- // filling the uint32_t buffer and then mixing into the output.
- template <typename RandomAccessIterator>
- void generate_impl(std::integral_constant<bool, false>,
- RandomAccessIterator begin, RandomAccessIterator end) {
- const size_t n = std::distance(begin, end);
- absl::InlinedVector<uint32_t, 8> data(n, 0);
- RandenPool<uint32_t>::Fill(absl::MakeSpan(data.begin(), data.end()));
- std::copy(std::begin(data), std::end(data), begin);
- }
- };
-
static URBG ConstructURBG() {
Seeder seeder;
return URBG(seeder);
diff --git a/absl/random/internal/nonsecure_base_test.cc b/absl/random/internal/nonsecure_base_test.cc
index 698027f..3502243 100644
--- a/absl/random/internal/nonsecure_base_test.cc
+++ b/absl/random/internal/nonsecure_base_test.cc
@@ -15,6 +15,7 @@
#include "absl/random/internal/nonsecure_base.h"
#include <algorithm>
+#include <cstdint>
#include <iostream>
#include <memory>
#include <random>
@@ -192,54 +193,35 @@
}
}
-// This is a PRNG-compatible type specifically designed to test
-// that NonsecureURBGBase::Seeder can correctly handle iterators
-// to arbitrary non-uint32_t size types.
-template <typename T>
-struct SeederTestEngine {
- using result_type = T;
+TEST(RandenPoolSeedSeqTest, SeederWorksForU32) {
+ absl::random_internal::RandenPoolSeedSeq seeder;
- static constexpr result_type(min)() {
- return (std::numeric_limits<result_type>::min)();
- }
- static constexpr result_type(max)() {
- return (std::numeric_limits<result_type>::max)();
- }
-
- template <class SeedSequence,
- typename = typename absl::enable_if_t<
- !std::is_same<SeedSequence, SeederTestEngine>::value>>
- explicit SeederTestEngine(SeedSequence&& seq) {
- seed(seq);
- }
-
- SeederTestEngine(const SeederTestEngine&) = default;
- SeederTestEngine& operator=(const SeederTestEngine&) = default;
- SeederTestEngine(SeederTestEngine&&) = default;
- SeederTestEngine& operator=(SeederTestEngine&&) = default;
-
- result_type operator()() { return state[0]; }
-
- template <class SeedSequence>
- void seed(SeedSequence&& seq) {
- std::fill(std::begin(state), std::end(state), T(0));
- seq.generate(std::begin(state), std::end(state));
- }
-
- T state[2];
-};
-
-TEST(NonsecureURBGBase, SeederWorksForU32) {
- using U32 =
- absl::random_internal::NonsecureURBGBase<SeederTestEngine<uint32_t>>;
- U32 x;
- EXPECT_NE(0, x());
+ uint32_t state[2] = {0, 0};
+ seeder.generate(std::begin(state), std::end(state));
+ EXPECT_FALSE(state[0] == 0 && state[1] == 0);
}
-TEST(NonsecureURBGBase, SeederWorksForU64) {
- using U64 =
- absl::random_internal::NonsecureURBGBase<SeederTestEngine<uint64_t>>;
+TEST(RandenPoolSeedSeqTest, SeederWorksForU64) {
+ absl::random_internal::RandenPoolSeedSeq seeder;
- U64 x;
- EXPECT_NE(0, x());
+ uint64_t state[2] = {0, 0};
+ seeder.generate(std::begin(state), std::end(state));
+ EXPECT_FALSE(state[0] == 0 && state[1] == 0);
+ EXPECT_FALSE((state[0] >> 32) == 0 && (state[1] >> 32) == 0);
+}
+
+TEST(RandenPoolSeedSeqTest, SeederWorksForS32) {
+ absl::random_internal::RandenPoolSeedSeq seeder;
+
+ int32_t state[2] = {0, 0};
+ seeder.generate(std::begin(state), std::end(state));
+ EXPECT_FALSE(state[0] == 0 && state[1] == 0);
+}
+
+TEST(RandenPoolSeedSeqTest, SeederWorksForVector) {
+ absl::random_internal::RandenPoolSeedSeq seeder;
+
+ std::vector<uint32_t> state(2);
+ seeder.generate(std::begin(state), std::end(state));
+ EXPECT_FALSE(state[0] == 0 && state[1] == 0);
}
diff --git a/absl/random/internal/pcg_engine.h b/absl/random/internal/pcg_engine.h
index 8efaf2e..e1f4ef3 100644
--- a/absl/random/internal/pcg_engine.h
+++ b/absl/random/internal/pcg_engine.h
@@ -221,48 +221,27 @@
template <uint64_t kMultA, uint64_t kMultB, uint64_t kIncA, uint64_t kIncB>
class pcg128_params {
public:
-#if ABSL_HAVE_INTRINSIC_INT128
- using state_type = __uint128_t;
- static inline constexpr state_type make_u128(uint64_t a, uint64_t b) {
- return (static_cast<__uint128_t>(a) << 64) | b;
- }
-#else
using state_type = absl::uint128;
- static inline constexpr state_type make_u128(uint64_t a, uint64_t b) {
- return absl::MakeUint128(a, b);
- }
-#endif
-
static inline constexpr state_type multiplier() {
- return make_u128(kMultA, kMultB);
+ return absl::MakeUint128(kMultA, kMultB);
}
static inline constexpr state_type increment() {
- return make_u128(kIncA, kIncB);
+ return absl::MakeUint128(kIncA, kIncB);
}
};
// Implementation of the PCG xsl_rr_128_64 128-bit mixing function, which
// accepts an input of state_type and mixes it into an output of result_type.
struct pcg_xsl_rr_128_64 {
-#if ABSL_HAVE_INTRINSIC_INT128
- using state_type = __uint128_t;
-#else
using state_type = absl::uint128;
-#endif
using result_type = uint64_t;
inline uint64_t operator()(state_type state) {
// This is equivalent to the xsl_rr_128_64 mixing function.
-#if ABSL_HAVE_INTRINSIC_INT128
uint64_t rotate = static_cast<uint64_t>(state >> 122u);
state ^= state >> 64;
uint64_t s = static_cast<uint64_t>(state);
-#else
- uint64_t h = Uint128High64(state);
- uint64_t rotate = h >> 58u;
- uint64_t s = Uint128Low64(state) ^ h;
-#endif
- return rotr(s, rotate);
+ return rotr(s, static_cast<int>(rotate));
}
};
diff --git a/absl/random/internal/platform.h b/absl/random/internal/platform.h
index bbdb4e6..d779f48 100644
--- a/absl/random/internal/platform.h
+++ b/absl/random/internal/platform.h
@@ -131,7 +131,7 @@
// ABSL_RANDOM_INTERNAL_AES_DISPATCH indicates whether the currently active
// platform has, or should use run-time dispatch for selecting the
-// acclerated Randen implementation.
+// accelerated Randen implementation.
#define ABSL_RANDOM_INTERNAL_AES_DISPATCH 0
#if defined(ABSL_ARCH_X86_64)
diff --git a/absl/random/internal/pool_urbg.cc b/absl/random/internal/pool_urbg.cc
index 725100a..5aefa7d 100644
--- a/absl/random/internal/pool_urbg.cc
+++ b/absl/random/internal/pool_urbg.cc
@@ -131,7 +131,7 @@
}
// Number of pooled urbg entries.
-static constexpr int kPoolSize = 8;
+static constexpr size_t kPoolSize = 8;
// Shared pool entries.
static absl::once_flag pool_once;
@@ -147,15 +147,15 @@
// on subsequent runs the order within the same program may be significantly
// different. However, as other thread IDs are not assigned sequentially,
// this is not expected to matter.
-int GetPoolID() {
+size_t GetPoolID() {
static_assert(kPoolSize >= 1,
"At least one urbg instance is required for PoolURBG");
- ABSL_CONST_INIT static std::atomic<int64_t> sequence{0};
+ ABSL_CONST_INIT static std::atomic<uint64_t> sequence{0};
#ifdef ABSL_HAVE_THREAD_LOCAL
- static thread_local int my_pool_id = -1;
- if (ABSL_PREDICT_FALSE(my_pool_id < 0)) {
+ static thread_local size_t my_pool_id = kPoolSize;
+ if (ABSL_PREDICT_FALSE(my_pool_id == kPoolSize)) {
my_pool_id = (sequence++ % kPoolSize);
}
return my_pool_id;
@@ -171,8 +171,8 @@
// Store the value in the pthread_{get/set}specific. However an uninitialized
// value is 0, so add +1 to distinguish from the null value.
- intptr_t my_pool_id =
- reinterpret_cast<intptr_t>(pthread_getspecific(tid_key));
+ uintptr_t my_pool_id =
+ reinterpret_cast<uintptr_t>(pthread_getspecific(tid_key));
if (ABSL_PREDICT_FALSE(my_pool_id == 0)) {
// No allocated ID, allocate the next value, cache it, and return.
my_pool_id = (sequence++ % kPoolSize) + 1;
@@ -194,7 +194,7 @@
// Not all the platforms that we build for have std::aligned_alloc, however
// since we never free these objects, we can over allocate and munge the
// pointers to the correct alignment.
- intptr_t x = reinterpret_cast<intptr_t>(
+ uintptr_t x = reinterpret_cast<uintptr_t>(
new char[sizeof(RandenPoolEntry) + kAlignment]);
auto y = x % kAlignment;
void* aligned = reinterpret_cast<void*>(y == 0 ? x : (x + kAlignment - y));
@@ -215,7 +215,7 @@
absl::MakeSpan(seed_material))) {
random_internal::ThrowSeedGenException();
}
- for (int i = 0; i < kPoolSize; i++) {
+ for (size_t i = 0; i < kPoolSize; i++) {
shared_pools[i] = PoolAlignedAlloc();
shared_pools[i]->Init(
absl::MakeSpan(&seed_material[i * kSeedSize], kSeedSize));
diff --git a/absl/random/internal/randen.h b/absl/random/internal/randen.h
index 9a3840b..9ff4a7a 100644
--- a/absl/random/internal/randen.h
+++ b/absl/random/internal/randen.h
@@ -43,10 +43,8 @@
// Generate updates the randen sponge. The outer portion of the sponge
// (kCapacityBytes .. kStateBytes) may be consumed as PRNG state.
- template <typename T, size_t N>
- void Generate(T (&state)[N]) const {
- static_assert(N * sizeof(T) == kStateBytes,
- "Randen::Generate() requires kStateBytes of state");
+ // REQUIRES: state points to kStateBytes of state.
+ inline void Generate(void* state) const {
#if ABSL_RANDOM_INTERNAL_AES_DISPATCH
// HW AES Dispatch.
if (has_crypto_) {
@@ -65,13 +63,9 @@
// Absorb incorporates additional seed material into the randen sponge. After
// absorb returns, Generate must be called before the state may be consumed.
- template <typename S, size_t M, typename T, size_t N>
- void Absorb(const S (&seed)[M], T (&state)[N]) const {
- static_assert(M * sizeof(S) == RandenTraits::kSeedBytes,
- "Randen::Absorb() requires kSeedBytes of seed");
-
- static_assert(N * sizeof(T) == RandenTraits::kStateBytes,
- "Randen::Absorb() requires kStateBytes of state");
+ // REQUIRES: seed points to kSeedBytes of seed.
+ // REQUIRES: state points to kStateBytes of state.
+ inline void Absorb(const void* seed, void* state) const {
#if ABSL_RANDOM_INTERNAL_AES_DISPATCH
// HW AES Dispatch.
if (has_crypto_) {
diff --git a/absl/random/internal/randen_benchmarks.cc b/absl/random/internal/randen_benchmarks.cc
index f589172..ec086ce 100644
--- a/absl/random/internal/randen_benchmarks.cc
+++ b/absl/random/internal/randen_benchmarks.cc
@@ -47,8 +47,10 @@
// Randen implementation benchmarks.
template <typename T>
struct AbsorbFn : public T {
- mutable uint64_t state[kStateSizeT] = {};
- mutable uint32_t seed[kSeedSizeT] = {};
+ // These are both cast to uint128* in the RandenHwAes implementation, so
+ // ensure they are 16 byte aligned.
+ alignas(16) mutable uint64_t state[kStateSizeT] = {};
+ alignas(16) mutable uint32_t seed[kSeedSizeT] = {};
static constexpr size_t bytes() { return sizeof(seed); }
diff --git a/absl/random/internal/randen_detect.cc b/absl/random/internal/randen_detect.cc
index bbe7b96..bdeab87 100644
--- a/absl/random/internal/randen_detect.cc
+++ b/absl/random/internal/randen_detect.cc
@@ -24,6 +24,11 @@
#include "absl/random/internal/platform.h"
+#if !defined(__UCLIBC__) && defined(__GLIBC__) && \
+ (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 16))
+#define ABSL_HAVE_GETAUXVAL
+#endif
+
#if defined(ABSL_ARCH_X86_64)
#define ABSL_INTERNAL_USE_X86_CPUID
#elif defined(ABSL_ARCH_PPC) || defined(ABSL_ARCH_ARM) || \
@@ -31,7 +36,7 @@
#if defined(__ANDROID__)
#define ABSL_INTERNAL_USE_ANDROID_GETAUXVAL
#define ABSL_INTERNAL_USE_GETAUXVAL
-#elif defined(__linux__)
+#elif defined(__linux__) && defined(ABSL_HAVE_GETAUXVAL)
#define ABSL_INTERNAL_USE_LINUX_GETAUXVAL
#define ABSL_INTERNAL_USE_GETAUXVAL
#endif
@@ -40,7 +45,10 @@
#if defined(ABSL_INTERNAL_USE_X86_CPUID)
#if defined(_WIN32) || defined(_WIN64)
#include <intrin.h> // NOLINT(build/include_order)
-#pragma intrinsic(__cpuid)
+#elif ABSL_HAVE_BUILTIN(__cpuid)
+// MSVC-equivalent __cpuid intrinsic declaration for clang-like compilers
+// for non-Windows build environments.
+extern void __cpuid(int[4], int);
#else
// MSVC-equivalent __cpuid intrinsic function.
static void __cpuid(int cpu_info[4], int info_type) {
diff --git a/absl/random/internal/randen_engine.h b/absl/random/internal/randen_engine.h
index 92bb890..fe2d9f6 100644
--- a/absl/random/internal/randen_engine.h
+++ b/absl/random/internal/randen_engine.h
@@ -42,7 +42,7 @@
// 'Strong' (well-distributed, unpredictable, backtracking-resistant) random
// generator, faster in some benchmarks than std::mt19937_64 and pcg64_c32.
template <typename T>
-class alignas(16) randen_engine {
+class alignas(8) randen_engine {
public:
// C++11 URBG interface:
using result_type = T;
@@ -58,7 +58,8 @@
return (std::numeric_limits<result_type>::max)();
}
- explicit randen_engine(result_type seed_value = 0) { seed(seed_value); }
+ randen_engine() : randen_engine(0) {}
+ explicit randen_engine(result_type seed_value) { seed(seed_value); }
template <class SeedSequence,
typename = typename absl::enable_if_t<
@@ -67,17 +68,27 @@
seed(seq);
}
- randen_engine(const randen_engine&) = default;
+ // alignment requirements dictate custom copy and move constructors.
+ randen_engine(const randen_engine& other)
+ : next_(other.next_), impl_(other.impl_) {
+ std::memcpy(state(), other.state(), kStateSizeT * sizeof(result_type));
+ }
+ randen_engine& operator=(const randen_engine& other) {
+ next_ = other.next_;
+ impl_ = other.impl_;
+ std::memcpy(state(), other.state(), kStateSizeT * sizeof(result_type));
+ return *this;
+ }
// Returns random bits from the buffer in units of result_type.
result_type operator()() {
// Refill the buffer if needed (unlikely).
+ auto* begin = state();
if (next_ >= kStateSizeT) {
next_ = kCapacityT;
- impl_.Generate(state_);
+ impl_.Generate(begin);
}
-
- return little_endian::ToHost(state_[next_++]);
+ return little_endian::ToHost(begin[next_++]);
}
template <class SeedSequence>
@@ -92,9 +103,10 @@
void seed(result_type seed_value = 0) {
next_ = kStateSizeT;
// Zeroes the inner state and fills the outer state with seed_value to
- // mimics behaviour of reseed
- std::fill(std::begin(state_), std::begin(state_) + kCapacityT, 0);
- std::fill(std::begin(state_) + kCapacityT, std::end(state_), seed_value);
+ // mimic the behaviour of reseed
+ auto* begin = state();
+ std::fill(begin, begin + kCapacityT, 0);
+ std::fill(begin + kCapacityT, begin + kStateSizeT, seed_value);
}
// Inserts entropy into (part of) the state. Calling this periodically with
@@ -105,7 +117,6 @@
using sequence_result_type = typename SeedSequence::result_type;
static_assert(sizeof(sequence_result_type) == 4,
"SeedSequence::result_type must be 32-bit");
-
constexpr size_t kBufferSize =
Randen::kSeedBytes / sizeof(sequence_result_type);
alignas(16) sequence_result_type buffer[kBufferSize];
@@ -119,12 +130,19 @@
if (entropy_size < kBufferSize) {
// ... and only request that many values, or 256-bits, when unspecified.
const size_t requested_entropy = (entropy_size == 0) ? 8u : entropy_size;
- std::fill(std::begin(buffer) + requested_entropy, std::end(buffer), 0);
- seq.generate(std::begin(buffer), std::begin(buffer) + requested_entropy);
+ std::fill(buffer + requested_entropy, buffer + kBufferSize, 0);
+ seq.generate(buffer, buffer + requested_entropy);
+#ifdef ABSL_IS_BIG_ENDIAN
+ // Randen expects the seed buffer to be in Little Endian; reverse it on
+ // Big Endian platforms.
+ for (sequence_result_type& e : buffer) {
+ e = absl::little_endian::FromHost(e);
+ }
+#endif
// The Randen paper suggests preferentially initializing even-numbered
// 128-bit vectors of the randen state (there are 16 such vectors).
// The seed data is merged into the state offset by 128-bits, which
- // implies prefering seed bytes [16..31, ..., 208..223]. Since the
+ // implies preferring seed bytes [16..31, ..., 208..223]. Since the
// buffer is 32-bit values, we swap the corresponding buffer positions in
// 128-bit chunks.
size_t dst = kBufferSize;
@@ -139,9 +157,9 @@
std::swap(buffer[--dst], buffer[--src]);
}
} else {
- seq.generate(std::begin(buffer), std::end(buffer));
+ seq.generate(buffer, buffer + kBufferSize);
}
- impl_.Absorb(buffer, state_);
+ impl_.Absorb(buffer, state());
// Generate will be called when operator() is called
next_ = kStateSizeT;
@@ -152,9 +170,10 @@
count -= step;
constexpr uint64_t kRateT = kStateSizeT - kCapacityT;
+ auto* begin = state();
while (count > 0) {
next_ = kCapacityT;
- impl_.Generate(state_);
+ impl_.Generate(*reinterpret_cast<result_type(*)[kStateSizeT]>(begin));
step = std::min<uint64_t>(kRateT, count);
count -= step;
}
@@ -162,9 +181,9 @@
}
bool operator==(const randen_engine& other) const {
+ const auto* begin = state();
return next_ == other.next_ &&
- std::equal(std::begin(state_), std::end(state_),
- std::begin(other.state_));
+ std::equal(begin, begin + kStateSizeT, other.state());
}
bool operator!=(const randen_engine& other) const {
@@ -178,11 +197,12 @@
using numeric_type =
typename random_internal::stream_format_type<result_type>::type;
auto saver = random_internal::make_ostream_state_saver(os);
- for (const auto& elem : engine.state_) {
+ auto* it = engine.state();
+ for (auto* end = it + kStateSizeT; it < end; ++it) {
// In the case that `elem` is `uint8_t`, it must be cast to something
// larger so that it prints as an integer rather than a character. For
// simplicity, apply the cast all circumstances.
- os << static_cast<numeric_type>(little_endian::FromHost(elem))
+ os << static_cast<numeric_type>(little_endian::FromHost(*it))
<< os.fill();
}
os << engine.next_;
@@ -208,7 +228,7 @@
if (is.fail()) {
return is;
}
- std::memcpy(engine.state_, state, sizeof(engine.state_));
+ std::memcpy(engine.state(), state, sizeof(state));
engine.next_ = next;
return is;
}
@@ -219,9 +239,21 @@
static constexpr size_t kCapacityT =
Randen::kCapacityBytes / sizeof(result_type);
- // First kCapacityT are `inner', the others are accessible random bits.
- alignas(16) result_type state_[kStateSizeT];
- size_t next_; // index within state_
+ // Returns the state array pointer, which is aligned to 16 bytes.
+ // The first kCapacityT are the `inner' sponge; the remainder are available.
+ result_type* state() {
+ return reinterpret_cast<result_type*>(
+ (reinterpret_cast<uintptr_t>(&raw_state_) & 0xf) ? (raw_state_ + 8)
+ : raw_state_);
+ }
+ const result_type* state() const {
+ return const_cast<randen_engine*>(this)->state();
+ }
+
+ // raw state array, manually aligned in state(). This overallocates
+ // by 8 bytes since C++ does not guarantee extended heap alignment.
+ alignas(8) char raw_state_[Randen::kStateBytes + 8];
+ size_t next_; // index within state()
Randen impl_;
};
diff --git a/absl/random/internal/randen_engine_test.cc b/absl/random/internal/randen_engine_test.cc
index c8e7685..a94f491 100644
--- a/absl/random/internal/randen_engine_test.cc
+++ b/absl/random/internal/randen_engine_test.cc
@@ -21,7 +21,7 @@
#include "gmock/gmock.h"
#include "gtest/gtest.h"
-#include "absl/base/internal/raw_logging.h"
+#include "absl/log/log.h"
#include "absl/random/internal/explicit_seed_seq.h"
#include "absl/strings/str_cat.h"
#include "absl/time/clock.h"
@@ -645,9 +645,8 @@
}
auto duration = absl::GetCurrentTimeNanos() - start;
- ABSL_INTERNAL_LOG(INFO, absl::StrCat(static_cast<double>(duration) /
- static_cast<double>(kCount),
- "ns"));
+ LOG(INFO) << static_cast<double>(duration) / static_cast<double>(kCount)
+ << "ns";
EXPECT_GT(sum, 0);
EXPECT_GE(duration, kCount); // Should be slower than 1ns per call.
diff --git a/absl/random/internal/randen_hwaes.cc b/absl/random/internal/randen_hwaes.cc
index ab51e4a..f535f4c 100644
--- a/absl/random/internal/randen_hwaes.cc
+++ b/absl/random/internal/randen_hwaes.cc
@@ -30,43 +30,13 @@
// ABSL_RANDEN_HWAES_IMPL indicates whether this file will contain
// a hardware accelerated implementation of randen, or whether it
// will contain stubs that exit the process.
-#if defined(ABSL_ARCH_X86_64) || defined(ABSL_ARCH_X86_32)
-// The platform.h directives are sufficient to indicate whether
-// we should build accelerated implementations for x86.
-#if (ABSL_HAVE_ACCELERATED_AES || ABSL_RANDOM_INTERNAL_AES_DISPATCH)
-#define ABSL_RANDEN_HWAES_IMPL 1
-#endif
-#elif defined(ABSL_ARCH_PPC)
-// The platform.h directives are sufficient to indicate whether
-// we should build accelerated implementations for PPC.
-//
-// NOTE: This has mostly been tested on 64-bit Power variants,
-// and not embedded cpus such as powerpc32-8540
#if ABSL_HAVE_ACCELERATED_AES
+// The following platforms have implemented RandenHwAes.
+#if defined(ABSL_ARCH_X86_64) || defined(ABSL_ARCH_X86_32) || \
+ defined(ABSL_ARCH_PPC) || defined(ABSL_ARCH_ARM) || \
+ defined(ABSL_ARCH_AARCH64)
#define ABSL_RANDEN_HWAES_IMPL 1
#endif
-#elif defined(ABSL_ARCH_ARM) || defined(ABSL_ARCH_AARCH64)
-// ARM is somewhat more complicated. We might support crypto natively...
-#if ABSL_HAVE_ACCELERATED_AES || \
- (defined(__ARM_NEON) && defined(__ARM_FEATURE_CRYPTO))
-#define ABSL_RANDEN_HWAES_IMPL 1
-
-#elif ABSL_RANDOM_INTERNAL_AES_DISPATCH && !defined(__APPLE__) && \
- (defined(__GNUC__) && __GNUC__ > 4 || __GNUC__ == 4 && __GNUC_MINOR__ > 9)
-// ...or, on GCC, we can use an ASM directive to
-// instruct the assember to allow crypto instructions.
-#define ABSL_RANDEN_HWAES_IMPL 1
-#define ABSL_RANDEN_HWAES_IMPL_CRYPTO_DIRECTIVE 1
-#endif
-#else
-// HWAES is unsupported by these architectures / platforms:
-// __myriad2__
-// __mips__
-//
-// Other architectures / platforms are unknown.
-//
-// See the Abseil documentation on supported macros at:
-// https://abseil.io/docs/cpp/platforms/macros
#endif
#if !defined(ABSL_RANDEN_HWAES_IMPL)
@@ -192,22 +162,6 @@
#elif defined(ABSL_ARCH_ARM) || defined(ABSL_ARCH_AARCH64)
-// This asm directive will cause the file to be compiled with crypto extensions
-// whether or not the cpu-architecture supports it.
-#if ABSL_RANDEN_HWAES_IMPL_CRYPTO_DIRECTIVE
-asm(".arch_extension crypto\n");
-
-// Override missing defines.
-#if !defined(__ARM_NEON)
-#define __ARM_NEON 1
-#endif
-
-#if !defined(__ARM_FEATURE_CRYPTO)
-#define __ARM_FEATURE_CRYPTO 1
-#endif
-
-#endif
-
// Rely on the ARM NEON+Crypto advanced simd types, defined in <arm_neon.h>.
// uint8x16_t is the user alias for underlying __simd128_uint8_t type.
// http://infocenter.arm.com/help/topic/com.arm.doc.ihi0073a/IHI0073A_arm_neon_intrinsics_ref.pdf
@@ -257,7 +211,7 @@
#elif defined(ABSL_ARCH_X86_64) || defined(ABSL_ARCH_X86_32)
// On x86 we rely on the aesni instructions
-#include <wmmintrin.h>
+#include <immintrin.h>
namespace {
diff --git a/absl/random/internal/randen_hwaes_test.cc b/absl/random/internal/randen_hwaes_test.cc
index 66ddb43..00d96ef 100644
--- a/absl/random/internal/randen_hwaes_test.cc
+++ b/absl/random/internal/randen_hwaes_test.cc
@@ -16,7 +16,7 @@
#include "gmock/gmock.h"
#include "gtest/gtest.h"
-#include "absl/base/internal/raw_logging.h"
+#include "absl/log/log.h"
#include "absl/random/internal/platform.h"
#include "absl/random/internal/randen_detect.h"
#include "absl/random/internal/randen_traits.h"
@@ -27,44 +27,39 @@
using absl::random_internal::RandenHwAes;
using absl::random_internal::RandenTraits;
-// Local state parameters.
-constexpr size_t kSeedBytes =
- RandenTraits::kStateBytes - RandenTraits::kCapacityBytes;
-constexpr size_t kStateSizeT = RandenTraits::kStateBytes / sizeof(uint64_t);
-constexpr size_t kSeedSizeT = kSeedBytes / sizeof(uint32_t);
-
-struct alignas(16) randen {
- uint64_t state[kStateSizeT];
- uint32_t seed[kSeedSizeT];
-};
-
TEST(RandenHwAesTest, Default) {
EXPECT_TRUE(absl::random_internal::CPUSupportsRandenHwAes());
- constexpr uint64_t kGolden[] = {
- 0x6c6534090ee6d3ee, 0x044e2b9b9d5333c6, 0xc3c14f134e433977,
- 0xdda9f47cd90410ee, 0x887bf3087fd8ca10, 0xf0b780f545c72912,
- 0x15dbb1d37696599f, 0x30ec63baff3c6d59, 0xb29f73606f7f20a6,
- 0x02808a316f49a54c, 0x3b8feaf9d5c8e50e, 0x9cbf605e3fd9de8a,
- 0xc970ae1a78183bbb, 0xd8b2ffd356301ed5, 0xf4b327fe0fc73c37,
- 0xcdfd8d76eb8f9a19, 0xc3a506eb91420c9d, 0xd5af05dd3eff9556,
- 0x48db1bb78f83c4a1, 0x7023920e0d6bfe8c, 0x58d3575834956d42,
- 0xed1ef4c26b87b840, 0x8eef32a23e0b2df3, 0x497cabf3431154fc,
- 0x4e24370570029a8b, 0xd88b5749f090e5ea, 0xc651a582a970692f,
- 0x78fcec2cbb6342f5, 0x463cb745612f55db, 0x352ee4ad1816afe3,
- 0x026ff374c101da7e, 0x811ef0821c3de851,
+ constexpr uint8_t kGolden[] = {
+ 0xee, 0xd3, 0xe6, 0x0e, 0x09, 0x34, 0x65, 0x6c, 0xc6, 0x33, 0x53, 0x9d,
+ 0x9b, 0x2b, 0x4e, 0x04, 0x77, 0x39, 0x43, 0x4e, 0x13, 0x4f, 0xc1, 0xc3,
+ 0xee, 0x10, 0x04, 0xd9, 0x7c, 0xf4, 0xa9, 0xdd, 0x10, 0xca, 0xd8, 0x7f,
+ 0x08, 0xf3, 0x7b, 0x88, 0x12, 0x29, 0xc7, 0x45, 0xf5, 0x80, 0xb7, 0xf0,
+ 0x9f, 0x59, 0x96, 0x76, 0xd3, 0xb1, 0xdb, 0x15, 0x59, 0x6d, 0x3c, 0xff,
+ 0xba, 0x63, 0xec, 0x30, 0xa6, 0x20, 0x7f, 0x6f, 0x60, 0x73, 0x9f, 0xb2,
+ 0x4c, 0xa5, 0x49, 0x6f, 0x31, 0x8a, 0x80, 0x02, 0x0e, 0xe5, 0xc8, 0xd5,
+ 0xf9, 0xea, 0x8f, 0x3b, 0x8a, 0xde, 0xd9, 0x3f, 0x5e, 0x60, 0xbf, 0x9c,
+ 0xbb, 0x3b, 0x18, 0x78, 0x1a, 0xae, 0x70, 0xc9, 0xd5, 0x1e, 0x30, 0x56,
+ 0xd3, 0xff, 0xb2, 0xd8, 0x37, 0x3c, 0xc7, 0x0f, 0xfe, 0x27, 0xb3, 0xf4,
+ 0x19, 0x9a, 0x8f, 0xeb, 0x76, 0x8d, 0xfd, 0xcd, 0x9d, 0x0c, 0x42, 0x91,
+ 0xeb, 0x06, 0xa5, 0xc3, 0x56, 0x95, 0xff, 0x3e, 0xdd, 0x05, 0xaf, 0xd5,
+ 0xa1, 0xc4, 0x83, 0x8f, 0xb7, 0x1b, 0xdb, 0x48, 0x8c, 0xfe, 0x6b, 0x0d,
+ 0x0e, 0x92, 0x23, 0x70, 0x42, 0x6d, 0x95, 0x34, 0x58, 0x57, 0xd3, 0x58,
+ 0x40, 0xb8, 0x87, 0x6b, 0xc2, 0xf4, 0x1e, 0xed, 0xf3, 0x2d, 0x0b, 0x3e,
+ 0xa2, 0x32, 0xef, 0x8e, 0xfc, 0x54, 0x11, 0x43, 0xf3, 0xab, 0x7c, 0x49,
+ 0x8b, 0x9a, 0x02, 0x70, 0x05, 0x37, 0x24, 0x4e, 0xea, 0xe5, 0x90, 0xf0,
+ 0x49, 0x57, 0x8b, 0xd8, 0x2f, 0x69, 0x70, 0xa9, 0x82, 0xa5, 0x51, 0xc6,
+ 0xf5, 0x42, 0x63, 0xbb, 0x2c, 0xec, 0xfc, 0x78, 0xdb, 0x55, 0x2f, 0x61,
+ 0x45, 0xb7, 0x3c, 0x46, 0xe3, 0xaf, 0x16, 0x18, 0xad, 0xe4, 0x2e, 0x35,
+ 0x7e, 0xda, 0x01, 0xc1, 0x74, 0xf3, 0x6f, 0x02, 0x51, 0xe8, 0x3d, 0x1c,
+ 0x82, 0xf0, 0x1e, 0x81,
};
- alignas(16) randen d;
- memset(d.state, 0, sizeof(d.state));
- RandenHwAes::Generate(RandenHwAes::GetKeys(), d.state);
+ alignas(16) uint8_t state[RandenTraits::kStateBytes];
+ std::memset(state, 0, sizeof(state));
- uint64_t* id = d.state;
- for (const auto& elem : kGolden) {
- auto a = absl::StrFormat("%#x", elem);
- auto b = absl::StrFormat("%#x", *id++);
- EXPECT_EQ(a, b);
- }
+ RandenHwAes::Generate(RandenHwAes::GetKeys(), state);
+ EXPECT_EQ(0, std::memcmp(state, kGolden, sizeof(state)));
}
} // namespace
@@ -72,32 +67,32 @@
int main(int argc, char* argv[]) {
testing::InitGoogleTest(&argc, argv);
- ABSL_RAW_LOG(INFO, "ABSL_HAVE_ACCELERATED_AES=%d", ABSL_HAVE_ACCELERATED_AES);
- ABSL_RAW_LOG(INFO, "ABSL_RANDOM_INTERNAL_AES_DISPATCH=%d",
- ABSL_RANDOM_INTERNAL_AES_DISPATCH);
+ LOG(INFO) << "ABSL_HAVE_ACCELERATED_AES=" << ABSL_HAVE_ACCELERATED_AES;
+ LOG(INFO) << "ABSL_RANDOM_INTERNAL_AES_DISPATCH="
+ << ABSL_RANDOM_INTERNAL_AES_DISPATCH;
#if defined(ABSL_ARCH_X86_64)
- ABSL_RAW_LOG(INFO, "ABSL_ARCH_X86_64");
+ LOG(INFO) << "ABSL_ARCH_X86_64";
#elif defined(ABSL_ARCH_X86_32)
- ABSL_RAW_LOG(INFO, "ABSL_ARCH_X86_32");
+ LOG(INFO) << "ABSL_ARCH_X86_32";
#elif defined(ABSL_ARCH_AARCH64)
- ABSL_RAW_LOG(INFO, "ABSL_ARCH_AARCH64");
+ LOG(INFO) << "ABSL_ARCH_AARCH64";
#elif defined(ABSL_ARCH_ARM)
- ABSL_RAW_LOG(INFO, "ABSL_ARCH_ARM");
+ LOG(INFO) << "ABSL_ARCH_ARM";
#elif defined(ABSL_ARCH_PPC)
- ABSL_RAW_LOG(INFO, "ABSL_ARCH_PPC");
+ LOG(INFO) << "ABSL_ARCH_PPC";
#else
- ABSL_RAW_LOG(INFO, "ARCH Unknown");
+ LOG(INFO) << "ARCH Unknown";
#endif
int x = absl::random_internal::HasRandenHwAesImplementation();
- ABSL_RAW_LOG(INFO, "HasRandenHwAesImplementation = %d", x);
+ LOG(INFO) << "HasRandenHwAesImplementation = " << x;
int y = absl::random_internal::CPUSupportsRandenHwAes();
- ABSL_RAW_LOG(INFO, "CPUSupportsRandenHwAes = %d", x);
+ LOG(INFO) << "CPUSupportsRandenHwAes = " << x;
if (!x || !y) {
- ABSL_RAW_LOG(INFO, "Skipping Randen HWAES tests.");
+ LOG(INFO) << "Skipping Randen HWAES tests.";
return 0;
}
return RUN_ALL_TESTS();
diff --git a/absl/random/internal/randen_slow.cc b/absl/random/internal/randen_slow.cc
index d5c9347..9bfd2a4 100644
--- a/absl/random/internal/randen_slow.cc
+++ b/absl/random/internal/randen_slow.cc
@@ -395,6 +395,23 @@
}
}
+// Enables native loads in the round loop by pre-swapping.
+inline ABSL_RANDOM_INTERNAL_ATTRIBUTE_ALWAYS_INLINE void SwapEndian(
+ absl::uint128* state) {
+#ifdef ABSL_IS_BIG_ENDIAN
+ for (uint32_t block = 0; block < RandenTraits::kFeistelBlocks; ++block) {
+ uint64_t new_lo = absl::little_endian::ToHost64(
+ static_cast<uint64_t>(state[block] >> 64));
+ uint64_t new_hi = absl::little_endian::ToHost64(
+ static_cast<uint64_t>((state[block] << 64) >> 64));
+ state[block] = (static_cast<absl::uint128>(new_hi) << 64) | new_lo;
+ }
+#else
+ // Avoid warning about unused variable.
+ (void)state;
+#endif
+}
+
} // namespace
namespace absl {
@@ -439,8 +456,12 @@
const absl::uint128 prev_inner = state[0];
+ SwapEndian(state);
+
Permute(state, keys);
+ SwapEndian(state);
+
// Ensure backtracking resistance.
*state ^= prev_inner;
}
diff --git a/absl/random/internal/randen_slow_test.cc b/absl/random/internal/randen_slow_test.cc
index 4861ffa..ed60395 100644
--- a/absl/random/internal/randen_slow_test.cc
+++ b/absl/random/internal/randen_slow_test.cc
@@ -25,40 +25,37 @@
using absl::random_internal::RandenSlow;
using absl::random_internal::RandenTraits;
-// Local state parameters.
-constexpr size_t kSeedBytes =
- RandenTraits::kStateBytes - RandenTraits::kCapacityBytes;
-constexpr size_t kStateSizeT = RandenTraits::kStateBytes / sizeof(uint64_t);
-constexpr size_t kSeedSizeT = kSeedBytes / sizeof(uint32_t);
-
-struct alignas(16) randen {
- uint64_t state[kStateSizeT];
- uint32_t seed[kSeedSizeT];
-};
-
TEST(RandenSlowTest, Default) {
- constexpr uint64_t kGolden[] = {
- 0x6c6534090ee6d3ee, 0x044e2b9b9d5333c6, 0xc3c14f134e433977,
- 0xdda9f47cd90410ee, 0x887bf3087fd8ca10, 0xf0b780f545c72912,
- 0x15dbb1d37696599f, 0x30ec63baff3c6d59, 0xb29f73606f7f20a6,
- 0x02808a316f49a54c, 0x3b8feaf9d5c8e50e, 0x9cbf605e3fd9de8a,
- 0xc970ae1a78183bbb, 0xd8b2ffd356301ed5, 0xf4b327fe0fc73c37,
- 0xcdfd8d76eb8f9a19, 0xc3a506eb91420c9d, 0xd5af05dd3eff9556,
- 0x48db1bb78f83c4a1, 0x7023920e0d6bfe8c, 0x58d3575834956d42,
- 0xed1ef4c26b87b840, 0x8eef32a23e0b2df3, 0x497cabf3431154fc,
- 0x4e24370570029a8b, 0xd88b5749f090e5ea, 0xc651a582a970692f,
- 0x78fcec2cbb6342f5, 0x463cb745612f55db, 0x352ee4ad1816afe3,
- 0x026ff374c101da7e, 0x811ef0821c3de851,
+ constexpr uint8_t kGolden[] = {
+ 0xee, 0xd3, 0xe6, 0x0e, 0x09, 0x34, 0x65, 0x6c, 0xc6, 0x33, 0x53, 0x9d,
+ 0x9b, 0x2b, 0x4e, 0x04, 0x77, 0x39, 0x43, 0x4e, 0x13, 0x4f, 0xc1, 0xc3,
+ 0xee, 0x10, 0x04, 0xd9, 0x7c, 0xf4, 0xa9, 0xdd, 0x10, 0xca, 0xd8, 0x7f,
+ 0x08, 0xf3, 0x7b, 0x88, 0x12, 0x29, 0xc7, 0x45, 0xf5, 0x80, 0xb7, 0xf0,
+ 0x9f, 0x59, 0x96, 0x76, 0xd3, 0xb1, 0xdb, 0x15, 0x59, 0x6d, 0x3c, 0xff,
+ 0xba, 0x63, 0xec, 0x30, 0xa6, 0x20, 0x7f, 0x6f, 0x60, 0x73, 0x9f, 0xb2,
+ 0x4c, 0xa5, 0x49, 0x6f, 0x31, 0x8a, 0x80, 0x02, 0x0e, 0xe5, 0xc8, 0xd5,
+ 0xf9, 0xea, 0x8f, 0x3b, 0x8a, 0xde, 0xd9, 0x3f, 0x5e, 0x60, 0xbf, 0x9c,
+ 0xbb, 0x3b, 0x18, 0x78, 0x1a, 0xae, 0x70, 0xc9, 0xd5, 0x1e, 0x30, 0x56,
+ 0xd3, 0xff, 0xb2, 0xd8, 0x37, 0x3c, 0xc7, 0x0f, 0xfe, 0x27, 0xb3, 0xf4,
+ 0x19, 0x9a, 0x8f, 0xeb, 0x76, 0x8d, 0xfd, 0xcd, 0x9d, 0x0c, 0x42, 0x91,
+ 0xeb, 0x06, 0xa5, 0xc3, 0x56, 0x95, 0xff, 0x3e, 0xdd, 0x05, 0xaf, 0xd5,
+ 0xa1, 0xc4, 0x83, 0x8f, 0xb7, 0x1b, 0xdb, 0x48, 0x8c, 0xfe, 0x6b, 0x0d,
+ 0x0e, 0x92, 0x23, 0x70, 0x42, 0x6d, 0x95, 0x34, 0x58, 0x57, 0xd3, 0x58,
+ 0x40, 0xb8, 0x87, 0x6b, 0xc2, 0xf4, 0x1e, 0xed, 0xf3, 0x2d, 0x0b, 0x3e,
+ 0xa2, 0x32, 0xef, 0x8e, 0xfc, 0x54, 0x11, 0x43, 0xf3, 0xab, 0x7c, 0x49,
+ 0x8b, 0x9a, 0x02, 0x70, 0x05, 0x37, 0x24, 0x4e, 0xea, 0xe5, 0x90, 0xf0,
+ 0x49, 0x57, 0x8b, 0xd8, 0x2f, 0x69, 0x70, 0xa9, 0x82, 0xa5, 0x51, 0xc6,
+ 0xf5, 0x42, 0x63, 0xbb, 0x2c, 0xec, 0xfc, 0x78, 0xdb, 0x55, 0x2f, 0x61,
+ 0x45, 0xb7, 0x3c, 0x46, 0xe3, 0xaf, 0x16, 0x18, 0xad, 0xe4, 0x2e, 0x35,
+ 0x7e, 0xda, 0x01, 0xc1, 0x74, 0xf3, 0x6f, 0x02, 0x51, 0xe8, 0x3d, 0x1c,
+ 0x82, 0xf0, 0x1e, 0x81,
};
- alignas(16) randen d;
- std::memset(d.state, 0, sizeof(d.state));
- RandenSlow::Generate(RandenSlow::GetKeys(), d.state);
+ alignas(16) uint8_t state[RandenTraits::kStateBytes];
+ std::memset(state, 0, sizeof(state));
- uint64_t* id = d.state;
- for (const auto& elem : kGolden) {
- EXPECT_EQ(absl::little_endian::FromHost64(elem), *id++);
- }
+ RandenSlow::Generate(RandenSlow::GetKeys(), state);
+ EXPECT_EQ(0, std::memcmp(state, kGolden, sizeof(state)));
}
} // namespace
diff --git a/absl/random/internal/randen_test.cc b/absl/random/internal/randen_test.cc
index c186fe0..92773b8 100644
--- a/absl/random/internal/randen_test.cc
+++ b/absl/random/internal/randen_test.cc
@@ -23,9 +23,6 @@
using absl::random_internal::Randen;
-// Local state parameters.
-constexpr size_t kStateSizeT = Randen::kStateBytes / sizeof(uint64_t);
-
TEST(RandenTest, CopyAndMove) {
static_assert(std::is_copy_constructible<Randen>::value,
"Randen must be copy constructible");
@@ -41,30 +38,38 @@
}
TEST(RandenTest, Default) {
- constexpr uint64_t kGolden[] = {
- 0x6c6534090ee6d3ee, 0x044e2b9b9d5333c6, 0xc3c14f134e433977,
- 0xdda9f47cd90410ee, 0x887bf3087fd8ca10, 0xf0b780f545c72912,
- 0x15dbb1d37696599f, 0x30ec63baff3c6d59, 0xb29f73606f7f20a6,
- 0x02808a316f49a54c, 0x3b8feaf9d5c8e50e, 0x9cbf605e3fd9de8a,
- 0xc970ae1a78183bbb, 0xd8b2ffd356301ed5, 0xf4b327fe0fc73c37,
- 0xcdfd8d76eb8f9a19, 0xc3a506eb91420c9d, 0xd5af05dd3eff9556,
- 0x48db1bb78f83c4a1, 0x7023920e0d6bfe8c, 0x58d3575834956d42,
- 0xed1ef4c26b87b840, 0x8eef32a23e0b2df3, 0x497cabf3431154fc,
- 0x4e24370570029a8b, 0xd88b5749f090e5ea, 0xc651a582a970692f,
- 0x78fcec2cbb6342f5, 0x463cb745612f55db, 0x352ee4ad1816afe3,
- 0x026ff374c101da7e, 0x811ef0821c3de851,
+ constexpr uint8_t kGolden[] = {
+ 0xee, 0xd3, 0xe6, 0x0e, 0x09, 0x34, 0x65, 0x6c, 0xc6, 0x33, 0x53, 0x9d,
+ 0x9b, 0x2b, 0x4e, 0x04, 0x77, 0x39, 0x43, 0x4e, 0x13, 0x4f, 0xc1, 0xc3,
+ 0xee, 0x10, 0x04, 0xd9, 0x7c, 0xf4, 0xa9, 0xdd, 0x10, 0xca, 0xd8, 0x7f,
+ 0x08, 0xf3, 0x7b, 0x88, 0x12, 0x29, 0xc7, 0x45, 0xf5, 0x80, 0xb7, 0xf0,
+ 0x9f, 0x59, 0x96, 0x76, 0xd3, 0xb1, 0xdb, 0x15, 0x59, 0x6d, 0x3c, 0xff,
+ 0xba, 0x63, 0xec, 0x30, 0xa6, 0x20, 0x7f, 0x6f, 0x60, 0x73, 0x9f, 0xb2,
+ 0x4c, 0xa5, 0x49, 0x6f, 0x31, 0x8a, 0x80, 0x02, 0x0e, 0xe5, 0xc8, 0xd5,
+ 0xf9, 0xea, 0x8f, 0x3b, 0x8a, 0xde, 0xd9, 0x3f, 0x5e, 0x60, 0xbf, 0x9c,
+ 0xbb, 0x3b, 0x18, 0x78, 0x1a, 0xae, 0x70, 0xc9, 0xd5, 0x1e, 0x30, 0x56,
+ 0xd3, 0xff, 0xb2, 0xd8, 0x37, 0x3c, 0xc7, 0x0f, 0xfe, 0x27, 0xb3, 0xf4,
+ 0x19, 0x9a, 0x8f, 0xeb, 0x76, 0x8d, 0xfd, 0xcd, 0x9d, 0x0c, 0x42, 0x91,
+ 0xeb, 0x06, 0xa5, 0xc3, 0x56, 0x95, 0xff, 0x3e, 0xdd, 0x05, 0xaf, 0xd5,
+ 0xa1, 0xc4, 0x83, 0x8f, 0xb7, 0x1b, 0xdb, 0x48, 0x8c, 0xfe, 0x6b, 0x0d,
+ 0x0e, 0x92, 0x23, 0x70, 0x42, 0x6d, 0x95, 0x34, 0x58, 0x57, 0xd3, 0x58,
+ 0x40, 0xb8, 0x87, 0x6b, 0xc2, 0xf4, 0x1e, 0xed, 0xf3, 0x2d, 0x0b, 0x3e,
+ 0xa2, 0x32, 0xef, 0x8e, 0xfc, 0x54, 0x11, 0x43, 0xf3, 0xab, 0x7c, 0x49,
+ 0x8b, 0x9a, 0x02, 0x70, 0x05, 0x37, 0x24, 0x4e, 0xea, 0xe5, 0x90, 0xf0,
+ 0x49, 0x57, 0x8b, 0xd8, 0x2f, 0x69, 0x70, 0xa9, 0x82, 0xa5, 0x51, 0xc6,
+ 0xf5, 0x42, 0x63, 0xbb, 0x2c, 0xec, 0xfc, 0x78, 0xdb, 0x55, 0x2f, 0x61,
+ 0x45, 0xb7, 0x3c, 0x46, 0xe3, 0xaf, 0x16, 0x18, 0xad, 0xe4, 0x2e, 0x35,
+ 0x7e, 0xda, 0x01, 0xc1, 0x74, 0xf3, 0x6f, 0x02, 0x51, 0xe8, 0x3d, 0x1c,
+ 0x82, 0xf0, 0x1e, 0x81,
};
- alignas(16) uint64_t state[kStateSizeT];
+ alignas(16) uint8_t state[Randen::kStateBytes];
std::memset(state, 0, sizeof(state));
Randen r;
r.Generate(state);
- auto id = std::begin(state);
- for (const auto& elem : kGolden) {
- EXPECT_EQ(elem, *id++);
- }
+ EXPECT_EQ(0, std::memcmp(state, kGolden, sizeof(state)));
}
} // namespace
diff --git a/absl/random/internal/salted_seed_seq.h b/absl/random/internal/salted_seed_seq.h
index 5953a09..0629186 100644
--- a/absl/random/internal/salted_seed_seq.h
+++ b/absl/random/internal/salted_seed_seq.h
@@ -22,6 +22,7 @@
#include <memory>
#include <type_traits>
#include <utility>
+#include <vector>
#include "absl/container/inlined_vector.h"
#include "absl/meta/type_traits.h"
@@ -65,15 +66,19 @@
template <typename RandomAccessIterator>
void generate(RandomAccessIterator begin, RandomAccessIterator end) {
+ using U = typename std::iterator_traits<RandomAccessIterator>::value_type;
+
// The common case is that generate is called with ContiguousIterators
// to uint arrays. Such contiguous memory regions may be optimized,
// which we detect here.
- using tag = absl::conditional_t<
- (std::is_pointer<RandomAccessIterator>::value &&
- std::is_same<absl::decay_t<decltype(*begin)>, uint32_t>::value),
+ using TagType = absl::conditional_t<
+ (std::is_same<U, uint32_t>::value &&
+ (std::is_pointer<RandomAccessIterator>::value ||
+ std::is_same<RandomAccessIterator,
+ typename std::vector<U>::iterator>::value)),
ContiguousAndUint32Tag, DefaultTag>;
if (begin != end) {
- generate_impl(begin, end, tag{});
+ generate_impl(TagType{}, begin, end, std::distance(begin, end));
}
}
@@ -89,8 +94,15 @@
struct DefaultTag {};
// Generate which requires the iterators are contiguous pointers to uint32_t.
- void generate_impl(uint32_t* begin, uint32_t* end, ContiguousAndUint32Tag) {
- generate_contiguous(absl::MakeSpan(begin, end));
+ // Fills the initial seed buffer the underlying SSeq::generate() call,
+ // then mixes in the salt material.
+ template <typename Contiguous>
+ void generate_impl(ContiguousAndUint32Tag, Contiguous begin, Contiguous end,
+ size_t n) {
+ seq_->generate(begin, end);
+ const uint32_t salt = absl::random_internal::GetSaltMaterial().value_or(0);
+ auto span = absl::Span<uint32_t>(&*begin, n);
+ MixIntoSeedMaterial(absl::MakeConstSpan(&salt, 1), span);
}
// The uncommon case for generate is that it is called with iterators over
@@ -98,27 +110,13 @@
// case we allocate a temporary 32-bit buffer and then copy-assign back
// to the initial inputs.
template <typename RandomAccessIterator>
- void generate_impl(RandomAccessIterator begin, RandomAccessIterator end,
- DefaultTag) {
- return generate_and_copy(std::distance(begin, end), begin);
- }
-
- // Fills the initial seed buffer the underlying SSeq::generate() call,
- // mixing in the salt material.
- void generate_contiguous(absl::Span<uint32_t> buffer) {
- seq_->generate(buffer.begin(), buffer.end());
- const uint32_t salt = absl::random_internal::GetSaltMaterial().value_or(0);
- MixIntoSeedMaterial(absl::MakeConstSpan(&salt, 1), buffer);
- }
-
- // Allocates a seed buffer of `n` elements, generates the seed, then
- // copies the result into the `out` iterator.
- template <typename Iterator>
- void generate_and_copy(size_t n, Iterator out) {
- // Allocate a temporary buffer, generate, and then copy.
+ void generate_impl(DefaultTag, RandomAccessIterator begin,
+ RandomAccessIterator, size_t n) {
+ // Allocates a seed buffer of `n` elements, generates the seed, then
+ // copies the result into the `out` iterator.
absl::InlinedVector<uint32_t, 8> data(n, 0);
- generate_contiguous(absl::MakeSpan(data.data(), data.size()));
- std::copy(data.begin(), data.end(), out);
+ generate_impl(ContiguousAndUint32Tag{}, data.begin(), data.end(), n);
+ std::copy(data.begin(), data.end(), begin);
}
// Because [rand.req.seedseq] is not required to be copy-constructible,
diff --git a/absl/random/internal/seed_material.cc b/absl/random/internal/seed_material.cc
index c03cad8..1041302 100644
--- a/absl/random/internal/seed_material.cc
+++ b/absl/random/internal/seed_material.cc
@@ -173,12 +173,12 @@
}
while (success && buffer_size > 0) {
- int bytes_read = read(dev_urandom, buffer, buffer_size);
+ ssize_t bytes_read = read(dev_urandom, buffer, buffer_size);
int read_error = errno;
success = (bytes_read > 0);
if (success) {
buffer += bytes_read;
- buffer_size -= bytes_read;
+ buffer_size -= static_cast<size_t>(bytes_read);
} else if (bytes_read == -1 && read_error == EINTR) {
success = true; // Need to try again.
}
diff --git a/absl/random/internal/traits.h b/absl/random/internal/traits.h
index 75772bd..f874a0f 100644
--- a/absl/random/internal/traits.h
+++ b/absl/random/internal/traits.h
@@ -20,6 +20,8 @@
#include <type_traits>
#include "absl/base/config.h"
+#include "absl/numeric/bits.h"
+#include "absl/numeric/int128.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
@@ -59,6 +61,31 @@
rank<A>() <= rank<B>();
};
+template <typename T>
+struct IsIntegral : std::is_integral<T> {};
+template <>
+struct IsIntegral<absl::int128> : std::true_type {};
+template <>
+struct IsIntegral<absl::uint128> : std::true_type {};
+
+template <typename T>
+struct MakeUnsigned : std::make_unsigned<T> {};
+template <>
+struct MakeUnsigned<absl::int128> {
+ using type = absl::uint128;
+};
+template <>
+struct MakeUnsigned<absl::uint128> {
+ using type = absl::uint128;
+};
+
+template <typename T>
+struct IsUnsigned : std::is_unsigned<T> {};
+template <>
+struct IsUnsigned<absl::int128> : std::false_type {};
+template <>
+struct IsUnsigned<absl::uint128> : std::true_type {};
+
// unsigned_bits<N>::type returns the unsigned int type with the indicated
// number of bits.
template <size_t N>
@@ -81,19 +108,40 @@
using type = uint64_t;
};
-#ifdef ABSL_HAVE_INTRINSIC_INT128
template <>
struct unsigned_bits<128> {
- using type = __uint128_t;
+ using type = absl::uint128;
};
-#endif
+
+// 256-bit wrapper for wide multiplications.
+struct U256 {
+ uint128 hi;
+ uint128 lo;
+};
+template <>
+struct unsigned_bits<256> {
+ using type = U256;
+};
template <typename IntType>
struct make_unsigned_bits {
- using type = typename unsigned_bits<std::numeric_limits<
- typename std::make_unsigned<IntType>::type>::digits>::type;
+ using type = typename unsigned_bits<
+ std::numeric_limits<typename MakeUnsigned<IntType>::type>::digits>::type;
};
+template <typename T>
+int BitWidth(T v) {
+ // Workaround for bit_width not supporting int128.
+ // Don't hardcode `64` to make sure this code does not trigger compiler
+ // warnings in smaller types.
+ constexpr int half_bits = sizeof(T) * 8 / 2;
+ if (sizeof(T) == 16 && (v >> half_bits) != 0) {
+ return bit_width(static_cast<uint64_t>(v >> half_bits)) + half_bits;
+ } else {
+ return bit_width(static_cast<uint64_t>(v));
+ }
+}
+
} // namespace random_internal
ABSL_NAMESPACE_END
} // namespace absl
diff --git a/absl/random/internal/uniform_helper.h b/absl/random/internal/uniform_helper.h
index 1243bc1..db737e1 100644
--- a/absl/random/internal/uniform_helper.h
+++ b/absl/random/internal/uniform_helper.h
@@ -100,7 +100,7 @@
template <typename IntType, typename Tag>
typename absl::enable_if_t<
absl::conjunction<
- std::is_integral<IntType>,
+ IsIntegral<IntType>,
absl::disjunction<std::is_same<Tag, IntervalOpenClosedTag>,
std::is_same<Tag, IntervalOpenOpenTag>>>::value,
IntType>
@@ -131,7 +131,7 @@
template <typename IntType, typename Tag>
typename absl::enable_if_t<
absl::conjunction<
- std::is_integral<IntType>,
+ IsIntegral<IntType>,
absl::disjunction<std::is_same<Tag, IntervalClosedOpenTag>,
std::is_same<Tag, IntervalOpenOpenTag>>>::value,
IntType>
@@ -153,7 +153,7 @@
template <typename IntType, typename Tag>
typename absl::enable_if_t<
absl::conjunction<
- std::is_integral<IntType>,
+ IsIntegral<IntType>,
absl::disjunction<std::is_same<Tag, IntervalClosedClosedTag>,
std::is_same<Tag, IntervalOpenClosedTag>>>::value,
IntType>
@@ -201,7 +201,7 @@
}
template <typename IntType>
-absl::enable_if_t<std::is_integral<IntType>::value, bool>
+absl::enable_if_t<IsIntegral<IntType>::value, bool>
is_uniform_range_valid(IntType a, IntType b) {
return a <= b;
}
@@ -210,14 +210,14 @@
// or absl::uniform_real_distribution depending on the NumType parameter.
template <typename NumType>
using UniformDistribution =
- typename std::conditional<std::is_integral<NumType>::value,
+ typename std::conditional<IsIntegral<NumType>::value,
absl::uniform_int_distribution<NumType>,
absl::uniform_real_distribution<NumType>>::type;
// UniformDistributionWrapper is used as the underlying distribution type
// by the absl::Uniform template function. It selects the proper Abseil
// uniform distribution and provides constructor overloads that match the
-// expected parameter order as well as adjusting distribtuion bounds based
+// expected parameter order as well as adjusting distribution bounds based
// on the tag.
template <typename NumType>
struct UniformDistributionWrapper : public UniformDistribution<NumType> {
diff --git a/absl/random/internal/wide_multiply.h b/absl/random/internal/wide_multiply.h
index b6e6c4b..891e363 100644
--- a/absl/random/internal/wide_multiply.h
+++ b/absl/random/internal/wide_multiply.h
@@ -34,43 +34,6 @@
ABSL_NAMESPACE_BEGIN
namespace random_internal {
-// Helper object to multiply two 64-bit values to a 128-bit value.
-// MultiplyU64ToU128 multiplies two 64-bit values to a 128-bit value.
-// If an intrinsic is available, it is used, otherwise use native 32-bit
-// multiplies to construct the result.
-inline absl::uint128 MultiplyU64ToU128(uint64_t a, uint64_t b) {
-#if defined(ABSL_HAVE_INTRINSIC_INT128)
- return absl::uint128(static_cast<__uint128_t>(a) * b);
-#elif defined(ABSL_INTERNAL_USE_UMUL128)
- // uint64_t * uint64_t => uint128 multiply using imul intrinsic on MSVC.
- uint64_t high = 0;
- const uint64_t low = _umul128(a, b, &high);
- return absl::MakeUint128(high, low);
-#else
- // uint128(a) * uint128(b) in emulated mode computes a full 128-bit x 128-bit
- // multiply. However there are many cases where that is not necessary, and it
- // is only necessary to support a 64-bit x 64-bit = 128-bit multiply. This is
- // for those cases.
- const uint64_t a00 = static_cast<uint32_t>(a);
- const uint64_t a32 = a >> 32;
- const uint64_t b00 = static_cast<uint32_t>(b);
- const uint64_t b32 = b >> 32;
-
- const uint64_t c00 = a00 * b00;
- const uint64_t c32a = a00 * b32;
- const uint64_t c32b = a32 * b00;
- const uint64_t c64 = a32 * b32;
-
- const uint32_t carry =
- static_cast<uint32_t>(((c00 >> 32) + static_cast<uint32_t>(c32a) +
- static_cast<uint32_t>(c32b)) >>
- 32);
-
- return absl::MakeUint128(c64 + (c32a >> 32) + (c32b >> 32) + carry,
- c00 + (c32a << 32) + (c32b << 32));
-#endif
-}
-
// wide_multiply<T> multiplies two N-bit values to a 2N-bit result.
template <typename UIntType>
struct wide_multiply {
@@ -82,27 +45,49 @@
return static_cast<result_type>(a) * b;
}
- static input_type hi(result_type r) { return r >> kN; }
- static input_type lo(result_type r) { return r; }
+ static input_type hi(result_type r) {
+ return static_cast<input_type>(r >> kN);
+ }
+ static input_type lo(result_type r) { return static_cast<input_type>(r); }
static_assert(std::is_unsigned<UIntType>::value,
"Class-template wide_multiply<> argument must be unsigned.");
};
-#ifndef ABSL_HAVE_INTRINSIC_INT128
-template <>
-struct wide_multiply<uint64_t> {
- using input_type = uint64_t;
- using result_type = absl::uint128;
+// MultiplyU128ToU256 multiplies two 128-bit values to a 256-bit value.
+inline U256 MultiplyU128ToU256(uint128 a, uint128 b) {
+ const uint128 a00 = static_cast<uint64_t>(a);
+ const uint128 a64 = a >> 64;
+ const uint128 b00 = static_cast<uint64_t>(b);
+ const uint128 b64 = b >> 64;
- static result_type multiply(uint64_t a, uint64_t b) {
- return MultiplyU64ToU128(a, b);
+ const uint128 c00 = a00 * b00;
+ const uint128 c64a = a00 * b64;
+ const uint128 c64b = a64 * b00;
+ const uint128 c128 = a64 * b64;
+
+ const uint64_t carry =
+ static_cast<uint64_t>(((c00 >> 64) + static_cast<uint64_t>(c64a) +
+ static_cast<uint64_t>(c64b)) >>
+ 64);
+
+ return {c128 + (c64a >> 64) + (c64b >> 64) + carry,
+ c00 + (c64a << 64) + (c64b << 64)};
+}
+
+
+template <>
+struct wide_multiply<uint128> {
+ using input_type = uint128;
+ using result_type = U256;
+
+ static result_type multiply(input_type a, input_type b) {
+ return MultiplyU128ToU256(a, b);
}
- static uint64_t hi(result_type r) { return absl::Uint128High64(r); }
- static uint64_t lo(result_type r) { return absl::Uint128Low64(r); }
+ static input_type hi(result_type r) { return r.hi; }
+ static input_type lo(result_type r) { return r.lo; }
};
-#endif
} // namespace random_internal
ABSL_NAMESPACE_END
diff --git a/absl/random/internal/wide_multiply_test.cc b/absl/random/internal/wide_multiply_test.cc
index e276cb5..f8ee35c 100644
--- a/absl/random/internal/wide_multiply_test.cc
+++ b/absl/random/internal/wide_multiply_test.cc
@@ -14,52 +14,106 @@
#include "absl/random/internal/wide_multiply.h"
+#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/numeric/int128.h"
-using absl::random_internal::MultiplyU64ToU128;
+using absl::random_internal::MultiplyU128ToU256;
+using absl::random_internal::U256;
namespace {
-TEST(WideMultiplyTest, MultiplyU64ToU128Test) {
- constexpr uint64_t k1 = 1;
- constexpr uint64_t kMax = ~static_cast<uint64_t>(0);
+U256 LeftShift(U256 v, int s) {
+ if (s == 0) {
+ return v;
+ } else if (s < 128) {
+ return {(v.hi << s) | (v.lo >> (128 - s)), v.lo << s};
+ } else {
+ return {v.lo << (s - 128), 0};
+ }
+}
- EXPECT_EQ(absl::uint128(0), MultiplyU64ToU128(0, 0));
+MATCHER_P2(Eq256, hi, lo, "") { return arg.hi == hi && arg.lo == lo; }
+MATCHER_P(Eq256, v, "") { return arg.hi == v.hi && arg.lo == v.lo; }
- // Max uint64_t
- EXPECT_EQ(MultiplyU64ToU128(kMax, kMax),
- absl::MakeUint128(0xfffffffffffffffe, 0x0000000000000001));
- EXPECT_EQ(absl::MakeUint128(0, kMax), MultiplyU64ToU128(kMax, 1));
- EXPECT_EQ(absl::MakeUint128(0, kMax), MultiplyU64ToU128(1, kMax));
+TEST(WideMultiplyTest, MultiplyU128ToU256Test) {
+ using absl::uint128;
+ constexpr uint128 k1 = 1;
+ constexpr uint128 kMax = ~static_cast<uint128>(0);
+
+ EXPECT_THAT(MultiplyU128ToU256(0, 0), Eq256(0, 0));
+
+ // Max uin128_t
+ EXPECT_THAT(MultiplyU128ToU256(kMax, kMax), Eq256(kMax << 1, 1));
+ EXPECT_THAT(MultiplyU128ToU256(kMax, 1), Eq256(0, kMax));
+ EXPECT_THAT(MultiplyU128ToU256(1, kMax), Eq256(0, kMax));
for (int i = 0; i < 64; ++i) {
- EXPECT_EQ(absl::MakeUint128(0, kMax) << i,
- MultiplyU64ToU128(kMax, k1 << i));
- EXPECT_EQ(absl::MakeUint128(0, kMax) << i,
- MultiplyU64ToU128(k1 << i, kMax));
+ SCOPED_TRACE(i);
+ EXPECT_THAT(MultiplyU128ToU256(kMax, k1 << i),
+ Eq256(LeftShift({0, kMax}, i)));
+ EXPECT_THAT(MultiplyU128ToU256(k1 << i, kMax),
+ Eq256(LeftShift({0, kMax}, i)));
}
// 1-bit x 1-bit.
for (int i = 0; i < 64; ++i) {
for (int j = 0; j < 64; ++j) {
- EXPECT_EQ(absl::MakeUint128(0, 1) << (i + j),
- MultiplyU64ToU128(k1 << i, k1 << j));
- EXPECT_EQ(absl::MakeUint128(0, 1) << (i + j),
- MultiplyU64ToU128(k1 << i, k1 << j));
+ EXPECT_THAT(MultiplyU128ToU256(k1 << i, k1 << j),
+ Eq256(LeftShift({0, 1}, i + j)));
}
}
// Verified multiplies
- EXPECT_EQ(MultiplyU64ToU128(0xffffeeeeddddcccc, 0xbbbbaaaa99998888),
- absl::MakeUint128(0xbbbb9e2692c5dddc, 0xc28f7531048d2c60));
- EXPECT_EQ(MultiplyU64ToU128(0x0123456789abcdef, 0xfedcba9876543210),
- absl::MakeUint128(0x0121fa00ad77d742, 0x2236d88fe5618cf0));
- EXPECT_EQ(MultiplyU64ToU128(0x0123456789abcdef, 0xfdb97531eca86420),
- absl::MakeUint128(0x0120ae99d26725fc, 0xce197f0ecac319e0));
- EXPECT_EQ(MultiplyU64ToU128(0x97a87f4f261ba3f2, 0xfedcba9876543210),
- absl::MakeUint128(0x96fbf1a8ae78d0ba, 0x5a6dd4b71f278320));
- EXPECT_EQ(MultiplyU64ToU128(0xfedcba9876543210, 0xfdb97531eca86420),
- absl::MakeUint128(0xfc98c6981a413e22, 0x342d0bbf48948200));
+ EXPECT_THAT(MultiplyU128ToU256(
+ absl::MakeUint128(0xc502da0d6ea99fe8, 0xfa3c9141a1f50912),
+ absl::MakeUint128(0x96bcf1ac37f97bd6, 0x27e2cdeb5fb2299e)),
+ Eq256(absl::MakeUint128(0x740113d838f96a64, 0x22e8cfa4d71f89ea),
+ absl::MakeUint128(0x19184a345c62e993, 0x237871b630337b1c)));
+ EXPECT_THAT(MultiplyU128ToU256(
+ absl::MakeUint128(0x6f29e670cee07230, 0xc3d8e6c3e4d86759),
+ absl::MakeUint128(0x3227d29fa6386db1, 0x231682bb1e4b764f)),
+ Eq256(absl::MakeUint128(0x15c779d9d5d3b07c, 0xd7e6c827f0c81cbe),
+ absl::MakeUint128(0xf88e3914f7fa287a, 0x15b79975137dea77)));
+ EXPECT_THAT(MultiplyU128ToU256(
+ absl::MakeUint128(0xafb77107215646e1, 0x3b844cb1ac5769e7),
+ absl::MakeUint128(0x1ff7b2d888b62479, 0x92f758ae96fcba0b)),
+ Eq256(absl::MakeUint128(0x15f13b70181f6985, 0x2adb36bbabce7d02),
+ absl::MakeUint128(0x6c470d72e13aad04, 0x63fba3f5841762ed)));
+ EXPECT_THAT(MultiplyU128ToU256(
+ absl::MakeUint128(0xd85d5558d67ac905, 0xf88c70654dae19b1),
+ absl::MakeUint128(0x17252c6727db3738, 0x399ff658c511eedc)),
+ Eq256(absl::MakeUint128(0x138fcdaf8b0421ee, 0x1b465ddf2a0d03f6),
+ absl::MakeUint128(0x8f573ba68296860f, 0xf327d2738741a21c)));
+ EXPECT_THAT(MultiplyU128ToU256(
+ absl::MakeUint128(0x46f0421a37ff6bee, 0xa61df89f09d140b1),
+ absl::MakeUint128(0x3d712ec9f37ca2e1, 0x9658a2cba47ef4b1)),
+ Eq256(absl::MakeUint128(0x11069cc48ee7c95d, 0xd35fb1c7aa91c978),
+ absl::MakeUint128(0xbe2f4a6de874b015, 0xd2f7ac1b76746e61)));
+ EXPECT_THAT(MultiplyU128ToU256(
+ absl::MakeUint128(0x730d27c72d58fa49, 0x3ebeda7498f8827c),
+ absl::MakeUint128(0xa2c959eca9f503af, 0x189c687eb842bbd8)),
+ Eq256(absl::MakeUint128(0x4928d0ea356ba022, 0x1546d34a2963393),
+ absl::MakeUint128(0x7481531e1e0a16d1, 0xdd8025015cf6aca0)));
+ EXPECT_THAT(MultiplyU128ToU256(
+ absl::MakeUint128(0x6ca41020f856d2f1, 0xb9b0838c04a7f4aa),
+ absl::MakeUint128(0x9cf41d28a8396f54, 0x1d681695e377ffe6)),
+ Eq256(absl::MakeUint128(0x429b92934d9be6f1, 0xea182877157c1e7),
+ absl::MakeUint128(0x7135c23f0a4a475, 0xc1adc366f4a126bc)));
+ EXPECT_THAT(MultiplyU128ToU256(
+ absl::MakeUint128(0x57472833797c332, 0x6c79272fdec4687a),
+ absl::MakeUint128(0xb5f022ea3838e46b, 0x16face2f003e27a6)),
+ Eq256(absl::MakeUint128(0x3e072e0962b3400, 0x5d9fe8fdc3d0e1f4),
+ absl::MakeUint128(0x7dc0df47cedafd62, 0xbe6501f1acd2551c)));
+ EXPECT_THAT(MultiplyU128ToU256(
+ absl::MakeUint128(0xf0fb4198322eb1c2, 0xfe7f5f31f3885938),
+ absl::MakeUint128(0xd99012b71bb7aa31, 0xac7a6f9eb190789)),
+ Eq256(absl::MakeUint128(0xcccc998cf075ca01, 0x642d144322fb873a),
+ absl::MakeUint128(0xc79dc12b69d91ed4, 0xa83459132ce046f8)));
+ EXPECT_THAT(MultiplyU128ToU256(
+ absl::MakeUint128(0xb5c04120848cdb47, 0x8aa62a827bf52635),
+ absl::MakeUint128(0x8d07a359be2f1380, 0x467bb90d59da0dea)),
+ Eq256(absl::MakeUint128(0x64205019d139a9ce, 0x99425c5fb6e7a977),
+ absl::MakeUint128(0xd3e99628a9e5fca7, 0x9c7824cb7279d72)));
}
} // namespace
diff --git a/absl/random/log_uniform_int_distribution.h b/absl/random/log_uniform_int_distribution.h
index 43e1011..4afff8f 100644
--- a/absl/random/log_uniform_int_distribution.h
+++ b/absl/random/log_uniform_int_distribution.h
@@ -69,10 +69,8 @@
if (base_ == 2) {
// Determine where the first set bit is on range(), giving a log2(range)
// value which can be used to construct bounds.
- log_range_ =
- (std::min)(bit_width(range()),
- static_cast<unsigned_type>(
- std::numeric_limits<unsigned_type>::digits));
+ log_range_ = (std::min)(random_internal::BitWidth(range()),
+ std::numeric_limits<unsigned_type>::digits);
} else {
// NOTE: Computing the logN(x) introduces error from 2 sources:
// 1. Conversion of int to double loses precision for values >=
@@ -83,7 +81,7 @@
//
// Thus a result which should equal K may equal K +/- epsilon,
// which can eliminate some values depending on where the bounds fall.
- const double inv_log_base = 1.0 / std::log(base_);
+ const double inv_log_base = 1.0 / std::log(static_cast<double>(base_));
const double log_range = std::log(static_cast<double>(range()) + 0.5);
log_range_ = static_cast<int>(std::ceil(inv_log_base * log_range));
}
@@ -113,7 +111,7 @@
unsigned_type range_; // max - min
int log_range_; // ceil(logN(range_))
- static_assert(std::is_integral<IntType>::value,
+ static_assert(random_internal::IsIntegral<IntType>::value,
"Class-template absl::log_uniform_int_distribution<> must be "
"parameterized using an integral type.");
};
@@ -139,7 +137,7 @@
template <typename URBG>
result_type operator()(URBG& g, // NOLINT(runtime/references)
const param_type& p) {
- return (p.min)() + Generate(g, p);
+ return static_cast<result_type>((p.min)() + Generate(g, p));
}
result_type(min)() const { return (param_.min)(); }
@@ -193,8 +191,8 @@
? (std::numeric_limits<unsigned_type>::max)()
: (static_cast<unsigned_type>(1) << e) - 1;
} else {
- const double r = std::pow(p.base(), d);
- const double s = (r * p.base()) - 1.0;
+ const double r = std::pow(static_cast<double>(p.base()), d);
+ const double s = (r * static_cast<double>(p.base())) - 1.0;
base_e =
(r > static_cast<double>((std::numeric_limits<unsigned_type>::max)()))
@@ -211,7 +209,8 @@
const unsigned_type hi = (top_e >= p.range()) ? p.range() : top_e;
// choose uniformly over [lo, hi]
- return absl::uniform_int_distribution<result_type>(lo, hi)(g);
+ return absl::uniform_int_distribution<result_type>(
+ static_cast<result_type>(lo), static_cast<result_type>(hi))(g);
}
template <typename CharT, typename Traits, typename IntType>
diff --git a/absl/random/log_uniform_int_distribution_test.cc b/absl/random/log_uniform_int_distribution_test.cc
index 5e780d9..5df3eda 100644
--- a/absl/random/log_uniform_int_distribution_test.cc
+++ b/absl/random/log_uniform_int_distribution_test.cc
@@ -24,7 +24,7 @@
#include "gmock/gmock.h"
#include "gtest/gtest.h"
-#include "absl/base/internal/raw_logging.h"
+#include "absl/log/log.h"
#include "absl/random/internal/chi_square.h"
#include "absl/random/internal/distribution_test_util.h"
#include "absl/random/internal/pcg_engine.h"
@@ -42,7 +42,7 @@
using IntTypes = ::testing::Types<int8_t, int16_t, int32_t, int64_t, //
uint8_t, uint16_t, uint32_t, uint64_t>;
-TYPED_TEST_CASE(LogUniformIntDistributionTypeTest, IntTypes);
+TYPED_TEST_SUITE(LogUniformIntDistributionTypeTest, IntTypes);
TYPED_TEST(LogUniformIntDistributionTypeTest, SerializeTest) {
using param_type =
@@ -108,8 +108,7 @@
if (sample > sample_max) sample_max = sample;
if (sample < sample_min) sample_min = sample;
}
- ABSL_INTERNAL_LOG(INFO,
- absl::StrCat("Range: ", +sample_min, ", ", +sample_max));
+ LOG(INFO) << "Range: " << sample_min << ", " << sample_max;
}
}
@@ -182,16 +181,14 @@
const double p = absl::random_internal::ChiSquarePValue(chi_square, dof);
if (chi_square > threshold) {
- ABSL_INTERNAL_LOG(INFO, "values");
+ LOG(INFO) << "values";
for (size_t i = 0; i < buckets.size(); i++) {
- ABSL_INTERNAL_LOG(INFO, absl::StrCat(i, ": ", buckets[i]));
+ LOG(INFO) << i << ": " << buckets[i];
}
- ABSL_INTERNAL_LOG(INFO,
- absl::StrFormat("trials=%d\n"
- "%s(data, %d) = %f (%f)\n"
- "%s @ 0.98 = %f",
- trials, kChiSquared, dof, chi_square, p,
- kChiSquared, threshold));
+ LOG(INFO) << "trials=" << trials << "\n"
+ << kChiSquared << "(data, " << dof << ") = " << chi_square << " ("
+ << p << ")\n"
+ << kChiSquared << " @ 0.98 = " << threshold;
}
return p;
}
diff --git a/absl/random/mocking_bit_gen.h b/absl/random/mocking_bit_gen.h
index 7b2b80e..89fa5a4 100644
--- a/absl/random/mocking_bit_gen.h
+++ b/absl/random/mocking_bit_gen.h
@@ -87,7 +87,7 @@
//
// ON_CALL(absl::MockUniform<int>(), Call(bitgen, testing::_, testing::_))
// .WillByDefault([] (int low, int high) {
-// return (low + high) / 2;
+// return low + (high - low) / 2;
// });
//
// EXPECT_EQ(absl::Uniform<int>(gen, 0, 10), 5);
diff --git a/absl/random/poisson_distribution.h b/absl/random/poisson_distribution.h
index cb5f5d5..f457308 100644
--- a/absl/random/poisson_distribution.h
+++ b/absl/random/poisson_distribution.h
@@ -26,6 +26,7 @@
#include "absl/random/internal/fastmath.h"
#include "absl/random/internal/generate_real.h"
#include "absl/random/internal/iostream_state_saver.h"
+#include "absl/random/internal/traits.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
@@ -80,7 +81,7 @@
double log_k_;
int split_;
- static_assert(std::is_integral<IntType>::value,
+ static_assert(random_internal::IsIntegral<IntType>::value,
"Class-template absl::poisson_distribution<> must be "
"parameterized using an integral type.");
};
@@ -133,7 +134,8 @@
poisson_distribution<IntType>::param_type::param_type(double mean)
: mean_(mean), split_(0) {
assert(mean >= 0);
- assert(mean <= (std::numeric_limits<result_type>::max)());
+ assert(mean <=
+ static_cast<double>((std::numeric_limits<result_type>::max)()));
// As a defensive measure, avoid large values of the mean. The rejection
// algorithm used does not support very large values well. It my be worth
// changing algorithms to better deal with these cases.
@@ -222,8 +224,9 @@
// clang-format on
const double lhs = 2.0 * std::log(u) + p.log_k_ + s;
if (lhs < rhs) {
- return x > (max)() ? (max)()
- : static_cast<result_type>(x); // f(x)/k >= u^2
+ return x > static_cast<double>((max)())
+ ? (max)()
+ : static_cast<result_type>(x); // f(x)/k >= u^2
}
}
}
diff --git a/absl/random/poisson_distribution_test.cc b/absl/random/poisson_distribution_test.cc
index 8baabd1..5475596 100644
--- a/absl/random/poisson_distribution_test.cc
+++ b/absl/random/poisson_distribution_test.cc
@@ -25,9 +25,9 @@
#include "gmock/gmock.h"
#include "gtest/gtest.h"
-#include "absl/base/internal/raw_logging.h"
#include "absl/base/macros.h"
#include "absl/container/flat_hash_map.h"
+#include "absl/log/log.h"
#include "absl/random/internal/chi_square.h"
#include "absl/random/internal/distribution_test_util.h"
#include "absl/random/internal/pcg_engine.h"
@@ -73,7 +73,7 @@
using IntTypes = ::testing::Types<int, int8_t, int16_t, int32_t, int64_t,
uint8_t, uint16_t, uint32_t, uint64_t>;
-TYPED_TEST_CASE(PoissonDistributionInterfaceTest, IntTypes);
+TYPED_TEST_SUITE(PoissonDistributionInterfaceTest, IntTypes);
TYPED_TEST(PoissonDistributionInterfaceTest, SerializeTest) {
using param_type = typename absl::poisson_distribution<TypeParam>::param_type;
@@ -134,8 +134,8 @@
if (sample < sample_min) sample_min = sample;
}
- ABSL_INTERNAL_LOG(INFO, absl::StrCat("Range {", param.mean(), "}: ",
- +sample_min, ", ", +sample_max));
+ LOG(INFO) << "Range {" << param.mean() << "}: " << sample_min << ", "
+ << sample_max;
// Validate stream serialization.
std::stringstream ss;
@@ -188,10 +188,9 @@
}
void LogCDF() {
- ABSL_INTERNAL_LOG(INFO, absl::StrCat("CDF (mean = ", mean_, ")"));
+ LOG(INFO) << "CDF (mean = " << mean_ << ")";
for (const auto c : cdf_) {
- ABSL_INTERNAL_LOG(INFO,
- absl::StrCat(c.index, ": pmf=", c.pmf, " cdf=", c.cdf));
+ LOG(INFO) << c.index << ": pmf=" << c.pmf << " cdf=" << c.cdf;
}
}
@@ -286,16 +285,15 @@
const bool pass = absl::random_internal::Near("z", z, 0.0, max_err);
if (!pass) {
- ABSL_INTERNAL_LOG(
- INFO, absl::StrFormat("p=%f max_err=%f\n"
- " mean=%f vs. %f\n"
- " stddev=%f vs. %f\n"
- " skewness=%f vs. %f\n"
- " kurtosis=%f vs. %f\n"
- " z=%f",
- p, max_err, m.mean, mean(), std::sqrt(m.variance),
- stddev(), m.skewness, skew(), m.kurtosis,
- kurtosis(), z));
+ // clang-format off
+ LOG(INFO)
+ << "p=" << p << " max_err=" << max_err << "\n"
+ " mean=" << m.mean << " vs. " << mean() << "\n"
+ " stddev=" << std::sqrt(m.variance) << " vs. " << stddev() << "\n"
+ " skewness=" << m.skewness << " vs. " << skew() << "\n"
+ " kurtosis=" << m.kurtosis << " vs. " << kurtosis() << "\n"
+ " z=" << z;
+ // clang-format on
}
return pass;
}
@@ -439,17 +437,16 @@
if (chi_square > threshold) {
LogCDF();
- ABSL_INTERNAL_LOG(INFO, absl::StrCat("VALUES buckets=", counts.size(),
- " samples=", kSamples));
+ LOG(INFO) << "VALUES buckets=" << counts.size()
+ << " samples=" << kSamples;
for (size_t i = 0; i < counts.size(); i++) {
- ABSL_INTERNAL_LOG(
- INFO, absl::StrCat(cutoffs_[i], ": ", counts[i], " vs. E=", e[i]));
+ LOG(INFO) << cutoffs_[i] << ": " << counts[i] << " vs. E=" << e[i];
}
- ABSL_INTERNAL_LOG(
- INFO,
- absl::StrCat(kChiSquared, "(data, dof=", dof, ") = ", chi_square, " (",
- p, ")\n", " vs.\n", kChiSquared, " @ 0.98 = ", threshold));
+ LOG(INFO) << kChiSquared << "(data, dof=" << dof << ") = " << chi_square
+ << " (" << p << ")\n"
+ << " vs.\n"
+ << kChiSquared << " @ 0.98 = " << threshold;
}
return p;
}
diff --git a/absl/random/random.h b/absl/random/random.h
index 71b6309..7672086 100644
--- a/absl/random/random.h
+++ b/absl/random/random.h
@@ -68,7 +68,7 @@
//
// `absl::BitGen` may be constructed with an optional seed sequence type,
// conforming to [rand.req.seed_seq], which will be mixed with additional
-// non-deterministic data.
+// non-deterministic data as detailed below.
//
// Example:
//
@@ -79,16 +79,16 @@
// // Generate an integer value in the closed interval [1,6]
// int die_roll2 = absl::uniform_int_distribution<int>(1, 6)(gen_with_seed);
//
+// Constructing two `absl::BitGen`s with the same seed sequence in the same
+// process will produce the same sequence of variates, but need not do so across
+// multiple processes even if they're executing the same binary.
+//
// `absl::BitGen` meets the requirements of the Uniform Random Bit Generator
// (URBG) concept as per the C++17 standard [rand.req.urng] though differs
// slightly with [rand.req.eng]. Like its standard library equivalents (e.g.
// `std::mersenne_twister_engine`) `absl::BitGen` is not cryptographically
// secure.
//
-// Constructing two `absl::BitGen`s with the same seed sequence in the same
-// binary will produce the same sequence of variates within the same binary, but
-// need not do so across multiple binary invocations.
-//
// This type has been optimized to perform better than Mersenne Twister
// (https://en.wikipedia.org/wiki/Mersenne_Twister) and many other complex URBG
// types on modern x86, ARM, and PPC architectures.
@@ -147,7 +147,7 @@
//
// `absl::InsecureBitGen` may be constructed with an optional seed sequence
// type, conforming to [rand.req.seed_seq], which will be mixed with additional
-// non-deterministic data. (See std_seed_seq.h for more information.)
+// non-deterministic data, as detailed in the `absl::BitGen` comment.
//
// `absl::InsecureBitGen` meets the requirements of the Uniform Random Bit
// Generator (URBG) concept as per the C++17 standard [rand.req.urng] though
diff --git a/absl/random/seed_sequences.h b/absl/random/seed_sequences.h
index ff1340c..33970be 100644
--- a/absl/random/seed_sequences.h
+++ b/absl/random/seed_sequences.h
@@ -28,9 +28,12 @@
#include <iterator>
#include <random>
+#include "absl/base/config.h"
+#include "absl/base/nullability.h"
#include "absl/random/internal/salted_seed_seq.h"
#include "absl/random/internal/seed_material.h"
#include "absl/random/seed_gen_exception.h"
+#include "absl/strings/string_view.h"
#include "absl/types/span.h"
namespace absl {
diff --git a/absl/random/uniform_int_distribution.h b/absl/random/uniform_int_distribution.h
index c1f54cc..fae8025 100644
--- a/absl/random/uniform_int_distribution.h
+++ b/absl/random/uniform_int_distribution.h
@@ -97,7 +97,7 @@
result_type lo_;
unsigned_type range_;
- static_assert(std::is_integral<result_type>::value,
+ static_assert(random_internal::IsIntegral<result_type>::value,
"Class-template absl::uniform_int_distribution<> must be "
"parameterized using an integral type.");
}; // param_type
@@ -125,7 +125,7 @@
template <typename URBG>
result_type operator()(
URBG& gen, const param_type& param) { // NOLINT(runtime/references)
- return param.a() + Generate(gen, param.range());
+ return static_cast<result_type>(param.a() + Generate(gen, param.range()));
}
result_type a() const { return param_.a(); }
diff --git a/absl/random/uniform_int_distribution_test.cc b/absl/random/uniform_int_distribution_test.cc
index 276d72a..b40d618 100644
--- a/absl/random/uniform_int_distribution_test.cc
+++ b/absl/random/uniform_int_distribution_test.cc
@@ -19,11 +19,12 @@
#include <iterator>
#include <random>
#include <sstream>
+#include <string>
#include <vector>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
-#include "absl/base/internal/raw_logging.h"
+#include "absl/log/log.h"
#include "absl/random/internal/chi_square.h"
#include "absl/random/internal/distribution_test_util.h"
#include "absl/random/internal/pcg_engine.h"
@@ -106,8 +107,7 @@
sample_min = sample;
}
}
- std::string msg = absl::StrCat("Range: ", +sample_min, ", ", +sample_max);
- ABSL_RAW_LOG(INFO, "%s", msg.c_str());
+ LOG(INFO) << "Range: " << sample_min << ", " << sample_max;
}
}
@@ -136,7 +136,7 @@
typename absl::uniform_int_distribution<TypeParam>::param_type;
// We use a fixed bit generator for distribution accuracy tests. This allows
- // these tests to be deterministic, while still testing the qualify of the
+ // these tests to be deterministic, while still testing the quality of the
// implementation.
absl::random_internal::pcg64_2018_engine rng{0x2B7E151628AED2A6};
@@ -172,7 +172,7 @@
using absl::random_internal::kChiSquared;
constexpr size_t kTrials = 1000;
- constexpr int kBuckets = 50; // inclusive, so actally +1
+ constexpr int kBuckets = 50; // inclusive, so actually +1
constexpr double kExpected =
static_cast<double>(kTrials) / static_cast<double>(kBuckets);
@@ -184,7 +184,7 @@
const TypeParam max = min + kBuckets;
// We use a fixed bit generator for distribution accuracy tests. This allows
- // these tests to be deterministic, while still testing the qualify of the
+ // these tests to be deterministic, while still testing the quality of the
// implementation.
absl::random_internal::pcg64_2018_engine rng{0x2B7E151628AED2A6};
@@ -209,7 +209,7 @@
absl::StrAppend(&msg, kChiSquared, " p-value ", p_value, "\n");
absl::StrAppend(&msg, "High ", kChiSquared, " value: ", chi_square, " > ",
kThreshold);
- ABSL_RAW_LOG(INFO, "%s", msg.c_str());
+ LOG(INFO) << msg;
FAIL() << msg;
}
}
diff --git a/absl/random/uniform_real_distribution.h b/absl/random/uniform_real_distribution.h
index 5ba17b2..1968334 100644
--- a/absl/random/uniform_real_distribution.h
+++ b/absl/random/uniform_real_distribution.h
@@ -73,12 +73,12 @@
: lo_(lo), hi_(hi), range_(hi - lo) {
// [rand.dist.uni.real] preconditions 2 & 3
assert(lo <= hi);
+
// NOTE: For integral types, we can promote the range to an unsigned type,
// which gives full width of the range. However for real (fp) types, this
// is not possible, so value generation cannot use the full range of the
// real type.
assert(range_ <= (std::numeric_limits<result_type>::max)());
- assert(std::isfinite(range_));
}
result_type a() const { return lo_; }
diff --git a/absl/random/uniform_real_distribution_test.cc b/absl/random/uniform_real_distribution_test.cc
index 035bd28..260aac9 100644
--- a/absl/random/uniform_real_distribution_test.cc
+++ b/absl/random/uniform_real_distribution_test.cc
@@ -26,7 +26,7 @@
#include "gmock/gmock.h"
#include "gtest/gtest.h"
-#include "absl/base/internal/raw_logging.h"
+#include "absl/log/log.h"
#include "absl/numeric/internal/representation.h"
#include "absl/random/internal/chi_square.h"
#include "absl/random/internal/distribution_test_util.h"
@@ -78,62 +78,74 @@
GTEST_SKIP()
<< "Skipping the test because we detected x87 floating-point semantics";
#endif
+ using DistributionType = absl::uniform_real_distribution<TypeParam>;
+ using real_type = TypeParam;
+ using param_type = typename DistributionType::param_type;
- using param_type =
- typename absl::uniform_real_distribution<TypeParam>::param_type;
+ constexpr const real_type kMax = std::numeric_limits<real_type>::max();
+ constexpr const real_type kMin = std::numeric_limits<real_type>::min();
+ constexpr const real_type kEpsilon =
+ std::numeric_limits<real_type>::epsilon();
+ constexpr const real_type kLowest =
+ std::numeric_limits<real_type>::lowest(); // -max
- constexpr const TypeParam a{1152921504606846976};
+ const real_type kDenormMax = std::nextafter(kMin, real_type{0});
+ const real_type kOneMinusE =
+ std::nextafter(real_type{1}, real_type{0}); // 1 - epsilon
+
+ constexpr const real_type kTwo60{1152921504606846976}; // 2^60
constexpr int kCount = 1000;
absl::InsecureBitGen gen;
for (const auto& param : {
param_type(),
- param_type(TypeParam(2.0), TypeParam(2.0)), // Same
- param_type(TypeParam(-0.1), TypeParam(0.1)),
- param_type(TypeParam(0.05), TypeParam(0.12)),
- param_type(TypeParam(-0.05), TypeParam(0.13)),
- param_type(TypeParam(-0.05), TypeParam(-0.02)),
+ param_type(real_type{0}, real_type{1}),
+ param_type(real_type(-0.1), real_type(0.1)),
+ param_type(real_type(0.05), real_type(0.12)),
+ param_type(real_type(-0.05), real_type(0.13)),
+ param_type(real_type(-0.05), real_type(-0.02)),
+ // range = 0
+ param_type(real_type(2.0), real_type(2.0)), // Same
// double range = 0
// 2^60 , 2^60 + 2^6
- param_type(a, TypeParam(1152921504606847040)),
+ param_type(kTwo60, real_type(1152921504606847040)),
// 2^60 , 2^60 + 2^7
- param_type(a, TypeParam(1152921504606847104)),
+ param_type(kTwo60, real_type(1152921504606847104)),
// double range = 2^8
// 2^60 , 2^60 + 2^8
- param_type(a, TypeParam(1152921504606847232)),
+ param_type(kTwo60, real_type(1152921504606847232)),
// float range = 0
// 2^60 , 2^60 + 2^36
- param_type(a, TypeParam(1152921573326323712)),
+ param_type(kTwo60, real_type(1152921573326323712)),
// 2^60 , 2^60 + 2^37
- param_type(a, TypeParam(1152921642045800448)),
+ param_type(kTwo60, real_type(1152921642045800448)),
// float range = 2^38
// 2^60 , 2^60 + 2^38
- param_type(a, TypeParam(1152921779484753920)),
+ param_type(kTwo60, real_type(1152921779484753920)),
// Limits
- param_type(0, std::numeric_limits<TypeParam>::max()),
- param_type(std::numeric_limits<TypeParam>::lowest(), 0),
- param_type(0, std::numeric_limits<TypeParam>::epsilon()),
- param_type(-std::numeric_limits<TypeParam>::epsilon(),
- std::numeric_limits<TypeParam>::epsilon()),
- param_type(std::numeric_limits<TypeParam>::epsilon(),
- 2 * std::numeric_limits<TypeParam>::epsilon()),
+ param_type(0, kMax),
+ param_type(kLowest, 0),
+ param_type(0, kMin),
+ param_type(0, kEpsilon),
+ param_type(-kEpsilon, kEpsilon),
+ param_type(0, kOneMinusE),
+ param_type(0, kDenormMax),
}) {
// Validate parameters.
const auto a = param.a();
const auto b = param.b();
- absl::uniform_real_distribution<TypeParam> before(a, b);
+ DistributionType before(a, b);
EXPECT_EQ(before.a(), param.a());
EXPECT_EQ(before.b(), param.b());
{
- absl::uniform_real_distribution<TypeParam> via_param(param);
+ DistributionType via_param(param);
EXPECT_EQ(via_param, before);
}
std::stringstream ss;
ss << before;
- absl::uniform_real_distribution<TypeParam> after(TypeParam(1.0),
- TypeParam(3.1));
+ DistributionType after(real_type(1.0), real_type(3.1));
EXPECT_NE(before.a(), after.a());
EXPECT_NE(before.b(), after.b());
@@ -168,11 +180,10 @@
}
}
- if (!std::is_same<TypeParam, long double>::value) {
+ if (!std::is_same<real_type, long double>::value) {
// static_cast<double>(long double) can overflow.
- std::string msg = absl::StrCat("Range: ", static_cast<double>(sample_min),
- ", ", static_cast<double>(sample_max));
- ABSL_RAW_LOG(INFO, "%s", msg.c_str());
+ LOG(INFO) << "Range: " << static_cast<double>(sample_min) << ", "
+ << static_cast<double>(sample_max);
}
}
}
@@ -182,33 +193,52 @@
#pragma warning(disable:4756) // Constant arithmetic overflow.
#endif
TYPED_TEST(UniformRealDistributionTest, ViolatesPreconditionsDeathTest) {
+ using DistributionType = absl::uniform_real_distribution<TypeParam>;
+ using real_type = TypeParam;
+
#if GTEST_HAS_DEATH_TEST
// Hi < Lo
- EXPECT_DEBUG_DEATH(
- { absl::uniform_real_distribution<TypeParam> dist(10.0, 1.0); }, "");
+ EXPECT_DEBUG_DEATH({ DistributionType dist(10.0, 1.0); }, "");
// Hi - Lo > numeric_limits<>::max()
EXPECT_DEBUG_DEATH(
{
- absl::uniform_real_distribution<TypeParam> dist(
- std::numeric_limits<TypeParam>::lowest(),
- std::numeric_limits<TypeParam>::max());
+ DistributionType dist(std::numeric_limits<real_type>::lowest(),
+ std::numeric_limits<real_type>::max());
},
"");
+
+ // kEpsilon guarantees that max + kEpsilon = inf.
+ const auto kEpsilon = std::nexttoward(
+ (std::numeric_limits<real_type>::max() -
+ std::nexttoward(std::numeric_limits<real_type>::max(), 0.0)) /
+ 2,
+ std::numeric_limits<real_type>::max());
+ EXPECT_DEBUG_DEATH(
+ {
+ DistributionType dist(-kEpsilon, std::numeric_limits<real_type>::max());
+ },
+ "");
+ EXPECT_DEBUG_DEATH(
+ {
+ DistributionType dist(std::numeric_limits<real_type>::lowest(),
+ kEpsilon);
+ },
+ "");
+
#endif // GTEST_HAS_DEATH_TEST
#if defined(NDEBUG)
// opt-mode, for invalid parameters, will generate a garbage value,
// but should not enter an infinite loop.
absl::InsecureBitGen gen;
{
- absl::uniform_real_distribution<TypeParam> dist(10.0, 1.0);
+ DistributionType dist(10.0, 1.0);
auto x = dist(gen);
EXPECT_FALSE(std::isnan(x)) << x;
}
{
- absl::uniform_real_distribution<TypeParam> dist(
- std::numeric_limits<TypeParam>::lowest(),
- std::numeric_limits<TypeParam>::max());
+ DistributionType dist(std::numeric_limits<real_type>::lowest(),
+ std::numeric_limits<real_type>::max());
auto x = dist(gen);
// Infinite result.
EXPECT_FALSE(std::isfinite(x)) << x;
@@ -220,6 +250,8 @@
#endif
TYPED_TEST(UniformRealDistributionTest, TestMoments) {
+ using DistributionType = absl::uniform_real_distribution<TypeParam>;
+
constexpr int kSize = 1000000;
std::vector<double> values(kSize);
@@ -228,7 +260,7 @@
// implementation.
absl::random_internal::pcg64_2018_engine rng{0x2B7E151628AED2A6};
- absl::uniform_real_distribution<TypeParam> dist;
+ DistributionType dist;
for (int i = 0; i < kSize; i++) {
values[i] = dist(rng);
}
@@ -242,9 +274,10 @@
}
TYPED_TEST(UniformRealDistributionTest, ChiSquaredTest50) {
+ using DistributionType = absl::uniform_real_distribution<TypeParam>;
+ using param_type = typename DistributionType::param_type;
+
using absl::random_internal::kChiSquared;
- using param_type =
- typename absl::uniform_real_distribution<TypeParam>::param_type;
constexpr size_t kTrials = 100000;
constexpr int kBuckets = 50;
@@ -269,7 +302,7 @@
const double factor = kBuckets / (max_val - min_val);
std::vector<int32_t> counts(kBuckets, 0);
- absl::uniform_real_distribution<TypeParam> dist(param);
+ DistributionType dist(param);
for (size_t i = 0; i < kTrials; i++) {
auto x = dist(rng);
auto bucket = static_cast<size_t>((x - min_val) * factor);
@@ -290,15 +323,18 @@
absl::StrAppend(&msg, kChiSquared, " p-value ", p_value, "\n");
absl::StrAppend(&msg, "High ", kChiSquared, " value: ", chi_square, " > ",
kThreshold);
- ABSL_RAW_LOG(INFO, "%s", msg.c_str());
+ LOG(INFO) << msg;
FAIL() << msg;
}
}
}
TYPED_TEST(UniformRealDistributionTest, StabilityTest) {
+ using DistributionType = absl::uniform_real_distribution<TypeParam>;
+ using real_type = TypeParam;
+
// absl::uniform_real_distribution stability relies only on
- // random_internal::RandU64ToDouble and random_internal::RandU64ToFloat.
+ // random_internal::GenerateRealFromBits.
absl::random_internal::sequence_urbg urbg(
{0x0003eb76f6f7f755ull, 0xFFCEA50FDB2F953Bull, 0xC332DDEFBE6C5AA5ull,
0x6558218568AB9702ull, 0x2AEF7DAD5B6E2F84ull, 0x1521B62829076170ull,
@@ -307,9 +343,9 @@
std::vector<int> output(12);
- absl::uniform_real_distribution<TypeParam> dist;
+ DistributionType dist;
std::generate(std::begin(output), std::end(output), [&] {
- return static_cast<int>(TypeParam(1000000) * dist(urbg));
+ return static_cast<int>(real_type(1000000) * dist(urbg));
});
EXPECT_THAT(
diff --git a/absl/random/zipf_distribution.h b/absl/random/zipf_distribution.h
index 22ebc75..03497b1 100644
--- a/absl/random/zipf_distribution.h
+++ b/absl/random/zipf_distribution.h
@@ -23,13 +23,14 @@
#include <type_traits>
#include "absl/random/internal/iostream_state_saver.h"
+#include "absl/random/internal/traits.h"
#include "absl/random/uniform_real_distribution.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
// absl::zipf_distribution produces random integer-values in the range [0, k],
-// distributed according to the discrete probability function:
+// distributed according to the unnormalized discrete probability function:
//
// P(x) = (v + x) ^ -q
//
@@ -94,7 +95,7 @@
double hxm_; // h(k + 0.5)
double hx0_minus_hxm_; // h(x0) - h(k + 0.5)
- static_assert(std::is_integral<IntType>::value,
+ static_assert(random_internal::IsIntegral<IntType>::value,
"Class-template absl::zipf_distribution<> must be "
"parameterized using an integral type.");
};
@@ -221,7 +222,7 @@
const double u = p.hxm_ + v * p.hx0_minus_hxm_;
const double x = p.hinv(u);
k = rint(x); // std::floor(x + 0.5);
- if (k > p.k()) continue; // reject k > max_k
+ if (k > static_cast<double>(p.k())) continue; // reject k > max_k
if (k - x <= p.s_) break;
const double h = p.h(k + 0.5);
const double r = p.pow_negative_q(p.v_ + k);
diff --git a/absl/random/zipf_distribution_test.cc b/absl/random/zipf_distribution_test.cc
index f8cf70e..801ec4f 100644
--- a/absl/random/zipf_distribution_test.cc
+++ b/absl/random/zipf_distribution_test.cc
@@ -25,7 +25,7 @@
#include "gmock/gmock.h"
#include "gtest/gtest.h"
-#include "absl/base/internal/raw_logging.h"
+#include "absl/log/log.h"
#include "absl/random/internal/chi_square.h"
#include "absl/random/internal/pcg_engine.h"
#include "absl/random/internal/sequence_urbg.h"
@@ -44,7 +44,7 @@
using IntTypes = ::testing::Types<int, int8_t, int16_t, int32_t, int64_t,
uint8_t, uint16_t, uint32_t, uint64_t>;
-TYPED_TEST_CASE(ZipfDistributionTypedTest, IntTypes);
+TYPED_TEST_SUITE(ZipfDistributionTypedTest, IntTypes);
TYPED_TEST(ZipfDistributionTypedTest, SerializeTest) {
using param_type = typename absl::zipf_distribution<TypeParam>::param_type;
@@ -102,8 +102,7 @@
if (sample > sample_max) sample_max = sample;
if (sample < sample_min) sample_min = sample;
}
- ABSL_INTERNAL_LOG(INFO,
- absl::StrCat("Range: ", +sample_min, ", ", +sample_max));
+ LOG(INFO) << "Range: " << sample_min << ", " << sample_max;
}
}
@@ -303,18 +302,15 @@
// Log if the chi_squared value is above the threshold.
if (chi_square > threshold) {
- ABSL_INTERNAL_LOG(INFO, "values");
+ LOG(INFO) << "values";
for (size_t i = 0; i < expected.size(); i++) {
- ABSL_INTERNAL_LOG(INFO, absl::StrCat(points[i], ": ", buckets[i],
- " vs. E=", expected[i]));
+ LOG(INFO) << points[i] << ": " << buckets[i] << " vs. E=" << expected[i];
}
- ABSL_INTERNAL_LOG(INFO, absl::StrCat("trials ", trials));
- ABSL_INTERNAL_LOG(INFO,
- absl::StrCat("mean ", avg, " vs. expected ", mean()));
- ABSL_INTERNAL_LOG(INFO, absl::StrCat(kChiSquared, "(data, ", dof, ") = ",
- chi_square, " (", p_actual, ")"));
- ABSL_INTERNAL_LOG(INFO,
- absl::StrCat(kChiSquared, " @ 0.9995 = ", threshold));
+ LOG(INFO) << "trials " << trials;
+ LOG(INFO) << "mean " << avg << " vs. expected " << mean();
+ LOG(INFO) << kChiSquared << "(data, " << dof << ") = " << chi_square << " ("
+ << p_actual << ")";
+ LOG(INFO) << kChiSquared << " @ 0.9995 = " << threshold;
FAIL() << kChiSquared << " value of " << chi_square
<< " is above the threshold.";
}
diff --git a/absl/status/BUILD.bazel b/absl/status/BUILD.bazel
index 30df22a..981b37f 100644
--- a/absl/status/BUILD.bazel
+++ b/absl/status/BUILD.bazel
@@ -17,20 +17,28 @@
# It will expand later to have utilities around `Status` like `StatusOr`,
# `StatusBuilder` and macros.
-load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test")
load(
"//absl:copts/configure_copts.bzl",
"ABSL_DEFAULT_COPTS",
+ "ABSL_DEFAULT_LINKOPTS",
"ABSL_TEST_COPTS",
)
-package(default_visibility = ["//visibility:public"])
+package(
+ default_visibility = ["//visibility:public"],
+ features = [
+ "header_modules",
+ "layering_check",
+ "parse_headers",
+ ],
+)
licenses(["notice"])
cc_library(
name = "status",
srcs = [
+ "internal/status_internal.cc",
"internal/status_internal.h",
"status.cc",
"status_payload_printer.cc",
@@ -40,18 +48,25 @@
"status_payload_printer.h",
],
copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
"//absl/base:atomic_hook",
"//absl/base:config",
"//absl/base:core_headers",
+ "//absl/base:no_destructor",
+ "//absl/base:nullability",
"//absl/base:raw_logging_internal",
+ "//absl/base:strerror",
"//absl/container:inlined_vector",
"//absl/debugging:stacktrace",
"//absl/debugging:symbolize",
+ "//absl/functional:function_ref",
+ "//absl/memory",
"//absl/strings",
"//absl/strings:cord",
"//absl/strings:str_format",
"//absl/types:optional",
+ "//absl/types:span",
],
)
@@ -59,9 +74,13 @@
name = "status_test",
srcs = ["status_test.cc"],
copts = ABSL_TEST_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
":status",
"//absl/strings",
+ "//absl/strings:cord",
+ "//absl/strings:str_format",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -76,13 +95,18 @@
"statusor.h",
],
copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
":status",
"//absl/base",
+ "//absl/base:config",
"//absl/base:core_headers",
+ "//absl/base:nullability",
"//absl/base:raw_logging_internal",
"//absl/meta:type_traits",
"//absl/strings",
+ "//absl/strings:has_ostream_operator",
+ "//absl/strings:str_format",
"//absl/types:variant",
"//absl/utility",
],
@@ -99,7 +123,9 @@
"//absl/memory",
"//absl/strings",
"//absl/types:any",
+ "//absl/types:variant",
"//absl/utility",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
diff --git a/absl/status/CMakeLists.txt b/absl/status/CMakeLists.txt
index 4389856..00415ab 100644
--- a/absl/status/CMakeLists.txt
+++ b/absl/status/CMakeLists.txt
@@ -20,23 +20,32 @@
"status.h"
SRCS
"internal/status_internal.h"
+ "internal/status_internal.cc"
"status.cc"
"status_payload_printer.h"
"status_payload_printer.cc"
COPTS
${ABSL_DEFAULT_COPTS}
+ DEFINES
+ "$<$<PLATFORM_ID:AIX>:_LINUX_SOURCE_COMPAT>"
DEPS
absl::atomic_hook
absl::config
- absl::core_headers
- absl::raw_logging_internal
- absl::inlined_vector
- absl::stacktrace
- absl::symbolize
- absl::strings
absl::cord
- absl::str_format
+ absl::core_headers
+ absl::function_ref
+ absl::inlined_vector
+ absl::memory
+ absl::no_destructor
+ absl::nullability
absl::optional
+ absl::raw_logging_internal
+ absl::span
+ absl::stacktrace
+ absl::strerror
+ absl::str_format
+ absl::strings
+ absl::symbolize
PUBLIC
)
@@ -49,6 +58,7 @@
${ABSL_TEST_COPTS}
DEPS
absl::status
+ absl::str_format
absl::strings
GTest::gmock_main
)
@@ -65,11 +75,15 @@
${ABSL_DEFAULT_COPTS}
DEPS
absl::base
- absl::status
+ absl::config
absl::core_headers
+ absl::has_ostream_operator
+ absl::nullability
absl::raw_logging_internal
- absl::type_traits
+ absl::status
+ absl::str_format
absl::strings
+ absl::type_traits
absl::utility
absl::variant
PUBLIC
@@ -85,5 +99,6 @@
DEPS
absl::status
absl::statusor
+ absl::strings
GTest::gmock_main
)
diff --git a/absl/status/internal/status_internal.cc b/absl/status/internal/status_internal.cc
new file mode 100644
index 0000000..a915675
--- /dev/null
+++ b/absl/status/internal/status_internal.cc
@@ -0,0 +1,248 @@
+// Copyright 2023 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/status/internal/status_internal.h"
+
+#include <atomic>
+#include <cassert>
+#include <cstddef>
+#include <cstdint>
+#include <cstdio>
+#include <cstring>
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "absl/base/attributes.h"
+#include "absl/base/config.h"
+#include "absl/base/macros.h"
+#include "absl/base/nullability.h"
+#include "absl/debugging/stacktrace.h"
+#include "absl/debugging/symbolize.h"
+#include "absl/memory/memory.h"
+#include "absl/status/status.h"
+#include "absl/status/status_payload_printer.h"
+#include "absl/strings/cord.h"
+#include "absl/strings/escaping.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/str_format.h"
+#include "absl/strings/str_split.h"
+#include "absl/strings/string_view.h"
+#include "absl/types/optional.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace status_internal {
+
+void StatusRep::Unref() const {
+ // Fast path: if ref==1, there is no need for a RefCountDec (since
+ // this is the only reference and therefore no other thread is
+ // allowed to be mucking with r).
+ if (ref_.load(std::memory_order_acquire) == 1 ||
+ ref_.fetch_sub(1, std::memory_order_acq_rel) - 1 == 0) {
+ delete this;
+ }
+}
+
+static absl::optional<size_t> FindPayloadIndexByUrl(
+ const Payloads* payloads, absl::string_view type_url) {
+ if (payloads == nullptr) return absl::nullopt;
+
+ for (size_t i = 0; i < payloads->size(); ++i) {
+ if ((*payloads)[i].type_url == type_url) return i;
+ }
+
+ return absl::nullopt;
+}
+
+absl::optional<absl::Cord> StatusRep::GetPayload(
+ absl::string_view type_url) const {
+ absl::optional<size_t> index =
+ status_internal::FindPayloadIndexByUrl(payloads_.get(), type_url);
+ if (index.has_value()) return (*payloads_)[index.value()].payload;
+
+ return absl::nullopt;
+}
+
+void StatusRep::SetPayload(absl::string_view type_url, absl::Cord payload) {
+ if (payloads_ == nullptr) {
+ payloads_ = absl::make_unique<status_internal::Payloads>();
+ }
+
+ absl::optional<size_t> index =
+ status_internal::FindPayloadIndexByUrl(payloads_.get(), type_url);
+ if (index.has_value()) {
+ (*payloads_)[index.value()].payload = std::move(payload);
+ return;
+ }
+
+ payloads_->push_back({std::string(type_url), std::move(payload)});
+}
+
+StatusRep::EraseResult StatusRep::ErasePayload(absl::string_view type_url) {
+ absl::optional<size_t> index =
+ status_internal::FindPayloadIndexByUrl(payloads_.get(), type_url);
+ if (!index.has_value()) return {false, Status::PointerToRep(this)};
+ payloads_->erase(payloads_->begin() + index.value());
+ if (payloads_->empty() && message_.empty()) {
+ // Special case: If this can be represented inlined, it MUST be inlined
+ // (== depends on this behavior).
+ EraseResult result = {true, Status::CodeToInlinedRep(code_)};
+ Unref();
+ return result;
+ }
+ return {true, Status::PointerToRep(this)};
+}
+
+void StatusRep::ForEachPayload(
+ absl::FunctionRef<void(absl::string_view, const absl::Cord&)> visitor)
+ const {
+ if (auto* payloads = payloads_.get()) {
+ bool in_reverse =
+ payloads->size() > 1 && reinterpret_cast<uintptr_t>(payloads) % 13 > 6;
+
+ for (size_t index = 0; index < payloads->size(); ++index) {
+ const auto& elem =
+ (*payloads)[in_reverse ? payloads->size() - 1 - index : index];
+
+#ifdef NDEBUG
+ visitor(elem.type_url, elem.payload);
+#else
+ // In debug mode invalidate the type url to prevent users from relying on
+ // this string lifetime.
+
+ // NOLINTNEXTLINE intentional extra conversion to force temporary.
+ visitor(std::string(elem.type_url), elem.payload);
+#endif // NDEBUG
+ }
+ }
+}
+
+std::string StatusRep::ToString(StatusToStringMode mode) const {
+ std::string text;
+ absl::StrAppend(&text, absl::StatusCodeToString(code()), ": ", message());
+
+ const bool with_payload = (mode & StatusToStringMode::kWithPayload) ==
+ StatusToStringMode::kWithPayload;
+
+ if (with_payload) {
+ status_internal::StatusPayloadPrinter printer =
+ status_internal::GetStatusPayloadPrinter();
+ this->ForEachPayload([&](absl::string_view type_url,
+ const absl::Cord& payload) {
+ absl::optional<std::string> result;
+ if (printer) result = printer(type_url, payload);
+ absl::StrAppend(
+ &text, " [", type_url, "='",
+ result.has_value() ? *result : absl::CHexEscape(std::string(payload)),
+ "']");
+ });
+ }
+
+ return text;
+}
+
+bool StatusRep::operator==(const StatusRep& other) const {
+ assert(this != &other);
+ if (code_ != other.code_) return false;
+ if (message_ != other.message_) return false;
+ const status_internal::Payloads* this_payloads = payloads_.get();
+ const status_internal::Payloads* other_payloads = other.payloads_.get();
+
+ const status_internal::Payloads no_payloads;
+ const status_internal::Payloads* larger_payloads =
+ this_payloads ? this_payloads : &no_payloads;
+ const status_internal::Payloads* smaller_payloads =
+ other_payloads ? other_payloads : &no_payloads;
+ if (larger_payloads->size() < smaller_payloads->size()) {
+ std::swap(larger_payloads, smaller_payloads);
+ }
+ if ((larger_payloads->size() - smaller_payloads->size()) > 1) return false;
+ // Payloads can be ordered differently, so we can't just compare payload
+ // vectors.
+ for (const auto& payload : *larger_payloads) {
+
+ bool found = false;
+ for (const auto& other_payload : *smaller_payloads) {
+ if (payload.type_url == other_payload.type_url) {
+ if (payload.payload != other_payload.payload) {
+ return false;
+ }
+ found = true;
+ break;
+ }
+ }
+ if (!found) return false;
+ }
+ return true;
+}
+
+absl::Nonnull<StatusRep*> StatusRep::CloneAndUnref() const {
+ // Optimization: no need to create a clone if we already have a refcount of 1.
+ if (ref_.load(std::memory_order_acquire) == 1) {
+ // All StatusRep instances are heap allocated and mutable, therefore this
+ // const_cast will never cast away const from a stack instance.
+ //
+ // CloneAndUnref is the only method that doesn't involve an external cast to
+ // get a mutable StatusRep* from the uintptr_t rep stored in Status.
+ return const_cast<StatusRep*>(this);
+ }
+ std::unique_ptr<status_internal::Payloads> payloads;
+ if (payloads_) {
+ payloads = absl::make_unique<status_internal::Payloads>(*payloads_);
+ }
+ auto* new_rep = new StatusRep(code_, message_, std::move(payloads));
+ Unref();
+ return new_rep;
+}
+
+// Convert canonical code to a value known to this binary.
+absl::StatusCode MapToLocalCode(int value) {
+ absl::StatusCode code = static_cast<absl::StatusCode>(value);
+ switch (code) {
+ case absl::StatusCode::kOk:
+ case absl::StatusCode::kCancelled:
+ case absl::StatusCode::kUnknown:
+ case absl::StatusCode::kInvalidArgument:
+ case absl::StatusCode::kDeadlineExceeded:
+ case absl::StatusCode::kNotFound:
+ case absl::StatusCode::kAlreadyExists:
+ case absl::StatusCode::kPermissionDenied:
+ case absl::StatusCode::kResourceExhausted:
+ case absl::StatusCode::kFailedPrecondition:
+ case absl::StatusCode::kAborted:
+ case absl::StatusCode::kOutOfRange:
+ case absl::StatusCode::kUnimplemented:
+ case absl::StatusCode::kInternal:
+ case absl::StatusCode::kUnavailable:
+ case absl::StatusCode::kDataLoss:
+ case absl::StatusCode::kUnauthenticated:
+ return code;
+ default:
+ return absl::StatusCode::kUnknown;
+ }
+}
+
+absl::Nonnull<std::string*> MakeCheckFailString(
+ absl::Nonnull<const absl::Status*> status,
+ absl::Nonnull<const char*> prefix) {
+ return new std::string(
+ absl::StrCat(prefix, " (",
+ status->ToString(StatusToStringMode::kWithEverything), ")"));
+}
+
+} // namespace status_internal
+
+ABSL_NAMESPACE_END
+} // namespace absl
diff --git a/absl/status/internal/status_internal.h b/absl/status/internal/status_internal.h
index ac12940..c9f4383 100644
--- a/absl/status/internal/status_internal.h
+++ b/absl/status/internal/status_internal.h
@@ -14,10 +14,19 @@
#ifndef ABSL_STATUS_INTERNAL_STATUS_INTERNAL_H_
#define ABSL_STATUS_INTERNAL_STATUS_INTERNAL_H_
+#include <atomic>
+#include <cstdint>
+#include <memory>
#include <string>
+#include <utility>
+#include "absl/base/attributes.h"
+#include "absl/base/config.h"
+#include "absl/base/nullability.h"
#include "absl/container/inlined_vector.h"
#include "absl/strings/cord.h"
+#include "absl/strings/string_view.h"
+#include "absl/types/optional.h"
#ifndef SWIG
// Disabled for SWIG as it doesn't parse attributes correctly.
@@ -25,7 +34,14 @@
ABSL_NAMESPACE_BEGIN
// Returned Status objects may not be ignored. Codesearch doesn't handle ifdefs
// as part of a class definitions (b/6995610), so we use a forward declaration.
-class ABSL_MUST_USE_RESULT Status;
+//
+// 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;
+#else
+class ABSL_MUST_USE_RESULT ABSL_ATTRIBUTE_TRIVIAL_ABI Status;
+#endif
ABSL_NAMESPACE_END
} // namespace absl
#endif // !SWIG
@@ -34,6 +50,7 @@
ABSL_NAMESPACE_BEGIN
enum class StatusCode : int;
+enum class StatusToStringMode : int;
namespace status_internal {
@@ -46,21 +63,67 @@
using Payloads = absl::InlinedVector<Payload, 1>;
// Reference-counted representation of Status data.
-struct StatusRep {
+class StatusRep {
+ public:
StatusRep(absl::StatusCode code_arg, absl::string_view message_arg,
std::unique_ptr<status_internal::Payloads> payloads_arg)
- : ref(int32_t{1}),
- code(code_arg),
- message(message_arg),
- payloads(std::move(payloads_arg)) {}
+ : ref_(int32_t{1}),
+ code_(code_arg),
+ message_(message_arg),
+ payloads_(std::move(payloads_arg)) {}
- std::atomic<int32_t> ref;
- absl::StatusCode code;
- std::string message;
- std::unique_ptr<status_internal::Payloads> payloads;
+ absl::StatusCode code() const { return code_; }
+ const std::string& message() const { return message_; }
+
+ // Ref and unref are const to allow access through a const pointer, and are
+ // used during copying operations.
+ void Ref() const { ref_.fetch_add(1, std::memory_order_relaxed); }
+ void Unref() const;
+
+ // Payload methods correspond to the same methods in absl::Status.
+ absl::optional<absl::Cord> GetPayload(absl::string_view type_url) const;
+ void SetPayload(absl::string_view type_url, absl::Cord payload);
+ struct EraseResult {
+ bool erased;
+ uintptr_t new_rep;
+ };
+ EraseResult ErasePayload(absl::string_view type_url);
+ void ForEachPayload(
+ absl::FunctionRef<void(absl::string_view, const absl::Cord&)> visitor)
+ const;
+
+ std::string ToString(StatusToStringMode mode) const;
+
+ bool operator==(const StatusRep& other) const;
+ bool operator!=(const StatusRep& other) const { return !(*this == other); }
+
+ // Returns an equivalent heap allocated StatusRep with refcount 1.
+ //
+ // `this` is not safe to be used after calling as it may have been deleted.
+ absl::Nonnull<StatusRep*> CloneAndUnref() const;
+
+ private:
+ mutable std::atomic<int32_t> ref_;
+ absl::StatusCode code_;
+
+ // As an internal implementation detail, we guarantee that if status.message()
+ // is non-empty, then the resulting string_view is null terminated.
+ // This is required to implement 'StatusMessageAsCStr(...)'
+ std::string message_;
+ std::unique_ptr<status_internal::Payloads> payloads_;
};
absl::StatusCode MapToLocalCode(int value);
+
+// Returns a pointer to a newly-allocated string with the given `prefix`,
+// suitable for output as an error message in assertion/`CHECK()` failures.
+//
+// This is an internal implementation detail for Abseil logging.
+ABSL_ATTRIBUTE_PURE_FUNCTION
+absl::Nonnull<std::string*> MakeCheckFailString(
+ absl::Nonnull<const absl::Status*> status,
+ absl::Nonnull<const char*> prefix);
+
} // namespace status_internal
ABSL_NAMESPACE_END
diff --git a/absl/status/internal/statusor_internal.h b/absl/status/internal/statusor_internal.h
index eaac2c0..5be9490 100644
--- a/absl/status/internal/statusor_internal.h
+++ b/absl/status/internal/statusor_internal.h
@@ -14,12 +14,15 @@
#ifndef ABSL_STATUS_INTERNAL_STATUSOR_INTERNAL_H_
#define ABSL_STATUS_INTERNAL_STATUSOR_INTERNAL_H_
+#include <cstdint>
#include <type_traits>
#include <utility>
#include "absl/base/attributes.h"
+#include "absl/base/nullability.h"
#include "absl/meta/type_traits.h"
#include "absl/status/status.h"
+#include "absl/strings/string_view.h"
#include "absl/utility/utility.h"
namespace absl {
@@ -69,11 +72,8 @@
template <typename T, typename U>
struct IsDirectInitializationAmbiguous
: public absl::conditional_t<
- std::is_same<absl::remove_cv_t<absl::remove_reference_t<U>>,
- U>::value,
- std::false_type,
- IsDirectInitializationAmbiguous<
- T, absl::remove_cv_t<absl::remove_reference_t<U>>>> {};
+ std::is_same<absl::remove_cvref_t<U>, U>::value, std::false_type,
+ IsDirectInitializationAmbiguous<T, absl::remove_cvref_t<U>>> {};
template <typename T, typename V>
struct IsDirectInitializationAmbiguous<T, absl::StatusOr<V>>
@@ -84,14 +84,11 @@
template <typename T, typename U>
using IsDirectInitializationValid = absl::disjunction<
// Short circuits if T is basically U.
- std::is_same<T, absl::remove_cv_t<absl::remove_reference_t<U>>>,
+ std::is_same<T, absl::remove_cvref_t<U>>,
absl::negation<absl::disjunction<
- std::is_same<absl::StatusOr<T>,
- absl::remove_cv_t<absl::remove_reference_t<U>>>,
- std::is_same<absl::Status,
- absl::remove_cv_t<absl::remove_reference_t<U>>>,
- std::is_same<absl::in_place_t,
- absl::remove_cv_t<absl::remove_reference_t<U>>>,
+ std::is_same<absl::StatusOr<T>, absl::remove_cvref_t<U>>,
+ std::is_same<absl::Status, absl::remove_cvref_t<U>>,
+ std::is_same<absl::in_place_t, absl::remove_cvref_t<U>>,
IsDirectInitializationAmbiguous<T, U>>>>;
// This trait detects whether `StatusOr<T>::operator=(U&&)` is ambiguous, which
@@ -107,11 +104,8 @@
template <typename T, typename U>
struct IsForwardingAssignmentAmbiguous
: public absl::conditional_t<
- std::is_same<absl::remove_cv_t<absl::remove_reference_t<U>>,
- U>::value,
- std::false_type,
- IsForwardingAssignmentAmbiguous<
- T, absl::remove_cv_t<absl::remove_reference_t<U>>>> {};
+ std::is_same<absl::remove_cvref_t<U>, U>::value, std::false_type,
+ IsForwardingAssignmentAmbiguous<T, absl::remove_cvref_t<U>>> {};
template <typename T, typename U>
struct IsForwardingAssignmentAmbiguous<T, absl::StatusOr<U>>
@@ -122,20 +116,17 @@
template <typename T, typename U>
using IsForwardingAssignmentValid = absl::disjunction<
// Short circuits if T is basically U.
- std::is_same<T, absl::remove_cv_t<absl::remove_reference_t<U>>>,
+ std::is_same<T, absl::remove_cvref_t<U>>,
absl::negation<absl::disjunction<
- std::is_same<absl::StatusOr<T>,
- absl::remove_cv_t<absl::remove_reference_t<U>>>,
- std::is_same<absl::Status,
- absl::remove_cv_t<absl::remove_reference_t<U>>>,
- std::is_same<absl::in_place_t,
- absl::remove_cv_t<absl::remove_reference_t<U>>>,
+ std::is_same<absl::StatusOr<T>, absl::remove_cvref_t<U>>,
+ std::is_same<absl::Status, absl::remove_cvref_t<U>>,
+ std::is_same<absl::in_place_t, absl::remove_cvref_t<U>>,
IsForwardingAssignmentAmbiguous<T, U>>>>;
class Helper {
public:
// Move type-agnostic error handling to the .cc.
- static void HandleInvalidStatusCtorArg(Status*);
+ static void HandleInvalidStatusCtorArg(absl::Nonnull<Status*>);
ABSL_ATTRIBUTE_NORETURN static void Crash(const absl::Status& status);
};
@@ -143,7 +134,8 @@
// the constructor.
// This abstraction is here mostly for the gcc performance fix.
template <typename T, typename... Args>
-ABSL_ATTRIBUTE_NONNULL(1) void PlacementNew(void* p, Args&&... args) {
+ABSL_ATTRIBUTE_NONNULL(1)
+void PlacementNew(absl::Nonnull<void*> p, Args&&... args) {
new (p) T(std::forward<Args>(args)...);
}
@@ -389,6 +381,53 @@
ABSL_ATTRIBUTE_NORETURN void ThrowBadStatusOrAccess(absl::Status status);
+// Used to introduce jitter into the output of printing functions for
+// `StatusOr` (i.e. `AbslStringify` and `operator<<`).
+class StringifyRandom {
+ enum BracesType {
+ kBareParens = 0,
+ kSpaceParens,
+ kBareBrackets,
+ kSpaceBrackets,
+ };
+
+ // Returns a random `BracesType` determined once per binary load.
+ static BracesType RandomBraces() {
+ static const BracesType kRandomBraces = static_cast<BracesType>(
+ (reinterpret_cast<uintptr_t>(&kRandomBraces) >> 4) % 4);
+ return kRandomBraces;
+ }
+
+ public:
+ static inline absl::string_view OpenBrackets() {
+ switch (RandomBraces()) {
+ case kBareParens:
+ return "(";
+ case kSpaceParens:
+ return "( ";
+ case kBareBrackets:
+ return "[";
+ case kSpaceBrackets:
+ return "[ ";
+ }
+ return "(";
+ }
+
+ static inline absl::string_view CloseBrackets() {
+ switch (RandomBraces()) {
+ case kBareParens:
+ return ")";
+ case kSpaceParens:
+ return " )";
+ case kBareBrackets:
+ return "]";
+ case kSpaceBrackets:
+ return " ]";
+ }
+ return ")";
+ }
+};
+
} // namespace internal_statusor
ABSL_NAMESPACE_END
} // namespace absl
diff --git a/absl/status/status.cc b/absl/status/status.cc
index 53c198e..4dd5ae0 100644
--- a/absl/status/status.cc
+++ b/absl/status/status.cc
@@ -13,20 +13,39 @@
// limitations under the License.
#include "absl/status/status.h"
-#include <cassert>
+#include <errno.h>
+#include <atomic>
+#include <cstddef>
+#include <cstdint>
+#include <cstring>
+#include <memory>
+#include <ostream>
+#include <string>
+
+#include "absl/base/attributes.h"
+#include "absl/base/config.h"
#include "absl/base/internal/raw_logging.h"
+#include "absl/base/internal/strerror.h"
+#include "absl/base/macros.h"
+#include "absl/base/no_destructor.h"
+#include "absl/base/nullability.h"
#include "absl/debugging/stacktrace.h"
#include "absl/debugging/symbolize.h"
-#include "absl/status/status_payload_printer.h"
-#include "absl/strings/escaping.h"
+#include "absl/status/internal/status_internal.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/strings/str_split.h"
+#include "absl/strings/string_view.h"
+#include "absl/types/optional.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
+static_assert(
+ alignof(status_internal::StatusRep) >= 4,
+ "absl::Status assumes it can use the bottom 2 bits of a StatusRep*.");
+
std::string StatusCodeToString(StatusCode code) {
switch (code) {
case StatusCode::kOk:
@@ -72,139 +91,18 @@
return os << StatusCodeToString(code);
}
-namespace status_internal {
-
-static int FindPayloadIndexByUrl(const Payloads* payloads,
- absl::string_view type_url) {
- if (payloads == nullptr) return -1;
-
- for (size_t i = 0; i < payloads->size(); ++i) {
- if ((*payloads)[i].type_url == type_url) return i;
- }
-
- return -1;
+absl::Nonnull<const std::string*> Status::EmptyString() {
+ static const absl::NoDestructor<std::string> kEmpty;
+ return kEmpty.get();
}
-// Convert canonical code to a value known to this binary.
-absl::StatusCode MapToLocalCode(int value) {
- absl::StatusCode code = static_cast<absl::StatusCode>(value);
- switch (code) {
- case absl::StatusCode::kOk:
- case absl::StatusCode::kCancelled:
- case absl::StatusCode::kUnknown:
- case absl::StatusCode::kInvalidArgument:
- case absl::StatusCode::kDeadlineExceeded:
- case absl::StatusCode::kNotFound:
- case absl::StatusCode::kAlreadyExists:
- case absl::StatusCode::kPermissionDenied:
- case absl::StatusCode::kResourceExhausted:
- case absl::StatusCode::kFailedPrecondition:
- case absl::StatusCode::kAborted:
- case absl::StatusCode::kOutOfRange:
- case absl::StatusCode::kUnimplemented:
- case absl::StatusCode::kInternal:
- case absl::StatusCode::kUnavailable:
- case absl::StatusCode::kDataLoss:
- case absl::StatusCode::kUnauthenticated:
- return code;
- default:
- return absl::StatusCode::kUnknown;
- }
-}
-} // namespace status_internal
-
-absl::optional<absl::Cord> Status::GetPayload(
- absl::string_view type_url) const {
- const auto* payloads = GetPayloads();
- int index = status_internal::FindPayloadIndexByUrl(payloads, type_url);
- if (index != -1) return (*payloads)[index].payload;
-
- return absl::nullopt;
-}
-
-void Status::SetPayload(absl::string_view type_url, absl::Cord payload) {
- if (ok()) return;
-
- PrepareToModify();
-
- status_internal::StatusRep* rep = RepToPointer(rep_);
- if (!rep->payloads) {
- rep->payloads = absl::make_unique<status_internal::Payloads>();
- }
-
- int index =
- status_internal::FindPayloadIndexByUrl(rep->payloads.get(), type_url);
- if (index != -1) {
- (*rep->payloads)[index].payload = std::move(payload);
- return;
- }
-
- rep->payloads->push_back({std::string(type_url), std::move(payload)});
-}
-
-bool Status::ErasePayload(absl::string_view type_url) {
- int index = status_internal::FindPayloadIndexByUrl(GetPayloads(), type_url);
- if (index != -1) {
- PrepareToModify();
- GetPayloads()->erase(GetPayloads()->begin() + index);
- if (GetPayloads()->empty() && message().empty()) {
- // Special case: If this can be represented inlined, it MUST be
- // inlined (EqualsSlow depends on this behavior).
- StatusCode c = static_cast<StatusCode>(raw_code());
- Unref(rep_);
- rep_ = CodeToInlinedRep(c);
- }
- return true;
- }
-
- return false;
-}
-
-void Status::ForEachPayload(
- const std::function<void(absl::string_view, const absl::Cord&)>& visitor)
- const {
- if (auto* payloads = GetPayloads()) {
- bool in_reverse =
- payloads->size() > 1 && reinterpret_cast<uintptr_t>(payloads) % 13 > 6;
-
- for (size_t index = 0; index < payloads->size(); ++index) {
- const auto& elem =
- (*payloads)[in_reverse ? payloads->size() - 1 - index : index];
-
-#ifdef NDEBUG
- visitor(elem.type_url, elem.payload);
-#else
- // In debug mode invalidate the type url to prevent users from relying on
- // this string lifetime.
-
- // NOLINTNEXTLINE intentional extra conversion to force temporary.
- visitor(std::string(elem.type_url), elem.payload);
-#endif // NDEBUG
- }
- }
-}
-
-const std::string* Status::EmptyString() {
- static std::string* empty_string = new std::string();
- return empty_string;
-}
-
+#ifdef ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL
constexpr const char Status::kMovedFromString[];
+#endif
-const std::string* Status::MovedFromString() {
- static std::string* moved_from_string = new std::string(kMovedFromString);
- return moved_from_string;
-}
-
-void Status::UnrefNonInlined(uintptr_t rep) {
- status_internal::StatusRep* r = RepToPointer(rep);
- // Fast path: if ref==1, there is no need for a RefCountDec (since
- // this is the only reference and therefore no other thread is
- // allowed to be mucking with r).
- if (r->ref.load(std::memory_order_acquire) == 1 ||
- r->ref.fetch_sub(1, std::memory_order_acq_rel) - 1 == 0) {
- delete r;
- }
+absl::Nonnull<const std::string*> Status::MovedFromString() {
+ static const absl::NoDestructor<std::string> kMovedFrom(kMovedFromString);
+ return kMovedFrom.get();
}
Status::Status(absl::StatusCode code, absl::string_view msg)
@@ -214,97 +112,20 @@
}
}
-int Status::raw_code() const {
- if (IsInlined(rep_)) {
- return static_cast<int>(InlinedRepToCode(rep_));
+absl::Nonnull<status_internal::StatusRep*> Status::PrepareToModify(
+ uintptr_t rep) {
+ if (IsInlined(rep)) {
+ return new status_internal::StatusRep(InlinedRepToCode(rep),
+ absl::string_view(), nullptr);
}
- status_internal::StatusRep* rep = RepToPointer(rep_);
- return static_cast<int>(rep->code);
+ return RepToPointer(rep)->CloneAndUnref();
}
-absl::StatusCode Status::code() const {
- return status_internal::MapToLocalCode(raw_code());
-}
-
-void Status::PrepareToModify() {
- ABSL_RAW_CHECK(!ok(), "PrepareToModify shouldn't be called on OK status.");
- if (IsInlined(rep_)) {
- rep_ = PointerToRep(new status_internal::StatusRep(
- static_cast<absl::StatusCode>(raw_code()), absl::string_view(),
- nullptr));
- return;
+std::string Status::ToStringSlow(uintptr_t rep, StatusToStringMode mode) {
+ if (IsInlined(rep)) {
+ return absl::StrCat(absl::StatusCodeToString(InlinedRepToCode(rep)), ": ");
}
-
- uintptr_t rep_i = rep_;
- status_internal::StatusRep* rep = RepToPointer(rep_);
- if (rep->ref.load(std::memory_order_acquire) != 1) {
- std::unique_ptr<status_internal::Payloads> payloads;
- if (rep->payloads) {
- payloads = absl::make_unique<status_internal::Payloads>(*rep->payloads);
- }
- status_internal::StatusRep* const new_rep = new status_internal::StatusRep(
- rep->code, message(), std::move(payloads));
- rep_ = PointerToRep(new_rep);
- UnrefNonInlined(rep_i);
- }
-}
-
-bool Status::EqualsSlow(const absl::Status& a, const absl::Status& b) {
- if (IsInlined(a.rep_) != IsInlined(b.rep_)) return false;
- if (a.message() != b.message()) return false;
- if (a.raw_code() != b.raw_code()) return false;
- if (a.GetPayloads() == b.GetPayloads()) return true;
-
- const status_internal::Payloads no_payloads;
- const status_internal::Payloads* larger_payloads =
- a.GetPayloads() ? a.GetPayloads() : &no_payloads;
- const status_internal::Payloads* smaller_payloads =
- b.GetPayloads() ? b.GetPayloads() : &no_payloads;
- if (larger_payloads->size() < smaller_payloads->size()) {
- std::swap(larger_payloads, smaller_payloads);
- }
- if ((larger_payloads->size() - smaller_payloads->size()) > 1) return false;
- // Payloads can be ordered differently, so we can't just compare payload
- // vectors.
- for (const auto& payload : *larger_payloads) {
-
- bool found = false;
- for (const auto& other_payload : *smaller_payloads) {
- if (payload.type_url == other_payload.type_url) {
- if (payload.payload != other_payload.payload) {
- return false;
- }
- found = true;
- break;
- }
- }
- if (!found) return false;
- }
- return true;
-}
-
-std::string Status::ToStringSlow(StatusToStringMode mode) const {
- std::string text;
- absl::StrAppend(&text, absl::StatusCodeToString(code()), ": ", message());
-
- const bool with_payload = (mode & StatusToStringMode::kWithPayload) ==
- StatusToStringMode::kWithPayload;
-
- if (with_payload) {
- status_internal::StatusPayloadPrinter printer =
- status_internal::GetStatusPayloadPrinter();
- this->ForEachPayload([&](absl::string_view type_url,
- const absl::Cord& payload) {
- absl::optional<std::string> result;
- if (printer) result = printer(type_url, payload);
- absl::StrAppend(
- &text, " [", type_url, "='",
- result.has_value() ? *result : absl::CHexEscape(std::string(payload)),
- "']");
- });
- }
-
- return text;
+ return RepToPointer(rep)->ToString(mode);
}
std::ostream& operator<<(std::ostream& os, const Status& x) {
@@ -440,5 +261,165 @@
return status.code() == absl::StatusCode::kUnknown;
}
+StatusCode ErrnoToStatusCode(int error_number) {
+ switch (error_number) {
+ case 0:
+ return StatusCode::kOk;
+ case EINVAL: // Invalid argument
+ case ENAMETOOLONG: // Filename too long
+ case E2BIG: // Argument list too long
+ case EDESTADDRREQ: // Destination address required
+ case EDOM: // Mathematics argument out of domain of function
+ case EFAULT: // Bad address
+ case EILSEQ: // Illegal byte sequence
+ case ENOPROTOOPT: // Protocol not available
+ case ENOSTR: // Not a STREAM
+ case ENOTSOCK: // Not a socket
+ case ENOTTY: // Inappropriate I/O control operation
+ case EPROTOTYPE: // Protocol wrong type for socket
+ case ESPIPE: // Invalid seek
+ return StatusCode::kInvalidArgument;
+ case ETIMEDOUT: // Connection timed out
+ case ETIME: // Timer expired
+ return StatusCode::kDeadlineExceeded;
+ case ENODEV: // No such device
+ case ENOENT: // No such file or directory
+#ifdef ENOMEDIUM
+ case ENOMEDIUM: // No medium found
+#endif
+ case ENXIO: // No such device or address
+ case ESRCH: // No such process
+ return StatusCode::kNotFound;
+ case EEXIST: // File exists
+ case EADDRNOTAVAIL: // Address not available
+ case EALREADY: // Connection already in progress
+#ifdef ENOTUNIQ
+ case ENOTUNIQ: // Name not unique on network
+#endif
+ return StatusCode::kAlreadyExists;
+ case EPERM: // Operation not permitted
+ case EACCES: // Permission denied
+#ifdef ENOKEY
+ case ENOKEY: // Required key not available
+#endif
+ case EROFS: // Read only file system
+ return StatusCode::kPermissionDenied;
+ case ENOTEMPTY: // Directory not empty
+ case EISDIR: // Is a directory
+ case ENOTDIR: // Not a directory
+ case EADDRINUSE: // Address already in use
+ case EBADF: // Invalid file descriptor
+#ifdef EBADFD
+ case EBADFD: // File descriptor in bad state
+#endif
+ case EBUSY: // Device or resource busy
+ case ECHILD: // No child processes
+ case EISCONN: // Socket is connected
+#ifdef EISNAM
+ case EISNAM: // Is a named type file
+#endif
+#ifdef ENOTBLK
+ case ENOTBLK: // Block device required
+#endif
+ case ENOTCONN: // The socket is not connected
+ case EPIPE: // Broken pipe
+#ifdef ESHUTDOWN
+ case ESHUTDOWN: // Cannot send after transport endpoint shutdown
+#endif
+ case ETXTBSY: // Text file busy
+#ifdef EUNATCH
+ case EUNATCH: // Protocol driver not attached
+#endif
+ return StatusCode::kFailedPrecondition;
+ case ENOSPC: // No space left on device
+#ifdef EDQUOT
+ case EDQUOT: // Disk quota exceeded
+#endif
+ case EMFILE: // Too many open files
+ case EMLINK: // Too many links
+ case ENFILE: // Too many open files in system
+ case ENOBUFS: // No buffer space available
+ case ENODATA: // No message is available on the STREAM read queue
+ case ENOMEM: // Not enough space
+ case ENOSR: // No STREAM resources
+#ifdef EUSERS
+ case EUSERS: // Too many users
+#endif
+ return StatusCode::kResourceExhausted;
+#ifdef ECHRNG
+ case ECHRNG: // Channel number out of range
+#endif
+ case EFBIG: // File too large
+ case EOVERFLOW: // Value too large to be stored in data type
+ case ERANGE: // Result too large
+ return StatusCode::kOutOfRange;
+#ifdef ENOPKG
+ case ENOPKG: // Package not installed
+#endif
+ case ENOSYS: // Function not implemented
+ case ENOTSUP: // Operation not supported
+ case EAFNOSUPPORT: // Address family not supported
+#ifdef EPFNOSUPPORT
+ case EPFNOSUPPORT: // Protocol family not supported
+#endif
+ case EPROTONOSUPPORT: // Protocol not supported
+#ifdef ESOCKTNOSUPPORT
+ case ESOCKTNOSUPPORT: // Socket type not supported
+#endif
+ case EXDEV: // Improper link
+ return StatusCode::kUnimplemented;
+ case EAGAIN: // Resource temporarily unavailable
+#ifdef ECOMM
+ case ECOMM: // Communication error on send
+#endif
+ case ECONNREFUSED: // Connection refused
+ case ECONNABORTED: // Connection aborted
+ case ECONNRESET: // Connection reset
+ case EINTR: // Interrupted function call
+#ifdef EHOSTDOWN
+ case EHOSTDOWN: // Host is down
+#endif
+ case EHOSTUNREACH: // Host is unreachable
+ case ENETDOWN: // Network is down
+ case ENETRESET: // Connection aborted by network
+ case ENETUNREACH: // Network unreachable
+ case ENOLCK: // No locks available
+ case ENOLINK: // Link has been severed
+#ifdef ENONET
+ case ENONET: // Machine is not on the network
+#endif
+ return StatusCode::kUnavailable;
+ case EDEADLK: // Resource deadlock avoided
+#ifdef ESTALE
+ case ESTALE: // Stale file handle
+#endif
+ return StatusCode::kAborted;
+ case ECANCELED: // Operation cancelled
+ return StatusCode::kCancelled;
+ default:
+ return StatusCode::kUnknown;
+ }
+}
+
+namespace {
+std::string MessageForErrnoToStatus(int error_number,
+ absl::string_view message) {
+ return absl::StrCat(message, ": ",
+ absl::base_internal::StrError(error_number));
+}
+} // namespace
+
+Status ErrnoToStatus(int error_number, absl::string_view message) {
+ return Status(ErrnoToStatusCode(error_number),
+ MessageForErrnoToStatus(error_number, message));
+}
+
+absl::Nonnull<const char*> StatusMessageAsCStr(const Status& status) {
+ // As an internal implementation detail, we guarantee that if status.message()
+ // is non-empty, then the resulting string_view is null terminated.
+ auto sv_message = status.message();
+ return sv_message.empty() ? "" : sv_message.data();
+}
+
ABSL_NAMESPACE_END
} // namespace absl
diff --git a/absl/status/status.h b/absl/status/status.h
index c5fe0a7..6cfe49f 100644
--- a/absl/status/status.h
+++ b/absl/status/status.h
@@ -24,11 +24,11 @@
// * A set of helper functions for creating status codes and checking their
// values
//
-// Within Google, `absl::Status` is the primary mechanism for gracefully
-// handling errors across API boundaries (and in particular across RPC
-// boundaries). Some of these errors may be recoverable, but others may not.
-// Most functions that can produce a recoverable error should be designed to
-// return an `absl::Status` (or `absl::StatusOr`).
+// Within Google, `absl::Status` is the primary mechanism for communicating
+// errors in C++, and is used to represent error state in both in-process
+// library calls as well as RPC calls. Some of these errors may be recoverable,
+// but others may not. Most functions that can produce a recoverable error
+// should be designed to return an `absl::Status` (or `absl::StatusOr`).
//
// Example:
//
@@ -51,10 +51,18 @@
#ifndef ABSL_STATUS_STATUS_H_
#define ABSL_STATUS_STATUS_H_
-#include <iostream>
+#include <cassert>
+#include <cstdint>
+#include <ostream>
#include <string>
+#include <utility>
-#include "absl/container/inlined_vector.h"
+#include "absl/base/attributes.h"
+#include "absl/base/config.h"
+#include "absl/base/macros.h"
+#include "absl/base/nullability.h"
+#include "absl/base/optimization.h"
+#include "absl/functional/function_ref.h"
#include "absl/status/internal/status_internal.h"
#include "absl/strings/cord.h"
#include "absl/strings/string_view.h"
@@ -346,7 +354,7 @@
// API developers should construct their functions to return `absl::OkStatus()`
// upon success, or an `absl::StatusCode` upon another type of error (e.g
// an `absl::StatusCode::kInvalidArgument` error). The API provides convenience
-// functions to constuct each status code.
+// functions to construct each status code.
//
// Example:
//
@@ -397,7 +405,7 @@
//
// * It may provide more fine-grained semantic information about the error to
// facilitate actionable remedies.
-// * It may provide human-readable contexual information that is more
+// * It may provide human-readable contextual information that is more
// appropriate to display to an end user.
//
// Example:
@@ -420,7 +428,7 @@
// Returned Status objects may not be ignored. status_internal.h has a forward
// declaration of the form
// class ABSL_MUST_USE_RESULT Status;
-class Status final {
+class ABSL_ATTRIBUTE_TRIVIAL_ABI Status final {
public:
// Constructors
@@ -444,7 +452,7 @@
// The moved-from state is valid but unspecified.
Status(Status&&) noexcept;
- Status& operator=(Status&&);
+ Status& operator=(Status&&) noexcept;
~Status();
@@ -468,8 +476,9 @@
// Status::ok()
//
- // Returns `true` if `this->ok()`. Prefer checking for an OK status using this
- // member function.
+ // Returns `true` if `this->code()` == `absl::StatusCode::kOk`,
+ // indicating the absence of an error.
+ // Prefer checking for an OK status using this member function.
ABSL_MUST_USE_RESULT bool ok() const;
// Status::code()
@@ -494,7 +503,7 @@
// Returns the error message associated with this error code, if available.
// Note that this message rarely describes the error code. It is not unusual
// for the error message to be the empty string. As a result, prefer
- // `Status::ToString()` for debug logging.
+ // `operator<<` or `Status::ToString()` for debug logging.
absl::string_view message() const;
friend bool operator==(const Status&, const Status&);
@@ -514,6 +523,12 @@
std::string ToString(
StatusToStringMode mode = StatusToStringMode::kDefault) const;
+ // Support `absl::StrCat`, `absl::StrFormat`, etc.
+ template <typename Sink>
+ friend void AbslStringify(Sink& sink, const Status& status) {
+ sink.Append(status.ToString(StatusToStringMode::kWithEverything));
+ }
+
// Status::IgnoreError()
//
// Ignores any errors. This method does nothing except potentially suppress
@@ -524,19 +539,19 @@
// swap()
//
// Swap the contents of one status with another.
- friend void swap(Status& a, Status& b);
+ friend void swap(Status& a, Status& b) noexcept;
//----------------------------------------------------------------------------
// Payload Management APIs
//----------------------------------------------------------------------------
// A payload may be attached to a status to provide additional context to an
- // error that may not be satisifed by an existing `absl::StatusCode`.
+ // error that may not be satisfied by an existing `absl::StatusCode`.
// Typically, this payload serves one of several purposes:
//
// * It may provide more fine-grained semantic information about the error
// to facilitate actionable remedies.
- // * It may provide human-readable contexual information that is more
+ // * It may provide human-readable contextual information that is more
// appropriate to display to an end user.
//
// A payload consists of a [key,value] pair, where the key is a string
@@ -590,7 +605,7 @@
// NOTE: Any mutation on the same 'absl::Status' object during visitation is
// forbidden and could result in undefined behavior.
void ForEachPayload(
- const std::function<void(absl::string_view, const absl::Cord&)>& visitor)
+ absl::FunctionRef<void(absl::string_view, const absl::Cord&)> visitor)
const;
private:
@@ -600,60 +615,57 @@
// code, and an empty error message.
explicit Status(absl::StatusCode code);
- static void UnrefNonInlined(uintptr_t rep);
+ // Underlying constructor for status from a rep_.
+ explicit Status(uintptr_t rep) : rep_(rep) {}
+
static void Ref(uintptr_t rep);
static void Unref(uintptr_t rep);
// REQUIRES: !ok()
- // Ensures rep_ is not shared with any other Status.
- void PrepareToModify();
-
- const status_internal::Payloads* GetPayloads() const;
- status_internal::Payloads* GetPayloads();
-
- // Takes ownership of payload.
- static uintptr_t NewRep(
- absl::StatusCode code, absl::string_view msg,
- std::unique_ptr<status_internal::Payloads> payload);
- static bool EqualsSlow(const absl::Status& a, const absl::Status& b);
+ // Ensures rep is not inlined or shared with any other Status.
+ static absl::Nonnull<status_internal::StatusRep*> PrepareToModify(
+ uintptr_t rep);
// MSVC 14.0 limitation requires the const.
static constexpr const char kMovedFromString[] =
"Status accessed after move.";
- static const std::string* EmptyString();
- static const std::string* MovedFromString();
+ static absl::Nonnull<const std::string*> EmptyString();
+ static absl::Nonnull<const std::string*> MovedFromString();
// Returns whether rep contains an inlined representation.
// See rep_ for details.
- static bool IsInlined(uintptr_t rep);
+ static constexpr bool IsInlined(uintptr_t rep);
// Indicates whether this Status was the rhs of a move operation. See rep_
// for details.
- static bool IsMovedFrom(uintptr_t rep);
- static uintptr_t MovedFromRep();
+ static constexpr bool IsMovedFrom(uintptr_t rep);
+ static constexpr uintptr_t MovedFromRep();
// Convert between error::Code and the inlined uintptr_t representation used
// by rep_. See rep_ for details.
- static uintptr_t CodeToInlinedRep(absl::StatusCode code);
- static absl::StatusCode InlinedRepToCode(uintptr_t rep);
+ static constexpr uintptr_t CodeToInlinedRep(absl::StatusCode code);
+ static constexpr absl::StatusCode InlinedRepToCode(uintptr_t rep);
// Converts between StatusRep* and the external uintptr_t representation used
// by rep_. See rep_ for details.
static uintptr_t PointerToRep(status_internal::StatusRep* r);
- static status_internal::StatusRep* RepToPointer(uintptr_t r);
+ static absl::Nonnull<const status_internal::StatusRep*> RepToPointer(
+ uintptr_t r);
- std::string ToStringSlow(StatusToStringMode mode) const;
+ static std::string ToStringSlow(uintptr_t rep, StatusToStringMode mode);
// Status supports two different representations.
- // - When the low bit is off it is an inlined representation.
+ // - When the low bit is set it is an inlined representation.
// It uses the canonical error space, no message or payload.
// The error code is (rep_ >> 2).
// The (rep_ & 2) bit is the "moved from" indicator, used in IsMovedFrom().
- // - When the low bit is on it is an external representation.
+ // - When the low bit is off it is an external representation.
// In this case all the data comes from a heap allocated Rep object.
- // (rep_ - 1) is a status_internal::StatusRep* pointer to that structure.
+ // rep_ is a status_internal::StatusRep* pointer to that structure.
uintptr_t rep_;
+
+ friend class status_internal::StatusRep;
};
// OkStatus()
@@ -740,15 +752,28 @@
Status UnimplementedError(absl::string_view message);
Status UnknownError(absl::string_view message);
+// ErrnoToStatusCode()
+//
+// Returns the StatusCode for `error_number`, which should be an `errno` value.
+// See https://en.cppreference.com/w/cpp/error/errno_macros and similar
+// references.
+absl::StatusCode ErrnoToStatusCode(int error_number);
+
+// ErrnoToStatus()
+//
+// Convenience function that creates a `absl::Status` using an `error_number`,
+// which should be an `errno` value.
+Status ErrnoToStatus(int error_number, absl::string_view message);
+
//------------------------------------------------------------------------------
// Implementation details follow
//------------------------------------------------------------------------------
-inline Status::Status() : rep_(CodeToInlinedRep(absl::StatusCode::kOk)) {}
+inline Status::Status() : Status(absl::StatusCode::kOk) {}
-inline Status::Status(absl::StatusCode code) : rep_(CodeToInlinedRep(code)) {}
+inline Status::Status(absl::StatusCode code) : Status(CodeToInlinedRep(code)) {}
-inline Status::Status(const Status& x) : rep_(x.rep_) { Ref(rep_); }
+inline Status::Status(const Status& x) : Status(x.rep_) { Ref(rep_); }
inline Status& Status::operator=(const Status& x) {
uintptr_t old_rep = rep_;
@@ -760,11 +785,11 @@
return *this;
}
-inline Status::Status(Status&& x) noexcept : rep_(x.rep_) {
+inline Status::Status(Status&& x) noexcept : Status(x.rep_) {
x.rep_ = MovedFromRep();
}
-inline Status& Status::operator=(Status&& x) {
+inline Status& Status::operator=(Status&& x) noexcept {
uintptr_t old_rep = rep_;
if (x.rep_ != old_rep) {
rep_ = x.rep_;
@@ -792,15 +817,27 @@
return rep_ == CodeToInlinedRep(absl::StatusCode::kOk);
}
+inline absl::StatusCode Status::code() const {
+ return status_internal::MapToLocalCode(raw_code());
+}
+
+inline int Status::raw_code() const {
+ if (IsInlined(rep_)) return static_cast<int>(InlinedRepToCode(rep_));
+ return static_cast<int>(RepToPointer(rep_)->code());
+}
+
inline absl::string_view Status::message() const {
return !IsInlined(rep_)
- ? RepToPointer(rep_)->message
+ ? RepToPointer(rep_)->message()
: (IsMovedFrom(rep_) ? absl::string_view(kMovedFromString)
: absl::string_view());
}
inline bool operator==(const Status& lhs, const Status& rhs) {
- return lhs.rep_ == rhs.rep_ || Status::EqualsSlow(lhs, rhs);
+ if (lhs.rep_ == rhs.rep_) return true;
+ if (Status::IsInlined(lhs.rep_)) return false;
+ if (Status::IsInlined(rhs.rep_)) return false;
+ return *Status::RepToPointer(lhs.rep_) == *Status::RepToPointer(rhs.rep_);
}
inline bool operator!=(const Status& lhs, const Status& rhs) {
@@ -808,64 +845,80 @@
}
inline std::string Status::ToString(StatusToStringMode mode) const {
- return ok() ? "OK" : ToStringSlow(mode);
+ return ok() ? "OK" : ToStringSlow(rep_, mode);
}
inline void Status::IgnoreError() const {
// no-op
}
-inline void swap(absl::Status& a, absl::Status& b) {
+inline void swap(absl::Status& a, absl::Status& b) noexcept {
using std::swap;
swap(a.rep_, b.rep_);
}
-inline const status_internal::Payloads* Status::GetPayloads() const {
- return IsInlined(rep_) ? nullptr : RepToPointer(rep_)->payloads.get();
+inline absl::optional<absl::Cord> Status::GetPayload(
+ absl::string_view type_url) const {
+ if (IsInlined(rep_)) return absl::nullopt;
+ return RepToPointer(rep_)->GetPayload(type_url);
}
-inline status_internal::Payloads* Status::GetPayloads() {
- return IsInlined(rep_) ? nullptr : RepToPointer(rep_)->payloads.get();
+inline void Status::SetPayload(absl::string_view type_url, absl::Cord payload) {
+ if (ok()) return;
+ status_internal::StatusRep* rep = PrepareToModify(rep_);
+ rep->SetPayload(type_url, std::move(payload));
+ rep_ = PointerToRep(rep);
}
-inline bool Status::IsInlined(uintptr_t rep) { return (rep & 1) == 0; }
-
-inline bool Status::IsMovedFrom(uintptr_t rep) {
- return IsInlined(rep) && (rep & 2) != 0;
+inline bool Status::ErasePayload(absl::string_view type_url) {
+ if (IsInlined(rep_)) return false;
+ status_internal::StatusRep* rep = PrepareToModify(rep_);
+ auto res = rep->ErasePayload(type_url);
+ rep_ = res.new_rep;
+ return res.erased;
}
-inline uintptr_t Status::MovedFromRep() {
- return CodeToInlinedRep(absl::StatusCode::kInternal) | 2;
+inline void Status::ForEachPayload(
+ absl::FunctionRef<void(absl::string_view, const absl::Cord&)> visitor)
+ const {
+ if (IsInlined(rep_)) return;
+ RepToPointer(rep_)->ForEachPayload(visitor);
}
-inline uintptr_t Status::CodeToInlinedRep(absl::StatusCode code) {
- return static_cast<uintptr_t>(code) << 2;
+constexpr bool Status::IsInlined(uintptr_t rep) { return (rep & 1) != 0; }
+
+constexpr bool Status::IsMovedFrom(uintptr_t rep) { return (rep & 2) != 0; }
+
+constexpr uintptr_t Status::CodeToInlinedRep(absl::StatusCode code) {
+ return (static_cast<uintptr_t>(code) << 2) + 1;
}
-inline absl::StatusCode Status::InlinedRepToCode(uintptr_t rep) {
- assert(IsInlined(rep));
+constexpr absl::StatusCode Status::InlinedRepToCode(uintptr_t rep) {
+ ABSL_ASSERT(IsInlined(rep));
return static_cast<absl::StatusCode>(rep >> 2);
}
-inline status_internal::StatusRep* Status::RepToPointer(uintptr_t rep) {
- assert(!IsInlined(rep));
- return reinterpret_cast<status_internal::StatusRep*>(rep - 1);
+constexpr uintptr_t Status::MovedFromRep() {
+ return CodeToInlinedRep(absl::StatusCode::kInternal) | 2;
}
-inline uintptr_t Status::PointerToRep(status_internal::StatusRep* rep) {
- return reinterpret_cast<uintptr_t>(rep) + 1;
+inline absl::Nonnull<const status_internal::StatusRep*> Status::RepToPointer(
+ uintptr_t rep) {
+ assert(!IsInlined(rep));
+ return reinterpret_cast<const status_internal::StatusRep*>(rep);
+}
+
+inline uintptr_t Status::PointerToRep(
+ absl::Nonnull<status_internal::StatusRep*> rep) {
+ return reinterpret_cast<uintptr_t>(rep);
}
inline void Status::Ref(uintptr_t rep) {
- if (!IsInlined(rep)) {
- RepToPointer(rep)->ref.fetch_add(1, std::memory_order_relaxed);
- }
+ if (!IsInlined(rep)) RepToPointer(rep)->Ref();
}
inline void Status::Unref(uintptr_t rep) {
- if (!IsInlined(rep)) {
- UnrefNonInlined(rep);
- }
+ if (!IsInlined(rep)) RepToPointer(rep)->Unref();
}
inline Status OkStatus() { return Status(); }
@@ -875,6 +928,15 @@
// message-less kCancelled errors are common in the infrastructure.
inline Status CancelledError() { return Status(absl::StatusCode::kCancelled); }
+// Retrieves a message's status as a null terminated C string. The lifetime of
+// this string is tied to the lifetime of the status object itself.
+//
+// If the status's message is empty, the empty string is returned.
+//
+// StatusMessageAsCStr exists for C support. Use `status.message()` in C++.
+absl::Nonnull<const char*> StatusMessageAsCStr(
+ const Status& status ABSL_ATTRIBUTE_LIFETIME_BOUND);
+
ABSL_NAMESPACE_END
} // namespace absl
diff --git a/absl/status/status_payload_printer.cc b/absl/status/status_payload_printer.cc
index a47aea1..98401e9 100644
--- a/absl/status/status_payload_printer.cc
+++ b/absl/status/status_payload_printer.cc
@@ -13,9 +13,7 @@
// limitations under the License.
#include "absl/status/status_payload_printer.h"
-#include <atomic>
-
-#include "absl/base/attributes.h"
+#include "absl/base/config.h"
#include "absl/base/internal/atomic_hook.h"
namespace absl {
diff --git a/absl/status/status_payload_printer.h b/absl/status/status_payload_printer.h
index 5e0937f..f22255e 100644
--- a/absl/status/status_payload_printer.h
+++ b/absl/status/status_payload_printer.h
@@ -16,6 +16,7 @@
#include <string>
+#include "absl/base/nullability.h"
#include "absl/strings/cord.h"
#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
@@ -34,8 +35,8 @@
// NOTE: This is an internal API and the design is subject to change in the
// future in a non-backward-compatible way. Since it's only meant for debugging
// purpose, you should not rely on it in any critical logic.
-using StatusPayloadPrinter = absl::optional<std::string> (*)(absl::string_view,
- const absl::Cord&);
+using StatusPayloadPrinter = absl::Nullable<absl::optional<std::string> (*)(
+ absl::string_view, const absl::Cord&)>;
// Sets the global payload printer. Only one printer should be set per process.
// If multiple printers are set, it's undefined which one will be used.
diff --git a/absl/status/status_test.cc b/absl/status/status_test.cc
index 1b038f6..585e780 100644
--- a/absl/status/status_test.cc
+++ b/absl/status/status_test.cc
@@ -14,9 +14,18 @@
#include "absl/status/status.h"
+#include <errno.h>
+
+#include <array>
+#include <cstddef>
+#include <sstream>
+#include <utility>
+
#include "gmock/gmock.h"
#include "gtest/gtest.h"
+#include "absl/strings/cord.h"
#include "absl/strings/str_cat.h"
+#include "absl/strings/str_format.h"
namespace {
@@ -130,6 +139,29 @@
}
}
+TEST(Status, StatusMessageCStringTest) {
+ {
+ absl::Status status = absl::OkStatus();
+ EXPECT_EQ(status.message(), "");
+ EXPECT_STREQ(absl::StatusMessageAsCStr(status), "");
+ EXPECT_EQ(status.message(), absl::StatusMessageAsCStr(status));
+ EXPECT_NE(absl::StatusMessageAsCStr(status), nullptr);
+ }
+ {
+ absl::Status status;
+ EXPECT_EQ(status.message(), "");
+ EXPECT_NE(absl::StatusMessageAsCStr(status), nullptr);
+ EXPECT_STREQ(absl::StatusMessageAsCStr(status), "");
+ }
+ {
+ absl::Status status(absl::StatusCode::kInternal, "message");
+ EXPECT_FALSE(status.ok());
+ EXPECT_EQ(absl::StatusCode::kInternal, status.code());
+ EXPECT_EQ("message", status.message());
+ EXPECT_STREQ("message", absl::StatusMessageAsCStr(status));
+ }
+}
+
TEST(Status, ConstructOutOfRangeCode) {
const int kRawCode = 9999;
absl::Status status(static_cast<absl::StatusCode>(kRawCode), "");
@@ -274,37 +306,77 @@
}
TEST(Status, ToString) {
- absl::Status s(absl::StatusCode::kInternal, "fail");
- EXPECT_EQ("INTERNAL: fail", s.ToString());
- s.SetPayload("foo", absl::Cord("bar"));
- EXPECT_EQ("INTERNAL: fail [foo='bar']", s.ToString());
- s.SetPayload("bar", absl::Cord("\377"));
- EXPECT_THAT(s.ToString(),
+ absl::Status status(absl::StatusCode::kInternal, "fail");
+ EXPECT_EQ("INTERNAL: fail", status.ToString());
+ status.SetPayload("foo", absl::Cord("bar"));
+ EXPECT_EQ("INTERNAL: fail [foo='bar']", status.ToString());
+ status.SetPayload("bar", absl::Cord("\377"));
+ EXPECT_THAT(status.ToString(),
AllOf(HasSubstr("INTERNAL: fail"), HasSubstr("[foo='bar']"),
HasSubstr("[bar='\\xff']")));
}
TEST(Status, ToStringMode) {
- absl::Status s(absl::StatusCode::kInternal, "fail");
- s.SetPayload("foo", absl::Cord("bar"));
- s.SetPayload("bar", absl::Cord("\377"));
+ absl::Status status(absl::StatusCode::kInternal, "fail");
+ status.SetPayload("foo", absl::Cord("bar"));
+ status.SetPayload("bar", absl::Cord("\377"));
EXPECT_EQ("INTERNAL: fail",
- s.ToString(absl::StatusToStringMode::kWithNoExtraData));
+ status.ToString(absl::StatusToStringMode::kWithNoExtraData));
- EXPECT_THAT(s.ToString(absl::StatusToStringMode::kWithPayload),
+ EXPECT_THAT(status.ToString(absl::StatusToStringMode::kWithPayload),
AllOf(HasSubstr("INTERNAL: fail"), HasSubstr("[foo='bar']"),
HasSubstr("[bar='\\xff']")));
- EXPECT_THAT(s.ToString(absl::StatusToStringMode::kWithEverything),
+ EXPECT_THAT(status.ToString(absl::StatusToStringMode::kWithEverything),
AllOf(HasSubstr("INTERNAL: fail"), HasSubstr("[foo='bar']"),
HasSubstr("[bar='\\xff']")));
- EXPECT_THAT(s.ToString(~absl::StatusToStringMode::kWithPayload),
+ EXPECT_THAT(status.ToString(~absl::StatusToStringMode::kWithPayload),
AllOf(HasSubstr("INTERNAL: fail"), Not(HasSubstr("[foo='bar']")),
Not(HasSubstr("[bar='\\xff']"))));
}
+TEST(Status, OstreamOperator) {
+ absl::Status status(absl::StatusCode::kInternal, "fail");
+ { std::stringstream stream;
+ stream << status;
+ EXPECT_EQ("INTERNAL: fail", stream.str());
+ }
+ status.SetPayload("foo", absl::Cord("bar"));
+ { std::stringstream stream;
+ stream << status;
+ EXPECT_EQ("INTERNAL: fail [foo='bar']", stream.str());
+ }
+ status.SetPayload("bar", absl::Cord("\377"));
+ { std::stringstream stream;
+ stream << status;
+ EXPECT_THAT(stream.str(),
+ AllOf(HasSubstr("INTERNAL: fail"), HasSubstr("[foo='bar']"),
+ HasSubstr("[bar='\\xff']")));
+ }
+}
+
+TEST(Status, AbslStringify) {
+ absl::Status status(absl::StatusCode::kInternal, "fail");
+ EXPECT_EQ("INTERNAL: fail", absl::StrCat(status));
+ EXPECT_EQ("INTERNAL: fail", absl::StrFormat("%v", status));
+ status.SetPayload("foo", absl::Cord("bar"));
+ EXPECT_EQ("INTERNAL: fail [foo='bar']", absl::StrCat(status));
+ status.SetPayload("bar", absl::Cord("\377"));
+ EXPECT_THAT(absl::StrCat(status),
+ AllOf(HasSubstr("INTERNAL: fail"), HasSubstr("[foo='bar']"),
+ HasSubstr("[bar='\\xff']")));
+}
+
+TEST(Status, OstreamEqStringify) {
+ absl::Status status(absl::StatusCode::kUnknown, "fail");
+ status.SetPayload("foo", absl::Cord("bar"));
+ std::stringstream stream;
+ stream << status;
+ EXPECT_EQ(stream.str(), absl::StrCat(status));
+}
+
absl::Status EraseAndReturn(const absl::Status& base) {
absl::Status copy = base;
EXPECT_TRUE(copy.ErasePayload(kUrl1));
@@ -485,4 +557,23 @@
test_swap(no_payload, with_payload);
test_swap(with_payload, no_payload);
}
+
+TEST(StatusErrno, ErrnoToStatusCode) {
+ EXPECT_EQ(absl::ErrnoToStatusCode(0), absl::StatusCode::kOk);
+
+ // Spot-check a few errno values.
+ EXPECT_EQ(absl::ErrnoToStatusCode(EINVAL),
+ absl::StatusCode::kInvalidArgument);
+ EXPECT_EQ(absl::ErrnoToStatusCode(ENOENT), absl::StatusCode::kNotFound);
+
+ // We'll pick a very large number so it hopefully doesn't collide to errno.
+ EXPECT_EQ(absl::ErrnoToStatusCode(19980927), absl::StatusCode::kUnknown);
+}
+
+TEST(StatusErrno, ErrnoToStatus) {
+ absl::Status status = absl::ErrnoToStatus(ENOENT, "Cannot open 'path'");
+ EXPECT_EQ(status.code(), absl::StatusCode::kNotFound);
+ EXPECT_EQ(status.message(), "Cannot open 'path': No such file or directory");
+}
+
} // namespace
diff --git a/absl/status/statusor.cc b/absl/status/statusor.cc
index 96642b3..7e6b334 100644
--- a/absl/status/statusor.cc
+++ b/absl/status/statusor.cc
@@ -17,7 +17,10 @@
#include <utility>
#include "absl/base/call_once.h"
+#include "absl/base/config.h"
#include "absl/base/internal/raw_logging.h"
+#include "absl/base/nullability.h"
+#include "absl/status/internal/statusor_internal.h"
#include "absl/status/status.h"
#include "absl/strings/str_cat.h"
@@ -52,7 +55,7 @@
BadStatusOrAccess::BadStatusOrAccess(BadStatusOrAccess&& other)
: status_(std::move(other.status_)) {}
-const char* BadStatusOrAccess::what() const noexcept {
+absl::Nonnull<const char*> BadStatusOrAccess::what() const noexcept {
InitWhat();
return what_.c_str();
}
@@ -67,7 +70,7 @@
namespace internal_statusor {
-void Helper::HandleInvalidStatusCtorArg(absl::Status* status) {
+void Helper::HandleInvalidStatusCtorArg(absl::Nonnull<absl::Status*> status) {
const char* kMessage =
"An OK status is not a valid constructor argument to StatusOr<T>";
#ifdef NDEBUG
diff --git a/absl/status/statusor.h b/absl/status/statusor.h
index 235a343..cd35e5b 100644
--- a/absl/status/statusor.h
+++ b/absl/status/statusor.h
@@ -39,15 +39,20 @@
#include <exception>
#include <initializer_list>
#include <new>
+#include <ostream>
#include <string>
#include <type_traits>
#include <utility>
#include "absl/base/attributes.h"
+#include "absl/base/nullability.h"
#include "absl/base/call_once.h"
#include "absl/meta/type_traits.h"
#include "absl/status/internal/statusor_internal.h"
#include "absl/status/status.h"
+#include "absl/strings/has_absl_stringify.h"
+#include "absl/strings/has_ostream_operator.h"
+#include "absl/strings/str_format.h"
#include "absl/types/variant.h"
#include "absl/utility/utility.h"
@@ -88,7 +93,7 @@
//
// The pointer of this string is guaranteed to be valid until any non-const
// function is invoked on the exception object.
- const char* what() const noexcept override;
+ absl::Nonnull<const char*> what() const noexcept override;
// BadStatusOrAccess::status()
//
@@ -106,7 +111,13 @@
// Returned StatusOr objects may not be ignored.
template <typename T>
+#if ABSL_HAVE_CPP_ATTRIBUTE(nodiscard)
+// TODO(b/176172494): ABSL_MUST_USE_RESULT should expand to the more strict
+// [[nodiscard]]. For now, just use [[nodiscard]] directly when it is available.
+class [[nodiscard]] StatusOr;
+#else
class ABSL_MUST_USE_RESULT StatusOr;
+#endif // ABSL_HAVE_CPP_ATTRIBUTE(nodiscard)
// absl::StatusOr<T>
//
@@ -140,7 +151,7 @@
//
// absl::StatusOr<int> i = GetCount();
// if (i.ok()) {
-// updated_total += *i
+// updated_total += *i;
// }
//
// NOTE: using `absl::StatusOr<T>::value()` when no valid value is present will
@@ -156,8 +167,8 @@
// A `absl::StatusOr<T*>` can be constructed from a null pointer like any other
// pointer value, and the result will be that `ok()` returns `true` and
// `value()` returns `nullptr`. Checking the value of pointer in an
-// `absl::StatusOr<T>` generally requires a bit more care, to ensure both that a
-// value is present and that value is not null:
+// `absl::StatusOr<T*>` generally requires a bit more care, to ensure both that
+// a value is present and that value is not null:
//
// StatusOr<std::unique_ptr<Foo>> result = FooFactory::MakeNewFoo(arg);
// if (!result.ok()) {
@@ -405,7 +416,7 @@
typename = typename std::enable_if<absl::conjunction<
std::is_constructible<T, U&&>, std::is_assignable<T&, U&&>,
absl::disjunction<
- std::is_same<absl::remove_cv_t<absl::remove_reference_t<U>>, T>,
+ std::is_same<absl::remove_cvref_t<U>, T>,
absl::conjunction<
absl::negation<std::is_convertible<U&&, absl::Status>>,
absl::negation<internal_statusor::
@@ -429,8 +440,8 @@
// if `T` can be constructed from a `U`. Can accept move or copy constructors.
//
// This constructor is explicit if `U` is not convertible to `T`. To avoid
- // ambiguity, this constuctor is disabled if `U` is a `StatusOr<J>`, where `J`
- // is convertible to `T`.
+ // ambiguity, this constructor is disabled if `U` is a `StatusOr<J>`, where
+ // `J` is convertible to `T`.
template <
typename U = T,
absl::enable_if_t<
@@ -438,8 +449,7 @@
internal_statusor::IsDirectInitializationValid<T, U&&>,
std::is_constructible<T, U&&>, std::is_convertible<U&&, T>,
absl::disjunction<
- std::is_same<absl::remove_cv_t<absl::remove_reference_t<U>>,
- T>,
+ std::is_same<absl::remove_cvref_t<U>, T>,
absl::conjunction<
absl::negation<std::is_convertible<U&&, absl::Status>>,
absl::negation<
@@ -455,8 +465,7 @@
absl::conjunction<
internal_statusor::IsDirectInitializationValid<T, U&&>,
absl::disjunction<
- std::is_same<absl::remove_cv_t<absl::remove_reference_t<U>>,
- T>,
+ std::is_same<absl::remove_cvref_t<U>, T>,
absl::conjunction<
absl::negation<std::is_constructible<absl::Status, U&&>>,
absl::negation<
@@ -471,7 +480,7 @@
// StatusOr<T>::ok()
//
// Returns whether or not this `absl::StatusOr<T>` holds a `T` value. This
- // member function is analagous to `absl::Status::ok()` and should be used
+ // member function is analogous to `absl::Status::ok()` and should be used
// similarly to check the status of return values.
//
// Example:
@@ -518,10 +527,10 @@
//
// 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&;
- T& value() &;
- const T&& value() const&&;
- T&& value() &&;
+ 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;
// StatusOr<T>:: operator*()
//
@@ -533,10 +542,10 @@
// `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&;
- T& operator*() &;
- const T&& operator*() const&&;
- T&& operator*() &&;
+ 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;
// StatusOr<T>::operator->()
//
@@ -545,8 +554,8 @@
// REQUIRES: `this->ok() == true`, otherwise the behavior is undefined.
//
// Use `this->ok()` to verify that there is a current value.
- const T* operator->() const;
- T* operator->();
+ const T* operator->() const ABSL_ATTRIBUTE_LIFETIME_BOUND;
+ T* operator->() ABSL_ATTRIBUTE_LIFETIME_BOUND;
// StatusOr<T>::value_or()
//
@@ -578,7 +587,7 @@
// Reconstructs the inner value T in-place using the provided args, using the
// T(args...) constructor. Returns reference to the reconstructed `T`.
template <typename... Args>
- T& emplace(Args&&... args) {
+ T& emplace(Args&&... args) ABSL_ATTRIBUTE_LIFETIME_BOUND {
if (ok()) {
this->Clear();
this->MakeValue(std::forward<Args>(args)...);
@@ -594,7 +603,8 @@
absl::enable_if_t<
std::is_constructible<T, std::initializer_list<U>&, Args&&...>::value,
int> = 0>
- T& emplace(std::initializer_list<U> ilist, Args&&... args) {
+ T& emplace(std::initializer_list<U> ilist,
+ Args&&... args) ABSL_ATTRIBUTE_LIFETIME_BOUND {
if (ok()) {
this->Clear();
this->MakeValue(ilist, std::forward<Args>(args)...);
@@ -605,6 +615,21 @@
return this->data_;
}
+ // StatusOr<T>::AssignStatus()
+ //
+ // Sets the status of `absl::StatusOr<T>` to the given non-ok status value.
+ //
+ // NOTE: We recommend using the constructor and `operator=` where possible.
+ // This method is intended for use in generic programming, to enable setting
+ // the status of a `StatusOr<T>` when `T` may be `Status`. In that case, the
+ // constructor and `operator=` would assign into the inner value of type
+ // `Status`, rather than status of the `StatusOr` (b/280392796).
+ //
+ // REQUIRES: !Status(std::forward<U>(v)).ok(). This requirement is DCHECKed.
+ // In optimized builds, passing absl::OkStatus() here will have the effect
+ // of passing absl::StatusCode::kInternal as a fallback.
+ using internal_statusor::StatusOrData<T>::AssignStatus;
+
private:
using internal_statusor::StatusOrData<T>::Assign;
template <typename U>
@@ -630,6 +655,41 @@
return !(lhs == rhs);
}
+// Prints the `value` or the status in brackets to `os`.
+//
+// Requires `T` supports `operator<<`. Do not rely on the output format which
+// may change without notice.
+template <typename T, typename std::enable_if<
+ absl::HasOstreamOperator<T>::value, int>::type = 0>
+std::ostream& operator<<(std::ostream& os, const StatusOr<T>& status_or) {
+ if (status_or.ok()) {
+ os << status_or.value();
+ } else {
+ os << internal_statusor::StringifyRandom::OpenBrackets()
+ << status_or.status()
+ << internal_statusor::StringifyRandom::CloseBrackets();
+ }
+ return os;
+}
+
+// As above, but supports `StrCat`, `StrFormat`, etc.
+//
+// Requires `T` has `AbslStringify`. Do not rely on the output format which
+// may change without notice.
+template <
+ typename Sink, typename T,
+ typename std::enable_if<absl::HasAbslStringify<T>::value, int>::type = 0>
+void AbslStringify(Sink& sink, const StatusOr<T>& status_or) {
+ if (status_or.ok()) {
+ absl::Format(&sink, "%v", status_or.value());
+ } else {
+ absl::Format(&sink, "%s%v%s",
+ internal_statusor::StringifyRandom::OpenBrackets(),
+ status_or.status(),
+ internal_statusor::StringifyRandom::CloseBrackets());
+ }
+}
+
//------------------------------------------------------------------------------
// Implementation details for StatusOr<T>
//------------------------------------------------------------------------------
@@ -730,13 +790,13 @@
}
template <typename T>
-const T* StatusOr<T>::operator->() const {
+absl::Nonnull<const T*> StatusOr<T>::operator->() const {
this->EnsureOk();
return &this->data_;
}
template <typename T>
-T* StatusOr<T>::operator->() {
+absl::Nonnull<T*> StatusOr<T>::operator->() {
this->EnsureOk();
return &this->data_;
}
diff --git a/absl/status/statusor_test.cc b/absl/status/statusor_test.cc
index 7cae90e..09ffc65 100644
--- a/absl/status/statusor_test.cc
+++ b/absl/status/statusor_test.cc
@@ -15,31 +15,41 @@
#include "absl/status/statusor.h"
#include <array>
+#include <cstddef>
#include <initializer_list>
+#include <map>
#include <memory>
+#include <ostream>
+#include <sstream>
#include <string>
#include <type_traits>
#include <utility>
+#include <vector>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/base/casts.h"
#include "absl/memory/memory.h"
#include "absl/status/status.h"
+#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "absl/types/any.h"
+#include "absl/types/variant.h"
#include "absl/utility/utility.h"
namespace {
using ::testing::AllOf;
+using ::testing::AnyOf;
using ::testing::AnyWith;
using ::testing::ElementsAre;
+using ::testing::EndsWith;
using ::testing::Field;
using ::testing::HasSubstr;
using ::testing::Ne;
using ::testing::Not;
using ::testing::Pointee;
+using ::testing::StartsWith;
using ::testing::VariantWith;
#ifdef GTEST_HAS_STATUS_MATCHERS
@@ -1521,7 +1531,7 @@
TEST(StatusOr, TestIgnoreError) { MakeStatus().IgnoreError(); }
TEST(StatusOr, EqualityOperator) {
- constexpr int kNumCases = 4;
+ constexpr size_t kNumCases = 4;
std::array<absl::StatusOr<int>, kNumCases> group1 = {
absl::StatusOr<int>(1), absl::StatusOr<int>(2),
absl::StatusOr<int>(absl::InvalidArgumentError("msg")),
@@ -1530,8 +1540,8 @@
absl::StatusOr<int>(1), absl::StatusOr<int>(2),
absl::StatusOr<int>(absl::InvalidArgumentError("msg")),
absl::StatusOr<int>(absl::InternalError("msg"))};
- for (int i = 0; i < kNumCases; ++i) {
- for (int j = 0; j < kNumCases; ++j) {
+ for (size_t i = 0; i < kNumCases; ++i) {
+ for (size_t j = 0; j < kNumCases; ++j) {
if (i == j) {
EXPECT_TRUE(group1[i] == group2[j]);
EXPECT_FALSE(group1[i] != group2[j]);
@@ -1844,4 +1854,68 @@
}
}
+TEST(StatusOr, StatusAssignmentFromStatusError) {
+ absl::StatusOr<absl::Status> statusor;
+ statusor.AssignStatus(absl::CancelledError());
+
+ EXPECT_FALSE(statusor.ok());
+ EXPECT_EQ(statusor.status(), absl::CancelledError());
+}
+
+#if GTEST_HAS_DEATH_TEST
+TEST(StatusOr, StatusAssignmentFromStatusOk) {
+ EXPECT_DEBUG_DEATH(
+ {
+ absl::StatusOr<absl::Status> statusor;
+ // This will DCHECK.
+ statusor.AssignStatus(absl::OkStatus());
+ // In optimized mode, we are actually going to get error::INTERNAL for
+ // status here, rather than crashing, so check that.
+ EXPECT_FALSE(statusor.ok());
+ EXPECT_EQ(statusor.status().code(), absl::StatusCode::kInternal);
+ },
+ "An OK status is not a valid constructor argument to StatusOr<T>");
+}
+#endif
+
+TEST(StatusOr, StatusAssignmentFromTypeConvertibleToStatus) {
+ CustomType<MyType, kConvToStatus> v;
+ absl::StatusOr<MyType> statusor;
+ statusor.AssignStatus(v);
+
+ EXPECT_FALSE(statusor.ok());
+ EXPECT_EQ(statusor.status(), static_cast<absl::Status>(v));
+}
+
+struct PrintTestStruct {
+ friend std::ostream& operator<<(std::ostream& os, const PrintTestStruct&) {
+ return os << "ostream";
+ }
+
+ template <typename Sink>
+ friend void AbslStringify(Sink& sink, const PrintTestStruct&) {
+ sink.Append("stringify");
+ }
+};
+
+TEST(StatusOr, OkPrinting) {
+ absl::StatusOr<PrintTestStruct> print_me = PrintTestStruct{};
+ std::stringstream stream;
+ stream << print_me;
+ EXPECT_EQ(stream.str(), "ostream");
+ EXPECT_EQ(absl::StrCat(print_me), "stringify");
+}
+
+TEST(StatusOr, ErrorPrinting) {
+ absl::StatusOr<PrintTestStruct> print_me = absl::UnknownError("error");
+ std::stringstream stream;
+ stream << print_me;
+ const auto error_matcher =
+ AllOf(HasSubstr("UNKNOWN"), HasSubstr("error"),
+ AnyOf(AllOf(StartsWith("("), EndsWith(")")),
+ AllOf(StartsWith("["), EndsWith("]"))));
+ EXPECT_THAT(stream.str(), error_matcher);
+ EXPECT_THAT(absl::StrCat(print_me), error_matcher);
+}
+
} // namespace
diff --git a/absl/strings/BUILD.bazel b/absl/strings/BUILD.bazel
index 9720b68..8b30783 100644
--- a/absl/strings/BUILD.bazel
+++ b/absl/strings/BUILD.bazel
@@ -13,21 +13,40 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test")
load(
"//absl:copts/configure_copts.bzl",
"ABSL_DEFAULT_COPTS",
+ "ABSL_DEFAULT_LINKOPTS",
"ABSL_TEST_COPTS",
)
package(
default_visibility = ["//visibility:public"],
- features = ["parse_headers"],
+ features = [
+ "header_modules",
+ "layering_check",
+ "parse_headers",
+ ],
)
licenses(["notice"])
cc_library(
+ name = "string_view",
+ srcs = ["string_view.cc"],
+ hdrs = ["string_view.h"],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ "//absl/base",
+ "//absl/base:config",
+ "//absl/base:core_headers",
+ "//absl/base:nullability",
+ "//absl/base:throw_delegate",
+ ],
+)
+
+cc_library(
name = "strings",
srcs = [
"ascii.cc",
@@ -37,23 +56,28 @@
"internal/charconv_bigint.h",
"internal/charconv_parse.cc",
"internal/charconv_parse.h",
+ "internal/damerau_levenshtein_distance.cc",
"internal/memutil.cc",
"internal/memutil.h",
"internal/stl_type_traits.h",
"internal/str_join_internal.h",
"internal/str_split_internal.h",
+ "internal/stringify_sink.cc",
+ "internal/stringify_sink.h",
"match.cc",
"numbers.cc",
"str_cat.cc",
"str_replace.cc",
"str_split.cc",
- "string_view.cc",
"substitute.cc",
],
hdrs = [
"ascii.h",
"charconv.h",
"escaping.h",
+ "has_absl_stringify.h",
+ "internal/damerau_levenshtein_distance.h",
+ "internal/has_absl_stringify.h",
"internal/string_constant.h",
"match.h",
"numbers.h",
@@ -66,12 +90,22 @@
"substitute.h",
],
copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ textual_hdrs = [
+ # string_view.h was once part of :strings, so string_view.h is
+ # re-exported for backwards compatibility.
+ # New code should directly depend on :string_view.
+ "string_view.h",
+ ],
deps = [
+ ":charset",
":internal",
+ ":string_view",
"//absl/base",
"//absl/base:config",
"//absl/base:core_headers",
"//absl/base:endian",
+ "//absl/base:nullability",
"//absl/base:raw_logging_internal",
"//absl/base:throw_delegate",
"//absl/memory",
@@ -89,13 +123,13 @@
"internal/utf8.cc",
],
hdrs = [
- "internal/char_map.h",
"internal/escaping.h",
"internal/ostringstream.h",
"internal/resize_uninitialized.h",
"internal/utf8.h",
],
copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
"//absl/base:config",
"//absl/base:core_headers",
@@ -113,6 +147,7 @@
visibility = ["//visibility:private"],
deps = [
":strings",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -131,6 +166,8 @@
":strings",
"//absl/base:core_headers",
"//absl/container:fixed_array",
+ "//absl/log:check",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -148,6 +185,45 @@
":strings",
"//absl/base:raw_logging_internal",
"@com_github_google_benchmark//:benchmark_main",
+ "@com_google_googletest//:gtest",
+ ],
+)
+
+cc_test(
+ name = "has_absl_stringify_test",
+ size = "small",
+ srcs = ["has_absl_stringify_test.cc"],
+ copts = ABSL_TEST_COPTS,
+ visibility = ["//visibility:private"],
+ deps = [
+ ":strings",
+ "//absl/types:optional",
+ "@com_google_googletest//:gtest",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_library(
+ name = "has_ostream_operator",
+ hdrs = ["has_ostream_operator.h"],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ "//absl/base:config",
+ ],
+)
+
+cc_test(
+ name = "has_ostream_operator_test",
+ size = "small",
+ srcs = ["has_ostream_operator_test.cc"],
+ copts = ABSL_TEST_COPTS,
+ visibility = ["//visibility:private"],
+ deps = [
+ ":has_ostream_operator",
+ "//absl/types:optional",
+ "@com_google_googletest//:gtest",
+ "@com_google_googletest//:gtest_main",
],
)
@@ -160,6 +236,7 @@
deps = [
":strings",
"//absl/base:core_headers",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -173,6 +250,21 @@
deps = [
":strings",
"@com_github_google_benchmark//:benchmark_main",
+ "@com_google_googletest//:gtest",
+ ],
+)
+
+cc_test(
+ name = "damerau_levenshtein_distance_test",
+ size = "small",
+ srcs = [
+ "internal/damerau_levenshtein_distance_test.cc",
+ ],
+ copts = ABSL_TEST_COPTS,
+ deps = [
+ "//absl/strings",
+ "@com_google_googletest//:gtest",
+ "@com_google_googletest//:gtest_main",
],
)
@@ -189,6 +281,7 @@
":strings",
"//absl/base:core_headers",
"@com_github_google_benchmark//:benchmark_main",
+ "@com_google_googletest//:gtest",
],
)
@@ -204,6 +297,7 @@
deps = [
":strings",
"//absl/base:core_headers",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -219,6 +313,7 @@
deps = [
":internal",
"//absl/base:core_headers",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -232,6 +327,7 @@
deps = [
":strings",
"//absl/meta:type_traits",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -243,10 +339,12 @@
tags = ["benchmark"],
visibility = ["//visibility:private"],
deps = [
+ ":string_view",
":strings",
"//absl/base:core_headers",
"//absl/base:raw_logging_internal",
"@com_github_google_benchmark//:benchmark_main",
+ "@com_google_googletest//:gtest",
],
)
@@ -257,10 +355,57 @@
copts = ABSL_TEST_COPTS,
visibility = ["//visibility:private"],
deps = [
- ":strings",
+ ":string_view",
"//absl/base:config",
"//absl/base:core_headers",
"//absl/base:dynamic_annotations",
+ "@com_google_googletest//:gtest",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "charset_benchmark",
+ size = "small",
+ srcs = [
+ "charset_benchmark.cc",
+ ],
+ copts = ABSL_TEST_COPTS,
+ tags = [
+ "benchmark",
+ ],
+ visibility = ["//visibility:private"],
+ deps = [
+ ":charset",
+ "//absl/log:check",
+ "@com_github_google_benchmark//:benchmark_main",
+ "@com_google_googletest//:gtest",
+ ],
+)
+
+cc_library(
+ name = "charset",
+ hdrs = [
+ "charset.h",
+ ],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ ":string_view",
+ "//absl/base:core_headers",
+ ],
+)
+
+cc_test(
+ name = "charset_test",
+ size = "small",
+ srcs = ["charset_test.cc"],
+ copts = ABSL_TEST_COPTS,
+ visibility = ["//visibility:private"],
+ deps = [
+ ":charset",
+ ":strings",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -273,19 +418,20 @@
"internal/cord_rep_btree_navigator.cc",
"internal/cord_rep_btree_reader.cc",
"internal/cord_rep_consume.cc",
- "internal/cord_rep_ring.cc",
+ "internal/cord_rep_crc.cc",
],
hdrs = [
+ "internal/cord_data_edge.h",
"internal/cord_internal.h",
"internal/cord_rep_btree.h",
"internal/cord_rep_btree_navigator.h",
"internal/cord_rep_btree_reader.h",
"internal/cord_rep_consume.h",
+ "internal/cord_rep_crc.h",
"internal/cord_rep_flat.h",
- "internal/cord_rep_ring.h",
- "internal/cord_rep_ring_reader.h",
],
copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
visibility = [
"//visibility:private",
],
@@ -298,8 +444,10 @@
"//absl/base:raw_logging_internal",
"//absl/base:throw_delegate",
"//absl/container:compressed_tuple",
+ "//absl/container:container_memory",
"//absl/container:inlined_vector",
"//absl/container:layout",
+ "//absl/crc:crc_cord_state",
"//absl/functional:function_ref",
"//absl/meta:type_traits",
"//absl/types:span",
@@ -307,8 +455,25 @@
)
cc_test(
+ name = "cord_data_edge_test",
+ size = "small",
+ srcs = ["internal/cord_data_edge_test.cc"],
+ copts = ABSL_TEST_COPTS,
+ visibility = ["//visibility:private"],
+ deps = [
+ ":cord_internal",
+ ":cord_rep_test_util",
+ ":strings",
+ "//absl/base:config",
+ "@com_google_googletest//:gtest",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_test(
name = "cord_rep_btree_test",
size = "medium",
+ timeout = "long",
srcs = ["internal/cord_rep_btree_test.cc"],
copts = ABSL_TEST_COPTS,
visibility = ["//visibility:private"],
@@ -318,6 +483,8 @@
":strings",
"//absl/base:config",
"//absl/base:raw_logging_internal",
+ "//absl/cleanup",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -334,6 +501,7 @@
":strings",
"//absl/base:config",
"//absl/base:raw_logging_internal",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -351,6 +519,23 @@
":strings",
"//absl/base:config",
"//absl/base:raw_logging_internal",
+ "@com_google_googletest//:gtest",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "cord_rep_crc_test",
+ size = "small",
+ srcs = ["internal/cord_rep_crc_test.cc"],
+ copts = ABSL_TEST_COPTS,
+ visibility = ["//visibility:private"],
+ deps = [
+ ":cord_internal",
+ ":cord_rep_test_util",
+ "//absl/base:config",
+ "//absl/crc:crc_cord_state",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -359,6 +544,7 @@
name = "cordz_update_tracker",
hdrs = ["internal/cordz_update_tracker.h"],
copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
visibility = [
"//absl:__subpackages__",
],
@@ -373,6 +559,7 @@
"//absl/base:config",
"//absl/base:core_headers",
"//absl/synchronization",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -381,11 +568,16 @@
name = "cord",
srcs = [
"cord.cc",
+ "cord_analysis.cc",
+ "cord_analysis.h",
+ "cord_buffer.cc",
],
hdrs = [
"cord.h",
+ "cord_buffer.h",
],
copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
":cord_internal",
":cordz_functions",
@@ -394,18 +586,21 @@
":cordz_update_scope",
":cordz_update_tracker",
":internal",
- ":str_format",
":strings",
"//absl/base",
"//absl/base:config",
"//absl/base:core_headers",
"//absl/base:endian",
+ "//absl/base:nullability",
"//absl/base:raw_logging_internal",
- "//absl/container:fixed_array",
"//absl/container:inlined_vector",
+ "//absl/crc:crc32c",
+ "//absl/crc:crc_cord_state",
"//absl/functional:function_ref",
"//absl/meta:type_traits",
+ "//absl/numeric:bits",
"//absl/types:optional",
+ "//absl/types:span",
],
)
@@ -414,12 +609,13 @@
srcs = ["internal/cordz_handle.cc"],
hdrs = ["internal/cordz_handle.h"],
copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
visibility = [
"//absl:__subpackages__",
],
deps = [
- "//absl/base",
"//absl/base:config",
+ "//absl/base:no_destructor",
"//absl/base:raw_logging_internal",
"//absl/synchronization",
],
@@ -430,6 +626,7 @@
srcs = ["internal/cordz_info.cc"],
hdrs = ["internal/cordz_info.h"],
copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
visibility = [
"//absl:__subpackages__",
],
@@ -446,6 +643,7 @@
"//absl/container:inlined_vector",
"//absl/debugging:stacktrace",
"//absl/synchronization",
+ "//absl/time",
"//absl/types:span",
],
)
@@ -454,6 +652,7 @@
name = "cordz_update_scope",
hdrs = ["internal/cordz_update_scope.h"],
copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
visibility = [
"//absl:__subpackages__",
],
@@ -477,6 +676,7 @@
":cordz_update_scope",
":cordz_update_tracker",
"//absl/base:config",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -486,6 +686,7 @@
srcs = ["internal/cordz_sample_token.cc"],
hdrs = ["internal/cordz_sample_token.h"],
copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
visibility = [
"//absl:__subpackages__",
],
@@ -501,14 +702,15 @@
srcs = ["internal/cordz_functions.cc"],
hdrs = ["internal/cordz_functions.h"],
copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
visibility = [
"//absl:__subpackages__",
],
deps = [
"//absl/base:config",
"//absl/base:core_headers",
- "//absl/base:exponential_biased",
"//absl/base:raw_logging_internal",
+ "//absl/profiling:exponential_biased",
],
)
@@ -516,6 +718,7 @@
name = "cordz_statistics",
hdrs = ["internal/cordz_statistics.h"],
copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
visibility = [
"//absl:__subpackages__",
],
@@ -534,6 +737,7 @@
":cordz_functions",
":cordz_test_helpers",
"//absl/base:config",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -552,6 +756,7 @@
"//absl/synchronization",
"//absl/synchronization:thread_pool",
"//absl/time",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -573,6 +778,7 @@
"//absl/debugging:stacktrace",
"//absl/debugging:symbolize",
"//absl/types:span",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -591,8 +797,10 @@
":cordz_update_scope",
":cordz_update_tracker",
"//absl/base:config",
+ "//absl/crc:crc_cord_state",
"//absl/synchronization",
"//absl/synchronization:thread_pool",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -614,17 +822,19 @@
"//absl/synchronization",
"//absl/synchronization:thread_pool",
"//absl/time",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
cc_library(
name = "cord_test_helpers",
- testonly = 1,
+ testonly = True,
hdrs = [
"cord_test_helpers.h",
],
copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
":cord",
":cord_internal",
@@ -635,9 +845,10 @@
cc_library(
name = "cord_rep_test_util",
- testonly = 1,
+ testonly = True,
hdrs = ["internal/cord_rep_test_util.h"],
copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
":cord_internal",
":strings",
@@ -648,9 +859,10 @@
cc_library(
name = "cordz_test_helpers",
- testonly = 1,
+ testonly = True,
hdrs = ["cordz_test_helpers.h"],
copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
":cord",
":cord_internal",
@@ -661,29 +873,58 @@
":strings",
"//absl/base:config",
"//absl/base:core_headers",
+ "//absl/base:nullability",
"@com_google_googletest//:gtest",
],
)
cc_test(
+ name = "cord_buffer_test",
+ size = "small",
+ srcs = ["cord_buffer_test.cc"],
+ copts = ABSL_TEST_COPTS,
+ visibility = ["//visibility:private"],
+ deps = [
+ ":cord",
+ ":cord_internal",
+ ":cord_rep_test_util",
+ ":string_view",
+ "//absl/base:config",
+ "//absl/types:span",
+ "@com_google_googletest//:gtest",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_test(
name = "cord_test",
size = "medium",
+ timeout = "long",
srcs = ["cord_test.cc"],
copts = ABSL_TEST_COPTS,
visibility = ["//visibility:private"],
deps = [
":cord",
+ ":cord_internal",
":cord_test_helpers",
":cordz_functions",
+ ":cordz_statistics",
":cordz_test_helpers",
+ ":cordz_update_tracker",
":str_format",
":strings",
- "//absl/base",
"//absl/base:config",
"//absl/base:core_headers",
"//absl/base:endian",
- "//absl/base:raw_logging_internal",
"//absl/container:fixed_array",
+ "//absl/functional:function_ref",
+ "//absl/hash",
+ "//absl/hash:hash_testing",
+ "//absl/log",
+ "//absl/log:check",
+ "//absl/random",
+ "//absl/types:optional",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -698,14 +939,14 @@
"no_test_android_arm",
"no_test_android_arm64",
"no_test_android_x86",
- "no_test_darwin_x86_64",
"no_test_ios_x86_64",
+ "no_test_lexan",
"no_test_loonix",
- "no_test_msvc_x64",
],
visibility = ["//visibility:private"],
deps = [
":cord",
+ ":cord_internal",
":cord_test_helpers",
":cordz_functions",
":cordz_info",
@@ -717,54 +958,7 @@
"//absl/base:config",
"//absl/base:core_headers",
"//absl/base:raw_logging_internal",
- "@com_google_googletest//:gtest_main",
- ],
-)
-
-cc_test(
- name = "cord_rep_consume_test",
- size = "medium",
- srcs = ["internal/cord_rep_consume_test.cc"],
- copts = ABSL_TEST_COPTS,
- visibility = ["//visibility:private"],
- deps = [
- ":cord_internal",
- ":strings",
- "//absl/base:config",
- "//absl/base:core_headers",
- "//absl/debugging:leak_check",
- "@com_google_googletest//:gtest_main",
- ],
-)
-
-cc_test(
- name = "cord_ring_test",
- size = "medium",
- srcs = ["cord_ring_test.cc"],
- copts = ABSL_TEST_COPTS,
- visibility = ["//visibility:private"],
- deps = [
- ":cord_internal",
- ":strings",
- "//absl/base:config",
- "//absl/base:core_headers",
- "//absl/base:raw_logging_internal",
- "//absl/debugging:leak_check",
- "@com_google_googletest//:gtest_main",
- ],
-)
-
-cc_test(
- name = "cord_ring_reader_test",
- size = "medium",
- srcs = ["cord_ring_reader_test.cc"],
- copts = ABSL_TEST_COPTS,
- visibility = ["//visibility:private"],
- deps = [
- ":cord_internal",
- ":strings",
- "//absl/base:core_headers",
- "//absl/debugging:leak_check",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -778,6 +972,7 @@
deps = [
":strings",
"//absl/base:core_headers",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -792,6 +987,7 @@
":strings",
"//absl/base:raw_logging_internal",
"@com_github_google_benchmark//:benchmark_main",
+ "@com_google_googletest//:gtest",
],
)
@@ -803,6 +999,7 @@
visibility = ["//visibility:private"],
deps = [
":strings",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -819,6 +1016,7 @@
"//absl/container:btree",
"//absl/container:flat_hash_map",
"//absl/container:node_hash_map",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -833,6 +1031,7 @@
":strings",
"//absl/base:raw_logging_internal",
"@com_github_google_benchmark//:benchmark_main",
+ "@com_google_googletest//:gtest",
],
)
@@ -844,6 +1043,7 @@
visibility = ["//visibility:private"],
deps = [
":internal",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -857,6 +1057,7 @@
deps = [
":internal",
"@com_github_google_benchmark//:benchmark_main",
+ "@com_google_googletest//:gtest",
],
)
@@ -872,6 +1073,7 @@
deps = [
"//absl/base:core_headers",
"//absl/meta:type_traits",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -886,6 +1088,7 @@
":strings",
"//absl/base:core_headers",
"//absl/memory",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -899,6 +1102,7 @@
deps = [
":strings",
"@com_github_google_benchmark//:benchmark_main",
+ "@com_google_googletest//:gtest",
],
)
@@ -909,8 +1113,9 @@
copts = ABSL_TEST_COPTS,
visibility = ["//visibility:private"],
deps = [
+ ":str_format",
":strings",
- "//absl/base:core_headers",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -923,7 +1128,10 @@
visibility = ["//visibility:private"],
deps = [
":strings",
+ "//absl/random",
+ "//absl/random:distributions",
"@com_github_google_benchmark//:benchmark_main",
+ "@com_google_googletest//:gtest",
],
)
@@ -941,9 +1149,11 @@
":pow10_helper",
":strings",
"//absl/base:config",
- "//absl/base:raw_logging_internal",
+ "//absl/log",
+ "//absl/numeric:int128",
"//absl/random",
"//absl/random:distributions",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -960,6 +1170,7 @@
"//absl/random",
"//absl/random:distributions",
"@com_github_google_benchmark//:benchmark_main",
+ "@com_google_googletest//:gtest",
],
)
@@ -971,32 +1182,12 @@
visibility = ["//visibility:private"],
deps = [
":strings",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
cc_test(
- name = "char_map_test",
- srcs = ["internal/char_map_test.cc"],
- copts = ABSL_TEST_COPTS,
- deps = [
- ":internal",
- "@com_google_googletest//:gtest_main",
- ],
-)
-
-cc_test(
- name = "char_map_benchmark",
- srcs = ["internal/char_map_benchmark.cc"],
- copts = ABSL_TEST_COPTS,
- tags = ["benchmark"],
- deps = [
- ":internal",
- "@com_github_google_benchmark//:benchmark_main",
- ],
-)
-
-cc_test(
name = "charconv_test",
srcs = ["charconv_test.cc"],
copts = ABSL_TEST_COPTS,
@@ -1004,6 +1195,7 @@
":pow10_helper",
":str_format",
":strings",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -1018,7 +1210,8 @@
deps = [
":strings",
"//absl/base:config",
- "//absl/base:raw_logging_internal",
+ "//absl/log:check",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -1034,6 +1227,7 @@
deps = [
":strings",
"//absl/base:config",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -1049,6 +1243,7 @@
deps = [
":strings",
"@com_github_google_benchmark//:benchmark_main",
+ "@com_google_googletest//:gtest",
],
)
@@ -1058,8 +1253,14 @@
"str_format.h",
],
copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
":str_format_internal",
+ ":string_view",
+ "//absl/base:config",
+ "//absl/base:core_headers",
+ "//absl/base:nullability",
+ "//absl/types:span",
],
)
@@ -1077,17 +1278,21 @@
"internal/str_format/arg.h",
"internal/str_format/bind.h",
"internal/str_format/checker.h",
+ "internal/str_format/constexpr_parser.h",
"internal/str_format/extension.h",
"internal/str_format/float_conversion.h",
"internal/str_format/output.h",
"internal/str_format/parser.h",
],
copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
visibility = ["//visibility:private"],
deps = [
":strings",
"//absl/base:config",
"//absl/base:core_headers",
+ "//absl/container:fixed_array",
+ "//absl/container:inlined_vector",
"//absl/functional:function_ref",
"//absl/meta:type_traits",
"//absl/numeric:bits",
@@ -1095,6 +1300,7 @@
"//absl/numeric:representation",
"//absl/types:optional",
"//absl/types:span",
+ "//absl/utility",
],
)
@@ -1107,7 +1313,10 @@
":cord",
":str_format",
":strings",
+ "//absl/base:config",
"//absl/base:core_headers",
+ "//absl/types:span",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -1123,6 +1332,7 @@
":str_format",
":str_format_internal",
":strings",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -1135,6 +1345,8 @@
deps = [
":str_format",
":str_format_internal",
+ "//absl/base:config",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -1146,6 +1358,7 @@
visibility = ["//visibility:private"],
deps = [
":str_format_internal",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -1157,6 +1370,7 @@
visibility = ["//visibility:private"],
deps = [
":str_format",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -1164,14 +1378,22 @@
cc_test(
name = "str_format_convert_test",
size = "medium",
+ timeout = "long",
srcs = ["internal/str_format/convert_test.cc"],
copts = ABSL_TEST_COPTS,
visibility = ["//visibility:private"],
deps = [
+ ":str_format",
":str_format_internal",
":strings",
+ "//absl/base:config",
+ "//absl/base:core_headers",
"//absl/base:raw_logging_internal",
+ "//absl/log",
+ "//absl/numeric:int128",
"//absl/types:optional",
+ "//absl/types:span",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -1184,6 +1406,7 @@
deps = [
":cord",
":str_format_internal",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -1195,7 +1418,10 @@
visibility = ["//visibility:private"],
deps = [
":str_format_internal",
+ ":string_view",
+ "//absl/base:config",
"//absl/base:core_headers",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -1205,6 +1431,7 @@
testonly = True,
srcs = ["internal/pow10_helper.cc"],
hdrs = ["internal/pow10_helper.h"],
+ linkopts = ABSL_DEFAULT_LINKOPTS,
visibility = ["//visibility:private"],
deps = ["//absl/base:config"],
)
@@ -1217,6 +1444,35 @@
deps = [
":pow10_helper",
":str_format",
+ "@com_google_googletest//:gtest",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_binary(
+ name = "atod_manual_test",
+ testonly = True,
+ srcs = ["atod_manual_test.cc"],
+ copts = ABSL_TEST_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ visibility = ["//visibility:private"],
+ deps = [
+ ":str_format",
+ ":strings",
+ "//absl/base",
+ "//absl/types:optional",
+ ],
+)
+
+cc_test(
+ name = "char_formatting_test",
+ srcs = [
+ "char_formatting_test.cc",
+ ],
+ deps = [
+ ":str_format",
+ ":strings",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
diff --git a/absl/strings/CMakeLists.txt b/absl/strings/CMakeLists.txt
index 88f076a..9258e55 100644
--- a/absl/strings/CMakeLists.txt
+++ b/absl/strings/CMakeLists.txt
@@ -16,19 +16,39 @@
absl_cc_library(
NAME
+ string_view
+ HDRS
+ string_view.h
+ SRCS
+ string_view.cc
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ DEPS
+ absl::base
+ absl::config
+ absl::core_headers
+ absl::nullability
+ absl::throw_delegate
+ PUBLIC
+)
+
+absl_cc_library(
+ NAME
strings
HDRS
"ascii.h"
"charconv.h"
"escaping.h"
+ "has_absl_stringify.h"
+ "internal/damerau_levenshtein_distance.h"
"internal/string_constant.h"
+ "internal/has_absl_stringify.h"
"match.h"
"numbers.h"
"str_cat.h"
"str_join.h"
"str_replace.h"
"str_split.h"
- "string_view.h"
"strip.h"
"substitute.h"
SRCS
@@ -39,8 +59,11 @@
"internal/charconv_bigint.h"
"internal/charconv_parse.cc"
"internal/charconv_parse.h"
+ "internal/damerau_levenshtein_distance.cc"
"internal/memutil.cc"
"internal/memutil.h"
+ "internal/stringify_sink.h"
+ "internal/stringify_sink.cc"
"internal/stl_type_traits.h"
"internal/str_join_internal.h"
"internal/str_split_internal.h"
@@ -49,19 +72,21 @@
"str_cat.cc"
"str_replace.cc"
"str_split.cc"
- "string_view.cc"
"substitute.cc"
COPTS
${ABSL_DEFAULT_COPTS}
DEPS
+ absl::string_view
absl::strings_internal
absl::base
absl::bits
+ absl::charset
absl::config
absl::core_headers
absl::endian
absl::int128
absl::memory
+ absl::nullability
absl::raw_logging_internal
absl::throw_delegate
absl::type_traits
@@ -70,9 +95,34 @@
absl_cc_library(
NAME
+ charset
+ HDRS
+ charset.h
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ DEPS
+ absl::core_headers
+ absl::string_view
+ PUBLIC
+)
+
+absl_cc_library(
+ NAME
+ has_ostream_operator
+ HDRS
+ "has_ostream_operator.h"
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ DEPS
+ absl::config
+ PUBLIC
+)
+
+# Internal-only target, do not depend on directly.
+absl_cc_library(
+ NAME
strings_internal
HDRS
- "internal/char_map.h"
"internal/escaping.cc"
"internal/escaping.h"
"internal/ostringstream.h"
@@ -116,6 +166,33 @@
absl::core_headers
absl::fixed_array
GTest::gmock_main
+ absl::check
+)
+
+absl_cc_test(
+ NAME
+ has_absl_stringify_test
+ SRCS
+ "has_absl_stringify_test.cc"
+ COPTS
+ ${ABSL_TEST_COPTS}
+ DEPS
+ absl::optional
+ absl::strings
+ GTest::gmock_main
+)
+
+absl_cc_test(
+ NAME
+ has_ostream_operator_test
+ SRCS
+ "has_ostream_operator_test.cc"
+ COPTS
+ ${ABSL_TEST_COPTS}
+ DEPS
+ absl::has_ostream_operator
+ absl::optional
+ GTest::gmock_main
)
absl_cc_test(
@@ -133,6 +210,19 @@
absl_cc_test(
NAME
+ damerau_levenshtein_distance_test
+ SRCS
+ "internal/damerau_levenshtein_distance_test.cc"
+ COPTS
+ ${ABSL_TEST_COPTS}
+ DEPS
+ absl::strings
+ absl::base
+ GTest::gmock_main
+)
+
+absl_cc_test(
+ NAME
memutil_test
SRCS
"internal/memutil.h"
@@ -280,6 +370,7 @@
${ABSL_TEST_COPTS}
DEPS
absl::strings
+ absl::str_format
absl::core_headers
GTest::gmock_main
)
@@ -293,13 +384,14 @@
COPTS
${ABSL_TEST_COPTS}
DEPS
- absl::strings
- absl::core_headers
- absl::pow10_helper
absl::config
- absl::raw_logging_internal
- absl::random_random
+ absl::core_headers
+ absl::int128
+ absl::log
+ absl::pow10_helper
absl::random_distributions
+ absl::random_random
+ absl::strings
absl::strings_internal
GTest::gmock_main
)
@@ -319,13 +411,13 @@
absl_cc_test(
NAME
- char_map_test
+ charset_test
SRCS
- "internal/char_map_test.cc"
+ "charset_test.cc"
COPTS
${ABSL_TEST_COPTS}
DEPS
- absl::strings_internal
+ absl::strings
GTest::gmock_main
)
@@ -352,9 +444,9 @@
COPTS
${ABSL_TEST_COPTS}
DEPS
- absl::strings
+ absl::check
absl::config
- absl::raw_logging_internal
+ absl::strings
GTest::gmock_main
)
@@ -381,10 +473,16 @@
COPTS
${ABSL_DEFAULT_COPTS}
DEPS
+ absl::config
+ absl::core_headers
+ absl::nullability
+ absl::span
absl::str_format_internal
+ absl::string_view
PUBLIC
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
str_format_internal
@@ -392,6 +490,7 @@
"internal/str_format/arg.h"
"internal/str_format/bind.h"
"internal/str_format/checker.h"
+ "internal/str_format/constexpr_parser.h"
"internal/str_format/extension.h"
"internal/str_format/float_conversion.h"
"internal/str_format/output.h"
@@ -410,8 +509,11 @@
absl::strings
absl::config
absl::core_headers
+ absl::fixed_array
+ absl::inlined_vector
absl::numeric_representation
absl::type_traits
+ absl::utility
absl::int128
absl::span
)
@@ -424,10 +526,12 @@
COPTS
${ABSL_TEST_COPTS}
DEPS
- absl::str_format
+ absl::config
absl::cord
- absl::strings
absl::core_headers
+ absl::span
+ absl::str_format
+ absl::strings
GTest::gmock_main
)
@@ -453,6 +557,7 @@
COPTS
${ABSL_TEST_COPTS}
DEPS
+ absl::config
absl::str_format
absl::str_format_internal
GTest::gmock_main
@@ -490,10 +595,15 @@
COPTS
${ABSL_TEST_COPTS}
DEPS
- absl::strings
- absl::str_format_internal
- absl::raw_logging_internal
+ absl::config
+ absl::core_headers
absl::int128
+ absl::log
+ absl::raw_logging_internal
+ absl::span
+ absl::str_format
+ absl::str_format_internal
+ absl::strings
GTest::gmock_main
)
@@ -519,10 +629,27 @@
${ABSL_TEST_COPTS}
DEPS
absl::str_format_internal
+ absl::string_view
+ absl::config
absl::core_headers
GTest::gmock_main
)
+absl_cc_test(
+ NAME
+ char_formatting_test
+ SRCS
+ "char_formatting_test.cc"
+ COPTS
+ ${ABSL_TEST_COPTS}
+ DEPS
+ absl::base
+ absl::str_format
+ absl::strings
+ GTest::gmock_main
+)
+
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
pow10_helper
@@ -550,32 +677,35 @@
GTest::gmock_main
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
cord_internal
HDRS
+ "internal/cord_data_edge.h"
"internal/cord_internal.h"
"internal/cord_rep_btree.h"
"internal/cord_rep_btree_navigator.h"
"internal/cord_rep_btree_reader.h"
+ "internal/cord_rep_crc.h"
"internal/cord_rep_consume.h"
"internal/cord_rep_flat.h"
- "internal/cord_rep_ring.h"
- "internal/cord_rep_ring_reader.h"
SRCS
"internal/cord_internal.cc"
"internal/cord_rep_btree.cc"
"internal/cord_rep_btree_navigator.cc"
"internal/cord_rep_btree_reader.cc"
+ "internal/cord_rep_crc.cc"
"internal/cord_rep_consume.cc"
- "internal/cord_rep_ring.cc"
COPTS
${ABSL_DEFAULT_COPTS}
DEPS
absl::base_internal
absl::compressed_tuple
absl::config
+ absl::container_memory
absl::core_headers
+ absl::crc_cord_state
absl::endian
absl::inlined_vector
absl::layout
@@ -585,6 +715,7 @@
absl::type_traits
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
cordz_update_tracker
@@ -611,6 +742,7 @@
GTest::gmock_main
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
cordz_functions
@@ -639,6 +771,7 @@
GTest::gmock_main
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
cordz_statistics
@@ -653,6 +786,7 @@
absl::synchronization
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
cordz_handle
@@ -665,6 +799,7 @@
DEPS
absl::base
absl::config
+ absl::no_destructor
absl::raw_logging_internal
absl::synchronization
)
@@ -686,6 +821,7 @@
GTest::gmock_main
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
cordz_info
@@ -709,6 +845,7 @@
absl::raw_logging_internal
absl::stacktrace
absl::synchronization
+ absl::time
)
absl_cc_test(
@@ -749,10 +886,12 @@
absl::cordz_statistics
absl::cordz_update_scope
absl::cordz_update_tracker
+ absl::crc_cord_state
absl::thread_pool
GTest::gmock_main
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
cordz_sample_token
@@ -791,6 +930,7 @@
GTest::gmock_main
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
cordz_update_scope
@@ -829,8 +969,12 @@
cord
HDRS
"cord.h"
+ "cord_buffer.h"
SRCS
"cord.cc"
+ "cord_analysis.cc"
+ "cord_analysis.h"
+ "cord_buffer.cc"
COPTS
${ABSL_DEFAULT_COPTS}
DEPS
@@ -842,17 +986,21 @@
absl::cordz_update_scope
absl::cordz_update_tracker
absl::core_headers
+ absl::crc32c
+ absl::crc_cord_state
absl::endian
- absl::fixed_array
absl::function_ref
absl::inlined_vector
+ absl::nullability
absl::optional
absl::raw_logging_internal
+ absl::span
absl::strings
absl::type_traits
PUBLIC
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
cord_rep_test_util
@@ -881,8 +1029,10 @@
absl::cord_internal
absl::strings
TESTONLY
+ PUBLIC
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
cordz_test_helpers
@@ -899,6 +1049,7 @@
absl::cordz_statistics
absl::cordz_update_tracker
absl::core_headers
+ absl::nullability
absl::strings
TESTONLY
)
@@ -911,34 +1062,39 @@
COPTS
${ABSL_TEST_COPTS}
DEPS
- absl::cord
- absl::str_format
- absl::strings
absl::base
+ absl::check
absl::config
+ absl::cord
absl::cord_test_helpers
absl::cordz_test_helpers
absl::core_headers
absl::endian
- absl::raw_logging_internal
absl::fixed_array
+ absl::function_ref
+ absl::hash
+ absl::hash_testing
+ absl::log
+ absl::optional
+ absl::random_random
+ absl::str_format
+ absl::strings
GTest::gmock_main
)
absl_cc_test(
NAME
- cord_rep_consume_test
+ cord_data_edge_test
SRCS
- "internal/cord_rep_consume_test.cc"
+ "internal/cord_data_edge_test.cc"
COPTS
${ABSL_TEST_COPTS}
DEPS
absl::base
absl::config
absl::cord_internal
+ absl::cord_rep_test_util
absl::core_headers
- absl::function_ref
- absl::raw_logging_internal
absl::strings
GTest::gmock_main
)
@@ -952,6 +1108,7 @@
${ABSL_TEST_COPTS}
DEPS
absl::base
+ absl::cleanup
absl::config
absl::cord_internal
absl::cord_rep_test_util
@@ -999,33 +1156,16 @@
absl_cc_test(
NAME
- cord_ring_test
+ cord_rep_crc_test
SRCS
- "cord_ring_test.cc"
+ "internal/cord_rep_crc_test.cc"
COPTS
${ABSL_TEST_COPTS}
DEPS
- absl::base
absl::config
absl::cord_internal
- absl::core_headers
- absl::raw_logging_internal
- absl::strings
- GTest::gmock_main
-)
-
-absl_cc_test(
- NAME
- cord_ring_reader_test
- SRCS
- "cord_ring_reader_test.cc"
- COPTS
- ${ABSL_TEST_COPTS}
- DEPS
- absl::base
- absl::cord_internal
- absl::core_headers
- absl::strings
+ absl::cord_rep_test_util
+ absl::crc_cord_state
GTest::gmock_main
)
@@ -1038,6 +1178,7 @@
${ABSL_TEST_COPTS}
DEPS
absl::cord
+ absl::cord_internal
absl::cord_test_helpers
absl::cordz_test_helpers
absl::cordz_functions
diff --git a/absl/strings/ascii.cc b/absl/strings/ascii.cc
index 93bb03e..5460b2c 100644
--- a/absl/strings/ascii.cc
+++ b/absl/strings/ascii.cc
@@ -14,6 +14,15 @@
#include "absl/strings/ascii.h"
+#include <climits>
+#include <cstdint>
+#include <cstring>
+#include <string>
+#include <type_traits>
+
+#include "absl/base/config.h"
+#include "absl/base/nullability.h"
+
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace ascii_internal {
@@ -153,21 +162,132 @@
};
// clang-format on
+template <class T>
+static constexpr T BroadcastByte(unsigned char value) {
+ static_assert(std::is_integral<T>::value && sizeof(T) <= sizeof(uint64_t) &&
+ std::is_unsigned<T>::value,
+ "only unsigned integers up to 64-bit allowed");
+ T result = value;
+ constexpr size_t result_bit_width = sizeof(result) * CHAR_BIT;
+ result |= result << ((CHAR_BIT << 0) & (result_bit_width - 1));
+ result |= result << ((CHAR_BIT << 1) & (result_bit_width - 1));
+ result |= result << ((CHAR_BIT << 2) & (result_bit_width - 1));
+ return result;
+}
+
+// Returns whether `c` is in the a-z/A-Z range (w.r.t. `ToUpper`).
+// Implemented by:
+// 1. Pushing the a-z/A-Z range to [SCHAR_MIN, SCHAR_MIN + 26).
+// 2. Comparing to SCHAR_MIN + 26.
+template <bool ToUpper>
+constexpr bool AsciiInAZRange(unsigned char c) {
+ constexpr unsigned char sub = (ToUpper ? 'a' : 'A') - SCHAR_MIN;
+ constexpr signed char threshold = SCHAR_MIN + 26; // 26 = alphabet size.
+ // Using unsigned arithmetic as overflows/underflows are well defined.
+ unsigned char u = c - sub;
+ // Using signed cmp, as SIMD unsigned cmp isn't available in many platforms.
+ return static_cast<signed char>(u) < threshold;
+}
+
+template <bool ToUpper>
+static constexpr char* PartialAsciiStrCaseFold(absl::Nonnull<char*> p,
+ absl::Nonnull<char*> end) {
+ using vec_t = size_t;
+ const size_t n = static_cast<size_t>(end - p);
+
+ // SWAR algorithm: http://0x80.pl/notesen/2016-01-06-swar-swap-case.html
+ constexpr char ch_a = ToUpper ? 'a' : 'A', ch_z = ToUpper ? 'z' : 'Z';
+ char* const swar_end = p + (n / sizeof(vec_t)) * sizeof(vec_t);
+ while (p < swar_end) {
+ vec_t v = vec_t();
+
+ // memcpy the vector, but constexpr
+ for (size_t i = 0; i < sizeof(vec_t); ++i) {
+ v |= static_cast<vec_t>(static_cast<unsigned char>(p[i]))
+ << (i * CHAR_BIT);
+ }
+
+ constexpr unsigned int msb = 1u << (CHAR_BIT - 1);
+ const vec_t v_msb = v & BroadcastByte<vec_t>(msb);
+ const vec_t v_nonascii_mask = (v_msb << 1) - (v_msb >> (CHAR_BIT - 1));
+ const vec_t v_nonascii = v & v_nonascii_mask;
+ const vec_t v_ascii = v & ~v_nonascii_mask;
+ const vec_t a = v_ascii + BroadcastByte<vec_t>(msb - ch_a - 0),
+ z = v_ascii + BroadcastByte<vec_t>(msb - ch_z - 1);
+ v = v_nonascii | (v_ascii ^ ((a ^ z) & BroadcastByte<vec_t>(msb)) >> 2);
+
+ // memcpy the vector, but constexpr
+ for (size_t i = 0; i < sizeof(vec_t); ++i) {
+ p[i] = static_cast<char>(v >> (i * CHAR_BIT));
+ }
+
+ p += sizeof(v);
+ }
+
+ return p;
+}
+
+template <bool ToUpper>
+static constexpr void AsciiStrCaseFold(absl::Nonnull<char*> p,
+ absl::Nonnull<char*> end) {
+ // The upper- and lowercase versions of ASCII characters differ by only 1 bit.
+ // When we need to flip the case, we can xor with this bit to achieve the
+ // desired result. Note that the choice of 'a' and 'A' here is arbitrary. We
+ // could have chosen 'z' and 'Z', or any other pair of characters as they all
+ // have the same single bit difference.
+ constexpr unsigned char kAsciiCaseBitFlip = 'a' ^ 'A';
+
+ using vec_t = size_t;
+ // TODO(b/316380338): When FDO becomes able to vectorize these,
+ // revert this manual optimization and just leave the naive loop.
+ if (static_cast<size_t>(end - p) >= sizeof(vec_t)) {
+ p = ascii_internal::PartialAsciiStrCaseFold<ToUpper>(p, end);
+ }
+ while (p < end) {
+ unsigned char v = static_cast<unsigned char>(*p);
+ v ^= AsciiInAZRange<ToUpper>(v) ? kAsciiCaseBitFlip : 0;
+ *p = static_cast<char>(v);
+ ++p;
+ }
+}
+
+static constexpr size_t ValidateAsciiCasefold() {
+ constexpr size_t num_chars = 1 + CHAR_MAX - CHAR_MIN;
+ size_t incorrect_index = 0;
+ char lowered[num_chars] = {};
+ char uppered[num_chars] = {};
+ for (unsigned int i = 0; i < num_chars; ++i) {
+ uppered[i] = lowered[i] = static_cast<char>(i);
+ }
+ AsciiStrCaseFold<false>(&lowered[0], &lowered[num_chars]);
+ AsciiStrCaseFold<true>(&uppered[0], &uppered[num_chars]);
+ for (size_t i = 0; i < num_chars; ++i) {
+ const char ch = static_cast<char>(i),
+ ch_upper = ('a' <= ch && ch <= 'z' ? 'A' + (ch - 'a') : ch),
+ ch_lower = ('A' <= ch && ch <= 'Z' ? 'a' + (ch - 'A') : ch);
+ if (uppered[i] != ch_upper || lowered[i] != ch_lower) {
+ incorrect_index = i > 0 ? i : num_chars;
+ break;
+ }
+ }
+ return incorrect_index;
+}
+
+static_assert(ValidateAsciiCasefold() == 0, "error in case conversion");
+
} // namespace ascii_internal
-void AsciiStrToLower(std::string* s) {
- for (auto& ch : *s) {
- ch = absl::ascii_tolower(ch);
- }
+void AsciiStrToLower(absl::Nonnull<std::string*> s) {
+ char* p = &(*s)[0]; // Guaranteed to be valid for empty strings
+ return ascii_internal::AsciiStrCaseFold<false>(p, p + s->size());
}
-void AsciiStrToUpper(std::string* s) {
- for (auto& ch : *s) {
- ch = absl::ascii_toupper(ch);
- }
+void AsciiStrToUpper(absl::Nonnull<std::string*> s) {
+ char* p = &(*s)[0]; // Guaranteed to be valid for empty strings
+ return ascii_internal::AsciiStrCaseFold<true>(p, p + s->size());
}
-void RemoveExtraAsciiWhitespace(std::string* str) {
+void RemoveExtraAsciiWhitespace(absl::Nonnull<std::string*> str) {
auto stripped = StripAsciiWhitespace(*str);
if (stripped.empty()) {
@@ -183,17 +303,17 @@
for (; input_it < input_end; ++input_it) {
if (is_ws) {
// Consecutive whitespace? Keep only the last.
- is_ws = absl::ascii_isspace(*input_it);
+ is_ws = absl::ascii_isspace(static_cast<unsigned char>(*input_it));
if (is_ws) --output_it;
} else {
- is_ws = absl::ascii_isspace(*input_it);
+ is_ws = absl::ascii_isspace(static_cast<unsigned char>(*input_it));
}
*output_it = *input_it;
++output_it;
}
- str->erase(output_it - &(*str)[0]);
+ str->erase(static_cast<size_t>(output_it - &(*str)[0]));
}
ABSL_NAMESPACE_END
diff --git a/absl/strings/ascii.h b/absl/strings/ascii.h
index b46bc71..c238f4d 100644
--- a/absl/strings/ascii.h
+++ b/absl/strings/ascii.h
@@ -53,10 +53,12 @@
#define ABSL_STRINGS_ASCII_H_
#include <algorithm>
+#include <cstddef>
#include <string>
#include "absl/base/attributes.h"
#include "absl/base/config.h"
+#include "absl/base/nullability.h"
#include "absl/strings/string_view.h"
namespace absl {
@@ -133,7 +135,7 @@
// ascii_isprint()
//
-// Determines whether the given character is printable, including whitespace.
+// Determines whether the given character is printable, including spaces.
inline bool ascii_isprint(unsigned char c) { return c >= 32 && c < 127; }
// ascii_isgraph()
@@ -165,7 +167,7 @@
}
// Converts the characters in `s` to lowercase, changing the contents of `s`.
-void AsciiStrToLower(std::string* s);
+void AsciiStrToLower(absl::Nonnull<std::string*> s);
// Creates a lowercase string from a given absl::string_view.
ABSL_MUST_USE_RESULT inline std::string AsciiStrToLower(absl::string_view s) {
@@ -183,7 +185,7 @@
}
// Converts the characters in `s` to uppercase, changing the contents of `s`.
-void AsciiStrToUpper(std::string* s);
+void AsciiStrToUpper(absl::Nonnull<std::string*> s);
// Creates an uppercase string from a given absl::string_view.
ABSL_MUST_USE_RESULT inline std::string AsciiStrToUpper(absl::string_view s) {
@@ -197,11 +199,11 @@
ABSL_MUST_USE_RESULT inline absl::string_view StripLeadingAsciiWhitespace(
absl::string_view str) {
auto it = std::find_if_not(str.begin(), str.end(), absl::ascii_isspace);
- return str.substr(it - str.begin());
+ return str.substr(static_cast<size_t>(it - str.begin()));
}
// Strips in place whitespace from the beginning of the given string.
-inline void StripLeadingAsciiWhitespace(std::string* str) {
+inline void StripLeadingAsciiWhitespace(absl::Nonnull<std::string*> str) {
auto it = std::find_if_not(str->begin(), str->end(), absl::ascii_isspace);
str->erase(str->begin(), it);
}
@@ -211,13 +213,13 @@
ABSL_MUST_USE_RESULT inline absl::string_view StripTrailingAsciiWhitespace(
absl::string_view str) {
auto it = std::find_if_not(str.rbegin(), str.rend(), absl::ascii_isspace);
- return str.substr(0, str.rend() - it);
+ return str.substr(0, static_cast<size_t>(str.rend() - it));
}
// Strips in place whitespace from the end of the given string
-inline void StripTrailingAsciiWhitespace(std::string* str) {
+inline void StripTrailingAsciiWhitespace(absl::Nonnull<std::string*> str) {
auto it = std::find_if_not(str->rbegin(), str->rend(), absl::ascii_isspace);
- str->erase(str->rend() - it);
+ str->erase(static_cast<size_t>(str->rend() - it));
}
// Returns absl::string_view with whitespace stripped from both ends of the
@@ -228,13 +230,13 @@
}
// Strips in place whitespace from both ends of the given string
-inline void StripAsciiWhitespace(std::string* str) {
+inline void StripAsciiWhitespace(absl::Nonnull<std::string*> str) {
StripTrailingAsciiWhitespace(str);
StripLeadingAsciiWhitespace(str);
}
// Removes leading, trailing, and consecutive internal whitespace.
-void RemoveExtraAsciiWhitespace(std::string*);
+void RemoveExtraAsciiWhitespace(absl::Nonnull<std::string*> str);
ABSL_NAMESPACE_END
} // namespace absl
diff --git a/absl/strings/ascii_benchmark.cc b/absl/strings/ascii_benchmark.cc
index aca458c..4ae7317 100644
--- a/absl/strings/ascii_benchmark.cc
+++ b/absl/strings/ascii_benchmark.cc
@@ -14,7 +14,9 @@
#include "absl/strings/ascii.h"
+#include <algorithm>
#include <cctype>
+#include <cstddef>
#include <string>
#include <array>
#include <random>
@@ -103,18 +105,28 @@
const int size = state.range(0);
std::string s(size, 'X');
for (auto _ : state) {
- benchmark::DoNotOptimize(absl::AsciiStrToLower(s));
+ benchmark::DoNotOptimize(s);
+ std::string res = absl::AsciiStrToLower(s);
+ benchmark::DoNotOptimize(res);
}
}
-BENCHMARK(BM_StrToLower)->Range(1, 1 << 20);
+BENCHMARK(BM_StrToLower)
+ ->DenseRange(0, 32)
+ ->RangeMultiplier(2)
+ ->Range(64, 1 << 26);
static void BM_StrToUpper(benchmark::State& state) {
const int size = state.range(0);
std::string s(size, 'x');
for (auto _ : state) {
- benchmark::DoNotOptimize(absl::AsciiStrToUpper(s));
+ benchmark::DoNotOptimize(s);
+ std::string res = absl::AsciiStrToUpper(s);
+ benchmark::DoNotOptimize(res);
}
}
-BENCHMARK(BM_StrToUpper)->Range(1, 1 << 20);
+BENCHMARK(BM_StrToUpper)
+ ->DenseRange(0, 32)
+ ->RangeMultiplier(2)
+ ->Range(64, 1 << 26);
} // namespace
diff --git a/absl/strings/ascii_test.cc b/absl/strings/ascii_test.cc
index 83af782..117140c 100644
--- a/absl/strings/ascii_test.cc
+++ b/absl/strings/ascii_test.cc
@@ -14,6 +14,7 @@
#include "absl/strings/ascii.h"
+#include <algorithm>
#include <cctype>
#include <clocale>
#include <cstring>
@@ -21,109 +22,105 @@
#include "gtest/gtest.h"
#include "absl/base/macros.h"
-#include "absl/base/port.h"
+#include "absl/strings/string_view.h"
namespace {
TEST(AsciiIsFoo, All) {
for (int i = 0; i < 256; i++) {
- if ((i >= 'a' && i <= 'z') || (i >= 'A' && i <= 'Z'))
- EXPECT_TRUE(absl::ascii_isalpha(i)) << ": failed on " << i;
+ const auto c = static_cast<unsigned char>(i);
+ if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
+ EXPECT_TRUE(absl::ascii_isalpha(c)) << ": failed on " << c;
else
- EXPECT_TRUE(!absl::ascii_isalpha(i)) << ": failed on " << i;
+ EXPECT_TRUE(!absl::ascii_isalpha(c)) << ": failed on " << c;
}
for (int i = 0; i < 256; i++) {
- if ((i >= '0' && i <= '9'))
- EXPECT_TRUE(absl::ascii_isdigit(i)) << ": failed on " << i;
+ const auto c = static_cast<unsigned char>(i);
+ if ((c >= '0' && c <= '9'))
+ EXPECT_TRUE(absl::ascii_isdigit(c)) << ": failed on " << c;
else
- EXPECT_TRUE(!absl::ascii_isdigit(i)) << ": failed on " << i;
+ EXPECT_TRUE(!absl::ascii_isdigit(c)) << ": failed on " << c;
}
for (int i = 0; i < 256; i++) {
- if (absl::ascii_isalpha(i) || absl::ascii_isdigit(i))
- EXPECT_TRUE(absl::ascii_isalnum(i)) << ": failed on " << i;
+ const auto c = static_cast<unsigned char>(i);
+ if (absl::ascii_isalpha(c) || absl::ascii_isdigit(c))
+ EXPECT_TRUE(absl::ascii_isalnum(c)) << ": failed on " << c;
else
- EXPECT_TRUE(!absl::ascii_isalnum(i)) << ": failed on " << i;
+ EXPECT_TRUE(!absl::ascii_isalnum(c)) << ": failed on " << c;
}
for (int i = 0; i < 256; i++) {
+ const auto c = static_cast<unsigned char>(i);
if (i != '\0' && strchr(" \r\n\t\v\f", i))
- EXPECT_TRUE(absl::ascii_isspace(i)) << ": failed on " << i;
+ EXPECT_TRUE(absl::ascii_isspace(c)) << ": failed on " << c;
else
- EXPECT_TRUE(!absl::ascii_isspace(i)) << ": failed on " << i;
+ EXPECT_TRUE(!absl::ascii_isspace(c)) << ": failed on " << c;
}
for (int i = 0; i < 256; i++) {
+ const auto c = static_cast<unsigned char>(i);
if (i >= 32 && i < 127)
- EXPECT_TRUE(absl::ascii_isprint(i)) << ": failed on " << i;
+ EXPECT_TRUE(absl::ascii_isprint(c)) << ": failed on " << c;
else
- EXPECT_TRUE(!absl::ascii_isprint(i)) << ": failed on " << i;
+ EXPECT_TRUE(!absl::ascii_isprint(c)) << ": failed on " << c;
}
for (int i = 0; i < 256; i++) {
- if (absl::ascii_isprint(i) && !absl::ascii_isspace(i) &&
- !absl::ascii_isalnum(i))
- EXPECT_TRUE(absl::ascii_ispunct(i)) << ": failed on " << i;
- else
- EXPECT_TRUE(!absl::ascii_ispunct(i)) << ": failed on " << i;
+ const auto c = static_cast<unsigned char>(i);
+ if (absl::ascii_isprint(c) && !absl::ascii_isspace(c) &&
+ !absl::ascii_isalnum(c)) {
+ EXPECT_TRUE(absl::ascii_ispunct(c)) << ": failed on " << c;
+ } else {
+ EXPECT_TRUE(!absl::ascii_ispunct(c)) << ": failed on " << c;
+ }
}
for (int i = 0; i < 256; i++) {
+ const auto c = static_cast<unsigned char>(i);
if (i == ' ' || i == '\t')
- EXPECT_TRUE(absl::ascii_isblank(i)) << ": failed on " << i;
+ EXPECT_TRUE(absl::ascii_isblank(c)) << ": failed on " << c;
else
- EXPECT_TRUE(!absl::ascii_isblank(i)) << ": failed on " << i;
+ EXPECT_TRUE(!absl::ascii_isblank(c)) << ": failed on " << c;
}
for (int i = 0; i < 256; i++) {
+ const auto c = static_cast<unsigned char>(i);
if (i < 32 || i == 127)
- EXPECT_TRUE(absl::ascii_iscntrl(i)) << ": failed on " << i;
+ EXPECT_TRUE(absl::ascii_iscntrl(c)) << ": failed on " << c;
else
- EXPECT_TRUE(!absl::ascii_iscntrl(i)) << ": failed on " << i;
+ EXPECT_TRUE(!absl::ascii_iscntrl(c)) << ": failed on " << c;
}
for (int i = 0; i < 256; i++) {
- if (absl::ascii_isdigit(i) || (i >= 'A' && i <= 'F') ||
- (i >= 'a' && i <= 'f'))
- EXPECT_TRUE(absl::ascii_isxdigit(i)) << ": failed on " << i;
- else
- EXPECT_TRUE(!absl::ascii_isxdigit(i)) << ": failed on " << i;
+ const auto c = static_cast<unsigned char>(i);
+ if (absl::ascii_isdigit(c) || (i >= 'A' && i <= 'F') ||
+ (i >= 'a' && i <= 'f')) {
+ EXPECT_TRUE(absl::ascii_isxdigit(c)) << ": failed on " << c;
+ } else {
+ EXPECT_TRUE(!absl::ascii_isxdigit(c)) << ": failed on " << c;
+ }
}
for (int i = 0; i < 256; i++) {
+ const auto c = static_cast<unsigned char>(i);
if (i > 32 && i < 127)
- EXPECT_TRUE(absl::ascii_isgraph(i)) << ": failed on " << i;
+ EXPECT_TRUE(absl::ascii_isgraph(c)) << ": failed on " << c;
else
- EXPECT_TRUE(!absl::ascii_isgraph(i)) << ": failed on " << i;
+ EXPECT_TRUE(!absl::ascii_isgraph(c)) << ": failed on " << c;
}
for (int i = 0; i < 256; i++) {
+ const auto c = static_cast<unsigned char>(i);
if (i >= 'A' && i <= 'Z')
- EXPECT_TRUE(absl::ascii_isupper(i)) << ": failed on " << i;
+ EXPECT_TRUE(absl::ascii_isupper(c)) << ": failed on " << c;
else
- EXPECT_TRUE(!absl::ascii_isupper(i)) << ": failed on " << i;
+ EXPECT_TRUE(!absl::ascii_isupper(c)) << ": failed on " << c;
}
for (int i = 0; i < 256; i++) {
+ const auto c = static_cast<unsigned char>(i);
if (i >= 'a' && i <= 'z')
- EXPECT_TRUE(absl::ascii_islower(i)) << ": failed on " << i;
+ EXPECT_TRUE(absl::ascii_islower(c)) << ": failed on " << c;
else
- EXPECT_TRUE(!absl::ascii_islower(i)) << ": failed on " << i;
+ EXPECT_TRUE(!absl::ascii_islower(c)) << ": failed on " << c;
}
- for (int i = 0; i < 128; i++) {
- EXPECT_TRUE(absl::ascii_isascii(i)) << ": failed on " << i;
+ for (unsigned char c = 0; c < 128; c++) {
+ EXPECT_TRUE(absl::ascii_isascii(c)) << ": failed on " << c;
}
for (int i = 128; i < 256; i++) {
- EXPECT_TRUE(!absl::ascii_isascii(i)) << ": failed on " << i;
- }
-
- // The official is* functions don't accept negative signed chars, but
- // our absl::ascii_is* functions do.
- for (int i = 0; i < 256; i++) {
- signed char sc = static_cast<signed char>(static_cast<unsigned char>(i));
- EXPECT_EQ(absl::ascii_isalpha(i), absl::ascii_isalpha(sc)) << i;
- EXPECT_EQ(absl::ascii_isdigit(i), absl::ascii_isdigit(sc)) << i;
- EXPECT_EQ(absl::ascii_isalnum(i), absl::ascii_isalnum(sc)) << i;
- EXPECT_EQ(absl::ascii_isspace(i), absl::ascii_isspace(sc)) << i;
- EXPECT_EQ(absl::ascii_ispunct(i), absl::ascii_ispunct(sc)) << i;
- EXPECT_EQ(absl::ascii_isblank(i), absl::ascii_isblank(sc)) << i;
- EXPECT_EQ(absl::ascii_iscntrl(i), absl::ascii_iscntrl(sc)) << i;
- EXPECT_EQ(absl::ascii_isxdigit(i), absl::ascii_isxdigit(sc)) << i;
- EXPECT_EQ(absl::ascii_isprint(i), absl::ascii_isprint(sc)) << i;
- EXPECT_EQ(absl::ascii_isgraph(i), absl::ascii_isgraph(sc)) << i;
- EXPECT_EQ(absl::ascii_isupper(i), absl::ascii_isupper(sc)) << i;
- EXPECT_EQ(absl::ascii_islower(i), absl::ascii_islower(sc)) << i;
- EXPECT_EQ(absl::ascii_isascii(i), absl::ascii_isascii(sc)) << i;
+ const auto c = static_cast<unsigned char>(i);
+ EXPECT_TRUE(!absl::ascii_isascii(c)) << ": failed on " << c;
}
}
@@ -137,19 +134,20 @@
#endif
for (int i = 0; i < 256; i++) {
- EXPECT_EQ(isalpha(i) != 0, absl::ascii_isalpha(i)) << i;
- EXPECT_EQ(isdigit(i) != 0, absl::ascii_isdigit(i)) << i;
- EXPECT_EQ(isalnum(i) != 0, absl::ascii_isalnum(i)) << i;
- EXPECT_EQ(isspace(i) != 0, absl::ascii_isspace(i)) << i;
- EXPECT_EQ(ispunct(i) != 0, absl::ascii_ispunct(i)) << i;
- EXPECT_EQ(isblank(i) != 0, absl::ascii_isblank(i)) << i;
- EXPECT_EQ(iscntrl(i) != 0, absl::ascii_iscntrl(i)) << i;
- EXPECT_EQ(isxdigit(i) != 0, absl::ascii_isxdigit(i)) << i;
- EXPECT_EQ(isprint(i) != 0, absl::ascii_isprint(i)) << i;
- EXPECT_EQ(isgraph(i) != 0, absl::ascii_isgraph(i)) << i;
- EXPECT_EQ(isupper(i) != 0, absl::ascii_isupper(i)) << i;
- EXPECT_EQ(islower(i) != 0, absl::ascii_islower(i)) << i;
- EXPECT_EQ(isascii(i) != 0, absl::ascii_isascii(i)) << i;
+ const auto c = static_cast<unsigned char>(i);
+ EXPECT_EQ(isalpha(c) != 0, absl::ascii_isalpha(c)) << c;
+ EXPECT_EQ(isdigit(c) != 0, absl::ascii_isdigit(c)) << c;
+ EXPECT_EQ(isalnum(c) != 0, absl::ascii_isalnum(c)) << c;
+ EXPECT_EQ(isspace(c) != 0, absl::ascii_isspace(c)) << c;
+ EXPECT_EQ(ispunct(c) != 0, absl::ascii_ispunct(c)) << c;
+ EXPECT_EQ(isblank(c) != 0, absl::ascii_isblank(c)) << c;
+ EXPECT_EQ(iscntrl(c) != 0, absl::ascii_iscntrl(c)) << c;
+ EXPECT_EQ(isxdigit(c) != 0, absl::ascii_isxdigit(c)) << c;
+ EXPECT_EQ(isprint(c) != 0, absl::ascii_isprint(c)) << c;
+ EXPECT_EQ(isgraph(c) != 0, absl::ascii_isgraph(c)) << c;
+ EXPECT_EQ(isupper(c) != 0, absl::ascii_isupper(c)) << c;
+ EXPECT_EQ(islower(c) != 0, absl::ascii_islower(c)) << c;
+ EXPECT_EQ(isascii(c) != 0, absl::ascii_isascii(c)) << c;
}
#ifndef __ANDROID__
@@ -166,25 +164,20 @@
#endif
for (int i = 0; i < 256; i++) {
- if (absl::ascii_islower(i))
- EXPECT_EQ(absl::ascii_toupper(i), 'A' + (i - 'a')) << i;
+ const auto c = static_cast<unsigned char>(i);
+ if (absl::ascii_islower(c))
+ EXPECT_EQ(absl::ascii_toupper(c), 'A' + (i - 'a')) << c;
else
- EXPECT_EQ(absl::ascii_toupper(i), static_cast<char>(i)) << i;
+ EXPECT_EQ(absl::ascii_toupper(c), static_cast<char>(i)) << c;
- if (absl::ascii_isupper(i))
- EXPECT_EQ(absl::ascii_tolower(i), 'a' + (i - 'A')) << i;
+ if (absl::ascii_isupper(c))
+ EXPECT_EQ(absl::ascii_tolower(c), 'a' + (i - 'A')) << c;
else
- EXPECT_EQ(absl::ascii_tolower(i), static_cast<char>(i)) << i;
+ EXPECT_EQ(absl::ascii_tolower(c), static_cast<char>(i)) << c;
// These CHECKs only hold in a C locale.
- EXPECT_EQ(static_cast<char>(tolower(i)), absl::ascii_tolower(i)) << i;
- EXPECT_EQ(static_cast<char>(toupper(i)), absl::ascii_toupper(i)) << i;
-
- // The official to* functions don't accept negative signed chars, but
- // our absl::ascii_to* functions do.
- signed char sc = static_cast<signed char>(static_cast<unsigned char>(i));
- EXPECT_EQ(absl::ascii_tolower(i), absl::ascii_tolower(sc)) << i;
- EXPECT_EQ(absl::ascii_toupper(i), absl::ascii_toupper(sc)) << i;
+ EXPECT_EQ(static_cast<char>(tolower(i)), absl::ascii_tolower(c)) << c;
+ EXPECT_EQ(static_cast<char>(toupper(i)), absl::ascii_toupper(c)) << c;
}
#ifndef __ANDROID__
// restore the old locale.
@@ -197,14 +190,14 @@
const std::string str("GHIJKL");
const std::string str2("MNOPQR");
const absl::string_view sp(str2);
- std::string mutable_str("STUVWX");
+ std::string mutable_str("_`?@[{AMNOPQRSTUVWXYZ");
EXPECT_EQ("abcdef", absl::AsciiStrToLower(buf));
EXPECT_EQ("ghijkl", absl::AsciiStrToLower(str));
EXPECT_EQ("mnopqr", absl::AsciiStrToLower(sp));
absl::AsciiStrToLower(&mutable_str);
- EXPECT_EQ("stuvwx", mutable_str);
+ EXPECT_EQ("_`?@[{amnopqrstuvwxyz", mutable_str);
char mutable_buf[] = "Mutable";
std::transform(mutable_buf, mutable_buf + strlen(mutable_buf),
@@ -215,12 +208,12 @@
TEST(AsciiStrTo, Upper) {
const char buf[] = "abcdef";
const std::string str("ghijkl");
- const std::string str2("mnopqr");
+ const std::string str2("_`?@[{amnopqrstuvwxyz");
const absl::string_view sp(str2);
EXPECT_EQ("ABCDEF", absl::AsciiStrToUpper(buf));
EXPECT_EQ("GHIJKL", absl::AsciiStrToUpper(str));
- EXPECT_EQ("MNOPQR", absl::AsciiStrToUpper(sp));
+ EXPECT_EQ("_`?@[{AMNOPQRSTUVWXYZ", absl::AsciiStrToUpper(sp));
char mutable_buf[] = "Mutable";
std::transform(mutable_buf, mutable_buf + strlen(mutable_buf),
diff --git a/absl/strings/atod_manual_test.cc b/absl/strings/atod_manual_test.cc
new file mode 100644
index 0000000..6cf28b0
--- /dev/null
+++ b/absl/strings/atod_manual_test.cc
@@ -0,0 +1,193 @@
+// Copyright 2022 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// This program tests the absl::SimpleAtod and absl::SimpleAtof functions. Run
+// it as "atod_manual_test pnftd/data/*.txt" where the pnftd directory is a
+// local checkout of the https://github.com/nigeltao/parse-number-fxx-test-data
+// repository. The test suite lives in a separate repository because its more
+// than 5 million test cases weigh over several hundred megabytes and because
+// the test cases are also useful to other software projects, not just Abseil.
+// Its data/*.txt files contain one test case per line, like:
+//
+// 3C00 3F800000 3FF0000000000000 1
+// 3D00 3FA00000 3FF4000000000000 1.25
+// 3D9A 3FB33333 3FF6666666666666 1.4
+// 57B7 42F6E979 405EDD2F1A9FBE77 123.456
+// 622A 44454000 4088A80000000000 789
+// 7C00 7F800000 7FF0000000000000 123.456e789
+//
+// For each line (and using 0-based column indexes), columns [5..13] and
+// [14..30] contain the 32-bit float and 64-bit double result of parsing
+// columns [31..].
+//
+// For example, parsing "1.4" as a float gives the bits 0x3FB33333.
+//
+// In this 6-line example, the final line's float and double values are all
+// infinity. The largest finite float and double values are approximately
+// 3.40e+38 and 1.80e+308.
+
+#include <cstdint>
+#include <cstdio>
+#include <string>
+
+#include "absl/base/casts.h"
+#include "absl/strings/numbers.h"
+#include "absl/strings/str_format.h"
+#include "absl/strings/string_view.h"
+#include "absl/types/optional.h"
+
+static constexpr uint8_t kUnhex[256] = {
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, //
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, //
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, //
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, //
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, //
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, //
+ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, // '0' ..= '7'
+ 0x8, 0x9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // '8' ..= '9'
+
+ 0x0, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0x0, // 'A' ..= 'F'
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, //
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, //
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, //
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, //
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, //
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, //
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, //
+
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, //
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, //
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, //
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, //
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, //
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, //
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, //
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, //
+
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, //
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, //
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, //
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, //
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, //
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, //
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, //
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, //
+};
+
+static absl::optional<std::string> ReadFileToString(const char* filename) {
+ FILE* f = fopen(filename, "rb");
+ if (!f) {
+ return absl::nullopt;
+ }
+ fseek(f, 0, SEEK_END);
+ size_t size = ftell(f);
+ fseek(f, 0, SEEK_SET);
+ std::string s(size, '\x00');
+ size_t n = fread(&s[0], 1, size, f);
+ fclose(f);
+ if (n != size) {
+ return absl::nullopt;
+ }
+ return s;
+}
+
+static bool ProcessOneTestFile(const char* filename) {
+ absl::optional<std::string> contents = ReadFileToString(filename);
+ if (!contents) {
+ absl::FPrintF(stderr, "Invalid file: %s\n", filename);
+ return false;
+ }
+
+ int num_cases = 0;
+ for (absl::string_view v(*contents); !v.empty();) {
+ size_t new_line = v.find('\n');
+ if ((new_line == absl::string_view::npos) || (new_line < 32)) {
+ break;
+ }
+ absl::string_view input = v.substr(31, new_line - 31);
+
+ // Test absl::SimpleAtof.
+ {
+ float f;
+ if (!absl::SimpleAtof(input, &f)) {
+ absl::FPrintF(stderr, "Could not parse \"%s\" in %s\n", input,
+ filename);
+ return false;
+ }
+ uint32_t have32 = absl::bit_cast<uint32_t>(f);
+
+ uint32_t want32 = 0;
+ for (int i = 0; i < 8; i++) {
+ want32 = (want32 << 4) | kUnhex[static_cast<unsigned char>(v[5 + i])];
+ }
+
+ if (have32 != want32) {
+ absl::FPrintF(stderr,
+ "absl::SimpleAtof failed parsing \"%s\" in %s\n have "
+ "%08X\n want %08X\n",
+ input, filename, have32, want32);
+ return false;
+ }
+ }
+
+ // Test absl::SimpleAtod.
+ {
+ double d;
+ if (!absl::SimpleAtod(input, &d)) {
+ absl::FPrintF(stderr, "Could not parse \"%s\" in %s\n", input,
+ filename);
+ return false;
+ }
+ uint64_t have64 = absl::bit_cast<uint64_t>(d);
+
+ uint64_t want64 = 0;
+ for (int i = 0; i < 16; i++) {
+ want64 = (want64 << 4) | kUnhex[static_cast<unsigned char>(v[14 + i])];
+ }
+
+ if (have64 != want64) {
+ absl::FPrintF(stderr,
+ "absl::SimpleAtod failed parsing \"%s\" in %s\n have "
+ "%016X\n want %016X\n",
+ input, filename, have64, want64);
+ return false;
+ }
+ }
+
+ num_cases++;
+ v = v.substr(new_line + 1);
+ }
+ printf("%8d OK in %s\n", num_cases, filename);
+ return true;
+}
+
+int main(int argc, char** argv) {
+ if (argc < 2) {
+ absl::FPrintF(
+ stderr,
+ "Usage: %s pnftd/data/*.txt\nwhere the pnftd directory is a local "
+ "checkout of "
+ "the\nhttps://github.com/nigeltao/parse-number-fxx-test-data "
+ "repository.\n",
+ argv[0]);
+ return 1;
+ }
+
+ for (int i = 1; i < argc; i++) {
+ if (!ProcessOneTestFile(argv[i])) {
+ return 1;
+ }
+ }
+ return 0;
+}
diff --git a/absl/strings/char_formatting_test.cc b/absl/strings/char_formatting_test.cc
new file mode 100644
index 0000000..1692da7
--- /dev/null
+++ b/absl/strings/char_formatting_test.cc
@@ -0,0 +1,169 @@
+// Copyright 2023 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <cstddef>
+
+#include "gtest/gtest.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/str_format.h"
+#include "absl/strings/substitute.h"
+
+namespace {
+
+TEST(CharFormatting, Char) {
+ const char v = 'A';
+
+ // Desired behavior: does not compile:
+ // EXPECT_EQ(absl::StrCat(v, "B"), "AB");
+ // EXPECT_EQ(absl::StrFormat("%vB", v), "AB");
+
+ // Legacy behavior: format as char:
+ EXPECT_EQ(absl::Substitute("$0B", v), "AB");
+}
+
+enum CharEnum : char {};
+TEST(CharFormatting, CharEnum) {
+ auto v = static_cast<CharEnum>('A');
+
+ // Desired behavior: format as decimal
+ EXPECT_EQ(absl::StrFormat("%vB", v), "65B");
+ EXPECT_EQ(absl::StrCat(v, "B"), "65B");
+
+ // Legacy behavior: format as character:
+
+ // Some older versions of gcc behave differently in this one case
+#if !defined(__GNUC__) || defined(__clang__)
+ EXPECT_EQ(absl::Substitute("$0B", v), "AB");
+#endif
+}
+
+enum class CharEnumClass: char {};
+TEST(CharFormatting, CharEnumClass) {
+ auto v = static_cast<CharEnumClass>('A');
+
+ // Desired behavior: format as decimal:
+ EXPECT_EQ(absl::StrFormat("%vB", v), "65B");
+ EXPECT_EQ(absl::StrCat(v, "B"), "65B");
+
+ // Legacy behavior: format as character:
+ EXPECT_EQ(absl::Substitute("$0B", v), "AB");
+}
+
+TEST(CharFormatting, UnsignedChar) {
+ const unsigned char v = 'A';
+
+ // Desired behavior: format as decimal:
+ EXPECT_EQ(absl::StrCat(v, "B"), "65B");
+ EXPECT_EQ(absl::Substitute("$0B", v), "65B");
+ EXPECT_EQ(absl::StrFormat("%vB", v), "65B");
+
+ // Signedness check
+ const unsigned char w = 255;
+ EXPECT_EQ(absl::StrCat(w, "B"), "255B");
+ EXPECT_EQ(absl::Substitute("$0B", w), "255B");
+ // EXPECT_EQ(absl::StrFormat("%vB", v), "255B");
+}
+
+TEST(CharFormatting, SignedChar) {
+ const signed char v = 'A';
+
+ // Desired behavior: format as decimal:
+ EXPECT_EQ(absl::StrCat(v, "B"), "65B");
+ EXPECT_EQ(absl::Substitute("$0B", v), "65B");
+ EXPECT_EQ(absl::StrFormat("%vB", v), "65B");
+
+ // Signedness check
+ const signed char w = -128;
+ EXPECT_EQ(absl::StrCat(w, "B"), "-128B");
+ EXPECT_EQ(absl::Substitute("$0B", w), "-128B");
+}
+
+enum UnsignedCharEnum : unsigned char {};
+TEST(CharFormatting, UnsignedCharEnum) {
+ auto v = static_cast<UnsignedCharEnum>('A');
+
+ // Desired behavior: format as decimal:
+ EXPECT_EQ(absl::StrCat(v, "B"), "65B");
+ EXPECT_EQ(absl::Substitute("$0B", v), "65B");
+ EXPECT_EQ(absl::StrFormat("%vB", v), "65B");
+
+ // Signedness check
+ auto w = static_cast<UnsignedCharEnum>(255);
+ EXPECT_EQ(absl::StrCat(w, "B"), "255B");
+ EXPECT_EQ(absl::Substitute("$0B", w), "255B");
+ EXPECT_EQ(absl::StrFormat("%vB", w), "255B");
+}
+
+enum SignedCharEnum : signed char {};
+TEST(CharFormatting, SignedCharEnum) {
+ auto v = static_cast<SignedCharEnum>('A');
+
+ // Desired behavior: format as decimal:
+ EXPECT_EQ(absl::StrCat(v, "B"), "65B");
+ EXPECT_EQ(absl::Substitute("$0B", v), "65B");
+ EXPECT_EQ(absl::StrFormat("%vB", v), "65B");
+
+ // Signedness check
+ auto w = static_cast<SignedCharEnum>(-128);
+ EXPECT_EQ(absl::StrCat(w, "B"), "-128B");
+ EXPECT_EQ(absl::Substitute("$0B", w), "-128B");
+ EXPECT_EQ(absl::StrFormat("%vB", w), "-128B");
+}
+
+enum class UnsignedCharEnumClass : unsigned char {};
+TEST(CharFormatting, UnsignedCharEnumClass) {
+ auto v = static_cast<UnsignedCharEnumClass>('A');
+
+ // Desired behavior: format as decimal:
+ EXPECT_EQ(absl::StrCat(v, "B"), "65B");
+ EXPECT_EQ(absl::Substitute("$0B", v), "65B");
+ EXPECT_EQ(absl::StrFormat("%vB", v), "65B");
+
+ // Signedness check
+ auto w = static_cast<UnsignedCharEnumClass>(255);
+ EXPECT_EQ(absl::StrCat(w, "B"), "255B");
+ EXPECT_EQ(absl::Substitute("$0B", w), "255B");
+ EXPECT_EQ(absl::StrFormat("%vB", w), "255B");
+}
+
+enum SignedCharEnumClass : signed char {};
+TEST(CharFormatting, SignedCharEnumClass) {
+ auto v = static_cast<SignedCharEnumClass>('A');
+
+ // Desired behavior: format as decimal:
+ EXPECT_EQ(absl::StrCat(v, "B"), "65B");
+ EXPECT_EQ(absl::Substitute("$0B", v), "65B");
+ EXPECT_EQ(absl::StrFormat("%vB", v), "65B");
+
+ // Signedness check
+ auto w = static_cast<SignedCharEnumClass>(-128);
+ EXPECT_EQ(absl::StrCat(w, "B"), "-128B");
+ EXPECT_EQ(absl::Substitute("$0B", w), "-128B");
+ EXPECT_EQ(absl::StrFormat("%vB", w), "-128B");
+}
+
+#ifdef __cpp_lib_byte
+TEST(CharFormatting, StdByte) {
+ auto v = static_cast<std::byte>('A');
+ // Desired behavior: format as 0xff
+ // (No APIs do this today.)
+
+ // Legacy behavior: format as decimal:
+ EXPECT_EQ(absl::StrCat(v, "B"), "65B");
+ EXPECT_EQ(absl::Substitute("$0B", v), "65B");
+ EXPECT_EQ(absl::StrFormat("%vB", v), "65B");
+}
+#endif // _cpp_lib_byte
+
+} // namespace
diff --git a/absl/strings/charconv.cc b/absl/strings/charconv.cc
index fefcfc9..0c9227f 100644
--- a/absl/strings/charconv.cc
+++ b/absl/strings/charconv.cc
@@ -16,10 +16,14 @@
#include <algorithm>
#include <cassert>
-#include <cmath>
-#include <cstring>
+#include <cstddef>
+#include <cstdint>
+#include <limits>
+#include <system_error> // NOLINT(build/c++11)
#include "absl/base/casts.h"
+#include "absl/base/config.h"
+#include "absl/base/nullability.h"
#include "absl/numeric/bits.h"
#include "absl/numeric/int128.h"
#include "absl/strings/internal/charconv_bigint.h"
@@ -65,6 +69,14 @@
template <>
struct FloatTraits<double> {
+ using mantissa_t = uint64_t;
+
+ // The number of bits in the given float type.
+ static constexpr int kTargetBits = 64;
+
+ // The number of exponent bits in the given float type.
+ static constexpr int kTargetExponentBits = 11;
+
// The number of mantissa bits in the given float type. This includes the
// implied high bit.
static constexpr int kTargetMantissaBits = 53;
@@ -83,11 +95,43 @@
// m * 2**kMinNormalExponent is exactly equal to DBL_MIN.
static constexpr int kMinNormalExponent = -1074;
- static double MakeNan(const char* tagp) {
+ // The IEEE exponent bias. It equals ((1 << (kTargetExponentBits - 1)) - 1).
+ static constexpr int kExponentBias = 1023;
+
+ // The Eisel-Lemire "Shifting to 54/25 Bits" adjustment. It equals (63 - 1 -
+ // kTargetMantissaBits).
+ static constexpr int kEiselLemireShift = 9;
+
+ // The Eisel-Lemire high64_mask. It equals ((1 << kEiselLemireShift) - 1).
+ static constexpr uint64_t kEiselLemireMask = uint64_t{0x1FF};
+
+ // The smallest negative integer N (smallest negative means furthest from
+ // zero) such that parsing 9999999999999999999eN, with 19 nines, is still
+ // positive. Parsing a smaller (more negative) N will produce zero.
+ //
+ // Adjusting the decimal point and exponent, without adjusting the value,
+ // 9999999999999999999eN equals 9.999999999999999999eM where M = N + 18.
+ //
+ // 9999999999999999999, with 19 nines but no decimal point, is the largest
+ // "repeated nines" integer that fits in a uint64_t.
+ static constexpr int kEiselLemireMinInclusiveExp10 = -324 - 18;
+
+ // The smallest positive integer N such that parsing 1eN produces infinity.
+ // Parsing a smaller N will produce something finite.
+ static constexpr int kEiselLemireMaxExclusiveExp10 = 309;
+
+ static double MakeNan(absl::Nonnull<const char*> tagp) {
+#if ABSL_HAVE_BUILTIN(__builtin_nan)
+ // Use __builtin_nan() if available since it has a fix for
+ // https://bugs.llvm.org/show_bug.cgi?id=37778
+ // std::nan may use the glibc implementation.
+ return __builtin_nan(tagp);
+#else
// Support nan no matter which namespace it's in. Some platforms
// incorrectly don't put it in namespace std.
using namespace std; // NOLINT
return nan(tagp);
+#endif
}
// Builds a nonzero floating point number out of the provided parts.
@@ -103,7 +147,7 @@
// a normal value is made, or it must be less narrow than that, in which case
// `exponent` must be exactly kMinNormalExponent, and a subnormal value is
// made.
- static double Make(uint64_t mantissa, int exponent, bool sign) {
+ static double Make(mantissa_t mantissa, int exponent, bool sign) {
#ifndef ABSL_BIT_PACK_FLOATS
// Support ldexp no matter which namespace it's in. Some platforms
// incorrectly don't put it in namespace std.
@@ -116,8 +160,10 @@
if (mantissa > kMantissaMask) {
// Normal value.
// Adjust by 1023 for the exponent representation bias, and an additional
- // 52 due to the implied decimal point in the IEEE mantissa represenation.
- dbl += uint64_t{exponent + 1023u + kTargetMantissaBits - 1} << 52;
+ // 52 due to the implied decimal point in the IEEE mantissa
+ // representation.
+ dbl += static_cast<uint64_t>(exponent + 1023 + kTargetMantissaBits - 1)
+ << 52;
mantissa &= kMantissaMask;
} else {
// subnormal value
@@ -134,16 +180,34 @@
// members and methods.
template <>
struct FloatTraits<float> {
+ using mantissa_t = uint32_t;
+
+ static constexpr int kTargetBits = 32;
+ static constexpr int kTargetExponentBits = 8;
static constexpr int kTargetMantissaBits = 24;
static constexpr int kMaxExponent = 104;
static constexpr int kMinNormalExponent = -149;
- static float MakeNan(const char* tagp) {
+ static constexpr int kExponentBias = 127;
+ static constexpr int kEiselLemireShift = 38;
+ static constexpr uint64_t kEiselLemireMask = uint64_t{0x3FFFFFFFFF};
+ static constexpr int kEiselLemireMinInclusiveExp10 = -46 - 18;
+ static constexpr int kEiselLemireMaxExclusiveExp10 = 39;
+
+ static float MakeNan(absl::Nonnull<const char*> tagp) {
+#if ABSL_HAVE_BUILTIN(__builtin_nanf)
+ // Use __builtin_nanf() if available since it has a fix for
+ // https://bugs.llvm.org/show_bug.cgi?id=37778
+ // std::nanf may use the glibc implementation.
+ return __builtin_nanf(tagp);
+#else
// Support nanf no matter which namespace it's in. Some platforms
// incorrectly don't put it in namespace std.
using namespace std; // NOLINT
- return nanf(tagp);
+ return std::nanf(tagp);
+#endif
}
- static float Make(uint32_t mantissa, int exponent, bool sign) {
+
+ static float Make(mantissa_t mantissa, int exponent, bool sign) {
#ifndef ABSL_BIT_PACK_FLOATS
// Support ldexpf no matter which namespace it's in. Some platforms
// incorrectly don't put it in namespace std.
@@ -156,8 +220,10 @@
if (mantissa > kMantissaMask) {
// Normal value.
// Adjust by 127 for the exponent representation bias, and an additional
- // 23 due to the implied decimal point in the IEEE mantissa represenation.
- flt += uint32_t{exponent + 127u + kTargetMantissaBits - 1} << 23;
+ // 23 due to the implied decimal point in the IEEE mantissa
+ // representation.
+ flt += static_cast<uint32_t>(exponent + 127 + kTargetMantissaBits - 1)
+ << 23;
mantissa &= kMantissaMask;
} else {
// subnormal value
@@ -181,39 +247,45 @@
//
// 2**63 <= Power10Mantissa(n) < 2**64.
//
+// See the "Table of powers of 10" comment below for a "1e60" example.
+//
// Lookups into the power-of-10 table must first check the Power10Overflow() and
// Power10Underflow() functions, to avoid out-of-bounds table access.
//
-// Indexes into these tables are biased by -kPower10TableMin, and the table has
-// values in the range [kPower10TableMin, kPower10TableMax].
-extern const uint64_t kPower10MantissaTable[];
-extern const int16_t kPower10ExponentTable[];
+// Indexes into these tables are biased by -kPower10TableMinInclusive. Valid
+// indexes range from kPower10TableMinInclusive to kPower10TableMaxExclusive.
+extern const uint64_t kPower10MantissaHighTable[]; // High 64 of 128 bits.
+extern const uint64_t kPower10MantissaLowTable[]; // Low 64 of 128 bits.
-// The smallest allowed value for use with the Power10Mantissa() and
-// Power10Exponent() functions below. (If a smaller exponent is needed in
+// The smallest (inclusive) allowed value for use with the Power10Mantissa()
+// and Power10Exponent() functions below. (If a smaller exponent is needed in
// calculations, the end result is guaranteed to underflow.)
-constexpr int kPower10TableMin = -342;
+constexpr int kPower10TableMinInclusive = -342;
-// The largest allowed value for use with the Power10Mantissa() and
-// Power10Exponent() functions below. (If a smaller exponent is needed in
-// calculations, the end result is guaranteed to overflow.)
-constexpr int kPower10TableMax = 308;
+// The largest (exclusive) allowed value for use with the Power10Mantissa() and
+// Power10Exponent() functions below. (If a larger-or-equal exponent is needed
+// in calculations, the end result is guaranteed to overflow.)
+constexpr int kPower10TableMaxExclusive = 309;
uint64_t Power10Mantissa(int n) {
- return kPower10MantissaTable[n - kPower10TableMin];
+ return kPower10MantissaHighTable[n - kPower10TableMinInclusive];
}
int Power10Exponent(int n) {
- return kPower10ExponentTable[n - kPower10TableMin];
+ // The 217706 etc magic numbers encode the results as a formula instead of a
+ // table. Their equivalence (over the kPower10TableMinInclusive ..
+ // kPower10TableMaxExclusive range) is confirmed by
+ // https://github.com/google/wuffs/blob/315b2e52625ebd7b02d8fac13e3cd85ea374fb80/script/print-mpb-powers-of-10.go
+ return (217706 * n >> 16) - 63;
}
// Returns true if n is large enough that 10**n always results in an IEEE
// overflow.
-bool Power10Overflow(int n) { return n > kPower10TableMax; }
+bool Power10Overflow(int n) { return n >= kPower10TableMaxExclusive; }
// Returns true if n is small enough that 10**n times a ParsedFloat mantissa
// always results in an IEEE underflow.
-bool Power10Underflow(int n) { return n < kPower10TableMin; }
+bool Power10Underflow(int n) { return n < kPower10TableMinInclusive; }
// Returns true if Power10Mantissa(n) * 2**Power10Exponent(n) is exactly equal
// to 10**n numerically. Put another way, this returns true if there is no
@@ -242,9 +314,11 @@
// Returns the bit width of the given uint128. (Equivalently, returns 128
// minus the number of leading zero bits.)
-unsigned BitWidth(uint128 value) {
+int BitWidth(uint128 value) {
if (Uint128High64(value) == 0) {
- return static_cast<unsigned>(bit_width(Uint128Low64(value)));
+ // This static_cast is only needed when using a std::bit_width()
+ // implementation that does not have the fix for LWG 3656 applied.
+ return static_cast<int>(bit_width(Uint128Low64(value)));
}
return 128 - countl_zero(Uint128High64(value));
}
@@ -271,7 +345,7 @@
// `value` must be wider than the requested bit width.
//
// Returns the number of bits shifted.
-int TruncateToBitWidth(int bit_width, uint128* value) {
+int TruncateToBitWidth(int bit_width, absl::Nonnull<uint128*> value) {
const int current_bit_width = BitWidth(*value);
const int shift = current_bit_width - bit_width;
*value >>= shift;
@@ -283,16 +357,22 @@
// the appropriate double, and returns true.
template <typename FloatType>
bool HandleEdgeCase(const strings_internal::ParsedFloat& input, bool negative,
- FloatType* value) {
+ absl::Nonnull<FloatType*> value) {
if (input.type == strings_internal::FloatType::kNan) {
- // A bug in both clang and gcc would cause the compiler to optimize away the
- // buffer we are building below. Declaring the buffer volatile avoids the
- // issue, and has no measurable performance impact in microbenchmarks.
+ // A bug in both clang < 7 and gcc would cause the compiler to optimize
+ // away the buffer we are building below. Declaring the buffer volatile
+ // avoids the issue, and has no measurable performance impact in
+ // microbenchmarks.
//
// https://bugs.llvm.org/show_bug.cgi?id=37778
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=86113
constexpr ptrdiff_t kNanBufferSize = 128;
+#if (defined(__GNUC__) && !defined(__clang__)) || \
+ (defined(__clang__) && __clang_major__ < 7)
volatile char n_char_sequence[kNanBufferSize];
+#else
+ char n_char_sequence[kNanBufferSize];
+#endif
if (input.subrange_begin == nullptr) {
n_char_sequence[0] = '\0';
} else {
@@ -326,7 +406,8 @@
// number is stored in *value.
template <typename FloatType>
void EncodeResult(const CalculatedFloat& calculated, bool negative,
- absl::from_chars_result* result, FloatType* value) {
+ absl::Nonnull<absl::from_chars_result*> result,
+ absl::Nonnull<FloatType*> value) {
if (calculated.exponent == kOverflow) {
result->ec = std::errc::result_out_of_range;
*value = negative ? -std::numeric_limits<FloatType>::max()
@@ -337,8 +418,10 @@
*value = negative ? -0.0 : 0.0;
return;
}
- *value = FloatTraits<FloatType>::Make(calculated.mantissa,
- calculated.exponent, negative);
+ *value = FloatTraits<FloatType>::Make(
+ static_cast<typename FloatTraits<FloatType>::mantissa_t>(
+ calculated.mantissa),
+ calculated.exponent, negative);
}
// Returns the given uint128 shifted to the right by `shift` bits, and rounds
@@ -370,7 +453,7 @@
// Zero and negative values of `shift` are accepted, in which case the word is
// shifted left, as necessary.
uint64_t ShiftRightAndRound(uint128 value, int shift, bool input_exact,
- bool* output_exact) {
+ absl::Nonnull<bool*> output_exact) {
if (shift <= 0) {
*output_exact = input_exact;
return static_cast<uint64_t>(value << -shift);
@@ -399,7 +482,7 @@
// the low bit of `value` is set.
//
// In inexact mode, the nonzero error means the actual value is greater
- // than the halfway point and we must alway round up.
+ // than the halfway point and we must always round up.
if ((value & 1) == 1 || !input_exact) {
++value;
}
@@ -519,7 +602,9 @@
const strings_internal::ParsedFloat& parsed_hex) {
uint64_t mantissa = parsed_hex.mantissa;
int exponent = parsed_hex.exponent;
- auto mantissa_width = static_cast<unsigned>(bit_width(mantissa));
+ // This static_cast is only needed when using a std::bit_width()
+ // implementation that does not have the fix for LWG 3656 applied.
+ int mantissa_width = static_cast<int>(bit_width(mantissa));
const int shift = NormalizedShiftSize<FloatType>(mantissa_width, exponent);
bool result_exact;
exponent += shift;
@@ -595,8 +680,189 @@
binary_exponent);
}
+// As discussed in https://nigeltao.github.io/blog/2020/eisel-lemire.html the
+// primary goal of the Eisel-Lemire algorithm is speed, for 99+% of the cases,
+// not 100% coverage. As long as Eisel-Lemire doesn’t claim false positives,
+// the combined approach (falling back to an alternative implementation when
+// this function returns false) is both fast and correct.
template <typename FloatType>
-from_chars_result FromCharsImpl(const char* first, const char* last,
+bool EiselLemire(const strings_internal::ParsedFloat& input, bool negative,
+ absl::Nonnull<FloatType*> value,
+ absl::Nonnull<std::errc*> ec) {
+ uint64_t man = input.mantissa;
+ int exp10 = input.exponent;
+ if (exp10 < FloatTraits<FloatType>::kEiselLemireMinInclusiveExp10) {
+ *value = negative ? -0.0 : 0.0;
+ *ec = std::errc::result_out_of_range;
+ return true;
+ } else if (exp10 >= FloatTraits<FloatType>::kEiselLemireMaxExclusiveExp10) {
+ // Return max (a finite value) consistent with from_chars and DR 3081. For
+ // SimpleAtod and SimpleAtof, post-processing will return infinity.
+ *value = negative ? -std::numeric_limits<FloatType>::max()
+ : std::numeric_limits<FloatType>::max();
+ *ec = std::errc::result_out_of_range;
+ return true;
+ }
+
+ // Assert kPower10TableMinInclusive <= exp10 < kPower10TableMaxExclusive.
+ // Equivalently, !Power10Underflow(exp10) and !Power10Overflow(exp10).
+ static_assert(
+ FloatTraits<FloatType>::kEiselLemireMinInclusiveExp10 >=
+ kPower10TableMinInclusive,
+ "(exp10-kPower10TableMinInclusive) in kPower10MantissaHighTable bounds");
+ static_assert(
+ FloatTraits<FloatType>::kEiselLemireMaxExclusiveExp10 <=
+ kPower10TableMaxExclusive,
+ "(exp10-kPower10TableMinInclusive) in kPower10MantissaHighTable bounds");
+
+ // The terse (+) comments in this function body refer to sections of the
+ // https://nigeltao.github.io/blog/2020/eisel-lemire.html blog post.
+ //
+ // That blog post discusses double precision (11 exponent bits with a -1023
+ // bias, 52 mantissa bits), but the same approach applies to single precision
+ // (8 exponent bits with a -127 bias, 23 mantissa bits). Either way, the
+ // computation here happens with 64-bit values (e.g. man) or 128-bit values
+ // (e.g. x) before finally converting to 64- or 32-bit floating point.
+ //
+ // See also "Number Parsing at a Gigabyte per Second, Software: Practice and
+ // Experience 51 (8), 2021" (https://arxiv.org/abs/2101.11408) for detail.
+
+ // (+) Normalization.
+ int clz = countl_zero(man);
+ man <<= static_cast<unsigned int>(clz);
+ // The 217706 etc magic numbers are from the Power10Exponent function.
+ uint64_t ret_exp2 =
+ static_cast<uint64_t>((217706 * exp10 >> 16) + 64 +
+ FloatTraits<FloatType>::kExponentBias - clz);
+
+ // (+) Multiplication.
+ uint128 x = static_cast<uint128>(man) *
+ static_cast<uint128>(
+ kPower10MantissaHighTable[exp10 - kPower10TableMinInclusive]);
+
+ // (+) Wider Approximation.
+ static constexpr uint64_t high64_mask =
+ FloatTraits<FloatType>::kEiselLemireMask;
+ if (((Uint128High64(x) & high64_mask) == high64_mask) &&
+ (man > (std::numeric_limits<uint64_t>::max() - Uint128Low64(x)))) {
+ uint128 y =
+ static_cast<uint128>(man) *
+ static_cast<uint128>(
+ kPower10MantissaLowTable[exp10 - kPower10TableMinInclusive]);
+ x += Uint128High64(y);
+ // For example, parsing "4503599627370497.5" will take the if-true
+ // branch here (for double precision), since:
+ // - x = 0x8000000000000BFF_FFFFFFFFFFFFFFFF
+ // - y = 0x8000000000000BFF_7FFFFFFFFFFFF400
+ // - man = 0xA000000000000F00
+ // Likewise, when parsing "0.0625" for single precision:
+ // - x = 0x7FFFFFFFFFFFFFFF_FFFFFFFFFFFFFFFF
+ // - y = 0x813FFFFFFFFFFFFF_8A00000000000000
+ // - man = 0x9C40000000000000
+ if (((Uint128High64(x) & high64_mask) == high64_mask) &&
+ ((Uint128Low64(x) + 1) == 0) &&
+ (man > (std::numeric_limits<uint64_t>::max() - Uint128Low64(y)))) {
+ return false;
+ }
+ }
+
+ // (+) Shifting to 54 Bits (or for single precision, to 25 bits).
+ uint64_t msb = Uint128High64(x) >> 63;
+ uint64_t ret_man =
+ Uint128High64(x) >> (msb + FloatTraits<FloatType>::kEiselLemireShift);
+ ret_exp2 -= 1 ^ msb;
+
+ // (+) Half-way Ambiguity.
+ //
+ // For example, parsing "1e+23" will take the if-true branch here (for double
+ // precision), since:
+ // - x = 0x54B40B1F852BDA00_0000000000000000
+ // - ret_man = 0x002A5A058FC295ED
+ // Likewise, when parsing "20040229.0" for single precision:
+ // - x = 0x4C72894000000000_0000000000000000
+ // - ret_man = 0x000000000131CA25
+ if ((Uint128Low64(x) == 0) && ((Uint128High64(x) & high64_mask) == 0) &&
+ ((ret_man & 3) == 1)) {
+ return false;
+ }
+
+ // (+) From 54 to 53 Bits (or for single precision, from 25 to 24 bits).
+ ret_man += ret_man & 1; // Line From54a.
+ ret_man >>= 1; // Line From54b.
+ // Incrementing ret_man (at line From54a) may have overflowed 54 bits (53
+ // bits after the right shift by 1 at line From54b), so adjust for that.
+ //
+ // For example, parsing "9223372036854775807" will take the if-true branch
+ // here (for double precision), since:
+ // - ret_man = 0x0020000000000000 = (1 << 53)
+ // Likewise, when parsing "2147483647.0" for single precision:
+ // - ret_man = 0x0000000001000000 = (1 << 24)
+ if ((ret_man >> FloatTraits<FloatType>::kTargetMantissaBits) > 0) {
+ ret_exp2 += 1;
+ // Conceptually, we need a "ret_man >>= 1" in this if-block to balance
+ // incrementing ret_exp2 in the line immediately above. However, we only
+ // get here when line From54a overflowed (after adding a 1), so ret_man
+ // here is (1 << 53). Its low 53 bits are therefore all zeroes. The only
+ // remaining use of ret_man is to mask it with ((1 << 52) - 1), so only its
+ // low 52 bits matter. A "ret_man >>= 1" would have no effect in practice.
+ //
+ // We omit the "ret_man >>= 1", even if it is cheap (and this if-branch is
+ // rarely taken) and technically 'more correct', so that mutation tests
+ // that would otherwise modify or omit that "ret_man >>= 1" don't complain
+ // that such code mutations have no observable effect.
+ }
+
+ // ret_exp2 is a uint64_t. Zero or underflow means that we're in subnormal
+ // space. max_exp2 (0x7FF for double precision, 0xFF for single precision) or
+ // above means that we're in Inf/NaN space.
+ //
+ // The if block is equivalent to (but has fewer branches than):
+ // if ((ret_exp2 <= 0) || (ret_exp2 >= max_exp2)) { etc }
+ //
+ // For example, parsing "4.9406564584124654e-324" will take the if-true
+ // branch here, since ret_exp2 = -51.
+ static constexpr uint64_t max_exp2 =
+ (1 << FloatTraits<FloatType>::kTargetExponentBits) - 1;
+ if ((ret_exp2 - 1) >= (max_exp2 - 1)) {
+ return false;
+ }
+
+#ifndef ABSL_BIT_PACK_FLOATS
+ if (FloatTraits<FloatType>::kTargetBits == 64) {
+ *value = FloatTraits<FloatType>::Make(
+ (ret_man & 0x000FFFFFFFFFFFFFu) | 0x0010000000000000u,
+ static_cast<int>(ret_exp2) - 1023 - 52, negative);
+ return true;
+ } else if (FloatTraits<FloatType>::kTargetBits == 32) {
+ *value = FloatTraits<FloatType>::Make(
+ (static_cast<uint32_t>(ret_man) & 0x007FFFFFu) | 0x00800000u,
+ static_cast<int>(ret_exp2) - 127 - 23, negative);
+ return true;
+ }
+#else
+ if (FloatTraits<FloatType>::kTargetBits == 64) {
+ uint64_t ret_bits = (ret_exp2 << 52) | (ret_man & 0x000FFFFFFFFFFFFFu);
+ if (negative) {
+ ret_bits |= 0x8000000000000000u;
+ }
+ *value = absl::bit_cast<double>(ret_bits);
+ return true;
+ } else if (FloatTraits<FloatType>::kTargetBits == 32) {
+ uint32_t ret_bits = (static_cast<uint32_t>(ret_exp2) << 23) |
+ (static_cast<uint32_t>(ret_man) & 0x007FFFFFu);
+ if (negative) {
+ ret_bits |= 0x80000000u;
+ }
+ *value = absl::bit_cast<float>(ret_bits);
+ return true;
+ }
+#endif // ABSL_BIT_PACK_FLOATS
+ return false;
+}
+
+template <typename FloatType>
+from_chars_result FromCharsImpl(absl::Nonnull<const char*> first,
+ absl::Nonnull<const char*> last,
FloatType& value, chars_format fmt_flags) {
from_chars_result result;
result.ptr = first; // overwritten on successful parse
@@ -668,6 +934,12 @@
if (HandleEdgeCase(decimal_parse, negative, &value)) {
return result;
}
+ // A nullptr subrange_begin means that the decimal_parse.mantissa is exact
+ // (not truncated), a precondition of the Eisel-Lemire algorithm.
+ if ((decimal_parse.subrange_begin == nullptr) &&
+ EiselLemire<FloatType>(decimal_parse, negative, &value, &result.ec)) {
+ return result;
+ }
CalculatedFloat calculated =
CalculateFromParsedDecimal<FloatType>(decimal_parse);
EncodeResult(calculated, negative, &result, &value);
@@ -676,27 +948,60 @@
}
} // namespace
-from_chars_result from_chars(const char* first, const char* last, double& value,
+from_chars_result from_chars(absl::Nonnull<const char*> first,
+ absl::Nonnull<const char*> last, double& value,
chars_format fmt) {
return FromCharsImpl(first, last, value, fmt);
}
-from_chars_result from_chars(const char* first, const char* last, float& value,
+from_chars_result from_chars(absl::Nonnull<const char*> first,
+ absl::Nonnull<const char*> last, float& value,
chars_format fmt) {
return FromCharsImpl(first, last, value, fmt);
}
namespace {
-// Table of powers of 10, from kPower10TableMin to kPower10TableMax.
+// Table of powers of 10, from kPower10TableMinInclusive to
+// kPower10TableMaxExclusive.
//
-// kPower10MantissaTable[i - kPower10TableMin] stores the 64-bit mantissa (high
-// bit always on), and kPower10ExponentTable[i - kPower10TableMin] stores the
-// power-of-two exponent. For a given number i, this gives the unique mantissa
-// and exponent such that mantissa * 2**exponent <= 10**i < (mantissa + 1) *
-// 2**exponent.
+// kPower10MantissaHighTable[i - kPower10TableMinInclusive] stores the 64-bit
+// mantissa. The high bit is always on.
+//
+// kPower10MantissaLowTable extends that 64-bit mantissa to 128 bits.
+//
+// Power10Exponent(i) calculates the power-of-two exponent.
+//
+// For a number i, this gives the unique mantissaHigh and exponent such that
+// (mantissaHigh * 2**exponent) <= 10**i < ((mantissaHigh + 1) * 2**exponent).
+//
+// For example, Python can confirm that the exact hexadecimal value of 1e60 is:
+// >>> a = 1000000000000000000000000000000000000000000000000000000000000
+// >>> hex(a)
+// '0x9f4f2726179a224501d762422c946590d91000000000000000'
+// Adding underscores at every 8th hex digit shows 50 hex digits:
+// '0x9f4f2726_179a2245_01d76242_2c946590_d9100000_00000000_00'.
+// In this case, the high bit of the first hex digit, 9, is coincidentally set,
+// so we do not have to do further shifting to deduce the 128-bit mantissa:
+// - kPower10MantissaHighTable[60 - kP10TMI] = 0x9f4f2726179a2245U
+// - kPower10MantissaLowTable[ 60 - kP10TMI] = 0x01d762422c946590U
+// where kP10TMI is kPower10TableMinInclusive. The low 18 of those 50 hex
+// digits are truncated.
+//
+// 50 hex digits (with the high bit set) is 200 bits and mantissaHigh holds 64
+// bits, so Power10Exponent(60) = 200 - 64 = 136. Again, Python can confirm:
+// >>> b = 0x9f4f2726179a2245
+// >>> ((b+0)<<136) <= a
+// True
+// >>> ((b+1)<<136) <= a
+// False
+//
+// The tables were generated by
+// https://github.com/google/wuffs/blob/315b2e52625ebd7b02d8fac13e3cd85ea374fb80/script/print-mpb-powers-of-10.go
+// after re-formatting its output into two arrays of N uint64_t values (instead
+// of an N element array of uint64_t pairs).
-const uint64_t kPower10MantissaTable[] = {
+const uint64_t kPower10MantissaHighTable[] = {
0xeef453d6923bd65aU, 0x9558b4661b6565f8U, 0xbaaee17fa23ebf76U,
0xe95a99df8ace6f53U, 0x91d8a02bb6c10594U, 0xb64ec836a47146f9U,
0xe3e27a444d8d98b7U, 0x8e6d8c6ab0787f72U, 0xb208ef855c969f4fU,
@@ -916,67 +1221,224 @@
0xb6472e511c81471dU, 0xe3d8f9e563a198e5U, 0x8e679c2f5e44ff8fU,
};
-const int16_t kPower10ExponentTable[] = {
- -1200, -1196, -1193, -1190, -1186, -1183, -1180, -1176, -1173, -1170, -1166,
- -1163, -1160, -1156, -1153, -1150, -1146, -1143, -1140, -1136, -1133, -1130,
- -1127, -1123, -1120, -1117, -1113, -1110, -1107, -1103, -1100, -1097, -1093,
- -1090, -1087, -1083, -1080, -1077, -1073, -1070, -1067, -1063, -1060, -1057,
- -1053, -1050, -1047, -1043, -1040, -1037, -1034, -1030, -1027, -1024, -1020,
- -1017, -1014, -1010, -1007, -1004, -1000, -997, -994, -990, -987, -984,
- -980, -977, -974, -970, -967, -964, -960, -957, -954, -950, -947,
- -944, -940, -937, -934, -931, -927, -924, -921, -917, -914, -911,
- -907, -904, -901, -897, -894, -891, -887, -884, -881, -877, -874,
- -871, -867, -864, -861, -857, -854, -851, -847, -844, -841, -838,
- -834, -831, -828, -824, -821, -818, -814, -811, -808, -804, -801,
- -798, -794, -791, -788, -784, -781, -778, -774, -771, -768, -764,
- -761, -758, -754, -751, -748, -744, -741, -738, -735, -731, -728,
- -725, -721, -718, -715, -711, -708, -705, -701, -698, -695, -691,
- -688, -685, -681, -678, -675, -671, -668, -665, -661, -658, -655,
- -651, -648, -645, -642, -638, -635, -632, -628, -625, -622, -618,
- -615, -612, -608, -605, -602, -598, -595, -592, -588, -585, -582,
- -578, -575, -572, -568, -565, -562, -558, -555, -552, -549, -545,
- -542, -539, -535, -532, -529, -525, -522, -519, -515, -512, -509,
- -505, -502, -499, -495, -492, -489, -485, -482, -479, -475, -472,
- -469, -465, -462, -459, -455, -452, -449, -446, -442, -439, -436,
- -432, -429, -426, -422, -419, -416, -412, -409, -406, -402, -399,
- -396, -392, -389, -386, -382, -379, -376, -372, -369, -366, -362,
- -359, -356, -353, -349, -346, -343, -339, -336, -333, -329, -326,
- -323, -319, -316, -313, -309, -306, -303, -299, -296, -293, -289,
- -286, -283, -279, -276, -273, -269, -266, -263, -259, -256, -253,
- -250, -246, -243, -240, -236, -233, -230, -226, -223, -220, -216,
- -213, -210, -206, -203, -200, -196, -193, -190, -186, -183, -180,
- -176, -173, -170, -166, -163, -160, -157, -153, -150, -147, -143,
- -140, -137, -133, -130, -127, -123, -120, -117, -113, -110, -107,
- -103, -100, -97, -93, -90, -87, -83, -80, -77, -73, -70,
- -67, -63, -60, -57, -54, -50, -47, -44, -40, -37, -34,
- -30, -27, -24, -20, -17, -14, -10, -7, -4, 0, 3,
- 6, 10, 13, 16, 20, 23, 26, 30, 33, 36, 39,
- 43, 46, 49, 53, 56, 59, 63, 66, 69, 73, 76,
- 79, 83, 86, 89, 93, 96, 99, 103, 106, 109, 113,
- 116, 119, 123, 126, 129, 132, 136, 139, 142, 146, 149,
- 152, 156, 159, 162, 166, 169, 172, 176, 179, 182, 186,
- 189, 192, 196, 199, 202, 206, 209, 212, 216, 219, 222,
- 226, 229, 232, 235, 239, 242, 245, 249, 252, 255, 259,
- 262, 265, 269, 272, 275, 279, 282, 285, 289, 292, 295,
- 299, 302, 305, 309, 312, 315, 319, 322, 325, 328, 332,
- 335, 338, 342, 345, 348, 352, 355, 358, 362, 365, 368,
- 372, 375, 378, 382, 385, 388, 392, 395, 398, 402, 405,
- 408, 412, 415, 418, 422, 425, 428, 431, 435, 438, 441,
- 445, 448, 451, 455, 458, 461, 465, 468, 471, 475, 478,
- 481, 485, 488, 491, 495, 498, 501, 505, 508, 511, 515,
- 518, 521, 524, 528, 531, 534, 538, 541, 544, 548, 551,
- 554, 558, 561, 564, 568, 571, 574, 578, 581, 584, 588,
- 591, 594, 598, 601, 604, 608, 611, 614, 617, 621, 624,
- 627, 631, 634, 637, 641, 644, 647, 651, 654, 657, 661,
- 664, 667, 671, 674, 677, 681, 684, 687, 691, 694, 697,
- 701, 704, 707, 711, 714, 717, 720, 724, 727, 730, 734,
- 737, 740, 744, 747, 750, 754, 757, 760, 764, 767, 770,
- 774, 777, 780, 784, 787, 790, 794, 797, 800, 804, 807,
- 810, 813, 817, 820, 823, 827, 830, 833, 837, 840, 843,
- 847, 850, 853, 857, 860, 863, 867, 870, 873, 877, 880,
- 883, 887, 890, 893, 897, 900, 903, 907, 910, 913, 916,
- 920, 923, 926, 930, 933, 936, 940, 943, 946, 950, 953,
- 956, 960,
+const uint64_t kPower10MantissaLowTable[] = {
+ 0x113faa2906a13b3fU, 0x4ac7ca59a424c507U, 0x5d79bcf00d2df649U,
+ 0xf4d82c2c107973dcU, 0x79071b9b8a4be869U, 0x9748e2826cdee284U,
+ 0xfd1b1b2308169b25U, 0xfe30f0f5e50e20f7U, 0xbdbd2d335e51a935U,
+ 0xad2c788035e61382U, 0x4c3bcb5021afcc31U, 0xdf4abe242a1bbf3dU,
+ 0xd71d6dad34a2af0dU, 0x8672648c40e5ad68U, 0x680efdaf511f18c2U,
+ 0x0212bd1b2566def2U, 0x014bb630f7604b57U, 0x419ea3bd35385e2dU,
+ 0x52064cac828675b9U, 0x7343efebd1940993U, 0x1014ebe6c5f90bf8U,
+ 0xd41a26e077774ef6U, 0x8920b098955522b4U, 0x55b46e5f5d5535b0U,
+ 0xeb2189f734aa831dU, 0xa5e9ec7501d523e4U, 0x47b233c92125366eU,
+ 0x999ec0bb696e840aU, 0xc00670ea43ca250dU, 0x380406926a5e5728U,
+ 0xc605083704f5ecf2U, 0xf7864a44c633682eU, 0x7ab3ee6afbe0211dU,
+ 0x5960ea05bad82964U, 0x6fb92487298e33bdU, 0xa5d3b6d479f8e056U,
+ 0x8f48a4899877186cU, 0x331acdabfe94de87U, 0x9ff0c08b7f1d0b14U,
+ 0x07ecf0ae5ee44dd9U, 0xc9e82cd9f69d6150U, 0xbe311c083a225cd2U,
+ 0x6dbd630a48aaf406U, 0x092cbbccdad5b108U, 0x25bbf56008c58ea5U,
+ 0xaf2af2b80af6f24eU, 0x1af5af660db4aee1U, 0x50d98d9fc890ed4dU,
+ 0xe50ff107bab528a0U, 0x1e53ed49a96272c8U, 0x25e8e89c13bb0f7aU,
+ 0x77b191618c54e9acU, 0xd59df5b9ef6a2417U, 0x4b0573286b44ad1dU,
+ 0x4ee367f9430aec32U, 0x229c41f793cda73fU, 0x6b43527578c1110fU,
+ 0x830a13896b78aaa9U, 0x23cc986bc656d553U, 0x2cbfbe86b7ec8aa8U,
+ 0x7bf7d71432f3d6a9U, 0xdaf5ccd93fb0cc53U, 0xd1b3400f8f9cff68U,
+ 0x23100809b9c21fa1U, 0xabd40a0c2832a78aU, 0x16c90c8f323f516cU,
+ 0xae3da7d97f6792e3U, 0x99cd11cfdf41779cU, 0x40405643d711d583U,
+ 0x482835ea666b2572U, 0xda3243650005eecfU, 0x90bed43e40076a82U,
+ 0x5a7744a6e804a291U, 0x711515d0a205cb36U, 0x0d5a5b44ca873e03U,
+ 0xe858790afe9486c2U, 0x626e974dbe39a872U, 0xfb0a3d212dc8128fU,
+ 0x7ce66634bc9d0b99U, 0x1c1fffc1ebc44e80U, 0xa327ffb266b56220U,
+ 0x4bf1ff9f0062baa8U, 0x6f773fc3603db4a9U, 0xcb550fb4384d21d3U,
+ 0x7e2a53a146606a48U, 0x2eda7444cbfc426dU, 0xfa911155fefb5308U,
+ 0x793555ab7eba27caU, 0x4bc1558b2f3458deU, 0x9eb1aaedfb016f16U,
+ 0x465e15a979c1cadcU, 0x0bfacd89ec191ec9U, 0xcef980ec671f667bU,
+ 0x82b7e12780e7401aU, 0xd1b2ecb8b0908810U, 0x861fa7e6dcb4aa15U,
+ 0x67a791e093e1d49aU, 0xe0c8bb2c5c6d24e0U, 0x58fae9f773886e18U,
+ 0xaf39a475506a899eU, 0x6d8406c952429603U, 0xc8e5087ba6d33b83U,
+ 0xfb1e4a9a90880a64U, 0x5cf2eea09a55067fU, 0xf42faa48c0ea481eU,
+ 0xf13b94daf124da26U, 0x76c53d08d6b70858U, 0x54768c4b0c64ca6eU,
+ 0xa9942f5dcf7dfd09U, 0xd3f93b35435d7c4cU, 0xc47bc5014a1a6dafU,
+ 0x359ab6419ca1091bU, 0xc30163d203c94b62U, 0x79e0de63425dcf1dU,
+ 0x985915fc12f542e4U, 0x3e6f5b7b17b2939dU, 0xa705992ceecf9c42U,
+ 0x50c6ff782a838353U, 0xa4f8bf5635246428U, 0x871b7795e136be99U,
+ 0x28e2557b59846e3fU, 0x331aeada2fe589cfU, 0x3ff0d2c85def7621U,
+ 0x0fed077a756b53a9U, 0xd3e8495912c62894U, 0x64712dd7abbbd95cU,
+ 0xbd8d794d96aacfb3U, 0xecf0d7a0fc5583a0U, 0xf41686c49db57244U,
+ 0x311c2875c522ced5U, 0x7d633293366b828bU, 0xae5dff9c02033197U,
+ 0xd9f57f830283fdfcU, 0xd072df63c324fd7bU, 0x4247cb9e59f71e6dU,
+ 0x52d9be85f074e608U, 0x67902e276c921f8bU, 0x00ba1cd8a3db53b6U,
+ 0x80e8a40eccd228a4U, 0x6122cd128006b2cdU, 0x796b805720085f81U,
+ 0xcbe3303674053bb0U, 0xbedbfc4411068a9cU, 0xee92fb5515482d44U,
+ 0x751bdd152d4d1c4aU, 0xd262d45a78a0635dU, 0x86fb897116c87c34U,
+ 0xd45d35e6ae3d4da0U, 0x8974836059cca109U, 0x2bd1a438703fc94bU,
+ 0x7b6306a34627ddcfU, 0x1a3bc84c17b1d542U, 0x20caba5f1d9e4a93U,
+ 0x547eb47b7282ee9cU, 0xe99e619a4f23aa43U, 0x6405fa00e2ec94d4U,
+ 0xde83bc408dd3dd04U, 0x9624ab50b148d445U, 0x3badd624dd9b0957U,
+ 0xe54ca5d70a80e5d6U, 0x5e9fcf4ccd211f4cU, 0x7647c3200069671fU,
+ 0x29ecd9f40041e073U, 0xf468107100525890U, 0x7182148d4066eeb4U,
+ 0xc6f14cd848405530U, 0xb8ada00e5a506a7cU, 0xa6d90811f0e4851cU,
+ 0x908f4a166d1da663U, 0x9a598e4e043287feU, 0x40eff1e1853f29fdU,
+ 0xd12bee59e68ef47cU, 0x82bb74f8301958ceU, 0xe36a52363c1faf01U,
+ 0xdc44e6c3cb279ac1U, 0x29ab103a5ef8c0b9U, 0x7415d448f6b6f0e7U,
+ 0x111b495b3464ad21U, 0xcab10dd900beec34U, 0x3d5d514f40eea742U,
+ 0x0cb4a5a3112a5112U, 0x47f0e785eaba72abU, 0x59ed216765690f56U,
+ 0x306869c13ec3532cU, 0x1e414218c73a13fbU, 0xe5d1929ef90898faU,
+ 0xdf45f746b74abf39U, 0x6b8bba8c328eb783U, 0x066ea92f3f326564U,
+ 0xc80a537b0efefebdU, 0xbd06742ce95f5f36U, 0x2c48113823b73704U,
+ 0xf75a15862ca504c5U, 0x9a984d73dbe722fbU, 0xc13e60d0d2e0ebbaU,
+ 0x318df905079926a8U, 0xfdf17746497f7052U, 0xfeb6ea8bedefa633U,
+ 0xfe64a52ee96b8fc0U, 0x3dfdce7aa3c673b0U, 0x06bea10ca65c084eU,
+ 0x486e494fcff30a62U, 0x5a89dba3c3efccfaU, 0xf89629465a75e01cU,
+ 0xf6bbb397f1135823U, 0x746aa07ded582e2cU, 0xa8c2a44eb4571cdcU,
+ 0x92f34d62616ce413U, 0x77b020baf9c81d17U, 0x0ace1474dc1d122eU,
+ 0x0d819992132456baU, 0x10e1fff697ed6c69U, 0xca8d3ffa1ef463c1U,
+ 0xbd308ff8a6b17cb2U, 0xac7cb3f6d05ddbdeU, 0x6bcdf07a423aa96bU,
+ 0x86c16c98d2c953c6U, 0xe871c7bf077ba8b7U, 0x11471cd764ad4972U,
+ 0xd598e40d3dd89bcfU, 0x4aff1d108d4ec2c3U, 0xcedf722a585139baU,
+ 0xc2974eb4ee658828U, 0x733d226229feea32U, 0x0806357d5a3f525fU,
+ 0xca07c2dcb0cf26f7U, 0xfc89b393dd02f0b5U, 0xbbac2078d443ace2U,
+ 0xd54b944b84aa4c0dU, 0x0a9e795e65d4df11U, 0x4d4617b5ff4a16d5U,
+ 0x504bced1bf8e4e45U, 0xe45ec2862f71e1d6U, 0x5d767327bb4e5a4cU,
+ 0x3a6a07f8d510f86fU, 0x890489f70a55368bU, 0x2b45ac74ccea842eU,
+ 0x3b0b8bc90012929dU, 0x09ce6ebb40173744U, 0xcc420a6a101d0515U,
+ 0x9fa946824a12232dU, 0x47939822dc96abf9U, 0x59787e2b93bc56f7U,
+ 0x57eb4edb3c55b65aU, 0xede622920b6b23f1U, 0xe95fab368e45ecedU,
+ 0x11dbcb0218ebb414U, 0xd652bdc29f26a119U, 0x4be76d3346f0495fU,
+ 0x6f70a4400c562ddbU, 0xcb4ccd500f6bb952U, 0x7e2000a41346a7a7U,
+ 0x8ed400668c0c28c8U, 0x728900802f0f32faU, 0x4f2b40a03ad2ffb9U,
+ 0xe2f610c84987bfa8U, 0x0dd9ca7d2df4d7c9U, 0x91503d1c79720dbbU,
+ 0x75a44c6397ce912aU, 0xc986afbe3ee11abaU, 0xfbe85badce996168U,
+ 0xfae27299423fb9c3U, 0xdccd879fc967d41aU, 0x5400e987bbc1c920U,
+ 0x290123e9aab23b68U, 0xf9a0b6720aaf6521U, 0xf808e40e8d5b3e69U,
+ 0xb60b1d1230b20e04U, 0xb1c6f22b5e6f48c2U, 0x1e38aeb6360b1af3U,
+ 0x25c6da63c38de1b0U, 0x579c487e5a38ad0eU, 0x2d835a9df0c6d851U,
+ 0xf8e431456cf88e65U, 0x1b8e9ecb641b58ffU, 0xe272467e3d222f3fU,
+ 0x5b0ed81dcc6abb0fU, 0x98e947129fc2b4e9U, 0x3f2398d747b36224U,
+ 0x8eec7f0d19a03aadU, 0x1953cf68300424acU, 0x5fa8c3423c052dd7U,
+ 0x3792f412cb06794dU, 0xe2bbd88bbee40bd0U, 0x5b6aceaeae9d0ec4U,
+ 0xf245825a5a445275U, 0xeed6e2f0f0d56712U, 0x55464dd69685606bU,
+ 0xaa97e14c3c26b886U, 0xd53dd99f4b3066a8U, 0xe546a8038efe4029U,
+ 0xde98520472bdd033U, 0x963e66858f6d4440U, 0xdde7001379a44aa8U,
+ 0x5560c018580d5d52U, 0xaab8f01e6e10b4a6U, 0xcab3961304ca70e8U,
+ 0x3d607b97c5fd0d22U, 0x8cb89a7db77c506aU, 0x77f3608e92adb242U,
+ 0x55f038b237591ed3U, 0x6b6c46dec52f6688U, 0x2323ac4b3b3da015U,
+ 0xabec975e0a0d081aU, 0x96e7bd358c904a21U, 0x7e50d64177da2e54U,
+ 0xdde50bd1d5d0b9e9U, 0x955e4ec64b44e864U, 0xbd5af13bef0b113eU,
+ 0xecb1ad8aeacdd58eU, 0x67de18eda5814af2U, 0x80eacf948770ced7U,
+ 0xa1258379a94d028dU, 0x096ee45813a04330U, 0x8bca9d6e188853fcU,
+ 0x775ea264cf55347dU, 0x95364afe032a819dU, 0x3a83ddbd83f52204U,
+ 0xc4926a9672793542U, 0x75b7053c0f178293U, 0x5324c68b12dd6338U,
+ 0xd3f6fc16ebca5e03U, 0x88f4bb1ca6bcf584U, 0x2b31e9e3d06c32e5U,
+ 0x3aff322e62439fcfU, 0x09befeb9fad487c2U, 0x4c2ebe687989a9b3U,
+ 0x0f9d37014bf60a10U, 0x538484c19ef38c94U, 0x2865a5f206b06fb9U,
+ 0xf93f87b7442e45d3U, 0xf78f69a51539d748U, 0xb573440e5a884d1bU,
+ 0x31680a88f8953030U, 0xfdc20d2b36ba7c3dU, 0x3d32907604691b4cU,
+ 0xa63f9a49c2c1b10fU, 0x0fcf80dc33721d53U, 0xd3c36113404ea4a8U,
+ 0x645a1cac083126e9U, 0x3d70a3d70a3d70a3U, 0xccccccccccccccccU,
+ 0x0000000000000000U, 0x0000000000000000U, 0x0000000000000000U,
+ 0x0000000000000000U, 0x0000000000000000U, 0x0000000000000000U,
+ 0x0000000000000000U, 0x0000000000000000U, 0x0000000000000000U,
+ 0x0000000000000000U, 0x0000000000000000U, 0x0000000000000000U,
+ 0x0000000000000000U, 0x0000000000000000U, 0x0000000000000000U,
+ 0x0000000000000000U, 0x0000000000000000U, 0x0000000000000000U,
+ 0x0000000000000000U, 0x0000000000000000U, 0x0000000000000000U,
+ 0x0000000000000000U, 0x0000000000000000U, 0x0000000000000000U,
+ 0x0000000000000000U, 0x0000000000000000U, 0x0000000000000000U,
+ 0x0000000000000000U, 0x4000000000000000U, 0x5000000000000000U,
+ 0xa400000000000000U, 0x4d00000000000000U, 0xf020000000000000U,
+ 0x6c28000000000000U, 0xc732000000000000U, 0x3c7f400000000000U,
+ 0x4b9f100000000000U, 0x1e86d40000000000U, 0x1314448000000000U,
+ 0x17d955a000000000U, 0x5dcfab0800000000U, 0x5aa1cae500000000U,
+ 0xf14a3d9e40000000U, 0x6d9ccd05d0000000U, 0xe4820023a2000000U,
+ 0xdda2802c8a800000U, 0xd50b2037ad200000U, 0x4526f422cc340000U,
+ 0x9670b12b7f410000U, 0x3c0cdd765f114000U, 0xa5880a69fb6ac800U,
+ 0x8eea0d047a457a00U, 0x72a4904598d6d880U, 0x47a6da2b7f864750U,
+ 0x999090b65f67d924U, 0xfff4b4e3f741cf6dU, 0xbff8f10e7a8921a4U,
+ 0xaff72d52192b6a0dU, 0x9bf4f8a69f764490U, 0x02f236d04753d5b4U,
+ 0x01d762422c946590U, 0x424d3ad2b7b97ef5U, 0xd2e0898765a7deb2U,
+ 0x63cc55f49f88eb2fU, 0x3cbf6b71c76b25fbU, 0x8bef464e3945ef7aU,
+ 0x97758bf0e3cbb5acU, 0x3d52eeed1cbea317U, 0x4ca7aaa863ee4bddU,
+ 0x8fe8caa93e74ef6aU, 0xb3e2fd538e122b44U, 0x60dbbca87196b616U,
+ 0xbc8955e946fe31cdU, 0x6babab6398bdbe41U, 0xc696963c7eed2dd1U,
+ 0xfc1e1de5cf543ca2U, 0x3b25a55f43294bcbU, 0x49ef0eb713f39ebeU,
+ 0x6e3569326c784337U, 0x49c2c37f07965404U, 0xdc33745ec97be906U,
+ 0x69a028bb3ded71a3U, 0xc40832ea0d68ce0cU, 0xf50a3fa490c30190U,
+ 0x792667c6da79e0faU, 0x577001b891185938U, 0xed4c0226b55e6f86U,
+ 0x544f8158315b05b4U, 0x696361ae3db1c721U, 0x03bc3a19cd1e38e9U,
+ 0x04ab48a04065c723U, 0x62eb0d64283f9c76U, 0x3ba5d0bd324f8394U,
+ 0xca8f44ec7ee36479U, 0x7e998b13cf4e1ecbU, 0x9e3fedd8c321a67eU,
+ 0xc5cfe94ef3ea101eU, 0xbba1f1d158724a12U, 0x2a8a6e45ae8edc97U,
+ 0xf52d09d71a3293bdU, 0x593c2626705f9c56U, 0x6f8b2fb00c77836cU,
+ 0x0b6dfb9c0f956447U, 0x4724bd4189bd5eacU, 0x58edec91ec2cb657U,
+ 0x2f2967b66737e3edU, 0xbd79e0d20082ee74U, 0xecd8590680a3aa11U,
+ 0xe80e6f4820cc9495U, 0x3109058d147fdcddU, 0xbd4b46f0599fd415U,
+ 0x6c9e18ac7007c91aU, 0x03e2cf6bc604ddb0U, 0x84db8346b786151cU,
+ 0xe612641865679a63U, 0x4fcb7e8f3f60c07eU, 0xe3be5e330f38f09dU,
+ 0x5cadf5bfd3072cc5U, 0x73d9732fc7c8f7f6U, 0x2867e7fddcdd9afaU,
+ 0xb281e1fd541501b8U, 0x1f225a7ca91a4226U, 0x3375788de9b06958U,
+ 0x0052d6b1641c83aeU, 0xc0678c5dbd23a49aU, 0xf840b7ba963646e0U,
+ 0xb650e5a93bc3d898U, 0xa3e51f138ab4cebeU, 0xc66f336c36b10137U,
+ 0xb80b0047445d4184U, 0xa60dc059157491e5U, 0x87c89837ad68db2fU,
+ 0x29babe4598c311fbU, 0xf4296dd6fef3d67aU, 0x1899e4a65f58660cU,
+ 0x5ec05dcff72e7f8fU, 0x76707543f4fa1f73U, 0x6a06494a791c53a8U,
+ 0x0487db9d17636892U, 0x45a9d2845d3c42b6U, 0x0b8a2392ba45a9b2U,
+ 0x8e6cac7768d7141eU, 0x3207d795430cd926U, 0x7f44e6bd49e807b8U,
+ 0x5f16206c9c6209a6U, 0x36dba887c37a8c0fU, 0xc2494954da2c9789U,
+ 0xf2db9baa10b7bd6cU, 0x6f92829494e5acc7U, 0xcb772339ba1f17f9U,
+ 0xff2a760414536efbU, 0xfef5138519684abaU, 0x7eb258665fc25d69U,
+ 0xef2f773ffbd97a61U, 0xaafb550ffacfd8faU, 0x95ba2a53f983cf38U,
+ 0xdd945a747bf26183U, 0x94f971119aeef9e4U, 0x7a37cd5601aab85dU,
+ 0xac62e055c10ab33aU, 0x577b986b314d6009U, 0xed5a7e85fda0b80bU,
+ 0x14588f13be847307U, 0x596eb2d8ae258fc8U, 0x6fca5f8ed9aef3bbU,
+ 0x25de7bb9480d5854U, 0xaf561aa79a10ae6aU, 0x1b2ba1518094da04U,
+ 0x90fb44d2f05d0842U, 0x353a1607ac744a53U, 0x42889b8997915ce8U,
+ 0x69956135febada11U, 0x43fab9837e699095U, 0x94f967e45e03f4bbU,
+ 0x1d1be0eebac278f5U, 0x6462d92a69731732U, 0x7d7b8f7503cfdcfeU,
+ 0x5cda735244c3d43eU, 0x3a0888136afa64a7U, 0x088aaa1845b8fdd0U,
+ 0x8aad549e57273d45U, 0x36ac54e2f678864bU, 0x84576a1bb416a7ddU,
+ 0x656d44a2a11c51d5U, 0x9f644ae5a4b1b325U, 0x873d5d9f0dde1feeU,
+ 0xa90cb506d155a7eaU, 0x09a7f12442d588f2U, 0x0c11ed6d538aeb2fU,
+ 0x8f1668c8a86da5faU, 0xf96e017d694487bcU, 0x37c981dcc395a9acU,
+ 0x85bbe253f47b1417U, 0x93956d7478ccec8eU, 0x387ac8d1970027b2U,
+ 0x06997b05fcc0319eU, 0x441fece3bdf81f03U, 0xd527e81cad7626c3U,
+ 0x8a71e223d8d3b074U, 0xf6872d5667844e49U, 0xb428f8ac016561dbU,
+ 0xe13336d701beba52U, 0xecc0024661173473U, 0x27f002d7f95d0190U,
+ 0x31ec038df7b441f4U, 0x7e67047175a15271U, 0x0f0062c6e984d386U,
+ 0x52c07b78a3e60868U, 0xa7709a56ccdf8a82U, 0x88a66076400bb691U,
+ 0x6acff893d00ea435U, 0x0583f6b8c4124d43U, 0xc3727a337a8b704aU,
+ 0x744f18c0592e4c5cU, 0x1162def06f79df73U, 0x8addcb5645ac2ba8U,
+ 0x6d953e2bd7173692U, 0xc8fa8db6ccdd0437U, 0x1d9c9892400a22a2U,
+ 0x2503beb6d00cab4bU, 0x2e44ae64840fd61dU, 0x5ceaecfed289e5d2U,
+ 0x7425a83e872c5f47U, 0xd12f124e28f77719U, 0x82bd6b70d99aaa6fU,
+ 0x636cc64d1001550bU, 0x3c47f7e05401aa4eU, 0x65acfaec34810a71U,
+ 0x7f1839a741a14d0dU, 0x1ede48111209a050U, 0x934aed0aab460432U,
+ 0xf81da84d5617853fU, 0x36251260ab9d668eU, 0xc1d72b7c6b426019U,
+ 0xb24cf65b8612f81fU, 0xdee033f26797b627U, 0x169840ef017da3b1U,
+ 0x8e1f289560ee864eU, 0xf1a6f2bab92a27e2U, 0xae10af696774b1dbU,
+ 0xacca6da1e0a8ef29U, 0x17fd090a58d32af3U, 0xddfc4b4cef07f5b0U,
+ 0x4abdaf101564f98eU, 0x9d6d1ad41abe37f1U, 0x84c86189216dc5edU,
+ 0x32fd3cf5b4e49bb4U, 0x3fbc8c33221dc2a1U, 0x0fabaf3feaa5334aU,
+ 0x29cb4d87f2a7400eU, 0x743e20e9ef511012U, 0x914da9246b255416U,
+ 0x1ad089b6c2f7548eU, 0xa184ac2473b529b1U, 0xc9e5d72d90a2741eU,
+ 0x7e2fa67c7a658892U, 0xddbb901b98feeab7U, 0x552a74227f3ea565U,
+ 0xd53a88958f87275fU, 0x8a892abaf368f137U, 0x2d2b7569b0432d85U,
+ 0x9c3b29620e29fc73U, 0x8349f3ba91b47b8fU, 0x241c70a936219a73U,
+ 0xed238cd383aa0110U, 0xf4363804324a40aaU, 0xb143c6053edcd0d5U,
+ 0xdd94b7868e94050aU, 0xca7cf2b4191c8326U, 0xfd1c2f611f63a3f0U,
+ 0xbc633b39673c8cecU, 0xd5be0503e085d813U, 0x4b2d8644d8a74e18U,
+ 0xddf8e7d60ed1219eU, 0xcabb90e5c942b503U, 0x3d6a751f3b936243U,
+ 0x0cc512670a783ad4U, 0x27fb2b80668b24c5U, 0xb1f9f660802dedf6U,
+ 0x5e7873f8a0396973U, 0xdb0b487b6423e1e8U, 0x91ce1a9a3d2cda62U,
+ 0x7641a140cc7810fbU, 0xa9e904c87fcb0a9dU, 0x546345fa9fbdcd44U,
+ 0xa97c177947ad4095U, 0x49ed8eabcccc485dU, 0x5c68f256bfff5a74U,
+ 0x73832eec6fff3111U, 0xc831fd53c5ff7eabU, 0xba3e7ca8b77f5e55U,
+ 0x28ce1bd2e55f35ebU, 0x7980d163cf5b81b3U, 0xd7e105bcc332621fU,
+ 0x8dd9472bf3fefaa7U, 0xb14f98f6f0feb951U, 0x6ed1bf9a569f33d3U,
+ 0x0a862f80ec4700c8U, 0xcd27bb612758c0faU, 0x8038d51cb897789cU,
+ 0xe0470a63e6bd56c3U, 0x1858ccfce06cac74U, 0x0f37801e0c43ebc8U,
+ 0xd30560258f54e6baU, 0x47c6b82ef32a2069U, 0x4cdc331d57fa5441U,
+ 0xe0133fe4adf8e952U, 0x58180fddd97723a6U, 0x570f09eaa7ea7648U,
};
} // namespace
diff --git a/absl/strings/charconv.h b/absl/strings/charconv.h
index e04be32..be25090 100644
--- a/absl/strings/charconv.h
+++ b/absl/strings/charconv.h
@@ -18,11 +18,12 @@
#include <system_error> // NOLINT(build/c++11)
#include "absl/base/config.h"
+#include "absl/base/nullability.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
-// Workalike compatibilty version of std::chars_format from C++17.
+// Workalike compatibility version of std::chars_format from C++17.
//
// This is an bitfield enumerator which can be passed to absl::from_chars to
// configure the string-to-float conversion.
@@ -44,11 +45,11 @@
// characters that were successfully parsed. If none was found, `ptr` is set
// to the `first` argument to from_chars.
struct from_chars_result {
- const char* ptr;
+ absl::Nonnull<const char*> ptr;
std::errc ec;
};
-// Workalike compatibilty version of std::from_chars from C++17. Currently
+// Workalike compatibility version of std::from_chars from C++17. Currently
// this only supports the `double` and `float` types.
//
// This interface incorporates the proposed resolutions for library issues
@@ -64,8 +65,9 @@
// the result in `value`.
//
// The matching pattern format is almost the same as that of strtod(), except
-// that C locale is not respected, and an initial '+' character in the input
-// range will never be matched.
+// that (1) C locale is not respected, (2) an initial '+' character in the
+// input range will never be matched, and (3) leading whitespaces are not
+// ignored.
//
// If `fmt` is set, it must be one of the enumerator values of the chars_format.
// (This is despite the fact that chars_format is a bitmask type.) If set to
@@ -75,11 +77,13 @@
// format that strtod() accepts, except that a "0x" prefix is NOT matched.
// (In particular, in `hex` mode, the input "0xff" results in the largest
// matching pattern "0".)
-absl::from_chars_result from_chars(const char* first, const char* last,
+absl::from_chars_result from_chars(absl::Nonnull<const char*> first,
+ absl::Nonnull<const char*> last,
double& value, // NOLINT
chars_format fmt = chars_format::general);
-absl::from_chars_result from_chars(const char* first, const char* last,
+absl::from_chars_result from_chars(absl::Nonnull<const char*> first,
+ absl::Nonnull<const char*> last,
float& value, // NOLINT
chars_format fmt = chars_format::general);
diff --git a/absl/strings/charconv_test.cc b/absl/strings/charconv_test.cc
index b83de5a..c16c735 100644
--- a/absl/strings/charconv_test.cc
+++ b/absl/strings/charconv_test.cc
@@ -14,14 +14,19 @@
#include "absl/strings/charconv.h"
+#include <cfloat>
+#include <cmath>
#include <cstdlib>
+#include <functional>
+#include <limits>
#include <string>
+#include <system_error> // NOLINT(build/c++11)
-#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/strings/internal/pow10_helper.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
+#include "absl/strings/string_view.h"
#ifdef _MSC_FULL_VER
#define ABSL_COMPILER_DOES_EXACT_ROUNDING 0
diff --git a/absl/strings/charset.h b/absl/strings/charset.h
new file mode 100644
index 0000000..ff4e81a
--- /dev/null
+++ b/absl/strings/charset.h
@@ -0,0 +1,164 @@
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// -----------------------------------------------------------------------------
+// File: charset.h
+// -----------------------------------------------------------------------------
+//
+// This file contains absl::CharSet, a fast, bit-vector set of 8-bit unsigned
+// characters.
+//
+// Instances can be initialized as constexpr constants. For example:
+//
+// constexpr absl::CharSet kJustX = absl::CharSet::Char('x');
+// constexpr absl::CharSet kMySymbols = absl::CharSet("$@!");
+// constexpr absl::CharSet kLetters = absl::CharSet::Range('a', 'z');
+//
+// Multiple instances can be combined that still forms a constexpr expression.
+// For example:
+//
+// constexpr absl::CharSet kLettersAndNumbers =
+// absl::CharSet::Range('a', 'z') | absl::CharSet::Range('0', '9');
+//
+// Several pre-defined character classes are available that mirror the methods
+// from <cctype>. For example:
+//
+// constexpr absl::CharSet kLettersAndWhitespace =
+// absl::CharSet::AsciiAlphabet() | absl::CharSet::AsciiWhitespace();
+//
+// To check membership, use the .contains method, e.g.
+//
+// absl::CharSet hex_letters("abcdef");
+// hex_letters.contains('a'); // true
+// hex_letters.contains('g'); // false
+
+#ifndef ABSL_STRINGS_CHARSET_H_
+#define ABSL_STRINGS_CHARSET_H_
+
+#include <cstddef>
+#include <cstdint>
+#include <cstring>
+
+#include "absl/base/macros.h"
+#include "absl/base/port.h"
+#include "absl/strings/string_view.h"
+
+namespace absl {
+
+class CharSet {
+ public:
+ constexpr CharSet() : m_() {}
+
+ // Initializes with a given string_view.
+ constexpr explicit CharSet(absl::string_view str) : m_() {
+ for (char c : str) {
+ SetChar(static_cast<unsigned char>(c));
+ }
+ }
+
+ constexpr bool contains(char c) const {
+ return ((m_[static_cast<unsigned char>(c) / 64] >>
+ (static_cast<unsigned char>(c) % 64)) &
+ 0x1) == 0x1;
+ }
+
+ constexpr bool empty() const {
+ for (uint64_t c : m_) {
+ if (c != 0) return false;
+ }
+ return true;
+ }
+
+ // Containing only a single specified char.
+ static constexpr CharSet Char(char x) {
+ return CharSet(CharMaskForWord(x, 0), CharMaskForWord(x, 1),
+ CharMaskForWord(x, 2), CharMaskForWord(x, 3));
+ }
+
+ // Containing all the chars in the closed interval [lo,hi].
+ static constexpr CharSet Range(char lo, char hi) {
+ return CharSet(RangeForWord(lo, hi, 0), RangeForWord(lo, hi, 1),
+ RangeForWord(lo, hi, 2), RangeForWord(lo, hi, 3));
+ }
+
+ friend constexpr CharSet operator&(const CharSet& a, const CharSet& b) {
+ return CharSet(a.m_[0] & b.m_[0], a.m_[1] & b.m_[1], a.m_[2] & b.m_[2],
+ a.m_[3] & b.m_[3]);
+ }
+
+ friend constexpr CharSet operator|(const CharSet& a, const CharSet& b) {
+ return CharSet(a.m_[0] | b.m_[0], a.m_[1] | b.m_[1], a.m_[2] | b.m_[2],
+ a.m_[3] | b.m_[3]);
+ }
+
+ friend constexpr CharSet operator~(const CharSet& a) {
+ return CharSet(~a.m_[0], ~a.m_[1], ~a.m_[2], ~a.m_[3]);
+ }
+
+ // Mirrors the char-classifying predicates in <cctype>.
+ static constexpr CharSet AsciiUppercase() { return CharSet::Range('A', 'Z'); }
+ static constexpr CharSet AsciiLowercase() { return CharSet::Range('a', 'z'); }
+ static constexpr CharSet AsciiDigits() { return CharSet::Range('0', '9'); }
+ static constexpr CharSet AsciiAlphabet() {
+ return AsciiLowercase() | AsciiUppercase();
+ }
+ static constexpr CharSet AsciiAlphanumerics() {
+ return AsciiDigits() | AsciiAlphabet();
+ }
+ static constexpr CharSet AsciiHexDigits() {
+ return AsciiDigits() | CharSet::Range('A', 'F') | CharSet::Range('a', 'f');
+ }
+ static constexpr CharSet AsciiPrintable() {
+ return CharSet::Range(0x20, 0x7e);
+ }
+ static constexpr CharSet AsciiWhitespace() { return CharSet("\t\n\v\f\r "); }
+ static constexpr CharSet AsciiPunctuation() {
+ return AsciiPrintable() & ~AsciiWhitespace() & ~AsciiAlphanumerics();
+ }
+
+ private:
+ constexpr CharSet(uint64_t b0, uint64_t b1, uint64_t b2, uint64_t b3)
+ : m_{b0, b1, b2, b3} {}
+
+ static constexpr uint64_t RangeForWord(char lo, char hi, uint64_t word) {
+ return OpenRangeFromZeroForWord(static_cast<unsigned char>(hi) + 1, word) &
+ ~OpenRangeFromZeroForWord(static_cast<unsigned char>(lo), word);
+ }
+
+ // All the chars in the specified word of the range [0, upper).
+ static constexpr uint64_t OpenRangeFromZeroForWord(uint64_t upper,
+ uint64_t word) {
+ return (upper <= 64 * word) ? 0
+ : (upper >= 64 * (word + 1))
+ ? ~static_cast<uint64_t>(0)
+ : (~static_cast<uint64_t>(0) >> (64 - upper % 64));
+ }
+
+ static constexpr uint64_t CharMaskForWord(char x, uint64_t word) {
+ return (static_cast<unsigned char>(x) / 64 == word)
+ ? (static_cast<uint64_t>(1)
+ << (static_cast<unsigned char>(x) % 64))
+ : 0;
+ }
+
+ constexpr void SetChar(unsigned char c) {
+ m_[c / 64] |= static_cast<uint64_t>(1) << (c % 64);
+ }
+
+ uint64_t m_[4];
+};
+
+} // namespace absl
+
+#endif // ABSL_STRINGS_CHARSET_H_
diff --git a/absl/strings/internal/char_map_benchmark.cc b/absl/strings/charset_benchmark.cc
similarity index 71%
rename from absl/strings/internal/char_map_benchmark.cc
rename to absl/strings/charset_benchmark.cc
index 5cef967..bf7ae56 100644
--- a/absl/strings/internal/char_map_benchmark.cc
+++ b/absl/strings/charset_benchmark.cc
@@ -1,4 +1,4 @@
-// Copyright 2017 The Abseil Authors.
+// Copyright 2020 The Abseil Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -12,30 +12,30 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#include "absl/strings/internal/char_map.h"
-
#include <cstdint>
#include "benchmark/benchmark.h"
+#include "absl/log/check.h"
+#include "absl/strings/charset.h"
namespace {
-absl::strings_internal::Charmap MakeBenchmarkMap() {
- absl::strings_internal::Charmap m;
+absl::CharSet MakeBenchmarkMap() {
+ absl::CharSet m;
uint32_t x[] = {0x0, 0x1, 0x2, 0x3, 0xf, 0xe, 0xd, 0xc};
for (uint32_t& t : x) t *= static_cast<uint32_t>(0x11111111UL);
for (uint32_t i = 0; i < 256; ++i) {
- if ((x[i / 32] >> (i % 32)) & 1)
- m = m | absl::strings_internal::Charmap::Char(i);
+ if ((x[i / 32] >> (i % 32)) & 1) m = m | absl::CharSet::Char(i);
}
return m;
}
// Micro-benchmark for Charmap::contains.
-void BM_Contains(benchmark::State& state) {
+static void BM_Contains(benchmark::State& state) {
// Loop-body replicated 10 times to increase time per iteration.
// Argument continuously changed to avoid generating common subexpressions.
- const absl::strings_internal::Charmap benchmark_map = MakeBenchmarkMap();
+ // Final CHECK used to discourage unwanted optimization.
+ const absl::CharSet benchmark_map = MakeBenchmarkMap();
unsigned char c = 0;
int ops = 0;
for (auto _ : state) {
@@ -50,12 +50,8 @@
ops += benchmark_map.contains(c++);
ops += benchmark_map.contains(c++);
}
- benchmark::DoNotOptimize(ops);
+ CHECK_NE(ops, -1);
}
BENCHMARK(BM_Contains);
-// We don't bother benchmarking Charmap::IsZero or Charmap::IntersectsWith;
-// their running time is data-dependent and it is not worth characterizing
-// "typical" data.
-
} // namespace
diff --git a/absl/strings/charset_test.cc b/absl/strings/charset_test.cc
new file mode 100644
index 0000000..fff943a
--- /dev/null
+++ b/absl/strings/charset_test.cc
@@ -0,0 +1,181 @@
+// Copyright 2020 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/strings/charset.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <string>
+#include <vector>
+
+#include "gtest/gtest.h"
+#include "absl/strings/ascii.h"
+#include "absl/strings/string_view.h"
+
+namespace {
+
+constexpr absl::CharSet everything_map = ~absl::CharSet();
+constexpr absl::CharSet nothing_map = absl::CharSet();
+
+TEST(Charmap, AllTests) {
+ const absl::CharSet also_nothing_map("");
+ EXPECT_TRUE(everything_map.contains('\0'));
+ EXPECT_FALSE(nothing_map.contains('\0'));
+ EXPECT_FALSE(also_nothing_map.contains('\0'));
+ for (unsigned char ch = 1; ch != 0; ++ch) {
+ SCOPED_TRACE(ch);
+ EXPECT_TRUE(everything_map.contains(ch));
+ EXPECT_FALSE(nothing_map.contains(ch));
+ EXPECT_FALSE(also_nothing_map.contains(ch));
+ }
+
+ const absl::CharSet symbols(absl::string_view("&@#@^!@?", 5));
+ EXPECT_TRUE(symbols.contains('&'));
+ EXPECT_TRUE(symbols.contains('@'));
+ EXPECT_TRUE(symbols.contains('#'));
+ EXPECT_TRUE(symbols.contains('^'));
+ EXPECT_FALSE(symbols.contains('!'));
+ EXPECT_FALSE(symbols.contains('?'));
+ int cnt = 0;
+ for (unsigned char ch = 1; ch != 0; ++ch) cnt += symbols.contains(ch);
+ EXPECT_EQ(cnt, 4);
+
+ const absl::CharSet lets(absl::string_view("^abcde", 3));
+ const absl::CharSet lets2(absl::string_view("fghij\0klmnop", 10));
+ const absl::CharSet lets3("fghij\0klmnop");
+ EXPECT_TRUE(lets2.contains('k'));
+ EXPECT_FALSE(lets3.contains('k'));
+
+ EXPECT_FALSE((symbols & lets).empty());
+ EXPECT_TRUE((lets2 & lets).empty());
+ EXPECT_FALSE((lets & symbols).empty());
+ EXPECT_TRUE((lets & lets2).empty());
+
+ EXPECT_TRUE(nothing_map.empty());
+ EXPECT_FALSE(lets.empty());
+}
+
+std::string Members(const absl::CharSet& m) {
+ std::string r;
+ for (size_t i = 0; i < 256; ++i)
+ if (m.contains(i)) r.push_back(i);
+ return r;
+}
+
+std::string ClosedRangeString(unsigned char lo, unsigned char hi) {
+ // Don't depend on lo<hi. Just increment until lo==hi.
+ std::string s;
+ while (true) {
+ s.push_back(lo);
+ if (lo == hi) break;
+ ++lo;
+ }
+ return s;
+}
+
+TEST(Charmap, Constexpr) {
+ constexpr absl::CharSet kEmpty = absl::CharSet();
+ EXPECT_EQ(Members(kEmpty), "");
+ constexpr absl::CharSet kA = absl::CharSet::Char('A');
+ EXPECT_EQ(Members(kA), "A");
+ constexpr absl::CharSet kAZ = absl::CharSet::Range('A', 'Z');
+ EXPECT_EQ(Members(kAZ), "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
+ constexpr absl::CharSet kIdentifier =
+ absl::CharSet::Range('0', '9') | absl::CharSet::Range('A', 'Z') |
+ absl::CharSet::Range('a', 'z') | absl::CharSet::Char('_');
+ EXPECT_EQ(Members(kIdentifier),
+ "0123456789"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "_"
+ "abcdefghijklmnopqrstuvwxyz");
+ constexpr absl::CharSet kAll = ~absl::CharSet();
+ for (size_t i = 0; i < 256; ++i) {
+ SCOPED_TRACE(i);
+ EXPECT_TRUE(kAll.contains(i));
+ }
+ constexpr absl::CharSet kHello = absl::CharSet("Hello, world!");
+ EXPECT_EQ(Members(kHello), " !,Hdelorw");
+
+ // test negation and intersection
+ constexpr absl::CharSet kABC =
+ absl::CharSet::Range('A', 'Z') & ~absl::CharSet::Range('D', 'Z');
+ EXPECT_EQ(Members(kABC), "ABC");
+
+ // contains
+ constexpr bool kContainsA = absl::CharSet("abc").contains('a');
+ EXPECT_TRUE(kContainsA);
+ constexpr bool kContainsD = absl::CharSet("abc").contains('d');
+ EXPECT_FALSE(kContainsD);
+
+ // empty
+ constexpr bool kEmptyIsEmpty = absl::CharSet().empty();
+ EXPECT_TRUE(kEmptyIsEmpty);
+ constexpr bool kNotEmptyIsEmpty = absl::CharSet("abc").empty();
+ EXPECT_FALSE(kNotEmptyIsEmpty);
+}
+
+TEST(Charmap, Range) {
+ // Exhaustive testing takes too long, so test some of the boundaries that
+ // are perhaps going to cause trouble.
+ std::vector<size_t> poi = {0, 1, 2, 3, 4, 7, 8, 9, 15,
+ 16, 17, 30, 31, 32, 33, 63, 64, 65,
+ 127, 128, 129, 223, 224, 225, 254, 255};
+ for (auto lo = poi.begin(); lo != poi.end(); ++lo) {
+ SCOPED_TRACE(*lo);
+ for (auto hi = lo; hi != poi.end(); ++hi) {
+ SCOPED_TRACE(*hi);
+ EXPECT_EQ(Members(absl::CharSet::Range(*lo, *hi)),
+ ClosedRangeString(*lo, *hi));
+ }
+ }
+}
+
+TEST(Charmap, NullByteWithStringView) {
+ char characters[5] = {'a', 'b', '\0', 'd', 'x'};
+ absl::string_view view(characters, 5);
+ absl::CharSet tester(view);
+ EXPECT_TRUE(tester.contains('a'));
+ EXPECT_TRUE(tester.contains('b'));
+ EXPECT_TRUE(tester.contains('\0'));
+ EXPECT_TRUE(tester.contains('d'));
+ EXPECT_TRUE(tester.contains('x'));
+ EXPECT_FALSE(tester.contains('c'));
+}
+
+TEST(CharmapCtype, Match) {
+ for (int c = 0; c < 256; ++c) {
+ SCOPED_TRACE(c);
+ SCOPED_TRACE(static_cast<char>(c));
+ EXPECT_EQ(absl::ascii_isupper(c),
+ absl::CharSet::AsciiUppercase().contains(c));
+ EXPECT_EQ(absl::ascii_islower(c),
+ absl::CharSet::AsciiLowercase().contains(c));
+ EXPECT_EQ(absl::ascii_isdigit(c), absl::CharSet::AsciiDigits().contains(c));
+ EXPECT_EQ(absl::ascii_isalpha(c),
+ absl::CharSet::AsciiAlphabet().contains(c));
+ EXPECT_EQ(absl::ascii_isalnum(c),
+ absl::CharSet::AsciiAlphanumerics().contains(c));
+ EXPECT_EQ(absl::ascii_isxdigit(c),
+ absl::CharSet::AsciiHexDigits().contains(c));
+ EXPECT_EQ(absl::ascii_isprint(c),
+ absl::CharSet::AsciiPrintable().contains(c));
+ EXPECT_EQ(absl::ascii_isspace(c),
+ absl::CharSet::AsciiWhitespace().contains(c));
+ EXPECT_EQ(absl::ascii_ispunct(c),
+ absl::CharSet::AsciiPunctuation().contains(c));
+ }
+}
+
+} // namespace
diff --git a/absl/strings/cord.cc b/absl/strings/cord.cc
index f5aa6e4..f67326f 100644
--- a/absl/strings/cord.cc
+++ b/absl/strings/cord.cc
@@ -15,248 +15,113 @@
#include "absl/strings/cord.h"
#include <algorithm>
-#include <atomic>
+#include <cassert>
#include <cstddef>
+#include <cstdint>
#include <cstdio>
#include <cstdlib>
+#include <cstring>
#include <iomanip>
+#include <ios>
#include <iostream>
#include <limits>
+#include <memory>
#include <ostream>
#include <sstream>
-#include <type_traits>
-#include <unordered_set>
-#include <vector>
+#include <string>
+#include <utility>
-#include "absl/base/casts.h"
+#include "absl/base/attributes.h"
+#include "absl/base/config.h"
+#include "absl/base/internal/endian.h"
#include "absl/base/internal/raw_logging.h"
#include "absl/base/macros.h"
-#include "absl/base/port.h"
-#include "absl/container/fixed_array.h"
+#include "absl/base/optimization.h"
+#include "absl/base/nullability.h"
#include "absl/container/inlined_vector.h"
+#include "absl/crc/crc32c.h"
+#include "absl/crc/internal/crc_cord_state.h"
+#include "absl/functional/function_ref.h"
+#include "absl/strings/cord_buffer.h"
#include "absl/strings/escaping.h"
+#include "absl/strings/internal/cord_data_edge.h"
#include "absl/strings/internal/cord_internal.h"
+#include "absl/strings/internal/cord_rep_btree.h"
+#include "absl/strings/internal/cord_rep_crc.h"
#include "absl/strings/internal/cord_rep_flat.h"
-#include "absl/strings/internal/cord_rep_ring.h"
-#include "absl/strings/internal/cordz_statistics.h"
-#include "absl/strings/internal/cordz_update_scope.h"
#include "absl/strings/internal/cordz_update_tracker.h"
#include "absl/strings/internal/resize_uninitialized.h"
+#include "absl/strings/match.h"
#include "absl/strings/str_cat.h"
-#include "absl/strings/str_format.h"
-#include "absl/strings/str_join.h"
#include "absl/strings/string_view.h"
+#include "absl/strings/strip.h"
+#include "absl/types/optional.h"
+#include "absl/types/span.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
using ::absl::cord_internal::CordRep;
-using ::absl::cord_internal::CordRepConcat;
+using ::absl::cord_internal::CordRepBtree;
+using ::absl::cord_internal::CordRepCrc;
using ::absl::cord_internal::CordRepExternal;
using ::absl::cord_internal::CordRepFlat;
-using ::absl::cord_internal::CordRepRing;
using ::absl::cord_internal::CordRepSubstring;
using ::absl::cord_internal::CordzUpdateTracker;
using ::absl::cord_internal::InlineData;
using ::absl::cord_internal::kMaxFlatLength;
using ::absl::cord_internal::kMinFlatLength;
-using ::absl::cord_internal::CONCAT;
-using ::absl::cord_internal::EXTERNAL;
-using ::absl::cord_internal::FLAT;
-using ::absl::cord_internal::RING;
-using ::absl::cord_internal::SUBSTRING;
-
using ::absl::cord_internal::kInlinedVectorSize;
using ::absl::cord_internal::kMaxBytesToCopy;
-constexpr uint64_t Fibonacci(unsigned char n, uint64_t a = 0, uint64_t b = 1) {
- return n == 0 ? a : Fibonacci(n - 1, b, a + b);
-}
+static void DumpNode(absl::Nonnull<CordRep*> rep, bool include_data,
+ absl::Nonnull<std::ostream*> os, int indent = 0);
+static bool VerifyNode(absl::Nonnull<CordRep*> root,
+ absl::Nonnull<CordRep*> start_node);
-static_assert(Fibonacci(63) == 6557470319842,
- "Fibonacci values computed incorrectly");
-
-// Minimum length required for a given depth tree -- a tree is considered
-// balanced if
-// length(t) >= min_length[depth(t)]
-// The root node depth is allowed to become twice as large to reduce rebalancing
-// for larger strings (see IsRootBalanced).
-static constexpr uint64_t min_length[] = {
- Fibonacci(2), Fibonacci(3), Fibonacci(4), Fibonacci(5),
- Fibonacci(6), Fibonacci(7), Fibonacci(8), Fibonacci(9),
- Fibonacci(10), Fibonacci(11), Fibonacci(12), Fibonacci(13),
- Fibonacci(14), Fibonacci(15), Fibonacci(16), Fibonacci(17),
- Fibonacci(18), Fibonacci(19), Fibonacci(20), Fibonacci(21),
- Fibonacci(22), Fibonacci(23), Fibonacci(24), Fibonacci(25),
- Fibonacci(26), Fibonacci(27), Fibonacci(28), Fibonacci(29),
- Fibonacci(30), Fibonacci(31), Fibonacci(32), Fibonacci(33),
- Fibonacci(34), Fibonacci(35), Fibonacci(36), Fibonacci(37),
- Fibonacci(38), Fibonacci(39), Fibonacci(40), Fibonacci(41),
- Fibonacci(42), Fibonacci(43), Fibonacci(44), Fibonacci(45),
- Fibonacci(46), Fibonacci(47),
- 0xffffffffffffffffull, // Avoid overflow
-};
-
-static const int kMinLengthSize = ABSL_ARRAYSIZE(min_length);
-
-static inline bool cord_ring_enabled() {
- return cord_internal::cord_ring_buffer_enabled.load(
- std::memory_order_relaxed);
-}
-
-static inline bool IsRootBalanced(CordRep* node) {
- if (node->tag != CONCAT) {
- return true;
- } else if (node->concat()->depth() <= 15) {
- return true;
- } else if (node->concat()->depth() > kMinLengthSize) {
- return false;
- } else {
- // Allow depth to become twice as large as implied by fibonacci rule to
- // reduce rebalancing for larger strings.
- return (node->length >= min_length[node->concat()->depth() / 2]);
- }
-}
-
-static CordRep* Rebalance(CordRep* node);
-static void DumpNode(CordRep* rep, bool include_data, std::ostream* os,
- int indent = 0);
-static bool VerifyNode(CordRep* root, CordRep* start_node,
- bool full_validation);
-
-static inline CordRep* VerifyTree(CordRep* node) {
- // Verification is expensive, so only do it in debug mode.
- // Even in debug mode we normally do only light validation.
- // If you are debugging Cord itself, you should define the
- // macro EXTRA_CORD_VALIDATION, e.g. by adding
- // --copt=-DEXTRA_CORD_VALIDATION to the blaze line.
-#ifdef EXTRA_CORD_VALIDATION
- assert(node == nullptr || VerifyNode(node, node, /*full_validation=*/true));
-#else // EXTRA_CORD_VALIDATION
- assert(node == nullptr || VerifyNode(node, node, /*full_validation=*/false));
-#endif // EXTRA_CORD_VALIDATION
+static inline absl::Nullable<CordRep*> VerifyTree(
+ absl::Nullable<CordRep*> node) {
+ assert(node == nullptr || VerifyNode(node, node));
static_cast<void>(&VerifyNode);
-
return node;
}
-// Return the depth of a node
-static int Depth(const CordRep* rep) {
- if (rep->tag == CONCAT) {
- return rep->concat()->depth();
- } else {
- return 0;
- }
-}
-
-static void SetConcatChildren(CordRepConcat* concat, CordRep* left,
- CordRep* right) {
- concat->left = left;
- concat->right = right;
-
- concat->length = left->length + right->length;
- concat->set_depth(1 + std::max(Depth(left), Depth(right)));
-}
-
-// Create a concatenation of the specified nodes.
-// Does not change the refcounts of "left" and "right".
-// The returned node has a refcount of 1.
-static CordRep* RawConcat(CordRep* left, CordRep* right) {
- // Avoid making degenerate concat nodes (one child is empty)
- if (left == nullptr) return right;
- if (right == nullptr) return left;
- if (left->length == 0) {
- CordRep::Unref(left);
- return right;
- }
- if (right->length == 0) {
- CordRep::Unref(right);
- return left;
- }
-
- CordRepConcat* rep = new CordRepConcat();
- rep->tag = CONCAT;
- SetConcatChildren(rep, left, right);
-
- return rep;
-}
-
-static CordRep* Concat(CordRep* left, CordRep* right) {
- CordRep* rep = RawConcat(left, right);
- if (rep != nullptr && !IsRootBalanced(rep)) {
- rep = Rebalance(rep);
- }
- return VerifyTree(rep);
-}
-
-// Make a balanced tree out of an array of leaf nodes.
-static CordRep* MakeBalancedTree(CordRep** reps, size_t n) {
- // Make repeated passes over the array, merging adjacent pairs
- // until we are left with just a single node.
- while (n > 1) {
- size_t dst = 0;
- for (size_t src = 0; src < n; src += 2) {
- if (src + 1 < n) {
- reps[dst] = Concat(reps[src], reps[src + 1]);
- } else {
- reps[dst] = reps[src];
- }
- dst++;
- }
- n = dst;
- }
-
- return reps[0];
-}
-
-static CordRepFlat* CreateFlat(const char* data, size_t length,
- size_t alloc_hint) {
+static absl::Nonnull<CordRepFlat*> CreateFlat(absl::Nonnull<const char*> data,
+ size_t length,
+ size_t alloc_hint) {
CordRepFlat* flat = CordRepFlat::New(length + alloc_hint);
flat->length = length;
memcpy(flat->Data(), data, length);
return flat;
}
-// Creates a new flat or ringbuffer out of the specified array.
+// Creates a new flat or Btree out of the specified array.
// The returned node has a refcount of 1.
-static CordRep* RingNewTree(const char* data, size_t length,
- size_t alloc_hint) {
+static absl::Nonnull<CordRep*> NewBtree(absl::Nonnull<const char*> data,
+ size_t length, size_t alloc_hint) {
if (length <= kMaxFlatLength) {
return CreateFlat(data, length, alloc_hint);
}
CordRepFlat* flat = CreateFlat(data, kMaxFlatLength, 0);
data += kMaxFlatLength;
length -= kMaxFlatLength;
- size_t extra = (length - 1) / kMaxFlatLength + 1;
- auto* root = CordRepRing::Create(flat, extra);
- return CordRepRing::Append(root, {data, length}, alloc_hint);
+ auto* root = CordRepBtree::Create(flat);
+ return CordRepBtree::Append(root, {data, length}, alloc_hint);
}
// Create a new tree out of the specified array.
// The returned node has a refcount of 1.
-static CordRep* NewTree(const char* data, size_t length, size_t alloc_hint) {
+static absl::Nullable<CordRep*> NewTree(absl::Nullable<const char*> data,
+ size_t length, size_t alloc_hint) {
if (length == 0) return nullptr;
- if (cord_ring_enabled()) {
- return RingNewTree(data, length, alloc_hint);
- }
- absl::FixedArray<CordRep*> reps((length - 1) / kMaxFlatLength + 1);
- size_t n = 0;
- do {
- const size_t len = std::min(length, kMaxFlatLength);
- CordRepFlat* rep = CordRepFlat::New(len + alloc_hint);
- rep->length = len;
- memcpy(rep->Data(), data, len);
- reps[n++] = VerifyTree(rep);
- data += len;
- length -= len;
- } while (length != 0);
- return MakeBalancedTree(reps.data(), n);
+ return NewBtree(data, length, alloc_hint);
}
namespace cord_internal {
-void InitializeCordRepExternal(absl::string_view data, CordRepExternal* rep) {
+void InitializeCordRepExternal(absl::string_view data,
+ absl::Nonnull<CordRepExternal*> rep) {
assert(!data.empty());
rep->length = data.size();
rep->tag = EXTERNAL;
@@ -266,27 +131,11 @@
} // namespace cord_internal
-static CordRep* NewSubstring(CordRep* child, size_t offset, size_t length) {
- // Never create empty substring nodes
- if (length == 0) {
- CordRep::Unref(child);
- return nullptr;
- } else {
- CordRepSubstring* rep = new CordRepSubstring();
- assert((offset + length) <= child->length);
- rep->length = length;
- rep->tag = SUBSTRING;
- rep->start = offset;
- rep->child = child;
- return VerifyTree(rep);
- }
-}
-
// Creates a CordRep from the provided string. If the string is large enough,
// and not wasteful, we move the string into an external cord rep, preserving
// the already allocated string contents.
// Requires the provided string length to be larger than `kMaxInline`.
-static CordRep* CordRepFromString(std::string&& src) {
+static absl::Nonnull<CordRep*> CordRepFromString(std::string&& src) {
assert(src.length() > cord_internal::kMaxInline);
if (
// String is short: copy data to avoid external block overhead.
@@ -314,17 +163,17 @@
// --------------------------------------------------------------------
// Cord::InlineRep functions
+#ifdef ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL
constexpr unsigned char Cord::InlineRep::kMaxInline;
+#endif
-inline void Cord::InlineRep::set_data(const char* data, size_t n,
- bool nullify_tail) {
+inline void Cord::InlineRep::set_data(absl::Nonnull<const char*> data,
+ size_t n) {
static_assert(kMaxInline == 15, "set_data is hard-coded for a length of 15");
-
- cord_internal::SmallMemmove(data_.as_chars(), data, n, nullify_tail);
- set_inline_size(n);
+ data_.set_inline_data(data, n);
}
-inline char* Cord::InlineRep::set_data(size_t n) {
+inline absl::Nonnull<char*> Cord::InlineRep::set_data(size_t n) {
assert(n <= kMaxInline);
ResetToEmpty();
set_inline_size(n);
@@ -337,7 +186,7 @@
assert(tag >= n);
tag -= n;
memset(data_.as_chars() + tag, 0, n);
- set_inline_size(static_cast<char>(tag));
+ set_inline_size(tag);
}
inline void Cord::InlineRep::remove_prefix(size_t n) {
@@ -346,39 +195,37 @@
reduce_size(n);
}
-// Returns `rep` converted into a CordRepRing.
-// Directly returns `rep` if `rep` is already a CordRepRing.
-static CordRepRing* ForceRing(CordRep* rep, size_t extra) {
- return (rep->tag == RING) ? rep->ring() : CordRepRing::Create(rep, extra);
+// Returns `rep` converted into a CordRepBtree.
+// Directly returns `rep` if `rep` is already a CordRepBtree.
+static absl::Nonnull<CordRepBtree*> ForceBtree(CordRep* rep) {
+ return rep->IsBtree()
+ ? rep->btree()
+ : CordRepBtree::Create(cord_internal::RemoveCrcNode(rep));
}
-void Cord::InlineRep::AppendTreeToInlined(CordRep* tree,
+void Cord::InlineRep::AppendTreeToInlined(absl::Nonnull<CordRep*> tree,
MethodIdentifier method) {
assert(!is_tree());
if (!data_.is_empty()) {
CordRepFlat* flat = MakeFlatWithExtraCapacity(0);
- if (cord_ring_enabled()) {
- tree = CordRepRing::Append(CordRepRing::Create(flat, 1), tree);
- } else {
- tree = Concat(flat, tree);
- }
+ tree = CordRepBtree::Append(CordRepBtree::Create(flat), tree);
}
EmplaceTree(tree, method);
}
-void Cord::InlineRep::AppendTreeToTree(CordRep* tree, MethodIdentifier method) {
+void Cord::InlineRep::AppendTreeToTree(absl::Nonnull<CordRep*> tree,
+ MethodIdentifier method) {
assert(is_tree());
const CordzUpdateScope scope(data_.cordz_info(), method);
- if (cord_ring_enabled()) {
- tree = CordRepRing::Append(ForceRing(data_.as_tree(), 1), tree);
- } else {
- tree = Concat(data_.as_tree(), tree);
- }
+ tree = CordRepBtree::Append(ForceBtree(data_.as_tree()), tree);
SetTree(tree, scope);
}
-void Cord::InlineRep::AppendTree(CordRep* tree, MethodIdentifier method) {
- if (tree == nullptr) return;
+void Cord::InlineRep::AppendTree(absl::Nonnull<CordRep*> tree,
+ MethodIdentifier method) {
+ assert(tree != nullptr);
+ assert(tree->length != 0);
+ assert(!tree->IsCrc());
if (data_.is_tree()) {
AppendTreeToTree(tree, method);
} else {
@@ -386,34 +233,29 @@
}
}
-void Cord::InlineRep::PrependTreeToInlined(CordRep* tree,
+void Cord::InlineRep::PrependTreeToInlined(absl::Nonnull<CordRep*> tree,
MethodIdentifier method) {
assert(!is_tree());
if (!data_.is_empty()) {
CordRepFlat* flat = MakeFlatWithExtraCapacity(0);
- if (cord_ring_enabled()) {
- tree = CordRepRing::Prepend(CordRepRing::Create(flat, 1), tree);
- } else {
- tree = Concat(tree, flat);
- }
+ tree = CordRepBtree::Prepend(CordRepBtree::Create(flat), tree);
}
EmplaceTree(tree, method);
}
-void Cord::InlineRep::PrependTreeToTree(CordRep* tree,
+void Cord::InlineRep::PrependTreeToTree(absl::Nonnull<CordRep*> tree,
MethodIdentifier method) {
assert(is_tree());
const CordzUpdateScope scope(data_.cordz_info(), method);
- if (cord_ring_enabled()) {
- tree = CordRepRing::Prepend(ForceRing(data_.as_tree(), 1), tree);
- } else {
- tree = Concat(tree, data_.as_tree());
- }
+ tree = CordRepBtree::Prepend(ForceBtree(data_.as_tree()), tree);
SetTree(tree, scope);
}
-void Cord::InlineRep::PrependTree(CordRep* tree, MethodIdentifier method) {
+void Cord::InlineRep::PrependTree(absl::Nonnull<CordRep*> tree,
+ MethodIdentifier method) {
assert(tree != nullptr);
+ assert(tree->length != 0);
+ assert(!tree->IsCrc());
if (data_.is_tree()) {
PrependTreeToTree(tree, method);
} else {
@@ -425,10 +267,11 @@
// suitable leaf is found, the function will update the length field for all
// nodes to account for the size increase. The append region address will be
// written to region and the actual size increase will be written to size.
-static inline bool PrepareAppendRegion(CordRep* root, char** region,
- size_t* size, size_t max_length) {
- if (root->tag == RING && root->refcount.IsOne()) {
- Span<char> span = root->ring()->GetAppendBuffer(max_length);
+static inline bool PrepareAppendRegion(
+ absl::Nonnull<CordRep*> root, absl::Nonnull<absl::Nullable<char*>*> region,
+ absl::Nonnull<size_t*> size, size_t max_length) {
+ if (root->IsBtree() && root->refcount.IsOne()) {
+ Span<char> span = root->btree()->GetAppendBuffer(max_length);
if (!span.empty()) {
*region = span.data();
*size = span.size();
@@ -436,13 +279,8 @@
}
}
- // Search down the right-hand path for a non-full FLAT node.
CordRep* dst = root;
- while (dst->tag == CONCAT && dst->refcount.IsOne()) {
- dst = dst->concat()->right;
- }
-
- if (dst->tag < FLAT || !dst->refcount.IsOne()) {
+ if (!dst->IsFlat() || !dst->refcount.IsOne()) {
*region = nullptr;
*size = 0;
return false;
@@ -456,12 +294,7 @@
return false;
}
- size_t size_increase = std::min(capacity - in_use, max_length);
-
- // We need to update the length fields for all nodes, including the leaf node.
- for (CordRep* rep = root; rep != dst; rep = rep->concat()->right) {
- rep->length += size_increase;
- }
+ const size_t size_increase = std::min(capacity - in_use, max_length);
dst->length += size_increase;
*region = dst->flat()->Data() + in_use;
@@ -469,64 +302,6 @@
return true;
}
-template <bool has_length>
-void Cord::InlineRep::GetAppendRegion(char** region, size_t* size,
- size_t length) {
- auto constexpr method = CordzUpdateTracker::kGetAppendRegion;
-
- CordRep* root = tree();
- size_t sz = root ? root->length : inline_size();
- if (root == nullptr) {
- size_t available = kMaxInline - sz;
- if (available >= (has_length ? length : 1)) {
- *region = data_.as_chars() + sz;
- *size = has_length ? length : available;
- set_inline_size(has_length ? sz + length : kMaxInline);
- return;
- }
- }
-
- size_t extra = has_length ? length : (std::max)(sz, kMinFlatLength);
- CordRep* rep = root ? root : MakeFlatWithExtraCapacity(extra);
- CordzUpdateScope scope(root ? data_.cordz_info() : nullptr, method);
- if (PrepareAppendRegion(rep, region, size, length)) {
- CommitTree(root, rep, scope, method);
- return;
- }
-
- // Allocate new node.
- CordRepFlat* new_node = CordRepFlat::New(extra);
- new_node->length = std::min(new_node->Capacity(), length);
- *region = new_node->Data();
- *size = new_node->length;
-
- if (cord_ring_enabled()) {
- rep = CordRepRing::Append(ForceRing(rep, 1), new_node);
- } else {
- rep = Concat(rep, new_node);
- }
- CommitTree(root, rep, scope, method);
-}
-
-// If the rep is a leaf, this will increment the value at total_mem_usage and
-// will return true.
-static bool RepMemoryUsageLeaf(const CordRep* rep, size_t* total_mem_usage) {
- if (rep->tag >= FLAT) {
- *total_mem_usage += rep->flat()->AllocatedSize();
- return true;
- }
- if (rep->tag == EXTERNAL) {
- // We don't know anything about the embedded / bound data, but we can safely
- // assume it is 'at least' a word / pointer to data. In the future we may
- // choose to use the 'data' byte as a tag to identify the types of some
- // well-known externals, such as a std::string instance.
- *total_mem_usage +=
- sizeof(cord_internal::CordRepExternalImpl<intptr_t>) + rep->length;
- return true;
- }
- return false;
-}
-
void Cord::InlineRep::AssignSlow(const Cord::InlineRep& src) {
assert(&src != this);
assert(is_tree() || src.is_tree());
@@ -563,7 +338,7 @@
: contents_(InlineData::kDefaultInit) {
const size_t n = src.size();
if (n <= InlineRep::kMaxInline) {
- contents_.set_data(src.data(), n, true);
+ contents_.set_data(src.data(), n);
} else {
CordRep* rep = NewTree(src.data(), n, 0);
contents_.EmplaceTree(rep, method);
@@ -573,7 +348,7 @@
template <typename T, Cord::EnableIfString<T>>
Cord::Cord(T&& src) : contents_(InlineData::kDefaultInit) {
if (src.size() <= InlineRep::kMaxInline) {
- contents_.set_data(src.data(), src.size(), true);
+ contents_.set_data(src.data(), src.size());
} else {
CordRep* rep = CordRepFromString(std::forward<T>(src));
contents_.EmplaceTree(rep, CordzUpdateTracker::kConstructorString);
@@ -624,13 +399,13 @@
// - MaybeUntrackCord must be called before set_data() clobbers cordz_info.
// - set_data() must be called before Unref(tree) as it may reference tree.
if (tree != nullptr) CordzInfo::MaybeUntrackCord(contents_.cordz_info());
- contents_.set_data(data, length, true);
+ contents_.set_data(data, length);
if (tree != nullptr) CordRep::Unref(tree);
return *this;
}
if (tree != nullptr) {
CordzUpdateScope scope(contents_.cordz_info(), method);
- if (tree->tag >= FLAT && tree->flat()->Capacity() >= length &&
+ if (tree->IsFlat() && tree->flat()->Capacity() >= length &&
tree->refcount.IsOne()) {
// Copy in place if the existing FLAT node is reusable.
memmove(tree->flat()->Data(), data, length);
@@ -650,6 +425,7 @@
// we keep it here to make diffs easier.
void Cord::InlineRep::AppendArray(absl::string_view src,
MethodIdentifier method) {
+ MaybeRemoveEmptyCrcNode();
if (src.empty()) return; // memcpy(_, nullptr, 0) is undefined.
size_t appended = 0;
@@ -657,6 +433,7 @@
const CordRep* const root = rep;
CordzUpdateScope scope(root ? cordz_info() : nullptr, method);
if (root != nullptr) {
+ rep = cord_internal::RemoveCrcNode(rep);
char* region;
if (PrepareAppendRegion(rep, ®ion, &appended, src.size())) {
memcpy(region, src.data(), appended);
@@ -666,18 +443,15 @@
size_t inline_length = inline_size();
if (src.size() <= kMaxInline - inline_length) {
// Append new data to embedded array
- memcpy(data_.as_chars() + inline_length, src.data(), src.size());
set_inline_size(inline_length + src.size());
+ memcpy(data_.as_chars() + inline_length, src.data(), src.size());
return;
}
- // Note: we don't concern ourselves if src aliases data stored in the
- // inlined data of 'this', as we update the InlineData only at the end.
- // We are going from an inline size to beyond inline size. Make the new size
- // either double the inlined size, or the added size + 10%.
- const size_t size1 = inline_length * 2 + src.size();
- const size_t size2 = inline_length + src.size() / 10;
- rep = CordRepFlat::New(std::max<size_t>(size1, size2));
+ // Allocate flat to be a perfect fit on first append exceeding inlined size.
+ // Subsequent growth will use amortized growth until we reach maximum flat
+ // size.
+ rep = CordRepFlat::New(inline_length + src.size());
appended = std::min(src.size(), rep->flat()->Capacity() - inline_length);
memcpy(rep->flat()->Data(), data_.as_chars(), inline_length);
memcpy(rep->flat()->Data() + inline_length, src.data(), appended);
@@ -690,33 +464,19 @@
return;
}
- if (cord_ring_enabled()) {
- rep = ForceRing(rep, (src.size() - 1) / kMaxFlatLength + 1);
- rep = CordRepRing::Append(rep->ring(), src);
- } else {
- // Use new block(s) for any remaining bytes that were not handled above.
- // Alloc extra memory only if the right child of the root of the new tree
- // is going to be a FLAT node, which will permit further inplace appends.
- size_t length = src.size();
- if (src.size() < kMaxFlatLength) {
- // The new length is either
- // - old size + 10%
- // - old_size + src.size()
- // This will cause a reasonable conservative step-up in size that is
- // still large enough to avoid excessive amounts of small fragments
- // being added.
- length = std::max<size_t>(rep->length / 10, src.size());
- }
- rep = Concat(rep, NewTree(src.data(), src.size(), length - src.size()));
- }
+ // TODO(b/192061034): keep legacy 10% growth rate: consider other rates.
+ rep = ForceBtree(rep);
+ const size_t min_growth = std::max<size_t>(rep->length / 10, src.size());
+ rep = CordRepBtree::Append(rep->btree(), src, min_growth - src.size());
+
CommitTree(root, rep, scope, method);
}
-inline CordRep* Cord::TakeRep() const& {
+inline absl::Nonnull<CordRep*> Cord::TakeRep() const& {
return CordRep::Ref(contents_.tree());
}
-inline CordRep* Cord::TakeRep() && {
+inline absl::Nonnull<CordRep*> Cord::TakeRep() && {
CordRep* rep = contents_.tree();
contents_.clear();
return rep;
@@ -725,11 +485,16 @@
template <typename C>
inline void Cord::AppendImpl(C&& src) {
auto constexpr method = CordzUpdateTracker::kAppendCord;
+
+ contents_.MaybeRemoveEmptyCrcNode();
+ if (src.empty()) return;
+
if (empty()) {
// Since destination is empty, we can avoid allocating a node,
if (src.contents_.is_tree()) {
// by taking the tree directly
- CordRep* rep = std::forward<C>(src).TakeRep();
+ CordRep* rep =
+ cord_internal::RemoveCrcNode(std::forward<C>(src).TakeRep());
contents_.EmplaceTree(rep, method);
} else {
// or copying over inline data
@@ -747,7 +512,7 @@
contents_.AppendArray({src.contents_.data(), src_size}, method);
return;
}
- if (src_tree->tag >= FLAT) {
+ if (src_tree->IsFlat()) {
// src tree just has one flat node.
contents_.AppendArray({src_tree->flat()->Data(), src_size}, method);
return;
@@ -765,10 +530,56 @@
}
// Guaranteed to be a tree (kMaxBytesToCopy > kInlinedSize)
- CordRep* rep = std::forward<C>(src).TakeRep();
+ CordRep* rep = cord_internal::RemoveCrcNode(std::forward<C>(src).TakeRep());
contents_.AppendTree(rep, CordzUpdateTracker::kAppendCord);
}
+static CordRep::ExtractResult ExtractAppendBuffer(absl::Nonnull<CordRep*> rep,
+ size_t min_capacity) {
+ switch (rep->tag) {
+ case cord_internal::BTREE:
+ return CordRepBtree::ExtractAppendBuffer(rep->btree(), min_capacity);
+ default:
+ if (rep->IsFlat() && rep->refcount.IsOne() &&
+ rep->flat()->Capacity() - rep->length >= min_capacity) {
+ return {nullptr, rep};
+ }
+ return {rep, nullptr};
+ }
+}
+
+static CordBuffer CreateAppendBuffer(InlineData& data, size_t block_size,
+ size_t capacity) {
+ // Watch out for overflow, people can ask for size_t::max().
+ const size_t size = data.inline_size();
+ const size_t max_capacity = std::numeric_limits<size_t>::max() - size;
+ capacity = (std::min)(max_capacity, capacity) + size;
+ CordBuffer buffer =
+ block_size ? CordBuffer::CreateWithCustomLimit(block_size, capacity)
+ : CordBuffer::CreateWithDefaultLimit(capacity);
+ cord_internal::SmallMemmove(buffer.data(), data.as_chars(), size);
+ buffer.SetLength(size);
+ data = {};
+ return buffer;
+}
+
+CordBuffer Cord::GetAppendBufferSlowPath(size_t block_size, size_t capacity,
+ size_t min_capacity) {
+ auto constexpr method = CordzUpdateTracker::kGetAppendBuffer;
+ CordRep* tree = contents_.tree();
+ if (tree != nullptr) {
+ CordzUpdateScope scope(contents_.cordz_info(), method);
+ CordRep::ExtractResult result = ExtractAppendBuffer(tree, min_capacity);
+ if (result.extracted != nullptr) {
+ contents_.SetTreeOrEmpty(result.tree, scope);
+ return CordBuffer(result.extracted->flat());
+ }
+ return block_size ? CordBuffer::CreateWithCustomLimit(block_size, capacity)
+ : CordBuffer::CreateWithDefaultLimit(capacity);
+ }
+ return CreateAppendBuffer(contents_.data_, block_size, capacity);
+}
+
void Cord::Append(const Cord& src) { AppendImpl(src); }
void Cord::Append(Cord&& src) { AppendImpl(std::move(src)); }
@@ -786,10 +597,14 @@
template void Cord::Append(std::string&& src);
void Cord::Prepend(const Cord& src) {
+ contents_.MaybeRemoveEmptyCrcNode();
+ if (src.empty()) return;
+
CordRep* src_tree = src.contents_.tree();
if (src_tree != nullptr) {
CordRep::Ref(src_tree);
- contents_.PrependTree(src_tree, CordzUpdateTracker::kPrependCord);
+ contents_.PrependTree(cord_internal::RemoveCrcNode(src_tree),
+ CordzUpdateTracker::kPrependCord);
return;
}
@@ -798,22 +613,51 @@
return Prepend(src_contents);
}
-void Cord::Prepend(absl::string_view src) {
+void Cord::PrependArray(absl::string_view src, MethodIdentifier method) {
+ contents_.MaybeRemoveEmptyCrcNode();
if (src.empty()) return; // memcpy(_, nullptr, 0) is undefined.
+
if (!contents_.is_tree()) {
size_t cur_size = contents_.inline_size();
if (cur_size + src.size() <= InlineRep::kMaxInline) {
// Use embedded storage.
- char data[InlineRep::kMaxInline + 1] = {0};
- memcpy(data, src.data(), src.size());
- memcpy(data + src.size(), contents_.data(), cur_size);
- memcpy(contents_.data_.as_chars(), data, InlineRep::kMaxInline + 1);
- contents_.set_inline_size(cur_size + src.size());
+ InlineData data;
+ data.set_inline_size(cur_size + src.size());
+ memcpy(data.as_chars(), src.data(), src.size());
+ memcpy(data.as_chars() + src.size(), contents_.data(), cur_size);
+ contents_.data_ = data;
return;
}
}
CordRep* rep = NewTree(src.data(), src.size(), 0);
- contents_.PrependTree(rep, CordzUpdateTracker::kPrependString);
+ contents_.PrependTree(rep, method);
+}
+
+void Cord::AppendPrecise(absl::string_view src, MethodIdentifier method) {
+ assert(!src.empty());
+ assert(src.size() <= cord_internal::kMaxFlatLength);
+ if (contents_.remaining_inline_capacity() >= src.size()) {
+ const size_t inline_length = contents_.inline_size();
+ contents_.set_inline_size(inline_length + src.size());
+ memcpy(contents_.data_.as_chars() + inline_length, src.data(), src.size());
+ } else {
+ contents_.AppendTree(CordRepFlat::Create(src), method);
+ }
+}
+
+void Cord::PrependPrecise(absl::string_view src, MethodIdentifier method) {
+ assert(!src.empty());
+ assert(src.size() <= cord_internal::kMaxFlatLength);
+ if (contents_.remaining_inline_capacity() >= src.size()) {
+ const size_t cur_size = contents_.inline_size();
+ InlineData data;
+ data.set_inline_size(cur_size + src.size());
+ memcpy(data.as_chars(), src.data(), src.size());
+ memcpy(data.as_chars() + src.size(), contents_.data(), cur_size);
+ contents_.data_ = data;
+ } else {
+ contents_.PrependTree(CordRepFlat::Create(src), method);
+ }
}
template <typename T, Cord::EnableIfString<T>>
@@ -828,107 +672,32 @@
template void Cord::Prepend(std::string&& src);
-static CordRep* RemovePrefixFrom(CordRep* node, size_t n) {
- if (n >= node->length) return nullptr;
- if (n == 0) return CordRep::Ref(node);
- absl::InlinedVector<CordRep*, kInlinedVectorSize> rhs_stack;
-
- while (node->tag == CONCAT) {
- assert(n <= node->length);
- if (n < node->concat()->left->length) {
- // Push right to stack, descend left.
- rhs_stack.push_back(node->concat()->right);
- node = node->concat()->left;
- } else {
- // Drop left, descend right.
- n -= node->concat()->left->length;
- node = node->concat()->right;
- }
- }
- assert(n <= node->length);
-
- if (n == 0) {
- CordRep::Ref(node);
- } else {
- size_t start = n;
- size_t len = node->length - n;
- if (node->tag == SUBSTRING) {
- // Consider in-place update of node, similar to in RemoveSuffixFrom().
- start += node->substring()->start;
- node = node->substring()->child;
- }
- node = NewSubstring(CordRep::Ref(node), start, len);
- }
- while (!rhs_stack.empty()) {
- node = Concat(node, CordRep::Ref(rhs_stack.back()));
- rhs_stack.pop_back();
- }
- return node;
-}
-
-// RemoveSuffixFrom() is very similar to RemovePrefixFrom(), with the
-// exception that removing a suffix has an optimization where a node may be
-// edited in place iff that node and all its ancestors have a refcount of 1.
-static CordRep* RemoveSuffixFrom(CordRep* node, size_t n) {
- if (n >= node->length) return nullptr;
- if (n == 0) return CordRep::Ref(node);
- absl::InlinedVector<CordRep*, kInlinedVectorSize> lhs_stack;
- bool inplace_ok = node->refcount.IsOne();
-
- while (node->tag == CONCAT) {
- assert(n <= node->length);
- if (n < node->concat()->right->length) {
- // Push left to stack, descend right.
- lhs_stack.push_back(node->concat()->left);
- node = node->concat()->right;
- } else {
- // Drop right, descend left.
- n -= node->concat()->right->length;
- node = node->concat()->left;
- }
- inplace_ok = inplace_ok && node->refcount.IsOne();
- }
- assert(n <= node->length);
-
- if (n == 0) {
- CordRep::Ref(node);
- } else if (inplace_ok && node->tag != EXTERNAL) {
- // Consider making a new buffer if the current node capacity is much
- // larger than the new length.
- CordRep::Ref(node);
- node->length -= n;
- } else {
- size_t start = 0;
- size_t len = node->length - n;
- if (node->tag == SUBSTRING) {
- start = node->substring()->start;
- node = node->substring()->child;
- }
- node = NewSubstring(CordRep::Ref(node), start, len);
- }
- while (!lhs_stack.empty()) {
- node = Concat(CordRep::Ref(lhs_stack.back()), node);
- lhs_stack.pop_back();
- }
- return node;
-}
-
void Cord::RemovePrefix(size_t n) {
ABSL_INTERNAL_CHECK(n <= size(),
absl::StrCat("Requested prefix size ", n,
" exceeds Cord's size ", size()));
+ contents_.MaybeRemoveEmptyCrcNode();
CordRep* tree = contents_.tree();
if (tree == nullptr) {
contents_.remove_prefix(n);
} else {
auto constexpr method = CordzUpdateTracker::kRemovePrefix;
CordzUpdateScope scope(contents_.cordz_info(), method);
- if (tree->tag == RING) {
- tree = CordRepRing::RemovePrefix(tree->ring(), n);
- } else {
- CordRep* newrep = RemovePrefixFrom(tree, n);
+ tree = cord_internal::RemoveCrcNode(tree);
+ if (n >= tree->length) {
CordRep::Unref(tree);
- tree = VerifyTree(newrep);
+ tree = nullptr;
+ } else if (tree->IsBtree()) {
+ CordRep* old = tree;
+ tree = tree->btree()->SubTree(n, tree->length - n);
+ CordRep::Unref(old);
+ } else if (tree->IsSubstring() && tree->refcount.IsOne()) {
+ tree->substring()->start += n;
+ tree->length -= n;
+ } else {
+ CordRep* rep = CordRepSubstring::Substring(tree, n, tree->length - n);
+ CordRep::Unref(tree);
+ tree = rep;
}
contents_.SetTreeOrEmpty(tree, scope);
}
@@ -938,74 +707,31 @@
ABSL_INTERNAL_CHECK(n <= size(),
absl::StrCat("Requested suffix size ", n,
" exceeds Cord's size ", size()));
+ contents_.MaybeRemoveEmptyCrcNode();
CordRep* tree = contents_.tree();
if (tree == nullptr) {
contents_.reduce_size(n);
} else {
auto constexpr method = CordzUpdateTracker::kRemoveSuffix;
CordzUpdateScope scope(contents_.cordz_info(), method);
- if (tree->tag == RING) {
- tree = CordRepRing::RemoveSuffix(tree->ring(), n);
- } else {
- CordRep* newrep = RemoveSuffixFrom(tree, n);
+ tree = cord_internal::RemoveCrcNode(tree);
+ if (n >= tree->length) {
CordRep::Unref(tree);
- tree = VerifyTree(newrep);
+ tree = nullptr;
+ } else if (tree->IsBtree()) {
+ tree = CordRepBtree::RemoveSuffix(tree->btree(), n);
+ } else if (!tree->IsExternal() && tree->refcount.IsOne()) {
+ assert(tree->IsFlat() || tree->IsSubstring());
+ tree->length -= n;
+ } else {
+ CordRep* rep = CordRepSubstring::Substring(tree, 0, tree->length - n);
+ CordRep::Unref(tree);
+ tree = rep;
}
contents_.SetTreeOrEmpty(tree, scope);
}
}
-// Work item for NewSubRange().
-struct SubRange {
- SubRange(CordRep* a_node, size_t a_pos, size_t a_n)
- : node(a_node), pos(a_pos), n(a_n) {}
- CordRep* node; // nullptr means concat last 2 results.
- size_t pos;
- size_t n;
-};
-
-static CordRep* NewSubRange(CordRep* node, size_t pos, size_t n) {
- absl::InlinedVector<CordRep*, kInlinedVectorSize> results;
- absl::InlinedVector<SubRange, kInlinedVectorSize> todo;
- todo.push_back(SubRange(node, pos, n));
- do {
- const SubRange& sr = todo.back();
- node = sr.node;
- pos = sr.pos;
- n = sr.n;
- todo.pop_back();
-
- if (node == nullptr) {
- assert(results.size() >= 2);
- CordRep* right = results.back();
- results.pop_back();
- CordRep* left = results.back();
- results.pop_back();
- results.push_back(Concat(left, right));
- } else if (pos == 0 && n == node->length) {
- results.push_back(CordRep::Ref(node));
- } else if (node->tag != CONCAT) {
- if (node->tag == SUBSTRING) {
- pos += node->substring()->start;
- node = node->substring()->child;
- }
- results.push_back(NewSubstring(CordRep::Ref(node), pos, n));
- } else if (pos + n <= node->concat()->left->length) {
- todo.push_back(SubRange(node->concat()->left, pos, n));
- } else if (pos >= node->concat()->left->length) {
- pos -= node->concat()->left->length;
- todo.push_back(SubRange(node->concat()->right, pos, n));
- } else {
- size_t left_n = node->concat()->left->length - pos;
- todo.push_back(SubRange(nullptr, 0, 0)); // Concat()
- todo.push_back(SubRange(node->concat()->right, 0, n - left_n));
- todo.push_back(SubRange(node->concat()->left, pos, left_n));
- }
- } while (!todo.empty());
- assert(results.size() == 1);
- return results[0];
-}
-
Cord Cord::Subcord(size_t pos, size_t new_size) const {
Cord sub_cord;
size_t length = size();
@@ -1015,13 +741,12 @@
CordRep* tree = contents_.tree();
if (tree == nullptr) {
- // sub_cord is newly constructed, no need to re-zero-out the tail of
- // contents_ memory.
- sub_cord.contents_.set_data(contents_.data() + pos, new_size, false);
+ sub_cord.contents_.set_data(contents_.data() + pos, new_size);
return sub_cord;
}
if (new_size <= InlineRep::kMaxInline) {
+ sub_cord.contents_.set_inline_size(new_size);
char* dest = sub_cord.contents_.data_.as_chars();
Cord::ChunkIterator it = chunk_begin();
it.AdvanceBytes(pos);
@@ -1033,15 +758,14 @@
++it;
}
cord_internal::SmallMemmove(dest, it->data(), remaining_size);
- sub_cord.contents_.set_inline_size(new_size);
return sub_cord;
}
- if (tree->tag == RING) {
- CordRepRing* ring = CordRep::Ref(tree)->ring();
- tree = CordRepRing::SubRing(ring, pos, new_size);
+ tree = cord_internal::SkipCrcNode(tree);
+ if (tree->IsBtree()) {
+ tree = tree->btree()->SubTree(pos, new_size);
} else {
- tree = NewSubRange(tree, pos, new_size);
+ tree = CordRepSubstring::Substring(tree, pos, new_size);
}
sub_cord.contents_.EmplaceTree(tree, contents_.data_,
CordzUpdateTracker::kSubCord);
@@ -1049,146 +773,6 @@
}
// --------------------------------------------------------------------
-// Balancing
-
-class CordForest {
- public:
- explicit CordForest(size_t length)
- : root_length_(length), trees_(kMinLengthSize, nullptr) {}
-
- void Build(CordRep* cord_root) {
- std::vector<CordRep*> pending = {cord_root};
-
- while (!pending.empty()) {
- CordRep* node = pending.back();
- pending.pop_back();
- CheckNode(node);
- if (ABSL_PREDICT_FALSE(node->tag != CONCAT)) {
- AddNode(node);
- continue;
- }
-
- CordRepConcat* concat_node = node->concat();
- if (concat_node->depth() >= kMinLengthSize ||
- concat_node->length < min_length[concat_node->depth()]) {
- pending.push_back(concat_node->right);
- pending.push_back(concat_node->left);
-
- if (concat_node->refcount.IsOne()) {
- concat_node->left = concat_freelist_;
- concat_freelist_ = concat_node;
- } else {
- CordRep::Ref(concat_node->right);
- CordRep::Ref(concat_node->left);
- CordRep::Unref(concat_node);
- }
- } else {
- AddNode(node);
- }
- }
- }
-
- CordRep* ConcatNodes() {
- CordRep* sum = nullptr;
- for (auto* node : trees_) {
- if (node == nullptr) continue;
-
- sum = PrependNode(node, sum);
- root_length_ -= node->length;
- if (root_length_ == 0) break;
- }
- ABSL_INTERNAL_CHECK(sum != nullptr, "Failed to locate sum node");
- return VerifyTree(sum);
- }
-
- private:
- CordRep* AppendNode(CordRep* node, CordRep* sum) {
- return (sum == nullptr) ? node : MakeConcat(sum, node);
- }
-
- CordRep* PrependNode(CordRep* node, CordRep* sum) {
- return (sum == nullptr) ? node : MakeConcat(node, sum);
- }
-
- void AddNode(CordRep* node) {
- CordRep* sum = nullptr;
-
- // Collect together everything with which we will merge with node
- int i = 0;
- for (; node->length > min_length[i + 1]; ++i) {
- auto& tree_at_i = trees_[i];
-
- if (tree_at_i == nullptr) continue;
- sum = PrependNode(tree_at_i, sum);
- tree_at_i = nullptr;
- }
-
- sum = AppendNode(node, sum);
-
- // Insert sum into appropriate place in the forest
- for (; sum->length >= min_length[i]; ++i) {
- auto& tree_at_i = trees_[i];
- if (tree_at_i == nullptr) continue;
-
- sum = MakeConcat(tree_at_i, sum);
- tree_at_i = nullptr;
- }
-
- // min_length[0] == 1, which means sum->length >= min_length[0]
- assert(i > 0);
- trees_[i - 1] = sum;
- }
-
- // Make concat node trying to resue existing CordRepConcat nodes we
- // already collected in the concat_freelist_.
- CordRep* MakeConcat(CordRep* left, CordRep* right) {
- if (concat_freelist_ == nullptr) return RawConcat(left, right);
-
- CordRepConcat* rep = concat_freelist_;
- if (concat_freelist_->left == nullptr) {
- concat_freelist_ = nullptr;
- } else {
- concat_freelist_ = concat_freelist_->left->concat();
- }
- SetConcatChildren(rep, left, right);
-
- return rep;
- }
-
- static void CheckNode(CordRep* node) {
- ABSL_INTERNAL_CHECK(node->length != 0u, "");
- if (node->tag == CONCAT) {
- ABSL_INTERNAL_CHECK(node->concat()->left != nullptr, "");
- ABSL_INTERNAL_CHECK(node->concat()->right != nullptr, "");
- ABSL_INTERNAL_CHECK(node->length == (node->concat()->left->length +
- node->concat()->right->length),
- "");
- }
- }
-
- size_t root_length_;
-
- // use an inlined vector instead of a flat array to get bounds checking
- absl::InlinedVector<CordRep*, kInlinedVectorSize> trees_;
-
- // List of concat nodes we can re-use for Cord balancing.
- CordRepConcat* concat_freelist_ = nullptr;
-};
-
-static CordRep* Rebalance(CordRep* node) {
- VerifyTree(node);
- assert(node->tag == CONCAT);
-
- if (node->length == 0) {
- return nullptr;
- }
-
- CordForest forest(node->length);
- forest.Build(node);
- return forest.ConcatNodes();
-}
-
-// --------------------------------------------------------------------
// Comparators
namespace {
@@ -1197,8 +781,9 @@
return static_cast<int>(memcmp_res > 0) - static_cast<int>(memcmp_res < 0);
}
-int CompareChunks(absl::string_view* lhs, absl::string_view* rhs,
- size_t* size_to_compare) {
+int CompareChunks(absl::Nonnull<absl::string_view*> lhs,
+ absl::Nonnull<absl::string_view*> rhs,
+ absl::Nonnull<size_t*> size_to_compare) {
size_t compared_size = std::min(lhs->size(), rhs->size());
assert(*size_to_compare >= compared_size);
*size_to_compare -= compared_size;
@@ -1213,7 +798,7 @@
}
// This overload set computes comparison results from memcmp result. This
-// interface is used inside GenericCompare below. Differet implementations
+// interface is used inside GenericCompare below. Different implementations
// are specialized for int and bool. For int we clamp result to {-1, 0, 1}
// set. For bool we just interested in "value == 0".
template <typename ResultType>
@@ -1227,29 +812,29 @@
} // namespace
-// Helper routine. Locates the first flat chunk of the Cord without
-// initializing the iterator.
+// Helper routine. Locates the first flat or external chunk of the Cord without
+// initializing the iterator, and returns a string_view referencing the data.
inline absl::string_view Cord::InlineRep::FindFlatStartPiece() const {
if (!is_tree()) {
return absl::string_view(data_.as_chars(), data_.inline_size());
}
- CordRep* node = tree();
- if (node->tag >= FLAT) {
+ CordRep* node = cord_internal::SkipCrcNode(tree());
+ if (node->IsFlat()) {
return absl::string_view(node->flat()->Data(), node->length);
}
- if (node->tag == EXTERNAL) {
+ if (node->IsExternal()) {
return absl::string_view(node->external()->base, node->length);
}
- if (node->tag == RING) {
- return node->ring()->entry_data(node->ring()->head());
- }
-
- // Walk down the left branches until we hit a non-CONCAT node.
- while (node->tag == CONCAT) {
- node = node->concat()->left;
+ if (node->IsBtree()) {
+ CordRepBtree* tree = node->btree();
+ int height = tree->height();
+ while (--height >= 0) {
+ tree = tree->Edge(CordRepBtree::kFront)->btree();
+ }
+ return tree->Data(tree->begin());
}
// Get the child node if we encounter a SUBSTRING.
@@ -1257,23 +842,65 @@
size_t length = node->length;
assert(length != 0);
- if (node->tag == SUBSTRING) {
+ if (node->IsSubstring()) {
offset = node->substring()->start;
node = node->substring()->child;
}
- if (node->tag >= FLAT) {
+ if (node->IsFlat()) {
return absl::string_view(node->flat()->Data() + offset, length);
}
- assert((node->tag == EXTERNAL) && "Expect FLAT or EXTERNAL node here");
+ assert(node->IsExternal() && "Expect FLAT or EXTERNAL node here");
return absl::string_view(node->external()->base + offset, length);
}
+void Cord::SetCrcCordState(crc_internal::CrcCordState state) {
+ auto constexpr method = CordzUpdateTracker::kSetExpectedChecksum;
+ if (empty()) {
+ contents_.MaybeRemoveEmptyCrcNode();
+ CordRep* rep = CordRepCrc::New(nullptr, std::move(state));
+ contents_.EmplaceTree(rep, method);
+ } else if (!contents_.is_tree()) {
+ CordRep* rep = contents_.MakeFlatWithExtraCapacity(0);
+ rep = CordRepCrc::New(rep, std::move(state));
+ contents_.EmplaceTree(rep, method);
+ } else {
+ const CordzUpdateScope scope(contents_.data_.cordz_info(), method);
+ CordRep* rep = CordRepCrc::New(contents_.data_.as_tree(), std::move(state));
+ contents_.SetTree(rep, scope);
+ }
+}
+
+void Cord::SetExpectedChecksum(uint32_t crc) {
+ // Construct a CrcCordState with a single chunk.
+ crc_internal::CrcCordState state;
+ state.mutable_rep()->prefix_crc.push_back(
+ crc_internal::CrcCordState::PrefixCrc(size(), absl::crc32c_t{crc}));
+ SetCrcCordState(std::move(state));
+}
+
+absl::Nullable<const crc_internal::CrcCordState*> Cord::MaybeGetCrcCordState()
+ const {
+ if (!contents_.is_tree() || !contents_.tree()->IsCrc()) {
+ return nullptr;
+ }
+ return &contents_.tree()->crc()->crc_cord_state;
+}
+
+absl::optional<uint32_t> Cord::ExpectedChecksum() const {
+ if (!contents_.is_tree() || !contents_.tree()->IsCrc()) {
+ return absl::nullopt;
+ }
+ return static_cast<uint32_t>(
+ contents_.tree()->crc()->crc_cord_state.Checksum());
+}
+
inline int Cord::CompareSlowPath(absl::string_view rhs, size_t compared_size,
size_t size_to_compare) const {
- auto advance = [](Cord::ChunkIterator* it, absl::string_view* chunk) {
+ auto advance = [](absl::Nonnull<Cord::ChunkIterator*> it,
+ absl::Nonnull<absl::string_view*> chunk) {
if (!chunk->empty()) return true;
++*it;
if (it->bytes_remaining_ == 0) return false;
@@ -1303,7 +930,8 @@
inline int Cord::CompareSlowPath(const Cord& rhs, size_t compared_size,
size_t size_to_compare) const {
- auto advance = [](Cord::ChunkIterator* it, absl::string_view* chunk) {
+ auto advance = [](absl::Nonnull<Cord::ChunkIterator*> it,
+ absl::Nonnull<absl::string_view*> chunk) {
if (!chunk->empty()) return true;
++*it;
if (it->bytes_remaining_ == 0) return false;
@@ -1336,6 +964,7 @@
}
inline absl::string_view Cord::GetFirstChunk(const Cord& c) {
+ if (c.empty()) return {};
return c.contents_.FindFlatStartPiece();
}
inline absl::string_view Cord::GetFirstChunk(absl::string_view sv) {
@@ -1424,7 +1053,7 @@
return s;
}
-void CopyCordToString(const Cord& src, std::string* dst) {
+void CopyCordToString(const Cord& src, absl::Nonnull<std::string*> dst) {
if (!src.contents_.is_tree()) {
src.contents_.CopyTo(dst);
} else {
@@ -1433,7 +1062,7 @@
}
}
-void Cord::CopyToArraySlowPath(char* dst) const {
+void Cord::CopyToArraySlowPath(absl::Nonnull<char*> dst) const {
assert(contents_.is_tree());
absl::string_view fragment;
if (GetFlatAux(contents_.tree(), &fragment)) {
@@ -1446,42 +1075,6 @@
}
}
-Cord::ChunkIterator& Cord::ChunkIterator::AdvanceStack() {
- auto& stack_of_right_children = stack_of_right_children_;
- if (stack_of_right_children.empty()) {
- assert(!current_chunk_.empty()); // Called on invalid iterator.
- // We have reached the end of the Cord.
- return *this;
- }
-
- // Process the next node on the stack.
- CordRep* node = stack_of_right_children.back();
- stack_of_right_children.pop_back();
-
- // Walk down the left branches until we hit a non-CONCAT node. Save the
- // right children to the stack for subsequent traversal.
- while (node->tag == CONCAT) {
- stack_of_right_children.push_back(node->concat()->right);
- node = node->concat()->left;
- }
-
- // Get the child node if we encounter a SUBSTRING.
- size_t offset = 0;
- size_t length = node->length;
- if (node->tag == SUBSTRING) {
- offset = node->substring()->start;
- node = node->substring()->child;
- }
-
- assert(node->tag == EXTERNAL || node->tag >= FLAT);
- assert(length != 0);
- const char* data =
- node->tag == EXTERNAL ? node->external()->base : node->flat()->Data();
- current_chunk_ = absl::string_view(data + offset, length);
- current_leaf_ = node;
- return *this;
-}
-
Cord Cord::ChunkIterator::AdvanceAndReadBytes(size_t n) {
ABSL_HARDENING_ASSERT(bytes_remaining_ >= n &&
"Attempted to iterate past `end()`");
@@ -1506,185 +1099,51 @@
return subcord;
}
- if (ring_reader_) {
+ if (btree_reader_) {
size_t chunk_size = current_chunk_.size();
if (n <= chunk_size && n <= kMaxBytesToCopy) {
subcord = Cord(current_chunk_.substr(0, n), method);
+ if (n < chunk_size) {
+ current_chunk_.remove_prefix(n);
+ } else {
+ current_chunk_ = btree_reader_.Next();
+ }
} else {
- auto* ring = CordRep::Ref(ring_reader_.ring())->ring();
- size_t offset = ring_reader_.length() - bytes_remaining_;
- CordRep* rep = CordRepRing::SubRing(ring, offset, n);
+ CordRep* rep;
+ current_chunk_ = btree_reader_.Read(n, chunk_size, rep);
subcord.contents_.EmplaceTree(rep, method);
}
- if (n < chunk_size) {
- bytes_remaining_ -= n;
- current_chunk_.remove_prefix(n);
- } else {
- AdvanceBytesRing(n);
- }
+ bytes_remaining_ -= n;
return subcord;
}
- auto& stack_of_right_children = stack_of_right_children_;
- if (n < current_chunk_.size()) {
- // Range to read is a proper subrange of the current chunk.
- assert(current_leaf_ != nullptr);
- CordRep* subnode = CordRep::Ref(current_leaf_);
- const char* data = subnode->tag == EXTERNAL ? subnode->external()->base
- : subnode->flat()->Data();
- subnode = NewSubstring(subnode, current_chunk_.data() - data, n);
- subcord.contents_.EmplaceTree(VerifyTree(subnode), method);
- RemoveChunkPrefix(n);
- return subcord;
- }
-
- // Range to read begins with a proper subrange of the current chunk.
- assert(!current_chunk_.empty());
+ // Short circuit if reading the entire data edge.
assert(current_leaf_ != nullptr);
- CordRep* subnode = CordRep::Ref(current_leaf_);
- if (current_chunk_.size() < subnode->length) {
- const char* data = subnode->tag == EXTERNAL ? subnode->external()->base
- : subnode->flat()->Data();
- subnode = NewSubstring(subnode, current_chunk_.data() - data,
- current_chunk_.size());
- }
- n -= current_chunk_.size();
- bytes_remaining_ -= current_chunk_.size();
-
- // Process the next node(s) on the stack, reading whole subtrees depending on
- // their length and how many bytes we are advancing.
- CordRep* node = nullptr;
- while (!stack_of_right_children.empty()) {
- node = stack_of_right_children.back();
- stack_of_right_children.pop_back();
- if (node->length > n) break;
- // TODO(qrczak): This might unnecessarily recreate existing concat nodes.
- // Avoiding that would need pretty complicated logic (instead of
- // current_leaf, keep current_subtree_ which points to the highest node
- // such that the current leaf can be found on the path of left children
- // starting from current_subtree_; delay creating subnode while node is
- // below current_subtree_; find the proper node along the path of left
- // children starting from current_subtree_ if this loop exits while staying
- // below current_subtree_; etc.; alternatively, push parents instead of
- // right children on the stack).
- subnode = Concat(subnode, CordRep::Ref(node));
- n -= node->length;
- bytes_remaining_ -= node->length;
- node = nullptr;
- }
-
- if (node == nullptr) {
- // We have reached the end of the Cord.
- assert(bytes_remaining_ == 0);
- subcord.contents_.EmplaceTree(VerifyTree(subnode), method);
+ if (n == current_leaf_->length) {
+ bytes_remaining_ = 0;
+ current_chunk_ = {};
+ CordRep* tree = CordRep::Ref(current_leaf_);
+ subcord.contents_.EmplaceTree(VerifyTree(tree), method);
return subcord;
}
- // Walk down the appropriate branches until we hit a non-CONCAT node. Save the
- // right children to the stack for subsequent traversal.
- while (node->tag == CONCAT) {
- if (node->concat()->left->length > n) {
- // Push right, descend left.
- stack_of_right_children.push_back(node->concat()->right);
- node = node->concat()->left;
- } else {
- // Read left, descend right.
- subnode = Concat(subnode, CordRep::Ref(node->concat()->left));
- n -= node->concat()->left->length;
- bytes_remaining_ -= node->concat()->left->length;
- node = node->concat()->right;
- }
- }
+ // From this point on, we need a partial substring node.
+ // Get pointer to the underlying flat or external data payload and
+ // compute data pointer and offset into current flat or external.
+ CordRep* payload = current_leaf_->IsSubstring()
+ ? current_leaf_->substring()->child
+ : current_leaf_;
+ const char* data = payload->IsExternal() ? payload->external()->base
+ : payload->flat()->Data();
+ const size_t offset = static_cast<size_t>(current_chunk_.data() - data);
- // Get the child node if we encounter a SUBSTRING.
- size_t offset = 0;
- size_t length = node->length;
- if (node->tag == SUBSTRING) {
- offset = node->substring()->start;
- node = node->substring()->child;
- }
-
- // Range to read ends with a proper (possibly empty) subrange of the current
- // chunk.
- assert(node->tag == EXTERNAL || node->tag >= FLAT);
- assert(length > n);
- if (n > 0) {
- subnode = Concat(subnode, NewSubstring(CordRep::Ref(node), offset, n));
- }
- const char* data =
- node->tag == EXTERNAL ? node->external()->base : node->flat()->Data();
- current_chunk_ = absl::string_view(data + offset + n, length - n);
- current_leaf_ = node;
+ auto* tree = CordRepSubstring::Substring(payload, offset, n);
+ subcord.contents_.EmplaceTree(VerifyTree(tree), method);
bytes_remaining_ -= n;
- subcord.contents_.EmplaceTree(VerifyTree(subnode), method);
+ current_chunk_.remove_prefix(n);
return subcord;
}
-void Cord::ChunkIterator::AdvanceBytesSlowPath(size_t n) {
- assert(bytes_remaining_ >= n && "Attempted to iterate past `end()`");
- assert(n >= current_chunk_.size()); // This should only be called when
- // iterating to a new node.
-
- n -= current_chunk_.size();
- bytes_remaining_ -= current_chunk_.size();
-
- if (stack_of_right_children_.empty()) {
- // We have reached the end of the Cord.
- assert(bytes_remaining_ == 0);
- return;
- }
-
- // Process the next node(s) on the stack, skipping whole subtrees depending on
- // their length and how many bytes we are advancing.
- CordRep* node = nullptr;
- auto& stack_of_right_children = stack_of_right_children_;
- while (!stack_of_right_children.empty()) {
- node = stack_of_right_children.back();
- stack_of_right_children.pop_back();
- if (node->length > n) break;
- n -= node->length;
- bytes_remaining_ -= node->length;
- node = nullptr;
- }
-
- if (node == nullptr) {
- // We have reached the end of the Cord.
- assert(bytes_remaining_ == 0);
- return;
- }
-
- // Walk down the appropriate branches until we hit a non-CONCAT node. Save the
- // right children to the stack for subsequent traversal.
- while (node->tag == CONCAT) {
- if (node->concat()->left->length > n) {
- // Push right, descend left.
- stack_of_right_children.push_back(node->concat()->right);
- node = node->concat()->left;
- } else {
- // Skip left, descend right.
- n -= node->concat()->left->length;
- bytes_remaining_ -= node->concat()->left->length;
- node = node->concat()->right;
- }
- }
-
- // Get the child node if we encounter a SUBSTRING.
- size_t offset = 0;
- size_t length = node->length;
- if (node->tag == SUBSTRING) {
- offset = node->substring()->start;
- node = node->substring()->child;
- }
-
- assert(node->tag == EXTERNAL || node->tag >= FLAT);
- assert(length > n);
- const char* data =
- node->tag == EXTERNAL ? node->external()->base : node->flat()->Data();
- current_chunk_ = absl::string_view(data + offset + n, length - n);
- current_leaf_ = node;
- bytes_remaining_ -= n;
-}
-
char Cord::operator[](size_t i) const {
ABSL_HARDENING_ASSERT(i < size());
size_t offset = i;
@@ -1692,36 +1151,215 @@
if (rep == nullptr) {
return contents_.data()[i];
}
+ rep = cord_internal::SkipCrcNode(rep);
while (true) {
assert(rep != nullptr);
assert(offset < rep->length);
- if (rep->tag >= FLAT) {
+ if (rep->IsFlat()) {
// Get the "i"th character directly from the flat array.
return rep->flat()->Data()[offset];
- } else if (rep->tag == RING) {
- return rep->ring()->GetCharacter(offset);
- } else if (rep->tag == EXTERNAL) {
+ } else if (rep->IsBtree()) {
+ return rep->btree()->GetCharacter(offset);
+ } else if (rep->IsExternal()) {
// Get the "i"th character from the external array.
return rep->external()->base[offset];
- } else if (rep->tag == CONCAT) {
- // Recursively branch to the side of the concatenation that the "i"th
- // character is on.
- size_t left_length = rep->concat()->left->length;
- if (offset < left_length) {
- rep = rep->concat()->left;
- } else {
- offset -= left_length;
- rep = rep->concat()->right;
- }
} else {
// This must be a substring a node, so bypass it to get to the child.
- assert(rep->tag == SUBSTRING);
+ assert(rep->IsSubstring());
offset += rep->substring()->start;
rep = rep->substring()->child;
}
}
}
+namespace {
+
+// Tests whether the sequence of chunks beginning at `position` starts with
+// `needle`.
+//
+// REQUIRES: remaining `absl::Cord` starting at `position` is greater than or
+// equal to `needle.size()`.
+bool IsSubstringInCordAt(absl::Cord::CharIterator position,
+ absl::string_view needle) {
+ auto haystack_chunk = absl::Cord::ChunkRemaining(position);
+ while (true) {
+ // Precondition is that `absl::Cord::ChunkRemaining(position)` is not
+ // empty. This assert will trigger if that is not true.
+ assert(!haystack_chunk.empty());
+ auto min_length = std::min(haystack_chunk.size(), needle.size());
+ if (!absl::ConsumePrefix(&needle, haystack_chunk.substr(0, min_length))) {
+ return false;
+ }
+ if (needle.empty()) {
+ return true;
+ }
+ absl::Cord::Advance(&position, min_length);
+ haystack_chunk = absl::Cord::ChunkRemaining(position);
+ }
+}
+
+} // namespace
+
+// A few options how this could be implemented:
+// (a) Flatten the Cord and find, i.e.
+// haystack.Flatten().find(needle)
+// For large 'haystack' (where Cord makes sense to be used), this copies
+// the whole 'haystack' and can be slow.
+// (b) Use std::search, i.e.
+// std::search(haystack.char_begin(), haystack.char_end(),
+// needle.begin(), needle.end())
+// This avoids the copy, but compares one byte at a time, and branches a
+// lot every time it has to advance. It is also not possible to use
+// std::search as is, because CharIterator is only an input iterator, not a
+// forward iterator.
+// (c) Use string_view::find in each fragment, and specifically handle fragment
+// boundaries.
+//
+// This currently implements option (b).
+absl::Cord::CharIterator absl::Cord::FindImpl(CharIterator it,
+ absl::string_view needle) const {
+ // Ensure preconditions are met by callers first.
+
+ // Needle must not be empty.
+ assert(!needle.empty());
+ // Haystack must be at least as large as needle.
+ assert(it.chunk_iterator_.bytes_remaining_ >= needle.size());
+
+ // Cord is a sequence of chunks. To find `needle` we go chunk by chunk looking
+ // for the first char of needle, up until we have advanced `N` defined as
+ // `haystack.size() - needle.size()`. If we find the first char of needle at
+ // `P` and `P` is less than `N`, we then call `IsSubstringInCordAt` to
+ // see if this is the needle. If not, we advance to `P + 1` and try again.
+ while (it.chunk_iterator_.bytes_remaining_ >= needle.size()) {
+ auto haystack_chunk = Cord::ChunkRemaining(it);
+ assert(!haystack_chunk.empty());
+ // Look for the first char of `needle` in the current chunk.
+ auto idx = haystack_chunk.find(needle.front());
+ if (idx == absl::string_view::npos) {
+ // No potential match in this chunk, advance past it.
+ Cord::Advance(&it, haystack_chunk.size());
+ continue;
+ }
+ // We found the start of a potential match in the chunk. Advance the
+ // iterator and haystack chunk to the match the position.
+ Cord::Advance(&it, idx);
+ // Check if there is enough haystack remaining to actually have a match.
+ if (it.chunk_iterator_.bytes_remaining_ < needle.size()) {
+ break;
+ }
+ // Check if this is `needle`.
+ if (IsSubstringInCordAt(it, needle)) {
+ return it;
+ }
+ // No match, increment the iterator for the next attempt.
+ Cord::Advance(&it, 1);
+ }
+ // If we got here, we did not find `needle`.
+ return char_end();
+}
+
+absl::Cord::CharIterator absl::Cord::Find(absl::string_view needle) const {
+ if (needle.empty()) {
+ return char_begin();
+ }
+ if (needle.size() > size()) {
+ return char_end();
+ }
+ if (needle.size() == size()) {
+ return *this == needle ? char_begin() : char_end();
+ }
+ return FindImpl(char_begin(), needle);
+}
+
+namespace {
+
+// Tests whether the sequence of chunks beginning at `haystack` starts with the
+// sequence of chunks beginning at `needle_begin` and extending to `needle_end`.
+//
+// REQUIRES: remaining `absl::Cord` starting at `position` is greater than or
+// equal to `needle_end - needle_begin` and `advance`.
+bool IsSubcordInCordAt(absl::Cord::CharIterator haystack,
+ absl::Cord::CharIterator needle_begin,
+ absl::Cord::CharIterator needle_end) {
+ while (needle_begin != needle_end) {
+ auto haystack_chunk = absl::Cord::ChunkRemaining(haystack);
+ assert(!haystack_chunk.empty());
+ auto needle_chunk = absl::Cord::ChunkRemaining(needle_begin);
+ auto min_length = std::min(haystack_chunk.size(), needle_chunk.size());
+ if (haystack_chunk.substr(0, min_length) !=
+ needle_chunk.substr(0, min_length)) {
+ return false;
+ }
+ absl::Cord::Advance(&haystack, min_length);
+ absl::Cord::Advance(&needle_begin, min_length);
+ }
+ return true;
+}
+
+// Tests whether the sequence of chunks beginning at `position` starts with the
+// cord `needle`.
+//
+// REQUIRES: remaining `absl::Cord` starting at `position` is greater than or
+// equal to `needle.size()`.
+bool IsSubcordInCordAt(absl::Cord::CharIterator position,
+ const absl::Cord& needle) {
+ return IsSubcordInCordAt(position, needle.char_begin(), needle.char_end());
+}
+
+} // namespace
+
+absl::Cord::CharIterator absl::Cord::Find(const absl::Cord& needle) const {
+ if (needle.empty()) {
+ return char_begin();
+ }
+ const auto needle_size = needle.size();
+ if (needle_size > size()) {
+ return char_end();
+ }
+ if (needle_size == size()) {
+ return *this == needle ? char_begin() : char_end();
+ }
+ const auto needle_chunk = Cord::ChunkRemaining(needle.char_begin());
+ auto haystack_it = char_begin();
+ while (true) {
+ haystack_it = FindImpl(haystack_it, needle_chunk);
+ if (haystack_it == char_end() ||
+ haystack_it.chunk_iterator_.bytes_remaining_ < needle_size) {
+ break;
+ }
+ // We found the first chunk of `needle` at `haystack_it` but not the entire
+ // subcord. Advance past the first chunk and check for the remainder.
+ auto haystack_advanced_it = haystack_it;
+ auto needle_it = needle.char_begin();
+ Cord::Advance(&haystack_advanced_it, needle_chunk.size());
+ Cord::Advance(&needle_it, needle_chunk.size());
+ if (IsSubcordInCordAt(haystack_advanced_it, needle_it, needle.char_end())) {
+ return haystack_it;
+ }
+ Cord::Advance(&haystack_it, 1);
+ if (haystack_it.chunk_iterator_.bytes_remaining_ < needle_size) {
+ break;
+ }
+ if (haystack_it.chunk_iterator_.bytes_remaining_ == needle_size) {
+ // Special case, if there is exactly `needle_size` bytes remaining, the
+ // subcord is either at `haystack_it` or not at all.
+ if (IsSubcordInCordAt(haystack_it, needle)) {
+ return haystack_it;
+ }
+ break;
+ }
+ }
+ return char_end();
+}
+
+bool Cord::Contains(absl::string_view rhs) const {
+ return rhs.empty() || Find(rhs) != char_end();
+}
+
+bool Cord::Contains(const absl::Cord& rhs) const {
+ return rhs.empty() || Find(rhs) != char_end();
+}
+
absl::string_view Cord::FlattenSlowPath() {
assert(contents_.is_tree());
size_t total_size = size();
@@ -1750,38 +1388,48 @@
return absl::string_view(new_buffer, total_size);
}
-/* static */ bool Cord::GetFlatAux(CordRep* rep, absl::string_view* fragment) {
+/* static */ bool Cord::GetFlatAux(absl::Nonnull<CordRep*> rep,
+ absl::Nonnull<absl::string_view*> fragment) {
assert(rep != nullptr);
- if (rep->tag >= FLAT) {
+ if (rep->length == 0) {
+ *fragment = absl::string_view();
+ return true;
+ }
+ rep = cord_internal::SkipCrcNode(rep);
+ if (rep->IsFlat()) {
*fragment = absl::string_view(rep->flat()->Data(), rep->length);
return true;
- } else if (rep->tag == EXTERNAL) {
+ } else if (rep->IsExternal()) {
*fragment = absl::string_view(rep->external()->base, rep->length);
return true;
- } else if (rep->tag == RING) {
- return rep->ring()->IsFlat(fragment);
- } else if (rep->tag == SUBSTRING) {
+ } else if (rep->IsBtree()) {
+ return rep->btree()->IsFlat(fragment);
+ } else if (rep->IsSubstring()) {
CordRep* child = rep->substring()->child;
- if (child->tag >= FLAT) {
+ if (child->IsFlat()) {
*fragment = absl::string_view(
child->flat()->Data() + rep->substring()->start, rep->length);
return true;
- } else if (child->tag == EXTERNAL) {
+ } else if (child->IsExternal()) {
*fragment = absl::string_view(
child->external()->base + rep->substring()->start, rep->length);
return true;
- } else if (child->tag == RING) {
- return child->ring()->IsFlat(rep->substring()->start, rep->length,
- fragment);
+ } else if (child->IsBtree()) {
+ return child->btree()->IsFlat(rep->substring()->start, rep->length,
+ fragment);
}
}
return false;
}
/* static */ void Cord::ForEachChunkAux(
- absl::cord_internal::CordRep* rep,
+ absl::Nonnull<absl::cord_internal::CordRep*> rep,
absl::FunctionRef<void(absl::string_view)> callback) {
- if (rep->tag == RING) {
+ assert(rep != nullptr);
+ if (rep->length == 0) return;
+ rep = cord_internal::SkipCrcNode(rep);
+
+ if (rep->IsBtree()) {
ChunkIterator it(rep), end;
while (it != end) {
callback(*it);
@@ -1790,49 +1438,18 @@
return;
}
- assert(rep != nullptr);
- int stack_pos = 0;
- constexpr int stack_max = 128;
- // Stack of right branches for tree traversal
- absl::cord_internal::CordRep* stack[stack_max];
- absl::cord_internal::CordRep* current_node = rep;
- while (true) {
- if (current_node->tag == CONCAT) {
- if (stack_pos == stack_max) {
- // There's no more room on our stack array to add another right branch,
- // and the idea is to avoid allocations, so call this function
- // recursively to navigate this subtree further. (This is not something
- // we expect to happen in practice).
- ForEachChunkAux(current_node, callback);
-
- // Pop the next right branch and iterate.
- current_node = stack[--stack_pos];
- continue;
- } else {
- // Save the right branch for later traversal and continue down the left
- // branch.
- stack[stack_pos++] = current_node->concat()->right;
- current_node = current_node->concat()->left;
- continue;
- }
- }
- // This is a leaf node, so invoke our callback.
- absl::string_view chunk;
- bool success = GetFlatAux(current_node, &chunk);
- assert(success);
- if (success) {
- callback(chunk);
- }
- if (stack_pos == 0) {
- // end of traversal
- return;
- }
- current_node = stack[--stack_pos];
+ // This is a leaf node, so invoke our callback.
+ absl::cord_internal::CordRep* current_node = cord_internal::SkipCrcNode(rep);
+ absl::string_view chunk;
+ bool success = GetFlatAux(current_node, &chunk);
+ assert(success);
+ if (success) {
+ callback(chunk);
}
}
-static void DumpNode(CordRep* rep, bool include_data, std::ostream* os,
- int indent) {
+static void DumpNode(absl::Nonnull<CordRep*> rep, bool include_data,
+ absl::Nonnull<std::ostream*> os, int indent) {
const int kIndentStep = 1;
absl::InlinedVector<CordRep*, kInlinedVectorSize> stack;
absl::InlinedVector<int, kInlinedVectorSize> indents;
@@ -1842,40 +1459,36 @@
*os << " [";
if (include_data) *os << static_cast<void*>(rep);
*os << "]";
- *os << " " << (IsRootBalanced(rep) ? 'b' : 'u');
*os << " " << std::setw(indent) << "";
- if (rep->tag == CONCAT) {
- *os << "CONCAT depth=" << Depth(rep) << "\n";
+ bool leaf = false;
+ if (rep == nullptr) {
+ *os << "NULL\n";
+ leaf = true;
+ } else if (rep->IsCrc()) {
+ *os << "CRC crc=" << rep->crc()->crc_cord_state.Checksum() << "\n";
indent += kIndentStep;
- indents.push_back(indent);
- stack.push_back(rep->concat()->right);
- rep = rep->concat()->left;
- } else if (rep->tag == SUBSTRING) {
+ rep = rep->crc()->child;
+ } else if (rep->IsSubstring()) {
*os << "SUBSTRING @ " << rep->substring()->start << "\n";
indent += kIndentStep;
rep = rep->substring()->child;
} else { // Leaf or ring
- if (rep->tag == EXTERNAL) {
+ leaf = true;
+ if (rep->IsExternal()) {
*os << "EXTERNAL [";
if (include_data)
*os << absl::CEscape(std::string(rep->external()->base, rep->length));
*os << "]\n";
- } else if (rep->tag >= FLAT) {
+ } else if (rep->IsFlat()) {
*os << "FLAT cap=" << rep->flat()->Capacity() << " [";
if (include_data)
*os << absl::CEscape(std::string(rep->flat()->Data(), rep->length));
*os << "]\n";
} else {
- assert(rep->tag == RING);
- auto* ring = rep->ring();
- *os << "RING, entries = " << ring->entries() << "\n";
- CordRepRing::index_type head = ring->head();
- do {
- DumpNode(ring->entry_child(head), include_data, os,
- indent + kIndentStep);
- head = ring->advance(head);
- } while (head != ring->tail());
+ CordRepBtree::Dump(rep, /*label=*/"", include_data, *os);
}
+ }
+ if (leaf) {
if (stack.empty()) break;
rep = stack.back();
stack.pop_back();
@@ -1886,16 +1499,17 @@
ABSL_INTERNAL_CHECK(indents.empty(), "");
}
-static std::string ReportError(CordRep* root, CordRep* node) {
+static std::string ReportError(absl::Nonnull<CordRep*> root,
+ absl::Nonnull<CordRep*> node) {
std::ostringstream buf;
buf << "Error at node " << node << " in:";
DumpNode(root, true, &buf);
return buf.str();
}
-static bool VerifyNode(CordRep* root, CordRep* start_node,
- bool full_validation) {
- absl::InlinedVector<CordRep*, 2> worklist;
+static bool VerifyNode(absl::Nonnull<CordRep*> root,
+ absl::Nonnull<CordRep*> start_node) {
+ absl::InlinedVector<absl::Nonnull<CordRep*>, 2> worklist;
worklist.push_back(start_node);
do {
CordRep* node = worklist.back();
@@ -1904,102 +1518,39 @@
ABSL_INTERNAL_CHECK(node != nullptr, ReportError(root, node));
if (node != root) {
ABSL_INTERNAL_CHECK(node->length != 0, ReportError(root, node));
+ ABSL_INTERNAL_CHECK(!node->IsCrc(), ReportError(root, node));
}
- if (node->tag == CONCAT) {
- ABSL_INTERNAL_CHECK(node->concat()->left != nullptr,
- ReportError(root, node));
- ABSL_INTERNAL_CHECK(node->concat()->right != nullptr,
- ReportError(root, node));
- ABSL_INTERNAL_CHECK((node->length == node->concat()->left->length +
- node->concat()->right->length),
- ReportError(root, node));
- if (full_validation) {
- worklist.push_back(node->concat()->right);
- worklist.push_back(node->concat()->left);
- }
- } else if (node->tag >= FLAT) {
+ if (node->IsFlat()) {
ABSL_INTERNAL_CHECK(node->length <= node->flat()->Capacity(),
ReportError(root, node));
- } else if (node->tag == EXTERNAL) {
+ } else if (node->IsExternal()) {
ABSL_INTERNAL_CHECK(node->external()->base != nullptr,
ReportError(root, node));
- } else if (node->tag == SUBSTRING) {
+ } else if (node->IsSubstring()) {
ABSL_INTERNAL_CHECK(
node->substring()->start < node->substring()->child->length,
ReportError(root, node));
ABSL_INTERNAL_CHECK(node->substring()->start + node->length <=
node->substring()->child->length,
ReportError(root, node));
+ } else if (node->IsCrc()) {
+ ABSL_INTERNAL_CHECK(
+ node->crc()->child != nullptr || node->crc()->length == 0,
+ ReportError(root, node));
+ if (node->crc()->child != nullptr) {
+ ABSL_INTERNAL_CHECK(node->crc()->length == node->crc()->child->length,
+ ReportError(root, node));
+ worklist.push_back(node->crc()->child);
+ }
}
} while (!worklist.empty());
return true;
}
-// Traverses the tree and computes the total memory allocated.
-/* static */ size_t Cord::MemoryUsageAux(const CordRep* rep) {
- size_t total_mem_usage = 0;
-
- // Allow a quick exit for the common case that the root is a leaf.
- if (RepMemoryUsageLeaf(rep, &total_mem_usage)) {
- return total_mem_usage;
- }
-
- // Iterate over the tree. cur_node is never a leaf node and leaf nodes will
- // never be appended to tree_stack. This reduces overhead from manipulating
- // tree_stack.
- absl::InlinedVector<const CordRep*, kInlinedVectorSize> tree_stack;
- const CordRep* cur_node = rep;
- while (true) {
- const CordRep* next_node = nullptr;
-
- if (cur_node->tag == CONCAT) {
- total_mem_usage += sizeof(CordRepConcat);
- const CordRep* left = cur_node->concat()->left;
- if (!RepMemoryUsageLeaf(left, &total_mem_usage)) {
- next_node = left;
- }
-
- const CordRep* right = cur_node->concat()->right;
- if (!RepMemoryUsageLeaf(right, &total_mem_usage)) {
- if (next_node) {
- tree_stack.push_back(next_node);
- }
- next_node = right;
- }
- } else if (cur_node->tag == RING) {
- total_mem_usage += CordRepRing::AllocSize(cur_node->ring()->capacity());
- const CordRepRing* ring = cur_node->ring();
- CordRepRing::index_type pos = ring->head(), tail = ring->tail();
- do {
- CordRep* node = ring->entry_child(pos);
- assert(node->tag >= FLAT || node->tag == EXTERNAL);
- RepMemoryUsageLeaf(node, &total_mem_usage);
- } while ((pos = ring->advance(pos)) != tail);
- } else {
- // Since cur_node is not a leaf or a concat node it must be a substring.
- assert(cur_node->tag == SUBSTRING);
- total_mem_usage += sizeof(CordRepSubstring);
- next_node = cur_node->substring()->child;
- if (RepMemoryUsageLeaf(next_node, &total_mem_usage)) {
- next_node = nullptr;
- }
- }
-
- if (!next_node) {
- if (tree_stack.empty()) {
- return total_mem_usage;
- }
- next_node = tree_stack.back();
- tree_stack.pop_back();
- }
- cur_node = next_node;
- }
-}
-
std::ostream& operator<<(std::ostream& out, const Cord& cord) {
for (absl::string_view chunk : cord.Chunks()) {
- out.write(chunk.data(), chunk.size());
+ out.write(chunk.data(), static_cast<std::streamsize>(chunk.size()));
}
return out;
}
@@ -2014,7 +1565,6 @@
ABSL_INTERNAL_CHECK(s <= kMaxFlatLength, absl::StrCat("Invalid length ", s));
return cord_internal::AllocatedSizeToTag(s + cord_internal::kFlatOverhead);
}
-size_t CordTestAccess::SizeofCordRepConcat() { return sizeof(CordRepConcat); }
size_t CordTestAccess::SizeofCordRepExternal() {
return sizeof(CordRepExternal);
}
diff --git a/absl/strings/cord.h b/absl/strings/cord.h
index e758f1c..2583aa8 100644
--- a/absl/strings/cord.h
+++ b/absl/strings/cord.h
@@ -20,8 +20,7 @@
// structure. A Cord is a string-like sequence of characters optimized for
// specific use cases. Unlike a `std::string`, which stores an array of
// contiguous characters, Cord data is stored in a structure consisting of
-// separate, reference-counted "chunks." (Currently, this implementation is a
-// tree structure, though that implementation may change.)
+// separate, reference-counted "chunks."
//
// Because a Cord consists of these chunks, data can be added to or removed from
// a Cord during its lifetime. Chunks may also be shared between Cords. Unlike a
@@ -70,17 +69,24 @@
#include <string>
#include <type_traits>
+#include "absl/base/attributes.h"
#include "absl/base/config.h"
#include "absl/base/internal/endian.h"
#include "absl/base/internal/per_thread_tls.h"
#include "absl/base/macros.h"
+#include "absl/base/nullability.h"
#include "absl/base/port.h"
#include "absl/container/inlined_vector.h"
+#include "absl/crc/internal/crc_cord_state.h"
#include "absl/functional/function_ref.h"
#include "absl/meta/type_traits.h"
+#include "absl/strings/cord_analysis.h"
+#include "absl/strings/cord_buffer.h"
+#include "absl/strings/internal/cord_data_edge.h"
#include "absl/strings/internal/cord_internal.h"
-#include "absl/strings/internal/cord_rep_ring.h"
-#include "absl/strings/internal/cord_rep_ring_reader.h"
+#include "absl/strings/internal/cord_rep_btree.h"
+#include "absl/strings/internal/cord_rep_btree_reader.h"
+#include "absl/strings/internal/cord_rep_crc.h"
#include "absl/strings/internal/cordz_functions.h"
#include "absl/strings/internal/cordz_info.h"
#include "absl/strings/internal/cordz_statistics.h"
@@ -97,7 +103,42 @@
class CordTestPeer;
template <typename Releaser>
Cord MakeCordFromExternal(absl::string_view, Releaser&&);
-void CopyCordToString(const Cord& src, std::string* dst);
+void CopyCordToString(const Cord& src, absl::Nonnull<std::string*> dst);
+
+// Cord memory accounting modes
+enum class CordMemoryAccounting {
+ // Counts the *approximate* number of bytes held in full or in part by this
+ // Cord (which may not remain the same between invocations). Cords that share
+ // memory could each be "charged" independently for the same shared memory.
+ // See also comment on `kTotalMorePrecise` on internally shared memory.
+ kTotal,
+
+ // Counts the *approximate* number of bytes held in full or in part by this
+ // Cord for the distinct memory held by this cord. This option is similar
+ // to `kTotal`, except that if the cord has multiple references to the same
+ // memory, that memory is only counted once.
+ //
+ // For example:
+ // absl::Cord cord;
+ // cord.Append(some_other_cord);
+ // cord.Append(some_other_cord);
+ // // Counts `some_other_cord` twice:
+ // cord.EstimatedMemoryUsage(kTotal);
+ // // Counts `some_other_cord` once:
+ // cord.EstimatedMemoryUsage(kTotalMorePrecise);
+ //
+ // The `kTotalMorePrecise` number is more expensive to compute as it requires
+ // deduplicating all memory references. Applications should prefer to use
+ // `kFairShare` or `kTotal` unless they really need a more precise estimate
+ // on "how much memory is potentially held / kept alive by this cord?"
+ kTotalMorePrecise,
+
+ // Counts the *approximate* number of bytes held in full or in part by this
+ // Cord weighted by the sharing ratio of that data. For example, if some data
+ // edge is shared by 4 different Cords, then each cord is attributed 1/4th of
+ // the total memory usage as a 'fair share' of the total memory usage.
+ kFairShare,
+};
// Cord
//
@@ -213,7 +254,7 @@
//
// Releases the Cord data. Any nodes that share data with other Cords, if
// applicable, will have their reference counts reduced by 1.
- void Clear();
+ ABSL_ATTRIBUTE_REINITIALIZES void Clear();
// Cord::Append()
//
@@ -225,6 +266,58 @@
template <typename T, EnableIfString<T> = 0>
void Append(T&& src);
+ // Appends `buffer` to this cord, unless `buffer` has a zero length in which
+ // case this method has no effect on this cord instance.
+ // This method is guaranteed to consume `buffer`.
+ void Append(CordBuffer buffer);
+
+ // Returns a CordBuffer, re-using potential existing capacity in this cord.
+ //
+ // Cord instances may have additional unused capacity in the last (or first)
+ // nodes of the underlying tree to facilitate amortized growth. This method
+ // allows applications to explicitly use this spare capacity if available,
+ // or create a new CordBuffer instance otherwise.
+ // If this cord has a final non-shared node with at least `min_capacity`
+ // available, then this method will return that buffer including its data
+ // contents. I.e.; the returned buffer will have a non-zero length, and
+ // a capacity of at least `buffer.length + min_capacity`. Otherwise, this
+ // method will return `CordBuffer::CreateWithDefaultLimit(capacity)`.
+ //
+ // Below an example of using GetAppendBuffer. Notice that in this example we
+ // use `GetAppendBuffer()` only on the first iteration. As we know nothing
+ // about any initial extra capacity in `cord`, we may be able to use the extra
+ // capacity. But as we add new buffers with fully utilized contents after that
+ // we avoid calling `GetAppendBuffer()` on subsequent iterations: while this
+ // works fine, it results in an unnecessary inspection of cord contents:
+ //
+ // void AppendRandomDataToCord(absl::Cord &cord, size_t n) {
+ // bool first = true;
+ // while (n > 0) {
+ // CordBuffer buffer = first ? cord.GetAppendBuffer(n)
+ // : CordBuffer::CreateWithDefaultLimit(n);
+ // absl::Span<char> data = buffer.available_up_to(n);
+ // FillRandomValues(data.data(), data.size());
+ // buffer.IncreaseLengthBy(data.size());
+ // cord.Append(std::move(buffer));
+ // n -= data.size();
+ // first = false;
+ // }
+ // }
+ CordBuffer GetAppendBuffer(size_t capacity, size_t min_capacity = 16);
+
+ // Returns a CordBuffer, re-using potential existing capacity in this cord.
+ //
+ // This function is identical to `GetAppendBuffer`, except that in the case
+ // where a new `CordBuffer` is allocated, it is allocated using the provided
+ // custom limit instead of the default limit. `GetAppendBuffer` will default
+ // to `CordBuffer::CreateWithDefaultLimit(capacity)` whereas this method
+ // will default to `CordBuffer::CreateWithCustomLimit(block_size, capacity)`.
+ // This method is equivalent to `GetAppendBuffer` if `block_size` is zero.
+ // See the documentation for `CreateWithCustomLimit` for more details on the
+ // restrictions and legal values for `block_size`.
+ CordBuffer GetCustomAppendBuffer(size_t block_size, size_t capacity,
+ size_t min_capacity = 16);
+
// Cord::Prepend()
//
// Prepends data to the Cord, which may come from another Cord or other string
@@ -234,6 +327,11 @@
template <typename T, EnableIfString<T> = 0>
void Prepend(T&& src);
+ // Prepends `buffer` to this cord, unless `buffer` has a zero length in which
+ // case this method has no effect on this cord instance.
+ // This method is guaranteed to consume `buffer`.
+ void Prepend(CordBuffer buffer);
+
// Cord::RemovePrefix()
//
// Removes the first `n` bytes of a Cord.
@@ -255,9 +353,7 @@
// swap()
//
// Swaps the contents of two Cords.
- friend void swap(Cord& x, Cord& y) noexcept {
- x.swap(y);
- }
+ friend void swap(Cord& x, Cord& y) noexcept { x.swap(y); }
// Cord::size()
//
@@ -266,16 +362,15 @@
// Cord::empty()
//
- // Determines whether the given Cord is empty, returning `true` is so.
+ // Determines whether the given Cord is empty, returning `true` if so.
bool empty() const;
// Cord::EstimatedMemoryUsage()
//
- // Returns the *approximate* number of bytes held in full or in part by this
- // Cord (which may not remain the same between invocations). Note that Cords
- // that share memory could each be "charged" independently for the same shared
- // memory.
- size_t EstimatedMemoryUsage() const;
+ // Returns the *approximate* number of bytes held by this cord.
+ // See CordMemoryAccounting for more information on the accounting method.
+ size_t EstimatedMemoryUsage(CordMemoryAccounting accounting_method =
+ CordMemoryAccounting::kTotal) const;
// Cord::Compare()
//
@@ -301,6 +396,12 @@
bool EndsWith(absl::string_view rhs) const;
bool EndsWith(const Cord& rhs) const;
+ // Cord::Contains()
+ //
+ // Determines whether the Cord contains the passed string data `rhs`.
+ bool Contains(absl::string_view rhs) const;
+ bool Contains(const Cord& rhs) const;
+
// Cord::operator std::string()
//
// Converts a Cord into a `std::string()`. This operator is marked explicit to
@@ -316,7 +417,8 @@
// guarantee that pointers previously returned by `dst->data()` remain valid
// even if `*dst` had enough capacity to hold `src`. If `*dst` is a new
// object, prefer to simply use the conversion operator to `std::string`.
- friend void CopyCordToString(const Cord& src, std::string* dst);
+ friend void CopyCordToString(const Cord& src,
+ absl::Nonnull<std::string*> dst);
class CharIterator;
@@ -325,7 +427,7 @@
//----------------------------------------------------------------------------
//
// A `Cord::ChunkIterator` allows iteration over the constituent chunks of its
- // Cord. Such iteration allows you to perform non-const operatons on the data
+ // Cord. Such iteration allows you to perform non-const operations on the data
// of a Cord without modifying it.
//
// Generally, you do not instantiate a `Cord::ChunkIterator` directly;
@@ -353,7 +455,7 @@
using iterator_category = std::input_iterator_tag;
using value_type = absl::string_view;
using difference_type = ptrdiff_t;
- using pointer = const value_type*;
+ using pointer = absl::Nonnull<const value_type*>;
using reference = value_type;
ChunkIterator() = default;
@@ -370,23 +472,17 @@
private:
using CordRep = absl::cord_internal::CordRep;
- using CordRepRing = absl::cord_internal::CordRepRing;
- using CordRepRingReader = absl::cord_internal::CordRepRingReader;
+ using CordRepBtree = absl::cord_internal::CordRepBtree;
+ using CordRepBtreeReader = absl::cord_internal::CordRepBtreeReader;
- // Stack of right children of concat nodes that we have to visit.
- // Keep this at the end of the structure to avoid cache-thrashing.
- // TODO(jgm): Benchmark to see if there's a more optimal value than 47 for
- // the inlined vector size (47 exists for backward compatibility).
- using Stack = absl::InlinedVector<absl::cord_internal::CordRep*, 47>;
-
- // Constructs a `begin()` iterator from `tree`. `tree` must not be null.
- explicit ChunkIterator(cord_internal::CordRep* tree);
+ // Constructs a `begin()` iterator from `tree`.
+ explicit ChunkIterator(absl::Nonnull<cord_internal::CordRep*> tree);
// Constructs a `begin()` iterator from `cord`.
- explicit ChunkIterator(const Cord* cord);
+ explicit ChunkIterator(absl::Nonnull<const Cord*> cord);
// Initializes this instance from a tree. Invoked by constructors.
- void InitTree(cord_internal::CordRep* tree);
+ void InitTree(absl::Nonnull<cord_internal::CordRep*> tree);
// Removes `n` bytes from `current_chunk_`. Expects `n` to be smaller than
// `current_chunk_.size()`.
@@ -394,16 +490,9 @@
Cord AdvanceAndReadBytes(size_t n);
void AdvanceBytes(size_t n);
- // Stack specific operator++
- ChunkIterator& AdvanceStack();
-
- // Ring buffer specific operator++
- ChunkIterator& AdvanceRing();
- void AdvanceBytesRing(size_t n);
-
- // Iterates `n` bytes, where `n` is expected to be greater than or equal to
- // `current_chunk_.size()`.
- void AdvanceBytesSlowPath(size_t n);
+ // Btree specific operator++
+ ChunkIterator& AdvanceBtree();
+ void AdvanceBytesBtree(size_t n);
// A view into bytes of the current `CordRep`. It may only be a view to a
// suffix of bytes if this is being used by `CharIterator`.
@@ -411,18 +500,15 @@
// The current leaf, or `nullptr` if the iterator points to short data.
// If the current chunk is a substring node, current_leaf_ points to the
// underlying flat or external node.
- absl::cord_internal::CordRep* current_leaf_ = nullptr;
+ absl::Nullable<absl::cord_internal::CordRep*> current_leaf_ = nullptr;
// The number of bytes left in the `Cord` over which we are iterating.
size_t bytes_remaining_ = 0;
- // Cord reader for ring buffers. Empty if not traversing a ring buffer.
- CordRepRingReader ring_reader_;
-
- // See 'Stack' alias definition.
- Stack stack_of_right_children_;
+ // Cord reader for cord btrees. Empty if not traversing a btree.
+ CordRepBtreeReader btree_reader_;
};
- // Cord::ChunkIterator::chunk_begin()
+ // Cord::chunk_begin()
//
// Returns an iterator to the first chunk of the `Cord`.
//
@@ -436,19 +522,19 @@
// absl::string_view s) {
// return std::find(c.chunk_begin(), c.chunk_end(), s);
// }
- ChunkIterator chunk_begin() const;
+ ChunkIterator chunk_begin() const ABSL_ATTRIBUTE_LIFETIME_BOUND;
- // Cord::ChunkItertator::chunk_end()
+ // Cord::chunk_end()
//
// Returns an iterator one increment past the last chunk of the `Cord`.
//
// Generally, prefer using `Cord::Chunks()` within a range-based for loop for
// iterating over the chunks of a Cord. This method may be useful for getting
// a `ChunkIterator` where range-based for-loops may not be available.
- ChunkIterator chunk_end() const;
+ ChunkIterator chunk_end() const ABSL_ATTRIBUTE_LIFETIME_BOUND;
//----------------------------------------------------------------------------
- // Cord::ChunkIterator::ChunkRange
+ // Cord::ChunkRange
//----------------------------------------------------------------------------
//
// `ChunkRange` is a helper class for iterating over the chunks of the `Cord`,
@@ -461,20 +547,30 @@
// `Cord::chunk_begin()` and `Cord::chunk_end()`.
class ChunkRange {
public:
- explicit ChunkRange(const Cord* cord) : cord_(cord) {}
+ // Fulfill minimum c++ container requirements [container.requirements]
+ // These (partial) container type definitions allow ChunkRange to be used
+ // in various utilities expecting a subset of [container.requirements].
+ // For example, the below enables using `::testing::ElementsAre(...)`
+ using value_type = absl::string_view;
+ using reference = value_type&;
+ using const_reference = const value_type&;
+ using iterator = ChunkIterator;
+ using const_iterator = ChunkIterator;
+
+ explicit ChunkRange(absl::Nonnull<const Cord*> cord) : cord_(cord) {}
ChunkIterator begin() const;
ChunkIterator end() const;
private:
- const Cord* cord_;
+ absl::Nonnull<const Cord*> cord_;
};
// Cord::Chunks()
//
- // Returns a `Cord::ChunkIterator::ChunkRange` for iterating over the chunks
- // of a `Cord` with a range-based for-loop. For most iteration tasks on a
- // Cord, use `Cord::Chunks()` to retrieve this iterator.
+ // Returns a `Cord::ChunkRange` for iterating over the chunks of a `Cord` with
+ // a range-based for-loop. For most iteration tasks on a Cord, use
+ // `Cord::Chunks()` to retrieve this iterator.
//
// Example:
//
@@ -489,7 +585,7 @@
// // The temporary Cord returned by CordFactory has been destroyed!
// }
// }
- ChunkRange Chunks() const;
+ ChunkRange Chunks() const ABSL_ATTRIBUTE_LIFETIME_BOUND;
//----------------------------------------------------------------------------
// Cord::CharIterator
@@ -520,7 +616,7 @@
using iterator_category = std::input_iterator_tag;
using value_type = char;
using difference_type = ptrdiff_t;
- using pointer = const char*;
+ using pointer = absl::Nonnull<const char*>;
using reference = const char&;
CharIterator() = default;
@@ -535,77 +631,88 @@
friend Cord;
private:
- explicit CharIterator(const Cord* cord) : chunk_iterator_(cord) {}
+ explicit CharIterator(absl::Nonnull<const Cord*> cord)
+ : chunk_iterator_(cord) {}
ChunkIterator chunk_iterator_;
};
- // Cord::CharIterator::AdvanceAndRead()
+ // Cord::AdvanceAndRead()
//
// Advances the `Cord::CharIterator` by `n_bytes` and returns the bytes
// advanced as a separate `Cord`. `n_bytes` must be less than or equal to the
// number of bytes within the Cord; otherwise, behavior is undefined. It is
// valid to pass `char_end()` and `0`.
- static Cord AdvanceAndRead(CharIterator* it, size_t n_bytes);
+ static Cord AdvanceAndRead(absl::Nonnull<CharIterator*> it, size_t n_bytes);
- // Cord::CharIterator::Advance()
+ // Cord::Advance()
//
// Advances the `Cord::CharIterator` by `n_bytes`. `n_bytes` must be less than
// or equal to the number of bytes remaining within the Cord; otherwise,
// behavior is undefined. It is valid to pass `char_end()` and `0`.
- static void Advance(CharIterator* it, size_t n_bytes);
+ static void Advance(absl::Nonnull<CharIterator*> it, size_t n_bytes);
- // Cord::CharIterator::ChunkRemaining()
+ // Cord::ChunkRemaining()
//
// Returns the longest contiguous view starting at the iterator's position.
//
// `it` must be dereferenceable.
static absl::string_view ChunkRemaining(const CharIterator& it);
- // Cord::CharIterator::char_begin()
+ // Cord::char_begin()
//
// Returns an iterator to the first character of the `Cord`.
//
// Generally, prefer using `Cord::Chars()` within a range-based for loop for
// iterating over the chunks of a Cord. This method may be useful for getting
// a `CharIterator` where range-based for-loops may not be available.
- CharIterator char_begin() const;
+ CharIterator char_begin() const ABSL_ATTRIBUTE_LIFETIME_BOUND;
- // Cord::CharIterator::char_end()
+ // Cord::char_end()
//
// Returns an iterator to one past the last character of the `Cord`.
//
// Generally, prefer using `Cord::Chars()` within a range-based for loop for
// iterating over the chunks of a Cord. This method may be useful for getting
// a `CharIterator` where range-based for-loops are not useful.
- CharIterator char_end() const;
+ CharIterator char_end() const ABSL_ATTRIBUTE_LIFETIME_BOUND;
- // Cord::CharIterator::CharRange
+ // Cord::CharRange
//
// `CharRange` is a helper class for iterating over the characters of a
// producing an iterator which can be used within a range-based for loop.
// Construction of a `CharRange` will return an iterator pointing to the first
// character of the Cord. Generally, do not construct a `CharRange` directly;
- // instead, prefer to use the `Cord::Chars()` method show below.
+ // instead, prefer to use the `Cord::Chars()` method shown below.
//
// Implementation note: `CharRange` is simply a convenience wrapper over
// `Cord::char_begin()` and `Cord::char_end()`.
class CharRange {
public:
- explicit CharRange(const Cord* cord) : cord_(cord) {}
+ // Fulfill minimum c++ container requirements [container.requirements]
+ // These (partial) container type definitions allow CharRange to be used
+ // in various utilities expecting a subset of [container.requirements].
+ // For example, the below enables using `::testing::ElementsAre(...)`
+ using value_type = char;
+ using reference = value_type&;
+ using const_reference = const value_type&;
+ using iterator = CharIterator;
+ using const_iterator = CharIterator;
+
+ explicit CharRange(absl::Nonnull<const Cord*> cord) : cord_(cord) {}
CharIterator begin() const;
CharIterator end() const;
private:
- const Cord* cord_;
+ absl::Nonnull<const Cord*> cord_;
};
- // Cord::CharIterator::Chars()
+ // Cord::Chars()
//
- // Returns a `Cord::CharIterator` for iterating over the characters of a
- // `Cord` with a range-based for-loop. For most character-based iteration
- // tasks on a Cord, use `Cord::Chars()` to retrieve this iterator.
+ // Returns a `Cord::CharRange` for iterating over the characters of a `Cord`
+ // with a range-based for-loop. For most character-based iteration tasks on a
+ // Cord, use `Cord::Chars()` to retrieve this iterator.
//
// Example:
//
@@ -620,7 +727,7 @@
// // The temporary Cord returned by CordFactory has been destroyed!
// }
// }
- CharRange Chars() const;
+ CharRange Chars() const ABSL_ATTRIBUTE_LIFETIME_BOUND;
// Cord::operator[]
//
@@ -638,20 +745,61 @@
//
// If this cord's representation is a single flat array, returns a
// string_view referencing that array. Otherwise returns nullopt.
- absl::optional<absl::string_view> TryFlat() const;
+ absl::optional<absl::string_view> TryFlat() const
+ ABSL_ATTRIBUTE_LIFETIME_BOUND;
// Cord::Flatten()
//
// Flattens the cord into a single array and returns a view of the data.
//
// If the cord was already flat, the contents are not modified.
- absl::string_view Flatten();
+ absl::string_view Flatten() ABSL_ATTRIBUTE_LIFETIME_BOUND;
+
+ // Cord::Find()
+ //
+ // Returns an iterator to the first occurrance of the substring `needle`.
+ //
+ // If the substring `needle` does not occur, `Cord::char_end()` is returned.
+ CharIterator Find(absl::string_view needle) const;
+ CharIterator Find(const absl::Cord& needle) const;
// Supports absl::Cord as a sink object for absl::Format().
- friend void AbslFormatFlush(absl::Cord* cord, absl::string_view part) {
+ friend void AbslFormatFlush(absl::Nonnull<absl::Cord*> cord,
+ absl::string_view part) {
cord->Append(part);
}
+ // Support automatic stringification with absl::StrCat and absl::StrFormat.
+ template <typename Sink>
+ friend void AbslStringify(Sink& sink, const absl::Cord& cord) {
+ for (absl::string_view chunk : cord.Chunks()) {
+ sink.Append(chunk);
+ }
+ }
+
+ // Cord::SetExpectedChecksum()
+ //
+ // Stores a checksum value with this non-empty cord instance, for later
+ // retrieval.
+ //
+ // The expected checksum is a number stored out-of-band, alongside the data.
+ // It is preserved across copies and assignments, but any mutations to a cord
+ // will cause it to lose its expected checksum.
+ //
+ // The expected checksum is not part of a Cord's value, and does not affect
+ // operations such as equality or hashing.
+ //
+ // This field is intended to store a CRC32C checksum for later validation, to
+ // help support end-to-end checksum workflows. However, the Cord API itself
+ // does no CRC validation, and assigns no meaning to this number.
+ //
+ // This call has no effect if this cord is empty.
+ void SetExpectedChecksum(uint32_t crc);
+
+ // Returns this cord's expected checksum, if it has one. Otherwise, returns
+ // nullopt.
+ absl::optional<uint32_t> ExpectedChecksum() const;
+
template <typename H>
friend H AbslHashValue(H hash_state, const absl::Cord& c) {
absl::optional<absl::string_view> maybe_flat = c.TryFlat();
@@ -667,7 +815,8 @@
// be used by spelling absl::strings_internal::MakeStringConstant, which is
// also an internal API.
template <typename T>
- explicit constexpr Cord(strings_internal::StringConstant<T>);
+ // NOLINTNEXTLINE(google-explicit-constructor)
+ constexpr Cord(strings_internal::StringConstant<T>);
private:
using CordRep = absl::cord_internal::CordRep;
@@ -686,7 +835,8 @@
friend bool operator==(const Cord& lhs, const Cord& rhs);
friend bool operator==(const Cord& lhs, absl::string_view rhs);
- friend const CordzInfo* GetCordzInfoForTesting(const Cord& cord);
+ friend absl::Nullable<const CordzInfo*> GetCordzInfoForTesting(
+ const Cord& cord);
// Calls the provided function once for each cord chunk, in order. Unlike
// Chunks(), this API will not allocate memory.
@@ -713,112 +863,106 @@
InlineRep& operator=(const InlineRep& src);
InlineRep& operator=(InlineRep&& src) noexcept;
- explicit constexpr InlineRep(cord_internal::InlineData data);
+ explicit constexpr InlineRep(absl::string_view sv,
+ absl::Nullable<CordRep*> rep);
- void Swap(InlineRep* rhs);
- bool empty() const;
+ void Swap(absl::Nonnull<InlineRep*> rhs);
size_t size() const;
- const char* data() const; // Returns nullptr if holding pointer
- void set_data(const char* data, size_t n,
- bool nullify_tail); // Discards pointer, if any
- char* set_data(size_t n); // Write data to the result
+ // Returns nullptr if holding pointer
+ absl::Nullable<const char*> data() const;
+ // Discards pointer, if any
+ void set_data(absl::Nonnull<const char*> data, size_t n);
+ absl::Nonnull<char*> set_data(size_t n); // Write data to the result
// Returns nullptr if holding bytes
- absl::cord_internal::CordRep* tree() const;
- absl::cord_internal::CordRep* as_tree() const;
+ absl::Nullable<absl::cord_internal::CordRep*> tree() const;
+ absl::Nonnull<absl::cord_internal::CordRep*> as_tree() const;
+ absl::Nonnull<const char*> as_chars() const;
// Returns non-null iff was holding a pointer
- absl::cord_internal::CordRep* clear();
+ absl::Nullable<absl::cord_internal::CordRep*> clear();
// Converts to pointer if necessary.
- void reduce_size(size_t n); // REQUIRES: holding data
+ void reduce_size(size_t n); // REQUIRES: holding data
void remove_prefix(size_t n); // REQUIRES: holding data
void AppendArray(absl::string_view src, MethodIdentifier method);
absl::string_view FindFlatStartPiece() const;
// Creates a CordRepFlat instance from the current inlined data with `extra'
// bytes of desired additional capacity.
- CordRepFlat* MakeFlatWithExtraCapacity(size_t extra);
+ absl::Nonnull<CordRepFlat*> MakeFlatWithExtraCapacity(size_t extra);
// Sets the tree value for this instance. `rep` must not be null.
// Requires the current instance to hold a tree, and a lock to be held on
// any CordzInfo referenced by this instance. The latter is enforced through
// the CordzUpdateScope argument. If the current instance is sampled, then
// the CordzInfo instance is updated to reference the new `rep` value.
- void SetTree(CordRep* rep, const CordzUpdateScope& scope);
+ void SetTree(absl::Nonnull<CordRep*> rep, const CordzUpdateScope& scope);
// Identical to SetTree(), except that `rep` is allowed to be null, in
// which case the current instance is reset to an empty value.
- void SetTreeOrEmpty(CordRep* rep, const CordzUpdateScope& scope);
+ void SetTreeOrEmpty(absl::Nullable<CordRep*> rep,
+ const CordzUpdateScope& scope);
// Sets the tree value for this instance, and randomly samples this cord.
// This function disregards existing contents in `data_`, and should be
// called when a Cord is 'promoted' from an 'uninitialized' or 'inlined'
// value to a non-inlined (tree / ring) value.
- void EmplaceTree(CordRep* rep, MethodIdentifier method);
+ void EmplaceTree(absl::Nonnull<CordRep*> rep, MethodIdentifier method);
// Identical to EmplaceTree, except that it copies the parent stack from
// the provided `parent` data if the parent is sampled.
- void EmplaceTree(CordRep* rep, const InlineData& parent,
+ void EmplaceTree(absl::Nonnull<CordRep*> rep, const InlineData& parent,
MethodIdentifier method);
// Commits the change of a newly created, or updated `rep` root value into
// this cord. `old_rep` indicates the old (inlined or tree) value of the
// cord, and determines if the commit invokes SetTree() or EmplaceTree().
- void CommitTree(const CordRep* old_rep, CordRep* rep,
- const CordzUpdateScope& scope, MethodIdentifier method);
+ void CommitTree(absl::Nullable<const CordRep*> old_rep,
+ absl::Nonnull<CordRep*> rep, const CordzUpdateScope& scope,
+ MethodIdentifier method);
- void AppendTreeToInlined(CordRep* tree, MethodIdentifier method);
- void AppendTreeToTree(CordRep* tree, MethodIdentifier method);
- void AppendTree(CordRep* tree, MethodIdentifier method);
- void PrependTreeToInlined(CordRep* tree, MethodIdentifier method);
- void PrependTreeToTree(CordRep* tree, MethodIdentifier method);
- void PrependTree(CordRep* tree, MethodIdentifier method);
+ void AppendTreeToInlined(absl::Nonnull<CordRep*> tree,
+ MethodIdentifier method);
+ void AppendTreeToTree(absl::Nonnull<CordRep*> tree,
+ MethodIdentifier method);
+ void AppendTree(absl::Nonnull<CordRep*> tree, MethodIdentifier method);
+ void PrependTreeToInlined(absl::Nonnull<CordRep*> tree,
+ MethodIdentifier method);
+ void PrependTreeToTree(absl::Nonnull<CordRep*> tree,
+ MethodIdentifier method);
+ void PrependTree(absl::Nonnull<CordRep*> tree, MethodIdentifier method);
- template <bool has_length>
- void GetAppendRegion(char** region, size_t* size, size_t length);
+ bool IsSame(const InlineRep& other) const { return data_ == other.data_; }
- bool IsSame(const InlineRep& other) const {
- return memcmp(&data_, &other.data_, sizeof(data_)) == 0;
- }
- int BitwiseCompare(const InlineRep& other) const {
- uint64_t x, y;
- // Use memcpy to avoid aliasing issues.
- memcpy(&x, &data_, sizeof(x));
- memcpy(&y, &other.data_, sizeof(y));
- if (x == y) {
- memcpy(&x, reinterpret_cast<const char*>(&data_) + 8, sizeof(x));
- memcpy(&y, reinterpret_cast<const char*>(&other.data_) + 8, sizeof(y));
- if (x == y) return 0;
- }
- return absl::big_endian::FromHost64(x) < absl::big_endian::FromHost64(y)
- ? -1
- : 1;
- }
- void CopyTo(std::string* dst) const {
+ void CopyTo(absl::Nonnull<std::string*> dst) const {
// memcpy is much faster when operating on a known size. On most supported
// platforms, the small string optimization is large enough that resizing
// to 15 bytes does not cause a memory allocation.
- absl::strings_internal::STLStringResizeUninitialized(dst,
- sizeof(data_) - 1);
- memcpy(&(*dst)[0], &data_, sizeof(data_) - 1);
+ absl::strings_internal::STLStringResizeUninitialized(dst, kMaxInline);
+ data_.copy_max_inline_to(&(*dst)[0]);
// erase is faster than resize because the logic for memory allocation is
// not needed.
dst->erase(inline_size());
}
// Copies the inline contents into `dst`. Assumes the cord is not empty.
- void CopyToArray(char* dst) const;
+ void CopyToArray(absl::Nonnull<char*> dst) const;
bool is_tree() const { return data_.is_tree(); }
// Returns true if the Cord is being profiled by cordz.
bool is_profiled() const { return data_.is_tree() && data_.is_profiled(); }
+ // Returns the available inlined capacity, or 0 if is_tree() == true.
+ size_t remaining_inline_capacity() const {
+ return data_.is_tree() ? 0 : kMaxInline - data_.inline_size();
+ }
+
// Returns the profiled CordzInfo, or nullptr if not sampled.
- absl::cord_internal::CordzInfo* cordz_info() const {
+ absl::Nullable<absl::cord_internal::CordzInfo*> cordz_info() const {
return data_.cordz_info();
}
- // Sets the profiled CordzInfo. `cordz_info` must not be null.
- void set_cordz_info(cord_internal::CordzInfo* cordz_info) {
+ // Sets the profiled CordzInfo.
+ void set_cordz_info(absl::Nonnull<cord_internal::CordzInfo*> cordz_info) {
assert(cordz_info != nullptr);
data_.set_cordz_info(cordz_info);
}
@@ -838,27 +982,31 @@
void set_inline_size(size_t size) { data_.set_inline_size(size); }
size_t inline_size() const { return data_.inline_size(); }
+ // Empty cords that carry a checksum have a CordRepCrc node with a null
+ // child node. The code can avoid lots of special cases where it would
+ // otherwise transition from tree to inline storage if we just remove the
+ // CordRepCrc node before mutations. Must never be called inside a
+ // CordzUpdateScope since it untracks the cordz info.
+ void MaybeRemoveEmptyCrcNode();
+
cord_internal::InlineData data_;
};
InlineRep contents_;
- // Helper for MemoryUsage().
- static size_t MemoryUsageAux(const absl::cord_internal::CordRep* rep);
-
// Helper for GetFlat() and TryFlat().
- static bool GetFlatAux(absl::cord_internal::CordRep* rep,
- absl::string_view* fragment);
+ static bool GetFlatAux(absl::Nonnull<absl::cord_internal::CordRep*> rep,
+ absl::Nonnull<absl::string_view*> fragment);
// Helper for ForEachChunk().
static void ForEachChunkAux(
- absl::cord_internal::CordRep* rep,
+ absl::Nonnull<absl::cord_internal::CordRep*> rep,
absl::FunctionRef<void(absl::string_view)> callback);
// The destructor for non-empty Cords.
void DestroyCordSlow();
// Out-of-line implementation of slower parts of logic.
- void CopyToArraySlowPath(char* dst) const;
+ void CopyToArraySlowPath(absl::Nonnull<char*> dst) const;
int CompareSlowPath(absl::string_view rhs, size_t compared_size,
size_t size_to_compare) const;
int CompareSlowPath(const Cord& rhs, size_t compared_size,
@@ -875,13 +1023,27 @@
// Returns a new reference to contents_.tree(), or steals an existing
// reference if called on an rvalue.
- absl::cord_internal::CordRep* TakeRep() const&;
- absl::cord_internal::CordRep* TakeRep() &&;
+ absl::Nonnull<absl::cord_internal::CordRep*> TakeRep() const&;
+ absl::Nonnull<absl::cord_internal::CordRep*> TakeRep() &&;
// Helper for Append().
template <typename C>
void AppendImpl(C&& src);
+ // Appends / Prepends `src` to this instance, using precise sizing.
+ // This method does explicitly not attempt to use any spare capacity
+ // in any pending last added private owned flat.
+ // Requires `src` to be <= kMaxFlatLength.
+ void AppendPrecise(absl::string_view src, MethodIdentifier method);
+ void PrependPrecise(absl::string_view src, MethodIdentifier method);
+
+ CordBuffer GetAppendBufferSlowPath(size_t block_size, size_t capacity,
+ size_t min_capacity);
+
+ // Prepends the provided data to this instance. `method` contains the public
+ // API method for this action which is tracked for Cordz sampling purposes.
+ void PrependArray(absl::string_view src, MethodIdentifier method);
+
// Assigns the value in 'src' to this instance, 'stealing' its contents.
// Requires src.length() > kMaxBytesToCopy.
Cord& AssignLargeString(std::string&& src);
@@ -896,6 +1058,13 @@
});
return H::combine(combiner.finalize(std::move(hash_state)), size());
}
+
+ friend class CrcCord;
+ void SetCrcCordState(crc_internal::CrcCordState state);
+ absl::Nullable<const crc_internal::CrcCordState*> MaybeGetCrcCordState()
+ const;
+
+ CharIterator FindImpl(CharIterator it, absl::string_view needle) const;
};
ABSL_NAMESPACE_END
@@ -912,63 +1081,19 @@
namespace cord_internal {
-// Fast implementation of memmove for up to 15 bytes. This implementation is
-// safe for overlapping regions. If nullify_tail is true, the destination is
-// padded with '\0' up to 16 bytes.
-inline void SmallMemmove(char* dst, const char* src, size_t n,
- bool nullify_tail = false) {
- if (n >= 8) {
- assert(n <= 16);
- uint64_t buf1;
- uint64_t buf2;
- memcpy(&buf1, src, 8);
- memcpy(&buf2, src + n - 8, 8);
- if (nullify_tail) {
- memset(dst + 8, 0, 8);
- }
- memcpy(dst, &buf1, 8);
- memcpy(dst + n - 8, &buf2, 8);
- } else if (n >= 4) {
- uint32_t buf1;
- uint32_t buf2;
- memcpy(&buf1, src, 4);
- memcpy(&buf2, src + n - 4, 4);
- if (nullify_tail) {
- memset(dst + 4, 0, 4);
- memset(dst + 8, 0, 8);
- }
- memcpy(dst, &buf1, 4);
- memcpy(dst + n - 4, &buf2, 4);
- } else {
- if (n != 0) {
- dst[0] = src[0];
- dst[n / 2] = src[n / 2];
- dst[n - 1] = src[n - 1];
- }
- if (nullify_tail) {
- memset(dst + 8, 0, 8);
- memset(dst + n, 0, 8);
- }
- }
-}
-
// Does non-template-specific `CordRepExternal` initialization.
-// Expects `data` to be non-empty.
-void InitializeCordRepExternal(absl::string_view data, CordRepExternal* rep);
+// Requires `data` to be non-empty.
+void InitializeCordRepExternal(absl::string_view data,
+ absl::Nonnull<CordRepExternal*> rep);
// Creates a new `CordRep` that owns `data` and `releaser` and returns a pointer
-// to it, or `nullptr` if `data` was empty.
+// to it. Requires `data` to be non-empty.
template <typename Releaser>
// NOLINTNEXTLINE - suppress clang-tidy raw pointer return.
-CordRep* NewExternalRep(absl::string_view data, Releaser&& releaser) {
+absl::Nonnull<CordRep*> NewExternalRep(absl::string_view data,
+ Releaser&& releaser) {
+ assert(!data.empty());
using ReleaserType = absl::decay_t<Releaser>;
- if (data.empty()) {
- // Never create empty external nodes.
- InvokeReleaser(Rank0{}, ReleaserType(std::forward<Releaser>(releaser)),
- data);
- return nullptr;
- }
-
CordRepExternal* rep = new CordRepExternalImpl<ReleaserType>(
std::forward<Releaser>(releaser), 0);
InitializeCordRepExternal(data, rep);
@@ -978,7 +1103,7 @@
// Overload for function reference types that dispatches using a function
// pointer because there are no `alignof()` or `sizeof()` a function reference.
// NOLINTNEXTLINE - suppress clang-tidy raw pointer return.
-inline CordRep* NewExternalRep(absl::string_view data,
+inline absl::Nonnull<CordRep*> NewExternalRep(absl::string_view data,
void (&releaser)(absl::string_view)) {
return NewExternalRep(data, &releaser);
}
@@ -988,16 +1113,22 @@
template <typename Releaser>
Cord MakeCordFromExternal(absl::string_view data, Releaser&& releaser) {
Cord cord;
- if (auto* rep = ::absl::cord_internal::NewExternalRep(
- data, std::forward<Releaser>(releaser))) {
- cord.contents_.EmplaceTree(rep,
+ if (ABSL_PREDICT_TRUE(!data.empty())) {
+ cord.contents_.EmplaceTree(::absl::cord_internal::NewExternalRep(
+ data, std::forward<Releaser>(releaser)),
Cord::MethodIdentifier::kMakeCordFromExternal);
+ } else {
+ using ReleaserType = absl::decay_t<Releaser>;
+ cord_internal::InvokeReleaser(
+ cord_internal::Rank1{}, ReleaserType(std::forward<Releaser>(releaser)),
+ data);
}
return cord;
}
-constexpr Cord::InlineRep::InlineRep(cord_internal::InlineData data)
- : data_(data) {}
+constexpr Cord::InlineRep::InlineRep(absl::string_view sv,
+ absl::Nullable<CordRep*> rep)
+ : data_(sv, rep) {}
inline Cord::InlineRep::InlineRep(const Cord::InlineRep& src)
: data_(InlineData::kDefaultInit) {
@@ -1035,23 +1166,31 @@
return *this;
}
-inline void Cord::InlineRep::Swap(Cord::InlineRep* rhs) {
+inline void Cord::InlineRep::Swap(absl::Nonnull<Cord::InlineRep*> rhs) {
if (rhs == this) {
return;
}
- std::swap(data_, rhs->data_);
+ using std::swap;
+ swap(data_, rhs->data_);
}
-inline const char* Cord::InlineRep::data() const {
+inline absl::Nullable<const char*> Cord::InlineRep::data() const {
return is_tree() ? nullptr : data_.as_chars();
}
-inline absl::cord_internal::CordRep* Cord::InlineRep::as_tree() const {
+inline absl::Nonnull<const char*> Cord::InlineRep::as_chars() const {
+ assert(!data_.is_tree());
+ return data_.as_chars();
+}
+
+inline absl::Nonnull<absl::cord_internal::CordRep*> Cord::InlineRep::as_tree()
+ const {
assert(data_.is_tree());
return data_.as_tree();
}
-inline absl::cord_internal::CordRep* Cord::InlineRep::tree() const {
+inline absl::Nullable<absl::cord_internal::CordRep*> Cord::InlineRep::tree()
+ const {
if (is_tree()) {
return as_tree();
} else {
@@ -1059,36 +1198,35 @@
}
}
-inline bool Cord::InlineRep::empty() const { return data_.is_empty(); }
-
inline size_t Cord::InlineRep::size() const {
return is_tree() ? as_tree()->length : inline_size();
}
-inline cord_internal::CordRepFlat* Cord::InlineRep::MakeFlatWithExtraCapacity(
- size_t extra) {
+inline absl::Nonnull<cord_internal::CordRepFlat*>
+Cord::InlineRep::MakeFlatWithExtraCapacity(size_t extra) {
static_assert(cord_internal::kMinFlatLength >= sizeof(data_), "");
size_t len = data_.inline_size();
auto* result = CordRepFlat::New(len + extra);
result->length = len;
- memcpy(result->Data(), data_.as_chars(), sizeof(data_));
+ data_.copy_max_inline_to(result->Data());
return result;
}
-inline void Cord::InlineRep::EmplaceTree(CordRep* rep,
+inline void Cord::InlineRep::EmplaceTree(absl::Nonnull<CordRep*> rep,
MethodIdentifier method) {
assert(rep);
data_.make_tree(rep);
CordzInfo::MaybeTrackCord(data_, method);
}
-inline void Cord::InlineRep::EmplaceTree(CordRep* rep, const InlineData& parent,
+inline void Cord::InlineRep::EmplaceTree(absl::Nonnull<CordRep*> rep,
+ const InlineData& parent,
MethodIdentifier method) {
data_.make_tree(rep);
CordzInfo::MaybeTrackCord(data_, parent, method);
}
-inline void Cord::InlineRep::SetTree(CordRep* rep,
+inline void Cord::InlineRep::SetTree(absl::Nonnull<CordRep*> rep,
const CordzUpdateScope& scope) {
assert(rep);
assert(data_.is_tree());
@@ -1096,7 +1234,7 @@
scope.SetCordRep(rep);
}
-inline void Cord::InlineRep::SetTreeOrEmpty(CordRep* rep,
+inline void Cord::InlineRep::SetTreeOrEmpty(absl::Nullable<CordRep*> rep,
const CordzUpdateScope& scope) {
assert(data_.is_tree());
if (rep) {
@@ -1107,7 +1245,8 @@
scope.SetCordRep(rep);
}
-inline void Cord::InlineRep::CommitTree(const CordRep* old_rep, CordRep* rep,
+inline void Cord::InlineRep::CommitTree(absl::Nullable<const CordRep*> old_rep,
+ absl::Nonnull<CordRep*> rep,
const CordzUpdateScope& scope,
MethodIdentifier method) {
if (old_rep) {
@@ -1117,7 +1256,7 @@
}
}
-inline absl::cord_internal::CordRep* Cord::InlineRep::clear() {
+inline absl::Nullable<absl::cord_internal::CordRep*> Cord::InlineRep::clear() {
if (is_tree()) {
CordzInfo::MaybeUntrackCord(cordz_info());
}
@@ -1126,13 +1265,25 @@
return result;
}
-inline void Cord::InlineRep::CopyToArray(char* dst) const {
+inline void Cord::InlineRep::CopyToArray(absl::Nonnull<char*> dst) const {
assert(!is_tree());
size_t n = inline_size();
assert(n != 0);
cord_internal::SmallMemmove(dst, data_.as_chars(), n);
}
+inline void Cord::InlineRep::MaybeRemoveEmptyCrcNode() {
+ CordRep* rep = tree();
+ if (rep == nullptr || ABSL_PREDICT_TRUE(rep->length > 0)) {
+ return;
+ }
+ assert(rep->IsCrc());
+ assert(rep->crc()->child == nullptr);
+ CordzInfo::MaybeUntrackCord(cordz_info());
+ CordRep::Unref(rep);
+ ResetToEmpty();
+}
+
constexpr inline Cord::Cord() noexcept {}
inline Cord::Cord(absl::string_view src)
@@ -1140,13 +1291,12 @@
template <typename T>
constexpr Cord::Cord(strings_internal::StringConstant<T>)
- : contents_(strings_internal::StringConstant<T>::value.size() <=
+ : contents_(strings_internal::StringConstant<T>::value,
+ strings_internal::StringConstant<T>::value.size() <=
cord_internal::kMaxInline
- ? cord_internal::InlineData(
- strings_internal::StringConstant<T>::value)
- : cord_internal::InlineData(
- &cord_internal::ConstInitExternalStorage<
- strings_internal::StringConstant<T>>::value)) {}
+ ? nullptr
+ : &cord_internal::ConstInitExternalStorage<
+ strings_internal::StringConstant<T>>::value) {}
inline Cord& Cord::operator=(const Cord& x) {
contents_ = x.contents_;
@@ -1182,12 +1332,23 @@
return contents_.size();
}
-inline bool Cord::empty() const { return contents_.empty(); }
+inline bool Cord::empty() const { return size() == 0; }
-inline size_t Cord::EstimatedMemoryUsage() const {
+inline size_t Cord::EstimatedMemoryUsage(
+ CordMemoryAccounting accounting_method) const {
size_t result = sizeof(Cord);
if (const absl::cord_internal::CordRep* rep = contents_.tree()) {
- result += MemoryUsageAux(rep);
+ switch (accounting_method) {
+ case CordMemoryAccounting::kFairShare:
+ result += cord_internal::GetEstimatedFairShareMemoryUsage(rep);
+ break;
+ case CordMemoryAccounting::kTotalMorePrecise:
+ result += cord_internal::GetMorePreciseMemoryUsage(rep);
+ break;
+ case CordMemoryAccounting::kTotal:
+ result += cord_internal::GetEstimatedMemoryUsage(rep);
+ break;
+ }
}
return result;
}
@@ -1221,12 +1382,51 @@
contents_.AppendArray(src, CordzUpdateTracker::kAppendString);
}
+inline void Cord::Prepend(absl::string_view src) {
+ PrependArray(src, CordzUpdateTracker::kPrependString);
+}
+
+inline void Cord::Append(CordBuffer buffer) {
+ if (ABSL_PREDICT_FALSE(buffer.length() == 0)) return;
+ absl::string_view short_value;
+ if (CordRep* rep = buffer.ConsumeValue(short_value)) {
+ contents_.AppendTree(rep, CordzUpdateTracker::kAppendCordBuffer);
+ } else {
+ AppendPrecise(short_value, CordzUpdateTracker::kAppendCordBuffer);
+ }
+}
+
+inline void Cord::Prepend(CordBuffer buffer) {
+ if (ABSL_PREDICT_FALSE(buffer.length() == 0)) return;
+ absl::string_view short_value;
+ if (CordRep* rep = buffer.ConsumeValue(short_value)) {
+ contents_.PrependTree(rep, CordzUpdateTracker::kPrependCordBuffer);
+ } else {
+ PrependPrecise(short_value, CordzUpdateTracker::kPrependCordBuffer);
+ }
+}
+
+inline CordBuffer Cord::GetAppendBuffer(size_t capacity, size_t min_capacity) {
+ if (empty()) return CordBuffer::CreateWithDefaultLimit(capacity);
+ return GetAppendBufferSlowPath(0, capacity, min_capacity);
+}
+
+inline CordBuffer Cord::GetCustomAppendBuffer(size_t block_size,
+ size_t capacity,
+ size_t min_capacity) {
+ if (empty()) {
+ return block_size ? CordBuffer::CreateWithCustomLimit(block_size, capacity)
+ : CordBuffer::CreateWithDefaultLimit(capacity);
+ }
+ return GetAppendBufferSlowPath(block_size, capacity, min_capacity);
+}
+
extern template void Cord::Append(std::string&& src);
extern template void Cord::Prepend(std::string&& src);
inline int Cord::Compare(const Cord& rhs) const {
if (!contents_.is_tree() && !rhs.contents_.is_tree()) {
- return contents_.BitwiseCompare(rhs.contents_);
+ return contents_.data_.Compare(rhs.contents_.data_);
}
return CompareImpl(rhs);
@@ -1246,45 +1446,51 @@
return EqualsImpl(rhs, rhs_size);
}
-inline void Cord::ChunkIterator::InitTree(cord_internal::CordRep* tree) {
- if (tree->tag == cord_internal::RING) {
- current_chunk_ = ring_reader_.Reset(tree->ring());
- return;
+inline void Cord::ChunkIterator::InitTree(
+ absl::Nonnull<cord_internal::CordRep*> tree) {
+ tree = cord_internal::SkipCrcNode(tree);
+ if (tree->tag == cord_internal::BTREE) {
+ current_chunk_ = btree_reader_.Init(tree->btree());
+ } else {
+ current_leaf_ = tree;
+ current_chunk_ = cord_internal::EdgeData(tree);
}
-
- stack_of_right_children_.push_back(tree);
- operator++();
}
-inline Cord::ChunkIterator::ChunkIterator(cord_internal::CordRep* tree)
- : bytes_remaining_(tree->length) {
+inline Cord::ChunkIterator::ChunkIterator(
+ absl::Nonnull<cord_internal::CordRep*> tree) {
+ bytes_remaining_ = tree->length;
InitTree(tree);
}
-inline Cord::ChunkIterator::ChunkIterator(const Cord* cord)
- : bytes_remaining_(cord->size()) {
- if (cord->contents_.is_tree()) {
- InitTree(cord->contents_.as_tree());
+inline Cord::ChunkIterator::ChunkIterator(absl::Nonnull<const Cord*> cord) {
+ if (CordRep* tree = cord->contents_.tree()) {
+ bytes_remaining_ = tree->length;
+ if (ABSL_PREDICT_TRUE(bytes_remaining_ != 0)) {
+ InitTree(tree);
+ } else {
+ current_chunk_ = {};
+ }
} else {
- current_chunk_ =
- absl::string_view(cord->contents_.data(), bytes_remaining_);
+ bytes_remaining_ = cord->contents_.inline_size();
+ current_chunk_ = {cord->contents_.data(), bytes_remaining_};
}
}
-inline Cord::ChunkIterator& Cord::ChunkIterator::AdvanceRing() {
- current_chunk_ = ring_reader_.Next();
+inline Cord::ChunkIterator& Cord::ChunkIterator::AdvanceBtree() {
+ current_chunk_ = btree_reader_.Next();
return *this;
}
-inline void Cord::ChunkIterator::AdvanceBytesRing(size_t n) {
+inline void Cord::ChunkIterator::AdvanceBytesBtree(size_t n) {
assert(n >= current_chunk_.size());
bytes_remaining_ -= n;
if (bytes_remaining_) {
if (n == current_chunk_.size()) {
- current_chunk_ = ring_reader_.Next();
+ current_chunk_ = btree_reader_.Next();
} else {
- size_t offset = ring_reader_.length() - bytes_remaining_;
- current_chunk_ = ring_reader_.Seek(offset);
+ size_t offset = btree_reader_.length() - bytes_remaining_;
+ current_chunk_ = btree_reader_.Seek(offset);
}
} else {
current_chunk_ = {};
@@ -1297,8 +1503,11 @@
assert(bytes_remaining_ >= current_chunk_.size());
bytes_remaining_ -= current_chunk_.size();
if (bytes_remaining_ > 0) {
- return ring_reader_ ? AdvanceRing() : AdvanceStack();
- } else {
+ if (btree_reader_) {
+ return AdvanceBtree();
+ } else {
+ assert(!current_chunk_.empty()); // Called on invalid iterator.
+ }
current_chunk_ = {};
}
return *this;
@@ -1339,7 +1548,11 @@
if (ABSL_PREDICT_TRUE(n < current_chunk_.size())) {
RemoveChunkPrefix(n);
} else if (n != 0) {
- ring_reader_ ? AdvanceBytesRing(n) : AdvanceBytesSlowPath(n);
+ if (btree_reader_) {
+ AdvanceBytesBtree(n);
+ } else {
+ bytes_remaining_ = 0;
+ }
}
}
@@ -1390,12 +1603,13 @@
return chunk_iterator_->data();
}
-inline Cord Cord::AdvanceAndRead(CharIterator* it, size_t n_bytes) {
+inline Cord Cord::AdvanceAndRead(absl::Nonnull<CharIterator*> it,
+ size_t n_bytes) {
assert(it != nullptr);
return it->chunk_iterator_.AdvanceAndReadBytes(n_bytes);
}
-inline void Cord::Advance(CharIterator* it, size_t n_bytes) {
+inline void Cord::Advance(absl::Nonnull<CharIterator*> it, size_t n_bytes) {
assert(it != nullptr);
it->chunk_iterator_.AdvanceBytes(n_bytes);
}
@@ -1426,11 +1640,11 @@
if (rep == nullptr) {
callback(absl::string_view(contents_.data(), contents_.size()));
} else {
- return ForEachChunkAux(rep, callback);
+ ForEachChunkAux(rep, callback);
}
}
-// Nonmember Cord-to-Cord relational operarators.
+// Nonmember Cord-to-Cord relational operators.
inline bool operator==(const Cord& lhs, const Cord& rhs) {
if (lhs.contents_.IsSame(rhs.contents_)) return true;
size_t rhs_size = rhs.size();
@@ -1439,12 +1653,8 @@
}
inline bool operator!=(const Cord& x, const Cord& y) { return !(x == y); }
-inline bool operator<(const Cord& x, const Cord& y) {
- return x.Compare(y) < 0;
-}
-inline bool operator>(const Cord& x, const Cord& y) {
- return x.Compare(y) > 0;
-}
+inline bool operator<(const Cord& x, const Cord& y) { return x.Compare(y) < 0; }
+inline bool operator>(const Cord& x, const Cord& y) { return x.Compare(y) > 0; }
inline bool operator<=(const Cord& x, const Cord& y) {
return x.Compare(y) <= 0;
}
@@ -1455,7 +1665,7 @@
// Nonmember Cord-to-absl::string_view relational operators.
//
// Due to implicit conversions, these also enable comparisons of Cord with
-// with std::string, ::string, and const char*.
+// std::string and const char*.
inline bool operator==(const Cord& lhs, absl::string_view rhs) {
size_t lhs_size = lhs.size();
size_t rhs_size = rhs.size();
@@ -1485,7 +1695,6 @@
public:
static size_t FlatOverhead();
static size_t MaxFlatLength();
- static size_t SizeofCordRepConcat();
static size_t SizeofCordRepExternal();
static size_t SizeofCordRepSubstring();
static size_t FlatTagToLength(uint8_t tag);
diff --git a/absl/strings/cord_analysis.cc b/absl/strings/cord_analysis.cc
new file mode 100644
index 0000000..19b0fa4
--- /dev/null
+++ b/absl/strings/cord_analysis.cc
@@ -0,0 +1,196 @@
+// Copyright 2021 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/strings/cord_analysis.h"
+
+#include <cassert>
+#include <cstddef>
+#include <cstdint>
+#include <unordered_set>
+
+#include "absl/base/config.h"
+#include "absl/base/nullability.h"
+#include "absl/strings/internal/cord_data_edge.h"
+#include "absl/strings/internal/cord_internal.h"
+#include "absl/strings/internal/cord_rep_btree.h"
+#include "absl/strings/internal/cord_rep_crc.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace cord_internal {
+namespace {
+
+// Accounting mode for analyzing memory usage.
+enum class Mode { kFairShare, kTotal, kTotalMorePrecise };
+
+// CordRepRef holds a `const CordRep*` reference in rep, and depending on mode,
+// holds a 'fraction' representing a cumulative inverse refcount weight.
+template <Mode mode>
+struct CordRepRef {
+ // Instantiates a CordRepRef instance.
+ explicit CordRepRef(absl::Nonnull<const CordRep*> r) : rep(r) {}
+
+ // Creates a child reference holding the provided child.
+ // Overloaded to add cumulative reference count for kFairShare.
+ CordRepRef Child(absl::Nonnull<const CordRep*> child) const {
+ return CordRepRef(child);
+ }
+
+ absl::Nonnull<const CordRep*> rep;
+};
+
+// RawUsage holds the computed total number of bytes.
+template <Mode mode>
+struct RawUsage {
+ size_t total = 0;
+
+ // Add 'size' to total, ignoring the CordRepRef argument.
+ void Add(size_t size, CordRepRef<mode>) { total += size; }
+};
+
+// Overloaded representation of RawUsage that tracks the set of objects
+// counted, and avoids double-counting objects referenced more than once
+// by the same Cord.
+template <>
+struct RawUsage<Mode::kTotalMorePrecise> {
+ size_t total = 0;
+ // TODO(b/289250880): Replace this with a flat_hash_set.
+ std::unordered_set<absl::Nonnull<const CordRep*>> counted;
+
+ void Add(size_t size, CordRepRef<Mode::kTotalMorePrecise> repref) {
+ if (counted.insert(repref.rep).second) {
+ total += size;
+ }
+ }
+};
+
+// Returns n / refcount avoiding a div for the common refcount == 1.
+template <typename refcount_t>
+double MaybeDiv(double d, refcount_t refcount) {
+ return refcount == 1 ? d : d / refcount;
+}
+
+// Overloaded 'kFairShare' specialization for CordRepRef. This class holds a
+// `fraction` value which represents a cumulative inverse refcount weight.
+// For example, a top node with a reference count of 2 will have a fraction
+// value of 1/2 = 0.5, representing the 'fair share' of memory it references.
+// A node below such a node with a reference count of 5 then has a fraction of
+// 0.5 / 5 = 0.1 representing the fair share of memory below that node, etc.
+template <>
+struct CordRepRef<Mode::kFairShare> {
+ // Creates a CordRepRef with the provided rep and top (parent) fraction.
+ explicit CordRepRef(absl::Nonnull<const CordRep*> r, double frac = 1.0)
+ : rep(r), fraction(MaybeDiv(frac, r->refcount.Get())) {}
+
+ // Returns a CordRepRef with a fraction of `this->fraction / child.refcount`
+ CordRepRef Child(absl::Nonnull<const CordRep*> child) const {
+ return CordRepRef(child, fraction);
+ }
+
+ absl::Nonnull<const CordRep*> rep;
+ double fraction;
+};
+
+// Overloaded 'kFairShare' specialization for RawUsage
+template <>
+struct RawUsage<Mode::kFairShare> {
+ double total = 0;
+
+ // Adds `size` multiplied by `rep.fraction` to the total size.
+ void Add(size_t size, CordRepRef<Mode::kFairShare> rep) {
+ total += static_cast<double>(size) * rep.fraction;
+ }
+};
+
+// Computes the estimated memory size of the provided data edge.
+// External reps are assumed 'heap allocated at their exact size'.
+template <Mode mode>
+void AnalyzeDataEdge(CordRepRef<mode> rep, RawUsage<mode>& raw_usage) {
+ assert(IsDataEdge(rep.rep));
+
+ // Consume all substrings
+ if (rep.rep->tag == SUBSTRING) {
+ raw_usage.Add(sizeof(CordRepSubstring), rep);
+ rep = rep.Child(rep.rep->substring()->child);
+ }
+
+ // Consume FLAT / EXTERNAL
+ const size_t size =
+ rep.rep->tag >= FLAT
+ ? rep.rep->flat()->AllocatedSize()
+ : rep.rep->length + sizeof(CordRepExternalImpl<intptr_t>);
+ raw_usage.Add(size, rep);
+}
+
+// Computes the memory size of the provided Btree tree.
+template <Mode mode>
+void AnalyzeBtree(CordRepRef<mode> rep, RawUsage<mode>& raw_usage) {
+ raw_usage.Add(sizeof(CordRepBtree), rep);
+ const CordRepBtree* tree = rep.rep->btree();
+ if (tree->height() > 0) {
+ for (CordRep* edge : tree->Edges()) {
+ AnalyzeBtree(rep.Child(edge), raw_usage);
+ }
+ } else {
+ for (CordRep* edge : tree->Edges()) {
+ AnalyzeDataEdge(rep.Child(edge), raw_usage);
+ }
+ }
+}
+
+template <Mode mode>
+size_t GetEstimatedUsage(absl::Nonnull<const CordRep*> rep) {
+ // Zero initialized memory usage totals.
+ RawUsage<mode> raw_usage;
+
+ // Capture top level node and refcount into a CordRepRef.
+ CordRepRef<mode> repref(rep);
+
+ // Consume the top level CRC node if present.
+ if (repref.rep->tag == CRC) {
+ raw_usage.Add(sizeof(CordRepCrc), repref);
+ if (repref.rep->crc()->child == nullptr) {
+ return static_cast<size_t>(raw_usage.total);
+ }
+ repref = repref.Child(repref.rep->crc()->child);
+ }
+
+ if (IsDataEdge(repref.rep)) {
+ AnalyzeDataEdge(repref, raw_usage);
+ } else if (repref.rep->tag == BTREE) {
+ AnalyzeBtree(repref, raw_usage);
+ } else {
+ assert(false);
+ }
+
+ return static_cast<size_t>(raw_usage.total);
+}
+
+} // namespace
+
+size_t GetEstimatedMemoryUsage(absl::Nonnull<const CordRep*> rep) {
+ return GetEstimatedUsage<Mode::kTotal>(rep);
+}
+
+size_t GetEstimatedFairShareMemoryUsage(absl::Nonnull<const CordRep*> rep) {
+ return GetEstimatedUsage<Mode::kFairShare>(rep);
+}
+
+size_t GetMorePreciseMemoryUsage(absl::Nonnull<const CordRep*> rep) {
+ return GetEstimatedUsage<Mode::kTotalMorePrecise>(rep);
+}
+
+} // namespace cord_internal
+ABSL_NAMESPACE_END
+} // namespace absl
diff --git a/absl/strings/cord_analysis.h b/absl/strings/cord_analysis.h
new file mode 100644
index 0000000..f8ce348
--- /dev/null
+++ b/absl/strings/cord_analysis.h
@@ -0,0 +1,63 @@
+// Copyright 2021 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef ABSL_STRINGS_CORD_ANALYSIS_H_
+#define ABSL_STRINGS_CORD_ANALYSIS_H_
+
+#include <cstddef>
+#include <cstdint>
+
+#include "absl/base/config.h"
+#include "absl/base/nullability.h"
+#include "absl/strings/internal/cord_internal.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace cord_internal {
+
+// Returns the *approximate* number of bytes held in full or in part by this
+// Cord (which may not remain the same between invocations). Cords that share
+// memory could each be "charged" independently for the same shared memory.
+size_t GetEstimatedMemoryUsage(absl::Nonnull<const CordRep*> rep);
+
+// Returns the *approximate* number of bytes held in full or in part by this
+// Cord for the distinct memory held by this cord. This is similar to
+// `GetEstimatedMemoryUsage()`, except that if the cord has multiple references
+// to the same memory, that memory is only counted once.
+//
+// For example:
+// absl::Cord cord;
+// cord.append(some_other_cord);
+// cord.append(some_other_cord);
+// // Calls GetEstimatedMemoryUsage() and counts `other_cord` twice:
+// cord.EstimatedMemoryUsage(kTotal);
+// // Calls GetMorePreciseMemoryUsage() and counts `other_cord` once:
+// cord.EstimatedMemoryUsage(kTotalMorePrecise);
+//
+// This is more expensive than `GetEstimatedMemoryUsage()` as it requires
+// deduplicating all memory references.
+size_t GetMorePreciseMemoryUsage(absl::Nonnull<const CordRep*> rep);
+
+// Returns the *approximate* number of bytes held in full or in part by this
+// CordRep weighted by the sharing ratio of that data. For example, if some data
+// edge is shared by 4 different Cords, then each cord is attribute 1/4th of
+// the total memory usage as a 'fair share' of the total memory usage.
+size_t GetEstimatedFairShareMemoryUsage(absl::Nonnull<const CordRep*> rep);
+
+} // namespace cord_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+
+
+#endif // ABSL_STRINGS_CORD_ANALYSIS_H_
diff --git a/absl/strings/cord_buffer.cc b/absl/strings/cord_buffer.cc
new file mode 100644
index 0000000..fad6269
--- /dev/null
+++ b/absl/strings/cord_buffer.cc
@@ -0,0 +1,30 @@
+// Copyright 2022 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/strings/cord_buffer.h"
+
+#include <cstddef>
+
+#include "absl/base/config.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+
+#ifdef ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL
+constexpr size_t CordBuffer::kDefaultLimit;
+constexpr size_t CordBuffer::kCustomLimit;
+#endif
+
+ABSL_NAMESPACE_END
+} // namespace absl
diff --git a/absl/strings/cord_buffer.h b/absl/strings/cord_buffer.h
new file mode 100644
index 0000000..bc0e4e4
--- /dev/null
+++ b/absl/strings/cord_buffer.h
@@ -0,0 +1,572 @@
+// Copyright 2021 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// -----------------------------------------------------------------------------
+// File: cord_buffer.h
+// -----------------------------------------------------------------------------
+//
+// This file defines an `absl::CordBuffer` data structure to hold data for
+// eventual inclusion within an existing `Cord` data structure. Cord buffers are
+// useful for building large Cords that may require custom allocation of its
+// associated memory.
+//
+#ifndef ABSL_STRINGS_CORD_BUFFER_H_
+#define ABSL_STRINGS_CORD_BUFFER_H_
+
+#include <algorithm>
+#include <cassert>
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+#include <utility>
+
+#include "absl/base/config.h"
+#include "absl/base/macros.h"
+#include "absl/numeric/bits.h"
+#include "absl/strings/internal/cord_internal.h"
+#include "absl/strings/internal/cord_rep_flat.h"
+#include "absl/types/span.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+
+class Cord;
+class CordBufferTestPeer;
+
+// CordBuffer
+//
+// CordBuffer manages memory buffers for purposes such as zero-copy APIs as well
+// as applications building cords with large data requiring granular control
+// over the allocation and size of cord data. For example, a function creating
+// a cord of random data could use a CordBuffer as follows:
+//
+// absl::Cord CreateRandomCord(size_t length) {
+// absl::Cord cord;
+// while (length > 0) {
+// CordBuffer buffer = CordBuffer::CreateWithDefaultLimit(length);
+// absl::Span<char> data = buffer.available_up_to(length);
+// FillRandomValues(data.data(), data.size());
+// buffer.IncreaseLengthBy(data.size());
+// cord.Append(std::move(buffer));
+// length -= data.size();
+// }
+// return cord;
+// }
+//
+// CordBuffer instances are by default limited to a capacity of `kDefaultLimit`
+// bytes. `kDefaultLimit` is currently just under 4KiB, but this default may
+// change in the future and/or for specific architectures. The default limit is
+// aimed to provide a good trade-off between performance and memory overhead.
+// Smaller buffers typically incur more compute cost while larger buffers are
+// more CPU efficient but create significant memory overhead because of such
+// allocations being less granular. Using larger buffers may also increase the
+// risk of memory fragmentation.
+//
+// Applications create a buffer using one of the `CreateWithDefaultLimit()` or
+// `CreateWithCustomLimit()` methods. The returned instance will have a non-zero
+// capacity and a zero length. Applications use the `data()` method to set the
+// contents of the managed memory, and once done filling the buffer, use the
+// `IncreaseLengthBy()` or 'SetLength()' method to specify the length of the
+// initialized data before adding the buffer to a Cord.
+//
+// The `CreateWithCustomLimit()` method is intended for applications needing
+// larger buffers than the default memory limit, allowing the allocation of up
+// to a capacity of `kCustomLimit` bytes minus some minimum internal overhead.
+// The usage of `CreateWithCustomLimit()` should be limited to only those use
+// cases where the distribution of the input is relatively well known, and/or
+// where the trade-off between the efficiency gains outweigh the risk of memory
+// fragmentation. See the documentation for `CreateWithCustomLimit()` for more
+// information on using larger custom limits.
+//
+// The capacity of a `CordBuffer` returned by one of the `Create` methods may
+// be larger than the requested capacity due to rounding, alignment and
+// granularity of the memory allocator. Applications should use the `capacity`
+// method to obtain the effective capacity of the returned instance as
+// demonstrated in the provided example above.
+//
+// CordBuffer is a move-only class. All references into the managed memory are
+// invalidated when an instance is moved into either another CordBuffer instance
+// or a Cord. Writing to a location obtained by a previous call to `data()`
+// after an instance was moved will lead to undefined behavior.
+//
+// A `moved from` CordBuffer instance will have a valid, but empty state.
+// CordBuffer is thread compatible.
+class CordBuffer {
+ public:
+ // kDefaultLimit
+ //
+ // Default capacity limits of allocated CordBuffers.
+ // See the class comments for more information on allocation limits.
+ static constexpr size_t kDefaultLimit = cord_internal::kMaxFlatLength;
+
+ // kCustomLimit
+ //
+ // Maximum size for CreateWithCustomLimit() allocated buffers.
+ // Note that the effective capacity may be slightly less
+ // because of internal overhead of internal cord buffers.
+ static constexpr size_t kCustomLimit = 64U << 10;
+
+ // Constructors, Destructors and Assignment Operators
+
+ // Creates an empty CordBuffer.
+ CordBuffer() = default;
+
+ // Destroys this CordBuffer instance and, if not empty, releases any memory
+ // managed by this instance, invalidating previously returned references.
+ ~CordBuffer();
+
+ // CordBuffer is move-only
+ CordBuffer(CordBuffer&& rhs) noexcept;
+ CordBuffer& operator=(CordBuffer&&) noexcept;
+ CordBuffer(const CordBuffer&) = delete;
+ CordBuffer& operator=(const CordBuffer&) = delete;
+
+ // CordBuffer::MaximumPayload()
+ //
+ // Returns the guaranteed maximum payload for a CordBuffer returned by the
+ // `CreateWithDefaultLimit()` method. While small, each internal buffer inside
+ // a Cord incurs an overhead to manage the length, type and reference count
+ // for the buffer managed inside the cord tree. Applications can use this
+ // method to get approximate number of buffers required for a given byte
+ // size, etc.
+ //
+ // For example:
+ // const size_t payload = absl::CordBuffer::MaximumPayload();
+ // const size_t buffer_count = (total_size + payload - 1) / payload;
+ // buffers.reserve(buffer_count);
+ static constexpr size_t MaximumPayload();
+
+ // Overload to the above `MaximumPayload()` except that it returns the
+ // maximum payload for a CordBuffer returned by the `CreateWithCustomLimit()`
+ // method given the provided `block_size`.
+ static constexpr size_t MaximumPayload(size_t block_size);
+
+ // CordBuffer::CreateWithDefaultLimit()
+ //
+ // Creates a CordBuffer instance of the desired `capacity`, capped at the
+ // default limit `kDefaultLimit`. The returned buffer has a guaranteed
+ // capacity of at least `min(kDefaultLimit, capacity)`. See the class comments
+ // for more information on buffer capacities and intended usage.
+ static CordBuffer CreateWithDefaultLimit(size_t capacity);
+
+ // CordBuffer::CreateWithCustomLimit()
+ //
+ // Creates a CordBuffer instance of the desired `capacity` rounded to an
+ // appropriate power of 2 size less than, or equal to `block_size`.
+ // Requires `block_size` to be a power of 2.
+ //
+ // If `capacity` is less than or equal to `kDefaultLimit`, then this method
+ // behaves identical to `CreateWithDefaultLimit`, which means that the caller
+ // is guaranteed to get a buffer of at least the requested capacity.
+ //
+ // If `capacity` is greater than or equal to `block_size`, then this method
+ // returns a buffer with an `allocated size` of `block_size` bytes. Otherwise,
+ // this methods returns a buffer with a suitable smaller power of 2 block size
+ // to satisfy the request. The actual size depends on a number of factors, and
+ // is typically (but not necessarily) the highest or second highest power of 2
+ // value less than or equal to `capacity`.
+ //
+ // The 'allocated size' includes a small amount of overhead required for
+ // internal state, which is currently 13 bytes on 64-bit platforms. For
+ // example: a buffer created with `block_size` and `capacity' set to 8KiB
+ // will have an allocated size of 8KiB, and an effective internal `capacity`
+ // of 8KiB - 13 = 8179 bytes.
+ //
+ // To demonstrate this in practice, let's assume we want to read data from
+ // somewhat larger files using approximately 64KiB buffers:
+ //
+ // absl::Cord ReadFromFile(int fd, size_t n) {
+ // absl::Cord cord;
+ // while (n > 0) {
+ // CordBuffer buffer = CordBuffer::CreateWithCustomLimit(64 << 10, n);
+ // absl::Span<char> data = buffer.available_up_to(n);
+ // ReadFileDataOrDie(fd, data.data(), data.size());
+ // buffer.IncreaseLengthBy(data.size());
+ // cord.Append(std::move(buffer));
+ // n -= data.size();
+ // }
+ // return cord;
+ // }
+ //
+ // If we'd use this function to read a file of 659KiB, we may get the
+ // following pattern of allocated cord buffer sizes:
+ //
+ // CreateWithCustomLimit(64KiB, 674816) --> ~64KiB (65523)
+ // CreateWithCustomLimit(64KiB, 674816) --> ~64KiB (65523)
+ // ...
+ // CreateWithCustomLimit(64KiB, 19586) --> ~16KiB (16371)
+ // CreateWithCustomLimit(64KiB, 3215) --> 3215 (at least 3215)
+ //
+ // The reason the method returns a 16K buffer instead of a roughly 19K buffer
+ // is to reduce memory overhead and fragmentation risks. Using carefully
+ // chosen power of 2 values reduces the entropy of allocated memory sizes.
+ //
+ // Additionally, let's assume we'd use the above function on files that are
+ // generally smaller than 64K. If we'd use 'precise' sized buffers for such
+ // files, than we'd get a very wide distribution of allocated memory sizes
+ // rounded to 4K page sizes, and we'd end up with a lot of unused capacity.
+ //
+ // In general, application should only use custom sizes if the data they are
+ // consuming or storing is expected to be many times the chosen block size,
+ // and be based on objective data and performance metrics. For example, a
+ // compress function may work faster and consume less CPU when using larger
+ // buffers. Such an application should pick a size offering a reasonable
+ // trade-off between expected data size, compute savings with larger buffers,
+ // and the cost or fragmentation effect of larger buffers.
+ // Applications must pick a reasonable spot on that curve, and make sure their
+ // data meets their expectations in size distributions such as "mostly large".
+ static CordBuffer CreateWithCustomLimit(size_t block_size, size_t capacity);
+
+ // CordBuffer::available()
+ //
+ // Returns the span delineating the available capacity in this buffer
+ // which is defined as `{ data() + length(), capacity() - length() }`.
+ absl::Span<char> available();
+
+ // CordBuffer::available_up_to()
+ //
+ // Returns the span delineating the available capacity in this buffer limited
+ // to `size` bytes. This is equivalent to `available().subspan(0, size)`.
+ absl::Span<char> available_up_to(size_t size);
+
+ // CordBuffer::data()
+ //
+ // Returns a non-null reference to the data managed by this instance.
+ // Applications are allowed to write up to `capacity` bytes of instance data.
+ // CordBuffer data is uninitialized by default. Reading data from an instance
+ // that has not yet been initialized will lead to undefined behavior.
+ char* data();
+ const char* data() const;
+
+ // CordBuffer::length()
+ //
+ // Returns the length of this instance. The default length of a CordBuffer is
+ // 0, indicating an 'empty' CordBuffer. Applications must specify the length
+ // of the data in a CordBuffer before adding it to a Cord.
+ size_t length() const;
+
+ // CordBuffer::capacity()
+ //
+ // Returns the capacity of this instance. All instances have a non-zero
+ // capacity: default and `moved from` instances have a small internal buffer.
+ size_t capacity() const;
+
+ // CordBuffer::IncreaseLengthBy()
+ //
+ // Increases the length of this buffer by the specified 'n' bytes.
+ // Applications must make sure all data in this buffer up to the new length
+ // has been initialized before adding a CordBuffer to a Cord: failure to do so
+ // will lead to undefined behavior. Requires `length() + n <= capacity()`.
+ // Typically, applications will use 'available_up_to()` to get a span of the
+ // desired capacity, and use `span.size()` to increase the length as in:
+ // absl::Span<char> span = buffer.available_up_to(desired);
+ // buffer.IncreaseLengthBy(span.size());
+ // memcpy(span.data(), src, span.size());
+ // etc...
+ void IncreaseLengthBy(size_t n);
+
+ // CordBuffer::SetLength()
+ //
+ // Sets the data length of this instance. Applications must make sure all data
+ // of the specified length has been initialized before adding a CordBuffer to
+ // a Cord: failure to do so will lead to undefined behavior.
+ // Setting the length to a small value or zero does not release any memory
+ // held by this CordBuffer instance. Requires `length <= capacity()`.
+ // Applications should preferably use the `IncreaseLengthBy()` method above
+ // in combination with the 'available()` or `available_up_to()` methods.
+ void SetLength(size_t length);
+
+ private:
+ // Make sure we don't accidentally over promise.
+ static_assert(kCustomLimit <= cord_internal::kMaxLargeFlatSize, "");
+
+ // Assume the cost of an 'uprounded' allocation to CeilPow2(size) versus
+ // the cost of allocating at least 1 extra flat <= 4KB:
+ // - Flat overhead = 13 bytes
+ // - Btree amortized cost / node =~ 13 bytes
+ // - 64 byte granularity of tcmalloc at 4K =~ 32 byte average
+ // CPU cost and efficiency requires we should at least 'save' something by
+ // splitting, as a poor man's measure, we say the slop needs to be
+ // at least double the cost offset to make it worth splitting: ~128 bytes.
+ static constexpr size_t kMaxPageSlop = 128;
+
+ // Overhead for allocation a flat.
+ static constexpr size_t kOverhead = cord_internal::kFlatOverhead;
+
+ using CordRepFlat = cord_internal::CordRepFlat;
+
+ // `Rep` is the internal data representation of a CordBuffer. The internal
+ // representation has an internal small size optimization similar to
+ // std::string (SSO).
+ struct Rep {
+ // Inline SSO size of a CordBuffer
+ static constexpr size_t kInlineCapacity = sizeof(intptr_t) * 2 - 1;
+
+ // Creates a default instance with kInlineCapacity.
+ Rep() : short_rep{} {}
+
+ // Creates an instance managing an allocated non zero CordRep.
+ explicit Rep(cord_internal::CordRepFlat* rep) : long_rep{rep} {
+ assert(rep != nullptr);
+ }
+
+ // Returns true if this instance manages the SSO internal buffer.
+ bool is_short() const {
+ constexpr size_t offset = offsetof(Short, raw_size);
+ return (reinterpret_cast<const char*>(this)[offset] & 1) != 0;
+ }
+
+ // Returns the available area of the internal SSO data
+ absl::Span<char> short_available() {
+ const size_t length = short_length();
+ return absl::Span<char>(short_rep.data + length,
+ kInlineCapacity - length);
+ }
+
+ // Returns the available area of the internal SSO data
+ absl::Span<char> long_available() const {
+ assert(!is_short());
+ const size_t length = long_rep.rep->length;
+ return absl::Span<char>(long_rep.rep->Data() + length,
+ long_rep.rep->Capacity() - length);
+ }
+
+ // Returns the length of the internal SSO data.
+ size_t short_length() const {
+ assert(is_short());
+ return static_cast<size_t>(short_rep.raw_size >> 1);
+ }
+
+ // Sets the length of the internal SSO data.
+ // Disregards any previously set CordRep instance.
+ void set_short_length(size_t length) {
+ short_rep.raw_size = static_cast<char>((length << 1) + 1);
+ }
+
+ // Adds `n` to the current short length.
+ void add_short_length(size_t n) {
+ assert(is_short());
+ short_rep.raw_size += static_cast<char>(n << 1);
+ }
+
+ // Returns reference to the internal SSO data buffer.
+ char* data() {
+ assert(is_short());
+ return short_rep.data;
+ }
+ const char* data() const {
+ assert(is_short());
+ return short_rep.data;
+ }
+
+ // Returns a pointer the external CordRep managed by this instance.
+ cord_internal::CordRepFlat* rep() const {
+ assert(!is_short());
+ return long_rep.rep;
+ }
+
+ // The internal representation takes advantage of the fact that allocated
+ // memory is always on an even address, and uses the least significant bit
+ // of the first or last byte (depending on endianness) as the inline size
+ // indicator overlapping with the least significant byte of the CordRep*.
+#if defined(ABSL_IS_BIG_ENDIAN)
+ struct Long {
+ explicit Long(cord_internal::CordRepFlat* rep_arg) : rep(rep_arg) {}
+ void* padding;
+ cord_internal::CordRepFlat* rep;
+ };
+ struct Short {
+ char data[sizeof(Long) - 1];
+ char raw_size = 1;
+ };
+#else
+ struct Long {
+ explicit Long(cord_internal::CordRepFlat* rep_arg) : rep(rep_arg) {}
+ cord_internal::CordRepFlat* rep;
+ void* padding;
+ };
+ struct Short {
+ char raw_size = 1;
+ char data[sizeof(Long) - 1];
+ };
+#endif
+
+ union {
+ Long long_rep;
+ Short short_rep;
+ };
+ };
+
+ // Power2 functions
+ static bool IsPow2(size_t size) { return absl::has_single_bit(size); }
+ static size_t Log2Floor(size_t size) {
+ return static_cast<size_t>(absl::bit_width(size) - 1);
+ }
+ static size_t Log2Ceil(size_t size) {
+ return static_cast<size_t>(absl::bit_width(size - 1));
+ }
+
+ // Implementation of `CreateWithCustomLimit()`.
+ // This implementation allows for future memory allocation hints to
+ // be passed down into the CordRepFlat allocation function.
+ template <typename... AllocationHints>
+ static CordBuffer CreateWithCustomLimitImpl(size_t block_size,
+ size_t capacity,
+ AllocationHints... hints);
+
+ // Consumes the value contained in this instance and resets the instance.
+ // This method returns a non-null Cordrep* if the current instances manages a
+ // CordRep*, and resets the instance to an empty SSO instance. If the current
+ // instance is an SSO instance, then this method returns nullptr and sets
+ // `short_value` to the inlined data value. In either case, the current
+ // instance length is reset to zero.
+ // This method is intended to be used by Cord internal functions only.
+ cord_internal::CordRep* ConsumeValue(absl::string_view& short_value) {
+ cord_internal::CordRep* rep = nullptr;
+ if (rep_.is_short()) {
+ short_value = absl::string_view(rep_.data(), rep_.short_length());
+ } else {
+ rep = rep_.rep();
+ }
+ rep_.set_short_length(0);
+ return rep;
+ }
+
+ // Internal constructor.
+ explicit CordBuffer(cord_internal::CordRepFlat* rep) : rep_(rep) {
+ assert(rep != nullptr);
+ }
+
+ Rep rep_;
+
+ friend class Cord;
+ friend class CordBufferTestPeer;
+};
+
+inline constexpr size_t CordBuffer::MaximumPayload() {
+ return cord_internal::kMaxFlatLength;
+}
+
+inline constexpr size_t CordBuffer::MaximumPayload(size_t block_size) {
+ return (std::min)(kCustomLimit, block_size) - cord_internal::kFlatOverhead;
+}
+
+inline CordBuffer CordBuffer::CreateWithDefaultLimit(size_t capacity) {
+ if (capacity > Rep::kInlineCapacity) {
+ auto* rep = cord_internal::CordRepFlat::New(capacity);
+ rep->length = 0;
+ return CordBuffer(rep);
+ }
+ return CordBuffer();
+}
+
+template <typename... AllocationHints>
+inline CordBuffer CordBuffer::CreateWithCustomLimitImpl(
+ size_t block_size, size_t capacity, AllocationHints... hints) {
+ assert(IsPow2(block_size));
+ capacity = (std::min)(capacity, kCustomLimit);
+ block_size = (std::min)(block_size, kCustomLimit);
+ if (capacity + kOverhead >= block_size) {
+ capacity = block_size;
+ } else if (capacity <= kDefaultLimit) {
+ capacity = capacity + kOverhead;
+ } else if (!IsPow2(capacity)) {
+ // Check if rounded up to next power 2 is a good enough fit
+ // with limited waste making it an acceptable direct fit.
+ const size_t rounded_up = size_t{1} << Log2Ceil(capacity);
+ const size_t slop = rounded_up - capacity;
+ if (slop >= kOverhead && slop <= kMaxPageSlop + kOverhead) {
+ capacity = rounded_up;
+ } else {
+ // Round down to highest power of 2 <= capacity.
+ // Consider a more aggressive step down if that may reduce the
+ // risk of fragmentation where 'people are holding it wrong'.
+ const size_t rounded_down = size_t{1} << Log2Floor(capacity);
+ capacity = rounded_down;
+ }
+ }
+ const size_t length = capacity - kOverhead;
+ auto* rep = CordRepFlat::New(CordRepFlat::Large(), length, hints...);
+ rep->length = 0;
+ return CordBuffer(rep);
+}
+
+inline CordBuffer CordBuffer::CreateWithCustomLimit(size_t block_size,
+ size_t capacity) {
+ return CreateWithCustomLimitImpl(block_size, capacity);
+}
+
+inline CordBuffer::~CordBuffer() {
+ if (!rep_.is_short()) {
+ cord_internal::CordRepFlat::Delete(rep_.rep());
+ }
+}
+
+inline CordBuffer::CordBuffer(CordBuffer&& rhs) noexcept : rep_(rhs.rep_) {
+ rhs.rep_.set_short_length(0);
+}
+
+inline CordBuffer& CordBuffer::operator=(CordBuffer&& rhs) noexcept {
+ if (!rep_.is_short()) cord_internal::CordRepFlat::Delete(rep_.rep());
+ rep_ = rhs.rep_;
+ rhs.rep_.set_short_length(0);
+ return *this;
+}
+
+inline absl::Span<char> CordBuffer::available() {
+ return rep_.is_short() ? rep_.short_available() : rep_.long_available();
+}
+
+inline absl::Span<char> CordBuffer::available_up_to(size_t size) {
+ return available().subspan(0, size);
+}
+
+inline char* CordBuffer::data() {
+ return rep_.is_short() ? rep_.data() : rep_.rep()->Data();
+}
+
+inline const char* CordBuffer::data() const {
+ return rep_.is_short() ? rep_.data() : rep_.rep()->Data();
+}
+
+inline size_t CordBuffer::capacity() const {
+ return rep_.is_short() ? Rep::kInlineCapacity : rep_.rep()->Capacity();
+}
+
+inline size_t CordBuffer::length() const {
+ return rep_.is_short() ? rep_.short_length() : rep_.rep()->length;
+}
+
+inline void CordBuffer::SetLength(size_t length) {
+ ABSL_HARDENING_ASSERT(length <= capacity());
+ if (rep_.is_short()) {
+ rep_.set_short_length(length);
+ } else {
+ rep_.rep()->length = length;
+ }
+}
+
+inline void CordBuffer::IncreaseLengthBy(size_t n) {
+ ABSL_HARDENING_ASSERT(n <= capacity() && length() + n <= capacity());
+ if (rep_.is_short()) {
+ rep_.add_short_length(n);
+ } else {
+ rep_.rep()->length += n;
+ }
+}
+
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_STRINGS_CORD_BUFFER_H_
diff --git a/absl/strings/cord_buffer_test.cc b/absl/strings/cord_buffer_test.cc
new file mode 100644
index 0000000..ab62808
--- /dev/null
+++ b/absl/strings/cord_buffer_test.cc
@@ -0,0 +1,322 @@
+// Copyright 2021 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/strings/cord_buffer.h"
+
+
+#include <algorithm>
+#include <cstring>
+#include <limits>
+#include <string>
+#include <utility>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/base/config.h"
+#include "absl/strings/internal/cord_internal.h"
+#include "absl/strings/internal/cord_rep_flat.h"
+#include "absl/strings/internal/cord_rep_test_util.h"
+#include "absl/strings/string_view.h"
+#include "absl/types/span.h"
+
+using testing::Eq;
+using testing::Ge;
+using testing::Le;
+using testing::Ne;
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+
+class CordBufferTestPeer {
+ public:
+ static cord_internal::CordRep* ConsumeValue(CordBuffer& buffer,
+ absl::string_view& short_value) {
+ return buffer.ConsumeValue(short_value);
+ }
+};
+
+namespace {
+
+using ::absl::cordrep_testing::CordToString;
+
+constexpr size_t kInlinedSize = sizeof(CordBuffer) - 1;
+constexpr size_t kDefaultLimit = CordBuffer::kDefaultLimit;
+constexpr size_t kCustomLimit = CordBuffer::kCustomLimit;
+constexpr size_t kMaxFlatSize = cord_internal::kMaxFlatSize;
+constexpr size_t kMaxFlatLength = cord_internal::kMaxFlatLength;
+constexpr size_t kFlatOverhead = cord_internal::kFlatOverhead;
+
+constexpr size_t k8KiB = 8 << 10;
+constexpr size_t k16KiB = 16 << 10;
+constexpr size_t k64KiB = 64 << 10;
+constexpr size_t k1MB = 1 << 20;
+
+class CordBufferTest : public testing::TestWithParam<size_t> {};
+
+INSTANTIATE_TEST_SUITE_P(MediumSize, CordBufferTest,
+ testing::Values(1, kInlinedSize - 1, kInlinedSize,
+ kInlinedSize + 1, kDefaultLimit - 1,
+ kDefaultLimit));
+
+TEST_P(CordBufferTest, MaximumPayload) {
+ EXPECT_THAT(CordBuffer::MaximumPayload(), Eq(kMaxFlatLength));
+ EXPECT_THAT(CordBuffer::MaximumPayload(512), Eq(512 - kFlatOverhead));
+ EXPECT_THAT(CordBuffer::MaximumPayload(k64KiB), Eq(k64KiB - kFlatOverhead));
+ EXPECT_THAT(CordBuffer::MaximumPayload(k1MB), Eq(k64KiB - kFlatOverhead));
+}
+
+TEST(CordBufferTest, ConstructDefault) {
+ CordBuffer buffer;
+ EXPECT_THAT(buffer.capacity(), Eq(sizeof(CordBuffer) - 1));
+ EXPECT_THAT(buffer.length(), Eq(0));
+ EXPECT_THAT(buffer.data(), Ne(nullptr));
+ EXPECT_THAT(buffer.available().data(), Eq(buffer.data()));
+ EXPECT_THAT(buffer.available().size(), Eq(buffer.capacity()));
+ memset(buffer.data(), 0xCD, buffer.capacity());
+}
+
+TEST(CordBufferTest, CreateSsoWithDefaultLimit) {
+ CordBuffer buffer = CordBuffer::CreateWithDefaultLimit(3);
+ EXPECT_THAT(buffer.capacity(), Ge(3));
+ EXPECT_THAT(buffer.capacity(), Le(sizeof(CordBuffer)));
+ EXPECT_THAT(buffer.length(), Eq(0));
+ memset(buffer.data(), 0xCD, buffer.capacity());
+
+ memcpy(buffer.data(), "Abc", 3);
+ buffer.SetLength(3);
+ EXPECT_THAT(buffer.length(), Eq(3));
+ absl::string_view short_value;
+ EXPECT_THAT(CordBufferTestPeer::ConsumeValue(buffer, short_value),
+ Eq(nullptr));
+ EXPECT_THAT(absl::string_view(buffer.data(), 3), Eq("Abc"));
+ EXPECT_THAT(short_value, Eq("Abc"));
+}
+
+TEST_P(CordBufferTest, Available) {
+ const size_t requested = GetParam();
+ CordBuffer buffer = CordBuffer::CreateWithDefaultLimit(requested);
+ EXPECT_THAT(buffer.available().data(), Eq(buffer.data()));
+ EXPECT_THAT(buffer.available().size(), Eq(buffer.capacity()));
+
+ buffer.SetLength(2);
+ EXPECT_THAT(buffer.available().data(), Eq(buffer.data() + 2));
+ EXPECT_THAT(buffer.available().size(), Eq(buffer.capacity() - 2));
+}
+
+TEST_P(CordBufferTest, IncreaseLengthBy) {
+ const size_t requested = GetParam();
+ CordBuffer buffer = CordBuffer::CreateWithDefaultLimit(requested);
+ buffer.IncreaseLengthBy(2);
+ EXPECT_THAT(buffer.length(), Eq(2));
+ buffer.IncreaseLengthBy(5);
+ EXPECT_THAT(buffer.length(), Eq(7));
+}
+
+TEST_P(CordBufferTest, AvailableUpTo) {
+ const size_t requested = GetParam();
+ CordBuffer buffer = CordBuffer::CreateWithDefaultLimit(requested);
+ size_t expected_up_to = std::min<size_t>(3, buffer.capacity());
+ EXPECT_THAT(buffer.available_up_to(3).data(), Eq(buffer.data()));
+ EXPECT_THAT(buffer.available_up_to(3).size(), Eq(expected_up_to));
+
+ buffer.SetLength(2);
+ expected_up_to = std::min<size_t>(3, buffer.capacity() - 2);
+ EXPECT_THAT(buffer.available_up_to(3).data(), Eq(buffer.data() + 2));
+ EXPECT_THAT(buffer.available_up_to(3).size(), Eq(expected_up_to));
+}
+
+// Returns the maximum capacity for a given block_size and requested size.
+size_t MaxCapacityFor(size_t block_size, size_t requested) {
+ requested = (std::min)(requested, cord_internal::kMaxLargeFlatSize);
+ // Maximum returned size is always capped at block_size - kFlatOverhead.
+ return block_size - kFlatOverhead;
+}
+
+TEST_P(CordBufferTest, CreateWithDefaultLimit) {
+ const size_t requested = GetParam();
+ CordBuffer buffer = CordBuffer::CreateWithDefaultLimit(requested);
+ EXPECT_THAT(buffer.capacity(), Ge(requested));
+ EXPECT_THAT(buffer.capacity(), Le(MaxCapacityFor(kMaxFlatSize, requested)));
+ EXPECT_THAT(buffer.length(), Eq(0));
+
+ memset(buffer.data(), 0xCD, buffer.capacity());
+
+ std::string data(requested - 1, 'x');
+ memcpy(buffer.data(), data.c_str(), requested);
+ buffer.SetLength(requested);
+
+ EXPECT_THAT(buffer.length(), Eq(requested));
+ EXPECT_THAT(absl::string_view(buffer.data()), Eq(data));
+}
+
+TEST(CordBufferTest, CreateWithDefaultLimitAskingFor2GB) {
+ constexpr size_t k2GiB = 1U << 31;
+ CordBuffer buffer = CordBuffer::CreateWithDefaultLimit(k2GiB);
+ // Expect to never be awarded more than a reasonable memory size, even in
+ // cases where a (debug) memory allocator may grant us somewhat more memory
+ // than `kDefaultLimit` which should be no more than `2 * kDefaultLimit`
+ EXPECT_THAT(buffer.capacity(), Le(2 * CordBuffer::kDefaultLimit));
+ EXPECT_THAT(buffer.length(), Eq(0));
+ EXPECT_THAT(buffer.data(), Ne(nullptr));
+ memset(buffer.data(), 0xCD, buffer.capacity());
+}
+
+TEST_P(CordBufferTest, MoveConstruct) {
+ const size_t requested = GetParam();
+ CordBuffer from = CordBuffer::CreateWithDefaultLimit(requested);
+ const size_t capacity = from.capacity();
+ memcpy(from.data(), "Abc", 4);
+ from.SetLength(4);
+
+ CordBuffer to(std::move(from));
+ EXPECT_THAT(to.capacity(), Eq(capacity));
+ EXPECT_THAT(to.length(), Eq(4));
+ EXPECT_THAT(absl::string_view(to.data()), Eq("Abc"));
+
+ EXPECT_THAT(from.length(), Eq(0)); // NOLINT
+}
+
+TEST_P(CordBufferTest, MoveAssign) {
+ const size_t requested = GetParam();
+ CordBuffer from = CordBuffer::CreateWithDefaultLimit(requested);
+ const size_t capacity = from.capacity();
+ memcpy(from.data(), "Abc", 4);
+ from.SetLength(4);
+
+ CordBuffer to;
+ to = std::move(from);
+ EXPECT_THAT(to.capacity(), Eq(capacity));
+ EXPECT_THAT(to.length(), Eq(4));
+ EXPECT_THAT(absl::string_view(to.data()), Eq("Abc"));
+
+ EXPECT_THAT(from.length(), Eq(0)); // NOLINT
+}
+
+TEST_P(CordBufferTest, ConsumeValue) {
+ const size_t requested = GetParam();
+ CordBuffer buffer = CordBuffer::CreateWithDefaultLimit(requested);
+ memcpy(buffer.data(), "Abc", 4);
+ buffer.SetLength(3);
+
+ absl::string_view short_value;
+ if (cord_internal::CordRep* rep =
+ CordBufferTestPeer::ConsumeValue(buffer, short_value)) {
+ EXPECT_THAT(CordToString(rep), Eq("Abc"));
+ cord_internal::CordRep::Unref(rep);
+ } else {
+ EXPECT_THAT(short_value, Eq("Abc"));
+ }
+ EXPECT_THAT(buffer.length(), Eq(0));
+}
+
+TEST_P(CordBufferTest, CreateWithCustomLimitWithinDefaultLimit) {
+ const size_t requested = GetParam();
+ CordBuffer buffer =
+ CordBuffer::CreateWithCustomLimit(kMaxFlatSize, requested);
+ EXPECT_THAT(buffer.capacity(), Ge(requested));
+ EXPECT_THAT(buffer.capacity(), Le(MaxCapacityFor(kMaxFlatSize, requested)));
+ EXPECT_THAT(buffer.length(), Eq(0));
+
+ memset(buffer.data(), 0xCD, buffer.capacity());
+
+ std::string data(requested - 1, 'x');
+ memcpy(buffer.data(), data.c_str(), requested);
+ buffer.SetLength(requested);
+
+ EXPECT_THAT(buffer.length(), Eq(requested));
+ EXPECT_THAT(absl::string_view(buffer.data()), Eq(data));
+}
+
+TEST(CordLargeBufferTest, CreateAtOrBelowDefaultLimit) {
+ CordBuffer buffer = CordBuffer::CreateWithCustomLimit(k64KiB, kDefaultLimit);
+ EXPECT_THAT(buffer.capacity(), Ge(kDefaultLimit));
+ EXPECT_THAT(buffer.capacity(),
+ Le(MaxCapacityFor(kMaxFlatSize, kDefaultLimit)));
+
+ buffer = CordBuffer::CreateWithCustomLimit(k64KiB, 3178);
+ EXPECT_THAT(buffer.capacity(), Ge(3178));
+}
+
+TEST(CordLargeBufferTest, CreateWithCustomLimit) {
+ ASSERT_THAT((kMaxFlatSize & (kMaxFlatSize - 1)) == 0, "Must be power of 2");
+
+ for (size_t size = kMaxFlatSize; size <= kCustomLimit; size *= 2) {
+ CordBuffer buffer = CordBuffer::CreateWithCustomLimit(size, size);
+ size_t expected = size - kFlatOverhead;
+ ASSERT_THAT(buffer.capacity(), Ge(expected));
+ EXPECT_THAT(buffer.capacity(), Le(MaxCapacityFor(size, expected)));
+ }
+}
+
+TEST(CordLargeBufferTest, CreateWithTooLargeLimit) {
+ CordBuffer buffer = CordBuffer::CreateWithCustomLimit(k64KiB, k1MB);
+ ASSERT_THAT(buffer.capacity(), Ge(k64KiB - kFlatOverhead));
+ EXPECT_THAT(buffer.capacity(), Le(MaxCapacityFor(k64KiB, k1MB)));
+}
+
+TEST(CordLargeBufferTest, CreateWithHugeValueForOverFlowHardening) {
+ for (size_t dist_from_max = 0; dist_from_max <= 32; ++dist_from_max) {
+ size_t capacity = std::numeric_limits<size_t>::max() - dist_from_max;
+
+ CordBuffer buffer = CordBuffer::CreateWithDefaultLimit(capacity);
+ ASSERT_THAT(buffer.capacity(), Ge(kDefaultLimit));
+ EXPECT_THAT(buffer.capacity(), Le(MaxCapacityFor(kMaxFlatSize, capacity)));
+
+ for (size_t limit = kMaxFlatSize; limit <= kCustomLimit; limit *= 2) {
+ CordBuffer buffer = CordBuffer::CreateWithCustomLimit(limit, capacity);
+ ASSERT_THAT(buffer.capacity(), Ge(limit - kFlatOverhead));
+ EXPECT_THAT(buffer.capacity(), Le(MaxCapacityFor(limit, capacity)));
+ }
+ }
+}
+
+TEST(CordLargeBufferTest, CreateWithSmallLimit) {
+ CordBuffer buffer = CordBuffer::CreateWithCustomLimit(512, 1024);
+ ASSERT_THAT(buffer.capacity(), Ge(512 - kFlatOverhead));
+ EXPECT_THAT(buffer.capacity(), Le(MaxCapacityFor(512, 1024)));
+
+ // Ask for precise block size, should return size - kOverhead
+ buffer = CordBuffer::CreateWithCustomLimit(512, 512);
+ ASSERT_THAT(buffer.capacity(), Ge(512 - kFlatOverhead));
+ EXPECT_THAT(buffer.capacity(), Le(MaxCapacityFor(512, 512)));
+
+ // Corner case: 511 < block_size, but 511 + kOverhead is above
+ buffer = CordBuffer::CreateWithCustomLimit(512, 511);
+ ASSERT_THAT(buffer.capacity(), Ge(512 - kFlatOverhead));
+ EXPECT_THAT(buffer.capacity(), Le(MaxCapacityFor(512, 511)));
+
+ // Corner case: 498 + kOverhead < block_size
+ buffer = CordBuffer::CreateWithCustomLimit(512, 498);
+ ASSERT_THAT(buffer.capacity(), Ge(512 - kFlatOverhead));
+ EXPECT_THAT(buffer.capacity(), Le(MaxCapacityFor(512, 498)));
+}
+
+TEST(CordLargeBufferTest, CreateWasteFull) {
+ // 15 KiB gets rounded down to next pow2 value.
+ const size_t requested = (15 << 10);
+ CordBuffer buffer = CordBuffer::CreateWithCustomLimit(k16KiB, requested);
+ ASSERT_THAT(buffer.capacity(), Ge(k8KiB - kFlatOverhead));
+ EXPECT_THAT(buffer.capacity(), Le(MaxCapacityFor(k8KiB, requested)));
+}
+
+TEST(CordLargeBufferTest, CreateSmallSlop) {
+ const size_t requested = k16KiB - 2 * kFlatOverhead;
+ CordBuffer buffer = CordBuffer::CreateWithCustomLimit(k16KiB, requested);
+ ASSERT_THAT(buffer.capacity(), Ge(k16KiB - kFlatOverhead));
+ EXPECT_THAT(buffer.capacity(), Le(MaxCapacityFor(k16KiB, requested)));
+}
+
+} // namespace
+ABSL_NAMESPACE_END
+} // namespace absl
diff --git a/absl/strings/cord_ring_reader_test.cc b/absl/strings/cord_ring_reader_test.cc
deleted file mode 100644
index d9a9a76..0000000
--- a/absl/strings/cord_ring_reader_test.cc
+++ /dev/null
@@ -1,180 +0,0 @@
-// Copyright 2020 The Abseil Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <cstdlib>
-#include <ctime>
-#include <memory>
-#include <random>
-#include <sstream>
-
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-#include "absl/debugging/leak_check.h"
-#include "absl/strings/internal/cord_internal.h"
-#include "absl/strings/internal/cord_rep_ring.h"
-#include "absl/strings/internal/cord_rep_ring_reader.h"
-#include "absl/strings/string_view.h"
-
-namespace absl {
-ABSL_NAMESPACE_BEGIN
-namespace cord_internal {
-namespace {
-
-using testing::Eq;
-
-// Creates a flat for testing
-CordRep* MakeFlat(absl::string_view s) {
- CordRepFlat* flat = CordRepFlat::New(s.length());
- memcpy(flat->Data(), s.data(), s.length());
- flat->length = s.length();
- return flat;
-}
-
-CordRepRing* FromFlats(Span<absl::string_view const> flats) {
- CordRepRing* ring = CordRepRing::Create(MakeFlat(flats[0]), flats.size() - 1);
- for (int i = 1; i < flats.size(); ++i) {
- ring = CordRepRing::Append(ring, MakeFlat(flats[i]));
- }
- return ring;
-}
-
-std::array<absl::string_view, 12> TestFlats() {
- return {"abcdefghij", "klmnopqrst", "uvwxyz", "ABCDEFGHIJ",
- "KLMNOPQRST", "UVWXYZ", "1234567890", "~!@#$%^&*()_",
- "+-=", "[]\\{}|;':", ",/<>?", "."};
-}
-
-TEST(CordRingReaderTest, DefaultInstance) {
- CordRepRingReader reader;
- EXPECT_FALSE(static_cast<bool>(reader));
- EXPECT_THAT(reader.ring(), Eq(nullptr));
-#ifndef NDEBUG
- EXPECT_DEATH_IF_SUPPORTED(reader.length(), ".*");
- EXPECT_DEATH_IF_SUPPORTED(reader.consumed(), ".*");
- EXPECT_DEATH_IF_SUPPORTED(reader.remaining(), ".*");
- EXPECT_DEATH_IF_SUPPORTED(reader.Next(), ".*");
- EXPECT_DEATH_IF_SUPPORTED(reader.Seek(0), ".*");
-#endif
-}
-
-TEST(CordRingReaderTest, Reset) {
- CordRepRingReader reader;
- auto flats = TestFlats();
- CordRepRing* ring = FromFlats(flats);
-
- absl::string_view first = reader.Reset(ring);
- EXPECT_THAT(first, Eq(flats[0]));
- EXPECT_TRUE(static_cast<bool>(reader));
- EXPECT_THAT(reader.ring(), Eq(ring));
- EXPECT_THAT(reader.index(), Eq(ring->head()));
- EXPECT_THAT(reader.node(), Eq(ring->entry_child(ring->head())));
- EXPECT_THAT(reader.length(), Eq(ring->length));
- EXPECT_THAT(reader.consumed(), Eq(flats[0].length()));
- EXPECT_THAT(reader.remaining(), Eq(ring->length - reader.consumed()));
-
- reader.Reset();
- EXPECT_FALSE(static_cast<bool>(reader));
- EXPECT_THAT(reader.ring(), Eq(nullptr));
-
- CordRep::Unref(ring);
-}
-
-TEST(CordRingReaderTest, Next) {
- CordRepRingReader reader;
- auto flats = TestFlats();
- CordRepRing* ring = FromFlats(flats);
- CordRepRing::index_type head = ring->head();
-
- reader.Reset(ring);
- size_t consumed = reader.consumed();
- size_t remaining = reader.remaining();
- for (int i = 1; i < flats.size(); ++i) {
- CordRepRing::index_type index = ring->advance(head, i);
- consumed += flats[i].length();
- remaining -= flats[i].length();
- absl::string_view next = reader.Next();
- ASSERT_THAT(next, Eq(flats[i]));
- ASSERT_THAT(reader.index(), Eq(index));
- ASSERT_THAT(reader.node(), Eq(ring->entry_child(index)));
- ASSERT_THAT(reader.consumed(), Eq(consumed));
- ASSERT_THAT(reader.remaining(), Eq(remaining));
- }
-
-#ifndef NDEBUG
- EXPECT_DEATH_IF_SUPPORTED(reader.Next(), ".*");
-#endif
-
- CordRep::Unref(ring);
-}
-
-TEST(CordRingReaderTest, SeekForward) {
- CordRepRingReader reader;
- auto flats = TestFlats();
- CordRepRing* ring = FromFlats(flats);
- CordRepRing::index_type head = ring->head();
-
- reader.Reset(ring);
- size_t consumed = 0;
- size_t remaining = ring->length;;
- for (int i = 0; i < flats.size(); ++i) {
- CordRepRing::index_type index = ring->advance(head, i);
- size_t offset = consumed;
- consumed += flats[i].length();
- remaining -= flats[i].length();
- for (int off = 0; off < flats[i].length(); ++off) {
- absl::string_view chunk = reader.Seek(offset + off);
- ASSERT_THAT(chunk, Eq(flats[i].substr(off)));
- ASSERT_THAT(reader.index(), Eq(index));
- ASSERT_THAT(reader.node(), Eq(ring->entry_child(index)));
- ASSERT_THAT(reader.consumed(), Eq(consumed));
- ASSERT_THAT(reader.remaining(), Eq(remaining));
- }
- }
-
- CordRep::Unref(ring);
-}
-
-TEST(CordRingReaderTest, SeekBackward) {
- CordRepRingReader reader;
- auto flats = TestFlats();
- CordRepRing* ring = FromFlats(flats);
- CordRepRing::index_type head = ring->head();
-
- reader.Reset(ring);
- size_t consumed = ring->length;
- size_t remaining = 0;
- for (int i = flats.size() - 1; i >= 0; --i) {
- CordRepRing::index_type index = ring->advance(head, i);
- size_t offset = consumed - flats[i].length();
- for (int off = 0; off < flats[i].length(); ++off) {
- absl::string_view chunk = reader.Seek(offset + off);
- ASSERT_THAT(chunk, Eq(flats[i].substr(off)));
- ASSERT_THAT(reader.index(), Eq(index));
- ASSERT_THAT(reader.node(), Eq(ring->entry_child(index)));
- ASSERT_THAT(reader.consumed(), Eq(consumed));
- ASSERT_THAT(reader.remaining(), Eq(remaining));
- }
- consumed -= flats[i].length();
- remaining += flats[i].length();
- }
-#ifndef NDEBUG
- EXPECT_DEATH_IF_SUPPORTED(reader.Seek(ring->length), ".*");
-#endif
- CordRep::Unref(ring);
-}
-
-} // namespace
-} // namespace cord_internal
-ABSL_NAMESPACE_END
-} // namespace absl
diff --git a/absl/strings/cord_ring_test.cc b/absl/strings/cord_ring_test.cc
deleted file mode 100644
index f131859..0000000
--- a/absl/strings/cord_ring_test.cc
+++ /dev/null
@@ -1,1495 +0,0 @@
-// Copyright 2020 The Abseil Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <cstdlib>
-#include <ctime>
-#include <memory>
-#include <random>
-#include <sstream>
-
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-#include "absl/base/config.h"
-#include "absl/base/internal/raw_logging.h"
-#include "absl/base/macros.h"
-#include "absl/debugging/leak_check.h"
-#include "absl/strings/internal/cord_internal.h"
-#include "absl/strings/internal/cord_rep_ring.h"
-#include "absl/strings/str_cat.h"
-#include "absl/strings/string_view.h"
-
-extern thread_local bool cord_ring;
-
-namespace absl {
-ABSL_NAMESPACE_BEGIN
-namespace {
-
-using RandomEngine = std::mt19937_64;
-
-using ::absl::cord_internal::CordRep;
-using ::absl::cord_internal::CordRepConcat;
-using ::absl::cord_internal::CordRepExternal;
-using ::absl::cord_internal::CordRepFlat;
-using ::absl::cord_internal::CordRepRing;
-using ::absl::cord_internal::CordRepSubstring;
-
-using ::absl::cord_internal::CONCAT;
-using ::absl::cord_internal::EXTERNAL;
-using ::absl::cord_internal::SUBSTRING;
-
-using testing::ElementsAre;
-using testing::ElementsAreArray;
-using testing::Eq;
-using testing::Ge;
-using testing::Le;
-using testing::Lt;
-using testing::Ne;
-using testing::SizeIs;
-
-using index_type = CordRepRing::index_type;
-
-enum InputShareMode { kPrivate, kShared, kSharedIndirect };
-
-// TestParam class used by all test fixtures.
-// Not all fixtures use all possible input combinations
-struct TestParam {
- TestParam() = default;
- explicit TestParam(InputShareMode input_share_mode)
- : input_share_mode(input_share_mode) {}
-
- // Run the test with the 'rep under test' to be privately owned.
- // Otherwise, the rep has a shared ref count of 2 or higher.
- bool refcount_is_one = true;
-
- // Run the test with the 'rep under test' being allocated with enough capacity
- // to accommodate any modifications made to it. Otherwise, the rep has zero
- // extra (reserve) capacity.
- bool with_capacity = true;
-
- // For test providing possibly shared input such as Append(.., CordpRep*),
- // this field defines if that input is adopted with a refcount of one
- // (privately owned / donated), or shared. For composite inputs such as
- // 'substring of flat', we also have the 'shared indirect' value which means
- // the top level node is not shared, but the contained child node is shared.
- InputShareMode input_share_mode = kPrivate;
-
- std::string ToString() const {
- return absl::StrCat(refcount_is_one ? "Private" : "Shared",
- with_capacity ? "" : "_NoCapacity",
- (input_share_mode == kPrivate) ? ""
- : (input_share_mode == kShared)
- ? "_SharedInput"
- : "_IndirectSharedInput");
- }
-};
-using TestParams = std::vector<TestParam>;
-
-// Matcher validating when mutable copies are required / performed.
-MATCHER_P2(EqIfPrivate, param, rep,
- absl::StrCat("Equal 0x", absl::Hex(rep), " if private")) {
- return param.refcount_is_one ? arg == rep : true;
-}
-
-// Matcher validating when mutable copies are required / performed.
-MATCHER_P2(EqIfPrivateAndCapacity, param, rep,
- absl::StrCat("Equal 0x", absl::Hex(rep),
- " if private and capacity")) {
- return (param.refcount_is_one && param.with_capacity) ? arg == rep : true;
-}
-
-// Matcher validating a shared ring was re-allocated. Should only be used for
-// tests doing exactly one update as subsequent updates could return the
-// original (freed and re-used) pointer.
-MATCHER_P2(NeIfShared, param, rep,
- absl::StrCat("Not equal 0x", absl::Hex(rep), " if shared")) {
- return param.refcount_is_one ? true : arg != rep;
-}
-
-MATCHER_P2(EqIfInputPrivate, param, rep, "Equal if input is private") {
- return param.input_share_mode == kPrivate ? arg == rep : arg != rep;
-}
-
-// Matcher validating the core in-variants of the CordRepRing instance.
-MATCHER(IsValidRingBuffer, "RingBuffer is valid") {
- std::stringstream ss;
- if (!arg->IsValid(ss)) {
- *result_listener << "\nERROR: " << ss.str() << "\nRING = " << *arg;
- return false;
- }
- return true;
-}
-
-// Returns the flats contained in the provided CordRepRing
-std::vector<string_view> ToFlats(const CordRepRing* r) {
- std::vector<string_view> flats;
- flats.reserve(r->entries());
- index_type pos = r->head();
- do {
- flats.push_back(r->entry_data(pos));
- } while ((pos = r->advance(pos)) != r->tail());
- return flats;
-}
-
-class not_a_string_view {
- public:
- explicit not_a_string_view(absl::string_view s)
- : data_(s.data()), size_(s.size()) {}
- explicit not_a_string_view(const void* data, size_t size)
- : data_(data), size_(size) {}
-
- not_a_string_view remove_prefix(size_t n) const {
- return not_a_string_view(static_cast<const char*>(data_) + n, size_ - n);
- }
-
- not_a_string_view remove_suffix(size_t n) const {
- return not_a_string_view(data_, size_ - n);
- }
-
- const void* data() const { return data_; }
- size_t size() const { return size_; }
-
- private:
- const void* data_;
- size_t size_;
-};
-
-bool operator==(not_a_string_view lhs, not_a_string_view rhs) {
- return lhs.data() == rhs.data() && lhs.size() == rhs.size();
-}
-
-std::ostream& operator<<(std::ostream& s, not_a_string_view rhs) {
- return s << "{ data: " << rhs.data() << " size: " << rhs.size() << "}";
-}
-
-std::vector<not_a_string_view> ToRawFlats(const CordRepRing* r) {
- std::vector<not_a_string_view> flats;
- flats.reserve(r->entries());
- index_type pos = r->head();
- do {
- flats.emplace_back(r->entry_data(pos));
- } while ((pos = r->advance(pos)) != r->tail());
- return flats;
-}
-
-// Returns the value contained in the provided CordRepRing
-std::string ToString(const CordRepRing* r) {
- std::string value;
- value.reserve(r->length);
- index_type pos = r->head();
- do {
- absl::string_view sv = r->entry_data(pos);
- value.append(sv.data(), sv.size());
- } while ((pos = r->advance(pos)) != r->tail());
- return value;
-}
-
-// Creates a flat for testing
-CordRep* MakeFlat(absl::string_view s, size_t extra = 0) {
- CordRepFlat* flat = CordRepFlat::New(s.length() + extra);
- memcpy(flat->Data(), s.data(), s.length());
- flat->length = s.length();
- return flat;
-}
-
-// Creates an external node for testing
-CordRepExternal* MakeExternal(absl::string_view s) {
- struct Rep : public CordRepExternal {
- std::string s;
- explicit Rep(absl::string_view s) : s(s) {
- this->tag = EXTERNAL;
- this->base = s.data();
- this->length = s.length();
- this->releaser_invoker = [](CordRepExternal* self) {
- delete static_cast<Rep*>(self);
- };
- }
- };
- return new Rep(s);
-}
-
-CordRepExternal* MakeFakeExternal(size_t length) {
- struct Rep : public CordRepExternal {
- std::string s;
- explicit Rep(size_t len) {
- this->tag = EXTERNAL;
- this->base = reinterpret_cast<const char*>(this->storage);
- this->length = len;
- this->releaser_invoker = [](CordRepExternal* self) {
- delete static_cast<Rep*>(self);
- };
- }
- };
- return new Rep(length);
-}
-
-// Creates a flat or an external node for testing depending on the size.
-CordRep* MakeLeaf(absl::string_view s, size_t extra = 0) {
- if (s.size() <= absl::cord_internal::kMaxFlatLength) {
- return MakeFlat(s, extra);
- } else {
- return MakeExternal(s);
- }
-}
-
-// Creates a substring node
-CordRepSubstring* MakeSubstring(size_t start, size_t len, CordRep* rep) {
- auto* sub = new CordRepSubstring;
- sub->tag = SUBSTRING;
- sub->start = start;
- sub->length = (len <= 0) ? rep->length - start + len : len;
- sub->child = rep;
- return sub;
-}
-
-// Creates a substring node removing the specified prefix
-CordRepSubstring* RemovePrefix(size_t start, CordRep* rep) {
- return MakeSubstring(start, rep->length - start, rep);
-}
-
-// Creates a substring node removing the specified suffix
-CordRepSubstring* RemoveSuffix(size_t length, CordRep* rep) {
- return MakeSubstring(0, rep->length - length, rep);
-}
-
-CordRepConcat* MakeConcat(CordRep* left, CordRep* right, int depth = 0) {
- auto* concat = new CordRepConcat;
- concat->tag = CONCAT;
- concat->length = left->length + right->length;
- concat->left = left;
- concat->right = right;
- concat->set_depth(depth);
- return concat;
-}
-
-enum Composition { kMix, kAppend, kPrepend };
-
-Composition RandomComposition() {
- RandomEngine rng(GTEST_FLAG_GET(random_seed));
- return (rng() & 1) ? kMix : ((rng() & 1) ? kAppend : kPrepend);
-}
-
-absl::string_view ToString(Composition composition) {
- switch (composition) {
- case kAppend:
- return "Append";
- case kPrepend:
- return "Prepend";
- case kMix:
- return "Mix";
- }
- assert(false);
- return "???";
-}
-
-constexpr const char* kFox = "The quick brown fox jumps over the lazy dog";
-constexpr const char* kFoxFlats[] = {"The ", "quick ", "brown ",
- "fox ", "jumps ", "over ",
- "the ", "lazy ", "dog"};
-constexpr const char* kAlphabet = "abcdefghijklmnopqrstuvwxyz";
-
-CordRepRing* FromFlats(Span<const char* const> flats,
- Composition composition = kAppend) {
- if (flats.empty()) return nullptr;
- CordRepRing* ring = nullptr;
- switch (composition) {
- case kAppend:
- ring = CordRepRing::Create(MakeLeaf(flats.front()), flats.size() - 1);
- for (int i = 1; i < flats.size(); ++i) {
- ring = CordRepRing::Append(ring, MakeLeaf(flats[i]));
- }
- break;
- case kPrepend:
- ring = CordRepRing::Create(MakeLeaf(flats.back()), flats.size() - 1);
- for (int i = static_cast<int>(flats.size() - 2); i >= 0; --i) {
- ring = CordRepRing::Prepend(ring, MakeLeaf(flats[i]));
- }
- break;
- case kMix:
- size_t middle1 = flats.size() / 2, middle2 = middle1;
- ring = CordRepRing::Create(MakeLeaf(flats[middle1]), flats.size() - 1);
- if (!flats.empty()) {
- if ((flats.size() & 1) == 0) {
- ring = CordRepRing::Prepend(ring, MakeLeaf(flats[--middle1]));
- }
- for (int i = 1; i <= middle1; ++i) {
- ring = CordRepRing::Prepend(ring, MakeLeaf(flats[middle1 - i]));
- ring = CordRepRing::Append(ring, MakeLeaf(flats[middle2 + i]));
- }
- }
- break;
- }
- EXPECT_THAT(ToFlats(ring), ElementsAreArray(flats));
- return ring;
-}
-
-std::ostream& operator<<(std::ostream& s, const TestParam& param) {
- return s << param.ToString();
-}
-
-std::string TestParamToString(const testing::TestParamInfo<TestParam>& info) {
- return info.param.ToString();
-}
-
-class CordRingTest : public testing::Test {
- public:
- ~CordRingTest() override {
- for (CordRep* rep : unrefs_) {
- CordRep::Unref(rep);
- }
- }
-
- template <typename CordRepType>
- CordRepType* NeedsUnref(CordRepType* rep) {
- assert(rep);
- unrefs_.push_back(rep);
- return rep;
- }
-
- template <typename CordRepType>
- CordRepType* Ref(CordRepType* rep) {
- CordRep::Ref(rep);
- return NeedsUnref(rep);
- }
-
- private:
- std::vector<CordRep*> unrefs_;
-};
-
-class CordRingTestWithParam : public testing::TestWithParam<TestParam> {
- public:
- ~CordRingTestWithParam() override {
- for (CordRep* rep : unrefs_) {
- CordRep::Unref(rep);
- }
- }
-
- CordRepRing* CreateWithCapacity(CordRep* child, size_t extra_capacity) {
- if (!GetParam().with_capacity) extra_capacity = 0;
- CordRepRing* ring = CordRepRing::Create(child, extra_capacity);
- ring->SetCapacityForTesting(1 + extra_capacity);
- return RefIfShared(ring);
- }
-
- bool Shared() const { return !GetParam().refcount_is_one; }
- bool InputShared() const { return GetParam().input_share_mode == kShared; }
- bool InputSharedIndirect() const {
- return GetParam().input_share_mode == kSharedIndirect;
- }
-
- template <typename CordRepType>
- CordRepType* NeedsUnref(CordRepType* rep) {
- assert(rep);
- unrefs_.push_back(rep);
- return rep;
- }
-
- template <typename CordRepType>
- CordRepType* Ref(CordRepType* rep) {
- CordRep::Ref(rep);
- return NeedsUnref(rep);
- }
-
- template <typename CordRepType>
- CordRepType* RefIfShared(CordRepType* rep) {
- return Shared() ? Ref(rep) : rep;
- }
-
- template <typename CordRepType>
- CordRepType* RefIfInputShared(CordRepType* rep) {
- return InputShared() ? Ref(rep) : rep;
- }
-
- template <typename CordRepType>
- CordRepType* RefIfInputSharedIndirect(CordRepType* rep) {
- return InputSharedIndirect() ? Ref(rep) : rep;
- }
-
- private:
- std::vector<CordRep*> unrefs_;
-};
-
-class CordRingCreateTest : public CordRingTestWithParam {
- public:
- static TestParams CreateTestParams() {
- TestParams params;
- params.emplace_back(InputShareMode::kPrivate);
- params.emplace_back(InputShareMode::kShared);
- return params;
- }
-};
-
-class CordRingSubTest : public CordRingTestWithParam {
- public:
- static TestParams CreateTestParams() {
- TestParams params;
- for (bool refcount_is_one : {true, false}) {
- TestParam param;
- param.refcount_is_one = refcount_is_one;
- params.push_back(param);
- }
- return params;
- }
-};
-
-class CordRingBuildTest : public CordRingTestWithParam {
- public:
- static TestParams CreateTestParams() {
- TestParams params;
- for (bool refcount_is_one : {true, false}) {
- for (bool with_capacity : {true, false}) {
- TestParam param;
- param.refcount_is_one = refcount_is_one;
- param.with_capacity = with_capacity;
- params.push_back(param);
- }
- }
- return params;
- }
-};
-
-class CordRingCreateFromTreeTest : public CordRingTestWithParam {
- public:
- static TestParams CreateTestParams() {
- TestParams params;
- params.emplace_back(InputShareMode::kPrivate);
- params.emplace_back(InputShareMode::kShared);
- params.emplace_back(InputShareMode::kSharedIndirect);
- return params;
- }
-};
-
-class CordRingBuildInputTest : public CordRingTestWithParam {
- public:
- static TestParams CreateTestParams() {
- TestParams params;
- for (bool refcount_is_one : {true, false}) {
- for (bool with_capacity : {true, false}) {
- for (InputShareMode share_mode : {kPrivate, kShared, kSharedIndirect}) {
- TestParam param;
- param.refcount_is_one = refcount_is_one;
- param.with_capacity = with_capacity;
- param.input_share_mode = share_mode;
- params.push_back(param);
- }
- }
- }
- return params;
- }
-};
-
-INSTANTIATE_TEST_SUITE_P(WithParam, CordRingSubTest,
- testing::ValuesIn(CordRingSubTest::CreateTestParams()),
- TestParamToString);
-
-INSTANTIATE_TEST_SUITE_P(
- WithParam, CordRingCreateTest,
- testing::ValuesIn(CordRingCreateTest::CreateTestParams()),
- TestParamToString);
-
-INSTANTIATE_TEST_SUITE_P(
- WithParam, CordRingCreateFromTreeTest,
- testing::ValuesIn(CordRingCreateFromTreeTest::CreateTestParams()),
- TestParamToString);
-
-INSTANTIATE_TEST_SUITE_P(
- WithParam, CordRingBuildTest,
- testing::ValuesIn(CordRingBuildTest::CreateTestParams()),
- TestParamToString);
-
-INSTANTIATE_TEST_SUITE_P(
- WithParam, CordRingBuildInputTest,
- testing::ValuesIn(CordRingBuildInputTest::CreateTestParams()),
- TestParamToString);
-
-TEST_P(CordRingCreateTest, CreateFromFlat) {
- absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz";
- CordRepRing* result = NeedsUnref(CordRepRing::Create(MakeFlat(str1)));
- ASSERT_THAT(result, IsValidRingBuffer());
- EXPECT_THAT(result->length, Eq(str1.size()));
- EXPECT_THAT(ToFlats(result), ElementsAre(str1));
-}
-
-TEST_P(CordRingCreateTest, CreateFromRing) {
- CordRepRing* ring = RefIfShared(FromFlats(kFoxFlats));
- CordRepRing* result = NeedsUnref(CordRepRing::Create(ring));
- ASSERT_THAT(result, IsValidRingBuffer());
- EXPECT_THAT(result, EqIfPrivate(GetParam(), ring));
- EXPECT_THAT(result, NeIfShared(GetParam(), ring));
- EXPECT_THAT(ToFlats(result), ElementsAreArray(kFoxFlats));
-}
-
-TEST_P(CordRingCreateFromTreeTest, CreateFromSubstringRing) {
- CordRepRing* ring = RefIfInputSharedIndirect(FromFlats(kFoxFlats));
- CordRep* sub = RefIfInputShared(MakeSubstring(2, 11, ring));
- CordRepRing* result = NeedsUnref(CordRepRing::Create(sub));
- ASSERT_THAT(result, IsValidRingBuffer());
- EXPECT_THAT(result, EqIfInputPrivate(GetParam(), ring));
- EXPECT_THAT(ToString(result), string_view(kFox).substr(2, 11));
-}
-
-TEST_F(CordRingTest, CreateWithIllegalExtraCapacity) {
-#if defined(ABSL_HAVE_EXCEPTIONS)
- CordRep* flat = NeedsUnref(MakeFlat("Hello world"));
- try {
- CordRepRing::Create(flat, CordRepRing::kMaxCapacity);
- GTEST_FAIL() << "expected std::length_error exception";
- } catch (const std::length_error&) {
- }
-#elif defined(GTEST_HAS_DEATH_TEST)
- CordRep* flat = NeedsUnref(MakeFlat("Hello world"));
- EXPECT_DEATH(CordRepRing::Create(flat, CordRepRing::kMaxCapacity), ".*");
-#endif
-}
-
-TEST_P(CordRingCreateFromTreeTest, CreateFromSubstringOfFlat) {
- absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz";
- auto* flat = RefIfInputShared(MakeFlat(str1));
- auto* child = RefIfInputSharedIndirect(MakeSubstring(4, 20, flat));
- CordRepRing* result = NeedsUnref(CordRepRing::Create(child));
- ASSERT_THAT(result, IsValidRingBuffer());
- EXPECT_THAT(result->length, Eq(20));
- EXPECT_THAT(ToFlats(result), ElementsAre(str1.substr(4, 20)));
-}
-
-TEST_P(CordRingCreateTest, CreateFromExternal) {
- absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz";
- auto* child = RefIfInputShared(MakeExternal(str1));
- CordRepRing* result = NeedsUnref(CordRepRing::Create(child));
- ASSERT_THAT(result, IsValidRingBuffer());
- EXPECT_THAT(result->length, Eq(str1.size()));
- EXPECT_THAT(ToFlats(result), ElementsAre(str1));
-}
-
-TEST_P(CordRingCreateFromTreeTest, CreateFromSubstringOfExternal) {
- absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz";
- auto* external = RefIfInputShared(MakeExternal(str1));
- auto* child = RefIfInputSharedIndirect(MakeSubstring(1, 24, external));
- CordRepRing* result = NeedsUnref(CordRepRing::Create(child));
- ASSERT_THAT(result, IsValidRingBuffer());
- EXPECT_THAT(result->length, Eq(24));
- EXPECT_THAT(ToFlats(result), ElementsAre(str1.substr(1, 24)));
-}
-
-TEST_P(CordRingCreateFromTreeTest, CreateFromSubstringOfLargeExternal) {
- auto* external = RefIfInputShared(MakeFakeExternal(1 << 20));
- auto str = not_a_string_view(external->base, 1 << 20)
- .remove_prefix(1 << 19)
- .remove_suffix(6);
- auto* child =
- RefIfInputSharedIndirect(MakeSubstring(1 << 19, (1 << 19) - 6, external));
- CordRepRing* result = NeedsUnref(CordRepRing::Create(child));
- ASSERT_THAT(result, IsValidRingBuffer());
- EXPECT_THAT(result->length, Eq(str.size()));
- EXPECT_THAT(ToRawFlats(result), ElementsAre(str));
-}
-
-TEST_P(CordRingBuildInputTest, CreateFromConcat) {
- CordRep* flats[] = {MakeFlat("abcdefgh"), MakeFlat("ijklm"),
- MakeFlat("nopqrstuv"), MakeFlat("wxyz")};
- auto* left = MakeConcat(RefIfInputSharedIndirect(flats[0]), flats[1]);
- auto* right = MakeConcat(flats[2], RefIfInputSharedIndirect(flats[3]));
- auto* concat = RefIfInputShared(MakeConcat(left, right));
- CordRepRing* result = NeedsUnref(CordRepRing::Create(concat));
- ASSERT_THAT(result, IsValidRingBuffer());
- EXPECT_THAT(result->length, Eq(26));
- EXPECT_THAT(ToString(result), Eq(kAlphabet));
-}
-
-TEST_P(CordRingBuildInputTest, CreateFromSubstringConcat) {
- for (size_t off = 0; off < 26; ++off) {
- for (size_t len = 1; len < 26 - off; ++len) {
- CordRep* flats[] = {MakeFlat("abcdefgh"), MakeFlat("ijklm"),
- MakeFlat("nopqrstuv"), MakeFlat("wxyz")};
- auto* left = MakeConcat(RefIfInputSharedIndirect(flats[0]), flats[1]);
- auto* right = MakeConcat(flats[2], RefIfInputSharedIndirect(flats[3]));
- auto* concat = MakeConcat(left, right);
- auto* child = RefIfInputShared(MakeSubstring(off, len, concat));
- CordRepRing* result = NeedsUnref(CordRepRing::Create(child));
- ASSERT_THAT(result, IsValidRingBuffer());
- ASSERT_THAT(result->length, Eq(len));
- ASSERT_THAT(ToString(result), string_view(kAlphabet).substr(off, len));
- }
- }
-}
-
-TEST_P(CordRingCreateTest, Properties) {
- absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz";
- CordRepRing* result = NeedsUnref(CordRepRing::Create(MakeFlat(str1), 120));
- ASSERT_THAT(result, IsValidRingBuffer());
- EXPECT_THAT(result->head(), Eq(0));
- EXPECT_THAT(result->tail(), Eq(1));
- EXPECT_THAT(result->capacity(), Ge(120 + 1));
- EXPECT_THAT(result->capacity(), Le(2 * 120 + 1));
- EXPECT_THAT(result->entries(), Eq(1));
- EXPECT_THAT(result->begin_pos(), Eq(0));
-}
-
-TEST_P(CordRingCreateTest, EntryForNewFlat) {
- absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz";
- CordRep* child = MakeFlat(str1);
- CordRepRing* result = NeedsUnref(CordRepRing::Create(child, 120));
- ASSERT_THAT(result, IsValidRingBuffer());
- EXPECT_THAT(result->entry_child(0), Eq(child));
- EXPECT_THAT(result->entry_end_pos(0), Eq(str1.length()));
- EXPECT_THAT(result->entry_data_offset(0), Eq(0));
-}
-
-TEST_P(CordRingCreateTest, EntryForNewFlatSubstring) {
- absl::string_view str1 = "1234567890abcdefghijklmnopqrstuvwxyz";
- CordRep* child = MakeFlat(str1);
- CordRep* substring = MakeSubstring(10, 26, child);
- CordRepRing* result = NeedsUnref(CordRepRing::Create(substring, 1));
- ASSERT_THAT(result, IsValidRingBuffer());
- EXPECT_THAT(result->entry_child(0), Eq(child));
- EXPECT_THAT(result->entry_end_pos(0), Eq(26));
- EXPECT_THAT(result->entry_data_offset(0), Eq(10));
-}
-
-TEST_P(CordRingBuildTest, AppendFlat) {
- absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz";
- absl::string_view str2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
- CordRepRing* ring = CreateWithCapacity(MakeExternal(str1), 1);
- CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, MakeFlat(str2)));
- ASSERT_THAT(result, IsValidRingBuffer());
- EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
- EXPECT_THAT(result, NeIfShared(GetParam(), ring));
- EXPECT_THAT(result->length, Eq(str1.size() + str2.size()));
- EXPECT_THAT(ToFlats(result), ElementsAre(str1, str2));
-}
-
-TEST_P(CordRingBuildTest, PrependFlat) {
- absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz";
- absl::string_view str2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
- CordRepRing* ring = CreateWithCapacity(MakeExternal(str1), 1);
- CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, MakeFlat(str2)));
- ASSERT_THAT(result, IsValidRingBuffer());
- EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
- EXPECT_THAT(result, NeIfShared(GetParam(), ring));
- EXPECT_THAT(result->length, Eq(str1.size() + str2.size()));
- EXPECT_THAT(ToFlats(result), ElementsAre(str2, str1));
-}
-
-TEST_P(CordRingBuildTest, AppendString) {
- absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz";
- absl::string_view str2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
- CordRepRing* ring = CreateWithCapacity(MakeExternal(str1), 1);
- CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, str2));
- ASSERT_THAT(result, IsValidRingBuffer());
- EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
- EXPECT_THAT(result, NeIfShared(GetParam(), ring));
- EXPECT_THAT(result->length, Eq(str1.size() + str2.size()));
- EXPECT_THAT(ToFlats(result), ElementsAre(str1, str2));
-}
-
-TEST_P(CordRingBuildTest, AppendStringHavingExtra) {
- absl::string_view str1 = "1234";
- absl::string_view str2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
- CordRepRing* ring = CreateWithCapacity(MakeFlat(str1, 26), 0);
- CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, str2));
- ASSERT_THAT(result, IsValidRingBuffer());
- EXPECT_THAT(result->length, Eq(str1.size() + str2.size()));
- EXPECT_THAT(result, EqIfPrivate(GetParam(), ring));
- EXPECT_THAT(result, NeIfShared(GetParam(), ring));
-}
-
-TEST_P(CordRingBuildTest, AppendStringHavingPartialExtra) {
- absl::string_view str1 = "1234";
- absl::string_view str2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
-
- // Create flat with at least one extra byte. We don't expect to have sized
- // alloc and capacity rounding to grant us enough to not make it partial.
- auto* flat = MakeFlat(str1, 1);
- size_t avail = flat->flat()->Capacity() - flat->length;
- ASSERT_THAT(avail, Lt(str2.size())) << " adjust test for larger flats!";
-
- // Construct the flats we do expect using all of `avail`.
- absl::string_view str1a = str2.substr(0, avail);
- absl::string_view str2a = str2.substr(avail);
-
- CordRepRing* ring = CreateWithCapacity(flat, 1);
- CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, str2));
- ASSERT_THAT(result, IsValidRingBuffer());
- EXPECT_THAT(result->length, Eq(str1.size() + str2.size()));
- EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
- EXPECT_THAT(result, NeIfShared(GetParam(), ring));
- if (GetParam().refcount_is_one) {
- EXPECT_THAT(ToFlats(result), ElementsAre(StrCat(str1, str1a), str2a));
- } else {
- EXPECT_THAT(ToFlats(result), ElementsAre(str1, str2));
- }
-}
-
-TEST_P(CordRingBuildTest, AppendStringHavingExtraInSubstring) {
- absl::string_view str1 = "123456789_1234";
- absl::string_view str2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
- CordRep* flat = RemovePrefix(10, MakeFlat(str1, 26));
- CordRepRing* ring = CreateWithCapacity(flat, 0);
- CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, str2));
- ASSERT_THAT(result, IsValidRingBuffer());
- EXPECT_THAT(result, EqIfPrivate(GetParam(), ring));
- EXPECT_THAT(result, NeIfShared(GetParam(), ring));
- EXPECT_THAT(result->length, Eq(4 + str2.size()));
- if (GetParam().refcount_is_one) {
- EXPECT_THAT(ToFlats(result), ElementsAre(StrCat("1234", str2)));
- } else {
- EXPECT_THAT(ToFlats(result), ElementsAre("1234", str2));
- }
-}
-
-TEST_P(CordRingBuildTest, AppendStringHavingSharedExtra) {
- absl::string_view str1 = "123456789_1234";
- absl::string_view str2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
- for (int shared_type = 0; shared_type < 2; ++shared_type) {
- SCOPED_TRACE(absl::StrCat("Shared extra type ", shared_type));
-
- // Create a flat that is shared in some way.
- CordRep* flat = nullptr;
- CordRep* flat1 = nullptr;
- if (shared_type == 0) {
- // Shared flat
- flat = CordRep::Ref(MakeFlat(str1.substr(10), 100));
- } else if (shared_type == 1) {
- // Shared flat inside private substring
- flat1 = CordRep::Ref(MakeFlat(str1));
- flat = RemovePrefix(10, flat1);
- } else {
- // Private flat inside shared substring
- flat = CordRep::Ref(RemovePrefix(10, MakeFlat(str1, 100)));
- }
-
- CordRepRing* ring = CreateWithCapacity(flat, 1);
- CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, str2));
- ASSERT_THAT(result, IsValidRingBuffer());
- EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
- EXPECT_THAT(result, NeIfShared(GetParam(), ring));
- EXPECT_THAT(result->length, Eq(4 + str2.size()));
- EXPECT_THAT(ToFlats(result), ElementsAre("1234", str2));
-
- CordRep::Unref(shared_type == 1 ? flat1 : flat);
- }
-}
-
-TEST_P(CordRingBuildTest, AppendStringWithExtra) {
- absl::string_view str1 = "1234";
- absl::string_view str2 = "1234567890";
- absl::string_view str3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
- CordRepRing* ring = CreateWithCapacity(MakeExternal(str1), 1);
- CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, str2, 26));
- result = CordRepRing::Append(result, str3);
- ASSERT_THAT(result, IsValidRingBuffer());
- EXPECT_THAT(result->length, Eq(str1.size() + str2.size() + str3.size()));
- EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
- EXPECT_THAT(ToFlats(result), ElementsAre(str1, StrCat(str2, str3)));
-}
-
-TEST_P(CordRingBuildTest, PrependString) {
- absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz";
- absl::string_view str2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
- // Use external rep to avoid appending to first flat
- CordRepRing* ring = CreateWithCapacity(MakeExternal(str1), 1);
- CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, str2));
- ASSERT_THAT(result, IsValidRingBuffer());
- if (GetParam().with_capacity && GetParam().refcount_is_one) {
- EXPECT_THAT(result, Eq(ring));
- } else {
- EXPECT_THAT(result, Ne(ring));
- }
- EXPECT_THAT(result->length, Eq(str1.size() + str2.size()));
- EXPECT_THAT(ToFlats(result), ElementsAre(str2, str1));
-}
-
-TEST_P(CordRingBuildTest, PrependStringHavingExtra) {
- absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz1234";
- absl::string_view str2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
- CordRep* flat = RemovePrefix(26, MakeFlat(str1));
- CordRepRing* ring = CreateWithCapacity(flat, 0);
- CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, str2));
- ASSERT_THAT(result, IsValidRingBuffer());
- EXPECT_THAT(result, EqIfPrivate(GetParam(), ring));
- EXPECT_THAT(result, NeIfShared(GetParam(), ring));
- EXPECT_THAT(result->length, Eq(4 + str2.size()));
- if (GetParam().refcount_is_one) {
- EXPECT_THAT(ToFlats(result), ElementsAre(StrCat(str2, "1234")));
- } else {
- EXPECT_THAT(ToFlats(result), ElementsAre(str2, "1234"));
- }
-}
-
-TEST_P(CordRingBuildTest, PrependStringHavingSharedExtra) {
- absl::string_view str1 = "123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZ";
- absl::string_view str2 = "abcdefghij";
- absl::string_view str1a = str1.substr(10);
- for (int shared_type = 1; shared_type < 2; ++shared_type) {
- SCOPED_TRACE(absl::StrCat("Shared extra type ", shared_type));
-
- // Create a flat that is shared in some way.
- CordRep* flat = nullptr;
- CordRep* flat1 = nullptr;
- if (shared_type == 1) {
- // Shared flat inside private substring
- flat = RemovePrefix(10, flat1 = CordRep::Ref(MakeFlat(str1)));
- } else {
- // Private flat inside shared substring
- flat = CordRep::Ref(RemovePrefix(10, MakeFlat(str1, 100)));
- }
-
- CordRepRing* ring = CreateWithCapacity(flat, 1);
- CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, str2));
- ASSERT_THAT(result, IsValidRingBuffer());
- EXPECT_THAT(result->length, Eq(str1a.size() + str2.size()));
- EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
- EXPECT_THAT(result, NeIfShared(GetParam(), ring));
- EXPECT_THAT(ToFlats(result), ElementsAre(str2, str1a));
- CordRep::Unref(shared_type == 1 ? flat1 : flat);
- }
-}
-
-TEST_P(CordRingBuildTest, PrependStringWithExtra) {
- absl::string_view str1 = "1234";
- absl::string_view str2 = "1234567890";
- absl::string_view str3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
- CordRepRing* ring = CreateWithCapacity(MakeExternal(str1), 1);
- CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, str2, 26));
- ASSERT_THAT(result, IsValidRingBuffer());
- result = CordRepRing::Prepend(result, str3);
- EXPECT_THAT(result->length, Eq(str1.size() + str2.size() + str3.size()));
- EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
- EXPECT_THAT(ToFlats(result), ElementsAre(StrCat(str3, str2), str1));
-}
-
-TEST_P(CordRingBuildTest, AppendPrependStringMix) {
- const auto& flats = kFoxFlats;
- CordRepRing* ring = CreateWithCapacity(MakeFlat(flats[4]), 8);
- CordRepRing* result = ring;
- for (int i = 1; i <= 4; ++i) {
- result = CordRepRing::Prepend(result, flats[4 - i]);
- result = CordRepRing::Append(result, flats[4 + i]);
- }
- NeedsUnref(result);
- ASSERT_THAT(result, IsValidRingBuffer());
- EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
- EXPECT_THAT(ToString(result), kFox);
-}
-
-TEST_P(CordRingBuildTest, AppendPrependStringMixWithExtra) {
- const auto& flats = kFoxFlats;
- CordRepRing* ring = CreateWithCapacity(MakeFlat(flats[4], 100), 8);
- CordRepRing* result = ring;
- for (int i = 1; i <= 4; ++i) {
- result = CordRepRing::Prepend(result, flats[4 - i], 100);
- result = CordRepRing::Append(result, flats[4 + i], 100);
- }
- NeedsUnref(result);
- ASSERT_THAT(result, IsValidRingBuffer());
- EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
- if (GetParam().refcount_is_one) {
- EXPECT_THAT(ToFlats(result),
- ElementsAre("The quick brown fox ", "jumps over the lazy dog"));
- } else {
- EXPECT_THAT(ToFlats(result), ElementsAre("The quick brown fox ", "jumps ",
- "over the lazy dog"));
- }
-}
-
-TEST_P(CordRingBuildTest, AppendPrependStringMixWithPrependedExtra) {
- const auto& flats = kFoxFlats;
- CordRep* flat = MakeFlat(StrCat(std::string(50, '.'), flats[4]), 50);
- CordRepRing* ring = CreateWithCapacity(RemovePrefix(50, flat), 0);
- CordRepRing* result = ring;
- for (int i = 1; i <= 4; ++i) {
- result = CordRepRing::Prepend(result, flats[4 - i], 100);
- result = CordRepRing::Append(result, flats[4 + i], 100);
- }
- result = NeedsUnref(result);
- ASSERT_THAT(result, IsValidRingBuffer());
- EXPECT_THAT(result, EqIfPrivate(GetParam(), ring));
- if (GetParam().refcount_is_one) {
- EXPECT_THAT(ToFlats(result), ElementsAre(kFox));
- } else {
- EXPECT_THAT(ToFlats(result), ElementsAre("The quick brown fox ", "jumps ",
- "over the lazy dog"));
- }
-}
-
-TEST_P(CordRingSubTest, SubRing) {
- auto composition = RandomComposition();
- SCOPED_TRACE(ToString(composition));
- auto flats = MakeSpan(kFoxFlats);
- string_view all = kFox;
- for (size_t offset = 0; offset < all.size() - 1; ++offset) {
- CordRepRing* ring = RefIfShared(FromFlats(flats, composition));
- CordRepRing* result = CordRepRing::SubRing(ring, offset, 0);
- EXPECT_THAT(result, nullptr);
-
- for (size_t len = 1; len < all.size() - offset; ++len) {
- ring = RefIfShared(FromFlats(flats, composition));
- result = NeedsUnref(CordRepRing::SubRing(ring, offset, len));
- ASSERT_THAT(result, IsValidRingBuffer());
- ASSERT_THAT(result, EqIfPrivate(GetParam(), ring));
- ASSERT_THAT(result, NeIfShared(GetParam(), ring));
- ASSERT_THAT(ToString(result), Eq(all.substr(offset, len)));
- }
- }
-}
-
-TEST_P(CordRingSubTest, SubRingFromLargeExternal) {
- auto composition = RandomComposition();
- std::string large_string(1 << 20, '.');
- const char* flats[] = {
- "abcdefghijklmnopqrstuvwxyz",
- large_string.c_str(),
- "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
- };
- std::string buffer = absl::StrCat(flats[0], flats[1], flats[2]);
- absl::string_view all = buffer;
- for (size_t offset = 0; offset < 30; ++offset) {
- CordRepRing* ring = RefIfShared(FromFlats(flats, composition));
- CordRepRing* result = CordRepRing::SubRing(ring, offset, 0);
- EXPECT_THAT(result, nullptr);
-
- for (size_t len = all.size() - 30; len < all.size() - offset; ++len) {
- ring = RefIfShared(FromFlats(flats, composition));
- result = NeedsUnref(CordRepRing::SubRing(ring, offset, len));
- ASSERT_THAT(result, IsValidRingBuffer());
- ASSERT_THAT(result, EqIfPrivate(GetParam(), ring));
- ASSERT_THAT(result, NeIfShared(GetParam(), ring));
- auto str = ToString(result);
- ASSERT_THAT(str, SizeIs(len));
- ASSERT_THAT(str, Eq(all.substr(offset, len)));
- }
- }
-}
-
-TEST_P(CordRingSubTest, RemovePrefix) {
- auto composition = RandomComposition();
- SCOPED_TRACE(ToString(composition));
- auto flats = MakeSpan(kFoxFlats);
- string_view all = kFox;
- CordRepRing* ring = RefIfShared(FromFlats(flats, composition));
- CordRepRing* result = CordRepRing::RemovePrefix(ring, all.size());
- EXPECT_THAT(result, nullptr);
-
- for (size_t len = 1; len < all.size(); ++len) {
- ring = RefIfShared(FromFlats(flats, composition));
- result = NeedsUnref(CordRepRing::RemovePrefix(ring, len));
- ASSERT_THAT(result, IsValidRingBuffer());
- EXPECT_THAT(result, EqIfPrivate(GetParam(), ring));
- ASSERT_THAT(result, NeIfShared(GetParam(), ring));
- EXPECT_THAT(ToString(result), Eq(all.substr(len)));
- }
-}
-
-TEST_P(CordRingSubTest, RemovePrefixFromLargeExternal) {
- CordRepExternal* external1 = MakeFakeExternal(1 << 20);
- CordRepExternal* external2 = MakeFakeExternal(1 << 20);
- CordRepRing* ring = CordRepRing::Create(external1, 1);
- ring = CordRepRing::Append(ring, external2);
- CordRepRing* result = NeedsUnref(CordRepRing::RemovePrefix(ring, 1 << 16));
- EXPECT_THAT(
- ToRawFlats(result),
- ElementsAre(
- not_a_string_view(external1->base, 1 << 20).remove_prefix(1 << 16),
- not_a_string_view(external2->base, 1 << 20)));
-}
-
-TEST_P(CordRingSubTest, RemoveSuffix) {
- auto composition = RandomComposition();
- SCOPED_TRACE(ToString(composition));
- auto flats = MakeSpan(kFoxFlats);
- string_view all = kFox;
- CordRepRing* ring = RefIfShared(FromFlats(flats, composition));
- CordRepRing* result = CordRepRing::RemoveSuffix(ring, all.size());
- EXPECT_THAT(result, nullptr);
-
- for (size_t len = 1; len < all.size(); ++len) {
- ring = RefIfShared(FromFlats(flats, composition));
- result = NeedsUnref(CordRepRing::RemoveSuffix(ring, len));
- ASSERT_THAT(result, IsValidRingBuffer());
- ASSERT_THAT(result, EqIfPrivate(GetParam(), ring));
- ASSERT_THAT(result, NeIfShared(GetParam(), ring));
- ASSERT_THAT(ToString(result), Eq(all.substr(0, all.size() - len)));
- }
-}
-
-TEST_P(CordRingSubTest, AppendRing) {
- auto composition = RandomComposition();
- SCOPED_TRACE(ToString(composition));
- auto flats = MakeSpan(kFoxFlats).subspan(1);
- CordRepRing* ring = CreateWithCapacity(MakeFlat(kFoxFlats[0]), flats.size());
- CordRepRing* child = FromFlats(flats, composition);
- CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, child));
- ASSERT_THAT(result, IsValidRingBuffer());
- EXPECT_THAT(result, EqIfPrivate(GetParam(), ring));
- EXPECT_THAT(result, NeIfShared(GetParam(), ring));
- EXPECT_THAT(ToFlats(result), ElementsAreArray(kFoxFlats));
-}
-
-TEST_P(CordRingBuildInputTest, AppendRingWithFlatOffset) {
- auto composition = RandomComposition();
- SCOPED_TRACE(ToString(composition));
- auto flats = MakeSpan(kFoxFlats);
- CordRepRing* ring = CreateWithCapacity(MakeFlat("Head"), flats.size());
- CordRep* child = RefIfInputSharedIndirect(FromFlats(flats, composition));
- CordRep* stripped = RemovePrefix(10, child);
- CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, stripped));
- ASSERT_THAT(result, IsValidRingBuffer());
- EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
- EXPECT_THAT(result, NeIfShared(GetParam(), ring));
- EXPECT_THAT(ToFlats(result), ElementsAre("Head", "brown ", "fox ", "jumps ",
- "over ", "the ", "lazy ", "dog"));
-}
-
-TEST_P(CordRingBuildInputTest, AppendRingWithBrokenOffset) {
- auto composition = RandomComposition();
- SCOPED_TRACE(ToString(composition));
- auto flats = MakeSpan(kFoxFlats);
- CordRepRing* ring = CreateWithCapacity(MakeFlat("Head"), flats.size());
- CordRep* child = RefIfInputSharedIndirect(FromFlats(flats, composition));
- CordRep* stripped = RemovePrefix(21, child);
- CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, stripped));
- ASSERT_THAT(result, IsValidRingBuffer());
- EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
- EXPECT_THAT(result, NeIfShared(GetParam(), ring));
- EXPECT_THAT(ToFlats(result),
- ElementsAre("Head", "umps ", "over ", "the ", "lazy ", "dog"));
-}
-
-TEST_P(CordRingBuildInputTest, AppendRingWithFlatLength) {
- auto composition = RandomComposition();
- SCOPED_TRACE(ToString(composition));
- auto flats = MakeSpan(kFoxFlats);
- CordRepRing* ring = CreateWithCapacity(MakeFlat("Head"), flats.size());
- CordRep* child = RefIfInputSharedIndirect(FromFlats(flats, composition));
- CordRep* stripped = RemoveSuffix(8, child);
- CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, stripped));
- ASSERT_THAT(result, IsValidRingBuffer());
- EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
- EXPECT_THAT(result, NeIfShared(GetParam(), ring));
- EXPECT_THAT(ToFlats(result), ElementsAre("Head", "The ", "quick ", "brown ",
- "fox ", "jumps ", "over ", "the "));
-}
-
-TEST_P(CordRingBuildTest, AppendRingWithBrokenFlatLength) {
- auto composition = RandomComposition();
- SCOPED_TRACE(ToString(composition));
- auto flats = MakeSpan(kFoxFlats);
- CordRepRing* ring = CreateWithCapacity(MakeFlat("Head"), flats.size());
- CordRep* child = RefIfInputSharedIndirect(FromFlats(flats, composition));
- CordRep* stripped = RemoveSuffix(15, child);
- CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, stripped));
- ASSERT_THAT(result, IsValidRingBuffer());
- EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
- EXPECT_THAT(result, NeIfShared(GetParam(), ring));
- EXPECT_THAT(ToFlats(result), ElementsAre("Head", "The ", "quick ", "brown ",
- "fox ", "jumps ", "ov"));
-}
-
-TEST_P(CordRingBuildTest, AppendRingMiddlePiece) {
- auto composition = RandomComposition();
- SCOPED_TRACE(ToString(composition));
- auto flats = MakeSpan(kFoxFlats);
- CordRepRing* ring = CreateWithCapacity(MakeFlat("Head"), flats.size());
- CordRep* child = RefIfInputSharedIndirect(FromFlats(flats, composition));
- CordRep* stripped = MakeSubstring(7, child->length - 27, child);
- CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, stripped));
- ASSERT_THAT(result, IsValidRingBuffer());
- EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
- EXPECT_THAT(result, NeIfShared(GetParam(), ring));
- EXPECT_THAT(ToFlats(result),
- ElementsAre("Head", "ck ", "brown ", "fox ", "jum"));
-}
-
-TEST_P(CordRingBuildTest, AppendRingSinglePiece) {
- auto composition = RandomComposition();
- SCOPED_TRACE(ToString(composition));
- auto flats = MakeSpan(kFoxFlats);
- CordRepRing* ring = CreateWithCapacity(MakeFlat("Head"), flats.size());
- CordRep* child = RefIfInputSharedIndirect(FromFlats(flats, composition));
- CordRep* stripped = RefIfInputShared(MakeSubstring(11, 3, child));
- CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, stripped));
- ASSERT_THAT(result, IsValidRingBuffer());
- EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
- EXPECT_THAT(result, NeIfShared(GetParam(), ring));
- EXPECT_THAT(ToFlats(result), ElementsAre("Head", "row"));
-}
-
-TEST_P(CordRingBuildInputTest, AppendRingSinglePieceWithPrefix) {
- auto composition = RandomComposition();
- SCOPED_TRACE(ToString(composition));
- auto flats = MakeSpan(kFoxFlats);
- size_t extra_capacity = 1 + (GetParam().with_capacity ? flats.size() : 0);
- CordRepRing* ring = CordRepRing::Create(MakeFlat("Head"), extra_capacity);
- ring->SetCapacityForTesting(1 + extra_capacity);
- ring = RefIfShared(CordRepRing::Prepend(ring, MakeFlat("Prepend")));
- assert(ring->IsValid(std::cout));
- CordRepRing* child = RefIfInputSharedIndirect(FromFlats(flats, composition));
- CordRep* stripped = RefIfInputShared(MakeSubstring(11, 3, child));
- CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, stripped));
- ASSERT_THAT(result, IsValidRingBuffer());
- EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
- EXPECT_THAT(result, NeIfShared(GetParam(), ring));
- EXPECT_THAT(ToFlats(result), ElementsAre("Prepend", "Head", "row"));
-}
-
-TEST_P(CordRingBuildInputTest, PrependRing) {
- auto composition = RandomComposition();
- SCOPED_TRACE(ToString(composition));
- auto fox = MakeSpan(kFoxFlats);
- auto flats = MakeSpan(fox).subspan(0, fox.size() - 1);
- CordRepRing* ring = CreateWithCapacity(MakeFlat(fox.back()), flats.size());
- CordRepRing* child = RefIfInputShared(FromFlats(flats, composition));
- CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, child));
- ASSERT_THAT(result, IsValidRingBuffer());
- EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
- EXPECT_THAT(result, NeIfShared(GetParam(), ring));
- EXPECT_THAT(ToFlats(result), ElementsAreArray(kFoxFlats));
-}
-
-TEST_P(CordRingBuildInputTest, PrependRingWithFlatOffset) {
- auto composition = RandomComposition();
- SCOPED_TRACE(ToString(composition));
- auto flats = MakeSpan(kFoxFlats);
- CordRepRing* ring = CreateWithCapacity(MakeFlat("Tail"), flats.size());
- CordRep* child = RefIfInputShared(FromFlats(flats, composition));
- CordRep* stripped = RefIfInputSharedIndirect(RemovePrefix(10, child));
- CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, stripped));
- ASSERT_THAT(result, IsValidRingBuffer());
- EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
- EXPECT_THAT(result, NeIfShared(GetParam(), ring));
- EXPECT_THAT(ToFlats(result), ElementsAre("brown ", "fox ", "jumps ", "over ",
- "the ", "lazy ", "dog", "Tail"));
-}
-
-TEST_P(CordRingBuildInputTest, PrependRingWithBrokenOffset) {
- auto composition = RandomComposition();
- SCOPED_TRACE(ToString(composition));
- auto flats = MakeSpan(kFoxFlats);
- CordRepRing* ring = CreateWithCapacity(MakeFlat("Tail"), flats.size());
- CordRep* child = RefIfInputShared(FromFlats(flats, composition));
- CordRep* stripped = RefIfInputSharedIndirect(RemovePrefix(21, child));
- CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, stripped));
- EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
- EXPECT_THAT(result, NeIfShared(GetParam(), ring));
- EXPECT_THAT(ToFlats(result),
- ElementsAre("umps ", "over ", "the ", "lazy ", "dog", "Tail"));
-}
-
-TEST_P(CordRingBuildInputTest, PrependRingWithFlatLength) {
- auto composition = RandomComposition();
- SCOPED_TRACE(ToString(composition));
- auto flats = MakeSpan(kFoxFlats);
- CordRepRing* ring = CreateWithCapacity(MakeFlat("Tail"), flats.size());
- CordRep* child = RefIfInputShared(FromFlats(flats, composition));
- CordRep* stripped = RefIfInputSharedIndirect(RemoveSuffix(8, child));
- CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, stripped));
- ASSERT_THAT(result, IsValidRingBuffer());
- EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
- EXPECT_THAT(result, NeIfShared(GetParam(), ring));
- EXPECT_THAT(ToFlats(result), ElementsAre("The ", "quick ", "brown ", "fox ",
- "jumps ", "over ", "the ", "Tail"));
-}
-
-TEST_P(CordRingBuildInputTest, PrependRingWithBrokenFlatLength) {
- auto composition = RandomComposition();
- SCOPED_TRACE(ToString(composition));
- auto flats = MakeSpan(kFoxFlats);
- CordRepRing* ring = CreateWithCapacity(MakeFlat("Tail"), flats.size());
- CordRep* child = RefIfInputShared(FromFlats(flats, composition));
- CordRep* stripped = RefIfInputSharedIndirect(RemoveSuffix(15, child));
- CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, stripped));
- ASSERT_THAT(result, IsValidRingBuffer());
- EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
- EXPECT_THAT(result, NeIfShared(GetParam(), ring));
- EXPECT_THAT(ToFlats(result), ElementsAre("The ", "quick ", "brown ", "fox ",
- "jumps ", "ov", "Tail"));
-}
-
-TEST_P(CordRingBuildInputTest, PrependRingMiddlePiece) {
- auto composition = RandomComposition();
- SCOPED_TRACE(ToString(composition));
- auto flats = MakeSpan(kFoxFlats);
- CordRepRing* ring = CreateWithCapacity(MakeFlat("Tail"), flats.size());
- CordRep* child = RefIfInputShared(FromFlats(flats, composition));
- CordRep* stripped =
- RefIfInputSharedIndirect(MakeSubstring(7, child->length - 27, child));
- CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, stripped));
- ASSERT_THAT(result, IsValidRingBuffer());
- EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
- EXPECT_THAT(result, NeIfShared(GetParam(), ring));
- EXPECT_THAT(ToFlats(result),
- ElementsAre("ck ", "brown ", "fox ", "jum", "Tail"));
-}
-
-TEST_P(CordRingBuildInputTest, PrependRingSinglePiece) {
- auto composition = RandomComposition();
- SCOPED_TRACE(ToString(composition));
- auto flats = MakeSpan(kFoxFlats);
- CordRepRing* ring = CreateWithCapacity(MakeFlat("Tail"), flats.size());
- CordRep* child = RefIfInputShared(FromFlats(flats, composition));
- CordRep* stripped = RefIfInputSharedIndirect(MakeSubstring(11, 3, child));
- CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, stripped));
- ASSERT_THAT(result, IsValidRingBuffer());
- EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
- EXPECT_THAT(result, NeIfShared(GetParam(), ring));
- EXPECT_THAT(ToFlats(result), ElementsAre("row", "Tail"));
-}
-
-TEST_P(CordRingBuildInputTest, PrependRingSinglePieceWithPrefix) {
- auto composition = RandomComposition();
- SCOPED_TRACE(ToString(composition));
- auto flats = MakeSpan(kFoxFlats);
- size_t extra_capacity = 1 + (GetParam().with_capacity ? flats.size() : 0);
- CordRepRing* ring = CordRepRing::Create(MakeFlat("Tail"), extra_capacity);
- ring->SetCapacityForTesting(1 + extra_capacity);
- ring = RefIfShared(CordRepRing::Prepend(ring, MakeFlat("Prepend")));
- CordRep* child = RefIfInputShared(FromFlats(flats, composition));
- CordRep* stripped = RefIfInputSharedIndirect(MakeSubstring(11, 3, child));
- CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, stripped));
- ASSERT_THAT(result, IsValidRingBuffer());
- EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring));
- EXPECT_THAT(result, NeIfShared(GetParam(), ring));
- EXPECT_THAT(ToFlats(result), ElementsAre("row", "Prepend", "Tail"));
-}
-
-TEST_F(CordRingTest, Find) {
- constexpr const char* flats[] = {
- "abcdefghij", "klmnopqrst", "uvwxyz", "ABCDEFGHIJ",
- "KLMNOPQRST", "UVWXYZ", "1234567890", "~!@#$%^&*()_",
- "+-=", "[]\\{}|;':", ",/<>?", "."};
- auto composition = RandomComposition();
- SCOPED_TRACE(ToString(composition));
- CordRepRing* ring = NeedsUnref(FromFlats(flats, composition));
- std::string value = ToString(ring);
- for (int i = 0; i < value.length(); ++i) {
- CordRepRing::Position found = ring->Find(i);
- auto data = ring->entry_data(found.index);
- ASSERT_THAT(found.offset, Lt(data.length()));
- ASSERT_THAT(data[found.offset], Eq(value[i]));
- }
-}
-
-TEST_F(CordRingTest, FindWithHint) {
- constexpr const char* flats[] = {
- "abcdefghij", "klmnopqrst", "uvwxyz", "ABCDEFGHIJ",
- "KLMNOPQRST", "UVWXYZ", "1234567890", "~!@#$%^&*()_",
- "+-=", "[]\\{}|;':", ",/<>?", "."};
- auto composition = RandomComposition();
- SCOPED_TRACE(ToString(composition));
- CordRepRing* ring = NeedsUnref(FromFlats(flats, composition));
- std::string value = ToString(ring);
-
-#if defined(GTEST_HAS_DEATH_TEST)
- // Test hint beyond valid position
- index_type head = ring->head();
- EXPECT_DEBUG_DEATH(ring->Find(ring->advance(head), 0), ".*");
- EXPECT_DEBUG_DEATH(ring->Find(ring->advance(head), 9), ".*");
- EXPECT_DEBUG_DEATH(ring->Find(ring->advance(head, 3), 24), ".*");
-#endif
-
- int flat_pos = 0;
- size_t flat_offset = 0;
- for (auto sflat : flats) {
- string_view flat(sflat);
- for (int offset = 0; offset < flat.length(); ++offset) {
- for (int start = 0; start <= flat_pos; ++start) {
- index_type hint = ring->advance(ring->head(), start);
- CordRepRing::Position found = ring->Find(hint, flat_offset + offset);
- ASSERT_THAT(found.index, Eq(ring->advance(ring->head(), flat_pos)));
- ASSERT_THAT(found.offset, Eq(offset));
- }
- }
- ++flat_pos;
- flat_offset += flat.length();
- }
-}
-
-TEST_F(CordRingTest, FindInLargeRing) {
- constexpr const char* flats[] = {
- "abcdefghij", "klmnopqrst", "uvwxyz", "ABCDEFGHIJ",
- "KLMNOPQRST", "UVWXYZ", "1234567890", "~!@#$%^&*()_",
- "+-=", "[]\\{}|;':", ",/<>?", "."};
- auto composition = RandomComposition();
- SCOPED_TRACE(ToString(composition));
- CordRepRing* ring = FromFlats(flats, composition);
- for (int i = 0; i < 13; ++i) {
- ring = CordRepRing::Append(ring, FromFlats(flats, composition));
- }
- NeedsUnref(ring);
- std::string value = ToString(ring);
- for (int i = 0; i < value.length(); ++i) {
- CordRepRing::Position pos = ring->Find(i);
- auto data = ring->entry_data(pos.index);
- ASSERT_THAT(pos.offset, Lt(data.length()));
- ASSERT_THAT(data[pos.offset], Eq(value[i]));
- }
-}
-
-TEST_F(CordRingTest, FindTail) {
- constexpr const char* flats[] = {
- "abcdefghij", "klmnopqrst", "uvwxyz", "ABCDEFGHIJ",
- "KLMNOPQRST", "UVWXYZ", "1234567890", "~!@#$%^&*()_",
- "+-=", "[]\\{}|;':", ",/<>?", "."};
- auto composition = RandomComposition();
- SCOPED_TRACE(ToString(composition));
- CordRepRing* ring = NeedsUnref(FromFlats(flats, composition));
- std::string value = ToString(ring);
-
- for (int i = 0; i < value.length(); ++i) {
- CordRepRing::Position pos = ring->FindTail(i + 1);
- auto data = ring->entry_data(ring->retreat(pos.index));
- ASSERT_THAT(pos.offset, Lt(data.length()));
- ASSERT_THAT(data[data.length() - pos.offset - 1], Eq(value[i]));
- }
-}
-
-TEST_F(CordRingTest, FindTailWithHint) {
- constexpr const char* flats[] = {
- "abcdefghij", "klmnopqrst", "uvwxyz", "ABCDEFGHIJ",
- "KLMNOPQRST", "UVWXYZ", "1234567890", "~!@#$%^&*()_",
- "+-=", "[]\\{}|;':", ",/<>?", "."};
- auto composition = RandomComposition();
- SCOPED_TRACE(ToString(composition));
- CordRepRing* ring = NeedsUnref(FromFlats(flats, composition));
- std::string value = ToString(ring);
-
- // Test hint beyond valid position
-#if defined(GTEST_HAS_DEATH_TEST)
- index_type head = ring->head();
- EXPECT_DEBUG_DEATH(ring->FindTail(ring->advance(head), 1), ".*");
- EXPECT_DEBUG_DEATH(ring->FindTail(ring->advance(head), 10), ".*");
- EXPECT_DEBUG_DEATH(ring->FindTail(ring->advance(head, 3), 26), ".*");
-#endif
-
- for (int i = 0; i < value.length(); ++i) {
- CordRepRing::Position pos = ring->FindTail(i + 1);
- auto data = ring->entry_data(ring->retreat(pos.index));
- ASSERT_THAT(pos.offset, Lt(data.length()));
- ASSERT_THAT(data[data.length() - pos.offset - 1], Eq(value[i]));
- }
-}
-
-TEST_F(CordRingTest, FindTailInLargeRing) {
- constexpr const char* flats[] = {
- "abcdefghij", "klmnopqrst", "uvwxyz", "ABCDEFGHIJ",
- "KLMNOPQRST", "UVWXYZ", "1234567890", "~!@#$%^&*()_",
- "+-=", "[]\\{}|;':", ",/<>?", "."};
- auto composition = RandomComposition();
- SCOPED_TRACE(ToString(composition));
- CordRepRing* ring = FromFlats(flats, composition);
- for (int i = 0; i < 13; ++i) {
- ring = CordRepRing::Append(ring, FromFlats(flats, composition));
- }
- NeedsUnref(ring);
- std::string value = ToString(ring);
- for (int i = 0; i < value.length(); ++i) {
- CordRepRing::Position pos = ring->FindTail(i + 1);
- auto data = ring->entry_data(ring->retreat(pos.index));
- ASSERT_THAT(pos.offset, Lt(data.length()));
- ASSERT_THAT(data[data.length() - pos.offset - 1], Eq(value[i]));
- }
-}
-
-TEST_F(CordRingTest, GetCharacter) {
- auto flats = MakeSpan(kFoxFlats);
- CordRepRing* ring = CordRepRing::Create(MakeFlat("Tail"), flats.size());
- CordRep* child = FromFlats(flats, kAppend);
- CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, child));
- std::string value = ToString(result);
- for (int i = 0; i < value.length(); ++i) {
- ASSERT_THAT(result->GetCharacter(i), Eq(value[i]));
- }
-}
-
-TEST_F(CordRingTest, GetCharacterWithSubstring) {
- absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz";
- auto* child = MakeSubstring(4, 20, MakeFlat(str1));
- CordRepRing* result = NeedsUnref(CordRepRing::Create(child));
- ASSERT_THAT(result, IsValidRingBuffer());
- std::string value = ToString(result);
- for (int i = 0; i < value.length(); ++i) {
- ASSERT_THAT(result->GetCharacter(i), Eq(value[i]));
- }
-}
-
-TEST_F(CordRingTest, IsFlatSingleFlat) {
- for (bool external : {false, true}) {
- SCOPED_TRACE(external ? "With External" : "With Flat");
- absl::string_view str = "Hello world";
- CordRep* rep = external ? MakeExternal(str) : MakeFlat(str);
- CordRepRing* ring = NeedsUnref(CordRepRing::Create(rep));
-
- // The ring is a single non-fragmented flat:
- absl::string_view fragment;
- EXPECT_TRUE(ring->IsFlat(nullptr));
- EXPECT_TRUE(ring->IsFlat(&fragment));
- EXPECT_THAT(fragment, Eq("Hello world"));
- fragment = "";
- EXPECT_TRUE(ring->IsFlat(0, 11, nullptr));
- EXPECT_TRUE(ring->IsFlat(0, 11, &fragment));
- EXPECT_THAT(fragment, Eq("Hello world"));
-
- // Arbitrary ranges must check true as well.
- EXPECT_TRUE(ring->IsFlat(1, 4, &fragment));
- EXPECT_THAT(fragment, Eq("ello"));
- EXPECT_TRUE(ring->IsFlat(6, 5, &fragment));
- EXPECT_THAT(fragment, Eq("world"));
- }
-}
-
-TEST_F(CordRingTest, IsFlatMultiFlat) {
- for (bool external : {false, true}) {
- SCOPED_TRACE(external ? "With External" : "With Flat");
- absl::string_view str1 = "Hello world";
- absl::string_view str2 = "Halt and catch fire";
- CordRep* rep1 = external ? MakeExternal(str1) : MakeFlat(str1);
- CordRep* rep2 = external ? MakeExternal(str2) : MakeFlat(str2);
- CordRepRing* ring = CordRepRing::Append(CordRepRing::Create(rep1), rep2);
- NeedsUnref(ring);
-
- // The ring is fragmented, IsFlat() on the entire cord must be false.
- EXPECT_FALSE(ring->IsFlat(nullptr));
- absl::string_view fragment = "Don't touch this";
- EXPECT_FALSE(ring->IsFlat(&fragment));
- EXPECT_THAT(fragment, Eq("Don't touch this"));
-
- // Check for ranges exactly within both flats.
- EXPECT_TRUE(ring->IsFlat(0, 11, &fragment));
- EXPECT_THAT(fragment, Eq("Hello world"));
- EXPECT_TRUE(ring->IsFlat(11, 19, &fragment));
- EXPECT_THAT(fragment, Eq("Halt and catch fire"));
-
- // Check for arbitrary partial range inside each flat.
- EXPECT_TRUE(ring->IsFlat(1, 4, &fragment));
- EXPECT_THAT(fragment, "ello");
- EXPECT_TRUE(ring->IsFlat(26, 4, &fragment));
- EXPECT_THAT(fragment, "fire");
-
- // Check ranges spanning across both flats
- fragment = "Don't touch this";
- EXPECT_FALSE(ring->IsFlat(1, 18, &fragment));
- EXPECT_FALSE(ring->IsFlat(10, 2, &fragment));
- EXPECT_THAT(fragment, Eq("Don't touch this"));
- }
-}
-
-TEST_F(CordRingTest, Dump) {
- std::stringstream ss;
- auto flats = MakeSpan(kFoxFlats);
- CordRepRing* ring = NeedsUnref(FromFlats(flats, kPrepend));
- ss << *ring;
-}
-
-} // namespace
-ABSL_NAMESPACE_END
-} // namespace absl
diff --git a/absl/strings/cord_test.cc b/absl/strings/cord_test.cc
index 597378c..cdd5246 100644
--- a/absl/strings/cord_test.cc
+++ b/absl/strings/cord_test.cc
@@ -15,33 +15,71 @@
#include "absl/strings/cord.h"
#include <algorithm>
-#include <climits>
+#include <array>
+#include <cassert>
+#include <cstddef>
+#include <cstdint>
#include <cstdio>
+#include <cstring>
+#include <iostream>
#include <iterator>
-#include <map>
-#include <numeric>
+#include <limits>
#include <random>
+#include <set>
#include <sstream>
+#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
-#include "absl/base/casts.h"
+#include "absl/base/attributes.h"
#include "absl/base/config.h"
#include "absl/base/internal/endian.h"
-#include "absl/base/internal/raw_logging.h"
#include "absl/base/macros.h"
+#include "absl/base/options.h"
#include "absl/container/fixed_array.h"
+#include "absl/functional/function_ref.h"
+#include "absl/hash/hash.h"
+#include "absl/hash/hash_testing.h"
+#include "absl/log/check.h"
+#include "absl/log/log.h"
+#include "absl/random/random.h"
+#include "absl/strings/cord_buffer.h"
#include "absl/strings/cord_test_helpers.h"
#include "absl/strings/cordz_test_helpers.h"
+#include "absl/strings/internal/cord_internal.h"
+#include "absl/strings/internal/cord_rep_crc.h"
+#include "absl/strings/internal/cord_rep_flat.h"
+#include "absl/strings/internal/cordz_statistics.h"
+#include "absl/strings/internal/cordz_update_tracker.h"
+#include "absl/strings/internal/string_constant.h"
+#include "absl/strings/match.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/strings/string_view.h"
+#include "absl/types/optional.h"
+
+// convenience local constants
+static constexpr auto FLAT = absl::cord_internal::FLAT;
+static constexpr auto MAX_FLAT_TAG = absl::cord_internal::MAX_FLAT_TAG;
typedef std::mt19937_64 RandomEngine;
+using absl::cord_internal::CordRep;
+using absl::cord_internal::CordRepBtree;
+using absl::cord_internal::CordRepConcat;
+using absl::cord_internal::CordRepCrc;
+using absl::cord_internal::CordRepExternal;
+using absl::cord_internal::CordRepFlat;
+using absl::cord_internal::CordRepSubstring;
+using absl::cord_internal::CordzUpdateTracker;
+using absl::cord_internal::kFlatOverhead;
+using absl::cord_internal::kMaxFlatLength;
+using ::testing::ElementsAre;
+using ::testing::Le;
+
static std::string RandomLowercaseString(RandomEngine* rng);
static std::string RandomLowercaseString(RandomEngine* rng, size_t length);
@@ -184,21 +222,19 @@
}
static bool IsTree(const Cord& c) { return c.contents_.is_tree(); }
+ static CordRep* Tree(const Cord& c) { return c.contents_.tree(); }
static cord_internal::CordzInfo* GetCordzInfo(const Cord& c) {
return c.contents_.cordz_info();
}
static Cord MakeSubstring(Cord src, size_t offset, size_t length) {
- ABSL_RAW_CHECK(src.contents_.is_tree(), "Can not be inlined");
+ CHECK(src.contents_.is_tree()) << "Can not be inlined";
+ CHECK(!src.ExpectedChecksum().has_value()) << "Can not be hardened";
Cord cord;
- auto* rep = new cord_internal::CordRepSubstring;
- rep->tag = cord_internal::SUBSTRING;
- rep->child = cord_internal::CordRep::Ref(src.contents_.tree());
- rep->start = offset;
- rep->length = length;
- cord.contents_.EmplaceTree(rep,
- cord_internal::CordzUpdateTracker::kSubCord);
+ auto* tree = cord_internal::SkipCrcNode(src.contents_.tree());
+ auto* rep = CordRepSubstring::Create(CordRep::Ref(tree), offset, length);
+ cord.contents_.EmplaceTree(rep, CordzUpdateTracker::kSubCord);
return cord;
}
};
@@ -206,7 +242,108 @@
ABSL_NAMESPACE_END
} // namespace absl
-TEST(Cord, AllFlatSizes) {
+// The CordTest fixture runs all tests with and without Cord Btree enabled,
+// and with our without expected CRCs being set on the subject Cords.
+class CordTest : public testing::TestWithParam<int> {
+ public:
+ // Returns true if test is running with btree enabled.
+ bool UseCrc() const { return GetParam() == 2 || GetParam() == 3; }
+ void MaybeHarden(absl::Cord& c) {
+ if (UseCrc()) {
+ c.SetExpectedChecksum(1);
+ }
+ }
+ absl::Cord MaybeHardened(absl::Cord c) {
+ MaybeHarden(c);
+ return c;
+ }
+
+ // Returns human readable string representation of the test parameter.
+ static std::string ToString(testing::TestParamInfo<int> param) {
+ switch (param.param) {
+ case 0:
+ return "Btree";
+ case 1:
+ return "BtreeHardened";
+ default:
+ assert(false);
+ return "???";
+ }
+ }
+};
+
+INSTANTIATE_TEST_SUITE_P(WithParam, CordTest, testing::Values(0, 1),
+ CordTest::ToString);
+
+TEST(CordRepFlat, AllFlatCapacities) {
+ // Explicitly and redundantly assert built-in min/max limits
+ static_assert(absl::cord_internal::kFlatOverhead < 32, "");
+ static_assert(absl::cord_internal::kMinFlatSize == 32, "");
+ static_assert(absl::cord_internal::kMaxLargeFlatSize == 256 << 10, "");
+ EXPECT_EQ(absl::cord_internal::TagToAllocatedSize(FLAT), 32);
+ EXPECT_EQ(absl::cord_internal::TagToAllocatedSize(MAX_FLAT_TAG), 256 << 10);
+
+ // Verify all tags to map perfectly back and forth, and
+ // that sizes are monotonically increasing.
+ size_t last_size = 0;
+ for (int tag = FLAT; tag <= MAX_FLAT_TAG; ++tag) {
+ size_t size = absl::cord_internal::TagToAllocatedSize(tag);
+ ASSERT_GT(size, last_size);
+ ASSERT_EQ(absl::cord_internal::TagToAllocatedSize(tag), size);
+ last_size = size;
+ }
+
+ // All flat size from 32 - 512 are 8 byte granularity
+ for (size_t size = 32; size <= 512; size += 8) {
+ ASSERT_EQ(absl::cord_internal::RoundUpForTag(size), size);
+ uint8_t tag = absl::cord_internal::AllocatedSizeToTag(size);
+ ASSERT_EQ(absl::cord_internal::TagToAllocatedSize(tag), size);
+ }
+
+ // All flat sizes from 512 - 8192 are 64 byte granularity
+ for (size_t size = 512; size <= 8192; size += 64) {
+ ASSERT_EQ(absl::cord_internal::RoundUpForTag(size), size);
+ uint8_t tag = absl::cord_internal::AllocatedSizeToTag(size);
+ ASSERT_EQ(absl::cord_internal::TagToAllocatedSize(tag), size);
+ }
+
+ // All flat sizes from 8KB to 256KB are 4KB granularity
+ for (size_t size = 8192; size <= 256 * 1024; size += 4 * 1024) {
+ ASSERT_EQ(absl::cord_internal::RoundUpForTag(size), size);
+ uint8_t tag = absl::cord_internal::AllocatedSizeToTag(size);
+ ASSERT_EQ(absl::cord_internal::TagToAllocatedSize(tag), size);
+ }
+}
+
+TEST(CordRepFlat, MaxFlatSize) {
+ CordRepFlat* flat = CordRepFlat::New(kMaxFlatLength);
+ EXPECT_EQ(flat->Capacity(), kMaxFlatLength);
+ CordRep::Unref(flat);
+
+ flat = CordRepFlat::New(kMaxFlatLength * 4);
+ EXPECT_EQ(flat->Capacity(), kMaxFlatLength);
+ CordRep::Unref(flat);
+}
+
+TEST(CordRepFlat, MaxLargeFlatSize) {
+ const size_t size = 256 * 1024 - kFlatOverhead;
+ CordRepFlat* flat = CordRepFlat::New(CordRepFlat::Large(), size);
+ EXPECT_GE(flat->Capacity(), size);
+ CordRep::Unref(flat);
+}
+
+TEST(CordRepFlat, AllFlatSizes) {
+ const size_t kMaxSize = 256 * 1024;
+ for (size_t size = 32; size <= kMaxSize; size *=2) {
+ const size_t length = size - kFlatOverhead - 1;
+ CordRepFlat* flat = CordRepFlat::New(CordRepFlat::Large(), length);
+ EXPECT_GE(flat->Capacity(), length);
+ memset(flat->Data(), 0xCD, flat->Capacity());
+ CordRep::Unref(flat);
+ }
+}
+
+TEST_P(CordTest, AllFlatSizes) {
using absl::strings_internal::CordTestAccess;
for (size_t s = 0; s < CordTestAccess::MaxFlatLength(); s++) {
@@ -217,6 +354,7 @@
}
absl::Cord dst(src);
+ MaybeHarden(dst);
EXPECT_EQ(std::string(dst), src) << s;
}
}
@@ -224,7 +362,7 @@
// We create a Cord at least 128GB in size using the fact that Cords can
// internally reference-count; thus the Cord is enormous without actually
// consuming very much memory.
-TEST(GigabyteCord, FromExternal) {
+TEST_P(CordTest, GigabyteCordFromExternal) {
const size_t one_gig = 1024U * 1024U * 1024U;
size_t max_size = 2 * one_gig;
if (sizeof(max_size) > 4) max_size = 128 * one_gig;
@@ -248,12 +386,13 @@
c.Append(from);
c.Append(from);
c.Append(from);
+ MaybeHarden(c);
}
for (int i = 0; i < 1024; ++i) {
c.Append(from);
}
- ABSL_RAW_LOG(INFO, "Made a Cord with %zu bytes!", c.size());
+ LOG(INFO) << "Made a Cord with " << c.size() << " bytes!";
// Note: on a 32-bit build, this comes out to 2,818,048,000 bytes.
// Note: on a 64-bit build, this comes out to 171,932,385,280 bytes.
}
@@ -273,9 +412,11 @@
extern bool my_unique_true_boolean;
bool my_unique_true_boolean = true;
-TEST(Cord, Assignment) {
+TEST_P(CordTest, Assignment) {
absl::Cord x(absl::string_view("hi there"));
absl::Cord y(x);
+ MaybeHarden(y);
+ ASSERT_EQ(x.ExpectedChecksum(), absl::nullopt);
ASSERT_EQ(std::string(x), "hi there");
ASSERT_EQ(std::string(y), "hi there");
ASSERT_TRUE(x == y);
@@ -327,8 +468,9 @@
}
}
-TEST(Cord, StartsEndsWith) {
+TEST_P(CordTest, StartsEndsWith) {
absl::Cord x(absl::string_view("abcde"));
+ MaybeHarden(x);
absl::Cord empty("");
ASSERT_TRUE(x.StartsWith(absl::Cord("abcde")));
@@ -360,13 +502,101 @@
ASSERT_TRUE(!empty.EndsWith("xyz"));
}
-TEST(Cord, Subcord) {
+TEST_P(CordTest, Contains) {
+ auto flat_haystack = absl::Cord("this is a flat cord");
+ auto fragmented_haystack = absl::MakeFragmentedCord(
+ {"this", " ", "is", " ", "a", " ", "fragmented", " ", "cord"});
+
+ EXPECT_TRUE(flat_haystack.Contains(""));
+ EXPECT_TRUE(fragmented_haystack.Contains(""));
+ EXPECT_TRUE(flat_haystack.Contains(absl::Cord("")));
+ EXPECT_TRUE(fragmented_haystack.Contains(absl::Cord("")));
+ EXPECT_TRUE(absl::Cord("").Contains(""));
+ EXPECT_TRUE(absl::Cord("").Contains(absl::Cord("")));
+ EXPECT_FALSE(absl::Cord("").Contains(flat_haystack));
+ EXPECT_FALSE(absl::Cord("").Contains(fragmented_haystack));
+
+ EXPECT_FALSE(flat_haystack.Contains("z"));
+ EXPECT_FALSE(fragmented_haystack.Contains("z"));
+ EXPECT_FALSE(flat_haystack.Contains(absl::Cord("z")));
+ EXPECT_FALSE(fragmented_haystack.Contains(absl::Cord("z")));
+
+ EXPECT_FALSE(flat_haystack.Contains("is an"));
+ EXPECT_FALSE(fragmented_haystack.Contains("is an"));
+ EXPECT_FALSE(flat_haystack.Contains(absl::Cord("is an")));
+ EXPECT_FALSE(fragmented_haystack.Contains(absl::Cord("is an")));
+ EXPECT_FALSE(
+ flat_haystack.Contains(absl::MakeFragmentedCord({"is", " ", "an"})));
+ EXPECT_FALSE(fragmented_haystack.Contains(
+ absl::MakeFragmentedCord({"is", " ", "an"})));
+
+ EXPECT_TRUE(flat_haystack.Contains("is a"));
+ EXPECT_TRUE(fragmented_haystack.Contains("is a"));
+ EXPECT_TRUE(flat_haystack.Contains(absl::Cord("is a")));
+ EXPECT_TRUE(fragmented_haystack.Contains(absl::Cord("is a")));
+ EXPECT_TRUE(
+ flat_haystack.Contains(absl::MakeFragmentedCord({"is", " ", "a"})));
+ EXPECT_TRUE(
+ fragmented_haystack.Contains(absl::MakeFragmentedCord({"is", " ", "a"})));
+}
+
+TEST_P(CordTest, Find) {
+ auto flat_haystack = absl::Cord("this is a flat cord");
+ auto fragmented_haystack = absl::MakeFragmentedCord(
+ {"this", " ", "is", " ", "a", " ", "fragmented", " ", "cord"});
+ auto empty_haystack = absl::Cord("");
+
+ EXPECT_EQ(flat_haystack.Find(""), flat_haystack.char_begin());
+ EXPECT_EQ(fragmented_haystack.Find(""), fragmented_haystack.char_begin());
+ EXPECT_EQ(flat_haystack.Find(absl::Cord("")), flat_haystack.char_begin());
+ EXPECT_EQ(fragmented_haystack.Find(absl::Cord("")),
+ fragmented_haystack.char_begin());
+ EXPECT_EQ(empty_haystack.Find(""), empty_haystack.char_begin());
+ EXPECT_EQ(empty_haystack.Find(absl::Cord("")), empty_haystack.char_begin());
+ EXPECT_EQ(empty_haystack.Find(flat_haystack), empty_haystack.char_end());
+ EXPECT_EQ(empty_haystack.Find(fragmented_haystack),
+ empty_haystack.char_end());
+
+ EXPECT_EQ(flat_haystack.Find("z"), flat_haystack.char_end());
+ EXPECT_EQ(fragmented_haystack.Find("z"), fragmented_haystack.char_end());
+ EXPECT_EQ(flat_haystack.Find(absl::Cord("z")), flat_haystack.char_end());
+ EXPECT_EQ(fragmented_haystack.Find(absl::Cord("z")),
+ fragmented_haystack.char_end());
+
+ EXPECT_EQ(flat_haystack.Find("is an"), flat_haystack.char_end());
+ EXPECT_EQ(fragmented_haystack.Find("is an"), fragmented_haystack.char_end());
+ EXPECT_EQ(flat_haystack.Find(absl::Cord("is an")), flat_haystack.char_end());
+ EXPECT_EQ(fragmented_haystack.Find(absl::Cord("is an")),
+ fragmented_haystack.char_end());
+ EXPECT_EQ(flat_haystack.Find(absl::MakeFragmentedCord({"is", " ", "an"})),
+ flat_haystack.char_end());
+ EXPECT_EQ(
+ fragmented_haystack.Find(absl::MakeFragmentedCord({"is", " ", "an"})),
+ fragmented_haystack.char_end());
+
+ EXPECT_EQ(flat_haystack.Find("is a"),
+ std::next(flat_haystack.char_begin(), 5));
+ EXPECT_EQ(fragmented_haystack.Find("is a"),
+ std::next(fragmented_haystack.char_begin(), 5));
+ EXPECT_EQ(flat_haystack.Find(absl::Cord("is a")),
+ std::next(flat_haystack.char_begin(), 5));
+ EXPECT_EQ(fragmented_haystack.Find(absl::Cord("is a")),
+ std::next(fragmented_haystack.char_begin(), 5));
+ EXPECT_EQ(flat_haystack.Find(absl::MakeFragmentedCord({"is", " ", "a"})),
+ std::next(flat_haystack.char_begin(), 5));
+ EXPECT_EQ(
+ fragmented_haystack.Find(absl::MakeFragmentedCord({"is", " ", "a"})),
+ std::next(fragmented_haystack.char_begin(), 5));
+}
+
+TEST_P(CordTest, Subcord) {
RandomEngine rng(GTEST_FLAG_GET(random_seed));
const std::string s = RandomLowercaseString(&rng, 1024);
absl::Cord a;
AppendWithFragments(s, &rng, &a);
- ASSERT_EQ(s.size(), a.size());
+ MaybeHarden(a);
+ ASSERT_EQ(s, std::string(a));
// Check subcords of a, from a variety of interesting points.
std::set<size_t> positions;
@@ -387,6 +617,9 @@
ASSERT_EQ(absl::string_view(s).substr(pos, end_pos - pos),
std::string(sa))
<< a;
+ if (pos != 0 || end_pos != a.size()) {
+ ASSERT_EQ(sa.ExpectedChecksum(), absl::nullopt);
+ }
}
}
@@ -421,15 +654,24 @@
EXPECT_TRUE(sa.empty());
}
-TEST(Cord, Swap) {
+TEST_P(CordTest, Swap) {
absl::string_view a("Dexter");
absl::string_view b("Mandark");
absl::Cord x(a);
absl::Cord y(b);
+ MaybeHarden(x);
swap(x, y);
+ if (UseCrc()) {
+ ASSERT_EQ(x.ExpectedChecksum(), absl::nullopt);
+ ASSERT_EQ(y.ExpectedChecksum(), 1);
+ }
ASSERT_EQ(x, absl::Cord(b));
ASSERT_EQ(y, absl::Cord(a));
x.swap(y);
+ if (UseCrc()) {
+ ASSERT_EQ(x.ExpectedChecksum(), 1);
+ ASSERT_EQ(y.ExpectedChecksum(), absl::nullopt);
+ }
ASSERT_EQ(x, absl::Cord(a));
ASSERT_EQ(y, absl::Cord(b));
}
@@ -453,61 +695,368 @@
}
}
-TEST(Cord, CopyToString) {
- VerifyCopyToString(absl::Cord());
- VerifyCopyToString(absl::Cord("small cord"));
- VerifyCopyToString(
+TEST_P(CordTest, CopyToString) {
+ VerifyCopyToString(absl::Cord()); // empty cords cannot carry CRCs
+ VerifyCopyToString(MaybeHardened(absl::Cord("small cord")));
+ VerifyCopyToString(MaybeHardened(
absl::MakeFragmentedCord({"fragmented ", "cord ", "to ", "test ",
- "copying ", "to ", "a ", "string."}));
+ "copying ", "to ", "a ", "string."})));
}
-TEST(TryFlat, Empty) {
+TEST_P(CordTest, AppendEmptyBuffer) {
+ absl::Cord cord;
+ cord.Append(absl::CordBuffer());
+ cord.Append(absl::CordBuffer::CreateWithDefaultLimit(2000));
+}
+
+TEST_P(CordTest, AppendEmptyBufferToFlat) {
+ absl::Cord cord(std::string(2000, 'x'));
+ cord.Append(absl::CordBuffer());
+ cord.Append(absl::CordBuffer::CreateWithDefaultLimit(2000));
+}
+
+TEST_P(CordTest, AppendEmptyBufferToTree) {
+ absl::Cord cord(std::string(2000, 'x'));
+ cord.Append(std::string(2000, 'y'));
+ cord.Append(absl::CordBuffer());
+ cord.Append(absl::CordBuffer::CreateWithDefaultLimit(2000));
+}
+
+TEST_P(CordTest, AppendSmallBuffer) {
+ absl::Cord cord;
+ absl::CordBuffer buffer = absl::CordBuffer::CreateWithDefaultLimit(3);
+ ASSERT_THAT(buffer.capacity(), Le(15));
+ memcpy(buffer.data(), "Abc", 3);
+ buffer.SetLength(3);
+ cord.Append(std::move(buffer));
+ EXPECT_EQ(buffer.length(), 0); // NOLINT
+ EXPECT_GT(buffer.capacity(), 0); // NOLINT
+
+ buffer = absl::CordBuffer::CreateWithDefaultLimit(3);
+ memcpy(buffer.data(), "defgh", 5);
+ buffer.SetLength(5);
+ cord.Append(std::move(buffer));
+ EXPECT_EQ(buffer.length(), 0); // NOLINT
+ EXPECT_GT(buffer.capacity(), 0); // NOLINT
+
+ EXPECT_THAT(cord.Chunks(), ElementsAre("Abcdefgh"));
+}
+
+TEST_P(CordTest, AppendAndPrependBufferArePrecise) {
+ // Create a cord large enough to force 40KB flats.
+ std::string test_data(absl::cord_internal::kMaxFlatLength * 10, 'x');
+ absl::Cord cord1(test_data);
+ absl::Cord cord2(test_data);
+ const size_t size1 = cord1.EstimatedMemoryUsage();
+ const size_t size2 = cord2.EstimatedMemoryUsage();
+
+ absl::CordBuffer buffer = absl::CordBuffer::CreateWithDefaultLimit(3);
+ memcpy(buffer.data(), "Abc", 3);
+ buffer.SetLength(3);
+ cord1.Append(std::move(buffer));
+
+ buffer = absl::CordBuffer::CreateWithDefaultLimit(3);
+ memcpy(buffer.data(), "Abc", 3);
+ buffer.SetLength(3);
+ cord2.Prepend(std::move(buffer));
+
+#ifndef NDEBUG
+ // Allow 32 bytes new CordRepFlat, and 128 bytes for 'glue nodes'
+ constexpr size_t kMaxDelta = 128 + 32;
+#else
+ // Allow 256 bytes extra for 'allocation debug overhead'
+ constexpr size_t kMaxDelta = 128 + 32 + 256;
+#endif
+
+ EXPECT_LE(cord1.EstimatedMemoryUsage() - size1, kMaxDelta);
+ EXPECT_LE(cord2.EstimatedMemoryUsage() - size2, kMaxDelta);
+
+ EXPECT_EQ(cord1, absl::StrCat(test_data, "Abc"));
+ EXPECT_EQ(cord2, absl::StrCat("Abc", test_data));
+}
+
+TEST_P(CordTest, PrependSmallBuffer) {
+ absl::Cord cord;
+ absl::CordBuffer buffer = absl::CordBuffer::CreateWithDefaultLimit(3);
+ ASSERT_THAT(buffer.capacity(), Le(15));
+ memcpy(buffer.data(), "Abc", 3);
+ buffer.SetLength(3);
+ cord.Prepend(std::move(buffer));
+ EXPECT_EQ(buffer.length(), 0); // NOLINT
+ EXPECT_GT(buffer.capacity(), 0); // NOLINT
+
+ buffer = absl::CordBuffer::CreateWithDefaultLimit(3);
+ memcpy(buffer.data(), "defgh", 5);
+ buffer.SetLength(5);
+ cord.Prepend(std::move(buffer));
+ EXPECT_EQ(buffer.length(), 0); // NOLINT
+ EXPECT_GT(buffer.capacity(), 0); // NOLINT
+
+ EXPECT_THAT(cord.Chunks(), ElementsAre("defghAbc"));
+}
+
+TEST_P(CordTest, AppendLargeBuffer) {
+ absl::Cord cord;
+
+ std::string s1(700, '1');
+ absl::CordBuffer buffer = absl::CordBuffer::CreateWithDefaultLimit(s1.size());
+ memcpy(buffer.data(), s1.data(), s1.size());
+ buffer.SetLength(s1.size());
+ cord.Append(std::move(buffer));
+ EXPECT_EQ(buffer.length(), 0); // NOLINT
+ EXPECT_GT(buffer.capacity(), 0); // NOLINT
+
+ std::string s2(1000, '2');
+ buffer = absl::CordBuffer::CreateWithDefaultLimit(s2.size());
+ memcpy(buffer.data(), s2.data(), s2.size());
+ buffer.SetLength(s2.size());
+ cord.Append(std::move(buffer));
+ EXPECT_EQ(buffer.length(), 0); // NOLINT
+ EXPECT_GT(buffer.capacity(), 0); // NOLINT
+
+ EXPECT_THAT(cord.Chunks(), ElementsAre(s1, s2));
+}
+
+TEST_P(CordTest, PrependLargeBuffer) {
+ absl::Cord cord;
+
+ std::string s1(700, '1');
+ absl::CordBuffer buffer = absl::CordBuffer::CreateWithDefaultLimit(s1.size());
+ memcpy(buffer.data(), s1.data(), s1.size());
+ buffer.SetLength(s1.size());
+ cord.Prepend(std::move(buffer));
+ EXPECT_EQ(buffer.length(), 0); // NOLINT
+ EXPECT_GT(buffer.capacity(), 0); // NOLINT
+
+ std::string s2(1000, '2');
+ buffer = absl::CordBuffer::CreateWithDefaultLimit(s2.size());
+ memcpy(buffer.data(), s2.data(), s2.size());
+ buffer.SetLength(s2.size());
+ cord.Prepend(std::move(buffer));
+ EXPECT_EQ(buffer.length(), 0); // NOLINT
+ EXPECT_GT(buffer.capacity(), 0); // NOLINT
+
+ EXPECT_THAT(cord.Chunks(), ElementsAre(s2, s1));
+}
+
+class CordAppendBufferTest : public testing::TestWithParam<bool> {
+ public:
+ size_t is_default() const { return GetParam(); }
+
+ // Returns human readable string representation of the test parameter.
+ static std::string ToString(testing::TestParamInfo<bool> param) {
+ return param.param ? "DefaultLimit" : "CustomLimit";
+ }
+
+ size_t limit() const {
+ return is_default() ? absl::CordBuffer::kDefaultLimit
+ : absl::CordBuffer::kCustomLimit;
+ }
+
+ size_t maximum_payload() const {
+ return is_default() ? absl::CordBuffer::MaximumPayload()
+ : absl::CordBuffer::MaximumPayload(limit());
+ }
+
+ absl::CordBuffer GetAppendBuffer(absl::Cord& cord, size_t capacity,
+ size_t min_capacity = 16) {
+ return is_default()
+ ? cord.GetAppendBuffer(capacity, min_capacity)
+ : cord.GetCustomAppendBuffer(limit(), capacity, min_capacity);
+ }
+};
+
+INSTANTIATE_TEST_SUITE_P(WithParam, CordAppendBufferTest, testing::Bool(),
+ CordAppendBufferTest::ToString);
+
+TEST_P(CordAppendBufferTest, GetAppendBufferOnEmptyCord) {
+ absl::Cord cord;
+ absl::CordBuffer buffer = GetAppendBuffer(cord, 1000);
+ EXPECT_GE(buffer.capacity(), 1000);
+ EXPECT_EQ(buffer.length(), 0);
+}
+
+TEST_P(CordAppendBufferTest, GetAppendBufferOnInlinedCord) {
+ static constexpr int kInlinedSize = sizeof(absl::CordBuffer) - 1;
+ for (int size : {6, kInlinedSize - 3, kInlinedSize - 2, 1000}) {
+ absl::Cord cord("Abc");
+ absl::CordBuffer buffer = GetAppendBuffer(cord, size, 1);
+ EXPECT_GE(buffer.capacity(), 3 + size);
+ EXPECT_EQ(buffer.length(), 3);
+ EXPECT_EQ(absl::string_view(buffer.data(), buffer.length()), "Abc");
+ EXPECT_TRUE(cord.empty());
+ }
+}
+
+TEST_P(CordAppendBufferTest, GetAppendBufferOnInlinedCordCapacityCloseToMax) {
+ // Cover the use case where we have a non empty inlined cord with some size
+ // 'n', and ask for something like 'uint64_max - k', assuming internal logic
+ // could overflow on 'uint64_max - k + size', and return a valid, but
+ // inefficiently smaller buffer if it would provide is the max allowed size.
+ for (size_t dist_from_max = 0; dist_from_max <= 4; ++dist_from_max) {
+ absl::Cord cord("Abc");
+ size_t size = std::numeric_limits<size_t>::max() - dist_from_max;
+ absl::CordBuffer buffer = GetAppendBuffer(cord, size, 1);
+ EXPECT_GE(buffer.capacity(), maximum_payload());
+ EXPECT_EQ(buffer.length(), 3);
+ EXPECT_EQ(absl::string_view(buffer.data(), buffer.length()), "Abc");
+ EXPECT_TRUE(cord.empty());
+ }
+}
+
+TEST_P(CordAppendBufferTest, GetAppendBufferOnFlat) {
+ // Create a cord with a single flat and extra capacity
+ absl::Cord cord;
+ absl::CordBuffer buffer = absl::CordBuffer::CreateWithDefaultLimit(500);
+ const size_t expected_capacity = buffer.capacity();
+ buffer.SetLength(3);
+ memcpy(buffer.data(), "Abc", 3);
+ cord.Append(std::move(buffer));
+
+ buffer = GetAppendBuffer(cord, 6);
+ EXPECT_EQ(buffer.capacity(), expected_capacity);
+ EXPECT_EQ(buffer.length(), 3);
+ EXPECT_EQ(absl::string_view(buffer.data(), buffer.length()), "Abc");
+ EXPECT_TRUE(cord.empty());
+}
+
+TEST_P(CordAppendBufferTest, GetAppendBufferOnFlatWithoutMinCapacity) {
+ // Create a cord with a single flat and extra capacity
+ absl::Cord cord;
+ absl::CordBuffer buffer = absl::CordBuffer::CreateWithDefaultLimit(500);
+ buffer.SetLength(30);
+ memset(buffer.data(), 'x', 30);
+ cord.Append(std::move(buffer));
+
+ buffer = GetAppendBuffer(cord, 1000, 900);
+ EXPECT_GE(buffer.capacity(), 1000);
+ EXPECT_EQ(buffer.length(), 0);
+ EXPECT_EQ(cord, std::string(30, 'x'));
+}
+
+TEST_P(CordAppendBufferTest, GetAppendBufferOnTree) {
+ RandomEngine rng;
+ for (int num_flats : {2, 3, 100}) {
+ // Create a cord with `num_flats` flats and extra capacity
+ absl::Cord cord;
+ std::string prefix;
+ std::string last;
+ for (int i = 0; i < num_flats - 1; ++i) {
+ prefix += last;
+ last = RandomLowercaseString(&rng, 10);
+ absl::CordBuffer buffer = absl::CordBuffer::CreateWithDefaultLimit(500);
+ buffer.SetLength(10);
+ memcpy(buffer.data(), last.data(), 10);
+ cord.Append(std::move(buffer));
+ }
+ absl::CordBuffer buffer = GetAppendBuffer(cord, 6);
+ EXPECT_GE(buffer.capacity(), 500);
+ EXPECT_EQ(buffer.length(), 10);
+ EXPECT_EQ(absl::string_view(buffer.data(), buffer.length()), last);
+ EXPECT_EQ(cord, prefix);
+ }
+}
+
+TEST_P(CordAppendBufferTest, GetAppendBufferOnTreeWithoutMinCapacity) {
+ absl::Cord cord;
+ for (int i = 0; i < 2; ++i) {
+ absl::CordBuffer buffer = absl::CordBuffer::CreateWithDefaultLimit(500);
+ buffer.SetLength(3);
+ memcpy(buffer.data(), i ? "def" : "Abc", 3);
+ cord.Append(std::move(buffer));
+ }
+ absl::CordBuffer buffer = GetAppendBuffer(cord, 1000, 900);
+ EXPECT_GE(buffer.capacity(), 1000);
+ EXPECT_EQ(buffer.length(), 0);
+ EXPECT_EQ(cord, "Abcdef");
+}
+
+TEST_P(CordAppendBufferTest, GetAppendBufferOnSubstring) {
+ // Create a large cord with a single flat and some extra capacity
+ absl::Cord cord;
+ absl::CordBuffer buffer = absl::CordBuffer::CreateWithDefaultLimit(500);
+ buffer.SetLength(450);
+ memset(buffer.data(), 'x', 450);
+ cord.Append(std::move(buffer));
+ cord.RemovePrefix(1);
+
+ // Deny on substring
+ buffer = GetAppendBuffer(cord, 6);
+ EXPECT_EQ(buffer.length(), 0);
+ EXPECT_EQ(cord, std::string(449, 'x'));
+}
+
+TEST_P(CordAppendBufferTest, GetAppendBufferOnSharedCord) {
+ // Create a shared cord with a single flat and extra capacity
+ absl::Cord cord;
+ absl::CordBuffer buffer = absl::CordBuffer::CreateWithDefaultLimit(500);
+ buffer.SetLength(3);
+ memcpy(buffer.data(), "Abc", 3);
+ cord.Append(std::move(buffer));
+ absl::Cord shared_cord = cord;
+
+ // Deny on flat
+ buffer = GetAppendBuffer(cord, 6);
+ EXPECT_EQ(buffer.length(), 0);
+ EXPECT_EQ(cord, "Abc");
+
+ buffer = absl::CordBuffer::CreateWithDefaultLimit(500);
+ buffer.SetLength(3);
+ memcpy(buffer.data(), "def", 3);
+ cord.Append(std::move(buffer));
+ shared_cord = cord;
+
+ // Deny on tree
+ buffer = GetAppendBuffer(cord, 6);
+ EXPECT_EQ(buffer.length(), 0);
+ EXPECT_EQ(cord, "Abcdef");
+}
+
+TEST_P(CordTest, TryFlatEmpty) {
absl::Cord c;
EXPECT_EQ(c.TryFlat(), "");
}
-TEST(TryFlat, Flat) {
+TEST_P(CordTest, TryFlatFlat) {
absl::Cord c("hello");
+ MaybeHarden(c);
EXPECT_EQ(c.TryFlat(), "hello");
}
-TEST(TryFlat, SubstrInlined) {
+TEST_P(CordTest, TryFlatSubstrInlined) {
absl::Cord c("hello");
c.RemovePrefix(1);
+ MaybeHarden(c);
EXPECT_EQ(c.TryFlat(), "ello");
}
-TEST(TryFlat, SubstrFlat) {
+TEST_P(CordTest, TryFlatSubstrFlat) {
absl::Cord c("longer than 15 bytes");
absl::Cord sub = absl::CordTestPeer::MakeSubstring(c, 1, c.size() - 1);
+ MaybeHarden(sub);
EXPECT_EQ(sub.TryFlat(), "onger than 15 bytes");
}
-TEST(TryFlat, Concat) {
+TEST_P(CordTest, TryFlatConcat) {
absl::Cord c = absl::MakeFragmentedCord({"hel", "lo"});
+ MaybeHarden(c);
EXPECT_EQ(c.TryFlat(), absl::nullopt);
}
-TEST(TryFlat, External) {
+TEST_P(CordTest, TryFlatExternal) {
absl::Cord c = absl::MakeCordFromExternal("hell", [](absl::string_view) {});
+ MaybeHarden(c);
EXPECT_EQ(c.TryFlat(), "hell");
}
-TEST(TryFlat, SubstrExternal) {
+TEST_P(CordTest, TryFlatSubstrExternal) {
absl::Cord c = absl::MakeCordFromExternal("hell", [](absl::string_view) {});
absl::Cord sub = absl::CordTestPeer::MakeSubstring(c, 1, c.size() - 1);
+ MaybeHarden(sub);
EXPECT_EQ(sub.TryFlat(), "ell");
}
-TEST(TryFlat, SubstrConcat) {
- absl::Cord c = absl::MakeFragmentedCord({"hello", " world"});
- absl::Cord sub = absl::CordTestPeer::MakeSubstring(c, 1, c.size() - 1);
- EXPECT_EQ(sub.TryFlat(), absl::nullopt);
- c.RemovePrefix(1);
- EXPECT_EQ(c.TryFlat(), absl::nullopt);
-}
-
-TEST(TryFlat, CommonlyAssumedInvariants) {
+TEST_P(CordTest, TryFlatCommonlyAssumedInvariants) {
// The behavior tested below is not part of the API contract of Cord, but it's
// something we intend to be true in our current implementation. This test
// exists to detect and prevent accidental breakage of the implementation.
@@ -521,6 +1070,7 @@
"returned by the ",
"iterator"};
absl::Cord c = absl::MakeFragmentedCord(fragments);
+ MaybeHarden(c);
int fragment = 0;
int offset = 0;
absl::Cord::CharIterator itc = c.char_begin();
@@ -563,15 +1113,17 @@
EXPECT_TRUE(IsFlat(c));
}
-TEST(Cord, Flatten) {
+TEST_P(CordTest, Flatten) {
VerifyFlatten(absl::Cord());
- VerifyFlatten(absl::Cord("small cord"));
- VerifyFlatten(absl::Cord("larger than small buffer optimization"));
- VerifyFlatten(absl::MakeFragmentedCord({"small ", "fragmented ", "cord"}));
+ VerifyFlatten(MaybeHardened(absl::Cord("small cord")));
+ VerifyFlatten(
+ MaybeHardened(absl::Cord("larger than small buffer optimization")));
+ VerifyFlatten(MaybeHardened(
+ absl::MakeFragmentedCord({"small ", "fragmented ", "cord"})));
// Test with a cord that is longer than the largest flat buffer
RandomEngine rng(GTEST_FLAG_GET(random_seed));
- VerifyFlatten(absl::Cord(RandomLowercaseString(&rng, 8192)));
+ VerifyFlatten(MaybeHardened(absl::Cord(RandomLowercaseString(&rng, 8192))));
}
// Test data
@@ -617,7 +1169,7 @@
};
} // namespace
-TEST(Cord, MultipleLengths) {
+TEST_P(CordTest, MultipleLengths) {
TestData d;
for (size_t i = 0; i < d.size(); i++) {
std::string a = d.data(i);
@@ -625,22 +1177,26 @@
{ // Construct from Cord
absl::Cord tmp(a);
absl::Cord x(tmp);
+ MaybeHarden(x);
EXPECT_EQ(a, std::string(x)) << "'" << a << "'";
}
{ // Construct from absl::string_view
absl::Cord x(a);
+ MaybeHarden(x);
EXPECT_EQ(a, std::string(x)) << "'" << a << "'";
}
{ // Append cord to self
absl::Cord self(a);
+ MaybeHarden(self);
self.Append(self);
EXPECT_EQ(a + a, std::string(self)) << "'" << a << "' + '" << a << "'";
}
{ // Prepend cord to self
absl::Cord self(a);
+ MaybeHarden(self);
self.Prepend(self);
EXPECT_EQ(a + a, std::string(self)) << "'" << a << "' + '" << a << "'";
}
@@ -652,12 +1208,14 @@
{ // CopyFrom Cord
absl::Cord x(a);
absl::Cord y(b);
+ MaybeHarden(x);
x = y;
EXPECT_EQ(b, std::string(x)) << "'" << a << "' + '" << b << "'";
}
{ // CopyFrom absl::string_view
absl::Cord x(a);
+ MaybeHarden(x);
x = b;
EXPECT_EQ(b, std::string(x)) << "'" << a << "' + '" << b << "'";
}
@@ -665,12 +1223,14 @@
{ // Cord::Append(Cord)
absl::Cord x(a);
absl::Cord y(b);
+ MaybeHarden(x);
x.Append(y);
EXPECT_EQ(a + b, std::string(x)) << "'" << a << "' + '" << b << "'";
}
{ // Cord::Append(absl::string_view)
absl::Cord x(a);
+ MaybeHarden(x);
x.Append(b);
EXPECT_EQ(a + b, std::string(x)) << "'" << a << "' + '" << b << "'";
}
@@ -678,12 +1238,14 @@
{ // Cord::Prepend(Cord)
absl::Cord x(a);
absl::Cord y(b);
+ MaybeHarden(x);
x.Prepend(y);
EXPECT_EQ(b + a, std::string(x)) << "'" << b << "' + '" << a << "'";
}
{ // Cord::Prepend(absl::string_view)
absl::Cord x(a);
+ MaybeHarden(x);
x.Prepend(b);
EXPECT_EQ(b + a, std::string(x)) << "'" << b << "' + '" << a << "'";
}
@@ -693,25 +1255,29 @@
namespace {
-TEST(Cord, RemoveSuffixWithExternalOrSubstring) {
+TEST_P(CordTest, RemoveSuffixWithExternalOrSubstring) {
absl::Cord cord = absl::MakeCordFromExternal(
"foo bar baz", [](absl::string_view s) { DoNothing(s, nullptr); });
-
EXPECT_EQ("foo bar baz", std::string(cord));
+ MaybeHarden(cord);
+
// This RemoveSuffix() will wrap the EXTERNAL node in a SUBSTRING node.
cord.RemoveSuffix(4);
EXPECT_EQ("foo bar", std::string(cord));
+ MaybeHarden(cord);
+
// This RemoveSuffix() will adjust the SUBSTRING node in-place.
cord.RemoveSuffix(4);
EXPECT_EQ("foo", std::string(cord));
}
-TEST(Cord, RemoveSuffixMakesZeroLengthNode) {
+TEST_P(CordTest, RemoveSuffixMakesZeroLengthNode) {
absl::Cord c;
c.Append(absl::Cord(std::string(100, 'x')));
absl::Cord other_ref = c; // Prevent inplace appends
+ MaybeHarden(c);
c.Append(absl::Cord(std::string(200, 'y')));
c.RemoveSuffix(200);
EXPECT_EQ(std::string(100, 'x'), std::string(c));
@@ -735,24 +1301,27 @@
}
// Establish that ZedBlock does what we think it does.
-TEST(CordSpliceTest, ZedBlock) {
+TEST_P(CordTest, CordSpliceTestZedBlock) {
absl::Cord blob = CordWithZedBlock(10);
+ MaybeHarden(blob);
EXPECT_EQ(10, blob.size());
std::string s;
absl::CopyCordToString(blob, &s);
EXPECT_EQ("zzzzzzzzzz", s);
}
-TEST(CordSpliceTest, ZedBlock0) {
+TEST_P(CordTest, CordSpliceTestZedBlock0) {
absl::Cord blob = CordWithZedBlock(0);
+ MaybeHarden(blob);
EXPECT_EQ(0, blob.size());
std::string s;
absl::CopyCordToString(blob, &s);
EXPECT_EQ("", s);
}
-TEST(CordSpliceTest, ZedBlockSuffix1) {
+TEST_P(CordTest, CordSpliceTestZedBlockSuffix1) {
absl::Cord blob = CordWithZedBlock(10);
+ MaybeHarden(blob);
EXPECT_EQ(10, blob.size());
absl::Cord suffix(blob);
suffix.RemovePrefix(9);
@@ -763,8 +1332,9 @@
}
// Remove all of a prefix block
-TEST(CordSpliceTest, ZedBlockSuffix0) {
+TEST_P(CordTest, CordSpliceTestZedBlockSuffix0) {
absl::Cord blob = CordWithZedBlock(10);
+ MaybeHarden(blob);
EXPECT_EQ(10, blob.size());
absl::Cord suffix(blob);
suffix.RemovePrefix(10);
@@ -782,29 +1352,31 @@
// Splice block into cord.
absl::Cord SpliceCord(const absl::Cord& blob, int64_t offset,
const absl::Cord& block) {
- ABSL_RAW_CHECK(offset >= 0, "");
- ABSL_RAW_CHECK(offset + block.size() <= blob.size(), "");
+ CHECK_GE(offset, 0);
+ CHECK_LE(static_cast<size_t>(offset) + block.size(), blob.size());
absl::Cord result(blob);
result.RemoveSuffix(blob.size() - offset);
result.Append(block);
absl::Cord suffix(blob);
suffix.RemovePrefix(offset + block.size());
result.Append(suffix);
- ABSL_RAW_CHECK(blob.size() == result.size(), "");
+ CHECK_EQ(blob.size(), result.size());
return result;
}
// Taking an empty suffix of a block breaks appending.
-TEST(CordSpliceTest, RemoveEntireBlock1) {
+TEST_P(CordTest, CordSpliceTestRemoveEntireBlock1) {
absl::Cord zero = CordWithZedBlock(10);
+ MaybeHarden(zero);
absl::Cord suffix(zero);
suffix.RemovePrefix(10);
absl::Cord result;
result.Append(suffix);
}
-TEST(CordSpliceTest, RemoveEntireBlock2) {
+TEST_P(CordTest, CordSpliceTestRemoveEntireBlock2) {
absl::Cord zero = CordWithZedBlock(10);
+ MaybeHarden(zero);
absl::Cord prefix(zero);
prefix.RemoveSuffix(10);
absl::Cord suffix(zero);
@@ -813,16 +1385,22 @@
result.Append(suffix);
}
-TEST(CordSpliceTest, RemoveEntireBlock3) {
+TEST_P(CordTest, CordSpliceTestRemoveEntireBlock3) {
absl::Cord blob = CordWithZedBlock(10);
absl::Cord block = BigCord(10, 'b');
+ MaybeHarden(blob);
+ MaybeHarden(block);
blob = SpliceCord(blob, 0, block);
}
struct CordCompareTestCase {
template <typename LHS, typename RHS>
- CordCompareTestCase(const LHS& lhs, const RHS& rhs)
- : lhs_cord(lhs), rhs_cord(rhs) {}
+ CordCompareTestCase(const LHS& lhs, const RHS& rhs, bool use_crc)
+ : lhs_cord(lhs), rhs_cord(rhs) {
+ if (use_crc) {
+ lhs_cord.SetExpectedChecksum(1);
+ }
+ }
absl::Cord lhs_cord;
absl::Cord rhs_cord;
@@ -844,7 +1422,7 @@
<< "LHS=" << rhs_string << "; RHS=" << lhs_string;
}
-TEST(Cord, Compare) {
+TEST_P(CordTest, Compare) {
absl::Cord subcord("aaaaaBBBBBcccccDDDDD");
subcord = subcord.Subcord(3, 10);
@@ -859,47 +1437,54 @@
concat2.Append("cccccccccccDDDDDDDDDDDDDD");
concat2.Append("DD");
+ const bool use_crc = UseCrc();
+
std::vector<CordCompareTestCase> test_cases = {{
// Inline cords
- {"abcdef", "abcdef"},
- {"abcdef", "abcdee"},
- {"abcdef", "abcdeg"},
- {"bbcdef", "abcdef"},
- {"bbcdef", "abcdeg"},
- {"abcdefa", "abcdef"},
- {"abcdef", "abcdefa"},
+ {"abcdef", "abcdef", use_crc},
+ {"abcdef", "abcdee", use_crc},
+ {"abcdef", "abcdeg", use_crc},
+ {"bbcdef", "abcdef", use_crc},
+ {"bbcdef", "abcdeg", use_crc},
+ {"abcdefa", "abcdef", use_crc},
+ {"abcdef", "abcdefa", use_crc},
// Small flat cords
- {"aaaaaBBBBBcccccDDDDD", "aaaaaBBBBBcccccDDDDD"},
- {"aaaaaBBBBBcccccDDDDD", "aaaaaBBBBBxccccDDDDD"},
- {"aaaaaBBBBBcxcccDDDDD", "aaaaaBBBBBcccccDDDDD"},
- {"aaaaaBBBBBxccccDDDDD", "aaaaaBBBBBcccccDDDDX"},
- {"aaaaaBBBBBcccccDDDDDa", "aaaaaBBBBBcccccDDDDD"},
- {"aaaaaBBBBBcccccDDDDD", "aaaaaBBBBBcccccDDDDDa"},
+ {"aaaaaBBBBBcccccDDDDD", "aaaaaBBBBBcccccDDDDD", use_crc},
+ {"aaaaaBBBBBcccccDDDDD", "aaaaaBBBBBxccccDDDDD", use_crc},
+ {"aaaaaBBBBBcxcccDDDDD", "aaaaaBBBBBcccccDDDDD", use_crc},
+ {"aaaaaBBBBBxccccDDDDD", "aaaaaBBBBBcccccDDDDX", use_crc},
+ {"aaaaaBBBBBcccccDDDDDa", "aaaaaBBBBBcccccDDDDD", use_crc},
+ {"aaaaaBBBBBcccccDDDDD", "aaaaaBBBBBcccccDDDDDa", use_crc},
// Subcords
- {subcord, subcord},
- {subcord, "aaBBBBBccc"},
- {subcord, "aaBBBBBccd"},
- {subcord, "aaBBBBBccb"},
- {subcord, "aaBBBBBxcb"},
- {subcord, "aaBBBBBccca"},
- {subcord, "aaBBBBBcc"},
+ {subcord, subcord, use_crc},
+ {subcord, "aaBBBBBccc", use_crc},
+ {subcord, "aaBBBBBccd", use_crc},
+ {subcord, "aaBBBBBccb", use_crc},
+ {subcord, "aaBBBBBxcb", use_crc},
+ {subcord, "aaBBBBBccca", use_crc},
+ {subcord, "aaBBBBBcc", use_crc},
// Concats
- {concat, concat},
+ {concat, concat, use_crc},
{concat,
- "aaaaaaaaaaaaaaaaBBBBBBBBBBBBBBBBccccccccccccccccDDDDDDDDDDDDDDDD"},
+ "aaaaaaaaaaaaaaaaBBBBBBBBBBBBBBBBccccccccccccccccDDDDDDDDDDDDDDDD",
+ use_crc},
{concat,
- "aaaaaaaaaaaaaaaaBBBBBBBBBBBBBBBBcccccccccccccccxDDDDDDDDDDDDDDDD"},
+ "aaaaaaaaaaaaaaaaBBBBBBBBBBBBBBBBcccccccccccccccxDDDDDDDDDDDDDDDD",
+ use_crc},
{concat,
- "aaaaaaaaaaaaaaaaBBBBBBBBBBBBBBBBacccccccccccccccDDDDDDDDDDDDDDDD"},
+ "aaaaaaaaaaaaaaaaBBBBBBBBBBBBBBBBacccccccccccccccDDDDDDDDDDDDDDDD",
+ use_crc},
{concat,
- "aaaaaaaaaaaaaaaaBBBBBBBBBBBBBBBBccccccccccccccccDDDDDDDDDDDDDDD"},
+ "aaaaaaaaaaaaaaaaBBBBBBBBBBBBBBBBccccccccccccccccDDDDDDDDDDDDDDD",
+ use_crc},
{concat,
- "aaaaaaaaaaaaaaaaBBBBBBBBBBBBBBBBccccccccccccccccDDDDDDDDDDDDDDDDe"},
+ "aaaaaaaaaaaaaaaaBBBBBBBBBBBBBBBBccccccccccccccccDDDDDDDDDDDDDDDDe",
+ use_crc},
- {concat, concat2},
+ {concat, concat2, use_crc},
}};
for (const auto& tc : test_cases) {
@@ -907,9 +1492,10 @@
}
}
-TEST(Cord, CompareAfterAssign) {
+TEST_P(CordTest, CompareAfterAssign) {
absl::Cord a("aaaaaa1111111");
absl::Cord b("aaaaaa2222222");
+ MaybeHarden(a);
a = "cccccc";
b = "cccccc";
EXPECT_EQ(a, b);
@@ -927,16 +1513,15 @@
// comparison methods from basic_string.
static void TestCompare(const absl::Cord& c, const absl::Cord& d,
RandomEngine* rng) {
- typedef std::basic_string<uint8_t> ustring;
- ustring cs(reinterpret_cast<const uint8_t*>(std::string(c).data()), c.size());
- ustring ds(reinterpret_cast<const uint8_t*>(std::string(d).data()), d.size());
- // ustring comparison is ideal because we expect Cord comparisons to be
- // based on unsigned byte comparisons regardless of whether char is signed.
- int expected = sign(cs.compare(ds));
+ // char_traits<char>::lt is guaranteed to do an unsigned comparison:
+ // https://en.cppreference.com/w/cpp/string/char_traits/cmp. We also expect
+ // Cord comparisons to be based on unsigned byte comparisons regardless of
+ // whether char is signed.
+ int expected = sign(std::string(c).compare(std::string(d)));
EXPECT_EQ(expected, sign(c.Compare(d))) << c << ", " << d;
}
-TEST(Compare, ComparisonIsUnsigned) {
+TEST_P(CordTest, CompareComparisonIsUnsigned) {
RandomEngine rng(GTEST_FLAG_GET(random_seed));
std::uniform_int_distribution<uint32_t> uniform_uint8(0, 255);
char x = static_cast<char>(uniform_uint8(rng));
@@ -945,7 +1530,7 @@
absl::Cord(std::string(GetUniformRandomUpTo(&rng, 100), x ^ 0x80)), &rng);
}
-TEST(Compare, RandomComparisons) {
+TEST_P(CordTest, CompareRandomComparisons) {
const int kIters = 5000;
RandomEngine rng(GTEST_FLAG_GET(random_seed));
@@ -968,6 +1553,8 @@
d.Append(a[GetUniformRandomUpTo(&rng, ABSL_ARRAYSIZE(a))]);
}
std::bernoulli_distribution coin_flip(0.5);
+ MaybeHarden(c);
+ MaybeHarden(d);
TestCompare(coin_flip(rng) ? c : absl::Cord(std::string(c)),
coin_flip(rng) ? d : absl::Cord(std::string(d)), &rng);
}
@@ -1003,43 +1590,43 @@
EXPECT_FALSE(b <= a);
}
-TEST(ComparisonOperators, Cord_Cord) {
+TEST_P(CordTest, ComparisonOperators_Cord_Cord) {
CompareOperators<absl::Cord, absl::Cord>();
}
-TEST(ComparisonOperators, Cord_StringPiece) {
+TEST_P(CordTest, ComparisonOperators_Cord_StringPiece) {
CompareOperators<absl::Cord, absl::string_view>();
}
-TEST(ComparisonOperators, StringPiece_Cord) {
+TEST_P(CordTest, ComparisonOperators_StringPiece_Cord) {
CompareOperators<absl::string_view, absl::Cord>();
}
-TEST(ComparisonOperators, Cord_string) {
+TEST_P(CordTest, ComparisonOperators_Cord_string) {
CompareOperators<absl::Cord, std::string>();
}
-TEST(ComparisonOperators, string_Cord) {
+TEST_P(CordTest, ComparisonOperators_string_Cord) {
CompareOperators<std::string, absl::Cord>();
}
-TEST(ComparisonOperators, stdstring_Cord) {
+TEST_P(CordTest, ComparisonOperators_stdstring_Cord) {
CompareOperators<std::string, absl::Cord>();
}
-TEST(ComparisonOperators, Cord_stdstring) {
+TEST_P(CordTest, ComparisonOperators_Cord_stdstring) {
CompareOperators<absl::Cord, std::string>();
}
-TEST(ComparisonOperators, charstar_Cord) {
+TEST_P(CordTest, ComparisonOperators_charstar_Cord) {
CompareOperators<const char*, absl::Cord>();
}
-TEST(ComparisonOperators, Cord_charstar) {
+TEST_P(CordTest, ComparisonOperators_Cord_charstar) {
CompareOperators<absl::Cord, const char*>();
}
-TEST(ConstructFromExternal, ReleaserInvoked) {
+TEST_P(CordTest, ConstructFromExternalReleaserInvoked) {
// Empty external memory means the releaser should be called immediately.
{
bool invoked = false;
@@ -1081,7 +1668,7 @@
}
}
-TEST(ConstructFromExternal, CompareContents) {
+TEST_P(CordTest, ConstructFromExternalCompareContents) {
RandomEngine rng(GTEST_FLAG_GET(random_seed));
for (int length = 1; length <= 2048; length *= 2) {
@@ -1093,11 +1680,12 @@
EXPECT_EQ(external->size(), sv.size());
delete external;
});
+ MaybeHarden(cord);
EXPECT_EQ(data, cord);
}
}
-TEST(ConstructFromExternal, LargeReleaser) {
+TEST_P(CordTest, ConstructFromExternalLargeReleaser) {
RandomEngine rng(GTEST_FLAG_GET(random_seed));
constexpr size_t kLength = 256;
std::string data = RandomLowercaseString(&rng, kLength);
@@ -1108,11 +1696,11 @@
EXPECT_EQ(data, absl::string_view(data_array.data(), data_array.size()));
invoked = true;
};
- (void)absl::MakeCordFromExternal(data, releaser);
+ (void)MaybeHardened(absl::MakeCordFromExternal(data, releaser));
EXPECT_TRUE(invoked);
}
-TEST(ConstructFromExternal, FunctionPointerReleaser) {
+TEST_P(CordTest, ConstructFromExternalFunctionPointerReleaser) {
static absl::string_view data("hello world");
static bool invoked;
auto* releaser =
@@ -1121,15 +1709,15 @@
invoked = true;
});
invoked = false;
- (void)absl::MakeCordFromExternal(data, releaser);
+ (void)MaybeHardened(absl::MakeCordFromExternal(data, releaser));
EXPECT_TRUE(invoked);
invoked = false;
- (void)absl::MakeCordFromExternal(data, *releaser);
+ (void)MaybeHardened(absl::MakeCordFromExternal(data, *releaser));
EXPECT_TRUE(invoked);
}
-TEST(ConstructFromExternal, MoveOnlyReleaser) {
+TEST_P(CordTest, ConstructFromExternalMoveOnlyReleaser) {
struct Releaser {
explicit Releaser(bool* invoked) : invoked(invoked) {}
Releaser(Releaser&& other) noexcept : invoked(other.invoked) {}
@@ -1139,24 +1727,25 @@
};
bool invoked = false;
- (void)absl::MakeCordFromExternal("dummy", Releaser(&invoked));
+ (void)MaybeHardened(absl::MakeCordFromExternal("dummy", Releaser(&invoked)));
EXPECT_TRUE(invoked);
}
-TEST(ConstructFromExternal, NoArgLambda) {
+TEST_P(CordTest, ConstructFromExternalNoArgLambda) {
bool invoked = false;
- (void)absl::MakeCordFromExternal("dummy", [&invoked]() { invoked = true; });
+ (void)MaybeHardened(
+ absl::MakeCordFromExternal("dummy", [&invoked]() { invoked = true; }));
EXPECT_TRUE(invoked);
}
-TEST(ConstructFromExternal, StringViewArgLambda) {
+TEST_P(CordTest, ConstructFromExternalStringViewArgLambda) {
bool invoked = false;
- (void)absl::MakeCordFromExternal(
- "dummy", [&invoked](absl::string_view) { invoked = true; });
+ (void)MaybeHardened(absl::MakeCordFromExternal(
+ "dummy", [&invoked](absl::string_view) { invoked = true; }));
EXPECT_TRUE(invoked);
}
-TEST(ConstructFromExternal, NonTrivialReleaserDestructor) {
+TEST_P(CordTest, ConstructFromExternalNonTrivialReleaserDestructor) {
struct Releaser {
explicit Releaser(bool* destroyed) : destroyed(destroyed) {}
~Releaser() { *destroyed = true; }
@@ -1167,57 +1756,94 @@
bool destroyed = false;
Releaser releaser(&destroyed);
- (void)absl::MakeCordFromExternal("dummy", releaser);
+ (void)MaybeHardened(absl::MakeCordFromExternal("dummy", releaser));
EXPECT_TRUE(destroyed);
}
-TEST(ConstructFromExternal, ReferenceQualifierOverloads) {
- struct Releaser {
- void operator()(absl::string_view) & { *lvalue_invoked = true; }
- void operator()(absl::string_view) && { *rvalue_invoked = true; }
+TEST_P(CordTest, ConstructFromExternalReferenceQualifierOverloads) {
+ enum InvokedAs { kMissing, kLValue, kRValue };
+ enum CopiedAs { kNone, kMove, kCopy };
+ struct Tracker {
+ CopiedAs copied_as = kNone;
+ InvokedAs invoked_as = kMissing;
- bool* lvalue_invoked;
- bool* rvalue_invoked;
+ void Record(InvokedAs rhs) {
+ ASSERT_EQ(invoked_as, kMissing);
+ invoked_as = rhs;
+ }
+
+ void Record(CopiedAs rhs) {
+ if (copied_as == kNone || rhs == kCopy) copied_as = rhs;
+ }
+ } tracker;
+
+ class Releaser {
+ public:
+ explicit Releaser(Tracker* tracker) : tr_(tracker) { *tracker = Tracker(); }
+ Releaser(Releaser&& rhs) : tr_(rhs.tr_) { tr_->Record(kMove); }
+ Releaser(const Releaser& rhs) : tr_(rhs.tr_) { tr_->Record(kCopy); }
+
+ void operator()(absl::string_view) & { tr_->Record(kLValue); }
+ void operator()(absl::string_view) && { tr_->Record(kRValue); }
+
+ private:
+ Tracker* tr_;
};
- bool lvalue_invoked = false;
- bool rvalue_invoked = false;
- Releaser releaser = {&lvalue_invoked, &rvalue_invoked};
- (void)absl::MakeCordFromExternal("", releaser);
- EXPECT_FALSE(lvalue_invoked);
- EXPECT_TRUE(rvalue_invoked);
- rvalue_invoked = false;
+ const Releaser releaser1(&tracker);
+ (void)MaybeHardened(absl::MakeCordFromExternal("", releaser1));
+ EXPECT_EQ(tracker.copied_as, kCopy);
+ EXPECT_EQ(tracker.invoked_as, kRValue);
- (void)absl::MakeCordFromExternal("dummy", releaser);
- EXPECT_FALSE(lvalue_invoked);
- EXPECT_TRUE(rvalue_invoked);
- rvalue_invoked = false;
+ const Releaser releaser2(&tracker);
+ (void)MaybeHardened(absl::MakeCordFromExternal("", releaser2));
+ EXPECT_EQ(tracker.copied_as, kCopy);
+ EXPECT_EQ(tracker.invoked_as, kRValue);
- // NOLINTNEXTLINE: suppress clang-tidy std::move on trivially copyable type.
- (void)absl::MakeCordFromExternal("dummy", std::move(releaser));
- EXPECT_FALSE(lvalue_invoked);
- EXPECT_TRUE(rvalue_invoked);
+ Releaser releaser3(&tracker);
+ (void)MaybeHardened(absl::MakeCordFromExternal("", std::move(releaser3)));
+ EXPECT_EQ(tracker.copied_as, kMove);
+ EXPECT_EQ(tracker.invoked_as, kRValue);
+
+ Releaser releaser4(&tracker);
+ (void)MaybeHardened(absl::MakeCordFromExternal("dummy", releaser4));
+ EXPECT_EQ(tracker.copied_as, kCopy);
+ EXPECT_EQ(tracker.invoked_as, kRValue);
+
+ const Releaser releaser5(&tracker);
+ (void)MaybeHardened(absl::MakeCordFromExternal("dummy", releaser5));
+ EXPECT_EQ(tracker.copied_as, kCopy);
+ EXPECT_EQ(tracker.invoked_as, kRValue);
+
+ Releaser releaser6(&tracker);
+ (void)MaybeHardened(absl::MakeCordFromExternal("foo", std::move(releaser6)));
+ EXPECT_EQ(tracker.copied_as, kMove);
+ EXPECT_EQ(tracker.invoked_as, kRValue);
}
-TEST(ExternalMemory, BasicUsage) {
+TEST_P(CordTest, ExternalMemoryBasicUsage) {
static const char* strings[] = {"", "hello", "there"};
for (const char* str : strings) {
absl::Cord dst("(prefix)");
+ MaybeHarden(dst);
AddExternalMemory(str, &dst);
+ MaybeHarden(dst);
dst.Append("(suffix)");
EXPECT_EQ((std::string("(prefix)") + str + std::string("(suffix)")),
std::string(dst));
}
}
-TEST(ExternalMemory, RemovePrefixSuffix) {
+TEST_P(CordTest, ExternalMemoryRemovePrefixSuffix) {
// Exhaustively try all sub-strings.
absl::Cord cord = MakeComposite();
std::string s = std::string(cord);
for (int offset = 0; offset <= s.size(); offset++) {
for (int length = 0; length <= s.size() - offset; length++) {
absl::Cord result(cord);
+ MaybeHarden(result);
result.RemovePrefix(offset);
+ MaybeHarden(result);
result.RemoveSuffix(result.size() - length);
EXPECT_EQ(s.substr(offset, length), std::string(result))
<< offset << " " << length;
@@ -1225,11 +1851,13 @@
}
}
-TEST(ExternalMemory, Get) {
+TEST_P(CordTest, ExternalMemoryGet) {
absl::Cord cord("hello");
AddExternalMemory(" world!", &cord);
+ MaybeHarden(cord);
AddExternalMemory(" how are ", &cord);
cord.Append(" you?");
+ MaybeHarden(cord);
std::string s = std::string(cord);
for (int i = 0; i < s.size(); i++) {
EXPECT_EQ(s[i], cord[i]);
@@ -1237,58 +1865,177 @@
}
// CordMemoryUsage tests verify the correctness of the EstimatedMemoryUsage()
-// These tests take into account that the reported memory usage is approximate
-// and non-deterministic. For all tests, We verify that the reported memory
-// usage is larger than `size()`, and less than `size() * 1.5` as a cord should
-// never reserve more 'extra' capacity than half of its size as it grows.
-// Additionally we have some whiteboxed expectations based on our knowledge of
-// the layout and size of empty and inlined cords, and flat nodes.
+// We use whiteboxed expectations based on our knowledge of the layout and size
+// of empty and inlined cords, and flat nodes.
-TEST(CordMemoryUsage, Empty) {
- EXPECT_EQ(sizeof(absl::Cord), absl::Cord().EstimatedMemoryUsage());
+constexpr auto kFairShare = absl::CordMemoryAccounting::kFairShare;
+constexpr auto kTotalMorePrecise =
+ absl::CordMemoryAccounting::kTotalMorePrecise;
+
+// Creates a cord of `n` `c` values, making sure no string stealing occurs.
+absl::Cord MakeCord(size_t n, char c) {
+ const std::string s(n, c);
+ return absl::Cord(s);
}
-TEST(CordMemoryUsage, Embedded) {
+TEST(CordTest, CordMemoryUsageEmpty) {
+ absl::Cord cord;
+ EXPECT_EQ(sizeof(absl::Cord), cord.EstimatedMemoryUsage());
+ EXPECT_EQ(sizeof(absl::Cord), cord.EstimatedMemoryUsage(kFairShare));
+ EXPECT_EQ(sizeof(absl::Cord), cord.EstimatedMemoryUsage(kTotalMorePrecise));
+}
+
+TEST(CordTest, CordMemoryUsageInlined) {
absl::Cord a("hello");
EXPECT_EQ(a.EstimatedMemoryUsage(), sizeof(absl::Cord));
+ EXPECT_EQ(a.EstimatedMemoryUsage(kFairShare), sizeof(absl::Cord));
+ EXPECT_EQ(a.EstimatedMemoryUsage(kTotalMorePrecise), sizeof(absl::Cord));
}
-TEST(CordMemoryUsage, EmbeddedAppend) {
- absl::Cord a("a");
- absl::Cord b("bcd");
- EXPECT_EQ(b.EstimatedMemoryUsage(), sizeof(absl::Cord));
- a.Append(b);
- EXPECT_EQ(a.EstimatedMemoryUsage(), sizeof(absl::Cord));
-}
-
-TEST(CordMemoryUsage, ExternalMemory) {
- static const int kLength = 1000;
+TEST(CordTest, CordMemoryUsageExternalMemory) {
absl::Cord cord;
- AddExternalMemory(std::string(kLength, 'x'), &cord);
- EXPECT_GT(cord.EstimatedMemoryUsage(), kLength);
- EXPECT_LE(cord.EstimatedMemoryUsage(), kLength * 1.5);
+ AddExternalMemory(std::string(1000, 'x'), &cord);
+ const size_t expected =
+ sizeof(absl::Cord) + 1000 + sizeof(CordRepExternal) + sizeof(intptr_t);
+ EXPECT_EQ(cord.EstimatedMemoryUsage(), expected);
+ EXPECT_EQ(cord.EstimatedMemoryUsage(kFairShare), expected);
+ EXPECT_EQ(cord.EstimatedMemoryUsage(kTotalMorePrecise), expected);
}
-TEST(CordMemoryUsage, Flat) {
- static const int kLength = 125;
- absl::Cord a(std::string(kLength, 'a'));
- EXPECT_GT(a.EstimatedMemoryUsage(), kLength);
- EXPECT_LE(a.EstimatedMemoryUsage(), kLength * 1.5);
+TEST(CordTest, CordMemoryUsageFlat) {
+ absl::Cord cord = MakeCord(1000, 'a');
+ const size_t flat_size =
+ absl::CordTestPeer::Tree(cord)->flat()->AllocatedSize();
+ EXPECT_EQ(cord.EstimatedMemoryUsage(), sizeof(absl::Cord) + flat_size);
+ EXPECT_EQ(cord.EstimatedMemoryUsage(kFairShare),
+ sizeof(absl::Cord) + flat_size);
+ EXPECT_EQ(cord.EstimatedMemoryUsage(kTotalMorePrecise),
+ sizeof(absl::Cord) + flat_size);
}
-TEST(CordMemoryUsage, AppendFlat) {
- using absl::strings_internal::CordTestAccess;
- absl::Cord a(std::string(CordTestAccess::MaxFlatLength(), 'a'));
- size_t length = a.EstimatedMemoryUsage();
- a.Append(std::string(CordTestAccess::MaxFlatLength(), 'b'));
- size_t delta = a.EstimatedMemoryUsage() - length;
- EXPECT_GT(delta, CordTestAccess::MaxFlatLength());
- EXPECT_LE(delta, CordTestAccess::MaxFlatLength() * 1.5);
+TEST(CordTest, CordMemoryUsageSubStringSharedFlat) {
+ absl::Cord flat = MakeCord(2000, 'a');
+ const size_t flat_size =
+ absl::CordTestPeer::Tree(flat)->flat()->AllocatedSize();
+ absl::Cord cord = flat.Subcord(500, 1000);
+ EXPECT_EQ(cord.EstimatedMemoryUsage(),
+ sizeof(absl::Cord) + sizeof(CordRepSubstring) + flat_size);
+ EXPECT_EQ(cord.EstimatedMemoryUsage(kTotalMorePrecise),
+ sizeof(absl::Cord) + sizeof(CordRepSubstring) + flat_size);
+ EXPECT_EQ(cord.EstimatedMemoryUsage(kFairShare),
+ sizeof(absl::Cord) + sizeof(CordRepSubstring) + flat_size / 2);
+}
+
+TEST(CordTest, CordMemoryUsageFlatShared) {
+ absl::Cord shared = MakeCord(1000, 'a');
+ absl::Cord cord(shared);
+ const size_t flat_size =
+ absl::CordTestPeer::Tree(cord)->flat()->AllocatedSize();
+ EXPECT_EQ(cord.EstimatedMemoryUsage(), sizeof(absl::Cord) + flat_size);
+ EXPECT_EQ(cord.EstimatedMemoryUsage(kTotalMorePrecise),
+ sizeof(absl::Cord) + flat_size);
+ EXPECT_EQ(cord.EstimatedMemoryUsage(kFairShare),
+ sizeof(absl::Cord) + flat_size / 2);
+}
+
+TEST(CordTest, CordMemoryUsageFlatHardenedAndShared) {
+ absl::Cord shared = MakeCord(1000, 'a');
+ absl::Cord cord(shared);
+ const size_t flat_size =
+ absl::CordTestPeer::Tree(cord)->flat()->AllocatedSize();
+ cord.SetExpectedChecksum(1);
+ EXPECT_EQ(cord.EstimatedMemoryUsage(),
+ sizeof(absl::Cord) + sizeof(CordRepCrc) + flat_size);
+ EXPECT_EQ(cord.EstimatedMemoryUsage(kFairShare),
+ sizeof(absl::Cord) + sizeof(CordRepCrc) + flat_size / 2);
+
+ absl::Cord cord2(cord);
+ EXPECT_EQ(cord2.EstimatedMemoryUsage(),
+ sizeof(absl::Cord) + sizeof(CordRepCrc) + flat_size);
+ EXPECT_EQ(cord2.EstimatedMemoryUsage(kTotalMorePrecise),
+ sizeof(absl::Cord) + sizeof(CordRepCrc) + flat_size);
+ EXPECT_EQ(cord2.EstimatedMemoryUsage(kFairShare),
+ sizeof(absl::Cord) + (sizeof(CordRepCrc) + flat_size / 2) / 2);
+}
+
+TEST(CordTest, CordMemoryUsageBTree) {
+ absl::Cord cord1;
+ size_t flats1_size = 0;
+ absl::Cord flats1[4] = {MakeCord(1000, 'a'), MakeCord(1100, 'a'),
+ MakeCord(1200, 'a'), MakeCord(1300, 'a')};
+ for (absl::Cord flat : flats1) {
+ flats1_size += absl::CordTestPeer::Tree(flat)->flat()->AllocatedSize();
+ cord1.Append(std::move(flat));
+ }
+
+ // Make sure the created cord is a BTREE tree. Under some builds such as
+ // windows DLL, we may have ODR like effects on the flag, meaning the DLL
+ // code will run with the picked up default.
+ if (!absl::CordTestPeer::Tree(cord1)->IsBtree()) {
+ LOG(WARNING) << "Cord library code not respecting btree flag";
+ return;
+ }
+
+ size_t rep1_size = sizeof(CordRepBtree) + flats1_size;
+ size_t rep1_shared_size = sizeof(CordRepBtree) + flats1_size / 2;
+
+ EXPECT_EQ(cord1.EstimatedMemoryUsage(), sizeof(absl::Cord) + rep1_size);
+ EXPECT_EQ(cord1.EstimatedMemoryUsage(kTotalMorePrecise),
+ sizeof(absl::Cord) + rep1_size);
+ EXPECT_EQ(cord1.EstimatedMemoryUsage(kFairShare),
+ sizeof(absl::Cord) + rep1_shared_size);
+
+ absl::Cord cord2;
+ size_t flats2_size = 0;
+ absl::Cord flats2[4] = {MakeCord(600, 'a'), MakeCord(700, 'a'),
+ MakeCord(800, 'a'), MakeCord(900, 'a')};
+ for (absl::Cord& flat : flats2) {
+ flats2_size += absl::CordTestPeer::Tree(flat)->flat()->AllocatedSize();
+ cord2.Append(std::move(flat));
+ }
+ size_t rep2_size = sizeof(CordRepBtree) + flats2_size;
+
+ EXPECT_EQ(cord2.EstimatedMemoryUsage(), sizeof(absl::Cord) + rep2_size);
+ EXPECT_EQ(cord2.EstimatedMemoryUsage(kTotalMorePrecise),
+ sizeof(absl::Cord) + rep2_size);
+ EXPECT_EQ(cord2.EstimatedMemoryUsage(kFairShare),
+ sizeof(absl::Cord) + rep2_size);
+
+ absl::Cord cord(cord1);
+ cord.Append(std::move(cord2));
+
+ EXPECT_EQ(cord.EstimatedMemoryUsage(),
+ sizeof(absl::Cord) + sizeof(CordRepBtree) + rep1_size + rep2_size);
+ EXPECT_EQ(cord.EstimatedMemoryUsage(kTotalMorePrecise),
+ sizeof(absl::Cord) + sizeof(CordRepBtree) + rep1_size + rep2_size);
+ EXPECT_EQ(cord.EstimatedMemoryUsage(kFairShare),
+ sizeof(absl::Cord) + sizeof(CordRepBtree) + rep1_shared_size / 2 +
+ rep2_size);
+}
+
+TEST(CordTest, TestHashFragmentation) {
+ // Make sure we hit these boundary cases precisely.
+ EXPECT_EQ(1024, absl::hash_internal::PiecewiseChunkSize());
+ EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly({
+ absl::Cord(),
+ absl::MakeFragmentedCord({std::string(600, 'a'), std::string(600, 'a')}),
+ absl::MakeFragmentedCord({std::string(1200, 'a')}),
+ absl::MakeFragmentedCord({std::string(900, 'b'), std::string(900, 'b')}),
+ absl::MakeFragmentedCord({std::string(1800, 'b')}),
+ absl::MakeFragmentedCord(
+ {std::string(2000, 'c'), std::string(2000, 'c')}),
+ absl::MakeFragmentedCord({std::string(4000, 'c')}),
+ absl::MakeFragmentedCord({std::string(1024, 'd')}),
+ absl::MakeFragmentedCord({std::string(1023, 'd'), "d"}),
+ absl::MakeFragmentedCord({std::string(1025, 'e')}),
+ absl::MakeFragmentedCord({std::string(1024, 'e'), "e"}),
+ absl::MakeFragmentedCord({std::string(1023, 'e'), "e", "e"}),
+ }));
}
// Regtest for a change that had to be rolled back because it expanded out
// of the InlineRep too soon, which was observable through MemoryUsage().
-TEST(CordMemoryUsage, InlineRep) {
+TEST_P(CordTest, CordMemoryUsageInlineRep) {
constexpr size_t kMaxInline = 15; // Cord::InlineRep::N
const std::string small_string(kMaxInline, 'x');
absl::Cord c1(small_string);
@@ -1299,17 +2046,79 @@
EXPECT_EQ(c1.EstimatedMemoryUsage(), c2.EstimatedMemoryUsage());
}
+TEST_P(CordTest, CordMemoryUsageTotalMorePreciseMode) {
+ constexpr size_t kChunkSize = 2000;
+ std::string tmp_str(kChunkSize, 'x');
+ const absl::Cord flat(std::move(tmp_str));
+
+ // Construct `fragmented` with two references into the same
+ // underlying buffer shared with `flat`:
+ absl::Cord fragmented(flat);
+ fragmented.Append(flat);
+
+ // Memory usage of `flat`, minus the top-level Cord object:
+ const size_t flat_internal_usage =
+ flat.EstimatedMemoryUsage() - sizeof(absl::Cord);
+
+ // `fragmented` holds a Cord and a CordRepBtree. That tree points to two
+ // copies of flat's internals, which we expect to dedup:
+ EXPECT_EQ(fragmented.EstimatedMemoryUsage(kTotalMorePrecise),
+ sizeof(absl::Cord) +
+ sizeof(CordRepBtree) +
+ flat_internal_usage);
+
+ // This is a case where kTotal produces an overestimate:
+ EXPECT_EQ(fragmented.EstimatedMemoryUsage(),
+ sizeof(absl::Cord) +
+ sizeof(CordRepBtree) +
+ 2 * flat_internal_usage);
+}
+
+TEST_P(CordTest, CordMemoryUsageTotalMorePreciseModeWithSubstring) {
+ constexpr size_t kChunkSize = 2000;
+ std::string tmp_str(kChunkSize, 'x');
+ const absl::Cord flat(std::move(tmp_str));
+
+ // Construct `fragmented` with two references into the same
+ // underlying buffer shared with `flat`.
+ //
+ // This time, each reference is through a Subcord():
+ absl::Cord fragmented;
+ fragmented.Append(flat.Subcord(1, kChunkSize - 2));
+ fragmented.Append(flat.Subcord(1, kChunkSize - 2));
+
+ // Memory usage of `flat`, minus the top-level Cord object:
+ const size_t flat_internal_usage =
+ flat.EstimatedMemoryUsage() - sizeof(absl::Cord);
+
+ // `fragmented` holds a Cord and a CordRepBtree. That tree points to two
+ // CordRepSubstrings, each pointing at flat's internals.
+ EXPECT_EQ(fragmented.EstimatedMemoryUsage(kTotalMorePrecise),
+ sizeof(absl::Cord) +
+ sizeof(CordRepBtree) +
+ 2 * sizeof(CordRepSubstring) +
+ flat_internal_usage);
+
+ // This is a case where kTotal produces an overestimate:
+ EXPECT_EQ(fragmented.EstimatedMemoryUsage(),
+ sizeof(absl::Cord) +
+ sizeof(CordRepBtree) +
+ 2 * sizeof(CordRepSubstring) +
+ 2 * flat_internal_usage);
+}
} // namespace
// Regtest for 7510292 (fix a bug introduced by 7465150)
-TEST(Cord, Concat_Append) {
+TEST_P(CordTest, Concat_Append) {
// Create a rep of type CONCAT
absl::Cord s1("foobarbarbarbarbar");
+ MaybeHarden(s1);
s1.Append("abcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefg");
size_t size = s1.size();
// Create a copy of s1 and append to it.
absl::Cord s2 = s1;
+ MaybeHarden(s2);
s2.Append("x");
// 7465150 modifies s1 when it shouldn't.
@@ -1317,30 +2126,94 @@
EXPECT_EQ(s2.size(), size + 1);
}
-TEST(Cord, DiabolicalGrowth) {
+TEST_P(CordTest, DiabolicalGrowth) {
// This test exercises a diabolical Append(<one char>) on a cord, making the
// cord shared before each Append call resulting in a terribly fragmented
// resulting cord.
- // TODO(b/183983616): Apply some minimum compaction when copying a shared
- // source cord into a mutable copy for updates in CordRepRing.
RandomEngine rng(GTEST_FLAG_GET(random_seed));
const std::string expected = RandomLowercaseString(&rng, 5000);
absl::Cord cord;
for (char c : expected) {
absl::Cord shared(cord);
cord.Append(absl::string_view(&c, 1));
+ MaybeHarden(cord);
}
std::string value;
absl::CopyCordToString(cord, &value);
EXPECT_EQ(value, expected);
- ABSL_RAW_LOG(INFO, "Diabolical size allocated = %zu",
- cord.EstimatedMemoryUsage());
+ LOG(INFO) << "Diabolical size allocated = " << cord.EstimatedMemoryUsage();
}
-TEST(MakeFragmentedCord, MakeFragmentedCordFromInitializerList) {
+// The following tests check support for >4GB cords in 64-bit binaries, and
+// 2GB-4GB cords in 32-bit binaries. This function returns the large cord size
+// that's appropriate for the binary.
+
+// Construct a huge cord with the specified valid prefix.
+static absl::Cord MakeHuge(absl::string_view prefix) {
+ absl::Cord cord;
+ if (sizeof(size_t) > 4) {
+ // In 64-bit binaries, test 64-bit Cord support.
+ const size_t size =
+ static_cast<size_t>(std::numeric_limits<uint32_t>::max()) + 314;
+ cord.Append(absl::MakeCordFromExternal(
+ absl::string_view(prefix.data(), size),
+ [](absl::string_view s) { DoNothing(s, nullptr); }));
+ } else {
+ // Cords are limited to 32-bit lengths in 32-bit binaries. The following
+ // tests check for use of "signed int" to represent Cord length/offset.
+ // However absl::string_view does not allow lengths >= (1u<<31), so we need
+ // to append in two parts;
+ const size_t s1 = (1u << 31) - 1;
+ // For shorter cord, `Append` copies the data rather than allocating a new
+ // node. The threshold is currently set to 511, so `s2` needs to be bigger
+ // to not trigger the copy.
+ const size_t s2 = 600;
+ cord.Append(absl::MakeCordFromExternal(
+ absl::string_view(prefix.data(), s1),
+ [](absl::string_view s) { DoNothing(s, nullptr); }));
+ cord.Append(absl::MakeCordFromExternal(
+ absl::string_view("", s2),
+ [](absl::string_view s) { DoNothing(s, nullptr); }));
+ }
+ return cord;
+}
+
+TEST_P(CordTest, HugeCord) {
+ absl::Cord cord = MakeHuge("huge cord");
+ MaybeHarden(cord);
+
+ const size_t acceptable_delta =
+ 100 + (UseCrc() ? sizeof(absl::cord_internal::CordRepCrc) : 0);
+ EXPECT_LE(cord.size(), cord.EstimatedMemoryUsage());
+ EXPECT_GE(cord.size() + acceptable_delta, cord.EstimatedMemoryUsage());
+}
+
+// Tests that Append() works ok when handed a self reference
+TEST_P(CordTest, AppendSelf) {
+ // Test the empty case.
+ absl::Cord empty;
+ MaybeHarden(empty);
+ empty.Append(empty);
+ ASSERT_EQ(empty, "");
+
+ // We run the test until data is ~16K
+ // This guarantees it covers small, medium and large data.
+ std::string control_data = "Abc";
+ absl::Cord data(control_data);
+ while (control_data.length() < 0x4000) {
+ MaybeHarden(data);
+ data.Append(data);
+ control_data.append(control_data);
+ ASSERT_EQ(control_data, data);
+ }
+}
+
+TEST_P(CordTest, MakeFragmentedCordFromInitializerList) {
absl::Cord fragmented =
absl::MakeFragmentedCord({"A ", "fragmented ", "Cord"});
+ MaybeHarden(fragmented);
+
EXPECT_EQ("A fragmented Cord", fragmented);
auto chunk_it = fragmented.chunk_begin();
@@ -1357,10 +2230,12 @@
ASSERT_TRUE(++chunk_it == fragmented.chunk_end());
}
-TEST(MakeFragmentedCord, MakeFragmentedCordFromVector) {
+TEST_P(CordTest, MakeFragmentedCordFromVector) {
std::vector<absl::string_view> chunks = {"A ", "fragmented ", "Cord"};
absl::Cord fragmented = absl::MakeFragmentedCord(chunks);
+ MaybeHarden(fragmented);
+
EXPECT_EQ("A fragmented Cord", fragmented);
auto chunk_it = fragmented.chunk_begin();
@@ -1377,7 +2252,7 @@
ASSERT_TRUE(++chunk_it == fragmented.chunk_end());
}
-TEST(CordChunkIterator, Traits) {
+TEST_P(CordTest, CordChunkIteratorTraits) {
static_assert(std::is_copy_constructible<absl::Cord::ChunkIterator>::value,
"");
static_assert(std::is_copy_assignable<absl::Cord::ChunkIterator>::value, "");
@@ -1458,27 +2333,31 @@
EXPECT_TRUE(post_iter == cord.chunk_end()); // NOLINT
}
-TEST(CordChunkIterator, Operations) {
+TEST_P(CordTest, CordChunkIteratorOperations) {
absl::Cord empty_cord;
VerifyChunkIterator(empty_cord, 0);
absl::Cord small_buffer_cord("small cord");
+ MaybeHarden(small_buffer_cord);
VerifyChunkIterator(small_buffer_cord, 1);
absl::Cord flat_node_cord("larger than small buffer optimization");
+ MaybeHarden(flat_node_cord);
VerifyChunkIterator(flat_node_cord, 1);
- VerifyChunkIterator(
- absl::MakeFragmentedCord({"a ", "small ", "fragmented ", "cord ", "for ",
- "testing ", "chunk ", "iterations."}),
- 8);
+ VerifyChunkIterator(MaybeHardened(absl::MakeFragmentedCord(
+ {"a ", "small ", "fragmented ", "cord ", "for ",
+ "testing ", "chunk ", "iterations."})),
+ 8);
absl::Cord reused_nodes_cord(std::string(40, 'c'));
reused_nodes_cord.Prepend(absl::Cord(std::string(40, 'b')));
+ MaybeHarden(reused_nodes_cord);
reused_nodes_cord.Prepend(absl::Cord(std::string(40, 'a')));
size_t expected_chunks = 3;
for (int i = 0; i < 8; ++i) {
reused_nodes_cord.Prepend(reused_nodes_cord);
+ MaybeHarden(reused_nodes_cord);
expected_chunks *= 2;
VerifyChunkIterator(reused_nodes_cord, expected_chunks);
}
@@ -1490,7 +2369,79 @@
VerifyChunkIterator(subcords, 128);
}
-TEST(CordCharIterator, Traits) {
+
+TEST_P(CordTest, AdvanceAndReadOnDataEdge) {
+ RandomEngine rng(GTEST_FLAG_GET(random_seed));
+ const std::string data = RandomLowercaseString(&rng, 2000);
+ for (bool as_flat : {true, false}) {
+ SCOPED_TRACE(as_flat ? "Flat" : "External");
+
+ absl::Cord cord =
+ as_flat ? absl::Cord(data)
+ : absl::MakeCordFromExternal(data, [](absl::string_view) {});
+ auto it = cord.Chars().begin();
+#if !defined(NDEBUG) || ABSL_OPTION_HARDENED
+ EXPECT_DEATH_IF_SUPPORTED(cord.AdvanceAndRead(&it, 2001), ".*");
+#endif
+
+ it = cord.Chars().begin();
+ absl::Cord frag = cord.AdvanceAndRead(&it, 2000);
+ EXPECT_EQ(frag, data);
+ EXPECT_TRUE(it == cord.Chars().end());
+
+ it = cord.Chars().begin();
+ frag = cord.AdvanceAndRead(&it, 200);
+ EXPECT_EQ(frag, data.substr(0, 200));
+ EXPECT_FALSE(it == cord.Chars().end());
+
+ frag = cord.AdvanceAndRead(&it, 1500);
+ EXPECT_EQ(frag, data.substr(200, 1500));
+ EXPECT_FALSE(it == cord.Chars().end());
+
+ frag = cord.AdvanceAndRead(&it, 300);
+ EXPECT_EQ(frag, data.substr(1700, 300));
+ EXPECT_TRUE(it == cord.Chars().end());
+ }
+}
+
+TEST_P(CordTest, AdvanceAndReadOnSubstringDataEdge) {
+ RandomEngine rng(GTEST_FLAG_GET(random_seed));
+ const std::string data = RandomLowercaseString(&rng, 2500);
+ for (bool as_flat : {true, false}) {
+ SCOPED_TRACE(as_flat ? "Flat" : "External");
+
+ absl::Cord cord =
+ as_flat ? absl::Cord(data)
+ : absl::MakeCordFromExternal(data, [](absl::string_view) {});
+ cord = cord.Subcord(200, 2000);
+ const std::string substr = data.substr(200, 2000);
+
+ auto it = cord.Chars().begin();
+#if !defined(NDEBUG) || ABSL_OPTION_HARDENED
+ EXPECT_DEATH_IF_SUPPORTED(cord.AdvanceAndRead(&it, 2001), ".*");
+#endif
+
+ it = cord.Chars().begin();
+ absl::Cord frag = cord.AdvanceAndRead(&it, 2000);
+ EXPECT_EQ(frag, substr);
+ EXPECT_TRUE(it == cord.Chars().end());
+
+ it = cord.Chars().begin();
+ frag = cord.AdvanceAndRead(&it, 200);
+ EXPECT_EQ(frag, substr.substr(0, 200));
+ EXPECT_FALSE(it == cord.Chars().end());
+
+ frag = cord.AdvanceAndRead(&it, 1500);
+ EXPECT_EQ(frag, substr.substr(200, 1500));
+ EXPECT_FALSE(it == cord.Chars().end());
+
+ frag = cord.AdvanceAndRead(&it, 300);
+ EXPECT_EQ(frag, substr.substr(1700, 300));
+ EXPECT_TRUE(it == cord.Chars().end());
+ }
+}
+
+TEST_P(CordTest, CharIteratorTraits) {
static_assert(std::is_copy_constructible<absl::Cord::CharIterator>::value,
"");
static_assert(std::is_copy_assignable<absl::Cord::CharIterator>::value, "");
@@ -1599,44 +2550,88 @@
}
}
-TEST(CordCharIterator, Operations) {
+TEST_P(CordTest, CharIteratorOperations) {
absl::Cord empty_cord;
VerifyCharIterator(empty_cord);
absl::Cord small_buffer_cord("small cord");
+ MaybeHarden(small_buffer_cord);
VerifyCharIterator(small_buffer_cord);
absl::Cord flat_node_cord("larger than small buffer optimization");
+ MaybeHarden(flat_node_cord);
VerifyCharIterator(flat_node_cord);
- VerifyCharIterator(
+ VerifyCharIterator(MaybeHardened(
absl::MakeFragmentedCord({"a ", "small ", "fragmented ", "cord ", "for ",
- "testing ", "character ", "iteration."}));
+ "testing ", "character ", "iteration."})));
absl::Cord reused_nodes_cord("ghi");
reused_nodes_cord.Prepend(absl::Cord("def"));
reused_nodes_cord.Prepend(absl::Cord("abc"));
for (int i = 0; i < 4; ++i) {
reused_nodes_cord.Prepend(reused_nodes_cord);
+ MaybeHarden(reused_nodes_cord);
VerifyCharIterator(reused_nodes_cord);
}
RandomEngine rng(GTEST_FLAG_GET(random_seed));
absl::Cord flat_cord(RandomLowercaseString(&rng, 256));
absl::Cord subcords;
- for (int i = 0; i < 4; ++i) subcords.Prepend(flat_cord.Subcord(16 * i, 128));
+ for (int i = 0; i < 4; ++i) {
+ subcords.Prepend(flat_cord.Subcord(16 * i, 128));
+ MaybeHarden(subcords);
+ }
VerifyCharIterator(subcords);
}
-TEST(Cord, StreamingOutput) {
+TEST_P(CordTest, CharIteratorAdvanceAndRead) {
+ // Create a Cord holding 6 flats of 2500 bytes each, and then iterate over it
+ // reading 150, 1500, 2500 and 3000 bytes. This will result in all possible
+ // partial, full and straddled read combinations including reads below
+ // kMaxBytesToCopy. b/197776822 surfaced a bug for a specific partial, small
+ // read 'at end' on Cord which caused a failure on attempting to read past the
+ // end in CordRepBtreeReader which was not covered by any existing test.
+ constexpr int kBlocks = 6;
+ constexpr size_t kBlockSize = 2500;
+ constexpr size_t kChunkSize1 = 1500;
+ constexpr size_t kChunkSize2 = 2500;
+ constexpr size_t kChunkSize3 = 3000;
+ constexpr size_t kChunkSize4 = 150;
+ RandomEngine rng;
+ std::string data = RandomLowercaseString(&rng, kBlocks * kBlockSize);
+ absl::Cord cord;
+ for (int i = 0; i < kBlocks; ++i) {
+ const std::string block = data.substr(i * kBlockSize, kBlockSize);
+ cord.Append(absl::Cord(block));
+ }
+
+ MaybeHarden(cord);
+
+ for (size_t chunk_size :
+ {kChunkSize1, kChunkSize2, kChunkSize3, kChunkSize4}) {
+ absl::Cord::CharIterator it = cord.char_begin();
+ size_t offset = 0;
+ while (offset < data.length()) {
+ const size_t n = std::min<size_t>(data.length() - offset, chunk_size);
+ absl::Cord chunk = cord.AdvanceAndRead(&it, n);
+ ASSERT_EQ(chunk.size(), n);
+ ASSERT_EQ(chunk.Compare(data.substr(offset, n)), 0);
+ offset += n;
+ }
+ }
+}
+
+TEST_P(CordTest, StreamingOutput) {
absl::Cord c =
absl::MakeFragmentedCord({"A ", "small ", "fragmented ", "Cord", "."});
+ MaybeHarden(c);
std::stringstream output;
output << c;
EXPECT_EQ("A small fragmented Cord.", output.str());
}
-TEST(Cord, ForEachChunk) {
+TEST_P(CordTest, ForEachChunk) {
for (int num_elements : {1, 10, 200}) {
SCOPED_TRACE(num_elements);
std::vector<std::string> cord_chunks;
@@ -1644,6 +2639,7 @@
cord_chunks.push_back(absl::StrCat("[", i, "]"));
}
absl::Cord c = absl::MakeFragmentedCord(cord_chunks);
+ MaybeHarden(c);
std::vector<std::string> iterated_chunks;
absl::CordTestPeer::ForEachChunk(c,
@@ -1654,13 +2650,14 @@
}
}
-TEST(Cord, SmallBufferAssignFromOwnData) {
+TEST_P(CordTest, SmallBufferAssignFromOwnData) {
constexpr size_t kMaxInline = 15;
std::string contents = "small buff cord";
EXPECT_EQ(contents.size(), kMaxInline);
for (size_t pos = 0; pos < contents.size(); ++pos) {
for (size_t count = contents.size() - pos; count > 0; --count) {
absl::Cord c(contents);
+ MaybeHarden(c);
absl::string_view flat = c.Flatten();
c = flat.substr(pos, count);
EXPECT_EQ(c, contents.substr(pos, count))
@@ -1669,16 +2666,27 @@
}
}
-TEST(Cord, Format) {
+TEST_P(CordTest, Format) {
absl::Cord c;
absl::Format(&c, "There were %04d little %s.", 3, "pigs");
EXPECT_EQ(c, "There were 0003 little pigs.");
+ MaybeHarden(c);
absl::Format(&c, "And %-3llx bad wolf!", 1);
+ MaybeHarden(c);
EXPECT_EQ(c, "There were 0003 little pigs.And 1 bad wolf!");
}
-TEST(CordDeathTest, Hardening) {
+TEST_P(CordTest, Stringify) {
+ absl::Cord c =
+ absl::MakeFragmentedCord({"A ", "small ", "fragmented ", "Cord", "."});
+ MaybeHarden(c);
+ EXPECT_EQ(absl::StrCat(c), "A small fragmented Cord.");
+}
+
+TEST_P(CordTest, Hardening) {
absl::Cord cord("hello");
+ MaybeHarden(cord);
+
// These statement should abort the program in all builds modes.
EXPECT_DEATH_IF_SUPPORTED(cord.RemovePrefix(6), "");
EXPECT_DEATH_IF_SUPPORTED(cord.RemoveSuffix(6), "");
@@ -1697,6 +2705,49 @@
EXPECT_DEATH_IF_SUPPORTED(++cord.chunk_end(), "");
}
+// This test mimics a specific (and rare) application repeatedly splitting a
+// cord, inserting (overwriting) a string value, and composing a new cord from
+// the three pieces. This is hostile towards a Btree implementation: A split of
+// a node at any level is likely to have the right-most edge of the left split,
+// and the left-most edge of the right split shared. For example, splitting a
+// leaf node with 6 edges will result likely in a 1-6, 2-5, 3-4, etc. split,
+// sharing the 'split node'. When recomposing such nodes, we 'injected' an edge
+// in that node. As this happens with some probability on each level of the
+// tree, this will quickly grow the tree until it reaches maximum height.
+TEST_P(CordTest, BtreeHostileSplitInsertJoin) {
+ absl::BitGen bitgen;
+
+ // Start with about 1GB of data
+ std::string data(1 << 10, 'x');
+ absl::Cord buffer(data);
+ absl::Cord cord;
+ for (int i = 0; i < 1000000; ++i) {
+ cord.Append(buffer);
+ }
+
+ for (int j = 0; j < 1000; ++j) {
+ MaybeHarden(cord);
+ size_t offset = absl::Uniform(bitgen, 0u, cord.size());
+ size_t length = absl::Uniform(bitgen, 100u, data.size());
+ if (cord.size() == offset) {
+ cord.Append(absl::string_view(data.data(), length));
+ } else {
+ absl::Cord suffix;
+ if (offset + length < cord.size()) {
+ suffix = cord;
+ suffix.RemovePrefix(offset + length);
+ }
+ if (cord.size() > offset) {
+ cord.RemoveSuffix(cord.size() - offset);
+ }
+ cord.Append(absl::string_view(data.data(), length));
+ if (!suffix.empty()) {
+ cord.Append(suffix);
+ }
+ }
+ }
+}
+
class AfterExitCordTester {
public:
bool Set(absl::Cord* cord, absl::string_view expected) {
@@ -1713,12 +2764,34 @@
absl::string_view expected_;
};
+// Deliberately prevents the destructor for an absl::Cord from running. The cord
+// is accessible via the cord member during the lifetime of the CordLeaker.
+// After the CordLeaker is destroyed, pointers to the cord will remain valid
+// until the CordLeaker's memory is deallocated.
+struct CordLeaker {
+ union {
+ absl::Cord cord;
+ };
+
+ template <typename Str>
+ constexpr explicit CordLeaker(const Str& str) : cord(str) {}
+
+ ~CordLeaker() {
+ // Don't do anything, including running cord's destructor. (cord's
+ // destructor won't run automatically because cord is hidden inside a
+ // union.)
+ }
+};
+
template <typename Str>
void TestConstinitConstructor(Str) {
const auto expected = Str::value;
// Defined before `cord` to be destroyed after it.
static AfterExitCordTester exit_tester; // NOLINT
- ABSL_CONST_INIT static absl::Cord cord(Str{}); // NOLINT
+ ABSL_CONST_INIT static CordLeaker cord_leaker(Str{}); // NOLINT
+ // cord_leaker is static, so this reference will remain valid through the end
+ // of program execution.
+ static absl::Cord& cord = cord_leaker.cord;
static bool init_exit_tester = exit_tester.Set(&cord, expected);
(void)init_exit_tester;
@@ -1770,9 +2843,498 @@
};
-TEST(Cord, ConstinitConstructor) {
+TEST_P(CordTest, ConstinitConstructor) {
TestConstinitConstructor(
absl::strings_internal::MakeStringConstant(ShortView{}));
TestConstinitConstructor(
absl::strings_internal::MakeStringConstant(LongView{}));
}
+
+namespace {
+
+// Test helper that generates a populated cord for future manipulation.
+//
+// By test convention, all generated cords begin with the characters "abcde" at
+// the start of the first chunk.
+class PopulatedCordFactory {
+ public:
+ constexpr PopulatedCordFactory(absl::string_view name,
+ absl::Cord (*generator)())
+ : name_(name), generator_(generator) {}
+
+ absl::string_view Name() const { return name_; }
+ absl::Cord Generate() const { return generator_(); }
+
+ private:
+ absl::string_view name_;
+ absl::Cord (*generator_)();
+};
+
+// clang-format off
+// This array is constant-initialized in conformant compilers.
+PopulatedCordFactory cord_factories[] = {
+ {"sso", [] { return absl::Cord("abcde"); }},
+ {"flat", [] {
+ // Too large to live in SSO space, but small enough to be a simple FLAT.
+ absl::Cord flat(absl::StrCat("abcde", std::string(1000, 'x')));
+ flat.Flatten();
+ return flat;
+ }},
+ {"external", [] {
+ // A cheat: we are using a string literal as the external storage, so a
+ // no-op releaser is correct here.
+ return absl::MakeCordFromExternal("abcde External!", []{});
+ }},
+ {"external substring", [] {
+ // A cheat: we are using a string literal as the external storage, so a
+ // no-op releaser is correct here.
+ absl::Cord ext = absl::MakeCordFromExternal("-abcde External!", []{});
+ return absl::CordTestPeer::MakeSubstring(ext, 1, ext.size() - 1);
+ }},
+ {"substring", [] {
+ absl::Cord flat(absl::StrCat("-abcde", std::string(1000, 'x')));
+ flat.Flatten();
+ return flat.Subcord(1, 998);
+ }},
+ {"fragmented", [] {
+ std::string fragment = absl::StrCat("abcde", std::string(195, 'x'));
+ std::vector<std::string> fragments(200, fragment);
+ absl::Cord cord = absl::MakeFragmentedCord(fragments);
+ assert(cord.size() == 40000);
+ return cord;
+ }},
+};
+// clang-format on
+
+// Test helper that can mutate a cord, and possibly undo the mutation, for
+// testing.
+class CordMutator {
+ public:
+ constexpr CordMutator(absl::string_view name, void (*mutate)(absl::Cord&),
+ void (*undo)(absl::Cord&) = nullptr)
+ : name_(name), mutate_(mutate), undo_(undo) {}
+
+ absl::string_view Name() const { return name_; }
+ void Mutate(absl::Cord& cord) const { mutate_(cord); }
+ bool CanUndo() const { return undo_ != nullptr; }
+ void Undo(absl::Cord& cord) const { undo_(cord); }
+
+ private:
+ absl::string_view name_;
+ void (*mutate_)(absl::Cord&);
+ void (*undo_)(absl::Cord&);
+};
+
+// clang-format off
+// This array is constant-initialized in conformant compilers.
+CordMutator cord_mutators[] = {
+ {"clear", [](absl::Cord& c) { c.Clear(); }},
+ {"overwrite", [](absl::Cord& c) { c = "overwritten"; }},
+ {
+ "append string",
+ [](absl::Cord& c) { c.Append("0123456789"); },
+ [](absl::Cord& c) { c.RemoveSuffix(10); }
+ },
+ {
+ "append cord",
+ [](absl::Cord& c) {
+ c.Append(absl::MakeFragmentedCord({"12345", "67890"}));
+ },
+ [](absl::Cord& c) { c.RemoveSuffix(10); }
+ },
+ {
+ "append checksummed cord",
+ [](absl::Cord& c) {
+ absl::Cord to_append = absl::MakeFragmentedCord({"12345", "67890"});
+ to_append.SetExpectedChecksum(999);
+ c.Append(to_append);
+ },
+ [](absl::Cord& c) { c.RemoveSuffix(10); }
+ },
+ {
+ "append self",
+ [](absl::Cord& c) { c.Append(c); },
+ [](absl::Cord& c) { c.RemoveSuffix(c.size() / 2); }
+ },
+ {
+ "append empty string",
+ [](absl::Cord& c) { c.Append(""); },
+ [](absl::Cord& c) { }
+ },
+ {
+ "append empty cord",
+ [](absl::Cord& c) { c.Append(absl::Cord()); },
+ [](absl::Cord& c) { }
+ },
+ {
+ "append empty checksummed cord",
+ [](absl::Cord& c) {
+ absl::Cord to_append;
+ to_append.SetExpectedChecksum(999);
+ c.Append(to_append);
+ },
+ [](absl::Cord& c) { }
+ },
+ {
+ "prepend string",
+ [](absl::Cord& c) { c.Prepend("9876543210"); },
+ [](absl::Cord& c) { c.RemovePrefix(10); }
+ },
+ {
+ "prepend cord",
+ [](absl::Cord& c) {
+ c.Prepend(absl::MakeFragmentedCord({"98765", "43210"}));
+ },
+ [](absl::Cord& c) { c.RemovePrefix(10); }
+ },
+ {
+ "prepend checksummed cord",
+ [](absl::Cord& c) {
+ absl::Cord to_prepend = absl::MakeFragmentedCord({"98765", "43210"});
+ to_prepend.SetExpectedChecksum(999);
+ c.Prepend(to_prepend);
+ },
+ [](absl::Cord& c) { c.RemovePrefix(10); }
+ },
+ {
+ "prepend empty string",
+ [](absl::Cord& c) { c.Prepend(""); },
+ [](absl::Cord& c) { }
+ },
+ {
+ "prepend empty cord",
+ [](absl::Cord& c) { c.Prepend(absl::Cord()); },
+ [](absl::Cord& c) { }
+ },
+ {
+ "prepend empty checksummed cord",
+ [](absl::Cord& c) {
+ absl::Cord to_prepend;
+ to_prepend.SetExpectedChecksum(999);
+ c.Prepend(to_prepend);
+ },
+ [](absl::Cord& c) { }
+ },
+ {
+ "prepend self",
+ [](absl::Cord& c) { c.Prepend(c); },
+ [](absl::Cord& c) { c.RemovePrefix(c.size() / 2); }
+ },
+ {"remove prefix", [](absl::Cord& c) { c.RemovePrefix(c.size() / 2); }},
+ {"remove suffix", [](absl::Cord& c) { c.RemoveSuffix(c.size() / 2); }},
+ {"remove 0-prefix", [](absl::Cord& c) { c.RemovePrefix(0); }},
+ {"remove 0-suffix", [](absl::Cord& c) { c.RemoveSuffix(0); }},
+ {"subcord", [](absl::Cord& c) { c = c.Subcord(1, c.size() - 2); }},
+ {
+ "swap inline",
+ [](absl::Cord& c) {
+ absl::Cord other("swap");
+ c.swap(other);
+ }
+ },
+ {
+ "swap tree",
+ [](absl::Cord& c) {
+ absl::Cord other(std::string(10000, 'x'));
+ c.swap(other);
+ }
+ },
+};
+// clang-format on
+} // namespace
+
+TEST_P(CordTest, ExpectedChecksum) {
+ for (const PopulatedCordFactory& factory : cord_factories) {
+ SCOPED_TRACE(factory.Name());
+ for (bool shared : {false, true}) {
+ SCOPED_TRACE(shared);
+
+ absl::Cord shared_cord_source = factory.Generate();
+ auto make_instance = [=] {
+ return shared ? shared_cord_source : factory.Generate();
+ };
+
+ const absl::Cord base_value = factory.Generate();
+ const std::string base_value_as_string(factory.Generate().Flatten());
+
+ absl::Cord c1 = make_instance();
+ EXPECT_FALSE(c1.ExpectedChecksum().has_value());
+
+ // Setting an expected checksum works, and retains the cord's bytes
+ c1.SetExpectedChecksum(12345);
+ EXPECT_EQ(c1.ExpectedChecksum().value_or(0), 12345);
+ EXPECT_EQ(c1, base_value);
+
+ // Test that setting an expected checksum again doesn't crash or leak
+ // memory.
+ c1.SetExpectedChecksum(12345);
+ EXPECT_EQ(c1.ExpectedChecksum().value_or(0), 12345);
+ EXPECT_EQ(c1, base_value);
+
+ // CRC persists through copies, assignments, and moves:
+ absl::Cord c1_copy_construct = c1;
+ EXPECT_EQ(c1_copy_construct.ExpectedChecksum().value_or(0), 12345);
+
+ absl::Cord c1_copy_assign;
+ c1_copy_assign = c1;
+ EXPECT_EQ(c1_copy_assign.ExpectedChecksum().value_or(0), 12345);
+
+ absl::Cord c1_move(std::move(c1_copy_assign));
+ EXPECT_EQ(c1_move.ExpectedChecksum().value_or(0), 12345);
+
+ EXPECT_EQ(c1.ExpectedChecksum().value_or(0), 12345);
+
+ // A CRC Cord compares equal to its non-CRC value.
+ EXPECT_EQ(c1, make_instance());
+
+ for (const CordMutator& mutator : cord_mutators) {
+ SCOPED_TRACE(mutator.Name());
+
+ // Test that mutating a cord removes its stored checksum
+ absl::Cord c2 = make_instance();
+ c2.SetExpectedChecksum(24680);
+
+ mutator.Mutate(c2);
+
+ if (c1 == c2) {
+ // Not a mutation (for example, appending the empty string).
+ // Whether the checksum is removed is not defined.
+ continue;
+ }
+
+ EXPECT_EQ(c2.ExpectedChecksum(), absl::nullopt);
+
+ if (mutator.CanUndo()) {
+ // Undoing an operation should not restore the checksum
+ mutator.Undo(c2);
+ EXPECT_EQ(c2, base_value);
+ EXPECT_EQ(c2.ExpectedChecksum(), absl::nullopt);
+ }
+ }
+
+ absl::Cord c3 = make_instance();
+ c3.SetExpectedChecksum(999);
+ const absl::Cord& cc3 = c3;
+
+ // Test that all cord reading operations function in the face of an
+ // expected checksum.
+
+ // Test data precondition
+ ASSERT_TRUE(cc3.StartsWith("abcde"));
+
+ EXPECT_EQ(cc3.size(), base_value_as_string.size());
+ EXPECT_FALSE(cc3.empty());
+ EXPECT_EQ(cc3.Compare(base_value), 0);
+ EXPECT_EQ(cc3.Compare(base_value_as_string), 0);
+ EXPECT_EQ(cc3.Compare("wxyz"), -1);
+ EXPECT_EQ(cc3.Compare(absl::Cord("wxyz")), -1);
+ EXPECT_EQ(cc3.Compare("aaaa"), 1);
+ EXPECT_EQ(cc3.Compare(absl::Cord("aaaa")), 1);
+ EXPECT_EQ(absl::Cord("wxyz").Compare(cc3), 1);
+ EXPECT_EQ(absl::Cord("aaaa").Compare(cc3), -1);
+ EXPECT_TRUE(cc3.StartsWith("abcd"));
+ EXPECT_EQ(std::string(cc3), base_value_as_string);
+
+ std::string dest;
+ absl::CopyCordToString(cc3, &dest);
+ EXPECT_EQ(dest, base_value_as_string);
+
+ bool first_pass = true;
+ for (absl::string_view chunk : cc3.Chunks()) {
+ if (first_pass) {
+ EXPECT_TRUE(absl::StartsWith(chunk, "abcde"));
+ }
+ first_pass = false;
+ }
+ first_pass = true;
+ for (char ch : cc3.Chars()) {
+ if (first_pass) {
+ EXPECT_EQ(ch, 'a');
+ }
+ first_pass = false;
+ }
+ EXPECT_TRUE(absl::StartsWith(*cc3.chunk_begin(), "abcde"));
+ EXPECT_EQ(*cc3.char_begin(), 'a');
+
+ auto char_it = cc3.char_begin();
+ absl::Cord::Advance(&char_it, 2);
+ EXPECT_EQ(absl::Cord::AdvanceAndRead(&char_it, 2), "cd");
+ EXPECT_EQ(*char_it, 'e');
+ char_it = cc3.char_begin();
+ absl::Cord::Advance(&char_it, 2);
+ EXPECT_TRUE(absl::StartsWith(absl::Cord::ChunkRemaining(char_it), "cde"));
+
+ EXPECT_EQ(cc3[0], 'a');
+ EXPECT_EQ(cc3[4], 'e');
+ EXPECT_EQ(absl::HashOf(cc3), absl::HashOf(base_value));
+ EXPECT_EQ(absl::HashOf(cc3), absl::HashOf(base_value_as_string));
+ }
+ }
+}
+
+// Test the special cases encountered with an empty checksummed cord.
+TEST_P(CordTest, ChecksummedEmptyCord) {
+ absl::Cord c1;
+ EXPECT_FALSE(c1.ExpectedChecksum().has_value());
+
+ // Setting an expected checksum works.
+ c1.SetExpectedChecksum(12345);
+ EXPECT_EQ(c1.ExpectedChecksum().value_or(0), 12345);
+ EXPECT_EQ(c1, "");
+ EXPECT_TRUE(c1.empty());
+
+ // Test that setting an expected checksum again doesn't crash or leak memory.
+ c1.SetExpectedChecksum(12345);
+ EXPECT_EQ(c1.ExpectedChecksum().value_or(0), 12345);
+ EXPECT_EQ(c1, "");
+ EXPECT_TRUE(c1.empty());
+
+ // CRC persists through copies, assignments, and moves:
+ absl::Cord c1_copy_construct = c1;
+ EXPECT_EQ(c1_copy_construct.ExpectedChecksum().value_or(0), 12345);
+
+ absl::Cord c1_copy_assign;
+ c1_copy_assign = c1;
+ EXPECT_EQ(c1_copy_assign.ExpectedChecksum().value_or(0), 12345);
+
+ absl::Cord c1_move(std::move(c1_copy_assign));
+ EXPECT_EQ(c1_move.ExpectedChecksum().value_or(0), 12345);
+
+ EXPECT_EQ(c1.ExpectedChecksum().value_or(0), 12345);
+
+ // A CRC Cord compares equal to its non-CRC value.
+ EXPECT_EQ(c1, absl::Cord());
+
+ for (const CordMutator& mutator : cord_mutators) {
+ SCOPED_TRACE(mutator.Name());
+
+ // Exercise mutating an empty checksummed cord to catch crashes and exercise
+ // memory sanitizers.
+ absl::Cord c2;
+ c2.SetExpectedChecksum(24680);
+ mutator.Mutate(c2);
+
+ if (c2.empty()) {
+ // Not a mutation
+ continue;
+ }
+ EXPECT_EQ(c2.ExpectedChecksum(), absl::nullopt);
+
+ if (mutator.CanUndo()) {
+ mutator.Undo(c2);
+ }
+ }
+
+ absl::Cord c3;
+ c3.SetExpectedChecksum(999);
+ const absl::Cord& cc3 = c3;
+
+ // Test that all cord reading operations function in the face of an
+ // expected checksum.
+ EXPECT_TRUE(cc3.StartsWith(""));
+ EXPECT_TRUE(cc3.EndsWith(""));
+ EXPECT_TRUE(cc3.empty());
+ EXPECT_EQ(cc3, "");
+ EXPECT_EQ(cc3, absl::Cord());
+ EXPECT_EQ(cc3.size(), 0);
+ EXPECT_EQ(cc3.Compare(absl::Cord()), 0);
+ EXPECT_EQ(cc3.Compare(c1), 0);
+ EXPECT_EQ(cc3.Compare(cc3), 0);
+ EXPECT_EQ(cc3.Compare(""), 0);
+ EXPECT_EQ(cc3.Compare("wxyz"), -1);
+ EXPECT_EQ(cc3.Compare(absl::Cord("wxyz")), -1);
+ EXPECT_EQ(absl::Cord("wxyz").Compare(cc3), 1);
+ EXPECT_EQ(std::string(cc3), "");
+
+ std::string dest;
+ absl::CopyCordToString(cc3, &dest);
+ EXPECT_EQ(dest, "");
+
+ for (absl::string_view chunk : cc3.Chunks()) { // NOLINT(unreachable loop)
+ static_cast<void>(chunk);
+ GTEST_FAIL() << "no chunks expected";
+ }
+ EXPECT_TRUE(cc3.chunk_begin() == cc3.chunk_end());
+
+ for (char ch : cc3.Chars()) { // NOLINT(unreachable loop)
+ static_cast<void>(ch);
+ GTEST_FAIL() << "no chars expected";
+ }
+ EXPECT_TRUE(cc3.char_begin() == cc3.char_end());
+
+ EXPECT_EQ(cc3.TryFlat(), "");
+ EXPECT_EQ(absl::HashOf(c3), absl::HashOf(absl::Cord()));
+ EXPECT_EQ(absl::HashOf(c3), absl::HashOf(absl::string_view()));
+}
+
+TEST(CrcCordTest, ChecksummedEmptyCordEstimateMemoryUsage) {
+ absl::Cord cord;
+ cord.SetExpectedChecksum(0);
+ EXPECT_NE(cord.EstimatedMemoryUsage(), 0);
+}
+
+#if defined(GTEST_HAS_DEATH_TEST) && defined(ABSL_INTERNAL_CORD_HAVE_SANITIZER)
+
+// Returns an expected poison / uninitialized death message expression.
+const char* MASanDeathExpr() {
+ return "(use-after-poison|use-of-uninitialized-value)";
+}
+
+TEST(CordSanitizerTest, SanitizesEmptyCord) {
+ absl::Cord cord;
+ const char* data = cord.Flatten().data();
+ EXPECT_DEATH(EXPECT_EQ(data[0], 0), MASanDeathExpr());
+}
+
+TEST(CordSanitizerTest, SanitizesSmallCord) {
+ absl::Cord cord("Hello");
+ const char* data = cord.Flatten().data();
+ EXPECT_DEATH(EXPECT_EQ(data[5], 0), MASanDeathExpr());
+}
+
+TEST(CordSanitizerTest, SanitizesCordOnSetSSOValue) {
+ absl::Cord cord("String that is too big to be an SSO value");
+ cord = "Hello";
+ const char* data = cord.Flatten().data();
+ EXPECT_DEATH(EXPECT_EQ(data[5], 0), MASanDeathExpr());
+}
+
+TEST(CordSanitizerTest, SanitizesCordOnCopyCtor) {
+ absl::Cord src("hello");
+ absl::Cord dst(src);
+ const char* data = dst.Flatten().data();
+ EXPECT_DEATH(EXPECT_EQ(data[5], 0), MASanDeathExpr());
+}
+
+TEST(CordSanitizerTest, SanitizesCordOnMoveCtor) {
+ absl::Cord src("hello");
+ absl::Cord dst(std::move(src));
+ const char* data = dst.Flatten().data();
+ EXPECT_DEATH(EXPECT_EQ(data[5], 0), MASanDeathExpr());
+}
+
+TEST(CordSanitizerTest, SanitizesCordOnAssign) {
+ absl::Cord src("hello");
+ absl::Cord dst;
+ dst = src;
+ const char* data = dst.Flatten().data();
+ EXPECT_DEATH(EXPECT_EQ(data[5], 0), MASanDeathExpr());
+}
+
+TEST(CordSanitizerTest, SanitizesCordOnMoveAssign) {
+ absl::Cord src("hello");
+ absl::Cord dst;
+ dst = std::move(src);
+ const char* data = dst.Flatten().data();
+ EXPECT_DEATH(EXPECT_EQ(data[5], 0), MASanDeathExpr());
+}
+
+TEST(CordSanitizerTest, SanitizesCordOnSsoAssign) {
+ absl::Cord src("hello");
+ absl::Cord dst("String that is too big to be an SSO value");
+ dst = src;
+ const char* data = dst.Flatten().data();
+ EXPECT_DEATH(EXPECT_EQ(data[5], 0), MASanDeathExpr());
+}
+
+#endif // GTEST_HAS_DEATH_TEST && ABSL_INTERNAL_CORD_HAVE_SANITIZER
diff --git a/absl/strings/cord_test_helpers.h b/absl/strings/cord_test_helpers.h
index 31a1dc8..ca52240 100644
--- a/absl/strings/cord_test_helpers.h
+++ b/absl/strings/cord_test_helpers.h
@@ -51,7 +51,7 @@
// existing inputs rather than copying contents of the input.
kMedium = cord_internal::kMaxFlatLength / 2 + 1,
- // A string value large enough to cause it to be stored in mutliple flats.
+ // A string value large enough to cause it to be stored in multiple flats.
kLarge = cord_internal::kMaxFlatLength * 4
};
diff --git a/absl/strings/cordz_test.cc b/absl/strings/cordz_test.cc
index 2b7d30b..a35493e 100644
--- a/absl/strings/cordz_test.cc
+++ b/absl/strings/cordz_test.cc
@@ -12,18 +12,20 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#include <cstdint>
+#include <cstddef>
+#include <cstring>
+#include <ostream>
#include <string>
+#include <utility>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/base/config.h"
-#include "absl/base/internal/raw_logging.h"
-#include "absl/base/macros.h"
#include "absl/strings/cord.h"
+#include "absl/strings/cord_buffer.h"
#include "absl/strings/cord_test_helpers.h"
#include "absl/strings/cordz_test_helpers.h"
-#include "absl/strings/internal/cordz_functions.h"
+#include "absl/strings/internal/cord_internal.h"
#include "absl/strings/internal/cordz_info.h"
#include "absl/strings/internal/cordz_sample_token.h"
#include "absl/strings/internal/cordz_statistics.h"
diff --git a/absl/strings/cordz_test_helpers.h b/absl/strings/cordz_test_helpers.h
index e410eec..619f13c 100644
--- a/absl/strings/cordz_test_helpers.h
+++ b/absl/strings/cordz_test_helpers.h
@@ -21,6 +21,7 @@
#include "gtest/gtest.h"
#include "absl/base/config.h"
#include "absl/base/macros.h"
+#include "absl/base/nullability.h"
#include "absl/strings/cord.h"
#include "absl/strings/internal/cord_internal.h"
#include "absl/strings/internal/cordz_info.h"
@@ -33,15 +34,16 @@
ABSL_NAMESPACE_BEGIN
// Returns the CordzInfo for the cord, or nullptr if the cord is not sampled.
-inline const cord_internal::CordzInfo* GetCordzInfoForTesting(
+inline absl::Nullable<const cord_internal::CordzInfo*> GetCordzInfoForTesting(
const Cord& cord) {
if (!cord.contents_.is_tree()) return nullptr;
return cord.contents_.cordz_info();
}
// Returns true if the provided cordz_info is in the list of sampled cords.
-inline bool CordzInfoIsListed(const cord_internal::CordzInfo* cordz_info,
- cord_internal::CordzSampleToken token = {}) {
+inline bool CordzInfoIsListed(
+ absl::Nonnull<const cord_internal::CordzInfo*> cordz_info,
+ cord_internal::CordzSampleToken token = {}) {
for (const cord_internal::CordzInfo& info : token) {
if (cordz_info == &info) return true;
}
@@ -119,7 +121,7 @@
// Wrapper struct managing a small CordRep `rep`
struct TestCordRep {
- cord_internal::CordRepFlat* rep;
+ absl::Nonnull<cord_internal::CordRepFlat*> rep;
TestCordRep() {
rep = cord_internal::CordRepFlat::New(100);
diff --git a/absl/strings/escaping.cc b/absl/strings/escaping.cc
index 18b20b8..af50fae 100644
--- a/absl/strings/escaping.cc
+++ b/absl/strings/escaping.cc
@@ -16,21 +16,23 @@
#include <algorithm>
#include <cassert>
+#include <cstddef>
#include <cstdint>
#include <cstring>
-#include <iterator>
#include <limits>
#include <string>
-#include "absl/base/internal/endian.h"
+#include "absl/base/config.h"
#include "absl/base/internal/raw_logging.h"
#include "absl/base/internal/unaligned_access.h"
-#include "absl/strings/internal/char_map.h"
+#include "absl/base/nullability.h"
+#include "absl/strings/ascii.h"
+#include "absl/strings/charset.h"
#include "absl/strings/internal/escaping.h"
#include "absl/strings/internal/resize_uninitialized.h"
#include "absl/strings/internal/utf8.h"
+#include "absl/strings/numbers.h"
#include "absl/strings/str_cat.h"
-#include "absl/strings/str_join.h"
#include "absl/strings/string_view.h"
namespace absl {
@@ -42,18 +44,19 @@
inline bool is_octal_digit(char c) { return ('0' <= c) && (c <= '7'); }
-inline int hex_digit_to_int(char c) {
+inline unsigned int hex_digit_to_int(char c) {
static_assert('0' == 0x30 && 'A' == 0x41 && 'a' == 0x61,
"Character set must be ASCII.");
- assert(absl::ascii_isxdigit(c));
- int x = static_cast<unsigned char>(c);
+ assert(absl::ascii_isxdigit(static_cast<unsigned char>(c)));
+ unsigned int x = static_cast<unsigned char>(c);
if (x > '9') {
x += 9;
}
return x & 0xf;
}
-inline bool IsSurrogate(char32_t c, absl::string_view src, std::string* error) {
+inline bool IsSurrogate(char32_t c, absl::string_view src,
+ absl::Nullable<std::string*> error) {
if (c >= 0xD800 && c <= 0xDFFF) {
if (error) {
*error = absl::StrCat("invalid surrogate character (0xD800-DFFF): \\",
@@ -82,7 +85,9 @@
// UnescapeCEscapeSequences().
// ----------------------------------------------------------------------
bool CUnescapeInternal(absl::string_view source, bool leave_nulls_escaped,
- char* dest, ptrdiff_t* dest_len, std::string* error) {
+ absl::Nonnull<char*> dest,
+ absl::Nonnull<ptrdiff_t*> dest_len,
+ absl::Nullable<std::string*> error) {
char* d = dest;
const char* p = source.data();
const char* end = p + source.size();
@@ -121,27 +126,29 @@
case '7': {
// octal digit: 1 to 3 digits
const char* octal_start = p;
- unsigned int ch = *p - '0';
- if (p < last_byte && is_octal_digit(p[1])) ch = ch * 8 + *++p - '0';
+ unsigned int ch = static_cast<unsigned int>(*p - '0'); // digit 1
if (p < last_byte && is_octal_digit(p[1]))
- ch = ch * 8 + *++p - '0'; // now points at last digit
+ ch = ch * 8 + static_cast<unsigned int>(*++p - '0'); // digit 2
+ if (p < last_byte && is_octal_digit(p[1]))
+ ch = ch * 8 + static_cast<unsigned int>(*++p - '0'); // digit 3
if (ch > 0xff) {
if (error) {
*error = "Value of \\" +
- std::string(octal_start, p + 1 - octal_start) +
+ std::string(octal_start,
+ static_cast<size_t>(p + 1 - octal_start)) +
" exceeds 0xff";
}
return false;
}
if ((ch == 0) && leave_nulls_escaped) {
// Copy the escape sequence for the null character
- const ptrdiff_t octal_size = p + 1 - octal_start;
+ const size_t octal_size = static_cast<size_t>(p + 1 - octal_start);
*d++ = '\\';
memmove(d, octal_start, octal_size);
d += octal_size;
break;
}
- *d++ = ch;
+ *d++ = static_cast<char>(ch);
break;
}
case 'x':
@@ -149,32 +156,34 @@
if (p >= last_byte) {
if (error) *error = "String cannot end with \\x";
return false;
- } else if (!absl::ascii_isxdigit(p[1])) {
+ } else if (!absl::ascii_isxdigit(static_cast<unsigned char>(p[1]))) {
if (error) *error = "\\x cannot be followed by a non-hex digit";
return false;
}
unsigned int ch = 0;
const char* hex_start = p;
- while (p < last_byte && absl::ascii_isxdigit(p[1]))
+ while (p < last_byte &&
+ absl::ascii_isxdigit(static_cast<unsigned char>(p[1])))
// Arbitrarily many hex digits
ch = (ch << 4) + hex_digit_to_int(*++p);
if (ch > 0xFF) {
if (error) {
*error = "Value of \\" +
- std::string(hex_start, p + 1 - hex_start) +
+ std::string(hex_start,
+ static_cast<size_t>(p + 1 - hex_start)) +
" exceeds 0xff";
}
return false;
}
if ((ch == 0) && leave_nulls_escaped) {
// Copy the escape sequence for the null character
- const ptrdiff_t hex_size = p + 1 - hex_start;
+ const size_t hex_size = static_cast<size_t>(p + 1 - hex_start);
*d++ = '\\';
memmove(d, hex_start, hex_size);
d += hex_size;
break;
}
- *d++ = ch;
+ *d++ = static_cast<char>(ch);
break;
}
case 'u': {
@@ -184,18 +193,20 @@
if (p + 4 >= end) {
if (error) {
*error = "\\u must be followed by 4 hex digits: \\" +
- std::string(hex_start, p + 1 - hex_start);
+ std::string(hex_start,
+ static_cast<size_t>(p + 1 - hex_start));
}
return false;
}
for (int i = 0; i < 4; ++i) {
// Look one char ahead.
- if (absl::ascii_isxdigit(p[1])) {
+ if (absl::ascii_isxdigit(static_cast<unsigned char>(p[1]))) {
rune = (rune << 4) + hex_digit_to_int(*++p); // Advance p.
} else {
if (error) {
*error = "\\u must be followed by 4 hex digits: \\" +
- std::string(hex_start, p + 1 - hex_start);
+ std::string(hex_start,
+ static_cast<size_t>(p + 1 - hex_start));
}
return false;
}
@@ -220,20 +231,22 @@
if (p + 8 >= end) {
if (error) {
*error = "\\U must be followed by 8 hex digits: \\" +
- std::string(hex_start, p + 1 - hex_start);
+ std::string(hex_start,
+ static_cast<size_t>(p + 1 - hex_start));
}
return false;
}
for (int i = 0; i < 8; ++i) {
// Look one char ahead.
- if (absl::ascii_isxdigit(p[1])) {
+ if (absl::ascii_isxdigit(static_cast<unsigned char>(p[1]))) {
// Don't change rune until we're sure this
// is within the Unicode limit, but do advance p.
uint32_t newrune = (rune << 4) + hex_digit_to_int(*++p);
if (newrune > 0x10FFFF) {
if (error) {
*error = "Value of \\" +
- std::string(hex_start, p + 1 - hex_start) +
+ std::string(hex_start,
+ static_cast<size_t>(p + 1 - hex_start)) +
" exceeds Unicode limit (0x10FFFF)";
}
return false;
@@ -243,7 +256,8 @@
} else {
if (error) {
*error = "\\U must be followed by 8 hex digits: \\" +
- std::string(hex_start, p + 1 - hex_start);
+ std::string(hex_start,
+ static_cast<size_t>(p + 1 - hex_start));
}
return false;
}
@@ -280,7 +294,8 @@
// may be the same.
// ----------------------------------------------------------------------
bool CUnescapeInternal(absl::string_view source, bool leave_nulls_escaped,
- std::string* dest, std::string* error) {
+ absl::Nonnull<std::string*> dest,
+ absl::Nullable<std::string*> error) {
strings_internal::STLStringResizeUninitialized(dest, source.size());
ptrdiff_t dest_size;
@@ -291,7 +306,7 @@
error)) {
return false;
}
- dest->erase(dest_size);
+ dest->erase(static_cast<size_t>(dest_size));
return true;
}
@@ -311,7 +326,7 @@
std::string dest;
bool last_hex_escape = false; // true if last output char was \xNN.
- for (unsigned char c : src) {
+ for (char c : src) {
bool is_hex_escape = false;
switch (c) {
case '\n': dest.append("\\" "n"); break;
@@ -320,28 +335,30 @@
case '\"': dest.append("\\" "\""); break;
case '\'': dest.append("\\" "'"); break;
case '\\': dest.append("\\" "\\"); break;
- default:
+ default: {
// Note that if we emit \xNN and the src character after that is a hex
// digit then that digit must be escaped too to prevent it being
// interpreted as part of the character code by C.
- if ((!utf8_safe || c < 0x80) &&
- (!absl::ascii_isprint(c) ||
- (last_hex_escape && absl::ascii_isxdigit(c)))) {
+ const unsigned char uc = static_cast<unsigned char>(c);
+ if ((!utf8_safe || uc < 0x80) &&
+ (!absl::ascii_isprint(uc) ||
+ (last_hex_escape && absl::ascii_isxdigit(uc)))) {
if (use_hex) {
dest.append("\\" "x");
- dest.push_back(numbers_internal::kHexChar[c / 16]);
- dest.push_back(numbers_internal::kHexChar[c % 16]);
+ dest.push_back(numbers_internal::kHexChar[uc / 16]);
+ dest.push_back(numbers_internal::kHexChar[uc % 16]);
is_hex_escape = true;
} else {
dest.append("\\");
- dest.push_back(numbers_internal::kHexChar[c / 64]);
- dest.push_back(numbers_internal::kHexChar[(c % 64) / 8]);
- dest.push_back(numbers_internal::kHexChar[c % 8]);
+ dest.push_back(numbers_internal::kHexChar[uc / 64]);
+ dest.push_back(numbers_internal::kHexChar[(uc % 64) / 8]);
+ dest.push_back(numbers_internal::kHexChar[uc % 8]);
}
} else {
dest.push_back(c);
break;
}
+ }
}
last_hex_escape = is_hex_escape;
}
@@ -350,7 +367,7 @@
}
/* clang-format off */
-constexpr char c_escaped_len[256] = {
+constexpr unsigned char kCEscapedLen[256] = {
4, 4, 4, 4, 4, 4, 4, 4, 4, 2, 2, 4, 4, 2, 4, 4, // \t, \n, \r
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // ", '
@@ -375,11 +392,28 @@
// that UTF-8 bytes are not handled specially.
inline size_t CEscapedLength(absl::string_view src) {
size_t escaped_len = 0;
- for (unsigned char c : src) escaped_len += c_escaped_len[c];
+ // The maximum value of kCEscapedLen[x] is 4, so we can escape any string of
+ // length size_t_max/4 without checking for overflow.
+ size_t unchecked_limit =
+ std::min<size_t>(src.size(), std::numeric_limits<size_t>::max() / 4);
+ size_t i = 0;
+ while (i < unchecked_limit) {
+ // Common case: No need to check for overflow.
+ escaped_len += kCEscapedLen[static_cast<unsigned char>(src[i++])];
+ }
+ while (i < src.size()) {
+ // Beyond unchecked_limit we need to check for overflow before adding.
+ size_t char_len = kCEscapedLen[static_cast<unsigned char>(src[i++])];
+ ABSL_INTERNAL_CHECK(
+ escaped_len <= std::numeric_limits<size_t>::max() - char_len,
+ "escaped_len overflow");
+ escaped_len += char_len;
+ }
return escaped_len;
}
-void CEscapeAndAppendInternal(absl::string_view src, std::string* dest) {
+void CEscapeAndAppendInternal(absl::string_view src,
+ absl::Nonnull<std::string*> dest) {
size_t escaped_len = CEscapedLength(src);
if (escaped_len == src.size()) {
dest->append(src.data(), src.size());
@@ -387,12 +421,15 @@
}
size_t cur_dest_len = dest->size();
+ ABSL_INTERNAL_CHECK(
+ cur_dest_len <= std::numeric_limits<size_t>::max() - escaped_len,
+ "std::string size overflow");
strings_internal::STLStringResizeUninitialized(dest,
cur_dest_len + escaped_len);
char* append_ptr = &(*dest)[cur_dest_len];
- for (unsigned char c : src) {
- int char_len = c_escaped_len[c];
+ for (char c : src) {
+ size_t char_len = kCEscapedLen[static_cast<unsigned char>(c)];
if (char_len == 1) {
*append_ptr++ = c;
} else if (char_len == 2) {
@@ -424,23 +461,26 @@
}
} else {
*append_ptr++ = '\\';
- *append_ptr++ = '0' + c / 64;
- *append_ptr++ = '0' + (c % 64) / 8;
- *append_ptr++ = '0' + c % 8;
+ *append_ptr++ = '0' + static_cast<unsigned char>(c) / 64;
+ *append_ptr++ = '0' + (static_cast<unsigned char>(c) % 64) / 8;
+ *append_ptr++ = '0' + static_cast<unsigned char>(c) % 8;
}
}
}
-bool Base64UnescapeInternal(const char* src_param, size_t szsrc, char* dest,
- size_t szdest, const signed char* unbase64,
- size_t* len) {
+// Reverses the mapping in Base64EscapeInternal; see that method's
+// documentation for details of the mapping.
+bool Base64UnescapeInternal(absl::Nullable<const char*> src_param, size_t szsrc,
+ absl::Nullable<char*> dest, size_t szdest,
+ absl::Nonnull<const signed char*> unbase64,
+ absl::Nonnull<size_t*> len) {
static const char kPad64Equals = '=';
static const char kPad64Dot = '.';
size_t destidx = 0;
int decode = 0;
int state = 0;
- unsigned int ch = 0;
+ unsigned char ch = 0;
unsigned int temp = 0;
// If "char" is signed by default, using *src as an array index results in
@@ -500,13 +540,13 @@
// how to handle those cases.
GET_INPUT(first, 4);
- temp = decode;
+ temp = static_cast<unsigned char>(decode);
GET_INPUT(second, 3);
- temp = (temp << 6) | decode;
+ temp = (temp << 6) | static_cast<unsigned char>(decode);
GET_INPUT(third, 2);
- temp = (temp << 6) | decode;
+ temp = (temp << 6) | static_cast<unsigned char>(decode);
GET_INPUT(fourth, 1);
- temp = (temp << 6) | decode;
+ temp = (temp << 6) | static_cast<unsigned char>(decode);
} else {
// We really did have four good data bytes, so advance four
// characters in the string.
@@ -518,11 +558,11 @@
// temp has 24 bits of input, so write that out as three bytes.
if (destidx + 3 > szdest) return false;
- dest[destidx + 2] = temp;
+ dest[destidx + 2] = static_cast<char>(temp);
temp >>= 8;
- dest[destidx + 1] = temp;
+ dest[destidx + 1] = static_cast<char>(temp);
temp >>= 8;
- dest[destidx] = temp;
+ dest[destidx] = static_cast<char>(temp);
destidx += 3;
}
} else {
@@ -583,18 +623,18 @@
}
// Each input character gives us six bits of output.
- temp = (temp << 6) | decode;
+ temp = (temp << 6) | static_cast<unsigned char>(decode);
++state;
if (state == 4) {
// If we've accumulated 24 bits of output, write that out as
// three bytes.
if (dest) {
if (destidx + 3 > szdest) return false;
- dest[destidx + 2] = temp;
+ dest[destidx + 2] = static_cast<char>(temp);
temp >>= 8;
- dest[destidx + 1] = temp;
+ dest[destidx + 1] = static_cast<char>(temp);
temp >>= 8;
- dest[destidx] = temp;
+ dest[destidx] = static_cast<char>(temp);
}
destidx += 3;
state = 0;
@@ -619,7 +659,7 @@
if (dest) {
if (destidx + 1 > szdest) return false;
temp >>= 4;
- dest[destidx] = temp;
+ dest[destidx] = static_cast<char>(temp);
}
++destidx;
expected_equals = 2;
@@ -630,9 +670,9 @@
if (dest) {
if (destidx + 2 > szdest) return false;
temp >>= 2;
- dest[destidx + 1] = temp;
+ dest[destidx + 1] = static_cast<char>(temp);
temp >>= 8;
- dest[destidx] = temp;
+ dest[destidx] = static_cast<char>(temp);
}
destidx += 2;
expected_equals = 1;
@@ -664,7 +704,10 @@
return ok;
}
-// The arrays below were generated by the following code
+// The arrays below map base64-escaped characters back to their original values.
+// For the inverse case, see k(WebSafe)Base64Chars in the internal
+// escaping.cc.
+// These arrays were generated by the following inversion code:
// #include <sys/time.h>
// #include <stdlib.h>
// #include <string.h>
@@ -691,8 +734,8 @@
// }
// }
//
-// where the value of "Base64[]" was replaced by one of the base-64 conversion
-// tables from the functions below.
+// where the value of "Base64[]" was replaced by one of k(WebSafe)Base64Chars
+// in the internal escaping.cc.
/* clang-format off */
constexpr signed char kUnBase64[] = {
-1, -1, -1, -1, -1, -1, -1, -1,
@@ -765,15 +808,12 @@
};
/* clang-format on */
-constexpr char kWebSafeBase64Chars[] =
- "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
-
template <typename String>
-bool Base64UnescapeInternal(const char* src, size_t slen, String* dest,
- const signed char* unbase64) {
+bool Base64UnescapeInternal(absl::Nullable<const char*> src, size_t slen,
+ absl::Nonnull<String*> dest,
+ absl::Nonnull<const signed char*> unbase64) {
// Determine the size of the output string. Base64 encodes every 3 bytes into
- // 4 characters. any leftover chars are added directly for good measure.
- // This is documented in the base64 RFC: http://tools.ietf.org/html/rfc3548
+ // 4 characters. Any leftover chars are added directly for good measure.
const size_t dest_len = 3 * (slen / 4) + (slen % 4);
strings_internal::STLStringResizeUninitialized(dest, dest_len);
@@ -815,15 +855,34 @@
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
};
+constexpr signed char kHexValueStrict[256] = {
+ -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,
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, // '0'..'9'
+ -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 'A'..'F'
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 'a'..'f'
+ -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,
+ -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,
+ -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,
+};
/* clang-format on */
// This is a templated function so that T can be either a char*
// or a string. This works because we use the [] operator to access
// individual characters at a time.
template <typename T>
-void HexStringToBytesInternal(const char* from, T to, ptrdiff_t num) {
- for (int i = 0; i < num; i++) {
- to[i] = (kHexValueLenient[from[i * 2] & 0xFF] << 4) +
+void HexStringToBytesInternal(absl::Nullable<const char*> from, T to,
+ size_t num) {
+ for (size_t i = 0; i < num; i++) {
+ to[i] = static_cast<char>(kHexValueLenient[from[i * 2] & 0xFF] << 4) +
(kHexValueLenient[from[i * 2 + 1] & 0xFF]);
}
}
@@ -831,7 +890,8 @@
// This is a templated function so that T can be either a char* or a
// std::string.
template <typename T>
-void BytesToHexStringInternal(const unsigned char* src, T dest, ptrdiff_t num) {
+void BytesToHexStringInternal(absl::Nullable<const unsigned char*> src, T dest,
+ size_t num) {
auto dest_ptr = &dest[0];
for (auto src_ptr = src; src_ptr != (src + num); ++src_ptr, dest_ptr += 2) {
const char* hex_p = &numbers_internal::kHexTable[*src_ptr * 2];
@@ -846,8 +906,8 @@
//
// See CUnescapeInternal() for implementation details.
// ----------------------------------------------------------------------
-bool CUnescape(absl::string_view source, std::string* dest,
- std::string* error) {
+bool CUnescape(absl::string_view source, absl::Nonnull<std::string*> dest,
+ absl::Nullable<std::string*> error) {
return CUnescapeInternal(source, kUnescapeNulls, dest, error);
}
@@ -869,48 +929,26 @@
return CEscapeInternal(src, true, true);
}
-// ----------------------------------------------------------------------
-// Base64Unescape() - base64 decoder
-// Base64Escape() - base64 encoder
-// WebSafeBase64Unescape() - Google's variation of base64 decoder
-// WebSafeBase64Escape() - Google's variation of base64 encoder
-//
-// Check out
-// http://tools.ietf.org/html/rfc2045 for formal description, but what we
-// care about is that...
-// Take the encoded stuff in groups of 4 characters and turn each
-// character into a code 0 to 63 thus:
-// A-Z map to 0 to 25
-// a-z map to 26 to 51
-// 0-9 map to 52 to 61
-// +(- for WebSafe) maps to 62
-// /(_ for WebSafe) maps to 63
-// There will be four numbers, all less than 64 which can be represented
-// by a 6 digit binary number (aaaaaa, bbbbbb, cccccc, dddddd respectively).
-// Arrange the 6 digit binary numbers into three bytes as such:
-// aaaaaabb bbbbcccc ccdddddd
-// Equals signs (one or two) are used at the end of the encoded block to
-// indicate that the text was not an integer multiple of three bytes long.
-// ----------------------------------------------------------------------
-
-bool Base64Unescape(absl::string_view src, std::string* dest) {
+bool Base64Unescape(absl::string_view src, absl::Nonnull<std::string*> dest) {
return Base64UnescapeInternal(src.data(), src.size(), dest, kUnBase64);
}
-bool WebSafeBase64Unescape(absl::string_view src, std::string* dest) {
+bool WebSafeBase64Unescape(absl::string_view src,
+ absl::Nonnull<std::string*> dest) {
return Base64UnescapeInternal(src.data(), src.size(), dest, kUnWebSafeBase64);
}
-void Base64Escape(absl::string_view src, std::string* dest) {
+void Base64Escape(absl::string_view src, absl::Nonnull<std::string*> dest) {
strings_internal::Base64EscapeInternal(
reinterpret_cast<const unsigned char*>(src.data()), src.size(), dest,
true, strings_internal::kBase64Chars);
}
-void WebSafeBase64Escape(absl::string_view src, std::string* dest) {
+void WebSafeBase64Escape(absl::string_view src,
+ absl::Nonnull<std::string*> dest) {
strings_internal::Base64EscapeInternal(
reinterpret_cast<const unsigned char*>(src.data()), src.size(), dest,
- false, kWebSafeBase64Chars);
+ false, strings_internal::kWebSafeBase64Chars);
}
std::string Base64Escape(absl::string_view src) {
@@ -925,10 +963,34 @@
std::string dest;
strings_internal::Base64EscapeInternal(
reinterpret_cast<const unsigned char*>(src.data()), src.size(), &dest,
- false, kWebSafeBase64Chars);
+ false, strings_internal::kWebSafeBase64Chars);
return dest;
}
+bool HexStringToBytes(absl::string_view hex,
+ absl::Nonnull<std::string*> bytes) {
+ size_t num_bytes = hex.size() / 2;
+ bytes->clear();
+ if (hex.size() != num_bytes * 2) {
+ return false;
+ }
+
+ absl::strings_internal::STLStringResizeUninitialized(bytes, num_bytes);
+ auto hex_p = hex.cbegin();
+ for (std::string::iterator bin_p = bytes->begin();
+ bin_p != bytes->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) {
+ bytes->resize(static_cast<size_t>(bin_p - bytes->begin()));
+ return false;
+ }
+ *bin_p = static_cast<char>((h1 << 4) + h2);
+ }
+
+ return true;
+}
+
std::string HexStringToBytes(absl::string_view from) {
std::string result;
const auto num = from.size() / 2;
diff --git a/absl/strings/escaping.h b/absl/strings/escaping.h
index f5ca26c..3f34fbf 100644
--- a/absl/strings/escaping.h
+++ b/absl/strings/escaping.h
@@ -27,7 +27,9 @@
#include <string>
#include <vector>
+#include "absl/base/attributes.h"
#include "absl/base/macros.h"
+#include "absl/base/nullability.h"
#include "absl/strings/ascii.h"
#include "absl/strings/str_join.h"
#include "absl/strings/string_view.h"
@@ -65,14 +67,16 @@
//
// std::string s = "foo\\rbar\\nbaz\\t";
// std::string unescaped_s;
-// if (!absl::CUnescape(s, &unescaped_s) {
+// if (!absl::CUnescape(s, &unescaped_s)) {
// ...
// }
// EXPECT_EQ(unescaped_s, "foo\rbar\nbaz\t");
-bool CUnescape(absl::string_view source, std::string* dest, std::string* error);
+bool CUnescape(absl::string_view source, absl::Nonnull<std::string*> dest,
+ absl::Nullable<std::string*> error);
// Overload of `CUnescape()` with no error reporting.
-inline bool CUnescape(absl::string_view source, std::string* dest) {
+inline bool CUnescape(absl::string_view source,
+ absl::Nonnull<std::string*> dest) {
return CUnescape(source, dest, nullptr);
}
@@ -117,39 +121,57 @@
// conversion.
std::string Utf8SafeCHexEscape(absl::string_view src);
-// Base64Unescape()
-//
-// Converts a `src` string encoded in Base64 to its binary equivalent, writing
-// it to a `dest` buffer, returning `true` on success. If `src` contains invalid
-// characters, `dest` is cleared and returns `false`.
-bool Base64Unescape(absl::string_view src, std::string* dest);
-
-// WebSafeBase64Unescape()
-//
-// Converts a `src` string encoded in Base64 to its binary equivalent, writing
-// it to a `dest` buffer, but using '-' instead of '+', and '_' instead of '/'.
-// If `src` contains invalid characters, `dest` is cleared and returns `false`.
-bool WebSafeBase64Unescape(absl::string_view src, std::string* dest);
-
// Base64Escape()
//
-// Encodes a `src` string into a base64-encoded string, with padding characters.
-// This function conforms with RFC 4648 section 4 (base64).
-void Base64Escape(absl::string_view src, std::string* dest);
+// Encodes a `src` string into a base64-encoded 'dest' string with padding
+// characters. This function conforms with RFC 4648 section 4 (base64) and RFC
+// 2045.
+void Base64Escape(absl::string_view src, absl::Nonnull<std::string*> dest);
std::string Base64Escape(absl::string_view src);
// WebSafeBase64Escape()
//
-// Encodes a `src` string into a base64-like string, using '-' instead of '+'
-// and '_' instead of '/', and without padding. This function conforms with RFC
-// 4648 section 5 (base64url).
-void WebSafeBase64Escape(absl::string_view src, std::string* dest);
+// Encodes a `src` string into a base64 string, like Base64Escape() does, but
+// outputs '-' instead of '+' and '_' instead of '/', and does not pad 'dest'.
+// This function conforms with RFC 4648 section 5 (base64url).
+void WebSafeBase64Escape(absl::string_view src,
+ absl::Nonnull<std::string*> dest);
std::string WebSafeBase64Escape(absl::string_view src);
+// Base64Unescape()
+//
+// Converts a `src` string encoded in Base64 (RFC 4648 section 4) to its binary
+// equivalent, writing it to a `dest` buffer, returning `true` on success. If
+// `src` contains invalid characters, `dest` is cleared and returns `false`.
+// If padding is included (note that `Base64Escape()` does produce it), it must
+// be correct. In the padding, '=' and '.' are treated identically.
+bool Base64Unescape(absl::string_view src, absl::Nonnull<std::string*> dest);
+
+// WebSafeBase64Unescape()
+//
+// Converts a `src` string encoded in "web safe" Base64 (RFC 4648 section 5) to
+// its binary equivalent, writing it to a `dest` buffer. If `src` contains
+// invalid characters, `dest` is cleared and returns `false`. If padding is
+// included (note that `WebSafeBase64Escape()` does not produce it), it must be
+// correct. In the padding, '=' and '.' are treated identically.
+bool WebSafeBase64Unescape(absl::string_view src,
+ absl::Nonnull<std::string*> dest);
+
+// HexStringToBytes()
+//
+// Converts the hexadecimal encoded data in `hex` into raw bytes in the `bytes`
+// output string. If `hex` does not consist of valid hexadecimal data, this
+// function returns false and leaves `bytes` in an unspecified state. Returns
+// true on success.
+ABSL_MUST_USE_RESULT bool HexStringToBytes(absl::string_view hex,
+ absl::Nonnull<std::string*> bytes);
+
// HexStringToBytes()
//
// Converts an ASCII hex string into bytes, returning binary data of length
-// `from.size()/2`.
+// `from.size()/2`. The input must be valid hexadecimal data, otherwise the
+// return value is unspecified.
+ABSL_DEPRECATED("Use the HexStringToBytes() that returns a bool")
std::string HexStringToBytes(absl::string_view from);
// BytesToHexString()
diff --git a/absl/strings/escaping_benchmark.cc b/absl/strings/escaping_benchmark.cc
index 10d5b03..f792226 100644
--- a/absl/strings/escaping_benchmark.cc
+++ b/absl/strings/escaping_benchmark.cc
@@ -14,13 +14,17 @@
#include "absl/strings/escaping.h"
+#include <cstdint>
#include <cstdio>
#include <cstring>
+#include <memory>
#include <random>
+#include <string>
#include "benchmark/benchmark.h"
#include "absl/base/internal/raw_logging.h"
#include "absl/strings/internal/escaping_test_common.h"
+#include "absl/strings/str_cat.h"
namespace {
diff --git a/absl/strings/escaping_test.cc b/absl/strings/escaping_test.cc
index 45671a0..ae3466a 100644
--- a/absl/strings/escaping_test.cc
+++ b/absl/strings/escaping_test.cc
@@ -15,17 +15,20 @@
#include "absl/strings/escaping.h"
#include <array>
+#include <cstddef>
#include <cstdio>
#include <cstring>
+#include <initializer_list>
#include <memory>
+#include <string>
#include <vector>
-#include "gmock/gmock.h"
#include "gtest/gtest.h"
-#include "absl/container/fixed_array.h"
+#include "absl/log/check.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/internal/escaping_test_common.h"
+#include "absl/strings/string_view.h"
namespace {
@@ -562,6 +565,7 @@
void TestEscapeAndUnescape() {
// Check the short strings; this tests the math (and boundaries)
for (const auto& tc : base64_tests) {
+ // Test plain base64.
StringType encoded("this junk should be ignored");
absl::Base64Escape(tc.plaintext, &encoded);
EXPECT_EQ(encoded, tc.cyphertext);
@@ -571,22 +575,26 @@
EXPECT_TRUE(absl::Base64Unescape(encoded, &decoded));
EXPECT_EQ(decoded, tc.plaintext);
- StringType websafe(tc.cyphertext);
- for (int c = 0; c < websafe.size(); ++c) {
- if ('+' == websafe[c]) websafe[c] = '-';
- if ('/' == websafe[c]) websafe[c] = '_';
+ StringType websafe_with_padding(tc.cyphertext);
+ for (unsigned int c = 0; c < websafe_with_padding.size(); ++c) {
+ if ('+' == websafe_with_padding[c]) websafe_with_padding[c] = '-';
+ if ('/' == websafe_with_padding[c]) websafe_with_padding[c] = '_';
+ // Intentionally keeping padding aka '='.
+ }
+
+ // Test plain websafe (aka without padding).
+ StringType websafe(websafe_with_padding);
+ for (unsigned int c = 0; c < websafe.size(); ++c) {
if ('=' == websafe[c]) {
websafe.resize(c);
break;
}
}
-
encoded = "this junk should be ignored";
absl::WebSafeBase64Escape(tc.plaintext, &encoded);
EXPECT_EQ(encoded, websafe);
EXPECT_EQ(absl::WebSafeBase64Escape(tc.plaintext), websafe);
- // Let's try the string version of the decoder
decoded = "this junk should be ignored";
EXPECT_TRUE(absl::WebSafeBase64Unescape(websafe, &decoded));
EXPECT_EQ(decoded, tc.plaintext);
@@ -617,6 +625,48 @@
TestEscapeAndUnescape<std::string>();
}
+TEST(Base64, Padding) {
+ // Padding is optional.
+ // '.' is an acceptable padding character, just like '='.
+ std::initializer_list<absl::string_view> good_padding = {
+ "YQ",
+ "YQ==",
+ "YQ=.",
+ "YQ.=",
+ "YQ..",
+ };
+ for (absl::string_view b64 : good_padding) {
+ std::string decoded;
+ EXPECT_TRUE(absl::Base64Unescape(b64, &decoded));
+ EXPECT_EQ(decoded, "a");
+ std::string websafe_decoded;
+ EXPECT_TRUE(absl::WebSafeBase64Unescape(b64, &websafe_decoded));
+ EXPECT_EQ(websafe_decoded, "a");
+ }
+ std::initializer_list<absl::string_view> bad_padding = {
+ "YQ=",
+ "YQ.",
+ "YQ===",
+ "YQ==.",
+ "YQ=.=",
+ "YQ=..",
+ "YQ.==",
+ "YQ.=.",
+ "YQ..=",
+ "YQ...",
+ "YQ====",
+ "YQ....",
+ "YQ=====",
+ "YQ.....",
+ };
+ for (absl::string_view b64 : bad_padding) {
+ std::string decoded;
+ EXPECT_FALSE(absl::Base64Unescape(b64, &decoded));
+ std::string websafe_decoded;
+ EXPECT_FALSE(absl::WebSafeBase64Unescape(b64, &websafe_decoded));
+ }
+}
+
TEST(Base64, DISABLED_HugeData) {
const size_t kSize = size_t(3) * 1000 * 1000 * 1000;
static_assert(kSize % 3 == 0, "kSize must be divisible by 3");
@@ -639,6 +689,35 @@
EXPECT_EQ(huge, unescaped);
}
+TEST(Escaping, HexStringToBytesBackToHex) {
+ std::string bytes, hex;
+
+ constexpr absl::string_view kTestHexLower = "1c2f0032f40123456789abcdef";
+ constexpr absl::string_view kTestHexUpper = "1C2F0032F40123456789ABCDEF";
+ constexpr absl::string_view kTestBytes = absl::string_view(
+ "\x1c\x2f\x00\x32\xf4\x01\x23\x45\x67\x89\xab\xcd\xef", 13);
+
+ EXPECT_TRUE(absl::HexStringToBytes(kTestHexLower, &bytes));
+ EXPECT_EQ(bytes, kTestBytes);
+
+ EXPECT_TRUE(absl::HexStringToBytes(kTestHexUpper, &bytes));
+ EXPECT_EQ(bytes, kTestBytes);
+
+ hex = absl::BytesToHexString(kTestBytes);
+ EXPECT_EQ(hex, kTestHexLower);
+
+ // Length not a multiple of two.
+ EXPECT_FALSE(absl::HexStringToBytes("1c2f003", &bytes));
+
+ // Not hex.
+ EXPECT_FALSE(absl::HexStringToBytes("1c2f00ft", &bytes));
+
+ // Empty input.
+ bytes = "abc";
+ EXPECT_TRUE(absl::HexStringToBytes("", &bytes));
+ EXPECT_EQ("", bytes); // Results in empty output.
+}
+
TEST(HexAndBack, HexStringToBytes_and_BytesToHexString) {
std::string hex_mixed = "0123456789abcdefABCDEF";
std::string bytes_expected = "\x01\x23\x45\x67\x89\xab\xcd\xef\xAB\xCD\xEF";
diff --git a/absl/strings/has_absl_stringify.h b/absl/strings/has_absl_stringify.h
new file mode 100644
index 0000000..274a786
--- /dev/null
+++ b/absl/strings/has_absl_stringify.h
@@ -0,0 +1,63 @@
+// Copyright 2022 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef ABSL_STRINGS_HAS_ABSL_STRINGIFY_H_
+#define ABSL_STRINGS_HAS_ABSL_STRINGIFY_H_
+
+#include <type_traits>
+#include <utility>
+
+#include "absl/strings/string_view.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+
+namespace strings_internal {
+
+// This is an empty class not intended to be used. It exists so that
+// `HasAbslStringify` can reference a universal class rather than needing to be
+// copied for each new sink.
+class UnimplementedSink {
+ public:
+ void Append(size_t count, char ch);
+
+ void Append(string_view v);
+
+ // Support `absl::Format(&sink, format, args...)`.
+ friend void AbslFormatFlush(UnimplementedSink* sink, absl::string_view v);
+};
+
+} // namespace strings_internal
+
+// `HasAbslStringify<T>` detects if type `T` supports the `AbslStringify()`
+// customization point (see
+// https://abseil.io/docs/cpp/guides/format#abslstringify for the
+// documentation).
+//
+// Note that there are types that can be `StrCat`-ed that do not use the
+// `AbslStringify` customization point (for example, `int`).
+
+template <typename T, typename = void>
+struct HasAbslStringify : std::false_type {};
+
+template <typename T>
+struct HasAbslStringify<
+ T, std::enable_if_t<std::is_void<decltype(AbslStringify(
+ std::declval<strings_internal::UnimplementedSink&>(),
+ std::declval<const T&>()))>::value>> : std::true_type {};
+
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_STRINGS_HAS_ABSL_STRINGIFY_H_
diff --git a/absl/strings/has_absl_stringify_test.cc b/absl/strings/has_absl_stringify_test.cc
new file mode 100644
index 0000000..59e7e1d
--- /dev/null
+++ b/absl/strings/has_absl_stringify_test.cc
@@ -0,0 +1,40 @@
+// Copyright 2023 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/strings/has_absl_stringify.h"
+
+#include <string>
+
+#include "gtest/gtest.h"
+#include "absl/types/optional.h"
+
+namespace {
+
+struct TypeWithoutAbslStringify {};
+
+struct TypeWithAbslStringify {
+ template <typename Sink>
+ friend void AbslStringify(Sink&, const TypeWithAbslStringify&) {}
+};
+
+TEST(HasAbslStringifyTest, Works) {
+ EXPECT_FALSE(absl::HasAbslStringify<int>::value);
+ EXPECT_FALSE(absl::HasAbslStringify<std::string>::value);
+ EXPECT_FALSE(absl::HasAbslStringify<TypeWithoutAbslStringify>::value);
+ EXPECT_TRUE(absl::HasAbslStringify<TypeWithAbslStringify>::value);
+ EXPECT_FALSE(
+ absl::HasAbslStringify<absl::optional<TypeWithAbslStringify>>::value);
+}
+
+} // namespace
diff --git a/absl/strings/has_ostream_operator.h b/absl/strings/has_ostream_operator.h
new file mode 100644
index 0000000..156ffc7
--- /dev/null
+++ b/absl/strings/has_ostream_operator.h
@@ -0,0 +1,42 @@
+// Copyright 2023 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef ABSL_STRINGS_HAS_OSTREAM_OPERATOR_H_
+#define ABSL_STRINGS_HAS_OSTREAM_OPERATOR_H_
+
+#include <ostream>
+#include <type_traits>
+#include <utility>
+
+#include "absl/base/config.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+
+// Detects if type `T` supports streaming to `std::ostream`s with `operator<<`.
+
+template <typename T, typename = void>
+struct HasOstreamOperator : std::false_type {};
+
+template <typename T>
+struct HasOstreamOperator<
+ T, std::enable_if_t<std::is_same<
+ std::ostream&, decltype(std::declval<std::ostream&>()
+ << std::declval<const T&>())>::value>>
+ : std::true_type {};
+
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_STRINGS_HAS_OSTREAM_OPERATOR_H_
diff --git a/absl/strings/has_ostream_operator_test.cc b/absl/strings/has_ostream_operator_test.cc
new file mode 100644
index 0000000..9ef4136
--- /dev/null
+++ b/absl/strings/has_ostream_operator_test.cc
@@ -0,0 +1,41 @@
+// Copyright 2023 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/strings/has_ostream_operator.h"
+
+#include <ostream>
+#include <string>
+
+#include "gtest/gtest.h"
+#include "absl/types/optional.h"
+
+namespace {
+
+struct TypeWithoutOstreamOp {};
+
+struct TypeWithOstreamOp {
+ friend std::ostream& operator<<(std::ostream& os, const TypeWithOstreamOp&) {
+ return os;
+ }
+};
+
+TEST(HasOstreamOperatorTest, Works) {
+ EXPECT_TRUE(absl::HasOstreamOperator<int>::value);
+ EXPECT_TRUE(absl::HasOstreamOperator<std::string>::value);
+ EXPECT_FALSE(absl::HasOstreamOperator<absl::optional<int>>::value);
+ EXPECT_FALSE(absl::HasOstreamOperator<TypeWithoutOstreamOp>::value);
+ EXPECT_TRUE(absl::HasOstreamOperator<TypeWithOstreamOp>::value);
+}
+
+} // namespace
diff --git a/absl/strings/internal/char_map.h b/absl/strings/internal/char_map.h
deleted file mode 100644
index 61484de..0000000
--- a/absl/strings/internal/char_map.h
+++ /dev/null
@@ -1,156 +0,0 @@
-// Copyright 2017 The Abseil Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-// Character Map Class
-//
-// A fast, bit-vector map for 8-bit unsigned characters.
-// This class is useful for non-character purposes as well.
-
-#ifndef ABSL_STRINGS_INTERNAL_CHAR_MAP_H_
-#define ABSL_STRINGS_INTERNAL_CHAR_MAP_H_
-
-#include <cstddef>
-#include <cstdint>
-#include <cstring>
-
-#include "absl/base/macros.h"
-#include "absl/base/port.h"
-
-namespace absl {
-ABSL_NAMESPACE_BEGIN
-namespace strings_internal {
-
-class Charmap {
- public:
- constexpr Charmap() : m_() {}
-
- // Initializes with a given char*. Note that NUL is not treated as
- // a terminator, but rather a char to be flicked.
- Charmap(const char* str, int len) : m_() {
- while (len--) SetChar(*str++);
- }
-
- // Initializes with a given char*. NUL is treated as a terminator
- // and will not be in the charmap.
- explicit Charmap(const char* str) : m_() {
- while (*str) SetChar(*str++);
- }
-
- constexpr bool contains(unsigned char c) const {
- return (m_[c / 64] >> (c % 64)) & 0x1;
- }
-
- // Returns true if and only if a character exists in both maps.
- bool IntersectsWith(const Charmap& c) const {
- for (size_t i = 0; i < ABSL_ARRAYSIZE(m_); ++i) {
- if ((m_[i] & c.m_[i]) != 0) return true;
- }
- return false;
- }
-
- bool IsZero() const {
- for (uint64_t c : m_) {
- if (c != 0) return false;
- }
- return true;
- }
-
- // Containing only a single specified char.
- static constexpr Charmap Char(char x) {
- return Charmap(CharMaskForWord(x, 0), CharMaskForWord(x, 1),
- CharMaskForWord(x, 2), CharMaskForWord(x, 3));
- }
-
- // Containing all the chars in the C-string 's'.
- // Note that this is expensively recursive because of the C++11 constexpr
- // formulation. Use only in constexpr initializers.
- static constexpr Charmap FromString(const char* s) {
- return *s == 0 ? Charmap() : (Char(*s) | FromString(s + 1));
- }
-
- // Containing all the chars in the closed interval [lo,hi].
- static constexpr Charmap Range(char lo, char hi) {
- return Charmap(RangeForWord(lo, hi, 0), RangeForWord(lo, hi, 1),
- RangeForWord(lo, hi, 2), RangeForWord(lo, hi, 3));
- }
-
- friend constexpr Charmap operator&(const Charmap& a, const Charmap& b) {
- return Charmap(a.m_[0] & b.m_[0], a.m_[1] & b.m_[1], a.m_[2] & b.m_[2],
- a.m_[3] & b.m_[3]);
- }
-
- friend constexpr Charmap operator|(const Charmap& a, const Charmap& b) {
- return Charmap(a.m_[0] | b.m_[0], a.m_[1] | b.m_[1], a.m_[2] | b.m_[2],
- a.m_[3] | b.m_[3]);
- }
-
- friend constexpr Charmap operator~(const Charmap& a) {
- return Charmap(~a.m_[0], ~a.m_[1], ~a.m_[2], ~a.m_[3]);
- }
-
- private:
- constexpr Charmap(uint64_t b0, uint64_t b1, uint64_t b2, uint64_t b3)
- : m_{b0, b1, b2, b3} {}
-
- static constexpr uint64_t RangeForWord(unsigned char lo, unsigned char hi,
- uint64_t word) {
- return OpenRangeFromZeroForWord(hi + 1, word) &
- ~OpenRangeFromZeroForWord(lo, word);
- }
-
- // All the chars in the specified word of the range [0, upper).
- static constexpr uint64_t OpenRangeFromZeroForWord(uint64_t upper,
- uint64_t word) {
- return (upper <= 64 * word)
- ? 0
- : (upper >= 64 * (word + 1))
- ? ~static_cast<uint64_t>(0)
- : (~static_cast<uint64_t>(0) >> (64 - upper % 64));
- }
-
- static constexpr uint64_t CharMaskForWord(unsigned char x, uint64_t word) {
- return (x / 64 == word) ? (static_cast<uint64_t>(1) << (x % 64)) : 0;
- }
-
- private:
- void SetChar(unsigned char c) {
- m_[c / 64] |= static_cast<uint64_t>(1) << (c % 64);
- }
-
- uint64_t m_[4];
-};
-
-// Mirror the char-classifying predicates in <cctype>
-constexpr Charmap UpperCharmap() { return Charmap::Range('A', 'Z'); }
-constexpr Charmap LowerCharmap() { return Charmap::Range('a', 'z'); }
-constexpr Charmap DigitCharmap() { return Charmap::Range('0', '9'); }
-constexpr Charmap AlphaCharmap() { return LowerCharmap() | UpperCharmap(); }
-constexpr Charmap AlnumCharmap() { return DigitCharmap() | AlphaCharmap(); }
-constexpr Charmap XDigitCharmap() {
- return DigitCharmap() | Charmap::Range('A', 'F') | Charmap::Range('a', 'f');
-}
-constexpr Charmap PrintCharmap() { return Charmap::Range(0x20, 0x7e); }
-constexpr Charmap SpaceCharmap() { return Charmap::FromString("\t\n\v\f\r "); }
-constexpr Charmap CntrlCharmap() {
- return Charmap::Range(0, 0x7f) & ~PrintCharmap();
-}
-constexpr Charmap BlankCharmap() { return Charmap::FromString("\t "); }
-constexpr Charmap GraphCharmap() { return PrintCharmap() & ~SpaceCharmap(); }
-constexpr Charmap PunctCharmap() { return GraphCharmap() & ~AlnumCharmap(); }
-
-} // namespace strings_internal
-ABSL_NAMESPACE_END
-} // namespace absl
-
-#endif // ABSL_STRINGS_INTERNAL_CHAR_MAP_H_
diff --git a/absl/strings/internal/char_map_test.cc b/absl/strings/internal/char_map_test.cc
deleted file mode 100644
index d330624..0000000
--- a/absl/strings/internal/char_map_test.cc
+++ /dev/null
@@ -1,172 +0,0 @@
-// Copyright 2017 The Abseil Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "absl/strings/internal/char_map.h"
-
-#include <cctype>
-#include <string>
-#include <vector>
-
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-
-namespace {
-
-constexpr absl::strings_internal::Charmap everything_map =
- ~absl::strings_internal::Charmap();
-constexpr absl::strings_internal::Charmap nothing_map{};
-
-TEST(Charmap, AllTests) {
- const absl::strings_internal::Charmap also_nothing_map("", 0);
- ASSERT_TRUE(everything_map.contains('\0'));
- ASSERT_TRUE(!nothing_map.contains('\0'));
- ASSERT_TRUE(!also_nothing_map.contains('\0'));
- for (unsigned char ch = 1; ch != 0; ++ch) {
- ASSERT_TRUE(everything_map.contains(ch));
- ASSERT_TRUE(!nothing_map.contains(ch));
- ASSERT_TRUE(!also_nothing_map.contains(ch));
- }
-
- const absl::strings_internal::Charmap symbols("&@#@^!@?", 5);
- ASSERT_TRUE(symbols.contains('&'));
- ASSERT_TRUE(symbols.contains('@'));
- ASSERT_TRUE(symbols.contains('#'));
- ASSERT_TRUE(symbols.contains('^'));
- ASSERT_TRUE(!symbols.contains('!'));
- ASSERT_TRUE(!symbols.contains('?'));
- int cnt = 0;
- for (unsigned char ch = 1; ch != 0; ++ch)
- cnt += symbols.contains(ch);
- ASSERT_EQ(cnt, 4);
-
- const absl::strings_internal::Charmap lets("^abcde", 3);
- const absl::strings_internal::Charmap lets2("fghij\0klmnop", 10);
- const absl::strings_internal::Charmap lets3("fghij\0klmnop");
- ASSERT_TRUE(lets2.contains('k'));
- ASSERT_TRUE(!lets3.contains('k'));
-
- ASSERT_TRUE(symbols.IntersectsWith(lets));
- ASSERT_TRUE(!lets2.IntersectsWith(lets));
- ASSERT_TRUE(lets.IntersectsWith(symbols));
- ASSERT_TRUE(!lets.IntersectsWith(lets2));
-
- ASSERT_TRUE(nothing_map.IsZero());
- ASSERT_TRUE(!lets.IsZero());
-}
-
-namespace {
-std::string Members(const absl::strings_internal::Charmap& m) {
- std::string r;
- for (size_t i = 0; i < 256; ++i)
- if (m.contains(i)) r.push_back(i);
- return r;
-}
-
-std::string ClosedRangeString(unsigned char lo, unsigned char hi) {
- // Don't depend on lo<hi. Just increment until lo==hi.
- std::string s;
- while (true) {
- s.push_back(lo);
- if (lo == hi) break;
- ++lo;
- }
- return s;
-}
-
-} // namespace
-
-TEST(Charmap, Constexpr) {
- constexpr absl::strings_internal::Charmap kEmpty = nothing_map;
- EXPECT_THAT(Members(kEmpty), "");
- constexpr absl::strings_internal::Charmap kA =
- absl::strings_internal::Charmap::Char('A');
- EXPECT_THAT(Members(kA), "A");
- constexpr absl::strings_internal::Charmap kAZ =
- absl::strings_internal::Charmap::Range('A', 'Z');
- EXPECT_THAT(Members(kAZ), "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
- constexpr absl::strings_internal::Charmap kIdentifier =
- absl::strings_internal::Charmap::Range('0', '9') |
- absl::strings_internal::Charmap::Range('A', 'Z') |
- absl::strings_internal::Charmap::Range('a', 'z') |
- absl::strings_internal::Charmap::Char('_');
- EXPECT_THAT(Members(kIdentifier),
- "0123456789"
- "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
- "_"
- "abcdefghijklmnopqrstuvwxyz");
- constexpr absl::strings_internal::Charmap kAll = everything_map;
- for (size_t i = 0; i < 256; ++i) {
- EXPECT_TRUE(kAll.contains(i)) << i;
- }
- constexpr absl::strings_internal::Charmap kHello =
- absl::strings_internal::Charmap::FromString("Hello, world!");
- EXPECT_THAT(Members(kHello), " !,Hdelorw");
-
- // test negation and intersection
- constexpr absl::strings_internal::Charmap kABC =
- absl::strings_internal::Charmap::Range('A', 'Z') &
- ~absl::strings_internal::Charmap::Range('D', 'Z');
- EXPECT_THAT(Members(kABC), "ABC");
-}
-
-TEST(Charmap, Range) {
- // Exhaustive testing takes too long, so test some of the boundaries that
- // are perhaps going to cause trouble.
- std::vector<size_t> poi = {0, 1, 2, 3, 4, 7, 8, 9, 15,
- 16, 17, 30, 31, 32, 33, 63, 64, 65,
- 127, 128, 129, 223, 224, 225, 254, 255};
- for (auto lo = poi.begin(); lo != poi.end(); ++lo) {
- SCOPED_TRACE(*lo);
- for (auto hi = lo; hi != poi.end(); ++hi) {
- SCOPED_TRACE(*hi);
- EXPECT_THAT(Members(absl::strings_internal::Charmap::Range(*lo, *hi)),
- ClosedRangeString(*lo, *hi));
- }
- }
-}
-
-bool AsBool(int x) { return static_cast<bool>(x); }
-
-TEST(CharmapCtype, Match) {
- for (int c = 0; c < 256; ++c) {
- SCOPED_TRACE(c);
- SCOPED_TRACE(static_cast<char>(c));
- EXPECT_EQ(AsBool(std::isupper(c)),
- absl::strings_internal::UpperCharmap().contains(c));
- EXPECT_EQ(AsBool(std::islower(c)),
- absl::strings_internal::LowerCharmap().contains(c));
- EXPECT_EQ(AsBool(std::isdigit(c)),
- absl::strings_internal::DigitCharmap().contains(c));
- EXPECT_EQ(AsBool(std::isalpha(c)),
- absl::strings_internal::AlphaCharmap().contains(c));
- EXPECT_EQ(AsBool(std::isalnum(c)),
- absl::strings_internal::AlnumCharmap().contains(c));
- EXPECT_EQ(AsBool(std::isxdigit(c)),
- absl::strings_internal::XDigitCharmap().contains(c));
- EXPECT_EQ(AsBool(std::isprint(c)),
- absl::strings_internal::PrintCharmap().contains(c));
- EXPECT_EQ(AsBool(std::isspace(c)),
- absl::strings_internal::SpaceCharmap().contains(c));
- EXPECT_EQ(AsBool(std::iscntrl(c)),
- absl::strings_internal::CntrlCharmap().contains(c));
- EXPECT_EQ(AsBool(std::isblank(c)),
- absl::strings_internal::BlankCharmap().contains(c));
- EXPECT_EQ(AsBool(std::isgraph(c)),
- absl::strings_internal::GraphCharmap().contains(c));
- EXPECT_EQ(AsBool(std::ispunct(c)),
- absl::strings_internal::PunctCharmap().contains(c));
- }
-}
-
-} // namespace
diff --git a/absl/strings/internal/charconv_bigint.cc b/absl/strings/internal/charconv_bigint.cc
index ebf8c07..46b5289 100644
--- a/absl/strings/internal/charconv_bigint.cc
+++ b/absl/strings/internal/charconv_bigint.cc
@@ -242,7 +242,7 @@
// decimal exponent to compensate.
--exponent_adjust;
}
- int digit = (*begin - '0');
+ char digit = (*begin - '0');
--significant_digits;
if (significant_digits == 0 && std::next(begin) != end &&
(digit == 0 || digit == 5)) {
@@ -255,7 +255,7 @@
// 500000...000000000001 to correctly round up, rather than to nearest.
++digit;
}
- queued = 10 * queued + digit;
+ queued = 10 * queued + static_cast<uint32_t>(digit);
++digits_queued;
if (digits_queued == kMaxSmallPowerOfTen) {
MultiplyBy(kTenToNth[kMaxSmallPowerOfTen]);
@@ -296,10 +296,8 @@
std::min(n / kLargePowerOfFiveStep, kLargestPowerOfFiveIndex);
if (first_pass) {
// just copy, rather than multiplying by 1
- std::copy(
- LargePowerOfFiveData(big_power),
- LargePowerOfFiveData(big_power) + LargePowerOfFiveSize(big_power),
- answer.words_);
+ std::copy_n(LargePowerOfFiveData(big_power),
+ LargePowerOfFiveSize(big_power), answer.words_);
answer.size_ = LargePowerOfFiveSize(big_power);
first_pass = false;
} else {
@@ -341,8 +339,8 @@
std::string result;
// Build result in reverse order
while (copy.size() > 0) {
- int next_digit = copy.DivMod<10>();
- result.push_back('0' + next_digit);
+ uint32_t next_digit = copy.DivMod<10>();
+ result.push_back('0' + static_cast<char>(next_digit));
}
if (result.empty()) {
result.push_back('0');
diff --git a/absl/strings/internal/charconv_bigint.h b/absl/strings/internal/charconv_bigint.h
index 8f70297..5c0c375 100644
--- a/absl/strings/internal/charconv_bigint.h
+++ b/absl/strings/internal/charconv_bigint.h
@@ -92,7 +92,7 @@
// numbers with this many decimal digits or fewer are representable by this
// type.
//
- // Analagous to std::numeric_limits<BigUnsigned>::digits10.
+ // Analogous to std::numeric_limits<BigUnsigned>::digits10.
static constexpr int Digits10() {
// 9975007/1035508 is very slightly less than log10(2**32).
return static_cast<uint64_t>(max_words) * 9975007 / 1035508;
@@ -121,7 +121,7 @@
++size_;
}
}
- std::fill(words_, words_ + word_shift, 0u);
+ std::fill_n(words_, word_shift, 0u);
}
}
@@ -197,7 +197,7 @@
}
void SetToZero() {
- std::fill(words_, words_ + size_, 0u);
+ std::fill_n(words_, size_, 0u);
size_ = 0;
}
diff --git a/absl/strings/internal/charconv_parse.cc b/absl/strings/internal/charconv_parse.cc
index d29acaf..98823de 100644
--- a/absl/strings/internal/charconv_parse.cc
+++ b/absl/strings/internal/charconv_parse.cc
@@ -190,11 +190,11 @@
template <>
unsigned ToDigit<10>(char ch) {
- return ch - '0';
+ return static_cast<unsigned>(ch - '0');
}
template <>
unsigned ToDigit<16>(char ch) {
- return kAsciiToInt[static_cast<unsigned char>(ch)];
+ return static_cast<unsigned>(kAsciiToInt[static_cast<unsigned char>(ch)]);
}
template <>
diff --git a/absl/strings/internal/charconv_parse_test.cc b/absl/strings/internal/charconv_parse_test.cc
index bc2d111..2b7b082 100644
--- a/absl/strings/internal/charconv_parse_test.cc
+++ b/absl/strings/internal/charconv_parse_test.cc
@@ -19,7 +19,7 @@
#include "gmock/gmock.h"
#include "gtest/gtest.h"
-#include "absl/base/internal/raw_logging.h"
+#include "absl/log/check.h"
#include "absl/strings/str_cat.h"
using absl::chars_format;
@@ -56,14 +56,14 @@
begin_subrange = static_cast<int>(open_bracket_pos);
s.replace(open_bracket_pos, 1, "");
std::string::size_type close_bracket_pos = s.find(']');
- ABSL_RAW_CHECK(close_bracket_pos != absl::string_view::npos,
- "Test input contains [ without matching ]");
+ CHECK_NE(close_bracket_pos, absl::string_view::npos)
+ << "Test input contains [ without matching ]";
end_subrange = static_cast<int>(close_bracket_pos);
s.replace(close_bracket_pos, 1, "");
}
const std::string::size_type expected_characters_matched = s.find('$');
- ABSL_RAW_CHECK(expected_characters_matched != std::string::npos,
- "Input string must contain $");
+ CHECK_NE(expected_characters_matched, std::string::npos)
+ << "Input string must contain $";
s.replace(expected_characters_matched, 1, "");
ParsedFloat parsed =
diff --git a/absl/strings/internal/cord_data_edge.h b/absl/strings/internal/cord_data_edge.h
new file mode 100644
index 0000000..e18b33e
--- /dev/null
+++ b/absl/strings/internal/cord_data_edge.h
@@ -0,0 +1,63 @@
+// Copyright 2022 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef ABSL_STRINGS_INTERNAL_CORD_DATA_EDGE_H_
+#define ABSL_STRINGS_INTERNAL_CORD_DATA_EDGE_H_
+
+#include <cassert>
+#include <cstddef>
+
+#include "absl/base/config.h"
+#include "absl/strings/internal/cord_internal.h"
+#include "absl/strings/internal/cord_rep_flat.h"
+#include "absl/strings/string_view.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace cord_internal {
+
+// Returns true if the provided rep is a FLAT, EXTERNAL or a SUBSTRING node
+// holding a FLAT or EXTERNAL child rep. Requires `rep != nullptr`.
+inline bool IsDataEdge(const CordRep* edge) {
+ assert(edge != nullptr);
+
+ // The fast path is that `edge` is an EXTERNAL or FLAT node, making the below
+ // if a single, well predicted branch. We then repeat the FLAT or EXTERNAL
+ // check in the slow path of the SUBSTRING check to optimize for the hot path.
+ if (edge->tag == EXTERNAL || edge->tag >= FLAT) return true;
+ if (edge->tag == SUBSTRING) edge = edge->substring()->child;
+ return edge->tag == EXTERNAL || edge->tag >= FLAT;
+}
+
+// Returns the `absl::string_view` data reference for the provided data edge.
+// Requires 'IsDataEdge(edge) == true`.
+inline absl::string_view EdgeData(const CordRep* edge) {
+ assert(IsDataEdge(edge));
+
+ size_t offset = 0;
+ const size_t length = edge->length;
+ if (edge->IsSubstring()) {
+ offset = edge->substring()->start;
+ edge = edge->substring()->child;
+ }
+ return edge->tag >= FLAT
+ ? absl::string_view{edge->flat()->Data() + offset, length}
+ : absl::string_view{edge->external()->base + offset, length};
+}
+
+} // namespace cord_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_STRINGS_INTERNAL_CORD_DATA_EDGE_H_
diff --git a/absl/strings/internal/cord_data_edge_test.cc b/absl/strings/internal/cord_data_edge_test.cc
new file mode 100644
index 0000000..8fce3bc
--- /dev/null
+++ b/absl/strings/internal/cord_data_edge_test.cc
@@ -0,0 +1,130 @@
+// Copyright 2022 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/strings/internal/cord_data_edge.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/strings/internal/cord_internal.h"
+#include "absl/strings/internal/cord_rep_test_util.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace cord_internal {
+namespace {
+
+using ::absl::cordrep_testing::MakeExternal;
+using ::absl::cordrep_testing::MakeFlat;
+using ::absl::cordrep_testing::MakeSubstring;
+
+TEST(CordDataEdgeTest, IsDataEdgeOnFlat) {
+ CordRep* rep = MakeFlat("Lorem ipsum dolor sit amet, consectetur ...");
+ EXPECT_TRUE(IsDataEdge(rep));
+ CordRep::Unref(rep);
+}
+
+TEST(CordDataEdgeTest, IsDataEdgeOnExternal) {
+ CordRep* rep = MakeExternal("Lorem ipsum dolor sit amet, consectetur ...");
+ EXPECT_TRUE(IsDataEdge(rep));
+ CordRep::Unref(rep);
+}
+
+TEST(CordDataEdgeTest, IsDataEdgeOnSubstringOfFlat) {
+ CordRep* rep = MakeFlat("Lorem ipsum dolor sit amet, consectetur ...");
+ CordRep* substr = MakeSubstring(1, 20, rep);
+ EXPECT_TRUE(IsDataEdge(substr));
+ CordRep::Unref(substr);
+}
+
+TEST(CordDataEdgeTest, IsDataEdgeOnSubstringOfExternal) {
+ CordRep* rep = MakeExternal("Lorem ipsum dolor sit amet, consectetur ...");
+ CordRep* substr = MakeSubstring(1, 20, rep);
+ EXPECT_TRUE(IsDataEdge(substr));
+ CordRep::Unref(substr);
+}
+
+TEST(CordDataEdgeTest, IsDataEdgeOnBtree) {
+ CordRep* rep = MakeFlat("Lorem ipsum dolor sit amet, consectetur ...");
+ CordRepBtree* tree = CordRepBtree::New(rep);
+ EXPECT_FALSE(IsDataEdge(tree));
+ CordRep::Unref(tree);
+}
+
+TEST(CordDataEdgeTest, IsDataEdgeOnBadSubstr) {
+ CordRep* rep = MakeFlat("Lorem ipsum dolor sit amet, consectetur ...");
+ CordRep* substr = MakeSubstring(1, 18, MakeSubstring(1, 20, rep));
+ EXPECT_FALSE(IsDataEdge(substr));
+ CordRep::Unref(substr);
+}
+
+TEST(CordDataEdgeTest, EdgeDataOnFlat) {
+ absl::string_view value = "Lorem ipsum dolor sit amet, consectetur ...";
+ CordRep* rep = MakeFlat(value);
+ EXPECT_EQ(EdgeData(rep), value);
+ CordRep::Unref(rep);
+}
+
+TEST(CordDataEdgeTest, EdgeDataOnExternal) {
+ absl::string_view value = "Lorem ipsum dolor sit amet, consectetur ...";
+ CordRep* rep = MakeExternal(value);
+ EXPECT_EQ(EdgeData(rep), value);
+ CordRep::Unref(rep);
+}
+
+TEST(CordDataEdgeTest, EdgeDataOnSubstringOfFlat) {
+ absl::string_view value = "Lorem ipsum dolor sit amet, consectetur ...";
+ CordRep* rep = MakeFlat(value);
+ CordRep* substr = MakeSubstring(1, 20, rep);
+ EXPECT_EQ(EdgeData(substr), value.substr(1, 20));
+ CordRep::Unref(substr);
+}
+
+TEST(CordDataEdgeTest, EdgeDataOnSubstringOfExternal) {
+ absl::string_view value = "Lorem ipsum dolor sit amet, consectetur ...";
+ CordRep* rep = MakeExternal(value);
+ CordRep* substr = MakeSubstring(1, 20, rep);
+ EXPECT_EQ(EdgeData(substr), value.substr(1, 20));
+ CordRep::Unref(substr);
+}
+
+#if defined(GTEST_HAS_DEATH_TEST) && !defined(NDEBUG)
+
+TEST(CordDataEdgeTest, IsDataEdgeOnNullPtr) {
+ EXPECT_DEATH(IsDataEdge(nullptr), ".*");
+}
+
+TEST(CordDataEdgeTest, EdgeDataOnNullPtr) {
+ EXPECT_DEATH(EdgeData(nullptr), ".*");
+}
+
+TEST(CordDataEdgeTest, EdgeDataOnBtree) {
+ CordRep* rep = MakeFlat("Lorem ipsum dolor sit amet, consectetur ...");
+ CordRepBtree* tree = CordRepBtree::New(rep);
+ EXPECT_DEATH(EdgeData(tree), ".*");
+ CordRep::Unref(tree);
+}
+
+TEST(CordDataEdgeTest, EdgeDataOnBadSubstr) {
+ CordRep* rep = MakeFlat("Lorem ipsum dolor sit amet, consectetur ...");
+ CordRep* substr = MakeSubstring(1, 18, MakeSubstring(1, 20, rep));
+ EXPECT_DEATH(EdgeData(substr), ".*");
+ CordRep::Unref(substr);
+}
+
+#endif // GTEST_HAS_DEATH_TEST && !NDEBUG
+
+} // namespace
+} // namespace cord_internal
+ABSL_NAMESPACE_END
+} // namespace absl
diff --git a/absl/strings/internal/cord_internal.cc b/absl/strings/internal/cord_internal.cc
index f79cb62..57d9d38 100644
--- a/absl/strings/internal/cord_internal.cc
+++ b/absl/strings/internal/cord_internal.cc
@@ -17,68 +17,50 @@
#include <cassert>
#include <memory>
+#include "absl/base/internal/raw_logging.h"
#include "absl/container/inlined_vector.h"
#include "absl/strings/internal/cord_rep_btree.h"
+#include "absl/strings/internal/cord_rep_crc.h"
#include "absl/strings/internal/cord_rep_flat.h"
-#include "absl/strings/internal/cord_rep_ring.h"
+#include "absl/strings/str_cat.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace cord_internal {
-ABSL_CONST_INIT std::atomic<bool> cord_btree_enabled(kCordEnableBtreeDefault);
-ABSL_CONST_INIT std::atomic<bool> cord_ring_buffer_enabled(
- kCordEnableRingBufferDefault);
ABSL_CONST_INIT std::atomic<bool> shallow_subcords_enabled(
kCordShallowSubcordsDefault);
+void LogFatalNodeType(CordRep* rep) {
+ ABSL_INTERNAL_LOG(FATAL, absl::StrCat("Unexpected node type: ",
+ static_cast<int>(rep->tag)));
+}
+
void CordRep::Destroy(CordRep* rep) {
assert(rep != nullptr);
- absl::InlinedVector<CordRep*, Constants::kInlinedVectorSize> pending;
while (true) {
assert(!rep->refcount.IsImmortal());
- if (rep->tag == CONCAT) {
- CordRepConcat* rep_concat = rep->concat();
- CordRep* right = rep_concat->right;
- if (!right->refcount.Decrement()) {
- pending.push_back(right);
- }
- CordRep* left = rep_concat->left;
- delete rep_concat;
- rep = nullptr;
- if (!left->refcount.Decrement()) {
- rep = left;
- continue;
- }
- } else if (rep->tag == BTREE) {
+ if (rep->tag == BTREE) {
CordRepBtree::Destroy(rep->btree());
- rep = nullptr;
- } else if (rep->tag == RING) {
- CordRepRing::Destroy(rep->ring());
- rep = nullptr;
+ return;
} else if (rep->tag == EXTERNAL) {
CordRepExternal::Delete(rep);
- rep = nullptr;
+ return;
} else if (rep->tag == SUBSTRING) {
CordRepSubstring* rep_substring = rep->substring();
- CordRep* child = rep_substring->child;
+ rep = rep_substring->child;
delete rep_substring;
- rep = nullptr;
- if (!child->refcount.Decrement()) {
- rep = child;
- continue;
+ if (rep->refcount.Decrement()) {
+ return;
}
+ } else if (rep->tag == CRC) {
+ CordRepCrc::Destroy(rep->crc());
+ return;
} else {
+ assert(rep->IsFlat());
CordRepFlat::Delete(rep);
- rep = nullptr;
- }
-
- if (!pending.empty()) {
- rep = pending.back();
- pending.pop_back();
- } else {
- break;
+ return;
}
}
}
diff --git a/absl/strings/internal/cord_internal.h b/absl/strings/internal/cord_internal.h
index 371a7f9..a52deea 100644
--- a/absl/strings/internal/cord_internal.h
+++ b/absl/strings/internal/cord_internal.h
@@ -21,39 +21,49 @@
#include <cstdint>
#include <type_traits>
+#include "absl/base/attributes.h"
#include "absl/base/config.h"
#include "absl/base/internal/endian.h"
#include "absl/base/internal/invoke.h"
#include "absl/base/optimization.h"
#include "absl/container/internal/compressed_tuple.h"
+#include "absl/container/internal/container_memory.h"
#include "absl/meta/type_traits.h"
#include "absl/strings/string_view.h"
+// We can only add poisoning if we can detect consteval executions.
+#if defined(ABSL_HAVE_CONSTANT_EVALUATED) && \
+ (defined(ABSL_HAVE_ADDRESS_SANITIZER) || \
+ defined(ABSL_HAVE_MEMORY_SANITIZER))
+#define ABSL_INTERNAL_CORD_HAVE_SANITIZER 1
+#endif
+
+#define ABSL_CORD_INTERNAL_NO_SANITIZE \
+ ABSL_ATTRIBUTE_NO_SANITIZE_ADDRESS ABSL_ATTRIBUTE_NO_SANITIZE_MEMORY
+
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace cord_internal {
+// The overhead of a vtable is too much for Cord, so we roll our own subclasses
+// using only a single byte to differentiate classes from each other - the "tag"
+// byte. Define the subclasses first so we can provide downcasting helper
+// functions in the base class.
+struct CordRep;
+struct CordRepConcat;
+struct CordRepExternal;
+struct CordRepFlat;
+struct CordRepSubstring;
+struct CordRepCrc;
+class CordRepBtree;
+
class CordzInfo;
// Default feature enable states for cord ring buffers
-enum CordFeatureDefaults {
- kCordEnableBtreeDefault = false,
- kCordEnableRingBufferDefault = false,
- kCordShallowSubcordsDefault = false
-};
+enum CordFeatureDefaults { kCordShallowSubcordsDefault = false };
-extern std::atomic<bool> cord_btree_enabled;
-extern std::atomic<bool> cord_ring_buffer_enabled;
extern std::atomic<bool> shallow_subcords_enabled;
-inline void enable_cord_btree(bool enable) {
- cord_btree_enabled.store(enable, std::memory_order_relaxed);
-}
-
-inline void enable_cord_ring_buffer(bool enable) {
- cord_ring_buffer_enabled.store(enable, std::memory_order_relaxed);
-}
-
inline void enable_shallow_subcords(bool enable) {
shallow_subcords_enabled.store(enable, std::memory_order_relaxed);
}
@@ -74,12 +84,64 @@
kMaxBytesToCopy = 511
};
-// Wraps std::atomic for reference counting.
-class Refcount {
+// Emits a fatal error "Unexpected node type: xyz" and aborts the program.
+ABSL_ATTRIBUTE_NORETURN void LogFatalNodeType(CordRep* rep);
+
+// Fast implementation of memmove for up to 15 bytes. This implementation is
+// safe for overlapping regions. If nullify_tail is true, the destination is
+// padded with '\0' up to 15 bytes.
+template <bool nullify_tail = false>
+inline void SmallMemmove(char* dst, const char* src, size_t n) {
+ if (n >= 8) {
+ assert(n <= 15);
+ uint64_t buf1;
+ uint64_t buf2;
+ memcpy(&buf1, src, 8);
+ memcpy(&buf2, src + n - 8, 8);
+ if (nullify_tail) {
+ memset(dst + 7, 0, 8);
+ }
+ // GCC 12 has a false-positive -Wstringop-overflow warning here.
+#if ABSL_INTERNAL_HAVE_MIN_GNUC_VERSION(12, 0)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wstringop-overflow"
+#endif
+ memcpy(dst, &buf1, 8);
+ memcpy(dst + n - 8, &buf2, 8);
+#if ABSL_INTERNAL_HAVE_MIN_GNUC_VERSION(12, 0)
+#pragma GCC diagnostic pop
+#endif
+ } else if (n >= 4) {
+ uint32_t buf1;
+ uint32_t buf2;
+ memcpy(&buf1, src, 4);
+ memcpy(&buf2, src + n - 4, 4);
+ if (nullify_tail) {
+ memset(dst + 4, 0, 4);
+ memset(dst + 7, 0, 8);
+ }
+ memcpy(dst, &buf1, 4);
+ memcpy(dst + n - 4, &buf2, 4);
+ } else {
+ if (n != 0) {
+ dst[0] = src[0];
+ dst[n / 2] = src[n / 2];
+ dst[n - 1] = src[n - 1];
+ }
+ if (nullify_tail) {
+ memset(dst + 7, 0, 8);
+ memset(dst + n, 0, 8);
+ }
+ }
+}
+
+// Compact class for tracking the reference count and state flags for CordRep
+// instances. Data is stored in an atomic int32_t for compactness and speed.
+class RefcountAndFlags {
public:
- constexpr Refcount() : count_{kRefIncrement} {}
+ constexpr RefcountAndFlags() : count_{kRefIncrement} {}
struct Immortal {};
- explicit constexpr Refcount(Immortal) : count_(kImmortalTag) {}
+ explicit constexpr RefcountAndFlags(Immortal) : count_(kImmortalFlag) {}
// Increments the reference count. Imposes no memory ordering.
inline void Increment() {
@@ -92,10 +154,10 @@
// Returns false if there are no references outstanding; true otherwise.
// Inserts barriers to ensure that state written before this method returns
// false will be visible to a thread that just observed this method returning
- // false.
+ // false. Always returns false when the immortal bit is set.
inline bool Decrement() {
int32_t refcount = count_.load(std::memory_order_acquire);
- assert(refcount > 0 || refcount & kImmortalTag);
+ assert(refcount > 0 || refcount & kImmortalFlag);
return refcount != kRefIncrement &&
count_.fetch_sub(kRefIncrement, std::memory_order_acq_rel) !=
kRefIncrement;
@@ -105,13 +167,14 @@
inline bool DecrementExpectHighRefcount() {
int32_t refcount =
count_.fetch_sub(kRefIncrement, std::memory_order_acq_rel);
- assert(refcount > 0 || refcount & kImmortalTag);
+ assert(refcount > 0 || refcount & kImmortalFlag);
return refcount != kRefIncrement;
}
// Returns the current reference count using acquire semantics.
- inline int32_t Get() const {
- return count_.load(std::memory_order_acquire) >> kImmortalShift;
+ inline size_t Get() const {
+ return static_cast<size_t>(count_.load(std::memory_order_acquire) >>
+ kNumFlags);
}
// Returns whether the atomic integer is 1.
@@ -121,81 +184,84 @@
// This call performs the test for a reference count of one, and
// performs the memory barrier needed for the owning thread
// to act on the object, knowing that it has exclusive access to the
- // object.
+ // object. Always returns false when the immortal bit is set.
inline bool IsOne() {
return count_.load(std::memory_order_acquire) == kRefIncrement;
}
bool IsImmortal() const {
- return (count_.load(std::memory_order_relaxed) & kImmortalTag) != 0;
+ return (count_.load(std::memory_order_relaxed) & kImmortalFlag) != 0;
}
private:
- // We reserve the bottom bit to tag a reference count as immortal.
- // By making it `1` we ensure that we never reach `0` when adding/subtracting
- // `2`, thus it never looks as if it should be destroyed.
- // These are used for the StringConstant constructor where we do not increase
- // the refcount at construction time (due to constinit requirements) but we
- // will still decrease it at destruction time to avoid branching on Unref.
- enum {
- kImmortalShift = 1,
- kRefIncrement = 1 << kImmortalShift,
- kImmortalTag = kRefIncrement - 1
+ // We reserve the bottom bit for flag.
+ // kImmortalBit indicates that this entity should never be collected; it is
+ // used for the StringConstant constructor to avoid collecting immutable
+ // constant cords.
+ enum Flags {
+ kNumFlags = 1,
+
+ kImmortalFlag = 0x1,
+ kRefIncrement = (1 << kNumFlags),
};
std::atomic<int32_t> count_;
};
-// The overhead of a vtable is too much for Cord, so we roll our own subclasses
-// using only a single byte to differentiate classes from each other - the "tag"
-// byte. Define the subclasses first so we can provide downcasting helper
-// functions in the base class.
-
-struct CordRepConcat;
-struct CordRepExternal;
-struct CordRepFlat;
-struct CordRepSubstring;
-class CordRepRing;
-class CordRepBtree;
-
// Various representations that we allow
enum CordRepKind {
- CONCAT = 0,
+ UNUSED_0 = 0,
SUBSTRING = 1,
- BTREE = 2,
- RING = 3,
- EXTERNAL = 4,
+ CRC = 2,
+ BTREE = 3,
+ UNUSED_4 = 4,
+ EXTERNAL = 5,
// We have different tags for different sized flat arrays,
- // starting with FLAT, and limited to MAX_FLAT_TAG. The 225 value is based on
- // the current 'size to tag' encoding of 8 / 32 bytes. If a new tag is needed
- // in the future, then 'FLAT' and 'MAX_FLAT_TAG' should be adjusted as well
- // as the Tag <---> Size logic so that FLAT stil represents the minimum flat
- // allocation size. (32 bytes as of now).
- FLAT = 5,
- MAX_FLAT_TAG = 225
+ // starting with FLAT, and limited to MAX_FLAT_TAG. The below values map to an
+ // allocated range of 32 bytes to 256 KB. The current granularity is:
+ // - 8 byte granularity for flat sizes in [32 - 512]
+ // - 64 byte granularity for flat sizes in (512 - 8KiB]
+ // - 4KiB byte granularity for flat sizes in (8KiB, 256 KiB]
+ // If a new tag is needed in the future, then 'FLAT' and 'MAX_FLAT_TAG' should
+ // be adjusted as well as the Tag <---> Size mapping logic so that FLAT still
+ // represents the minimum flat allocation size. (32 bytes as of now).
+ FLAT = 6,
+ MAX_FLAT_TAG = 248
};
// There are various locations where we want to check if some rep is a 'plain'
// data edge, i.e. an external or flat rep. By having FLAT == EXTERNAL + 1, we
// can perform this check in a single branch as 'tag >= EXTERNAL'
-// Likewise, we have some locations where we check for 'ring or external/flat',
-// so likewise align RING to EXTERNAL.
// Note that we can leave this optimization to the compiler. The compiler will
// DTRT when it sees a condition like `tag == EXTERNAL || tag >= FLAT`.
-static_assert(RING == BTREE + 1, "BTREE and RING not consecutive");
-static_assert(EXTERNAL == RING + 1, "BTREE and EXTERNAL not consecutive");
static_assert(FLAT == EXTERNAL + 1, "EXTERNAL and FLAT not consecutive");
struct CordRep {
+ // Result from an `extract edge` operation. Contains the (possibly changed)
+ // tree node as well as the extracted edge, or {tree, nullptr} if no edge
+ // could be extracted.
+ // On success, the returned `tree` value is null if `extracted` was the only
+ // data edge inside the tree, a data edge if there were only two data edges in
+ // the tree, or the (possibly new / smaller) remaining tree with the extracted
+ // data edge removed.
+ struct ExtractResult {
+ CordRep* tree;
+ CordRep* extracted;
+ };
+
CordRep() = default;
- constexpr CordRep(Refcount::Immortal immortal, size_t l)
+ constexpr CordRep(RefcountAndFlags::Immortal immortal, size_t l)
: length(l), refcount(immortal), tag(EXTERNAL), storage{} {}
// The following three fields have to be less than 32 bytes since
- // that is the smallest supported flat node size.
+ // that is the smallest supported flat node size. Some code optimizations rely
+ // on the specific layout of these fields. Notably: the non-trivial field
+ // `refcount` being preceded by `length`, and being tailed by POD data
+ // members only.
+ // # LINT.IfChange
size_t length;
- Refcount refcount;
+ RefcountAndFlags refcount;
// If tag < FLAT, it represents CordRepKind and indicates the type of node.
// Otherwise, the node type is CordRepFlat and the tag is the encoded size.
uint8_t tag;
@@ -209,18 +275,23 @@
// allocate room for these in the derived class, as not all compilers reuse
// padding space from the base class (clang and gcc do, MSVC does not, etc)
uint8_t storage[3];
+ // # LINT.ThenChange(cord_rep_btree.h:copy_raw)
- inline CordRepRing* ring();
- inline const CordRepRing* ring() const;
- inline CordRepConcat* concat();
- inline const CordRepConcat* concat() const;
+ // Returns true if this instance's tag matches the requested type.
+ constexpr bool IsSubstring() const { return tag == SUBSTRING; }
+ constexpr bool IsCrc() const { return tag == CRC; }
+ constexpr bool IsExternal() const { return tag == EXTERNAL; }
+ constexpr bool IsFlat() const { return tag >= FLAT; }
+ constexpr bool IsBtree() const { return tag == BTREE; }
+
inline CordRepSubstring* substring();
inline const CordRepSubstring* substring() const;
+ inline CordRepCrc* crc();
+ inline const CordRepCrc* crc() const;
inline CordRepExternal* external();
inline const CordRepExternal* external() const;
inline CordRepFlat* flat();
inline const CordRepFlat* flat() const;
-
inline CordRepBtree* btree();
inline const CordRepBtree* btree() const;
@@ -239,17 +310,23 @@
static inline void Unref(CordRep* rep);
};
-struct CordRepConcat : public CordRep {
- CordRep* left;
- CordRep* right;
-
- uint8_t depth() const { return storage[0]; }
- void set_depth(uint8_t depth) { storage[0] = depth; }
-};
-
struct CordRepSubstring : public CordRep {
size_t start; // Starting offset of substring in child
CordRep* child;
+
+ // Creates a substring on `child`, adopting a reference on `child`.
+ // Requires `child` to be either a flat or external node, and `pos` and `n` to
+ // form a non-empty partial sub range of `'child`, i.e.:
+ // `n > 0 && n < length && n + pos <= length`
+ static inline CordRepSubstring* Create(CordRep* child, size_t pos, size_t n);
+
+ // Creates a substring of `rep`. Does not adopt a reference on `rep`.
+ // Requires `IsDataEdge(rep) && n > 0 && pos + n <= rep->length`.
+ // If `n == rep->length` then this method returns `CordRep::Ref(rep)`
+ // If `rep` is a substring of a flat or external node, then this method will
+ // return a new substring of that flat or external node with `pos` adjusted
+ // with the original `start` position.
+ static inline CordRep* Substring(CordRep* rep, size_t pos, size_t n);
};
// Type for function pointer that will invoke the releaser function and also
@@ -262,7 +339,7 @@
struct CordRepExternal : public CordRep {
CordRepExternal() = default;
explicit constexpr CordRepExternal(absl::string_view str)
- : CordRep(Refcount::Immortal{}, str.size()),
+ : CordRep(RefcountAndFlags::Immortal{}, str.size()),
base(str.data()),
releaser_invoker(nullptr) {}
@@ -271,22 +348,23 @@
ExternalReleaserInvoker releaser_invoker;
// Deletes (releases) the external rep.
- // Requires rep != nullptr and rep->tag == EXTERNAL
+ // Requires rep != nullptr and rep->IsExternal()
static void Delete(CordRep* rep);
};
-struct Rank1 {};
-struct Rank0 : Rank1 {};
+// Use go/ranked-overloads for dispatching.
+struct Rank0 {};
+struct Rank1 : Rank0 {};
template <typename Releaser, typename = ::absl::base_internal::invoke_result_t<
Releaser, absl::string_view>>
-void InvokeReleaser(Rank0, Releaser&& releaser, absl::string_view data) {
+void InvokeReleaser(Rank1, Releaser&& releaser, absl::string_view data) {
::absl::base_internal::invoke(std::forward<Releaser>(releaser), data);
}
template <typename Releaser,
typename = ::absl::base_internal::invoke_result_t<Releaser>>
-void InvokeReleaser(Rank1, Releaser&& releaser, absl::string_view) {
+void InvokeReleaser(Rank0, Releaser&& releaser, absl::string_view) {
::absl::base_internal::invoke(std::forward<Releaser>(releaser));
}
@@ -304,7 +382,7 @@
}
~CordRepExternalImpl() {
- InvokeReleaser(Rank0{}, std::move(this->template get<0>()),
+ InvokeReleaser(Rank1{}, std::move(this->template get<0>()),
absl::string_view(base, length));
}
@@ -313,8 +391,49 @@
}
};
+inline CordRepSubstring* CordRepSubstring::Create(CordRep* child, size_t pos,
+ size_t n) {
+ assert(child != nullptr);
+ assert(n > 0);
+ assert(n < child->length);
+ assert(pos < child->length);
+ assert(n <= child->length - pos);
+
+ // TODO(b/217376272): Harden internal logic.
+ // Move to strategical places inside the Cord logic and make this an assert.
+ if (ABSL_PREDICT_FALSE(!(child->IsExternal() || child->IsFlat()))) {
+ LogFatalNodeType(child);
+ }
+
+ CordRepSubstring* rep = new CordRepSubstring();
+ rep->length = n;
+ rep->tag = SUBSTRING;
+ rep->start = pos;
+ rep->child = child;
+ return rep;
+}
+
+inline CordRep* CordRepSubstring::Substring(CordRep* rep, size_t pos,
+ size_t n) {
+ assert(rep != nullptr);
+ assert(n != 0);
+ assert(pos < rep->length);
+ assert(n <= rep->length - pos);
+ if (n == rep->length) return CordRep::Ref(rep);
+ if (rep->IsSubstring()) {
+ pos += rep->substring()->start;
+ rep = rep->substring()->child;
+ }
+ CordRepSubstring* substr = new CordRepSubstring();
+ substr->length = n;
+ substr->tag = SUBSTRING;
+ substr->start = pos;
+ substr->child = CordRep::Ref(rep);
+ return substr;
+}
+
inline void CordRepExternal::Delete(CordRep* rep) {
- assert(rep != nullptr && rep->tag == EXTERNAL);
+ assert(rep != nullptr && rep->IsExternal());
auto* rep_external = static_cast<CordRepExternal*>(rep);
assert(rep_external->releaser_invoker != nullptr);
rep_external->releaser_invoker(rep_external);
@@ -326,7 +445,8 @@
};
template <typename Str>
-CordRepExternal ConstInitExternalStorage<Str>::value(Str::value);
+ABSL_CONST_INIT CordRepExternal
+ ConstInitExternalStorage<Str>::value(Str::value);
enum {
kMaxInline = 15,
@@ -336,25 +456,25 @@
return pos < data.size() ? data[pos] : '\0';
}
-// We store cordz_info as 64 bit pointer value in big endian format. This
-// guarantees that the least significant byte of cordz_info matches the last
-// byte of the inline data representation in as_chars_, which holds the inlined
+// We store cordz_info as 64 bit pointer value in little endian format. This
+// guarantees that the least significant byte of cordz_info matches the first
+// byte of the inline data representation in `data`, which holds the inlined
// size or the 'is_tree' bit.
using cordz_info_t = int64_t;
// Assert that the `cordz_info` pointer value perfectly overlaps the last half
-// of `as_chars_` and can hold a pointer value.
+// of `data` and can hold a pointer value.
static_assert(sizeof(cordz_info_t) * 2 == kMaxInline + 1, "");
static_assert(sizeof(cordz_info_t) >= sizeof(intptr_t), "");
-// BigEndianByte() creates a big endian representation of 'value', i.e.: a big
-// endian value where the last byte in the host's representation holds 'value`,
-// with all other bytes being 0.
-static constexpr cordz_info_t BigEndianByte(unsigned char value) {
+// LittleEndianByte() creates a little endian representation of 'value', i.e.:
+// a little endian value where the first byte in the host's representation
+// holds 'value`, with all other bytes being 0.
+static constexpr cordz_info_t LittleEndianByte(unsigned char value) {
#if defined(ABSL_IS_BIG_ENDIAN)
- return value;
-#else
return static_cast<cordz_info_t>(value) << ((sizeof(cordz_info_t) - 1) * 8);
+#else
+ return value;
#endif
}
@@ -363,38 +483,81 @@
// DefaultInitType forces the use of the default initialization constructor.
enum DefaultInitType { kDefaultInit };
- // kNullCordzInfo holds the big endian representation of intptr_t(1)
+ // kNullCordzInfo holds the little endian representation of intptr_t(1)
// This is the 'null' / initial value of 'cordz_info'. The null value
// is specifically big endian 1 as with 64-bit pointers, the last
// byte of cordz_info overlaps with the last byte holding the tag.
- static constexpr cordz_info_t kNullCordzInfo = BigEndianByte(1);
+ static constexpr cordz_info_t kNullCordzInfo = LittleEndianByte(1);
- constexpr InlineData() : as_chars_{0} {}
- explicit InlineData(DefaultInitType) {}
- explicit constexpr InlineData(CordRep* rep) : as_tree_(rep) {}
- explicit constexpr InlineData(absl::string_view chars)
- : as_chars_{
- GetOrNull(chars, 0), GetOrNull(chars, 1),
- GetOrNull(chars, 2), GetOrNull(chars, 3),
- GetOrNull(chars, 4), GetOrNull(chars, 5),
- GetOrNull(chars, 6), GetOrNull(chars, 7),
- GetOrNull(chars, 8), GetOrNull(chars, 9),
- GetOrNull(chars, 10), GetOrNull(chars, 11),
- GetOrNull(chars, 12), GetOrNull(chars, 13),
- GetOrNull(chars, 14), static_cast<char>((chars.size() << 1))} {}
+ // kTagOffset contains the offset of the control byte / tag. This constant is
+ // intended mostly for debugging purposes: do not remove this constant as it
+ // is actively inspected and used by gdb pretty printing code.
+ static constexpr size_t kTagOffset = 0;
+
+ // Implement `~InlineData()` conditionally: we only need this destructor to
+ // unpoison poisoned instances under *SAN, and it will only compile correctly
+ // if the current compiler supports `absl::is_constant_evaluated()`.
+#ifdef ABSL_INTERNAL_CORD_HAVE_SANITIZER
+ ~InlineData() noexcept { unpoison(); }
+#endif
+
+ constexpr InlineData() noexcept { poison_this(); }
+
+ explicit InlineData(DefaultInitType) noexcept : rep_(kDefaultInit) {
+ poison_this();
+ }
+
+ explicit InlineData(CordRep* rep) noexcept : rep_(rep) {
+ ABSL_ASSERT(rep != nullptr);
+ }
+
+ // Explicit constexpr constructor to create a constexpr InlineData
+ // value. Creates an inlined SSO value if `rep` is null, otherwise
+ // creates a tree instance value.
+ constexpr InlineData(absl::string_view sv, CordRep* rep) noexcept
+ : rep_(rep ? Rep(rep) : Rep(sv)) {
+ poison();
+ }
+
+ constexpr InlineData(const InlineData& rhs) noexcept;
+ InlineData& operator=(const InlineData& rhs) noexcept;
+ friend void swap(InlineData& lhs, InlineData& rhs) noexcept;
+
+ friend bool operator==(const InlineData& lhs, const InlineData& rhs) {
+#ifdef ABSL_INTERNAL_CORD_HAVE_SANITIZER
+ const Rep l = lhs.rep_.SanitizerSafeCopy();
+ const Rep r = rhs.rep_.SanitizerSafeCopy();
+ return memcmp(&l, &r, sizeof(l)) == 0;
+#else
+ return memcmp(&lhs, &rhs, sizeof(lhs)) == 0;
+#endif
+ }
+ friend bool operator!=(const InlineData& lhs, const InlineData& rhs) {
+ return !operator==(lhs, rhs);
+ }
+
+ // Poisons the unused inlined SSO data if the current instance
+ // is inlined, else un-poisons the entire instance.
+ constexpr void poison();
+
+ // Un-poisons this instance.
+ constexpr void unpoison();
+
+ // Poisons the current instance. This is used on default initialization.
+ constexpr void poison_this();
// Returns true if the current instance is empty.
// The 'empty value' is an inlined data value of zero length.
- bool is_empty() const { return tag() == 0; }
+ bool is_empty() const { return rep_.tag() == 0; }
// Returns true if the current instance holds a tree value.
- bool is_tree() const { return (tag() & 1) != 0; }
+ bool is_tree() const { return (rep_.tag() & 1) != 0; }
// Returns true if the current instance holds a cordz_info value.
// Requires the current instance to hold a tree value.
bool is_profiled() const {
assert(is_tree());
- return as_tree_.cordz_info != kNullCordzInfo;
+ return rep_.cordz_info() != kNullCordzInfo;
}
// Returns true if either of the provided instances hold a cordz_info value.
@@ -403,7 +566,7 @@
static bool is_either_profiled(const InlineData& data1,
const InlineData& data2) {
assert(data1.is_tree() && data2.is_tree());
- return (data1.as_tree_.cordz_info | data2.as_tree_.cordz_info) !=
+ return (data1.rep_.cordz_info() | data2.rep_.cordz_info()) !=
kNullCordzInfo;
}
@@ -412,8 +575,8 @@
// Requires the current instance to hold a tree value.
CordzInfo* cordz_info() const {
assert(is_tree());
- intptr_t info =
- static_cast<intptr_t>(absl::big_endian::ToHost64(as_tree_.cordz_info));
+ intptr_t info = static_cast<intptr_t>(absl::little_endian::ToHost64(
+ static_cast<uint64_t>(rep_.cordz_info())));
assert(info & 1);
return reinterpret_cast<CordzInfo*>(info - 1);
}
@@ -423,21 +586,22 @@
// Requires the current instance to hold a tree value.
void set_cordz_info(CordzInfo* cordz_info) {
assert(is_tree());
- intptr_t info = reinterpret_cast<intptr_t>(cordz_info) | 1;
- as_tree_.cordz_info = absl::big_endian::FromHost64(info);
+ uintptr_t info = reinterpret_cast<uintptr_t>(cordz_info) | 1;
+ rep_.set_cordz_info(
+ static_cast<cordz_info_t>(absl::little_endian::FromHost64(info)));
}
// Resets the current cordz_info to null / empty.
void clear_cordz_info() {
assert(is_tree());
- as_tree_.cordz_info = kNullCordzInfo;
+ rep_.set_cordz_info(kNullCordzInfo);
}
// Returns a read only pointer to the character data inside this instance.
// Requires the current instance to hold inline data.
const char* as_chars() const {
assert(!is_tree());
- return as_chars_;
+ return rep_.as_chars();
}
// Returns a mutable pointer to the character data inside this instance.
@@ -455,20 +619,33 @@
//
// It's an error to read from the returned pointer without a preceding write
// if the current instance does not hold inline data, i.e.: is_tree() == true.
- char* as_chars() { return as_chars_; }
+ char* as_chars() { return rep_.as_chars(); }
// Returns the tree value of this value.
// Requires the current instance to hold a tree value.
CordRep* as_tree() const {
assert(is_tree());
- return as_tree_.rep;
+ return rep_.tree();
+ }
+
+ void set_inline_data(const char* data, size_t n) {
+ ABSL_ASSERT(n <= kMaxInline);
+ unpoison();
+ rep_.set_tag(static_cast<int8_t>(n << 1));
+ SmallMemmove<true>(rep_.as_chars(), data, n);
+ poison();
+ }
+
+ void copy_max_inline_to(char* dst) const {
+ assert(!is_tree());
+ memcpy(dst, rep_.SanitizerSafeCopy().as_chars(), kMaxInline);
}
// Initialize this instance to holding the tree value `rep`,
// initializing the cordz_info to null, i.e.: 'not profiled'.
void make_tree(CordRep* rep) {
- as_tree_.rep = rep;
- as_tree_.cordz_info = kNullCordzInfo;
+ unpoison();
+ rep_.make_tree(rep);
}
// Set the tree value of this instance to 'rep`.
@@ -476,86 +653,232 @@
// Does not affect the value of cordz_info.
void set_tree(CordRep* rep) {
assert(is_tree());
- as_tree_.rep = rep;
+ rep_.set_tree(rep);
}
// Returns the size of the inlined character data inside this instance.
// Requires the current instance to hold inline data.
- size_t inline_size() const {
- assert(!is_tree());
- return tag() >> 1;
- }
+ size_t inline_size() const { return rep_.inline_size(); }
// Sets the size of the inlined character data inside this instance.
// Requires `size` to be <= kMaxInline.
// See the documentation on 'as_chars()' for more information and examples.
void set_inline_size(size_t size) {
- ABSL_ASSERT(size <= kMaxInline);
- tag() = static_cast<char>(size << 1);
+ unpoison();
+ rep_.set_inline_size(size);
+ poison();
+ }
+
+ // Compares 'this' inlined data with rhs. The comparison is a straightforward
+ // lexicographic comparison. `Compare()` returns values as follows:
+ //
+ // -1 'this' InlineData instance is smaller
+ // 0 the InlineData instances are equal
+ // 1 'this' InlineData instance larger
+ int Compare(const InlineData& rhs) const {
+ return Compare(rep_.SanitizerSafeCopy(), rhs.rep_.SanitizerSafeCopy());
}
private:
- // See cordz_info_t for forced alignment and size of `cordz_info` details.
- struct AsTree {
- explicit constexpr AsTree(absl::cord_internal::CordRep* tree)
- : rep(tree), cordz_info(kNullCordzInfo) {}
- // This union uses up extra space so that whether rep is 32 or 64 bits,
- // cordz_info will still start at the eighth byte, and the last
- // byte of cordz_info will still be the last byte of InlineData.
- union {
+ struct Rep {
+ // See cordz_info_t for forced alignment and size of `cordz_info` details.
+ struct AsTree {
+ explicit constexpr AsTree(absl::cord_internal::CordRep* tree)
+ : rep(tree) {}
+ cordz_info_t cordz_info = kNullCordzInfo;
absl::cord_internal::CordRep* rep;
- cordz_info_t unused_aligner;
};
- cordz_info_t cordz_info;
+
+ explicit Rep(DefaultInitType) {}
+ constexpr Rep() : data{0} {}
+ constexpr Rep(const Rep&) = default;
+ constexpr Rep& operator=(const Rep&) = default;
+
+ explicit constexpr Rep(CordRep* rep) : as_tree(rep) {}
+
+ explicit constexpr Rep(absl::string_view chars)
+ : data{static_cast<char>((chars.size() << 1)),
+ GetOrNull(chars, 0),
+ GetOrNull(chars, 1),
+ GetOrNull(chars, 2),
+ GetOrNull(chars, 3),
+ GetOrNull(chars, 4),
+ GetOrNull(chars, 5),
+ GetOrNull(chars, 6),
+ GetOrNull(chars, 7),
+ GetOrNull(chars, 8),
+ GetOrNull(chars, 9),
+ GetOrNull(chars, 10),
+ GetOrNull(chars, 11),
+ GetOrNull(chars, 12),
+ GetOrNull(chars, 13),
+ GetOrNull(chars, 14)} {}
+
+ // Disable sanitizer as we must always be able to read `tag`.
+ ABSL_CORD_INTERNAL_NO_SANITIZE
+ int8_t tag() const { return reinterpret_cast<const int8_t*>(this)[0]; }
+ void set_tag(int8_t rhs) { reinterpret_cast<int8_t*>(this)[0] = rhs; }
+
+ char* as_chars() { return data + 1; }
+ const char* as_chars() const { return data + 1; }
+
+ bool is_tree() const { return (tag() & 1) != 0; }
+
+ size_t inline_size() const {
+ ABSL_ASSERT(!is_tree());
+ return static_cast<size_t>(tag()) >> 1;
+ }
+
+ void set_inline_size(size_t size) {
+ ABSL_ASSERT(size <= kMaxInline);
+ set_tag(static_cast<int8_t>(size << 1));
+ }
+
+ CordRep* tree() const { return as_tree.rep; }
+ void set_tree(CordRep* rhs) { as_tree.rep = rhs; }
+
+ cordz_info_t cordz_info() const { return as_tree.cordz_info; }
+ void set_cordz_info(cordz_info_t rhs) { as_tree.cordz_info = rhs; }
+
+ void make_tree(CordRep* tree) {
+ as_tree.rep = tree;
+ as_tree.cordz_info = kNullCordzInfo;
+ }
+
+#ifdef ABSL_INTERNAL_CORD_HAVE_SANITIZER
+ constexpr Rep SanitizerSafeCopy() const {
+ if (!absl::is_constant_evaluated()) {
+ Rep res;
+ if (is_tree()) {
+ res = *this;
+ } else {
+ res.set_tag(tag());
+ memcpy(res.as_chars(), as_chars(), inline_size());
+ }
+ return res;
+ } else {
+ return *this;
+ }
+ }
+#else
+ constexpr const Rep& SanitizerSafeCopy() const { return *this; }
+#endif
+
+ // If the data has length <= kMaxInline, we store it in `data`, and
+ // store the size in the first char of `data` shifted left + 1.
+ // Else we store it in a tree and store a pointer to that tree in
+ // `as_tree.rep` with a tagged pointer to make `tag() & 1` non zero.
+ union {
+ char data[kMaxInline + 1];
+ AsTree as_tree;
+ };
+
+ // TODO(b/145829486): see swap(InlineData, InlineData) for more info.
+ inline void SwapValue(Rep rhs, Rep& refrhs) {
+ memcpy(&refrhs, this, sizeof(*this));
+ memcpy(this, &rhs, sizeof(*this));
+ }
};
- char& tag() { return reinterpret_cast<char*>(this)[kMaxInline]; }
- char tag() const { return reinterpret_cast<const char*>(this)[kMaxInline]; }
+ // Private implementation of `Compare()`
+ static inline int Compare(const Rep& lhs, const Rep& rhs) {
+ uint64_t x, y;
+ memcpy(&x, lhs.as_chars(), sizeof(x));
+ memcpy(&y, rhs.as_chars(), sizeof(y));
+ if (x == y) {
+ memcpy(&x, lhs.as_chars() + 7, sizeof(x));
+ memcpy(&y, rhs.as_chars() + 7, sizeof(y));
+ if (x == y) {
+ if (lhs.inline_size() == rhs.inline_size()) return 0;
+ return lhs.inline_size() < rhs.inline_size() ? -1 : 1;
+ }
+ }
+ x = absl::big_endian::FromHost64(x);
+ y = absl::big_endian::FromHost64(y);
+ return x < y ? -1 : 1;
+ }
- // If the data has length <= kMaxInline, we store it in `as_chars_`, and
- // store the size in the last char of `as_chars_` shifted left + 1.
- // Else we store it in a tree and store a pointer to that tree in
- // `as_tree_.rep` and store a tag in `tagged_size`.
- union {
- char as_chars_[kMaxInline + 1];
- AsTree as_tree_;
- };
+ Rep rep_;
};
static_assert(sizeof(InlineData) == kMaxInline + 1, "");
-inline CordRepConcat* CordRep::concat() {
- assert(tag == CONCAT);
- return static_cast<CordRepConcat*>(this);
+#ifdef ABSL_INTERNAL_CORD_HAVE_SANITIZER
+
+constexpr InlineData::InlineData(const InlineData& rhs) noexcept
+ : rep_(rhs.rep_.SanitizerSafeCopy()) {
+ poison();
}
-inline const CordRepConcat* CordRep::concat() const {
- assert(tag == CONCAT);
- return static_cast<const CordRepConcat*>(this);
+inline InlineData& InlineData::operator=(const InlineData& rhs) noexcept {
+ unpoison();
+ rep_ = rhs.rep_.SanitizerSafeCopy();
+ poison();
+ return *this;
}
+constexpr void InlineData::poison_this() {
+ if (!absl::is_constant_evaluated()) {
+ container_internal::SanitizerPoisonObject(this);
+ }
+}
+
+constexpr void InlineData::unpoison() {
+ if (!absl::is_constant_evaluated()) {
+ container_internal::SanitizerUnpoisonObject(this);
+ }
+}
+
+constexpr void InlineData::poison() {
+ if (!absl::is_constant_evaluated()) {
+ if (is_tree()) {
+ container_internal::SanitizerUnpoisonObject(this);
+ } else if (const size_t size = inline_size()) {
+ if (size < kMaxInline) {
+ const char* end = rep_.as_chars() + size;
+ container_internal::SanitizerPoisonMemoryRegion(end, kMaxInline - size);
+ }
+ } else {
+ container_internal::SanitizerPoisonObject(this);
+ }
+ }
+}
+
+#else // ABSL_INTERNAL_CORD_HAVE_SANITIZER
+
+constexpr InlineData::InlineData(const InlineData&) noexcept = default;
+inline InlineData& InlineData::operator=(const InlineData&) noexcept = default;
+
+constexpr void InlineData::poison_this() {}
+constexpr void InlineData::unpoison() {}
+constexpr void InlineData::poison() {}
+
+#endif // ABSL_INTERNAL_CORD_HAVE_SANITIZER
+
inline CordRepSubstring* CordRep::substring() {
- assert(tag == SUBSTRING);
+ assert(IsSubstring());
return static_cast<CordRepSubstring*>(this);
}
inline const CordRepSubstring* CordRep::substring() const {
- assert(tag == SUBSTRING);
+ assert(IsSubstring());
return static_cast<const CordRepSubstring*>(this);
}
inline CordRepExternal* CordRep::external() {
- assert(tag == EXTERNAL);
+ assert(IsExternal());
return static_cast<CordRepExternal*>(this);
}
inline const CordRepExternal* CordRep::external() const {
- assert(tag == EXTERNAL);
+ assert(IsExternal());
return static_cast<const CordRepExternal*>(this);
}
inline CordRep* CordRep::Ref(CordRep* rep) {
- assert(rep != nullptr);
+ // ABSL_ASSUME is a workaround for
+ // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105585
+ ABSL_ASSUME(rep != nullptr);
rep->refcount.Increment();
return rep;
}
@@ -569,6 +892,19 @@
}
}
+inline void swap(InlineData& lhs, InlineData& rhs) noexcept {
+ lhs.unpoison();
+ rhs.unpoison();
+ // TODO(b/145829486): `std::swap(lhs.rep_, rhs.rep_)` results in bad codegen
+ // on clang, spilling the temporary swap value on the stack. Since `Rep` is
+ // trivial, we can make clang DTRT by calling a hand-rolled `SwapValue` where
+ // we pass `rhs` both by value (register allocated) and by reference. The IR
+ // then folds and inlines correctly into an optimized swap without spill.
+ lhs.rep_.SwapValue(rhs.rep_, rhs.rep_);
+ rhs.poison();
+ lhs.poison();
+}
+
} // namespace cord_internal
ABSL_NAMESPACE_END
diff --git a/absl/strings/internal/cord_rep_btree.cc b/absl/strings/internal/cord_rep_btree.cc
index 6978cfd..05bd0e2 100644
--- a/absl/strings/internal/cord_rep_btree.cc
+++ b/absl/strings/internal/cord_rep_btree.cc
@@ -14,14 +14,18 @@
#include "absl/strings/internal/cord_rep_btree.h"
+#include <atomic>
#include <cassert>
#include <cstdint>
#include <iostream>
+#include <ostream>
#include <string>
#include "absl/base/attributes.h"
#include "absl/base/config.h"
#include "absl/base/internal/raw_logging.h"
+#include "absl/base/optimization.h"
+#include "absl/strings/internal/cord_data_edge.h"
#include "absl/strings/internal/cord_internal.h"
#include "absl/strings/internal/cord_rep_consume.h"
#include "absl/strings/internal/cord_rep_flat.h"
@@ -32,6 +36,10 @@
ABSL_NAMESPACE_BEGIN
namespace cord_internal {
+#ifdef ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL
+constexpr size_t CordRepBtree::kMaxCapacity;
+#endif
+
namespace {
using NodeStack = CordRepBtree * [CordRepBtree::kMaxDepth];
@@ -42,12 +50,16 @@
constexpr auto kFront = CordRepBtree::kFront;
constexpr auto kBack = CordRepBtree::kBack;
+ABSL_CONST_INIT std::atomic<bool> cord_btree_exhaustive_validation(false);
+
// Implementation of the various 'Dump' functions.
// Prints the entire tree structure or 'rep'. External callers should
// not specify 'depth' and leave it to its default (0) value.
// Rep may be a CordRepBtree tree, or a SUBSTRING / EXTERNAL / FLAT node.
-void DumpAll(const CordRep* rep, bool include_contents, std::ostream& stream,
- int depth = 0) {
+void DumpAll(const CordRep* rep,
+ bool include_contents,
+ std::ostream& stream,
+ size_t depth = 0) {
// Allow for full height trees + substring -> flat / external nodes.
assert(depth <= CordRepBtree::kMaxDepth + 2);
std::string sharing = const_cast<CordRep*>(rep)->refcount.IsOne()
@@ -63,7 +75,7 @@
// indentation and prefix / labels keeps us within roughly 80-100 wide.
constexpr size_t kMaxDataLength = 60;
stream << ", data = \""
- << CordRepBtree::EdgeData(r).substr(0, kMaxDataLength)
+ << EdgeData(r).substr(0, kMaxDataLength)
<< (r->length > kMaxDataLength ? "\"..." : "\"");
}
stream << '\n';
@@ -73,7 +85,7 @@
// indented by two spaces per recursive depth.
stream << std::string(depth * 2, ' ') << sharing << " (" << sptr << ") ";
- if (rep->tag == BTREE) {
+ if (rep->IsBtree()) {
const CordRepBtree* node = rep->btree();
std::string label =
node->height() ? absl::StrCat("Node(", node->height(), ")") : "Leaf";
@@ -90,7 +102,8 @@
maybe_dump_data(rep);
DumpAll(substring->child, include_contents, stream, depth + 1);
} else if (rep->tag >= FLAT) {
- stream << "Flat, len = " << rep->length;
+ stream << "Flat, len = " << rep->length
+ << ", cap = " << rep->flat()->Capacity();
maybe_dump_data(rep);
} else if (rep->tag == EXTERNAL) {
stream << "Extn, len = " << rep->length;
@@ -112,6 +125,7 @@
rep = CordRep::Ref(substring->child);
CordRep::Unref(substring);
}
+ assert(rep->IsExternal() || rep->IsFlat());
CordRepSubstring* substring = new CordRepSubstring();
substring->length = n;
substring->tag = SUBSTRING;
@@ -133,6 +147,26 @@
return CreateSubstring(rep, offset, rep->length - offset);
}
+// Resizes `edge` to the provided `length`. Adopts a reference on `edge`.
+// This method directly returns `edge` if `length` equals `edge->length`.
+// If `is_mutable` is set to true, this function may return `edge` with
+// `edge->length` set to the new length depending on the type and size of
+// `edge`. Otherwise, this function returns a new CordRepSubstring value.
+// Requires `length > 0 && length <= edge->length`.
+CordRep* ResizeEdge(CordRep* edge, size_t length, bool is_mutable) {
+ assert(length > 0);
+ assert(length <= edge->length);
+ assert(IsDataEdge(edge));
+ if (length >= edge->length) return edge;
+
+ if (is_mutable && (edge->tag >= FLAT || edge->tag == SUBSTRING)) {
+ edge->length = length;
+ return edge;
+ }
+
+ return CreateSubstring(edge, 0, length);
+}
+
template <EdgeType edge_type>
inline absl::string_view Consume(absl::string_view s, size_t n) {
return edge_type == kBack ? s.substr(n) : s.substr(0, s.size() - n);
@@ -163,24 +197,29 @@
}
}
-// Deletes a leaf node data edge. Requires `rep` to be an EXTERNAL or FLAT
-// node, or a SUBSTRING of an EXTERNAL or FLAT node.
-void DeleteLeafEdge(CordRep* rep) {
- for (;;) {
+
+void DeleteSubstring(CordRepSubstring* substring) {
+ CordRep* rep = substring->child;
+ if (!rep->refcount.Decrement()) {
if (rep->tag >= FLAT) {
CordRepFlat::Delete(rep->flat());
- return;
- }
- if (rep->tag == EXTERNAL) {
+ } else {
+ assert(rep->tag == EXTERNAL);
CordRepExternal::Delete(rep->external());
- return;
}
- assert(rep->tag == SUBSTRING);
- CordRepSubstring* substring = rep->substring();
- rep = substring->child;
- assert(rep->tag == EXTERNAL || rep->tag >= FLAT);
- delete substring;
- if (rep->refcount.Decrement()) return;
+ }
+ delete substring;
+}
+
+// Deletes a leaf node data edge. Requires `IsDataEdge(rep)`.
+void DeleteLeafEdge(CordRep* rep) {
+ assert(IsDataEdge(rep));
+ if (rep->tag >= FLAT) {
+ CordRepFlat::Delete(rep->flat());
+ } else if (rep->tag == EXTERNAL) {
+ CordRepExternal::Delete(rep->external());
+ } else {
+ DeleteSubstring(rep->substring());
}
}
@@ -233,18 +272,21 @@
static inline CordRepBtree* Finalize(CordRepBtree* tree, OpResult result) {
switch (result.action) {
case CordRepBtree::kPopped:
- if (ABSL_PREDICT_FALSE(tree->height() >= CordRepBtree::kMaxHeight)) {
- ABSL_RAW_LOG(FATAL, "Max height exceeded");
- }
- return edge_type == kBack ? CordRepBtree::New(tree, result.tree)
+ tree = edge_type == kBack ? CordRepBtree::New(tree, result.tree)
: CordRepBtree::New(result.tree, tree);
+ if (ABSL_PREDICT_FALSE(tree->height() > CordRepBtree::kMaxHeight)) {
+ tree = CordRepBtree::Rebuild(tree);
+ ABSL_RAW_CHECK(tree->height() <= CordRepBtree::kMaxHeight,
+ "Max height exceeded");
+ }
+ return tree;
case CordRepBtree::kCopied:
CordRep::Unref(tree);
ABSL_FALLTHROUGH_INTENDED;
case CordRepBtree::kSelf:
return result.tree;
}
- ABSL_INTERNAL_UNREACHABLE;
+ ABSL_UNREACHABLE();
return result.tree;
}
@@ -319,6 +361,15 @@
} // namespace
+void SetCordBtreeExhaustiveValidation(bool do_exaustive_validation) {
+ cord_btree_exhaustive_validation.store(do_exaustive_validation,
+ std::memory_order_relaxed);
+}
+
+bool IsCordBtreeExhaustiveValidationEnabled() {
+ return cord_btree_exhaustive_validation.load(std::memory_order_relaxed);
+}
+
void CordRepBtree::Dump(const CordRep* rep, absl::string_view label,
bool include_contents, std::ostream& stream) {
stream << "===================================\n";
@@ -342,22 +393,40 @@
Dump(rep, absl::string_view(), false, stream);
}
-void CordRepBtree::DestroyLeaf(CordRepBtree* tree, size_t begin, size_t end) {
- for (CordRep* edge : tree->Edges(begin, end)) {
- FastUnref(edge, DeleteLeafEdge);
+template <size_t size>
+static void DestroyTree(CordRepBtree* tree) {
+ for (CordRep* node : tree->Edges()) {
+ if (node->refcount.Decrement()) continue;
+ for (CordRep* edge : node->btree()->Edges()) {
+ if (edge->refcount.Decrement()) continue;
+ if (size == 1) {
+ DeleteLeafEdge(edge);
+ } else {
+ CordRepBtree::Destroy(edge->btree());
+ }
+ }
+ CordRepBtree::Delete(node->btree());
}
- Delete(tree);
+ CordRepBtree::Delete(tree);
}
-void CordRepBtree::DestroyNonLeaf(CordRepBtree* tree, size_t begin,
- size_t end) {
- for (CordRep* edge : tree->Edges(begin, end)) {
- FastUnref(edge->btree(), Destroy);
+void CordRepBtree::Destroy(CordRepBtree* tree) {
+ switch (tree->height()) {
+ case 0:
+ for (CordRep* edge : tree->Edges()) {
+ if (!edge->refcount.Decrement()) {
+ DeleteLeafEdge(edge);
+ }
+ }
+ return CordRepBtree::Delete(tree);
+ case 1:
+ return DestroyTree<1>(tree);
+ default:
+ return DestroyTree<2>(tree);
}
- Delete(tree);
}
-bool CordRepBtree::IsValid(const CordRepBtree* tree) {
+bool CordRepBtree::IsValid(const CordRepBtree* tree, bool shallow) {
#define NODE_CHECK_VALID(x) \
if (!(x)) { \
ABSL_RAW_LOG(ERROR, "CordRepBtree::CheckValid() FAILED: %s", #x); \
@@ -372,7 +441,7 @@
}
NODE_CHECK_VALID(tree != nullptr);
- NODE_CHECK_EQ(tree->tag, BTREE);
+ NODE_CHECK_VALID(tree->IsBtree());
NODE_CHECK_VALID(tree->height() <= kMaxHeight);
NODE_CHECK_VALID(tree->begin() < tree->capacity());
NODE_CHECK_VALID(tree->end() <= tree->capacity());
@@ -381,7 +450,7 @@
for (CordRep* edge : tree->Edges()) {
NODE_CHECK_VALID(edge != nullptr);
if (tree->height() > 0) {
- NODE_CHECK_VALID(edge->tag == BTREE);
+ NODE_CHECK_VALID(edge->IsBtree());
NODE_CHECK_VALID(edge->btree()->height() == tree->height() - 1);
} else {
NODE_CHECK_VALID(IsDataEdge(edge));
@@ -389,9 +458,10 @@
child_length += edge->length;
}
NODE_CHECK_EQ(child_length, tree->length);
- if (tree->height() > 0) {
+ if ((!shallow || IsCordBtreeExhaustiveValidationEnabled()) &&
+ tree->height() > 0) {
for (CordRep* edge : tree->Edges()) {
- if (!IsValid(edge->btree())) return false;
+ if (!IsValid(edge->btree(), shallow)) return false;
}
}
return true;
@@ -402,16 +472,17 @@
#ifndef NDEBUG
-CordRepBtree* CordRepBtree::AssertValid(CordRepBtree* tree) {
- if (!IsValid(tree)) {
+CordRepBtree* CordRepBtree::AssertValid(CordRepBtree* tree, bool shallow) {
+ if (!IsValid(tree, shallow)) {
Dump(tree, "CordRepBtree validation failed:", false, std::cout);
ABSL_RAW_LOG(FATAL, "CordRepBtree::CheckValid() FAILED");
}
return tree;
}
-const CordRepBtree* CordRepBtree::AssertValid(const CordRepBtree* tree) {
- if (!IsValid(tree)) {
+const CordRepBtree* CordRepBtree::AssertValid(const CordRepBtree* tree,
+ bool shallow) {
+ if (!IsValid(tree, shallow)) {
Dump(tree, "CordRepBtree validation failed:", false, std::cout);
ABSL_RAW_LOG(FATAL, "CordRepBtree::CheckValid() FAILED");
}
@@ -441,7 +512,7 @@
// open interval [begin, back) or [begin + 1, end) depending on `edge_type`.
// We conveniently cover both case using a constexpr `shift` being 0 or 1
// as `end :== back + 1`.
- result = {CopyRaw(), kCopied};
+ result = {CopyRaw(length), kCopied};
constexpr int shift = edge_type == kFront ? 1 : 0;
for (CordRep* r : Edges(begin() + shift, back() + shift)) {
CordRep::Ref(r);
@@ -684,7 +755,7 @@
return result;
}
-CopyResult CordRepBtree::CopyPrefix(size_t n) {
+CopyResult CordRepBtree::CopyPrefix(size_t n, bool allow_folding) {
assert(n > 0);
assert(n <= this->length);
@@ -696,10 +767,12 @@
int height = this->height();
CordRepBtree* node = this;
CordRep* front = node->Edge(kFront);
- while (front->length >= n) {
- if (--height < 0) return {MakeSubstring(CordRep::Ref(front), 0, n), -1};
- node = front->btree();
- front = node->Edge(kFront);
+ if (allow_folding) {
+ while (front->length >= n) {
+ if (--height < 0) return {MakeSubstring(CordRep::Ref(front), 0, n), -1};
+ node = front->btree();
+ front = node->Edge(kFront);
+ }
}
if (node->length == n) return {CordRep::Ref(node), height};
@@ -738,6 +811,97 @@
return result;
}
+CordRep* CordRepBtree::ExtractFront(CordRepBtree* tree) {
+ CordRep* front = tree->Edge(tree->begin());
+ if (tree->refcount.IsOne()) {
+ Unref(tree->Edges(tree->begin() + 1, tree->end()));
+ CordRepBtree::Delete(tree);
+ } else {
+ CordRep::Ref(front);
+ CordRep::Unref(tree);
+ }
+ return front;
+}
+
+CordRepBtree* CordRepBtree::ConsumeBeginTo(CordRepBtree* tree, size_t end,
+ size_t new_length) {
+ assert(end <= tree->end());
+ if (tree->refcount.IsOne()) {
+ Unref(tree->Edges(end, tree->end()));
+ tree->set_end(end);
+ tree->length = new_length;
+ } else {
+ CordRepBtree* old = tree;
+ tree = tree->CopyBeginTo(end, new_length);
+ CordRep::Unref(old);
+ }
+ return tree;
+}
+
+CordRep* CordRepBtree::RemoveSuffix(CordRepBtree* tree, size_t n) {
+ // Check input and deal with trivial cases 'Remove all/none'
+ assert(tree != nullptr);
+ assert(n <= tree->length);
+ const size_t len = tree->length;
+ if (ABSL_PREDICT_FALSE(n == 0)) {
+ return tree;
+ }
+ if (ABSL_PREDICT_FALSE(n >= len)) {
+ CordRepBtree::Unref(tree);
+ return nullptr;
+ }
+
+ size_t length = len - n;
+ int height = tree->height();
+ bool is_mutable = tree->refcount.IsOne();
+
+ // Extract all top nodes which are reduced to size = 1
+ Position pos = tree->IndexOfLength(length);
+ while (pos.index == tree->begin()) {
+ CordRep* edge = ExtractFront(tree);
+ is_mutable &= edge->refcount.IsOne();
+ if (height-- == 0) return ResizeEdge(edge, length, is_mutable);
+ tree = edge->btree();
+ pos = tree->IndexOfLength(length);
+ }
+
+ // Repeat the following sequence traversing down the tree:
+ // - Crop the top node to the 'last remaining edge' adjusting length.
+ // - Set the length for down edges to the partial length in that last edge.
+ // - Repeat this until the last edge is 'included in full'
+ // - If we hit the data edge level, resize and return the last data edge
+ CordRepBtree* top = tree = ConsumeBeginTo(tree, pos.index + 1, length);
+ CordRep* edge = tree->Edge(pos.index);
+ length = pos.n;
+ while (length != edge->length) {
+ // ConsumeBeginTo guarantees `tree` is a clean, privately owned copy.
+ assert(tree->refcount.IsOne());
+ const bool edge_is_mutable = edge->refcount.IsOne();
+
+ if (height-- == 0) {
+ tree->edges_[pos.index] = ResizeEdge(edge, length, edge_is_mutable);
+ return AssertValid(top);
+ }
+
+ if (!edge_is_mutable) {
+ // We can't 'in place' remove any suffixes down this edge.
+ // Replace this edge with a prefix copy instead.
+ tree->edges_[pos.index] = edge->btree()->CopyPrefix(length, false).edge;
+ CordRep::Unref(edge);
+ return AssertValid(top);
+ }
+
+ // Move down one level, rinse repeat.
+ tree = edge->btree();
+ pos = tree->IndexOfLength(length);
+ tree = ConsumeBeginTo(edge->btree(), pos.index + 1, length);
+ edge = tree->Edge(pos.index);
+ length = pos.n;
+ }
+
+ return AssertValid(top);
+}
+
CordRep* CordRepBtree::SubTree(size_t offset, size_t n) {
assert(n <= this->length);
assert(offset <= this->length - n);
@@ -862,7 +1026,7 @@
stack[i] = node;
}
- // Must be a privately owned flat.
+ // Must be a privately owned, mutable flat.
CordRep* const edge = node->Edge(kBack);
if (!edge->refcount.IsOne() || edge->tag < FLAT) return {};
@@ -882,7 +1046,7 @@
}
CordRepBtree* CordRepBtree::CreateSlow(CordRep* rep) {
- if (rep->tag == BTREE) return rep->btree();
+ if (rep->IsBtree()) return rep->btree();
CordRepBtree* node = nullptr;
auto consume = [&node](CordRep* r, size_t offset, size_t length) {
@@ -898,7 +1062,7 @@
}
CordRepBtree* CordRepBtree::AppendSlow(CordRepBtree* tree, CordRep* rep) {
- if (ABSL_PREDICT_TRUE(rep->tag == BTREE)) {
+ if (ABSL_PREDICT_TRUE(rep->IsBtree())) {
return MergeTrees(tree, rep->btree());
}
auto consume = [&tree](CordRep* r, size_t offset, size_t length) {
@@ -910,7 +1074,7 @@
}
CordRepBtree* CordRepBtree::PrependSlow(CordRepBtree* tree, CordRep* rep) {
- if (ABSL_PREDICT_TRUE(rep->tag == BTREE)) {
+ if (ABSL_PREDICT_TRUE(rep->IsBtree())) {
return MergeTrees(rep->btree(), tree);
}
auto consume = [&tree](CordRep* r, size_t offset, size_t length) {
@@ -942,6 +1106,136 @@
absl::string_view data,
size_t extra);
+void CordRepBtree::Rebuild(CordRepBtree** stack, CordRepBtree* tree,
+ bool consume) {
+ bool owned = consume && tree->refcount.IsOne();
+ if (tree->height() == 0) {
+ for (CordRep* edge : tree->Edges()) {
+ if (!owned) edge = CordRep::Ref(edge);
+ size_t height = 0;
+ size_t length = edge->length;
+ CordRepBtree* node = stack[0];
+ OpResult result = node->AddEdge<kBack>(true, edge, length);
+ while (result.action == CordRepBtree::kPopped) {
+ stack[height] = result.tree;
+ if (stack[++height] == nullptr) {
+ result.action = CordRepBtree::kSelf;
+ stack[height] = CordRepBtree::New(node, result.tree);
+ } else {
+ node = stack[height];
+ result = node->AddEdge<kBack>(true, result.tree, length);
+ }
+ }
+ while (stack[++height] != nullptr) {
+ stack[height]->length += length;
+ }
+ }
+ } else {
+ for (CordRep* rep : tree->Edges()) {
+ Rebuild(stack, rep->btree(), owned);
+ }
+ }
+ if (consume) {
+ if (owned) {
+ CordRepBtree::Delete(tree);
+ } else {
+ CordRepBtree::Unref(tree);
+ }
+ }
+}
+
+CordRepBtree* CordRepBtree::Rebuild(CordRepBtree* tree) {
+ // Set up initial stack with empty leaf node.
+ CordRepBtree* node = CordRepBtree::New();
+ CordRepBtree* stack[CordRepBtree::kMaxDepth + 1] = {node};
+
+ // Recursively build the tree, consuming the input tree.
+ Rebuild(stack, tree, /* consume reference */ true);
+
+ // Return top most node
+ for (CordRepBtree* parent : stack) {
+ if (parent == nullptr) return node;
+ node = parent;
+ }
+
+ // Unreachable
+ assert(false);
+ return nullptr;
+}
+
+CordRepBtree::ExtractResult CordRepBtree::ExtractAppendBuffer(
+ CordRepBtree* tree, size_t extra_capacity) {
+ int depth = 0;
+ NodeStack stack;
+
+ // Set up default 'no success' result which is {tree, nullptr}.
+ ExtractResult result;
+ result.tree = tree;
+ result.extracted = nullptr;
+
+ // Dive down the right side of the tree, making sure no edges are shared.
+ while (tree->height() > 0) {
+ if (!tree->refcount.IsOne()) return result;
+ stack[depth++] = tree;
+ tree = tree->Edge(kBack)->btree();
+ }
+ if (!tree->refcount.IsOne()) return result;
+
+ // Validate we ended on a non shared flat.
+ CordRep* rep = tree->Edge(kBack);
+ if (!(rep->IsFlat() && rep->refcount.IsOne())) return result;
+
+ // Verify it has at least the requested extra capacity.
+ CordRepFlat* flat = rep->flat();
+ const size_t length = flat->length;
+ const size_t avail = flat->Capacity() - flat->length;
+ if (extra_capacity > avail) return result;
+
+ // Set the extracted flat in the result.
+ result.extracted = flat;
+
+ // Cascading delete all nodes that become empty.
+ while (tree->size() == 1) {
+ CordRepBtree::Delete(tree);
+ if (--depth < 0) {
+ // We consumed the entire tree: return nullptr for new tree.
+ result.tree = nullptr;
+ return result;
+ }
+ rep = tree;
+ tree = stack[depth];
+ }
+
+ // Remove the edge or cascaded up parent node.
+ tree->set_end(tree->end() - 1);
+ tree->length -= length;
+
+ // Adjust lengths up the tree.
+ while (depth > 0) {
+ tree = stack[--depth];
+ tree->length -= length;
+ }
+
+ // Remove unnecessary top nodes with size = 1. This may iterate all the way
+ // down to the leaf node in which case we simply return the remaining last
+ // edge in that node and the extracted flat.
+ while (tree->size() == 1) {
+ int height = tree->height();
+ rep = tree->Edge(kBack);
+ Delete(tree);
+ if (height == 0) {
+ // We consumed the leaf: return the sole data edge as the new tree.
+ result.tree = rep;
+ return result;
+ }
+ tree = rep->btree();
+ }
+
+ // Done: return the (new) top level node and extracted flat.
+ result.tree = tree;
+ return result;
+}
+
} // namespace cord_internal
ABSL_NAMESPACE_END
} // namespace absl
diff --git a/absl/strings/internal/cord_rep_btree.h b/absl/strings/internal/cord_rep_btree.h
index 7d85473..be94b62 100644
--- a/absl/strings/internal/cord_rep_btree.h
+++ b/absl/strings/internal/cord_rep_btree.h
@@ -22,8 +22,8 @@
#include "absl/base/config.h"
#include "absl/base/internal/raw_logging.h"
#include "absl/base/optimization.h"
+#include "absl/strings/internal/cord_data_edge.h"
#include "absl/strings/internal/cord_internal.h"
-#include "absl/strings/internal/cord_rep_btree.h"
#include "absl/strings/internal/cord_rep_flat.h"
#include "absl/strings/string_view.h"
#include "absl/types/span.h"
@@ -32,6 +32,14 @@
ABSL_NAMESPACE_BEGIN
namespace cord_internal {
+// `SetCordBtreeExhaustiveValidation()` can be set to force exhaustive
+// validation in debug assertions, and code that calls `IsValid()`
+// explicitly. By default, assertions should be relatively cheap and
+// AssertValid() can easily lead to O(n^2) complexity as recursive / full tree
+// validation is O(n).
+void SetCordBtreeExhaustiveValidation(bool do_exaustive_validation);
+bool IsCordBtreeExhaustiveValidationEnabled();
+
class CordRepBtreeNavigator;
// CordRepBtree is as the name implies a btree implementation of a Cordrep tree.
@@ -95,8 +103,9 @@
// local stack variable compared to Cord's current near 400 bytes stack use.
// The maximum `height` value of a node is then `kMaxDepth - 1` as node height
// values start with a value of 0 for leaf nodes.
- static constexpr int kMaxDepth = 12;
- static constexpr int kMaxHeight = kMaxDepth - 1;
+ static constexpr size_t kMaxDepth = 12;
+ // See comments on height() for why this is an int and not a size_t.
+ static constexpr int kMaxHeight = static_cast<int>(kMaxDepth - 1);
// `Action` defines the action for unwinding changes done at the btree's leaf
// level that need to be propagated up to the parent node(s). Each operation
@@ -153,7 +162,7 @@
};
// Creates a btree from the given input. Adopts a ref of `rep`.
- // If the input `rep` is itself a btree, i.e., `tag == BTREE`, then this
+ // If the input `rep` is itself a btree, i.e., `IsBtree()`, then this
// function immediately returns `rep->btree()`. If the input is a valid data
// edge (see IsDataEdge()), then a new leaf node is returned containing `rep`
// as the sole data edge. Else, the input is assumed to be a (legacy) concat
@@ -164,6 +173,15 @@
// typically after a ref_count.Decrement() on the last reference count.
static void Destroy(CordRepBtree* tree);
+ // Destruction
+ static void Delete(CordRepBtree* tree) { delete tree; }
+
+ // Use CordRep::Unref() as we overload for absl::Span<CordRep* const>.
+ using CordRep::Unref;
+
+ // Unrefs all edges in `edges` which are assumed to be 'likely one'.
+ static void Unref(absl::Span<CordRep* const> edges);
+
// Appends / Prepends an existing CordRep instance to this tree.
// The below methods accept three types of input:
// 1) `rep` is a data node (See `IsDataNode` for valid data edges).
@@ -199,6 +217,19 @@
// Requires `offset + n <= length`. Returns `nullptr` if `n` is zero.
CordRep* SubTree(size_t offset, size_t n);
+ // Removes `n` trailing bytes from `tree`, and returns the resulting tree
+ // or data edge. Returns `tree` if n is zero, and nullptr if n == length.
+ // This function is logically identical to:
+ // result = tree->SubTree(0, tree->length - n);
+ // Unref(tree);
+ // return result;
+ // However, the actual implementation will as much as possible perform 'in
+ // place' modifications on the tree on all nodes and edges that are mutable.
+ // For example, in a fully privately owned tree with the last edge being a
+ // flat of length 12, RemoveSuffix(1) will simply set the length of that data
+ // edge to 11, and reduce the length of all nodes on the edge path by 1.
+ static CordRep* RemoveSuffix(CordRepBtree* tree, size_t n);
+
// Returns the character at the given offset.
char GetCharacter(size_t offset) const;
@@ -227,6 +258,36 @@
// shared node of a cord tree.
Span<char> GetAppendBuffer(size_t size);
+ // Extracts the right-most data edge from this tree iff:
+ // - the tree and all internal edges to the right-most node are not shared.
+ // - the right-most node is a FLAT node and not shared.
+ // - the right-most node has at least the desired extra capacity.
+ //
+ // Returns {tree, nullptr} if any of the above conditions are not met.
+ // This method effectively removes data from the tree. The intent of this
+ // method is to allow applications appending small string data to use
+ // pre-existing capacity, and add the modified rep back to the tree.
+ //
+ // Simplified such code would look similar to this:
+ // void MyTreeBuilder::Append(string_view data) {
+ // ExtractResult result = CordRepBtree::ExtractAppendBuffer(tree_, 1);
+ // if (CordRep* rep = result.extracted) {
+ // size_t available = rep->Capacity() - rep->length;
+ // size_t n = std::min(data.size(), n);
+ // memcpy(rep->Data(), data.data(), n);
+ // rep->length += n;
+ // data.remove_prefix(n);
+ // if (!result.tree->IsBtree()) {
+ // tree_ = CordRepBtree::Create(result.tree);
+ // }
+ // tree_ = CordRepBtree::Append(tree_, rep);
+ // }
+ // ...
+ // // Remaining edge in `result.tree`.
+ // }
+ static ExtractResult ExtractAppendBuffer(CordRepBtree* tree,
+ size_t extra_capacity = 1);
+
// Returns the `height` of the tree. The height of a tree is limited to
// kMaxHeight. `height` is implemented as an `int` as in some places we
// use negative (-1) values for 'data edges'.
@@ -259,17 +320,28 @@
// Requires this instance to be a leaf node, and `index` to be valid index.
inline absl::string_view Data(size_t index) const;
- static const char* EdgeDataPtr(const CordRep* r);
- static absl::string_view EdgeData(const CordRep* r);
+ // Diagnostics: returns true if `tree` is valid and internally consistent.
+ // If `shallow` is false, then the provided top level node and all child nodes
+ // below it are recursively checked. If `shallow` is true, only the provided
+ // node in `tree` and the cumulative length, type and height of the direct
+ // child nodes of `tree` are checked. The value of `shallow` is ignored if the
+ // internal `cord_btree_exhaustive_validation` diagnostics variable is true,
+ // in which case the performed validations works as if `shallow` were false.
+ // This function is intended for debugging and testing purposes only.
+ static bool IsValid(const CordRepBtree* tree, bool shallow = false);
- // Returns true if the provided rep is a FLAT, EXTERNAL or a SUBSTRING node
- // holding a FLAT or EXTERNAL child rep.
- static bool IsDataEdge(const CordRep* rep);
+ // Diagnostics: asserts that the provided tree is valid.
+ // `AssertValid()` performs a shallow validation by default. `shallow` can be
+ // set to false in which case an exhaustive validation is performed. This
+ // function is implemented in terms of calling `IsValid()` and asserting the
+ // return value to be true. See `IsValid()` for more information.
+ // This function is intended for debugging and testing purposes only.
+ static CordRepBtree* AssertValid(CordRepBtree* tree, bool shallow = true);
+ static const CordRepBtree* AssertValid(const CordRepBtree* tree,
+ bool shallow = true);
- // Diagnostics
- static bool IsValid(const CordRepBtree* tree);
- static CordRepBtree* AssertValid(CordRepBtree* tree);
- static const CordRepBtree* AssertValid(const CordRepBtree* tree);
+ // Diagnostics: dump the contents of this tree to `stream`.
+ // This function is intended for debugging and testing purposes only.
static void Dump(const CordRep* rep, std::ostream& stream);
static void Dump(const CordRep* rep, absl::string_view label,
std::ostream& stream);
@@ -307,6 +379,11 @@
// `front.height() + 1`. Requires `back.height() == front.height()`.
static CordRepBtree* New(CordRepBtree* front, CordRepBtree* back);
+ // Creates a fully balanced tree from the provided tree by rebuilding a new
+ // tree from all data edges in the input. This function is automatically
+ // invoked internally when the tree exceeds the maximum height.
+ static CordRepBtree* Rebuild(CordRepBtree* tree);
+
private:
CordRepBtree() = default;
~CordRepBtree() = default;
@@ -351,6 +428,12 @@
// Requires 0 < `offset` <= length.
Position IndexBefore(size_t offset) const;
+ // Returns the index of the edge ending at (or on) length `length`, and the
+ // number of bytes inside that edge up to `length`. For example, if we have a
+ // Node with 2 edges, one of 10 and one of 20 long, then IndexOfLength(27)
+ // will return {1, 17}, and IndexOfLength(10) will return {0, 10}.
+ Position IndexOfLength(size_t n) const;
+
// Identical to the above function except starting from the position `front`.
// This function is equivalent to `IndexBefore(front.n + offset)`, with
// the difference that this function is optimized to start at `front.index`.
@@ -363,12 +446,6 @@
// Requires `offset` < length.
Position IndexBeyond(size_t offset) const;
- // Destruction
- static void DestroyLeaf(CordRepBtree* tree, size_t begin, size_t end);
- static void DestroyNonLeaf(CordRepBtree* tree, size_t begin, size_t end);
- static void DestroyTree(CordRepBtree* tree, size_t begin, size_t end);
- static void Delete(CordRepBtree* tree) { delete tree; }
-
// Creates a new leaf node containing as much data as possible from `data`.
// The data is added either forwards or reversed depending on `edge_type`.
// Callers must check the length of the returned node to determine if all data
@@ -377,9 +454,9 @@
template <EdgeType edge_type>
static CordRepBtree* NewLeaf(absl::string_view data, size_t extra);
- // Creates a raw copy of this Btree node, copying all properties, but
- // without adding any references to existing edges.
- CordRepBtree* CopyRaw() const;
+ // Creates a raw copy of this Btree node with the specified length, copying
+ // all properties, but without adding any references to existing edges.
+ CordRepBtree* CopyRaw(size_t new_length) const;
// Creates a full copy of this Btree node, adding a reference on all edges.
CordRepBtree* Copy() const;
@@ -389,11 +466,28 @@
// created copy to `new_length`.
CordRepBtree* CopyBeginTo(size_t end, size_t new_length) const;
+ // Returns a tree containing the edges [tree->begin(), end) and length
+ // of `new_length`. This method consumes a reference on the provided
+ // tree, and logically performs the following operation:
+ // result = tree->CopyBeginTo(end, new_length);
+ // CordRep::Unref(tree);
+ // return result;
+ static CordRepBtree* ConsumeBeginTo(CordRepBtree* tree, size_t end,
+ size_t new_length);
+
// Creates a partial copy of this Btree node, copying all edges starting at
// `begin`, adding a reference on each copied edge, and sets the length of
// the newly created copy to `new_length`.
CordRepBtree* CopyToEndFrom(size_t begin, size_t new_length) const;
+ // Extracts and returns the front edge from the provided tree.
+ // This method consumes a reference on the provided tree, and logically
+ // performs the following operation:
+ // edge = CordRep::Ref(tree->Edge(kFront));
+ // CordRep::Unref(tree);
+ // return edge;
+ static CordRep* ExtractFront(CordRepBtree* tree);
+
// Returns a tree containing the result of appending `right` to `left`.
static CordRepBtree* MergeTrees(CordRepBtree* left, CordRepBtree* right);
@@ -403,6 +497,12 @@
static CordRepBtree* AppendSlow(CordRepBtree*, CordRep* rep);
static CordRepBtree* PrependSlow(CordRepBtree*, CordRep* rep);
+ // Recursively rebuilds `tree` into `stack`. If 'consume` is set to true, the
+ // function will consume a reference on `tree`. `stack` is a null terminated
+ // array containing the new tree's state, with the current leaf node at
+ // stack[0], and parent nodes above that, or null for 'top of tree'.
+ static void Rebuild(CordRepBtree** stack, CordRepBtree* tree, bool consume);
+
// Aligns existing edges to start at index 0, to allow for a new edge to be
// added to the back of the current edges.
inline void AlignBegin();
@@ -442,11 +542,11 @@
// Returns a partial copy of the current tree containing the first `n` bytes
// of data. `CopyResult` contains both the resulting edge and its height. The
// resulting tree may be less high than the current tree, or even be a single
- // matching data edge. For example, if `n == 1`, then the result will be the
- // single data edge, and height will be set to -1 (one below the owning leaf
- // node). If n == 0, this function returns null.
- // Requires `n <= length`
- CopyResult CopyPrefix(size_t n);
+ // matching data edge if `allow_folding` is set to true.
+ // For example, if `n == 1`, then the result will be the single data edge, and
+ // height will be set to -1 (one below the owning leaf node). If n == 0, this
+ // function returns null. Requires `n <= length`
+ CopyResult CopyPrefix(size_t n, bool allow_folding = true);
// Returns a partial copy of the current tree containing all data starting
// after `offset`. `CopyResult` contains both the resulting edge and its
@@ -496,12 +596,12 @@
};
inline CordRepBtree* CordRep::btree() {
- assert(tag == BTREE);
+ assert(IsBtree());
return static_cast<CordRepBtree*>(this);
}
inline const CordRepBtree* CordRep::btree() const {
- assert(tag == BTREE);
+ assert(IsBtree());
return static_cast<const CordRepBtree*>(this);
}
@@ -534,34 +634,11 @@
return {edges_ + begin, static_cast<size_t>(end - begin)};
}
-inline const char* CordRepBtree::EdgeDataPtr(const CordRep* r) {
- assert(IsDataEdge(r));
- size_t offset = 0;
- if (r->tag == SUBSTRING) {
- offset = r->substring()->start;
- r = r->substring()->child;
- }
- return (r->tag >= FLAT ? r->flat()->Data() : r->external()->base) + offset;
-}
-
-inline absl::string_view CordRepBtree::EdgeData(const CordRep* r) {
- return absl::string_view(EdgeDataPtr(r), r->length);
-}
-
inline absl::string_view CordRepBtree::Data(size_t index) const {
assert(height() == 0);
return EdgeData(Edge(index));
}
-inline bool CordRepBtree::IsDataEdge(const CordRep* rep) {
- // The fast path is that `rep` is an EXTERNAL or FLAT node, making the below
- // if a single, well predicted branch. We then repeat the FLAT or EXTERNAL
- // check in the slow path the SUBSTRING check to optimize for the hot path.
- if (rep->tag == EXTERNAL || rep->tag >= FLAT) return true;
- if (rep->tag == SUBSTRING) rep = rep->substring()->child;
- return rep->tag == EXTERNAL || rep->tag >= FLAT;
-}
-
inline CordRepBtree* CordRepBtree::New(int height) {
CordRepBtree* tree = new CordRepBtree;
tree->length = 0;
@@ -571,7 +648,7 @@
inline CordRepBtree* CordRepBtree::New(CordRep* rep) {
CordRepBtree* tree = new CordRepBtree;
- int height = rep->tag == BTREE ? rep->btree()->height() + 1 : 0;
+ int height = rep->IsBtree() ? rep->btree()->height() + 1 : 0;
tree->length = rep->length;
tree->InitInstance(height, /*begin=*/0, /*end=*/1);
tree->edges_[0] = rep;
@@ -589,28 +666,36 @@
return tree;
}
-inline void CordRepBtree::DestroyTree(CordRepBtree* tree, size_t begin,
- size_t end) {
- if (tree->height() == 0) {
- DestroyLeaf(tree, begin, end);
- } else {
- DestroyNonLeaf(tree, begin, end);
+inline void CordRepBtree::Unref(absl::Span<CordRep* const> edges) {
+ for (CordRep* edge : edges) {
+ if (ABSL_PREDICT_FALSE(!edge->refcount.Decrement())) {
+ CordRep::Destroy(edge);
+ }
}
}
-inline void CordRepBtree::Destroy(CordRepBtree* tree) {
- DestroyTree(tree, tree->begin(), tree->end());
-}
+inline CordRepBtree* CordRepBtree::CopyRaw(size_t new_length) const {
+ CordRepBtree* tree = new CordRepBtree;
-inline CordRepBtree* CordRepBtree::CopyRaw() const {
- auto* tree = static_cast<CordRepBtree*>(::operator new(sizeof(CordRepBtree)));
- memcpy(static_cast<void*>(tree), this, sizeof(CordRepBtree));
- new (&tree->refcount) Refcount;
+ // `length` and `refcount` are the first members of `CordRepBtree`.
+ // We initialize `length` using the given length, have `refcount` be set to
+ // ref = 1 through its default constructor, and copy all data beyond
+ // 'refcount' which starts with `tag` using a single memcpy: all contents
+ // except `refcount` is trivially copyable, and the compiler does not
+ // efficiently coalesce member-wise copy of these members.
+ // See https://gcc.godbolt.org/z/qY8zsca6z
+ // # LINT.IfChange(copy_raw)
+ tree->length = new_length;
+ uint8_t* dst = &tree->tag;
+ const uint8_t* src = &tag;
+ const ptrdiff_t offset = src - reinterpret_cast<const uint8_t*>(this);
+ memcpy(dst, src, sizeof(CordRepBtree) - static_cast<size_t>(offset));
return tree;
+ // # LINT.ThenChange()
}
inline CordRepBtree* CordRepBtree::Copy() const {
- CordRepBtree* tree = CopyRaw();
+ CordRepBtree* tree = CopyRaw(length);
for (CordRep* rep : Edges()) CordRep::Ref(rep);
return tree;
}
@@ -619,8 +704,7 @@
size_t new_length) const {
assert(begin >= this->begin());
assert(begin <= this->end());
- CordRepBtree* tree = CopyRaw();
- tree->length = new_length;
+ CordRepBtree* tree = CopyRaw(new_length);
tree->set_begin(begin);
for (CordRep* edge : tree->Edges()) CordRep::Ref(edge);
return tree;
@@ -630,8 +714,7 @@
size_t new_length) const {
assert(end <= capacity());
assert(end >= this->begin());
- CordRepBtree* tree = CopyRaw();
- tree->length = new_length;
+ CordRepBtree* tree = CopyRaw(new_length);
tree->set_end(end);
for (CordRep* edge : tree->Edges()) CordRep::Ref(edge);
return tree;
@@ -653,7 +736,7 @@
// size, and then do overlapping load/store of up to 4 pointers (inlined as
// XMM, YMM or ZMM load/store) and up to 2 pointers (XMM / YMM), which is a)
// compact and b) not clobbering any registers.
- ABSL_INTERNAL_ASSUME(new_end <= kMaxCapacity);
+ ABSL_ASSUME(new_end <= kMaxCapacity);
#ifdef __clang__
#pragma unroll 1
#endif
@@ -671,7 +754,7 @@
const size_t new_end = end() + delta;
set_begin(new_begin);
set_end(new_end);
- ABSL_INTERNAL_ASSUME(new_end <= kMaxCapacity);
+ ABSL_ASSUME(new_end <= kMaxCapacity);
#ifdef __clang__
#pragma unroll 1
#endif
@@ -745,6 +828,14 @@
return {index, offset};
}
+inline CordRepBtree::Position CordRepBtree::IndexOfLength(size_t n) const {
+ assert(n <= length);
+ size_t index = back();
+ size_t strip = length - n;
+ while (strip >= edges_[index]->length) strip -= edges_[index--]->length;
+ return {index, edges_[index]->length - strip};
+}
+
inline CordRepBtree::Position CordRepBtree::IndexBeyond(
const size_t offset) const {
// We need to find the edge which `starting offset` is beyond (>=)`offset`.
@@ -834,11 +925,13 @@
#ifdef NDEBUG
-inline CordRepBtree* CordRepBtree::AssertValid(CordRepBtree* tree) {
+inline CordRepBtree* CordRepBtree::AssertValid(CordRepBtree* tree,
+ bool /* shallow */) {
return tree;
}
-inline const CordRepBtree* CordRepBtree::AssertValid(const CordRepBtree* tree) {
+inline const CordRepBtree* CordRepBtree::AssertValid(const CordRepBtree* tree,
+ bool /* shallow */) {
return tree;
}
diff --git a/absl/strings/internal/cord_rep_btree_navigator.cc b/absl/strings/internal/cord_rep_btree_navigator.cc
index d1f9995..6ed20c2 100644
--- a/absl/strings/internal/cord_rep_btree_navigator.cc
+++ b/absl/strings/internal/cord_rep_btree_navigator.cc
@@ -16,6 +16,7 @@
#include <cassert>
+#include "absl/strings/internal/cord_data_edge.h"
#include "absl/strings/internal/cord_internal.h"
#include "absl/strings/internal/cord_rep_btree.h"
@@ -39,7 +40,7 @@
assert(n <= rep->length);
assert(offset < rep->length);
assert(offset <= rep->length - n);
- assert(CordRepBtree::IsDataEdge(rep));
+ assert(IsDataEdge(rep));
if (n == 0) return nullptr;
if (n == rep->length) return CordRep::Ref(rep);
@@ -49,6 +50,7 @@
rep = rep->substring()->child;
}
+ assert(rep->IsExternal() || rep->IsFlat());
CordRepSubstring* substring = new CordRepSubstring();
substring->length = n;
substring->tag = SUBSTRING;
@@ -88,7 +90,7 @@
// edges that must be skipped.
while (height > 0) {
node = edge->btree();
- index_[height] = index;
+ index_[height] = static_cast<uint8_t>(index);
node_[--height] = node;
index = node->begin();
edge = node->Edge(index);
@@ -99,7 +101,7 @@
edge = node->Edge(index);
}
}
- index_[0] = index;
+ index_[0] = static_cast<uint8_t>(index);
return {edge, n};
}
@@ -124,7 +126,7 @@
do {
length -= edge->length;
while (++index == node->end()) {
- index_[height] = index;
+ index_[height] = static_cast<uint8_t>(index);
if (++height > height_) {
subtree->set_end(subtree_end);
if (length == 0) return {subtree, 0};
@@ -152,7 +154,7 @@
// edges that must be read, adding 'down' nodes to `subtree`.
while (height > 0) {
node = edge->btree();
- index_[height] = index;
+ index_[height] = static_cast<uint8_t>(index);
node_[--height] = node;
index = node->begin();
edge = node->Edge(index);
@@ -176,7 +178,7 @@
subtree->edges_[subtree_end++] = Substring(edge, 0, length);
}
subtree->set_end(subtree_end);
- index_[0] = index;
+ index_[0] = static_cast<uint8_t>(index);
return {tree, length};
}
diff --git a/absl/strings/internal/cord_rep_btree_navigator.h b/absl/strings/internal/cord_rep_btree_navigator.h
index 971b92e..3d581c8 100644
--- a/absl/strings/internal/cord_rep_btree_navigator.h
+++ b/absl/strings/internal/cord_rep_btree_navigator.h
@@ -143,8 +143,8 @@
// `index_` and `node_` contain the navigation state as the 'path' to the
// current data edge which is at `node_[0]->Edge(index_[0])`. The contents
// of these are undefined until the instance is initialized (`height_ >= 0`).
- uint8_t index_[CordRepBtree::kMaxHeight];
- CordRepBtree* node_[CordRepBtree::kMaxHeight];
+ uint8_t index_[CordRepBtree::kMaxDepth];
+ CordRepBtree* node_[CordRepBtree::kMaxDepth];
};
// Returns true if this instance is not empty.
@@ -173,6 +173,7 @@
inline CordRep* CordRepBtreeNavigator::Init(CordRepBtree* tree) {
assert(tree != nullptr);
assert(tree->size() > 0);
+ assert(tree->height() <= CordRepBtree::kMaxHeight);
int height = height_ = tree->height();
size_t index = tree->index(edge_type);
node_[height] = tree;
@@ -206,6 +207,7 @@
inline CordRepBtreeNavigator::Position CordRepBtreeNavigator::InitOffset(
CordRepBtree* tree, size_t offset) {
assert(tree != nullptr);
+ assert(tree->height() <= CordRepBtree::kMaxHeight);
if (ABSL_PREDICT_FALSE(offset >= tree->length)) return {nullptr, 0};
height_ = tree->height();
node_[height_] = tree;
diff --git a/absl/strings/internal/cord_rep_btree_navigator_test.cc b/absl/strings/internal/cord_rep_btree_navigator_test.cc
index ce09b19..bed7550 100644
--- a/absl/strings/internal/cord_rep_btree_navigator_test.cc
+++ b/absl/strings/internal/cord_rep_btree_navigator_test.cc
@@ -48,7 +48,7 @@
// CordRepBtreeNavigatorTest is a test fixture which automatically creates a
// tree to test navigation logic on. The parameter `count' defines the number of
// data edges in the test tree.
-class CordRepBtreeNavigatorTest : public testing::TestWithParam<int> {
+class CordRepBtreeNavigatorTest : public testing::TestWithParam<size_t> {
public:
using Flats = std::vector<CordRep*>;
static constexpr size_t kCharsPerFlat = 3;
@@ -71,12 +71,12 @@
~CordRepBtreeNavigatorTest() override { CordRep::Unref(tree_); }
- int count() const { return GetParam(); }
+ size_t count() const { return GetParam(); }
CordRepBtree* tree() { return tree_; }
const std::string& data() const { return data_; }
const std::vector<CordRep*>& flats() const { return flats_; }
- static std::string ToString(testing::TestParamInfo<int> param) {
+ static std::string ToString(testing::TestParamInfo<size_t> param) {
return absl::StrCat(param.param, "_Flats");
}
@@ -131,15 +131,15 @@
EXPECT_THAT(nav.Previous(), Eq(nullptr));
EXPECT_THAT(nav.Current(), Eq(flats.front()));
- for (int i = 1; i < flats.size(); ++i) {
+ for (size_t i = 1; i < flats.size(); ++i) {
ASSERT_THAT(nav.Next(), Eq(flats[i]));
EXPECT_THAT(nav.Current(), Eq(flats[i]));
}
EXPECT_THAT(nav.Next(), Eq(nullptr));
EXPECT_THAT(nav.Current(), Eq(flats.back()));
- for (int i = static_cast<int>(flats.size()) - 2; i >= 0; --i) {
- ASSERT_THAT(nav.Previous(), Eq(flats[i]));
- EXPECT_THAT(nav.Current(), Eq(flats[i]));
+ for (size_t i = flats.size() - 1; i > 0; --i) {
+ ASSERT_THAT(nav.Previous(), Eq(flats[i - 1]));
+ EXPECT_THAT(nav.Current(), Eq(flats[i - 1]));
}
EXPECT_THAT(nav.Previous(), Eq(nullptr));
EXPECT_THAT(nav.Current(), Eq(flats.front()));
@@ -152,13 +152,13 @@
EXPECT_THAT(nav.Next(), Eq(nullptr));
EXPECT_THAT(nav.Current(), Eq(flats.back()));
- for (int i = static_cast<int>(flats.size()) - 2; i >= 0; --i) {
- ASSERT_THAT(nav.Previous(), Eq(flats[i]));
- EXPECT_THAT(nav.Current(), Eq(flats[i]));
+ for (size_t i = flats.size() - 1; i > 0; --i) {
+ ASSERT_THAT(nav.Previous(), Eq(flats[i - 1]));
+ EXPECT_THAT(nav.Current(), Eq(flats[i - 1]));
}
EXPECT_THAT(nav.Previous(), Eq(nullptr));
EXPECT_THAT(nav.Current(), Eq(flats.front()));
- for (int i = 1; i < flats.size(); ++i) {
+ for (size_t i = 1; i < flats.size(); ++i) {
ASSERT_THAT(nav.Next(), Eq(flats[i]));
EXPECT_THAT(nav.Current(), Eq(flats[i]));
}
@@ -180,21 +180,21 @@
}
TEST_P(CordRepBtreeNavigatorTest, Skip) {
- int count = this->count();
+ size_t count = this->count();
const Flats& flats = this->flats();
CordRepBtreeNavigator nav;
nav.InitFirst(tree());
- for (int char_offset = 0; char_offset < kCharsPerFlat; ++char_offset) {
+ for (size_t char_offset = 0; char_offset < kCharsPerFlat; ++char_offset) {
Position pos = nav.Skip(char_offset);
EXPECT_THAT(pos.edge, Eq(nav.Current()));
EXPECT_THAT(pos.edge, Eq(flats[0]));
EXPECT_THAT(pos.offset, Eq(char_offset));
}
- for (int index1 = 0; index1 < count; ++index1) {
- for (int index2 = index1; index2 < count; ++index2) {
- for (int char_offset = 0; char_offset < kCharsPerFlat; ++char_offset) {
+ for (size_t index1 = 0; index1 < count; ++index1) {
+ for (size_t index2 = index1; index2 < count; ++index2) {
+ for (size_t char_offset = 0; char_offset < kCharsPerFlat; ++char_offset) {
CordRepBtreeNavigator nav;
nav.InitFirst(tree());
@@ -215,20 +215,20 @@
}
TEST_P(CordRepBtreeNavigatorTest, Seek) {
- int count = this->count();
+ size_t count = this->count();
const Flats& flats = this->flats();
CordRepBtreeNavigator nav;
nav.InitFirst(tree());
- for (int char_offset = 0; char_offset < kCharsPerFlat; ++char_offset) {
+ for (size_t char_offset = 0; char_offset < kCharsPerFlat; ++char_offset) {
Position pos = nav.Seek(char_offset);
EXPECT_THAT(pos.edge, Eq(nav.Current()));
EXPECT_THAT(pos.edge, Eq(flats[0]));
EXPECT_THAT(pos.offset, Eq(char_offset));
}
- for (int index = 0; index < count; ++index) {
- for (int char_offset = 0; char_offset < kCharsPerFlat; ++char_offset) {
+ for (size_t index = 0; index < count; ++index) {
+ for (size_t char_offset = 0; char_offset < kCharsPerFlat; ++char_offset) {
size_t offset = index * kCharsPerFlat + char_offset;
Position pos1 = nav.Seek(offset);
ASSERT_THAT(pos1.edge, Eq(flats[index]));
@@ -249,7 +249,7 @@
EXPECT_THAT(nav.btree(), Eq(tree));
EXPECT_THAT(pos.edge, Eq(tree->Edges()[1]));
EXPECT_THAT(pos.edge, Eq(nav.Current()));
- EXPECT_THAT(pos.offset, Eq(2));
+ EXPECT_THAT(pos.offset, Eq(2u));
CordRep::Unref(tree);
}
@@ -319,6 +319,27 @@
ASSERT_THAT(result.tree, Eq(nullptr));
}
+TEST(CordRepBtreeNavigatorTest, NavigateMaximumTreeDepth) {
+ CordRepFlat* flat1 = MakeFlat("Hello world");
+ CordRepFlat* flat2 = MakeFlat("World Hello");
+
+ CordRepBtree* node = CordRepBtree::Create(flat1);
+ node = CordRepBtree::Append(node, flat2);
+ while (node->height() < CordRepBtree::kMaxHeight) {
+ node = CordRepBtree::New(node);
+ }
+
+ CordRepBtreeNavigator nav;
+ CordRep* edge = nav.InitFirst(node);
+ EXPECT_THAT(edge, Eq(flat1));
+ EXPECT_THAT(nav.Next(), Eq(flat2));
+ EXPECT_THAT(nav.Next(), Eq(nullptr));
+ EXPECT_THAT(nav.Previous(), Eq(flat1));
+ EXPECT_THAT(nav.Previous(), Eq(nullptr));
+
+ CordRep::Unref(node);
+}
+
} // namespace
} // namespace cord_internal
ABSL_NAMESPACE_END
diff --git a/absl/strings/internal/cord_rep_btree_reader.cc b/absl/strings/internal/cord_rep_btree_reader.cc
index 3ba4314..0d0e860 100644
--- a/absl/strings/internal/cord_rep_btree_reader.cc
+++ b/absl/strings/internal/cord_rep_btree_reader.cc
@@ -17,6 +17,7 @@
#include <cassert>
#include "absl/base/config.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_btree_navigator.h"
@@ -44,7 +45,7 @@
// can directly return the substring into the current data edge as the next
// chunk. We can easily establish from the above code that `navigator_.Next()`
// has not been called as that requires `chunk_size` to be zero.
- if (n < chunk_size) return CordRepBtree::EdgeData(edge).substr(result.n);
+ if (n < chunk_size) return EdgeData(edge).substr(result.n);
// The amount of data taken from the last edge is `chunk_size` and `result.n`
// contains the offset into the current edge trailing the read data (which can
@@ -52,15 +53,15 @@
// data, calling `navigator_.Current()` is not safe before checking if we
// already consumed all remaining data.
const size_t consumed_by_read = n - chunk_size - result.n;
- if (consumed_ + consumed_by_read >= length()) {
- consumed_ = length();
+ if (consumed_by_read >= remaining_) {
+ remaining_ = 0;
return {};
}
// We did not read all data, return remaining data from current edge.
edge = navigator_.Current();
- consumed_ += consumed_by_read + edge->length;
- return CordRepBtree::EdgeData(edge).substr(result.n);
+ remaining_ -= consumed_by_read + edge->length;
+ return EdgeData(edge).substr(result.n);
}
} // namespace cord_internal
diff --git a/absl/strings/internal/cord_rep_btree_reader.h b/absl/strings/internal/cord_rep_btree_reader.h
index c19fa43..8db8f8d 100644
--- a/absl/strings/internal/cord_rep_btree_reader.h
+++ b/absl/strings/internal/cord_rep_btree_reader.h
@@ -18,6 +18,7 @@
#include <cassert>
#include "absl/base/config.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_btree_navigator.h"
@@ -31,9 +32,7 @@
// References to the underlying data are returned as absl::string_view values.
// The most typical use case is a forward only iteration over tree data.
// The class also provides `Skip()`, `Seek()` and `Read()` methods similar to
-// CordRepBtreeNavigator that allow more advanced navigation. The class provides
-// a `consumed` property which contains the end offset of the chunk last
-// returned to the user which is useful in cord iteration logic.
+// CordRepBtreeNavigator that allow more advanced navigation.
//
// Example: iterate over all data inside a cord btree:
//
@@ -61,9 +60,9 @@
// absl::string_view sv = reader.Next();
// }
//
-// It is important to notice that `consumed` represents the end position of the
-// last data edge returned to the caller, not the cumulative data returned to
-// the caller which can be less in cases of skipping or seeking over data.
+// It is important to notice that `remaining` is based on the end position of
+// the last data edge returned to the caller, not the cumulative data returned
+// to the caller which can be less in cases of skipping or seeking over data.
//
// For example, consider a cord btree with five data edges: "abc", "def", "ghi",
// "jkl" and "mno":
@@ -71,14 +70,12 @@
// absl::string_view sv;
// CordRepBtreeReader reader;
//
-// sv = reader.Init(tree); // sv = "abc", reader.consumed() = 3
-// sv = reader.Skip(4); // sv = "hi", reader.consumed() = 9
-// sv = reader.Skip(2); // sv = "l", reader.consumed() = 12
-// sv = reader.Next(); // sv = "mno", reader.consumed() = 15
+// sv = reader.Init(tree); // sv = "abc", remaining = 12
+// sv = reader.Skip(4); // sv = "hi", remaining = 6
+// sv = reader.Skip(2); // sv = "l", remaining = 3
+// sv = reader.Next(); // sv = "mno", remaining = 0
+// sv = reader.Seek(1); // sv = "bc", remaining = 12
//
-// In the above example, `reader.consumed()` reflects the data edges iterated
-// over or skipped by the reader, not the amount of data 'consumed' by the
-// caller.
class CordRepBtreeReader {
public:
using ReadResult = CordRepBtreeNavigator::ReadResult;
@@ -98,13 +95,14 @@
// Requires that the current instance is not empty.
size_t length() const;
- // Returns the end offset of the last navigated to chunk, which represents the
- // total bytes 'consumed' relative to the start of the tree. The returned
- // value is never zero. For example, initializing a reader with a tree with a
- // first data edge of 19 bytes will return `consumed() = 19`. See also the
- // class comments on the meaning of `consumed`.
- // Requires that the current instance is not empty.
- size_t consumed() const;
+ // Returns the number of remaining bytes available for iteration, which is the
+ // number of bytes directly following the end of the last chunk returned.
+ // This value will be zero if we iterated over the last edge in the bound
+ // tree, in which case any call to Next() or Skip() will return an empty
+ // string_view reflecting the EOF state.
+ // Note that a call to `Seek()` resets `remaining` to a value based on the
+ // end position of the chunk returned by that call.
+ size_t remaining() const { return remaining_; }
// Resets this instance to an empty value.
void Reset() { navigator_.Reset(); }
@@ -157,7 +155,7 @@
absl::string_view Seek(size_t offset);
private:
- size_t consumed_;
+ size_t remaining_ = 0;
CordRepBtreeNavigator navigator_;
};
@@ -166,24 +164,19 @@
return btree()->length;
}
-inline size_t CordRepBtreeReader::consumed() const {
- assert(btree() != nullptr);
- return consumed_;
-}
-
inline absl::string_view CordRepBtreeReader::Init(CordRepBtree* tree) {
assert(tree != nullptr);
const CordRep* edge = navigator_.InitFirst(tree);
- consumed_ = edge->length;
- return CordRepBtree::EdgeData(edge);
+ remaining_ = tree->length - edge->length;
+ return EdgeData(edge);
}
inline absl::string_view CordRepBtreeReader::Next() {
- assert(consumed() < length());
+ if (remaining_ == 0) return {};
const CordRep* edge = navigator_.Next();
assert(edge != nullptr);
- consumed_ += edge->length;
- return CordRepBtree::EdgeData(edge);
+ remaining_ -= edge->length;
+ return EdgeData(edge);
}
inline absl::string_view CordRepBtreeReader::Skip(size_t skip) {
@@ -192,23 +185,23 @@
const size_t edge_length = navigator_.Current()->length;
CordRepBtreeNavigator::Position pos = navigator_.Skip(skip + edge_length);
if (ABSL_PREDICT_FALSE(pos.edge == nullptr)) {
- consumed_ = length();
+ remaining_ = 0;
return {};
}
// The combined length of all edges skipped before `pos.edge` is `skip -
// pos.offset`, all of which are 'consumed', as well as the current edge.
- consumed_ += skip - pos.offset + pos.edge->length;
- return CordRepBtree::EdgeData(pos.edge).substr(pos.offset);
+ remaining_ -= skip - pos.offset + pos.edge->length;
+ return EdgeData(pos.edge).substr(pos.offset);
}
inline absl::string_view CordRepBtreeReader::Seek(size_t offset) {
const CordRepBtreeNavigator::Position pos = navigator_.Seek(offset);
if (ABSL_PREDICT_FALSE(pos.edge == nullptr)) {
- consumed_ = length();
+ remaining_ = 0;
return {};
}
- absl::string_view chunk = CordRepBtree::EdgeData(pos.edge).substr(pos.offset);
- consumed_ = offset + chunk.length();
+ absl::string_view chunk = EdgeData(pos.edge).substr(pos.offset);
+ remaining_ = length() - offset - chunk.length();
return chunk;
}
diff --git a/absl/strings/internal/cord_rep_btree_reader_test.cc b/absl/strings/internal/cord_rep_btree_reader_test.cc
index 44d3365..b4cdd8e 100644
--- a/absl/strings/internal/cord_rep_btree_reader_test.cc
+++ b/absl/strings/internal/cord_rep_btree_reader_test.cc
@@ -50,30 +50,34 @@
TEST(CordRepBtreeReaderTest, Next) {
constexpr size_t kChars = 3;
const size_t cap = CordRepBtree::kMaxCapacity;
- int counts[] = {1, 2, cap, cap * cap, cap * cap + 1, cap * cap * 2 + 17};
+ size_t counts[] = {1, 2, cap, cap * cap, cap * cap + 1, cap * cap * 2 + 17};
- for (int count : counts) {
+ for (size_t count : counts) {
std::string data = CreateRandomString(count * kChars);
std::vector<CordRep*> flats = CreateFlatsFromString(data, kChars);
CordRepBtree* node = CordRepBtreeFromFlats(flats);
CordRepBtreeReader reader;
+ size_t remaining = data.length();
absl::string_view chunk = reader.Init(node);
EXPECT_THAT(chunk, Eq(data.substr(0, chunk.length())));
- size_t consumed = chunk.length();
- EXPECT_THAT(reader.consumed(), Eq(consumed));
+ remaining -= chunk.length();
+ EXPECT_THAT(reader.remaining(), Eq(remaining));
- while (consumed < data.length()) {
+ while (remaining > 0) {
+ const size_t offset = data.length() - remaining;
chunk = reader.Next();
- EXPECT_THAT(chunk, Eq(data.substr(consumed, chunk.length())));
+ EXPECT_THAT(chunk, Eq(data.substr(offset, chunk.length())));
- consumed += chunk.length();
- EXPECT_THAT(reader.consumed(), Eq(consumed));
+ remaining -= chunk.length();
+ EXPECT_THAT(reader.remaining(), Eq(remaining));
}
- EXPECT_THAT(consumed, Eq(data.length()));
- EXPECT_THAT(reader.consumed(), Eq(data.length()));
+ EXPECT_THAT(reader.remaining(), Eq(0u));
+
+ // Verify trying to read beyond EOF returns empty string_view
+ EXPECT_THAT(reader.Next(), testing::IsEmpty());
CordRep::Unref(node);
}
@@ -82,9 +86,9 @@
TEST(CordRepBtreeReaderTest, Skip) {
constexpr size_t kChars = 3;
const size_t cap = CordRepBtree::kMaxCapacity;
- int counts[] = {1, 2, cap, cap * cap, cap * cap + 1, cap * cap * 2 + 17};
+ size_t counts[] = {1, 2, cap, cap * cap, cap * cap + 1, cap * cap * 2 + 17};
- for (int count : counts) {
+ for (size_t count : counts) {
std::string data = CreateRandomString(count * kChars);
std::vector<CordRep*> flats = CreateFlatsFromString(data, kChars);
CordRepBtree* node = CordRepBtreeFromFlats(flats);
@@ -92,19 +96,22 @@
for (size_t skip1 = 0; skip1 < data.length() - kChars; ++skip1) {
for (size_t skip2 = 0; skip2 < data.length() - kChars; ++skip2) {
CordRepBtreeReader reader;
+ size_t remaining = data.length();
absl::string_view chunk = reader.Init(node);
- size_t consumed = chunk.length();
+ remaining -= chunk.length();
chunk = reader.Skip(skip1);
- ASSERT_THAT(chunk, Eq(data.substr(consumed + skip1, chunk.length())));
- consumed += chunk.length() + skip1;
- ASSERT_THAT(reader.consumed(), Eq(consumed));
+ size_t offset = data.length() - remaining;
+ ASSERT_THAT(chunk, Eq(data.substr(offset + skip1, chunk.length())));
+ remaining -= chunk.length() + skip1;
+ ASSERT_THAT(reader.remaining(), Eq(remaining));
- if (consumed >= data.length()) continue;
+ if (remaining == 0) continue;
- size_t skip = std::min(data.length() - consumed - 1, skip2);
+ size_t skip = std::min(remaining - 1, skip2);
chunk = reader.Skip(skip);
- ASSERT_THAT(chunk, Eq(data.substr(consumed + skip, chunk.length())));
+ offset = data.length() - remaining;
+ ASSERT_THAT(chunk, Eq(data.substr(offset + skip, chunk.length())));
}
}
@@ -118,16 +125,16 @@
CordRepBtreeReader reader;
reader.Init(tree);
EXPECT_THAT(reader.Skip(100), IsEmpty());
- EXPECT_THAT(reader.consumed(), Eq(6));
+ EXPECT_THAT(reader.remaining(), Eq(0u));
CordRep::Unref(tree);
}
TEST(CordRepBtreeReaderTest, Seek) {
constexpr size_t kChars = 3;
const size_t cap = CordRepBtree::kMaxCapacity;
- int counts[] = {1, 2, cap, cap * cap, cap * cap + 1, cap * cap * 2 + 17};
+ size_t counts[] = {1, 2, cap, cap * cap, cap * cap + 1, cap * cap * 2 + 17};
- for (int count : counts) {
+ for (size_t count : counts) {
std::string data = CreateRandomString(count * kChars);
std::vector<CordRep*> flats = CreateFlatsFromString(data, kChars);
CordRepBtree* node = CordRepBtreeFromFlats(flats);
@@ -138,7 +145,8 @@
absl::string_view chunk = reader.Seek(seek);
ASSERT_THAT(chunk, Not(IsEmpty()));
ASSERT_THAT(chunk, Eq(data.substr(seek, chunk.length())));
- ASSERT_THAT(reader.consumed(), Eq(seek + chunk.length()));
+ ASSERT_THAT(reader.remaining(),
+ Eq(data.length() - seek - chunk.length()));
}
CordRep::Unref(node);
@@ -151,9 +159,9 @@
CordRepBtreeReader reader;
reader.Init(tree);
EXPECT_THAT(reader.Seek(6), IsEmpty());
- EXPECT_THAT(reader.consumed(), Eq(6));
+ EXPECT_THAT(reader.remaining(), Eq(0u));
EXPECT_THAT(reader.Seek(100), IsEmpty());
- EXPECT_THAT(reader.consumed(), Eq(6));
+ EXPECT_THAT(reader.remaining(), Eq(0u));
CordRep::Unref(tree);
}
@@ -171,7 +179,7 @@
chunk = reader.Read(0, chunk.length(), tree);
EXPECT_THAT(tree, Eq(nullptr));
EXPECT_THAT(chunk, Eq("abcde"));
- EXPECT_THAT(reader.consumed(), Eq(5));
+ EXPECT_THAT(reader.remaining(), Eq(10u));
EXPECT_THAT(reader.Next(), Eq("fghij"));
// Read in full
@@ -180,7 +188,7 @@
EXPECT_THAT(tree, Ne(nullptr));
EXPECT_THAT(CordToString(tree), Eq("abcdefghijklmno"));
EXPECT_THAT(chunk, Eq(""));
- EXPECT_THAT(reader.consumed(), Eq(15));
+ EXPECT_THAT(reader.remaining(), Eq(0u));
CordRep::Unref(tree);
// Read < chunk bytes
@@ -189,7 +197,7 @@
ASSERT_THAT(tree, Ne(nullptr));
EXPECT_THAT(CordToString(tree), Eq("abc"));
EXPECT_THAT(chunk, Eq("de"));
- EXPECT_THAT(reader.consumed(), Eq(5));
+ EXPECT_THAT(reader.remaining(), Eq(10u));
EXPECT_THAT(reader.Next(), Eq("fghij"));
CordRep::Unref(tree);
@@ -199,7 +207,7 @@
ASSERT_THAT(tree, Ne(nullptr));
EXPECT_THAT(CordToString(tree), Eq("cd"));
EXPECT_THAT(chunk, Eq("e"));
- EXPECT_THAT(reader.consumed(), Eq(5));
+ EXPECT_THAT(reader.remaining(), Eq(10u));
EXPECT_THAT(reader.Next(), Eq("fghij"));
CordRep::Unref(tree);
@@ -209,7 +217,7 @@
ASSERT_THAT(tree, Ne(nullptr));
EXPECT_THAT(CordToString(tree), Eq("fgh"));
EXPECT_THAT(chunk, Eq("ij"));
- EXPECT_THAT(reader.consumed(), Eq(10));
+ EXPECT_THAT(reader.remaining(), Eq(5u));
EXPECT_THAT(reader.Next(), Eq("klmno"));
CordRep::Unref(tree);
@@ -219,7 +227,7 @@
ASSERT_THAT(tree, Ne(nullptr));
EXPECT_THAT(CordToString(tree), Eq("cdefghijklmn"));
EXPECT_THAT(chunk, Eq("o"));
- EXPECT_THAT(reader.consumed(), Eq(15));
+ EXPECT_THAT(reader.remaining(), Eq(0u));
CordRep::Unref(tree);
// Read across chunks landing on exact edge boundary
@@ -228,7 +236,7 @@
ASSERT_THAT(tree, Ne(nullptr));
EXPECT_THAT(CordToString(tree), Eq("cdefghij"));
EXPECT_THAT(chunk, Eq("klmno"));
- EXPECT_THAT(reader.consumed(), Eq(15));
+ EXPECT_THAT(reader.remaining(), Eq(0u));
CordRep::Unref(tree);
CordRep::Unref(node);
@@ -237,9 +245,9 @@
TEST(CordRepBtreeReaderTest, ReadExhaustive) {
constexpr size_t kChars = 3;
const size_t cap = CordRepBtree::kMaxCapacity;
- int counts[] = {1, 2, cap, cap * cap + 1, cap * cap * cap * 2 + 17};
+ size_t counts[] = {1, 2, cap, cap * cap + 1, cap * cap * cap * 2 + 17};
- for (int count : counts) {
+ for (size_t count : counts) {
std::string data = CreateRandomString(count * kChars);
std::vector<CordRep*> flats = CreateFlatsFromString(data, kChars);
CordRepBtree* node = CordRepBtreeFromFlats(flats);
@@ -264,7 +272,7 @@
consumed += n;
remaining -= n;
- EXPECT_THAT(reader.consumed(), Eq(consumed + chunk.length()));
+ EXPECT_THAT(reader.remaining(), Eq(remaining - chunk.length()));
if (remaining > 0) {
ASSERT_FALSE(chunk.empty());
diff --git a/absl/strings/internal/cord_rep_btree_test.cc b/absl/strings/internal/cord_rep_btree_test.cc
index 7a0b781..840acf9 100644
--- a/absl/strings/internal/cord_rep_btree_test.cc
+++ b/absl/strings/internal/cord_rep_btree_test.cc
@@ -24,6 +24,8 @@
#include "gtest/gtest.h"
#include "absl/base/config.h"
#include "absl/base/internal/raw_logging.h"
+#include "absl/cleanup/cleanup.h"
+#include "absl/strings/internal/cord_data_edge.h"
#include "absl/strings/internal/cord_internal.h"
#include "absl/strings/internal/cord_rep_test_util.h"
#include "absl/strings/str_cat.h"
@@ -46,10 +48,11 @@
namespace {
using ::absl::cordrep_testing::AutoUnref;
+using ::absl::cordrep_testing::CordCollectRepsIf;
using ::absl::cordrep_testing::CordToString;
+using ::absl::cordrep_testing::CordVisitReps;
using ::absl::cordrep_testing::CreateFlatsFromString;
using ::absl::cordrep_testing::CreateRandomString;
-using ::absl::cordrep_testing::MakeConcat;
using ::absl::cordrep_testing::MakeExternal;
using ::absl::cordrep_testing::MakeFlat;
using ::absl::cordrep_testing::MakeSubstring;
@@ -61,6 +64,7 @@
using ::testing::ElementsAreArray;
using ::testing::Eq;
using ::testing::HasSubstr;
+using ::testing::Le;
using ::testing::Ne;
using ::testing::Not;
using ::testing::SizeIs;
@@ -124,6 +128,16 @@
return true;
}
+MATCHER_P2(EqExtractResult, tree, rep, "Equals ExtractResult") {
+ if (arg.tree != tree || arg.extracted != rep) {
+ *result_listener << "Expected {" << static_cast<const void*>(tree) << ", "
+ << static_cast<const void*>(rep) << "}, got {" << arg.tree
+ << ", " << arg.extracted << "}";
+ return false;
+ }
+ return true;
+}
+
// DataConsumer is a simple helper class used by tests to 'consume' string
// fragments from the provided input in forward or backward direction.
class DataConsumer {
@@ -204,14 +218,17 @@
return tree;
}
-CordRepBtree* CreateTree(absl::string_view data, size_t chunk_size) {
- std::vector<CordRep*> flats = CreateFlatsFromString(data, chunk_size);
- auto it = flats.begin();
+CordRepBtree* CreateTree(absl::Span<CordRep* const> reps) {
+ auto it = reps.begin();
CordRepBtree* tree = CordRepBtree::Create(*it);
- while (++it != flats.end()) tree = CordRepBtree::Append(tree, *it);
+ while (++it != reps.end()) tree = CordRepBtree::Append(tree, *it);
return tree;
}
+CordRepBtree* CreateTree(absl::string_view data, size_t chunk_size) {
+ return CreateTree(CreateFlatsFromString(data, chunk_size));
+}
+
CordRepBtree* CreateTreeReverse(absl::string_view data, size_t chunk_size) {
std::vector<CordRep*> flats = CreateFlatsFromString(data, chunk_size);
auto rit = flats.rbegin();
@@ -267,13 +284,14 @@
TEST(CordRepBtreeTest, SizeIsMultipleOf64) {
// Only enforce for fully 64-bit platforms.
if (sizeof(size_t) == 8 && sizeof(void*) == 8) {
- EXPECT_THAT(sizeof(CordRepBtree) % 64, Eq(0)) << "Should be multiple of 64";
+ EXPECT_THAT(sizeof(CordRepBtree) % 64, Eq(0u))
+ << "Should be multiple of 64";
}
}
TEST(CordRepBtreeTest, NewDestroyEmptyTree) {
auto* tree = CordRepBtree::New();
- EXPECT_THAT(tree->size(), Eq(0));
+ EXPECT_THAT(tree->size(), Eq(0u));
EXPECT_THAT(tree->height(), Eq(0));
EXPECT_THAT(tree->Edges(), ElementsAre());
CordRepBtree::Destroy(tree);
@@ -281,7 +299,7 @@
TEST(CordRepBtreeTest, NewDestroyEmptyTreeAtHeight) {
auto* tree = CordRepBtree::New(3);
- EXPECT_THAT(tree->size(), Eq(0));
+ EXPECT_THAT(tree->size(), Eq(0u));
EXPECT_THAT(tree->height(), Eq(3));
EXPECT_THAT(tree->Edges(), ElementsAre());
CordRepBtree::Destroy(tree);
@@ -305,40 +323,31 @@
CordRepExternal* external = MakeExternal("Hello external");
CordRep* substr1 = MakeSubstring(1, 6, CordRep::Ref(flat));
CordRep* substr2 = MakeSubstring(1, 6, CordRep::Ref(external));
- CordRep* concat = MakeConcat(CordRep::Ref(flat), CordRep::Ref(external));
CordRep* bad_substr = MakeSubstring(1, 2, CordRep::Ref(substr1));
- EXPECT_TRUE(CordRepBtree::IsDataEdge(flat));
- EXPECT_THAT(CordRepBtree::EdgeDataPtr(flat),
- TypedEq<const void*>(flat->Data()));
- EXPECT_THAT(CordRepBtree::EdgeData(flat), Eq("Hello world"));
+ EXPECT_TRUE(IsDataEdge(flat));
+ EXPECT_THAT(EdgeData(flat).data(), TypedEq<const void*>(flat->Data()));
+ EXPECT_THAT(EdgeData(flat), Eq("Hello world"));
- EXPECT_TRUE(CordRepBtree::IsDataEdge(external));
- EXPECT_THAT(CordRepBtree::EdgeDataPtr(external),
- TypedEq<const void*>(external->base));
- EXPECT_THAT(CordRepBtree::EdgeData(external), Eq("Hello external"));
+ EXPECT_TRUE(IsDataEdge(external));
+ EXPECT_THAT(EdgeData(external).data(), TypedEq<const void*>(external->base));
+ EXPECT_THAT(EdgeData(external), Eq("Hello external"));
- EXPECT_TRUE(CordRepBtree::IsDataEdge(substr1));
- EXPECT_THAT(CordRepBtree::EdgeDataPtr(substr1),
- TypedEq<const void*>(flat->Data() + 1));
- EXPECT_THAT(CordRepBtree::EdgeData(substr1), Eq("ello w"));
+ EXPECT_TRUE(IsDataEdge(substr1));
+ EXPECT_THAT(EdgeData(substr1).data(), TypedEq<const void*>(flat->Data() + 1));
+ EXPECT_THAT(EdgeData(substr1), Eq("ello w"));
- EXPECT_TRUE(CordRepBtree::IsDataEdge(substr2));
- EXPECT_THAT(CordRepBtree::EdgeDataPtr(substr2),
+ EXPECT_TRUE(IsDataEdge(substr2));
+ EXPECT_THAT(EdgeData(substr2).data(),
TypedEq<const void*>(external->base + 1));
- EXPECT_THAT(CordRepBtree::EdgeData(substr2), Eq("ello e"));
+ EXPECT_THAT(EdgeData(substr2), Eq("ello e"));
- EXPECT_FALSE(CordRepBtree::IsDataEdge(concat));
- EXPECT_FALSE(CordRepBtree::IsDataEdge(bad_substr));
+ EXPECT_FALSE(IsDataEdge(bad_substr));
#if defined(GTEST_HAS_DEATH_TEST) && !defined(NDEBUG)
- EXPECT_DEATH(CordRepBtree::EdgeData(concat), ".*");
- EXPECT_DEATH(CordRepBtree::EdgeDataPtr(concat), ".*");
- EXPECT_DEATH(CordRepBtree::EdgeData(bad_substr), ".*");
- EXPECT_DEATH(CordRepBtree::EdgeDataPtr(bad_substr), ".*");
+ EXPECT_DEATH(EdgeData(bad_substr), ".*");
#endif
CordRep::Unref(bad_substr);
- CordRep::Unref(concat);
CordRep::Unref(substr2);
CordRep::Unref(substr1);
CordRep::Unref(external);
@@ -348,7 +357,7 @@
TEST(CordRepBtreeTest, CreateUnrefLeaf) {
auto* flat = MakeFlat("a");
auto* leaf = CordRepBtree::Create(flat);
- EXPECT_THAT(leaf->size(), Eq(1));
+ EXPECT_THAT(leaf->size(), Eq(1u));
EXPECT_THAT(leaf->height(), Eq(0));
EXPECT_THAT(leaf->Edges(), ElementsAre(flat));
CordRepBtree::Unref(leaf);
@@ -357,7 +366,7 @@
TEST(CordRepBtreeTest, NewUnrefNode) {
auto* leaf = CordRepBtree::Create(MakeFlat("a"));
CordRepBtree* tree = CordRepBtree::New(leaf);
- EXPECT_THAT(tree->size(), Eq(1));
+ EXPECT_THAT(tree->size(), Eq(1u));
EXPECT_THAT(tree->height(), Eq(1));
EXPECT_THAT(tree->Edges(), ElementsAre(leaf));
CordRepBtree::Unref(tree);
@@ -498,7 +507,7 @@
for (size_t i = max_cap * max_cap + 1; i < max_cap * max_cap * max_cap; ++i) {
// Ref top level tree based on param.
// Ref child node once every 16 iterations, and leaf node every 4
- // iterrations which which should not have an observable effect other than
+ // iterations which which should not have an observable effect other than
// the node and/or the leaf below it being copied.
refs.RefIf(shared(), tree);
refs.RefIf(i % 16 == 0, tree->Edges().back());
@@ -559,7 +568,7 @@
for (size_t i = max_cap * max_cap + 1; i < max_cap * max_cap * max_cap; ++i) {
// Ref top level tree based on param.
// Ref child node once every 16 iterations, and leaf node every 4
- // iterrations which which should not have an observable effect other than
+ // iterations which which should not have an observable effect other than
// the node and/or the leaf below it being copied.
refs.RefIf(shared(), tree);
refs.RefIf(i % 16 == 0, tree->Edges().back());
@@ -645,7 +654,7 @@
CordRepBtree* tree = use_append ? CordRepBtree::Append(left, right)
: CordRepBtree::Prepend(right, left);
EXPECT_THAT(tree, IsNode(1));
- EXPECT_THAT(tree->Edges(), SizeIs(5));
+ EXPECT_THAT(tree->Edges(), SizeIs(5u));
// `tree` contains all flats originally belonging to `left` and `right`.
EXPECT_THAT(GetLeafEdges(tree), ElementsAreArray(flats));
@@ -673,7 +682,7 @@
CordRepBtree* tree = use_append ? CordRepBtree::Append(left, right)
: CordRepBtree::Prepend(right, left);
EXPECT_THAT(tree, IsNode(1));
- EXPECT_THAT(tree->Edges(), SizeIs(3));
+ EXPECT_THAT(tree->Edges(), SizeIs(3u));
// `tree` contains all flats originally belonging to `left` and `right`.
EXPECT_THAT(GetLeafEdges(tree), ElementsAreArray(flats));
@@ -701,7 +710,7 @@
CordRepBtree* tree = use_append ? CordRepBtree::Append(left, right)
: CordRepBtree::Prepend(right, left);
EXPECT_THAT(tree, IsNode(1));
- EXPECT_THAT(tree->Edges(), SizeIs(4));
+ EXPECT_THAT(tree->Edges(), SizeIs(4u));
// `tree` contains all flats originally belonging to `left` and `right`.
EXPECT_THAT(GetLeafEdges(tree), ElementsAreArray(flats));
@@ -730,7 +739,7 @@
auto random_leaf_count = [&]() {
std::uniform_int_distribution<int> dist_height(0, 3);
std::uniform_int_distribution<int> dist_leaf(0, max_cap - 1);
- const size_t height = dist_height(rnd);
+ const int height = dist_height(rnd);
return (height ? pow(max_cap, height) : 0) + dist_leaf(rnd);
};
@@ -741,14 +750,16 @@
CordRepBtree* left = MakeTree(random_leaf_count(), coin_flip(rnd));
GetLeafEdges(left, flats);
if (dice_throw(rnd) == 1) {
- std::uniform_int_distribution<int> dist(0, left->height());
+ std::uniform_int_distribution<size_t> dist(
+ 0, static_cast<size_t>(left->height()));
RefEdgesAt(dist(rnd), refs, left);
}
CordRepBtree* right = MakeTree(random_leaf_count(), coin_flip(rnd));
GetLeafEdges(right, flats);
if (dice_throw(rnd) == 1) {
- std::uniform_int_distribution<int> dist(0, right->height());
+ std::uniform_int_distribution<size_t> dist(
+ 0, static_cast<size_t>(right->height()));
RefEdgesAt(dist(rnd), refs, right);
}
@@ -758,6 +769,63 @@
}
}
+TEST_P(CordRepBtreeTest, RemoveSuffix) {
+ // Create tree of 1, 2 and 3 levels high
+ constexpr size_t max_cap = CordRepBtree::kMaxCapacity;
+ for (size_t cap : {max_cap - 1, max_cap * 2, max_cap * max_cap * 2}) {
+ const std::string data = CreateRandomString(cap * 512);
+
+ {
+ // Verify RemoveSuffix(<all>)
+ AutoUnref refs;
+ CordRepBtree* node = refs.RefIf(shared(), CreateTree(data, 512));
+ EXPECT_THAT(CordRepBtree::RemoveSuffix(node, data.length()), Eq(nullptr));
+
+ // Verify RemoveSuffix(<none>)
+ node = refs.RefIf(shared(), CreateTree(data, 512));
+ EXPECT_THAT(CordRepBtree::RemoveSuffix(node, 0), Eq(node));
+ CordRep::Unref(node);
+ }
+
+ for (size_t n = 1; n < data.length(); ++n) {
+ AutoUnref refs;
+ auto flats = CreateFlatsFromString(data, 512);
+ CordRepBtree* node = refs.RefIf(shared(), CreateTree(flats));
+ CordRep* rep = refs.Add(CordRepBtree::RemoveSuffix(node, n));
+ EXPECT_THAT(CordToString(rep), Eq(data.substr(0, data.length() - n)));
+
+ // Collect all flats
+ auto is_flat = [](CordRep* rep) { return rep->tag >= FLAT; };
+ std::vector<CordRep*> edges = CordCollectRepsIf(is_flat, rep);
+ ASSERT_THAT(edges.size(), Le(flats.size()));
+
+ // Isolate last edge
+ CordRep* last_edge = edges.back();
+ edges.pop_back();
+ const size_t last_length = rep->length - edges.size() * 512;
+
+ // All flats except the last edge must be kept or copied 'as is'
+ size_t index = 0;
+ for (CordRep* edge : edges) {
+ ASSERT_THAT(edge, Eq(flats[index++]));
+ ASSERT_THAT(edge->length, Eq(512u));
+ }
+
+ // CordRepBtree may optimize small substrings to avoid waste, so only
+ // check for flat sharing / updates where the code should always do this.
+ if (last_length >= 500) {
+ EXPECT_THAT(last_edge, Eq(flats[index++]));
+ if (shared()) {
+ EXPECT_THAT(last_edge->length, Eq(512u));
+ } else {
+ EXPECT_TRUE(last_edge->refcount.IsOne());
+ EXPECT_THAT(last_edge->length, Eq(last_length));
+ }
+ }
+ }
+ }
+}
+
TEST(CordRepBtreeTest, SubTree) {
// Create tree of at least 2 levels high
constexpr size_t max_cap = CordRepBtree::kMaxCapacity;
@@ -772,8 +840,8 @@
node = CordRepBtree::Append(node, CordRep::Ref(flats[i]));
}
- for (int offset = 0; offset < data.length(); ++offset) {
- for (int length = 1; length <= data.length() - offset; ++length) {
+ for (size_t offset = 0; offset < data.length(); ++offset) {
+ for (size_t length = 1; length <= data.length() - offset; ++length) {
CordRep* rep = node->SubTree(offset, length);
EXPECT_THAT(CordToString(rep), Eq(data.substr(offset, length)));
CordRep::Unref(rep);
@@ -800,12 +868,12 @@
ASSERT_THAT(result->tag, Eq(BTREE));
CordRep::Unref(leaf);
leaf = result->btree();
- ASSERT_THAT(leaf->Edges(), ElementsAre(_, IsSubstring(0, 990)));
+ ASSERT_THAT(leaf->Edges(), ElementsAre(_, IsSubstring(0u, 990u)));
EXPECT_THAT(leaf->Edges()[1]->substring()->child, Eq(flat));
// Verify substring of substring.
result = leaf->SubTree(3 + 5, 970);
- ASSERT_THAT(result, IsSubstring(5, 970));
+ ASSERT_THAT(result, IsSubstring(5u, 970u));
EXPECT_THAT(result->substring()->child, Eq(flat));
CordRep::Unref(result);
@@ -932,50 +1000,6 @@
}
}
-TEST_P(CordRepBtreeDualTest, CreateFromConcat) {
- AutoUnref refs;
- CordRep* flats[] = {MakeFlat("abcdefgh"), MakeFlat("ijklm"),
- MakeFlat("nopqrstuv"), MakeFlat("wxyz")};
- auto* left = MakeConcat(flats[0], flats[1]);
- auto* right = MakeConcat(flats[2], refs.RefIf(first_shared(), flats[3]));
- auto* concat = refs.RefIf(second_shared(), MakeConcat(left, right));
- CordRepBtree* result = CordRepBtree::Create(concat);
- ASSERT_TRUE(CordRepBtree::IsValid(result));
- EXPECT_THAT(result->length, Eq(26));
- EXPECT_THAT(CordToString(result), Eq("abcdefghijklmnopqrstuvwxyz"));
- CordRep::Unref(result);
-}
-
-TEST_P(CordRepBtreeDualTest, AppendConcat) {
- AutoUnref refs;
- CordRep* flats[] = {MakeFlat("defgh"), MakeFlat("ijklm"),
- MakeFlat("nopqrstuv"), MakeFlat("wxyz")};
- auto* left = MakeConcat(flats[0], flats[1]);
- auto* right = MakeConcat(flats[2], refs.RefIf(first_shared(), flats[3]));
- auto* concat = refs.RefIf(second_shared(), MakeConcat(left, right));
- CordRepBtree* result = CordRepBtree::Create(MakeFlat("abc"));
- result = CordRepBtree::Append(result, concat);
- ASSERT_TRUE(CordRepBtree::IsValid(result));
- EXPECT_THAT(result->length, Eq(26));
- EXPECT_THAT(CordToString(result), Eq("abcdefghijklmnopqrstuvwxyz"));
- CordRep::Unref(result);
-}
-
-TEST_P(CordRepBtreeDualTest, PrependConcat) {
- AutoUnref refs;
- CordRep* flats[] = {MakeFlat("abcdefgh"), MakeFlat("ijklm"),
- MakeFlat("nopqrstuv"), MakeFlat("wx")};
- auto* left = MakeConcat(flats[0], flats[1]);
- auto* right = MakeConcat(flats[2], refs.RefIf(first_shared(), flats[3]));
- auto* concat = refs.RefIf(second_shared(), MakeConcat(left, right));
- CordRepBtree* result = CordRepBtree::Create(MakeFlat("yz"));
- result = CordRepBtree::Prepend(result, concat);
- ASSERT_TRUE(CordRepBtree::IsValid(result));
- EXPECT_THAT(result->length, Eq(26));
- EXPECT_THAT(CordToString(result), Eq("abcdefghijklmnopqrstuvwxyz"));
- CordRep::Unref(result);
-}
-
TEST_P(CordRepBtreeTest, CreateFromTreeReturnsTree) {
AutoUnref refs;
CordRepBtree* leaf = CordRepBtree::Create(MakeFlat("Hello world"));
@@ -985,23 +1009,6 @@
CordRep::Unref(result);
}
-TEST_P(CordRepBtreeTest, ExceedMaxHeight) {
- AutoUnref refs;
- CordRepBtree* tree = MakeLeaf();
- for (int h = 1; h <= CordRepBtree::kMaxHeight; ++h) {
- CordRepBtree* newtree = CordRepBtree::New(tree);
- for (size_t i = 1; i < CordRepBtree::kMaxCapacity; ++i) {
- newtree = CordRepBtree::Append(newtree, CordRep::Ref(tree));
- }
- tree = newtree;
- }
- refs.RefIf(shared(), tree);
-#if defined(GTEST_HAS_DEATH_TEST)
- EXPECT_DEATH(tree = CordRepBtree::Append(tree, MakeFlat("Boom")), ".*");
-#endif
- CordRep::Unref(tree);
-}
-
TEST(CordRepBtreeTest, GetCharacter) {
size_t n = CordRepBtree::kMaxCapacity * CordRepBtree::kMaxCapacity + 2;
std::string data = CreateRandomString(n * 3);
@@ -1088,7 +1095,7 @@
for (int i = 1; i <= height(); ++i) {
tree = CordRepBtree::New(tree);
}
- EXPECT_THAT(tree->GetAppendBuffer(1), SizeIs(0));
+ EXPECT_THAT(tree->GetAppendBuffer(1), SizeIs(0u));
CordRepBtree::Unref(tree);
}
@@ -1098,7 +1105,7 @@
for (int i = 1; i <= height(); ++i) {
tree = CordRepBtree::New(tree);
}
- EXPECT_THAT(tree->GetAppendBuffer(1), SizeIs(0));
+ EXPECT_THAT(tree->GetAppendBuffer(1), SizeIs(0u));
CordRepBtree::Unref(tree);
CordRep::Unref(flat);
}
@@ -1112,7 +1119,7 @@
if (i == (height() + 1) / 2) refs.Ref(tree);
tree = CordRepBtree::New(tree);
}
- EXPECT_THAT(tree->GetAppendBuffer(1), SizeIs(0));
+ EXPECT_THAT(tree->GetAppendBuffer(1), SizeIs(0u));
CordRepBtree::Unref(tree);
CordRep::Unref(flat);
}
@@ -1124,7 +1131,7 @@
for (int i = 1; i <= height(); ++i) {
tree = CordRepBtree::New(tree);
}
- EXPECT_THAT(tree->GetAppendBuffer(1), SizeIs(0));
+ EXPECT_THAT(tree->GetAppendBuffer(1), SizeIs(0u));
CordRepBtree::Unref(tree);
}
@@ -1135,9 +1142,9 @@
tree = CordRepBtree::New(tree);
}
absl::Span<char> span = tree->GetAppendBuffer(2);
- EXPECT_THAT(span, SizeIs(2));
+ EXPECT_THAT(span, SizeIs(2u));
EXPECT_THAT(span.data(), TypedEq<void*>(flat->Data() + 3));
- EXPECT_THAT(tree->length, Eq(5));
+ EXPECT_THAT(tree->length, Eq(5u));
size_t avail = flat->Capacity() - 5;
span = tree->GetAppendBuffer(avail + 100);
@@ -1346,6 +1353,215 @@
CordRep::Unref(tree);
}
+TEST(CordRepBtreeTest, CheckAssertValidShallowVsDeep) {
+ // Restore exhaustive validation on any exit.
+ const bool exhaustive_validation = IsCordBtreeExhaustiveValidationEnabled();
+ auto cleanup = absl::MakeCleanup([exhaustive_validation] {
+ SetCordBtreeExhaustiveValidation(exhaustive_validation);
+ });
+
+ // Create a tree of at least 2 levels, and mess with the original flat, which
+ // should go undetected in shallow mode as the flat is too far away, but
+ // should be detected in forced non-shallow mode.
+ CordRep* flat = MakeFlat("abc");
+ CordRepBtree* tree = CordRepBtree::Create(flat);
+ constexpr size_t max_cap = CordRepBtree::kMaxCapacity;
+ const size_t n = max_cap * max_cap * 2;
+ for (size_t i = 0; i < n; ++i) {
+ tree = CordRepBtree::Append(tree, MakeFlat("Hello world"));
+ }
+ flat->length = 100;
+
+ SetCordBtreeExhaustiveValidation(false);
+ EXPECT_FALSE(CordRepBtree::IsValid(tree));
+ EXPECT_TRUE(CordRepBtree::IsValid(tree, true));
+ EXPECT_FALSE(CordRepBtree::IsValid(tree, false));
+ CordRepBtree::AssertValid(tree);
+ CordRepBtree::AssertValid(tree, true);
+#if defined(GTEST_HAS_DEATH_TEST)
+ EXPECT_DEBUG_DEATH(CordRepBtree::AssertValid(tree, false), ".*");
+#endif
+
+ SetCordBtreeExhaustiveValidation(true);
+ EXPECT_FALSE(CordRepBtree::IsValid(tree));
+ EXPECT_FALSE(CordRepBtree::IsValid(tree, true));
+ EXPECT_FALSE(CordRepBtree::IsValid(tree, false));
+#if defined(GTEST_HAS_DEATH_TEST)
+ EXPECT_DEBUG_DEATH(CordRepBtree::AssertValid(tree), ".*");
+ EXPECT_DEBUG_DEATH(CordRepBtree::AssertValid(tree, true), ".*");
+#endif
+
+ flat->length = 3;
+ CordRep::Unref(tree);
+}
+
+TEST_P(CordRepBtreeTest, Rebuild) {
+ for (size_t size : {3u, 8u, 100u, 10000u, 1000000u}) {
+ SCOPED_TRACE(absl::StrCat("Rebuild @", size));
+
+ std::vector<CordRepFlat*> flats;
+ for (size_t i = 0; i < size; ++i) {
+ flats.push_back(CordRepFlat::New(2));
+ flats.back()->Data()[0] = 'x';
+ flats.back()->length = 1;
+ }
+
+ // Build the tree into 'right', and each so many 'split_limit' edges,
+ // combine 'left' + 'right' into a new 'left', and start a new 'right'.
+ // This guarantees we get a reasonable amount of chaos in the tree.
+ size_t split_count = 0;
+ size_t split_limit = 3;
+ auto it = flats.begin();
+ CordRepBtree* left = nullptr;
+ CordRepBtree* right = CordRepBtree::New(*it);
+ while (++it != flats.end()) {
+ if (++split_count >= split_limit) {
+ split_limit += split_limit / 16;
+ left = left ? CordRepBtree::Append(left, right) : right;
+ right = CordRepBtree::New(*it);
+ } else {
+ right = CordRepBtree::Append(right, *it);
+ }
+ }
+
+ // Finalize tree
+ left = left ? CordRepBtree::Append(left, right) : right;
+
+ // Rebuild
+ AutoUnref ref;
+ left = ref.Add(CordRepBtree::Rebuild(ref.RefIf(shared(), left)));
+ ASSERT_TRUE(CordRepBtree::IsValid(left));
+
+ // Verify we have the exact same edges in the exact same order.
+ bool ok = true;
+ it = flats.begin();
+ CordVisitReps(left, [&](CordRep* edge) {
+ if (edge->tag < FLAT) return;
+ ok = ok && (it != flats.end() && *it++ == edge);
+ });
+ EXPECT_TRUE(ok && it == flats.end()) << "Rebuild edges mismatch";
+ }
+}
+
+// Convenience helper for CordRepBtree::ExtractAppendBuffer
+CordRepBtree::ExtractResult ExtractLast(CordRepBtree* input, size_t cap = 1) {
+ return CordRepBtree::ExtractAppendBuffer(input, cap);
+}
+
+TEST(CordRepBtreeTest, ExtractAppendBufferLeafSingleFlat) {
+ CordRep* flat = MakeFlat("Abc");
+ CordRepBtree* leaf = CordRepBtree::Create(flat);
+ EXPECT_THAT(ExtractLast(leaf), EqExtractResult(nullptr, flat));
+ CordRep::Unref(flat);
+}
+
+TEST(CordRepBtreeTest, ExtractAppendBufferNodeSingleFlat) {
+ CordRep* flat = MakeFlat("Abc");
+ CordRepBtree* leaf = CordRepBtree::Create(flat);
+ CordRepBtree* node = CordRepBtree::New(leaf);
+ EXPECT_THAT(ExtractLast(node), EqExtractResult(nullptr, flat));
+ CordRep::Unref(flat);
+}
+
+TEST(CordRepBtreeTest, ExtractAppendBufferLeafTwoFlats) {
+ std::vector<CordRep*> flats = CreateFlatsFromString("abcdef", 3);
+ CordRepBtree* leaf = CreateTree(flats);
+ EXPECT_THAT(ExtractLast(leaf), EqExtractResult(flats[0], flats[1]));
+ CordRep::Unref(flats[0]);
+ CordRep::Unref(flats[1]);
+}
+
+TEST(CordRepBtreeTest, ExtractAppendBufferNodeTwoFlats) {
+ std::vector<CordRep*> flats = CreateFlatsFromString("abcdef", 3);
+ CordRepBtree* leaf = CreateTree(flats);
+ CordRepBtree* node = CordRepBtree::New(leaf);
+ EXPECT_THAT(ExtractLast(node), EqExtractResult(flats[0], flats[1]));
+ CordRep::Unref(flats[0]);
+ CordRep::Unref(flats[1]);
+}
+
+TEST(CordRepBtreeTest, ExtractAppendBufferNodeTwoFlatsInTwoLeafs) {
+ std::vector<CordRep*> flats = CreateFlatsFromString("abcdef", 3);
+ CordRepBtree* leaf1 = CordRepBtree::Create(flats[0]);
+ CordRepBtree* leaf2 = CordRepBtree::Create(flats[1]);
+ CordRepBtree* node = CordRepBtree::New(leaf1, leaf2);
+ EXPECT_THAT(ExtractLast(node), EqExtractResult(flats[0], flats[1]));
+ CordRep::Unref(flats[0]);
+ CordRep::Unref(flats[1]);
+}
+
+TEST(CordRepBtreeTest, ExtractAppendBufferLeafThreeFlats) {
+ std::vector<CordRep*> flats = CreateFlatsFromString("abcdefghi", 3);
+ CordRepBtree* leaf = CreateTree(flats);
+ EXPECT_THAT(ExtractLast(leaf), EqExtractResult(leaf, flats[2]));
+ CordRep::Unref(flats[2]);
+ CordRep::Unref(leaf);
+}
+
+TEST(CordRepBtreeTest, ExtractAppendBufferNodeThreeFlatsRightNoFolding) {
+ CordRep* flat = MakeFlat("Abc");
+ std::vector<CordRep*> flats = CreateFlatsFromString("defghi", 3);
+ CordRepBtree* leaf1 = CordRepBtree::Create(flat);
+ CordRepBtree* leaf2 = CreateTree(flats);
+ CordRepBtree* node = CordRepBtree::New(leaf1, leaf2);
+ EXPECT_THAT(ExtractLast(node), EqExtractResult(node, flats[1]));
+ EXPECT_THAT(node->Edges(), ElementsAre(leaf1, leaf2));
+ EXPECT_THAT(leaf1->Edges(), ElementsAre(flat));
+ EXPECT_THAT(leaf2->Edges(), ElementsAre(flats[0]));
+ CordRep::Unref(node);
+ CordRep::Unref(flats[1]);
+}
+
+TEST(CordRepBtreeTest, ExtractAppendBufferNodeThreeFlatsRightLeafFolding) {
+ CordRep* flat = MakeFlat("Abc");
+ std::vector<CordRep*> flats = CreateFlatsFromString("defghi", 3);
+ CordRepBtree* leaf1 = CreateTree(flats);
+ CordRepBtree* leaf2 = CordRepBtree::Create(flat);
+ CordRepBtree* node = CordRepBtree::New(leaf1, leaf2);
+ EXPECT_THAT(ExtractLast(node), EqExtractResult(leaf1, flat));
+ EXPECT_THAT(leaf1->Edges(), ElementsAreArray(flats));
+ CordRep::Unref(leaf1);
+ CordRep::Unref(flat);
+}
+
+TEST(CordRepBtreeTest, ExtractAppendBufferNoCapacity) {
+ std::vector<CordRep*> flats = CreateFlatsFromString("abcdef", 3);
+ CordRepBtree* leaf = CreateTree(flats);
+ size_t avail = flats[1]->flat()->Capacity() - flats[1]->length;
+ EXPECT_THAT(ExtractLast(leaf, avail + 1), EqExtractResult(leaf, nullptr));
+ EXPECT_THAT(ExtractLast(leaf, avail), EqExtractResult(flats[0], flats[1]));
+ CordRep::Unref(flats[0]);
+ CordRep::Unref(flats[1]);
+}
+
+TEST(CordRepBtreeTest, ExtractAppendBufferNotFlat) {
+ std::vector<CordRep*> flats = CreateFlatsFromString("abcdef", 3);
+ auto substr = MakeSubstring(1, 2, flats[1]);
+ CordRepBtree* leaf = CreateTree({flats[0], substr});
+ EXPECT_THAT(ExtractLast(leaf), EqExtractResult(leaf, nullptr));
+ CordRep::Unref(leaf);
+}
+
+TEST(CordRepBtreeTest, ExtractAppendBufferShared) {
+ std::vector<CordRep*> flats = CreateFlatsFromString("abcdef", 3);
+ CordRepBtree* leaf = CreateTree(flats);
+
+ CordRep::Ref(flats[1]);
+ EXPECT_THAT(ExtractLast(leaf), EqExtractResult(leaf, nullptr));
+ CordRep::Unref(flats[1]);
+
+ CordRep::Ref(leaf);
+ EXPECT_THAT(ExtractLast(leaf), EqExtractResult(leaf, nullptr));
+ CordRep::Unref(leaf);
+
+ CordRepBtree* node = CordRepBtree::New(leaf);
+ CordRep::Ref(node);
+ EXPECT_THAT(ExtractLast(node), EqExtractResult(node, nullptr));
+ CordRep::Unref(node);
+
+ CordRep::Unref(node);
+}
+
} // namespace
} // namespace cord_internal
ABSL_NAMESPACE_END
diff --git a/absl/strings/internal/cord_rep_consume.cc b/absl/strings/internal/cord_rep_consume.cc
index 8151454..db7d4fe 100644
--- a/absl/strings/internal/cord_rep_consume.cc
+++ b/absl/strings/internal/cord_rep_consume.cc
@@ -40,88 +40,23 @@
return child;
}
-// Unrefs the provided `concat`, and returns `{concat->left, concat->right}`
-// Adds or assumes a reference on `concat->left` and `concat->right`.
-// Returns an array of 2 elements containing the left and right nodes.
-std::array<CordRep*, 2> ClipConcat(CordRepConcat* concat) {
- std::array<CordRep*, 2> result{concat->left, concat->right};
- if (concat->refcount.IsOne()) {
- delete concat;
- } else {
- CordRep::Ref(result[0]);
- CordRep::Ref(result[1]);
- CordRep::Unref(concat);
- }
- return result;
-}
-
-void Consume(bool forward, CordRep* rep, ConsumeFn consume_fn) {
- size_t offset = 0;
- size_t length = rep->length;
- struct Entry {
- CordRep* rep;
- size_t offset;
- size_t length;
- };
- absl::InlinedVector<Entry, 40> stack;
-
- for (;;) {
- if (rep->tag == CONCAT) {
- std::array<CordRep*, 2> res = ClipConcat(rep->concat());
- CordRep* left = res[0];
- CordRep* right = res[1];
-
- if (left->length <= offset) {
- // Don't need left node
- offset -= left->length;
- CordRep::Unref(left);
- rep = right;
- continue;
- }
-
- size_t length_left = left->length - offset;
- if (length_left >= length) {
- // Don't need right node
- CordRep::Unref(right);
- rep = left;
- continue;
- }
-
- // Need both nodes
- size_t length_right = length - length_left;
- if (forward) {
- stack.push_back({right, 0, length_right});
- rep = left;
- length = length_left;
- } else {
- stack.push_back({left, offset, length_left});
- rep = right;
- offset = 0;
- length = length_right;
- }
- } else if (rep->tag == SUBSTRING) {
- offset += rep->substring()->start;
- rep = ClipSubstring(rep->substring());
- } else {
- consume_fn(rep, offset, length);
- if (stack.empty()) return;
-
- rep = stack.back().rep;
- offset = stack.back().offset;
- length = stack.back().length;
- stack.pop_back();
- }
- }
-}
-
} // namespace
-void Consume(CordRep* rep, ConsumeFn consume_fn) {
- return Consume(true, rep, std::move(consume_fn));
+void Consume(CordRep* rep,
+ FunctionRef<void(CordRep*, size_t, size_t)> consume_fn) {
+ size_t offset = 0;
+ size_t length = rep->length;
+
+ if (rep->tag == SUBSTRING) {
+ offset += rep->substring()->start;
+ rep = ClipSubstring(rep->substring());
+ }
+ consume_fn(rep, offset, length);
}
-void ReverseConsume(CordRep* rep, ConsumeFn consume_fn) {
- return Consume(false, rep, std::move(consume_fn));
+void ReverseConsume(CordRep* rep,
+ FunctionRef<void(CordRep*, size_t, size_t)> consume_fn) {
+ return Consume(rep, consume_fn);
}
} // namespace cord_internal
diff --git a/absl/strings/internal/cord_rep_consume.h b/absl/strings/internal/cord_rep_consume.h
index d46fca2..bece187 100644
--- a/absl/strings/internal/cord_rep_consume.h
+++ b/absl/strings/internal/cord_rep_consume.h
@@ -24,11 +24,6 @@
ABSL_NAMESPACE_BEGIN
namespace cord_internal {
-// Functor for the Consume() and ReverseConsume() functions:
-// void ConsumeFunc(CordRep* rep, size_t offset, size_t length);
-// See the Consume() and ReverseConsume() function comments for documentation.
-using ConsumeFn = FunctionRef<void(CordRep*, size_t, size_t)>;
-
// Consume() and ReverseConsume() consume CONCAT based trees and invoke the
// provided functor with the contained nodes in the proper forward or reverse
// order, which is used to convert CONCAT trees into other tree or cord data.
@@ -40,8 +35,10 @@
// violations, we can not 100% guarantee that all code respects 'new format'
// settings and flags, so we need to be able to parse old data on the fly until
// all old code is deprecated / no longer the default format.
-void Consume(CordRep* rep, ConsumeFn consume_fn);
-void ReverseConsume(CordRep* rep, ConsumeFn consume_fn);
+void Consume(CordRep* rep,
+ FunctionRef<void(CordRep*, size_t, size_t)> consume_fn);
+void ReverseConsume(CordRep* rep,
+ FunctionRef<void(CordRep*, size_t, size_t)> consume_fn);
} // namespace cord_internal
ABSL_NAMESPACE_END
diff --git a/absl/strings/internal/cord_rep_consume_test.cc b/absl/strings/internal/cord_rep_consume_test.cc
deleted file mode 100644
index e507824..0000000
--- a/absl/strings/internal/cord_rep_consume_test.cc
+++ /dev/null
@@ -1,173 +0,0 @@
-// Copyright 2021 The Abseil Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "absl/strings/internal/cord_rep_consume.h"
-
-#include <functional>
-#include <utility>
-
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-#include "absl/strings/internal/cord_internal.h"
-#include "absl/strings/internal/cord_rep_flat.h"
-
-namespace absl {
-ABSL_NAMESPACE_BEGIN
-namespace cord_internal {
-namespace {
-
-using testing::InSequence;
-using testing::MockFunction;
-
-// Returns the depth of a node
-int Depth(const CordRep* rep) {
- return (rep->tag == CONCAT) ? rep->concat()->depth() : 0;
-}
-
-// Creates a concatenation of the specified nodes.
-CordRepConcat* CreateConcat(CordRep* left, CordRep* right) {
- auto* concat = new CordRepConcat();
- concat->tag = CONCAT;
- concat->left = left;
- concat->right = right;
- concat->length = left->length + right->length;
- concat->set_depth(1 + (std::max)(Depth(left), Depth(right)));
- return concat;
-}
-
-// Creates a flat with the length set to `length`
-CordRepFlat* CreateFlatWithLength(size_t length) {
- auto* flat = CordRepFlat::New(length);
- flat->length = length;
- return flat;
-}
-
-// Creates a substring node on the specified child.
-CordRepSubstring* CreateSubstring(CordRep* child, size_t start, size_t length) {
- auto* rep = new CordRepSubstring();
- rep->length = length;
- rep->tag = SUBSTRING;
- rep->start = start;
- rep->child = child;
- return rep;
-}
-
-// Flats we use in the tests
-CordRep* flat[6];
-
-// Creates a test tree
-CordRep* CreateTestTree() {
- flat[0] = CreateFlatWithLength(1);
- flat[1] = CreateFlatWithLength(7);
- CordRepConcat* left = CreateConcat(flat[0], CreateSubstring(flat[1], 2, 4));
-
- flat[2] = CreateFlatWithLength(9);
- flat[3] = CreateFlatWithLength(13);
- CordRepConcat* right1 = CreateConcat(flat[2], flat[3]);
-
- flat[4] = CreateFlatWithLength(15);
- flat[5] = CreateFlatWithLength(19);
- CordRepConcat* right2 = CreateConcat(flat[4], flat[5]);
-
- CordRepConcat* right = CreateConcat(right1, CreateSubstring(right2, 5, 17));
- return CreateConcat(left, right);
-}
-
-TEST(CordRepConsumeTest, Consume) {
- InSequence in_sequence;
- CordRep* tree = CreateTestTree();
- MockFunction<void(CordRep*, size_t, size_t)> consume;
- EXPECT_CALL(consume, Call(flat[0], 0, 1));
- EXPECT_CALL(consume, Call(flat[1], 2, 4));
- EXPECT_CALL(consume, Call(flat[2], 0, 9));
- EXPECT_CALL(consume, Call(flat[3], 0, 13));
- EXPECT_CALL(consume, Call(flat[4], 5, 10));
- EXPECT_CALL(consume, Call(flat[5], 0, 7));
- Consume(tree, consume.AsStdFunction());
- for (CordRep* rep : flat) {
- EXPECT_TRUE(rep->refcount.IsOne());
- CordRep::Unref(rep);
- }
-}
-
-TEST(CordRepConsumeTest, ConsumeShared) {
- InSequence in_sequence;
- CordRep* tree = CreateTestTree();
- MockFunction<void(CordRep*, size_t, size_t)> consume;
- EXPECT_CALL(consume, Call(flat[0], 0, 1));
- EXPECT_CALL(consume, Call(flat[1], 2, 4));
- EXPECT_CALL(consume, Call(flat[2], 0, 9));
- EXPECT_CALL(consume, Call(flat[3], 0, 13));
- EXPECT_CALL(consume, Call(flat[4], 5, 10));
- EXPECT_CALL(consume, Call(flat[5], 0, 7));
- Consume(CordRep::Ref(tree), consume.AsStdFunction());
- for (CordRep* rep : flat) {
- EXPECT_FALSE(rep->refcount.IsOne());
- CordRep::Unref(rep);
- }
- CordRep::Unref(tree);
-}
-
-TEST(CordRepConsumeTest, Reverse) {
- InSequence in_sequence;
- CordRep* tree = CreateTestTree();
- MockFunction<void(CordRep*, size_t, size_t)> consume;
- EXPECT_CALL(consume, Call(flat[5], 0, 7));
- EXPECT_CALL(consume, Call(flat[4], 5, 10));
- EXPECT_CALL(consume, Call(flat[3], 0, 13));
- EXPECT_CALL(consume, Call(flat[2], 0, 9));
- EXPECT_CALL(consume, Call(flat[1], 2, 4));
- EXPECT_CALL(consume, Call(flat[0], 0, 1));
- ReverseConsume(tree, consume.AsStdFunction());
- for (CordRep* rep : flat) {
- EXPECT_TRUE(rep->refcount.IsOne());
- CordRep::Unref(rep);
- }
-}
-
-TEST(CordRepConsumeTest, ReverseShared) {
- InSequence in_sequence;
- CordRep* tree = CreateTestTree();
- MockFunction<void(CordRep*, size_t, size_t)> consume;
- EXPECT_CALL(consume, Call(flat[5], 0, 7));
- EXPECT_CALL(consume, Call(flat[4], 5, 10));
- EXPECT_CALL(consume, Call(flat[3], 0, 13));
- EXPECT_CALL(consume, Call(flat[2], 0, 9));
- EXPECT_CALL(consume, Call(flat[1], 2, 4));
- EXPECT_CALL(consume, Call(flat[0], 0, 1));
- ReverseConsume(CordRep::Ref(tree), consume.AsStdFunction());
- for (CordRep* rep : flat) {
- EXPECT_FALSE(rep->refcount.IsOne());
- CordRep::Unref(rep);
- }
- CordRep::Unref(tree);
-}
-
-TEST(CordRepConsumeTest, UnreachableFlat) {
- InSequence in_sequence;
- CordRepFlat* flat1 = CreateFlatWithLength(10);
- CordRepFlat* flat2 = CreateFlatWithLength(20);
- CordRepConcat* concat = CreateConcat(flat1, flat2);
- CordRepSubstring* tree = CreateSubstring(concat, 15, 10);
- MockFunction<void(CordRep*, size_t, size_t)> consume;
- EXPECT_CALL(consume, Call(flat2, 5, 10));
- Consume(tree, consume.AsStdFunction());
- EXPECT_TRUE(flat2->refcount.IsOne());
- CordRep::Unref(flat2);
-}
-
-} // namespace
-} // namespace cord_internal
-ABSL_NAMESPACE_END
-} // namespace absl
diff --git a/absl/strings/internal/cord_rep_crc.cc b/absl/strings/internal/cord_rep_crc.cc
new file mode 100644
index 0000000..dbe54cc
--- /dev/null
+++ b/absl/strings/internal/cord_rep_crc.cc
@@ -0,0 +1,56 @@
+// Copyright 2021 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/strings/internal/cord_rep_crc.h"
+
+#include <cassert>
+#include <cstdint>
+#include <utility>
+
+#include "absl/base/config.h"
+#include "absl/strings/internal/cord_internal.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace cord_internal {
+
+CordRepCrc* CordRepCrc::New(CordRep* child, crc_internal::CrcCordState state) {
+ if (child != nullptr && child->IsCrc()) {
+ if (child->refcount.IsOne()) {
+ child->crc()->crc_cord_state = std::move(state);
+ return child->crc();
+ }
+ CordRep* old = child;
+ child = old->crc()->child;
+ CordRep::Ref(child);
+ CordRep::Unref(old);
+ }
+ auto* new_cordrep = new CordRepCrc;
+ new_cordrep->length = child != nullptr ? child->length : 0;
+ new_cordrep->tag = cord_internal::CRC;
+ new_cordrep->child = child;
+ new_cordrep->crc_cord_state = std::move(state);
+ return new_cordrep;
+}
+
+void CordRepCrc::Destroy(CordRepCrc* node) {
+ if (node->child != nullptr) {
+ CordRep::Unref(node->child);
+ }
+ delete node;
+}
+
+} // namespace cord_internal
+ABSL_NAMESPACE_END
+} // namespace absl
diff --git a/absl/strings/internal/cord_rep_crc.h b/absl/strings/internal/cord_rep_crc.h
new file mode 100644
index 0000000..379d7a6
--- /dev/null
+++ b/absl/strings/internal/cord_rep_crc.h
@@ -0,0 +1,103 @@
+// Copyright 2021 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef ABSL_STRINGS_INTERNAL_CORD_REP_CRC_H_
+#define ABSL_STRINGS_INTERNAL_CORD_REP_CRC_H_
+
+#include <cassert>
+#include <cstdint>
+
+#include "absl/base/config.h"
+#include "absl/base/optimization.h"
+#include "absl/crc/internal/crc_cord_state.h"
+#include "absl/strings/internal/cord_internal.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace cord_internal {
+
+// CordRepCrc is a CordRep node intended only to appear at the top level of a
+// cord tree. It associates an "expected CRC" with the contained data, to allow
+// for easy passage of checksum data in Cord data flows.
+//
+// From Cord's perspective, the crc value has no semantics; any validation of
+// the contained checksum is the user's responsibility.
+struct CordRepCrc : public CordRep {
+ CordRep* child;
+ absl::crc_internal::CrcCordState crc_cord_state;
+
+ // Consumes `child` and returns a CordRepCrc prefixed tree containing `child`.
+ // If the specified `child` is itself a CordRepCrc node, then this method
+ // either replaces the existing node, or directly updates the crc state in it
+ // depending on the node being shared or not, i.e.: refcount.IsOne().
+ // `child` must only be null if the Cord is empty. Never returns null.
+ static CordRepCrc* New(CordRep* child, crc_internal::CrcCordState state);
+
+ // Destroys (deletes) the provided node. `node` must not be null.
+ static void Destroy(CordRepCrc* node);
+};
+
+// Consumes `rep` and returns a CordRep* with any outer CordRepCrc wrapper
+// removed. This is usually a no-op (returning `rep`), but this will remove and
+// unref an outer CordRepCrc node.
+inline CordRep* RemoveCrcNode(CordRep* rep) {
+ assert(rep != nullptr);
+ if (ABSL_PREDICT_FALSE(rep->IsCrc())) {
+ CordRep* child = rep->crc()->child;
+ if (rep->refcount.IsOne()) {
+ delete rep->crc();
+ } else {
+ CordRep::Ref(child);
+ CordRep::Unref(rep);
+ }
+ return child;
+ }
+ return rep;
+}
+
+// Returns `rep` if it is not a CordRepCrc node, or its child if it is.
+// Does not consume or create a reference on `rep` or the returned value.
+inline CordRep* SkipCrcNode(CordRep* rep) {
+ assert(rep != nullptr);
+ if (ABSL_PREDICT_FALSE(rep->IsCrc())) {
+ return rep->crc()->child;
+ } else {
+ return rep;
+ }
+}
+
+inline const CordRep* SkipCrcNode(const CordRep* rep) {
+ assert(rep != nullptr);
+ if (ABSL_PREDICT_FALSE(rep->IsCrc())) {
+ return rep->crc()->child;
+ } else {
+ return rep;
+ }
+}
+
+inline CordRepCrc* CordRep::crc() {
+ assert(IsCrc());
+ return static_cast<CordRepCrc*>(this);
+}
+
+inline const CordRepCrc* CordRep::crc() const {
+ assert(IsCrc());
+ return static_cast<const CordRepCrc*>(this);
+}
+
+} // namespace cord_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_STRINGS_INTERNAL_CORD_REP_CRC_H_
diff --git a/absl/strings/internal/cord_rep_crc_test.cc b/absl/strings/internal/cord_rep_crc_test.cc
new file mode 100644
index 0000000..3d27c33
--- /dev/null
+++ b/absl/strings/internal/cord_rep_crc_test.cc
@@ -0,0 +1,130 @@
+// Copyright 2021 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/strings/internal/cord_rep_crc.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/base/config.h"
+#include "absl/crc/internal/crc_cord_state.h"
+#include "absl/strings/internal/cord_internal.h"
+#include "absl/strings/internal/cord_rep_test_util.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace cord_internal {
+namespace {
+
+using ::absl::cordrep_testing::MakeFlat;
+using ::testing::Eq;
+using ::testing::IsNull;
+using ::testing::Ne;
+
+#if !defined(NDEBUG) && GTEST_HAS_DEATH_TEST
+
+TEST(CordRepCrc, RemoveCrcWithNullptr) {
+ EXPECT_DEATH(RemoveCrcNode(nullptr), "");
+}
+
+#endif // !NDEBUG && GTEST_HAS_DEATH_TEST
+
+absl::crc_internal::CrcCordState MakeCrcCordState(uint32_t crc) {
+ crc_internal::CrcCordState state;
+ state.mutable_rep()->prefix_crc.push_back(
+ crc_internal::CrcCordState::PrefixCrc(42, crc32c_t{crc}));
+ return state;
+}
+
+TEST(CordRepCrc, NewDestroy) {
+ CordRep* rep = cordrep_testing::MakeFlat("Hello world");
+ CordRepCrc* crc = CordRepCrc::New(rep, MakeCrcCordState(12345));
+ EXPECT_TRUE(crc->refcount.IsOne());
+ EXPECT_THAT(crc->child, Eq(rep));
+ EXPECT_THAT(crc->crc_cord_state.Checksum(), Eq(crc32c_t{12345u}));
+ EXPECT_TRUE(rep->refcount.IsOne());
+ CordRepCrc::Destroy(crc);
+}
+
+TEST(CordRepCrc, NewExistingCrcNotShared) {
+ CordRep* rep = cordrep_testing::MakeFlat("Hello world");
+ CordRepCrc* crc = CordRepCrc::New(rep, MakeCrcCordState(12345));
+ CordRepCrc* new_crc = CordRepCrc::New(crc, MakeCrcCordState(54321));
+ EXPECT_THAT(new_crc, Eq(crc));
+ EXPECT_TRUE(new_crc->refcount.IsOne());
+ EXPECT_THAT(new_crc->child, Eq(rep));
+ EXPECT_THAT(new_crc->crc_cord_state.Checksum(), Eq(crc32c_t{54321u}));
+ EXPECT_TRUE(rep->refcount.IsOne());
+ CordRepCrc::Destroy(new_crc);
+}
+
+TEST(CordRepCrc, NewExistingCrcShared) {
+ CordRep* rep = cordrep_testing::MakeFlat("Hello world");
+ CordRepCrc* crc = CordRepCrc::New(rep, MakeCrcCordState(12345));
+ CordRep::Ref(crc);
+ CordRepCrc* new_crc = CordRepCrc::New(crc, MakeCrcCordState(54321));
+
+ EXPECT_THAT(new_crc, Ne(crc));
+ EXPECT_TRUE(new_crc->refcount.IsOne());
+ EXPECT_TRUE(crc->refcount.IsOne());
+ EXPECT_FALSE(rep->refcount.IsOne());
+ EXPECT_THAT(crc->child, Eq(rep));
+ EXPECT_THAT(new_crc->child, Eq(rep));
+ EXPECT_THAT(crc->crc_cord_state.Checksum(), Eq(crc32c_t{12345u}));
+ EXPECT_THAT(new_crc->crc_cord_state.Checksum(), Eq(crc32c_t{54321u}));
+
+ CordRep::Unref(crc);
+ CordRep::Unref(new_crc);
+}
+
+TEST(CordRepCrc, NewEmpty) {
+ CordRepCrc* crc = CordRepCrc::New(nullptr, MakeCrcCordState(12345));
+ EXPECT_TRUE(crc->refcount.IsOne());
+ EXPECT_THAT(crc->child, IsNull());
+ EXPECT_THAT(crc->length, Eq(0u));
+ EXPECT_THAT(crc->crc_cord_state.Checksum(), Eq(crc32c_t{12345u}));
+ EXPECT_TRUE(crc->refcount.IsOne());
+ CordRepCrc::Destroy(crc);
+}
+
+TEST(CordRepCrc, RemoveCrcNotCrc) {
+ CordRep* rep = cordrep_testing::MakeFlat("Hello world");
+ CordRep* nocrc = RemoveCrcNode(rep);
+ EXPECT_THAT(nocrc, Eq(rep));
+ CordRep::Unref(nocrc);
+}
+
+TEST(CordRepCrc, RemoveCrcNotShared) {
+ CordRep* rep = cordrep_testing::MakeFlat("Hello world");
+ CordRepCrc* crc = CordRepCrc::New(rep, MakeCrcCordState(12345));
+ CordRep* nocrc = RemoveCrcNode(crc);
+ EXPECT_THAT(nocrc, Eq(rep));
+ EXPECT_TRUE(rep->refcount.IsOne());
+ CordRep::Unref(nocrc);
+}
+
+TEST(CordRepCrc, RemoveCrcShared) {
+ CordRep* rep = cordrep_testing::MakeFlat("Hello world");
+ CordRepCrc* crc = CordRepCrc::New(rep, MakeCrcCordState(12345));
+ CordRep::Ref(crc);
+ CordRep* nocrc = RemoveCrcNode(crc);
+ EXPECT_THAT(nocrc, Eq(rep));
+ EXPECT_FALSE(rep->refcount.IsOne());
+ CordRep::Unref(nocrc);
+ CordRep::Unref(crc);
+}
+
+} // namespace
+} // namespace cord_internal
+ABSL_NAMESPACE_END
+} // namespace absl
diff --git a/absl/strings/internal/cord_rep_flat.h b/absl/strings/internal/cord_rep_flat.h
index 4d0f988..27c4b21 100644
--- a/absl/strings/internal/cord_rep_flat.h
+++ b/absl/strings/internal/cord_rep_flat.h
@@ -20,6 +20,8 @@
#include <cstdint>
#include <memory>
+#include "absl/base/config.h"
+#include "absl/base/macros.h"
#include "absl/strings/internal/cord_internal.h"
namespace absl {
@@ -42,23 +44,45 @@
static constexpr size_t kMaxFlatSize = 4096;
static constexpr size_t kMaxFlatLength = kMaxFlatSize - kFlatOverhead;
static constexpr size_t kMinFlatLength = kMinFlatSize - kFlatOverhead;
+static constexpr size_t kMaxLargeFlatSize = 256 * 1024;
+static constexpr size_t kMaxLargeFlatLength = kMaxLargeFlatSize - kFlatOverhead;
+// kTagBase should make the Size <--> Tag computation resilient
+// against changes to the value of FLAT when we add a new tag..
+static constexpr uint8_t kTagBase = FLAT - 4;
+
+// Converts the provided rounded size to the corresponding tag
constexpr uint8_t AllocatedSizeToTagUnchecked(size_t size) {
- return static_cast<uint8_t>((size <= 1024) ? size / 8 + 1
- : 129 + size / 32 - 1024 / 32);
+ return static_cast<uint8_t>(size <= 512 ? kTagBase + size / 8
+ : size <= 8192
+ ? kTagBase + 512 / 8 + size / 64 - 512 / 64
+ : kTagBase + 512 / 8 + ((8192 - 512) / 64) +
+ size / 4096 - 8192 / 4096);
}
-static_assert(kMinFlatSize / 8 + 1 >= FLAT, "");
-static_assert(AllocatedSizeToTagUnchecked(kMaxFlatSize) <= MAX_FLAT_TAG, "");
+// Converts the provided tag to the corresponding allocated size
+constexpr size_t TagToAllocatedSize(uint8_t tag) {
+ return (tag <= kTagBase + 512 / 8) ? tag * 8 - kTagBase * 8
+ : (tag <= kTagBase + (512 / 8) + ((8192 - 512) / 64))
+ ? 512 + tag * 64 - kTagBase * 64 - 512 / 8 * 64
+ : 8192 + tag * 4096 - kTagBase * 4096 -
+ ((512 / 8) + ((8192 - 512) / 64)) * 4096;
+}
-// Helper functions for rounded div, and rounding to exact sizes.
-constexpr size_t DivUp(size_t n, size_t m) { return (n + m - 1) / m; }
-constexpr size_t RoundUp(size_t n, size_t m) { return DivUp(n, m) * m; }
+static_assert(AllocatedSizeToTagUnchecked(kMinFlatSize) == FLAT, "");
+static_assert(AllocatedSizeToTagUnchecked(kMaxLargeFlatSize) == MAX_FLAT_TAG,
+ "");
+
+// 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.
+constexpr size_t RoundUp(size_t n, size_t m) {
+ return (n + m - 1) & (0 - m);
+}
// Returns the size to the nearest equal or larger value that can be
// expressed exactly as a tag value.
inline size_t RoundUpForTag(size_t size) {
- return RoundUp(size, (size <= 1024) ? 8 : 32);
+ return RoundUp(size, (size <= 512) ? 8 : (size <= 8192 ? 64 : 4096));
}
// Converts the allocated size to a tag, rounding down if the size
@@ -71,36 +95,50 @@
return tag;
}
-// Converts the provided tag to the corresponding allocated size
-constexpr size_t TagToAllocatedSize(uint8_t tag) {
- return (tag <= 129) ? ((tag - 1) * 8) : (1024 + (tag - 129) * 32);
-}
-
// Converts the provided tag to the corresponding available data length
constexpr size_t TagToLength(uint8_t tag) {
return TagToAllocatedSize(tag) - kFlatOverhead;
}
// Enforce that kMaxFlatSize maps to a well-known exact tag value.
-static_assert(TagToAllocatedSize(225) == kMaxFlatSize, "Bad tag logic");
+static_assert(TagToAllocatedSize(MAX_FLAT_TAG) == kMaxLargeFlatSize,
+ "Bad tag logic");
struct CordRepFlat : public CordRep {
+ // Tag for explicit 'large flat' allocation
+ struct Large {};
+
// Creates a new flat node.
- static CordRepFlat* New(size_t len) {
+ template <size_t max_flat_size, typename... Args>
+ static CordRepFlat* NewImpl(size_t len, Args... args ABSL_ATTRIBUTE_UNUSED) {
if (len <= kMinFlatLength) {
len = kMinFlatLength;
- } else if (len > kMaxFlatLength) {
- len = kMaxFlatLength;
+ } else if (len > max_flat_size - kFlatOverhead) {
+ len = max_flat_size - kFlatOverhead;
}
// Round size up so it matches a size we can exactly express in a tag.
const size_t size = RoundUpForTag(len + kFlatOverhead);
void* const raw_rep = ::operator new(size);
+ // GCC 13 has a false-positive -Wstringop-overflow warning here.
+ #if ABSL_INTERNAL_HAVE_MIN_GNUC_VERSION(13, 0)
+ #pragma GCC diagnostic push
+ #pragma GCC diagnostic ignored "-Wstringop-overflow"
+ #endif
CordRepFlat* rep = new (raw_rep) CordRepFlat();
rep->tag = AllocatedSizeToTag(size);
+ #if ABSL_INTERNAL_HAVE_MIN_GNUC_VERSION(13, 0)
+ #pragma GCC diagnostic pop
+ #endif
return rep;
}
+ static CordRepFlat* New(size_t len) { return NewImpl<kMaxFlatSize>(len); }
+
+ static CordRepFlat* New(Large, size_t len) {
+ return NewImpl<kMaxLargeFlatSize>(len);
+ }
+
// Deletes a CordRepFlat instance created previously through a call to New().
// Flat CordReps are allocated and constructed with raw ::operator new and
// placement new, and must be destructed and deallocated accordingly.
@@ -117,6 +155,17 @@
#endif
}
+ // Create a CordRepFlat containing `data`, with an optional additional
+ // extra capacity of up to `extra` bytes. Requires that `data.size()`
+ // is less than kMaxFlatLength.
+ static CordRepFlat* Create(absl::string_view data, size_t extra = 0) {
+ assert(data.size() <= kMaxFlatLength);
+ CordRepFlat* flat = New(data.size() + (std::min)(extra, kMaxFlatLength));
+ memcpy(flat->Data(), data.data(), data.size());
+ flat->length = data.size();
+ return flat;
+ }
+
// Returns a pointer to the data inside this flat rep.
char* Data() { return reinterpret_cast<char*>(storage); }
const char* Data() const { return reinterpret_cast<const char*>(storage); }
diff --git a/absl/strings/internal/cord_rep_ring.cc b/absl/strings/internal/cord_rep_ring.cc
deleted file mode 100644
index 20a6fc2..0000000
--- a/absl/strings/internal/cord_rep_ring.cc
+++ /dev/null
@@ -1,771 +0,0 @@
-// Copyright 2020 The Abseil Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-#include "absl/strings/internal/cord_rep_ring.h"
-
-#include <cassert>
-#include <cstddef>
-#include <cstdint>
-#include <iostream>
-#include <limits>
-#include <memory>
-#include <string>
-
-#include "absl/base/internal/raw_logging.h"
-#include "absl/base/internal/throw_delegate.h"
-#include "absl/base/macros.h"
-#include "absl/container/inlined_vector.h"
-#include "absl/strings/internal/cord_internal.h"
-#include "absl/strings/internal/cord_rep_consume.h"
-#include "absl/strings/internal/cord_rep_flat.h"
-
-namespace absl {
-ABSL_NAMESPACE_BEGIN
-namespace cord_internal {
-
-namespace {
-
-using index_type = CordRepRing::index_type;
-
-enum class Direction { kForward, kReversed };
-
-inline bool IsFlatOrExternal(CordRep* rep) {
- return rep->tag >= FLAT || rep->tag == EXTERNAL;
-}
-
-// Verifies that n + extra <= kMaxCapacity: throws std::length_error otherwise.
-inline void CheckCapacity(size_t n, size_t extra) {
- if (ABSL_PREDICT_FALSE(extra > CordRepRing::kMaxCapacity - n)) {
- base_internal::ThrowStdLengthError("Maximum capacity exceeded");
- }
-}
-
-// Creates a flat from the provided string data, allocating up to `extra`
-// capacity in the returned flat depending on kMaxFlatLength limitations.
-// Requires `len` to be less or equal to `kMaxFlatLength`
-CordRepFlat* CreateFlat(const char* s, size_t n, size_t extra = 0) { // NOLINT
- assert(n <= kMaxFlatLength);
- auto* rep = CordRepFlat::New(n + extra);
- rep->length = n;
- memcpy(rep->Data(), s, n);
- return rep;
-}
-
-// Unrefs the entries in `[head, tail)`.
-// Requires all entries to be a FLAT or EXTERNAL node.
-void UnrefEntries(const CordRepRing* rep, index_type head, index_type tail) {
- rep->ForEach(head, tail, [rep](index_type ix) {
- CordRep* child = rep->entry_child(ix);
- if (!child->refcount.Decrement()) {
- if (child->tag >= FLAT) {
- CordRepFlat::Delete(child->flat());
- } else {
- CordRepExternal::Delete(child->external());
- }
- }
- });
-}
-
-} // namespace
-
-std::ostream& operator<<(std::ostream& s, const CordRepRing& rep) {
- // Note: 'pos' values are defined as size_t (for overflow reasons), but that
- // prints really awkward for small prepended values such as -5. ssize_t is not
- // portable (POSIX), so we use ptrdiff_t instead to cast to signed values.
- s << " CordRepRing(" << &rep << ", length = " << rep.length
- << ", head = " << rep.head_ << ", tail = " << rep.tail_
- << ", cap = " << rep.capacity_ << ", rc = " << rep.refcount.Get()
- << ", begin_pos_ = " << static_cast<ptrdiff_t>(rep.begin_pos_) << ") {\n";
- CordRepRing::index_type head = rep.head();
- do {
- CordRep* child = rep.entry_child(head);
- s << " entry[" << head << "] length = " << rep.entry_length(head)
- << ", child " << child << ", clen = " << child->length
- << ", tag = " << static_cast<int>(child->tag)
- << ", rc = " << child->refcount.Get()
- << ", offset = " << rep.entry_data_offset(head)
- << ", end_pos = " << static_cast<ptrdiff_t>(rep.entry_end_pos(head))
- << "\n";
- head = rep.advance(head);
- } while (head != rep.tail());
- return s << "}\n";
-}
-
-void CordRepRing::AddDataOffset(index_type index, size_t n) {
- entry_data_offset()[index] += static_cast<offset_type>(n);
-}
-
-void CordRepRing::SubLength(index_type index, size_t n) {
- entry_end_pos()[index] -= n;
-}
-
-class CordRepRing::Filler {
- public:
- Filler(CordRepRing* rep, index_type pos) : rep_(rep), head_(pos), pos_(pos) {}
-
- index_type head() const { return head_; }
- index_type pos() const { return pos_; }
-
- void Add(CordRep* child, size_t offset, pos_type end_pos) {
- rep_->entry_end_pos()[pos_] = end_pos;
- rep_->entry_child()[pos_] = child;
- rep_->entry_data_offset()[pos_] = static_cast<offset_type>(offset);
- pos_ = rep_->advance(pos_);
- }
-
- private:
- CordRepRing* rep_;
- index_type head_;
- index_type pos_;
-};
-
-constexpr size_t CordRepRing::kMaxCapacity; // NOLINT: needed for c++11
-
-bool CordRepRing::IsValid(std::ostream& output) const {
- if (capacity_ == 0) {
- output << "capacity == 0";
- return false;
- }
-
- if (head_ >= capacity_ || tail_ >= capacity_) {
- output << "head " << head_ << " and/or tail " << tail_ << "exceed capacity "
- << capacity_;
- return false;
- }
-
- const index_type back = retreat(tail_);
- size_t pos_length = Distance(begin_pos_, entry_end_pos(back));
- if (pos_length != length) {
- output << "length " << length << " does not match positional length "
- << pos_length << " from begin_pos " << begin_pos_ << " and entry["
- << back << "].end_pos " << entry_end_pos(back);
- return false;
- }
-
- index_type head = head_;
- pos_type begin_pos = begin_pos_;
- do {
- pos_type end_pos = entry_end_pos(head);
- size_t entry_length = Distance(begin_pos, end_pos);
- if (entry_length == 0) {
- output << "entry[" << head << "] has an invalid length " << entry_length
- << " from begin_pos " << begin_pos << " and end_pos " << end_pos;
- return false;
- }
-
- CordRep* child = entry_child(head);
- if (child == nullptr) {
- output << "entry[" << head << "].child == nullptr";
- return false;
- }
- if (child->tag < FLAT && child->tag != EXTERNAL) {
- output << "entry[" << head << "].child has an invalid tag "
- << static_cast<int>(child->tag);
- return false;
- }
-
- size_t offset = entry_data_offset(head);
- if (offset >= child->length || entry_length > child->length - offset) {
- output << "entry[" << head << "] has offset " << offset
- << " and entry length " << entry_length
- << " which are outside of the child's length of " << child->length;
- return false;
- }
-
- begin_pos = end_pos;
- head = advance(head);
- } while (head != tail_);
-
- return true;
-}
-
-#ifdef EXTRA_CORD_RING_VALIDATION
-CordRepRing* CordRepRing::Validate(CordRepRing* rep, const char* file,
- int line) {
- if (!rep->IsValid(std::cerr)) {
- std::cerr << "\nERROR: CordRepRing corrupted";
- if (line) std::cerr << " at line " << line;
- if (file) std::cerr << " in file " << file;
- std::cerr << "\nContent = " << *rep;
- abort();
- }
- return rep;
-}
-#endif // EXTRA_CORD_RING_VALIDATION
-
-CordRepRing* CordRepRing::New(size_t capacity, size_t extra) {
- CheckCapacity(capacity, extra);
-
- size_t size = AllocSize(capacity += extra);
- void* mem = ::operator new(size);
- auto* rep = new (mem) CordRepRing(static_cast<index_type>(capacity));
- rep->tag = RING;
- rep->capacity_ = static_cast<index_type>(capacity);
- rep->begin_pos_ = 0;
- return rep;
-}
-
-void CordRepRing::SetCapacityForTesting(size_t capacity) {
- // Adjust for the changed layout
- assert(capacity <= capacity_);
- assert(head() == 0 || head() < tail());
- memmove(Layout::Partial(capacity).Pointer<1>(data_) + head(),
- Layout::Partial(capacity_).Pointer<1>(data_) + head(),
- entries() * sizeof(Layout::ElementType<1>));
- memmove(Layout::Partial(capacity, capacity).Pointer<2>(data_) + head(),
- Layout::Partial(capacity_, capacity_).Pointer<2>(data_) + head(),
- entries() * sizeof(Layout::ElementType<2>));
- capacity_ = static_cast<index_type>(capacity);
-}
-
-void CordRepRing::Delete(CordRepRing* rep) {
- assert(rep != nullptr && rep->tag == RING);
-#if defined(__cpp_sized_deallocation)
- size_t size = AllocSize(rep->capacity_);
- rep->~CordRepRing();
- ::operator delete(rep, size);
-#else
- rep->~CordRepRing();
- ::operator delete(rep);
-#endif
-}
-
-void CordRepRing::Destroy(CordRepRing* rep) {
- UnrefEntries(rep, rep->head(), rep->tail());
- Delete(rep);
-}
-
-template <bool ref>
-void CordRepRing::Fill(const CordRepRing* src, index_type head,
- index_type tail) {
- this->length = src->length;
- head_ = 0;
- tail_ = advance(0, src->entries(head, tail));
- begin_pos_ = src->begin_pos_;
-
- // TODO(mvels): there may be opportunities here for large buffers.
- auto* dst_pos = entry_end_pos();
- auto* dst_child = entry_child();
- auto* dst_offset = entry_data_offset();
- src->ForEach(head, tail, [&](index_type index) {
- *dst_pos++ = src->entry_end_pos(index);
- CordRep* child = src->entry_child(index);
- *dst_child++ = ref ? CordRep::Ref(child) : child;
- *dst_offset++ = src->entry_data_offset(index);
- });
-}
-
-CordRepRing* CordRepRing::Copy(CordRepRing* rep, index_type head,
- index_type tail, size_t extra) {
- CordRepRing* newrep = CordRepRing::New(rep->entries(head, tail), extra);
- newrep->Fill<true>(rep, head, tail);
- CordRep::Unref(rep);
- return newrep;
-}
-
-CordRepRing* CordRepRing::Mutable(CordRepRing* rep, size_t extra) {
- // Get current number of entries, and check for max capacity.
- size_t entries = rep->entries();
-
- if (!rep->refcount.IsOne()) {
- return Copy(rep, rep->head(), rep->tail(), extra);
- } else if (entries + extra > rep->capacity()) {
- const size_t min_grow = rep->capacity() + rep->capacity() / 2;
- const size_t min_extra = (std::max)(extra, min_grow - entries);
- CordRepRing* newrep = CordRepRing::New(entries, min_extra);
- newrep->Fill<false>(rep, rep->head(), rep->tail());
- CordRepRing::Delete(rep);
- return newrep;
- } else {
- return rep;
- }
-}
-
-Span<char> CordRepRing::GetAppendBuffer(size_t size) {
- assert(refcount.IsOne());
- index_type back = retreat(tail_);
- CordRep* child = entry_child(back);
- if (child->tag >= FLAT && child->refcount.IsOne()) {
- size_t capacity = child->flat()->Capacity();
- pos_type end_pos = entry_end_pos(back);
- size_t data_offset = entry_data_offset(back);
- size_t entry_length = Distance(entry_begin_pos(back), end_pos);
- size_t used = data_offset + entry_length;
- if (size_t n = (std::min)(capacity - used, size)) {
- child->length = data_offset + entry_length + n;
- entry_end_pos()[back] = end_pos + n;
- this->length += n;
- return {child->flat()->Data() + used, n};
- }
- }
- return {nullptr, 0};
-}
-
-Span<char> CordRepRing::GetPrependBuffer(size_t size) {
- assert(refcount.IsOne());
- CordRep* child = entry_child(head_);
- size_t data_offset = entry_data_offset(head_);
- if (data_offset && child->refcount.IsOne() && child->tag >= FLAT) {
- size_t n = (std::min)(data_offset, size);
- this->length += n;
- begin_pos_ -= n;
- data_offset -= n;
- entry_data_offset()[head_] = static_cast<offset_type>(data_offset);
- return {child->flat()->Data() + data_offset, n};
- }
- return {nullptr, 0};
-}
-
-CordRepRing* CordRepRing::CreateFromLeaf(CordRep* child, size_t offset,
- size_t len, size_t extra) {
- CordRepRing* rep = CordRepRing::New(1, extra);
- rep->head_ = 0;
- rep->tail_ = rep->advance(0);
- rep->length = len;
- rep->entry_end_pos()[0] = len;
- rep->entry_child()[0] = child;
- rep->entry_data_offset()[0] = static_cast<offset_type>(offset);
- return Validate(rep);
-}
-
-CordRepRing* CordRepRing::CreateSlow(CordRep* child, size_t extra) {
- CordRepRing* rep = nullptr;
- Consume(child, [&](CordRep* child_arg, size_t offset, size_t len) {
- if (IsFlatOrExternal(child_arg)) {
- rep = rep ? AppendLeaf(rep, child_arg, offset, len)
- : CreateFromLeaf(child_arg, offset, len, extra);
- } else if (rep) {
- rep = AddRing<AddMode::kAppend>(rep, child_arg->ring(), offset, len);
- } else if (offset == 0 && child_arg->length == len) {
- rep = Mutable(child_arg->ring(), extra);
- } else {
- rep = SubRing(child_arg->ring(), offset, len, extra);
- }
- });
- return Validate(rep, nullptr, __LINE__);
-}
-
-CordRepRing* CordRepRing::Create(CordRep* child, size_t extra) {
- size_t length = child->length;
- if (IsFlatOrExternal(child)) {
- return CreateFromLeaf(child, 0, length, extra);
- }
- if (child->tag == RING) {
- return Mutable(child->ring(), extra);
- }
- return CreateSlow(child, extra);
-}
-
-template <CordRepRing::AddMode mode>
-CordRepRing* CordRepRing::AddRing(CordRepRing* rep, CordRepRing* ring,
- size_t offset, size_t len) {
- assert(offset < ring->length);
- constexpr bool append = mode == AddMode::kAppend;
- Position head = ring->Find(offset);
- Position tail = ring->FindTail(head.index, offset + len);
- const index_type entries = ring->entries(head.index, tail.index);
-
- rep = Mutable(rep, entries);
-
- // The delta for making ring[head].end_pos into 'len - offset'
- const pos_type delta_length =
- (append ? rep->begin_pos_ + rep->length : rep->begin_pos_ - len) -
- ring->entry_begin_pos(head.index) - head.offset;
-
- // Start filling at `tail`, or `entries` before `head`
- Filler filler(rep, append ? rep->tail_ : rep->retreat(rep->head_, entries));
-
- if (ring->refcount.IsOne()) {
- // Copy entries from source stealing the ref and adjusting the end position.
- // Commit the filler as this is no-op.
- ring->ForEach(head.index, tail.index, [&](index_type ix) {
- filler.Add(ring->entry_child(ix), ring->entry_data_offset(ix),
- ring->entry_end_pos(ix) + delta_length);
- });
-
- // Unref entries we did not copy over, and delete source.
- if (head.index != ring->head_) UnrefEntries(ring, ring->head_, head.index);
- if (tail.index != ring->tail_) UnrefEntries(ring, tail.index, ring->tail_);
- CordRepRing::Delete(ring);
- } else {
- ring->ForEach(head.index, tail.index, [&](index_type ix) {
- CordRep* child = ring->entry_child(ix);
- filler.Add(child, ring->entry_data_offset(ix),
- ring->entry_end_pos(ix) + delta_length);
- CordRep::Ref(child);
- });
- CordRepRing::Unref(ring);
- }
-
- if (head.offset) {
- // Increase offset of first 'source' entry appended or prepended.
- // This is always the entry in `filler.head()`
- rep->AddDataOffset(filler.head(), head.offset);
- }
-
- if (tail.offset) {
- // Reduce length of last 'source' entry appended or prepended.
- // This is always the entry tailed by `filler.pos()`
- rep->SubLength(rep->retreat(filler.pos()), tail.offset);
- }
-
- // Commit changes
- rep->length += len;
- if (append) {
- rep->tail_ = filler.pos();
- } else {
- rep->head_ = filler.head();
- rep->begin_pos_ -= len;
- }
-
- return Validate(rep);
-}
-
-CordRepRing* CordRepRing::AppendSlow(CordRepRing* rep, CordRep* child) {
- Consume(child, [&rep](CordRep* child_arg, size_t offset, size_t len) {
- if (child_arg->tag == RING) {
- rep = AddRing<AddMode::kAppend>(rep, child_arg->ring(), offset, len);
- } else {
- rep = AppendLeaf(rep, child_arg, offset, len);
- }
- });
- return rep;
-}
-
-CordRepRing* CordRepRing::AppendLeaf(CordRepRing* rep, CordRep* child,
- size_t offset, size_t len) {
- rep = Mutable(rep, 1);
- index_type back = rep->tail_;
- const pos_type begin_pos = rep->begin_pos_ + rep->length;
- rep->tail_ = rep->advance(rep->tail_);
- rep->length += len;
- rep->entry_end_pos()[back] = begin_pos + len;
- rep->entry_child()[back] = child;
- rep->entry_data_offset()[back] = static_cast<offset_type>(offset);
- return Validate(rep, nullptr, __LINE__);
-}
-
-CordRepRing* CordRepRing::Append(CordRepRing* rep, CordRep* child) {
- size_t length = child->length;
- if (IsFlatOrExternal(child)) {
- return AppendLeaf(rep, child, 0, length);
- }
- if (child->tag == RING) {
- return AddRing<AddMode::kAppend>(rep, child->ring(), 0, length);
- }
- return AppendSlow(rep, child);
-}
-
-CordRepRing* CordRepRing::PrependSlow(CordRepRing* rep, CordRep* child) {
- ReverseConsume(child, [&](CordRep* child_arg, size_t offset, size_t len) {
- if (IsFlatOrExternal(child_arg)) {
- rep = PrependLeaf(rep, child_arg, offset, len);
- } else {
- rep = AddRing<AddMode::kPrepend>(rep, child_arg->ring(), offset, len);
- }
- });
- return Validate(rep);
-}
-
-CordRepRing* CordRepRing::PrependLeaf(CordRepRing* rep, CordRep* child,
- size_t offset, size_t len) {
- rep = Mutable(rep, 1);
- index_type head = rep->retreat(rep->head_);
- pos_type end_pos = rep->begin_pos_;
- rep->head_ = head;
- rep->length += len;
- rep->begin_pos_ -= len;
- rep->entry_end_pos()[head] = end_pos;
- rep->entry_child()[head] = child;
- rep->entry_data_offset()[head] = static_cast<offset_type>(offset);
- return Validate(rep);
-}
-
-CordRepRing* CordRepRing::Prepend(CordRepRing* rep, CordRep* child) {
- size_t length = child->length;
- if (IsFlatOrExternal(child)) {
- return PrependLeaf(rep, child, 0, length);
- }
- if (child->tag == RING) {
- return AddRing<AddMode::kPrepend>(rep, child->ring(), 0, length);
- }
- return PrependSlow(rep, child);
-}
-
-CordRepRing* CordRepRing::Append(CordRepRing* rep, absl::string_view data,
- size_t extra) {
- if (rep->refcount.IsOne()) {
- Span<char> avail = rep->GetAppendBuffer(data.length());
- if (!avail.empty()) {
- memcpy(avail.data(), data.data(), avail.length());
- data.remove_prefix(avail.length());
- }
- }
- if (data.empty()) return Validate(rep);
-
- const size_t flats = (data.length() - 1) / kMaxFlatLength + 1;
- rep = Mutable(rep, flats);
-
- Filler filler(rep, rep->tail_);
- pos_type pos = rep->begin_pos_ + rep->length;
-
- while (data.length() >= kMaxFlatLength) {
- auto* flat = CreateFlat(data.data(), kMaxFlatLength);
- filler.Add(flat, 0, pos += kMaxFlatLength);
- data.remove_prefix(kMaxFlatLength);
- }
-
- if (data.length()) {
- auto* flat = CreateFlat(data.data(), data.length(), extra);
- filler.Add(flat, 0, pos += data.length());
- }
-
- rep->length = pos - rep->begin_pos_;
- rep->tail_ = filler.pos();
-
- return Validate(rep);
-}
-
-CordRepRing* CordRepRing::Prepend(CordRepRing* rep, absl::string_view data,
- size_t extra) {
- if (rep->refcount.IsOne()) {
- Span<char> avail = rep->GetPrependBuffer(data.length());
- if (!avail.empty()) {
- const char* tail = data.data() + data.length() - avail.length();
- memcpy(avail.data(), tail, avail.length());
- data.remove_suffix(avail.length());
- }
- }
- if (data.empty()) return rep;
-
- const size_t flats = (data.length() - 1) / kMaxFlatLength + 1;
- rep = Mutable(rep, flats);
- pos_type pos = rep->begin_pos_;
- Filler filler(rep, rep->retreat(rep->head_, static_cast<index_type>(flats)));
-
- size_t first_size = data.size() - (flats - 1) * kMaxFlatLength;
- CordRepFlat* flat = CordRepFlat::New(first_size + extra);
- flat->length = first_size + extra;
- memcpy(flat->Data() + extra, data.data(), first_size);
- data.remove_prefix(first_size);
- filler.Add(flat, extra, pos);
- pos -= first_size;
-
- while (!data.empty()) {
- assert(data.size() >= kMaxFlatLength);
- flat = CreateFlat(data.data(), kMaxFlatLength);
- filler.Add(flat, 0, pos);
- pos -= kMaxFlatLength;
- data.remove_prefix(kMaxFlatLength);
- }
-
- rep->head_ = filler.head();
- rep->length += rep->begin_pos_ - pos;
- rep->begin_pos_ = pos;
-
- return Validate(rep);
-}
-
-// 32 entries is 32 * sizeof(pos_type) = 4 cache lines on x86
-static constexpr index_type kBinarySearchThreshold = 32;
-static constexpr index_type kBinarySearchEndCount = 8;
-
-template <bool wrap>
-CordRepRing::index_type CordRepRing::FindBinary(index_type head,
- index_type tail,
- size_t offset) const {
- index_type count = tail + (wrap ? capacity_ : 0) - head;
- do {
- count = (count - 1) / 2;
- assert(count < entries(head, tail_));
- index_type mid = wrap ? advance(head, count) : head + count;
- index_type after_mid = wrap ? advance(mid) : mid + 1;
- bool larger = (offset >= entry_end_offset(mid));
- head = larger ? after_mid : head;
- tail = larger ? tail : mid;
- assert(head != tail);
- } while (ABSL_PREDICT_TRUE(count > kBinarySearchEndCount));
- return head;
-}
-
-CordRepRing::Position CordRepRing::FindSlow(index_type head,
- size_t offset) const {
- index_type tail = tail_;
-
- // Binary search until we are good for linear search
- // Optimize for branchless / non wrapping ops
- if (tail > head) {
- index_type count = tail - head;
- if (count > kBinarySearchThreshold) {
- head = FindBinary<false>(head, tail, offset);
- }
- } else {
- index_type count = capacity_ + tail - head;
- if (count > kBinarySearchThreshold) {
- head = FindBinary<true>(head, tail, offset);
- }
- }
-
- pos_type pos = entry_begin_pos(head);
- pos_type end_pos = entry_end_pos(head);
- while (offset >= Distance(begin_pos_, end_pos)) {
- head = advance(head);
- pos = end_pos;
- end_pos = entry_end_pos(head);
- }
-
- return {head, offset - Distance(begin_pos_, pos)};
-}
-
-CordRepRing::Position CordRepRing::FindTailSlow(index_type head,
- size_t offset) const {
- index_type tail = tail_;
- const size_t tail_offset = offset - 1;
-
- // Binary search until we are good for linear search
- // Optimize for branchless / non wrapping ops
- if (tail > head) {
- index_type count = tail - head;
- if (count > kBinarySearchThreshold) {
- head = FindBinary<false>(head, tail, tail_offset);
- }
- } else {
- index_type count = capacity_ + tail - head;
- if (count > kBinarySearchThreshold) {
- head = FindBinary<true>(head, tail, tail_offset);
- }
- }
-
- size_t end_offset = entry_end_offset(head);
- while (tail_offset >= end_offset) {
- head = advance(head);
- end_offset = entry_end_offset(head);
- }
-
- return {advance(head), end_offset - offset};
-}
-
-char CordRepRing::GetCharacter(size_t offset) const {
- assert(offset < length);
-
- Position pos = Find(offset);
- size_t data_offset = entry_data_offset(pos.index) + pos.offset;
- return GetRepData(entry_child(pos.index))[data_offset];
-}
-
-CordRepRing* CordRepRing::SubRing(CordRepRing* rep, size_t offset,
- size_t len, size_t extra) {
- assert(offset <= rep->length);
- assert(offset <= rep->length - len);
-
- if (len == 0) {
- CordRep::Unref(rep);
- return nullptr;
- }
-
- // Find position of first byte
- Position head = rep->Find(offset);
- Position tail = rep->FindTail(head.index, offset + len);
- const size_t new_entries = rep->entries(head.index, tail.index);
-
- if (rep->refcount.IsOne() && extra <= (rep->capacity() - new_entries)) {
- // We adopt a privately owned rep and no extra entries needed.
- if (head.index != rep->head_) UnrefEntries(rep, rep->head_, head.index);
- if (tail.index != rep->tail_) UnrefEntries(rep, tail.index, rep->tail_);
- rep->head_ = head.index;
- rep->tail_ = tail.index;
- } else {
- // Copy subset to new rep
- rep = Copy(rep, head.index, tail.index, extra);
- head.index = rep->head_;
- tail.index = rep->tail_;
- }
-
- // Adjust begin_pos and length
- rep->length = len;
- rep->begin_pos_ += offset;
-
- // Adjust head and tail blocks
- if (head.offset) {
- rep->AddDataOffset(head.index, head.offset);
- }
- if (tail.offset) {
- rep->SubLength(rep->retreat(tail.index), tail.offset);
- }
-
- return Validate(rep);
-}
-
-CordRepRing* CordRepRing::RemovePrefix(CordRepRing* rep, size_t len,
- size_t extra) {
- assert(len <= rep->length);
- if (len == rep->length) {
- CordRep::Unref(rep);
- return nullptr;
- }
-
- Position head = rep->Find(len);
- if (rep->refcount.IsOne()) {
- if (head.index != rep->head_) UnrefEntries(rep, rep->head_, head.index);
- rep->head_ = head.index;
- } else {
- rep = Copy(rep, head.index, rep->tail_, extra);
- head.index = rep->head_;
- }
-
- // Adjust begin_pos and length
- rep->length -= len;
- rep->begin_pos_ += len;
-
- // Adjust head block
- if (head.offset) {
- rep->AddDataOffset(head.index, head.offset);
- }
-
- return Validate(rep);
-}
-
-CordRepRing* CordRepRing::RemoveSuffix(CordRepRing* rep, size_t len,
- size_t extra) {
- assert(len <= rep->length);
-
- if (len == rep->length) {
- CordRep::Unref(rep);
- return nullptr;
- }
-
- Position tail = rep->FindTail(rep->length - len);
- if (rep->refcount.IsOne()) {
- // We adopt a privately owned rep, scrub.
- if (tail.index != rep->tail_) UnrefEntries(rep, tail.index, rep->tail_);
- rep->tail_ = tail.index;
- } else {
- // Copy subset to new rep
- rep = Copy(rep, rep->head_, tail.index, extra);
- tail.index = rep->tail_;
- }
-
- // Adjust length
- rep->length -= len;
-
- // Adjust tail block
- if (tail.offset) {
- rep->SubLength(rep->retreat(tail.index), tail.offset);
- }
-
- return Validate(rep);
-}
-
-} // namespace cord_internal
-ABSL_NAMESPACE_END
-} // namespace absl
diff --git a/absl/strings/internal/cord_rep_ring.h b/absl/strings/internal/cord_rep_ring.h
deleted file mode 100644
index 2082a56..0000000
--- a/absl/strings/internal/cord_rep_ring.h
+++ /dev/null
@@ -1,607 +0,0 @@
-// Copyright 2020 The Abseil Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef ABSL_STRINGS_INTERNAL_CORD_REP_RING_H_
-#define ABSL_STRINGS_INTERNAL_CORD_REP_RING_H_
-
-#include <cassert>
-#include <cstddef>
-#include <cstdint>
-#include <iosfwd>
-#include <limits>
-#include <memory>
-
-#include "absl/container/internal/layout.h"
-#include "absl/strings/internal/cord_internal.h"
-#include "absl/strings/internal/cord_rep_flat.h"
-
-namespace absl {
-ABSL_NAMESPACE_BEGIN
-namespace cord_internal {
-
-// All operations modifying a ring buffer are implemented as static methods
-// requiring a CordRepRing instance with a reference adopted by the method.
-//
-// The methods return the modified ring buffer, which may be equal to the input
-// if the input was not shared, and having large enough capacity to accommodate
-// any newly added node(s). Otherwise, a copy of the input rep with the new
-// node(s) added is returned.
-//
-// Any modification on non shared ring buffers with enough capacity will then
-// require minimum atomic operations. Caller should where possible provide
-// reasonable `extra` hints for both anticipated extra `flat` byte space, as
-// well as anticipated extra nodes required for complex operations.
-//
-// Example of code creating a ring buffer, adding some data to it,
-// and discarding the buffer when done:
-//
-// void FunWithRings() {
-// // Create ring with 3 flats
-// CordRep* flat = CreateFlat("Hello");
-// CordRepRing* ring = CordRepRing::Create(flat, 2);
-// ring = CordRepRing::Append(ring, CreateFlat(" "));
-// ring = CordRepRing::Append(ring, CreateFlat("world"));
-// DoSomethingWithRing(ring);
-// CordRep::Unref(ring);
-// }
-//
-// Example of code Copying an existing ring buffer and modifying it:
-//
-// void MoreFunWithRings(CordRepRing* src) {
-// CordRepRing* ring = CordRep::Ref(src)->ring();
-// ring = CordRepRing::Append(ring, CreateFlat("Hello"));
-// ring = CordRepRing::Append(ring, CreateFlat(" "));
-// ring = CordRepRing::Append(ring, CreateFlat("world"));
-// DoSomethingWithRing(ring);
-// CordRep::Unref(ring);
-// }
-//
-class CordRepRing : public CordRep {
- public:
- // `pos_type` represents a 'logical position'. A CordRepRing instance has a
- // `begin_pos` (default 0), and each node inside the buffer will have an
- // `end_pos` which is the `end_pos` of the previous node (or `begin_pos`) plus
- // this node's length. The purpose is to allow for a binary search on this
- // position, while allowing O(1) prepend and append operations.
- using pos_type = size_t;
-
- // `index_type` is the type for the `head`, `tail` and `capacity` indexes.
- // Ring buffers are limited to having no more than four billion entries.
- using index_type = uint32_t;
-
- // `offset_type` is the type for the data offset inside a child rep's data.
- using offset_type = uint32_t;
-
- // Position holds the node index and relative offset into the node for
- // some physical offset in the contained data as returned by the Find()
- // and FindTail() methods.
- struct Position {
- index_type index;
- size_t offset;
- };
-
- // The maximum # of child nodes that can be hosted inside a CordRepRing.
- static constexpr size_t kMaxCapacity = (std::numeric_limits<uint32_t>::max)();
-
- // CordRepring can not be default constructed, moved, copied or assigned.
- CordRepRing() = delete;
- CordRepRing(const CordRepRing&) = delete;
- CordRepRing& operator=(const CordRepRing&) = delete;
-
- // Returns true if this instance is valid, false if some or all of the
- // invariants are broken. Intended for debug purposes only.
- // `output` receives an explanation of the broken invariants.
- bool IsValid(std::ostream& output) const;
-
- // Returns the size in bytes for a CordRepRing with `capacity' entries.
- static constexpr size_t AllocSize(size_t capacity);
-
- // Returns the distance in bytes from `pos` to `end_pos`.
- static constexpr size_t Distance(pos_type pos, pos_type end_pos);
-
- // Creates a new ring buffer from the provided `rep`. Adopts a reference
- // on `rep`. The returned ring buffer has a capacity of at least `extra + 1`
- static CordRepRing* Create(CordRep* child, size_t extra = 0);
-
- // `head`, `tail` and `capacity` indexes defining the ring buffer boundaries.
- index_type head() const { return head_; }
- index_type tail() const { return tail_; }
- index_type capacity() const { return capacity_; }
-
- // Returns the number of entries in this instance.
- index_type entries() const { return entries(head_, tail_); }
-
- // Returns the logical begin position of this instance.
- pos_type begin_pos() const { return begin_pos_; }
-
- // Returns the number of entries for a given head-tail range.
- // Requires `head` and `tail` values to be less than `capacity()`.
- index_type entries(index_type head, index_type tail) const {
- assert(head < capacity_ && tail < capacity_);
- return tail - head + ((tail > head) ? 0 : capacity_);
- }
-
- // Returns the logical end position of entry `index`.
- pos_type const& entry_end_pos(index_type index) const {
- assert(IsValidIndex(index));
- return Layout::Partial().Pointer<0>(data_)[index];
- }
-
- // Returns the child pointer of entry `index`.
- CordRep* const& entry_child(index_type index) const {
- assert(IsValidIndex(index));
- return Layout::Partial(capacity()).Pointer<1>(data_)[index];
- }
-
- // Returns the data offset of entry `index`
- offset_type const& entry_data_offset(index_type index) const {
- assert(IsValidIndex(index));
- return Layout::Partial(capacity(), capacity()).Pointer<2>(data_)[index];
- }
-
- // Appends the provided child node to the `rep` instance.
- // Adopts a reference from `rep` and `child` which may not be null.
- // If the provided child is a FLAT or EXTERNAL node, or a SUBSTRING node
- // containing a FLAT or EXTERNAL node, then flat or external the node is added
- // 'as is', with an offset added for the SUBSTRING case.
- // If the provided child is a RING or CONCAT tree, or a SUBSTRING of a RING or
- // CONCAT tree, then all child nodes not excluded by any start offset or
- // length values are added recursively.
- static CordRepRing* Append(CordRepRing* rep, CordRep* child);
-
- // Appends the provided string data to the `rep` instance.
- // This function will attempt to utilize any remaining capacity in the last
- // node of the input if that node is not shared (directly or indirectly), and
- // of type FLAT. Remaining data will be added as one or more FLAT nodes.
- // Any last node added to the ring buffer will be allocated with up to
- // `extra` bytes of capacity for (anticipated) subsequent append actions.
- static CordRepRing* Append(CordRepRing* rep, string_view data,
- size_t extra = 0);
-
- // Prepends the provided child node to the `rep` instance.
- // Adopts a reference from `rep` and `child` which may not be null.
- // If the provided child is a FLAT or EXTERNAL node, or a SUBSTRING node
- // containing a FLAT or EXTERNAL node, then flat or external the node is
- // prepended 'as is', with an optional offset added for the SUBSTRING case.
- // If the provided child is a RING or CONCAT tree, or a SUBSTRING of a RING
- // or CONCAT tree, then all child nodes not excluded by any start offset or
- // length values are added recursively.
- static CordRepRing* Prepend(CordRepRing* rep, CordRep* child);
-
- // Prepends the provided string data to the `rep` instance.
- // This function will attempt to utilize any remaining capacity in the first
- // node of the input if that node is not shared (directly or indirectly), and
- // of type FLAT. Remaining data will be added as one or more FLAT nodes.
- // Any first node prepnded to the ring buffer will be allocated with up to
- // `extra` bytes of capacity for (anticipated) subsequent prepend actions.
- static CordRepRing* Prepend(CordRepRing* rep, string_view data,
- size_t extra = 0);
-
- // Returns a span referencing potentially unused capacity in the last node.
- // The returned span may be empty if no such capacity is available, or if the
- // current instance is shared. Else, a span of size `n <= size` is returned.
- // If non empty, the ring buffer is adjusted to the new length, with the newly
- // added capacity left uninitialized. Callers should assign a value to the
- // entire span before any other operations on this instance.
- Span<char> GetAppendBuffer(size_t size);
-
- // Returns a span referencing potentially unused capacity in the first node.
- // This function is identical to GetAppendBuffer except that it returns a span
- // referencing up to `size` capacity directly before the existing data.
- Span<char> GetPrependBuffer(size_t size);
-
- // Returns a cord ring buffer containing `len` bytes of data starting at
- // `offset`. If the input is not shared, this function will remove all head
- // and tail child nodes outside of the requested range, and adjust the new
- // head and tail nodes as required. If the input is shared, this function
- // returns a new instance sharing some or all of the nodes from the input.
- static CordRepRing* SubRing(CordRepRing* r, size_t offset, size_t len,
- size_t extra = 0);
-
- // Returns a cord ring buffer with the first `len` bytes removed.
- // If the input is not shared, this function will remove all head child nodes
- // fully inside the first `length` bytes, and adjust the new head as required.
- // If the input is shared, this function returns a new instance sharing some
- // or all of the nodes from the input.
- static CordRepRing* RemoveSuffix(CordRepRing* r, size_t len,
- size_t extra = 0);
-
- // Returns a cord ring buffer with the last `len` bytes removed.
- // If the input is not shared, this function will remove all head child nodes
- // fully inside the first `length` bytes, and adjust the new head as required.
- // If the input is shared, this function returns a new instance sharing some
- // or all of the nodes from the input.
- static CordRepRing* RemovePrefix(CordRepRing* r, size_t len,
- size_t extra = 0);
-
- // Returns the character at `offset`. Requires that `offset < length`.
- char GetCharacter(size_t offset) const;
-
- // Returns true if this instance manages a single contiguous buffer, in which
- // case the (optional) output parameter `fragment` is set. Otherwise, the
- // function returns false, and `fragment` is left unchanged.
- bool IsFlat(absl::string_view* fragment) const;
-
- // Returns true if the data starting at `offset` with length `len` is
- // managed by this instance inside a single contiguous buffer, in which case
- // the (optional) output parameter `fragment` is set to the contiguous memory
- // starting at offset `offset` with length `length`. Otherwise, the function
- // returns false, and `fragment` is left unchanged.
- bool IsFlat(size_t offset, size_t len, absl::string_view* fragment) const;
-
- // Testing only: set capacity to requested capacity.
- void SetCapacityForTesting(size_t capacity);
-
- // Returns the CordRep data pointer for the provided CordRep.
- // Requires that the provided `rep` is either a FLAT or EXTERNAL CordRep.
- static const char* GetLeafData(const CordRep* rep);
-
- // Returns the CordRep data pointer for the provided CordRep.
- // Requires that `rep` is either a FLAT, EXTERNAL, or SUBSTRING CordRep.
- static const char* GetRepData(const CordRep* rep);
-
- // Advances the provided position, wrapping around capacity as needed.
- // Requires `index` < capacity()
- inline index_type advance(index_type index) const;
-
- // Advances the provided position by 'n`, wrapping around capacity as needed.
- // Requires `index` < capacity() and `n` <= capacity.
- inline index_type advance(index_type index, index_type n) const;
-
- // Retreats the provided position, wrapping around 0 as needed.
- // Requires `index` < capacity()
- inline index_type retreat(index_type index) const;
-
- // Retreats the provided position by 'n', wrapping around 0 as needed.
- // Requires `index` < capacity()
- inline index_type retreat(index_type index, index_type n) const;
-
- // Returns the logical begin position of entry `index`
- pos_type const& entry_begin_pos(index_type index) const {
- return (index == head_) ? begin_pos_ : entry_end_pos(retreat(index));
- }
-
- // Returns the physical start offset of entry `index`
- size_t entry_start_offset(index_type index) const {
- return Distance(begin_pos_, entry_begin_pos(index));
- }
-
- // Returns the physical end offset of entry `index`
- size_t entry_end_offset(index_type index) const {
- return Distance(begin_pos_, entry_end_pos(index));
- }
-
- // Returns the data length for entry `index`
- size_t entry_length(index_type index) const {
- return Distance(entry_begin_pos(index), entry_end_pos(index));
- }
-
- // Returns the data for entry `index`
- absl::string_view entry_data(index_type index) const;
-
- // Returns the position for `offset` as {index, prefix}. `index` holds the
- // index of the entry at the specified offset and `prefix` holds the relative
- // offset inside that entry.
- // Requires `offset` < length.
- //
- // For example we can implement GetCharacter(offset) as:
- // char GetCharacter(size_t offset) {
- // Position pos = this->Find(offset);
- // return this->entry_data(pos.pos)[pos.offset];
- // }
- inline Position Find(size_t offset) const;
-
- // Find starting at `head`
- inline Position Find(index_type head, size_t offset) const;
-
- // Returns the tail position for `offset` as {tail index, suffix}.
- // `tail index` holds holds the index of the entry holding the offset directly
- // before 'offset` advanced by one. 'suffix` holds the relative offset from
- // that relative offset in the entry to the end of the entry.
- // For example, FindTail(length) will return {tail(), 0}, FindTail(length - 5)
- // will return {retreat(tail), 5)} provided the preceding entry contains at
- // least 5 bytes of data.
- // Requires offset >= 1 && offset <= length.
- //
- // This function is very useful in functions that need to clip the end of some
- // ring buffer such as 'RemovePrefix'.
- // For example, we could implement RemovePrefix for non shared instances as:
- // void RemoveSuffix(size_t n) {
- // Position pos = FindTail(length - n);
- // UnrefEntries(pos.pos, this->tail_);
- // this->tail_ = pos.pos;
- // entry(retreat(pos.pos)).end_pos -= pos.offset;
- // }
- inline Position FindTail(size_t offset) const;
-
- // Find tail starting at `head`
- inline Position FindTail(index_type head, size_t offset) const;
-
- // Invokes f(index_type index) for each entry inside the range [head, tail>
- template <typename F>
- void ForEach(index_type head, index_type tail, F&& f) const {
- index_type n1 = (tail > head) ? tail : capacity_;
- for (index_type i = head; i < n1; ++i) f(i);
- if (tail <= head) {
- for (index_type i = 0; i < tail; ++i) f(i);
- }
- }
-
- // Invokes f(index_type index) for each entry inside this instance.
- template <typename F>
- void ForEach(F&& f) const {
- ForEach(head_, tail_, std::forward<F>(f));
- }
-
- // Dump this instance's data tp stream `s` in human readable format, excluding
- // the actual data content itself. Intended for debug purposes only.
- friend std::ostream& operator<<(std::ostream& s, const CordRepRing& rep);
-
- private:
- enum class AddMode { kAppend, kPrepend };
-
- using Layout = container_internal::Layout<pos_type, CordRep*, offset_type>;
-
- class Filler;
- class Transaction;
- class CreateTransaction;
-
- static constexpr size_t kLayoutAlignment = Layout::Partial().Alignment();
-
- // Creates a new CordRepRing.
- explicit CordRepRing(index_type capacity) : capacity_(capacity) {}
-
- // Returns true if `index` is a valid index into this instance.
- bool IsValidIndex(index_type index) const;
-
- // Debug use only: validates the provided CordRepRing invariants.
- // Verification of all CordRepRing methods can be enabled by defining
- // EXTRA_CORD_RING_VALIDATION, i.e.: `--copts=-DEXTRA_CORD_RING_VALIDATION`
- // Verification is VERY expensive, so only do it for debugging purposes.
- static CordRepRing* Validate(CordRepRing* rep, const char* file = nullptr,
- int line = 0);
-
- // Allocates a CordRepRing large enough to hold `capacity + extra' entries.
- // The returned capacity may be larger if the allocated memory allows for it.
- // The maximum capacity of a CordRepRing is capped at kMaxCapacity.
- // Throws `std::length_error` if `capacity + extra' exceeds kMaxCapacity.
- static CordRepRing* New(size_t capacity, size_t extra);
-
- // Deallocates (but does not destroy) the provided ring buffer.
- static void Delete(CordRepRing* rep);
-
- // Destroys the provided ring buffer, decrementing the reference count of all
- // contained child CordReps. The provided 1\`rep` should have a ref count of
- // one (pre decrement destroy call observing `refcount.IsOne()`) or zero (post
- // decrement destroy call observing `!refcount.Decrement()`).
- static void Destroy(CordRepRing* rep);
-
- // Returns a mutable reference to the logical end position array.
- pos_type* entry_end_pos() {
- return Layout::Partial().Pointer<0>(data_);
- }
-
- // Returns a mutable reference to the child pointer array.
- CordRep** entry_child() {
- return Layout::Partial(capacity()).Pointer<1>(data_);
- }
-
- // Returns a mutable reference to the data offset array.
- offset_type* entry_data_offset() {
- return Layout::Partial(capacity(), capacity()).Pointer<2>(data_);
- }
-
- // Find implementations for the non fast path 0 / length cases.
- Position FindSlow(index_type head, size_t offset) const;
- Position FindTailSlow(index_type head, size_t offset) const;
-
- // Finds the index of the first node that is inside a reasonable distance
- // of the node at `offset` from which we can continue with a linear search.
- template <bool wrap>
- index_type FindBinary(index_type head, index_type tail, size_t offset) const;
-
- // Fills the current (initialized) instance from the provided source, copying
- // entries [head, tail). Adds a reference to copied entries if `ref` is true.
- template <bool ref>
- void Fill(const CordRepRing* src, index_type head, index_type tail);
-
- // Create a copy of 'rep', copying all entries [head, tail), allocating room
- // for `extra` entries. Adds a reference on all copied entries.
- static CordRepRing* Copy(CordRepRing* rep, index_type head, index_type tail,
- size_t extra = 0);
-
- // Returns a Mutable CordRepRing reference from `rep` with room for at least
- // `extra` additional nodes. Adopts a reference count from `rep`.
- // This function will return `rep` if, and only if:
- // - rep.entries + extra <= rep.capacity
- // - rep.refcount == 1
- // Otherwise, this function will create a new copy of `rep` with additional
- // capacity to satisfy `extra` extra nodes, and unref the old `rep` instance.
- //
- // If a new CordRepRing can not be allocated, or the new capacity would exceed
- // the maxmimum capacity, then the input is consumed only, and an exception is
- // thrown.
- static CordRepRing* Mutable(CordRepRing* rep, size_t extra);
-
- // Slow path for Append(CordRepRing* rep, CordRep* child). This function is
- // exercised if the provided `child` in Append() is not a leaf node, i.e., a
- // ring buffer or old (concat) cord tree.
- static CordRepRing* AppendSlow(CordRepRing* rep, CordRep* child);
-
- // Appends the provided leaf node. Requires `child` to be FLAT or EXTERNAL.
- static CordRepRing* AppendLeaf(CordRepRing* rep, CordRep* child,
- size_t offset, size_t length);
-
- // Prepends the provided leaf node. Requires `child` to be FLAT or EXTERNAL.
- static CordRepRing* PrependLeaf(CordRepRing* rep, CordRep* child,
- size_t offset, size_t length);
-
- // Slow path for Prepend(CordRepRing* rep, CordRep* child). This function is
- // exercised if the provided `child` in Prepend() is not a leaf node, i.e., a
- // ring buffer or old (concat) cord tree.
- static CordRepRing* PrependSlow(CordRepRing* rep, CordRep* child);
-
- // Slow path for Create(CordRep* child, size_t extra). This function is
- // exercised if the provided `child` in Prepend() is not a leaf node, i.e., a
- // ring buffer or old (concat) cord tree.
- static CordRepRing* CreateSlow(CordRep* child, size_t extra);
-
- // Creates a new ring buffer from the provided `child` leaf node. Requires
- // `child` to be FLAT or EXTERNAL. on `rep`.
- // The returned ring buffer has a capacity of at least `1 + extra`
- static CordRepRing* CreateFromLeaf(CordRep* child, size_t offset,
- size_t length, size_t extra);
-
- // Appends or prepends (depending on AddMode) the ring buffer in `ring' to
- // `rep` starting at `offset` with length `len`.
- template <AddMode mode>
- static CordRepRing* AddRing(CordRepRing* rep, CordRepRing* ring,
- size_t offset, size_t len);
-
- // Increases the data offset for entry `index` by `n`.
- void AddDataOffset(index_type index, size_t n);
-
- // Descreases the length for entry `index` by `n`.
- void SubLength(index_type index, size_t n);
-
- index_type head_;
- index_type tail_;
- index_type capacity_;
- pos_type begin_pos_;
-
- alignas(kLayoutAlignment) char data_[kLayoutAlignment];
-
- friend struct CordRep;
-};
-
-constexpr size_t CordRepRing::AllocSize(size_t capacity) {
- return sizeof(CordRepRing) - sizeof(data_) +
- Layout(capacity, capacity, capacity).AllocSize();
-}
-
-inline constexpr size_t CordRepRing::Distance(pos_type pos, pos_type end_pos) {
- return (end_pos - pos);
-}
-
-inline const char* CordRepRing::GetLeafData(const CordRep* rep) {
- return rep->tag != EXTERNAL ? rep->flat()->Data() : rep->external()->base;
-}
-
-inline const char* CordRepRing::GetRepData(const CordRep* rep) {
- if (rep->tag >= FLAT) return rep->flat()->Data();
- if (rep->tag == EXTERNAL) return rep->external()->base;
- return GetLeafData(rep->substring()->child) + rep->substring()->start;
-}
-
-inline CordRepRing::index_type CordRepRing::advance(index_type index) const {
- assert(index < capacity_);
- return ++index == capacity_ ? 0 : index;
-}
-
-inline CordRepRing::index_type CordRepRing::advance(index_type index,
- index_type n) const {
- assert(index < capacity_ && n <= capacity_);
- return (index += n) >= capacity_ ? index - capacity_ : index;
-}
-
-inline CordRepRing::index_type CordRepRing::retreat(index_type index) const {
- assert(index < capacity_);
- return (index > 0 ? index : capacity_) - 1;
-}
-
-inline CordRepRing::index_type CordRepRing::retreat(index_type index,
- index_type n) const {
- assert(index < capacity_ && n <= capacity_);
- return index >= n ? index - n : capacity_ - n + index;
-}
-
-inline absl::string_view CordRepRing::entry_data(index_type index) const {
- size_t data_offset = entry_data_offset(index);
- return {GetRepData(entry_child(index)) + data_offset, entry_length(index)};
-}
-
-inline bool CordRepRing::IsValidIndex(index_type index) const {
- if (index >= capacity_) return false;
- return (tail_ > head_) ? (index >= head_ && index < tail_)
- : (index >= head_ || index < tail_);
-}
-
-#ifndef EXTRA_CORD_RING_VALIDATION
-inline CordRepRing* CordRepRing::Validate(CordRepRing* rep,
- const char* /*file*/, int /*line*/) {
- return rep;
-}
-#endif
-
-inline CordRepRing::Position CordRepRing::Find(size_t offset) const {
- assert(offset < length);
- return (offset == 0) ? Position{head_, 0} : FindSlow(head_, offset);
-}
-
-inline CordRepRing::Position CordRepRing::Find(index_type head,
- size_t offset) const {
- assert(offset < length);
- assert(IsValidIndex(head) && offset >= entry_start_offset(head));
- return (offset == 0) ? Position{head_, 0} : FindSlow(head, offset);
-}
-
-inline CordRepRing::Position CordRepRing::FindTail(size_t offset) const {
- assert(offset > 0 && offset <= length);
- return (offset == length) ? Position{tail_, 0} : FindTailSlow(head_, offset);
-}
-
-inline CordRepRing::Position CordRepRing::FindTail(index_type head,
- size_t offset) const {
- assert(offset > 0 && offset <= length);
- assert(IsValidIndex(head) && offset >= entry_start_offset(head) + 1);
- return (offset == length) ? Position{tail_, 0} : FindTailSlow(head, offset);
-}
-
-// Now that CordRepRing is defined, we can define CordRep's helper casts:
-inline CordRepRing* CordRep::ring() {
- assert(tag == RING);
- return static_cast<CordRepRing*>(this);
-}
-
-inline const CordRepRing* CordRep::ring() const {
- assert(tag == RING);
- return static_cast<const CordRepRing*>(this);
-}
-
-inline bool CordRepRing::IsFlat(absl::string_view* fragment) const {
- if (entries() == 1) {
- if (fragment) *fragment = entry_data(head());
- return true;
- }
- return false;
-}
-
-inline bool CordRepRing::IsFlat(size_t offset, size_t len,
- absl::string_view* fragment) const {
- const Position pos = Find(offset);
- const absl::string_view data = entry_data(pos.index);
- if (data.length() >= len && data.length() - len >= pos.offset) {
- if (fragment) *fragment = data.substr(pos.offset, len);
- return true;
- }
- return false;
-}
-
-std::ostream& operator<<(std::ostream& s, const CordRepRing& rep);
-
-} // namespace cord_internal
-ABSL_NAMESPACE_END
-} // namespace absl
-
-#endif // ABSL_STRINGS_INTERNAL_CORD_REP_RING_H_
diff --git a/absl/strings/internal/cord_rep_ring_reader.h b/absl/strings/internal/cord_rep_ring_reader.h
deleted file mode 100644
index 7ceeaa0..0000000
--- a/absl/strings/internal/cord_rep_ring_reader.h
+++ /dev/null
@@ -1,118 +0,0 @@
-// Copyright 2021 The Abseil Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef ABSL_STRINGS_INTERNAL_CORD_REP_RING_READER_H_
-#define ABSL_STRINGS_INTERNAL_CORD_REP_RING_READER_H_
-
-#include <cassert>
-#include <cstddef>
-#include <cstdint>
-
-#include "absl/strings/internal/cord_internal.h"
-#include "absl/strings/internal/cord_rep_ring.h"
-#include "absl/strings/string_view.h"
-
-namespace absl {
-ABSL_NAMESPACE_BEGIN
-namespace cord_internal {
-
-// CordRepRingReader provides basic navigation over CordRepRing data.
-class CordRepRingReader {
- public:
- // Returns true if this instance is not empty.
- explicit operator bool() const { return ring_ != nullptr; }
-
- // Returns the ring buffer reference for this instance, or nullptr if empty.
- CordRepRing* ring() const { return ring_; }
-
- // Returns the current node index inside the ring buffer for this instance.
- // The returned value is undefined if this instance is empty.
- CordRepRing::index_type index() const { return index_; }
-
- // Returns the current node inside the ring buffer for this instance.
- // The returned value is undefined if this instance is empty.
- CordRep* node() const { return ring_->entry_child(index_); }
-
- // Returns the length of the referenced ring buffer.
- // Requires the current instance to be non empty.
- size_t length() const {
- assert(ring_);
- return ring_->length;
- }
-
- // Returns the end offset of the last navigated-to chunk, which represents the
- // total bytes 'consumed' relative to the start of the ring. The returned
- // value is never zero. For example, initializing a reader with a ring buffer
- // with a first chunk of 19 bytes will return consumed() = 19.
- // Requires the current instance to be non empty.
- size_t consumed() const {
- assert(ring_);
- return ring_->entry_end_offset(index_);
- }
-
- // Returns the number of bytes remaining beyond the last navigated-to chunk.
- // Requires the current instance to be non empty.
- size_t remaining() const {
- assert(ring_);
- return length() - consumed();
- }
-
- // Resets this instance to an empty value
- void Reset() { ring_ = nullptr; }
-
- // Resets this instance to the start of `ring`. `ring` must not be null.
- // Returns a reference into the first chunk of the provided ring.
- absl::string_view Reset(CordRepRing* ring) {
- assert(ring);
- ring_ = ring;
- index_ = ring_->head();
- return ring_->entry_data(index_);
- }
-
- // Navigates to the next chunk inside the reference ring buffer.
- // Returns a reference into the navigated-to chunk.
- // Requires remaining() to be non zero.
- absl::string_view Next() {
- assert(remaining());
- index_ = ring_->advance(index_);
- return ring_->entry_data(index_);
- }
-
- // Navigates to the chunk at offset `offset`.
- // Returns a reference into the navigated-to chunk, adjusted for the relative
- // position of `offset` into that chunk. For example, calling Seek(13) on a
- // ring buffer containing 2 chunks of 10 and 20 bytes respectively will return
- // a string view into the second chunk starting at offset 3 with a size of 17.
- // Requires `offset` to be less than `length()`
- absl::string_view Seek(size_t offset) {
- assert(offset < length());
- size_t current = ring_->entry_end_offset(index_);
- CordRepRing::index_type hint = (offset >= current) ? index_ : ring_->head();
- const CordRepRing::Position head = ring_->Find(hint, offset);
- index_ = head.index;
- auto data = ring_->entry_data(head.index);
- data.remove_prefix(head.offset);
- return data;
- }
-
- private:
- CordRepRing* ring_ = nullptr;
- CordRepRing::index_type index_;
-};
-
-} // namespace cord_internal
-ABSL_NAMESPACE_END
-} // namespace absl
-
-#endif // ABSL_STRINGS_INTERNAL_CORD_REP_RING_READER_H_
diff --git a/absl/strings/internal/cord_rep_test_util.h b/absl/strings/internal/cord_rep_test_util.h
index bc50006..18a0a19 100644
--- a/absl/strings/internal/cord_rep_test_util.h
+++ b/absl/strings/internal/cord_rep_test_util.h
@@ -42,18 +42,6 @@
return sub;
}
-inline cord_internal::CordRepConcat* MakeConcat(cord_internal::CordRep* left,
- cord_internal::CordRep* right,
- int depth = 0) {
- auto* concat = new cord_internal::CordRepConcat;
- concat->tag = cord_internal::CONCAT;
- concat->length = left->length + right->length;
- concat->left = left;
- concat->right = right;
- concat->set_depth(depth);
- return concat;
-}
-
inline cord_internal::CordRepFlat* MakeFlat(absl::string_view value) {
assert(value.length() <= cord_internal::kMaxFlatLength);
auto* flat = cord_internal::CordRepFlat::New(value.length());
@@ -115,6 +103,38 @@
return node;
}
+template <typename Fn>
+inline void CordVisitReps(cord_internal::CordRep* rep, Fn&& fn) {
+ fn(rep);
+ while (rep->tag == cord_internal::SUBSTRING) {
+ rep = rep->substring()->child;
+ fn(rep);
+ }
+ if (rep->tag == cord_internal::BTREE) {
+ for (cord_internal::CordRep* edge : rep->btree()->Edges()) {
+ CordVisitReps(edge, fn);
+ }
+ }
+}
+
+template <typename Predicate>
+inline std::vector<cord_internal::CordRep*> CordCollectRepsIf(
+ Predicate&& predicate, cord_internal::CordRep* rep) {
+ std::vector<cord_internal::CordRep*> reps;
+ CordVisitReps(rep, [&reps, &predicate](cord_internal::CordRep* rep) {
+ if (predicate(rep)) reps.push_back(rep);
+ });
+ return reps;
+}
+
+inline std::vector<cord_internal::CordRep*> CordCollectReps(
+ cord_internal::CordRep* rep) {
+ std::vector<cord_internal::CordRep*> reps;
+ auto fn = [&reps](cord_internal::CordRep* rep) { reps.push_back(rep); };
+ CordVisitReps(rep, fn);
+ return reps;
+}
+
inline void CordToString(cord_internal::CordRep* rep, std::string& s) {
size_t offset = 0;
size_t length = rep->length;
diff --git a/absl/strings/internal/cordz_functions.cc b/absl/strings/internal/cordz_functions.cc
index 4836993..20d314f 100644
--- a/absl/strings/internal/cordz_functions.cc
+++ b/absl/strings/internal/cordz_functions.cc
@@ -21,8 +21,8 @@
#include "absl/base/attributes.h"
#include "absl/base/config.h"
-#include "absl/base/internal/exponential_biased.h"
#include "absl/base/internal/raw_logging.h"
+#include "absl/profiling/internal/exponential_biased.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
@@ -48,7 +48,7 @@
ABSL_ATTRIBUTE_NOINLINE bool cordz_should_profile_slow() {
- thread_local absl::base_internal::ExponentialBiased
+ thread_local absl::profiling_internal::ExponentialBiased
exponential_biased_generator;
int32_t mean_interval = get_cordz_mean_interval();
diff --git a/absl/strings/internal/cordz_functions.h b/absl/strings/internal/cordz_functions.h
index c9ba145..ed108bf 100644
--- a/absl/strings/internal/cordz_functions.h
+++ b/absl/strings/internal/cordz_functions.h
@@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#ifndef ABSL_STRINGS_CORDZ_FUNCTIONS_H_
-#define ABSL_STRINGS_CORDZ_FUNCTIONS_H_
+#ifndef ABSL_STRINGS_INTERNAL_CORDZ_FUNCTIONS_H_
+#define ABSL_STRINGS_INTERNAL_CORDZ_FUNCTIONS_H_
#include <stdint.h>
@@ -32,18 +32,10 @@
// Sets the sample rate with the average interval between samples.
void set_cordz_mean_interval(int32_t mean_interval);
-// Enable cordz unless any of the following applies:
-// - no thread local support
-// - MSVC build
-// - Android build
-// - Apple build
-// - DLL build
-// Hashtablez is turned off completely in opensource builds.
-// MSVC's static atomics are dynamically initialized in debug mode, which breaks
-// sampling.
-#if defined(ABSL_HAVE_THREAD_LOCAL) && !defined(_MSC_VER) && \
- !defined(ABSL_BUILD_DLL) && !defined(ABSL_CONSUME_DLL) && \
- !defined(__ANDROID__) && !defined(__APPLE__)
+// Cordz is only enabled on Linux with thread_local support.
+#if defined(ABSL_INTERNAL_CORDZ_ENABLED)
+#error ABSL_INTERNAL_CORDZ_ENABLED cannot be set directly
+#elif defined(__linux__) && defined(ABSL_HAVE_THREAD_LOCAL)
#define ABSL_INTERNAL_CORDZ_ENABLED 1
#endif
@@ -82,4 +74,4 @@
ABSL_NAMESPACE_END
} // namespace absl
-#endif // ABSL_STRINGS_CORDZ_FUNCTIONS_H_
+#endif // ABSL_STRINGS_INTERNAL_CORDZ_FUNCTIONS_H_
diff --git a/absl/strings/internal/cordz_functions_test.cc b/absl/strings/internal/cordz_functions_test.cc
index 350623c..b70a685 100644
--- a/absl/strings/internal/cordz_functions_test.cc
+++ b/absl/strings/internal/cordz_functions_test.cc
@@ -38,7 +38,7 @@
}
// Cordz is disabled when we don't have thread_local. All calls to
-// should_profile will return false when cordz is diabled, so we might want to
+// should_profile will return false when cordz is disabled, so we might want to
// avoid those tests.
#ifdef ABSL_INTERNAL_CORDZ_ENABLED
diff --git a/absl/strings/internal/cordz_handle.cc b/absl/strings/internal/cordz_handle.cc
index a73fefe..53d5f52 100644
--- a/absl/strings/internal/cordz_handle.cc
+++ b/absl/strings/internal/cordz_handle.cc
@@ -16,34 +16,60 @@
#include <atomic>
#include "absl/base/internal/raw_logging.h" // For ABSL_RAW_CHECK
-#include "absl/base/internal/spinlock.h"
+#include "absl/base/no_destructor.h"
+#include "absl/synchronization/mutex.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace cord_internal {
-using ::absl::base_internal::SpinLockHolder;
+namespace {
-ABSL_CONST_INIT CordzHandle::Queue CordzHandle::global_queue_(absl::kConstInit);
+struct Queue {
+ Queue() = default;
+
+ absl::Mutex mutex;
+ std::atomic<CordzHandle*> dq_tail ABSL_GUARDED_BY(mutex){nullptr};
+
+ // Returns true if this delete queue is empty. This method does not acquire
+ // the lock, but does a 'load acquire' observation on the delete queue tail.
+ // It is used inside Delete() to check for the presence of a delete queue
+ // without holding the lock. The assumption is that the caller is in the
+ // state of 'being deleted', and can not be newly discovered by a concurrent
+ // 'being constructed' snapshot instance. Practically, this means that any
+ // such discovery (`find`, 'first' or 'next', etc) must have proper 'happens
+ // before / after' semantics and atomic fences.
+ bool IsEmpty() const ABSL_NO_THREAD_SAFETY_ANALYSIS {
+ return dq_tail.load(std::memory_order_acquire) == nullptr;
+ }
+};
+
+static Queue& GlobalQueue() {
+ static absl::NoDestructor<Queue> global_queue;
+ return *global_queue;
+}
+
+} // namespace
CordzHandle::CordzHandle(bool is_snapshot) : is_snapshot_(is_snapshot) {
+ Queue& global_queue = GlobalQueue();
if (is_snapshot) {
- SpinLockHolder lock(&queue_->mutex);
- CordzHandle* dq_tail = queue_->dq_tail.load(std::memory_order_acquire);
+ MutexLock lock(&global_queue.mutex);
+ CordzHandle* dq_tail = global_queue.dq_tail.load(std::memory_order_acquire);
if (dq_tail != nullptr) {
dq_prev_ = dq_tail;
dq_tail->dq_next_ = this;
}
- queue_->dq_tail.store(this, std::memory_order_release);
+ global_queue.dq_tail.store(this, std::memory_order_release);
}
}
CordzHandle::~CordzHandle() {
- ODRCheck();
+ Queue& global_queue = GlobalQueue();
if (is_snapshot_) {
std::vector<CordzHandle*> to_delete;
{
- SpinLockHolder lock(&queue_->mutex);
+ MutexLock lock(&global_queue.mutex);
CordzHandle* next = dq_next_;
if (dq_prev_ == nullptr) {
// We were head of the queue, delete every CordzHandle until we reach
@@ -59,7 +85,7 @@
if (next) {
next->dq_prev_ = dq_prev_;
} else {
- queue_->dq_tail.store(dq_prev_, std::memory_order_release);
+ global_queue.dq_tail.store(dq_prev_, std::memory_order_release);
}
}
for (CordzHandle* handle : to_delete) {
@@ -69,21 +95,20 @@
}
bool CordzHandle::SafeToDelete() const {
- return is_snapshot_ || queue_->IsEmpty();
+ return is_snapshot_ || GlobalQueue().IsEmpty();
}
void CordzHandle::Delete(CordzHandle* handle) {
assert(handle);
if (handle) {
- handle->ODRCheck();
- Queue* const queue = handle->queue_;
+ Queue& queue = GlobalQueue();
if (!handle->SafeToDelete()) {
- SpinLockHolder lock(&queue->mutex);
- CordzHandle* dq_tail = queue->dq_tail.load(std::memory_order_acquire);
+ MutexLock lock(&queue.mutex);
+ CordzHandle* dq_tail = queue.dq_tail.load(std::memory_order_acquire);
if (dq_tail != nullptr) {
handle->dq_prev_ = dq_tail;
dq_tail->dq_next_ = handle;
- queue->dq_tail.store(handle, std::memory_order_release);
+ queue.dq_tail.store(handle, std::memory_order_release);
return;
}
}
@@ -93,8 +118,9 @@
std::vector<const CordzHandle*> CordzHandle::DiagnosticsGetDeleteQueue() {
std::vector<const CordzHandle*> handles;
- SpinLockHolder lock(&global_queue_.mutex);
- CordzHandle* dq_tail = global_queue_.dq_tail.load(std::memory_order_acquire);
+ Queue& global_queue = GlobalQueue();
+ MutexLock lock(&global_queue.mutex);
+ CordzHandle* dq_tail = global_queue.dq_tail.load(std::memory_order_acquire);
for (const CordzHandle* p = dq_tail; p; p = p->dq_prev_) {
handles.push_back(p);
}
@@ -103,13 +129,13 @@
bool CordzHandle::DiagnosticsHandleIsSafeToInspect(
const CordzHandle* handle) const {
- ODRCheck();
if (!is_snapshot_) return false;
if (handle == nullptr) return true;
if (handle->is_snapshot_) return false;
bool snapshot_found = false;
- SpinLockHolder lock(&queue_->mutex);
- for (const CordzHandle* p = queue_->dq_tail; p; p = p->dq_prev_) {
+ Queue& global_queue = GlobalQueue();
+ MutexLock lock(&global_queue.mutex);
+ for (const CordzHandle* p = global_queue.dq_tail; p; p = p->dq_prev_) {
if (p == handle) return !snapshot_found;
if (p == this) snapshot_found = true;
}
@@ -119,13 +145,13 @@
std::vector<const CordzHandle*>
CordzHandle::DiagnosticsGetSafeToInspectDeletedHandles() {
- ODRCheck();
std::vector<const CordzHandle*> handles;
if (!is_snapshot()) {
return handles;
}
- SpinLockHolder lock(&queue_->mutex);
+ Queue& global_queue = GlobalQueue();
+ MutexLock lock(&global_queue.mutex);
for (const CordzHandle* p = dq_next_; p != nullptr; p = p->dq_next_) {
if (!p->is_snapshot()) {
handles.push_back(p);
diff --git a/absl/strings/internal/cordz_handle.h b/absl/strings/internal/cordz_handle.h
index 5df53c7..08e3f0d 100644
--- a/absl/strings/internal/cordz_handle.h
+++ b/absl/strings/internal/cordz_handle.h
@@ -12,16 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#ifndef ABSL_STRINGS_CORDZ_HANDLE_H_
-#define ABSL_STRINGS_CORDZ_HANDLE_H_
+#ifndef ABSL_STRINGS_INTERNAL_CORDZ_HANDLE_H_
+#define ABSL_STRINGS_INTERNAL_CORDZ_HANDLE_H_
#include <atomic>
#include <vector>
#include "absl/base/config.h"
#include "absl/base/internal/raw_logging.h"
-#include "absl/base/internal/spinlock.h"
-#include "absl/synchronization/mutex.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
@@ -34,7 +32,7 @@
// has gained visibility into a CordzInfo object, that CordzInfo object will not
// be deleted prematurely. This allows the profiler to inspect all CordzInfo
// objects that are alive without needing to hold a global lock.
-class CordzHandle {
+class ABSL_DLL CordzHandle {
public:
CordzHandle() : CordzHandle(false) {}
@@ -79,37 +77,6 @@
virtual ~CordzHandle();
private:
- // Global queue data. CordzHandle stores a pointer to the global queue
- // instance to harden against ODR violations.
- struct Queue {
- constexpr explicit Queue(absl::ConstInitType)
- : mutex(absl::kConstInit,
- absl::base_internal::SCHEDULE_COOPERATIVE_AND_KERNEL) {}
-
- absl::base_internal::SpinLock mutex;
- std::atomic<CordzHandle*> dq_tail ABSL_GUARDED_BY(mutex){nullptr};
-
- // Returns true if this delete queue is empty. This method does not acquire
- // the lock, but does a 'load acquire' observation on the delete queue tail.
- // It is used inside Delete() to check for the presence of a delete queue
- // without holding the lock. The assumption is that the caller is in the
- // state of 'being deleted', and can not be newly discovered by a concurrent
- // 'being constructed' snapshot instance. Practically, this means that any
- // such discovery (`find`, 'first' or 'next', etc) must have proper 'happens
- // before / after' semantics and atomic fences.
- bool IsEmpty() const ABSL_NO_THREAD_SAFETY_ANALYSIS {
- return dq_tail.load(std::memory_order_acquire) == nullptr;
- }
- };
-
- void ODRCheck() const {
-#ifndef NDEBUG
- ABSL_RAW_CHECK(queue_ == &global_queue_, "ODR violation in Cord");
-#endif
- }
-
- ABSL_CONST_INIT static Queue global_queue_;
- Queue* const queue_ = &global_queue_;
const bool is_snapshot_;
// dq_prev_ and dq_next_ require the global queue mutex to be held.
@@ -128,4 +95,4 @@
ABSL_NAMESPACE_END
} // namespace absl
-#endif // ABSL_STRINGS_CORDZ_HANDLE_H_
+#endif // ABSL_STRINGS_INTERNAL_CORDZ_HANDLE_H_
diff --git a/absl/strings/internal/cordz_info.cc b/absl/strings/internal/cordz_info.cc
index a3a0b9c..b24c3da 100644
--- a/absl/strings/internal/cordz_info.cc
+++ b/absl/strings/internal/cordz_info.cc
@@ -19,20 +19,22 @@
#include "absl/container/inlined_vector.h"
#include "absl/debugging/stacktrace.h"
#include "absl/strings/internal/cord_internal.h"
-#include "absl/strings/internal/cord_rep_ring.h"
+#include "absl/strings/internal/cord_rep_btree.h"
+#include "absl/strings/internal/cord_rep_crc.h"
#include "absl/strings/internal/cordz_handle.h"
#include "absl/strings/internal/cordz_statistics.h"
#include "absl/strings/internal/cordz_update_tracker.h"
#include "absl/synchronization/mutex.h"
+#include "absl/time/clock.h"
#include "absl/types/span.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace cord_internal {
-using ::absl::base_internal::SpinLockHolder;
-
-constexpr int CordzInfo::kMaxStackDepth;
+#ifdef ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL
+constexpr size_t CordzInfo::kMaxStackDepth;
+#endif
ABSL_CONST_INIT CordzInfo::List CordzInfo::global_list_{absl::kConstInit};
@@ -49,7 +51,7 @@
// The top level node is treated specially: we assume the current thread
// (typically called from the CordzHandler) to hold a reference purely to
// perform a safe analysis, and not being part of the application. So we
-// substract 1 from the reference count of the top node to compute the
+// subtract 1 from the reference count of the top node to compute the
// 'application fair share' excluding the reference of the current thread.
//
// An example of fair sharing, and why we multiply reference counts:
@@ -74,28 +76,39 @@
// adds the results to `statistics`. Note that node counts and memory sizes
// are not initialized, computed values are added to any existing values.
void AnalyzeCordRep(const CordRep* rep) {
+ ABSL_ASSERT(rep != nullptr);
+
// Process all linear nodes.
// As per the class comments, use refcout - 1 on the top level node, as the
// top level node is assumed to be referenced only for analysis purposes.
size_t refcount = rep->refcount.Get();
RepRef repref{rep, (refcount > 1) ? refcount - 1 : 1};
+ // Process the top level CRC node, if present.
+ if (repref.tag() == CRC) {
+ statistics_.node_count++;
+ statistics_.node_counts.crc++;
+ memory_usage_.Add(sizeof(CordRepCrc), repref.refcount);
+ repref = repref.Child(repref.rep->crc()->child);
+ }
+
// Process all top level linear nodes (substrings and flats).
repref = CountLinearReps(repref, memory_usage_);
- // We should have have either a concat or ring node node if not null.
- if (repref.rep != nullptr) {
- assert(repref.rep->tag == RING || repref.rep->tag == CONCAT);
- if (repref.rep->tag == RING) {
- AnalyzeRing(repref);
- } else if (repref.rep->tag == CONCAT) {
- AnalyzeConcat(repref);
- }
+ switch (repref.tag()) {
+ case CordRepKind::BTREE:
+ AnalyzeBtree(repref);
+ break;
+ default:
+ // We should have a btree node if not null.
+ ABSL_ASSERT(repref.tag() == CordRepKind::UNUSED_0);
+ break;
}
// Adds values to output
statistics_.estimated_memory_usage += memory_usage_.total;
- statistics_.estimated_fair_share_memory_usage += memory_usage_.fair_share;
+ statistics_.estimated_fair_share_memory_usage +=
+ static_cast<size_t>(memory_usage_.fair_share);
}
private:
@@ -107,34 +120,34 @@
const CordRep* rep;
size_t refcount;
- // Returns a 'child' RepRef which contains the cumulative reference count of
- // this instance multiplied by the child's reference count.
+ // Returns a 'child' RepRef which contains the cumulative reference count
+ // of this instance multiplied by the child's reference count. Returns a
+ // nullptr RepRef value with a refcount of 0 if `child` is nullptr.
RepRef Child(const CordRep* child) const {
+ if (child == nullptr) return RepRef{nullptr, 0};
return RepRef{child, refcount * child->refcount.Get()};
}
+
+ // Returns the tag of this rep, or UNUSED_0 if this instance is null
+ constexpr CordRepKind tag() const {
+ ABSL_ASSERT(rep == nullptr || rep->tag != CordRepKind::UNUSED_0);
+ return rep ? static_cast<CordRepKind>(rep->tag) : CordRepKind::UNUSED_0;
+ }
};
// Memory usage values
struct MemoryUsage {
size_t total = 0;
- size_t fair_share = 0;
+ double fair_share = 0.0;
// Adds 'size` memory usage to this class, with a cumulative (recursive)
// reference count of `refcount`
void Add(size_t size, size_t refcount) {
total += size;
- fair_share += size / refcount;
+ fair_share += static_cast<double>(size) / refcount;
}
};
- // Returns `rr` if `rr.rep` is not null and a CONCAT type.
- // Asserts that `rr.rep` is a concat node or null.
- static RepRef AssertConcat(RepRef repref) {
- const CordRep* rep = repref.rep;
- assert(rep == nullptr || rep->tag == CONCAT);
- return (rep != nullptr && rep->tag == CONCAT) ? repref : RepRef{nullptr, 0};
- }
-
// Counts a flat of the provide allocated size
void CountFlat(size_t size) {
statistics_.node_count++;
@@ -160,7 +173,7 @@
// buffers where we count children unrounded.
RepRef CountLinearReps(RepRef rep, MemoryUsage& memory_usage) {
// Consume all substrings
- while (rep.rep->tag == SUBSTRING) {
+ while (rep.tag() == SUBSTRING) {
statistics_.node_count++;
statistics_.node_counts.substring++;
memory_usage.Add(sizeof(CordRepSubstring), rep.refcount);
@@ -168,7 +181,7 @@
}
// Consume possible FLAT
- if (rep.rep->tag >= FLAT) {
+ if (rep.tag() >= FLAT) {
size_t size = rep.rep->flat()->AllocatedSize();
CountFlat(size);
memory_usage.Add(size, rep.refcount);
@@ -176,7 +189,7 @@
}
// Consume possible external
- if (rep.rep->tag == EXTERNAL) {
+ if (rep.tag() == EXTERNAL) {
statistics_.node_count++;
statistics_.node_counts.external++;
size_t size = rep.rep->length + sizeof(CordRepExternalImpl<intptr_t>);
@@ -187,58 +200,23 @@
return rep;
}
- // Analyzes the provided concat node in a flattened recursive way.
- void AnalyzeConcat(RepRef rep) {
- absl::InlinedVector<RepRef, 47> pending;
-
- while (rep.rep != nullptr) {
- const CordRepConcat* concat = rep.rep->concat();
- RepRef left = rep.Child(concat->left);
- RepRef right = rep.Child(concat->right);
-
- statistics_.node_count++;
- statistics_.node_counts.concat++;
- memory_usage_.Add(sizeof(CordRepConcat), rep.refcount);
-
- right = AssertConcat(CountLinearReps(right, memory_usage_));
- rep = AssertConcat(CountLinearReps(left, memory_usage_));
- if (rep.rep != nullptr) {
- if (right.rep != nullptr) {
- pending.push_back(right);
- }
- } else if (right.rep != nullptr) {
- rep = right;
- } else if (!pending.empty()) {
- rep = pending.back();
- pending.pop_back();
+ // Analyzes the provided btree.
+ void AnalyzeBtree(RepRef rep) {
+ statistics_.node_count++;
+ statistics_.node_counts.btree++;
+ memory_usage_.Add(sizeof(CordRepBtree), rep.refcount);
+ const CordRepBtree* tree = rep.rep->btree();
+ if (tree->height() > 0) {
+ for (CordRep* edge : tree->Edges()) {
+ AnalyzeBtree(rep.Child(edge));
+ }
+ } else {
+ for (CordRep* edge : tree->Edges()) {
+ CountLinearReps(rep.Child(edge), memory_usage_);
}
}
}
- // Counts the provided ring buffer child into `child_usage`.
- void CountRingChild(const CordRep* child, MemoryUsage& child_usage) {
- RepRef rep{child, static_cast<size_t>(child->refcount.Get())};
- rep = CountLinearReps(rep, child_usage);
- assert(rep.rep == nullptr);
- }
-
- // Analyzes the provided ring. As ring buffers can have many child nodes, the
- // effect of rounding errors can become non trivial, so we compute the totals
- // first at the ring level, and then divide the fair share of the total
- // including children fair share totals.
- void AnalyzeRing(RepRef rep) {
- statistics_.node_count++;
- statistics_.node_counts.ring++;
- MemoryUsage ring_usage;
- const CordRepRing* ring = rep.rep->ring();
- ring_usage.Add(CordRepRing::AllocSize(ring->capacity()), 1);
- ring->ForEach([&](CordRepRing::index_type pos) {
- CountRingChild(ring->entry_child(pos), ring_usage);
- });
- memory_usage_.total += ring_usage.total;
- memory_usage_.fair_share += ring_usage.fair_share / rep.refcount;
- }
-
CordzStatistics& statistics_;
MemoryUsage memory_usage_;
};
@@ -309,7 +287,7 @@
: src->method_;
}
-int CordzInfo::FillParentStack(const CordzInfo* src, void** stack) {
+size_t CordzInfo::FillParentStack(const CordzInfo* src, void** stack) {
assert(stack);
if (src == nullptr) return 0;
if (src->parent_stack_depth_) {
@@ -320,11 +298,14 @@
return src->stack_depth_;
}
-CordzInfo::CordzInfo(CordRep* rep, const CordzInfo* src,
+CordzInfo::CordzInfo(CordRep* rep,
+ const CordzInfo* src,
MethodIdentifier method)
: rep_(rep),
- stack_depth_(absl::GetStackTrace(stack_, /*max_depth=*/kMaxStackDepth,
- /*skip_count=*/1)),
+ stack_depth_(
+ static_cast<size_t>(absl::GetStackTrace(stack_,
+ /*max_depth=*/kMaxStackDepth,
+ /*skip_count=*/1))),
parent_stack_depth_(FillParentStack(src, parent_stack_)),
method_(method),
parent_method_(GetParentMethod(src)),
diff --git a/absl/strings/internal/cordz_info.h b/absl/strings/internal/cordz_info.h
index 026d5b9..17eaa91 100644
--- a/absl/strings/internal/cordz_info.h
+++ b/absl/strings/internal/cordz_info.h
@@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#ifndef ABSL_STRINGS_CORDZ_INFO_H_
-#define ABSL_STRINGS_CORDZ_INFO_H_
+#ifndef ABSL_STRINGS_INTERNAL_CORDZ_INFO_H_
+#define ABSL_STRINGS_INTERNAL_CORDZ_INFO_H_
#include <atomic>
#include <cstdint>
@@ -196,7 +196,7 @@
std::atomic<CordzInfo*> head ABSL_GUARDED_BY(mutex){nullptr};
};
- static constexpr int kMaxStackDepth = 64;
+ static constexpr size_t kMaxStackDepth = 64;
explicit CordzInfo(CordRep* rep, const CordzInfo* src,
MethodIdentifier method);
@@ -216,7 +216,7 @@
// `stack_` depending on `parent_stack_` being empty, returning the size of
// the parent stack.
// Returns 0 if `src` is null.
- static int FillParentStack(const CordzInfo* src, void** stack);
+ static size_t FillParentStack(const CordzInfo* src, void** stack);
void ODRCheck() const {
#ifndef NDEBUG
@@ -244,8 +244,8 @@
void* stack_[kMaxStackDepth];
void* parent_stack_[kMaxStackDepth];
- const int stack_depth_;
- const int parent_stack_depth_;
+ const size_t stack_depth_;
+ const size_t parent_stack_depth_;
const MethodIdentifier method_;
const MethodIdentifier parent_method_;
CordzUpdateTracker update_tracker_;
@@ -295,4 +295,4 @@
ABSL_NAMESPACE_END
} // namespace absl
-#endif // ABSL_STRINGS_CORDZ_INFO_H_
+#endif // ABSL_STRINGS_INTERNAL_CORDZ_INFO_H_
diff --git a/absl/strings/internal/cordz_info_statistics_test.cc b/absl/strings/internal/cordz_info_statistics_test.cc
index 9f2842d..d55773f 100644
--- a/absl/strings/internal/cordz_info_statistics_test.cc
+++ b/absl/strings/internal/cordz_info_statistics_test.cc
@@ -19,10 +19,12 @@
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/base/config.h"
+#include "absl/crc/internal/crc_cord_state.h"
#include "absl/strings/cord.h"
#include "absl/strings/internal/cord_internal.h"
+#include "absl/strings/internal/cord_rep_btree.h"
+#include "absl/strings/internal/cord_rep_crc.h"
#include "absl/strings/internal/cord_rep_flat.h"
-#include "absl/strings/internal/cord_rep_ring.h"
#include "absl/strings/internal/cordz_info.h"
#include "absl/strings/internal/cordz_sample_token.h"
#include "absl/strings/internal/cordz_statistics.h"
@@ -42,6 +44,8 @@
namespace {
+using ::testing::Ge;
+
// Creates a flat of the specified allocated size
CordRepFlat* Flat(size_t size) {
// Round up to a tag size, as we are going to poke an exact tag size back into
@@ -58,7 +62,7 @@
}
// Creates an external of the specified length
-CordRepExternal* External(int length = 512) {
+CordRepExternal* External(size_t length = 512) {
return static_cast<CordRepExternal*>(
NewExternalRep(absl::string_view("", length), [](absl::string_view) {}));
}
@@ -72,16 +76,6 @@
return substring;
}
-// Creates a concat on the provided reps
-CordRepConcat* Concat(CordRep* left, CordRep* right) {
- auto* concat = new CordRepConcat;
- concat->length = left->length + right->length;
- concat->tag = CONCAT;
- concat->left = left;
- concat->right = right;
- return concat;
-}
-
// Reference count helper
struct RefHelper {
std::vector<CordRep*> refs;
@@ -128,14 +122,9 @@
return sizeof(CordRepExternalImpl<intptr_t>) + rep->length;
}
-template <>
-size_t SizeOf(const CordRepRing* rep) {
- return CordRepRing::AllocSize(rep->capacity());
-}
-
// Computes fair share memory used in a naive 'we dare to recurse' way.
-size_t FairShare(CordRep* rep, size_t ref = 1) {
- size_t self = 0, children = 0;
+double FairShareImpl(CordRep* rep, size_t ref) {
+ double self = 0.0, children = 0.0;
ref *= rep->refcount.Get();
if (rep->tag >= FLAT) {
self = SizeOf(rep->flat());
@@ -143,22 +132,23 @@
self = SizeOf(rep->external());
} else if (rep->tag == SUBSTRING) {
self = SizeOf(rep->substring());
- children = FairShare(rep->substring()->child, ref);
- } else if (rep->tag == RING) {
- self = SizeOf(rep->ring());
- rep->ring()->ForEach([&](CordRepRing::index_type i) {
- self += FairShare(rep->ring()->entry_child(i));
- });
- } else if (rep->tag == CONCAT) {
- self = SizeOf(rep->concat());
- children = FairShare(rep->concat()->left, ref) +
- FairShare(rep->concat()->right, ref);
+ children = FairShareImpl(rep->substring()->child, ref);
+ } else if (rep->tag == BTREE) {
+ self = SizeOf(rep->btree());
+ for (CordRep*edge : rep->btree()->Edges()) {
+ children += FairShareImpl(edge, ref);
+ }
} else {
assert(false);
}
return self / ref + children;
}
+// Returns the fair share memory size from `ShareFhareImpl()` as a size_t.
+size_t FairShare(CordRep* rep, size_t ref = 1) {
+ return static_cast<size_t>(FairShareImpl(rep, ref));
+}
+
// Samples the cord and returns CordzInfo::GetStatistics()
CordzStatistics SampleCord(CordRep* rep) {
InlineData cord(rep);
@@ -191,6 +181,7 @@
STATS_MATCHER_EXPECT_EQ(node_counts.concat);
STATS_MATCHER_EXPECT_EQ(node_counts.substring);
STATS_MATCHER_EXPECT_EQ(node_counts.ring);
+ STATS_MATCHER_EXPECT_EQ(node_counts.btree);
STATS_MATCHER_EXPECT_EQ(estimated_memory_usage);
STATS_MATCHER_EXPECT_EQ(estimated_fair_share_memory_usage);
@@ -292,136 +283,132 @@
EXPECT_THAT(SampleCord(substring), EqStatistics(expected));
}
-TEST(CordzInfoStatisticsTest, Concat) {
+TEST(CordzInfoStatisticsTest, BtreeLeaf) {
+ ASSERT_THAT(CordRepBtree::kMaxCapacity, Ge(3u));
RefHelper ref;
- auto* flat1 = Flat(300);
- auto* flat2 = Flat(2000);
- auto* concat = ref.NeedsUnref(Concat(flat1, flat2));
-
- CordzStatistics expected;
- expected.size = concat->length;
- expected.estimated_memory_usage =
- SizeOf(concat) + SizeOf(flat1) + SizeOf(flat2);
- expected.estimated_fair_share_memory_usage = expected.estimated_memory_usage;
- expected.node_count = 3;
- expected.node_counts.flat = 2;
- expected.node_counts.flat_512 = 1;
- expected.node_counts.concat = 1;
-
- EXPECT_THAT(SampleCord(concat), EqStatistics(expected));
-}
-
-TEST(CordzInfoStatisticsTest, DeepConcat) {
- RefHelper ref;
- auto* flat1 = Flat(300);
- auto* flat2 = Flat(2000);
- auto* flat3 = Flat(400);
+ auto* flat1 = Flat(2000);
+ auto* flat2 = Flat(200);
+ auto* substr = Substring(flat2);
auto* external = External(3000);
- auto* substring = Substring(external);
- auto* concat1 = Concat(flat1, flat2);
- auto* concat2 = Concat(flat3, substring);
- auto* concat = ref.NeedsUnref(Concat(concat1, concat2));
+
+ CordRepBtree* tree = CordRepBtree::Create(flat1);
+ tree = CordRepBtree::Append(tree, substr);
+ tree = CordRepBtree::Append(tree, external);
+ size_t flat3_count = CordRepBtree::kMaxCapacity - 3;
+ size_t flat3_size = 0;
+ for (size_t i = 0; i < flat3_count; ++i) {
+ auto* flat3 = Flat(70);
+ flat3_size += SizeOf(flat3);
+ tree = CordRepBtree::Append(tree, flat3);
+ }
+ ref.NeedsUnref(tree);
CordzStatistics expected;
- expected.size = concat->length;
- expected.estimated_memory_usage = SizeOf(concat) * 3 + SizeOf(flat1) +
- SizeOf(flat2) + SizeOf(flat3) +
- SizeOf(external) + SizeOf(substring);
+ expected.size = tree->length;
+ expected.estimated_memory_usage = SizeOf(tree) + SizeOf(flat1) +
+ SizeOf(flat2) + SizeOf(substr) +
+ flat3_size + SizeOf(external);
expected.estimated_fair_share_memory_usage = expected.estimated_memory_usage;
-
- expected.node_count = 8;
- expected.node_counts.flat = 3;
- expected.node_counts.flat_512 = 2;
- expected.node_counts.external = 1;
- expected.node_counts.concat = 3;
- expected.node_counts.substring = 1;
-
- EXPECT_THAT(SampleCord(concat), EqStatistics(expected));
-}
-
-TEST(CordzInfoStatisticsTest, DeepSharedConcat) {
- RefHelper ref;
- auto* flat1 = Flat(40);
- auto* flat2 = ref.Ref(Flat(2000), 4);
- auto* flat3 = Flat(70);
- auto* external = ref.Ref(External(3000));
- auto* substring = ref.Ref(Substring(external), 3);
- auto* concat1 = Concat(flat1, flat2);
- auto* concat2 = Concat(flat3, substring);
- auto* concat = ref.Ref(ref.NeedsUnref(Concat(concat1, concat2)));
-
- CordzStatistics expected;
- expected.size = concat->length;
- expected.estimated_memory_usage = SizeOf(concat) * 3 + SizeOf(flat1) +
- SizeOf(flat2) + SizeOf(flat3) +
- SizeOf(external) + SizeOf(substring);
- expected.estimated_fair_share_memory_usage = FairShare(concat);
- expected.node_count = 8;
- expected.node_counts.flat = 3;
- expected.node_counts.flat_64 = 1;
- expected.node_counts.flat_128 = 1;
- expected.node_counts.external = 1;
- expected.node_counts.concat = 3;
- expected.node_counts.substring = 1;
-
- EXPECT_THAT(SampleCord(concat), EqStatistics(expected));
-}
-
-TEST(CordzInfoStatisticsTest, Ring) {
- RefHelper ref;
- auto* flat1 = Flat(240);
- auto* flat2 = Flat(2000);
- auto* flat3 = Flat(70);
- auto* external = External(3000);
- CordRepRing* ring = CordRepRing::Create(flat1);
- ring = CordRepRing::Append(ring, flat2);
- ring = CordRepRing::Append(ring, flat3);
- ring = ref.NeedsUnref(CordRepRing::Append(ring, external));
-
- CordzStatistics expected;
- expected.size = ring->length;
- expected.estimated_memory_usage = SizeOf(ring) + SizeOf(flat1) +
- SizeOf(flat2) + SizeOf(flat3) +
- SizeOf(external);
- expected.estimated_fair_share_memory_usage = expected.estimated_memory_usage;
- expected.node_count = 5;
- expected.node_counts.flat = 3;
- expected.node_counts.flat_128 = 1;
+ expected.node_count = 1 + 3 + 1 + flat3_count;
+ expected.node_counts.flat = 2 + flat3_count;
+ expected.node_counts.flat_128 = flat3_count;
expected.node_counts.flat_256 = 1;
expected.node_counts.external = 1;
- expected.node_counts.ring = 1;
+ expected.node_counts.substring = 1;
+ expected.node_counts.btree = 1;
- EXPECT_THAT(SampleCord(ring), EqStatistics(expected));
+ EXPECT_THAT(SampleCord(tree), EqStatistics(expected));
}
-TEST(CordzInfoStatisticsTest, SharedSubstringRing) {
+TEST(CordzInfoStatisticsTest, BtreeNodeShared) {
RefHelper ref;
- auto* flat1 = ref.Ref(Flat(240));
- auto* flat2 = Flat(200);
- auto* flat3 = Flat(70);
- auto* external = ref.Ref(External(3000), 5);
- CordRepRing* ring = CordRepRing::Create(flat1);
- ring = CordRepRing::Append(ring, flat2);
- ring = CordRepRing::Append(ring, flat3);
- ring = ref.Ref(CordRepRing::Append(ring, external), 4);
- auto* substring = ref.Ref(ref.NeedsUnref(Substring(ring)));
+ static constexpr int leaf_count = 3;
+ const size_t flat3_count = CordRepBtree::kMaxCapacity - 3;
+ ASSERT_THAT(flat3_count, Ge(0u));
+ CordRepBtree* tree = nullptr;
+ size_t mem_size = 0;
+ for (int i = 0; i < leaf_count; ++i) {
+ auto* flat1 = ref.Ref(Flat(2000), 9);
+ mem_size += SizeOf(flat1);
+ if (i == 0) {
+ tree = CordRepBtree::Create(flat1);
+ } else {
+ tree = CordRepBtree::Append(tree, flat1);
+ }
+
+ auto* flat2 = Flat(200);
+ auto* substr = Substring(flat2);
+ mem_size += SizeOf(flat2) + SizeOf(substr);
+ tree = CordRepBtree::Append(tree, substr);
+
+ auto* external = External(30);
+ mem_size += SizeOf(external);
+ tree = CordRepBtree::Append(tree, external);
+
+ for (size_t i = 0; i < flat3_count; ++i) {
+ auto* flat3 = Flat(70);
+ mem_size += SizeOf(flat3);
+ tree = CordRepBtree::Append(tree, flat3);
+ }
+
+ if (i == 0) {
+ mem_size += SizeOf(tree);
+ } else {
+ mem_size += SizeOf(tree->Edges().back()->btree());
+ }
+ }
+ ref.NeedsUnref(tree);
+
+ // Ref count: 2 for top (add 1), 5 for leaf 0 (add 4).
+ ref.Ref(tree, 1);
+ ref.Ref(tree->Edges().front(), 4);
CordzStatistics expected;
- expected.size = substring->length;
- expected.estimated_memory_usage = SizeOf(ring) + SizeOf(flat1) +
- SizeOf(flat2) + SizeOf(flat3) +
- SizeOf(external) + SizeOf(substring);
- expected.estimated_fair_share_memory_usage = FairShare(substring);
- expected.node_count = 6;
- expected.node_counts.flat = 3;
- expected.node_counts.flat_128 = 1;
- expected.node_counts.flat_256 = 2;
- expected.node_counts.external = 1;
- expected.node_counts.ring = 1;
- expected.node_counts.substring = 1;
+ expected.size = tree->length;
+ expected.estimated_memory_usage = SizeOf(tree) + mem_size;
+ expected.estimated_fair_share_memory_usage = FairShare(tree);
- EXPECT_THAT(SampleCord(substring), EqStatistics(expected));
+ expected.node_count = 1 + leaf_count * (1 + 3 + 1 + flat3_count);
+ expected.node_counts.flat = leaf_count * (2 + flat3_count);
+ expected.node_counts.flat_128 = leaf_count * flat3_count;
+ expected.node_counts.flat_256 = leaf_count;
+ expected.node_counts.external = leaf_count;
+ expected.node_counts.substring = leaf_count;
+ expected.node_counts.btree = 1 + leaf_count;
+
+ EXPECT_THAT(SampleCord(tree), EqStatistics(expected));
+}
+
+TEST(CordzInfoStatisticsTest, Crc) {
+ RefHelper ref;
+ auto* left = Flat(1000);
+ auto* crc = ref.NeedsUnref(CordRepCrc::New(left, {}));
+
+ CordzStatistics expected;
+ expected.size = left->length;
+ expected.estimated_memory_usage = SizeOf(crc) + SizeOf(left);
+ expected.estimated_fair_share_memory_usage = expected.estimated_memory_usage;
+ expected.node_count = 2;
+ expected.node_counts.flat = 1;
+ expected.node_counts.flat_1k = 1;
+ expected.node_counts.crc = 1;
+
+ EXPECT_THAT(SampleCord(crc), EqStatistics(expected));
+}
+
+TEST(CordzInfoStatisticsTest, EmptyCrc) {
+ RefHelper ref;
+ auto* crc = ref.NeedsUnref(CordRepCrc::New(nullptr, {}));
+
+ CordzStatistics expected;
+ expected.size = 0;
+ expected.estimated_memory_usage = SizeOf(crc);
+ expected.estimated_fair_share_memory_usage = expected.estimated_memory_usage;
+ expected.node_count = 1;
+ expected.node_counts.crc = 1;
+
+ EXPECT_THAT(SampleCord(crc), EqStatistics(expected));
}
TEST(CordzInfoStatisticsTest, ThreadSafety) {
@@ -454,6 +441,7 @@
InlineData cords[2];
std::minstd_rand gen;
std::uniform_int_distribution<int> coin_toss(0, 1);
+ std::uniform_int_distribution<int> dice_roll(1, 6);
while (!stop.HasBeenNotified()) {
for (InlineData& cord : cords) {
@@ -471,9 +459,23 @@
CordRep::Unref(cord.as_tree());
cord.set_inline_size(0);
} else {
- // 50/50 Ring or Flat coin toss
+ // Coin toss to 50% btree, and 50% flat.
CordRep* rep = Flat(256);
- rep = (coin_toss(gen) != 0) ? CordRepRing::Create(rep) : rep;
+ if (coin_toss(gen) != 0) {
+ rep = CordRepBtree::Create(rep);
+ }
+
+ // Maybe CRC this cord
+ if (dice_roll(gen) == 6) {
+ if (dice_roll(gen) == 6) {
+ // Empty CRC rep
+ CordRep::Unref(rep);
+ rep = CordRepCrc::New(nullptr, {});
+ } else {
+ // Regular CRC rep
+ rep = CordRepCrc::New(rep, {});
+ }
+ }
cord.make_tree(rep);
// 50/50 sample
diff --git a/absl/strings/internal/cordz_info_test.cc b/absl/strings/internal/cordz_info_test.cc
index b98343a..cd226c3 100644
--- a/absl/strings/internal/cordz_info_test.cc
+++ b/absl/strings/internal/cordz_info_test.cc
@@ -124,7 +124,7 @@
CordzInfo* info = data.data.cordz_info();
info->Untrack();
- EXPECT_THAT(DeleteQueue(), SizeIs(0));
+ EXPECT_THAT(DeleteQueue(), SizeIs(0u));
}
TEST(CordzInfoTest, UntrackCordWithSnapshot) {
@@ -263,8 +263,9 @@
// resultant formatted stack will be "", but that still equals the stack
// recorded in CordzInfo, which is also empty. The skip_count is 1 so that the
// line number of the current stack isn't included in the HasSubstr check.
- local_stack.resize(absl::GetStackTrace(local_stack.data(), kMaxStackDepth,
- /*skip_count=*/1));
+ local_stack.resize(static_cast<size_t>(
+ absl::GetStackTrace(local_stack.data(), kMaxStackDepth,
+ /*skip_count=*/1)));
std::string got_stack = FormatStack(info->GetStack());
std::string expected_stack = FormatStack(local_stack);
diff --git a/absl/strings/internal/cordz_sample_token.h b/absl/strings/internal/cordz_sample_token.h
index 28a1d70..2a86bc3 100644
--- a/absl/strings/internal/cordz_sample_token.h
+++ b/absl/strings/internal/cordz_sample_token.h
@@ -16,8 +16,8 @@
#include "absl/strings/internal/cordz_handle.h"
#include "absl/strings/internal/cordz_info.h"
-#ifndef ABSL_STRINGS_CORDZ_SAMPLE_TOKEN_H_
-#define ABSL_STRINGS_CORDZ_SAMPLE_TOKEN_H_
+#ifndef ABSL_STRINGS_INTERNAL_CORDZ_SAMPLE_TOKEN_H_
+#define ABSL_STRINGS_INTERNAL_CORDZ_SAMPLE_TOKEN_H_
namespace absl {
ABSL_NAMESPACE_BEGIN
@@ -33,11 +33,11 @@
// ST1 <- CH1 <- CH2 <- ST2 <- CH3 <- global_delete_queue_tail
//
// This list tracks that CH1 and CH2 were created after ST1, so the thread
-// holding ST1 might have a referece to CH1, CH2, ST2, and CH3. However, ST2 was
-// created later, so the thread holding the ST2 token cannot have a reference to
-// ST1, CH1, or CH2. If ST1 is cleaned up first, that thread will delete ST1,
-// CH1, and CH2. If instead ST2 is cleaned up first, that thread will only
-// delete ST2.
+// holding ST1 might have a reference to CH1, CH2, ST2, and CH3. However, ST2
+// was created later, so the thread holding the ST2 token cannot have a
+// reference to ST1, CH1, or CH2. If ST1 is cleaned up first, that thread will
+// delete ST1, CH1, and CH2. If instead ST2 is cleaned up first, that thread
+// will only delete ST2.
//
// If ST1 is cleaned up first, the new list will be:
// ST2 <- CH3 <- global_delete_queue_tail
@@ -94,4 +94,4 @@
ABSL_NAMESPACE_END
} // namespace absl
-#endif // ABSL_STRINGS_CORDZ_SAMPLE_TOKEN_H_
+#endif // ABSL_STRINGS_INTERNAL_CORDZ_SAMPLE_TOKEN_H_
diff --git a/absl/strings/internal/cordz_sample_token_test.cc b/absl/strings/internal/cordz_sample_token_test.cc
index 9f54301..6be1770 100644
--- a/absl/strings/internal/cordz_sample_token_test.cc
+++ b/absl/strings/internal/cordz_sample_token_test.cc
@@ -167,7 +167,7 @@
if (cord.data.is_profiled()) {
// 1) Untrack
cord.data.cordz_info()->Untrack();
- cord.data.clear_cordz_info();;
+ cord.data.clear_cordz_info();
} else {
// 2) Track
CordzInfo::TrackCord(cord.data, kTrackCordMethod);
diff --git a/absl/strings/internal/cordz_statistics.h b/absl/strings/internal/cordz_statistics.h
index e03c651..9f558df 100644
--- a/absl/strings/internal/cordz_statistics.h
+++ b/absl/strings/internal/cordz_statistics.h
@@ -40,15 +40,17 @@
size_t substring = 0; // #substring reps
size_t concat = 0; // #concat reps
size_t ring = 0; // #ring buffer reps
+ size_t btree = 0; // #btree reps
+ size_t crc = 0; // #crc reps
};
// The size of the cord in bytes. This matches the result of Cord::size().
- int64_t size = 0;
+ size_t size = 0;
// The estimated memory used by the sampled cord. This value matches the
// value as reported by Cord::EstimatedMemoryUsage().
// A value of 0 implies the property has not been recorded.
- int64_t estimated_memory_usage = 0;
+ size_t estimated_memory_usage = 0;
// The effective memory used by the sampled cord, inversely weighted by the
// effective indegree of each allocated node. This is a representation of the
@@ -57,12 +59,14 @@
// by multiple Cord instances, and for cases where a Cord includes the same
// node multiple times (either directly or indirectly).
// A value of 0 implies the property has not been recorded.
- int64_t estimated_fair_share_memory_usage = 0;
+ size_t estimated_fair_share_memory_usage = 0;
// The total number of nodes referenced by this cord.
// For ring buffer Cords, this includes the 'ring buffer' node.
+ // For btree Cords, this includes all 'CordRepBtree' tree nodes as well as all
+ // the substring, flat and external nodes referenced by the tree.
// A value of 0 implies the property has not been recorded.
- int64_t node_count = 0;
+ size_t node_count = 0;
// Detailed node counts per type
NodeCounts node_counts;
diff --git a/absl/strings/internal/cordz_update_tracker.h b/absl/strings/internal/cordz_update_tracker.h
index 02efcc3..c517066 100644
--- a/absl/strings/internal/cordz_update_tracker.h
+++ b/absl/strings/internal/cordz_update_tracker.h
@@ -40,6 +40,7 @@
enum MethodIdentifier {
kUnknown,
kAppendCord,
+ kAppendCordBuffer,
kAppendExternalMemory,
kAppendString,
kAssignCord,
@@ -49,15 +50,18 @@
kConstructorString,
kCordReader,
kFlatten,
+ kGetAppendBuffer,
kGetAppendRegion,
kMakeCordFromExternal,
kMoveAppendCord,
kMoveAssignCord,
kMovePrependCord,
kPrependCord,
+ kPrependCordBuffer,
kPrependString,
kRemovePrefix,
kRemoveSuffix,
+ kSetExpectedChecksum,
kSubCord,
// kNumMethods defines the number of entries: must be the last entry.
diff --git a/absl/strings/internal/cordz_update_tracker_test.cc b/absl/strings/internal/cordz_update_tracker_test.cc
index fcd17df..9b1f798 100644
--- a/absl/strings/internal/cordz_update_tracker_test.cc
+++ b/absl/strings/internal/cordz_update_tracker_test.cc
@@ -38,6 +38,7 @@
Methods AllMethods() {
return Methods{Method::kUnknown,
Method::kAppendCord,
+ Method::kAppendCordBuffer,
Method::kAppendExternalMemory,
Method::kAppendString,
Method::kAssignCord,
@@ -47,15 +48,18 @@
Method::kConstructorString,
Method::kCordReader,
Method::kFlatten,
+ Method::kGetAppendBuffer,
Method::kGetAppendRegion,
Method::kMakeCordFromExternal,
Method::kMoveAppendCord,
Method::kMoveAssignCord,
Method::kMovePrependCord,
Method::kPrependCord,
+ Method::kPrependCordBuffer,
Method::kPrependString,
Method::kRemovePrefix,
Method::kRemoveSuffix,
+ Method::kSetExpectedChecksum,
Method::kSubCord};
}
diff --git a/absl/strings/internal/damerau_levenshtein_distance.cc b/absl/strings/internal/damerau_levenshtein_distance.cc
new file mode 100644
index 0000000..a084568
--- /dev/null
+++ b/absl/strings/internal/damerau_levenshtein_distance.cc
@@ -0,0 +1,93 @@
+// Copyright 2022 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/strings/internal/damerau_levenshtein_distance.h"
+
+#include <algorithm>
+#include <array>
+#include <numeric>
+
+#include "absl/strings/string_view.h"
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace strings_internal {
+// Calculate DamerauLevenshtein (adjacent transpositions) distance
+// between two strings,
+// https://en.wikipedia.org/wiki/Damerau%E2%80%93Levenshtein_distance. The
+// algorithm follows the condition that no substring is edited more than once.
+// While this can reduce is larger distance, it's a) a much simpler algorithm
+// and b) more realistic for the case that typographic mistakes should be
+// detected.
+// When the distance is larger than cutoff, or one of the strings has more
+// than MAX_SIZE=100 characters, the code returns min(MAX_SIZE, cutoff) + 1.
+uint8_t CappedDamerauLevenshteinDistance(absl::string_view s1,
+ absl::string_view s2, uint8_t cutoff) {
+ const uint8_t MAX_SIZE = 100;
+ const uint8_t _cutoff = std::min(MAX_SIZE, cutoff);
+ const uint8_t cutoff_plus_1 = static_cast<uint8_t>(_cutoff + 1);
+
+ if (s1.size() > s2.size()) std::swap(s1, s2);
+ if (s1.size() + _cutoff < s2.size() || s2.size() > MAX_SIZE)
+ return cutoff_plus_1;
+
+ if (s1.empty())
+ return static_cast<uint8_t>(s2.size());
+
+ // Lower diagonal bound: y = x - lower_diag
+ const uint8_t lower_diag =
+ _cutoff - static_cast<uint8_t>(s2.size() - s1.size());
+ // Upper diagonal bound: y = x + upper_diag
+ const uint8_t upper_diag = _cutoff;
+
+ // d[i][j] is the number of edits required to convert s1[0, i] to s2[0, j]
+ std::array<std::array<uint8_t, MAX_SIZE + 2>, MAX_SIZE + 2> d;
+ std::iota(d[0].begin(), d[0].begin() + upper_diag + 1, 0);
+ d[0][cutoff_plus_1] = cutoff_plus_1;
+ for (size_t i = 1; i <= s1.size(); ++i) {
+ // Deduce begin of relevant window.
+ size_t j_begin = 1;
+ if (i > lower_diag) {
+ j_begin = i - lower_diag;
+ d[i][j_begin - 1] = cutoff_plus_1;
+ } else {
+ d[i][0] = static_cast<uint8_t>(i);
+ }
+
+ // Deduce end of relevant window.
+ size_t j_end = i + upper_diag;
+ if (j_end > s2.size()) {
+ j_end = s2.size();
+ } else {
+ d[i][j_end + 1] = cutoff_plus_1;
+ }
+
+ for (size_t j = j_begin; j <= j_end; ++j) {
+ const uint8_t deletion_distance = d[i - 1][j] + 1;
+ const uint8_t insertion_distance = d[i][j - 1] + 1;
+ const uint8_t mismatched_tail_cost = s1[i - 1] == s2[j - 1] ? 0 : 1;
+ const uint8_t mismatch_distance = d[i - 1][j - 1] + mismatched_tail_cost;
+ uint8_t transposition_distance = _cutoff + 1;
+ if (i > 1 && j > 1 && s1[i - 1] == s2[j - 2] && s1[i - 2] == s2[j - 1])
+ transposition_distance = d[i - 2][j - 2] + 1;
+ d[i][j] = std::min({cutoff_plus_1, deletion_distance, insertion_distance,
+ mismatch_distance, transposition_distance});
+ }
+ }
+ return d[s1.size()][s2.size()];
+}
+
+} // namespace strings_internal
+
+ABSL_NAMESPACE_END
+} // namespace absl
diff --git a/absl/strings/internal/damerau_levenshtein_distance.h b/absl/strings/internal/damerau_levenshtein_distance.h
new file mode 100644
index 0000000..7a4bd64
--- /dev/null
+++ b/absl/strings/internal/damerau_levenshtein_distance.h
@@ -0,0 +1,34 @@
+// Copyright 2022 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef ABSL_STRINGS_INTERNAL_DAMERAU_LEVENSHTEIN_DISTANCE_H_
+#define ABSL_STRINGS_INTERNAL_DAMERAU_LEVENSHTEIN_DISTANCE_H_
+
+#include <cstdint>
+
+#include "absl/strings/string_view.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace strings_internal {
+// Calculate DamerauLevenshtein distance between two strings.
+// When the distance is larger than cutoff, the code just returns cutoff + 1.
+uint8_t CappedDamerauLevenshteinDistance(absl::string_view s1,
+ absl::string_view s2, uint8_t cutoff);
+
+} // namespace strings_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_STRINGS_INTERNAL_DAMERAU_LEVENSHTEIN_DISTANCE_H_
diff --git a/absl/strings/internal/damerau_levenshtein_distance_test.cc b/absl/strings/internal/damerau_levenshtein_distance_test.cc
new file mode 100644
index 0000000..49dd105
--- /dev/null
+++ b/absl/strings/internal/damerau_levenshtein_distance_test.cc
@@ -0,0 +1,99 @@
+// Copyright 2022 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/strings/internal/damerau_levenshtein_distance.h"
+
+#include <cstdint>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace {
+
+using absl::strings_internal::CappedDamerauLevenshteinDistance;
+
+TEST(Distance, TestDistances) {
+ EXPECT_THAT(CappedDamerauLevenshteinDistance("ab", "ab", 6), uint8_t{0});
+ EXPECT_THAT(CappedDamerauLevenshteinDistance("a", "b", 6), uint8_t{1});
+ EXPECT_THAT(CappedDamerauLevenshteinDistance("ca", "abc", 6), uint8_t{3});
+ EXPECT_THAT(CappedDamerauLevenshteinDistance("abcd", "ad", 6), uint8_t{2});
+ EXPECT_THAT(CappedDamerauLevenshteinDistance("abcd", "cadb", 6), uint8_t{4});
+ EXPECT_THAT(CappedDamerauLevenshteinDistance("abcd", "bdac", 6), uint8_t{4});
+ EXPECT_THAT(CappedDamerauLevenshteinDistance("ab", "ab", 0), uint8_t{0});
+ EXPECT_THAT(CappedDamerauLevenshteinDistance("", "", 0), uint8_t{0});
+ // combinations for 3-character strings:
+ // 1, 2, 3 removals, insertions or replacements and transpositions
+ EXPECT_THAT(CappedDamerauLevenshteinDistance("abc", "abc", 6), uint8_t{0});
+ for (auto res :
+ {"", "ca", "efg", "ea", "ce", "ceb", "eca", "cae", "cea", "bea"}) {
+ EXPECT_THAT(CappedDamerauLevenshteinDistance("abc", res, 6), uint8_t{3});
+ EXPECT_THAT(CappedDamerauLevenshteinDistance(res, "abc", 6), uint8_t{3});
+ }
+ for (auto res :
+ {"a", "b", "c", "ba", "cb", "bca", "cab", "cba", "ace",
+ "efc", "ebf", "aef", "ae", "be", "eb", "ec", "ecb", "bec",
+ "bce", "cbe", "ace", "eac", "aeb", "bae", "eab", "eba"}) {
+ EXPECT_THAT(CappedDamerauLevenshteinDistance("abc", res, 6), uint8_t{2});
+ EXPECT_THAT(CappedDamerauLevenshteinDistance(res, "abc", 6), uint8_t{2});
+ }
+ for (auto res : {"ab", "ac", "bc", "acb", "bac", "ebc", "aec", "abe"}) {
+ EXPECT_THAT(CappedDamerauLevenshteinDistance("abc", res, 6), uint8_t{1});
+ EXPECT_THAT(CappedDamerauLevenshteinDistance(res, "abc", 6), uint8_t{1});
+ }
+}
+
+TEST(Distance, TestCutoff) {
+ // Returning cutoff + 1 if the value is larger than cutoff or string longer
+ // than MAX_SIZE.
+ EXPECT_THAT(CappedDamerauLevenshteinDistance("abcd", "a", 3), uint8_t{3});
+ EXPECT_THAT(CappedDamerauLevenshteinDistance("abcd", "a", 2), uint8_t{3});
+ EXPECT_THAT(CappedDamerauLevenshteinDistance("abcd", "a", 1), uint8_t{2});
+ EXPECT_THAT(CappedDamerauLevenshteinDistance("abcdefg", "a", 2), uint8_t{3});
+ EXPECT_THAT(CappedDamerauLevenshteinDistance("a", "abcde", 2), uint8_t{3});
+ EXPECT_THAT(CappedDamerauLevenshteinDistance(std::string(102, 'a'),
+ std::string(102, 'a'), 105),
+ uint8_t{101});
+ EXPECT_THAT(CappedDamerauLevenshteinDistance(std::string(100, 'a'),
+ std::string(100, 'a'), 100),
+ uint8_t{0});
+ EXPECT_THAT(CappedDamerauLevenshteinDistance(std::string(100, 'a'),
+ std::string(100, 'b'), 100),
+ uint8_t{100});
+ EXPECT_THAT(CappedDamerauLevenshteinDistance(std::string(100, 'a'),
+ std::string(99, 'a'), 2),
+ uint8_t{1});
+ EXPECT_THAT(CappedDamerauLevenshteinDistance(std::string(100, 'a'),
+ std::string(101, 'a'), 2),
+ uint8_t{3});
+ EXPECT_THAT(CappedDamerauLevenshteinDistance(std::string(100, 'a'),
+ std::string(101, 'a'), 2),
+ uint8_t{3});
+ EXPECT_THAT(CappedDamerauLevenshteinDistance(std::string(UINT8_MAX + 1, 'a'),
+ std::string(UINT8_MAX + 1, 'b'),
+ UINT8_MAX),
+ uint8_t{101});
+ EXPECT_THAT(CappedDamerauLevenshteinDistance(std::string(UINT8_MAX - 1, 'a'),
+ std::string(UINT8_MAX - 1, 'b'),
+ UINT8_MAX),
+ uint8_t{101});
+ EXPECT_THAT(
+ CappedDamerauLevenshteinDistance(std::string(UINT8_MAX, 'a'),
+ std::string(UINT8_MAX, 'b'), UINT8_MAX),
+ uint8_t{101});
+ EXPECT_THAT(CappedDamerauLevenshteinDistance(std::string(UINT8_MAX - 1, 'a'),
+ std::string(UINT8_MAX - 1, 'a'),
+ UINT8_MAX),
+ uint8_t{101});
+}
+} // namespace
diff --git a/absl/strings/internal/escaping.cc b/absl/strings/internal/escaping.cc
index c527128..56a4cbe 100644
--- a/absl/strings/internal/escaping.cc
+++ b/absl/strings/internal/escaping.cc
@@ -21,26 +21,26 @@
ABSL_NAMESPACE_BEGIN
namespace strings_internal {
-const char kBase64Chars[] =
+// The two strings below provide maps from normal 6-bit characters to their
+// base64-escaped equivalent.
+// For the inverse case, see kUn(WebSafe)Base64 in the external
+// escaping.cc.
+ABSL_CONST_INIT const char kBase64Chars[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+ABSL_CONST_INIT const char kWebSafeBase64Chars[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
+
+
size_t CalculateBase64EscapedLenInternal(size_t input_len, bool do_padding) {
// Base64 encodes three bytes of input at a time. If the input is not
// divisible by three, we pad as appropriate.
//
- // (from https://tools.ietf.org/html/rfc3548)
- // Special processing is performed if fewer than 24 bits are available
- // at the end of the data being encoded. A full encoding quantum is
- // always completed at the end of a quantity. When fewer than 24 input
- // bits are available in an input group, zero bits are added (on the
- // right) to form an integral number of 6-bit groups. Padding at the
- // end of the data is performed using the '=' character. Since all base
- // 64 input is an integral number of octets, only the following cases
- // can arise:
-
// Base64 encodes each three bytes of input into four bytes of output.
size_t len = (input_len / 3) * 4;
+ // Since all base 64 input is an integral number of octets, only the following
+ // cases can arise:
if (input_len % 3 == 0) {
// (from https://tools.ietf.org/html/rfc3548)
// (1) the final quantum of encoding input is an integral multiple of 24
@@ -70,6 +70,21 @@
return len;
}
+// ----------------------------------------------------------------------
+// Take the input in groups of 4 characters and turn each
+// character into a code 0 to 63 thus:
+// A-Z map to 0 to 25
+// a-z map to 26 to 51
+// 0-9 map to 52 to 61
+// +(- for WebSafe) maps to 62
+// /(_ for WebSafe) maps to 63
+// There will be four numbers, all less than 64 which can be represented
+// by a 6 digit binary number (aaaaaa, bbbbbb, cccccc, dddddd respectively).
+// Arrange the 6 digit binary numbers into three bytes as such:
+// aaaaaabb bbbbcccc ccdddddd
+// Equals signs (one or two) are used at the end of the encoded block to
+// indicate that the text was not an integer multiple of three bytes long.
+// ----------------------------------------------------------------------
size_t Base64EscapeInternal(const unsigned char* src, size_t szsrc, char* dest,
size_t szdest, const char* base64,
bool do_padding) {
@@ -83,6 +98,16 @@
char* const limit_dest = dest + szdest;
const unsigned char* const limit_src = src + szsrc;
+ // (from https://tools.ietf.org/html/rfc3548)
+ // Special processing is performed if fewer than 24 bits are available
+ // at the end of the data being encoded. A full encoding quantum is
+ // always completed at the end of a quantity. When fewer than 24 input
+ // bits are available in an input group, zero bits are added (on the
+ // right) to form an integral number of 6-bit groups.
+ //
+ // If do_padding is true, padding at the end of the data is performed. This
+ // output padding uses the '=' character.
+
// Three bytes of data encodes to four characters of cyphertext.
// So we can pump through three-byte chunks atomically.
if (szsrc >= 3) { // "limit_src - 3" is UB if szsrc < 3.
@@ -102,8 +127,8 @@
}
}
// To save time, we didn't update szdest or szsrc in the loop. So do it now.
- szdest = limit_dest - cur_dest;
- szsrc = limit_src - cur_src;
+ szdest = static_cast<size_t>(limit_dest - cur_dest);
+ szsrc = static_cast<size_t>(limit_src - cur_src);
/* now deal with the tail (<=3 bytes) */
switch (szsrc) {
@@ -154,7 +179,8 @@
// the loop because the loop above always reads 4 bytes, and the fourth
// byte is past the end of the input.
if (szdest < 4) return 0;
- uint32_t in = (cur_src[0] << 16) + absl::big_endian::Load16(cur_src + 1);
+ uint32_t in =
+ (uint32_t{cur_src[0]} << 16) + absl::big_endian::Load16(cur_src + 1);
cur_dest[0] = base64[in >> 18];
in &= 0x3FFFF;
cur_dest[1] = base64[in >> 12];
@@ -172,7 +198,7 @@
ABSL_RAW_LOG(FATAL, "Logic problem? szsrc = %zu", szsrc);
break;
}
- return (cur_dest - dest);
+ return static_cast<size_t>(cur_dest - dest);
}
} // namespace strings_internal
diff --git a/absl/strings/internal/escaping.h b/absl/strings/internal/escaping.h
index 6a9ce60..2186f77 100644
--- a/absl/strings/internal/escaping.h
+++ b/absl/strings/internal/escaping.h
@@ -24,20 +24,19 @@
namespace strings_internal {
ABSL_CONST_INIT extern const char kBase64Chars[];
+ABSL_CONST_INIT extern const char kWebSafeBase64Chars[];
-// Calculates how long a string will be when it is base64 encoded given its
-// length and whether or not the result should be padded.
+// Calculates the length of a Base64 encoding (RFC 4648) of a string of length
+// `input_len`, with or without padding per `do_padding`. Note that 'web-safe'
+// encoding (section 5 of the RFC) does not change this length.
size_t CalculateBase64EscapedLenInternal(size_t input_len, bool do_padding);
-// Base64-encodes `src` using the alphabet provided in `base64` and writes the
-// result to `dest`. If `do_padding` is true, `dest` is padded with '=' chars
-// until its length is a multiple of 3. Returns the length of `dest`.
+// Base64-encodes `src` using the alphabet provided in `base64` (which
+// determines whether to do web-safe encoding or not) and writes the result to
+// `dest`. If `do_padding` is true, `dest` is padded with '=' chars until its
+// length is a multiple of 3. Returns the length of `dest`.
size_t Base64EscapeInternal(const unsigned char* src, size_t szsrc, char* dest,
size_t szdest, const char* base64, bool do_padding);
-
-// Base64-encodes `src` using the alphabet provided in `base64` and writes the
-// result to `dest`. If `do_padding` is true, `dest` is padded with '=' chars
-// until its length is a multiple of 3.
template <typename String>
void Base64EscapeInternal(const unsigned char* src, size_t szsrc, String* dest,
bool do_padding, const char* base64_chars) {
diff --git a/absl/strings/internal/has_absl_stringify.h b/absl/strings/internal/has_absl_stringify.h
new file mode 100644
index 0000000..f82cfe2
--- /dev/null
+++ b/absl/strings/internal/has_absl_stringify.h
@@ -0,0 +1,44 @@
+// Copyright 2024 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef ABSL_STRINGS_INTERNAL_HAS_ABSL_STRINGIFY_H_
+#define ABSL_STRINGS_INTERNAL_HAS_ABSL_STRINGIFY_H_
+
+#include "absl/strings/has_absl_stringify.h"
+
+#include "absl/base/config.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+
+namespace strings_internal {
+
+// This exists to fix a circular dependency problem with the GoogleTest release.
+// GoogleTest referenced this internal file and this internal trait. Since
+// simultaneous releases are not possible since once release must reference
+// another, we will temporarily add this back.
+// https://github.com/google/googletest/blob/v1.14.x/googletest/include/gtest/gtest-printers.h#L119
+//
+// This file can be deleted after the next Abseil and GoogleTest release.
+//
+// https://github.com/google/googletest/pull/4368#issuecomment-1717699895
+// https://github.com/google/googletest/pull/4368#issuecomment-1717699895
+using ::absl::HasAbslStringify;
+
+} // namespace strings_internal
+
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_STRINGS_INTERNAL_HAS_ABSL_STRINGIFY_H_
diff --git a/absl/strings/internal/memutil.cc b/absl/strings/internal/memutil.cc
index 2519c68..0bbd8aa 100644
--- a/absl/strings/internal/memutil.cc
+++ b/absl/strings/internal/memutil.cc
@@ -16,6 +16,8 @@
#include <cstdlib>
+#include "absl/strings/ascii.h"
+
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace strings_internal {
@@ -25,88 +27,22 @@
const unsigned char* us2 = reinterpret_cast<const unsigned char*>(s2);
for (size_t i = 0; i < len; i++) {
- const int diff =
- int{static_cast<unsigned char>(absl::ascii_tolower(us1[i]))} -
- int{static_cast<unsigned char>(absl::ascii_tolower(us2[i]))};
- if (diff != 0) return diff;
+ unsigned char c1 = us1[i];
+ unsigned char c2 = us2[i];
+ // If bytes are the same, they will be the same when converted to lower.
+ // So we only need to convert if bytes are not equal.
+ // NOTE(b/308193381): We do not use `absl::ascii_tolower` here in order
+ // to avoid its lookup table and improve performance.
+ if (c1 != c2) {
+ c1 = c1 >= 'A' && c1 <= 'Z' ? c1 - 'A' + 'a' : c1;
+ c2 = c2 >= 'A' && c2 <= 'Z' ? c2 - 'A' + 'a' : c2;
+ const int diff = int{c1} - int{c2};
+ if (diff != 0) return diff;
+ }
}
return 0;
}
-char* memdup(const char* s, size_t slen) {
- void* copy;
- if ((copy = malloc(slen)) == nullptr) return nullptr;
- memcpy(copy, s, slen);
- return reinterpret_cast<char*>(copy);
-}
-
-char* memrchr(const char* s, int c, size_t slen) {
- for (const char* e = s + slen - 1; e >= s; e--) {
- if (*e == c) return const_cast<char*>(e);
- }
- return nullptr;
-}
-
-size_t memspn(const char* s, size_t slen, const char* accept) {
- const char* p = s;
- const char* spanp;
- char c, sc;
-
-cont:
- c = *p++;
- if (slen-- == 0) return p - 1 - s;
- for (spanp = accept; (sc = *spanp++) != '\0';)
- if (sc == c) goto cont;
- return p - 1 - s;
-}
-
-size_t memcspn(const char* s, size_t slen, const char* reject) {
- const char* p = s;
- const char* spanp;
- char c, sc;
-
- while (slen-- != 0) {
- c = *p++;
- for (spanp = reject; (sc = *spanp++) != '\0';)
- if (sc == c) return p - 1 - s;
- }
- return p - s;
-}
-
-char* mempbrk(const char* s, size_t slen, const char* accept) {
- const char* scanp;
- int sc;
-
- for (; slen; ++s, --slen) {
- for (scanp = accept; (sc = *scanp++) != '\0';)
- if (sc == *s) return const_cast<char*>(s);
- }
- return nullptr;
-}
-
-// This is significantly faster for case-sensitive matches with very
-// few possible matches. See unit test for benchmarks.
-const char* memmatch(const char* phaystack, size_t haylen, const char* pneedle,
- size_t neelen) {
- if (0 == neelen) {
- return phaystack; // even if haylen is 0
- }
- if (haylen < neelen) return nullptr;
-
- const char* match;
- const char* hayend = phaystack + haylen - neelen + 1;
- // A static cast is used here to work around the fact that memchr returns
- // a void* on Posix-compliant systems and const void* on Windows.
- while ((match = static_cast<const char*>(
- memchr(phaystack, pneedle[0], hayend - phaystack)))) {
- if (memcmp(match, pneedle, neelen) == 0)
- return match;
- else
- phaystack = match + 1;
- }
- return nullptr;
-}
-
} // namespace strings_internal
ABSL_NAMESPACE_END
} // namespace absl
diff --git a/absl/strings/internal/memutil.h b/absl/strings/internal/memutil.h
index 9ad0535..b5911a0 100644
--- a/absl/strings/internal/memutil.h
+++ b/absl/strings/internal/memutil.h
@@ -14,51 +14,6 @@
// limitations under the License.
//
-// These routines provide mem versions of standard C string routines,
-// such as strpbrk. They function exactly the same as the str versions,
-// so if you wonder what they are, replace the word "mem" by
-// "str" and check out the man page. I could return void*, as the
-// strutil.h mem*() routines tend to do, but I return char* instead
-// since this is by far the most common way these functions are called.
-//
-// The difference between the mem and str versions is the mem version
-// takes a pointer and a length, rather than a '\0'-terminated string.
-// The memcase* routines defined here assume the locale is "C"
-// (they use absl::ascii_tolower instead of tolower).
-//
-// These routines are based on the BSD library.
-//
-// Here's a list of routines from string.h, and their mem analogues.
-// Functions in lowercase are defined in string.h; those in UPPERCASE
-// are defined here:
-//
-// strlen --
-// strcat strncat MEMCAT
-// strcpy strncpy memcpy
-// -- memccpy (very cool function, btw)
-// -- memmove
-// -- memset
-// strcmp strncmp memcmp
-// strcasecmp strncasecmp MEMCASECMP
-// strchr memchr
-// strcoll --
-// strxfrm --
-// strdup strndup MEMDUP
-// strrchr MEMRCHR
-// strspn MEMSPN
-// strcspn MEMCSPN
-// strpbrk MEMPBRK
-// strstr MEMSTR MEMMEM
-// (g)strcasestr MEMCASESTR MEMCASEMEM
-// strtok --
-// strprefix MEMPREFIX (strprefix is from strutil.h)
-// strcaseprefix MEMCASEPREFIX (strcaseprefix is from strutil.h)
-// strsuffix MEMSUFFIX (strsuffix is from strutil.h)
-// strcasesuffix MEMCASESUFFIX (strcasesuffix is from strutil.h)
-// -- MEMIS
-// -- MEMCASEIS
-// strcount MEMCOUNT (strcount is from strutil.h)
-
#ifndef ABSL_STRINGS_INTERNAL_MEMUTIL_H_
#define ABSL_STRINGS_INTERNAL_MEMUTIL_H_
@@ -72,74 +27,11 @@
ABSL_NAMESPACE_BEGIN
namespace strings_internal {
-inline char* memcat(char* dest, size_t destlen, const char* src,
- size_t srclen) {
- return reinterpret_cast<char*>(memcpy(dest + destlen, src, srclen));
-}
-
+// Performs a byte-by-byte comparison of `len` bytes of the strings `s1` and
+// `s2`, ignoring the case of the characters. It returns an integer less than,
+// equal to, or greater than zero if `s1` is found, respectively, to be less
+// than, to match, or be greater than `s2`.
int memcasecmp(const char* s1, const char* s2, size_t len);
-char* memdup(const char* s, size_t slen);
-char* memrchr(const char* s, int c, size_t slen);
-size_t memspn(const char* s, size_t slen, const char* accept);
-size_t memcspn(const char* s, size_t slen, const char* reject);
-char* mempbrk(const char* s, size_t slen, const char* accept);
-
-// This is for internal use only. Don't call this directly
-template <bool case_sensitive>
-const char* int_memmatch(const char* haystack, size_t haylen,
- const char* needle, size_t neelen) {
- if (0 == neelen) {
- return haystack; // even if haylen is 0
- }
- const char* hayend = haystack + haylen;
- const char* needlestart = needle;
- const char* needleend = needlestart + neelen;
-
- for (; haystack < hayend; ++haystack) {
- char hay = case_sensitive
- ? *haystack
- : absl::ascii_tolower(static_cast<unsigned char>(*haystack));
- char nee = case_sensitive
- ? *needle
- : absl::ascii_tolower(static_cast<unsigned char>(*needle));
- if (hay == nee) {
- if (++needle == needleend) {
- return haystack + 1 - neelen;
- }
- } else if (needle != needlestart) {
- // must back up haystack in case a prefix matched (find "aab" in "aaab")
- haystack -= needle - needlestart; // for loop will advance one more
- needle = needlestart;
- }
- }
- return nullptr;
-}
-
-// These are the guys you can call directly
-inline const char* memstr(const char* phaystack, size_t haylen,
- const char* pneedle) {
- return int_memmatch<true>(phaystack, haylen, pneedle, strlen(pneedle));
-}
-
-inline const char* memcasestr(const char* phaystack, size_t haylen,
- const char* pneedle) {
- return int_memmatch<false>(phaystack, haylen, pneedle, strlen(pneedle));
-}
-
-inline const char* memmem(const char* phaystack, size_t haylen,
- const char* pneedle, size_t needlelen) {
- return int_memmatch<true>(phaystack, haylen, pneedle, needlelen);
-}
-
-inline const char* memcasemem(const char* phaystack, size_t haylen,
- const char* pneedle, size_t needlelen) {
- return int_memmatch<false>(phaystack, haylen, pneedle, needlelen);
-}
-
-// This is significantly faster for case-sensitive matches with very
-// few possible matches. See unit test for benchmarks.
-const char* memmatch(const char* phaystack, size_t haylen, const char* pneedle,
- size_t neelen);
} // namespace strings_internal
ABSL_NAMESPACE_END
diff --git a/absl/strings/internal/memutil_benchmark.cc b/absl/strings/internal/memutil_benchmark.cc
index dc95c3e..61e323a 100644
--- a/absl/strings/internal/memutil_benchmark.cc
+++ b/absl/strings/internal/memutil_benchmark.cc
@@ -25,62 +25,6 @@
// - an easy search: 'b'
// - a medium search: 'ab'. That means every letter is a possible match.
// - a pathological search: 'aaaaaa.......aaaaab' (half as many a's as haytack)
-// We benchmark case-sensitive and case-insensitive versions of
-// three memmem implementations:
-// - memmem() from memutil.h
-// - search() from STL
-// - memmatch(), a custom implementation using memchr and memcmp.
-// Here are sample results:
-//
-// Run on (12 X 3800 MHz CPU s)
-// CPU Caches:
-// L1 Data 32K (x6)
-// L1 Instruction 32K (x6)
-// L2 Unified 256K (x6)
-// L3 Unified 15360K (x1)
-// ----------------------------------------------------------------
-// Benchmark Time CPU Iterations
-// ----------------------------------------------------------------
-// BM_Memmem 3583 ns 3582 ns 196469 2.59966GB/s
-// BM_MemmemMedium 13743 ns 13742 ns 50901 693.986MB/s
-// BM_MemmemPathological 13695030 ns 13693977 ns 51 713.133kB/s
-// BM_Memcasemem 3299 ns 3299 ns 212942 2.82309GB/s
-// BM_MemcasememMedium 16407 ns 16406 ns 42170 581.309MB/s
-// BM_MemcasememPathological 17267745 ns 17266030 ns 41 565.598kB/s
-// BM_Search 1610 ns 1609 ns 431321 5.78672GB/s
-// BM_SearchMedium 11111 ns 11110 ns 63001 858.414MB/s
-// BM_SearchPathological 12117390 ns 12116397 ns 58 805.984kB/s
-// BM_Searchcase 3081 ns 3081 ns 229949 3.02313GB/s
-// BM_SearchcaseMedium 16003 ns 16001 ns 44170 595.998MB/s
-// BM_SearchcasePathological 15823413 ns 15821909 ns 44 617.222kB/s
-// BM_Memmatch 197 ns 197 ns 3584225 47.2951GB/s
-// BM_MemmatchMedium 52333 ns 52329 ns 13280 182.244MB/s
-// BM_MemmatchPathological 659799 ns 659727 ns 1058 14.4556MB/s
-// BM_Memcasematch 5460 ns 5460 ns 127606 1.70586GB/s
-// BM_MemcasematchMedium 32861 ns 32857 ns 21258 290.248MB/s
-// BM_MemcasematchPathological 15154243 ns 15153089 ns 46 644.464kB/s
-// BM_MemmemStartup 5 ns 5 ns 150821500
-// BM_SearchStartup 5 ns 5 ns 150644203
-// BM_MemmatchStartup 7 ns 7 ns 97068802
-//
-// Conclusions:
-//
-// The following recommendations are based on the sample results above. However,
-// we have found that the performance of STL search can vary significantly
-// depending on compiler and standard library implementation. We recommend you
-// run the benchmarks for yourself on relevant platforms.
-//
-// If you need case-insensitive, STL search is slightly better than memmem for
-// all cases.
-//
-// Case-sensitive is more subtle:
-// Custom memmatch is _very_ fast at scanning, so if you have very few possible
-// matches in your haystack, that's the way to go. Performance drops
-// significantly with more matches.
-//
-// STL search is slightly faster than memmem in the medium and pathological
-// benchmarks. However, the performance of memmem is currently more dependable
-// across platforms and build configurations.
namespace {
@@ -94,96 +38,10 @@
}
const char* const kHaystack = MakeHaystack();
-void BM_Memmem(benchmark::State& state) {
- for (auto _ : state) {
- benchmark::DoNotOptimize(
- absl::strings_internal::memmem(kHaystack, kHaystackSize, "b", 1));
- }
- state.SetBytesProcessed(kHaystackSize64 * state.iterations());
-}
-BENCHMARK(BM_Memmem);
-
-void BM_MemmemMedium(benchmark::State& state) {
- for (auto _ : state) {
- benchmark::DoNotOptimize(
- absl::strings_internal::memmem(kHaystack, kHaystackSize, "ab", 2));
- }
- state.SetBytesProcessed(kHaystackSize64 * state.iterations());
-}
-BENCHMARK(BM_MemmemMedium);
-
-void BM_MemmemPathological(benchmark::State& state) {
- for (auto _ : state) {
- benchmark::DoNotOptimize(absl::strings_internal::memmem(
- kHaystack, kHaystackSize, kHaystack + kHaystackSize / 2,
- kHaystackSize - kHaystackSize / 2));
- }
- state.SetBytesProcessed(kHaystackSize64 * state.iterations());
-}
-BENCHMARK(BM_MemmemPathological);
-
-void BM_Memcasemem(benchmark::State& state) {
- for (auto _ : state) {
- benchmark::DoNotOptimize(
- absl::strings_internal::memcasemem(kHaystack, kHaystackSize, "b", 1));
- }
- state.SetBytesProcessed(kHaystackSize64 * state.iterations());
-}
-BENCHMARK(BM_Memcasemem);
-
-void BM_MemcasememMedium(benchmark::State& state) {
- for (auto _ : state) {
- benchmark::DoNotOptimize(
- absl::strings_internal::memcasemem(kHaystack, kHaystackSize, "ab", 2));
- }
- state.SetBytesProcessed(kHaystackSize64 * state.iterations());
-}
-BENCHMARK(BM_MemcasememMedium);
-
-void BM_MemcasememPathological(benchmark::State& state) {
- for (auto _ : state) {
- benchmark::DoNotOptimize(absl::strings_internal::memcasemem(
- kHaystack, kHaystackSize, kHaystack + kHaystackSize / 2,
- kHaystackSize - kHaystackSize / 2));
- }
- state.SetBytesProcessed(kHaystackSize64 * state.iterations());
-}
-BENCHMARK(BM_MemcasememPathological);
-
bool case_eq(const char a, const char b) {
return absl::ascii_tolower(a) == absl::ascii_tolower(b);
}
-void BM_Search(benchmark::State& state) {
- for (auto _ : state) {
- benchmark::DoNotOptimize(std::search(kHaystack, kHaystack + kHaystackSize,
- kHaystack + kHaystackSize - 1,
- kHaystack + kHaystackSize));
- }
- state.SetBytesProcessed(kHaystackSize64 * state.iterations());
-}
-BENCHMARK(BM_Search);
-
-void BM_SearchMedium(benchmark::State& state) {
- for (auto _ : state) {
- benchmark::DoNotOptimize(std::search(kHaystack, kHaystack + kHaystackSize,
- kHaystack + kHaystackSize - 2,
- kHaystack + kHaystackSize));
- }
- state.SetBytesProcessed(kHaystackSize64 * state.iterations());
-}
-BENCHMARK(BM_SearchMedium);
-
-void BM_SearchPathological(benchmark::State& state) {
- for (auto _ : state) {
- benchmark::DoNotOptimize(std::search(kHaystack, kHaystack + kHaystackSize,
- kHaystack + kHaystackSize / 2,
- kHaystack + kHaystackSize));
- }
- state.SetBytesProcessed(kHaystackSize64 * state.iterations());
-}
-BENCHMARK(BM_SearchPathological);
-
void BM_Searchcase(benchmark::State& state) {
for (auto _ : state) {
benchmark::DoNotOptimize(std::search(kHaystack, kHaystack + kHaystackSize,
@@ -241,34 +99,6 @@
return nullptr;
}
-void BM_Memmatch(benchmark::State& state) {
- for (auto _ : state) {
- benchmark::DoNotOptimize(
- absl::strings_internal::memmatch(kHaystack, kHaystackSize, "b", 1));
- }
- state.SetBytesProcessed(kHaystackSize64 * state.iterations());
-}
-BENCHMARK(BM_Memmatch);
-
-void BM_MemmatchMedium(benchmark::State& state) {
- for (auto _ : state) {
- benchmark::DoNotOptimize(
- absl::strings_internal::memmatch(kHaystack, kHaystackSize, "ab", 2));
- }
- state.SetBytesProcessed(kHaystackSize64 * state.iterations());
-}
-BENCHMARK(BM_MemmatchMedium);
-
-void BM_MemmatchPathological(benchmark::State& state) {
- for (auto _ : state) {
- benchmark::DoNotOptimize(absl::strings_internal::memmatch(
- kHaystack, kHaystackSize, kHaystack + kHaystackSize / 2,
- kHaystackSize - kHaystackSize / 2));
- }
- state.SetBytesProcessed(kHaystackSize64 * state.iterations());
-}
-BENCHMARK(BM_MemmatchPathological);
-
void BM_Memcasematch(benchmark::State& state) {
for (auto _ : state) {
benchmark::DoNotOptimize(memcasematch(kHaystack, kHaystackSize, "b", 1));
@@ -295,29 +125,4 @@
}
BENCHMARK(BM_MemcasematchPathological);
-void BM_MemmemStartup(benchmark::State& state) {
- for (auto _ : state) {
- benchmark::DoNotOptimize(absl::strings_internal::memmem(
- kHaystack + kHaystackSize - 10, 10, kHaystack + kHaystackSize - 1, 1));
- }
-}
-BENCHMARK(BM_MemmemStartup);
-
-void BM_SearchStartup(benchmark::State& state) {
- for (auto _ : state) {
- benchmark::DoNotOptimize(
- std::search(kHaystack + kHaystackSize - 10, kHaystack + kHaystackSize,
- kHaystack + kHaystackSize - 1, kHaystack + kHaystackSize));
- }
-}
-BENCHMARK(BM_SearchStartup);
-
-void BM_MemmatchStartup(benchmark::State& state) {
- for (auto _ : state) {
- benchmark::DoNotOptimize(absl::strings_internal::memmatch(
- kHaystack + kHaystackSize - 10, 10, kHaystack + kHaystackSize - 1, 1));
- }
-}
-BENCHMARK(BM_MemmatchStartup);
-
} // namespace
diff --git a/absl/strings/internal/memutil_test.cc b/absl/strings/internal/memutil_test.cc
index d8681dd..277be2c 100644
--- a/absl/strings/internal/memutil_test.cc
+++ b/absl/strings/internal/memutil_test.cc
@@ -19,42 +19,12 @@
#include <cstdlib>
#include "gtest/gtest.h"
-#include "absl/strings/ascii.h"
namespace {
-static char* memcasechr(const char* s, int c, size_t slen) {
- c = absl::ascii_tolower(c);
- for (; slen; ++s, --slen) {
- if (absl::ascii_tolower(*s) == c) return const_cast<char*>(s);
- }
- return nullptr;
-}
-
-static const char* memcasematch(const char* phaystack, size_t haylen,
- const char* pneedle, size_t neelen) {
- if (0 == neelen) {
- return phaystack; // even if haylen is 0
- }
- if (haylen < neelen) return nullptr;
-
- const char* match;
- const char* hayend = phaystack + haylen - neelen + 1;
- while ((match = static_cast<char*>(
- memcasechr(phaystack, pneedle[0], hayend - phaystack)))) {
- if (absl::strings_internal::memcasecmp(match, pneedle, neelen) == 0)
- return match;
- else
- phaystack = match + 1;
- }
- return nullptr;
-}
-
-TEST(MemUtilTest, AllTests) {
+TEST(MemUtil, memcasecmp) {
// check memutil functions
- char a[1000];
- absl::strings_internal::memcat(a, 0, "hello", sizeof("hello") - 1);
- absl::strings_internal::memcat(a, 5, " there", sizeof(" there") - 1);
+ const char a[] = "hello there";
EXPECT_EQ(absl::strings_internal::memcasecmp(a, "heLLO there",
sizeof("hello there") - 1),
@@ -66,114 +36,6 @@
sizeof("hello there") - 2),
0);
EXPECT_EQ(absl::strings_internal::memcasecmp(a, "whatever", 0), 0);
-
- char* p = absl::strings_internal::memdup("hello", 5);
- free(p);
-
- p = absl::strings_internal::memrchr("hello there", 'e',
- sizeof("hello there") - 1);
- EXPECT_TRUE(p && p[-1] == 'r');
- p = absl::strings_internal::memrchr("hello there", 'e',
- sizeof("hello there") - 2);
- EXPECT_TRUE(p && p[-1] == 'h');
- p = absl::strings_internal::memrchr("hello there", 'u',
- sizeof("hello there") - 1);
- EXPECT_TRUE(p == nullptr);
-
- int len = absl::strings_internal::memspn("hello there",
- sizeof("hello there") - 1, "hole");
- EXPECT_EQ(len, sizeof("hello") - 1);
- len = absl::strings_internal::memspn("hello there", sizeof("hello there") - 1,
- "u");
- EXPECT_EQ(len, 0);
- len = absl::strings_internal::memspn("hello there", sizeof("hello there") - 1,
- "");
- EXPECT_EQ(len, 0);
- len = absl::strings_internal::memspn("hello there", sizeof("hello there") - 1,
- "trole h");
- EXPECT_EQ(len, sizeof("hello there") - 1);
- len = absl::strings_internal::memspn("hello there!",
- sizeof("hello there!") - 1, "trole h");
- EXPECT_EQ(len, sizeof("hello there") - 1);
- len = absl::strings_internal::memspn("hello there!",
- sizeof("hello there!") - 2, "trole h!");
- EXPECT_EQ(len, sizeof("hello there!") - 2);
-
- len = absl::strings_internal::memcspn("hello there",
- sizeof("hello there") - 1, "leho");
- EXPECT_EQ(len, 0);
- len = absl::strings_internal::memcspn("hello there",
- sizeof("hello there") - 1, "u");
- EXPECT_EQ(len, sizeof("hello there") - 1);
- len = absl::strings_internal::memcspn("hello there",
- sizeof("hello there") - 1, "");
- EXPECT_EQ(len, sizeof("hello there") - 1);
- len = absl::strings_internal::memcspn("hello there",
- sizeof("hello there") - 1, " ");
- EXPECT_EQ(len, 5);
-
- p = absl::strings_internal::mempbrk("hello there", sizeof("hello there") - 1,
- "leho");
- EXPECT_TRUE(p && p[1] == 'e' && p[2] == 'l');
- p = absl::strings_internal::mempbrk("hello there", sizeof("hello there") - 1,
- "nu");
- EXPECT_TRUE(p == nullptr);
- p = absl::strings_internal::mempbrk("hello there!",
- sizeof("hello there!") - 2, "!");
- EXPECT_TRUE(p == nullptr);
- p = absl::strings_internal::mempbrk("hello there", sizeof("hello there") - 1,
- " t ");
- EXPECT_TRUE(p && p[-1] == 'o' && p[1] == 't');
-
- {
- const char kHaystack[] = "0123456789";
- EXPECT_EQ(absl::strings_internal::memmem(kHaystack, 0, "", 0), kHaystack);
- EXPECT_EQ(absl::strings_internal::memmem(kHaystack, 10, "012", 3),
- kHaystack);
- EXPECT_EQ(absl::strings_internal::memmem(kHaystack, 10, "0xx", 1),
- kHaystack);
- EXPECT_EQ(absl::strings_internal::memmem(kHaystack, 10, "789", 3),
- kHaystack + 7);
- EXPECT_EQ(absl::strings_internal::memmem(kHaystack, 10, "9xx", 1),
- kHaystack + 9);
- EXPECT_TRUE(absl::strings_internal::memmem(kHaystack, 10, "9xx", 3) ==
- nullptr);
- EXPECT_TRUE(absl::strings_internal::memmem(kHaystack, 10, "xxx", 1) ==
- nullptr);
- }
- {
- const char kHaystack[] = "aBcDeFgHiJ";
- EXPECT_EQ(absl::strings_internal::memcasemem(kHaystack, 0, "", 0),
- kHaystack);
- EXPECT_EQ(absl::strings_internal::memcasemem(kHaystack, 10, "Abc", 3),
- kHaystack);
- EXPECT_EQ(absl::strings_internal::memcasemem(kHaystack, 10, "Axx", 1),
- kHaystack);
- EXPECT_EQ(absl::strings_internal::memcasemem(kHaystack, 10, "hIj", 3),
- kHaystack + 7);
- EXPECT_EQ(absl::strings_internal::memcasemem(kHaystack, 10, "jxx", 1),
- kHaystack + 9);
- EXPECT_TRUE(absl::strings_internal::memcasemem(kHaystack, 10, "jxx", 3) ==
- nullptr);
- EXPECT_TRUE(absl::strings_internal::memcasemem(kHaystack, 10, "xxx", 1) ==
- nullptr);
- }
- {
- const char kHaystack[] = "0123456789";
- EXPECT_EQ(absl::strings_internal::memmatch(kHaystack, 0, "", 0), kHaystack);
- EXPECT_EQ(absl::strings_internal::memmatch(kHaystack, 10, "012", 3),
- kHaystack);
- EXPECT_EQ(absl::strings_internal::memmatch(kHaystack, 10, "0xx", 1),
- kHaystack);
- EXPECT_EQ(absl::strings_internal::memmatch(kHaystack, 10, "789", 3),
- kHaystack + 7);
- EXPECT_EQ(absl::strings_internal::memmatch(kHaystack, 10, "9xx", 1),
- kHaystack + 9);
- EXPECT_TRUE(absl::strings_internal::memmatch(kHaystack, 10, "9xx", 3) ==
- nullptr);
- EXPECT_TRUE(absl::strings_internal::memmatch(kHaystack, 10, "xxx", 1) ==
- nullptr);
- }
}
} // namespace
diff --git a/absl/strings/internal/ostringstream.cc b/absl/strings/internal/ostringstream.cc
index 05324c7..a0e5ec0 100644
--- a/absl/strings/internal/ostringstream.cc
+++ b/absl/strings/internal/ostringstream.cc
@@ -14,20 +14,27 @@
#include "absl/strings/internal/ostringstream.h"
+#include <cassert>
+#include <cstddef>
+#include <ios>
+#include <streambuf>
+
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace strings_internal {
-OStringStream::Buf::int_type OStringStream::overflow(int c) {
- assert(s_);
- if (!Buf::traits_type::eq_int_type(c, Buf::traits_type::eof()))
- s_->push_back(static_cast<char>(c));
+OStringStream::Streambuf::int_type OStringStream::Streambuf::overflow(int c) {
+ assert(str_);
+ if (!std::streambuf::traits_type::eq_int_type(
+ c, std::streambuf::traits_type::eof()))
+ str_->push_back(static_cast<char>(c));
return 1;
}
-std::streamsize OStringStream::xsputn(const char* s, std::streamsize n) {
- assert(s_);
- s_->append(s, n);
+std::streamsize OStringStream::Streambuf::xsputn(const char* s,
+ std::streamsize n) {
+ assert(str_);
+ str_->append(s, static_cast<size_t>(n));
return n;
}
diff --git a/absl/strings/internal/ostringstream.h b/absl/strings/internal/ostringstream.h
index d25d604..c0e237d 100644
--- a/absl/strings/internal/ostringstream.h
+++ b/absl/strings/internal/ostringstream.h
@@ -16,11 +16,13 @@
#define ABSL_STRINGS_INTERNAL_OSTRINGSTREAM_H_
#include <cassert>
+#include <ios>
#include <ostream>
#include <streambuf>
#include <string>
+#include <utility>
-#include "absl/base/port.h"
+#include "absl/base/config.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
@@ -60,26 +62,49 @@
// strm << 3.14;
//
// Note: flush() has no effect. No reason to call it.
-class OStringStream : private std::basic_streambuf<char>, public std::ostream {
+class OStringStream final : public std::ostream {
public:
// The argument can be null, in which case you'll need to call str(p) with a
// non-null argument before you can write to the stream.
//
// The destructor of OStringStream doesn't use the std::string. It's OK to
// destroy the std::string before the stream.
- explicit OStringStream(std::string* s) : std::ostream(this), s_(s) {}
+ explicit OStringStream(std::string* str)
+ : std::ostream(&buf_), buf_(str) {}
+ OStringStream(OStringStream&& that)
+ : std::ostream(std::move(static_cast<std::ostream&>(that))),
+ buf_(that.buf_) {
+ rdbuf(&buf_);
+ }
+ OStringStream& operator=(OStringStream&& that) {
+ std::ostream::operator=(std::move(static_cast<std::ostream&>(that)));
+ buf_ = that.buf_;
+ rdbuf(&buf_);
+ return *this;
+ }
- std::string* str() { return s_; }
- const std::string* str() const { return s_; }
- void str(std::string* s) { s_ = s; }
+ std::string* str() { return buf_.str(); }
+ const std::string* str() const { return buf_.str(); }
+ void str(std::string* str) { buf_.str(str); }
private:
- using Buf = std::basic_streambuf<char>;
+ class Streambuf final : public std::streambuf {
+ public:
+ explicit Streambuf(std::string* str) : str_(str) {}
+ Streambuf(const Streambuf&) = default;
+ Streambuf& operator=(const Streambuf&) = default;
- Buf::int_type overflow(int c) override;
- std::streamsize xsputn(const char* s, std::streamsize n) override;
+ std::string* str() { return str_; }
+ const std::string* str() const { return str_; }
+ void str(std::string* str) { str_ = str; }
- std::string* s_;
+ protected:
+ int_type overflow(int c) override;
+ std::streamsize xsputn(const char* s, std::streamsize n) override;
+
+ private:
+ std::string* str_;
+ } buf_;
};
} // namespace strings_internal
diff --git a/absl/strings/internal/ostringstream_test.cc b/absl/strings/internal/ostringstream_test.cc
index 2879e50..ef3ad57 100644
--- a/absl/strings/internal/ostringstream_test.cc
+++ b/absl/strings/internal/ostringstream_test.cc
@@ -14,10 +14,12 @@
#include "absl/strings/internal/ostringstream.h"
+#include <ios>
#include <memory>
#include <ostream>
#include <string>
#include <type_traits>
+#include <utility>
#include "gtest/gtest.h"
@@ -29,24 +31,51 @@
"");
}
-TEST(OStringStream, ConstructDestroy) {
+TEST(OStringStream, ConstructNullptr) {
+ absl::strings_internal::OStringStream strm(nullptr);
+ EXPECT_EQ(nullptr, strm.str());
+}
+
+TEST(OStringStream, ConstructStr) {
+ std::string s = "abc";
{
- absl::strings_internal::OStringStream strm(nullptr);
- EXPECT_EQ(nullptr, strm.str());
+ absl::strings_internal::OStringStream strm(&s);
+ EXPECT_EQ(&s, strm.str());
}
+ EXPECT_EQ("abc", s);
+}
+
+TEST(OStringStream, Destroy) {
+ std::unique_ptr<std::string> s(new std::string);
+ absl::strings_internal::OStringStream strm(s.get());
+ s.reset();
+}
+
+TEST(OStringStream, MoveConstruct) {
+ std::string s = "abc";
{
- std::string s = "abc";
- {
- absl::strings_internal::OStringStream strm(&s);
- EXPECT_EQ(&s, strm.str());
- }
- EXPECT_EQ("abc", s);
+ absl::strings_internal::OStringStream strm1(&s);
+ strm1 << std::hex << 16;
+ EXPECT_EQ(&s, strm1.str());
+ absl::strings_internal::OStringStream strm2(std::move(strm1));
+ strm2 << 16; // We should still be in base 16.
+ EXPECT_EQ(&s, strm2.str());
}
+ EXPECT_EQ("abc1010", s);
+}
+
+TEST(OStringStream, MoveAssign) {
+ std::string s = "abc";
{
- std::unique_ptr<std::string> s(new std::string);
- absl::strings_internal::OStringStream strm(s.get());
- s.reset();
+ absl::strings_internal::OStringStream strm1(&s);
+ strm1 << std::hex << 16;
+ EXPECT_EQ(&s, strm1.str());
+ absl::strings_internal::OStringStream strm2(nullptr);
+ strm2 = std::move(strm1);
+ strm2 << 16; // We should still be in base 16.
+ EXPECT_EQ(&s, strm2.str());
}
+ EXPECT_EQ("abc1010", s);
}
TEST(OStringStream, Str) {
diff --git a/absl/strings/internal/resize_uninitialized.h b/absl/strings/internal/resize_uninitialized.h
index 749c66e..49859dc 100644
--- a/absl/strings/internal/resize_uninitialized.h
+++ b/absl/strings/internal/resize_uninitialized.h
@@ -29,8 +29,9 @@
ABSL_NAMESPACE_BEGIN
namespace strings_internal {
-// Is a subclass of true_type or false_type, depending on whether or not
-// T has a __resize_default_init member.
+// In this type trait, we look for a __resize_default_init member function, and
+// we use it if available, otherwise, we use resize. We provide HasMember to
+// indicate whether __resize_default_init is present.
template <typename string_type, typename = void>
struct ResizeUninitializedTraits {
using HasMember = std::false_type;
@@ -79,14 +80,36 @@
}
}
+// 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>
+struct AppendUninitializedTraits {
+ static void Append(string_type* s, size_t n) {
+ s->append(n, typename string_type::value_type());
+ }
+};
+
+template <typename string_type>
+struct AppendUninitializedTraits<
+ string_type, absl::void_t<decltype(std::declval<string_type&>()
+ .__append_default_init(237))> > {
+ static void Append(string_type* s, size_t n) {
+ s->__append_default_init(n);
+ }
+};
+
// Like STLStringResizeUninitialized(str, new_size), except guaranteed to use
// 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 STLStringResizeUninitializedAmortized(string_type* s, size_t new_size) {
- STLStringReserveAmortized(s, new_size);
- STLStringResizeUninitialized(s, new_size);
+ const size_t size = s->size();
+ if (new_size > size) {
+ AppendUninitializedTraits<string_type>::Append(s, new_size - size);
+ } else {
+ s->erase(new_size);
+ }
}
} // namespace strings_internal
diff --git a/absl/strings/internal/resize_uninitialized_test.cc b/absl/strings/internal/resize_uninitialized_test.cc
index 01ee476..ad1b9c5 100644
--- a/absl/strings/internal/resize_uninitialized_test.cc
+++ b/absl/strings/internal/resize_uninitialized_test.cc
@@ -19,10 +19,12 @@
namespace {
int resize_call_count = 0;
+int append_call_count = 0;
// A mock string class whose only purpose is to track how many times its
-// resize() method has been called.
+// resize()/append() methods have been called.
struct resizable_string {
+ using value_type = char;
size_t size() const { return 0; }
size_t capacity() const { return 0; }
char& operator[](size_t) {
@@ -30,14 +32,18 @@
return c;
}
void resize(size_t) { resize_call_count += 1; }
+ void append(size_t, value_type) { append_call_count += 1; }
void reserve(size_t) {}
+ resizable_string& erase(size_t = 0, size_t = 0) { return *this; }
};
int resize_default_init_call_count = 0;
+int append_default_init_call_count = 0;
// A mock string class whose only purpose is to track how many times its
-// resize() and __resize_default_init() methods have been called.
-struct resize_default_init_string {
+// resize()/__resize_default_init()/append()/__append_default_init() methods
+// have been called.
+struct default_init_string {
size_t size() const { return 0; }
size_t capacity() const { return 0; }
char& operator[](size_t) {
@@ -46,46 +52,68 @@
}
void resize(size_t) { resize_call_count += 1; }
void __resize_default_init(size_t) { resize_default_init_call_count += 1; }
+ void __append_default_init(size_t) { append_default_init_call_count += 1; }
void reserve(size_t) {}
+ default_init_string& erase(size_t = 0, size_t = 0) { return *this; }
};
TEST(ResizeUninit, WithAndWithout) {
resize_call_count = 0;
+ append_call_count = 0;
resize_default_init_call_count = 0;
+ append_default_init_call_count = 0;
{
resizable_string rs;
EXPECT_EQ(resize_call_count, 0);
+ EXPECT_EQ(append_call_count, 0);
EXPECT_EQ(resize_default_init_call_count, 0);
+ EXPECT_EQ(append_default_init_call_count, 0);
EXPECT_FALSE(
absl::strings_internal::STLStringSupportsNontrashingResize(&rs));
EXPECT_EQ(resize_call_count, 0);
+ EXPECT_EQ(append_call_count, 0);
EXPECT_EQ(resize_default_init_call_count, 0);
+ EXPECT_EQ(append_default_init_call_count, 0);
absl::strings_internal::STLStringResizeUninitialized(&rs, 237);
EXPECT_EQ(resize_call_count, 1);
+ EXPECT_EQ(append_call_count, 0);
EXPECT_EQ(resize_default_init_call_count, 0);
+ EXPECT_EQ(append_default_init_call_count, 0);
absl::strings_internal::STLStringResizeUninitializedAmortized(&rs, 1000);
- EXPECT_EQ(resize_call_count, 2);
+ EXPECT_EQ(resize_call_count, 1);
+ EXPECT_EQ(append_call_count, 1);
EXPECT_EQ(resize_default_init_call_count, 0);
+ EXPECT_EQ(append_default_init_call_count, 0);
}
resize_call_count = 0;
+ append_call_count = 0;
resize_default_init_call_count = 0;
+ append_default_init_call_count = 0;
{
- resize_default_init_string rus;
+ default_init_string rus;
EXPECT_EQ(resize_call_count, 0);
+ EXPECT_EQ(append_call_count, 0);
EXPECT_EQ(resize_default_init_call_count, 0);
+ EXPECT_EQ(append_default_init_call_count, 0);
EXPECT_TRUE(
absl::strings_internal::STLStringSupportsNontrashingResize(&rus));
EXPECT_EQ(resize_call_count, 0);
+ EXPECT_EQ(append_call_count, 0);
EXPECT_EQ(resize_default_init_call_count, 0);
+ EXPECT_EQ(append_default_init_call_count, 0);
absl::strings_internal::STLStringResizeUninitialized(&rus, 237);
EXPECT_EQ(resize_call_count, 0);
+ EXPECT_EQ(append_call_count, 0);
EXPECT_EQ(resize_default_init_call_count, 1);
+ EXPECT_EQ(append_default_init_call_count, 0);
absl::strings_internal::STLStringResizeUninitializedAmortized(&rus, 1000);
EXPECT_EQ(resize_call_count, 0);
- EXPECT_EQ(resize_default_init_call_count, 2);
+ EXPECT_EQ(append_call_count, 0);
+ EXPECT_EQ(resize_default_init_call_count, 1);
+ EXPECT_EQ(append_default_init_call_count, 1);
}
}
diff --git a/absl/strings/internal/stl_type_traits.h b/absl/strings/internal/stl_type_traits.h
index 6035ca4..e50468b 100644
--- a/absl/strings/internal/stl_type_traits.h
+++ b/absl/strings/internal/stl_type_traits.h
@@ -13,7 +13,7 @@
// limitations under the License.
//
-// Thie file provides the IsStrictlyBaseOfAndConvertibleToSTLContainer type
+// The file provides the IsStrictlyBaseOfAndConvertibleToSTLContainer type
// trait metafunction to assist in working with the _GLIBCXX_DEBUG debug
// wrappers of STL containers.
//
diff --git a/absl/strings/internal/str_format/arg.cc b/absl/strings/internal/str_format/arg.cc
index e28a29b..eeb2108 100644
--- a/absl/strings/internal/str_format/arg.cc
+++ b/absl/strings/internal/str_format/arg.cc
@@ -18,15 +18,28 @@
//
#include "absl/strings/internal/str_format/arg.h"
+#include <algorithm>
#include <cassert>
-#include <cerrno>
+#include <cstddef>
+#include <cstdint>
#include <cstdlib>
+#include <cstring>
+#include <cwchar>
#include <string>
#include <type_traits>
-#include "absl/base/port.h"
+#include "absl/base/config.h"
+#include "absl/base/optimization.h"
+#include "absl/container/fixed_array.h"
+#include "absl/numeric/int128.h"
+#include "absl/strings/internal/str_format/extension.h"
#include "absl/strings/internal/str_format/float_conversion.h"
#include "absl/strings/numbers.h"
+#include "absl/strings/string_view.h"
+
+#if defined(ABSL_HAVE_STD_STRING_VIEW)
+#include <string_view>
+#endif
namespace absl {
ABSL_NAMESPACE_BEGIN
@@ -77,7 +90,7 @@
v >>= 3;
} while (v);
start_ = p;
- size_ = storage_ + sizeof(storage_) - p;
+ size_ = static_cast<size_t>(storage_ + sizeof(storage_) - p);
}
// Print the signed or unsigned integer as decimal.
@@ -86,7 +99,8 @@
void PrintAsDec(T v) {
static_assert(std::is_integral<T>::value, "");
start_ = storage_;
- size_ = numbers_internal::FastIntToBuffer(v, storage_) - storage_;
+ size_ = static_cast<size_t>(numbers_internal::FastIntToBuffer(v, storage_) -
+ storage_);
}
void PrintAsDec(int128 v) {
@@ -105,7 +119,7 @@
char *p = storage_ + sizeof(storage_);
do {
p -= 2;
- numbers_internal::PutTwoDigits(static_cast<size_t>(v % 100), p);
+ numbers_internal::PutTwoDigits(static_cast<uint32_t>(v % 100), p);
v /= 100;
} while (v);
if (p[0] == '0') {
@@ -115,7 +129,7 @@
if (add_neg) {
*--p = '-';
}
- size_ = storage_ + sizeof(storage_) - p;
+ size_ = static_cast<size_t>(storage_ + sizeof(storage_) - p);
start_ = p;
}
@@ -138,7 +152,7 @@
++p;
}
start_ = p;
- size_ = storage_ + sizeof(storage_) - p;
+ size_ = static_cast<size_t>(storage_ + sizeof(storage_) - p);
}
// Print the unsigned integer as hex using uppercase.
@@ -154,7 +168,7 @@
v >>= 4;
} while (v);
start_ = p;
- size_ = storage_ + sizeof(storage_) - p;
+ size_ = static_cast<size_t>(storage_ + sizeof(storage_) - p);
}
// The printed value including the '-' sign if available.
@@ -208,10 +222,12 @@
return {};
}
-bool ConvertCharImpl(unsigned char v, const FormatConversionSpecImpl conv,
- FormatSinkImpl *sink) {
+bool ConvertCharImpl(char v,
+ const FormatConversionSpecImpl conv,
+ FormatSinkImpl* sink) {
size_t fill = 0;
- if (conv.width() >= 0) fill = conv.width();
+ if (conv.width() >= 0)
+ fill = static_cast<size_t>(conv.width());
ReducePadding(1, &fill);
if (!conv.has_left_flag()) sink->Append(fill, ' ');
sink->Append(1, v);
@@ -225,7 +241,8 @@
// Print as a sequence of Substrings:
// [left_spaces][sign][base_indicator][zeroes][formatted][right_spaces]
size_t fill = 0;
- if (conv.width() >= 0) fill = conv.width();
+ if (conv.width() >= 0)
+ fill = static_cast<size_t>(conv.width());
string_view formatted = as_digits.without_neg_or_zero();
ReducePadding(formatted, &fill);
@@ -236,10 +253,9 @@
string_view base_indicator = BaseIndicator(as_digits, conv);
ReducePadding(base_indicator, &fill);
- int precision = conv.precision();
- bool precision_specified = precision >= 0;
- if (!precision_specified)
- precision = 1;
+ bool precision_specified = conv.precision() >= 0;
+ size_t precision =
+ precision_specified ? static_cast<size_t>(conv.precision()) : size_t{1};
if (conv.has_alt_flag() &&
conv.conversion_char() == FormatConversionCharInternal::o) {
@@ -247,7 +263,7 @@
// "For o conversion, it increases the precision (if necessary) to
// force the first digit of the result to be zero."
if (formatted.empty() || *formatted.begin() != '0') {
- int needed = static_cast<int>(formatted.size()) + 1;
+ size_t needed = formatted.size() + 1;
precision = std::max(precision, needed);
}
}
@@ -276,18 +292,128 @@
}
template <typename T>
-bool ConvertIntArg(T v, const FormatConversionSpecImpl conv,
- FormatSinkImpl *sink) {
+bool ConvertFloatArg(T v, FormatConversionSpecImpl conv, FormatSinkImpl *sink) {
+ if (conv.conversion_char() == FormatConversionCharInternal::v) {
+ conv.set_conversion_char(FormatConversionCharInternal::g);
+ }
+
+ return FormatConversionCharIsFloat(conv.conversion_char()) &&
+ ConvertFloatImpl(v, conv, sink);
+}
+
+inline bool ConvertStringArg(string_view v, const FormatConversionSpecImpl conv,
+ FormatSinkImpl *sink) {
+ if (conv.is_basic()) {
+ sink->Append(v);
+ return true;
+ }
+ return sink->PutPaddedString(v, conv.width(), conv.precision(),
+ conv.has_left_flag());
+}
+
+struct ShiftState {
+ bool saw_high_surrogate = false;
+ uint8_t bits = 0;
+};
+
+// Converts `v` from UTF-16 or UTF-32 to UTF-8 and writes to `buf`. `buf` is
+// assumed to have enough space for the output. `s` is used to carry state
+// between successive calls with a UTF-16 surrogate pair. Returns the number of
+// chars written, or `static_cast<size_t>(-1)` on failure.
+//
+// This is basically std::wcrtomb(), but always outputting UTF-8 instead of
+// respecting the current locale.
+inline size_t WideToUtf8(wchar_t wc, char *buf, ShiftState &s) {
+ const auto v = static_cast<uint32_t>(wc);
+ if (v < 0x80) {
+ *buf = static_cast<char>(v);
+ return 1;
+ } else if (v < 0x800) {
+ *buf++ = static_cast<char>(0xc0 | (v >> 6));
+ *buf = static_cast<char>(0x80 | (v & 0x3f));
+ return 2;
+ } else if (v < 0xd800 || (v - 0xe000) < 0x2000) {
+ *buf++ = static_cast<char>(0xe0 | (v >> 12));
+ *buf++ = static_cast<char>(0x80 | ((v >> 6) & 0x3f));
+ *buf = static_cast<char>(0x80 | (v & 0x3f));
+ return 3;
+ } else if ((v - 0x10000) < 0x100000) {
+ *buf++ = static_cast<char>(0xf0 | (v >> 18));
+ *buf++ = static_cast<char>(0x80 | ((v >> 12) & 0x3f));
+ *buf++ = static_cast<char>(0x80 | ((v >> 6) & 0x3f));
+ *buf = static_cast<char>(0x80 | (v & 0x3f));
+ return 4;
+ } else if (v < 0xdc00) {
+ s.saw_high_surrogate = true;
+ s.bits = static_cast<uint8_t>(v & 0x3);
+ const uint8_t high_bits = ((v >> 6) & 0xf) + 1;
+ *buf++ = static_cast<char>(0xf0 | (high_bits >> 2));
+ *buf =
+ static_cast<char>(0x80 | static_cast<uint8_t>((high_bits & 0x3) << 4) |
+ static_cast<uint8_t>((v >> 2) & 0xf));
+ return 2;
+ } else if (v < 0xe000 && s.saw_high_surrogate) {
+ *buf++ = static_cast<char>(0x80 | static_cast<uint8_t>(s.bits << 4) |
+ static_cast<uint8_t>((v >> 6) & 0xf));
+ *buf = static_cast<char>(0x80 | (v & 0x3f));
+ s.saw_high_surrogate = false;
+ s.bits = 0;
+ return 2;
+ } else {
+ return static_cast<size_t>(-1);
+ }
+}
+
+inline bool ConvertStringArg(const wchar_t *v,
+ size_t len,
+ const FormatConversionSpecImpl conv,
+ FormatSinkImpl *sink) {
+ FixedArray<char> mb(len * 4);
+ ShiftState s;
+ size_t chars_written = 0;
+ for (size_t i = 0; i < len; ++i) {
+ const size_t chars = WideToUtf8(v[i], &mb[chars_written], s);
+ if (chars == static_cast<size_t>(-1)) { return false; }
+ chars_written += chars;
+ }
+ return ConvertStringArg(string_view(mb.data(), chars_written), conv, sink);
+}
+
+bool ConvertWCharTImpl(wchar_t v, const FormatConversionSpecImpl conv,
+ FormatSinkImpl *sink) {
+ char mb[4];
+ ShiftState s;
+ const size_t chars_written = WideToUtf8(v, mb, s);
+ return chars_written != static_cast<size_t>(-1) && !s.saw_high_surrogate &&
+ ConvertStringArg(string_view(mb, chars_written), conv, sink);
+}
+
+} // namespace
+
+bool ConvertBoolArg(bool v, FormatSinkImpl *sink) {
+ if (v) {
+ sink->Append("true");
+ } else {
+ sink->Append("false");
+ }
+ return true;
+}
+
+template <typename T>
+bool ConvertIntArg(T v, FormatConversionSpecImpl conv, FormatSinkImpl *sink) {
using U = typename MakeUnsigned<T>::type;
IntDigits as_digits;
// This odd casting is due to a bug in -Wswitch behavior in gcc49 which causes
// it to complain about a switch/case type mismatch, even though both are
- // FormatConverionChar. Likely this is because at this point
+ // FormatConversionChar. Likely this is because at this point
// FormatConversionChar is declared, but not defined.
switch (static_cast<uint8_t>(conv.conversion_char())) {
case static_cast<uint8_t>(FormatConversionCharInternal::c):
- return ConvertCharImpl(static_cast<unsigned char>(v), conv, sink);
+ return (std::is_same<T, wchar_t>::value ||
+ (conv.length_mod() == LengthMod::l))
+ ? ConvertWCharTImpl(static_cast<wchar_t>(v), conv, sink)
+ : ConvertCharImpl(static_cast<char>(v), conv, sink);
case static_cast<uint8_t>(FormatConversionCharInternal::o):
as_digits.PrintAsOct(static_cast<U>(v));
@@ -306,6 +432,7 @@
case static_cast<uint8_t>(FormatConversionCharInternal::d):
case static_cast<uint8_t>(FormatConversionCharInternal::i):
+ case static_cast<uint8_t>(FormatConversionCharInternal::v):
as_digits.PrintAsDec(v);
break;
@@ -320,7 +447,7 @@
return ConvertFloatImpl(static_cast<double>(v), conv, sink);
default:
- ABSL_INTERNAL_ASSUME(false);
+ ABSL_ASSUME(false);
}
if (conv.is_basic()) {
@@ -330,24 +457,39 @@
return ConvertIntImplInnerSlow(as_digits, conv, sink);
}
-template <typename T>
-bool ConvertFloatArg(T v, const FormatConversionSpecImpl conv,
- FormatSinkImpl *sink) {
- return FormatConversionCharIsFloat(conv.conversion_char()) &&
- ConvertFloatImpl(v, conv, sink);
-}
-
-inline bool ConvertStringArg(string_view v, const FormatConversionSpecImpl conv,
- FormatSinkImpl *sink) {
- if (conv.is_basic()) {
- sink->Append(v);
- return true;
- }
- return sink->PutPaddedString(v, conv.width(), conv.precision(),
- conv.has_left_flag());
-}
-
-} // namespace
+template bool ConvertIntArg<char>(char v, FormatConversionSpecImpl conv,
+ FormatSinkImpl *sink);
+template bool ConvertIntArg<signed char>(signed char v,
+ FormatConversionSpecImpl conv,
+ FormatSinkImpl *sink);
+template bool ConvertIntArg<unsigned char>(unsigned char v,
+ FormatConversionSpecImpl conv,
+ FormatSinkImpl *sink);
+template bool ConvertIntArg<wchar_t>(wchar_t v, FormatConversionSpecImpl conv,
+ FormatSinkImpl *sink);
+template bool ConvertIntArg<short>(short v, // NOLINT
+ FormatConversionSpecImpl conv,
+ FormatSinkImpl *sink);
+template bool ConvertIntArg<unsigned short>(unsigned short v, // NOLINT
+ FormatConversionSpecImpl conv,
+ FormatSinkImpl *sink);
+template bool ConvertIntArg<int>(int v, FormatConversionSpecImpl conv,
+ FormatSinkImpl *sink);
+template bool ConvertIntArg<unsigned int>(unsigned int v,
+ FormatConversionSpecImpl conv,
+ FormatSinkImpl *sink);
+template bool ConvertIntArg<long>(long v, // NOLINT
+ FormatConversionSpecImpl conv,
+ FormatSinkImpl *sink);
+template bool ConvertIntArg<unsigned long>(unsigned long v, // NOLINT
+ FormatConversionSpecImpl conv,
+ FormatSinkImpl *sink);
+template bool ConvertIntArg<long long>(long long v, // NOLINT
+ FormatConversionSpecImpl conv,
+ FormatSinkImpl *sink);
+template bool ConvertIntArg<unsigned long long>(unsigned long long v, // NOLINT
+ FormatConversionSpecImpl conv,
+ FormatSinkImpl *sink);
// ==================== Strings ====================
StringConvertResult FormatConvertImpl(const std::string &v,
@@ -356,16 +498,29 @@
return {ConvertStringArg(v, conv, sink)};
}
+StringConvertResult FormatConvertImpl(const std::wstring &v,
+ const FormatConversionSpecImpl conv,
+ FormatSinkImpl *sink) {
+ return {ConvertStringArg(v.data(), v.size(), conv, sink)};
+}
+
StringConvertResult FormatConvertImpl(string_view v,
const FormatConversionSpecImpl conv,
FormatSinkImpl *sink) {
return {ConvertStringArg(v, conv, sink)};
}
-ArgConvertResult<FormatConversionCharSetUnion(
- FormatConversionCharSetInternal::s, FormatConversionCharSetInternal::p)>
-FormatConvertImpl(const char *v, const FormatConversionSpecImpl conv,
- FormatSinkImpl *sink) {
+#if defined(ABSL_HAVE_STD_STRING_VIEW)
+StringConvertResult FormatConvertImpl(std::wstring_view v,
+ const FormatConversionSpecImpl conv,
+ FormatSinkImpl* sink) {
+ return {ConvertStringArg(v.data(), v.size(), conv, sink)};
+}
+#endif
+
+StringPtrConvertResult FormatConvertImpl(const char* v,
+ const FormatConversionSpecImpl conv,
+ FormatSinkImpl* sink) {
if (conv.conversion_char() == FormatConversionCharInternal::p)
return {FormatConvertImpl(VoidPtr(v), conv, sink).value};
size_t len;
@@ -375,11 +530,35 @@
len = std::strlen(v);
} else {
// If precision is set, we look for the NUL-terminator on the valid range.
- len = std::find(v, v + conv.precision(), '\0') - v;
+ len = static_cast<size_t>(std::find(v, v + conv.precision(), '\0') - v);
}
return {ConvertStringArg(string_view(v, len), conv, sink)};
}
+StringPtrConvertResult FormatConvertImpl(const wchar_t* v,
+ const FormatConversionSpecImpl conv,
+ FormatSinkImpl* sink) {
+ if (conv.conversion_char() == FormatConversionCharInternal::p) {
+ return {FormatConvertImpl(VoidPtr(v), conv, sink).value};
+ }
+ size_t len;
+ if (v == nullptr) {
+ len = 0;
+ } else if (conv.precision() < 0) {
+ len = std::wcslen(v);
+ } else {
+ // If precision is set, we look for the NUL-terminator on the valid range.
+ len = static_cast<size_t>(std::find(v, v + conv.precision(), L'\0') - v);
+ }
+ return {ConvertStringArg(v, len, conv, sink)};
+}
+
+StringPtrConvertResult FormatConvertImpl(std::nullptr_t,
+ const FormatConversionSpecImpl conv,
+ FormatSinkImpl* sink) {
+ return FormatConvertImpl(static_cast<const char*>(nullptr), conv, sink);
+}
+
// ==================== Raw pointers ====================
ArgConvertResult<FormatConversionCharSetInternal::p> FormatConvertImpl(
VoidPtr v, const FormatConversionSpecImpl conv, FormatSinkImpl *sink) {
@@ -410,11 +589,17 @@
}
// ==================== Chars ====================
-IntegralConvertResult FormatConvertImpl(char v,
- const FormatConversionSpecImpl conv,
- FormatSinkImpl *sink) {
+CharConvertResult FormatConvertImpl(char v, const FormatConversionSpecImpl conv,
+ FormatSinkImpl *sink) {
return {ConvertIntArg(v, conv, sink)};
}
+CharConvertResult FormatConvertImpl(wchar_t v,
+ const FormatConversionSpecImpl conv,
+ FormatSinkImpl* sink) {
+ return {ConvertIntArg(v, conv, sink)};
+}
+
+// ==================== Ints ====================
IntegralConvertResult FormatConvertImpl(signed char v,
const FormatConversionSpecImpl conv,
FormatSinkImpl *sink) {
@@ -425,8 +610,6 @@
FormatSinkImpl *sink) {
return {ConvertIntArg(v, conv, sink)};
}
-
-// ==================== Ints ====================
IntegralConvertResult FormatConvertImpl(short v, // NOLINT
const FormatConversionSpecImpl conv,
FormatSinkImpl *sink) {
diff --git a/absl/strings/internal/str_format/arg.h b/absl/strings/internal/str_format/arg.h
index 3c91be7..309161d 100644
--- a/absl/strings/internal/str_format/arg.h
+++ b/absl/strings/internal/str_format/arg.h
@@ -18,20 +18,29 @@
#include <string.h>
#include <wchar.h>
+#include <algorithm>
+#include <cstddef>
+#include <cstdint>
#include <cstdio>
-#include <iomanip>
#include <limits>
#include <memory>
#include <sstream>
#include <string>
#include <type_traits>
+#include <utility>
-#include "absl/base/port.h"
+#include "absl/base/config.h"
+#include "absl/base/optimization.h"
#include "absl/meta/type_traits.h"
#include "absl/numeric/int128.h"
+#include "absl/strings/has_absl_stringify.h"
#include "absl/strings/internal/str_format/extension.h"
#include "absl/strings/string_view.h"
+#if defined(ABSL_HAVE_STD_STRING_VIEW)
+#include <string_view>
+#endif
+
namespace absl {
ABSL_NAMESPACE_BEGIN
@@ -45,6 +54,24 @@
namespace str_format_internal {
+template <FormatConversionCharSet C>
+struct ArgConvertResult {
+ bool value;
+};
+
+using IntegralConvertResult = ArgConvertResult<FormatConversionCharSetUnion(
+ FormatConversionCharSetInternal::c,
+ FormatConversionCharSetInternal::kNumeric,
+ FormatConversionCharSetInternal::kStar,
+ FormatConversionCharSetInternal::v)>;
+using FloatingConvertResult = ArgConvertResult<FormatConversionCharSetUnion(
+ FormatConversionCharSetInternal::kFloating,
+ FormatConversionCharSetInternal::v)>;
+using CharConvertResult = ArgConvertResult<FormatConversionCharSetUnion(
+ FormatConversionCharSetInternal::c,
+ FormatConversionCharSetInternal::kNumeric,
+ FormatConversionCharSetInternal::kStar)>;
+
template <typename T, typename = void>
struct HasUserDefinedConvert : std::false_type {};
@@ -55,7 +82,53 @@
std::declval<FormatSink*>()))>>
: std::true_type {};
-void AbslFormatConvert(); // Stops the lexical name lookup
+// These declarations prevent ADL lookup from continuing in absl namespaces,
+// we are deliberately using these as ADL hooks and want them to consider
+// non-absl namespaces only.
+void AbslFormatConvert();
+void AbslStringify();
+
+template <typename T>
+bool ConvertIntArg(T v, FormatConversionSpecImpl conv, FormatSinkImpl* sink);
+
+// Forward declarations of internal `ConvertIntArg` function template
+// instantiations are here to avoid including the template body in the headers
+// and instantiating it in large numbers of translation units. Explicit
+// instantiations can be found in "absl/strings/internal/str_format/arg.cc"
+extern template bool ConvertIntArg<char>(char v, FormatConversionSpecImpl conv,
+ FormatSinkImpl* sink);
+extern template bool ConvertIntArg<signed char>(signed char v,
+ FormatConversionSpecImpl conv,
+ FormatSinkImpl* sink);
+extern template bool ConvertIntArg<unsigned char>(unsigned char v,
+ FormatConversionSpecImpl conv,
+ FormatSinkImpl* sink);
+extern template bool ConvertIntArg<wchar_t>(wchar_t v,
+ FormatConversionSpecImpl conv,
+ FormatSinkImpl* sink);
+extern template bool ConvertIntArg<short>(short v, // NOLINT
+ FormatConversionSpecImpl conv,
+ FormatSinkImpl* sink);
+extern template bool ConvertIntArg<unsigned short>( // NOLINT
+ unsigned short v, FormatConversionSpecImpl conv, // NOLINT
+ FormatSinkImpl* sink);
+extern template bool ConvertIntArg<int>(int v, FormatConversionSpecImpl conv,
+ FormatSinkImpl* sink);
+extern template bool ConvertIntArg<unsigned int>(unsigned int v,
+ FormatConversionSpecImpl conv,
+ FormatSinkImpl* sink);
+extern template bool ConvertIntArg<long>( // NOLINT
+ long v, FormatConversionSpecImpl conv, FormatSinkImpl* sink); // NOLINT
+extern template bool ConvertIntArg<unsigned long>(unsigned long v, // NOLINT
+ FormatConversionSpecImpl conv,
+ FormatSinkImpl* sink);
+extern template bool ConvertIntArg<long long>(long long v, // NOLINT
+ FormatConversionSpecImpl conv,
+ FormatSinkImpl* sink);
+extern template bool ConvertIntArg<unsigned long long>( // NOLINT
+ unsigned long long v, FormatConversionSpecImpl conv, // NOLINT
+ FormatSinkImpl* sink);
+
template <typename T>
auto FormatConvertImpl(const T& v, FormatConversionSpecImpl conv,
FormatSinkImpl* sink)
@@ -72,6 +145,40 @@
}
template <typename T>
+auto FormatConvertImpl(const T& v, FormatConversionSpecImpl conv,
+ FormatSinkImpl* sink)
+ -> std::enable_if_t<std::is_enum<T>::value &&
+ std::is_void<decltype(AbslStringify(
+ std::declval<FormatSink&>(), v))>::value,
+ IntegralConvertResult> {
+ if (conv.conversion_char() == FormatConversionCharInternal::v) {
+ using FormatSinkT =
+ absl::enable_if_t<sizeof(const T& (*)()) != 0, FormatSink>;
+ auto fs = sink->Wrap<FormatSinkT>();
+ AbslStringify(fs, v);
+ return {true};
+ } else {
+ return {ConvertIntArg(
+ static_cast<typename std::underlying_type<T>::type>(v), conv, sink)};
+ }
+}
+
+template <typename T>
+auto FormatConvertImpl(const T& v, FormatConversionSpecImpl,
+ FormatSinkImpl* sink)
+ -> std::enable_if_t<!std::is_enum<T>::value &&
+ !std::is_same<T, absl::Cord>::value &&
+ std::is_void<decltype(AbslStringify(
+ std::declval<FormatSink&>(), v))>::value,
+ ArgConvertResult<FormatConversionCharSetInternal::v>> {
+ using FormatSinkT =
+ absl::enable_if_t<sizeof(const T& (*)()) != 0, FormatSink>;
+ auto fs = sink->Wrap<FormatSinkT>();
+ AbslStringify(fs, v);
+ return {true};
+}
+
+template <typename T>
class StreamedWrapper;
// If 'v' can be converted (in the printf sense) according to 'conv',
@@ -96,11 +203,6 @@
};
template <FormatConversionCharSet C>
-struct ArgConvertResult {
- bool value;
-};
-
-template <FormatConversionCharSet C>
constexpr FormatConversionCharSet ExtractCharSet(FormatConvertResult<C>) {
return C;
}
@@ -110,30 +212,49 @@
return C;
}
-using StringConvertResult =
- ArgConvertResult<FormatConversionCharSetInternal::s>;
ArgConvertResult<FormatConversionCharSetInternal::p> FormatConvertImpl(
VoidPtr v, FormatConversionSpecImpl conv, FormatSinkImpl* sink);
// Strings.
+using StringConvertResult = ArgConvertResult<FormatConversionCharSetUnion(
+ FormatConversionCharSetInternal::s,
+ FormatConversionCharSetInternal::v)>;
StringConvertResult FormatConvertImpl(const std::string& v,
FormatConversionSpecImpl conv,
FormatSinkImpl* sink);
+StringConvertResult FormatConvertImpl(const std::wstring& v,
+ FormatConversionSpecImpl conv,
+ FormatSinkImpl* sink);
StringConvertResult FormatConvertImpl(string_view v,
FormatConversionSpecImpl conv,
FormatSinkImpl* sink);
-#if defined(ABSL_HAVE_STD_STRING_VIEW) && !defined(ABSL_USES_STD_STRING_VIEW)
+#if defined(ABSL_HAVE_STD_STRING_VIEW)
+StringConvertResult FormatConvertImpl(std::wstring_view v,
+ FormatConversionSpecImpl conv,
+ FormatSinkImpl* sink);
+#if !defined(ABSL_USES_STD_STRING_VIEW)
inline StringConvertResult FormatConvertImpl(std::string_view v,
FormatConversionSpecImpl conv,
FormatSinkImpl* sink) {
return FormatConvertImpl(absl::string_view(v.data(), v.size()), conv, sink);
}
-#endif // ABSL_HAVE_STD_STRING_VIEW && !ABSL_USES_STD_STRING_VIEW
+#endif // !ABSL_USES_STD_STRING_VIEW
+#endif // ABSL_HAVE_STD_STRING_VIEW
-ArgConvertResult<FormatConversionCharSetUnion(
- FormatConversionCharSetInternal::s, FormatConversionCharSetInternal::p)>
-FormatConvertImpl(const char* v, const FormatConversionSpecImpl conv,
- FormatSinkImpl* sink);
+using StringPtrConvertResult = ArgConvertResult<FormatConversionCharSetUnion(
+ FormatConversionCharSetInternal::s,
+ FormatConversionCharSetInternal::p)>;
+StringPtrConvertResult FormatConvertImpl(const char* v,
+ FormatConversionSpecImpl conv,
+ FormatSinkImpl* sink);
+StringPtrConvertResult FormatConvertImpl(const wchar_t* v,
+ FormatConversionSpecImpl conv,
+ FormatSinkImpl* sink);
+// This overload is needed to disambiguate, since `nullptr` could match either
+// of the other overloads equally well.
+StringPtrConvertResult FormatConvertImpl(std::nullptr_t,
+ FormatConversionSpecImpl conv,
+ FormatSinkImpl* sink);
template <class AbslCord, typename std::enable_if<std::is_same<
AbslCord, absl::Cord>::value>::type* = nullptr>
@@ -144,7 +265,7 @@
size_t space_remaining = 0;
int width = conv.width();
- if (width >= 0) space_remaining = width;
+ if (width >= 0) space_remaining = static_cast<size_t>(width);
size_t to_write = value.size();
@@ -173,12 +294,7 @@
return {true};
}
-using IntegralConvertResult = ArgConvertResult<FormatConversionCharSetUnion(
- FormatConversionCharSetInternal::c,
- FormatConversionCharSetInternal::kNumeric,
- FormatConversionCharSetInternal::kStar)>;
-using FloatingConvertResult =
- ArgConvertResult<FormatConversionCharSetInternal::kFloating>;
+bool ConvertBoolArg(bool v, FormatSinkImpl* sink);
// Floats.
FloatingConvertResult FormatConvertImpl(float v, FormatConversionSpecImpl conv,
@@ -190,16 +306,19 @@
FormatSinkImpl* sink);
// Chars.
-IntegralConvertResult FormatConvertImpl(char v, FormatConversionSpecImpl conv,
- FormatSinkImpl* sink);
+CharConvertResult FormatConvertImpl(char v, FormatConversionSpecImpl conv,
+ FormatSinkImpl* sink);
+CharConvertResult FormatConvertImpl(wchar_t v,
+ FormatConversionSpecImpl conv,
+ FormatSinkImpl* sink);
+
+// Ints.
IntegralConvertResult FormatConvertImpl(signed char v,
FormatConversionSpecImpl conv,
FormatSinkImpl* sink);
IntegralConvertResult FormatConvertImpl(unsigned char v,
FormatConversionSpecImpl conv,
FormatSinkImpl* sink);
-
-// Ints.
IntegralConvertResult FormatConvertImpl(short v, // NOLINT
FormatConversionSpecImpl conv,
FormatSinkImpl* sink);
@@ -228,9 +347,16 @@
IntegralConvertResult FormatConvertImpl(uint128 v,
FormatConversionSpecImpl conv,
FormatSinkImpl* sink);
+
+// This function needs to be a template due to ambiguity regarding type
+// conversions.
template <typename T, enable_if_t<std::is_same<T, bool>::value, int> = 0>
IntegralConvertResult FormatConvertImpl(T v, FormatConversionSpecImpl conv,
FormatSinkImpl* sink) {
+ if (conv.conversion_char() == FormatConversionCharInternal::v) {
+ return {ConvertBoolArg(v, sink)};
+ }
+
return FormatConvertImpl(static_cast<int>(v), conv, sink);
}
@@ -238,7 +364,8 @@
// FormatArgImpl will use the underlying Convert functions instead.
template <typename T>
typename std::enable_if<std::is_enum<T>::value &&
- !HasUserDefinedConvert<T>::value,
+ !HasUserDefinedConvert<T>::value &&
+ !HasAbslStringify<T>::value,
IntegralConvertResult>::type
FormatConvertImpl(T v, FormatConversionSpecImpl conv, FormatSinkImpl* sink);
@@ -301,11 +428,11 @@
template <typename Arg>
constexpr FormatConversionCharSet ArgumentToConv() {
- return absl::str_format_internal::ExtractCharSet(
- decltype(str_format_internal::FormatConvertImpl(
- std::declval<const Arg&>(),
- std::declval<const FormatConversionSpecImpl&>(),
- std::declval<FormatSinkImpl*>())){});
+ using ConvResult = decltype(str_format_internal::FormatConvertImpl(
+ std::declval<const Arg&>(),
+ std::declval<const FormatConversionSpecImpl&>(),
+ std::declval<FormatSinkImpl*>()));
+ return absl::str_format_internal::ExtractCharSet(ConvResult{});
}
// A type-erased handle to a format argument.
@@ -345,26 +472,32 @@
// Anything with a user-defined Convert will get its own vtable.
// For everything else:
// - Decay char* and char arrays into `const char*`
+ // - Decay wchar_t* and wchar_t arrays into `const wchar_t*`
// - Decay any other pointer to `const void*`
- // - Decay all enums to their underlying type.
+ // - Decay all enums to the integral promotion of their underlying type.
// - Decay function pointers to void*.
template <typename T, typename = void>
struct DecayType {
static constexpr bool kHasUserDefined =
- str_format_internal::HasUserDefinedConvert<T>::value;
+ str_format_internal::HasUserDefinedConvert<T>::value ||
+ HasAbslStringify<T>::value;
using type = typename std::conditional<
!kHasUserDefined && std::is_convertible<T, const char*>::value,
const char*,
- typename std::conditional<!kHasUserDefined &&
- std::is_convertible<T, VoidPtr>::value,
- VoidPtr, const T&>::type>::type;
+ typename std::conditional<
+ !kHasUserDefined && std::is_convertible<T, const wchar_t*>::value,
+ const wchar_t*,
+ typename std::conditional<
+ !kHasUserDefined && std::is_convertible<T, VoidPtr>::value,
+ VoidPtr,
+ const T&>::type>::type>::type;
};
template <typename T>
- struct DecayType<T,
- typename std::enable_if<
- !str_format_internal::HasUserDefinedConvert<T>::value &&
- std::is_enum<T>::value>::type> {
- using type = typename std::underlying_type<T>::type;
+ struct DecayType<
+ T, typename std::enable_if<
+ !str_format_internal::HasUserDefinedConvert<T>::value &&
+ !HasAbslStringify<T>::value && std::is_enum<T>::value>::type> {
+ using type = decltype(+typename std::underlying_type<T>::type());
};
public:
@@ -488,7 +621,7 @@
E template bool FormatArgImpl::Dispatch<T>(Data, FormatConversionSpecImpl, \
void*)
-#define ABSL_INTERNAL_FORMAT_DISPATCH_OVERLOADS_EXPAND_(...) \
+#define ABSL_INTERNAL_FORMAT_DISPATCH_OVERLOADS_EXPAND_NO_WSTRING_VIEW_(...) \
ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(str_format_internal::VoidPtr, \
__VA_ARGS__); \
ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(bool, __VA_ARGS__); \
@@ -514,7 +647,19 @@
ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(long double, __VA_ARGS__); \
ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(const char*, __VA_ARGS__); \
ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(std::string, __VA_ARGS__); \
- ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(string_view, __VA_ARGS__)
+ ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(string_view, __VA_ARGS__); \
+ ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(const wchar_t*, __VA_ARGS__); \
+ ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(std::wstring, __VA_ARGS__)
+
+#if defined(ABSL_HAVE_STD_STRING_VIEW)
+#define ABSL_INTERNAL_FORMAT_DISPATCH_OVERLOADS_EXPAND_(...) \
+ ABSL_INTERNAL_FORMAT_DISPATCH_OVERLOADS_EXPAND_NO_WSTRING_VIEW_( \
+ __VA_ARGS__); \
+ ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(std::wstring_view, __VA_ARGS__)
+#else
+#define ABSL_INTERNAL_FORMAT_DISPATCH_OVERLOADS_EXPAND_(...) \
+ ABSL_INTERNAL_FORMAT_DISPATCH_OVERLOADS_EXPAND_NO_WSTRING_VIEW_(__VA_ARGS__)
+#endif
ABSL_INTERNAL_FORMAT_DISPATCH_OVERLOADS_EXPAND_(extern);
diff --git a/absl/strings/internal/str_format/arg_test.cc b/absl/strings/internal/str_format/arg_test.cc
index 1261937..f663d7c 100644
--- a/absl/strings/internal/str_format/arg_test.cc
+++ b/absl/strings/internal/str_format/arg_test.cc
@@ -14,9 +14,10 @@
#include "absl/strings/internal/str_format/arg.h"
-#include <ostream>
+#include <limits>
#include <string>
#include "gtest/gtest.h"
+#include "absl/base/config.h"
#include "absl/strings/str_format.h"
namespace absl {
@@ -93,6 +94,21 @@
FormatArgImplFriend::GetVTablePtrForTest(FormatArgImpl(kMyArray)));
}
+extern const wchar_t kMyWCharTArray[];
+
+TEST_F(FormatArgImplTest, WCharTArraysDecayToWCharTPtr) {
+ const wchar_t* a = L"";
+ EXPECT_EQ(FormatArgImplFriend::GetVTablePtrForTest(FormatArgImpl(a)),
+ FormatArgImplFriend::GetVTablePtrForTest(FormatArgImpl(L"")));
+ EXPECT_EQ(FormatArgImplFriend::GetVTablePtrForTest(FormatArgImpl(a)),
+ FormatArgImplFriend::GetVTablePtrForTest(FormatArgImpl(L"A")));
+ EXPECT_EQ(FormatArgImplFriend::GetVTablePtrForTest(FormatArgImpl(a)),
+ FormatArgImplFriend::GetVTablePtrForTest(FormatArgImpl(L"ABC")));
+ EXPECT_EQ(
+ FormatArgImplFriend::GetVTablePtrForTest(FormatArgImpl(a)),
+ FormatArgImplFriend::GetVTablePtrForTest(FormatArgImpl(kMyWCharTArray)));
+}
+
TEST_F(FormatArgImplTest, OtherPtrDecayToVoidPtr) {
auto expected = FormatArgImplFriend::GetVTablePtrForTest(
FormatArgImpl(static_cast<void *>(nullptr)));
@@ -124,6 +140,22 @@
}
const char kMyArray[] = "ABCDE";
+TEST_F(FormatArgImplTest, WorksWithWCharTArraysOfUnknownSize) {
+ std::string s;
+ FormatSinkImpl sink(&s);
+ FormatConversionSpecImpl conv;
+ FormatConversionSpecImplFriend::SetConversionChar(
+ FormatConversionCharInternal::s, &conv);
+ FormatConversionSpecImplFriend::SetFlags(Flags(), &conv);
+ FormatConversionSpecImplFriend::SetWidth(-1, &conv);
+ FormatConversionSpecImplFriend::SetPrecision(-1, &conv);
+ EXPECT_TRUE(
+ FormatArgImplFriend::Convert(FormatArgImpl(kMyWCharTArray), conv, &sink));
+ sink.Flush();
+ EXPECT_EQ("ABCDE", s);
+}
+const wchar_t kMyWCharTArray[] = L"ABCDE";
+
} // namespace
} // namespace str_format_internal
ABSL_NAMESPACE_END
diff --git a/absl/strings/internal/str_format/bind.cc b/absl/strings/internal/str_format/bind.cc
index c988ba8..87e23b5 100644
--- a/absl/strings/internal/str_format/bind.cc
+++ b/absl/strings/internal/str_format/bind.cc
@@ -14,10 +14,24 @@
#include "absl/strings/internal/str_format/bind.h"
+#include <algorithm>
+#include <cassert>
#include <cerrno>
+#include <cstddef>
+#include <cstdio>
+#include <ios>
#include <limits>
+#include <ostream>
#include <sstream>
#include <string>
+#include "absl/base/config.h"
+#include "absl/base/optimization.h"
+#include "absl/strings/internal/str_format/arg.h"
+#include "absl/strings/internal/str_format/constexpr_parser.h"
+#include "absl/strings/internal/str_format/extension.h"
+#include "absl/strings/internal/str_format/output.h"
+#include "absl/strings/string_view.h"
+#include "absl/types/span.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
@@ -32,7 +46,8 @@
return false;
}
// -1 because positions are 1-based
- return FormatArgImplFriend::ToInt(pack[position - 1], value);
+ return FormatArgImplFriend::ToInt(pack[static_cast<size_t>(position) - 1],
+ value);
}
class ArgContext {
@@ -56,7 +71,7 @@
const FormatArgImpl* arg = nullptr;
int arg_position = unbound->arg_position;
if (static_cast<size_t>(arg_position - 1) >= pack_.size()) return false;
- arg = &pack_[arg_position - 1]; // 1-based
+ arg = &pack_[static_cast<size_t>(arg_position - 1)]; // 1-based
if (unbound->flags != Flags::kBasic) {
int width = unbound->width.value();
@@ -89,6 +104,8 @@
} else {
FormatConversionSpecImplFriend::SetFlags(unbound->flags, bound);
}
+
+ FormatConversionSpecImplFriend::SetLengthMod(unbound->length_mod, bound);
} else {
FormatConversionSpecImplFriend::SetFlags(unbound->flags, bound);
FormatConversionSpecImplFriend::SetWidth(-1, bound);
@@ -214,7 +231,7 @@
return *out;
}
-std::string FormatPack(const UntypedFormatSpecImpl format,
+std::string FormatPack(UntypedFormatSpecImpl format,
absl::Span<const FormatArgImpl> args) {
std::string out;
if (ABSL_PREDICT_FALSE(!FormatUntyped(&out, format, args))) {
diff --git a/absl/strings/internal/str_format/bind.h b/absl/strings/internal/str_format/bind.h
index 267cc0e..120bc35 100644
--- a/absl/strings/internal/str_format/bind.h
+++ b/absl/strings/internal/str_format/bind.h
@@ -15,16 +15,21 @@
#ifndef ABSL_STRINGS_INTERNAL_STR_FORMAT_BIND_H_
#define ABSL_STRINGS_INTERNAL_STR_FORMAT_BIND_H_
-#include <array>
+#include <cassert>
#include <cstdio>
-#include <sstream>
+#include <ostream>
#include <string>
-#include "absl/base/port.h"
+#include "absl/base/config.h"
+#include "absl/container/inlined_vector.h"
#include "absl/strings/internal/str_format/arg.h"
#include "absl/strings/internal/str_format/checker.h"
+#include "absl/strings/internal/str_format/constexpr_parser.h"
+#include "absl/strings/internal/str_format/extension.h"
#include "absl/strings/internal/str_format/parser.h"
+#include "absl/strings/string_view.h"
#include "absl/types/span.h"
+#include "absl/utility/utility.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
@@ -87,6 +92,36 @@
: public MakeDependent<UntypedFormatSpec, Args...>::type {
using Base = typename MakeDependent<UntypedFormatSpec, Args...>::type;
+ template <bool res>
+ struct ErrorMaker {
+ constexpr bool operator()(int) const { return res; }
+ };
+
+ template <int i, int j>
+ static constexpr bool CheckArity(ErrorMaker<true> SpecifierCount = {},
+ ErrorMaker<i == j> ParametersPassed = {}) {
+ static_assert(SpecifierCount(i) == ParametersPassed(j),
+ "Number of arguments passed must match the number of "
+ "conversion specifiers.");
+ return true;
+ }
+
+ template <FormatConversionCharSet specified, FormatConversionCharSet passed,
+ int arg>
+ static constexpr bool CheckMatch(
+ ErrorMaker<Contains(specified, passed)> MismatchedArgumentNumber = {}) {
+ static_assert(MismatchedArgumentNumber(arg),
+ "Passed argument must match specified format.");
+ return true;
+ }
+
+ template <FormatConversionCharSet... C, size_t... I>
+ static bool CheckMatches(absl::index_sequence<I...>) {
+ bool res[] = {true, CheckMatch<Args, C, I + 1>()...};
+ (void)res;
+ return true;
+ }
+
public:
#ifdef ABSL_INTERNAL_ENABLE_FORMAT_CHECKER
@@ -100,7 +135,7 @@
// We use the 'unavailable' attribute to give a better compiler error than
// just 'method is deleted'.
// To avoid checking the format twice, we just check that the format is
- // constexpr. If is it valid, then the overload below will kick in.
+ // constexpr. If it is valid, then the overload below will kick in.
// We add the template here to make this overload have lower priority.
template <typename = void>
FormatSpecTemplate(const char* s) // NOLINT
@@ -112,7 +147,8 @@
template <typename T = void>
FormatSpecTemplate(string_view s) // NOLINT
__attribute__((enable_if(str_format_internal::EnsureConstexpr(s),
- "constexpr trap"))) {
+ "constexpr trap")))
+ : Base("to avoid noise in the compiler error") {
static_assert(sizeof(T*) == 0,
"Format specified does not match the arguments passed.");
}
@@ -133,30 +169,19 @@
#endif // ABSL_INTERNAL_ENABLE_FORMAT_CHECKER
- template <
- FormatConversionCharSet... C,
- typename = typename std::enable_if<sizeof...(C) == sizeof...(Args)>::type,
- typename = typename std::enable_if<AllOf(Contains(Args,
- C)...)>::type>
+ template <FormatConversionCharSet... C>
FormatSpecTemplate(const ExtendedParsedFormat<C...>& pc) // NOLINT
- : Base(&pc) {}
+ : Base(&pc) {
+ CheckArity<sizeof...(C), sizeof...(Args)>();
+ CheckMatches<C...>(absl::make_index_sequence<sizeof...(C)>{});
+ }
};
class Streamable {
public:
Streamable(const UntypedFormatSpecImpl& format,
absl::Span<const FormatArgImpl> args)
- : format_(format) {
- if (args.size() <= ABSL_ARRAYSIZE(few_args_)) {
- for (size_t i = 0; i < args.size(); ++i) {
- few_args_[i] = args[i];
- }
- args_ = absl::MakeSpan(few_args_, args.size());
- } else {
- many_args_.assign(args.begin(), args.end());
- args_ = many_args_;
- }
- }
+ : format_(format), args_(args.begin(), args.end()) {}
std::ostream& Print(std::ostream& os) const;
@@ -166,12 +191,7 @@
private:
const UntypedFormatSpecImpl& format_;
- absl::Span<const FormatArgImpl> args_;
- // if args_.size() is 4 or less:
- FormatArgImpl few_args_[4] = {FormatArgImpl(0), FormatArgImpl(0),
- FormatArgImpl(0), FormatArgImpl(0)};
- // if args_.size() is more than 4:
- std::vector<FormatArgImpl> many_args_;
+ absl::InlinedVector<FormatArgImpl, 4> args_;
};
// for testing
@@ -180,14 +200,13 @@
bool BindWithPack(const UnboundConversion* props,
absl::Span<const FormatArgImpl> pack, BoundConversion* bound);
-bool FormatUntyped(FormatRawSinkImpl raw_sink,
- UntypedFormatSpecImpl format,
+bool FormatUntyped(FormatRawSinkImpl raw_sink, UntypedFormatSpecImpl format,
absl::Span<const FormatArgImpl> args);
std::string& AppendPack(std::string* out, UntypedFormatSpecImpl format,
absl::Span<const FormatArgImpl> args);
-std::string FormatPack(const UntypedFormatSpecImpl format,
+std::string FormatPack(UntypedFormatSpecImpl format,
absl::Span<const FormatArgImpl> args);
int FprintF(std::FILE* output, UntypedFormatSpecImpl format,
@@ -200,13 +219,14 @@
template <typename T>
class StreamedWrapper {
public:
- explicit StreamedWrapper(const T& v) : v_(v) { }
+ explicit StreamedWrapper(const T& v) : v_(v) {}
private:
template <typename S>
- friend ArgConvertResult<FormatConversionCharSetInternal::s> FormatConvertImpl(
- const StreamedWrapper<S>& v, FormatConversionSpecImpl conv,
- FormatSinkImpl* out);
+ friend ArgConvertResult<FormatConversionCharSetUnion(
+ FormatConversionCharSetInternal::s, FormatConversionCharSetInternal::v)>
+ FormatConvertImpl(const StreamedWrapper<S>& v, FormatConversionSpecImpl conv,
+ FormatSinkImpl* out);
const T& v_;
};
diff --git a/absl/strings/internal/str_format/checker.h b/absl/strings/internal/str_format/checker.h
index 2a2601e..eab6ab9 100644
--- a/absl/strings/internal/str_format/checker.h
+++ b/absl/strings/internal/str_format/checker.h
@@ -15,313 +15,80 @@
#ifndef ABSL_STRINGS_INTERNAL_STR_FORMAT_CHECKER_H_
#define ABSL_STRINGS_INTERNAL_STR_FORMAT_CHECKER_H_
+#include <algorithm>
+
#include "absl/base/attributes.h"
#include "absl/strings/internal/str_format/arg.h"
+#include "absl/strings/internal/str_format/constexpr_parser.h"
#include "absl/strings/internal/str_format/extension.h"
// Compile time check support for entry points.
#ifndef ABSL_INTERNAL_ENABLE_FORMAT_CHECKER
-#if ABSL_HAVE_ATTRIBUTE(enable_if) && !defined(__native_client__)
+// 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__)
#define ABSL_INTERNAL_ENABLE_FORMAT_CHECKER 1
-#endif // ABSL_HAVE_ATTRIBUTE(enable_if) && !defined(__native_client__)
+#endif // ABSL_HAVE_ATTRIBUTE(enable_if) && !defined(__native_client__) &&
+ // !defined(__INTELLISENSE__)
#endif // ABSL_INTERNAL_ENABLE_FORMAT_CHECKER
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace str_format_internal {
-constexpr bool AllOf() { return true; }
-
-template <typename... T>
-constexpr bool AllOf(bool b, T... t) {
- return b && AllOf(t...);
-}
-
#ifdef ABSL_INTERNAL_ENABLE_FORMAT_CHECKER
-constexpr bool ContainsChar(const char* chars, char c) {
- return *chars == c || (*chars && ContainsChar(chars + 1, c));
-}
-
-// A constexpr compatible list of Convs.
-struct ConvList {
- const FormatConversionCharSet* array;
- int count;
-
- // We do the bound check here to avoid having to do it on the callers.
- // Returning an empty FormatConversionCharSet has the same effect as
- // short circuiting because it will never match any conversion.
- constexpr FormatConversionCharSet operator[](int i) const {
- return i < count ? array[i] : FormatConversionCharSet{};
- }
-
- constexpr ConvList without_front() const {
- return count != 0 ? ConvList{array + 1, count - 1} : *this;
- }
-};
-
-template <size_t count>
-struct ConvListT {
- // Make sure the array has size > 0.
- FormatConversionCharSet list[count ? count : 1];
-};
-
-constexpr char GetChar(string_view str, size_t index) {
- return index < str.size() ? str[index] : char{};
-}
-
-constexpr string_view ConsumeFront(string_view str, size_t len = 1) {
- return len <= str.size() ? string_view(str.data() + len, str.size() - len)
- : string_view();
-}
-
-constexpr string_view ConsumeAnyOf(string_view format, const char* chars) {
- return ContainsChar(chars, GetChar(format, 0))
- ? ConsumeAnyOf(ConsumeFront(format), chars)
- : format;
-}
-
-constexpr bool IsDigit(char c) { return c >= '0' && c <= '9'; }
-
-// Helper class for the ParseDigits function.
-// It encapsulates the two return values we need there.
-struct Integer {
- string_view format;
- int value;
-
- // If the next character is a '$', consume it.
- // Otherwise, make `this` an invalid positional argument.
- constexpr Integer ConsumePositionalDollar() const {
- return GetChar(format, 0) == '$' ? Integer{ConsumeFront(format), value}
- : Integer{format, 0};
- }
-};
-
-constexpr Integer ParseDigits(string_view format, int value = 0) {
- return IsDigit(GetChar(format, 0))
- ? ParseDigits(ConsumeFront(format),
- 10 * value + GetChar(format, 0) - '0')
- : Integer{format, value};
-}
-
-// Parse digits for a positional argument.
-// The parsing also consumes the '$'.
-constexpr Integer ParsePositional(string_view format) {
- return ParseDigits(format).ConsumePositionalDollar();
-}
-
-// Parses a single conversion specifier.
-// See ConvParser::Run() for post conditions.
-class ConvParser {
- constexpr ConvParser SetFormat(string_view format) const {
- return ConvParser(format, args_, error_, arg_position_, is_positional_);
- }
-
- constexpr ConvParser SetArgs(ConvList args) const {
- return ConvParser(format_, args, error_, arg_position_, is_positional_);
- }
-
- constexpr ConvParser SetError(bool error) const {
- return ConvParser(format_, args_, error_ || error, arg_position_,
- is_positional_);
- }
-
- constexpr ConvParser SetArgPosition(int arg_position) const {
- return ConvParser(format_, args_, error_, arg_position, is_positional_);
- }
-
- // Consumes the next arg and verifies that it matches `conv`.
- // `error_` is set if there is no next arg or if it doesn't match `conv`.
- constexpr ConvParser ConsumeNextArg(char conv) const {
- return SetArgs(args_.without_front()).SetError(!Contains(args_[0], conv));
- }
-
- // Verify that positional argument `i.value` matches `conv`.
- // `error_` is set if `i.value` is not a valid argument or if it doesn't
- // match.
- constexpr ConvParser VerifyPositional(Integer i, char conv) const {
- return SetFormat(i.format).SetError(!Contains(args_[i.value - 1], conv));
- }
-
- // Parse the position of the arg and store it in `arg_position_`.
- constexpr ConvParser ParseArgPosition(Integer arg) const {
- return SetFormat(arg.format).SetArgPosition(arg.value);
- }
-
- // Consume the flags.
- constexpr ConvParser ParseFlags() const {
- return SetFormat(ConsumeAnyOf(format_, "-+ #0"));
- }
-
- // Consume the width.
- // If it is '*', we verify that it matches `args_`. `error_` is set if it
- // doesn't match.
- constexpr ConvParser ParseWidth() const {
- return IsDigit(GetChar(format_, 0))
- ? SetFormat(ParseDigits(format_).format)
- : GetChar(format_, 0) == '*'
- ? is_positional_
- ? VerifyPositional(
- ParsePositional(ConsumeFront(format_)), '*')
- : SetFormat(ConsumeFront(format_))
- .ConsumeNextArg('*')
- : *this;
- }
-
- // Consume the precision.
- // If it is '*', we verify that it matches `args_`. `error_` is set if it
- // doesn't match.
- constexpr ConvParser ParsePrecision() const {
- return GetChar(format_, 0) != '.'
- ? *this
- : GetChar(format_, 1) == '*'
- ? is_positional_
- ? VerifyPositional(
- ParsePositional(ConsumeFront(format_, 2)), '*')
- : SetFormat(ConsumeFront(format_, 2))
- .ConsumeNextArg('*')
- : SetFormat(ParseDigits(ConsumeFront(format_)).format);
- }
-
- // Consume the length characters.
- constexpr ConvParser ParseLength() const {
- return SetFormat(ConsumeAnyOf(format_, "lLhjztq"));
- }
-
- // Consume the conversion character and verify that it matches `args_`.
- // `error_` is set if it doesn't match.
- constexpr ConvParser ParseConversion() const {
- return is_positional_
- ? VerifyPositional({ConsumeFront(format_), arg_position_},
- GetChar(format_, 0))
- : ConsumeNextArg(GetChar(format_, 0))
- .SetFormat(ConsumeFront(format_));
- }
-
- constexpr ConvParser(string_view format, ConvList args, bool error,
- int arg_position, bool is_positional)
- : format_(format),
- args_(args),
- error_(error),
- arg_position_(arg_position),
- is_positional_(is_positional) {}
-
- public:
- constexpr ConvParser(string_view format, ConvList args, bool is_positional)
- : format_(format),
- args_(args),
- error_(false),
- arg_position_(0),
- is_positional_(is_positional) {}
-
- // Consume the whole conversion specifier.
- // `format()` will be set to the character after the conversion character.
- // `error()` will be set if any of the arguments do not match.
- constexpr ConvParser Run() const {
- return (is_positional_ ? ParseArgPosition(ParsePositional(format_)) : *this)
- .ParseFlags()
- .ParseWidth()
- .ParsePrecision()
- .ParseLength()
- .ParseConversion();
- }
-
- constexpr string_view format() const { return format_; }
- constexpr ConvList args() const { return args_; }
- constexpr bool error() const { return error_; }
- constexpr bool is_positional() const { return is_positional_; }
-
- private:
- string_view format_;
- // Current list of arguments. If we are not in positional mode we will consume
- // from the front.
- ConvList args_;
- bool error_;
- // Holds the argument position of the conversion character, if we are in
- // positional mode. Otherwise, it is unspecified.
- int arg_position_;
- // Whether we are in positional mode.
- // It changes the behavior of '*' and where to find the converted argument.
- bool is_positional_;
-};
-
-// Parses a whole format expression.
-// See FormatParser::Run().
-class FormatParser {
- static constexpr bool FoundPercent(string_view format) {
- return format.empty() ||
- (GetChar(format, 0) == '%' && GetChar(format, 1) != '%');
- }
-
- // We use an inner function to increase the recursion limit.
- // The inner function consumes up to `limit` characters on every run.
- // This increases the limit from 512 to ~512*limit.
- static constexpr string_view ConsumeNonPercentInner(string_view format,
- int limit = 20) {
- return FoundPercent(format) || !limit
- ? format
- : ConsumeNonPercentInner(
- ConsumeFront(format, GetChar(format, 0) == '%' &&
- GetChar(format, 1) == '%'
- ? 2
- : 1),
- limit - 1);
- }
-
- // Consume characters until the next conversion spec %.
- // It skips %%.
- static constexpr string_view ConsumeNonPercent(string_view format) {
- return FoundPercent(format)
- ? format
- : ConsumeNonPercent(ConsumeNonPercentInner(format));
- }
-
- static constexpr bool IsPositional(string_view format) {
- return IsDigit(GetChar(format, 0)) ? IsPositional(ConsumeFront(format))
- : GetChar(format, 0) == '$';
- }
-
- constexpr bool RunImpl(bool is_positional) const {
- // In non-positional mode we require all arguments to be consumed.
- // In positional mode just reaching the end of the format without errors is
- // enough.
- return (format_.empty() && (is_positional || args_.count == 0)) ||
- (!format_.empty() &&
- ValidateArg(
- ConvParser(ConsumeFront(format_), args_, is_positional).Run()));
- }
-
- constexpr bool ValidateArg(ConvParser conv) const {
- return !conv.error() && FormatParser(conv.format(), conv.args())
- .RunImpl(conv.is_positional());
- }
-
- public:
- constexpr FormatParser(string_view format, ConvList args)
- : format_(ConsumeNonPercent(format)), args_(args) {}
-
- // Runs the parser for `format` and `args`.
- // It verifies that the format is valid and that all conversion specifiers
- // match the arguments passed.
- // In non-positional mode it also verfies that all arguments are consumed.
- constexpr bool Run() const {
- return RunImpl(!format_.empty() && IsPositional(ConsumeFront(format_)));
- }
-
- private:
- string_view format_;
- // Current list of arguments.
- // If we are not in positional mode we will consume from the front and will
- // have to be empty in the end.
- ConvList args_;
-};
-
template <FormatConversionCharSet... C>
constexpr bool ValidFormatImpl(string_view format) {
- return FormatParser(format,
- {ConvListT<sizeof...(C)>{{C...}}.list, sizeof...(C)})
- .Run();
+ int next_arg = 0;
+ const char* p = format.data();
+ const char* const end = p + format.size();
+ constexpr FormatConversionCharSet
+ kAllowedConvs[(std::max)(sizeof...(C), size_t{1})] = {C...};
+ bool used[(std::max)(sizeof...(C), size_t{1})]{};
+ constexpr int kNumArgs = sizeof...(C);
+ while (p != end) {
+ while (p != end && *p != '%') ++p;
+ if (p == end) {
+ break;
+ }
+ if (p + 1 >= end) return false;
+ if (p[1] == '%') {
+ // %%
+ p += 2;
+ continue;
+ }
+
+ UnboundConversion conv(absl::kConstInit);
+ p = ConsumeUnboundConversion(p + 1, end, &conv, &next_arg);
+ if (p == nullptr) return false;
+ if (conv.arg_position <= 0 || conv.arg_position > kNumArgs) {
+ return false;
+ }
+ if (!Contains(kAllowedConvs[conv.arg_position - 1], conv.conv)) {
+ return false;
+ }
+ used[conv.arg_position - 1] = true;
+ for (auto extra : {conv.width, conv.precision}) {
+ if (extra.is_from_arg()) {
+ int pos = extra.get_from_arg();
+ if (pos <= 0 || pos > kNumArgs) return false;
+ used[pos - 1] = true;
+ if (!Contains(kAllowedConvs[pos - 1], '*')) {
+ return false;
+ }
+ }
+ }
+ }
+ if (sizeof...(C) != 0) {
+ for (bool b : used) {
+ if (!b) return false;
+ }
+ }
+ return true;
}
#endif // ABSL_INTERNAL_ENABLE_FORMAT_CHECKER
diff --git a/absl/strings/internal/str_format/checker_test.cc b/absl/strings/internal/str_format/checker_test.cc
index 7c70f47..a86bed3 100644
--- a/absl/strings/internal/str_format/checker_test.cc
+++ b/absl/strings/internal/str_format/checker_test.cc
@@ -39,16 +39,16 @@
TEST(StrFormatChecker, ArgumentToConv) {
FormatConversionCharSet conv = ArgumentToConv<std::string>();
- EXPECT_EQ(ConvToString(conv), "s");
+ EXPECT_EQ(ConvToString(conv), "sv");
conv = ArgumentToConv<const char*>();
EXPECT_EQ(ConvToString(conv), "sp");
conv = ArgumentToConv<double>();
- EXPECT_EQ(ConvToString(conv), "fFeEgGaA");
+ EXPECT_EQ(ConvToString(conv), "fFeEgGaAv");
conv = ArgumentToConv<int>();
- EXPECT_EQ(ConvToString(conv), "cdiouxXfFeEgGaA*");
+ EXPECT_EQ(ConvToString(conv), "cdiouxXfFeEgGaAv*");
conv = ArgumentToConv<std::string*>();
EXPECT_EQ(ConvToString(conv), "p");
@@ -93,6 +93,7 @@
ValidFormat<void (*)(), volatile int*>("%p %p"), //
ValidFormat<string_view, const char*, double, void*>(
"string_view=%s const char*=%s double=%f void*=%p)"),
+ ValidFormat<int>("%v"), //
ValidFormat<int>("%% %1$d"), //
ValidFormat<int>("%1$ld"), //
@@ -109,7 +110,9 @@
ValidFormat<int, double>("%2$.*1$f"), //
ValidFormat<void*, string_view, const char*, double>(
"string_view=%2$s const char*=%3$s double=%4$f void*=%1$p "
- "repeat=%3$s)")};
+ "repeat=%3$s)"),
+ ValidFormat<std::string>("%1$v"),
+ };
for (Case c : trues) {
EXPECT_TRUE(c.result) << c.format;
@@ -130,6 +133,8 @@
ValidFormat<int>("%*d"), //
ValidFormat<std::string>("%p"), //
ValidFormat<int (*)(int)>("%d"), //
+ ValidFormat<int>("%1v"), //
+ ValidFormat<int>("%.1v"), //
ValidFormat<>("%3$d"), //
ValidFormat<>("%1$r"), //
@@ -138,13 +143,14 @@
ValidFormat<int>("%1$*2$1d"), //
ValidFormat<int>("%1$1-d"), //
ValidFormat<std::string, int>("%2$*1$s"), //
- ValidFormat<std::string>("%1$p"),
+ ValidFormat<std::string>("%1$p"), //
+ ValidFormat<int>("%1$*2$v"), //
ValidFormat<int, int>("%d %2$d"), //
};
for (Case c : falses) {
- EXPECT_FALSE(c.result) << c.format;
+ EXPECT_FALSE(c.result) << "format<" << c.format << ">";
}
}
diff --git a/absl/strings/internal/str_format/constexpr_parser.h b/absl/strings/internal/str_format/constexpr_parser.h
new file mode 100644
index 0000000..8f59387
--- /dev/null
+++ b/absl/strings/internal/str_format/constexpr_parser.h
@@ -0,0 +1,357 @@
+// Copyright 2022 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef ABSL_STRINGS_INTERNAL_STR_FORMAT_CONSTEXPR_PARSER_H_
+#define ABSL_STRINGS_INTERNAL_STR_FORMAT_CONSTEXPR_PARSER_H_
+
+#include <cassert>
+#include <cstdint>
+#include <cstdio>
+#include <limits>
+
+#include "absl/base/config.h"
+#include "absl/base/const_init.h"
+#include "absl/base/optimization.h"
+#include "absl/strings/internal/str_format/extension.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace str_format_internal {
+
+// The analyzed properties of a single specified conversion.
+struct UnboundConversion {
+ // This is a user defined default constructor on purpose to skip the
+ // initialization of parts of the object that are not necessary.
+ UnboundConversion() {} // NOLINT
+
+ // This constructor is provided for the static checker. We don't want to do
+ // the unnecessary initialization in the normal case.
+ explicit constexpr UnboundConversion(absl::ConstInitType)
+ : arg_position{}, width{}, precision{} {}
+
+ class InputValue {
+ public:
+ constexpr void set_value(int value) {
+ assert(value >= 0);
+ value_ = value;
+ }
+ constexpr int value() const { return value_; }
+
+ // Marks the value as "from arg". aka the '*' format.
+ // Requires `value >= 1`.
+ // When set, is_from_arg() return true and get_from_arg() returns the
+ // original value.
+ // `value()`'s return value is unspecified in this state.
+ constexpr void set_from_arg(int value) {
+ assert(value > 0);
+ value_ = -value - 1;
+ }
+ constexpr bool is_from_arg() const { return value_ < -1; }
+ constexpr int get_from_arg() const {
+ assert(is_from_arg());
+ return -value_ - 1;
+ }
+
+ private:
+ int value_ = -1;
+ };
+
+ // No need to initialize. It will always be set in the parser.
+ int arg_position;
+
+ InputValue width;
+ InputValue precision;
+
+ Flags flags = Flags::kBasic;
+ LengthMod length_mod = LengthMod::none;
+ FormatConversionChar conv = FormatConversionCharInternal::kNone;
+};
+
+// Helper tag class for the table below.
+// It allows fast `char -> ConversionChar/LengthMod/Flags` checking and
+// conversions.
+class ConvTag {
+ public:
+ constexpr ConvTag(FormatConversionChar conversion_char) // NOLINT
+ : tag_(static_cast<uint8_t>(conversion_char)) {}
+ constexpr ConvTag(LengthMod length_mod) // NOLINT
+ : tag_(0x80 | static_cast<uint8_t>(length_mod)) {}
+ constexpr ConvTag(Flags flags) // NOLINT
+ : tag_(0xc0 | static_cast<uint8_t>(flags)) {}
+ constexpr ConvTag() : tag_(0xFF) {}
+
+ constexpr bool is_conv() const { return (tag_ & 0x80) == 0; }
+ constexpr bool is_length() const { return (tag_ & 0xC0) == 0x80; }
+ constexpr bool is_flags() const { return (tag_ & 0xE0) == 0xC0; }
+
+ constexpr FormatConversionChar as_conv() const {
+ assert(is_conv());
+ assert(!is_length());
+ assert(!is_flags());
+ return static_cast<FormatConversionChar>(tag_);
+ }
+ constexpr LengthMod as_length() const {
+ assert(!is_conv());
+ assert(is_length());
+ assert(!is_flags());
+ return static_cast<LengthMod>(tag_ & 0x3F);
+ }
+ constexpr Flags as_flags() const {
+ assert(!is_conv());
+ assert(!is_length());
+ assert(is_flags());
+ return static_cast<Flags>(tag_ & 0x1F);
+ }
+
+ private:
+ uint8_t tag_;
+};
+
+struct ConvTagHolder {
+ using CC = FormatConversionCharInternal;
+ using LM = LengthMod;
+
+ // Abbreviations to fit in the table below.
+ static constexpr auto kFSign = Flags::kSignCol;
+ static constexpr auto kFAlt = Flags::kAlt;
+ static constexpr auto kFPos = Flags::kShowPos;
+ static constexpr auto kFLeft = Flags::kLeft;
+ static constexpr auto kFZero = Flags::kZero;
+
+ static constexpr ConvTag value[256] = {
+ {}, {}, {}, {}, {}, {}, {}, {}, // 00-07
+ {}, {}, {}, {}, {}, {}, {}, {}, // 08-0f
+ {}, {}, {}, {}, {}, {}, {}, {}, // 10-17
+ {}, {}, {}, {}, {}, {}, {}, {}, // 18-1f
+ kFSign, {}, {}, kFAlt, {}, {}, {}, {}, // !"#$%&'
+ {}, {}, {}, kFPos, {}, kFLeft, {}, {}, // ()*+,-./
+ kFZero, {}, {}, {}, {}, {}, {}, {}, // 01234567
+ {}, {}, {}, {}, {}, {}, {}, {}, // 89:;<=>?
+ {}, CC::A, {}, {}, {}, CC::E, CC::F, CC::G, // @ABCDEFG
+ {}, {}, {}, {}, LM::L, {}, {}, {}, // HIJKLMNO
+ {}, {}, {}, {}, {}, {}, {}, {}, // PQRSTUVW
+ CC::X, {}, {}, {}, {}, {}, {}, {}, // XYZ[\]^_
+ {}, CC::a, {}, CC::c, CC::d, CC::e, CC::f, CC::g, // `abcdefg
+ LM::h, CC::i, LM::j, {}, LM::l, {}, CC::n, CC::o, // hijklmno
+ CC::p, LM::q, {}, CC::s, LM::t, CC::u, CC::v, {}, // pqrstuvw
+ CC::x, {}, LM::z, {}, {}, {}, {}, {}, // xyz{|}!
+ {}, {}, {}, {}, {}, {}, {}, {}, // 80-87
+ {}, {}, {}, {}, {}, {}, {}, {}, // 88-8f
+ {}, {}, {}, {}, {}, {}, {}, {}, // 90-97
+ {}, {}, {}, {}, {}, {}, {}, {}, // 98-9f
+ {}, {}, {}, {}, {}, {}, {}, {}, // a0-a7
+ {}, {}, {}, {}, {}, {}, {}, {}, // a8-af
+ {}, {}, {}, {}, {}, {}, {}, {}, // b0-b7
+ {}, {}, {}, {}, {}, {}, {}, {}, // b8-bf
+ {}, {}, {}, {}, {}, {}, {}, {}, // c0-c7
+ {}, {}, {}, {}, {}, {}, {}, {}, // c8-cf
+ {}, {}, {}, {}, {}, {}, {}, {}, // d0-d7
+ {}, {}, {}, {}, {}, {}, {}, {}, // d8-df
+ {}, {}, {}, {}, {}, {}, {}, {}, // e0-e7
+ {}, {}, {}, {}, {}, {}, {}, {}, // e8-ef
+ {}, {}, {}, {}, {}, {}, {}, {}, // f0-f7
+ {}, {}, {}, {}, {}, {}, {}, {}, // f8-ff
+ };
+};
+
+// Keep a single table for all the conversion chars and length modifiers.
+constexpr ConvTag GetTagForChar(char c) {
+ return ConvTagHolder::value[static_cast<unsigned char>(c)];
+}
+
+constexpr bool CheckFastPathSetting(const UnboundConversion& conv) {
+ bool width_precision_needed =
+ conv.width.value() >= 0 || conv.precision.value() >= 0;
+ if (width_precision_needed && conv.flags == Flags::kBasic) {
+#if defined(__clang__)
+ // Some compilers complain about this in constexpr even when not executed,
+ // so only enable the error dump in clang.
+ fprintf(stderr,
+ "basic=%d left=%d show_pos=%d sign_col=%d alt=%d zero=%d "
+ "width=%d precision=%d\n",
+ conv.flags == Flags::kBasic ? 1 : 0,
+ FlagsContains(conv.flags, Flags::kLeft) ? 1 : 0,
+ FlagsContains(conv.flags, Flags::kShowPos) ? 1 : 0,
+ FlagsContains(conv.flags, Flags::kSignCol) ? 1 : 0,
+ FlagsContains(conv.flags, Flags::kAlt) ? 1 : 0,
+ FlagsContains(conv.flags, Flags::kZero) ? 1 : 0, conv.width.value(),
+ conv.precision.value());
+#endif // defined(__clang__)
+ return false;
+ }
+ return true;
+}
+
+constexpr int ParseDigits(char& c, const char*& pos, const char* const end) {
+ int digits = c - '0';
+ // We do not want to overflow `digits` so we consume at most digits10
+ // digits. If there are more digits the parsing will fail later on when the
+ // digit doesn't match the expected characters.
+ int num_digits = std::numeric_limits<int>::digits10;
+ for (;;) {
+ if (ABSL_PREDICT_FALSE(pos == end)) break;
+ c = *pos++;
+ if ('0' > c || c > '9') break;
+ --num_digits;
+ if (ABSL_PREDICT_FALSE(!num_digits)) break;
+ digits = 10 * digits + c - '0';
+ }
+ return digits;
+}
+
+template <bool is_positional>
+constexpr const char* ConsumeConversion(const char* pos, const char* const end,
+ UnboundConversion* conv,
+ int* next_arg) {
+ const char* const original_pos = pos;
+ char c = 0;
+ // Read the next char into `c` and update `pos`. Returns false if there are
+ // no more chars to read.
+#define ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR() \
+ do { \
+ if (ABSL_PREDICT_FALSE(pos == end)) return nullptr; \
+ c = *pos++; \
+ } while (0)
+
+ if (is_positional) {
+ ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR();
+ if (ABSL_PREDICT_FALSE(c < '1' || c > '9')) return nullptr;
+ conv->arg_position = ParseDigits(c, pos, end);
+ assert(conv->arg_position > 0);
+ if (ABSL_PREDICT_FALSE(c != '$')) return nullptr;
+ }
+
+ ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR();
+
+ // We should start with the basic flag on.
+ assert(conv->flags == Flags::kBasic);
+
+ // Any non alpha character makes this conversion not basic.
+ // This includes flags (-+ #0), width (1-9, *) or precision (.).
+ // All conversion characters and length modifiers are alpha characters.
+ if (c < 'A') {
+ while (c <= '0') {
+ auto tag = GetTagForChar(c);
+ if (tag.is_flags()) {
+ conv->flags = conv->flags | tag.as_flags();
+ ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR();
+ } else {
+ break;
+ }
+ }
+
+ if (c <= '9') {
+ if (c >= '0') {
+ int maybe_width = ParseDigits(c, pos, end);
+ if (!is_positional && c == '$') {
+ if (ABSL_PREDICT_FALSE(*next_arg != 0)) return nullptr;
+ // Positional conversion.
+ *next_arg = -1;
+ return ConsumeConversion<true>(original_pos, end, conv, next_arg);
+ }
+ conv->flags = conv->flags | Flags::kNonBasic;
+ conv->width.set_value(maybe_width);
+ } else if (c == '*') {
+ conv->flags = conv->flags | Flags::kNonBasic;
+ ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR();
+ if (is_positional) {
+ if (ABSL_PREDICT_FALSE(c < '1' || c > '9')) return nullptr;
+ conv->width.set_from_arg(ParseDigits(c, pos, end));
+ if (ABSL_PREDICT_FALSE(c != '$')) return nullptr;
+ ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR();
+ } else {
+ conv->width.set_from_arg(++*next_arg);
+ }
+ }
+ }
+
+ if (c == '.') {
+ conv->flags = conv->flags | Flags::kNonBasic;
+ ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR();
+ if ('0' <= c && c <= '9') {
+ conv->precision.set_value(ParseDigits(c, pos, end));
+ } else if (c == '*') {
+ ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR();
+ if (is_positional) {
+ if (ABSL_PREDICT_FALSE(c < '1' || c > '9')) return nullptr;
+ conv->precision.set_from_arg(ParseDigits(c, pos, end));
+ if (c != '$') return nullptr;
+ ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR();
+ } else {
+ conv->precision.set_from_arg(++*next_arg);
+ }
+ } else {
+ conv->precision.set_value(0);
+ }
+ }
+ }
+
+ auto tag = GetTagForChar(c);
+
+ if (ABSL_PREDICT_FALSE(c == 'v' && conv->flags != Flags::kBasic)) {
+ return nullptr;
+ }
+
+ if (ABSL_PREDICT_FALSE(!tag.is_conv())) {
+ if (ABSL_PREDICT_FALSE(!tag.is_length())) return nullptr;
+
+ // It is a length modifier.
+ LengthMod length_mod = tag.as_length();
+ ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR();
+ if (c == 'h' && length_mod == LengthMod::h) {
+ conv->length_mod = LengthMod::hh;
+ ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR();
+ } else if (c == 'l' && length_mod == LengthMod::l) {
+ conv->length_mod = LengthMod::ll;
+ ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR();
+ } else {
+ conv->length_mod = length_mod;
+ }
+ tag = GetTagForChar(c);
+
+ if (ABSL_PREDICT_FALSE(c == 'v')) return nullptr;
+ if (ABSL_PREDICT_FALSE(!tag.is_conv())) return nullptr;
+
+ // `wchar_t` args are marked non-basic so `Bind()` will copy the length mod.
+ if (conv->length_mod == LengthMod::l && c == 'c') {
+ conv->flags = conv->flags | Flags::kNonBasic;
+ }
+ }
+#undef ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR
+
+ assert(CheckFastPathSetting(*conv));
+ (void)(&CheckFastPathSetting);
+
+ conv->conv = tag.as_conv();
+ if (!is_positional) conv->arg_position = ++*next_arg;
+ return pos;
+}
+
+// Consume conversion spec prefix (not including '%') of [p, end) if valid.
+// Examples of valid specs would be e.g.: "s", "d", "-12.6f".
+// If valid, it returns the first character following the conversion spec,
+// and the spec part is broken down and returned in 'conv'.
+// If invalid, returns nullptr.
+constexpr const char* ConsumeUnboundConversion(const char* p, const char* end,
+ UnboundConversion* conv,
+ int* next_arg) {
+ if (*next_arg < 0) return ConsumeConversion<true>(p, end, conv, next_arg);
+ return ConsumeConversion<false>(p, end, conv, next_arg);
+}
+
+} // namespace str_format_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_STRINGS_INTERNAL_STR_FORMAT_CONSTEXPR_PARSER_H_
diff --git a/absl/strings/internal/str_format/convert_test.cc b/absl/strings/internal/str_format/convert_test.cc
index 91e0360..7f22277 100644
--- a/absl/strings/internal/str_format/convert_test.cc
+++ b/absl/strings/internal/str_format/convert_test.cc
@@ -12,22 +12,43 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#include <errno.h>
+#include <assert.h>
+#include <locale.h>
#include <stdarg.h>
#include <stdio.h>
-#include <cctype>
+#include <algorithm>
+#include <climits>
#include <cmath>
+#include <cstdlib>
+#include <cstring>
+#include <cwctype>
#include <limits>
+#include <set>
+#include <sstream>
#include <string>
#include <thread> // NOLINT
+#include <type_traits>
+#include <vector>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
+#include "absl/base/attributes.h"
#include "absl/base/internal/raw_logging.h"
+#include "absl/log/log.h"
+#include "absl/numeric/int128.h"
+#include "absl/strings/ascii.h"
+#include "absl/strings/internal/str_format/arg.h"
#include "absl/strings/internal/str_format/bind.h"
#include "absl/strings/match.h"
+#include "absl/strings/str_format.h"
+#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
+#include "absl/types/span.h"
+
+#if defined(ABSL_HAVE_STD_STRING_VIEW)
+#include <string_view>
+#endif
namespace absl {
ABSL_NAMESPACE_BEGIN
@@ -46,36 +67,103 @@
return N;
}
-std::string LengthModFor(float) { return ""; }
-std::string LengthModFor(double) { return ""; }
-std::string LengthModFor(long double) { return "L"; }
-std::string LengthModFor(char) { return "hh"; }
-std::string LengthModFor(signed char) { return "hh"; }
-std::string LengthModFor(unsigned char) { return "hh"; }
-std::string LengthModFor(short) { return "h"; } // NOLINT
-std::string LengthModFor(unsigned short) { return "h"; } // NOLINT
-std::string LengthModFor(int) { return ""; }
-std::string LengthModFor(unsigned) { return ""; }
-std::string LengthModFor(long) { return "l"; } // NOLINT
-std::string LengthModFor(unsigned long) { return "l"; } // NOLINT
-std::string LengthModFor(long long) { return "ll"; } // NOLINT
-std::string LengthModFor(unsigned long long) { return "ll"; } // NOLINT
+template <typename T>
+struct AlwaysFalse : std::false_type {};
+
+template <typename T>
+std::string LengthModFor() {
+ static_assert(AlwaysFalse<T>::value, "Unsupported type");
+ return "";
+}
+template <>
+std::string LengthModFor<char>() {
+ return "hh";
+}
+template <>
+std::string LengthModFor<signed char>() {
+ return "hh";
+}
+template <>
+std::string LengthModFor<unsigned char>() {
+ return "hh";
+}
+template <>
+std::string LengthModFor<short>() { // NOLINT
+ return "h";
+}
+template <>
+std::string LengthModFor<unsigned short>() { // NOLINT
+ return "h";
+}
+template <>
+std::string LengthModFor<int>() {
+ return "";
+}
+template <>
+std::string LengthModFor<unsigned>() {
+ return "";
+}
+template <>
+std::string LengthModFor<long>() { // NOLINT
+ return "l";
+}
+template <>
+std::string LengthModFor<unsigned long>() { // NOLINT
+ return "l";
+}
+template <>
+std::string LengthModFor<long long>() { // NOLINT
+ return "ll";
+}
+template <>
+std::string LengthModFor<unsigned long long>() { // NOLINT
+ return "ll";
+}
+
+// An integral type of the same rank and signedness as `wchar_t`, that isn't
+// `wchar_t`.
+using IntegralTypeForWCharT =
+ std::conditional_t<std::is_signed<wchar_t>::value,
+ // Some STLs are broken and return `wchar_t` from
+ // `std::make_[un]signed_t<wchar_t>` when the signedness
+ // matches. Work around by round-tripping through the
+ // opposite signedness.
+ std::make_signed_t<std::make_unsigned_t<wchar_t>>,
+ std::make_unsigned_t<std::make_signed_t<wchar_t>>>;
+
+// Given an integral type `T`, returns a type of the same rank and signedness
+// that is guaranteed to not be `wchar_t`.
+template <typename T>
+using MatchingIntegralType = std::conditional_t<std::is_same<T, wchar_t>::value,
+ IntegralTypeForWCharT, T>;
std::string EscCharImpl(int v) {
- if (std::isprint(static_cast<unsigned char>(v))) {
- return std::string(1, static_cast<char>(v));
- }
char buf[64];
- int n = snprintf(buf, sizeof(buf), "\\%#.2x",
- static_cast<unsigned>(v & 0xff));
- assert(n > 0 && n < sizeof(buf));
- return std::string(buf, n);
+ int n = absl::ascii_isprint(static_cast<unsigned char>(v))
+ ? snprintf(buf, sizeof(buf), "'%c'", v)
+ : snprintf(buf, sizeof(buf), "'\\x%.*x'", CHAR_BIT / 4,
+ static_cast<unsigned>(
+ static_cast<std::make_unsigned_t<char>>(v)));
+ assert(n > 0 && static_cast<size_t>(n) < sizeof(buf));
+ return std::string(buf, static_cast<size_t>(n));
}
std::string Esc(char v) { return EscCharImpl(v); }
std::string Esc(signed char v) { return EscCharImpl(v); }
std::string Esc(unsigned char v) { return EscCharImpl(v); }
+std::string Esc(wchar_t v) {
+ char buf[64];
+ int n = std::iswprint(static_cast<wint_t>(v))
+ ? snprintf(buf, sizeof(buf), "L'%lc'", static_cast<wint_t>(v))
+ : snprintf(buf, sizeof(buf), "L'\\x%.*llx'",
+ static_cast<int>(sizeof(wchar_t) * CHAR_BIT / 4),
+ static_cast<unsigned long long>(
+ static_cast<std::make_unsigned_t<wchar_t>>(v)));
+ assert(n > 0 && static_cast<size_t>(n) < sizeof(buf));
+ return std::string(buf, static_cast<size_t>(n));
+}
+
template <typename T>
std::string Esc(const T &v) {
std::ostringstream oss;
@@ -98,7 +186,7 @@
if (result < kSpaceLength) {
if (result >= 0) {
// Normal case -- everything fit.
- dst->append(space, result);
+ dst->append(space, static_cast<size_t>(result));
return;
}
if (result < 0) {
@@ -109,7 +197,7 @@
// Increase the buffer size to the size requested by vsnprintf,
// plus one for the closing \0.
- int length = result + 1;
+ size_t length = static_cast<size_t>(result) + 1;
char *buf = new char[length];
// Restore the va_list before we use it again
@@ -117,13 +205,14 @@
result = vsnprintf(buf, length, format, backup_ap);
va_end(backup_ap);
- if (result >= 0 && result < length) {
+ if (result >= 0 && static_cast<size_t>(result) < length) {
// It fit
- dst->append(buf, result);
+ dst->append(buf, static_cast<size_t>(result));
}
delete[] buf;
}
+void StrAppend(std::string *, const char *, ...) ABSL_PRINTF_ATTRIBUTE(2, 3);
void StrAppend(std::string *out, const char *format, ...) {
va_list ap;
va_start(ap, format);
@@ -131,6 +220,7 @@
va_end(ap);
}
+std::string StrPrint(const char *, ...) ABSL_PRINTF_ATTRIBUTE(1, 2);
std::string StrPrint(const char *format, ...) {
va_list ap;
va_start(ap, format);
@@ -226,11 +316,15 @@
TEST_F(FormatConvertTest, BasicString) {
TestStringConvert("hello"); // As char array.
+ TestStringConvert(L"hello");
TestStringConvert(static_cast<const char*>("hello"));
+ TestStringConvert(static_cast<const wchar_t*>(L"hello"));
TestStringConvert(std::string("hello"));
+ TestStringConvert(std::wstring(L"hello"));
TestStringConvert(string_view("hello"));
#if defined(ABSL_HAVE_STD_STRING_VIEW)
TestStringConvert(std::string_view("hello"));
+ TestStringConvert(std::wstring_view(L"hello"));
#endif // ABSL_HAVE_STD_STRING_VIEW
}
@@ -238,6 +332,10 @@
const char* p = nullptr;
UntypedFormatSpecImpl format("%s");
EXPECT_EQ("", FormatPack(format, {FormatArgImpl(p)}));
+
+ const wchar_t* wp = nullptr;
+ UntypedFormatSpecImpl wformat("%ls");
+ EXPECT_EQ("", FormatPack(wformat, {FormatArgImpl(wp)}));
}
TEST_F(FormatConvertTest, StringPrecision) {
@@ -247,10 +345,19 @@
UntypedFormatSpecImpl format("%.1s");
EXPECT_EQ("a", FormatPack(format, {FormatArgImpl(p)}));
+ wchar_t wc = L'a';
+ const wchar_t* wp = &wc;
+ UntypedFormatSpecImpl wformat("%.1ls");
+ EXPECT_EQ("a", FormatPack(wformat, {FormatArgImpl(wp)}));
+
// We cap at the NUL-terminator.
p = "ABC";
UntypedFormatSpecImpl format2("%.10s");
EXPECT_EQ("ABC", FormatPack(format2, {FormatArgImpl(p)}));
+
+ wp = L"ABC";
+ UntypedFormatSpecImpl wformat2("%.10ls");
+ EXPECT_EQ("ABC", FormatPack(wformat2, {FormatArgImpl(wp)}));
}
// Pointer formatting is implementation defined. This checks that the argument
@@ -261,7 +368,7 @@
}
void* parsed = nullptr;
if (sscanf(arg.c_str(), "%p", &parsed) != 1) {
- ABSL_RAW_LOG(FATAL, "Could not parse %s", arg.c_str());
+ LOG(FATAL) << "Could not parse " << arg;
}
return ptr == parsed;
}
@@ -273,16 +380,25 @@
char *mcp = &c;
const char *cp = "hi";
const char *cnil = nullptr;
+ wchar_t wc = L'h';
+ wchar_t *mwcp = &wc;
+ const wchar_t *wcp = L"hi";
+ const wchar_t *wcnil = nullptr;
const int *inil = nullptr;
using VoidF = void (*)();
VoidF fp = [] {}, fnil = nullptr;
volatile char vc;
volatile char *vcp = &vc;
volatile char *vcnil = nullptr;
+ volatile wchar_t vwc;
+ volatile wchar_t *vwcp = &vwc;
+ volatile wchar_t *vwcnil = nullptr;
const FormatArgImpl args_array[] = {
- FormatArgImpl(xp), FormatArgImpl(cp), FormatArgImpl(inil),
- FormatArgImpl(cnil), FormatArgImpl(mcp), FormatArgImpl(fp),
- FormatArgImpl(fnil), FormatArgImpl(vcp), FormatArgImpl(vcnil),
+ FormatArgImpl(xp), FormatArgImpl(cp), FormatArgImpl(wcp),
+ FormatArgImpl(inil), FormatArgImpl(cnil), FormatArgImpl(wcnil),
+ FormatArgImpl(mcp), FormatArgImpl(mwcp), FormatArgImpl(fp),
+ FormatArgImpl(fnil), FormatArgImpl(vcp), FormatArgImpl(vwcp),
+ FormatArgImpl(vcnil), FormatArgImpl(vwcnil),
};
auto args = absl::MakeConstSpan(args_array);
@@ -308,30 +424,49 @@
EXPECT_THAT(FormatPack(UntypedFormatSpecImpl("%-30.20p"), args),
MatchesPointerString(&x));
+ // const int*
+ EXPECT_THAT(FormatPack(UntypedFormatSpecImpl("%1$p"), args),
+ MatchesPointerString(xp));
// const char*
EXPECT_THAT(FormatPack(UntypedFormatSpecImpl("%2$p"), args),
MatchesPointerString(cp));
- // null const int*
+ // const wchar_t*
EXPECT_THAT(FormatPack(UntypedFormatSpecImpl("%3$p"), args),
- MatchesPointerString(nullptr));
- // null const char*
+ MatchesPointerString(wcp));
+ // null const int*
EXPECT_THAT(FormatPack(UntypedFormatSpecImpl("%4$p"), args),
MatchesPointerString(nullptr));
- // nonconst char*
+ // null const char*
EXPECT_THAT(FormatPack(UntypedFormatSpecImpl("%5$p"), args),
- MatchesPointerString(mcp));
-
- // function pointers
- EXPECT_THAT(FormatPack(UntypedFormatSpecImpl("%6$p"), args),
- MatchesPointerString(reinterpret_cast<const void*>(fp)));
- EXPECT_THAT(
- FormatPack(UntypedFormatSpecImpl("%8$p"), args),
- MatchesPointerString(reinterpret_cast<volatile const void *>(vcp)));
-
- // null function pointers
- EXPECT_THAT(FormatPack(UntypedFormatSpecImpl("%7$p"), args),
MatchesPointerString(nullptr));
+ // null const wchar_t*
+ EXPECT_THAT(FormatPack(UntypedFormatSpecImpl("%6$p"), args),
+ MatchesPointerString(nullptr));
+ // nonconst char*
+ EXPECT_THAT(FormatPack(UntypedFormatSpecImpl("%7$p"), args),
+ MatchesPointerString(mcp));
+ // nonconst wchar_t*
+ EXPECT_THAT(FormatPack(UntypedFormatSpecImpl("%8$p"), args),
+ MatchesPointerString(mwcp));
+ // function pointer
EXPECT_THAT(FormatPack(UntypedFormatSpecImpl("%9$p"), args),
+ MatchesPointerString(reinterpret_cast<const void *>(fp)));
+ // null function pointer
+ EXPECT_THAT(FormatPack(UntypedFormatSpecImpl("%10$p"), args),
+ MatchesPointerString(nullptr));
+ // volatile char*
+ EXPECT_THAT(
+ FormatPack(UntypedFormatSpecImpl("%11$p"), args),
+ MatchesPointerString(reinterpret_cast<volatile const void *>(vcp)));
+ // volatile wchar_t*
+ EXPECT_THAT(
+ FormatPack(UntypedFormatSpecImpl("%12$p"), args),
+ MatchesPointerString(reinterpret_cast<volatile const void *>(vwcp)));
+ // null volatile char*
+ EXPECT_THAT(FormatPack(UntypedFormatSpecImpl("%13$p"), args),
+ MatchesPointerString(nullptr));
+ // null volatile wchar_t*
+ EXPECT_THAT(FormatPack(UntypedFormatSpecImpl("%14$p"), args),
MatchesPointerString(nullptr));
}
@@ -431,12 +566,15 @@
// as printf can't do that conversion properly. For those
// cases, we do expect agreement with printf with a "%u"
// and the unsigned equivalent of 'val'.
- UnsignedT uval = val;
- old_fmt += LengthModFor(uval);
+ UnsignedT uval =
+ static_cast<std::remove_volatile_t<UnsignedT>>(val);
+ old_fmt += LengthModFor<
+ MatchingIntegralType<std::remove_cv_t<decltype(uval)>>>();
old_fmt += "u";
old_result = StrPrint(old_fmt.c_str(), uval);
} else {
- old_fmt += LengthModFor(val);
+ old_fmt += LengthModFor<
+ MatchingIntegralType<std::remove_cv_t<decltype(val)>>>();
old_fmt += conv_char;
old_result = StrPrint(old_fmt.c_str(), val);
}
@@ -454,36 +592,110 @@
}
}
+template <typename T>
+absl::optional<std::string> StrPrintChar(T c) {
+ return StrPrint("%c", static_cast<int>(c));
+}
+template <>
+absl::optional<std::string> StrPrintChar(wchar_t c) {
+ // musl libc has a bug where ("%lc", 0) writes no characters, and Android
+ // doesn't support forcing UTF-8 via setlocale(). Hardcode the expected
+ // answers for ASCII inputs to maximize test coverage on these platforms.
+ if (static_cast<std::make_unsigned_t<wchar_t>>(c) < 0x80) {
+ return std::string(1, static_cast<char>(c));
+ }
+
+ // Force a UTF-8 locale to match the expected `StrFormat()` behavior.
+ // It's important to copy the string returned by `old_locale` here, because
+ // its contents are not guaranteed to be valid after the next `setlocale()`
+ // call.
+ std::string old_locale = setlocale(LC_CTYPE, nullptr);
+ if (!setlocale(LC_CTYPE, "en_US.UTF-8")) {
+ return absl::nullopt;
+ }
+ const std::string output = StrPrint("%lc", static_cast<wint_t>(c));
+ setlocale(LC_CTYPE, old_locale.c_str());
+ return output;
+}
+
+template <typename T>
+typename std::remove_volatile<T>::type GetMaxForConversion() {
+ return static_cast<typename std::remove_volatile<T>::type>(
+ std::numeric_limits<int>::max());
+}
+
+template <>
+wchar_t GetMaxForConversion<wchar_t>() {
+ // Don't return values that aren't legal Unicode. For wchar_t conversions in a
+ // UTF-8 locale, conversion behavior for such values is unspecified, and we
+ // don't care about matching it.
+ return (sizeof(wchar_t) * CHAR_BIT <= 16) ? wchar_t{0xffff}
+ : static_cast<wchar_t>(0x10ffff);
+}
+
TYPED_TEST_P(TypedFormatConvertTest, Char) {
+ // Pass a bunch of values of type TypeParam to both FormatPack and libc's
+ // vsnprintf("%c", ...) (wrapped in StrPrint) to make sure we get the same
+ // value.
typedef TypeParam T;
using remove_volatile_t = typename std::remove_volatile<T>::type;
- static const T kMin = std::numeric_limits<remove_volatile_t>::min();
- static const T kMax = std::numeric_limits<remove_volatile_t>::max();
- T kVals[] = {
- remove_volatile_t(1), remove_volatile_t(2), remove_volatile_t(10),
- remove_volatile_t(-1), remove_volatile_t(-2), remove_volatile_t(-10),
- remove_volatile_t(0),
- kMin + remove_volatile_t(1), kMin,
- kMax - remove_volatile_t(1), kMax
+ std::vector<remove_volatile_t> vals = {
+ remove_volatile_t(1), remove_volatile_t(2), remove_volatile_t(10), //
+ remove_volatile_t(-1), remove_volatile_t(-2), remove_volatile_t(-10), //
+ remove_volatile_t(0),
};
- for (const T &c : kVals) {
+
+ // We'd like to test values near std::numeric_limits::min() and
+ // std::numeric_limits::max(), too, but vsnprintf("%c", ...) can't handle
+ // anything larger than an int. Add in the most extreme values we can without
+ // exceeding that range.
+ // Special case: Formatting a wchar_t should behave like vsnprintf("%lc").
+ // Technically vsnprintf can accept a wint_t in this case, but since we must
+ // pass a wchar_t to FormatPack, the largest type we can use here is wchar_t.
+ using ArgType =
+ std::conditional_t<std::is_same<T, wchar_t>::value, wchar_t, int>;
+ static const T kMin =
+ static_cast<remove_volatile_t>(std::numeric_limits<ArgType>::min());
+ static const T kMax = GetMaxForConversion<T>();
+ vals.insert(vals.end(), {static_cast<remove_volatile_t>(kMin + 1), kMin,
+ static_cast<remove_volatile_t>(kMax - 1), kMax});
+
+ static const auto kMaxWCharT =
+ static_cast<remove_volatile_t>(GetMaxForConversion<wchar_t>());
+ for (const T c : vals) {
+ SCOPED_TRACE(Esc(c));
const FormatArgImpl args[] = {FormatArgImpl(c)};
UntypedFormatSpecImpl format("%c");
- EXPECT_EQ(StrPrint("%c", c), FormatPack(format, absl::MakeSpan(args)));
+ absl::optional<std::string> result = StrPrintChar(c);
+ if (result.has_value()) {
+ EXPECT_EQ(result.value(), FormatPack(format, absl::MakeSpan(args)));
+ }
+
+ // Also test that if the format specifier is "%lc", the argument is treated
+ // as if it's a `wchar_t`.
+ const T wc =
+ std::max(remove_volatile_t{0},
+ std::min(static_cast<remove_volatile_t>(c), kMaxWCharT));
+ SCOPED_TRACE(Esc(wc));
+ const FormatArgImpl wide_args[] = {FormatArgImpl(wc)};
+ UntypedFormatSpecImpl wide_format("%lc");
+ result = StrPrintChar(static_cast<wchar_t>(wc));
+ if (result.has_value()) {
+ EXPECT_EQ(result.value(),
+ FormatPack(wide_format, absl::MakeSpan(wide_args)));
+ }
}
}
-REGISTER_TYPED_TEST_CASE_P(TypedFormatConvertTest, AllIntsWithFlags, Char);
+REGISTER_TYPED_TEST_SUITE_P(TypedFormatConvertTest, AllIntsWithFlags, Char);
-typedef ::testing::Types<
- int, unsigned, volatile int,
- short, unsigned short,
- long, unsigned long,
- long long, unsigned long long,
- signed char, unsigned char, char>
+typedef ::testing::Types<int, unsigned, volatile int, short, // NOLINT
+ unsigned short, long, unsigned long, // NOLINT
+ long long, unsigned long long, // NOLINT
+ signed char, unsigned char, char, wchar_t>
AllIntTypes;
-INSTANTIATE_TYPED_TEST_CASE_P(TypedFormatConvertTestWithAllIntTypes,
- TypedFormatConvertTest, AllIntTypes);
+INSTANTIATE_TYPED_TEST_SUITE_P(TypedFormatConvertTestWithAllIntTypes,
+ TypedFormatConvertTest, AllIntTypes);
TEST_F(FormatConvertTest, VectorBool) {
// Make sure vector<bool>'s values behave as bools.
std::vector<bool> v = {true, false};
@@ -495,6 +707,22 @@
FormatArgImpl(cv[0]), FormatArgImpl(cv[1])})));
}
+TEST_F(FormatConvertTest, UnicodeWideString) {
+ // StrFormat() should be able to convert wide strings containing Unicode
+ // characters (to UTF-8).
+ const FormatArgImpl args[] = {FormatArgImpl(L"\u47e3 \U00011112")};
+ // `u8""` forces UTF-8 encoding; MSVC will default to e.g. CP1252 (and warn)
+ // without it. However, the resulting character type differs between pre-C++20
+ // (`char`) and C++20 (`char8_t`). So deduce the right character type for all
+ // C++ versions, init it with UTF-8, then `memcpy()` to get the result as a
+ // `char*`.
+ using ConstChar8T = std::remove_reference_t<decltype(*u8"a")>;
+ ConstChar8T kOutputUtf8[] = u8"\u47e3 \U00011112";
+ char output[sizeof kOutputUtf8];
+ std::memcpy(output, kOutputUtf8, sizeof kOutputUtf8);
+ EXPECT_EQ(output,
+ FormatPack(UntypedFormatSpecImpl("%ls"), absl::MakeSpan(args)));
+}
TEST_F(FormatConvertTest, Int128) {
absl::int128 positive = static_cast<absl::int128>(0x1234567890abcdef) * 1979;
@@ -669,7 +897,11 @@
}
// Remove duplicates to speed up the logic below.
- std::sort(floats.begin(), floats.end());
+ std::sort(floats.begin(), floats.end(), [](const float a, const float b) {
+ if (std::isnan(a)) return false;
+ if (std::isnan(b)) return true;
+ return a < b;
+ });
floats.erase(std::unique(floats.begin(), floats.end()), floats.end());
TestWithMultipleFormatsHelper(floats, {});
@@ -743,7 +975,11 @@
}
// Remove duplicates to speed up the logic below.
- std::sort(doubles.begin(), doubles.end());
+ std::sort(doubles.begin(), doubles.end(), [](const double a, const double b) {
+ if (std::isnan(a)) return false;
+ if (std::isnan(b)) return true;
+ return a < b;
+ });
doubles.erase(std::unique(doubles.begin(), doubles.end()), doubles.end());
TestWithMultipleFormatsHelper(doubles, skip_verify);
@@ -1044,7 +1280,7 @@
// We don't actually store the results. This is just to exercise the rest of the
// machinery.
struct NullSink {
- friend void AbslFormatFlush(NullSink *sink, string_view str) {}
+ friend void AbslFormatFlush(NullSink *, string_view) {}
};
template <typename... T>
@@ -1221,15 +1457,22 @@
// Sanity check to make sure that we are testing what we think we're testing on
// e.g. the x86_64+glibc platform.
TEST_F(FormatConvertTest, GlibcHasCorrectTraits) {
-#if !defined(__GLIBC__) || !defined(__x86_64__)
- return;
+#if defined(__GLIBC__) && defined(__x86_64__)
+ constexpr bool kIsSupportedGlibc = true;
+#else
+ constexpr bool kIsSupportedGlibc = false;
#endif
+
+ if (!kIsSupportedGlibc) {
+ GTEST_SKIP() << "Test does not support this platform";
+ }
+
const NativePrintfTraits &native_traits = VerifyNativeImplementation();
// If one of the following tests break then it is either because the above PP
// macro guards failed to exclude a new platform (likely) or because something
- // has changed in the implemention of glibc sprintf float formatting behavior.
- // If the latter, then the code that computes these flags needs to be
- // revisited and/or possibly the StrFormat implementation.
+ // has changed in the implementation of glibc sprintf float formatting
+ // behavior. If the latter, then the code that computes these flags needs to
+ // be revisited and/or possibly the StrFormat implementation.
EXPECT_TRUE(native_traits.hex_float_has_glibc_rounding);
EXPECT_TRUE(native_traits.hex_float_prefers_denormal_repr);
EXPECT_TRUE(
diff --git a/absl/strings/internal/str_format/extension.cc b/absl/strings/internal/str_format/extension.cc
index 484f6eb..2a0ceb1 100644
--- a/absl/strings/internal/str_format/extension.cc
+++ b/absl/strings/internal/str_format/extension.cc
@@ -33,6 +33,8 @@
return s;
}
+#ifdef ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL
+
#define ABSL_INTERNAL_X_VAL(id) \
constexpr absl::FormatConversionChar FormatConversionCharInternal::id;
ABSL_INTERNAL_CONVERSION_CHARS_EXPAND_(ABSL_INTERNAL_X_VAL, )
@@ -45,21 +47,19 @@
ABSL_INTERNAL_CONVERSION_CHARS_EXPAND_(ABSL_INTERNAL_CHAR_SET_CASE, )
#undef ABSL_INTERNAL_CHAR_SET_CASE
-// NOLINTNEXTLINE(readability-redundant-declaration)
constexpr FormatConversionCharSet FormatConversionCharSetInternal::kStar;
-// NOLINTNEXTLINE(readability-redundant-declaration)
constexpr FormatConversionCharSet FormatConversionCharSetInternal::kIntegral;
-// NOLINTNEXTLINE(readability-redundant-declaration)
constexpr FormatConversionCharSet FormatConversionCharSetInternal::kFloating;
-// NOLINTNEXTLINE(readability-redundant-declaration)
constexpr FormatConversionCharSet FormatConversionCharSetInternal::kNumeric;
-// NOLINTNEXTLINE(readability-redundant-declaration)
constexpr FormatConversionCharSet FormatConversionCharSetInternal::kPointer;
+#endif // ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL
+
bool FormatSinkImpl::PutPaddedString(string_view value, int width,
int precision, bool left) {
size_t space_remaining = 0;
- if (width >= 0) space_remaining = width;
+ if (width >= 0)
+ space_remaining = static_cast<size_t>(width);
size_t n = value.size();
if (precision >= 0) n = std::min(n, static_cast<size_t>(precision));
string_view shown(value.data(), n);
diff --git a/absl/strings/internal/str_format/extension.h b/absl/strings/internal/str_format/extension.h
index 55cbb56..173284c 100644
--- a/absl/strings/internal/str_format/extension.h
+++ b/absl/strings/internal/str_format/extension.h
@@ -16,15 +16,14 @@
#ifndef ABSL_STRINGS_INTERNAL_STR_FORMAT_EXTENSION_H_
#define ABSL_STRINGS_INTERNAL_STR_FORMAT_EXTENSION_H_
-#include <limits.h>
#include <cstddef>
+#include <cstdint>
#include <cstring>
#include <ostream>
+#include <string>
#include "absl/base/config.h"
-#include "absl/base/port.h"
-#include "absl/meta/type_traits.h"
#include "absl/strings/internal/str_format/output.h"
#include "absl/strings/string_view.h"
@@ -33,6 +32,7 @@
enum class FormatConversionChar : uint8_t;
enum class FormatConversionCharSet : uint64_t;
+enum class LengthMod : std::uint8_t { h, hh, l, ll, L, j, z, t, q, none };
namespace str_format_internal {
@@ -70,7 +70,7 @@
~FormatSinkImpl() { Flush(); }
void Flush() {
- raw_.Write(string_view(buf_, pos_ - buf_));
+ raw_.Write(string_view(buf_, static_cast<size_t>(pos_ - buf_)));
pos_ = buf_;
}
@@ -120,7 +120,9 @@
}
private:
- size_t Avail() const { return buf_ + sizeof(buf_) - pos_; }
+ size_t Avail() const {
+ return static_cast<size_t>(buf_ + sizeof(buf_) - pos_);
+ }
FormatRawSinkImpl raw_;
size_t size_ = 0;
@@ -136,7 +138,8 @@
kAlt = 1 << 3,
kZero = 1 << 4,
// This is not a real flag. It just exists to turn off kBasic when no other
- // flags are set. This is for when width/precision are specified.
+ // flags are set. This is for when width/precision are specified, or a length
+ // modifier affects the behavior ("%lc").
kNonBasic = 1 << 5,
};
@@ -166,7 +169,7 @@
X_VAL(f) X_SEP X_VAL(F) X_SEP X_VAL(e) X_SEP X_VAL(E) X_SEP \
X_VAL(g) X_SEP X_VAL(G) X_SEP X_VAL(a) X_SEP X_VAL(A) X_SEP \
/* misc */ \
- X_VAL(n) X_SEP X_VAL(p)
+ X_VAL(n) X_SEP X_VAL(p) X_SEP X_VAL(v)
// clang-format on
// This type should not be referenced, it exists only to provide labels
@@ -188,7 +191,7 @@
c, s, // text
d, i, o, u, x, X, // int
f, F, e, E, g, G, a, A, // float
- n, p, // misc
+ n, p, v, // misc
kNone
};
// clang-format on
@@ -270,7 +273,7 @@
class FormatConversionSpecImpl {
public:
- // Width and precison are not specified, no flags are set.
+ // Width and precision are not specified, no flags are set.
bool is_basic() const { return flags_ == Flags::kBasic; }
bool has_left_flag() const { return FlagsContains(flags_, Flags::kLeft); }
bool has_show_pos_flag() const {
@@ -282,6 +285,8 @@
bool has_alt_flag() const { return FlagsContains(flags_, Flags::kAlt); }
bool has_zero_flag() const { return FlagsContains(flags_, Flags::kZero); }
+ LengthMod length_mod() const { return length_mod_; }
+
FormatConversionChar conversion_char() const {
// Keep this field first in the struct . It generates better code when
// accessing it when ConversionSpec is passed by value in registers.
@@ -289,6 +294,8 @@
return conv_;
}
+ void set_conversion_char(FormatConversionChar c) { conv_ = c; }
+
// Returns the specified width. If width is unspecfied, it returns a negative
// value.
int width() const { return width_; }
@@ -305,6 +312,7 @@
friend struct str_format_internal::FormatConversionSpecImplFriend;
FormatConversionChar conv_ = FormatConversionCharInternal::kNone;
Flags flags_;
+ LengthMod length_mod_ = LengthMod::none;
int width_;
int precision_;
};
@@ -313,6 +321,9 @@
static void SetFlags(Flags f, FormatConversionSpecImpl* conv) {
conv->flags_ = f;
}
+ static void SetLengthMod(LengthMod l, FormatConversionSpecImpl* conv) {
+ conv->length_mod_ = l;
+ }
static void SetConversionChar(FormatConversionChar c,
FormatConversionSpecImpl* conv) {
conv->conv_ = c;
diff --git a/absl/strings/internal/str_format/extension_test.cc b/absl/strings/internal/str_format/extension_test.cc
index 1c93fdb..694c126 100644
--- a/absl/strings/internal/str_format/extension_test.cc
+++ b/absl/strings/internal/str_format/extension_test.cc
@@ -19,6 +19,7 @@
#include <random>
#include <string>
+#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/strings/str_format.h"
#include "absl/strings/string_view.h"
@@ -95,4 +96,14 @@
#undef X_VAL
}
+TEST(FormatExtensionTest, SetConversionChar) {
+ absl::str_format_internal::FormatConversionSpecImpl spec;
+ EXPECT_EQ(spec.conversion_char(),
+ absl::str_format_internal::FormatConversionCharInternal::kNone);
+ spec.set_conversion_char(
+ absl::str_format_internal::FormatConversionCharInternal::d);
+ EXPECT_EQ(spec.conversion_char(),
+ absl::str_format_internal::FormatConversionCharInternal::d);
+}
+
} // namespace
diff --git a/absl/strings/internal/str_format/float_conversion.cc b/absl/strings/internal/str_format/float_conversion.cc
index b1c4068..8edf520 100644
--- a/absl/strings/internal/str_format/float_conversion.cc
+++ b/absl/strings/internal/str_format/float_conversion.cc
@@ -92,27 +92,30 @@
// Calculates `10 * (*v) + carry` and stores the result in `*v` and returns
// the carry.
+// Requires: `0 <= carry <= 9`
template <typename Int>
-inline Int MultiplyBy10WithCarry(Int *v, Int carry) {
+inline char MultiplyBy10WithCarry(Int* v, char carry) {
using BiggerInt = absl::conditional_t<sizeof(Int) == 4, uint64_t, uint128>;
- BiggerInt tmp = 10 * static_cast<BiggerInt>(*v) + carry;
+ BiggerInt tmp =
+ 10 * static_cast<BiggerInt>(*v) + static_cast<BiggerInt>(carry);
*v = static_cast<Int>(tmp);
- return static_cast<Int>(tmp >> (sizeof(Int) * 8));
+ return static_cast<char>(tmp >> (sizeof(Int) * 8));
}
// Calculates `(2^64 * carry + *v) / 10`.
// Stores the quotient in `*v` and returns the remainder.
// Requires: `0 <= carry <= 9`
-inline uint64_t DivideBy10WithCarry(uint64_t *v, uint64_t carry) {
+inline char DivideBy10WithCarry(uint64_t* v, char carry) {
constexpr uint64_t divisor = 10;
// 2^64 / divisor = chunk_quotient + chunk_remainder / divisor
constexpr uint64_t chunk_quotient = (uint64_t{1} << 63) / (divisor / 2);
constexpr uint64_t chunk_remainder = uint64_t{} - chunk_quotient * divisor;
+ const uint64_t carry_u64 = static_cast<uint64_t>(carry);
const uint64_t mod = *v % divisor;
- const uint64_t next_carry = chunk_remainder * carry + mod;
- *v = *v / divisor + carry * chunk_quotient + next_carry / divisor;
- return next_carry % divisor;
+ const uint64_t next_carry = chunk_remainder * carry_u64 + mod;
+ *v = *v / divisor + carry_u64 * chunk_quotient + next_carry / divisor;
+ return static_cast<char>(next_carry % divisor);
}
using MaxFloatType =
@@ -125,11 +128,11 @@
//
// Requires `0 <= exp` and `exp <= numeric_limits<MaxFloatType>::max_exponent`.
class BinaryToDecimal {
- static constexpr int ChunksNeeded(int exp) {
+ static constexpr size_t ChunksNeeded(int exp) {
// We will left shift a uint128 by `exp` bits, so we need `128+exp` total
// bits. Round up to 32.
// See constructor for details about adding `10%` to the value.
- return (128 + exp + 31) / 32 * 11 / 10;
+ return static_cast<size_t>((128 + exp + 31) / 32 * 11 / 10);
}
public:
@@ -140,7 +143,7 @@
assert(exp > 0);
assert(exp <= std::numeric_limits<MaxFloatType>::max_exponent);
static_assert(
- static_cast<int>(StackArray::kMaxCapacity) >=
+ StackArray::kMaxCapacity >=
ChunksNeeded(std::numeric_limits<MaxFloatType>::max_exponent),
"");
@@ -149,9 +152,9 @@
[=](absl::Span<uint32_t> input) { f(BinaryToDecimal(input, v, exp)); });
}
- int TotalDigits() const {
- return static_cast<int>((decimal_end_ - decimal_start_) * kDigitsPerChunk +
- CurrentDigits().size());
+ size_t TotalDigits() const {
+ return (decimal_end_ - decimal_start_) * kDigitsPerChunk +
+ CurrentDigits().size();
}
// See the current block of digits.
@@ -190,30 +193,31 @@
// the decimal representation is around 7% less efficient in space than the
// binary one. We allocate an extra 10% memory to account for this. See
// ChunksNeeded for this calculation.
- int chunk_index = exp / 32;
+ size_t after_chunk_index = static_cast<size_t>(exp / 32 + 1);
decimal_start_ = decimal_end_ = ChunksNeeded(exp);
const int offset = exp % 32;
// Left shift v by exp bits.
- data_[chunk_index] = static_cast<uint32_t>(v << offset);
+ data_[after_chunk_index - 1] = static_cast<uint32_t>(v << offset);
for (v >>= (32 - offset); v; v >>= 32)
- data_[++chunk_index] = static_cast<uint32_t>(v);
+ data_[++after_chunk_index - 1] = static_cast<uint32_t>(v);
- while (chunk_index >= 0) {
+ while (after_chunk_index > 0) {
// While we have more than one chunk available, go in steps of 1e9.
- // `data_[chunk_index]` holds the highest non-zero binary chunk, so keep
- // the variable updated.
+ // `data_[after_chunk_index - 1]` holds the highest non-zero binary chunk,
+ // so keep the variable updated.
uint32_t carry = 0;
- for (int i = chunk_index; i >= 0; --i) {
- uint64_t tmp = uint64_t{data_[i]} + (uint64_t{carry} << 32);
- data_[i] = static_cast<uint32_t>(tmp / uint64_t{1000000000});
+ for (size_t i = after_chunk_index; i > 0; --i) {
+ uint64_t tmp = uint64_t{data_[i - 1]} + (uint64_t{carry} << 32);
+ data_[i - 1] = static_cast<uint32_t>(tmp / uint64_t{1000000000});
carry = static_cast<uint32_t>(tmp % uint64_t{1000000000});
}
// If the highest chunk is now empty, remove it from view.
- if (data_[chunk_index] == 0) --chunk_index;
+ if (data_[after_chunk_index - 1] == 0)
+ --after_chunk_index;
--decimal_start_;
- assert(decimal_start_ != chunk_index);
+ assert(decimal_start_ != after_chunk_index - 1);
data_[decimal_start_] = carry;
}
@@ -225,13 +229,13 @@
}
private:
- static constexpr int kDigitsPerChunk = 9;
+ static constexpr size_t kDigitsPerChunk = 9;
- int decimal_start_;
- int decimal_end_;
+ size_t decimal_start_;
+ size_t decimal_end_;
char digits_[kDigitsPerChunk];
- int size_ = 0;
+ size_t size_ = 0;
absl::Span<uint32_t> data_;
};
@@ -251,25 +255,26 @@
static_assert(StackArray::kMaxCapacity >=
(Limits::digits + 128 - Limits::min_exponent + 31) / 32,
"");
- StackArray::RunWithCapacity((Limits::digits + exp + 31) / 32,
- [=](absl::Span<uint32_t> input) {
- f(FractionalDigitGenerator(input, v, exp));
- });
+ StackArray::RunWithCapacity(
+ static_cast<size_t>((Limits::digits + exp + 31) / 32),
+ [=](absl::Span<uint32_t> input) {
+ f(FractionalDigitGenerator(input, v, exp));
+ });
}
// Returns true if there are any more non-zero digits left.
- bool HasMoreDigits() const { return next_digit_ != 0 || chunk_index_ >= 0; }
+ bool HasMoreDigits() const { return next_digit_ != 0 || after_chunk_index_; }
// Returns true if the remainder digits are greater than 5000...
bool IsGreaterThanHalf() const {
- return next_digit_ > 5 || (next_digit_ == 5 && chunk_index_ >= 0);
+ return next_digit_ > 5 || (next_digit_ == 5 && after_chunk_index_);
}
// Returns true if the remainder digits are exactly 5000...
- bool IsExactlyHalf() const { return next_digit_ == 5 && chunk_index_ < 0; }
+ bool IsExactlyHalf() const { return next_digit_ == 5 && !after_chunk_index_; }
struct Digits {
- int digit_before_nine;
- int num_nines;
+ char digit_before_nine;
+ size_t num_nines;
};
// Get the next set of digits.
@@ -288,35 +293,37 @@
private:
// Return the next digit.
- int GetOneDigit() {
- if (chunk_index_ < 0) return 0;
+ char GetOneDigit() {
+ if (!after_chunk_index_)
+ return 0;
- uint32_t carry = 0;
- for (int i = chunk_index_; i >= 0; --i) {
- carry = MultiplyBy10WithCarry(&data_[i], carry);
+ char carry = 0;
+ for (size_t i = after_chunk_index_; i > 0; --i) {
+ carry = MultiplyBy10WithCarry(&data_[i - 1], carry);
}
// If the lowest chunk is now empty, remove it from view.
- if (data_[chunk_index_] == 0) --chunk_index_;
+ if (data_[after_chunk_index_ - 1] == 0)
+ --after_chunk_index_;
return carry;
}
FractionalDigitGenerator(absl::Span<uint32_t> data, uint128 v, int exp)
- : chunk_index_(exp / 32), data_(data) {
+ : after_chunk_index_(static_cast<size_t>(exp / 32 + 1)), data_(data) {
const int offset = exp % 32;
// Right shift `v` by `exp` bits.
- data_[chunk_index_] = static_cast<uint32_t>(v << (32 - offset));
+ data_[after_chunk_index_ - 1] = static_cast<uint32_t>(v << (32 - offset));
v >>= offset;
// Make sure we don't overflow the data. We already calculated that
// non-zero bits fit, so we might not have space for leading zero bits.
- for (int pos = chunk_index_; v; v >>= 32)
+ for (size_t pos = after_chunk_index_ - 1; v; v >>= 32)
data_[--pos] = static_cast<uint32_t>(v);
// Fill next_digit_, as GetDigits expects it to be populated always.
next_digit_ = GetOneDigit();
}
- int next_digit_;
- int chunk_index_;
+ char next_digit_;
+ size_t after_chunk_index_;
absl::Span<uint32_t> data_;
};
@@ -362,7 +369,7 @@
auto low = static_cast<uint64_t>(v);
while (high != 0) {
- uint64_t carry = DivideBy10WithCarry(&high, 0);
+ char carry = DivideBy10WithCarry(&high, 0);
carry = DivideBy10WithCarry(&low, carry);
*--p = carry + '0';
}
@@ -373,13 +380,15 @@
// shifting.
// Performs rounding if necessary to fit within `precision`.
// Returns the pointer to one after the last character written.
-char *PrintFractionalDigitsFast(uint64_t v, char *start, int exp,
- int precision) {
+char* PrintFractionalDigitsFast(uint64_t v,
+ char* start,
+ int exp,
+ size_t precision) {
char *p = start;
v <<= (64 - exp);
while (precision > 0) {
if (!v) return p;
- *p++ = MultiplyBy10WithCarry(&v, uint64_t{0}) + '0';
+ *p++ = MultiplyBy10WithCarry(&v, 0) + '0';
--precision;
}
@@ -393,8 +402,6 @@
RoundToEven(p - 1);
}
- assert(precision == 0);
- // Precision can only be zero here.
return p;
}
@@ -402,8 +409,10 @@
// after shifting.
// Performs rounding if necessary to fit within `precision`.
// Returns the pointer to one after the last character written.
-char *PrintFractionalDigitsFast(uint128 v, char *start, int exp,
- int precision) {
+char* PrintFractionalDigitsFast(uint128 v,
+ char* start,
+ int exp,
+ size_t precision) {
char *p = start;
v <<= (128 - exp);
auto high = static_cast<uint64_t>(v >> 64);
@@ -412,7 +421,7 @@
// While we have digits to print and `low` is not empty, do the long
// multiplication.
while (precision > 0 && low != 0) {
- uint64_t carry = MultiplyBy10WithCarry(&low, uint64_t{0});
+ char carry = MultiplyBy10WithCarry(&low, 0);
carry = MultiplyBy10WithCarry(&high, carry);
*p++ = carry + '0';
@@ -424,7 +433,7 @@
// above.
while (precision > 0) {
if (!high) return p;
- *p++ = MultiplyBy10WithCarry(&high, uint64_t{0}) + '0';
+ *p++ = MultiplyBy10WithCarry(&high, 0) + '0';
--precision;
}
@@ -438,14 +447,12 @@
RoundToEven(p - 1);
}
- assert(precision == 0);
- // Precision can only be zero here.
return p;
}
struct FormatState {
char sign_char;
- int precision;
+ size_t precision;
const FormatConversionSpecImpl &conv;
FormatSinkImpl *sink;
@@ -455,9 +462,9 @@
};
struct Padding {
- int left_spaces;
- int zeros;
- int right_spaces;
+ size_t left_spaces;
+ size_t zeros;
+ size_t right_spaces;
};
Padding ExtraWidthToPadding(size_t total_size, const FormatState &state) {
@@ -465,7 +472,7 @@
static_cast<size_t>(state.conv.width()) <= total_size) {
return {0, 0, 0};
}
- int missing_chars = state.conv.width() - total_size;
+ size_t missing_chars = static_cast<size_t>(state.conv.width()) - total_size;
if (state.conv.has_left_flag()) {
return {0, 0, missing_chars};
} else if (state.conv.has_zero_flag()) {
@@ -475,8 +482,10 @@
}
}
-void FinalPrint(const FormatState &state, absl::string_view data,
- int padding_offset, int trailing_zeros,
+void FinalPrint(const FormatState& state,
+ absl::string_view data,
+ size_t padding_offset,
+ size_t trailing_zeros,
absl::string_view data_postfix) {
if (state.conv.width() < 0) {
// No width specified. Fast-path.
@@ -487,10 +496,10 @@
return;
}
- auto padding = ExtraWidthToPadding((state.sign_char != '\0' ? 1 : 0) +
- data.size() + data_postfix.size() +
- static_cast<size_t>(trailing_zeros),
- state);
+ auto padding =
+ ExtraWidthToPadding((state.sign_char != '\0' ? 1 : 0) + data.size() +
+ data_postfix.size() + trailing_zeros,
+ state);
state.sink->Append(padding.left_spaces, ' ');
if (state.sign_char != '\0') state.sink->Append(1, state.sign_char);
@@ -547,15 +556,16 @@
if (integral_digits_start[-1] != '0') --integral_digits_start;
}
- size_t size = fractional_digits_end - integral_digits_start;
+ size_t size =
+ static_cast<size_t>(fractional_digits_end - integral_digits_start);
// In `alt` mode (flag #) we keep the `.` even if there are no fractional
// digits. In non-alt mode, we strip it.
if (!state.ShouldPrintDot()) --size;
FinalPrint(state, absl::string_view(integral_digits_start, size),
/*padding_offset=*/0,
- static_cast<int>(state.precision - (fractional_digits_end -
- fractional_digits_start)),
+ state.precision - static_cast<size_t>(fractional_digits_end -
+ fractional_digits_start),
/*data_postfix=*/"");
}
@@ -567,21 +577,22 @@
void FormatFPositiveExpSlow(uint128 v, int exp, const FormatState &state) {
BinaryToDecimal::RunConversion(v, exp, [&](BinaryToDecimal btd) {
const size_t total_digits =
- btd.TotalDigits() +
- (state.ShouldPrintDot() ? static_cast<size_t>(state.precision) + 1 : 0);
+ btd.TotalDigits() + (state.ShouldPrintDot() ? state.precision + 1 : 0);
const auto padding = ExtraWidthToPadding(
total_digits + (state.sign_char != '\0' ? 1 : 0), state);
state.sink->Append(padding.left_spaces, ' ');
- if (state.sign_char != '\0') state.sink->Append(1, state.sign_char);
+ if (state.sign_char != '\0')
+ state.sink->Append(1, state.sign_char);
state.sink->Append(padding.zeros, '0');
do {
state.sink->Append(btd.CurrentDigits());
} while (btd.AdvanceDigits());
- if (state.ShouldPrintDot()) state.sink->Append(1, '.');
+ if (state.ShouldPrintDot())
+ state.sink->Append(1, '.');
state.sink->Append(state.precision, '0');
state.sink->Append(padding.right_spaces, ' ');
});
@@ -594,8 +605,7 @@
// digits.
void FormatFNegativeExpSlow(uint128 v, int exp, const FormatState &state) {
const size_t total_digits =
- /* 0 */ 1 +
- (state.ShouldPrintDot() ? static_cast<size_t>(state.precision) + 1 : 0);
+ /* 0 */ 1 + (state.ShouldPrintDot() ? state.precision + 1 : 0);
auto padding =
ExtraWidthToPadding(total_digits + (state.sign_char ? 1 : 0), state);
padding.zeros += 1;
@@ -606,7 +616,7 @@
if (state.ShouldPrintDot()) state.sink->Append(1, '.');
// Print digits
- int digits_to_go = state.precision;
+ size_t digits_to_go = state.precision;
FractionalDigitGenerator::RunConversion(
v, exp, [&](FractionalDigitGenerator digit_gen) {
@@ -666,7 +676,8 @@
template <typename Int>
void FormatF(Int mantissa, int exp, const FormatState &state) {
if (exp >= 0) {
- const int total_bits = sizeof(Int) * 8 - LeadingZeros(mantissa) + exp;
+ const int total_bits =
+ static_cast<int>(sizeof(Int) * 8) - LeadingZeros(mantissa) + exp;
// Fallback to the slow stack-based approach if we can't do it in a 64 or
// 128 bit state.
@@ -686,9 +697,9 @@
// Grab the group of four bits (nibble) from `n`. E.g., nibble 1 corresponds to
// bits 4-7.
template <typename Int>
-uint8_t GetNibble(Int n, int nibble_index) {
+uint8_t GetNibble(Int n, size_t nibble_index) {
constexpr Int mask_low_nibble = Int{0xf};
- int shift = nibble_index * 4;
+ int shift = static_cast<int>(nibble_index * 4);
n &= mask_low_nibble << shift;
return static_cast<uint8_t>((n >> shift) & 0xf);
}
@@ -696,38 +707,42 @@
// Add one to the given nibble, applying carry to higher nibbles. Returns true
// if overflow, false otherwise.
template <typename Int>
-bool IncrementNibble(int nibble_index, Int *n) {
- constexpr int kShift = sizeof(Int) * 8 - 1;
- constexpr int kNumNibbles = sizeof(Int) * 8 / 4;
+bool IncrementNibble(size_t nibble_index, Int* n) {
+ constexpr size_t kShift = sizeof(Int) * 8 - 1;
+ constexpr size_t kNumNibbles = sizeof(Int) * 8 / 4;
Int before = *n >> kShift;
- // Here we essentially want to take the number 1 and move it into the requsted
- // nibble, then add it to *n to effectively increment the nibble. However,
- // ASan will complain if we try to shift the 1 beyond the limits of the Int,
- // i.e., if the nibble_index is out of range. So therefore we check for this
- // and if we are out of range we just add 0 which leaves *n unchanged, which
- // seems like the reasonable thing to do in that case.
- *n += ((nibble_index >= kNumNibbles) ? 0 : (Int{1} << (nibble_index * 4)));
+ // Here we essentially want to take the number 1 and move it into the
+ // requested nibble, then add it to *n to effectively increment the nibble.
+ // However, ASan will complain if we try to shift the 1 beyond the limits of
+ // the Int, i.e., if the nibble_index is out of range. So therefore we check
+ // for this and if we are out of range we just add 0 which leaves *n
+ // unchanged, which seems like the reasonable thing to do in that case.
+ *n += ((nibble_index >= kNumNibbles)
+ ? 0
+ : (Int{1} << static_cast<int>(nibble_index * 4)));
Int after = *n >> kShift;
return (before && !after) || (nibble_index >= kNumNibbles);
}
// Return a mask with 1's in the given nibble and all lower nibbles.
template <typename Int>
-Int MaskUpToNibbleInclusive(int nibble_index) {
- constexpr int kNumNibbles = sizeof(Int) * 8 / 4;
+Int MaskUpToNibbleInclusive(size_t nibble_index) {
+ constexpr size_t kNumNibbles = sizeof(Int) * 8 / 4;
static const Int ones = ~Int{0};
- return ones >> std::max(0, 4 * (kNumNibbles - nibble_index - 1));
+ ++nibble_index;
+ return ones >> static_cast<int>(
+ 4 * (std::max(kNumNibbles, nibble_index) - nibble_index));
}
// Return a mask with 1's below the given nibble.
template <typename Int>
-Int MaskUpToNibbleExclusive(int nibble_index) {
- return nibble_index <= 0 ? 0 : MaskUpToNibbleInclusive<Int>(nibble_index - 1);
+Int MaskUpToNibbleExclusive(size_t nibble_index) {
+ return nibble_index == 0 ? 0 : MaskUpToNibbleInclusive<Int>(nibble_index - 1);
}
template <typename Int>
-Int MoveToNibble(uint8_t nibble, int nibble_index) {
- return Int{nibble} << (4 * nibble_index);
+Int MoveToNibble(uint8_t nibble, size_t nibble_index) {
+ return Int{nibble} << static_cast<int>(4 * nibble_index);
}
// Given mantissa size, find optimal # of mantissa bits to put in initial digit.
@@ -744,10 +759,10 @@
// a multiple of four. Once again, the goal is to have all fractional digits
// represent real precision.
template <typename Float>
-constexpr int HexFloatLeadingDigitSizeInBits() {
+constexpr size_t HexFloatLeadingDigitSizeInBits() {
return std::numeric_limits<Float>::digits % 4 > 0
- ? std::numeric_limits<Float>::digits % 4
- : 4;
+ ? static_cast<size_t>(std::numeric_limits<Float>::digits % 4)
+ : size_t{4};
}
// This function captures the rounding behavior of glibc for hex float
@@ -757,16 +772,17 @@
// point that is not followed by 800000..., it disregards the parity and rounds
// up if > 8 and rounds down if < 8.
template <typename Int>
-bool HexFloatNeedsRoundUp(Int mantissa, int final_nibble_displayed,
+bool HexFloatNeedsRoundUp(Int mantissa,
+ size_t final_nibble_displayed,
uint8_t leading) {
// If the last nibble (hex digit) to be displayed is the lowest on in the
// mantissa then that means that we don't have any further nibbles to inform
// rounding, so don't round.
- if (final_nibble_displayed <= 0) {
+ if (final_nibble_displayed == 0) {
return false;
}
- int rounding_nibble_idx = final_nibble_displayed - 1;
- constexpr int kTotalNibbles = sizeof(Int) * 8 / 4;
+ size_t rounding_nibble_idx = final_nibble_displayed - 1;
+ constexpr size_t kTotalNibbles = sizeof(Int) * 8 / 4;
assert(final_nibble_displayed <= kTotalNibbles);
Int mantissa_up_to_rounding_nibble_inclusive =
mantissa & MaskUpToNibbleInclusive<Int>(rounding_nibble_idx);
@@ -793,7 +809,7 @@
}
int min_exponent;
- int leading_digit_size_bits;
+ size_t leading_digit_size_bits;
};
// Hex Float Rounding. First check if we need to round; if so, then we do that
@@ -803,10 +819,12 @@
template <typename Int>
void FormatARound(bool precision_specified, const FormatState &state,
uint8_t *leading, Int *mantissa, int *exp) {
- constexpr int kTotalNibbles = sizeof(Int) * 8 / 4;
+ constexpr size_t kTotalNibbles = sizeof(Int) * 8 / 4;
// Index of the last nibble that we could display given precision.
- int final_nibble_displayed =
- precision_specified ? std::max(0, (kTotalNibbles - state.precision)) : 0;
+ size_t final_nibble_displayed =
+ precision_specified
+ ? (std::max(kTotalNibbles, state.precision) - state.precision)
+ : 0;
if (HexFloatNeedsRoundUp(*mantissa, final_nibble_displayed, *leading)) {
// Need to round up.
bool overflow = IncrementNibble(final_nibble_displayed, mantissa);
@@ -830,9 +848,9 @@
template <typename Int>
void FormatANormalize(const HexFloatTypeParams float_traits, uint8_t *leading,
Int *mantissa, int *exp) {
- constexpr int kIntBits = sizeof(Int) * 8;
+ constexpr size_t kIntBits = sizeof(Int) * 8;
static const Int kHighIntBit = Int{1} << (kIntBits - 1);
- const int kLeadDigitBitsCount = float_traits.leading_digit_size_bits;
+ const size_t kLeadDigitBitsCount = float_traits.leading_digit_size_bits;
// Normalize mantissa so that highest bit set is in MSB position, unless we
// get interrupted by the exponent threshold.
while (*mantissa && !(*mantissa & kHighIntBit)) {
@@ -846,18 +864,18 @@
}
// Extract bits for leading digit then shift them away leaving the
// fractional part.
- *leading =
- static_cast<uint8_t>(*mantissa >> (kIntBits - kLeadDigitBitsCount));
- *exp -= (*mantissa != 0) ? kLeadDigitBitsCount : *exp;
- *mantissa <<= kLeadDigitBitsCount;
+ *leading = static_cast<uint8_t>(
+ *mantissa >> static_cast<int>(kIntBits - kLeadDigitBitsCount));
+ *exp -= (*mantissa != 0) ? static_cast<int>(kLeadDigitBitsCount) : *exp;
+ *mantissa <<= static_cast<int>(kLeadDigitBitsCount);
}
template <typename Int>
void FormatA(const HexFloatTypeParams float_traits, Int mantissa, int exp,
bool uppercase, const FormatState &state) {
// Int properties.
- constexpr int kIntBits = sizeof(Int) * 8;
- constexpr int kTotalNibbles = sizeof(Int) * 8 / 4;
+ constexpr size_t kIntBits = sizeof(Int) * 8;
+ constexpr size_t kTotalNibbles = sizeof(Int) * 8 / 4;
// Did the user specify a precision explicitly?
const bool precision_specified = state.conv.precision() >= 0;
@@ -903,20 +921,23 @@
}
// ============ Fractional Digits ============
- int digits_emitted = 0;
+ size_t digits_emitted = 0;
while (mantissa > 0) {
*digits_iter++ = digits[GetNibble(mantissa, kTotalNibbles - 1)];
mantissa <<= 4;
++digits_emitted;
}
- int trailing_zeros =
- precision_specified ? state.precision - digits_emitted : 0;
- assert(trailing_zeros >= 0);
- auto digits_result = string_view(digits_buffer, digits_iter - digits_buffer);
+ size_t trailing_zeros = 0;
+ if (precision_specified) {
+ assert(state.precision >= digits_emitted);
+ trailing_zeros = state.precision - digits_emitted;
+ }
+ auto digits_result = string_view(
+ digits_buffer, static_cast<size_t>(digits_iter - digits_buffer));
// =============== Exponent ==================
constexpr size_t kBufSizeForExpDecRepr =
- numbers_internal::kFastToBufferSize // requred for FastIntToBuffer
+ numbers_internal::kFastToBufferSize // required for FastIntToBuffer
+ 1 // 'p' or 'P'
+ 1; // '+' or '-'
char exp_buffer[kBufSizeForExpDecRepr];
@@ -925,11 +946,11 @@
numbers_internal::FastIntToBuffer(exp < 0 ? -exp : exp, exp_buffer + 2);
// ============ Assemble Result ==============
- FinalPrint(state, //
- digits_result, // 0xN.NNN...
- 2, // offset in `data` to start padding if needed.
- trailing_zeros, // num remaining mantissa padding zeros
- exp_buffer); // exponent
+ FinalPrint(state,
+ digits_result, // 0xN.NNN...
+ 2, // offset of any padding
+ static_cast<size_t>(trailing_zeros), // remaining mantissa padding
+ exp_buffer); // exponent
}
char *CopyStringTo(absl::string_view v, char *out) {
@@ -961,10 +982,10 @@
int n = snprintf(&space[0], space.size(), fmt, w, p, v);
if (n < 0) return false;
if (static_cast<size_t>(n) < space.size()) {
- result = absl::string_view(space.data(), n);
+ result = absl::string_view(space.data(), static_cast<size_t>(n));
break;
}
- space.resize(n + 1);
+ space.resize(static_cast<size_t>(n) + 1);
}
sink->Append(result);
return true;
@@ -972,13 +993,13 @@
// 128-bits in decimal: ceil(128*log(2)/log(10))
// or std::numeric_limits<__uint128_t>::digits10
-constexpr int kMaxFixedPrecision = 39;
+constexpr size_t kMaxFixedPrecision = 39;
-constexpr int kBufferLength = /*sign*/ 1 +
- /*integer*/ kMaxFixedPrecision +
- /*point*/ 1 +
- /*fraction*/ kMaxFixedPrecision +
- /*exponent e+123*/ 5;
+constexpr size_t kBufferLength = /*sign*/ 1 +
+ /*integer*/ kMaxFixedPrecision +
+ /*point*/ 1 +
+ /*fraction*/ kMaxFixedPrecision +
+ /*exponent e+123*/ 5;
struct Buffer {
void push_front(char c) {
@@ -994,14 +1015,14 @@
--end;
}
- char &back() {
+ char &back() const {
assert(begin < end);
return end[-1];
}
char last_digit() const { return end[-1] == '.' ? end[-2] : end[-1]; }
- int size() const { return static_cast<int>(end - begin); }
+ size_t size() const { return static_cast<size_t>(end - begin); }
char data[kBufferLength];
char *begin;
@@ -1030,8 +1051,9 @@
return false;
}
- return sink->PutPaddedString(string_view(text, ptr - text), conv.width(), -1,
- conv.has_left_flag());
+ return sink->PutPaddedString(
+ string_view(text, static_cast<size_t>(ptr - text)), conv.width(), -1,
+ conv.has_left_flag());
}
// Round up the last digit of the value.
@@ -1068,19 +1090,19 @@
}
// Exponent digits.
if (exp > 99) {
- out->push_back(exp / 100 + '0');
- out->push_back(exp / 10 % 10 + '0');
- out->push_back(exp % 10 + '0');
+ out->push_back(static_cast<char>(exp / 100 + '0'));
+ out->push_back(static_cast<char>(exp / 10 % 10 + '0'));
+ out->push_back(static_cast<char>(exp % 10 + '0'));
} else {
- out->push_back(exp / 10 + '0');
- out->push_back(exp % 10 + '0');
+ out->push_back(static_cast<char>(exp / 10 + '0'));
+ out->push_back(static_cast<char>(exp % 10 + '0'));
}
}
template <typename Float, typename Int>
constexpr bool CanFitMantissa() {
return
-#if defined(__clang__) && !defined(__SSE3__)
+#if defined(__clang__) && (__clang_major__ < 9) && !defined(__SSE3__)
// Workaround for clang bug: https://bugs.llvm.org/show_bug.cgi?id=38289
// Casting from long double to uint64_t is miscompiled and drops bits.
(!std::is_same<Float, long double>::value ||
@@ -1115,8 +1137,8 @@
// In Fixed mode, we add a '.' at the end.
// In Precision mode, we add a '.' after the first digit.
template <FormatStyle mode, typename Int>
-int PrintIntegralDigits(Int digits, Buffer *out) {
- int printed = 0;
+size_t PrintIntegralDigits(Int digits, Buffer* out) {
+ size_t printed = 0;
if (digits) {
for (; digits; digits /= 10) out->push_front(digits % 10 + '0');
printed = out->size();
@@ -1135,10 +1157,10 @@
}
// Back out 'extra_digits' digits and round up if necessary.
-bool RemoveExtraPrecision(int extra_digits, bool has_leftover_value,
- Buffer *out, int *exp_out) {
- if (extra_digits <= 0) return false;
-
+void RemoveExtraPrecision(size_t extra_digits,
+ bool has_leftover_value,
+ Buffer* out,
+ int* exp_out) {
// Back out the extra digits
out->end -= extra_digits;
@@ -1158,15 +1180,17 @@
if (needs_to_round_up) {
RoundUp<FormatStyle::Precision>(out, exp_out);
}
- return true;
}
// Print the value into the buffer.
// This will not include the exponent, which will be returned in 'exp_out' for
// Precision mode.
template <typename Int, typename Float, FormatStyle mode>
-bool FloatToBufferImpl(Int int_mantissa, int exp, int precision, Buffer *out,
- int *exp_out) {
+bool FloatToBufferImpl(Int int_mantissa,
+ int exp,
+ size_t precision,
+ Buffer* out,
+ int* exp_out) {
assert((CanFitMantissa<Float, Int>()));
const int int_bits = std::numeric_limits<Int>::digits;
@@ -1182,14 +1206,16 @@
// The value will overflow the Int
return false;
}
- int digits_printed = PrintIntegralDigits<mode>(int_mantissa << exp, out);
- int digits_to_zero_pad = precision;
+ size_t digits_printed = PrintIntegralDigits<mode>(int_mantissa << exp, out);
+ size_t digits_to_zero_pad = precision;
if (mode == FormatStyle::Precision) {
- *exp_out = digits_printed - 1;
- digits_to_zero_pad -= digits_printed - 1;
- if (RemoveExtraPrecision(-digits_to_zero_pad, false, out, exp_out)) {
+ *exp_out = static_cast<int>(digits_printed - 1);
+ if (digits_to_zero_pad < digits_printed - 1) {
+ RemoveExtraPrecision(digits_printed - 1 - digits_to_zero_pad, false,
+ out, exp_out);
return true;
}
+ digits_to_zero_pad -= digits_printed - 1;
}
for (; digits_to_zero_pad-- > 0;) out->push_back('0');
return true;
@@ -1203,10 +1229,10 @@
const Int mask = (Int{1} << exp) - 1;
// Print the integral part first.
- int digits_printed = PrintIntegralDigits<mode>(int_mantissa >> exp, out);
+ size_t digits_printed = PrintIntegralDigits<mode>(int_mantissa >> exp, out);
int_mantissa &= mask;
- int fractional_count = precision;
+ size_t fractional_count = precision;
if (mode == FormatStyle::Precision) {
if (digits_printed == 0) {
// Find the first non-zero digit, when in Precision mode.
@@ -1222,20 +1248,21 @@
int_mantissa &= mask;
} else {
// We already have a digit, and a '.'
- *exp_out = digits_printed - 1;
- fractional_count -= *exp_out;
- if (RemoveExtraPrecision(-fractional_count, int_mantissa != 0, out,
- exp_out)) {
+ *exp_out = static_cast<int>(digits_printed - 1);
+ if (fractional_count < digits_printed - 1) {
// If we had enough digits, return right away.
// The code below will try to round again otherwise.
+ RemoveExtraPrecision(digits_printed - 1 - fractional_count,
+ int_mantissa != 0, out, exp_out);
return true;
}
+ fractional_count -= digits_printed - 1;
}
}
auto get_next_digit = [&] {
int_mantissa *= 10;
- int digit = static_cast<int>(int_mantissa >> exp);
+ char digit = static_cast<char>(int_mantissa >> exp);
int_mantissa &= mask;
return digit;
};
@@ -1245,7 +1272,7 @@
out->push_back(get_next_digit() + '0');
}
- int next_digit = get_next_digit();
+ char next_digit = get_next_digit();
if (next_digit > 5 ||
(next_digit == 5 && (int_mantissa || out->last_digit() % 2 == 1))) {
RoundUp<mode>(out, exp_out);
@@ -1255,24 +1282,25 @@
}
template <FormatStyle mode, typename Float>
-bool FloatToBuffer(Decomposed<Float> decomposed, int precision, Buffer *out,
- int *exp) {
+bool FloatToBuffer(Decomposed<Float> decomposed,
+ size_t precision,
+ Buffer* out,
+ int* exp) {
if (precision > kMaxFixedPrecision) return false;
// Try with uint64_t.
if (CanFitMantissa<Float, std::uint64_t>() &&
FloatToBufferImpl<std::uint64_t, Float, mode>(
- static_cast<std::uint64_t>(decomposed.mantissa),
- static_cast<std::uint64_t>(decomposed.exponent), precision, out, exp))
+ static_cast<std::uint64_t>(decomposed.mantissa), decomposed.exponent,
+ precision, out, exp))
return true;
#if defined(ABSL_HAVE_INTRINSIC_INT128)
// If that is not enough, try with __uint128_t.
return CanFitMantissa<Float, __uint128_t>() &&
FloatToBufferImpl<__uint128_t, Float, mode>(
- static_cast<__uint128_t>(decomposed.mantissa),
- static_cast<__uint128_t>(decomposed.exponent), precision, out,
- exp);
+ static_cast<__uint128_t>(decomposed.mantissa), decomposed.exponent,
+ precision, out, exp);
#endif
return false;
}
@@ -1280,12 +1308,15 @@
void WriteBufferToSink(char sign_char, absl::string_view str,
const FormatConversionSpecImpl &conv,
FormatSinkImpl *sink) {
- int left_spaces = 0, zeros = 0, right_spaces = 0;
- int missing_chars =
- conv.width() >= 0 ? std::max(conv.width() - static_cast<int>(str.size()) -
- static_cast<int>(sign_char != 0),
- 0)
- : 0;
+ size_t left_spaces = 0, zeros = 0, right_spaces = 0;
+ size_t missing_chars = 0;
+ if (conv.width() >= 0) {
+ const size_t conv_width_size_t = static_cast<size_t>(conv.width());
+ const size_t existing_chars =
+ str.size() + static_cast<size_t>(sign_char != 0);
+ if (conv_width_size_t > existing_chars)
+ missing_chars = conv_width_size_t - existing_chars;
+ }
if (conv.has_left_flag()) {
right_spaces = missing_chars;
} else if (conv.has_zero_flag()) {
@@ -1321,7 +1352,8 @@
return true;
}
- int precision = conv.precision() < 0 ? 6 : conv.precision();
+ size_t precision =
+ conv.precision() < 0 ? 6 : static_cast<size_t>(conv.precision());
int exp = 0;
@@ -1348,12 +1380,12 @@
&buffer);
} else if (c == FormatConversionCharInternal::g ||
c == FormatConversionCharInternal::G) {
- precision = std::max(0, precision - 1);
+ precision = std::max(precision, size_t{1}) - 1;
if (!FloatToBuffer<FormatStyle::Precision>(decomposed, precision, &buffer,
&exp)) {
return FallbackToSnprintf(v, conv, sink);
}
- if (precision + 1 > exp && exp >= -4) {
+ if ((exp < 0 || precision + 1 > static_cast<size_t>(exp)) && exp >= -4) {
if (exp < 0) {
// Have 1.23456, needs 0.00123456
// Move the first digit
@@ -1388,9 +1420,11 @@
return false;
}
- WriteBufferToSink(sign_char,
- absl::string_view(buffer.begin, buffer.end - buffer.begin),
- conv, sink);
+ WriteBufferToSink(
+ sign_char,
+ absl::string_view(buffer.begin,
+ static_cast<size_t>(buffer.end - buffer.begin)),
+ conv, sink);
return true;
}
diff --git a/absl/strings/internal/str_format/output.h b/absl/strings/internal/str_format/output.h
index 8030dae..15e751a 100644
--- a/absl/strings/internal/str_format/output.h
+++ b/absl/strings/internal/str_format/output.h
@@ -22,6 +22,7 @@
#define ABSL_STRINGS_INTERNAL_STR_FORMAT_OUTPUT_H_
#include <cstdio>
+#include <ios>
#include <ostream>
#include <string>
@@ -71,7 +72,7 @@
out->append(s.data(), s.size());
}
inline void AbslFormatFlush(std::ostream* out, string_view s) {
- out->write(s.data(), s.size());
+ out->write(s.data(), static_cast<std::streamsize>(s.size()));
}
inline void AbslFormatFlush(FILERawSink* sink, string_view v) {
diff --git a/absl/strings/internal/str_format/parser.cc b/absl/strings/internal/str_format/parser.cc
index 2c9c07d..5aaab69 100644
--- a/absl/strings/internal/str_format/parser.cc
+++ b/absl/strings/internal/str_format/parser.cc
@@ -31,207 +31,14 @@
ABSL_NAMESPACE_BEGIN
namespace str_format_internal {
-using CC = FormatConversionCharInternal;
-using LM = LengthMod;
+// Define the array for non-constexpr uses.
+constexpr ConvTag ConvTagHolder::value[256];
-// Abbreviations to fit in the table below.
-constexpr auto f_sign = Flags::kSignCol;
-constexpr auto f_alt = Flags::kAlt;
-constexpr auto f_pos = Flags::kShowPos;
-constexpr auto f_left = Flags::kLeft;
-constexpr auto f_zero = Flags::kZero;
-
-ABSL_CONST_INIT const ConvTag kTags[256] = {
- {}, {}, {}, {}, {}, {}, {}, {}, // 00-07
- {}, {}, {}, {}, {}, {}, {}, {}, // 08-0f
- {}, {}, {}, {}, {}, {}, {}, {}, // 10-17
- {}, {}, {}, {}, {}, {}, {}, {}, // 18-1f
- f_sign, {}, {}, f_alt, {}, {}, {}, {}, // !"#$%&'
- {}, {}, {}, f_pos, {}, f_left, {}, {}, // ()*+,-./
- f_zero, {}, {}, {}, {}, {}, {}, {}, // 01234567
- {}, {}, {}, {}, {}, {}, {}, {}, // 89:;<=>?
- {}, CC::A, {}, {}, {}, CC::E, CC::F, CC::G, // @ABCDEFG
- {}, {}, {}, {}, LM::L, {}, {}, {}, // HIJKLMNO
- {}, {}, {}, {}, {}, {}, {}, {}, // PQRSTUVW
- CC::X, {}, {}, {}, {}, {}, {}, {}, // XYZ[\]^_
- {}, CC::a, {}, CC::c, CC::d, CC::e, CC::f, CC::g, // `abcdefg
- LM::h, CC::i, LM::j, {}, LM::l, {}, CC::n, CC::o, // hijklmno
- CC::p, LM::q, {}, CC::s, LM::t, CC::u, {}, {}, // pqrstuvw
- CC::x, {}, LM::z, {}, {}, {}, {}, {}, // xyz{|}!
- {}, {}, {}, {}, {}, {}, {}, {}, // 80-87
- {}, {}, {}, {}, {}, {}, {}, {}, // 88-8f
- {}, {}, {}, {}, {}, {}, {}, {}, // 90-97
- {}, {}, {}, {}, {}, {}, {}, {}, // 98-9f
- {}, {}, {}, {}, {}, {}, {}, {}, // a0-a7
- {}, {}, {}, {}, {}, {}, {}, {}, // a8-af
- {}, {}, {}, {}, {}, {}, {}, {}, // b0-b7
- {}, {}, {}, {}, {}, {}, {}, {}, // b8-bf
- {}, {}, {}, {}, {}, {}, {}, {}, // c0-c7
- {}, {}, {}, {}, {}, {}, {}, {}, // c8-cf
- {}, {}, {}, {}, {}, {}, {}, {}, // d0-d7
- {}, {}, {}, {}, {}, {}, {}, {}, // d8-df
- {}, {}, {}, {}, {}, {}, {}, {}, // e0-e7
- {}, {}, {}, {}, {}, {}, {}, {}, // e8-ef
- {}, {}, {}, {}, {}, {}, {}, {}, // f0-f7
- {}, {}, {}, {}, {}, {}, {}, {}, // f8-ff
-};
-
-namespace {
-
-bool CheckFastPathSetting(const UnboundConversion& conv) {
- bool width_precision_needed =
- conv.width.value() >= 0 || conv.precision.value() >= 0;
- if (width_precision_needed && conv.flags == Flags::kBasic) {
- fprintf(stderr,
- "basic=%d left=%d show_pos=%d sign_col=%d alt=%d zero=%d "
- "width=%d precision=%d\n",
- conv.flags == Flags::kBasic ? 1 : 0,
- FlagsContains(conv.flags, Flags::kLeft) ? 1 : 0,
- FlagsContains(conv.flags, Flags::kShowPos) ? 1 : 0,
- FlagsContains(conv.flags, Flags::kSignCol) ? 1 : 0,
- FlagsContains(conv.flags, Flags::kAlt) ? 1 : 0,
- FlagsContains(conv.flags, Flags::kZero) ? 1 : 0, conv.width.value(),
- conv.precision.value());
- return false;
- }
- return true;
+ABSL_ATTRIBUTE_NOINLINE const char* ConsumeUnboundConversionNoInline(
+ const char* p, const char* end, UnboundConversion* conv, int* next_arg) {
+ return ConsumeUnboundConversion(p, end, conv, next_arg);
}
-template <bool is_positional>
-const char *ConsumeConversion(const char *pos, const char *const end,
- UnboundConversion *conv, int *next_arg) {
- const char* const original_pos = pos;
- char c;
- // Read the next char into `c` and update `pos`. Returns false if there are
- // no more chars to read.
-#define ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR() \
- do { \
- if (ABSL_PREDICT_FALSE(pos == end)) return nullptr; \
- c = *pos++; \
- } while (0)
-
- const auto parse_digits = [&] {
- int digits = c - '0';
- // We do not want to overflow `digits` so we consume at most digits10
- // digits. If there are more digits the parsing will fail later on when the
- // digit doesn't match the expected characters.
- int num_digits = std::numeric_limits<int>::digits10;
- for (;;) {
- if (ABSL_PREDICT_FALSE(pos == end)) break;
- c = *pos++;
- if (!std::isdigit(c)) break;
- --num_digits;
- if (ABSL_PREDICT_FALSE(!num_digits)) break;
- digits = 10 * digits + c - '0';
- }
- return digits;
- };
-
- if (is_positional) {
- ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR();
- if (ABSL_PREDICT_FALSE(c < '1' || c > '9')) return nullptr;
- conv->arg_position = parse_digits();
- assert(conv->arg_position > 0);
- if (ABSL_PREDICT_FALSE(c != '$')) return nullptr;
- }
-
- ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR();
-
- // We should start with the basic flag on.
- assert(conv->flags == Flags::kBasic);
-
- // Any non alpha character makes this conversion not basic.
- // This includes flags (-+ #0), width (1-9, *) or precision (.).
- // All conversion characters and length modifiers are alpha characters.
- if (c < 'A') {
- while (c <= '0') {
- auto tag = GetTagForChar(c);
- if (tag.is_flags()) {
- conv->flags = conv->flags | tag.as_flags();
- ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR();
- } else {
- break;
- }
- }
-
- if (c <= '9') {
- if (c >= '0') {
- int maybe_width = parse_digits();
- if (!is_positional && c == '$') {
- if (ABSL_PREDICT_FALSE(*next_arg != 0)) return nullptr;
- // Positional conversion.
- *next_arg = -1;
- return ConsumeConversion<true>(original_pos, end, conv, next_arg);
- }
- conv->flags = conv->flags | Flags::kNonBasic;
- conv->width.set_value(maybe_width);
- } else if (c == '*') {
- conv->flags = conv->flags | Flags::kNonBasic;
- ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR();
- if (is_positional) {
- if (ABSL_PREDICT_FALSE(c < '1' || c > '9')) return nullptr;
- conv->width.set_from_arg(parse_digits());
- if (ABSL_PREDICT_FALSE(c != '$')) return nullptr;
- ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR();
- } else {
- conv->width.set_from_arg(++*next_arg);
- }
- }
- }
-
- if (c == '.') {
- conv->flags = conv->flags | Flags::kNonBasic;
- ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR();
- if (std::isdigit(c)) {
- conv->precision.set_value(parse_digits());
- } else if (c == '*') {
- ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR();
- if (is_positional) {
- if (ABSL_PREDICT_FALSE(c < '1' || c > '9')) return nullptr;
- conv->precision.set_from_arg(parse_digits());
- if (c != '$') return nullptr;
- ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR();
- } else {
- conv->precision.set_from_arg(++*next_arg);
- }
- } else {
- conv->precision.set_value(0);
- }
- }
- }
-
- auto tag = GetTagForChar(c);
-
- if (ABSL_PREDICT_FALSE(!tag.is_conv())) {
- if (ABSL_PREDICT_FALSE(!tag.is_length())) return nullptr;
-
- // It is a length modifier.
- using str_format_internal::LengthMod;
- LengthMod length_mod = tag.as_length();
- ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR();
- if (c == 'h' && length_mod == LengthMod::h) {
- conv->length_mod = LengthMod::hh;
- ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR();
- } else if (c == 'l' && length_mod == LengthMod::l) {
- conv->length_mod = LengthMod::ll;
- ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR();
- } else {
- conv->length_mod = length_mod;
- }
- tag = GetTagForChar(c);
- if (ABSL_PREDICT_FALSE(!tag.is_conv())) return nullptr;
- }
-
- assert(CheckFastPathSetting(*conv));
- (void)(&CheckFastPathSetting);
-
- conv->conv = tag.as_conv();
- if (!is_positional) conv->arg_position = ++*next_arg;
- return pos;
-}
-
-} // namespace
-
std::string LengthModToString(LengthMod v) {
switch (v) {
case LengthMod::h:
@@ -258,12 +65,6 @@
return "";
}
-const char *ConsumeUnboundConversion(const char *p, const char *end,
- UnboundConversion *conv, int *next_arg) {
- if (*next_arg < 0) return ConsumeConversion<true>(p, end, conv, next_arg);
- return ConsumeConversion<false>(p, end, conv, next_arg);
-}
-
struct ParsedFormatBase::ParsedFormatConsumer {
explicit ParsedFormatConsumer(ParsedFormatBase *parsedformat)
: parsed(parsedformat), data_pos(parsedformat->data_.get()) {}
@@ -312,11 +113,11 @@
std::initializer_list<FormatConversionCharSet> convs) const {
std::unordered_set<int> used;
auto add_if_valid_conv = [&](int pos, char c) {
- if (static_cast<size_t>(pos) > convs.size() ||
- !Contains(convs.begin()[pos - 1], c))
- return false;
- used.insert(pos);
- return true;
+ if (static_cast<size_t>(pos) > convs.size() ||
+ !Contains(convs.begin()[pos - 1], c))
+ return false;
+ used.insert(pos);
+ return true;
};
for (const ConversionItem &item : items_) {
if (!item.is_conversion) continue;
diff --git a/absl/strings/internal/str_format/parser.h b/absl/strings/internal/str_format/parser.h
index ad8646e..b1d6d5f 100644
--- a/absl/strings/internal/str_format/parser.h
+++ b/absl/strings/internal/str_format/parser.h
@@ -15,125 +15,33 @@
#ifndef ABSL_STRINGS_INTERNAL_STR_FORMAT_PARSER_H_
#define ABSL_STRINGS_INTERNAL_STR_FORMAT_PARSER_H_
-#include <limits.h>
#include <stddef.h>
#include <stdlib.h>
#include <cassert>
-#include <cstdint>
+#include <cstring>
#include <initializer_list>
-#include <iosfwd>
-#include <iterator>
#include <memory>
#include <string>
+#include <utility>
#include <vector>
+#include "absl/base/config.h"
+#include "absl/base/optimization.h"
#include "absl/strings/internal/str_format/checker.h"
+#include "absl/strings/internal/str_format/constexpr_parser.h"
#include "absl/strings/internal/str_format/extension.h"
+#include "absl/strings/string_view.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace str_format_internal {
-enum class LengthMod : std::uint8_t { h, hh, l, ll, L, j, z, t, q, none };
-
std::string LengthModToString(LengthMod v);
-// The analyzed properties of a single specified conversion.
-struct UnboundConversion {
- UnboundConversion() {}
-
- class InputValue {
- public:
- void set_value(int value) {
- assert(value >= 0);
- value_ = value;
- }
- int value() const { return value_; }
-
- // Marks the value as "from arg". aka the '*' format.
- // Requires `value >= 1`.
- // When set, is_from_arg() return true and get_from_arg() returns the
- // original value.
- // `value()`'s return value is unspecfied in this state.
- void set_from_arg(int value) {
- assert(value > 0);
- value_ = -value - 1;
- }
- bool is_from_arg() const { return value_ < -1; }
- int get_from_arg() const {
- assert(is_from_arg());
- return -value_ - 1;
- }
-
- private:
- int value_ = -1;
- };
-
- // No need to initialize. It will always be set in the parser.
- int arg_position;
-
- InputValue width;
- InputValue precision;
-
- Flags flags = Flags::kBasic;
- LengthMod length_mod = LengthMod::none;
- FormatConversionChar conv = FormatConversionCharInternal::kNone;
-};
-
-// Consume conversion spec prefix (not including '%') of [p, end) if valid.
-// Examples of valid specs would be e.g.: "s", "d", "-12.6f".
-// If valid, it returns the first character following the conversion spec,
-// and the spec part is broken down and returned in 'conv'.
-// If invalid, returns nullptr.
-const char* ConsumeUnboundConversion(const char* p, const char* end,
- UnboundConversion* conv, int* next_arg);
-
-// Helper tag class for the table below.
-// It allows fast `char -> ConversionChar/LengthMod/Flags` checking and
-// conversions.
-class ConvTag {
- public:
- constexpr ConvTag(FormatConversionChar conversion_char) // NOLINT
- : tag_(static_cast<uint8_t>(conversion_char)) {}
- constexpr ConvTag(LengthMod length_mod) // NOLINT
- : tag_(0x80 | static_cast<uint8_t>(length_mod)) {}
- constexpr ConvTag(Flags flags) // NOLINT
- : tag_(0xc0 | static_cast<uint8_t>(flags)) {}
- constexpr ConvTag() : tag_(0xFF) {}
-
- bool is_conv() const { return (tag_ & 0x80) == 0; }
- bool is_length() const { return (tag_ & 0xC0) == 0x80; }
- bool is_flags() const { return (tag_ & 0xE0) == 0xC0; }
-
- FormatConversionChar as_conv() const {
- assert(is_conv());
- assert(!is_length());
- assert(!is_flags());
- return static_cast<FormatConversionChar>(tag_);
- }
- LengthMod as_length() const {
- assert(!is_conv());
- assert(is_length());
- assert(!is_flags());
- return static_cast<LengthMod>(tag_ & 0x3F);
- }
- Flags as_flags() const {
- assert(!is_conv());
- assert(!is_length());
- assert(is_flags());
- return static_cast<Flags>(tag_ & 0x1F);
- }
-
- private:
- uint8_t tag_;
-};
-
-extern const ConvTag kTags[256];
-// Keep a single table for all the conversion chars and length modifiers.
-inline ConvTag GetTagForChar(char c) {
- return kTags[static_cast<unsigned char>(c)];
-}
+const char* ConsumeUnboundConversionNoInline(const char* p, const char* end,
+ UnboundConversion* conv,
+ int* next_arg);
// Parse the format string provided in 'src' and pass the identified items into
// 'consumer'.
@@ -151,13 +59,15 @@
const char* p = src.data();
const char* const end = p + src.size();
while (p != end) {
- const char* percent = static_cast<const char*>(memchr(p, '%', end - p));
+ const char* percent =
+ static_cast<const char*>(memchr(p, '%', static_cast<size_t>(end - p)));
if (!percent) {
// We found the last substring.
- return consumer.Append(string_view(p, end - p));
+ return consumer.Append(string_view(p, static_cast<size_t>(end - p)));
}
// We found a percent, so push the text run then process the percent.
- if (ABSL_PREDICT_FALSE(!consumer.Append(string_view(p, percent - p)))) {
+ if (ABSL_PREDICT_FALSE(!consumer.Append(
+ string_view(p, static_cast<size_t>(percent - p))))) {
return false;
}
if (ABSL_PREDICT_FALSE(percent + 1 >= end)) return false;
@@ -185,10 +95,11 @@
}
} else if (percent[1] != '%') {
UnboundConversion conv;
- p = ConsumeUnboundConversion(percent + 1, end, &conv, &next_arg);
+ p = ConsumeUnboundConversionNoInline(percent + 1, end, &conv, &next_arg);
if (ABSL_PREDICT_FALSE(p == nullptr)) return false;
if (ABSL_PREDICT_FALSE(!consumer.ConvertOne(
- conv, string_view(percent + 1, p - (percent + 1))))) {
+ conv, string_view(percent + 1,
+ static_cast<size_t>(p - (percent + 1)))))) {
return false;
}
} else {
@@ -242,7 +153,8 @@
string_view text(base, 0);
for (const auto& item : items_) {
const char* const end = text.data() + text.size();
- text = string_view(end, (base + item.text_end) - end);
+ text =
+ string_view(end, static_cast<size_t>((base + item.text_end) - end));
if (item.is_conversion) {
if (!consumer.ConvertOne(item.conv, text)) return false;
} else {
diff --git a/absl/strings/internal/str_format/parser_test.cc b/absl/strings/internal/str_format/parser_test.cc
index fe0d296..e2225c6 100644
--- a/absl/strings/internal/str_format/parser_test.cc
+++ b/absl/strings/internal/str_format/parser_test.cc
@@ -15,10 +15,18 @@
#include "absl/strings/internal/str_format/parser.h"
#include <string.h>
+#include <algorithm>
+#include <initializer_list>
+#include <string>
+#include <utility>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
+#include "absl/base/config.h"
#include "absl/base/macros.h"
+#include "absl/strings/internal/str_format/constexpr_parser.h"
+#include "absl/strings/internal/str_format/extension.h"
+#include "absl/strings/string_view.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
@@ -110,10 +118,14 @@
{__LINE__, "ba", "", "ba"}, // 'b' is invalid
{__LINE__, "l", "", "l" }, // just length mod isn't okay
{__LINE__, "d", "d", "" }, // basic
+ {__LINE__, "v", "v", "" }, // basic
{__LINE__, "d ", "d", " " }, // leave suffix
{__LINE__, "dd", "d", "d" }, // don't be greedy
{__LINE__, "d9", "d", "9" }, // leave non-space suffix
{__LINE__, "dzz", "d", "zz"}, // length mod as suffix
+ {__LINE__, "3v", "", "3v"}, // 'v' cannot have modifiers
+ {__LINE__, "hv", "", "hv"}, // 'v' cannot have modifiers
+ {__LINE__, "1$v", "1$v", ""}, // 'v' can have use posix syntax
{__LINE__, "1$*2$d", "1$*2$d", "" }, // arg indexing and * allowed.
{__LINE__, "0-14.3hhd", "0-14.3hhd", ""}, // precision, width
{__LINE__, " 0-+#14.3hhd", " 0-+#14.3hhd", ""}, // flags
@@ -299,7 +311,7 @@
}
// Flag is off
- for (const char* fmt : {"3d", ".llx", "-G", "1$#X"}) {
+ for (const char* fmt : {"3d", ".llx", "-G", "1$#X", "lc"}) {
SCOPED_TRACE(fmt);
EXPECT_TRUE(Run(fmt));
EXPECT_NE(o.flags, Flags::kBasic);
diff --git a/absl/strings/internal/str_join_internal.h b/absl/strings/internal/str_join_internal.h
index 31dbf67..d97d503 100644
--- a/absl/strings/internal/str_join_internal.h
+++ b/absl/strings/internal/str_join_internal.h
@@ -229,10 +229,11 @@
std::string result;
if (start != end) {
// Sums size
- size_t result_size = start->size();
+ auto&& start_value = *start;
+ size_t result_size = start_value.size();
for (Iterator it = start; ++it != end;) {
result_size += s.size();
- result_size += it->size();
+ result_size += (*it).size();
}
if (result_size > 0) {
@@ -240,13 +241,15 @@
// Joins strings
char* result_buf = &*result.begin();
- memcpy(result_buf, start->data(), start->size());
- result_buf += start->size();
+
+ 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();
- memcpy(result_buf, it->data(), it->size());
- result_buf += it->size();
+ auto&& value = *it;
+ memcpy(result_buf, value.data(), value.size());
+ result_buf += value.size();
}
}
}
diff --git a/absl/strings/internal/str_split_internal.h b/absl/strings/internal/str_split_internal.h
index e766421..081ad85 100644
--- a/absl/strings/internal/str_split_internal.h
+++ b/absl/strings/internal/str_split_internal.h
@@ -132,7 +132,8 @@
const absl::string_view text = splitter_->text();
const absl::string_view d = delimiter_.Find(text, pos_);
if (d.data() == text.data() + text.size()) state_ = kLastState;
- curr_ = text.substr(pos_, d.data() - (text.data() + pos_));
+ curr_ = text.substr(pos_,
+ static_cast<size_t>(d.data() - (text.data() + pos_)));
pos_ += curr_.size() + d.size();
} while (!predicate_(curr_));
return *this;
@@ -234,6 +235,24 @@
HasMappedType<C>::value> {
};
+template <typename StringType, typename Container, typename = void>
+struct ShouldUseLifetimeBound : std::false_type {};
+
+template <typename StringType, typename Container>
+struct ShouldUseLifetimeBound<
+ StringType, Container,
+ std::enable_if_t<
+ std::is_same<StringType, std::string>::value &&
+ std::is_same<typename Container::value_type, absl::string_view>::value>>
+ : std::true_type {};
+
+template <typename StringType, typename First, typename Second>
+using ShouldUseLifetimeBoundForPair = std::integral_constant<
+ bool, std::is_same<StringType, std::string>::value &&
+ (std::is_same<First, absl::string_view>::value ||
+ std::is_same<Second, absl::string_view>::value)>;
+
+
// This class implements the range that is returned by absl::StrSplit(). This
// class has templated conversion operators that allow it to be implicitly
// converted to a variety of types that the caller may have specified on the
@@ -280,10 +299,24 @@
// An implicit conversion operator that is restricted to only those containers
// that the splitter is convertible to.
- template <typename Container,
- typename = typename std::enable_if<
- SplitterIsConvertibleTo<Container>::value>::type>
- operator Container() const { // NOLINT(runtime/explicit)
+ template <
+ typename Container,
+ std::enable_if_t<ShouldUseLifetimeBound<StringType, Container>::value &&
+ SplitterIsConvertibleTo<Container>::value,
+ std::nullptr_t> = nullptr>
+ // NOLINTNEXTLINE(google-explicit-constructor)
+ operator Container() const ABSL_ATTRIBUTE_LIFETIME_BOUND {
+ return ConvertToContainer<Container, typename Container::value_type,
+ HasMappedType<Container>::value>()(*this);
+ }
+
+ template <
+ typename Container,
+ std::enable_if_t<!ShouldUseLifetimeBound<StringType, Container>::value &&
+ SplitterIsConvertibleTo<Container>::value,
+ std::nullptr_t> = nullptr>
+ // NOLINTNEXTLINE(google-explicit-constructor)
+ operator Container() const {
return ConvertToContainer<Container, typename Container::value_type,
HasMappedType<Container>::value>()(*this);
}
@@ -292,8 +325,27 @@
// strings returned by the begin() iterator. Either/both of .first and .second
// will be constructed with empty strings if the iterator doesn't have a
// corresponding value.
+ template <typename First, typename Second,
+ std::enable_if_t<
+ ShouldUseLifetimeBoundForPair<StringType, First, Second>::value,
+ std::nullptr_t> = nullptr>
+ // NOLINTNEXTLINE(google-explicit-constructor)
+ operator std::pair<First, Second>() const ABSL_ATTRIBUTE_LIFETIME_BOUND {
+ return ConvertToPair<First, Second>();
+ }
+
+ template <typename First, typename Second,
+ std::enable_if_t<!ShouldUseLifetimeBoundForPair<StringType, First,
+ Second>::value,
+ std::nullptr_t> = nullptr>
+ // NOLINTNEXTLINE(google-explicit-constructor)
+ operator std::pair<First, Second>() const {
+ return ConvertToPair<First, Second>();
+ }
+
+ private:
template <typename First, typename Second>
- operator std::pair<First, Second>() const { // NOLINT(runtime/explicit)
+ std::pair<First, Second> ConvertToPair() const {
absl::string_view first, second;
auto it = begin();
if (it != end()) {
@@ -305,7 +357,6 @@
return {First(first), Second(second)};
}
- private:
// ConvertToContainer is a functor converting a Splitter to the requested
// Container of ValueType. It is specialized below to optimize splitting to
// certain combinations of Container and ValueType.
diff --git a/absl/strings/internal/string_constant.h b/absl/strings/internal/string_constant.h
index a11336b..f68b17d 100644
--- a/absl/strings/internal/string_constant.h
+++ b/absl/strings/internal/string_constant.h
@@ -35,17 +35,25 @@
// below.
template <typename T>
struct StringConstant {
+ private:
+ static constexpr bool TryConstexprEval(absl::string_view view) {
+ return view.empty() || 2 * view[0] != 1;
+ }
+
+ public:
static constexpr absl::string_view value = T{}();
constexpr absl::string_view operator()() const { return value; }
// Check to be sure `view` points to constant data.
// Otherwise, it can't be constant evaluated.
- static_assert(value.empty() || 2 * value[0] != 1,
+ static_assert(TryConstexprEval(value),
"The input string_view must point to constant data.");
};
+#ifdef ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL
template <typename T>
-constexpr absl::string_view StringConstant<T>::value; // NOLINT
+constexpr absl::string_view StringConstant<T>::value;
+#endif
// Factory function for `StringConstant` instances.
// It supports callables that have a constexpr default constructor and a
diff --git a/absl/strings/internal/stringify_sink.cc b/absl/strings/internal/stringify_sink.cc
new file mode 100644
index 0000000..7c6995a
--- /dev/null
+++ b/absl/strings/internal/stringify_sink.cc
@@ -0,0 +1,28 @@
+// Copyright 2022 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/strings/internal/stringify_sink.h"
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace strings_internal {
+
+void StringifySink::Append(size_t count, char ch) { buffer_.append(count, ch); }
+
+void StringifySink::Append(string_view v) {
+ buffer_.append(v.data(), v.size());
+}
+
+} // namespace strings_internal
+ABSL_NAMESPACE_END
+} // namespace absl
diff --git a/absl/strings/internal/stringify_sink.h b/absl/strings/internal/stringify_sink.h
new file mode 100644
index 0000000..fc3747b
--- /dev/null
+++ b/absl/strings/internal/stringify_sink.h
@@ -0,0 +1,57 @@
+// Copyright 2022 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef ABSL_STRINGS_INTERNAL_STRINGIFY_SINK_H_
+#define ABSL_STRINGS_INTERNAL_STRINGIFY_SINK_H_
+
+#include <string>
+#include <type_traits>
+#include <utility>
+
+#include "absl/strings/string_view.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+
+namespace strings_internal {
+class StringifySink {
+ public:
+ void Append(size_t count, char ch);
+
+ void Append(string_view v);
+
+ // Support `absl::Format(&sink, format, args...)`.
+ friend void AbslFormatFlush(StringifySink* sink, absl::string_view v) {
+ sink->Append(v);
+ }
+
+ private:
+ template <typename T>
+ friend string_view ExtractStringification(StringifySink& sink, const T& v);
+
+ std::string buffer_;
+};
+
+template <typename T>
+string_view ExtractStringification(StringifySink& sink, const T& v) {
+ AbslStringify(sink, v);
+ return sink.buffer_;
+}
+
+} // namespace strings_internal
+
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_STRINGS_INTERNAL_STRINGIFY_SINK_H_
diff --git a/absl/strings/internal/utf8.cc b/absl/strings/internal/utf8.cc
index 8fd8edc..7ecb93d 100644
--- a/absl/strings/internal/utf8.cc
+++ b/absl/strings/internal/utf8.cc
@@ -25,25 +25,25 @@
*buffer = static_cast<char>(utf8_char);
return 1;
} else if (utf8_char <= 0x7FF) {
- buffer[1] = 0x80 | (utf8_char & 0x3F);
+ buffer[1] = static_cast<char>(0x80 | (utf8_char & 0x3F));
utf8_char >>= 6;
- buffer[0] = 0xC0 | utf8_char;
+ buffer[0] = static_cast<char>(0xC0 | utf8_char);
return 2;
} else if (utf8_char <= 0xFFFF) {
- buffer[2] = 0x80 | (utf8_char & 0x3F);
+ buffer[2] = static_cast<char>(0x80 | (utf8_char & 0x3F));
utf8_char >>= 6;
- buffer[1] = 0x80 | (utf8_char & 0x3F);
+ buffer[1] = static_cast<char>(0x80 | (utf8_char & 0x3F));
utf8_char >>= 6;
- buffer[0] = 0xE0 | utf8_char;
+ buffer[0] = static_cast<char>(0xE0 | utf8_char);
return 3;
} else {
- buffer[3] = 0x80 | (utf8_char & 0x3F);
+ buffer[3] = static_cast<char>(0x80 | (utf8_char & 0x3F));
utf8_char >>= 6;
- buffer[2] = 0x80 | (utf8_char & 0x3F);
+ buffer[2] = static_cast<char>(0x80 | (utf8_char & 0x3F));
utf8_char >>= 6;
- buffer[1] = 0x80 | (utf8_char & 0x3F);
+ buffer[1] = static_cast<char>(0x80 | (utf8_char & 0x3F));
utf8_char >>= 6;
- buffer[0] = 0xF0 | utf8_char;
+ buffer[0] = static_cast<char>(0xF0 | utf8_char);
return 4;
}
}
diff --git a/absl/strings/match.cc b/absl/strings/match.cc
index 2d67250..72ae6a4 100644
--- a/absl/strings/match.cc
+++ b/absl/strings/match.cc
@@ -14,7 +14,16 @@
#include "absl/strings/match.h"
+#include <algorithm>
+#include <cstdint>
+
+#include "absl/base/config.h"
+#include "absl/base/internal/endian.h"
+#include "absl/base/optimization.h"
+#include "absl/numeric/bits.h"
+#include "absl/strings/ascii.h"
#include "absl/strings/internal/memutil.h"
+#include "absl/strings/string_view.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
@@ -27,6 +36,27 @@
// memcasecmp uses absl::ascii_tolower().
}
+bool StrContainsIgnoreCase(absl::string_view haystack,
+ absl::string_view needle) noexcept {
+ while (haystack.size() >= needle.size()) {
+ if (StartsWithIgnoreCase(haystack, needle)) return true;
+ haystack.remove_prefix(1);
+ }
+ return false;
+}
+
+bool StrContainsIgnoreCase(absl::string_view haystack,
+ char needle) noexcept {
+ char upper_needle = absl::ascii_toupper(static_cast<unsigned char>(needle));
+ char lower_needle = absl::ascii_tolower(static_cast<unsigned char>(needle));
+ if (upper_needle == lower_needle) {
+ return StrContains(haystack, needle);
+ } else {
+ const char both_cstr[3] = {lower_needle, upper_needle, '\0'};
+ return haystack.find_first_of(both_cstr) != absl::string_view::npos;
+ }
+}
+
bool StartsWithIgnoreCase(absl::string_view text,
absl::string_view prefix) noexcept {
return (text.size() >= prefix.size()) &&
@@ -39,5 +69,65 @@
EqualsIgnoreCase(text.substr(text.size() - suffix.size()), suffix);
}
+absl::string_view FindLongestCommonPrefix(absl::string_view a,
+ absl::string_view b) {
+ const absl::string_view::size_type limit = std::min(a.size(), b.size());
+ const char* const pa = a.data();
+ const char* const pb = b.data();
+ absl::string_view::size_type count = (unsigned) 0;
+
+ if (ABSL_PREDICT_FALSE(limit < 8)) {
+ while (ABSL_PREDICT_TRUE(count + 2 <= limit)) {
+ uint16_t xor_bytes = absl::little_endian::Load16(pa + count) ^
+ absl::little_endian::Load16(pb + count);
+ if (ABSL_PREDICT_FALSE(xor_bytes != 0)) {
+ if (ABSL_PREDICT_TRUE((xor_bytes & 0xff) == 0)) ++count;
+ return absl::string_view(pa, count);
+ }
+ count += 2;
+ }
+ if (ABSL_PREDICT_TRUE(count != limit)) {
+ if (ABSL_PREDICT_TRUE(pa[count] == pb[count])) ++count;
+ }
+ return absl::string_view(pa, count);
+ }
+
+ do {
+ uint64_t xor_bytes = absl::little_endian::Load64(pa + count) ^
+ absl::little_endian::Load64(pb + count);
+ if (ABSL_PREDICT_FALSE(xor_bytes != 0)) {
+ count += static_cast<uint64_t>(absl::countr_zero(xor_bytes) >> 3);
+ return absl::string_view(pa, count);
+ }
+ count += 8;
+ } while (ABSL_PREDICT_TRUE(count + 8 < limit));
+
+ count = limit - 8;
+ uint64_t xor_bytes = absl::little_endian::Load64(pa + count) ^
+ absl::little_endian::Load64(pb + count);
+ if (ABSL_PREDICT_TRUE(xor_bytes != 0)) {
+ count += static_cast<uint64_t>(absl::countr_zero(xor_bytes) >> 3);
+ return absl::string_view(pa, count);
+ }
+ return absl::string_view(pa, limit);
+}
+
+absl::string_view FindLongestCommonSuffix(absl::string_view a,
+ absl::string_view b) {
+ const absl::string_view::size_type limit = std::min(a.size(), b.size());
+ if (limit == 0) return absl::string_view();
+
+ const char* pa = a.data() + a.size() - 1;
+ const char* pb = b.data() + b.size() - 1;
+ absl::string_view::size_type count = (unsigned) 0;
+ while (count < limit && *pa == *pb) {
+ --pa;
+ --pb;
+ ++count;
+ }
+
+ return absl::string_view(++pa, count);
+}
+
ABSL_NAMESPACE_END
} // namespace absl
diff --git a/absl/strings/match.h b/absl/strings/match.h
index 038cbb3..1eeafbb 100644
--- a/absl/strings/match.h
+++ b/absl/strings/match.h
@@ -72,6 +72,15 @@
memcmp(text.data() + (text.size() - suffix.size()), suffix.data(),
suffix.size()) == 0);
}
+// StrContainsIgnoreCase()
+//
+// Returns whether a given ASCII string `haystack` contains the ASCII substring
+// `needle`, ignoring case in the comparison.
+bool StrContainsIgnoreCase(absl::string_view haystack,
+ absl::string_view needle) noexcept;
+
+bool StrContainsIgnoreCase(absl::string_view haystack,
+ char needle) noexcept;
// EqualsIgnoreCase()
//
@@ -94,6 +103,16 @@
bool EndsWithIgnoreCase(absl::string_view text,
absl::string_view suffix) noexcept;
+// Yields the longest prefix in common between both input strings.
+// Pointer-wise, the returned result is a subset of input "a".
+absl::string_view FindLongestCommonPrefix(absl::string_view a,
+ absl::string_view b);
+
+// Yields the longest suffix in common between both input strings.
+// Pointer-wise, the returned result is a subset of input "a".
+absl::string_view FindLongestCommonSuffix(absl::string_view a,
+ absl::string_view b);
+
ABSL_NAMESPACE_END
} // namespace absl
diff --git a/absl/strings/match_test.cc b/absl/strings/match_test.cc
index 5841bc1..6218ce4 100644
--- a/absl/strings/match_test.cc
+++ b/absl/strings/match_test.cc
@@ -14,7 +14,10 @@
#include "absl/strings/match.h"
+#include <string>
+
#include "gtest/gtest.h"
+#include "absl/strings/string_view.h"
namespace {
@@ -124,4 +127,165 @@
EXPECT_FALSE(absl::EndsWithIgnoreCase("", "fo"));
}
+TEST(MatchTest, ContainsIgnoreCase) {
+ EXPECT_TRUE(absl::StrContainsIgnoreCase("foo", "foo"));
+ EXPECT_TRUE(absl::StrContainsIgnoreCase("FOO", "Foo"));
+ EXPECT_TRUE(absl::StrContainsIgnoreCase("--FOO", "Foo"));
+ EXPECT_TRUE(absl::StrContainsIgnoreCase("FOO--", "Foo"));
+ EXPECT_FALSE(absl::StrContainsIgnoreCase("BAR", "Foo"));
+ EXPECT_FALSE(absl::StrContainsIgnoreCase("BAR", "Foo"));
+ EXPECT_TRUE(absl::StrContainsIgnoreCase("123456", "123456"));
+ EXPECT_TRUE(absl::StrContainsIgnoreCase("123456", "234"));
+ EXPECT_TRUE(absl::StrContainsIgnoreCase("", ""));
+ EXPECT_TRUE(absl::StrContainsIgnoreCase("abc", ""));
+ EXPECT_FALSE(absl::StrContainsIgnoreCase("", "a"));
+}
+
+TEST(MatchTest, ContainsCharIgnoreCase) {
+ absl::string_view a("AaBCdefg!");
+ absl::string_view b("AaBCd!");
+ EXPECT_TRUE(absl::StrContainsIgnoreCase(a, 'a'));
+ EXPECT_TRUE(absl::StrContainsIgnoreCase(a, 'A'));
+ EXPECT_TRUE(absl::StrContainsIgnoreCase(a, 'b'));
+ EXPECT_TRUE(absl::StrContainsIgnoreCase(a, 'B'));
+ EXPECT_TRUE(absl::StrContainsIgnoreCase(a, 'e'));
+ EXPECT_TRUE(absl::StrContainsIgnoreCase(a, 'E'));
+ EXPECT_FALSE(absl::StrContainsIgnoreCase(a, 'h'));
+ EXPECT_FALSE(absl::StrContainsIgnoreCase(a, 'H'));
+ EXPECT_TRUE(absl::StrContainsIgnoreCase(a, '!'));
+ EXPECT_FALSE(absl::StrContainsIgnoreCase(a, '?'));
+
+ EXPECT_TRUE(absl::StrContainsIgnoreCase(b, 'a'));
+ EXPECT_TRUE(absl::StrContainsIgnoreCase(b, 'A'));
+ EXPECT_TRUE(absl::StrContainsIgnoreCase(b, 'b'));
+ EXPECT_TRUE(absl::StrContainsIgnoreCase(b, 'B'));
+ EXPECT_FALSE(absl::StrContainsIgnoreCase(b, 'e'));
+ EXPECT_FALSE(absl::StrContainsIgnoreCase(b, 'E'));
+ EXPECT_FALSE(absl::StrContainsIgnoreCase(b, 'h'));
+ EXPECT_FALSE(absl::StrContainsIgnoreCase(b, 'H'));
+ EXPECT_TRUE(absl::StrContainsIgnoreCase(b, '!'));
+ EXPECT_FALSE(absl::StrContainsIgnoreCase(b, '?'));
+
+ EXPECT_FALSE(absl::StrContainsIgnoreCase("", 'a'));
+ EXPECT_FALSE(absl::StrContainsIgnoreCase("", 'A'));
+ EXPECT_FALSE(absl::StrContainsIgnoreCase("", '0'));
+}
+
+TEST(MatchTest, FindLongestCommonPrefix) {
+ EXPECT_EQ(absl::FindLongestCommonPrefix("", ""), "");
+ EXPECT_EQ(absl::FindLongestCommonPrefix("", "abc"), "");
+ EXPECT_EQ(absl::FindLongestCommonPrefix("abc", ""), "");
+ EXPECT_EQ(absl::FindLongestCommonPrefix("ab", "abc"), "ab");
+ EXPECT_EQ(absl::FindLongestCommonPrefix("abc", "ab"), "ab");
+ EXPECT_EQ(absl::FindLongestCommonPrefix("abc", "abd"), "ab");
+ EXPECT_EQ(absl::FindLongestCommonPrefix("abc", "abcd"), "abc");
+ EXPECT_EQ(absl::FindLongestCommonPrefix("abcd", "abcd"), "abcd");
+ EXPECT_EQ(absl::FindLongestCommonPrefix("abcd", "efgh"), "");
+
+ // "abcde" v. "abc" but in the middle of other data
+ EXPECT_EQ(absl::FindLongestCommonPrefix(
+ absl::string_view("1234 abcdef").substr(5, 5),
+ absl::string_view("5678 abcdef").substr(5, 3)),
+ "abc");
+}
+
+// Since the little-endian implementation involves a bit of if-else and various
+// return paths, the following tests aims to provide full test coverage of the
+// implementation.
+TEST(MatchTest, FindLongestCommonPrefixLoad16Mismatch) {
+ const std::string x1 = "abcdefgh";
+ const std::string x2 = "abcde_";
+ EXPECT_EQ(absl::FindLongestCommonPrefix(x1, x2), "abcde");
+ EXPECT_EQ(absl::FindLongestCommonPrefix(x2, x1), "abcde");
+}
+
+TEST(MatchTest, FindLongestCommonPrefixLoad16MatchesNoLast) {
+ const std::string x1 = "abcdef";
+ const std::string x2 = "abcdef";
+ EXPECT_EQ(absl::FindLongestCommonPrefix(x1, x2), "abcdef");
+ EXPECT_EQ(absl::FindLongestCommonPrefix(x2, x1), "abcdef");
+}
+
+TEST(MatchTest, FindLongestCommonPrefixLoad16MatchesLastCharMismatches) {
+ const std::string x1 = "abcdefg";
+ const std::string x2 = "abcdef_h";
+ EXPECT_EQ(absl::FindLongestCommonPrefix(x1, x2), "abcdef");
+ EXPECT_EQ(absl::FindLongestCommonPrefix(x2, x1), "abcdef");
+}
+
+TEST(MatchTest, FindLongestCommonPrefixLoad16MatchesLastMatches) {
+ const std::string x1 = "abcde";
+ const std::string x2 = "abcdefgh";
+ EXPECT_EQ(absl::FindLongestCommonPrefix(x1, x2), "abcde");
+ EXPECT_EQ(absl::FindLongestCommonPrefix(x2, x1), "abcde");
+}
+
+TEST(MatchTest, FindLongestCommonPrefixSize8Load64Mismatches) {
+ const std::string x1 = "abcdefghijk";
+ const std::string x2 = "abcde_g_";
+ EXPECT_EQ(absl::FindLongestCommonPrefix(x1, x2), "abcde");
+ EXPECT_EQ(absl::FindLongestCommonPrefix(x2, x1), "abcde");
+}
+
+TEST(MatchTest, FindLongestCommonPrefixSize8Load64Matches) {
+ const std::string x1 = "abcdefgh";
+ const std::string x2 = "abcdefgh";
+ EXPECT_EQ(absl::FindLongestCommonPrefix(x1, x2), "abcdefgh");
+ EXPECT_EQ(absl::FindLongestCommonPrefix(x2, x1), "abcdefgh");
+}
+
+TEST(MatchTest, FindLongestCommonPrefixSize15Load64Mismatches) {
+ const std::string x1 = "012345670123456";
+ const std::string x2 = "0123456701_34_6";
+ EXPECT_EQ(absl::FindLongestCommonPrefix(x1, x2), "0123456701");
+ EXPECT_EQ(absl::FindLongestCommonPrefix(x2, x1), "0123456701");
+}
+
+TEST(MatchTest, FindLongestCommonPrefixSize15Load64Matches) {
+ const std::string x1 = "012345670123456";
+ const std::string x2 = "0123456701234567";
+ EXPECT_EQ(absl::FindLongestCommonPrefix(x1, x2), "012345670123456");
+ EXPECT_EQ(absl::FindLongestCommonPrefix(x2, x1), "012345670123456");
+}
+
+TEST(MatchTest, FindLongestCommonPrefixSizeFirstByteOfLast8BytesMismatch) {
+ const std::string x1 = "012345670123456701234567";
+ const std::string x2 = "0123456701234567_1234567";
+ EXPECT_EQ(absl::FindLongestCommonPrefix(x1, x2), "0123456701234567");
+ EXPECT_EQ(absl::FindLongestCommonPrefix(x2, x1), "0123456701234567");
+}
+
+TEST(MatchTest, FindLongestCommonPrefixLargeLastCharMismatches) {
+ const std::string x1(300, 'x');
+ std::string x2 = x1;
+ x2.back() = '#';
+ EXPECT_EQ(absl::FindLongestCommonPrefix(x1, x2), std::string(299, 'x'));
+ EXPECT_EQ(absl::FindLongestCommonPrefix(x2, x1), std::string(299, 'x'));
+}
+
+TEST(MatchTest, FindLongestCommonPrefixLargeFullMatch) {
+ const std::string x1(300, 'x');
+ const std::string x2 = x1;
+ EXPECT_EQ(absl::FindLongestCommonPrefix(x1, x2), std::string(300, 'x'));
+ EXPECT_EQ(absl::FindLongestCommonPrefix(x2, x1), std::string(300, 'x'));
+}
+
+TEST(MatchTest, FindLongestCommonSuffix) {
+ EXPECT_EQ(absl::FindLongestCommonSuffix("", ""), "");
+ EXPECT_EQ(absl::FindLongestCommonSuffix("", "abc"), "");
+ EXPECT_EQ(absl::FindLongestCommonSuffix("abc", ""), "");
+ EXPECT_EQ(absl::FindLongestCommonSuffix("bc", "abc"), "bc");
+ EXPECT_EQ(absl::FindLongestCommonSuffix("abc", "bc"), "bc");
+ EXPECT_EQ(absl::FindLongestCommonSuffix("abc", "dbc"), "bc");
+ EXPECT_EQ(absl::FindLongestCommonSuffix("bcd", "abcd"), "bcd");
+ EXPECT_EQ(absl::FindLongestCommonSuffix("abcd", "abcd"), "abcd");
+ EXPECT_EQ(absl::FindLongestCommonSuffix("abcd", "efgh"), "");
+
+ // "abcde" v. "cde" but in the middle of other data
+ EXPECT_EQ(absl::FindLongestCommonSuffix(
+ absl::string_view("1234 abcdef").substr(5, 5),
+ absl::string_view("5678 abcdef").substr(7, 3)),
+ "cde");
+}
+
} // namespace
diff --git a/absl/strings/numbers.cc b/absl/strings/numbers.cc
index 966d94b..882c3a8 100644
--- a/absl/strings/numbers.cc
+++ b/absl/strings/numbers.cc
@@ -20,30 +20,36 @@
#include <algorithm>
#include <cassert>
#include <cfloat> // for DBL_DIG and FLT_DIG
+#include <climits>
#include <cmath> // for HUGE_VAL
+#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iterator>
#include <limits>
-#include <memory>
+#include <system_error> // NOLINT(build/c++11)
+#include <type_traits>
#include <utility>
#include "absl/base/attributes.h"
+#include "absl/base/config.h"
+#include "absl/base/internal/endian.h"
#include "absl/base/internal/raw_logging.h"
+#include "absl/base/nullability.h"
+#include "absl/base/optimization.h"
#include "absl/numeric/bits.h"
+#include "absl/numeric/int128.h"
#include "absl/strings/ascii.h"
#include "absl/strings/charconv.h"
-#include "absl/strings/escaping.h"
-#include "absl/strings/internal/memutil.h"
#include "absl/strings/match.h"
-#include "absl/strings/str_cat.h"
+#include "absl/strings/string_view.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
-bool SimpleAtof(absl::string_view str, float* out) {
+bool SimpleAtof(absl::string_view str, absl::Nonnull<float*> out) {
*out = 0.0;
str = StripAsciiWhitespace(str);
// std::from_chars doesn't accept an initial +, but SimpleAtof does, so if one
@@ -74,7 +80,7 @@
return true;
}
-bool SimpleAtod(absl::string_view str, double* out) {
+bool SimpleAtod(absl::string_view str, absl::Nonnull<double*> out) {
*out = 0.0;
str = StripAsciiWhitespace(str);
// std::from_chars doesn't accept an initial +, but SimpleAtod does, so if one
@@ -105,7 +111,7 @@
return true;
}
-bool SimpleAtob(absl::string_view str, bool* out) {
+bool SimpleAtob(absl::string_view str, absl::Nonnull<bool*> out) {
ABSL_RAW_CHECK(out != nullptr, "Output pointer must not be nullptr.");
if (EqualsIgnoreCase(str, "true") || EqualsIgnoreCase(str, "t") ||
EqualsIgnoreCase(str, "yes") || EqualsIgnoreCase(str, "y") ||
@@ -136,144 +142,424 @@
namespace {
-// Used to optimize printing a decimal number's final digit.
-const char one_ASCII_final_digits[10][2] {
- {'0', 0}, {'1', 0}, {'2', 0}, {'3', 0}, {'4', 0},
- {'5', 0}, {'6', 0}, {'7', 0}, {'8', 0}, {'9', 0},
+// Various routines to encode integers to strings.
+
+// We split data encodings into a group of 2 digits, 4 digits, 8 digits as
+// it's easier to combine powers of two into scalar arithmetic.
+
+// Previous implementation used a lookup table of 200 bytes for every 2 bytes
+// and it was memory bound, any L1 cache miss would result in a much slower
+// result. When benchmarking with a cache eviction rate of several percent,
+// this implementation proved to be better.
+
+// These constants represent '00', '0000' and '00000000' as ascii strings in
+// integers. We can add these numbers if we encode to bytes from 0 to 9. as
+// 'i' = '0' + i for 0 <= i <= 9.
+constexpr uint32_t kTwoZeroBytes = 0x0101 * '0';
+constexpr uint64_t kFourZeroBytes = 0x01010101 * '0';
+constexpr uint64_t kEightZeroBytes = 0x0101010101010101ull * '0';
+
+template <typename T>
+constexpr T Pow(T base, uint32_t n) {
+ // Exponentiation by squaring
+ return static_cast<T>((n > 1 ? Pow(base * base, n >> 1) : static_cast<T>(1)) *
+ ((n & 1) ? base : static_cast<T>(1)));
+}
+
+// Given n, calculates C where the following holds for all 0 <= x < Pow(100, n):
+// x / Pow(10, n) == x * C / Pow(2, n * 10)
+// In other words, it allows us to divide by a power of 10 via a single
+// multiplication and bit shifts, assuming the input will be smaller than the
+// square of that power of 10.
+template <typename T>
+constexpr T ComputePowerOf100DivisionCoefficient(uint32_t n) {
+ if (n > 4) {
+ // This doesn't work for large powers of 100, due to overflow
+ abort();
+ }
+ T denom = 16 - 1;
+ T num = (denom + 1) - 10;
+ T gcd = 3; // Greatest common divisor of numerator and denominator
+ denom = Pow(denom / gcd, n);
+ num = Pow(num / gcd, 9 * n);
+ T quotient = num / denom;
+ if (num % denom >= denom / 2) {
+ // Round up, since the remainder is more than half the denominator
+ ++quotient;
+ }
+ return quotient;
+}
+
+// * kDivisionBy10Mul / kDivisionBy10Div is a division by 10 for values from 0
+// to 99. It's also a division of a structure [k takes 2 bytes][m takes 2
+// bytes], then * kDivisionBy10Mul / kDivisionBy10Div will be [k / 10][m / 10].
+// It allows parallel division.
+constexpr uint64_t kDivisionBy10Mul =
+ ComputePowerOf100DivisionCoefficient<uint64_t>(1);
+static_assert(kDivisionBy10Mul == 103,
+ "division coefficient for 10 is incorrect");
+constexpr uint64_t kDivisionBy10Div = 1 << 10;
+
+// * kDivisionBy100Mul / kDivisionBy100Div is a division by 100 for values from
+// 0 to 9999.
+constexpr uint64_t kDivisionBy100Mul =
+ ComputePowerOf100DivisionCoefficient<uint64_t>(2);
+static_assert(kDivisionBy100Mul == 10486,
+ "division coefficient for 100 is incorrect");
+constexpr uint64_t kDivisionBy100Div = 1 << 20;
+
+static_assert(ComputePowerOf100DivisionCoefficient<uint64_t>(3) == 1073742,
+ "division coefficient for 1000 is incorrect");
+
+// Same as `PrepareEightDigits`, but produces 2 digits for integers < 100.
+inline uint32_t PrepareTwoDigitsImpl(uint32_t i, bool reversed) {
+ assert(i < 100);
+ uint32_t div10 = (i * kDivisionBy10Mul) / kDivisionBy10Div;
+ uint32_t mod10 = i - 10u * div10;
+ return (div10 << (reversed ? 8 : 0)) + (mod10 << (reversed ? 0 : 8));
+}
+inline uint32_t PrepareTwoDigits(uint32_t i) {
+ return PrepareTwoDigitsImpl(i, false);
+}
+
+// Same as `PrepareEightDigits`, but produces 4 digits for integers < 10000.
+inline uint32_t PrepareFourDigitsImpl(uint32_t n, bool reversed) {
+ // We split lower 2 digits and upper 2 digits of n into 2 byte consecutive
+ // blocks. 123 -> [\0\1][\0\23]. We divide by 10 both blocks
+ // (it's 1 division + zeroing upper bits), and compute modulo 10 as well "in
+ // parallel". Then we combine both results to have both ASCII digits,
+ // strip trailing zeros, add ASCII '0000' and return.
+ uint32_t div100 = (n * kDivisionBy100Mul) / kDivisionBy100Div;
+ uint32_t mod100 = n - 100ull * div100;
+ uint32_t hundreds =
+ (mod100 << (reversed ? 0 : 16)) + (div100 << (reversed ? 16 : 0));
+ uint32_t tens = (hundreds * kDivisionBy10Mul) / kDivisionBy10Div;
+ tens &= (0xFull << 16) | 0xFull;
+ tens = (tens << (reversed ? 8 : 0)) +
+ static_cast<uint32_t>((hundreds - 10ull * tens) << (reversed ? 0 : 8));
+ return tens;
+}
+inline uint32_t PrepareFourDigits(uint32_t n) {
+ return PrepareFourDigitsImpl(n, false);
+}
+inline uint32_t PrepareFourDigitsReversed(uint32_t n) {
+ return PrepareFourDigitsImpl(n, true);
+}
+
+// Helper function to produce an ASCII representation of `i`.
+//
+// Function returns an 8-byte integer which when summed with `kEightZeroBytes`,
+// can be treated as a printable buffer with ascii representation of `i`,
+// possibly with leading zeros.
+//
+// Example:
+//
+// uint64_t buffer = PrepareEightDigits(102030) + kEightZeroBytes;
+// char* ascii = reinterpret_cast<char*>(&buffer);
+// // Note two leading zeros:
+// EXPECT_EQ(absl::string_view(ascii, 8), "00102030");
+//
+// If `Reversed` is set to true, the result becomes reversed to "03020100".
+//
+// Pre-condition: `i` must be less than 100000000.
+inline uint64_t PrepareEightDigitsImpl(uint32_t i, bool reversed) {
+ ABSL_ASSUME(i < 10000'0000);
+ // Prepare 2 blocks of 4 digits "in parallel".
+ uint32_t hi = i / 10000;
+ uint32_t lo = i % 10000;
+ uint64_t merged = (uint64_t{hi} << (reversed ? 32 : 0)) |
+ (uint64_t{lo} << (reversed ? 0 : 32));
+ uint64_t div100 = ((merged * kDivisionBy100Mul) / kDivisionBy100Div) &
+ ((0x7Full << 32) | 0x7Full);
+ uint64_t mod100 = merged - 100ull * div100;
+ uint64_t hundreds =
+ (mod100 << (reversed ? 0 : 16)) + (div100 << (reversed ? 16 : 0));
+ uint64_t tens = (hundreds * kDivisionBy10Mul) / kDivisionBy10Div;
+ tens &= (0xFull << 48) | (0xFull << 32) | (0xFull << 16) | 0xFull;
+ tens = (tens << (reversed ? 8 : 0)) +
+ ((hundreds - 10ull * tens) << (reversed ? 0 : 8));
+ return tens;
+}
+inline uint64_t PrepareEightDigits(uint32_t i) {
+ return PrepareEightDigitsImpl(i, false);
+}
+inline uint64_t PrepareEightDigitsReversed(uint32_t i) {
+ return PrepareEightDigitsImpl(i, true);
+}
+
+template <typename T, typename BackwardIt>
+class FastUIntToStringConverter {
+ static_assert(
+ std::is_same<T, decltype(+std::declval<T>())>::value,
+ "to avoid code bloat, only instantiate this for int and larger types");
+ static_assert(std::is_unsigned<T>::value,
+ "this class is only for unsigned types");
+
+ public:
+ // Outputs the given number backward (like with std::copy_backward),
+ // starting from the end of the string.
+ // The number of digits in the number must have been already measured and
+ // passed *exactly*, otherwise the behavior is undefined.
+ // (This is an optimization, as calculating the number of digits again would
+ // slow down the hot path.)
+ // Returns an iterator to the start of the suffix that was appended.
+ static BackwardIt FastIntToBufferBackward(T v, BackwardIt end) {
+ // THIS IS A HOT FUNCTION with a very deliberate structure to exploit branch
+ // prediction and shorten the critical path for smaller numbers.
+ // Do not move around the if/else blocks or attempt to simplify it
+ // without benchmarking any changes.
+
+ if (v < 10) {
+ goto AT_LEAST_1 /* NOTE: mandatory for the 0 case */;
+ }
+ if (v < 1000) {
+ goto AT_LEAST_10;
+ }
+ if (v < 10000000) {
+ goto AT_LEAST_1000;
+ }
+
+ if (v >= 100000000 / 10) {
+ if (v >= 10000000000000000 / 10) {
+ DoFastIntToBufferBackward<8>(v, end);
+ }
+ DoFastIntToBufferBackward<8>(v, end);
+ }
+
+ if (v >= 10000 / 10) {
+ AT_LEAST_1000:
+ DoFastIntToBufferBackward<4>(v, end);
+ }
+
+ if (v >= 100 / 10) {
+ AT_LEAST_10:
+ DoFastIntToBufferBackward<2>(v, end);
+ }
+
+ if (v >= 10 / 10) {
+ AT_LEAST_1:
+ end = DoFastIntToBufferBackward(v, end, std::integral_constant<int, 1>());
+ }
+ return end;
+ }
+
+ private:
+ // Only assume pointers are contiguous for now. String and vector iterators
+ // could be special-cased as well, but there's no need for them here.
+ // With C++20 we can probably switch to std::contiguous_iterator_tag.
+ static constexpr bool kIsContiguousIterator =
+ std::is_pointer<BackwardIt>::value;
+
+ template <int Exponent>
+ static void DoFastIntToBufferBackward(T& v, BackwardIt& end) {
+ constexpr T kModulus = Pow<T>(10, Exponent);
+ T remainder = static_cast<T>(v % kModulus);
+ v = static_cast<T>(v / kModulus);
+ end = DoFastIntToBufferBackward(remainder, end,
+ std::integral_constant<int, Exponent>());
+ }
+
+ static BackwardIt DoFastIntToBufferBackward(const T&, BackwardIt end,
+ std::integral_constant<int, 0>) {
+ return end;
+ }
+
+ static BackwardIt DoFastIntToBufferBackward(T v, BackwardIt end,
+ std::integral_constant<int, 1>) {
+ *--end = static_cast<char>('0' + v);
+ return DoFastIntToBufferBackward(v, end, std::integral_constant<int, 0>());
+ }
+
+ static BackwardIt DoFastIntToBufferBackward(T v, BackwardIt end,
+ std::integral_constant<int, 4>) {
+ if (kIsContiguousIterator) {
+ const uint32_t digits =
+ PrepareFourDigits(static_cast<uint32_t>(v)) + kFourZeroBytes;
+ end -= sizeof(digits);
+ little_endian::Store32(&*end, digits);
+ } else {
+ uint32_t digits =
+ PrepareFourDigitsReversed(static_cast<uint32_t>(v)) + kFourZeroBytes;
+ for (size_t i = 0; i < sizeof(digits); ++i) {
+ *--end = static_cast<char>(digits);
+ digits >>= CHAR_BIT;
+ }
+ }
+ return end;
+ }
+
+ static BackwardIt DoFastIntToBufferBackward(T v, BackwardIt end,
+ std::integral_constant<int, 8>) {
+ if (kIsContiguousIterator) {
+ const uint64_t digits =
+ PrepareEightDigits(static_cast<uint32_t>(v)) + kEightZeroBytes;
+ end -= sizeof(digits);
+ little_endian::Store64(&*end, digits);
+ } else {
+ uint64_t digits = PrepareEightDigitsReversed(static_cast<uint32_t>(v)) +
+ kEightZeroBytes;
+ for (size_t i = 0; i < sizeof(digits); ++i) {
+ *--end = static_cast<char>(digits);
+ digits >>= CHAR_BIT;
+ }
+ }
+ return end;
+ }
+
+ template <int Digits>
+ static BackwardIt DoFastIntToBufferBackward(
+ T v, BackwardIt end, std::integral_constant<int, Digits>) {
+ constexpr int kLogModulus = Digits - Digits / 2;
+ constexpr T kModulus = Pow(static_cast<T>(10), kLogModulus);
+ bool is_safe_to_use_division_trick = Digits <= 8;
+ T quotient, remainder;
+ if (is_safe_to_use_division_trick) {
+ constexpr uint64_t kCoefficient =
+ ComputePowerOf100DivisionCoefficient<uint64_t>(kLogModulus);
+ quotient = (v * kCoefficient) >> (10 * kLogModulus);
+ remainder = v - quotient * kModulus;
+ } else {
+ quotient = v / kModulus;
+ remainder = v % kModulus;
+ }
+ end = DoFastIntToBufferBackward(remainder, end,
+ std::integral_constant<int, kLogModulus>());
+ return DoFastIntToBufferBackward(
+ quotient, end, std::integral_constant<int, Digits - kLogModulus>());
+ }
};
+// Returns an iterator to the start of the suffix that was appended
+template <typename T, typename BackwardIt>
+std::enable_if_t<std::is_unsigned<T>::value, BackwardIt>
+DoFastIntToBufferBackward(T v, BackwardIt end, uint32_t digits) {
+ using PromotedT = std::decay_t<decltype(+v)>;
+ using Converter = FastUIntToStringConverter<PromotedT, BackwardIt>;
+ (void)digits;
+ return Converter().FastIntToBufferBackward(v, end);
+}
+
+template <typename T, typename BackwardIt>
+std::enable_if_t<std::is_signed<T>::value, BackwardIt>
+DoFastIntToBufferBackward(T v, BackwardIt end, uint32_t digits) {
+ if (absl::numbers_internal::IsNegative(v)) {
+ // Store the minus sign *before* we produce the number itself, not after.
+ // This gets us a tail call.
+ end[-static_cast<ptrdiff_t>(digits) - 1] = '-';
+ }
+ return DoFastIntToBufferBackward(
+ absl::numbers_internal::UnsignedAbsoluteValue(v), end, digits);
+}
+
+template <class T>
+std::enable_if_t<std::is_integral<T>::value, int>
+GetNumDigitsOrNegativeIfNegativeImpl(T v) {
+ const auto /* either bool or std::false_type */ is_negative =
+ absl::numbers_internal::IsNegative(v);
+ const int digits = static_cast<int>(absl::numbers_internal::Base10Digits(
+ absl::numbers_internal::UnsignedAbsoluteValue(v)));
+ return is_negative ? ~digits : digits;
+}
+
} // namespace
-char* numbers_internal::FastIntToBuffer(uint32_t i, char* buffer) {
- uint32_t digits;
- // The idea of this implementation is to trim the number of divides to as few
- // as possible, and also reducing memory stores and branches, by going in
- // steps of two digits at a time rather than one whenever possible.
- // The huge-number case is first, in the hopes that the compiler will output
- // that case in one branch-free block of code, and only output conditional
- // branches into it from below.
- if (i >= 1000000000) { // >= 1,000,000,000
- digits = i / 100000000; // 100,000,000
- i -= digits * 100000000;
- PutTwoDigits(digits, buffer);
- buffer += 2;
- lt100_000_000:
- digits = i / 1000000; // 1,000,000
- i -= digits * 1000000;
- PutTwoDigits(digits, buffer);
- buffer += 2;
- lt1_000_000:
- digits = i / 10000; // 10,000
- i -= digits * 10000;
- PutTwoDigits(digits, buffer);
- buffer += 2;
- lt10_000:
- digits = i / 100;
- i -= digits * 100;
- PutTwoDigits(digits, buffer);
- buffer += 2;
- lt100:
- digits = i;
- PutTwoDigits(digits, buffer);
- buffer += 2;
- *buffer = 0;
- return buffer;
- }
-
- if (i < 100) {
- digits = i;
- if (i >= 10) goto lt100;
- memcpy(buffer, one_ASCII_final_digits[i], 2);
- return buffer + 1;
- }
- if (i < 10000) { // 10,000
- if (i >= 1000) goto lt10_000;
- digits = i / 100;
- i -= digits * 100;
- *buffer++ = '0' + digits;
- goto lt100;
- }
- if (i < 1000000) { // 1,000,000
- if (i >= 100000) goto lt1_000_000;
- digits = i / 10000; // 10,000
- i -= digits * 10000;
- *buffer++ = '0' + digits;
- goto lt10_000;
- }
- if (i < 100000000) { // 100,000,000
- if (i >= 10000000) goto lt100_000_000;
- digits = i / 1000000; // 1,000,000
- i -= digits * 1000000;
- *buffer++ = '0' + digits;
- goto lt1_000_000;
- }
- // we already know that i < 1,000,000,000
- digits = i / 100000000; // 100,000,000
- i -= digits * 100000000;
- *buffer++ = '0' + digits;
- goto lt100_000_000;
+void numbers_internal::PutTwoDigits(uint32_t i, absl::Nonnull<char*> buf) {
+ little_endian::Store16(
+ buf, static_cast<uint16_t>(PrepareTwoDigits(i) + kTwoZeroBytes));
}
-char* numbers_internal::FastIntToBuffer(int32_t i, char* buffer) {
- uint32_t u = i;
- if (i < 0) {
- *buffer++ = '-';
- // We need to do the negation in modular (i.e., "unsigned")
- // arithmetic; MSVC++ apprently warns for plain "-u", so
- // we write the equivalent expression "0 - u" instead.
- u = 0 - u;
- }
- return numbers_internal::FastIntToBuffer(u, buffer);
+absl::Nonnull<char*> numbers_internal::FastIntToBuffer(
+ uint32_t i, absl::Nonnull<char*> buffer) {
+ const uint32_t digits = absl::numbers_internal::Base10Digits(i);
+ buffer += digits;
+ *buffer = '\0'; // We're going backward, so store this first
+ FastIntToBufferBackward(i, buffer, digits);
+ return buffer;
}
-char* numbers_internal::FastIntToBuffer(uint64_t i, char* buffer) {
- uint32_t u32 = static_cast<uint32_t>(i);
- if (u32 == i) return numbers_internal::FastIntToBuffer(u32, buffer);
-
- // Here we know i has at least 10 decimal digits.
- uint64_t top_1to11 = i / 1000000000;
- u32 = static_cast<uint32_t>(i - top_1to11 * 1000000000);
- uint32_t top_1to11_32 = static_cast<uint32_t>(top_1to11);
-
- if (top_1to11_32 == top_1to11) {
- buffer = numbers_internal::FastIntToBuffer(top_1to11_32, buffer);
- } else {
- // top_1to11 has more than 32 bits too; print it in two steps.
- uint32_t top_8to9 = static_cast<uint32_t>(top_1to11 / 100);
- uint32_t mid_2 = static_cast<uint32_t>(top_1to11 - top_8to9 * 100);
- buffer = numbers_internal::FastIntToBuffer(top_8to9, buffer);
- PutTwoDigits(mid_2, buffer);
- buffer += 2;
- }
-
- // We have only 9 digits now, again the maximum uint32_t can handle fully.
- uint32_t digits = u32 / 10000000; // 10,000,000
- u32 -= digits * 10000000;
- PutTwoDigits(digits, buffer);
- buffer += 2;
- digits = u32 / 100000; // 100,000
- u32 -= digits * 100000;
- PutTwoDigits(digits, buffer);
- buffer += 2;
- digits = u32 / 1000; // 1,000
- u32 -= digits * 1000;
- PutTwoDigits(digits, buffer);
- buffer += 2;
- digits = u32 / 10;
- u32 -= digits * 10;
- PutTwoDigits(digits, buffer);
- buffer += 2;
- memcpy(buffer, one_ASCII_final_digits[u32], 2);
- return buffer + 1;
+absl::Nonnull<char*> numbers_internal::FastIntToBuffer(
+ int32_t i, absl::Nonnull<char*> buffer) {
+ buffer += static_cast<int>(i < 0);
+ uint32_t digits = absl::numbers_internal::Base10Digits(
+ absl::numbers_internal::UnsignedAbsoluteValue(i));
+ buffer += digits;
+ *buffer = '\0'; // We're going backward, so store this first
+ FastIntToBufferBackward(i, buffer, digits);
+ return buffer;
}
-char* numbers_internal::FastIntToBuffer(int64_t i, char* buffer) {
- uint64_t u = i;
- if (i < 0) {
- *buffer++ = '-';
- u = 0 - u;
- }
- return numbers_internal::FastIntToBuffer(u, buffer);
+absl::Nonnull<char*> numbers_internal::FastIntToBuffer(
+ uint64_t i, absl::Nonnull<char*> buffer) {
+ uint32_t digits = absl::numbers_internal::Base10Digits(i);
+ buffer += digits;
+ *buffer = '\0'; // We're going backward, so store this first
+ FastIntToBufferBackward(i, buffer, digits);
+ return buffer;
+}
+
+absl::Nonnull<char*> numbers_internal::FastIntToBuffer(
+ int64_t i, absl::Nonnull<char*> buffer) {
+ buffer += static_cast<int>(i < 0);
+ uint32_t digits = absl::numbers_internal::Base10Digits(
+ absl::numbers_internal::UnsignedAbsoluteValue(i));
+ buffer += digits;
+ *buffer = '\0'; // We're going backward, so store this first
+ FastIntToBufferBackward(i, buffer, digits);
+ return buffer;
+}
+
+absl::Nonnull<char*> numbers_internal::FastIntToBufferBackward(
+ uint32_t i, absl::Nonnull<char*> buffer_end, uint32_t exact_digit_count) {
+ return DoFastIntToBufferBackward(i, buffer_end, exact_digit_count);
+}
+
+absl::Nonnull<char*> numbers_internal::FastIntToBufferBackward(
+ int32_t i, absl::Nonnull<char*> buffer_end, uint32_t exact_digit_count) {
+ return DoFastIntToBufferBackward(i, buffer_end, exact_digit_count);
+}
+
+absl::Nonnull<char*> numbers_internal::FastIntToBufferBackward(
+ uint64_t i, absl::Nonnull<char*> buffer_end, uint32_t exact_digit_count) {
+ return DoFastIntToBufferBackward(i, buffer_end, exact_digit_count);
+}
+
+absl::Nonnull<char*> numbers_internal::FastIntToBufferBackward(
+ int64_t i, absl::Nonnull<char*> buffer_end, uint32_t exact_digit_count) {
+ return DoFastIntToBufferBackward(i, buffer_end, exact_digit_count);
+}
+
+int numbers_internal::GetNumDigitsOrNegativeIfNegative(signed char v) {
+ return GetNumDigitsOrNegativeIfNegativeImpl(v);
+}
+int numbers_internal::GetNumDigitsOrNegativeIfNegative(unsigned char v) {
+ return GetNumDigitsOrNegativeIfNegativeImpl(v);
+}
+int numbers_internal::GetNumDigitsOrNegativeIfNegative(short v) { // NOLINT
+ return GetNumDigitsOrNegativeIfNegativeImpl(v);
+}
+int numbers_internal::GetNumDigitsOrNegativeIfNegative(
+ unsigned short v) { // NOLINT
+ return GetNumDigitsOrNegativeIfNegativeImpl(v);
+}
+int numbers_internal::GetNumDigitsOrNegativeIfNegative(int v) {
+ return GetNumDigitsOrNegativeIfNegativeImpl(v);
+}
+int numbers_internal::GetNumDigitsOrNegativeIfNegative(unsigned int v) {
+ return GetNumDigitsOrNegativeIfNegativeImpl(v);
+}
+int numbers_internal::GetNumDigitsOrNegativeIfNegative(long v) { // NOLINT
+ return GetNumDigitsOrNegativeIfNegativeImpl(v);
+}
+int numbers_internal::GetNumDigitsOrNegativeIfNegative(
+ unsigned long v) { // NOLINT
+ return GetNumDigitsOrNegativeIfNegativeImpl(v);
+}
+int numbers_internal::GetNumDigitsOrNegativeIfNegative(long long v) { // NOLINT
+ return GetNumDigitsOrNegativeIfNegativeImpl(v);
+}
+int numbers_internal::GetNumDigitsOrNegativeIfNegative(
+ unsigned long long v) { // NOLINT
+ return GetNumDigitsOrNegativeIfNegativeImpl(v);
}
// Given a 128-bit number expressed as a pair of uint64_t, high half first,
@@ -329,7 +615,7 @@
result = Mul32(result, 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5);
expfive -= 13;
}
- constexpr int powers_of_five[13] = {
+ constexpr uint32_t powers_of_five[13] = {
1,
5,
5 * 5,
@@ -404,14 +690,14 @@
// we multiply it by 65536 and see if the fractional part is close to 32768.
// (The number doesn't have to be a power of two,but powers of two are faster)
uint64_t d64k = d * 65536;
- int dddddd; // A 6-digit decimal integer.
+ uint32_t dddddd; // A 6-digit decimal integer.
if ((d64k % 65536) == 32767 || (d64k % 65536) == 32768) {
// OK, it's fairly likely that precision was lost above, which is
// not a surprise given only 52 mantissa bits are available. Therefore
// redo the calculation using 128-bit numbers. (64 bits are not enough).
// Start out with digits rounded down; maybe add one below.
- dddddd = static_cast<int>(d64k / 65536);
+ dddddd = static_cast<uint32_t>(d64k / 65536);
// mantissa is a 64-bit integer representing M.mmm... * 2^63. The actual
// value we're representing, of course, is M.mmm... * 2^exp2.
@@ -461,7 +747,7 @@
}
} else {
// Here, we are not close to the edge.
- dddddd = static_cast<int>((d64k + 32768) / 65536);
+ dddddd = static_cast<uint32_t>((d64k + 32768) / 65536);
}
if (dddddd == 1000000) {
dddddd = 100000;
@@ -469,7 +755,7 @@
}
exp_dig.exponent = exp;
- int two_digits = dddddd / 10000;
+ uint32_t two_digits = dddddd / 10000;
dddddd -= two_digits * 10000;
numbers_internal::PutTwoDigits(two_digits, &exp_dig.digits[0]);
@@ -483,7 +769,8 @@
// Helper function for fast formatting of floating-point.
// The result is the same as "%g", a.k.a. "%.6g".
-size_t numbers_internal::SixDigitsToBuffer(double d, char* const buffer) {
+size_t numbers_internal::SixDigitsToBuffer(double d,
+ absl::Nonnull<char*> const buffer) {
static_assert(std::numeric_limits<float>::is_iec559,
"IEEE-754/IEC-559 support only");
@@ -499,15 +786,15 @@
if (std::signbit(d)) *out++ = '-';
*out++ = '0';
*out = 0;
- return out - buffer;
+ return static_cast<size_t>(out - buffer);
}
if (d < 0) {
*out++ = '-';
d = -d;
}
- if (std::isinf(d)) {
+ if (d > std::numeric_limits<double>::max()) {
strcpy(out, "inf"); // NOLINT(runtime/printf)
- return out + 3 - buffer;
+ return static_cast<size_t>(out + 3 - buffer);
}
auto exp_dig = SplitToSix(d);
@@ -519,7 +806,7 @@
case 5:
memcpy(out, &digits[0], 6), out += 6;
*out = 0;
- return out - buffer;
+ return static_cast<size_t>(out - buffer);
case 4:
memcpy(out, &digits[0], 5), out += 5;
if (digits[5] != '0') {
@@ -527,7 +814,7 @@
*out++ = digits[5];
}
*out = 0;
- return out - buffer;
+ return static_cast<size_t>(out - buffer);
case 3:
memcpy(out, &digits[0], 4), out += 4;
if ((digits[5] | digits[4]) != '0') {
@@ -536,7 +823,7 @@
if (digits[5] != '0') *out++ = digits[5];
}
*out = 0;
- return out - buffer;
+ return static_cast<size_t>(out - buffer);
case 2:
memcpy(out, &digits[0], 3), out += 3;
*out++ = '.';
@@ -545,7 +832,7 @@
while (out[-1] == '0') --out;
if (out[-1] == '.') --out;
*out = 0;
- return out - buffer;
+ return static_cast<size_t>(out - buffer);
case 1:
memcpy(out, &digits[0], 2), out += 2;
*out++ = '.';
@@ -554,7 +841,7 @@
while (out[-1] == '0') --out;
if (out[-1] == '.') --out;
*out = 0;
- return out - buffer;
+ return static_cast<size_t>(out - buffer);
case 0:
memcpy(out, &digits[0], 1), out += 1;
*out++ = '.';
@@ -563,7 +850,7 @@
while (out[-1] == '0') --out;
if (out[-1] == '.') --out;
*out = 0;
- return out - buffer;
+ return static_cast<size_t>(out - buffer);
case -4:
out[2] = '0';
++out;
@@ -582,7 +869,7 @@
out += 6;
while (out[-1] == '0') --out;
*out = 0;
- return out - buffer;
+ return static_cast<size_t>(out - buffer);
}
assert(exp < -4 || exp >= 6);
out[0] = digits[0];
@@ -601,12 +888,12 @@
if (exp > 99) {
int dig1 = exp / 100;
exp -= dig1 * 100;
- *out++ = '0' + dig1;
+ *out++ = '0' + static_cast<char>(dig1);
}
- PutTwoDigits(exp, out);
+ PutTwoDigits(static_cast<uint32_t>(exp), out);
out += 2;
*out = 0;
- return out - buffer;
+ return static_cast<size_t>(out - buffer);
}
namespace {
@@ -630,9 +917,10 @@
36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36};
// Parse the sign and optional hex or oct prefix in text.
-inline bool safe_parse_sign_and_base(absl::string_view* text /*inout*/,
- int* base_ptr /*inout*/,
- bool* negative_ptr /*output*/) {
+inline bool safe_parse_sign_and_base(
+ absl::Nonnull<absl::string_view*> text /*inout*/,
+ absl::Nonnull<int*> base_ptr /*inout*/,
+ absl::Nonnull<bool*> negative_ptr /*output*/) {
if (text->data() == nullptr) {
return false;
}
@@ -642,10 +930,12 @@
int base = *base_ptr;
// Consume whitespace.
- while (start < end && absl::ascii_isspace(start[0])) {
+ while (start < end &&
+ absl::ascii_isspace(static_cast<unsigned char>(start[0]))) {
++start;
}
- while (start < end && absl::ascii_isspace(end[-1])) {
+ while (start < end &&
+ absl::ascii_isspace(static_cast<unsigned char>(end[-1]))) {
--end;
}
if (start >= end) {
@@ -694,7 +984,7 @@
} else {
return false;
}
- *text = absl::string_view(start, end - start);
+ *text = absl::string_view(start, static_cast<size_t>(end - start));
*base_ptr = base;
return true;
}
@@ -757,8 +1047,8 @@
//
// uint128& operator/=(uint128) is not constexpr, so hardcode the resulting
// array to avoid a static initializer.
-template<>
-const uint128 LookupTables<uint128>::kVmaxOverBase[] = {
+template <>
+ABSL_CONST_INIT const uint128 LookupTables<uint128>::kVmaxOverBase[] = {
0,
0,
MakeUint128(9223372036854775807u, 18446744073709551615u),
@@ -809,8 +1099,8 @@
//
// int128& operator/=(int128) is not constexpr, so hardcode the resulting array
// to avoid a static initializer.
-template<>
-const int128 LookupTables<int128>::kVmaxOverBase[] = {
+template <>
+ABSL_CONST_INIT const int128 LookupTables<int128>::kVmaxOverBase[] = {
0,
0,
MakeInt128(4611686018427387903, 18446744073709551615u),
@@ -862,8 +1152,8 @@
//
// int128& operator/=(int128) is not constexpr, so hardcode the resulting array
// to avoid a static initializer.
-template<>
-const int128 LookupTables<int128>::kVminOverBase[] = {
+template <>
+ABSL_CONST_INIT const int128 LookupTables<int128>::kVminOverBase[] = {
0,
0,
MakeInt128(-4611686018427387904, 0u),
@@ -904,33 +1194,34 @@
};
template <typename IntType>
-const IntType LookupTables<IntType>::kVmaxOverBase[] =
+ABSL_CONST_INIT const IntType LookupTables<IntType>::kVmaxOverBase[] =
X_OVER_BASE_INITIALIZER(std::numeric_limits<IntType>::max());
template <typename IntType>
-const IntType LookupTables<IntType>::kVminOverBase[] =
+ABSL_CONST_INIT const IntType LookupTables<IntType>::kVminOverBase[] =
X_OVER_BASE_INITIALIZER(std::numeric_limits<IntType>::min());
#undef X_OVER_BASE_INITIALIZER
template <typename IntType>
inline bool safe_parse_positive_int(absl::string_view text, int base,
- IntType* value_p) {
+ absl::Nonnull<IntType*> value_p) {
IntType value = 0;
const IntType vmax = std::numeric_limits<IntType>::max();
assert(vmax > 0);
assert(base >= 0);
- assert(vmax >= static_cast<IntType>(base));
+ const IntType base_inttype = static_cast<IntType>(base);
+ assert(vmax >= base_inttype);
const IntType vmax_over_base = LookupTables<IntType>::kVmaxOverBase[base];
assert(base < 2 ||
- std::numeric_limits<IntType>::max() / base == vmax_over_base);
+ std::numeric_limits<IntType>::max() / base_inttype == vmax_over_base);
const char* start = text.data();
const char* end = start + text.size();
// loop over digits
for (; start < end; ++start) {
unsigned char c = static_cast<unsigned char>(start[0]);
- int digit = kAsciiToInt[c];
- if (digit >= base) {
+ IntType digit = static_cast<IntType>(kAsciiToInt[c]);
+ if (digit >= base_inttype) {
*value_p = value;
return false;
}
@@ -938,7 +1229,7 @@
*value_p = vmax;
return false;
}
- value *= base;
+ value *= base_inttype;
if (value > vmax - digit) {
*value_p = vmax;
return false;
@@ -951,7 +1242,7 @@
template <typename IntType>
inline bool safe_parse_negative_int(absl::string_view text, int base,
- IntType* value_p) {
+ absl::Nonnull<IntType*> value_p) {
IntType value = 0;
const IntType vmin = std::numeric_limits<IntType>::min();
assert(vmin < 0);
@@ -995,8 +1286,8 @@
// Input format based on POSIX.1-2008 strtol
// http://pubs.opengroup.org/onlinepubs/9699919799/functions/strtol.html
template <typename IntType>
-inline bool safe_int_internal(absl::string_view text, IntType* value_p,
- int base) {
+inline bool safe_int_internal(absl::string_view text,
+ absl::Nonnull<IntType*> value_p, int base) {
*value_p = 0;
bool negative;
if (!safe_parse_sign_and_base(&text, &base, &negative)) {
@@ -1010,8 +1301,8 @@
}
template <typename IntType>
-inline bool safe_uint_internal(absl::string_view text, IntType* value_p,
- int base) {
+inline bool safe_uint_internal(absl::string_view text,
+ absl::Nonnull<IntType*> value_p, int base) {
*value_p = 0;
bool negative;
if (!safe_parse_sign_and_base(&text, &base, &negative) || negative) {
@@ -1045,46 +1336,33 @@
"e0e1e2e3e4e5e6e7e8e9eaebecedeeef"
"f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff";
-ABSL_CONST_INIT ABSL_DLL const char two_ASCII_digits[100][2] = {
- {'0', '0'}, {'0', '1'}, {'0', '2'}, {'0', '3'}, {'0', '4'}, {'0', '5'},
- {'0', '6'}, {'0', '7'}, {'0', '8'}, {'0', '9'}, {'1', '0'}, {'1', '1'},
- {'1', '2'}, {'1', '3'}, {'1', '4'}, {'1', '5'}, {'1', '6'}, {'1', '7'},
- {'1', '8'}, {'1', '9'}, {'2', '0'}, {'2', '1'}, {'2', '2'}, {'2', '3'},
- {'2', '4'}, {'2', '5'}, {'2', '6'}, {'2', '7'}, {'2', '8'}, {'2', '9'},
- {'3', '0'}, {'3', '1'}, {'3', '2'}, {'3', '3'}, {'3', '4'}, {'3', '5'},
- {'3', '6'}, {'3', '7'}, {'3', '8'}, {'3', '9'}, {'4', '0'}, {'4', '1'},
- {'4', '2'}, {'4', '3'}, {'4', '4'}, {'4', '5'}, {'4', '6'}, {'4', '7'},
- {'4', '8'}, {'4', '9'}, {'5', '0'}, {'5', '1'}, {'5', '2'}, {'5', '3'},
- {'5', '4'}, {'5', '5'}, {'5', '6'}, {'5', '7'}, {'5', '8'}, {'5', '9'},
- {'6', '0'}, {'6', '1'}, {'6', '2'}, {'6', '3'}, {'6', '4'}, {'6', '5'},
- {'6', '6'}, {'6', '7'}, {'6', '8'}, {'6', '9'}, {'7', '0'}, {'7', '1'},
- {'7', '2'}, {'7', '3'}, {'7', '4'}, {'7', '5'}, {'7', '6'}, {'7', '7'},
- {'7', '8'}, {'7', '9'}, {'8', '0'}, {'8', '1'}, {'8', '2'}, {'8', '3'},
- {'8', '4'}, {'8', '5'}, {'8', '6'}, {'8', '7'}, {'8', '8'}, {'8', '9'},
- {'9', '0'}, {'9', '1'}, {'9', '2'}, {'9', '3'}, {'9', '4'}, {'9', '5'},
- {'9', '6'}, {'9', '7'}, {'9', '8'}, {'9', '9'}};
-
-bool safe_strto32_base(absl::string_view text, int32_t* value, int base) {
+bool safe_strto32_base(absl::string_view text, absl::Nonnull<int32_t*> value,
+ int base) {
return safe_int_internal<int32_t>(text, value, base);
}
-bool safe_strto64_base(absl::string_view text, int64_t* value, int base) {
+bool safe_strto64_base(absl::string_view text, absl::Nonnull<int64_t*> value,
+ int base) {
return safe_int_internal<int64_t>(text, value, base);
}
-bool safe_strto128_base(absl::string_view text, int128* value, int base) {
+bool safe_strto128_base(absl::string_view text, absl::Nonnull<int128*> value,
+ int base) {
return safe_int_internal<absl::int128>(text, value, base);
}
-bool safe_strtou32_base(absl::string_view text, uint32_t* value, int base) {
+bool safe_strtou32_base(absl::string_view text, absl::Nonnull<uint32_t*> value,
+ int base) {
return safe_uint_internal<uint32_t>(text, value, base);
}
-bool safe_strtou64_base(absl::string_view text, uint64_t* value, int base) {
+bool safe_strtou64_base(absl::string_view text, absl::Nonnull<uint64_t*> value,
+ int base) {
return safe_uint_internal<uint64_t>(text, value, base);
}
-bool safe_strtou128_base(absl::string_view text, uint128* value, int base) {
+bool safe_strtou128_base(absl::string_view text, absl::Nonnull<uint128*> value,
+ int base) {
return safe_uint_internal<absl::uint128>(text, value, base);
}
diff --git a/absl/strings/numbers.h b/absl/strings/numbers.h
index 1780bb4..ad4e66b 100644
--- a/absl/strings/numbers.h
+++ b/absl/strings/numbers.h
@@ -23,11 +23,16 @@
#ifndef ABSL_STRINGS_NUMBERS_H_
#define ABSL_STRINGS_NUMBERS_H_
-#ifdef __SSE4_2__
-#include <x86intrin.h>
+#ifdef __SSSE3__
+#include <tmmintrin.h>
+#endif
+
+#ifdef _MSC_VER
+#include <intrin.h>
#endif
#include <cstddef>
+#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <ctime>
@@ -35,16 +40,12 @@
#include <string>
#include <type_traits>
+#include "absl/base/attributes.h"
#include "absl/base/config.h"
-#ifdef __SSE4_2__
-// TODO(jorg): Remove this when we figure out the right way
-// to swap bytes on SSE 4.2 that works with the compilers
-// we claim to support. Also, add tests for the compiler
-// that doesn't support the Intel _bswap64 intrinsic but
-// does support all the SSE 4.2 intrinsics
#include "absl/base/internal/endian.h"
-#endif
#include "absl/base/macros.h"
+#include "absl/base/nullability.h"
+#include "absl/base/optimization.h"
#include "absl/base/port.h"
#include "absl/numeric/bits.h"
#include "absl/numeric/int128.h"
@@ -62,7 +63,8 @@
// encountered, this function returns `false`, leaving `out` in an unspecified
// state.
template <typename int_type>
-ABSL_MUST_USE_RESULT bool SimpleAtoi(absl::string_view str, int_type* out);
+ABSL_MUST_USE_RESULT bool SimpleAtoi(absl::string_view str,
+ absl::Nonnull<int_type*> out);
// SimpleAtof()
//
@@ -73,7 +75,8 @@
// allowed formats for `str`, except SimpleAtof() is locale-independent and will
// always use the "C" locale. If any errors are encountered, this function
// returns `false`, leaving `out` in an unspecified state.
-ABSL_MUST_USE_RESULT bool SimpleAtof(absl::string_view str, float* out);
+ABSL_MUST_USE_RESULT bool SimpleAtof(absl::string_view str,
+ absl::Nonnull<float*> out);
// SimpleAtod()
//
@@ -84,7 +87,8 @@
// allowed formats for `str`, except SimpleAtod is locale-independent and will
// always use the "C" locale. If any errors are encountered, this function
// returns `false`, leaving `out` in an unspecified state.
-ABSL_MUST_USE_RESULT bool SimpleAtod(absl::string_view str, double* out);
+ABSL_MUST_USE_RESULT bool SimpleAtod(absl::string_view str,
+ absl::Nonnull<double*> out);
// SimpleAtob()
//
@@ -94,7 +98,28 @@
// are interpreted as boolean `false`: "false", "f", "no", "n", "0". If any
// errors are encountered, this function returns `false`, leaving `out` in an
// unspecified state.
-ABSL_MUST_USE_RESULT bool SimpleAtob(absl::string_view str, bool* out);
+ABSL_MUST_USE_RESULT bool SimpleAtob(absl::string_view str,
+ absl::Nonnull<bool*> out);
+
+// SimpleHexAtoi()
+//
+// Converts a hexadecimal string (optionally followed or preceded by ASCII
+// whitespace) to an integer, returning `true` if successful. Only valid base-16
+// hexadecimal integers whose value falls within the range of the integer type
+// (optionally preceded by a `+` or `-`) can be converted. A valid hexadecimal
+// value may include both upper and lowercase character symbols, and may
+// optionally include a leading "0x" (or "0X") number prefix, which is ignored
+// by this function. If any errors are encountered, this function returns
+// `false`, leaving `out` in an unspecified state.
+template <typename int_type>
+ABSL_MUST_USE_RESULT bool SimpleHexAtoi(absl::string_view str,
+ absl::Nonnull<int_type*> out);
+
+// Overloads of SimpleHexAtoi() for 128 bit integers.
+ABSL_MUST_USE_RESULT inline bool SimpleHexAtoi(
+ absl::string_view str, absl::Nonnull<absl::int128*> out);
+ABSL_MUST_USE_RESULT inline bool SimpleHexAtoi(
+ absl::string_view str, absl::Nonnull<absl::uint128*> out);
ABSL_NAMESPACE_END
} // namespace absl
@@ -109,8 +134,6 @@
ABSL_DLL extern const char kHexChar[17]; // 0123456789abcdef
ABSL_DLL extern const char
kHexTable[513]; // 000102030405060708090a0b0c0d0e0f1011...
-ABSL_DLL extern const char
- two_ASCII_digits[100][2]; // 00, 01, 02, 03...
// Writes a two-character representation of 'i' to 'buf'. 'i' must be in the
// range 0 <= i < 100, and buf must have space for two characters. Example:
@@ -118,69 +141,216 @@
// PutTwoDigits(42, buf);
// // buf[0] == '4'
// // buf[1] == '2'
-inline void PutTwoDigits(size_t i, char* buf) {
- assert(i < 100);
- memcpy(buf, two_ASCII_digits[i], 2);
-}
+void PutTwoDigits(uint32_t i, absl::Nonnull<char*> buf);
// safe_strto?() functions for implementing SimpleAtoi()
-bool safe_strto32_base(absl::string_view text, int32_t* value, int base);
-bool safe_strto64_base(absl::string_view text, int64_t* value, int base);
-bool safe_strto128_base(absl::string_view text, absl::int128* value,
- int base);
-bool safe_strtou32_base(absl::string_view text, uint32_t* value, int base);
-bool safe_strtou64_base(absl::string_view text, uint64_t* value, int base);
-bool safe_strtou128_base(absl::string_view text, absl::uint128* value,
- int base);
+bool safe_strto32_base(absl::string_view text, absl::Nonnull<int32_t*> value,
+ int base);
+bool safe_strto64_base(absl::string_view text, absl::Nonnull<int64_t*> value,
+ int base);
+bool safe_strto128_base(absl::string_view text,
+ absl::Nonnull<absl::int128*> value, int base);
+bool safe_strtou32_base(absl::string_view text, absl::Nonnull<uint32_t*> value,
+ int base);
+bool safe_strtou64_base(absl::string_view text, absl::Nonnull<uint64_t*> value,
+ int base);
+bool safe_strtou128_base(absl::string_view text,
+ absl::Nonnull<absl::uint128*> value, int base);
static const int kFastToBufferSize = 32;
static const int kSixDigitsToBufferSize = 16;
+template <class T>
+std::enable_if_t<!std::is_unsigned<T>::value, bool> IsNegative(const T& v) {
+ return v < T();
+}
+
+template <class T>
+std::enable_if_t<std::is_unsigned<T>::value, std::false_type> IsNegative(
+ const T&) {
+ // The integer is unsigned, so return a compile-time constant.
+ // This can help the optimizer avoid having to prove bool to be false later.
+ return std::false_type();
+}
+
+template <class T>
+std::enable_if_t<std::is_unsigned<std::decay_t<T>>::value, T&&>
+UnsignedAbsoluteValue(T&& v ABSL_ATTRIBUTE_LIFETIME_BOUND) {
+ // The value is unsigned; just return the original.
+ return std::forward<T>(v);
+}
+
+template <class T>
+ABSL_ATTRIBUTE_CONST_FUNCTION
+ std::enable_if_t<!std::is_unsigned<T>::value, std::make_unsigned_t<T>>
+ UnsignedAbsoluteValue(T v) {
+ using U = std::make_unsigned_t<T>;
+ return IsNegative(v) ? U() - static_cast<U>(v) : static_cast<U>(v);
+}
+
+// Returns the number of base-10 digits in the given number.
+// Note that this strictly counts digits. It does not count the sign.
+// The `initial_digits` parameter is the starting point, which is normally equal
+// to 1 because the number of digits in 0 is 1 (a special case).
+// However, callers may e.g. wish to change it to 2 to account for the sign.
+template <typename T>
+std::enable_if_t<std::is_unsigned<T>::value, uint32_t> Base10Digits(
+ T v, const uint32_t initial_digits = 1) {
+ uint32_t r = initial_digits;
+ // If code size becomes an issue, the 'if' stage can be removed for a minor
+ // performance loss.
+ for (;;) {
+ if (ABSL_PREDICT_TRUE(v < 10 * 10)) {
+ r += (v >= 10);
+ break;
+ }
+ if (ABSL_PREDICT_TRUE(v < 1000 * 10)) {
+ r += (v >= 1000) + 2;
+ break;
+ }
+ if (ABSL_PREDICT_TRUE(v < 100000 * 10)) {
+ r += (v >= 100000) + 4;
+ break;
+ }
+ r += 6;
+ v = static_cast<T>(v / 1000000);
+ }
+ return r;
+}
+
+template <typename T>
+std::enable_if_t<std::is_signed<T>::value, uint32_t> Base10Digits(
+ T v, uint32_t r = 1) {
+ // Branchlessly add 1 to account for a minus sign.
+ r += static_cast<uint32_t>(IsNegative(v));
+ return Base10Digits(UnsignedAbsoluteValue(v), r);
+}
+
+// These functions return the number of base-10 digits, but multiplied by -1 if
+// the input itself is negative. This is handy and efficient for later usage,
+// since the bitwise complement of the result becomes equal to the number of
+// characters required.
+ABSL_ATTRIBUTE_CONST_FUNCTION int GetNumDigitsOrNegativeIfNegative(
+ signed char v);
+ABSL_ATTRIBUTE_CONST_FUNCTION int GetNumDigitsOrNegativeIfNegative(
+ unsigned char v);
+ABSL_ATTRIBUTE_CONST_FUNCTION int GetNumDigitsOrNegativeIfNegative(
+ short v); // NOLINT
+ABSL_ATTRIBUTE_CONST_FUNCTION int GetNumDigitsOrNegativeIfNegative(
+ unsigned short v); // NOLINT
+ABSL_ATTRIBUTE_CONST_FUNCTION int GetNumDigitsOrNegativeIfNegative(int v);
+ABSL_ATTRIBUTE_CONST_FUNCTION int GetNumDigitsOrNegativeIfNegative(
+ unsigned int v);
+ABSL_ATTRIBUTE_CONST_FUNCTION int GetNumDigitsOrNegativeIfNegative(
+ long v); // NOLINT
+ABSL_ATTRIBUTE_CONST_FUNCTION int GetNumDigitsOrNegativeIfNegative(
+ unsigned long v); // NOLINT
+ABSL_ATTRIBUTE_CONST_FUNCTION int GetNumDigitsOrNegativeIfNegative(
+ long long v); // NOLINT
+ABSL_ATTRIBUTE_CONST_FUNCTION int GetNumDigitsOrNegativeIfNegative(
+ unsigned long long v); // NOLINT
+
// Helper function for fast formatting of floating-point values.
// The result is the same as printf's "%g", a.k.a. "%.6g"; that is, six
// significant digits are returned, trailing zeros are removed, and numbers
// outside the range 0.0001-999999 are output using scientific notation
// (1.23456e+06). This routine is heavily optimized.
// Required buffer size is `kSixDigitsToBufferSize`.
-size_t SixDigitsToBuffer(double d, char* buffer);
+size_t SixDigitsToBuffer(double d, absl::Nonnull<char*> buffer);
-// These functions are intended for speed. All functions take an output buffer
+// All of these functions take an output buffer
// as an argument and return a pointer to the last byte they wrote, which is the
// terminating '\0'. At most `kFastToBufferSize` bytes are written.
-char* FastIntToBuffer(int32_t, char*);
-char* FastIntToBuffer(uint32_t, char*);
-char* FastIntToBuffer(int64_t, char*);
-char* FastIntToBuffer(uint64_t, char*);
+absl::Nonnull<char*> FastIntToBuffer(int32_t i, absl::Nonnull<char*> buffer);
+absl::Nonnull<char*> FastIntToBuffer(uint32_t i, absl::Nonnull<char*> buffer);
+absl::Nonnull<char*> FastIntToBuffer(int64_t i, absl::Nonnull<char*> buffer);
+absl::Nonnull<char*> FastIntToBuffer(uint64_t i, absl::Nonnull<char*> buffer);
// For enums and integer types that are not an exact match for the types above,
// use templates to call the appropriate one of the four overloads above.
template <typename int_type>
-char* FastIntToBuffer(int_type i, char* buffer) {
+absl::Nonnull<char*> FastIntToBuffer(int_type i, absl::Nonnull<char*> buffer) {
static_assert(sizeof(i) <= 64 / 8,
"FastIntToBuffer works only with 64-bit-or-less integers.");
// TODO(jorg): This signed-ness check is used because it works correctly
// with enums, and it also serves to check that int_type is not a pointer.
// If one day something like std::is_signed<enum E> works, switch to it.
- if (static_cast<int_type>(1) - 2 < 0) { // Signed
- if (sizeof(i) > 32 / 8) { // 33-bit to 64-bit
+ // These conditions are constexpr bools to suppress MSVC warning C4127.
+ constexpr bool kIsSigned = static_cast<int_type>(1) - 2 < 0;
+ constexpr bool kUse64Bit = sizeof(i) > 32 / 8;
+ if (kIsSigned) {
+ if (kUse64Bit) {
return FastIntToBuffer(static_cast<int64_t>(i), buffer);
- } else { // 32-bit or less
+ } else {
return FastIntToBuffer(static_cast<int32_t>(i), buffer);
}
- } else { // Unsigned
- if (sizeof(i) > 32 / 8) { // 33-bit to 64-bit
+ } else {
+ if (kUse64Bit) {
return FastIntToBuffer(static_cast<uint64_t>(i), buffer);
- } else { // 32-bit or less
+ } else {
return FastIntToBuffer(static_cast<uint32_t>(i), buffer);
}
}
}
+// These functions do NOT add any null-terminator.
+// They return a pointer to the beginning of the written string.
+// The digit counts provided must *exactly* match the number of base-10 digits
+// in the number, or the behavior is undefined.
+// (i.e. do NOT count the minus sign, or over- or under-count the digits.)
+absl::Nonnull<char*> FastIntToBufferBackward(int32_t i,
+ absl::Nonnull<char*> buffer_end,
+ uint32_t exact_digit_count);
+absl::Nonnull<char*> FastIntToBufferBackward(uint32_t i,
+ absl::Nonnull<char*> buffer_end,
+ uint32_t exact_digit_count);
+absl::Nonnull<char*> FastIntToBufferBackward(int64_t i,
+ absl::Nonnull<char*> buffer_end,
+ uint32_t exact_digit_count);
+absl::Nonnull<char*> FastIntToBufferBackward(uint64_t i,
+ absl::Nonnull<char*> buffer_end,
+ uint32_t exact_digit_count);
+
+// For enums and integer types that are not an exact match for the types above,
+// use templates to call the appropriate one of the four overloads above.
+template <typename int_type>
+absl::Nonnull<char*> FastIntToBufferBackward(int_type i,
+ absl::Nonnull<char*> buffer_end,
+ uint32_t exact_digit_count) {
+ static_assert(
+ sizeof(i) <= 64 / 8,
+ "FastIntToBufferBackward works only with 64-bit-or-less integers.");
+ // This signed-ness check is used because it works correctly
+ // with enums, and it also serves to check that int_type is not a pointer.
+ // If one day something like std::is_signed<enum E> works, switch to it.
+ // These conditions are constexpr bools to suppress MSVC warning C4127.
+ constexpr bool kIsSigned = static_cast<int_type>(1) - 2 < 0;
+ constexpr bool kUse64Bit = sizeof(i) > 32 / 8;
+ if (kIsSigned) {
+ if (kUse64Bit) {
+ return FastIntToBufferBackward(static_cast<int64_t>(i), buffer_end,
+ exact_digit_count);
+ } else {
+ return FastIntToBufferBackward(static_cast<int32_t>(i), buffer_end,
+ exact_digit_count);
+ }
+ } else {
+ if (kUse64Bit) {
+ return FastIntToBufferBackward(static_cast<uint64_t>(i), buffer_end,
+ exact_digit_count);
+ } else {
+ return FastIntToBufferBackward(static_cast<uint32_t>(i), buffer_end,
+ exact_digit_count);
+ }
+ }
+}
+
// Implementation of SimpleAtoi, generalized to support arbitrary base (used
// with base different from 10 elsewhere in Abseil implementation).
template <typename int_type>
-ABSL_MUST_USE_RESULT bool safe_strtoi_base(absl::string_view s, int_type* out,
+ABSL_MUST_USE_RESULT bool safe_strtoi_base(absl::string_view s,
+ absl::Nonnull<int_type*> out,
int base) {
static_assert(sizeof(*out) == 4 || sizeof(*out) == 8,
"SimpleAtoi works only with 32-bit or 64-bit integers.");
@@ -190,22 +360,25 @@
// TODO(jorg): This signed-ness check is used because it works correctly
// with enums, and it also serves to check that int_type is not a pointer.
// If one day something like std::is_signed<enum E> works, switch to it.
- if (static_cast<int_type>(1) - 2 < 0) { // Signed
- if (sizeof(*out) == 64 / 8) { // 64-bit
+ // These conditions are constexpr bools to suppress MSVC warning C4127.
+ constexpr bool kIsSigned = static_cast<int_type>(1) - 2 < 0;
+ constexpr bool kUse64Bit = sizeof(*out) == 64 / 8;
+ if (kIsSigned) {
+ if (kUse64Bit) {
int64_t val;
parsed = numbers_internal::safe_strto64_base(s, &val, base);
*out = static_cast<int_type>(val);
- } else { // 32-bit
+ } else {
int32_t val;
parsed = numbers_internal::safe_strto32_base(s, &val, base);
*out = static_cast<int_type>(val);
}
- } else { // Unsigned
- if (sizeof(*out) == 64 / 8) { // 64-bit
+ } else {
+ if (kUse64Bit) {
uint64_t val;
parsed = numbers_internal::safe_strtou64_base(s, &val, base);
*out = static_cast<int_type>(val);
- } else { // 32-bit
+ } else {
uint32_t val;
parsed = numbers_internal::safe_strtou32_base(s, &val, base);
*out = static_cast<int_type>(val);
@@ -220,8 +393,8 @@
// without the terminating null character. Thus `out` must be of length >= 16.
// Returns the number of non-pad digits of the output (it can never be zero
// since 0 has one digit).
-inline size_t FastHexToBufferZeroPad16(uint64_t val, char* out) {
-#ifdef __SSE4_2__
+inline size_t FastHexToBufferZeroPad16(uint64_t val, absl::Nonnull<char*> out) {
+#ifdef ABSL_INTERNAL_HAVE_SSSE3
uint64_t be = absl::big_endian::FromHost64(val);
const auto kNibbleMask = _mm_set1_epi8(0xf);
const auto kHexDigits = _mm_setr_epi8('0', '1', '2', '3', '4', '5', '6', '7',
@@ -240,26 +413,43 @@
}
#endif
// | 0x1 so that even 0 has 1 digit.
- return 16 - countl_zero(val | 0x1) / 4;
+ return 16 - static_cast<size_t>(countl_zero(val | 0x1) / 4);
}
} // namespace numbers_internal
template <typename int_type>
-ABSL_MUST_USE_RESULT bool SimpleAtoi(absl::string_view str, int_type* out) {
+ABSL_MUST_USE_RESULT bool SimpleAtoi(absl::string_view str,
+ absl::Nonnull<int_type*> out) {
return numbers_internal::safe_strtoi_base(str, out, 10);
}
ABSL_MUST_USE_RESULT inline bool SimpleAtoi(absl::string_view str,
- absl::int128* out) {
+ absl::Nonnull<absl::int128*> out) {
return numbers_internal::safe_strto128_base(str, out, 10);
}
ABSL_MUST_USE_RESULT inline bool SimpleAtoi(absl::string_view str,
- absl::uint128* out) {
+ absl::Nonnull<absl::uint128*> out) {
return numbers_internal::safe_strtou128_base(str, out, 10);
}
+template <typename int_type>
+ABSL_MUST_USE_RESULT bool SimpleHexAtoi(absl::string_view str,
+ absl::Nonnull<int_type*> out) {
+ return numbers_internal::safe_strtoi_base(str, out, 16);
+}
+
+ABSL_MUST_USE_RESULT inline bool SimpleHexAtoi(
+ absl::string_view str, absl::Nonnull<absl::int128*> out) {
+ return numbers_internal::safe_strto128_base(str, out, 16);
+}
+
+ABSL_MUST_USE_RESULT inline bool SimpleHexAtoi(
+ absl::string_view str, absl::Nonnull<absl::uint128*> out) {
+ return numbers_internal::safe_strtou128_base(str, out, 16);
+}
+
ABSL_NAMESPACE_END
} // namespace absl
diff --git a/absl/strings/numbers_benchmark.cc b/absl/strings/numbers_benchmark.cc
index 6e79b3e..e7cb60a 100644
--- a/absl/strings/numbers_benchmark.cc
+++ b/absl/strings/numbers_benchmark.cc
@@ -13,6 +13,7 @@
// limitations under the License.
#include <cstdint>
+#include <limits>
#include <random>
#include <string>
#include <type_traits>
@@ -23,6 +24,7 @@
#include "absl/random/distributions.h"
#include "absl/random/random.h"
#include "absl/strings/numbers.h"
+#include "absl/strings/string_view.h"
namespace {
diff --git a/absl/strings/numbers_test.cc b/absl/strings/numbers_test.cc
index f310310..1ceff70 100644
--- a/absl/strings/numbers_test.cc
+++ b/absl/strings/numbers_test.cc
@@ -19,6 +19,7 @@
#include <sys/types.h>
#include <cfenv> // NOLINT(build/c++11)
+#include <cfloat>
#include <cinttypes>
#include <climits>
#include <cmath>
@@ -27,6 +28,7 @@
#include <cstdio>
#include <cstdlib>
#include <cstring>
+#include <ios>
#include <limits>
#include <numeric>
#include <random>
@@ -36,17 +38,20 @@
#include "gmock/gmock.h"
#include "gtest/gtest.h"
-#include "absl/base/internal/raw_logging.h"
+#include "absl/log/log.h"
+#include "absl/numeric/int128.h"
#include "absl/random/distributions.h"
#include "absl/random/random.h"
#include "absl/strings/internal/numbers_test_common.h"
#include "absl/strings/internal/ostringstream.h"
#include "absl/strings/internal/pow10_helper.h"
#include "absl/strings/str_cat.h"
+#include "absl/strings/string_view.h"
namespace {
using absl::SimpleAtoi;
+using absl::SimpleHexAtoi;
using absl::numbers_internal::kSixDigitsToBufferSize;
using absl::numbers_internal::safe_strto32_base;
using absl::numbers_internal::safe_strto64_base;
@@ -58,6 +63,7 @@
using absl::strings_internal::strtouint64_test_cases;
using testing::Eq;
using testing::MatchesRegex;
+using testing::Pointee;
// Number of floats to test with.
// 5,000,000 is a reasonable default for a test that only takes a few seconds.
@@ -225,10 +231,15 @@
CheckInt32(INT_MIN);
CheckInt32(INT_MAX);
CheckInt64(LONG_MIN);
+ CheckInt64(uint64_t{10000000});
+ CheckInt64(uint64_t{100000000});
CheckInt64(uint64_t{1000000000});
CheckInt64(uint64_t{9999999999});
CheckInt64(uint64_t{100000000000000});
CheckInt64(uint64_t{999999999999999});
+ CheckInt64(uint64_t{1000000000000000});
+ CheckInt64(uint64_t{10000000000000000});
+ CheckInt64(uint64_t{100000000000000000});
CheckInt64(uint64_t{1000000000000000000});
CheckInt64(uint64_t{1199999999999999999});
CheckInt64(int64_t{-700000000000000000});
@@ -240,6 +251,8 @@
CheckUInt64(uint64_t{999999999999999});
CheckUInt64(uint64_t{1000000000000000000});
CheckUInt64(uint64_t{1199999999999999999});
+ CheckUInt64(uint64_t{10000000000000000000u});
+ CheckUInt64(uint64_t{10200300040000500006u});
CheckUInt64(std::numeric_limits<uint64_t>::max());
for (int i = 0; i < 10000; i++) {
@@ -387,9 +400,209 @@
}
TEST(NumbersTest, Atod) {
+ // DBL_TRUE_MIN and FLT_TRUE_MIN were not mandated in <cfloat> before C++17.
+#if !defined(DBL_TRUE_MIN)
+ static constexpr double DBL_TRUE_MIN =
+ 4.940656458412465441765687928682213723650598026143247644255856825e-324;
+#endif
+#if !defined(FLT_TRUE_MIN)
+ static constexpr float FLT_TRUE_MIN =
+ 1.401298464324817070923729583289916131280261941876515771757068284e-45f;
+#endif
+
double d;
- EXPECT_TRUE(absl::SimpleAtod("nan", &d));
+ float f;
+
+ // NaN can be spelled in multiple ways.
+ EXPECT_TRUE(absl::SimpleAtod("NaN", &d));
EXPECT_TRUE(std::isnan(d));
+ EXPECT_TRUE(absl::SimpleAtod("nAN", &d));
+ EXPECT_TRUE(std::isnan(d));
+ EXPECT_TRUE(absl::SimpleAtod("-nan", &d));
+ EXPECT_TRUE(std::isnan(d));
+
+ // Likewise for Infinity.
+ EXPECT_TRUE(absl::SimpleAtod("inf", &d));
+ EXPECT_TRUE(std::isinf(d) && (d > 0));
+ EXPECT_TRUE(absl::SimpleAtod("+Infinity", &d));
+ EXPECT_TRUE(std::isinf(d) && (d > 0));
+ EXPECT_TRUE(absl::SimpleAtod("-INF", &d));
+ EXPECT_TRUE(std::isinf(d) && (d < 0));
+
+ // Parse DBL_MAX. Parsing something more than twice as big should also
+ // produce infinity.
+ EXPECT_TRUE(absl::SimpleAtod("1.7976931348623157e+308", &d));
+ EXPECT_EQ(d, 1.7976931348623157e+308);
+ EXPECT_TRUE(absl::SimpleAtod("5e308", &d));
+ EXPECT_TRUE(std::isinf(d) && (d > 0));
+ // Ditto, but for FLT_MAX.
+ EXPECT_TRUE(absl::SimpleAtof("3.4028234663852886e+38", &f));
+ EXPECT_EQ(f, 3.4028234663852886e+38f);
+ EXPECT_TRUE(absl::SimpleAtof("7e38", &f));
+ EXPECT_TRUE(std::isinf(f) && (f > 0));
+
+ // Parse the largest N such that parsing 1eN produces a finite value and the
+ // smallest M = N + 1 such that parsing 1eM produces infinity.
+ //
+ // The 309 exponent (and 39) confirms the "definition of
+ // kEiselLemireMaxExclExp10" comment in charconv.cc.
+ EXPECT_TRUE(absl::SimpleAtod("1e308", &d));
+ EXPECT_EQ(d, 1e308);
+ EXPECT_FALSE(std::isinf(d));
+ EXPECT_TRUE(absl::SimpleAtod("1e309", &d));
+ EXPECT_TRUE(std::isinf(d));
+ // Ditto, but for Atof instead of Atod.
+ EXPECT_TRUE(absl::SimpleAtof("1e38", &f));
+ EXPECT_EQ(f, 1e38f);
+ EXPECT_FALSE(std::isinf(f));
+ EXPECT_TRUE(absl::SimpleAtof("1e39", &f));
+ EXPECT_TRUE(std::isinf(f));
+
+ // Parse the largest N such that parsing 9.999999999999999999eN, with 19
+ // nines, produces a finite value.
+ //
+ // 9999999999999999999, with 19 nines but no decimal point, is the largest
+ // "repeated nines" integer that fits in a uint64_t.
+ EXPECT_TRUE(absl::SimpleAtod("9.999999999999999999e307", &d));
+ EXPECT_EQ(d, 9.999999999999999999e307);
+ EXPECT_FALSE(std::isinf(d));
+ EXPECT_TRUE(absl::SimpleAtod("9.999999999999999999e308", &d));
+ EXPECT_TRUE(std::isinf(d));
+ // Ditto, but for Atof instead of Atod.
+ EXPECT_TRUE(absl::SimpleAtof("9.999999999999999999e37", &f));
+ EXPECT_EQ(f, 9.999999999999999999e37f);
+ EXPECT_FALSE(std::isinf(f));
+ EXPECT_TRUE(absl::SimpleAtof("9.999999999999999999e38", &f));
+ EXPECT_TRUE(std::isinf(f));
+
+ // Parse DBL_MIN (normal), DBL_TRUE_MIN (subnormal) and (DBL_TRUE_MIN / 10)
+ // (effectively zero).
+ EXPECT_TRUE(absl::SimpleAtod("2.2250738585072014e-308", &d));
+ EXPECT_EQ(d, 2.2250738585072014e-308);
+ EXPECT_TRUE(absl::SimpleAtod("4.9406564584124654e-324", &d));
+ EXPECT_EQ(d, 4.9406564584124654e-324);
+ EXPECT_TRUE(absl::SimpleAtod("4.9406564584124654e-325", &d));
+ EXPECT_EQ(d, 0);
+ // Ditto, but for FLT_MIN, FLT_TRUE_MIN and (FLT_TRUE_MIN / 10).
+ EXPECT_TRUE(absl::SimpleAtof("1.1754943508222875e-38", &f));
+ EXPECT_EQ(f, 1.1754943508222875e-38f);
+ EXPECT_TRUE(absl::SimpleAtof("1.4012984643248171e-45", &f));
+ EXPECT_EQ(f, 1.4012984643248171e-45f);
+ EXPECT_TRUE(absl::SimpleAtof("1.4012984643248171e-46", &f));
+ EXPECT_EQ(f, 0);
+
+ // Parse the largest N (the most negative -N) such that parsing 1e-N produces
+ // a normal or subnormal (but still positive) or zero value.
+ EXPECT_TRUE(absl::SimpleAtod("1e-307", &d));
+ EXPECT_EQ(d, 1e-307);
+ EXPECT_GE(d, DBL_MIN);
+ EXPECT_LT(d, DBL_MIN * 10);
+ EXPECT_TRUE(absl::SimpleAtod("1e-323", &d));
+ EXPECT_EQ(d, 1e-323);
+ EXPECT_GE(d, DBL_TRUE_MIN);
+ EXPECT_LT(d, DBL_TRUE_MIN * 10);
+ EXPECT_TRUE(absl::SimpleAtod("1e-324", &d));
+ EXPECT_EQ(d, 0);
+ // Ditto, but for Atof instead of Atod.
+ EXPECT_TRUE(absl::SimpleAtof("1e-37", &f));
+ EXPECT_EQ(f, 1e-37f);
+ EXPECT_GE(f, FLT_MIN);
+ EXPECT_LT(f, FLT_MIN * 10);
+ EXPECT_TRUE(absl::SimpleAtof("1e-45", &f));
+ EXPECT_EQ(f, 1e-45f);
+ EXPECT_GE(f, FLT_TRUE_MIN);
+ EXPECT_LT(f, FLT_TRUE_MIN * 10);
+ EXPECT_TRUE(absl::SimpleAtof("1e-46", &f));
+ EXPECT_EQ(f, 0);
+
+ // Parse the largest N (the most negative -N) such that parsing
+ // 9.999999999999999999e-N, with 19 nines, produces a normal or subnormal
+ // (but still positive) or zero value.
+ //
+ // 9999999999999999999, with 19 nines but no decimal point, is the largest
+ // "repeated nines" integer that fits in a uint64_t.
+ //
+ // The -324/-325 exponents (and -46/-47) confirms the "definition of
+ // kEiselLemireMinInclExp10" comment in charconv.cc.
+ EXPECT_TRUE(absl::SimpleAtod("9.999999999999999999e-308", &d));
+ EXPECT_EQ(d, 9.999999999999999999e-308);
+ EXPECT_GE(d, DBL_MIN);
+ EXPECT_LT(d, DBL_MIN * 10);
+ EXPECT_TRUE(absl::SimpleAtod("9.999999999999999999e-324", &d));
+ EXPECT_EQ(d, 9.999999999999999999e-324);
+ EXPECT_GE(d, DBL_TRUE_MIN);
+ EXPECT_LT(d, DBL_TRUE_MIN * 10);
+ EXPECT_TRUE(absl::SimpleAtod("9.999999999999999999e-325", &d));
+ EXPECT_EQ(d, 0);
+ // Ditto, but for Atof instead of Atod.
+ EXPECT_TRUE(absl::SimpleAtof("9.999999999999999999e-38", &f));
+ EXPECT_EQ(f, 9.999999999999999999e-38f);
+ EXPECT_GE(f, FLT_MIN);
+ EXPECT_LT(f, FLT_MIN * 10);
+ EXPECT_TRUE(absl::SimpleAtof("9.999999999999999999e-46", &f));
+ EXPECT_EQ(f, 9.999999999999999999e-46f);
+ EXPECT_GE(f, FLT_TRUE_MIN);
+ EXPECT_LT(f, FLT_TRUE_MIN * 10);
+ EXPECT_TRUE(absl::SimpleAtof("9.999999999999999999e-47", &f));
+ EXPECT_EQ(f, 0);
+
+ // Leading and/or trailing whitespace is OK.
+ EXPECT_TRUE(absl::SimpleAtod(" \t\r\n 2.718", &d));
+ EXPECT_EQ(d, 2.718);
+ EXPECT_TRUE(absl::SimpleAtod(" 3.141 ", &d));
+ EXPECT_EQ(d, 3.141);
+
+ // Leading or trailing not-whitespace is not OK.
+ EXPECT_FALSE(absl::SimpleAtod("n 0", &d));
+ EXPECT_FALSE(absl::SimpleAtod("0n ", &d));
+
+ // Multiple leading 0s are OK.
+ EXPECT_TRUE(absl::SimpleAtod("000123", &d));
+ EXPECT_EQ(d, 123);
+ EXPECT_TRUE(absl::SimpleAtod("000.456", &d));
+ EXPECT_EQ(d, 0.456);
+
+ // An absent leading 0 (for a fraction < 1) is OK.
+ EXPECT_TRUE(absl::SimpleAtod(".5", &d));
+ EXPECT_EQ(d, 0.5);
+ EXPECT_TRUE(absl::SimpleAtod("-.707", &d));
+ EXPECT_EQ(d, -0.707);
+
+ // Unary + is OK.
+ EXPECT_TRUE(absl::SimpleAtod("+6.0221408e+23", &d));
+ EXPECT_EQ(d, 6.0221408e+23);
+
+ // Underscores are not OK.
+ EXPECT_FALSE(absl::SimpleAtod("123_456", &d));
+
+ // The decimal separator must be '.' and is never ','.
+ EXPECT_TRUE(absl::SimpleAtod("8.9", &d));
+ EXPECT_FALSE(absl::SimpleAtod("8,9", &d));
+
+ // These examples are called out in the EiselLemire function's comments.
+ EXPECT_TRUE(absl::SimpleAtod("4503599627370497.5", &d));
+ EXPECT_EQ(d, 4503599627370497.5);
+ EXPECT_TRUE(absl::SimpleAtod("1e+23", &d));
+ EXPECT_EQ(d, 1e+23);
+ EXPECT_TRUE(absl::SimpleAtod("9223372036854775807", &d));
+ EXPECT_EQ(d, 9223372036854775807);
+ // Ditto, but for Atof instead of Atod.
+ EXPECT_TRUE(absl::SimpleAtof("0.0625", &f));
+ EXPECT_EQ(f, 0.0625f);
+ EXPECT_TRUE(absl::SimpleAtof("20040229.0", &f));
+ EXPECT_EQ(f, 20040229.0f);
+ EXPECT_TRUE(absl::SimpleAtof("2147483647.0", &f));
+ EXPECT_EQ(f, 2147483647.0f);
+
+ // Some parsing algorithms don't always round correctly (but absl::SimpleAtod
+ // should). This test case comes from
+ // https://github.com/serde-rs/json/issues/707
+ //
+ // See also atod_manual_test.cc for running many more test cases.
+ EXPECT_TRUE(absl::SimpleAtod("122.416294033786585", &d));
+ EXPECT_EQ(d, 122.416294033786585);
+ EXPECT_TRUE(absl::SimpleAtof("122.416294033786585", &f));
+ EXPECT_EQ(f, 122.416294033786585f);
}
TEST(NumbersTest, Prefixes) {
@@ -468,6 +681,148 @@
VerifySimpleAtoiGood<E_biguint>(E_biguint_max32, E_biguint_max32);
}
+template <typename int_type, typename in_val_type>
+void VerifySimpleHexAtoiGood(in_val_type in_value, int_type exp_value) {
+ std::string s;
+ // uint128 can be streamed but not StrCat'd
+ absl::strings_internal::OStringStream strm(&s);
+ if (in_value >= 0) {
+ strm << std::hex << in_value;
+ } else {
+ // Inefficient for small integers, but works with all integral types.
+ strm << "-" << std::hex << -absl::uint128(in_value);
+ }
+ int_type x = static_cast<int_type>(~exp_value);
+ EXPECT_TRUE(SimpleHexAtoi(s, &x))
+ << "in_value=" << std::hex << in_value << " s=" << s << " x=" << x;
+ EXPECT_EQ(exp_value, x);
+ x = static_cast<int_type>(~exp_value);
+ EXPECT_TRUE(SimpleHexAtoi(
+ s.c_str(), &x)); // NOLINT: readability-redundant-string-conversions
+ EXPECT_EQ(exp_value, x);
+}
+
+template <typename int_type, typename in_val_type>
+void VerifySimpleHexAtoiBad(in_val_type in_value) {
+ std::string s;
+ // uint128 can be streamed but not StrCat'd
+ absl::strings_internal::OStringStream strm(&s);
+ if (in_value >= 0) {
+ strm << std::hex << in_value;
+ } else {
+ // Inefficient for small integers, but works with all integral types.
+ strm << "-" << std::hex << -absl::uint128(in_value);
+ }
+ int_type x;
+ EXPECT_FALSE(SimpleHexAtoi(s, &x));
+ EXPECT_FALSE(SimpleHexAtoi(
+ s.c_str(), &x)); // NOLINT: readability-redundant-string-conversions
+}
+
+TEST(NumbersTest, HexAtoi) {
+ // SimpleHexAtoi(absl::string_view, int32_t)
+ VerifySimpleHexAtoiGood<int32_t>(0, 0);
+ VerifySimpleHexAtoiGood<int32_t>(0x42, 0x42);
+ VerifySimpleHexAtoiGood<int32_t>(-0x42, -0x42);
+
+ VerifySimpleHexAtoiGood<int32_t>(std::numeric_limits<int32_t>::min(),
+ std::numeric_limits<int32_t>::min());
+ VerifySimpleHexAtoiGood<int32_t>(std::numeric_limits<int32_t>::max(),
+ std::numeric_limits<int32_t>::max());
+
+ // SimpleHexAtoi(absl::string_view, uint32_t)
+ VerifySimpleHexAtoiGood<uint32_t>(0, 0);
+ VerifySimpleHexAtoiGood<uint32_t>(0x42, 0x42);
+ VerifySimpleHexAtoiBad<uint32_t>(-0x42);
+
+ VerifySimpleHexAtoiBad<uint32_t>(std::numeric_limits<int32_t>::min());
+ VerifySimpleHexAtoiGood<uint32_t>(std::numeric_limits<int32_t>::max(),
+ std::numeric_limits<int32_t>::max());
+ VerifySimpleHexAtoiGood<uint32_t>(std::numeric_limits<uint32_t>::max(),
+ std::numeric_limits<uint32_t>::max());
+ VerifySimpleHexAtoiBad<uint32_t>(std::numeric_limits<int64_t>::min());
+ VerifySimpleHexAtoiBad<uint32_t>(std::numeric_limits<int64_t>::max());
+ VerifySimpleHexAtoiBad<uint32_t>(std::numeric_limits<uint64_t>::max());
+
+ // SimpleHexAtoi(absl::string_view, int64_t)
+ VerifySimpleHexAtoiGood<int64_t>(0, 0);
+ VerifySimpleHexAtoiGood<int64_t>(0x42, 0x42);
+ VerifySimpleHexAtoiGood<int64_t>(-0x42, -0x42);
+
+ VerifySimpleHexAtoiGood<int64_t>(std::numeric_limits<int32_t>::min(),
+ std::numeric_limits<int32_t>::min());
+ VerifySimpleHexAtoiGood<int64_t>(std::numeric_limits<int32_t>::max(),
+ std::numeric_limits<int32_t>::max());
+ VerifySimpleHexAtoiGood<int64_t>(std::numeric_limits<uint32_t>::max(),
+ std::numeric_limits<uint32_t>::max());
+ VerifySimpleHexAtoiGood<int64_t>(std::numeric_limits<int64_t>::min(),
+ std::numeric_limits<int64_t>::min());
+ VerifySimpleHexAtoiGood<int64_t>(std::numeric_limits<int64_t>::max(),
+ std::numeric_limits<int64_t>::max());
+ VerifySimpleHexAtoiBad<int64_t>(std::numeric_limits<uint64_t>::max());
+
+ // SimpleHexAtoi(absl::string_view, uint64_t)
+ VerifySimpleHexAtoiGood<uint64_t>(0, 0);
+ VerifySimpleHexAtoiGood<uint64_t>(0x42, 0x42);
+ VerifySimpleHexAtoiBad<uint64_t>(-0x42);
+
+ VerifySimpleHexAtoiBad<uint64_t>(std::numeric_limits<int32_t>::min());
+ VerifySimpleHexAtoiGood<uint64_t>(std::numeric_limits<int32_t>::max(),
+ std::numeric_limits<int32_t>::max());
+ VerifySimpleHexAtoiGood<uint64_t>(std::numeric_limits<uint32_t>::max(),
+ std::numeric_limits<uint32_t>::max());
+ VerifySimpleHexAtoiBad<uint64_t>(std::numeric_limits<int64_t>::min());
+ VerifySimpleHexAtoiGood<uint64_t>(std::numeric_limits<int64_t>::max(),
+ std::numeric_limits<int64_t>::max());
+ VerifySimpleHexAtoiGood<uint64_t>(std::numeric_limits<uint64_t>::max(),
+ std::numeric_limits<uint64_t>::max());
+
+ // SimpleHexAtoi(absl::string_view, absl::uint128)
+ VerifySimpleHexAtoiGood<absl::uint128>(0, 0);
+ VerifySimpleHexAtoiGood<absl::uint128>(0x42, 0x42);
+ VerifySimpleHexAtoiBad<absl::uint128>(-0x42);
+
+ VerifySimpleHexAtoiBad<absl::uint128>(std::numeric_limits<int32_t>::min());
+ VerifySimpleHexAtoiGood<absl::uint128>(std::numeric_limits<int32_t>::max(),
+ std::numeric_limits<int32_t>::max());
+ VerifySimpleHexAtoiGood<absl::uint128>(std::numeric_limits<uint32_t>::max(),
+ std::numeric_limits<uint32_t>::max());
+ VerifySimpleHexAtoiBad<absl::uint128>(std::numeric_limits<int64_t>::min());
+ VerifySimpleHexAtoiGood<absl::uint128>(std::numeric_limits<int64_t>::max(),
+ std::numeric_limits<int64_t>::max());
+ VerifySimpleHexAtoiGood<absl::uint128>(std::numeric_limits<uint64_t>::max(),
+ std::numeric_limits<uint64_t>::max());
+ VerifySimpleHexAtoiGood<absl::uint128>(
+ std::numeric_limits<absl::uint128>::max(),
+ std::numeric_limits<absl::uint128>::max());
+
+ // Some other types
+ VerifySimpleHexAtoiGood<int>(-0x42, -0x42);
+ VerifySimpleHexAtoiGood<int32_t>(-0x42, -0x42);
+ VerifySimpleHexAtoiGood<uint32_t>(0x42, 0x42);
+ VerifySimpleHexAtoiGood<unsigned int>(0x42, 0x42);
+ VerifySimpleHexAtoiGood<int64_t>(-0x42, -0x42);
+ VerifySimpleHexAtoiGood<long>(-0x42, -0x42); // NOLINT: runtime-int
+ VerifySimpleHexAtoiGood<uint64_t>(0x42, 0x42);
+ VerifySimpleHexAtoiGood<size_t>(0x42, 0x42);
+ VerifySimpleHexAtoiGood<std::string::size_type>(0x42, 0x42);
+
+ // Number prefix
+ int32_t value;
+ EXPECT_TRUE(safe_strto32_base("0x34234324", &value, 16));
+ EXPECT_EQ(0x34234324, value);
+
+ EXPECT_TRUE(safe_strto32_base("0X34234324", &value, 16));
+ EXPECT_EQ(0x34234324, value);
+
+ // ASCII whitespace
+ EXPECT_TRUE(safe_strto32_base(" \t\n 34234324", &value, 16));
+ EXPECT_EQ(0x34234324, value);
+
+ EXPECT_TRUE(safe_strto32_base("34234324 \t\n ", &value, 16));
+ EXPECT_EQ(0x34234324, value);
+}
+
TEST(stringtest, safe_strto32_base) {
int32_t value;
EXPECT_TRUE(safe_strto32_base("0x34234324", &value, 16));
@@ -993,11 +1348,9 @@
if (strcmp(sixdigitsbuf, snprintfbuf) != 0) {
mismatches.push_back(d);
if (mismatches.size() < 10) {
- ABSL_RAW_LOG(ERROR, "%s",
- absl::StrCat("Six-digit failure with double. ", "d=", d,
- "=", d, " sixdigits=", sixdigitsbuf,
- " printf(%g)=", snprintfbuf)
- .c_str());
+ LOG(ERROR) << "Six-digit failure with double. d=" << d
+ << " sixdigits=" << sixdigitsbuf
+ << " printf(%g)=" << snprintfbuf;
}
}
};
@@ -1045,12 +1398,10 @@
if (kFloatNumCases >= 1e9) {
// The exhaustive test takes a very long time, so log progress.
char buf[kSixDigitsToBufferSize];
- ABSL_RAW_LOG(
- INFO, "%s",
- absl::StrCat("Exp ", exponent, " powten=", powten, "(", powten,
- ") (",
- std::string(buf, SixDigitsToBuffer(powten, buf)), ")")
- .c_str());
+ LOG(INFO) << "Exp " << exponent << " powten=" << powten << "(" << powten
+ << ") ("
+ << absl::string_view(buf, SixDigitsToBuffer(powten, buf))
+ << ")";
}
for (int digits : digit_testcases) {
if (exponent == 308 && digits >= 179769) break; // don't overflow!
@@ -1075,20 +1426,17 @@
double before = nextafter(d, 0.0);
double after = nextafter(d, 1.7976931348623157e308);
char b1[32], b2[kSixDigitsToBufferSize];
- ABSL_RAW_LOG(
- ERROR, "%s",
- absl::StrCat(
- "Mismatch #", i, " d=", d, " (", ToNineDigits(d), ")",
- " sixdigits='", sixdigitsbuf, "'", " snprintf='", snprintfbuf,
- "'", " Before.=", PerfectDtoa(before), " ",
- (SixDigitsToBuffer(before, b2), b2),
- " vs snprintf=", (snprintf(b1, sizeof(b1), "%g", before), b1),
- " Perfect=", PerfectDtoa(d), " ", (SixDigitsToBuffer(d, b2), b2),
- " vs snprintf=", (snprintf(b1, sizeof(b1), "%g", d), b1),
- " After.=.", PerfectDtoa(after), " ",
- (SixDigitsToBuffer(after, b2), b2),
- " vs snprintf=", (snprintf(b1, sizeof(b1), "%g", after), b1))
- .c_str());
+ LOG(ERROR) << "Mismatch #" << i << " d=" << d << " (" << ToNineDigits(d)
+ << ") sixdigits='" << sixdigitsbuf << "' snprintf='"
+ << snprintfbuf << "' Before.=" << PerfectDtoa(before) << " "
+ << (SixDigitsToBuffer(before, b2), b2) << " vs snprintf="
+ << (snprintf(b1, sizeof(b1), "%g", before), b1)
+ << " Perfect=" << PerfectDtoa(d) << " "
+ << (SixDigitsToBuffer(d, b2), b2)
+ << " vs snprintf=" << (snprintf(b1, sizeof(b1), "%g", d), b1)
+ << " After.=." << PerfectDtoa(after) << " "
+ << (SixDigitsToBuffer(after, b2), b2) << " vs snprintf="
+ << (snprintf(b1, sizeof(b1), "%g", after), b1);
}
}
}
@@ -1375,4 +1723,25 @@
}
}
+template <typename Int>
+void ExpectWritesNull() {
+ {
+ char buf[absl::numbers_internal::kFastToBufferSize];
+ Int x = std::numeric_limits<Int>::min();
+ EXPECT_THAT(absl::numbers_internal::FastIntToBuffer(x, buf), Pointee('\0'));
+ }
+ {
+ char buf[absl::numbers_internal::kFastToBufferSize];
+ Int x = std::numeric_limits<Int>::max();
+ EXPECT_THAT(absl::numbers_internal::FastIntToBuffer(x, buf), Pointee('\0'));
+ }
+}
+
+TEST(FastIntToBuffer, WritesNull) {
+ ExpectWritesNull<int32_t>();
+ ExpectWritesNull<uint32_t>();
+ ExpectWritesNull<int64_t>();
+ ExpectWritesNull<uint32_t>();
+}
+
} // namespace
diff --git a/absl/strings/str_cat.cc b/absl/strings/str_cat.cc
index f4a7749..098ab18 100644
--- a/absl/strings/str_cat.cc
+++ b/absl/strings/str_cat.cc
@@ -16,65 +16,22 @@
#include <assert.h>
-#include <algorithm>
+#include <cstddef>
#include <cstdint>
#include <cstring>
+#include <initializer_list>
+#include <string>
+#include <type_traits>
-#include "absl/strings/ascii.h"
+#include "absl/base/config.h"
+#include "absl/base/nullability.h"
#include "absl/strings/internal/resize_uninitialized.h"
#include "absl/strings/numbers.h"
+#include "absl/strings/string_view.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
-AlphaNum::AlphaNum(Hex hex) {
- static_assert(numbers_internal::kFastToBufferSize >= 32,
- "This function only works when output buffer >= 32 bytes long");
- char* const end = &digits_[numbers_internal::kFastToBufferSize];
- auto real_width =
- absl::numbers_internal::FastHexToBufferZeroPad16(hex.value, end - 16);
- if (real_width >= hex.width) {
- piece_ = absl::string_view(end - real_width, real_width);
- } else {
- // Pad first 16 chars because FastHexToBufferZeroPad16 pads only to 16 and
- // max pad width can be up to 20.
- std::memset(end - 32, hex.fill, 16);
- // Patch up everything else up to the real_width.
- std::memset(end - real_width - 16, hex.fill, 16);
- piece_ = absl::string_view(end - hex.width, hex.width);
- }
-}
-
-AlphaNum::AlphaNum(Dec dec) {
- assert(dec.width <= numbers_internal::kFastToBufferSize);
- char* const end = &digits_[numbers_internal::kFastToBufferSize];
- char* const minfill = end - dec.width;
- char* writer = end;
- uint64_t value = dec.value;
- bool neg = dec.neg;
- while (value > 9) {
- *--writer = '0' + (value % 10);
- value /= 10;
- }
- *--writer = '0' + value;
- if (neg) *--writer = '-';
-
- ptrdiff_t fillers = writer - minfill;
- if (fillers > 0) {
- // Tricky: if the fill character is ' ', then it's <fill><+/-><digits>
- // But...: if the fill character is '0', then it's <+/-><fill><digits>
- bool add_sign_again = false;
- if (neg && dec.fill == '0') { // If filling with '0',
- ++writer; // ignore the sign we just added
- add_sign_again = true; // and re-add the sign later.
- }
- writer -= fillers;
- std::fill_n(writer, fillers, dec.fill);
- if (add_sign_again) *--writer = '-';
- }
-
- piece_ = absl::string_view(writer, end - writer);
-}
// ----------------------------------------------------------------------
// StrCat()
@@ -83,9 +40,10 @@
// of a mix of raw C strings, string_views, strings, and integer values.
// ----------------------------------------------------------------------
+namespace {
// Append is merely a version of memcpy that returns the address of the byte
// after the area just overwritten.
-static char* Append(char* out, const AlphaNum& x) {
+absl::Nonnull<char*> Append(absl::Nonnull<char*> out, const AlphaNum& x) {
// memcpy is allowed to overwrite arbitrary memory, so doing this after the
// call would force an extra fetch of x.size().
char* after = out + x.size();
@@ -95,6 +53,8 @@
return after;
}
+} // namespace
+
std::string StrCat(const AlphaNum& a, const AlphaNum& b) {
std::string result;
absl::strings_internal::STLStringResizeUninitialized(&result,
@@ -138,15 +98,139 @@
namespace strings_internal {
// Do not call directly - these are not part of the public API.
+void STLStringAppendUninitializedAmortized(std::string* dest,
+ size_t to_append) {
+ strings_internal::AppendUninitializedTraits<std::string>::Append(dest,
+ to_append);
+}
+
+template <typename Integer>
+std::enable_if_t<std::is_integral<Integer>::value, std::string> IntegerToString(
+ Integer i) {
+ std::string str;
+ const auto /* either bool or std::false_type */ is_negative =
+ absl::numbers_internal::IsNegative(i);
+ const uint32_t digits = absl::numbers_internal::Base10Digits(
+ absl::numbers_internal::UnsignedAbsoluteValue(i));
+ absl::strings_internal::STLStringResizeUninitialized(
+ &str, digits + static_cast<uint32_t>(is_negative));
+ absl::numbers_internal::FastIntToBufferBackward(i, &str[str.size()], digits);
+ return str;
+}
+
+template <>
+std::string IntegerToString(long i) { // NOLINT
+ if (sizeof(i) <= sizeof(int)) {
+ return IntegerToString(static_cast<int>(i));
+ } else {
+ return IntegerToString(static_cast<long long>(i)); // NOLINT
+ }
+}
+
+template <>
+std::string IntegerToString(unsigned long i) { // NOLINT
+ if (sizeof(i) <= sizeof(unsigned int)) {
+ return IntegerToString(static_cast<unsigned int>(i));
+ } else {
+ return IntegerToString(static_cast<unsigned long long>(i)); // NOLINT
+ }
+}
+
+template <typename Float>
+std::enable_if_t<std::is_floating_point<Float>::value, std::string>
+FloatToString(Float f) {
+ std::string result;
+ strings_internal::STLStringResizeUninitialized(
+ &result, numbers_internal::kSixDigitsToBufferSize);
+ char* start = &result[0];
+ result.erase(numbers_internal::SixDigitsToBuffer(f, start));
+ return result;
+}
+
+std::string SingleArgStrCat(int x) { return IntegerToString(x); }
+std::string SingleArgStrCat(unsigned int x) { return IntegerToString(x); }
+// NOLINTNEXTLINE
+std::string SingleArgStrCat(long x) { return IntegerToString(x); }
+// NOLINTNEXTLINE
+std::string SingleArgStrCat(unsigned long x) { return IntegerToString(x); }
+// NOLINTNEXTLINE
+std::string SingleArgStrCat(long long x) { return IntegerToString(x); }
+// NOLINTNEXTLINE
+std::string SingleArgStrCat(unsigned long long x) { return IntegerToString(x); }
+std::string SingleArgStrCat(float x) { return FloatToString(x); }
+std::string SingleArgStrCat(double x) { return FloatToString(x); }
+
+template <class Integer>
+std::enable_if_t<std::is_integral<Integer>::value, void> AppendIntegerToString(
+ std::string& str, Integer i) {
+ const auto /* either bool or std::false_type */ is_negative =
+ absl::numbers_internal::IsNegative(i);
+ const uint32_t digits = absl::numbers_internal::Base10Digits(
+ absl::numbers_internal::UnsignedAbsoluteValue(i));
+ absl::strings_internal::STLStringAppendUninitializedAmortized(
+ &str, digits + static_cast<uint32_t>(is_negative));
+ absl::numbers_internal::FastIntToBufferBackward(i, &str[str.size()], digits);
+}
+
+template <>
+void AppendIntegerToString(std::string& str, long i) { // NOLINT
+ if (sizeof(i) <= sizeof(int)) {
+ return AppendIntegerToString(str, static_cast<int>(i));
+ } else {
+ return AppendIntegerToString(str, static_cast<long long>(i)); // NOLINT
+ }
+}
+
+template <>
+void AppendIntegerToString(std::string& str,
+ unsigned long i) { // NOLINT
+ if (sizeof(i) <= sizeof(unsigned int)) {
+ return AppendIntegerToString(str, static_cast<unsigned int>(i));
+ } else {
+ return AppendIntegerToString(str,
+ static_cast<unsigned long long>(i)); // NOLINT
+ }
+}
+
+// `SingleArgStrAppend` overloads are defined here for the same reasons as with
+// `SingleArgStrCat` above.
+void SingleArgStrAppend(std::string& str, int x) {
+ return AppendIntegerToString(str, x);
+}
+
+void SingleArgStrAppend(std::string& str, unsigned int x) {
+ return AppendIntegerToString(str, x);
+}
+
+// NOLINTNEXTLINE
+void SingleArgStrAppend(std::string& str, long x) {
+ return AppendIntegerToString(str, x);
+}
+
+// NOLINTNEXTLINE
+void SingleArgStrAppend(std::string& str, unsigned long x) {
+ return AppendIntegerToString(str, x);
+}
+
+// NOLINTNEXTLINE
+void SingleArgStrAppend(std::string& str, long long x) {
+ return AppendIntegerToString(str, x);
+}
+
+// NOLINTNEXTLINE
+void SingleArgStrAppend(std::string& str, unsigned long long x) {
+ return AppendIntegerToString(str, x);
+}
+
std::string CatPieces(std::initializer_list<absl::string_view> pieces) {
std::string result;
size_t total_size = 0;
- for (const absl::string_view& piece : pieces) total_size += piece.size();
+ for (absl::string_view piece : pieces) total_size += piece.size();
strings_internal::STLStringResizeUninitialized(&result, total_size);
char* const begin = &result[0];
char* out = begin;
- for (const absl::string_view& piece : pieces) {
+ for (absl::string_view piece : pieces) {
const size_t this_size = piece.size();
if (this_size != 0) {
memcpy(out, piece.data(), this_size);
@@ -166,19 +250,19 @@
assert(((src).size() == 0) || \
(uintptr_t((src).data() - (dest).data()) > uintptr_t((dest).size())))
-void AppendPieces(std::string* dest,
+void AppendPieces(absl::Nonnull<std::string*> dest,
std::initializer_list<absl::string_view> pieces) {
size_t old_size = dest->size();
- size_t total_size = old_size;
- for (const absl::string_view& piece : pieces) {
+ size_t to_append = 0;
+ for (absl::string_view piece : pieces) {
ASSERT_NO_OVERLAP(*dest, piece);
- total_size += piece.size();
+ to_append += piece.size();
}
- strings_internal::STLStringResizeUninitializedAmortized(dest, total_size);
+ strings_internal::STLStringAppendUninitializedAmortized(dest, to_append);
char* const begin = &(*dest)[0];
char* out = begin + old_size;
- for (const absl::string_view& piece : pieces) {
+ for (absl::string_view piece : pieces) {
const size_t this_size = piece.size();
if (this_size != 0) {
memcpy(out, piece.data(), this_size);
@@ -190,17 +274,23 @@
} // namespace strings_internal
-void StrAppend(std::string* dest, const AlphaNum& a) {
+void StrAppend(absl::Nonnull<std::string*> dest, const AlphaNum& a) {
ASSERT_NO_OVERLAP(*dest, a);
- dest->append(a.data(), a.size());
+ std::string::size_type old_size = dest->size();
+ strings_internal::STLStringAppendUninitializedAmortized(dest, a.size());
+ char* const begin = &(*dest)[0];
+ char* out = begin + old_size;
+ out = Append(out, a);
+ assert(out == begin + dest->size());
}
-void StrAppend(std::string* dest, const AlphaNum& a, const AlphaNum& b) {
+void StrAppend(absl::Nonnull<std::string*> dest, const AlphaNum& a,
+ const AlphaNum& b) {
ASSERT_NO_OVERLAP(*dest, a);
ASSERT_NO_OVERLAP(*dest, b);
std::string::size_type old_size = dest->size();
- strings_internal::STLStringResizeUninitializedAmortized(
- dest, old_size + a.size() + b.size());
+ strings_internal::STLStringAppendUninitializedAmortized(dest,
+ a.size() + b.size());
char* const begin = &(*dest)[0];
char* out = begin + old_size;
out = Append(out, a);
@@ -208,14 +298,14 @@
assert(out == begin + dest->size());
}
-void StrAppend(std::string* dest, const AlphaNum& a, const AlphaNum& b,
- const AlphaNum& c) {
+void StrAppend(absl::Nonnull<std::string*> dest, const AlphaNum& a,
+ const AlphaNum& b, const AlphaNum& c) {
ASSERT_NO_OVERLAP(*dest, a);
ASSERT_NO_OVERLAP(*dest, b);
ASSERT_NO_OVERLAP(*dest, c);
std::string::size_type old_size = dest->size();
- strings_internal::STLStringResizeUninitializedAmortized(
- dest, old_size + a.size() + b.size() + c.size());
+ strings_internal::STLStringAppendUninitializedAmortized(
+ dest, a.size() + b.size() + c.size());
char* const begin = &(*dest)[0];
char* out = begin + old_size;
out = Append(out, a);
@@ -224,15 +314,15 @@
assert(out == begin + dest->size());
}
-void StrAppend(std::string* dest, const AlphaNum& a, const AlphaNum& b,
- const AlphaNum& c, const AlphaNum& d) {
+void StrAppend(absl::Nonnull<std::string*> dest, const AlphaNum& a,
+ const AlphaNum& b, const AlphaNum& c, const AlphaNum& d) {
ASSERT_NO_OVERLAP(*dest, a);
ASSERT_NO_OVERLAP(*dest, b);
ASSERT_NO_OVERLAP(*dest, c);
ASSERT_NO_OVERLAP(*dest, d);
std::string::size_type old_size = dest->size();
- strings_internal::STLStringResizeUninitializedAmortized(
- dest, old_size + a.size() + b.size() + c.size() + d.size());
+ strings_internal::STLStringAppendUninitializedAmortized(
+ dest, a.size() + b.size() + c.size() + d.size());
char* const begin = &(*dest)[0];
char* out = begin + old_size;
out = Append(out, a);
diff --git a/absl/strings/str_cat.h b/absl/strings/str_cat.h
index a8a85c7..33355bf 100644
--- a/absl/strings/str_cat.h
+++ b/absl/strings/str_cat.h
@@ -48,18 +48,64 @@
// `StrCat()` or `StrAppend()`. You may specify a minimum hex field width using
// a `PadSpec` enum.
//
+// User-defined types can be formatted with the `AbslStringify()` customization
+// point. The API relies on detecting an overload in the user-defined type's
+// namespace of a free (non-member) `AbslStringify()` function as a definition
+// (typically declared as a friend and implemented in-line.
+// with the following signature:
+//
+// class MyClass { ... };
+//
+// template <typename Sink>
+// void AbslStringify(Sink& sink, const MyClass& value);
+//
+// An `AbslStringify()` overload for a type should only be declared in the same
+// file and namespace as said type.
+//
+// Note that `AbslStringify()` also supports use with `absl::StrFormat()` and
+// `absl::Substitute()`.
+//
+// Example:
+//
+// struct Point {
+// // To add formatting support to `Point`, we simply need to add a free
+// // (non-member) function `AbslStringify()`. This method specifies how
+// // Point should be printed when absl::StrCat() is called on it. You can add
+// // such a free function using a friend declaration within the body of the
+// // class. The sink parameter is a templated type to avoid requiring
+// // dependencies.
+// template <typename Sink> friend void AbslStringify(Sink&
+// sink, const Point& p) {
+// absl::Format(&sink, "(%v, %v)", p.x, p.y);
+// }
+//
+// int x;
+// int y;
+// };
// -----------------------------------------------------------------------------
#ifndef ABSL_STRINGS_STR_CAT_H_
#define ABSL_STRINGS_STR_CAT_H_
+#include <algorithm>
#include <array>
+#include <cassert>
+#include <cstddef>
#include <cstdint>
+#include <cstring>
+#include <initializer_list>
#include <string>
#include <type_traits>
+#include <utility>
#include <vector>
+#include "absl/base/attributes.h"
+#include "absl/base/nullability.h"
#include "absl/base/port.h"
+#include "absl/meta/type_traits.h"
+#include "absl/strings/has_absl_stringify.h"
+#include "absl/strings/internal/resize_uninitialized.h"
+#include "absl/strings/internal/stringify_sink.h"
#include "absl/strings/numbers.h"
#include "absl/strings/string_view.h"
@@ -161,9 +207,30 @@
!std::is_pointer<Int>::value>::type* = nullptr)
: Hex(spec, static_cast<uint64_t>(v)) {}
template <typename Pointee>
- explicit Hex(Pointee* v, PadSpec spec = absl::kNoPad)
+ explicit Hex(absl::Nullable<Pointee*> v, PadSpec spec = absl::kNoPad)
: Hex(spec, reinterpret_cast<uintptr_t>(v)) {}
+ template <typename S>
+ friend void AbslStringify(S& sink, Hex hex) {
+ static_assert(
+ numbers_internal::kFastToBufferSize >= 32,
+ "This function only works when output buffer >= 32 bytes long");
+ char buffer[numbers_internal::kFastToBufferSize];
+ char* const end = &buffer[numbers_internal::kFastToBufferSize];
+ auto real_width =
+ absl::numbers_internal::FastHexToBufferZeroPad16(hex.value, end - 16);
+ if (real_width >= hex.width) {
+ sink.Append(absl::string_view(end - real_width, real_width));
+ } else {
+ // Pad first 16 chars because FastHexToBufferZeroPad16 pads only to 16 and
+ // max pad width can be up to 20.
+ std::memset(end - 32, hex.fill, 16);
+ // Patch up everything else up to the real_width.
+ std::memset(end - real_width - 16, hex.fill, 16);
+ sink.Append(absl::string_view(end - hex.width, hex.width));
+ }
+ }
+
private:
Hex(PadSpec spec, uint64_t v)
: value(v),
@@ -192,12 +259,43 @@
typename std::enable_if<(sizeof(Int) <= 8)>::type* = nullptr)
: value(v >= 0 ? static_cast<uint64_t>(v)
: uint64_t{0} - static_cast<uint64_t>(v)),
- width(spec == absl::kNoPad
- ? 1
- : spec >= absl::kSpacePad2 ? spec - absl::kSpacePad2 + 2
- : spec - absl::kZeroPad2 + 2),
+ width(spec == absl::kNoPad ? 1
+ : spec >= absl::kSpacePad2 ? spec - absl::kSpacePad2 + 2
+ : spec - absl::kZeroPad2 + 2),
fill(spec >= absl::kSpacePad2 ? ' ' : '0'),
neg(v < 0) {}
+
+ template <typename S>
+ friend void AbslStringify(S& sink, Dec dec) {
+ assert(dec.width <= numbers_internal::kFastToBufferSize);
+ char buffer[numbers_internal::kFastToBufferSize];
+ char* const end = &buffer[numbers_internal::kFastToBufferSize];
+ char* const minfill = end - dec.width;
+ char* writer = end;
+ uint64_t val = dec.value;
+ while (val > 9) {
+ *--writer = '0' + (val % 10);
+ val /= 10;
+ }
+ *--writer = '0' + static_cast<char>(val);
+ if (dec.neg) *--writer = '-';
+
+ ptrdiff_t fillers = writer - minfill;
+ if (fillers > 0) {
+ // Tricky: if the fill character is ' ', then it's <fill><+/-><digits>
+ // But...: if the fill character is '0', then it's <+/-><fill><digits>
+ bool add_sign_again = false;
+ if (dec.neg && dec.fill == '0') { // If filling with '0',
+ ++writer; // ignore the sign we just added
+ add_sign_again = true; // and re-add the sign later.
+ }
+ writer -= fillers;
+ std::fill_n(writer, fillers, dec.fill);
+ if (add_sign_again) *--writer = '-';
+ }
+
+ sink.Append(absl::string_view(writer, static_cast<size_t>(end - writer)));
+ }
};
// -----------------------------------------------------------------------------
@@ -205,52 +303,74 @@
// -----------------------------------------------------------------------------
//
// The `AlphaNum` class acts as the main parameter type for `StrCat()` and
-// `StrAppend()`, providing efficient conversion of numeric, boolean, and
-// hexadecimal values (through the `Hex` type) into strings.
+// `StrAppend()`, providing efficient conversion of numeric, boolean, decimal,
+// and hexadecimal values (through the `Dec` and `Hex` types) into strings.
+// `AlphaNum` should only be used as a function parameter. Do not instantiate
+// `AlphaNum` directly as a stack variable.
class AlphaNum {
public:
// No bool ctor -- bools convert to an integral type.
// A bool ctor would also convert incoming pointers (bletch).
+ // Prevent brace initialization
+ template <typename T>
+ AlphaNum(std::initializer_list<T>) = delete; // NOLINT(runtime/explicit)
+
AlphaNum(int x) // NOLINT(runtime/explicit)
- : piece_(digits_,
- numbers_internal::FastIntToBuffer(x, digits_) - &digits_[0]) {}
+ : piece_(digits_, static_cast<size_t>(
+ numbers_internal::FastIntToBuffer(x, digits_) -
+ &digits_[0])) {}
AlphaNum(unsigned int x) // NOLINT(runtime/explicit)
- : piece_(digits_,
- numbers_internal::FastIntToBuffer(x, digits_) - &digits_[0]) {}
+ : piece_(digits_, static_cast<size_t>(
+ numbers_internal::FastIntToBuffer(x, digits_) -
+ &digits_[0])) {}
AlphaNum(long x) // NOLINT(*)
- : piece_(digits_,
- numbers_internal::FastIntToBuffer(x, digits_) - &digits_[0]) {}
+ : piece_(digits_, static_cast<size_t>(
+ numbers_internal::FastIntToBuffer(x, digits_) -
+ &digits_[0])) {}
AlphaNum(unsigned long x) // NOLINT(*)
- : piece_(digits_,
- numbers_internal::FastIntToBuffer(x, digits_) - &digits_[0]) {}
+ : piece_(digits_, static_cast<size_t>(
+ numbers_internal::FastIntToBuffer(x, digits_) -
+ &digits_[0])) {}
AlphaNum(long long x) // NOLINT(*)
- : piece_(digits_,
- numbers_internal::FastIntToBuffer(x, digits_) - &digits_[0]) {}
+ : piece_(digits_, static_cast<size_t>(
+ numbers_internal::FastIntToBuffer(x, digits_) -
+ &digits_[0])) {}
AlphaNum(unsigned long long x) // NOLINT(*)
- : piece_(digits_,
- numbers_internal::FastIntToBuffer(x, digits_) - &digits_[0]) {}
+ : piece_(digits_, static_cast<size_t>(
+ numbers_internal::FastIntToBuffer(x, digits_) -
+ &digits_[0])) {}
AlphaNum(float f) // NOLINT(runtime/explicit)
: piece_(digits_, numbers_internal::SixDigitsToBuffer(f, digits_)) {}
AlphaNum(double f) // NOLINT(runtime/explicit)
: piece_(digits_, numbers_internal::SixDigitsToBuffer(f, digits_)) {}
- AlphaNum(Hex hex); // NOLINT(runtime/explicit)
- AlphaNum(Dec dec); // NOLINT(runtime/explicit)
-
template <size_t size>
AlphaNum( // NOLINT(runtime/explicit)
- const strings_internal::AlphaNumBuffer<size>& buf)
+ const strings_internal::AlphaNumBuffer<size>& buf
+ ABSL_ATTRIBUTE_LIFETIME_BOUND)
: piece_(&buf.data[0], buf.size) {}
- AlphaNum(const char* c_str) : piece_(c_str) {} // NOLINT(runtime/explicit)
- AlphaNum(absl::string_view pc) : piece_(pc) {} // NOLINT(runtime/explicit)
+ AlphaNum(absl::Nullable<const char*> c_str // NOLINT(runtime/explicit)
+ ABSL_ATTRIBUTE_LIFETIME_BOUND)
+ : piece_(NullSafeStringView(c_str)) {}
+ AlphaNum(absl::string_view pc // NOLINT(runtime/explicit)
+ ABSL_ATTRIBUTE_LIFETIME_BOUND)
+ : piece_(pc) {}
+
+ template <typename T, typename = typename std::enable_if<
+ HasAbslStringify<T>::value>::type>
+ AlphaNum( // NOLINT(runtime/explicit)
+ const T& v ABSL_ATTRIBUTE_LIFETIME_BOUND,
+ strings_internal::StringifySink&& sink ABSL_ATTRIBUTE_LIFETIME_BOUND = {})
+ : piece_(strings_internal::ExtractStringification(sink, v)) {}
template <typename Allocator>
AlphaNum( // NOLINT(runtime/explicit)
- const std::basic_string<char, std::char_traits<char>, Allocator>& str)
+ const std::basic_string<char, std::char_traits<char>, Allocator>& str
+ ABSL_ATTRIBUTE_LIFETIME_BOUND)
: piece_(str) {}
// Use string literals ":" instead of character literals ':'.
@@ -260,16 +380,27 @@
AlphaNum& operator=(const AlphaNum&) = delete;
absl::string_view::size_type size() const { return piece_.size(); }
- const char* data() const { return piece_.data(); }
+ absl::Nullable<const char*> data() const { return piece_.data(); }
absl::string_view Piece() const { return piece_; }
- // Normal enums are already handled by the integer formatters.
- // This overload matches only scoped enums.
+ // Match unscoped enums. Use integral promotion so that a `char`-backed
+ // enum becomes a wider integral type AlphaNum will accept.
template <typename T,
typename = typename std::enable_if<
- std::is_enum<T>{} && !std::is_convertible<T, int>{}>::type>
+ std::is_enum<T>{} && std::is_convertible<T, int>{} &&
+ !HasAbslStringify<T>::value>::type>
AlphaNum(T e) // NOLINT(runtime/explicit)
- : AlphaNum(static_cast<typename std::underlying_type<T>::type>(e)) {}
+ : AlphaNum(+e) {}
+
+ // This overload matches scoped enums. We must explicitly cast to the
+ // underlying type, but use integral promotion for the same reason as above.
+ template <typename T,
+ typename std::enable_if<std::is_enum<T>{} &&
+ !std::is_convertible<T, int>{} &&
+ !HasAbslStringify<T>::value,
+ char*>::type = nullptr>
+ AlphaNum(T e) // NOLINT(runtime/explicit)
+ : AlphaNum(+static_cast<typename std::underlying_type<T>::type>(e)) {}
// vector<bool>::reference and const_reference require special help to
// convert to `AlphaNum` because it requires two user defined conversions.
@@ -319,13 +450,48 @@
// Do not call directly - this is not part of the public API.
std::string CatPieces(std::initializer_list<absl::string_view> pieces);
-void AppendPieces(std::string* dest,
+void AppendPieces(absl::Nonnull<std::string*> dest,
std::initializer_list<absl::string_view> pieces);
+void STLStringAppendUninitializedAmortized(std::string* dest, size_t to_append);
+
+// `SingleArgStrCat` overloads take built-in `int`, `long` and `long long` types
+// (signed / unsigned) to avoid ambiguity on the call side. If we used int32_t
+// and int64_t, then at least one of the three (`int` / `long` / `long long`)
+// would have been ambiguous when passed to `SingleArgStrCat`.
+std::string SingleArgStrCat(int x);
+std::string SingleArgStrCat(unsigned int x);
+std::string SingleArgStrCat(long x); // NOLINT
+std::string SingleArgStrCat(unsigned long x); // NOLINT
+std::string SingleArgStrCat(long long x); // NOLINT
+std::string SingleArgStrCat(unsigned long long x); // NOLINT
+std::string SingleArgStrCat(float x);
+std::string SingleArgStrCat(double x);
+
+// `SingleArgStrAppend` overloads are defined here for the same reasons as with
+// `SingleArgStrCat` above.
+void SingleArgStrAppend(std::string& str, int x);
+void SingleArgStrAppend(std::string& str, unsigned int x);
+void SingleArgStrAppend(std::string& str, long x); // NOLINT
+void SingleArgStrAppend(std::string& str, unsigned long x); // NOLINT
+void SingleArgStrAppend(std::string& str, long long x); // NOLINT
+void SingleArgStrAppend(std::string& str, unsigned long long x); // NOLINT
+
+template <typename T,
+ typename = std::enable_if_t<std::is_arithmetic<T>::value &&
+ !std::is_same<T, char>::value &&
+ !std::is_same<T, bool>::value>>
+using EnableIfFastCase = T;
+
} // namespace strings_internal
ABSL_MUST_USE_RESULT inline std::string StrCat() { return std::string(); }
+template <typename T>
+ABSL_MUST_USE_RESULT inline std::string StrCat(
+ strings_internal::EnableIfFastCase<T> a) {
+ return strings_internal::SingleArgStrCat(a);
+}
ABSL_MUST_USE_RESULT inline std::string StrCat(const AlphaNum& a) {
return std::string(a.data(), a.size());
}
@@ -373,24 +539,86 @@
// absl::string_view p = s;
// StrAppend(&s, p);
-inline void StrAppend(std::string*) {}
-void StrAppend(std::string* dest, const AlphaNum& a);
-void StrAppend(std::string* dest, const AlphaNum& a, const AlphaNum& b);
-void StrAppend(std::string* dest, const AlphaNum& a, const AlphaNum& b,
- const AlphaNum& c);
-void StrAppend(std::string* dest, const AlphaNum& a, const AlphaNum& b,
- const AlphaNum& c, const AlphaNum& d);
+inline void StrAppend(absl::Nonnull<std::string*>) {}
+void StrAppend(absl::Nonnull<std::string*> dest, const AlphaNum& a);
+void StrAppend(absl::Nonnull<std::string*> dest, const AlphaNum& a,
+ const AlphaNum& b);
+void StrAppend(absl::Nonnull<std::string*> dest, const AlphaNum& a,
+ const AlphaNum& b, const AlphaNum& c);
+void StrAppend(absl::Nonnull<std::string*> dest, const AlphaNum& a,
+ const AlphaNum& b, const AlphaNum& c, const AlphaNum& d);
// Support 5 or more arguments
template <typename... AV>
-inline void StrAppend(std::string* dest, const AlphaNum& a, const AlphaNum& b,
- const AlphaNum& c, const AlphaNum& d, const AlphaNum& e,
- const AV&... args) {
+inline void StrAppend(absl::Nonnull<std::string*> dest, const AlphaNum& a,
+ const AlphaNum& b, const AlphaNum& c, const AlphaNum& d,
+ const AlphaNum& e, const AV&... args) {
strings_internal::AppendPieces(
dest, {a.Piece(), b.Piece(), c.Piece(), d.Piece(), e.Piece(),
static_cast<const AlphaNum&>(args).Piece()...});
}
+template <class String, class T>
+std::enable_if_t<
+ std::is_integral<absl::strings_internal::EnableIfFastCase<T>>::value, void>
+StrAppend(absl::Nonnull<String*> result, T i) {
+ return absl::strings_internal::SingleArgStrAppend(*result, i);
+}
+
+// This overload is only selected if all the parameters are numbers that can be
+// handled quickly.
+// Later we can look into how we can extend this to more general argument
+// mixtures without bloating codegen too much, or copying unnecessarily.
+template <typename String, typename... T>
+std::enable_if_t<
+ (sizeof...(T) > 1),
+ std::common_type_t<std::conditional_t<
+ true, void, absl::strings_internal::EnableIfFastCase<T>>...>>
+StrAppend(absl::Nonnull<String*> str, T... args) {
+ // Do not add unnecessary variables, logic, or even "free" lambdas here.
+ // They can add overhead for the compiler and/or at run time.
+ // Furthermore, assume this function will be inlined.
+ // This function is carefully tailored to be able to be largely optimized away
+ // so that it becomes near-equivalent to the caller handling each argument
+ // individually while minimizing register pressure, so that the compiler
+ // can inline it with minimal overhead.
+
+ // First, calculate the total length, so we can perform just a single resize.
+ // Save all the lengths for later.
+ size_t total_length = 0;
+ const ptrdiff_t lengths[] = {
+ absl::numbers_internal::GetNumDigitsOrNegativeIfNegative(args)...};
+ for (const ptrdiff_t possibly_negative_length : lengths) {
+ // Lengths are negative for negative numbers. Keep them for later use, but
+ // take their absolute values for calculating total lengths;
+ total_length += possibly_negative_length < 0
+ ? static_cast<size_t>(-possibly_negative_length)
+ : static_cast<size_t>(possibly_negative_length);
+ }
+
+ // Now reserve space for all the arguments.
+ const size_t old_size = str->size();
+ absl::strings_internal::STLStringAppendUninitializedAmortized(str,
+ total_length);
+
+ // Finally, output each argument one-by-one, from left to right.
+ size_t i = 0; // The current argument we're processing
+ ptrdiff_t n; // The length of the current argument
+ typename String::pointer pos = &(*str)[old_size];
+ using SomeTrivialEmptyType = std::false_type;
+ const SomeTrivialEmptyType dummy;
+ // Ugly code due to the lack of C++17 fold expressions
+ const SomeTrivialEmptyType dummies[] = {
+ (/* Comma expressions are poor man's C++17 fold expression for C++14 */
+ (void)(n = lengths[i]),
+ (void)(n < 0 ? (void)(*pos++ = '-'), (n = ~n) : 0),
+ (void)absl::numbers_internal::FastIntToBufferBackward(
+ absl::numbers_internal::UnsignedAbsoluteValue(std::move(args)),
+ pos += n, static_cast<uint32_t>(n)),
+ (void)++i, dummy)...};
+ (void)dummies; // Remove & migrate to fold expressions in C++17
+}
+
// Helper function for the future StrCat default floating-point format, %.6g
// This is fast.
inline strings_internal::AlphaNumBuffer<
diff --git a/absl/strings/str_cat_benchmark.cc b/absl/strings/str_cat_benchmark.cc
index 02c4dbe..0a851e7 100644
--- a/absl/strings/str_cat_benchmark.cc
+++ b/absl/strings/str_cat_benchmark.cc
@@ -12,12 +12,20 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#include "absl/strings/str_cat.h"
-
+#include <array>
#include <cstdint>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
#include <string>
+#include <tuple>
+#include <utility>
#include "benchmark/benchmark.h"
+#include "absl/random/log_uniform_int_distribution.h"
+#include "absl/random/random.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/string_view.h"
#include "absl/strings/substitute.h"
namespace {
@@ -137,39 +145,65 @@
}
BENCHMARK(BM_DoubleToString_By_SixDigits);
-template <typename... Chunks>
-void BM_StrAppendImpl(benchmark::State& state, size_t total_bytes,
- Chunks... chunks) {
+template <typename Table, size_t... Index>
+void BM_StrAppendImpl(benchmark::State& state, Table table, size_t total_bytes,
+ std::index_sequence<Index...>) {
for (auto s : state) {
+ const size_t table_size = table.size();
+ size_t i = 0;
std::string result;
while (result.size() < total_bytes) {
- absl::StrAppend(&result, chunks...);
+ absl::StrAppend(&result, std::get<Index>(table[i])...);
benchmark::DoNotOptimize(result);
+ ++i;
+ i -= i >= table_size ? table_size : 0;
}
}
}
-void BM_StrAppend(benchmark::State& state) {
- const int total_bytes = state.range(0);
+template <typename Array>
+void BM_StrAppend(benchmark::State& state, Array&& table) {
+ const size_t total_bytes = state.range(0);
const int chunks_at_a_time = state.range(1);
- const absl::string_view kChunk = "0123456789";
switch (chunks_at_a_time) {
case 1:
- return BM_StrAppendImpl(state, total_bytes, kChunk);
+ return BM_StrAppendImpl(state, std::forward<Array>(table), total_bytes,
+ std::make_index_sequence<1>());
case 2:
- return BM_StrAppendImpl(state, total_bytes, kChunk, kChunk);
+ return BM_StrAppendImpl(state, std::forward<Array>(table), total_bytes,
+ std::make_index_sequence<2>());
case 4:
- return BM_StrAppendImpl(state, total_bytes, kChunk, kChunk, kChunk,
- kChunk);
+ return BM_StrAppendImpl(state, std::forward<Array>(table), total_bytes,
+ std::make_index_sequence<4>());
case 8:
- return BM_StrAppendImpl(state, total_bytes, kChunk, kChunk, kChunk,
- kChunk, kChunk, kChunk, kChunk, kChunk);
+ return BM_StrAppendImpl(state, std::forward<Array>(table), total_bytes,
+ std::make_index_sequence<8>());
default:
std::abort();
}
}
+void BM_StrAppendStr(benchmark::State& state) {
+ using T = absl::string_view;
+ using Row = std::tuple<T, T, T, T, T, T, T, T>;
+ constexpr absl::string_view kChunk = "0123456789";
+ Row row = {kChunk, kChunk, kChunk, kChunk, kChunk, kChunk, kChunk, kChunk};
+ return BM_StrAppend(state, std::array<Row, 1>({row}));
+}
+
+template <typename T>
+void BM_StrAppendInt(benchmark::State& state) {
+ absl::BitGen rng;
+ absl::log_uniform_int_distribution<T> dist;
+ std::array<std::tuple<T, T, T, T, T, T, T, T>, (1 << 7)> table;
+ for (size_t i = 0; i < table.size(); ++i) {
+ table[i] = {dist(rng), dist(rng), dist(rng), dist(rng),
+ dist(rng), dist(rng), dist(rng), dist(rng)};
+ }
+ return BM_StrAppend(state, table);
+}
+
template <typename B>
void StrAppendConfig(B* benchmark) {
for (int bytes : {10, 100, 1000, 10000}) {
@@ -182,6 +216,50 @@
}
}
-BENCHMARK(BM_StrAppend)->Apply(StrAppendConfig);
+BENCHMARK(BM_StrAppendStr)->Apply(StrAppendConfig);
+BENCHMARK(BM_StrAppendInt<int64_t>)->Apply(StrAppendConfig);
+BENCHMARK(BM_StrAppendInt<uint64_t>)->Apply(StrAppendConfig);
+BENCHMARK(BM_StrAppendInt<int32_t>)->Apply(StrAppendConfig);
+BENCHMARK(BM_StrAppendInt<uint32_t>)->Apply(StrAppendConfig);
+
+template <typename... Chunks>
+void BM_StrCatImpl(benchmark::State& state,
+ Chunks... chunks) {
+ for (auto s : state) {
+ std::string result = absl::StrCat(chunks...);
+ benchmark::DoNotOptimize(result);
+ }
+}
+
+void BM_StrCat(benchmark::State& state) {
+ const int chunks_at_a_time = state.range(0);
+ const absl::string_view kChunk = "0123456789";
+
+ switch (chunks_at_a_time) {
+ case 1:
+ return BM_StrCatImpl(state, kChunk);
+ case 2:
+ return BM_StrCatImpl(state, kChunk, kChunk);
+ case 3:
+ return BM_StrCatImpl(state, kChunk, kChunk, kChunk);
+ case 4:
+ return BM_StrCatImpl(state, kChunk, kChunk, kChunk, kChunk);
+ default:
+ std::abort();
+ }
+}
+
+BENCHMARK(BM_StrCat)->Arg(1)->Arg(2)->Arg(3)->Arg(4);
+
+void BM_StrCat_int(benchmark::State& state) {
+ int i = 0;
+ for (auto s : state) {
+ std::string result = absl::StrCat(i);
+ benchmark::DoNotOptimize(result);
+ i = IncrementAlternatingSign(i);
+ }
+}
+
+BENCHMARK(BM_StrCat_int);
} // namespace
diff --git a/absl/strings/str_cat_test.cc b/absl/strings/str_cat_test.cc
index f3770dc..b30a86f 100644
--- a/absl/strings/str_cat_test.cc
+++ b/absl/strings/str_cat_test.cc
@@ -16,12 +16,16 @@
#include "absl/strings/str_cat.h"
+#include <cstddef>
#include <cstdint>
+#include <cstdlib>
+#include <limits>
#include <string>
#include <vector>
#include "gtest/gtest.h"
-#include "absl/strings/substitute.h"
+#include "absl/strings/str_format.h"
+#include "absl/strings/string_view.h"
#ifdef __ANDROID__
// Android assert messages only go to system log, so death tests cannot inspect
@@ -35,6 +39,24 @@
namespace {
+template <typename Integer>
+void VerifyInteger(Integer value) {
+ const std::string expected = std::to_string(value);
+
+ EXPECT_EQ(absl::StrCat(value), expected);
+
+ const char* short_prefix = "x";
+ const char* long_prefix = "2;k.msabxiuow2[09i;o3k21-93-9=29]";
+
+ std::string short_str = short_prefix;
+ absl::StrAppend(&short_str, value);
+ EXPECT_EQ(short_str, short_prefix + expected);
+
+ std::string long_str = long_prefix;
+ absl::StrAppend(&long_str, value);
+ EXPECT_EQ(long_str, long_prefix + expected);
+}
+
// Test absl::StrCat of ints and longs of various sizes and signdedness.
TEST(StrCat, Ints) {
const short s = -1; // NOLINT(runtime/int)
@@ -64,6 +86,34 @@
EXPECT_EQ(answer, "-9-12");
answer = absl::StrCat(uintptr, 0);
EXPECT_EQ(answer, "130");
+
+ for (const uint32_t base : {2u, 10u}) {
+ for (const int extra_shift : {0, 12}) {
+ for (uint64_t i = 0; i < (1 << 8); ++i) {
+ uint64_t j = i;
+ while (true) {
+ uint64_t v = j ^ (extra_shift != 0 ? (j << extra_shift) * base : 0);
+ VerifyInteger(static_cast<bool>(v));
+ VerifyInteger(static_cast<wchar_t>(v));
+ VerifyInteger(static_cast<signed char>(v));
+ VerifyInteger(static_cast<unsigned char>(v));
+ VerifyInteger(static_cast<short>(v)); // NOLINT
+ VerifyInteger(static_cast<unsigned short>(v)); // NOLINT
+ VerifyInteger(static_cast<int>(v)); // NOLINT
+ VerifyInteger(static_cast<unsigned int>(v)); // NOLINT
+ VerifyInteger(static_cast<long>(v)); // NOLINT
+ VerifyInteger(static_cast<unsigned long>(v)); // NOLINT
+ VerifyInteger(static_cast<long long>(v)); // NOLINT
+ VerifyInteger(static_cast<unsigned long long>(v)); // NOLINT
+ const uint64_t next = j == 0 ? 1 : j * base;
+ if (next <= j) {
+ break;
+ }
+ j = next;
+ }
+ }
+ }
+ }
}
TEST(StrCat, Enums) {
@@ -210,6 +260,11 @@
EXPECT_EQ(result, "");
}
+TEST(StrCat, NullConstCharPtr) {
+ const char* null = nullptr;
+ EXPECT_EQ(absl::StrCat("mon", null, "key"), "monkey");
+}
+
// A minimal allocator that uses malloc().
template <typename T>
struct Mallocator {
@@ -437,7 +492,7 @@
EXPECT_EQ(result, "12345");
}
-#ifdef GTEST_HAS_DEATH_TEST
+#if GTEST_HAS_DEATH_TEST
TEST(StrAppend, Death) {
std::string s = "self";
// on linux it's "assertion", on mac it's "Assertion",
@@ -607,4 +662,69 @@
TestFastPrints();
}
+struct PointStringify {
+ template <typename FormatSink>
+ friend void AbslStringify(FormatSink& sink, const PointStringify& p) {
+ sink.Append("(");
+ sink.Append(absl::StrCat(p.x));
+ sink.Append(", ");
+ sink.Append(absl::StrCat(p.y));
+ sink.Append(")");
+ }
+
+ double x = 10.0;
+ double y = 20.0;
+};
+
+TEST(StrCat, AbslStringifyExample) {
+ PointStringify p;
+ EXPECT_EQ(absl::StrCat(p), "(10, 20)");
+ EXPECT_EQ(absl::StrCat("a ", p, " z"), "a (10, 20) z");
+}
+
+struct PointStringifyUsingFormat {
+ template <typename FormatSink>
+ friend void AbslStringify(FormatSink& sink,
+ const PointStringifyUsingFormat& p) {
+ absl::Format(&sink, "(%g, %g)", p.x, p.y);
+ }
+
+ double x = 10.0;
+ double y = 20.0;
+};
+
+TEST(StrCat, AbslStringifyExampleUsingFormat) {
+ PointStringifyUsingFormat p;
+ EXPECT_EQ(absl::StrCat(p), "(10, 20)");
+ EXPECT_EQ(absl::StrCat("a ", p, " z"), "a (10, 20) z");
+}
+
+enum class EnumWithStringify { Many = 0, Choices = 1 };
+
+template <typename Sink>
+void AbslStringify(Sink& sink, EnumWithStringify e) {
+ absl::Format(&sink, "%s", e == EnumWithStringify::Many ? "Many" : "Choices");
+}
+
+TEST(StrCat, AbslStringifyWithEnum) {
+ const auto e = EnumWithStringify::Choices;
+ EXPECT_EQ(absl::StrCat(e), "Choices");
+}
+
+template <typename Integer>
+void CheckSingleArgumentIntegerLimits() {
+ Integer max = std::numeric_limits<Integer>::max();
+ Integer min = std::numeric_limits<Integer>::min();
+
+ EXPECT_EQ(absl::StrCat(max), std::to_string(max));
+ EXPECT_EQ(absl::StrCat(min), std::to_string(min));
+}
+
+TEST(StrCat, SingleArgumentLimits) {
+ CheckSingleArgumentIntegerLimits<int32_t>();
+ CheckSingleArgumentIntegerLimits<uint32_t>();
+ CheckSingleArgumentIntegerLimits<int64_t>();
+ CheckSingleArgumentIntegerLimits<uint64_t>();
+}
+
} // namespace
diff --git a/absl/strings/str_format.h b/absl/strings/str_format.h
index 4b05c70..66b6af5 100644
--- a/absl/strings/str_format.h
+++ b/absl/strings/str_format.h
@@ -36,10 +36,12 @@
// * `absl::StreamFormat()` to more efficiently write a format string to a
// stream, such as`std::cout`.
// * `absl::PrintF()`, `absl::FPrintF()` and `absl::SNPrintF()` as
-// replacements for `std::printf()`, `std::fprintf()` and `std::snprintf()`.
+// drop-in replacements for `std::printf()`, `std::fprintf()` and
+// `std::snprintf()`.
//
-// Note: a version of `std::sprintf()` is not supported as it is
-// generally unsafe due to buffer overflows.
+// Note: An `absl::SPrintF()` drop-in replacement is not supported as it
+// is generally unsafe due to buffer overflows. Use `absl::StrFormat` which
+// returns the string as output instead of expecting a pre-allocated buffer.
//
// Additionally, you can provide a format string (and its associated arguments)
// using one of the following abstractions:
@@ -70,14 +72,21 @@
#ifndef ABSL_STRINGS_STR_FORMAT_H_
#define ABSL_STRINGS_STR_FORMAT_H_
+#include <cstdint>
#include <cstdio>
#include <string>
+#include <type_traits>
+#include "absl/base/attributes.h"
+#include "absl/base/config.h"
+#include "absl/base/nullability.h"
#include "absl/strings/internal/str_format/arg.h" // IWYU pragma: export
#include "absl/strings/internal/str_format/bind.h" // IWYU pragma: export
#include "absl/strings/internal/str_format/checker.h" // IWYU pragma: export
#include "absl/strings/internal/str_format/extension.h" // IWYU pragma: export
#include "absl/strings/internal/str_format/parser.h" // IWYU pragma: export
+#include "absl/strings/string_view.h"
+#include "absl/types/span.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
@@ -102,7 +111,8 @@
explicit UntypedFormatSpec(string_view s) : spec_(s) {}
protected:
- explicit UntypedFormatSpec(const str_format_internal::ParsedFormatBase* pc)
+ explicit UntypedFormatSpec(
+ absl::Nonnull<const str_format_internal::ParsedFormatBase*> pc)
: spec_(pc) {}
private:
@@ -142,7 +152,7 @@
// EXPECT_EQ(8, n);
class FormatCountCapture {
public:
- explicit FormatCountCapture(int* p) : p_(p) {}
+ explicit FormatCountCapture(absl::Nonnull<int*> p) : p_(p) {}
private:
// FormatCountCaptureHelper is used to define FormatConvertImpl() for this
@@ -151,8 +161,8 @@
// Unused() is here because of the false positive from -Wunused-private-field
// p_ is used in the templated function of the friend FormatCountCaptureHelper
// class.
- int* Unused() { return p_; }
- int* p_;
+ absl::Nonnull<int*> Unused() { return p_; }
+ absl::Nonnull<int*> p_;
};
// FormatSpec
@@ -191,9 +201,9 @@
// absl::StrFormat(formatString, "TheVillage", 6);
//
// A format string generally follows the POSIX syntax as used within the POSIX
-// `printf` specification.
+// `printf` specification. (Exceptions are noted below.)
//
-// (See http://pubs.opengroup.org/onlinepubs/9699919799/functions/fprintf.html.)
+// (See http://pubs.opengroup.org/onlinepubs/9699919799/functions/fprintf.html)
//
// In specific, the `FormatSpec` supports the following type specifiers:
// * `c` for characters
@@ -211,6 +221,10 @@
// * `n` for the special case of writing out the number of characters
// written to this point. The resulting value must be captured within an
// `absl::FormatCountCapture` type.
+// * `v` for values using the default format for a deduced type. These deduced
+// types include many of the primitive types denoted here as well as
+// user-defined types containing the proper extensions. (See below for more
+// information.)
//
// Implementation-defined behavior:
// * A null pointer provided to "%s" or "%p" is output as "(nil)".
@@ -239,18 +253,28 @@
// "%s%d%n", "hello", 123, absl::FormatCountCapture(&n));
// EXPECT_EQ(8, n);
//
+// NOTE: the `v` specifier (for "value") is a type specifier not present in the
+// POSIX specification. %v will format values according to their deduced type.
+// `v` uses `d` for signed integer values, `u` for unsigned integer values, `g`
+// for floating point values, and formats boolean values as "true"/"false"
+// (instead of 1 or 0 for booleans formatted using d). `const char*` is not
+// supported; please use `std::string` and `string_view`. `char` is also not
+// supported due to ambiguity of the type. This specifier does not support
+// modifiers.
+//
// The `FormatSpec` intrinsically supports all of these fundamental C++ types:
//
-// * Characters: `char`, `signed char`, `unsigned char`
+// * Characters: `char`, `signed char`, `unsigned char`, `wchar_t`
// * Integers: `int`, `short`, `unsigned short`, `unsigned`, `long`,
// `unsigned long`, `long long`, `unsigned long long`
+// * Enums: printed as their underlying integral value
// * Floating-point: `float`, `double`, `long double`
//
// However, in the `str_format` library, a format conversion specifies a broader
// C++ conceptual category instead of an exact type. For example, `%s` binds to
-// any string-like argument, so `std::string`, `absl::string_view`, and
-// `const char*` are all accepted. Likewise, `%d` accepts any integer-like
-// argument, etc.
+// any string-like argument, so `std::string`, `std::wstring`,
+// `absl::string_view`, `const char*`, and `const wchar_t*` are all accepted.
+// Likewise, `%d` accepts any integer-like argument, etc.
template <typename... Args>
using FormatSpec = str_format_internal::FormatSpecTemplate<
@@ -271,8 +295,8 @@
// Example:
//
// // Verified at compile time.
-// absl::ParsedFormat<'s', 'd'> formatString("Welcome to %s, Number %d!");
-// absl::StrFormat(formatString, "TheVillage", 6);
+// absl::ParsedFormat<'s', 'd'> format_string("Welcome to %s, Number %d!");
+// absl::StrFormat(format_string, "TheVillage", 6);
//
// // Verified at runtime.
// auto format_runtime = absl::ParsedFormat<'d'>::New(format_string);
@@ -353,7 +377,7 @@
// std::string orig("For example PI is approximately ");
// std::cout << StrAppendFormat(&orig, "%12.6f", 3.14);
template <typename... Args>
-std::string& StrAppendFormat(std::string* dst,
+std::string& StrAppendFormat(absl::Nonnull<std::string*> dst,
const FormatSpec<Args...>& format,
const Args&... args) {
return str_format_internal::AppendPack(
@@ -365,7 +389,7 @@
//
// Writes to an output stream given a format string and zero or more arguments,
// generally in a manner that is more efficient than streaming the result of
-// `absl:: StrFormat()`. The returned object must be streamed before the full
+// `absl::StrFormat()`. The returned object must be streamed before the full
// expression ends.
//
// Example:
@@ -413,7 +437,7 @@
// Outputs: "The capital of Mongolia is Ulaanbaatar"
//
template <typename... Args>
-int FPrintF(std::FILE* output, const FormatSpec<Args...>& format,
+int FPrintF(absl::Nonnull<std::FILE*> output, const FormatSpec<Args...>& format,
const Args&... args) {
return str_format_internal::FprintF(
output, str_format_internal::UntypedFormatSpecImpl::Extract(format),
@@ -442,8 +466,8 @@
// Post-condition: output == "The capital of Mongolia is Ulaanbaatar"
//
template <typename... Args>
-int SNPrintF(char* output, std::size_t size, const FormatSpec<Args...>& format,
- const Args&... args) {
+int SNPrintF(absl::Nonnull<char*> output, std::size_t size,
+ const FormatSpec<Args...>& format, const Args&... args) {
return str_format_internal::SnprintF(
output, size, str_format_internal::UntypedFormatSpecImpl::Extract(format),
{str_format_internal::FormatArgImpl(args)...});
@@ -476,7 +500,7 @@
template <typename T,
typename = typename std::enable_if<std::is_constructible<
str_format_internal::FormatRawSinkImpl, T*>::value>::type>
- FormatRawSink(T* raw) // NOLINT
+ FormatRawSink(absl::Nonnull<T*> raw) // NOLINT
: sink_(raw) {}
private:
@@ -570,6 +594,41 @@
// StrFormat Extensions
//------------------------------------------------------------------------------
//
+// AbslStringify()
+//
+// A simpler customization API for formatting user-defined types using
+// absl::StrFormat(). The API relies on detecting an overload in the
+// user-defined type's namespace of a free (non-member) `AbslStringify()`
+// function as a friend definition with the following signature:
+//
+// template <typename Sink>
+// void AbslStringify(Sink& sink, const X& value);
+//
+// An `AbslStringify()` overload for a type should only be declared in the same
+// file and namespace as said type.
+//
+// Note that unlike with AbslFormatConvert(), AbslStringify() does not allow
+// customization of allowed conversion characters. AbslStringify() uses `%v` as
+// the underlying conversion specififer. Additionally, AbslStringify() supports
+// use with absl::StrCat while AbslFormatConvert() does not.
+//
+// Example:
+//
+// struct Point {
+// // To add formatting support to `Point`, we simply need to add a free
+// // (non-member) function `AbslStringify()`. This method prints in the
+// // request format using the underlying `%v` specifier. You can add such a
+// // free function using a friend declaration within the body of the class.
+// // The sink parameter is a templated type to avoid requiring dependencies.
+// template <typename Sink>
+// friend void AbslStringify(Sink& sink, const Point& p) {
+// absl::Format(&sink, "(%v, %v)", p.x, p.y);
+// }
+//
+// int x;
+// int y;
+// };
+//
// AbslFormatConvert()
//
// The StrFormat library provides a customization API for formatting
@@ -616,9 +675,9 @@
// AbslFormatConvert(const Point& p, const absl::FormatConversionSpec& spec,
// absl::FormatSink* s) {
// if (spec.conversion_char() == absl::FormatConversionChar::s) {
-// s->Append(absl::StrCat("x=", p.x, " y=", p.y));
+// absl::Format(s, "x=%vy=%v", p.x, p.y);
// } else {
-// s->Append(absl::StrCat(p.x, ",", p.y));
+// absl::Format(s, "%v,%v", p.x, p.y);
// }
// return {true};
// }
@@ -637,7 +696,7 @@
c, s, // text
d, i, o, u, x, X, // int
f, F, e, E, g, G, a, A, // float
- n, p // misc
+ n, p, v // misc
};
// clang-format on
@@ -757,6 +816,7 @@
// misc
n = str_format_internal::FormatConversionCharToConvInt('n'),
p = str_format_internal::FormatConversionCharToConvInt('p'),
+ v = str_format_internal::FormatConversionCharToConvInt('v'),
// Used for width/precision '*' specification.
kStar = static_cast<uint64_t>(
@@ -771,27 +831,42 @@
// FormatSink
//
-// An abstraction to which conversions write their string data.
+// A format sink is a generic abstraction to which conversions may write their
+// formatted string data. `absl::FormatConvert()` uses this sink to write its
+// formatted string.
//
class FormatSink {
public:
- // Appends `count` copies of `ch`.
+ // FormatSink::Append()
+ //
+ // Appends `count` copies of `ch` to the format sink.
void Append(size_t count, char ch) { sink_->Append(count, ch); }
+ // Overload of FormatSink::Append() for appending the characters of a string
+ // view to a format sink.
void Append(string_view v) { sink_->Append(v); }
- // Appends the first `precision` bytes of `v`. If this is less than
- // `width`, spaces will be appended first (if `left` is false), or
+ // FormatSink::PutPaddedString()
+ //
+ // Appends `precision` number of bytes of `v` to the format sink. If this is
+ // less than `width`, spaces will be appended first (if `left` is false), or
// after (if `left` is true) to ensure the total amount appended is
// at least `width`.
bool PutPaddedString(string_view v, int width, int precision, bool left) {
return sink_->PutPaddedString(v, width, precision, left);
}
+ // Support `absl::Format(&sink, format, args...)`.
+ friend void AbslFormatFlush(absl::Nonnull<FormatSink*> sink,
+ absl::string_view v) {
+ sink->Append(v);
+ }
+
private:
friend str_format_internal::FormatSinkImpl;
- explicit FormatSink(str_format_internal::FormatSinkImpl* s) : sink_(s) {}
- str_format_internal::FormatSinkImpl* sink_;
+ explicit FormatSink(absl::Nonnull<str_format_internal::FormatSinkImpl*> s)
+ : sink_(s) {}
+ absl::Nonnull<str_format_internal::FormatSinkImpl*> sink_;
};
// FormatConvertResult
diff --git a/absl/strings/str_format_test.cc b/absl/strings/str_format_test.cc
index c60027a..3c52be1 100644
--- a/absl/strings/str_format_test.cc
+++ b/absl/strings/str_format_test.cc
@@ -14,16 +14,22 @@
#include "absl/strings/str_format.h"
+#include <cerrno>
#include <cstdarg>
#include <cstdint>
#include <cstdio>
+#include <ostream>
+#include <sstream>
#include <string>
+#include <type_traits>
-#include "gmock/gmock.h"
#include "gtest/gtest.h"
+#include "absl/base/config.h"
+#include "absl/base/macros.h"
#include "absl/strings/cord.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
+#include "absl/types/span.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
@@ -42,6 +48,18 @@
EXPECT_TRUE(Format(&sink, pc, 123));
EXPECT_EQ("A format 123", sink);
}
+
+TEST_F(FormatEntryPointTest, FormatWithV) {
+ std::string sink;
+ EXPECT_TRUE(Format(&sink, "A format %v", 123));
+ EXPECT_EQ("A format 123", sink);
+ sink.clear();
+
+ ParsedFormat<'v'> pc("A format %v");
+ EXPECT_TRUE(Format(&sink, pc, 123));
+ EXPECT_EQ("A format 123", sink);
+}
+
TEST_F(FormatEntryPointTest, UntypedFormat) {
constexpr const char* formats[] = {
"",
@@ -84,6 +102,14 @@
EXPECT_EQ("=123=", StrFormat(view, 123));
}
+TEST_F(FormatEntryPointTest, StringFormatV) {
+ std::string hello = "hello";
+ EXPECT_EQ("hello", StrFormat("%v", hello));
+ EXPECT_EQ("123", StrFormat("%v", 123));
+ constexpr absl::string_view view("=%v=", 4);
+ EXPECT_EQ("=123=", StrFormat(view, 123));
+}
+
TEST_F(FormatEntryPointTest, AppendFormat) {
std::string s;
std::string& r = StrAppendFormat(&s, "%d", 123);
@@ -91,6 +117,13 @@
EXPECT_EQ("123", r);
}
+TEST_F(FormatEntryPointTest, AppendFormatWithV) {
+ std::string s;
+ std::string& r = StrAppendFormat(&s, "%v", 123);
+ EXPECT_EQ(&s, &r); // should be same object
+ EXPECT_EQ("123", r);
+}
+
TEST_F(FormatEntryPointTest, AppendFormatFail) {
std::string s = "orig";
@@ -103,15 +136,33 @@
{&arg, 1}));
}
+TEST_F(FormatEntryPointTest, AppendFormatFailWithV) {
+ std::string s = "orig";
+
+ UntypedFormatSpec format(" more %v");
+ FormatArgImpl arg("not an int");
+
+ EXPECT_EQ("orig",
+ str_format_internal::AppendPack(
+ &s, str_format_internal::UntypedFormatSpecImpl::Extract(format),
+ {&arg, 1}));
+}
TEST_F(FormatEntryPointTest, ManyArgs) {
- EXPECT_EQ("24", StrFormat("%24$d", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
- 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24));
- EXPECT_EQ("60", StrFormat("%60$d", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
- 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26,
- 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
- 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52,
- 53, 54, 55, 56, 57, 58, 59, 60));
+ EXPECT_EQ(
+ "60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 "
+ "36 35 34 33 32 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 "
+ "12 11 10 9 8 7 6 5 4 3 2 1",
+ StrFormat("%60$d %59$d %58$d %57$d %56$d %55$d %54$d %53$d %52$d %51$d "
+ "%50$d %49$d %48$d %47$d %46$d %45$d %44$d %43$d %42$d %41$d "
+ "%40$d %39$d %38$d %37$d %36$d %35$d %34$d %33$d %32$d %31$d "
+ "%30$d %29$d %28$d %27$d %26$d %25$d %24$d %23$d %22$d %21$d "
+ "%20$d %19$d %18$d %17$d %16$d %15$d %14$d %13$d %12$d %11$d "
+ "%10$d %9$d %8$d %7$d %6$d %5$d %4$d %3$d %2$d %1$d",
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
+ 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34,
+ 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
+ 51, 52, 53, 54, 55, 56, 57, 58, 59, 60));
}
TEST_F(FormatEntryPointTest, Preparsed) {
@@ -123,6 +174,15 @@
EXPECT_EQ("=123=", StrFormat(ParsedFormat<'d'>(view), 123));
}
+TEST_F(FormatEntryPointTest, PreparsedWithV) {
+ ParsedFormat<'v'> pc("%v");
+ EXPECT_EQ("123", StrFormat(pc, 123));
+ // rvalue ok?
+ EXPECT_EQ("123", StrFormat(ParsedFormat<'v'>("%v"), 123));
+ constexpr absl::string_view view("=%v=", 4);
+ EXPECT_EQ("=123=", StrFormat(ParsedFormat<'v'>(view), 123));
+}
+
TEST_F(FormatEntryPointTest, FormatCountCapture) {
int n = 0;
EXPECT_EQ("", StrFormat("%n", FormatCountCapture(&n)));
@@ -131,6 +191,14 @@
EXPECT_EQ(3, n);
}
+TEST_F(FormatEntryPointTest, FormatCountCaptureWithV) {
+ int n = 0;
+ EXPECT_EQ("", StrFormat("%n", FormatCountCapture(&n)));
+ EXPECT_EQ(0, n);
+ EXPECT_EQ("123", StrFormat("%v%n", 123, FormatCountCapture(&n)));
+ EXPECT_EQ(3, n);
+}
+
TEST_F(FormatEntryPointTest, FormatCountCaptureWrongType) {
// Should reject int*.
int n = 0;
@@ -143,6 +211,18 @@
absl::MakeSpan(args)));
}
+TEST_F(FormatEntryPointTest, FormatCountCaptureWrongTypeWithV) {
+ // Should reject int*.
+ int n = 0;
+ UntypedFormatSpec format("%v%n");
+ int i = 123, *ip = &n;
+ FormatArgImpl args[2] = {FormatArgImpl(i), FormatArgImpl(ip)};
+
+ EXPECT_EQ("", str_format_internal::FormatPack(
+ str_format_internal::UntypedFormatSpecImpl::Extract(format),
+ absl::MakeSpan(args)));
+}
+
TEST_F(FormatEntryPointTest, FormatCountCaptureMultiple) {
int n1 = 0;
int n2 = 0;
@@ -165,6 +245,21 @@
s);
}
+TEST_F(FormatEntryPointTest, FormatCountCaptureExampleWithV) {
+ int n;
+ std::string s;
+ std::string a1 = "(1,1)";
+ std::string a2 = "(1,2)";
+ std::string a3 = "(2,2)";
+ StrAppendFormat(&s, "%v: %n%v\n", a1, FormatCountCapture(&n), a2);
+ StrAppendFormat(&s, "%*s%v\n", n, "", a3);
+ EXPECT_EQ(7, n);
+ EXPECT_EQ(
+ "(1,1): (1,2)\n"
+ " (2,2)\n",
+ s);
+}
+
TEST_F(FormatEntryPointTest, Stream) {
const std::string formats[] = {
"",
@@ -183,7 +278,7 @@
std::ostringstream oss;
oss << StreamFormat(*parsed, 123, 3, 49, "multistreaming!!!", 1.01, 1.01);
int fmt_result = snprintf(&*buf.begin(), buf.size(), fmt.c_str(), //
- 123, 3, 49, "multistreaming!!!", 1.01, 1.01);
+ 123, 3, 49, "multistreaming!!!", 1.01, 1.01);
ASSERT_TRUE(oss) << fmt;
ASSERT_TRUE(fmt_result >= 0 && static_cast<size_t>(fmt_result) < buf.size())
<< fmt_result;
@@ -191,6 +286,36 @@
}
}
+TEST_F(FormatEntryPointTest, StreamWithV) {
+ const std::string formats[] = {
+ "",
+ "a",
+ "%v %u %c %v %f %v",
+ };
+
+ const std::string formats_for_buf[] = {
+ "",
+ "a",
+ "%d %u %c %s %f %g",
+ };
+
+ std::string buf(4096, '\0');
+ for (auto i = 0; i < ABSL_ARRAYSIZE(formats); ++i) {
+ const auto parsed =
+ ParsedFormat<'v', 'u', 'c', 'v', 'f', 'v'>::NewAllowIgnored(formats[i]);
+ std::ostringstream oss;
+ oss << StreamFormat(*parsed, 123, 3, 49,
+ absl::string_view("multistreaming!!!"), 1.01, 1.01);
+ int fmt_result =
+ snprintf(&*buf.begin(), buf.size(), formats_for_buf[i].c_str(), //
+ 123, 3, 49, "multistreaming!!!", 1.01, 1.01);
+ ASSERT_TRUE(oss) << formats[i];
+ ASSERT_TRUE(fmt_result >= 0 && static_cast<size_t>(fmt_result) < buf.size())
+ << fmt_result;
+ EXPECT_EQ(buf.c_str(), oss.str());
+ }
+}
+
TEST_F(FormatEntryPointTest, StreamOk) {
std::ostringstream oss;
oss << StreamFormat("hello %d", 123);
@@ -198,6 +323,13 @@
EXPECT_TRUE(oss.good());
}
+TEST_F(FormatEntryPointTest, StreamOkWithV) {
+ std::ostringstream oss;
+ oss << StreamFormat("hello %v", 123);
+ EXPECT_EQ("hello 123", oss.str());
+ EXPECT_TRUE(oss.good());
+}
+
TEST_F(FormatEntryPointTest, StreamFail) {
std::ostringstream oss;
UntypedFormatSpec format("hello %d");
@@ -208,6 +340,16 @@
EXPECT_TRUE(oss.fail());
}
+TEST_F(FormatEntryPointTest, StreamFailWithV) {
+ std::ostringstream oss;
+ UntypedFormatSpec format("hello %v");
+ FormatArgImpl arg("non-numeric");
+ oss << str_format_internal::Streamable(
+ str_format_internal::UntypedFormatSpecImpl::Extract(format), {&arg, 1});
+ EXPECT_EQ("hello ", oss.str()); // partial write
+ EXPECT_TRUE(oss.fail());
+}
+
std::string WithSnprintf(const char* fmt, ...) {
std::string buf;
buf.resize(128);
@@ -249,6 +391,12 @@
EXPECT_EQ("123", StrFormat("%s", FormatStreamed(StreamFormat("%d", 123))));
}
+TEST_F(FormatEntryPointTest, FormatStreamedWithV) {
+ EXPECT_EQ("123", StrFormat("%v", FormatStreamed(123)));
+ EXPECT_EQ("X", StrFormat("%v", FormatStreamed(streamed_test::X())));
+ EXPECT_EQ("123", StrFormat("%v", FormatStreamed(StreamFormat("%d", 123))));
+}
+
// Helper class that creates a temporary file and exposes a FILE* to it.
// It will close the file on destruction.
class TempFile {
@@ -284,6 +432,14 @@
EXPECT_EQ(tmp.ReadFile(), "STRING: ABC NUMBER: -000000019");
}
+TEST_F(FormatEntryPointTest, FPrintFWithV) {
+ TempFile tmp;
+ int result =
+ FPrintF(tmp.file(), "STRING: %v NUMBER: %010d", std::string("ABC"), -19);
+ EXPECT_EQ(result, 30);
+ EXPECT_EQ(tmp.ReadFile(), "STRING: ABC NUMBER: -000000019");
+}
+
TEST_F(FormatEntryPointTest, FPrintFError) {
errno = 0;
int result = FPrintF(stdin, "ABC");
@@ -318,6 +474,23 @@
EXPECT_EQ(result, 30);
EXPECT_EQ(tmp.ReadFile(), "STRING: ABC NUMBER: -000000019");
}
+
+TEST_F(FormatEntryPointTest, PrintFWithV) {
+ int stdout_tmp = dup(STDOUT_FILENO);
+
+ TempFile tmp;
+ std::fflush(stdout);
+ dup2(fileno(tmp.file()), STDOUT_FILENO);
+
+ int result = PrintF("STRING: %v NUMBER: %010d", std::string("ABC"), -19);
+
+ std::fflush(stdout);
+ dup2(stdout_tmp, STDOUT_FILENO);
+ close(stdout_tmp);
+
+ EXPECT_EQ(result, 30);
+ EXPECT_EQ(tmp.ReadFile(), "STRING: ABC NUMBER: -000000019");
+}
#endif // __GLIBC__
TEST_F(FormatEntryPointTest, SNPrintF) {
@@ -347,9 +520,41 @@
EXPECT_EQ(result, 37);
}
+TEST_F(FormatEntryPointTest, SNPrintFWithV) {
+ char buffer[16];
+ int result =
+ SNPrintF(buffer, sizeof(buffer), "STRING: %v", std::string("ABC"));
+ EXPECT_EQ(result, 11);
+ EXPECT_EQ(std::string(buffer), "STRING: ABC");
+
+ result = SNPrintF(buffer, sizeof(buffer), "NUMBER: %v", 123456);
+ EXPECT_EQ(result, 14);
+ EXPECT_EQ(std::string(buffer), "NUMBER: 123456");
+
+ result = SNPrintF(buffer, sizeof(buffer), "NUMBER: %v", 1234567);
+ EXPECT_EQ(result, 15);
+ EXPECT_EQ(std::string(buffer), "NUMBER: 1234567");
+
+ result = SNPrintF(buffer, sizeof(buffer), "NUMBER: %v", 12345678);
+ EXPECT_EQ(result, 16);
+ EXPECT_EQ(std::string(buffer), "NUMBER: 1234567");
+
+ result = SNPrintF(buffer, sizeof(buffer), "NUMBER: %v", 123456789);
+ EXPECT_EQ(result, 17);
+ EXPECT_EQ(std::string(buffer), "NUMBER: 1234567");
+
+ std::string size = "size";
+
+ result = SNPrintF(nullptr, 0, "Just checking the %v of the output.", size);
+ EXPECT_EQ(result, 37);
+}
+
TEST(StrFormat, BehavesAsDocumented) {
std::string s = absl::StrFormat("%s, %d!", "Hello", 123);
EXPECT_EQ("Hello, 123!", s);
+ std::string hello = "Hello";
+ std::string s2 = absl::StrFormat("%v, %v!", hello, 123);
+ EXPECT_EQ("Hello, 123!", s2);
// The format of a replacement is
// '%'[position][flags][width['.'precision]][length_modifier][format]
EXPECT_EQ(absl::StrFormat("%1$+3.2Lf", 1.1), "+1.10");
@@ -364,22 +569,31 @@
// "s" - string Eg: "C" -> "C", std::string("C++") -> "C++"
// Formats std::string, char*, string_view, and Cord.
EXPECT_EQ(StrFormat("%s", "C"), "C");
+ EXPECT_EQ(StrFormat("%v", std::string("C")), "C");
EXPECT_EQ(StrFormat("%s", std::string("C++")), "C++");
+ EXPECT_EQ(StrFormat("%v", std::string("C++")), "C++");
EXPECT_EQ(StrFormat("%s", string_view("view")), "view");
+ EXPECT_EQ(StrFormat("%v", string_view("view")), "view");
EXPECT_EQ(StrFormat("%s", absl::Cord("cord")), "cord");
+ EXPECT_EQ(StrFormat("%v", absl::Cord("cord")), "cord");
// Integral Conversion
// These format integral types: char, int, long, uint64_t, etc.
EXPECT_EQ(StrFormat("%d", char{10}), "10");
EXPECT_EQ(StrFormat("%d", int{10}), "10");
EXPECT_EQ(StrFormat("%d", long{10}), "10"); // NOLINT
EXPECT_EQ(StrFormat("%d", uint64_t{10}), "10");
+ EXPECT_EQ(StrFormat("%v", int{10}), "10");
+ EXPECT_EQ(StrFormat("%v", long{10}), "10"); // NOLINT
+ EXPECT_EQ(StrFormat("%v", uint64_t{10}), "10");
// d,i - signed decimal Eg: -10 -> "-10"
EXPECT_EQ(StrFormat("%d", -10), "-10");
EXPECT_EQ(StrFormat("%i", -10), "-10");
+ EXPECT_EQ(StrFormat("%v", -10), "-10");
// o - octal Eg: 10 -> "12"
EXPECT_EQ(StrFormat("%o", 10), "12");
// u - unsigned decimal Eg: 10 -> "10"
EXPECT_EQ(StrFormat("%u", 10), "10");
+ EXPECT_EQ(StrFormat("%v", 10), "10");
// x/X - lower,upper case hex Eg: 10 -> "a"/"A"
EXPECT_EQ(StrFormat("%x", 10), "a");
EXPECT_EQ(StrFormat("%X", 10), "A");
@@ -404,6 +618,8 @@
EXPECT_EQ(StrFormat("%g", .01), "0.01");
EXPECT_EQ(StrFormat("%g", 1e10), "1e+10");
EXPECT_EQ(StrFormat("%G", 1e10), "1E+10");
+ EXPECT_EQ(StrFormat("%v", .01), "0.01");
+ EXPECT_EQ(StrFormat("%v", 1e10), "1e+10");
// a/A - lower,upper case hex Eg: -3.0 -> "-0x1.8p+1"/"-0X1.8P+1"
// On Android platform <=21, there is a regression in hexfloat formatting.
@@ -418,6 +634,10 @@
const int& something = *reinterpret_cast<const int*>(ptr_value);
EXPECT_EQ(StrFormat("%p", &something), StrFormat("0x%x", ptr_value));
+ // The output of formatting a null pointer is not documented as being a
+ // specific thing, but the attempt should at least compile.
+ (void)StrFormat("%p", nullptr);
+
// Output widths are supported, with optional flags.
EXPECT_EQ(StrFormat("%3d", 1), " 1");
EXPECT_EQ(StrFormat("%3d", 123456), "123456");
@@ -428,6 +648,8 @@
EXPECT_EQ(StrFormat("%#o", 10), "012");
EXPECT_EQ(StrFormat("%#x", 15), "0xf");
EXPECT_EQ(StrFormat("%04d", 8), "0008");
+ EXPECT_EQ(StrFormat("%#04x", 0), "0000");
+ EXPECT_EQ(StrFormat("%#04x", 1), "0x01");
// Posix positional substitution.
EXPECT_EQ(absl::StrFormat("%2$s, %3$s, %1$s!", "vici", "veni", "vidi"),
"veni, vidi, vici!");
@@ -441,6 +663,11 @@
EXPECT_EQ(StrFormat("%zd", int{1}), "1");
EXPECT_EQ(StrFormat("%td", int{1}), "1");
EXPECT_EQ(StrFormat("%qd", int{1}), "1");
+
+ // Bool is handled correctly depending on whether %v is used
+ EXPECT_EQ(StrFormat("%v", true), "true");
+ EXPECT_EQ(StrFormat("%v", false), "false");
+ EXPECT_EQ(StrFormat("%d", true), "1");
}
using str_format_internal::ExtendedParsedFormat;
@@ -490,6 +717,15 @@
SummarizeParsedFormat(ParsedFormat<'s', '*', 'd'>("%s %.*d")));
}
+TEST_F(ParsedFormatTest, SimpleCheckedWithV) {
+ EXPECT_EQ("[ABC]{v:1$v}[DEF]",
+ SummarizeParsedFormat(ParsedFormat<'v'>("ABC%vDEF")));
+ EXPECT_EQ("{v:1$v}[FFF]{v:2$v}[ZZZ]{f:3$f}",
+ SummarizeParsedFormat(ParsedFormat<'v', 'v', 'f'>("%vFFF%vZZZ%f")));
+ EXPECT_EQ("{v:1$v}[ ]{.*d:3$.2$*d}",
+ SummarizeParsedFormat(ParsedFormat<'v', '*', 'd'>("%v %.*d")));
+}
+
TEST_F(ParsedFormatTest, SimpleUncheckedCorrect) {
auto f = ParsedFormat<'d'>::New("ABC%dDEF");
ASSERT_TRUE(f);
@@ -520,6 +756,23 @@
SummarizeParsedFormat(*dollar));
}
+TEST_F(ParsedFormatTest, SimpleUncheckedCorrectWithV) {
+ auto f = ParsedFormat<'v'>::New("ABC%vDEF");
+ ASSERT_TRUE(f);
+ EXPECT_EQ("[ABC]{v:1$v}[DEF]", SummarizeParsedFormat(*f));
+
+ std::string format = "%vFFF%vZZZ%f";
+ auto f2 = ParsedFormat<'v', 'v', 'f'>::New(format);
+
+ ASSERT_TRUE(f2);
+ EXPECT_EQ("{v:1$v}[FFF]{v:2$v}[ZZZ]{f:3$f}", SummarizeParsedFormat(*f2));
+
+ f2 = ParsedFormat<'v', 'v', 'f'>::New("%v %v %f");
+
+ ASSERT_TRUE(f2);
+ EXPECT_EQ("{v:1$v}[ ]{v:2$v}[ ]{f:3$f}", SummarizeParsedFormat(*f2));
+}
+
TEST_F(ParsedFormatTest, SimpleUncheckedIgnoredArgs) {
EXPECT_FALSE((ParsedFormat<'d', 's'>::New("ABC")));
EXPECT_FALSE((ParsedFormat<'d', 's'>::New("%dABC")));
@@ -535,6 +788,18 @@
EXPECT_EQ("[ABC]{2$s:2$s}", SummarizeParsedFormat(*f));
}
+TEST_F(ParsedFormatTest, SimpleUncheckedIgnoredArgsWithV) {
+ EXPECT_FALSE((ParsedFormat<'v', 'v'>::New("ABC")));
+ EXPECT_FALSE((ParsedFormat<'v', 'v'>::New("%vABC")));
+ EXPECT_FALSE((ParsedFormat<'v', 's'>::New("ABC%2$s")));
+ auto f = ParsedFormat<'v', 'v'>::NewAllowIgnored("ABC");
+ ASSERT_TRUE(f);
+ EXPECT_EQ("[ABC]", SummarizeParsedFormat(*f));
+ f = ParsedFormat<'v', 'v'>::NewAllowIgnored("%vABC");
+ ASSERT_TRUE(f);
+ EXPECT_EQ("{v:1$v}[ABC]", SummarizeParsedFormat(*f));
+}
+
TEST_F(ParsedFormatTest, SimpleUncheckedUnsupported) {
EXPECT_FALSE(ParsedFormat<'d'>::New("%1$d %1$x"));
EXPECT_FALSE(ParsedFormat<'x'>::New("%1$d %1$x"));
@@ -549,6 +814,15 @@
EXPECT_FALSE((ParsedFormat<'s', 'd', 'g'>::New(format)));
}
+TEST_F(ParsedFormatTest, SimpleUncheckedIncorrectWithV) {
+ EXPECT_FALSE(ParsedFormat<'v'>::New(""));
+
+ EXPECT_FALSE(ParsedFormat<'v'>::New("ABC%vDEF%v"));
+
+ std::string format = "%vFFF%vZZZ%f";
+ EXPECT_FALSE((ParsedFormat<'v', 'v', 'g'>::New(format)));
+}
+
#if defined(__cpp_nontype_template_parameter_auto)
template <auto T>
@@ -595,6 +869,23 @@
's'>::New("%s%s");
ASSERT_TRUE(v4);
}
+
+TEST_F(ParsedFormatTest, ExtendedTypingWithV) {
+ EXPECT_FALSE(ParsedFormat<FormatConversionCharSet::v>::New(""));
+ ASSERT_TRUE(ParsedFormat<absl::FormatConversionCharSet::v>::New("%v"));
+ auto v1 = ParsedFormat<'v', absl::FormatConversionCharSet::v>::New("%v%v");
+ ASSERT_TRUE(v1);
+ auto v2 = ParsedFormat<absl::FormatConversionCharSet::v, 'v'>::New("%v%v");
+ ASSERT_TRUE(v2);
+ auto v3 = ParsedFormat<absl::FormatConversionCharSet::v |
+ absl::FormatConversionCharSet::v,
+ 'v'>::New("%v%v");
+ ASSERT_TRUE(v3);
+ auto v4 = ParsedFormat<absl::FormatConversionCharSet::v |
+ absl::FormatConversionCharSet::v,
+ 'v'>::New("%v%v");
+ ASSERT_TRUE(v4);
+}
#endif
TEST_F(ParsedFormatTest, UncheckedCorrect) {
@@ -638,6 +929,28 @@
SummarizeParsedFormat(*dollar));
}
+TEST_F(ParsedFormatTest, UncheckedCorrectWithV) {
+ auto f =
+ ExtendedParsedFormat<absl::FormatConversionCharSet::v>::New("ABC%vDEF");
+ ASSERT_TRUE(f);
+ EXPECT_EQ("[ABC]{v:1$v}[DEF]", SummarizeParsedFormat(*f));
+
+ std::string format = "%vFFF%vZZZ%f";
+ auto f2 = ExtendedParsedFormat<
+ absl::FormatConversionCharSet::v, absl::FormatConversionCharSet::v,
+ absl::FormatConversionCharSet::kFloating>::New(format);
+
+ ASSERT_TRUE(f2);
+ EXPECT_EQ("{v:1$v}[FFF]{v:2$v}[ZZZ]{f:3$f}", SummarizeParsedFormat(*f2));
+
+ f2 = ExtendedParsedFormat<
+ absl::FormatConversionCharSet::v, absl::FormatConversionCharSet::v,
+ absl::FormatConversionCharSet::kFloating>::New("%v %v %f");
+
+ ASSERT_TRUE(f2);
+ EXPECT_EQ("{v:1$v}[ ]{v:2$v}[ ]{f:3$f}", SummarizeParsedFormat(*f2));
+}
+
TEST_F(ParsedFormatTest, UncheckedIgnoredArgs) {
EXPECT_FALSE(
(ExtendedParsedFormat<absl::FormatConversionCharSet::d,
@@ -665,6 +978,28 @@
EXPECT_EQ("[ABC]{2$s:2$s}", SummarizeParsedFormat(*f));
}
+TEST_F(ParsedFormatTest, UncheckedIgnoredArgsWithV) {
+ EXPECT_FALSE(
+ (ExtendedParsedFormat<absl::FormatConversionCharSet::v,
+ absl::FormatConversionCharSet::v>::New("ABC")));
+ EXPECT_FALSE(
+ (ExtendedParsedFormat<absl::FormatConversionCharSet::v,
+ absl::FormatConversionCharSet::v>::New("%vABC")));
+ EXPECT_FALSE((ExtendedParsedFormat<absl::FormatConversionCharSet::v,
+ absl::FormatConversionCharSet::s>::
+ New("ABC%2$s")));
+ auto f = ExtendedParsedFormat<
+ absl::FormatConversionCharSet::v,
+ absl::FormatConversionCharSet::v>::NewAllowIgnored("ABC");
+ ASSERT_TRUE(f);
+ EXPECT_EQ("[ABC]", SummarizeParsedFormat(*f));
+ f = ExtendedParsedFormat<
+ absl::FormatConversionCharSet::v,
+ absl::FormatConversionCharSet::v>::NewAllowIgnored("%vABC");
+ ASSERT_TRUE(f);
+ EXPECT_EQ("{v:1$v}[ABC]", SummarizeParsedFormat(*f));
+}
+
TEST_F(ParsedFormatTest, UncheckedMultipleTypes) {
auto dx =
ExtendedParsedFormat<absl::FormatConversionCharSet::d |
@@ -691,12 +1026,35 @@
absl::FormatConversionCharSet::g>::New(format)));
}
+TEST_F(ParsedFormatTest, UncheckedIncorrectWithV) {
+ EXPECT_FALSE(ExtendedParsedFormat<absl::FormatConversionCharSet::v>::New(""));
+
+ EXPECT_FALSE(ExtendedParsedFormat<absl::FormatConversionCharSet::v>::New(
+ "ABC%vDEF%v"));
+
+ std::string format = "%vFFF%vZZZ%f";
+ EXPECT_FALSE(
+ (ExtendedParsedFormat<absl::FormatConversionCharSet::v,
+ absl::FormatConversionCharSet::g>::New(format)));
+}
+
TEST_F(ParsedFormatTest, RegressionMixPositional) {
EXPECT_FALSE(
(ExtendedParsedFormat<absl::FormatConversionCharSet::d,
absl::FormatConversionCharSet::o>::New("%1$d %o")));
}
+TEST_F(ParsedFormatTest, DisallowModifiersWithV) {
+ auto f = ParsedFormat<'v'>::New("ABC%80vDEF");
+ EXPECT_EQ(f, nullptr);
+
+ f = ParsedFormat<'v'>::New("ABC%0vDEF");
+ EXPECT_EQ(f, nullptr);
+
+ f = ParsedFormat<'v'>::New("ABC%.1vDEF");
+ EXPECT_EQ(f, nullptr);
+}
+
using FormatWrapperTest = ::testing::Test;
// Plain wrapper for StrFormat.
@@ -710,20 +1068,33 @@
EXPECT_EQ(WrappedFormat("%s there", "hello"), "hello there");
}
+TEST_F(FormatWrapperTest, ConstexprStringFormatWithV) {
+ std::string hello = "hello";
+ EXPECT_EQ(WrappedFormat("%v there", hello), "hello there");
+}
+
TEST_F(FormatWrapperTest, ParsedFormat) {
ParsedFormat<'s'> format("%s there");
EXPECT_EQ(WrappedFormat(format, "hello"), "hello there");
}
+TEST_F(FormatWrapperTest, ParsedFormatWithV) {
+ std::string hello = "hello";
+ ParsedFormat<'v'> format("%v there");
+ EXPECT_EQ(WrappedFormat(format, hello), "hello there");
+}
+
} // namespace
ABSL_NAMESPACE_END
} // namespace absl
+namespace {
using FormatExtensionTest = ::testing::Test;
struct Point {
friend absl::FormatConvertResult<absl::FormatConversionCharSet::kString |
- absl::FormatConversionCharSet::kIntegral>
+ absl::FormatConversionCharSet::kIntegral |
+ absl::FormatConversionCharSet::v>
AbslFormatConvert(const Point& p, const absl::FormatConversionSpec& spec,
absl::FormatSink* s) {
if (spec.conversion_char() == absl::FormatConversionChar::s) {
@@ -742,6 +1113,7 @@
Point p;
EXPECT_EQ(absl::StrFormat("a %s z", p), "a x=10 y=20 z");
EXPECT_EQ(absl::StrFormat("a %d z", p), "a 10,20 z");
+ EXPECT_EQ(absl::StrFormat("a %v z", p), "a 10,20 z");
// Typed formatting will fail to compile an invalid format.
// StrFormat("%f", p); // Does not compile.
@@ -751,6 +1123,84 @@
EXPECT_FALSE(absl::FormatUntyped(&actual, f1, {absl::FormatArg(p)}));
}
+struct PointStringify {
+ template <typename FormatSink>
+ friend void AbslStringify(FormatSink& sink, const PointStringify& p) {
+ sink.Append(absl::StrCat("(", p.x, ", ", p.y, ")"));
+ }
+
+ double x = 10.0;
+ double y = 20.0;
+};
+
+TEST_F(FormatExtensionTest, AbslStringifyExample) {
+ PointStringify p;
+ EXPECT_EQ(absl::StrFormat("a %v z", p), "a (10, 20) z");
+}
+
+struct PointStringifyUsingFormat {
+ template <typename FormatSink>
+ friend void AbslStringify(FormatSink& sink,
+ const PointStringifyUsingFormat& p) {
+ absl::Format(&sink, "(%g, %g)", p.x, p.y);
+ }
+
+ double x = 10.0;
+ double y = 20.0;
+};
+
+TEST_F(FormatExtensionTest, AbslStringifyExampleUsingFormat) {
+ PointStringifyUsingFormat p;
+ EXPECT_EQ(absl::StrFormat("a %v z", p), "a (10, 20) z");
+}
+
+enum class EnumClassWithStringify { Many = 0, Choices = 1 };
+
+template <typename Sink>
+void AbslStringify(Sink& sink, EnumClassWithStringify e) {
+ absl::Format(&sink, "%s",
+ e == EnumClassWithStringify::Many ? "Many" : "Choices");
+}
+
+enum EnumWithStringify { Many, Choices };
+
+template <typename Sink>
+void AbslStringify(Sink& sink, EnumWithStringify e) {
+ absl::Format(&sink, "%s", e == EnumWithStringify::Many ? "Many" : "Choices");
+}
+
+TEST_F(FormatExtensionTest, AbslStringifyWithEnumWithV) {
+ const auto e_class = EnumClassWithStringify::Choices;
+ EXPECT_EQ(absl::StrFormat("My choice is %v", e_class),
+ "My choice is Choices");
+
+ const auto e = EnumWithStringify::Choices;
+ EXPECT_EQ(absl::StrFormat("My choice is %v", e), "My choice is Choices");
+}
+
+TEST_F(FormatExtensionTest, AbslStringifyEnumWithD) {
+ const auto e_class = EnumClassWithStringify::Many;
+ EXPECT_EQ(absl::StrFormat("My choice is %d", e_class), "My choice is 0");
+
+ const auto e = EnumWithStringify::Choices;
+ EXPECT_EQ(absl::StrFormat("My choice is %d", e), "My choice is 1");
+}
+
+enum class EnumWithLargerValue { x = 32 };
+
+template <typename Sink>
+void AbslStringify(Sink& sink, EnumWithLargerValue e) {
+ absl::Format(&sink, "%s", "Many");
+}
+
+TEST_F(FormatExtensionTest, AbslStringifyEnumOtherSpecifiers) {
+ const auto e = EnumWithLargerValue::x;
+ EXPECT_EQ(absl::StrFormat("My choice is %g", e), "My choice is 32");
+ EXPECT_EQ(absl::StrFormat("My choice is %x", e), "My choice is 20");
+}
+
+} // namespace
+
// Some codegen thunks that we can use to easily dump the generated assembly for
// different StrFormat calls.
diff --git a/absl/strings/str_join.h b/absl/strings/str_join.h
index 3353453..6a92c0f 100644
--- a/absl/strings/str_join.h
+++ b/absl/strings/str_join.h
@@ -72,21 +72,15 @@
// functions. You may provide your own Formatter to enable `absl::StrJoin()` to
// work with arbitrary types.
//
-// The following is an example of a custom Formatter that simply uses
-// `std::to_string()` to format an integer as a std::string.
+// The following is an example of a custom Formatter that uses
+// `absl::FormatDuration` to join a list of `absl::Duration`s.
//
-// struct MyFormatter {
-// void operator()(std::string* out, int i) const {
-// out->append(std::to_string(i));
-// }
-// };
-//
-// You would use the above formatter by passing an instance of it as the final
-// argument to `absl::StrJoin()`:
-//
-// std::vector<int> v = {1, 2, 3, 4};
-// std::string s = absl::StrJoin(v, "-", MyFormatter());
-// EXPECT_EQ("1-2-3-4", s);
+// std::vector<absl::Duration> v = {absl::Seconds(1), absl::Milliseconds(10)};
+// std::string s =
+// absl::StrJoin(v, ", ", [](std::string* out, absl::Duration dur) {
+// absl::StrAppend(out, absl::FormatDuration(dur));
+// });
+// EXPECT_EQ(s, "1s, 10ms");
//
// The following standard formatters are provided within this file:
//
@@ -170,21 +164,21 @@
// // of `absl::string_view` or even `const char*`.
// std::vector<std::string> v = {"foo", "bar", "baz"};
// std::string s = absl::StrJoin(v, "-");
-// EXPECT_EQ("foo-bar-baz", s);
+// EXPECT_EQ(s, "foo-bar-baz");
//
// Example 2:
// // Joins the values in the given `std::initializer_list<>` specified using
// // brace initialization. This pattern also works with an initializer_list
// // of ints or `absl::string_view` -- any `AlphaNum`-compatible type.
// std::string s = absl::StrJoin({"foo", "bar", "baz"}, "-");
-// EXPECT_EQ("foo-bar-baz", s);
+// EXPECT_EQs, "foo-bar-baz");
//
// Example 3:
// // Joins a collection of ints. This pattern also works with floats,
// // doubles, int64s -- any `StrCat()`-compatible type.
// std::vector<int> v = {1, 2, 3, -4};
// std::string s = absl::StrJoin(v, "-");
-// EXPECT_EQ("1-2-3--4", s);
+// EXPECT_EQ(s, "1-2-3--4");
//
// Example 4:
// // Joins a collection of pointer-to-int. By default, pointers are
@@ -195,7 +189,7 @@
// int x = 1, y = 2, z = 3;
// std::vector<int*> v = {&x, &y, &z};
// std::string s = absl::StrJoin(v, "-");
-// EXPECT_EQ("1-2-3", s);
+// EXPECT_EQ(s, "1-2-3");
//
// Example 5:
// // Dereferencing of `std::unique_ptr<>` is also supported:
@@ -204,42 +198,42 @@
// v.emplace_back(new int(2));
// v.emplace_back(new int(3));
// std::string s = absl::StrJoin(v, "-");
-// EXPECT_EQ("1-2-3", s);
+// EXPECT_EQ(s, "1-2-3");
//
// Example 6:
// // Joins a `std::map`, with each key-value pair separated by an equals
// // sign. This pattern would also work with, say, a
// // `std::vector<std::pair<>>`.
// std::map<std::string, int> m = {
-// std::make_pair("a", 1),
-// std::make_pair("b", 2),
-// std::make_pair("c", 3)};
+// {"a", 1},
+// {"b", 2},
+// {"c", 3}};
// std::string s = absl::StrJoin(m, ",", absl::PairFormatter("="));
-// EXPECT_EQ("a=1,b=2,c=3", s);
+// EXPECT_EQ(s, "a=1,b=2,c=3");
//
// Example 7:
// // These examples show how `absl::StrJoin()` handles a few common edge
// // cases:
// std::vector<std::string> v_empty;
-// EXPECT_EQ("", absl::StrJoin(v_empty, "-"));
+// EXPECT_EQ(absl::StrJoin(v_empty, "-"), "");
//
// std::vector<std::string> v_one_item = {"foo"};
-// EXPECT_EQ("foo", absl::StrJoin(v_one_item, "-"));
+// EXPECT_EQ(absl::StrJoin(v_one_item, "-"), "foo");
//
// std::vector<std::string> v_empty_string = {""};
-// EXPECT_EQ("", absl::StrJoin(v_empty_string, "-"));
+// EXPECT_EQ(absl::StrJoin(v_empty_string, "-"), "");
//
// std::vector<std::string> v_one_item_empty_string = {"a", ""};
-// EXPECT_EQ("a-", absl::StrJoin(v_one_item_empty_string, "-"));
+// EXPECT_EQ(absl::StrJoin(v_one_item_empty_string, "-"), "a-");
//
// std::vector<std::string> v_two_empty_string = {"", ""};
-// EXPECT_EQ("-", absl::StrJoin(v_two_empty_string, "-"));
+// EXPECT_EQ(absl::StrJoin(v_two_empty_string, "-"), "-");
//
// Example 8:
// // Joins a `std::tuple<T...>` of heterogeneous types, converting each to
// // a std::string using the `absl::AlphaNum` class.
// std::string s = absl::StrJoin(std::make_tuple(123, "abc", 0.456), "-");
-// EXPECT_EQ("123-abc-0.456", s);
+// EXPECT_EQ(s, "123-abc-0.456");
template <typename Iterator, typename Formatter>
std::string StrJoin(Iterator start, Iterator end, absl::string_view sep,
diff --git a/absl/strings/str_join_test.cc b/absl/strings/str_join_test.cc
index 2be6256..449f95b 100644
--- a/absl/strings/str_join_test.cc
+++ b/absl/strings/str_join_test.cc
@@ -21,11 +21,13 @@
#include <cstdio>
#include <functional>
#include <initializer_list>
+#include <iterator>
#include <map>
#include <memory>
#include <ostream>
+#include <string>
#include <tuple>
-#include <type_traits>
+#include <utility>
#include <vector>
#include "gtest/gtest.h"
@@ -33,6 +35,7 @@
#include "absl/memory/memory.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_split.h"
+#include "absl/strings/string_view.h"
namespace {
@@ -471,4 +474,136 @@
"-", absl::DereferenceFormatter(TestFormatter())));
}
+// A minimal value type for `StrJoin` inputs.
+// Used to ensure we do not excessively require more a specific type, such as a
+// `string_view`.
+//
+// Anything that can be `data()` and `size()` is OK.
+class TestValue {
+ public:
+ TestValue(const char* data, size_t size) : data_(data), size_(size) {}
+ const char* data() const { return data_; }
+ size_t size() const { return size_; }
+
+ private:
+ const char* data_;
+ size_t size_;
+};
+
+// A minimal C++20 forward iterator, used to test that we do not impose
+// excessive requirements on StrJoin inputs.
+//
+// The 2 main differences between pre-C++20 LegacyForwardIterator and the
+// C++20 ForwardIterator are:
+// 1. `operator->` is not required in C++20.
+// 2. `operator*` result does not need to be an lvalue (a reference).
+//
+// The `operator->` requirement was removed on page 17 in:
+// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1037r0.pdf
+//
+// See the `[iterator.requirements]` section of the C++ standard.
+//
+// The value type is a template parameter so that we can test the behaviour
+// of `StrJoin` specializations, e.g. the NoFormatter specialization for
+// `string_view`.
+template <typename ValueT>
+class TestIterator {
+ public:
+ using iterator_category = std::forward_iterator_tag;
+ using value_type = ValueT;
+ using pointer = void;
+ using reference = const value_type&;
+ using difference_type = int;
+
+ // `data` must outlive the result.
+ static TestIterator begin(const std::vector<absl::string_view>& data) {
+ return TestIterator(&data, 0);
+ }
+
+ static TestIterator end(const std::vector<absl::string_view>& data) {
+ return TestIterator(nullptr, data.size());
+ }
+
+ bool operator==(const TestIterator& other) const {
+ return pos_ == other.pos_;
+ }
+ bool operator!=(const TestIterator& other) const {
+ return pos_ != other.pos_;
+ }
+
+ // This deliberately returns a `prvalue`.
+ // The requirement to return a reference was removed in C++20.
+ value_type operator*() const {
+ return ValueT((*data_)[pos_].data(), (*data_)[pos_].size());
+ }
+
+ // `operator->()` is deliberately omitted.
+ // The requirement to provide it was removed in C++20.
+
+ TestIterator& operator++() {
+ ++pos_;
+ return *this;
+ }
+
+ TestIterator operator++(int) {
+ TestIterator result = *this;
+ ++(*this);
+ return result;
+ }
+
+ TestIterator& operator--() {
+ --pos_;
+ return *this;
+ }
+
+ TestIterator operator--(int) {
+ TestIterator result = *this;
+ --(*this);
+ return result;
+ }
+
+ private:
+ TestIterator(const std::vector<absl::string_view>* data, size_t pos)
+ : data_(data), pos_(pos) {}
+
+ const std::vector<absl::string_view>* data_;
+ size_t pos_;
+};
+
+template <typename ValueT>
+class TestIteratorRange {
+ public:
+ // `data` must be non-null and must outlive the result.
+ explicit TestIteratorRange(const std::vector<absl::string_view>& data)
+ : begin_(TestIterator<ValueT>::begin(data)),
+ end_(TestIterator<ValueT>::end(data)) {}
+
+ const TestIterator<ValueT>& begin() const { return begin_; }
+ const TestIterator<ValueT>& end() const { return end_; }
+
+ private:
+ TestIterator<ValueT> begin_;
+ TestIterator<ValueT> end_;
+};
+
+TEST(StrJoin, TestIteratorRequirementsNoFormatter) {
+ const std::vector<absl::string_view> a = {"a", "b", "c"};
+
+ // When the value type is string-like (`std::string` or `string_view`),
+ // the NoFormatter template specialization is used internally.
+ EXPECT_EQ("a-b-c",
+ absl::StrJoin(TestIteratorRange<absl::string_view>(a), "-"));
+}
+
+TEST(StrJoin, TestIteratorRequirementsCustomFormatter) {
+ const std::vector<absl::string_view> a = {"a", "b", "c"};
+ EXPECT_EQ("a-b-c",
+ absl::StrJoin(TestIteratorRange<TestValue>(a), "-",
+ [](std::string* out, const TestValue& value) {
+ absl::StrAppend(
+ out,
+ absl::string_view(value.data(), value.size()));
+ }));
+}
+
} // namespace
diff --git a/absl/strings/str_replace.cc b/absl/strings/str_replace.cc
index 2bd5fa9..a7ab52f 100644
--- a/absl/strings/str_replace.cc
+++ b/absl/strings/str_replace.cc
@@ -14,7 +14,16 @@
#include "absl/strings/str_replace.h"
+#include <cstddef>
+#include <initializer_list>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "absl/base/config.h"
+#include "absl/base/nullability.h"
#include "absl/strings/str_cat.h"
+#include "absl/strings/string_view.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
@@ -28,8 +37,8 @@
// occurred.
int ApplySubstitutions(
absl::string_view s,
- std::vector<strings_internal::ViableSubstitution>* subs_ptr,
- std::string* result_ptr) {
+ absl::Nonnull<std::vector<strings_internal::ViableSubstitution>*> subs_ptr,
+ absl::Nonnull<std::string*> result_ptr) {
auto& subs = *subs_ptr;
int substitutions = 0;
size_t pos = 0;
@@ -74,7 +83,7 @@
}
int StrReplaceAll(strings_internal::FixedMapping replacements,
- std::string* target) {
+ absl::Nonnull<std::string*> target) {
return StrReplaceAll<strings_internal::FixedMapping>(replacements, target);
}
diff --git a/absl/strings/str_replace.h b/absl/strings/str_replace.h
index 273c707..e77ced3 100644
--- a/absl/strings/str_replace.h
+++ b/absl/strings/str_replace.h
@@ -43,6 +43,7 @@
#include <vector>
#include "absl/base/attributes.h"
+#include "absl/base/nullability.h"
#include "absl/strings/string_view.h"
namespace absl {
@@ -113,7 +114,7 @@
int StrReplaceAll(
std::initializer_list<std::pair<absl::string_view, absl::string_view>>
replacements,
- std::string* target);
+ absl::Nonnull<std::string*> target);
// Overload of `StrReplaceAll()` to replace patterns within a given output
// string *in place* with replacements provided within a container of key/value
@@ -128,7 +129,8 @@
// EXPECT_EQ(count, 2);
// EXPECT_EQ("if (ptr < &foo)", s);
template <typename StrToStrMapping>
-int StrReplaceAll(const StrToStrMapping& replacements, std::string* target);
+int StrReplaceAll(const StrToStrMapping& replacements,
+ absl::Nonnull<std::string*> target);
// Implementation details only, past this point.
namespace strings_internal {
@@ -185,8 +187,8 @@
}
int ApplySubstitutions(absl::string_view s,
- std::vector<ViableSubstitution>* subs_ptr,
- std::string* result_ptr);
+ absl::Nonnull<std::vector<ViableSubstitution>*> subs_ptr,
+ absl::Nonnull<std::string*> result_ptr);
} // namespace strings_internal
@@ -201,7 +203,8 @@
}
template <typename StrToStrMapping>
-int StrReplaceAll(const StrToStrMapping& replacements, std::string* target) {
+int StrReplaceAll(const StrToStrMapping& replacements,
+ absl::Nonnull<std::string*> target) {
auto subs = strings_internal::FindSubstitutions(*target, replacements);
if (subs.empty()) return 0;
diff --git a/absl/strings/str_replace_test.cc b/absl/strings/str_replace_test.cc
index 9d8c7f7..04b23af 100644
--- a/absl/strings/str_replace_test.cc
+++ b/absl/strings/str_replace_test.cc
@@ -16,11 +16,15 @@
#include <list>
#include <map>
+#include <string>
#include <tuple>
+#include <utility>
+#include <vector>
#include "gtest/gtest.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_split.h"
+#include "absl/strings/string_view.h"
TEST(StrReplaceAll, OneReplacement) {
std::string s;
@@ -175,7 +179,7 @@
}
struct Cont {
- Cont() {}
+ Cont() = default;
explicit Cont(absl::string_view src) : data(src) {}
absl::string_view data;
diff --git a/absl/strings/str_split.cc b/absl/strings/str_split.cc
index e08c26b..abe486b 100644
--- a/absl/strings/str_split.cc
+++ b/absl/strings/str_split.cc
@@ -15,16 +15,13 @@
#include "absl/strings/str_split.h"
#include <algorithm>
-#include <cassert>
-#include <cstdint>
+#include <cstddef>
#include <cstdlib>
#include <cstring>
-#include <iterator>
-#include <limits>
-#include <memory>
+#include "absl/base/config.h"
#include "absl/base/internal/raw_logging.h"
-#include "absl/strings/ascii.h"
+#include "absl/strings/string_view.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
@@ -60,19 +57,23 @@
// Finds using absl::string_view::find(), therefore the length of the found
// delimiter is delimiter.length().
struct LiteralPolicy {
- size_t Find(absl::string_view text, absl::string_view delimiter, size_t pos) {
+ static size_t Find(absl::string_view text, absl::string_view delimiter,
+ size_t pos) {
return text.find(delimiter, pos);
}
- size_t Length(absl::string_view delimiter) { return delimiter.length(); }
+ static size_t Length(absl::string_view delimiter) {
+ return delimiter.length();
+ }
};
// Finds using absl::string_view::find_first_of(), therefore the length of the
// found delimiter is 1.
struct AnyOfPolicy {
- size_t Find(absl::string_view text, absl::string_view delimiter, size_t pos) {
+ static size_t Find(absl::string_view text, absl::string_view delimiter,
+ size_t pos) {
return text.find_first_of(delimiter, pos);
}
- size_t Length(absl::string_view /* delimiter */) { return 1; }
+ static size_t Length(absl::string_view /* delimiter */) { return 1; }
};
} // namespace
@@ -95,6 +96,11 @@
return GenericFind(text, delimiter_, pos, LiteralPolicy());
}
+absl::string_view ByAsciiWhitespace::Find(absl::string_view text,
+ size_t pos) const {
+ return GenericFind(text, " \t\v\f\r\n", pos, AnyOfPolicy());
+}
+
//
// ByChar
//
@@ -123,8 +129,7 @@
ABSL_RAW_CHECK(length > 0, "");
}
-absl::string_view ByLength::Find(absl::string_view text,
- size_t pos) const {
+absl::string_view ByLength::Find(absl::string_view text, size_t pos) const {
pos = std::min(pos, text.size()); // truncate `pos`
absl::string_view substr = text.substr(pos);
// If the string is shorter than the chunk size we say we
diff --git a/absl/strings/str_split.h b/absl/strings/str_split.h
index bfbca42..49bff7b 100644
--- a/absl/strings/str_split.h
+++ b/absl/strings/str_split.h
@@ -130,6 +130,24 @@
const std::string delimiter_;
};
+// ByAsciiWhitespace
+//
+// A sub-string delimiter that splits by ASCII whitespace
+// (space, tab, vertical tab, formfeed, linefeed, or carriage return).
+// Note: you probably want to use absl::SkipEmpty() as well!
+//
+// This class is equivalent to ByAnyChar with ASCII whitespace chars.
+//
+// Example:
+//
+// std::vector<std::string> v = absl::StrSplit(
+// "a b\tc\n d \n", absl::ByAsciiWhitespace(), absl::SkipEmpty());
+// // v[0] == "a", v[1] == "b", v[2] == "c", v[3] == "d"
+class ByAsciiWhitespace {
+ public:
+ absl::string_view Find(absl::string_view text, size_t pos) const;
+};
+
// ByChar
//
// A single character delimiter. `ByChar` is functionally equivalent to a
@@ -438,7 +456,7 @@
// // Stores results in a std::set<std::string>, which also performs
// // de-duplication and orders the elements in ascending order.
// std::set<std::string> a = absl::StrSplit("b,a,c,a,b", ',');
-// // v[0] == "a", v[1] == "b", v[2] = "c"
+// // a[0] == "a", a[1] == "b", a[2] == "c"
//
// // `StrSplit()` can be used within a range-based for loop, in which case
// // each element will be of type `absl::string_view`.
@@ -461,8 +479,7 @@
// first two split strings become the `std::pair` `.first` and `.second`
// members, respectively. The remaining split substrings are discarded. If there
// are less than two split substrings, the empty string is used for the
-// corresponding
-// `std::pair` member.
+// corresponding `std::pair` member.
//
// Example:
//
diff --git a/absl/strings/str_split_benchmark.cc b/absl/strings/str_split_benchmark.cc
index f38dfcf..003a66b 100644
--- a/absl/strings/str_split_benchmark.cc
+++ b/absl/strings/str_split_benchmark.cc
@@ -14,6 +14,7 @@
#include "absl/strings/str_split.h"
+#include <cstddef>
#include <iterator>
#include <string>
#include <unordered_map>
diff --git a/absl/strings/str_split_test.cc b/absl/strings/str_split_test.cc
index f472f9e..df6c460 100644
--- a/absl/strings/str_split_test.cc
+++ b/absl/strings/str_split_test.cc
@@ -14,30 +14,33 @@
#include "absl/strings/str_split.h"
+#include <cstddef>
+#include <cstdint>
#include <deque>
#include <initializer_list>
#include <list>
#include <map>
#include <memory>
+#include <set>
#include <string>
-#include <type_traits>
#include <unordered_map>
#include <unordered_set>
+#include <utility>
#include <vector>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
-#include "absl/base/dynamic_annotations.h"
#include "absl/base/macros.h"
#include "absl/container/btree_map.h"
#include "absl/container/btree_set.h"
#include "absl/container/flat_hash_map.h"
#include "absl/container/node_hash_map.h"
-#include "absl/strings/numbers.h"
+#include "absl/strings/string_view.h"
namespace {
using ::testing::ElementsAre;
+using ::testing::IsEmpty;
using ::testing::Pair;
using ::testing::UnorderedElementsAre;
@@ -369,7 +372,7 @@
TEST(Splitter, RangeIterators) {
auto splitter = absl::StrSplit("a,b,c", ',');
std::vector<absl::string_view> output;
- for (const absl::string_view& p : splitter) {
+ for (absl::string_view p : splitter) {
output.push_back(p);
}
EXPECT_THAT(output, ElementsAre("a", "b", "c"));
@@ -922,6 +925,45 @@
}
//
+// Tests for ByAsciiWhitespace
+//
+TEST(Split, ByAsciiWhitespace) {
+ using absl::ByAsciiWhitespace;
+ using absl::SkipEmpty;
+ std::vector<absl::string_view> results;
+
+ results = absl::StrSplit("aaaa\n", ByAsciiWhitespace());
+ EXPECT_THAT(results, ElementsAre("aaaa", ""));
+
+ results = absl::StrSplit("aaaa\n", ByAsciiWhitespace(), SkipEmpty());
+ EXPECT_THAT(results, ElementsAre("aaaa"));
+
+ results = absl::StrSplit(" ", ByAsciiWhitespace());
+ EXPECT_THAT(results, ElementsAre("", ""));
+
+ results = absl::StrSplit(" ", ByAsciiWhitespace(), SkipEmpty());
+ EXPECT_THAT(results, IsEmpty());
+
+ results = absl::StrSplit("a", ByAsciiWhitespace());
+ EXPECT_THAT(results, ElementsAre("a"));
+
+ results = absl::StrSplit("", ByAsciiWhitespace());
+ EXPECT_THAT(results, ElementsAre(""));
+
+ results = absl::StrSplit("", ByAsciiWhitespace(), SkipEmpty());
+ EXPECT_THAT(results, IsEmpty());
+
+ results = absl::StrSplit("a b\tc\n d\n", ByAsciiWhitespace());
+ EXPECT_THAT(results, ElementsAre("a", "b", "c", "", "", "d", ""));
+
+ results = absl::StrSplit("a b\tc\n d \n", ByAsciiWhitespace(), SkipEmpty());
+ EXPECT_THAT(results, ElementsAre("a", "b", "c", "d"));
+
+ results = absl::StrSplit("a\t\n\v\f\r b", ByAsciiWhitespace(), SkipEmpty());
+ EXPECT_THAT(results, ElementsAre("a", "b"));
+}
+
+//
// Tests for ByLength
//
@@ -943,8 +985,14 @@
}
TEST(Split, WorksWithLargeStrings) {
+#if defined(ABSL_HAVE_ADDRESS_SANITIZER) || \
+ defined(ABSL_HAVE_MEMORY_SANITIZER) || defined(ABSL_HAVE_THREAD_SANITIZER)
+ constexpr size_t kSize = (uint32_t{1} << 26) + 1; // 64M + 1 byte
+#else
+ constexpr size_t kSize = (uint32_t{1} << 31) + 1; // 2G + 1 byte
+#endif
if (sizeof(size_t) > 4) {
- std::string s((uint32_t{1} << 31) + 1, 'x'); // 2G + 1 byte
+ std::string s(kSize, 'x');
s.back() = '-';
std::vector<absl::string_view> v = absl::StrSplit(s, '-');
EXPECT_EQ(2, v.size());
diff --git a/absl/strings/string_view.cc b/absl/strings/string_view.cc
index d596e08..97025c3 100644
--- a/absl/strings/string_view.cc
+++ b/absl/strings/string_view.cc
@@ -21,18 +21,45 @@
#include <cstring>
#include <ostream>
-#include "absl/strings/internal/memutil.h"
+#include "absl/base/nullability.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace {
+
+// This is significantly faster for case-sensitive matches with very
+// few possible matches.
+absl::Nullable<const char*> memmatch(absl::Nullable<const char*> phaystack,
+ size_t haylen,
+ absl::Nullable<const char*> pneedle,
+ size_t neelen) {
+ if (0 == neelen) {
+ return phaystack; // even if haylen is 0
+ }
+ if (haylen < neelen) return nullptr;
+
+ const char* match;
+ const char* hayend = phaystack + haylen - neelen + 1;
+ // A static cast is used here as memchr returns a const void *, and pointer
+ // arithmetic is not allowed on pointers to void.
+ while (
+ (match = static_cast<const char*>(memchr(
+ phaystack, pneedle[0], static_cast<size_t>(hayend - phaystack))))) {
+ if (memcmp(match, pneedle, neelen) == 0)
+ return match;
+ else
+ phaystack = match + 1;
+ }
+ return nullptr;
+}
+
void WritePadding(std::ostream& o, size_t pad) {
char fill_buf[32];
memset(fill_buf, o.fill(), sizeof(fill_buf));
while (pad) {
size_t n = std::min(pad, sizeof(fill_buf));
- o.write(fill_buf, n);
+ o.write(fill_buf, static_cast<std::streamsize>(n));
pad -= n;
}
}
@@ -63,7 +90,7 @@
size_t lpad = 0;
size_t rpad = 0;
if (static_cast<size_t>(o.width()) > piece.size()) {
- size_t pad = o.width() - piece.size();
+ size_t pad = static_cast<size_t>(o.width()) - piece.size();
if ((o.flags() & o.adjustfield) == o.left) {
rpad = pad;
} else {
@@ -71,7 +98,7 @@
}
}
if (lpad) WritePadding(o, lpad);
- o.write(piece.data(), piece.size());
+ o.write(piece.data(), static_cast<std::streamsize>(piece.size()));
if (rpad) WritePadding(o, rpad);
o.width(0);
}
@@ -84,9 +111,8 @@
if (empty() && pos == 0 && s.empty()) return 0;
return npos;
}
- const char* result =
- strings_internal::memmatch(ptr_ + pos, length_ - pos, s.ptr_, s.length_);
- return result ? result - ptr_ : 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 {
@@ -95,7 +121,7 @@
}
const char* result =
static_cast<const char*>(memchr(ptr_ + pos, c, length_ - pos));
- return result != nullptr ? result - ptr_ : npos;
+ return result != nullptr ? static_cast<size_type>(result - ptr_) : npos;
}
string_view::size_type string_view::rfind(string_view s,
@@ -104,7 +130,7 @@
if (s.empty()) return std::min(length_, pos);
const char* last = ptr_ + std::min(length_ - s.length_, pos) + s.length_;
const char* result = std::find_end(ptr_, last, s.ptr_, s.ptr_ + s.length_);
- return result != last ? result - ptr_ : npos;
+ return result != last ? static_cast<size_type>(result - ptr_) : npos;
}
// Search range is [0..pos] inclusive. If pos == npos, search everything.
@@ -207,24 +233,30 @@
return npos;
}
-// MSVC has non-standard behavior that implicitly creates definitions for static
-// const members. These implicit definitions conflict with explicit out-of-class
-// member definitions that are required by the C++ standard, resulting in
-// LNK1169 "multiply defined" errors at link time. __declspec(selectany) asks
-// MSVC to choose only one definition for the symbol it decorates. See details
-// at https://msdn.microsoft.com/en-us/library/34h23df8(v=vs.100).aspx
-#ifdef _MSC_VER
-#define ABSL_STRING_VIEW_SELECTANY __declspec(selectany)
-#else
-#define ABSL_STRING_VIEW_SELECTANY
-#endif
-
-ABSL_STRING_VIEW_SELECTANY
+#ifdef ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL
constexpr string_view::size_type string_view::npos;
-ABSL_STRING_VIEW_SELECTANY
constexpr string_view::size_type string_view::kMaxSize;
+#endif
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 ec6c431..b393c6f 100644
--- a/absl/strings/string_view.h
+++ b/absl/strings/string_view.h
@@ -37,6 +37,7 @@
#include <string>
#include "absl/base/attributes.h"
+#include "absl/base/nullability.h"
#include "absl/base/config.h"
#include "absl/base/internal/throw_delegate.h"
#include "absl/base/macros.h"
@@ -55,19 +56,14 @@
#else // ABSL_USES_STD_STRING_VIEW
-#if ABSL_HAVE_BUILTIN(__builtin_memcmp) || \
- (defined(__GNUC__) && !defined(__clang__))
+#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)
-#if defined(__cplusplus) && __cplusplus >= 201402L
-#define ABSL_INTERNAL_STRING_VIEW_CXX14_CONSTEXPR constexpr
-#else
-#define ABSL_INTERNAL_STRING_VIEW_CXX14_CONSTEXPR
-#endif
-
namespace absl {
ABSL_NAMESPACE_BEGIN
@@ -167,11 +163,11 @@
public:
using traits_type = std::char_traits<char>;
using value_type = char;
- using pointer = char*;
- using const_pointer = const char*;
+ using pointer = absl::Nullable<char*>;
+ using const_pointer = absl::Nullable<const char*>;
using reference = char&;
using const_reference = const char&;
- using const_iterator = const char*;
+ using const_iterator = absl::Nullable<const char*>;
using iterator = const_iterator;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
using reverse_iterator = const_reverse_iterator;
@@ -198,12 +194,13 @@
// Implicit constructor of a `string_view` from NUL-terminated `str`. When
// accepting possibly null strings, use `absl::NullSafeStringView(str)`
// instead (see below).
- constexpr string_view(const char* str) // NOLINT(runtime/explicit)
- : ptr_(str),
- length_(str ? CheckLengthInternal(StrlenInternal(str)) : 0) {}
+ // The length check is skipped since it is unnecessary and causes code bloat.
+ constexpr string_view( // NOLINT(runtime/explicit)
+ absl::Nonnull<const char*> str)
+ : ptr_(str), length_(str ? StrlenInternal(str) : 0) {}
// Implicit constructor of a `string_view` from a `const char*` and length.
- constexpr string_view(const char* data, size_type len)
+ constexpr string_view(absl::Nullable<const char*> data, size_type len)
: ptr_(data), length_(CheckLengthInternal(len)) {}
// NOTE: Harmlessly omitted to work around gdb bug.
@@ -340,7 +337,7 @@
//
// Removes the first `n` characters from the `string_view`. Note that the
// underlying string is not changed, only the view.
- ABSL_INTERNAL_STRING_VIEW_CXX14_CONSTEXPR void remove_prefix(size_type n) {
+ constexpr void remove_prefix(size_type n) {
ABSL_HARDENING_ASSERT(n <= length_);
ptr_ += n;
length_ -= n;
@@ -350,7 +347,7 @@
//
// Removes the last `n` characters from the `string_view`. Note that the
// underlying string is not changed, only the view.
- ABSL_INTERNAL_STRING_VIEW_CXX14_CONSTEXPR void remove_suffix(size_type n) {
+ constexpr void remove_suffix(size_type n) {
ABSL_HARDENING_ASSERT(n <= length_);
length_ -= n;
}
@@ -358,7 +355,7 @@
// string_view::swap()
//
// Swaps this `string_view` with another `string_view`.
- ABSL_INTERNAL_STRING_VIEW_CXX14_CONSTEXPR void swap(string_view& s) noexcept {
+ constexpr void swap(string_view& s) noexcept {
auto t = *this;
*this = s;
s = t;
@@ -432,18 +429,21 @@
// Overload of `string_view::compare()` for comparing a `string_view` and a
// a different C-style string `s`.
- constexpr int compare(const char* s) const { return compare(string_view(s)); }
+ constexpr int compare(absl::Nonnull<const char*> s) const {
+ return compare(string_view(s));
+ }
// Overload of `string_view::compare()` for comparing a substring of the
// `string_view` and a different string C-style string `s`.
- constexpr int compare(size_type pos1, size_type count1, const char* s) const {
+ constexpr int compare(size_type pos1, size_type count1,
+ absl::Nonnull<const char*> s) const {
return substr(pos1, count1).compare(string_view(s));
}
// Overload of `string_view::compare()` for comparing a substring of the
// `string_view` and a substring of a different C-style string `s`.
- constexpr int compare(size_type pos1, size_type count1, const char* s,
- size_type count2) const {
+ constexpr int compare(size_type pos1, size_type count1,
+ absl::Nonnull<const char*> s, size_type count2) const {
return substr(pos1, count1).compare(string_view(s, count2));
}
@@ -462,13 +462,14 @@
// Overload of `string_view::find()` for finding a substring of a different
// C-style string `s` within the `string_view`.
- size_type find(const char* s, size_type pos, size_type count) const {
+ size_type find(absl::Nonnull<const char*> s, size_type pos,
+ size_type count) const {
return find(string_view(s, count), pos);
}
// Overload of `string_view::find()` for finding a different C-style string
// `s` within the `string_view`.
- size_type find(const char* s, size_type pos = 0) const {
+ size_type find(absl::Nonnull<const char *> s, size_type pos = 0) const {
return find(string_view(s), pos);
}
@@ -485,13 +486,14 @@
// Overload of `string_view::rfind()` for finding a substring of a different
// C-style string `s` within the `string_view`.
- size_type rfind(const char* s, size_type pos, size_type count) const {
+ size_type rfind(absl::Nonnull<const char*> s, size_type pos,
+ size_type count) const {
return rfind(string_view(s, count), pos);
}
// Overload of `string_view::rfind()` for finding a different C-style string
// `s` within the `string_view`.
- size_type rfind(const char* s, size_type pos = npos) const {
+ size_type rfind(absl::Nonnull<const char*> s, size_type pos = npos) const {
return rfind(string_view(s), pos);
}
@@ -510,14 +512,15 @@
// Overload of `string_view::find_first_of()` for finding a substring of a
// different C-style string `s` within the `string_view`.
- size_type find_first_of(const char* s, size_type pos,
- size_type count) const {
+ size_type find_first_of(absl::Nonnull<const char*> s, size_type pos,
+ size_type count) const {
return find_first_of(string_view(s, count), pos);
}
// Overload of `string_view::find_first_of()` for finding a different C-style
// string `s` within the `string_view`.
- size_type find_first_of(const char* s, size_type pos = 0) const {
+ size_type find_first_of(absl::Nonnull<const char*> s,
+ size_type pos = 0) const {
return find_first_of(string_view(s), pos);
}
@@ -536,13 +539,15 @@
// Overload of `string_view::find_last_of()` for finding a substring of a
// different C-style string `s` within the `string_view`.
- size_type find_last_of(const char* s, size_type pos, size_type count) const {
+ size_type find_last_of(absl::Nonnull<const char*> s, size_type pos,
+ size_type count) const {
return find_last_of(string_view(s, count), pos);
}
// Overload of `string_view::find_last_of()` for finding a different C-style
// string `s` within the `string_view`.
- size_type find_last_of(const char* s, size_type pos = npos) const {
+ size_type find_last_of(absl::Nonnull<const char*> s,
+ size_type pos = npos) const {
return find_last_of(string_view(s), pos);
}
@@ -559,14 +564,15 @@
// Overload of `string_view::find_first_not_of()` for finding a substring of a
// different C-style string `s` within the `string_view`.
- size_type find_first_not_of(const char* s, size_type pos,
+ size_type find_first_not_of(absl::Nonnull<const char*> s, size_type pos,
size_type count) const {
return find_first_not_of(string_view(s, count), pos);
}
// Overload of `string_view::find_first_not_of()` for finding a different
// C-style string `s` within the `string_view`.
- size_type find_first_not_of(const char* s, size_type pos = 0) const {
+ size_type find_first_not_of(absl::Nonnull<const char*> s,
+ size_type pos = 0) const {
return find_first_not_of(string_view(s), pos);
}
@@ -584,22 +590,76 @@
// Overload of `string_view::find_last_not_of()` for finding a substring of a
// different C-style string `s` within the `string_view`.
- size_type find_last_not_of(const char* s, size_type pos,
+ size_type find_last_not_of(absl::Nonnull<const char*> s, size_type pos,
size_type count) const {
return find_last_not_of(string_view(s, count), pos);
}
// Overload of `string_view::find_last_not_of()` for finding a different
// C-style string `s` within the `string_view`.
- size_type find_last_not_of(const char* s, size_type pos = npos) const {
+ size_type find_last_not_of(absl::Nonnull<const char*> s,
+ size_type pos = npos) const {
return find_last_not_of(string_view(s), pos);
}
+#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 202002L
+ // string_view::starts_with()
+ //
+ // Returns true if the `string_view` starts with the prefix `s`.
+ //
+ // This method only exists when targeting at least C++20.
+ // If support for C++ prior to C++20 is required, use `absl::StartsWith()`
+ // from `//absl/strings/match.h` for compatibility.
+ constexpr bool starts_with(string_view s) const noexcept {
+ return s.empty() ||
+ (size() >= s.size() &&
+ ABSL_INTERNAL_STRING_VIEW_MEMCMP(data(), s.data(), s.size()) == 0);
+ }
+
+ // Overload of `string_view::starts_with()` that returns true if `c` is the
+ // first character of the `string_view`.
+ constexpr bool starts_with(char c) const noexcept {
+ return !empty() && front() == c;
+ }
+
+ // Overload of `string_view::starts_with()` that returns true if the
+ // `string_view` starts with the C-style prefix `s`.
+ constexpr bool starts_with(const char* s) const {
+ return starts_with(string_view(s));
+ }
+
+ // string_view::ends_with()
+ //
+ // Returns true if the `string_view` ends with the suffix `s`.
+ //
+ // This method only exists when targeting at least C++20.
+ // If support for C++ prior to C++20 is required, use `absl::EndsWith()`
+ // from `//absl/strings/match.h` for compatibility.
+ constexpr bool ends_with(string_view s) const noexcept {
+ return s.empty() || (size() >= s.size() && ABSL_INTERNAL_STRING_VIEW_MEMCMP(
+ data() + (size() - s.size()),
+ s.data(), s.size()) == 0);
+ }
+
+ // Overload of `string_view::ends_with()` that returns true if `c` is the
+ // last character of the `string_view`.
+ constexpr bool ends_with(char c) const noexcept {
+ return !empty() && back() == c;
+ }
+
+ // Overload of `string_view::ends_with()` that returns true if the
+ // `string_view` ends with the C-style suffix `s`.
+ constexpr bool ends_with(const char* s) const {
+ return ends_with(string_view(s));
+ }
+#endif // ABSL_INTERNAL_CPLUSPLUS_LANG >= 202002L
+
private:
- // The constructor from std::string delegates to this constuctor.
+ // The constructor from std::string delegates to this constructor.
// See the comment on that constructor for the rationale.
struct SkipCheckLengthTag {};
- string_view(const char* data, size_type len, SkipCheckLengthTag) noexcept
+ string_view(absl::Nullable<const char*> data, size_type len,
+ SkipCheckLengthTag) noexcept
: ptr_(data), length_(len) {}
static constexpr size_type kMaxSize =
@@ -609,8 +669,8 @@
return ABSL_HARDENING_ASSERT(len <= kMaxSize), len;
}
- static constexpr size_type StrlenInternal(const char* str) {
-#if defined(_MSC_VER) && _MSC_VER >= 1910 && !defined(__clang__)
+ static constexpr size_type StrlenInternal(absl::Nonnull<const char*> str) {
+#if defined(_MSC_VER) && !defined(__clang__)
// MSVC 2017+ can evaluate this at compile-time.
const char* begin = str;
while (*str != '\0') ++str;
@@ -638,7 +698,7 @@
: (compare_result < 0 ? -1 : 1);
}
- const char* ptr_;
+ absl::Nullable<const char*> ptr_;
size_type length_;
};
@@ -677,7 +737,6 @@
ABSL_NAMESPACE_END
} // namespace absl
-#undef ABSL_INTERNAL_STRING_VIEW_CXX14_CONSTEXPR
#undef ABSL_INTERNAL_STRING_VIEW_MEMCMP
#endif // ABSL_USES_STD_STRING_VIEW
@@ -700,7 +759,7 @@
// Creates an `absl::string_view` from a pointer `p` even if it's null-valued.
// This function should be used where an `absl::string_view` can be created from
// a possibly-null pointer.
-constexpr string_view NullSafeStringView(const char* p) {
+constexpr string_view NullSafeStringView(absl::Nullable<const char*> p) {
return p ? string_view(p) : string_view();
}
diff --git a/absl/strings/string_view_benchmark.cc b/absl/strings/string_view_benchmark.cc
index 0d74e23..98f747c 100644
--- a/absl/strings/string_view_benchmark.cc
+++ b/absl/strings/string_view_benchmark.cc
@@ -15,6 +15,7 @@
#include "absl/strings/string_view.h"
#include <algorithm>
+#include <cstddef>
#include <cstdint>
#include <map>
#include <random>
diff --git a/absl/strings/string_view_test.cc b/absl/strings/string_view_test.cc
index 2c13dd1..251f184 100644
--- a/absl/strings/string_view_test.cc
+++ b/absl/strings/string_view_test.cc
@@ -15,20 +15,23 @@
#include "absl/strings/string_view.h"
#include <stdlib.h>
+
+#include <cstddef>
+#include <cstdlib>
+#include <cstring>
#include <iomanip>
+#include <ios>
#include <iterator>
#include <limits>
#include <map>
+#include <memory>
#include <sstream>
-#include <stdexcept>
#include <string>
#include <type_traits>
#include <utility>
#include "gtest/gtest.h"
#include "absl/base/config.h"
-#include "absl/base/dynamic_annotations.h"
-#include "absl/base/options.h"
#if defined(ABSL_HAVE_STD_STRING_VIEW) || defined(__ANDROID__)
// We don't control the death messaging when using std::string_view.
@@ -82,7 +85,7 @@
// Null.
absl::string_view s10;
EXPECT_TRUE(s10.data() == nullptr);
- EXPECT_EQ(0, s10.length());
+ EXPECT_EQ(0u, s10.length());
}
{
@@ -90,17 +93,17 @@
const char* hello = "hello";
absl::string_view s20(hello);
EXPECT_TRUE(s20.data() == hello);
- EXPECT_EQ(5, s20.length());
+ EXPECT_EQ(5u, s20.length());
// const char* with length.
absl::string_view s21(hello, 4);
EXPECT_TRUE(s21.data() == hello);
- EXPECT_EQ(4, s21.length());
+ EXPECT_EQ(4u, s21.length());
// Not recommended, but valid C++
absl::string_view s22(hello, 6);
EXPECT_TRUE(s22.data() == hello);
- EXPECT_EQ(6, s22.length());
+ EXPECT_EQ(6u, s22.length());
}
{
@@ -108,7 +111,7 @@
std::string hola = "hola";
absl::string_view s30(hola);
EXPECT_TRUE(s30.data() == hola.data());
- EXPECT_EQ(4, s30.length());
+ EXPECT_EQ(4u, s30.length());
// std::string with embedded '\0'.
hola.push_back('\0');
@@ -116,7 +119,7 @@
hola.push_back('\0');
absl::string_view s31(hola);
EXPECT_TRUE(s31.data() == hola.data());
- EXPECT_EQ(8, s31.length());
+ EXPECT_EQ(8u, s31.length());
}
{
@@ -165,7 +168,7 @@
map.insert(std::make_pair(p1, 0));
map.insert(std::make_pair(p2, 1));
map.insert(std::make_pair(p3, 2));
- EXPECT_EQ(map.size(), 3);
+ EXPECT_EQ(map.size(), 3u);
TestMap::const_iterator iter = map.begin();
EXPECT_EQ(iter->second, 1);
@@ -183,7 +186,7 @@
EXPECT_TRUE(new_iter != map.end());
map.erase(new_iter);
- EXPECT_EQ(map.size(), 2);
+ EXPECT_EQ(map.size(), 2u);
iter = map.begin();
EXPECT_EQ(iter->second, 2);
@@ -261,11 +264,11 @@
TEST(StringViewTest, ComparisonOperatorsByCharacterPosition) {
std::string x;
- for (int i = 0; i < 256; i++) {
+ for (size_t i = 0; i < 256; i++) {
x += 'a';
std::string y = x;
COMPARE(true, ==, x, y);
- for (int j = 0; j < i; j++) {
+ for (size_t j = 0; j < i; j++) {
std::string z = x;
z[j] = 'b'; // Differs in position 'j'
COMPARE(false, ==, x, z);
@@ -341,12 +344,12 @@
EXPECT_EQ(*(c.rend() - 1), 'x');
EXPECT_TRUE(a.rbegin() + 26 == a.rend());
- EXPECT_EQ(a.size(), 26);
- EXPECT_EQ(b.size(), 3);
- EXPECT_EQ(c.size(), 3);
- EXPECT_EQ(d.size(), 6);
- EXPECT_EQ(e.size(), 0);
- EXPECT_EQ(f.size(), 7);
+ EXPECT_EQ(a.size(), 26u);
+ EXPECT_EQ(b.size(), 3u);
+ EXPECT_EQ(c.size(), 3u);
+ EXPECT_EQ(d.size(), 6u);
+ EXPECT_EQ(e.size(), 0u);
+ EXPECT_EQ(f.size(), 7u);
EXPECT_TRUE(!d.empty());
EXPECT_TRUE(d.begin() != d.end());
@@ -356,17 +359,17 @@
EXPECT_TRUE(e.begin() == e.end());
char buf[4] = { '%', '%', '%', '%' };
- EXPECT_EQ(a.copy(buf, 4), 4);
+ EXPECT_EQ(a.copy(buf, 4), 4u);
EXPECT_EQ(buf[0], a[0]);
EXPECT_EQ(buf[1], a[1]);
EXPECT_EQ(buf[2], a[2]);
EXPECT_EQ(buf[3], a[3]);
- EXPECT_EQ(a.copy(buf, 3, 7), 3);
+ EXPECT_EQ(a.copy(buf, 3, 7), 3u);
EXPECT_EQ(buf[0], a[7]);
EXPECT_EQ(buf[1], a[8]);
EXPECT_EQ(buf[2], a[9]);
EXPECT_EQ(buf[3], a[3]);
- EXPECT_EQ(c.copy(buf, 99), 3);
+ EXPECT_EQ(c.copy(buf, 99), 3u);
EXPECT_EQ(buf[0], c[0]);
EXPECT_EQ(buf[1], c[1]);
EXPECT_EQ(buf[2], c[2]);
@@ -393,22 +396,22 @@
7);
d = absl::string_view();
- EXPECT_EQ(d.size(), 0);
+ EXPECT_EQ(d.size(), 0u);
EXPECT_TRUE(d.empty());
EXPECT_TRUE(d.data() == nullptr);
EXPECT_TRUE(d.begin() == d.end());
- EXPECT_EQ(a.find(b), 0);
+ EXPECT_EQ(a.find(b), 0u);
EXPECT_EQ(a.find(b, 1), absl::string_view::npos);
- EXPECT_EQ(a.find(c), 23);
- EXPECT_EQ(a.find(c, 9), 23);
+ EXPECT_EQ(a.find(c), 23u);
+ EXPECT_EQ(a.find(c, 9), 23u);
EXPECT_EQ(a.find(c, absl::string_view::npos), absl::string_view::npos);
EXPECT_EQ(b.find(c), absl::string_view::npos);
EXPECT_EQ(b.find(c, absl::string_view::npos), absl::string_view::npos);
- EXPECT_EQ(a.find(d), 0);
- EXPECT_EQ(a.find(e), 0);
- EXPECT_EQ(a.find(d, 12), 12);
- EXPECT_EQ(a.find(e, 17), 17);
+ EXPECT_EQ(a.find(d), 0u);
+ EXPECT_EQ(a.find(e), 0u);
+ EXPECT_EQ(a.find(d, 12), 12u);
+ EXPECT_EQ(a.find(e, 17), 17u);
absl::string_view g("xx not found bb");
EXPECT_EQ(a.find(g), absl::string_view::npos);
// empty string nonsense
@@ -427,17 +430,17 @@
EXPECT_EQ(e.find(d, 4), std::string().find(std::string(), 4));
EXPECT_EQ(e.find(e, 4), std::string().find(std::string(), 4));
- EXPECT_EQ(a.find('a'), 0);
- EXPECT_EQ(a.find('c'), 2);
- EXPECT_EQ(a.find('z'), 25);
+ EXPECT_EQ(a.find('a'), 0u);
+ EXPECT_EQ(a.find('c'), 2u);
+ EXPECT_EQ(a.find('z'), 25u);
EXPECT_EQ(a.find('$'), absl::string_view::npos);
EXPECT_EQ(a.find('\0'), absl::string_view::npos);
- EXPECT_EQ(f.find('\0'), 3);
- EXPECT_EQ(f.find('3'), 2);
- EXPECT_EQ(f.find('5'), 5);
- EXPECT_EQ(g.find('o'), 4);
- EXPECT_EQ(g.find('o', 4), 4);
- EXPECT_EQ(g.find('o', 5), 8);
+ EXPECT_EQ(f.find('\0'), 3u);
+ EXPECT_EQ(f.find('3'), 2u);
+ EXPECT_EQ(f.find('5'), 5u);
+ EXPECT_EQ(g.find('o'), 4u);
+ EXPECT_EQ(g.find('o', 4), 4u);
+ EXPECT_EQ(g.find('o', 5), 8u);
EXPECT_EQ(a.find('b', 5), absl::string_view::npos);
// empty string nonsense
EXPECT_EQ(d.find('\0'), absl::string_view::npos);
@@ -449,8 +452,8 @@
EXPECT_EQ(d.find('x', 4), absl::string_view::npos);
EXPECT_EQ(e.find('x', 7), absl::string_view::npos);
- EXPECT_EQ(a.find(b.data(), 1, 0), 1);
- EXPECT_EQ(a.find(c.data(), 9, 0), 9);
+ EXPECT_EQ(a.find(b.data(), 1, 0), 1u);
+ EXPECT_EQ(a.find(c.data(), 9, 0), 9u);
EXPECT_EQ(a.find(c.data(), absl::string_view::npos, 0),
absl::string_view::npos);
EXPECT_EQ(b.find(c.data(), absl::string_view::npos, 0),
@@ -460,16 +463,16 @@
EXPECT_EQ(e.find(b.data(), 7, 0), absl::string_view::npos);
EXPECT_EQ(a.find(b.data(), 1), absl::string_view::npos);
- EXPECT_EQ(a.find(c.data(), 9), 23);
+ EXPECT_EQ(a.find(c.data(), 9), 23u);
EXPECT_EQ(a.find(c.data(), absl::string_view::npos), absl::string_view::npos);
EXPECT_EQ(b.find(c.data(), absl::string_view::npos), absl::string_view::npos);
// empty string nonsense
EXPECT_EQ(d.find(b.data(), 4), absl::string_view::npos);
EXPECT_EQ(e.find(b.data(), 7), absl::string_view::npos);
- EXPECT_EQ(a.rfind(b), 0);
- EXPECT_EQ(a.rfind(b, 1), 0);
- EXPECT_EQ(a.rfind(c), 23);
+ EXPECT_EQ(a.rfind(b), 0u);
+ EXPECT_EQ(a.rfind(b, 1), 0u);
+ EXPECT_EQ(a.rfind(c), 23u);
EXPECT_EQ(a.rfind(c, 22), absl::string_view::npos);
EXPECT_EQ(a.rfind(c, 1), absl::string_view::npos);
EXPECT_EQ(a.rfind(c, 0), absl::string_view::npos);
@@ -477,8 +480,8 @@
EXPECT_EQ(b.rfind(c, 0), absl::string_view::npos);
EXPECT_EQ(a.rfind(d), std::string(a).rfind(std::string()));
EXPECT_EQ(a.rfind(e), std::string(a).rfind(std::string()));
- EXPECT_EQ(a.rfind(d, 12), 12);
- EXPECT_EQ(a.rfind(e, 17), 17);
+ EXPECT_EQ(a.rfind(d, 12), 12u);
+ EXPECT_EQ(a.rfind(e, 17), 17u);
EXPECT_EQ(a.rfind(g), absl::string_view::npos);
EXPECT_EQ(d.rfind(b), absl::string_view::npos);
EXPECT_EQ(e.rfind(b), absl::string_view::npos);
@@ -494,28 +497,28 @@
EXPECT_EQ(d.rfind(e), std::string().rfind(std::string()));
EXPECT_EQ(e.rfind(e), std::string().rfind(std::string()));
- EXPECT_EQ(g.rfind('o'), 8);
+ EXPECT_EQ(g.rfind('o'), 8u);
EXPECT_EQ(g.rfind('q'), absl::string_view::npos);
- EXPECT_EQ(g.rfind('o', 8), 8);
- EXPECT_EQ(g.rfind('o', 7), 4);
+ EXPECT_EQ(g.rfind('o', 8), 8u);
+ EXPECT_EQ(g.rfind('o', 7), 4u);
EXPECT_EQ(g.rfind('o', 3), absl::string_view::npos);
- EXPECT_EQ(f.rfind('\0'), 3);
- EXPECT_EQ(f.rfind('\0', 12), 3);
- EXPECT_EQ(f.rfind('3'), 2);
- EXPECT_EQ(f.rfind('5'), 5);
+ EXPECT_EQ(f.rfind('\0'), 3u);
+ EXPECT_EQ(f.rfind('\0', 12), 3u);
+ EXPECT_EQ(f.rfind('3'), 2u);
+ EXPECT_EQ(f.rfind('5'), 5u);
// empty string nonsense
EXPECT_EQ(d.rfind('o'), absl::string_view::npos);
EXPECT_EQ(e.rfind('o'), absl::string_view::npos);
EXPECT_EQ(d.rfind('o', 4), absl::string_view::npos);
EXPECT_EQ(e.rfind('o', 7), absl::string_view::npos);
- EXPECT_EQ(a.rfind(b.data(), 1, 0), 1);
- EXPECT_EQ(a.rfind(c.data(), 22, 0), 22);
- EXPECT_EQ(a.rfind(c.data(), 1, 0), 1);
- EXPECT_EQ(a.rfind(c.data(), 0, 0), 0);
- EXPECT_EQ(b.rfind(c.data(), 0, 0), 0);
- EXPECT_EQ(d.rfind(b.data(), 4, 0), 0);
- EXPECT_EQ(e.rfind(b.data(), 7, 0), 0);
+ EXPECT_EQ(a.rfind(b.data(), 1, 0), 1u);
+ EXPECT_EQ(a.rfind(c.data(), 22, 0), 22u);
+ EXPECT_EQ(a.rfind(c.data(), 1, 0), 1u);
+ EXPECT_EQ(a.rfind(c.data(), 0, 0), 0u);
+ EXPECT_EQ(b.rfind(c.data(), 0, 0), 0u);
+ EXPECT_EQ(d.rfind(b.data(), 4, 0), 0u);
+ EXPECT_EQ(e.rfind(b.data(), 7, 0), 0u);
}
// Continued from STL2
@@ -533,18 +536,18 @@
absl::string_view g("xx not found bb");
d = absl::string_view();
- EXPECT_EQ(a.find_first_of(b), 0);
- EXPECT_EQ(a.find_first_of(b, 0), 0);
- EXPECT_EQ(a.find_first_of(b, 1), 1);
- EXPECT_EQ(a.find_first_of(b, 2), 2);
+ EXPECT_EQ(a.find_first_of(b), 0u);
+ EXPECT_EQ(a.find_first_of(b, 0), 0u);
+ EXPECT_EQ(a.find_first_of(b, 1), 1u);
+ EXPECT_EQ(a.find_first_of(b, 2), 2u);
EXPECT_EQ(a.find_first_of(b, 3), absl::string_view::npos);
- EXPECT_EQ(a.find_first_of(c), 23);
- EXPECT_EQ(a.find_first_of(c, 23), 23);
- EXPECT_EQ(a.find_first_of(c, 24), 24);
- EXPECT_EQ(a.find_first_of(c, 25), 25);
+ EXPECT_EQ(a.find_first_of(c), 23u);
+ EXPECT_EQ(a.find_first_of(c, 23), 23u);
+ EXPECT_EQ(a.find_first_of(c, 24), 24u);
+ EXPECT_EQ(a.find_first_of(c, 25), 25u);
EXPECT_EQ(a.find_first_of(c, 26), absl::string_view::npos);
- EXPECT_EQ(g.find_first_of(b), 13);
- EXPECT_EQ(g.find_first_of(c), 0);
+ EXPECT_EQ(g.find_first_of(b), 13u);
+ EXPECT_EQ(g.find_first_of(c), 0u);
EXPECT_EQ(a.find_first_of(f), absl::string_view::npos);
EXPECT_EQ(f.find_first_of(a), absl::string_view::npos);
// empty string nonsense
@@ -557,19 +560,19 @@
EXPECT_EQ(d.find_first_of(e), absl::string_view::npos);
EXPECT_EQ(e.find_first_of(e), absl::string_view::npos);
- EXPECT_EQ(a.find_first_not_of(b), 3);
- EXPECT_EQ(a.find_first_not_of(c), 0);
+ EXPECT_EQ(a.find_first_not_of(b), 3u);
+ EXPECT_EQ(a.find_first_not_of(c), 0u);
EXPECT_EQ(b.find_first_not_of(a), absl::string_view::npos);
EXPECT_EQ(c.find_first_not_of(a), absl::string_view::npos);
- EXPECT_EQ(f.find_first_not_of(a), 0);
- EXPECT_EQ(a.find_first_not_of(f), 0);
- EXPECT_EQ(a.find_first_not_of(d), 0);
- EXPECT_EQ(a.find_first_not_of(e), 0);
+ EXPECT_EQ(f.find_first_not_of(a), 0u);
+ EXPECT_EQ(a.find_first_not_of(f), 0u);
+ EXPECT_EQ(a.find_first_not_of(d), 0u);
+ EXPECT_EQ(a.find_first_not_of(e), 0u);
// empty string nonsense
- EXPECT_EQ(a.find_first_not_of(d), 0);
- EXPECT_EQ(a.find_first_not_of(e), 0);
- EXPECT_EQ(a.find_first_not_of(d, 1), 1);
- EXPECT_EQ(a.find_first_not_of(e, 1), 1);
+ EXPECT_EQ(a.find_first_not_of(d), 0u);
+ EXPECT_EQ(a.find_first_not_of(e), 0u);
+ EXPECT_EQ(a.find_first_not_of(d, 1), 1u);
+ EXPECT_EQ(a.find_first_not_of(e, 1), 1u);
EXPECT_EQ(a.find_first_not_of(d, a.size() - 1), a.size() - 1);
EXPECT_EQ(a.find_first_not_of(e, a.size() - 1), a.size() - 1);
EXPECT_EQ(a.find_first_not_of(d, a.size()), absl::string_view::npos);
@@ -588,11 +591,11 @@
absl::string_view h("====");
EXPECT_EQ(h.find_first_not_of('='), absl::string_view::npos);
EXPECT_EQ(h.find_first_not_of('=', 3), absl::string_view::npos);
- EXPECT_EQ(h.find_first_not_of('\0'), 0);
- EXPECT_EQ(g.find_first_not_of('x'), 2);
- EXPECT_EQ(f.find_first_not_of('\0'), 0);
- EXPECT_EQ(f.find_first_not_of('\0', 3), 4);
- EXPECT_EQ(f.find_first_not_of('\0', 2), 2);
+ EXPECT_EQ(h.find_first_not_of('\0'), 0u);
+ EXPECT_EQ(g.find_first_not_of('x'), 2u);
+ EXPECT_EQ(f.find_first_not_of('\0'), 0u);
+ EXPECT_EQ(f.find_first_not_of('\0', 3), 4u);
+ EXPECT_EQ(f.find_first_not_of('\0', 2), 2u);
// empty string nonsense
EXPECT_EQ(d.find_first_not_of('x'), absl::string_view::npos);
EXPECT_EQ(e.find_first_not_of('x'), absl::string_view::npos);
@@ -618,20 +621,20 @@
d = absl::string_view();
EXPECT_EQ(h.find_last_of(a), absl::string_view::npos);
- EXPECT_EQ(g.find_last_of(a), g.size()-1);
- EXPECT_EQ(a.find_last_of(b), 2);
- EXPECT_EQ(a.find_last_of(c), a.size()-1);
- EXPECT_EQ(f.find_last_of(i), 6);
- EXPECT_EQ(a.find_last_of('a'), 0);
- EXPECT_EQ(a.find_last_of('b'), 1);
- EXPECT_EQ(a.find_last_of('z'), 25);
- EXPECT_EQ(a.find_last_of('a', 5), 0);
- EXPECT_EQ(a.find_last_of('b', 5), 1);
+ EXPECT_EQ(g.find_last_of(a), g.size() - 1);
+ EXPECT_EQ(a.find_last_of(b), 2u);
+ EXPECT_EQ(a.find_last_of(c), a.size() - 1);
+ EXPECT_EQ(f.find_last_of(i), 6u);
+ EXPECT_EQ(a.find_last_of('a'), 0u);
+ EXPECT_EQ(a.find_last_of('b'), 1u);
+ EXPECT_EQ(a.find_last_of('z'), 25u);
+ EXPECT_EQ(a.find_last_of('a', 5), 0u);
+ EXPECT_EQ(a.find_last_of('b', 5), 1u);
EXPECT_EQ(a.find_last_of('b', 0), absl::string_view::npos);
- EXPECT_EQ(a.find_last_of('z', 25), 25);
+ EXPECT_EQ(a.find_last_of('z', 25), 25u);
EXPECT_EQ(a.find_last_of('z', 24), absl::string_view::npos);
- EXPECT_EQ(f.find_last_of(i, 5), 5);
- EXPECT_EQ(f.find_last_of(i, 6), 6);
+ EXPECT_EQ(f.find_last_of(i, 5), 5u);
+ EXPECT_EQ(f.find_last_of(i, 6), 6u);
EXPECT_EQ(f.find_last_of(a, 4), absl::string_view::npos);
// empty string nonsense
EXPECT_EQ(f.find_last_of(d), absl::string_view::npos);
@@ -651,19 +654,19 @@
EXPECT_EQ(d.find_last_of(f, 4), absl::string_view::npos);
EXPECT_EQ(e.find_last_of(f, 4), absl::string_view::npos);
- EXPECT_EQ(a.find_last_not_of(b), a.size()-1);
- EXPECT_EQ(a.find_last_not_of(c), 22);
+ EXPECT_EQ(a.find_last_not_of(b), a.size() - 1);
+ EXPECT_EQ(a.find_last_not_of(c), 22u);
EXPECT_EQ(b.find_last_not_of(a), absl::string_view::npos);
EXPECT_EQ(b.find_last_not_of(b), absl::string_view::npos);
- EXPECT_EQ(f.find_last_not_of(i), 4);
- EXPECT_EQ(a.find_last_not_of(c, 24), 22);
- EXPECT_EQ(a.find_last_not_of(b, 3), 3);
+ EXPECT_EQ(f.find_last_not_of(i), 4u);
+ EXPECT_EQ(a.find_last_not_of(c, 24), 22u);
+ EXPECT_EQ(a.find_last_not_of(b, 3), 3u);
EXPECT_EQ(a.find_last_not_of(b, 2), absl::string_view::npos);
// empty string nonsense
- EXPECT_EQ(f.find_last_not_of(d), f.size()-1);
- EXPECT_EQ(f.find_last_not_of(e), f.size()-1);
- EXPECT_EQ(f.find_last_not_of(d, 4), 4);
- EXPECT_EQ(f.find_last_not_of(e, 4), 4);
+ EXPECT_EQ(f.find_last_not_of(d), f.size() - 1);
+ EXPECT_EQ(f.find_last_not_of(e), f.size() - 1);
+ EXPECT_EQ(f.find_last_not_of(d, 4), 4u);
+ EXPECT_EQ(f.find_last_not_of(e, 4), 4u);
EXPECT_EQ(d.find_last_not_of(d), absl::string_view::npos);
EXPECT_EQ(d.find_last_not_of(e), absl::string_view::npos);
EXPECT_EQ(e.find_last_not_of(d), absl::string_view::npos);
@@ -679,10 +682,10 @@
EXPECT_EQ(h.find_last_not_of('x'), h.size() - 1);
EXPECT_EQ(h.find_last_not_of('='), absl::string_view::npos);
- EXPECT_EQ(b.find_last_not_of('c'), 1);
- EXPECT_EQ(h.find_last_not_of('x', 2), 2);
+ EXPECT_EQ(b.find_last_not_of('c'), 1u);
+ EXPECT_EQ(h.find_last_not_of('x', 2), 2u);
EXPECT_EQ(h.find_last_not_of('=', 2), absl::string_view::npos);
- EXPECT_EQ(b.find_last_not_of('b', 1), 0);
+ EXPECT_EQ(b.find_last_not_of('b', 1), 0u);
// empty string nonsense
EXPECT_EQ(d.find_last_not_of('x'), absl::string_view::npos);
EXPECT_EQ(e.find_last_not_of('x'), absl::string_view::npos);
@@ -734,7 +737,7 @@
TEST(StringViewTest, UTF8) {
std::string utf8 = "\u00E1";
std::string utf8_twice = utf8 + " " + utf8;
- int utf8_len = strlen(utf8.data());
+ size_t utf8_len = strlen(utf8.data());
EXPECT_EQ(utf8_len, absl::string_view(utf8_twice).find_first_of(" "));
EXPECT_EQ(utf8_len, absl::string_view(utf8_twice).find_first_of(" \t"));
}
@@ -879,12 +882,12 @@
TEST(StringViewTest, NULLInput) {
absl::string_view s;
EXPECT_EQ(s.data(), nullptr);
- EXPECT_EQ(s.size(), 0);
+ EXPECT_EQ(s.size(), 0u);
#ifdef ABSL_HAVE_STRING_VIEW_FROM_NULLPTR
s = absl::string_view(nullptr);
EXPECT_EQ(s.data(), nullptr);
- EXPECT_EQ(s.size(), 0);
+ EXPECT_EQ(s.size(), 0u);
// .ToString() on a absl::string_view with nullptr should produce the empty
// string.
@@ -948,6 +951,76 @@
#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) {
@@ -959,7 +1032,7 @@
{
absl::string_view s = absl::NullSafeStringView(nullptr);
EXPECT_EQ(nullptr, s.data());
- EXPECT_EQ(0, s.size());
+ EXPECT_EQ(0u, s.size());
EXPECT_EQ(absl::string_view(), s);
}
{
@@ -975,12 +1048,9 @@
{
constexpr absl::string_view s = absl::NullSafeStringView(nullptr);
EXPECT_EQ(nullptr, s.data());
- EXPECT_EQ(0, s.size());
+ EXPECT_EQ(0u, s.size());
EXPECT_EQ(absl::string_view(), s);
}
-#if !defined(_MSC_VER) || _MSC_VER >= 1910
- // MSVC 2017+ is required for good constexpr string_view support.
- // See the implementation of `absl::string_view::StrlenInternal()`.
{
static constexpr char kHi[] = "hi";
absl::string_view s = absl::NullSafeStringView(kHi);
@@ -990,10 +1060,9 @@
}
{
constexpr absl::string_view s = absl::NullSafeStringView("hello");
- EXPECT_EQ(s.size(), 5);
+ EXPECT_EQ(s.size(), 5u);
EXPECT_EQ("hello", s);
}
-#endif
}
TEST(StringViewTest, ConstexprCompiles) {
@@ -1036,7 +1105,7 @@
#ifdef ABSL_HAVE_CONSTEXPR_STRING_VIEW_FROM_CSTR
constexpr absl::string_view cstr_strlen("foo");
- EXPECT_EQ(cstr_strlen.length(), 3);
+ EXPECT_EQ(cstr_strlen.length(), 3u);
constexpr absl::string_view cstr_strlen2 = "bar";
EXPECT_EQ(cstr_strlen2, "bar");
@@ -1111,7 +1180,7 @@
EXPECT_NE(cstr_ptr, nullptr);
constexpr size_t sp_npos = sp.npos;
- EXPECT_EQ(sp_npos, -1);
+ EXPECT_EQ(sp_npos, static_cast<size_t>(-1));
}
constexpr char ConstexprMethodsHelper() {
@@ -1179,7 +1248,7 @@
// Abseil's string_view implementation has bounds-checking in debug mode.
absl::string_view h = "hello";
ABSL_EXPECT_DEATH_IF_SUPPORTED(h[5], "");
- ABSL_EXPECT_DEATH_IF_SUPPORTED(h[-1], "");
+ ABSL_EXPECT_DEATH_IF_SUPPORTED(h[static_cast<size_t>(-1)], "");
#endif
#endif
}
@@ -1189,7 +1258,7 @@
EXPECT_LT("hello", std::string("world"));
}
-TEST(ComparisonOpsTest, HeterogenousStringViewEquals) {
+TEST(ComparisonOpsTest, HeterogeneousStringViewEquals) {
EXPECT_EQ(absl::string_view("hello"), std::string("hello"));
EXPECT_EQ("hello", absl::string_view("hello"));
}
@@ -1201,17 +1270,17 @@
a.remove_prefix(1);
a.remove_suffix(1);
- EXPECT_EQ(0, a.find('x'));
- EXPECT_EQ(0, a.find('x', 0));
- EXPECT_EQ(4, a.find('x', 1));
- EXPECT_EQ(4, a.find('x', 4));
+ EXPECT_EQ(0u, a.find('x'));
+ EXPECT_EQ(0u, a.find('x', 0));
+ EXPECT_EQ(4u, a.find('x', 1));
+ EXPECT_EQ(4u, a.find('x', 4));
EXPECT_EQ(absl::string_view::npos, a.find('x', 5));
- EXPECT_EQ(4, a.rfind('x'));
- EXPECT_EQ(4, a.rfind('x', 5));
- EXPECT_EQ(4, a.rfind('x', 4));
- EXPECT_EQ(0, a.rfind('x', 3));
- EXPECT_EQ(0, a.rfind('x', 0));
+ EXPECT_EQ(4u, a.rfind('x'));
+ EXPECT_EQ(4u, a.rfind('x', 5));
+ EXPECT_EQ(4u, a.rfind('x', 4));
+ EXPECT_EQ(0u, a.rfind('x', 3));
+ EXPECT_EQ(0u, a.rfind('x', 0));
// Set a = "yyy".
a.remove_prefix(1);
@@ -1239,8 +1308,8 @@
#if !defined(NDEBUG) && !defined(ABSL_USES_STD_STRING_VIEW)
TEST(NonNegativeLenTest, NonNegativeLen) {
- ABSL_EXPECT_DEATH_IF_SUPPORTED(absl::string_view("xyz", -1),
- "len <= kMaxSize");
+ ABSL_EXPECT_DEATH_IF_SUPPORTED(
+ absl::string_view("xyz", static_cast<size_t>(-1)), "len <= kMaxSize");
}
TEST(LenExceedsMaxSizeTest, LenExceedsMaxSize) {
diff --git a/absl/strings/strip.h b/absl/strings/strip.h
index 111872c..e3cda5b 100644
--- a/absl/strings/strip.h
+++ b/absl/strings/strip.h
@@ -25,6 +25,7 @@
#include <string>
#include "absl/base/macros.h"
+#include "absl/base/nullability.h"
#include "absl/strings/ascii.h"
#include "absl/strings/match.h"
#include "absl/strings/string_view.h"
@@ -34,30 +35,34 @@
// ConsumePrefix()
//
-// Strips the `expected` prefix from the start of the given string, returning
-// `true` if the strip operation succeeded or false otherwise.
+// Strips the `expected` prefix, if found, from the start of `str`.
+// If the operation succeeded, `true` is returned. If not, `false`
+// is returned and `str` is not modified.
//
// Example:
//
// absl::string_view input("abc");
// EXPECT_TRUE(absl::ConsumePrefix(&input, "a"));
// EXPECT_EQ(input, "bc");
-inline bool ConsumePrefix(absl::string_view* str, absl::string_view expected) {
+inline bool ConsumePrefix(absl::Nonnull<absl::string_view*> str,
+ absl::string_view expected) {
if (!absl::StartsWith(*str, expected)) return false;
str->remove_prefix(expected.size());
return true;
}
// ConsumeSuffix()
//
-// Strips the `expected` suffix from the end of the given string, returning
-// `true` if the strip operation succeeded or false otherwise.
+// Strips the `expected` suffix, if found, from the end of `str`.
+// If the operation succeeded, `true` is returned. If not, `false`
+// is returned and `str` is not modified.
//
// Example:
//
// absl::string_view input("abcdef");
// EXPECT_TRUE(absl::ConsumeSuffix(&input, "def"));
// EXPECT_EQ(input, "abc");
-inline bool ConsumeSuffix(absl::string_view* str, absl::string_view expected) {
+inline bool ConsumeSuffix(absl::Nonnull<absl::string_view*> str,
+ absl::string_view expected) {
if (!absl::EndsWith(*str, expected)) return false;
str->remove_suffix(expected.size());
return true;
@@ -65,7 +70,7 @@
// StripPrefix()
//
-// Returns a view into the input string 'str' with the given 'prefix' removed,
+// Returns a view into the input string `str` with the given `prefix` removed,
// but leaving the original string intact. If the prefix does not match at the
// start of the string, returns the original string instead.
ABSL_MUST_USE_RESULT inline absl::string_view StripPrefix(
@@ -76,7 +81,7 @@
// StripSuffix()
//
-// Returns a view into the input string 'str' with the given 'suffix' removed,
+// Returns a view into the input string `str` with the given `suffix` removed,
// but leaving the original string intact. If the suffix does not match at the
// end of the string, returns the original string instead.
ABSL_MUST_USE_RESULT inline absl::string_view StripSuffix(
diff --git a/absl/strings/substitute.cc b/absl/strings/substitute.cc
index 1f3c740..dd32c75 100644
--- a/absl/strings/substitute.cc
+++ b/absl/strings/substitute.cc
@@ -15,20 +15,28 @@
#include "absl/strings/substitute.h"
#include <algorithm>
+#include <cassert>
+#include <cstddef>
+#include <cstdint>
+#include <string>
+#include "absl/base/config.h"
#include "absl/base/internal/raw_logging.h"
+#include "absl/base/nullability.h"
#include "absl/strings/ascii.h"
#include "absl/strings/escaping.h"
#include "absl/strings/internal/resize_uninitialized.h"
+#include "absl/strings/numbers.h"
+#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace substitute_internal {
-void SubstituteAndAppendArray(std::string* output, absl::string_view format,
- const absl::string_view* args_array,
- size_t num_args) {
+void SubstituteAndAppendArray(
+ absl::Nonnull<std::string*> output, absl::string_view format,
+ absl::Nullable<const absl::string_view*> args_array, size_t num_args) {
// Determine total size needed.
size_t size = 0;
for (size_t i = 0; i < format.size(); i++) {
@@ -40,7 +48,8 @@
absl::CEscape(format).c_str());
#endif
return;
- } else if (absl::ascii_isdigit(format[i + 1])) {
+ } else if (absl::ascii_isdigit(
+ static_cast<unsigned char>(format[i + 1]))) {
int index = format[i + 1] - '0';
if (static_cast<size_t>(index) >= num_args) {
#ifndef NDEBUG
@@ -75,11 +84,12 @@
// Build the string.
size_t original_size = output->size();
- strings_internal::STLStringResizeUninitialized(output, original_size + size);
+ 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(format[i + 1])) {
+ if (absl::ascii_isdigit(static_cast<unsigned char>(format[i + 1]))) {
const absl::string_view src = args_array[format[i + 1] - '0'];
target = std::copy(src.begin(), src.end(), target);
++i; // Skip next char.
@@ -95,7 +105,7 @@
assert(target == output->data() + output->size());
}
-Arg::Arg(const void* value) {
+Arg::Arg(absl::Nullable<const void*> value) {
static_assert(sizeof(scratch_) >= sizeof(value) * 2 + 2,
"fix sizeof(scratch_)");
if (value == nullptr) {
@@ -109,7 +119,8 @@
} while (num != 0);
*--ptr = 'x';
*--ptr = '0';
- piece_ = absl::string_view(ptr, scratch_ + sizeof(scratch_) - ptr);
+ piece_ = absl::string_view(
+ ptr, static_cast<size_t>(scratch_ + sizeof(scratch_) - ptr));
}
}
@@ -131,7 +142,7 @@
beg = writer;
}
- piece_ = absl::string_view(beg, end - beg);
+ piece_ = absl::string_view(beg, static_cast<size_t>(end - beg));
}
// TODO(jorg): Don't duplicate so much code between here and str_cat.cc
@@ -146,7 +157,7 @@
*--writer = '0' + (value % 10);
value /= 10;
}
- *--writer = '0' + value;
+ *--writer = '0' + static_cast<char>(value);
if (neg) *--writer = '-';
ptrdiff_t fillers = writer - minfill;
@@ -163,7 +174,7 @@
if (add_sign_again) *--writer = '-';
}
- piece_ = absl::string_view(writer, end - writer);
+ piece_ = absl::string_view(writer, static_cast<size_t>(end - writer));
}
} // namespace substitute_internal
diff --git a/absl/strings/substitute.h b/absl/strings/substitute.h
index c6da4dc..6c7cba4 100644
--- a/absl/strings/substitute.h
+++ b/absl/strings/substitute.h
@@ -55,6 +55,8 @@
// * bool (Printed as "true" or "false")
// * pointer types other than char* (Printed as "0x<lower case hex string>",
// except that null is printed as "NULL")
+// * user-defined types via the `AbslStringify()` customization point. See the
+// documentation for `absl::StrCat` for an explanation on how to use this.
//
// If an invalid format string is provided, Substitute returns an empty string
// and SubstituteAndAppend does not change the provided output string.
@@ -76,9 +78,11 @@
#include <vector>
#include "absl/base/macros.h"
+#include "absl/base/nullability.h"
#include "absl/base/port.h"
#include "absl/strings/ascii.h"
#include "absl/strings/escaping.h"
+#include "absl/strings/internal/stringify_sink.h"
#include "absl/strings/numbers.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_split.h"
@@ -102,14 +106,14 @@
// Overloads for string-y things
//
// Explicitly overload `const char*` so the compiler doesn't cast to `bool`.
- Arg(const char* value) // NOLINT(runtime/explicit)
+ Arg(absl::Nullable<const char*> value) // NOLINT(google-explicit-constructor)
: piece_(absl::NullSafeStringView(value)) {}
template <typename Allocator>
Arg( // NOLINT
const std::basic_string<char, std::char_traits<char>, Allocator>&
value) noexcept
: piece_(value) {}
- Arg(absl::string_view value) // NOLINT(runtime/explicit)
+ Arg(absl::string_view value) // NOLINT(google-explicit-constructor)
: piece_(value) {}
// Overloads for primitives
@@ -119,48 +123,70 @@
// probably using them as 8-bit integers and would probably prefer an integer
// representation. However, we can't really know, so we make the caller decide
// what to do.
- Arg(char value) // NOLINT(runtime/explicit)
+ Arg(char value) // NOLINT(google-explicit-constructor)
: piece_(scratch_, 1) {
scratch_[0] = value;
}
Arg(short value) // NOLINT(*)
: piece_(scratch_,
- numbers_internal::FastIntToBuffer(value, scratch_) - scratch_) {}
+ static_cast<size_t>(
+ numbers_internal::FastIntToBuffer(value, scratch_) -
+ scratch_)) {}
Arg(unsigned short value) // NOLINT(*)
: piece_(scratch_,
- numbers_internal::FastIntToBuffer(value, scratch_) - scratch_) {}
- Arg(int value) // NOLINT(runtime/explicit)
+ static_cast<size_t>(
+ numbers_internal::FastIntToBuffer(value, scratch_) -
+ scratch_)) {}
+ Arg(int value) // NOLINT(google-explicit-constructor)
: piece_(scratch_,
- numbers_internal::FastIntToBuffer(value, scratch_) - scratch_) {}
- Arg(unsigned int value) // NOLINT(runtime/explicit)
+ static_cast<size_t>(
+ numbers_internal::FastIntToBuffer(value, scratch_) -
+ scratch_)) {}
+ Arg(unsigned int value) // NOLINT(google-explicit-constructor)
: piece_(scratch_,
- numbers_internal::FastIntToBuffer(value, scratch_) - scratch_) {}
+ static_cast<size_t>(
+ numbers_internal::FastIntToBuffer(value, scratch_) -
+ scratch_)) {}
Arg(long value) // NOLINT(*)
: piece_(scratch_,
- numbers_internal::FastIntToBuffer(value, scratch_) - scratch_) {}
+ static_cast<size_t>(
+ numbers_internal::FastIntToBuffer(value, scratch_) -
+ scratch_)) {}
Arg(unsigned long value) // NOLINT(*)
: piece_(scratch_,
- numbers_internal::FastIntToBuffer(value, scratch_) - scratch_) {}
+ static_cast<size_t>(
+ numbers_internal::FastIntToBuffer(value, scratch_) -
+ scratch_)) {}
Arg(long long value) // NOLINT(*)
: piece_(scratch_,
- numbers_internal::FastIntToBuffer(value, scratch_) - scratch_) {}
+ static_cast<size_t>(
+ numbers_internal::FastIntToBuffer(value, scratch_) -
+ scratch_)) {}
Arg(unsigned long long value) // NOLINT(*)
: piece_(scratch_,
- numbers_internal::FastIntToBuffer(value, scratch_) - scratch_) {}
- Arg(float value) // NOLINT(runtime/explicit)
+ static_cast<size_t>(
+ numbers_internal::FastIntToBuffer(value, scratch_) -
+ scratch_)) {}
+ Arg(float value) // NOLINT(google-explicit-constructor)
: piece_(scratch_, numbers_internal::SixDigitsToBuffer(value, scratch_)) {
}
- Arg(double value) // NOLINT(runtime/explicit)
+ Arg(double value) // NOLINT(google-explicit-constructor)
: piece_(scratch_, numbers_internal::SixDigitsToBuffer(value, scratch_)) {
}
- Arg(bool value) // NOLINT(runtime/explicit)
+ Arg(bool value) // NOLINT(google-explicit-constructor)
: piece_(value ? "true" : "false") {}
- Arg(Hex hex); // NOLINT(runtime/explicit)
- Arg(Dec dec); // NOLINT(runtime/explicit)
+ template <typename T, typename = typename std::enable_if<
+ HasAbslStringify<T>::value>::type>
+ Arg( // NOLINT(google-explicit-constructor)
+ const T& v, strings_internal::StringifySink&& sink = {})
+ : piece_(strings_internal::ExtractStringification(sink, v)) {}
- // vector<bool>::reference and const_reference require special help to
- // convert to `AlphaNum` because it requires two user defined conversions.
+ Arg(Hex hex); // NOLINT(google-explicit-constructor)
+ Arg(Dec dec); // NOLINT(google-explicit-constructor)
+
+ // vector<bool>::reference and const_reference require special help to convert
+ // to `Arg` because it requires two user defined conversions.
template <typename T,
absl::enable_if_t<
std::is_class<T>::value &&
@@ -172,7 +198,17 @@
// `void*` values, with the exception of `char*`, are printed as
// "0x<hex value>". However, in the case of `nullptr`, "NULL" is printed.
- Arg(const void* value); // NOLINT(runtime/explicit)
+ Arg( // NOLINT(google-explicit-constructor)
+ absl::Nullable<const void*> value);
+
+ // Normal enums are already handled by the integer formatters.
+ // This overload matches only scoped enums.
+ template <typename T,
+ typename = typename std::enable_if<
+ std::is_enum<T>{} && !std::is_convertible<T, int>{} &&
+ !HasAbslStringify<T>::value>::type>
+ Arg(T value) // NOLINT(google-explicit-constructor)
+ : Arg(static_cast<typename std::underlying_type<T>::type>(value)) {}
Arg(const Arg&) = delete;
Arg& operator=(const Arg&) = delete;
@@ -186,12 +222,12 @@
// Internal helper function. Don't call this from outside this implementation.
// This interface may change without notice.
-void SubstituteAndAppendArray(std::string* output, absl::string_view format,
- const absl::string_view* args_array,
- size_t num_args);
+void SubstituteAndAppendArray(
+ absl::Nonnull<std::string*> output, absl::string_view format,
+ absl::Nullable<const absl::string_view*> args_array, size_t num_args);
#if defined(ABSL_BAD_CALL_IF)
-constexpr int CalculateOneBit(const char* format) {
+constexpr int CalculateOneBit(absl::Nonnull<const char*> format) {
// Returns:
// * 2^N for '$N' when N is in [0-9]
// * 0 for correct '$' escaping: '$$'.
@@ -200,11 +236,11 @@
: (1 << (*format - '0'));
}
-constexpr const char* SkipNumber(const char* format) {
+constexpr const char* SkipNumber(absl::Nonnull<const char*> format) {
return !*format ? format : (format + 1);
}
-constexpr int PlaceholderBitmask(const char* format) {
+constexpr int PlaceholderBitmask(absl::Nonnull<const char*> format) {
return !*format
? 0
: *format != '$' ? PlaceholderBitmask(format + 1)
@@ -237,18 +273,21 @@
// absl::SubstituteAndAppend(boilerplate, format, args...);
// }
//
-inline void SubstituteAndAppend(std::string* output, absl::string_view format) {
+inline void SubstituteAndAppend(absl::Nonnull<std::string*> output,
+ absl::string_view format) {
substitute_internal::SubstituteAndAppendArray(output, format, nullptr, 0);
}
-inline void SubstituteAndAppend(std::string* output, absl::string_view format,
+inline void SubstituteAndAppend(absl::Nonnull<std::string*> output,
+ absl::string_view format,
const substitute_internal::Arg& a0) {
const absl::string_view args[] = {a0.piece()};
substitute_internal::SubstituteAndAppendArray(output, format, args,
ABSL_ARRAYSIZE(args));
}
-inline void SubstituteAndAppend(std::string* output, absl::string_view format,
+inline void SubstituteAndAppend(absl::Nonnull<std::string*> output,
+ absl::string_view format,
const substitute_internal::Arg& a0,
const substitute_internal::Arg& a1) {
const absl::string_view args[] = {a0.piece(), a1.piece()};
@@ -256,7 +295,8 @@
ABSL_ARRAYSIZE(args));
}
-inline void SubstituteAndAppend(std::string* output, absl::string_view format,
+inline void SubstituteAndAppend(absl::Nonnull<std::string*> output,
+ absl::string_view format,
const substitute_internal::Arg& a0,
const substitute_internal::Arg& a1,
const substitute_internal::Arg& a2) {
@@ -265,7 +305,8 @@
ABSL_ARRAYSIZE(args));
}
-inline void SubstituteAndAppend(std::string* output, absl::string_view format,
+inline void SubstituteAndAppend(absl::Nonnull<std::string*> output,
+ absl::string_view format,
const substitute_internal::Arg& a0,
const substitute_internal::Arg& a1,
const substitute_internal::Arg& a2,
@@ -276,7 +317,8 @@
ABSL_ARRAYSIZE(args));
}
-inline void SubstituteAndAppend(std::string* output, absl::string_view format,
+inline void SubstituteAndAppend(absl::Nonnull<std::string*> output,
+ absl::string_view format,
const substitute_internal::Arg& a0,
const substitute_internal::Arg& a1,
const substitute_internal::Arg& a2,
@@ -288,27 +330,23 @@
ABSL_ARRAYSIZE(args));
}
-inline void SubstituteAndAppend(std::string* output, absl::string_view format,
- const substitute_internal::Arg& a0,
- const substitute_internal::Arg& a1,
- const substitute_internal::Arg& a2,
- const substitute_internal::Arg& a3,
- const substitute_internal::Arg& a4,
- const substitute_internal::Arg& a5) {
+inline void SubstituteAndAppend(
+ absl::Nonnull<std::string*> output, absl::string_view format,
+ const substitute_internal::Arg& a0, const substitute_internal::Arg& a1,
+ const substitute_internal::Arg& a2, const substitute_internal::Arg& a3,
+ const substitute_internal::Arg& a4, const substitute_internal::Arg& a5) {
const absl::string_view args[] = {a0.piece(), a1.piece(), a2.piece(),
a3.piece(), a4.piece(), a5.piece()};
substitute_internal::SubstituteAndAppendArray(output, format, args,
ABSL_ARRAYSIZE(args));
}
-inline void SubstituteAndAppend(std::string* output, absl::string_view format,
- const substitute_internal::Arg& a0,
- const substitute_internal::Arg& a1,
- const substitute_internal::Arg& a2,
- const substitute_internal::Arg& a3,
- const substitute_internal::Arg& a4,
- const substitute_internal::Arg& a5,
- const substitute_internal::Arg& a6) {
+inline void SubstituteAndAppend(
+ absl::Nonnull<std::string*> output, absl::string_view format,
+ const substitute_internal::Arg& a0, const substitute_internal::Arg& a1,
+ const substitute_internal::Arg& a2, const substitute_internal::Arg& a3,
+ const substitute_internal::Arg& a4, const substitute_internal::Arg& a5,
+ const substitute_internal::Arg& a6) {
const absl::string_view args[] = {a0.piece(), a1.piece(), a2.piece(),
a3.piece(), a4.piece(), a5.piece(),
a6.piece()};
@@ -317,7 +355,7 @@
}
inline void SubstituteAndAppend(
- std::string* output, absl::string_view format,
+ absl::Nonnull<std::string*> output, absl::string_view format,
const substitute_internal::Arg& a0, const substitute_internal::Arg& a1,
const substitute_internal::Arg& a2, const substitute_internal::Arg& a3,
const substitute_internal::Arg& a4, const substitute_internal::Arg& a5,
@@ -330,7 +368,7 @@
}
inline void SubstituteAndAppend(
- std::string* output, absl::string_view format,
+ absl::Nonnull<std::string*> output, absl::string_view format,
const substitute_internal::Arg& a0, const substitute_internal::Arg& a1,
const substitute_internal::Arg& a2, const substitute_internal::Arg& a3,
const substitute_internal::Arg& a4, const substitute_internal::Arg& a5,
@@ -344,7 +382,7 @@
}
inline void SubstituteAndAppend(
- std::string* output, absl::string_view format,
+ absl::Nonnull<std::string*> output, absl::string_view format,
const substitute_internal::Arg& a0, const substitute_internal::Arg& a1,
const substitute_internal::Arg& a2, const substitute_internal::Arg& a3,
const substitute_internal::Arg& a4, const substitute_internal::Arg& a5,
@@ -360,110 +398,129 @@
#if defined(ABSL_BAD_CALL_IF)
// This body of functions catches cases where the number of placeholders
// doesn't match the number of data arguments.
-void SubstituteAndAppend(std::string* output, const char* format)
- ABSL_BAD_CALL_IF(substitute_internal::PlaceholderBitmask(format) != 0,
- "There were no substitution arguments "
- "but this format string has a $[0-9] in it");
+void SubstituteAndAppend(absl::Nonnull<std::string*> output,
+ absl::Nonnull<const char*> format)
+ ABSL_BAD_CALL_IF(
+ substitute_internal::PlaceholderBitmask(format) != 0,
+ "There were no substitution arguments "
+ "but this format string either has a $[0-9] in it or contains "
+ "an unescaped $ character (use $$ instead)");
-void SubstituteAndAppend(std::string* output, const char* format,
+void SubstituteAndAppend(absl::Nonnull<std::string*> output,
+ absl::Nonnull<const char*> format,
const substitute_internal::Arg& a0)
ABSL_BAD_CALL_IF(substitute_internal::PlaceholderBitmask(format) != 1,
"There was 1 substitution argument given, but "
- "this format string is either missing its $0, or "
- "contains one of $1-$9");
+ "this format string is missing its $0, contains "
+ "one of $1-$9, or contains an unescaped $ character (use "
+ "$$ instead)");
-void SubstituteAndAppend(std::string* output, const char* format,
+void SubstituteAndAppend(absl::Nonnull<std::string*> output,
+ absl::Nonnull<const char*> format,
const substitute_internal::Arg& a0,
const substitute_internal::Arg& a1)
- ABSL_BAD_CALL_IF(substitute_internal::PlaceholderBitmask(format) != 3,
- "There were 2 substitution arguments given, but "
- "this format string is either missing its $0/$1, or "
- "contains one of $2-$9");
+ ABSL_BAD_CALL_IF(
+ substitute_internal::PlaceholderBitmask(format) != 3,
+ "There were 2 substitution arguments given, but this format string is "
+ "missing its $0/$1, contains one of $2-$9, or contains an "
+ "unescaped $ character (use $$ instead)");
-void SubstituteAndAppend(std::string* output, const char* format,
+void SubstituteAndAppend(absl::Nonnull<std::string*> output,
+ absl::Nonnull<const char*> format,
const substitute_internal::Arg& a0,
const substitute_internal::Arg& a1,
const substitute_internal::Arg& a2)
- ABSL_BAD_CALL_IF(substitute_internal::PlaceholderBitmask(format) != 7,
- "There were 3 substitution arguments given, but "
- "this format string is either missing its $0/$1/$2, or "
- "contains one of $3-$9");
+ ABSL_BAD_CALL_IF(
+ substitute_internal::PlaceholderBitmask(format) != 7,
+ "There were 3 substitution arguments given, but "
+ "this format string is missing its $0/$1/$2, contains one of "
+ "$3-$9, or contains an unescaped $ character (use $$ instead)");
-void SubstituteAndAppend(std::string* output, const char* format,
+void SubstituteAndAppend(absl::Nonnull<std::string*> output,
+ absl::Nonnull<const char*> format,
const substitute_internal::Arg& a0,
const substitute_internal::Arg& a1,
const substitute_internal::Arg& a2,
const substitute_internal::Arg& a3)
- ABSL_BAD_CALL_IF(substitute_internal::PlaceholderBitmask(format) != 15,
- "There were 4 substitution arguments given, but "
- "this format string is either missing its $0-$3, or "
- "contains one of $4-$9");
+ ABSL_BAD_CALL_IF(
+ substitute_internal::PlaceholderBitmask(format) != 15,
+ "There were 4 substitution arguments given, but "
+ "this format string is missing its $0-$3, contains one of "
+ "$4-$9, or contains an unescaped $ character (use $$ instead)");
-void SubstituteAndAppend(std::string* output, const char* format,
+void SubstituteAndAppend(absl::Nonnull<std::string*> output,
+ absl::Nonnull<const char*> format,
const substitute_internal::Arg& a0,
const substitute_internal::Arg& a1,
const substitute_internal::Arg& a2,
const substitute_internal::Arg& a3,
const substitute_internal::Arg& a4)
- ABSL_BAD_CALL_IF(substitute_internal::PlaceholderBitmask(format) != 31,
- "There were 5 substitution arguments given, but "
- "this format string is either missing its $0-$4, or "
- "contains one of $5-$9");
-
-void SubstituteAndAppend(std::string* output, const char* format,
- const substitute_internal::Arg& a0,
- const substitute_internal::Arg& a1,
- const substitute_internal::Arg& a2,
- const substitute_internal::Arg& a3,
- const substitute_internal::Arg& a4,
- const substitute_internal::Arg& a5)
- ABSL_BAD_CALL_IF(substitute_internal::PlaceholderBitmask(format) != 63,
- "There were 6 substitution arguments given, but "
- "this format string is either missing its $0-$5, or "
- "contains one of $6-$9");
+ ABSL_BAD_CALL_IF(
+ substitute_internal::PlaceholderBitmask(format) != 31,
+ "There were 5 substitution arguments given, but "
+ "this format string is missing its $0-$4, contains one of "
+ "$5-$9, or contains an unescaped $ character (use $$ instead)");
void SubstituteAndAppend(
- std::string* output, const char* format, const substitute_internal::Arg& a0,
- const substitute_internal::Arg& a1, const substitute_internal::Arg& a2,
- const substitute_internal::Arg& a3, const substitute_internal::Arg& a4,
- const substitute_internal::Arg& a5, const substitute_internal::Arg& a6)
- ABSL_BAD_CALL_IF(substitute_internal::PlaceholderBitmask(format) != 127,
- "There were 7 substitution arguments given, but "
- "this format string is either missing its $0-$6, or "
- "contains one of $7-$9");
+ absl::Nonnull<std::string*> output, absl::Nonnull<const char*> format,
+ const substitute_internal::Arg& a0, const substitute_internal::Arg& a1,
+ const substitute_internal::Arg& a2, const substitute_internal::Arg& a3,
+ const substitute_internal::Arg& a4, const substitute_internal::Arg& a5)
+ ABSL_BAD_CALL_IF(
+ substitute_internal::PlaceholderBitmask(format) != 63,
+ "There were 6 substitution arguments given, but "
+ "this format string is missing its $0-$5, contains one of "
+ "$6-$9, or contains an unescaped $ character (use $$ instead)");
void SubstituteAndAppend(
- std::string* output, const char* format, const substitute_internal::Arg& a0,
- const substitute_internal::Arg& a1, const substitute_internal::Arg& a2,
- const substitute_internal::Arg& a3, const substitute_internal::Arg& a4,
- const substitute_internal::Arg& a5, const substitute_internal::Arg& a6,
- const substitute_internal::Arg& a7)
- ABSL_BAD_CALL_IF(substitute_internal::PlaceholderBitmask(format) != 255,
- "There were 8 substitution arguments given, but "
- "this format string is either missing its $0-$7, or "
- "contains one of $8-$9");
+ absl::Nonnull<std::string*> output, absl::Nonnull<const char*> format,
+ const substitute_internal::Arg& a0, const substitute_internal::Arg& a1,
+ const substitute_internal::Arg& a2, const substitute_internal::Arg& a3,
+ const substitute_internal::Arg& a4, const substitute_internal::Arg& a5,
+ const substitute_internal::Arg& a6)
+ ABSL_BAD_CALL_IF(
+ substitute_internal::PlaceholderBitmask(format) != 127,
+ "There were 7 substitution arguments given, but "
+ "this format string is missing its $0-$6, contains one of "
+ "$7-$9, or contains an unescaped $ character (use $$ instead)");
void SubstituteAndAppend(
- std::string* output, const char* format, const substitute_internal::Arg& a0,
- const substitute_internal::Arg& a1, const substitute_internal::Arg& a2,
- const substitute_internal::Arg& a3, const substitute_internal::Arg& a4,
- const substitute_internal::Arg& a5, const substitute_internal::Arg& a6,
- const substitute_internal::Arg& a7, const substitute_internal::Arg& a8)
+ absl::Nonnull<std::string*> output, absl::Nonnull<const char*> format,
+ const substitute_internal::Arg& a0, const substitute_internal::Arg& a1,
+ const substitute_internal::Arg& a2, const substitute_internal::Arg& a3,
+ const substitute_internal::Arg& a4, const substitute_internal::Arg& a5,
+ const substitute_internal::Arg& a6, const substitute_internal::Arg& a7)
+ ABSL_BAD_CALL_IF(
+ substitute_internal::PlaceholderBitmask(format) != 255,
+ "There were 8 substitution arguments given, but "
+ "this format string is missing its $0-$7, contains one of "
+ "$8-$9, or contains an unescaped $ character (use $$ instead)");
+
+void SubstituteAndAppend(
+ absl::Nonnull<std::string*> output, absl::Nonnull<const char*> format,
+ const substitute_internal::Arg& a0, const substitute_internal::Arg& a1,
+ const substitute_internal::Arg& a2, const substitute_internal::Arg& a3,
+ const substitute_internal::Arg& a4, const substitute_internal::Arg& a5,
+ const substitute_internal::Arg& a6, const substitute_internal::Arg& a7,
+ const substitute_internal::Arg& a8)
ABSL_BAD_CALL_IF(
substitute_internal::PlaceholderBitmask(format) != 511,
"There were 9 substitution arguments given, but "
- "this format string is either missing its $0-$8, or contains a $9");
+ "this format string is missing its $0-$8, contains a $9, or "
+ "contains an unescaped $ character (use $$ instead)");
void SubstituteAndAppend(
- std::string* output, const char* format, const substitute_internal::Arg& a0,
- const substitute_internal::Arg& a1, const substitute_internal::Arg& a2,
- const substitute_internal::Arg& a3, const substitute_internal::Arg& a4,
- const substitute_internal::Arg& a5, const substitute_internal::Arg& a6,
- const substitute_internal::Arg& a7, const substitute_internal::Arg& a8,
- const substitute_internal::Arg& a9)
- ABSL_BAD_CALL_IF(substitute_internal::PlaceholderBitmask(format) != 1023,
- "There were 10 substitution arguments given, but this "
- "format string doesn't contain all of $0 through $9");
+ absl::Nonnull<std::string*> output, absl::Nonnull<const char*> format,
+ const substitute_internal::Arg& a0, const substitute_internal::Arg& a1,
+ const substitute_internal::Arg& a2, const substitute_internal::Arg& a3,
+ const substitute_internal::Arg& a4, const substitute_internal::Arg& a5,
+ const substitute_internal::Arg& a6, const substitute_internal::Arg& a7,
+ const substitute_internal::Arg& a8, const substitute_internal::Arg& a9)
+ ABSL_BAD_CALL_IF(
+ substitute_internal::PlaceholderBitmask(format) != 1023,
+ "There were 10 substitution arguments given, but this "
+ "format string either doesn't contain all of $0 through $9 or "
+ "contains an unescaped $ character (use $$ instead)");
#endif // ABSL_BAD_CALL_IF
// Substitute()
@@ -586,89 +643,100 @@
#if defined(ABSL_BAD_CALL_IF)
// This body of functions catches cases where the number of placeholders
// doesn't match the number of data arguments.
-std::string Substitute(const char* format)
+std::string Substitute(absl::Nonnull<const char*> format)
ABSL_BAD_CALL_IF(substitute_internal::PlaceholderBitmask(format) != 0,
"There were no substitution arguments "
- "but this format string has a $[0-9] in it");
+ "but this format string either has a $[0-9] in it or "
+ "contains an unescaped $ character (use $$ instead)");
-std::string Substitute(const char* format, const substitute_internal::Arg& a0)
- ABSL_BAD_CALL_IF(substitute_internal::PlaceholderBitmask(format) != 1,
- "There was 1 substitution argument given, but "
- "this format string is either missing its $0, or "
- "contains one of $1-$9");
+std::string Substitute(absl::Nonnull<const char*> format,
+ const substitute_internal::Arg& a0)
+ ABSL_BAD_CALL_IF(
+ substitute_internal::PlaceholderBitmask(format) != 1,
+ "There was 1 substitution argument given, but "
+ "this format string is missing its $0, contains one of $1-$9, "
+ "or contains an unescaped $ character (use $$ instead)");
-std::string Substitute(const char* format, const substitute_internal::Arg& a0,
+std::string Substitute(absl::Nonnull<const char*> format,
+ const substitute_internal::Arg& a0,
const substitute_internal::Arg& a1)
- ABSL_BAD_CALL_IF(substitute_internal::PlaceholderBitmask(format) != 3,
- "There were 2 substitution arguments given, but "
- "this format string is either missing its $0/$1, or "
- "contains one of $2-$9");
+ ABSL_BAD_CALL_IF(
+ substitute_internal::PlaceholderBitmask(format) != 3,
+ "There were 2 substitution arguments given, but "
+ "this format string is missing its $0/$1, contains one of "
+ "$2-$9, or contains an unescaped $ character (use $$ instead)");
-std::string Substitute(const char* format, const substitute_internal::Arg& a0,
+std::string Substitute(absl::Nonnull<const char*> format,
+ const substitute_internal::Arg& a0,
const substitute_internal::Arg& a1,
const substitute_internal::Arg& a2)
- ABSL_BAD_CALL_IF(substitute_internal::PlaceholderBitmask(format) != 7,
- "There were 3 substitution arguments given, but "
- "this format string is either missing its $0/$1/$2, or "
- "contains one of $3-$9");
+ ABSL_BAD_CALL_IF(
+ substitute_internal::PlaceholderBitmask(format) != 7,
+ "There were 3 substitution arguments given, but "
+ "this format string is missing its $0/$1/$2, contains one of "
+ "$3-$9, or contains an unescaped $ character (use $$ instead)");
-std::string Substitute(const char* format, const substitute_internal::Arg& a0,
+std::string Substitute(absl::Nonnull<const char*> format,
+ const substitute_internal::Arg& a0,
const substitute_internal::Arg& a1,
const substitute_internal::Arg& a2,
const substitute_internal::Arg& a3)
- ABSL_BAD_CALL_IF(substitute_internal::PlaceholderBitmask(format) != 15,
- "There were 4 substitution arguments given, but "
- "this format string is either missing its $0-$3, or "
- "contains one of $4-$9");
+ ABSL_BAD_CALL_IF(
+ substitute_internal::PlaceholderBitmask(format) != 15,
+ "There were 4 substitution arguments given, but "
+ "this format string is missing its $0-$3, contains one of "
+ "$4-$9, or contains an unescaped $ character (use $$ instead)");
-std::string Substitute(const char* format, const substitute_internal::Arg& a0,
+std::string Substitute(absl::Nonnull<const char*> format,
+ const substitute_internal::Arg& a0,
const substitute_internal::Arg& a1,
const substitute_internal::Arg& a2,
const substitute_internal::Arg& a3,
const substitute_internal::Arg& a4)
- ABSL_BAD_CALL_IF(substitute_internal::PlaceholderBitmask(format) != 31,
- "There were 5 substitution arguments given, but "
- "this format string is either missing its $0-$4, or "
- "contains one of $5-$9");
+ ABSL_BAD_CALL_IF(
+ substitute_internal::PlaceholderBitmask(format) != 31,
+ "There were 5 substitution arguments given, but "
+ "this format string is missing its $0-$4, contains one of "
+ "$5-$9, or contains an unescaped $ character (use $$ instead)");
-std::string Substitute(const char* format, const substitute_internal::Arg& a0,
+std::string Substitute(absl::Nonnull<const char*> format,
+ const substitute_internal::Arg& a0,
const substitute_internal::Arg& a1,
const substitute_internal::Arg& a2,
const substitute_internal::Arg& a3,
const substitute_internal::Arg& a4,
const substitute_internal::Arg& a5)
- ABSL_BAD_CALL_IF(substitute_internal::PlaceholderBitmask(format) != 63,
- "There were 6 substitution arguments given, but "
- "this format string is either missing its $0-$5, or "
- "contains one of $6-$9");
-
-std::string Substitute(const char* format, const substitute_internal::Arg& a0,
- const substitute_internal::Arg& a1,
- const substitute_internal::Arg& a2,
- const substitute_internal::Arg& a3,
- const substitute_internal::Arg& a4,
- const substitute_internal::Arg& a5,
- const substitute_internal::Arg& a6)
- ABSL_BAD_CALL_IF(substitute_internal::PlaceholderBitmask(format) != 127,
- "There were 7 substitution arguments given, but "
- "this format string is either missing its $0-$6, or "
- "contains one of $7-$9");
-
-std::string Substitute(const char* format, const substitute_internal::Arg& a0,
- const substitute_internal::Arg& a1,
- const substitute_internal::Arg& a2,
- const substitute_internal::Arg& a3,
- const substitute_internal::Arg& a4,
- const substitute_internal::Arg& a5,
- const substitute_internal::Arg& a6,
- const substitute_internal::Arg& a7)
- ABSL_BAD_CALL_IF(substitute_internal::PlaceholderBitmask(format) != 255,
- "There were 8 substitution arguments given, but "
- "this format string is either missing its $0-$7, or "
- "contains one of $8-$9");
+ ABSL_BAD_CALL_IF(
+ substitute_internal::PlaceholderBitmask(format) != 63,
+ "There were 6 substitution arguments given, but "
+ "this format string is missing its $0-$5, contains one of "
+ "$6-$9, or contains an unescaped $ character (use $$ instead)");
std::string Substitute(
- const char* format, const substitute_internal::Arg& a0,
+ absl::Nonnull<const char*> format, const substitute_internal::Arg& a0,
+ const substitute_internal::Arg& a1, const substitute_internal::Arg& a2,
+ const substitute_internal::Arg& a3, const substitute_internal::Arg& a4,
+ const substitute_internal::Arg& a5, const substitute_internal::Arg& a6)
+ ABSL_BAD_CALL_IF(
+ substitute_internal::PlaceholderBitmask(format) != 127,
+ "There were 7 substitution arguments given, but "
+ "this format string is missing its $0-$6, contains one of "
+ "$7-$9, or contains an unescaped $ character (use $$ instead)");
+
+std::string Substitute(
+ absl::Nonnull<const char*> format, const substitute_internal::Arg& a0,
+ const substitute_internal::Arg& a1, const substitute_internal::Arg& a2,
+ const substitute_internal::Arg& a3, const substitute_internal::Arg& a4,
+ const substitute_internal::Arg& a5, const substitute_internal::Arg& a6,
+ const substitute_internal::Arg& a7)
+ ABSL_BAD_CALL_IF(
+ substitute_internal::PlaceholderBitmask(format) != 255,
+ "There were 8 substitution arguments given, but "
+ "this format string is missing its $0-$7, contains one of "
+ "$8-$9, or contains an unescaped $ character (use $$ instead)");
+
+std::string Substitute(
+ absl::Nonnull<const char*> format, const substitute_internal::Arg& a0,
const substitute_internal::Arg& a1, const substitute_internal::Arg& a2,
const substitute_internal::Arg& a3, const substitute_internal::Arg& a4,
const substitute_internal::Arg& a5, const substitute_internal::Arg& a6,
@@ -676,18 +744,21 @@
ABSL_BAD_CALL_IF(
substitute_internal::PlaceholderBitmask(format) != 511,
"There were 9 substitution arguments given, but "
- "this format string is either missing its $0-$8, or contains a $9");
+ "this format string is missing its $0-$8, contains a $9, or "
+ "contains an unescaped $ character (use $$ instead)");
std::string Substitute(
- const char* format, const substitute_internal::Arg& a0,
+ absl::Nonnull<const char*> format, const substitute_internal::Arg& a0,
const substitute_internal::Arg& a1, const substitute_internal::Arg& a2,
const substitute_internal::Arg& a3, const substitute_internal::Arg& a4,
const substitute_internal::Arg& a5, const substitute_internal::Arg& a6,
const substitute_internal::Arg& a7, const substitute_internal::Arg& a8,
const substitute_internal::Arg& a9)
- ABSL_BAD_CALL_IF(substitute_internal::PlaceholderBitmask(format) != 1023,
- "There were 10 substitution arguments given, but this "
- "format string doesn't contain all of $0 through $9");
+ ABSL_BAD_CALL_IF(
+ substitute_internal::PlaceholderBitmask(format) != 1023,
+ "There were 10 substitution arguments given, but this "
+ "format string either doesn't contain all of $0 through $9 or "
+ "contains an unescaped $ character (use $$ instead)");
#endif // ABSL_BAD_CALL_IF
ABSL_NAMESPACE_END
diff --git a/absl/strings/substitute_test.cc b/absl/strings/substitute_test.cc
index 442c921..70f9119 100644
--- a/absl/strings/substitute_test.cc
+++ b/absl/strings/substitute_test.cc
@@ -15,13 +15,26 @@
#include "absl/strings/substitute.h"
#include <cstdint>
+#include <cstring>
+#include <string>
#include <vector>
#include "gtest/gtest.h"
#include "absl/strings/str_cat.h"
+#include "absl/strings/string_view.h"
namespace {
+struct MyStruct {
+ template <typename Sink>
+ friend void AbslStringify(Sink& sink, const MyStruct& s) {
+ sink.Append("MyStruct{.value = ");
+ sink.Append(absl::StrCat(s.value));
+ sink.Append("}");
+ }
+ int value;
+};
+
TEST(SubstituteTest, Substitute) {
// Basic.
EXPECT_EQ("Hello, world!", absl::Substitute("$0, $1!", "Hello", "world"));
@@ -70,7 +83,7 @@
// Volatile Pointer.
// Like C++ streamed I/O, such pointers implicitly become bool
volatile int vol = 237;
- volatile int *volatile volptr = &vol;
+ volatile int* volatile volptr = &vol;
str = absl::Substitute("$0", volptr);
EXPECT_EQ("true", str);
@@ -128,6 +141,11 @@
const char* null_cstring = nullptr;
EXPECT_EQ("Text: ''", absl::Substitute("Text: '$0'", null_cstring));
+
+ MyStruct s1 = MyStruct{17};
+ MyStruct s2 = MyStruct{1043};
+ EXPECT_EQ("MyStruct{.value = 17}, MyStruct{.value = 1043}",
+ absl::Substitute("$0, $1", s1, s2));
}
TEST(SubstituteTest, SubstituteAndAppend) {
@@ -171,6 +189,12 @@
absl::SubstituteAndAppend(&str, "$0 $1 $2 $3 $4 $5 $6 $7 $8 $9", "a", "b",
"c", "d", "e", "f", "g", "h", "i", "j");
EXPECT_EQ("a b c d e f g h i j", str);
+
+ str.clear();
+ MyStruct s1 = MyStruct{17};
+ MyStruct s2 = MyStruct{1043};
+ absl::SubstituteAndAppend(&str, "$0, $1", s1, s2);
+ EXPECT_EQ("MyStruct{.value = 17}, MyStruct{.value = 1043}", str);
}
TEST(SubstituteTest, VectorBoolRef) {
@@ -184,7 +208,67 @@
EXPECT_EQ("Logic be like: true false true false", str);
}
-#ifdef GTEST_HAS_DEATH_TEST
+TEST(SubstituteTest, Enums) {
+ enum UnscopedEnum { kEnum0 = 0, kEnum1 = 1 };
+ EXPECT_EQ("0 1", absl::Substitute("$0 $1", UnscopedEnum::kEnum0,
+ UnscopedEnum::kEnum1));
+
+ enum class ScopedEnum { kEnum0 = 0, kEnum1 = 1 };
+ EXPECT_EQ("0 1",
+ absl::Substitute("$0 $1", ScopedEnum::kEnum0, ScopedEnum::kEnum1));
+
+ enum class ScopedEnumInt32 : int32_t { kEnum0 = 989, kEnum1 = INT32_MIN };
+ EXPECT_EQ("989 -2147483648",
+ absl::Substitute("$0 $1", ScopedEnumInt32::kEnum0,
+ ScopedEnumInt32::kEnum1));
+
+ enum class ScopedEnumUInt32 : uint32_t { kEnum0 = 1, kEnum1 = UINT32_MAX };
+ EXPECT_EQ("1 4294967295", absl::Substitute("$0 $1", ScopedEnumUInt32::kEnum0,
+ ScopedEnumUInt32::kEnum1));
+
+ enum class ScopedEnumInt64 : int64_t { kEnum0 = -1, kEnum1 = 42949672950 };
+ EXPECT_EQ("-1 42949672950", absl::Substitute("$0 $1", ScopedEnumInt64::kEnum0,
+ ScopedEnumInt64::kEnum1));
+
+ enum class ScopedEnumUInt64 : uint64_t { kEnum0 = 1, kEnum1 = 42949672950 };
+ EXPECT_EQ("1 42949672950", absl::Substitute("$0 $1", ScopedEnumUInt64::kEnum0,
+ ScopedEnumUInt64::kEnum1));
+
+ enum class ScopedEnumChar : signed char { kEnum0 = -1, kEnum1 = 1 };
+ EXPECT_EQ("-1 1", absl::Substitute("$0 $1", ScopedEnumChar::kEnum0,
+ ScopedEnumChar::kEnum1));
+
+ enum class ScopedEnumUChar : unsigned char {
+ kEnum0 = 0,
+ kEnum1 = 1,
+ kEnumMax = 255
+ };
+ EXPECT_EQ("0 1 255", absl::Substitute("$0 $1 $2", ScopedEnumUChar::kEnum0,
+ ScopedEnumUChar::kEnum1,
+ ScopedEnumUChar::kEnumMax));
+
+ enum class ScopedEnumInt16 : int16_t { kEnum0 = -100, kEnum1 = 10000 };
+ EXPECT_EQ("-100 10000", absl::Substitute("$0 $1", ScopedEnumInt16::kEnum0,
+ ScopedEnumInt16::kEnum1));
+
+ enum class ScopedEnumUInt16 : uint16_t { kEnum0 = 0, kEnum1 = 10000 };
+ EXPECT_EQ("0 10000", absl::Substitute("$0 $1", ScopedEnumUInt16::kEnum0,
+ ScopedEnumUInt16::kEnum1));
+}
+
+enum class EnumWithStringify { Many = 0, Choices = 1 };
+
+template <typename Sink>
+void AbslStringify(Sink& sink, EnumWithStringify e) {
+ sink.Append(e == EnumWithStringify::Many ? "Many" : "Choices");
+}
+
+TEST(SubstituteTest, AbslStringifyWithEnum) {
+ const auto e = EnumWithStringify::Choices;
+ EXPECT_EQ(absl::Substitute("$0", e), "Choices");
+}
+
+#if GTEST_HAS_DEATH_TEST
TEST(SubstituteDeathTest, SubstituteDeath) {
EXPECT_DEBUG_DEATH(
diff --git a/absl/synchronization/BUILD.bazel b/absl/synchronization/BUILD.bazel
index 92e2448..dafeba3 100644
--- a/absl/synchronization/BUILD.bazel
+++ b/absl/synchronization/BUILD.bazel
@@ -14,7 +14,6 @@
# limitations under the License.
#
-load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library", "cc_test")
load(
"//absl:copts/configure_copts.bzl",
"ABSL_DEFAULT_COPTS",
@@ -22,7 +21,14 @@
"ABSL_TEST_COPTS",
)
-package(default_visibility = ["//visibility:public"])
+package(
+ default_visibility = ["//visibility:private"],
+ features = [
+ "header_modules",
+ "layering_check",
+ "parse_headers",
+ ],
+)
licenses(["notice"])
@@ -35,11 +41,10 @@
hdrs = [
"internal/graphcycles.h",
],
- copts = ABSL_DEFAULT_COPTS,
+ copts = ABSL_DEFAULT_COPTS + select({
+ "//conditions:default": [],
+ }),
linkopts = ABSL_DEFAULT_LINKOPTS,
- visibility = [
- "//absl:__subpackages__",
- ],
deps = [
"//absl/base",
"//absl/base:base_internal",
@@ -52,16 +57,36 @@
cc_library(
name = "kernel_timeout_internal",
+ srcs = ["internal/kernel_timeout.cc"],
hdrs = ["internal/kernel_timeout.h"],
copts = ABSL_DEFAULT_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
visibility = [
- "//absl/synchronization:__pkg__",
],
deps = [
+ "//absl/base",
+ "//absl/base:config",
"//absl/base:core_headers",
"//absl/base:raw_logging_internal",
"//absl/time",
+ ] + select({
+ "//conditions:default": [],
+ }),
+)
+
+cc_test(
+ name = "kernel_timeout_internal_test",
+ srcs = ["internal/kernel_timeout_test.cc"],
+ copts = ABSL_TEST_COPTS,
+ flaky = 1,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ ":kernel_timeout_internal",
+ "//absl/base:config",
+ "//absl/random",
+ "//absl/time",
+ "@com_google_googletest//:gtest",
+ "@com_google_googletest//:gtest_main",
],
)
@@ -71,8 +96,13 @@
"barrier.cc",
"blocking_counter.cc",
"internal/create_thread_identity.cc",
+ "internal/futex_waiter.cc",
"internal/per_thread_sem.cc",
- "internal/waiter.cc",
+ "internal/pthread_waiter.cc",
+ "internal/sem_waiter.cc",
+ "internal/stdcpp_waiter.cc",
+ "internal/waiter_base.cc",
+ "internal/win32_waiter.cc",
"mutex.cc",
"notification.cc",
],
@@ -81,8 +111,14 @@
"blocking_counter.h",
"internal/create_thread_identity.h",
"internal/futex.h",
+ "internal/futex_waiter.h",
"internal/per_thread_sem.h",
+ "internal/pthread_waiter.h",
+ "internal/sem_waiter.h",
+ "internal/stdcpp_waiter.h",
"internal/waiter.h",
+ "internal/waiter_base.h",
+ "internal/win32_waiter.h",
"mutex.h",
"notification.h",
],
@@ -93,6 +129,7 @@
"//absl:wasm": [],
"//conditions:default": ["-pthread"],
}) + ABSL_DEFAULT_LINKOPTS,
+ visibility = ["//visibility:public"],
deps = [
":graphcycles_internal",
":kernel_timeout_internal",
@@ -107,7 +144,9 @@
"//absl/debugging:stacktrace",
"//absl/debugging:symbolize",
"//absl/time",
- ],
+ ] + select({
+ "//conditions:default": [],
+ }),
)
cc_test(
@@ -116,9 +155,13 @@
srcs = ["barrier_test.cc"],
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
+ tags = [
+ "no_test_wasm", # b/122473323
+ ],
deps = [
":synchronization",
"//absl/time",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -129,24 +172,28 @@
srcs = ["blocking_counter_test.cc"],
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
+ tags = [
+ "no_test_wasm", # b/122473323
+ ],
deps = [
":synchronization",
"//absl/time",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
cc_binary(
name = "blocking_counter_benchmark",
- testonly = 1,
+ testonly = True,
srcs = ["blocking_counter_benchmark.cc"],
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
tags = ["benchmark"],
- visibility = ["//visibility:private"],
deps = [
":synchronization",
":thread_pool",
+ "//absl/base:no_destructor",
"@com_github_google_benchmark//:benchmark_main",
],
)
@@ -160,7 +207,9 @@
deps = [
":graphcycles_internal",
"//absl/base:core_headers",
- "//absl/base:raw_logging_internal",
+ "//absl/log",
+ "//absl/log:check",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -177,12 +226,13 @@
":graphcycles_internal",
"//absl/base:raw_logging_internal",
"@com_github_google_benchmark//:benchmark_main",
+ "@com_google_googletest//:gtest",
],
)
cc_library(
name = "thread_pool",
- testonly = 1,
+ testonly = True,
hdrs = ["internal/thread_pool.h"],
linkopts = ABSL_DEFAULT_LINKOPTS,
visibility = [
@@ -191,6 +241,7 @@
deps = [
":synchronization",
"//absl/base:core_headers",
+ "//absl/functional:any_invocable",
],
)
@@ -199,6 +250,7 @@
size = "large",
srcs = ["mutex_test.cc"],
copts = ABSL_TEST_COPTS,
+ flaky = 1,
linkopts = ABSL_DEFAULT_LINKOPTS,
shard_count = 25,
deps = [
@@ -207,27 +259,42 @@
"//absl/base",
"//absl/base:config",
"//absl/base:core_headers",
- "//absl/base:raw_logging_internal",
+ "//absl/log",
+ "//absl/log:check",
"//absl/memory",
"//absl/time",
+ "@com_google_googletest//:gtest",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "mutex_method_pointer_test",
+ srcs = ["mutex_method_pointer_test.cc"],
+ copts = ABSL_TEST_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ ":synchronization",
+ "//absl/base:config",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
cc_library(
name = "mutex_benchmark_common",
- testonly = 1,
+ testonly = True,
srcs = ["mutex_benchmark.cc"],
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
visibility = [
- "//absl/synchronization:__pkg__",
],
deps = [
":synchronization",
":thread_pool",
"//absl/base",
"//absl/base:config",
+ "//absl/base:no_destructor",
"@com_github_google_benchmark//:benchmark_main",
],
alwayslink = 1,
@@ -235,10 +302,9 @@
cc_binary(
name = "mutex_benchmark",
- testonly = 1,
+ testonly = True,
copts = ABSL_DEFAULT_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
- visibility = ["//visibility:private"],
deps = [
":mutex_benchmark_common",
],
@@ -249,20 +315,25 @@
size = "small",
srcs = ["notification_test.cc"],
copts = ABSL_TEST_COPTS,
+ flaky = 1,
linkopts = ABSL_DEFAULT_LINKOPTS,
+ tags = ["no_test_lexan"],
deps = [
":synchronization",
"//absl/time",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
cc_library(
name = "per_thread_sem_test_common",
- testonly = 1,
+ testonly = True,
srcs = ["internal/per_thread_sem_test.cc"],
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
+ visibility = [
+ ],
deps = [
":synchronization",
"//absl/base",
@@ -276,9 +347,12 @@
cc_test(
name = "per_thread_sem_test",
- size = "medium",
+ size = "large",
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
+ tags = [
+ "no_test_wasm",
+ ],
deps = [
":per_thread_sem_test_common",
":synchronization",
@@ -289,16 +363,37 @@
)
cc_test(
+ name = "waiter_test",
+ srcs = ["internal/waiter_test.cc"],
+ copts = ABSL_TEST_COPTS,
+ flaky = 1,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ ":kernel_timeout_internal",
+ ":synchronization",
+ ":thread_pool",
+ "//absl/base:config",
+ "//absl/random",
+ "//absl/time",
+ "@com_google_googletest//:gtest",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_test(
name = "lifetime_test",
srcs = [
"lifetime_test.cc",
],
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
- tags = ["no_test_ios_x86_64"],
+ tags = [
+ "no_test_ios_x86_64",
+ "no_test_wasm",
+ ],
deps = [
":synchronization",
"//absl/base:core_headers",
- "//absl/base:raw_logging_internal",
+ "//absl/log:check",
],
)
diff --git a/absl/synchronization/CMakeLists.txt b/absl/synchronization/CMakeLists.txt
index 605efe2..a0f64e5 100644
--- a/absl/synchronization/CMakeLists.txt
+++ b/absl/synchronization/CMakeLists.txt
@@ -14,6 +14,7 @@
# limitations under the License.
#
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
graphcycles_internal
@@ -32,19 +33,39 @@
absl::raw_logging_internal
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
kernel_timeout_internal
HDRS
"internal/kernel_timeout.h"
+ SRCS
+ "internal/kernel_timeout.cc"
COPTS
${ABSL_DEFAULT_COPTS}
DEPS
+ absl::base
+ absl::config
absl::core_headers
absl::raw_logging_internal
absl::time
)
+absl_cc_test(
+ NAME
+ kernel_timeout_internal_test
+ SRCS
+ "internal/kernel_timeout_test.cc"
+ COPTS
+ ${ABSL_TEST_COPTS}
+ DEPS
+ absl::kernel_timeout_internal
+ absl::config
+ absl::random_random
+ absl::time
+ GTest::gmock_main
+)
+
absl_cc_library(
NAME
synchronization
@@ -53,16 +74,27 @@
"blocking_counter.h"
"internal/create_thread_identity.h"
"internal/futex.h"
+ "internal/futex_waiter.h"
"internal/per_thread_sem.h"
+ "internal/pthread_waiter.h"
+ "internal/sem_waiter.h"
+ "internal/stdcpp_waiter.h"
"internal/waiter.h"
+ "internal/waiter_base.h"
+ "internal/win32_waiter.h"
"mutex.h"
"notification.h"
SRCS
"barrier.cc"
"blocking_counter.cc"
"internal/create_thread_identity.cc"
+ "internal/futex_waiter.cc"
"internal/per_thread_sem.cc"
- "internal/waiter.cc"
+ "internal/pthread_waiter.cc"
+ "internal/sem_waiter.cc"
+ "internal/stdcpp_waiter.cc"
+ "internal/waiter_base.cc"
+ "internal/win32_waiter.cc"
"notification.cc"
"mutex.cc"
COPTS
@@ -119,12 +151,14 @@
COPTS
${ABSL_TEST_COPTS}
DEPS
- absl::graphcycles_internal
+ absl::check
absl::core_headers
- absl::raw_logging_internal
+ absl::graphcycles_internal
+ absl::log
GTest::gmock_main
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
thread_pool
@@ -133,8 +167,9 @@
COPTS
${ABSL_DEFAULT_COPTS}
DEPS
- absl::synchronization
+ absl::any_invocable
absl::core_headers
+ absl::synchronization
TESTONLY
)
@@ -149,16 +184,31 @@
absl::synchronization
absl::thread_pool
absl::base
+ absl::check
absl::config
absl::core_headers
+ absl::log
absl::memory
- absl::raw_logging_internal
absl::time
GTest::gmock_main
)
absl_cc_test(
NAME
+ mutex_method_pointer_test
+ SRCS
+ "mutex_method_pointer_test.cc"
+ COPTS
+ ${ABSL_TEST_COPTS}
+ DEPS
+ absl::base
+ absl::config
+ absl::synchronization
+ GTest::gmock_main
+)
+
+absl_cc_test(
+ NAME
notification_test
SRCS
"notification_test.cc"
@@ -170,6 +220,7 @@
GTest::gmock_main
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
per_thread_sem_test_common
@@ -204,6 +255,23 @@
absl_cc_test(
NAME
+ waiter_test
+ SRCS
+ "internal/waiter_test.cc"
+ COPTS
+ ${ABSL_TEST_COPTS}
+ DEPS
+ absl::config
+ absl::kernel_timeout_internal
+ absl::random_random
+ absl::synchronization
+ absl::thread_pool
+ absl::time
+ GTest::gmock_main
+)
+
+absl_cc_test(
+ NAME
lifetime_test
SRCS
"lifetime_test.cc"
@@ -212,5 +280,5 @@
DEPS
absl::synchronization
absl::core_headers
- absl::raw_logging_internal
+ absl::check
)
diff --git a/absl/synchronization/blocking_counter_benchmark.cc b/absl/synchronization/blocking_counter_benchmark.cc
index b504d1a..ea2bf9f 100644
--- a/absl/synchronization/blocking_counter_benchmark.cc
+++ b/absl/synchronization/blocking_counter_benchmark.cc
@@ -14,6 +14,7 @@
#include <limits>
+#include "absl/base/no_destructor.h"
#include "absl/synchronization/blocking_counter.h"
#include "absl/synchronization/internal/thread_pool.h"
#include "benchmark/benchmark.h"
@@ -39,8 +40,8 @@
->Arg(256);
void BM_BlockingCounter_DecrementCount(benchmark::State& state) {
- static absl::BlockingCounter* counter =
- new absl::BlockingCounter{std::numeric_limits<int>::max()};
+ static absl::NoDestructor<absl::BlockingCounter> counter(
+ std::numeric_limits<int>::max());
for (auto _ : state) {
counter->DecrementCount();
}
diff --git a/absl/synchronization/internal/create_thread_identity.cc b/absl/synchronization/internal/create_thread_identity.cc
index 53a71b3..eacaa28 100644
--- a/absl/synchronization/internal/create_thread_identity.cc
+++ b/absl/synchronization/internal/create_thread_identity.cc
@@ -13,10 +13,12 @@
// limitations under the License.
#include <stdint.h>
+
#include <new>
// This file is a no-op if the required LowLevelAlloc support is missing.
#include "absl/base/internal/low_level_alloc.h"
+#include "absl/synchronization/internal/waiter.h"
#ifndef ABSL_LOW_LEVEL_ALLOC_MISSING
#include <string.h>
@@ -38,7 +40,7 @@
// A per-thread destructor for reclaiming associated ThreadIdentity objects.
// Since we must preserve their storage we cache them for re-use.
-void ReclaimThreadIdentity(void* v) {
+static void ReclaimThreadIdentity(void* v) {
base_internal::ThreadIdentity* identity =
static_cast<base_internal::ThreadIdentity*>(v);
@@ -48,8 +50,6 @@
base_internal::LowLevelAlloc::Free(identity->per_thread_synch.all_locks);
}
- PerThreadSem::Destroy(identity);
-
// We must explicitly clear the current thread's identity:
// (a) Subsequent (unrelated) per-thread destructors may require an identity.
// We must guarantee a new identity is used in this case (this instructor
@@ -71,7 +71,15 @@
return (addr + align - 1) & ~(align - 1);
}
-static void ResetThreadIdentity(base_internal::ThreadIdentity* identity) {
+void OneTimeInitThreadIdentity(base_internal::ThreadIdentity* identity) {
+ PerThreadSem::Init(identity);
+ identity->ticker.store(0, std::memory_order_relaxed);
+ identity->wait_start.store(0, std::memory_order_relaxed);
+ identity->is_idle.store(false, std::memory_order_relaxed);
+}
+
+static void ResetThreadIdentityBetweenReuse(
+ base_internal::ThreadIdentity* identity) {
base_internal::PerThreadSynch* pts = &identity->per_thread_synch;
pts->next = nullptr;
pts->skip = nullptr;
@@ -116,8 +124,9 @@
identity = reinterpret_cast<base_internal::ThreadIdentity*>(
RoundUp(reinterpret_cast<intptr_t>(allocation),
base_internal::PerThreadSynch::kAlignment));
+ OneTimeInitThreadIdentity(identity);
}
- ResetThreadIdentity(identity);
+ ResetThreadIdentityBetweenReuse(identity);
return identity;
}
@@ -127,7 +136,6 @@
// REQUIRES: CurrentThreadIdentity(false) == nullptr
base_internal::ThreadIdentity* CreateThreadIdentity() {
base_internal::ThreadIdentity* identity = NewThreadIdentity();
- PerThreadSem::Init(identity);
// Associate the value with the current thread, and attach our destructor.
base_internal::SetCurrentThreadIdentity(identity, ReclaimThreadIdentity);
return identity;
diff --git a/absl/synchronization/internal/create_thread_identity.h b/absl/synchronization/internal/create_thread_identity.h
index e121f68..4cfde09 100644
--- a/absl/synchronization/internal/create_thread_identity.h
+++ b/absl/synchronization/internal/create_thread_identity.h
@@ -36,10 +36,6 @@
// For private use only.
base_internal::ThreadIdentity* CreateThreadIdentity();
-// A per-thread destructor for reclaiming associated ThreadIdentity objects.
-// For private use only.
-void ReclaimThreadIdentity(void* v);
-
// Returns the ThreadIdentity object representing the calling thread; guaranteed
// to be unique for its lifetime. The returned object will remain valid for the
// program's lifetime; although it may be re-assigned to a subsequent thread.
diff --git a/absl/synchronization/internal/futex.h b/absl/synchronization/internal/futex.h
index 06fbd6d..573c01b 100644
--- a/absl/synchronization/internal/futex.h
+++ b/absl/synchronization/internal/futex.h
@@ -16,9 +16,7 @@
#include "absl/base/config.h"
-#ifdef _WIN32
-#include <windows.h>
-#else
+#ifndef _WIN32
#include <sys/time.h>
#include <unistd.h>
#endif
@@ -34,6 +32,7 @@
#include <atomic>
#include <cstdint>
+#include <limits>
#include "absl/base/optimization.h"
#include "absl/synchronization/internal/kernel_timeout.h"
@@ -81,65 +80,89 @@
#if defined(SYS_futex_time64) && !defined(SYS_futex)
#define SYS_futex SYS_futex_time64
+using FutexTimespec = struct timespec;
+#else
+// Some libc implementations have switched to an unconditional 64-bit `time_t`
+// definition. This means that `struct timespec` may not match the layout
+// expected by the kernel ABI on 32-bit platforms. So we define the
+// FutexTimespec that matches the kernel timespec definition. It should be safe
+// to use this struct for 64-bit userspace builds too, since it will use another
+// SYS_futex kernel call with 64-bit tv_sec inside timespec.
+struct FutexTimespec {
+ long tv_sec; // NOLINT
+ long tv_nsec; // NOLINT
+};
#endif
class FutexImpl {
public:
- static int WaitUntil(std::atomic<int32_t> *v, int32_t val,
- KernelTimeout t) {
- int err = 0;
- if (t.has_timeout()) {
- // https://locklessinc.com/articles/futex_cheat_sheet/
- // Unlike FUTEX_WAIT, FUTEX_WAIT_BITSET uses absolute time.
- struct timespec abs_timeout = t.MakeAbsTimespec();
- // Atomically check that the futex value is still 0, and if it
- // is, sleep until abs_timeout or until woken by FUTEX_WAKE.
- err = syscall(
- SYS_futex, reinterpret_cast<int32_t *>(v),
- FUTEX_WAIT_BITSET | FUTEX_PRIVATE_FLAG | FUTEX_CLOCK_REALTIME, val,
- &abs_timeout, nullptr, FUTEX_BITSET_MATCH_ANY);
+ // Atomically check that `*v == val`, and if it is, then sleep until the until
+ // woken by `Wake()`.
+ static int Wait(std::atomic<int32_t>* v, int32_t val) {
+ return WaitAbsoluteTimeout(v, val, nullptr);
+ }
+
+ // Atomically check that `*v == val`, and if it is, then sleep until
+ // CLOCK_REALTIME reaches `*abs_timeout`, or until woken by `Wake()`.
+ static int WaitAbsoluteTimeout(std::atomic<int32_t>* v, int32_t val,
+ const struct timespec* abs_timeout) {
+ FutexTimespec ts;
+ // https://locklessinc.com/articles/futex_cheat_sheet/
+ // Unlike FUTEX_WAIT, FUTEX_WAIT_BITSET uses absolute time.
+ auto err = syscall(
+ SYS_futex, reinterpret_cast<int32_t*>(v),
+ FUTEX_WAIT_BITSET | FUTEX_PRIVATE_FLAG | FUTEX_CLOCK_REALTIME, val,
+ ToFutexTimespec(abs_timeout, &ts), nullptr, FUTEX_BITSET_MATCH_ANY);
+ if (err != 0) {
+ return -errno;
+ }
+ return 0;
+ }
+
+ // Atomically check that `*v == val`, and if it is, then sleep until
+ // `*rel_timeout` has elapsed, or until woken by `Wake()`.
+ static int WaitRelativeTimeout(std::atomic<int32_t>* v, int32_t val,
+ const struct timespec* rel_timeout) {
+ FutexTimespec ts;
+ // Atomically check that the futex value is still 0, and if it
+ // is, sleep until abs_timeout or until woken by FUTEX_WAKE.
+ auto err =
+ syscall(SYS_futex, reinterpret_cast<int32_t*>(v), FUTEX_PRIVATE_FLAG,
+ val, ToFutexTimespec(rel_timeout, &ts));
+ if (err != 0) {
+ return -errno;
+ }
+ return 0;
+ }
+
+ // Wakes at most `count` waiters that have entered the sleep state on `v`.
+ static int Wake(std::atomic<int32_t>* v, int32_t count) {
+ auto err = syscall(SYS_futex, reinterpret_cast<int32_t*>(v),
+ FUTEX_WAKE | FUTEX_PRIVATE_FLAG, count);
+ if (ABSL_PREDICT_FALSE(err < 0)) {
+ return -errno;
+ }
+ return 0;
+ }
+
+ private:
+ static FutexTimespec* ToFutexTimespec(const struct timespec* userspace_ts,
+ FutexTimespec* futex_ts) {
+ if (userspace_ts == nullptr) {
+ return nullptr;
+ }
+
+ using FutexSeconds = decltype(futex_ts->tv_sec);
+ using FutexNanoseconds = decltype(futex_ts->tv_nsec);
+
+ constexpr auto kMaxSeconds{(std::numeric_limits<FutexSeconds>::max)()};
+ if (userspace_ts->tv_sec > kMaxSeconds) {
+ futex_ts->tv_sec = kMaxSeconds;
} else {
- // Atomically check that the futex value is still 0, and if it
- // is, sleep until woken by FUTEX_WAKE.
- err = syscall(SYS_futex, reinterpret_cast<int32_t *>(v),
- FUTEX_WAIT | FUTEX_PRIVATE_FLAG, val, nullptr);
+ futex_ts->tv_sec = static_cast<FutexSeconds>(userspace_ts->tv_sec);
}
- if (ABSL_PREDICT_FALSE(err != 0)) {
- err = -errno;
- }
- return err;
- }
-
- static int WaitBitsetAbsoluteTimeout(std::atomic<int32_t> *v, int32_t val,
- int32_t bits,
- const struct timespec *abstime) {
- int err = syscall(SYS_futex, reinterpret_cast<int32_t *>(v),
- FUTEX_WAIT_BITSET | FUTEX_PRIVATE_FLAG, val, abstime,
- nullptr, bits);
- if (ABSL_PREDICT_FALSE(err != 0)) {
- err = -errno;
- }
- return err;
- }
-
- static int Wake(std::atomic<int32_t> *v, int32_t count) {
- int err = syscall(SYS_futex, reinterpret_cast<int32_t *>(v),
- FUTEX_WAKE | FUTEX_PRIVATE_FLAG, count);
- if (ABSL_PREDICT_FALSE(err < 0)) {
- err = -errno;
- }
- return err;
- }
-
- // FUTEX_WAKE_BITSET
- static int WakeBitset(std::atomic<int32_t> *v, int32_t count, int32_t bits) {
- int err = syscall(SYS_futex, reinterpret_cast<int32_t *>(v),
- FUTEX_WAKE_BITSET | FUTEX_PRIVATE_FLAG, count, nullptr,
- nullptr, bits);
- if (ABSL_PREDICT_FALSE(err < 0)) {
- err = -errno;
- }
- return err;
+ futex_ts->tv_nsec = static_cast<FutexNanoseconds>(userspace_ts->tv_nsec);
+ return futex_ts;
}
};
diff --git a/absl/synchronization/internal/futex_waiter.cc b/absl/synchronization/internal/futex_waiter.cc
new file mode 100644
index 0000000..87eb3b2
--- /dev/null
+++ b/absl/synchronization/internal/futex_waiter.cc
@@ -0,0 +1,111 @@
+// Copyright 2023 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/synchronization/internal/futex_waiter.h"
+
+#ifdef ABSL_INTERNAL_HAVE_FUTEX_WAITER
+
+#include <atomic>
+#include <cstdint>
+#include <cerrno>
+
+#include "absl/base/config.h"
+#include "absl/base/internal/raw_logging.h"
+#include "absl/base/internal/thread_identity.h"
+#include "absl/base/optimization.h"
+#include "absl/synchronization/internal/kernel_timeout.h"
+#include "absl/synchronization/internal/futex.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace synchronization_internal {
+
+#ifdef ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL
+constexpr char FutexWaiter::kName[];
+#endif
+
+int FutexWaiter::WaitUntil(std::atomic<int32_t>* v, int32_t val,
+ KernelTimeout t) {
+#ifdef CLOCK_MONOTONIC
+ constexpr bool kHasClockMonotonic = true;
+#else
+ constexpr bool kHasClockMonotonic = false;
+#endif
+
+ // We can't call Futex::WaitUntil() here because the prodkernel implementation
+ // does not know about KernelTimeout::SupportsSteadyClock().
+ if (!t.has_timeout()) {
+ return Futex::Wait(v, val);
+ } else if (kHasClockMonotonic && KernelTimeout::SupportsSteadyClock() &&
+ t.is_relative_timeout()) {
+ auto rel_timespec = t.MakeRelativeTimespec();
+ return Futex::WaitRelativeTimeout(v, val, &rel_timespec);
+ } else {
+ auto abs_timespec = t.MakeAbsTimespec();
+ return Futex::WaitAbsoluteTimeout(v, val, &abs_timespec);
+ }
+}
+
+bool FutexWaiter::Wait(KernelTimeout t) {
+ // Loop until we can atomically decrement futex from a positive
+ // value, waiting on a futex while we believe it is zero.
+ // Note that, since the thread ticker is just reset, we don't need to check
+ // whether the thread is idle on the very first pass of the loop.
+ bool first_pass = true;
+ while (true) {
+ int32_t x = futex_.load(std::memory_order_relaxed);
+ while (x != 0) {
+ if (!futex_.compare_exchange_weak(x, x - 1,
+ std::memory_order_acquire,
+ std::memory_order_relaxed)) {
+ continue; // Raced with someone, retry.
+ }
+ return true; // Consumed a wakeup, we are done.
+ }
+
+ if (!first_pass) MaybeBecomeIdle();
+ const int err = WaitUntil(&futex_, 0, t);
+ if (err != 0) {
+ if (err == -EINTR || err == -EWOULDBLOCK) {
+ // Do nothing, the loop will retry.
+ } else if (err == -ETIMEDOUT) {
+ return false;
+ } else {
+ ABSL_RAW_LOG(FATAL, "Futex operation failed with error %d\n", err);
+ }
+ }
+ first_pass = false;
+ }
+}
+
+void FutexWaiter::Post() {
+ if (futex_.fetch_add(1, std::memory_order_release) == 0) {
+ // We incremented from 0, need to wake a potential waiter.
+ Poke();
+ }
+}
+
+void FutexWaiter::Poke() {
+ // Wake one thread waiting on the futex.
+ const int err = Futex::Wake(&futex_, 1);
+ if (ABSL_PREDICT_FALSE(err < 0)) {
+ ABSL_RAW_LOG(FATAL, "Futex operation failed with error %d\n", err);
+ }
+}
+
+} // namespace synchronization_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_INTERNAL_HAVE_FUTEX_WAITER
diff --git a/absl/synchronization/internal/futex_waiter.h b/absl/synchronization/internal/futex_waiter.h
new file mode 100644
index 0000000..11dfa93
--- /dev/null
+++ b/absl/synchronization/internal/futex_waiter.h
@@ -0,0 +1,63 @@
+// Copyright 2023 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef ABSL_SYNCHRONIZATION_INTERNAL_FUTEX_WAITER_H_
+#define ABSL_SYNCHRONIZATION_INTERNAL_FUTEX_WAITER_H_
+
+#include <atomic>
+#include <cstdint>
+
+#include "absl/base/config.h"
+#include "absl/synchronization/internal/kernel_timeout.h"
+#include "absl/synchronization/internal/futex.h"
+#include "absl/synchronization/internal/waiter_base.h"
+
+#ifdef ABSL_INTERNAL_HAVE_FUTEX
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace synchronization_internal {
+
+#define ABSL_INTERNAL_HAVE_FUTEX_WAITER 1
+
+class FutexWaiter : public WaiterCrtp<FutexWaiter> {
+ public:
+ FutexWaiter() : futex_(0) {}
+
+ bool Wait(KernelTimeout t);
+ void Post();
+ void Poke();
+
+ static constexpr char kName[] = "FutexWaiter";
+
+ private:
+ // Atomically check that `*v == val`, and if it is, then sleep until the
+ // timeout `t` has been reached, or until woken by `Wake()`.
+ static int WaitUntil(std::atomic<int32_t>* v, int32_t val,
+ KernelTimeout t);
+
+ // Futexes are defined by specification to be 32-bits.
+ // Thus std::atomic<int32_t> must be just an int32_t with lockfree methods.
+ std::atomic<int32_t> futex_;
+ static_assert(sizeof(int32_t) == sizeof(futex_), "Wrong size for futex");
+};
+
+} // namespace synchronization_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_INTERNAL_HAVE_FUTEX
+
+#endif // ABSL_SYNCHRONIZATION_INTERNAL_FUTEX_WAITER_H_
diff --git a/absl/synchronization/internal/graphcycles.cc b/absl/synchronization/internal/graphcycles.cc
index 27fec21..61b4ec0 100644
--- a/absl/synchronization/internal/graphcycles.cc
+++ b/absl/synchronization/internal/graphcycles.cc
@@ -37,6 +37,7 @@
#include <algorithm>
#include <array>
+#include <cinttypes>
#include <limits>
#include "absl/base/internal/hide_ptr.h"
#include "absl/base/internal/raw_logging.h"
@@ -114,7 +115,7 @@
if (src->ptr_ == src->space_) {
// Need to actually copy
resize(src->size_);
- std::copy(src->ptr_, src->ptr_ + src->size_, ptr_);
+ std::copy_n(src->ptr_, src->size_, ptr_);
src->size_ = 0;
} else {
Discard();
@@ -148,7 +149,7 @@
size_t request = static_cast<size_t>(capacity_) * sizeof(T);
T* copy = static_cast<T*>(
base_internal::LowLevelAlloc::AllocWithArena(request, arena));
- std::copy(ptr_, ptr_ + size_, copy);
+ std::copy_n(ptr_, size_, copy);
Discard();
ptr_ = copy;
}
@@ -181,9 +182,9 @@
return true;
}
- void erase(uint32_t v) {
+ void erase(int32_t v) {
uint32_t i = FindIndex(v);
- if (static_cast<uint32_t>(table_[i]) == v) {
+ if (table_[i] == v) {
table_[i] = kDel;
}
}
@@ -195,7 +196,7 @@
for (int32_t elem, _cursor = 0; (eset).Next(&_cursor, &elem); )
bool Next(int32_t* cursor, int32_t* elem) {
while (static_cast<uint32_t>(*cursor) < table_.size()) {
- int32_t v = table_[*cursor];
+ int32_t v = table_[static_cast<uint32_t>(*cursor)];
(*cursor)++;
if (v >= 0) {
*elem = v;
@@ -210,24 +211,26 @@
Vec<int32_t> table_;
uint32_t occupied_; // Count of non-empty slots (includes deleted slots)
- static uint32_t Hash(uint32_t a) { return a * 41; }
+ static uint32_t Hash(int32_t a) { return static_cast<uint32_t>(a * 41); }
// Return index for storing v. May return an empty index or deleted index
- int FindIndex(int32_t v) const {
+ uint32_t FindIndex(int32_t v) const {
// Search starting at hash index.
const uint32_t mask = table_.size() - 1;
uint32_t i = Hash(v) & mask;
- int deleted_index = -1; // If >= 0, index of first deleted element we see
+ uint32_t deleted_index = 0; // index of first deleted element we see
+ bool seen_deleted_element = false;
while (true) {
int32_t e = table_[i];
if (v == e) {
return i;
} else if (e == kEmpty) {
// Return any previously encountered deleted slot.
- return (deleted_index >= 0) ? deleted_index : i;
- } else if (e == kDel && deleted_index < 0) {
+ return seen_deleted_element ? deleted_index : i;
+ } else if (e == kDel && !seen_deleted_element) {
// Keep searching since v might be present later.
deleted_index = i;
+ seen_deleted_element = true;
}
i = (i + 1) & mask; // Linear probing; quadratic is slightly slower.
}
@@ -268,7 +271,7 @@
}
inline int32_t NodeIndex(GraphId id) {
- return static_cast<uint32_t>(id.handle & 0xfffffffful);
+ return static_cast<int32_t>(id.handle);
}
inline uint32_t NodeVersion(GraphId id) {
@@ -298,7 +301,7 @@
int32_t Find(void* ptr) {
auto masked = base_internal::HidePtr(ptr);
for (int32_t i = table_[Hash(ptr)]; i != -1;) {
- Node* n = (*nodes_)[i];
+ Node* n = (*nodes_)[static_cast<uint32_t>(i)];
if (n->masked_ptr == masked) return i;
i = n->next_hash;
}
@@ -307,7 +310,7 @@
void Add(void* ptr, int32_t i) {
int32_t* head = &table_[Hash(ptr)];
- (*nodes_)[i]->next_hash = *head;
+ (*nodes_)[static_cast<uint32_t>(i)]->next_hash = *head;
*head = i;
}
@@ -317,7 +320,7 @@
auto masked = base_internal::HidePtr(ptr);
for (int32_t* slot = &table_[Hash(ptr)]; *slot != -1; ) {
int32_t index = *slot;
- Node* n = (*nodes_)[index];
+ Node* n = (*nodes_)[static_cast<uint32_t>(index)];
if (n->masked_ptr == masked) {
*slot = n->next_hash; // Remove n from linked list
n->next_hash = -1;
@@ -330,7 +333,7 @@
private:
// Number of buckets in hash table for pointer lookups.
- static constexpr uint32_t kHashTableSize = 8171; // should be prime
+ static constexpr uint32_t kHashTableSize = 262139; // should be prime
const Vec<Node*>* nodes_;
std::array<int32_t, kHashTableSize> table_;
@@ -358,7 +361,7 @@
};
static Node* FindNode(GraphCycles::Rep* rep, GraphId id) {
- Node* n = rep->nodes_[NodeIndex(id)];
+ Node* n = rep->nodes_[static_cast<uint32_t>(NodeIndex(id))];
return (n->version == NodeVersion(id)) ? n : nullptr;
}
@@ -384,19 +387,22 @@
Node* nx = r->nodes_[x];
void* ptr = base_internal::UnhidePtr<void>(nx->masked_ptr);
if (ptr != nullptr && static_cast<uint32_t>(r->ptrmap_.Find(ptr)) != x) {
- ABSL_RAW_LOG(FATAL, "Did not find live node in hash table %u %p", x, ptr);
+ ABSL_RAW_LOG(FATAL, "Did not find live node in hash table %" PRIu32 " %p",
+ x, ptr);
}
if (nx->visited) {
- ABSL_RAW_LOG(FATAL, "Did not clear visited marker on node %u", x);
+ ABSL_RAW_LOG(FATAL, "Did not clear visited marker on node %" PRIu32, x);
}
if (!ranks.insert(nx->rank)) {
- ABSL_RAW_LOG(FATAL, "Duplicate occurrence of rank %d", nx->rank);
+ ABSL_RAW_LOG(FATAL, "Duplicate occurrence of rank %" PRId32, nx->rank);
}
HASH_FOR_EACH(y, nx->out) {
- Node* ny = r->nodes_[y];
+ Node* ny = r->nodes_[static_cast<uint32_t>(y)];
if (nx->rank >= ny->rank) {
- ABSL_RAW_LOG(FATAL, "Edge %u->%d has bad rank assignment %d->%d", x, y,
- nx->rank, ny->rank);
+ ABSL_RAW_LOG(FATAL,
+ "Edge %" PRIu32 " ->%" PRId32
+ " has bad rank assignment %" PRId32 "->%" PRId32,
+ x, y, nx->rank, ny->rank);
}
}
}
@@ -406,14 +412,14 @@
GraphId GraphCycles::GetId(void* ptr) {
int32_t i = rep_->ptrmap_.Find(ptr);
if (i != -1) {
- return MakeId(i, rep_->nodes_[i]->version);
+ return MakeId(i, rep_->nodes_[static_cast<uint32_t>(i)]->version);
} else if (rep_->free_nodes_.empty()) {
Node* n =
new (base_internal::LowLevelAlloc::AllocWithArena(sizeof(Node), arena))
Node;
n->version = 1; // Avoid 0 since it is used by InvalidGraphId()
n->visited = false;
- n->rank = rep_->nodes_.size();
+ n->rank = static_cast<int32_t>(rep_->nodes_.size());
n->masked_ptr = base_internal::HidePtr(ptr);
n->nstack = 0;
n->priority = 0;
@@ -425,7 +431,7 @@
// a permutation of [0,rep_->nodes_.size()-1].
int32_t r = rep_->free_nodes_.back();
rep_->free_nodes_.pop_back();
- Node* n = rep_->nodes_[r];
+ Node* n = rep_->nodes_[static_cast<uint32_t>(r)];
n->masked_ptr = base_internal::HidePtr(ptr);
n->nstack = 0;
n->priority = 0;
@@ -439,12 +445,12 @@
if (i == -1) {
return;
}
- Node* x = rep_->nodes_[i];
+ Node* x = rep_->nodes_[static_cast<uint32_t>(i)];
HASH_FOR_EACH(y, x->out) {
- rep_->nodes_[y]->in.erase(i);
+ rep_->nodes_[static_cast<uint32_t>(y)]->in.erase(i);
}
HASH_FOR_EACH(y, x->in) {
- rep_->nodes_[y]->out.erase(i);
+ rep_->nodes_[static_cast<uint32_t>(y)]->out.erase(i);
}
x->in.clear();
x->out.clear();
@@ -520,7 +526,7 @@
// Since we do not call Reorder() on this path, clear any visited
// markers left by ForwardDFS.
for (const auto& d : r->deltaf_) {
- r->nodes_[d]->visited = false;
+ r->nodes_[static_cast<uint32_t>(d)]->visited = false;
}
return false;
}
@@ -538,14 +544,14 @@
while (!r->stack_.empty()) {
n = r->stack_.back();
r->stack_.pop_back();
- Node* nn = r->nodes_[n];
+ Node* nn = r->nodes_[static_cast<uint32_t>(n)];
if (nn->visited) continue;
nn->visited = true;
r->deltaf_.push_back(n);
HASH_FOR_EACH(w, nn->out) {
- Node* nw = r->nodes_[w];
+ Node* nw = r->nodes_[static_cast<uint32_t>(w)];
if (nw->rank == upper_bound) {
return false; // Cycle
}
@@ -564,14 +570,14 @@
while (!r->stack_.empty()) {
n = r->stack_.back();
r->stack_.pop_back();
- Node* nn = r->nodes_[n];
+ Node* nn = r->nodes_[static_cast<uint32_t>(n)];
if (nn->visited) continue;
nn->visited = true;
r->deltab_.push_back(n);
HASH_FOR_EACH(w, nn->in) {
- Node* nw = r->nodes_[w];
+ Node* nw = r->nodes_[static_cast<uint32_t>(w)];
if (!nw->visited && lower_bound < nw->rank) {
r->stack_.push_back(w);
}
@@ -596,7 +602,7 @@
// Assign the ranks in order to the collected list.
for (uint32_t i = 0; i < r->list_.size(); i++) {
- r->nodes_[r->list_[i]]->rank = r->merged_[i];
+ r->nodes_[static_cast<uint32_t>(r->list_[i])]->rank = r->merged_[i];
}
}
@@ -604,7 +610,8 @@
struct ByRank {
const Vec<Node*>* nodes;
bool operator()(int32_t a, int32_t b) const {
- return (*nodes)[a]->rank < (*nodes)[b]->rank;
+ return (*nodes)[static_cast<uint32_t>(a)]->rank <
+ (*nodes)[static_cast<uint32_t>(b)]->rank;
}
};
ByRank cmp;
@@ -616,8 +623,10 @@
GraphCycles::Rep* r, Vec<int32_t>* src, Vec<int32_t>* dst) {
for (auto& v : *src) {
int32_t w = v;
- v = r->nodes_[w]->rank; // Replace v entry with its rank
- r->nodes_[w]->visited = false; // Prepare for future DFS calls
+ // Replace v entry with its rank
+ v = r->nodes_[static_cast<uint32_t>(w)]->rank;
+ // Prepare for future DFS calls
+ r->nodes_[static_cast<uint32_t>(w)]->visited = false;
dst->push_back(w);
}
}
@@ -647,7 +656,8 @@
}
if (path_len < max_path_len) {
- path[path_len] = MakeId(n, rep_->nodes_[n]->version);
+ path[path_len] =
+ MakeId(n, rep_->nodes_[static_cast<uint32_t>(n)]->version);
}
path_len++;
r->stack_.push_back(-1); // Will remove tentative path entry
@@ -656,7 +666,7 @@
return path_len;
}
- HASH_FOR_EACH(w, r->nodes_[n]->out) {
+ HASH_FOR_EACH(w, r->nodes_[static_cast<uint32_t>(n)]->out) {
if (seen.insert(w)) {
r->stack_.push_back(w);
}
diff --git a/absl/synchronization/internal/graphcycles_test.cc b/absl/synchronization/internal/graphcycles_test.cc
index 74eaffe..3c6ef79 100644
--- a/absl/synchronization/internal/graphcycles_test.cc
+++ b/absl/synchronization/internal/graphcycles_test.cc
@@ -21,8 +21,9 @@
#include <vector>
#include "gtest/gtest.h"
-#include "absl/base/internal/raw_logging.h"
#include "absl/base/macros.h"
+#include "absl/log/check.h"
+#include "absl/log/log.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
@@ -65,51 +66,51 @@
}
static void PrintEdges(Edges *edges) {
- ABSL_RAW_LOG(INFO, "EDGES (%zu)", edges->size());
+ LOG(INFO) << "EDGES (" << edges->size() << ")";
for (const auto &edge : *edges) {
int a = edge.from;
int b = edge.to;
- ABSL_RAW_LOG(INFO, "%d %d", a, b);
+ LOG(INFO) << a << " " << b;
}
- ABSL_RAW_LOG(INFO, "---");
+ LOG(INFO) << "---";
}
static void PrintGCEdges(Nodes *nodes, const IdMap &id, GraphCycles *gc) {
- ABSL_RAW_LOG(INFO, "GC EDGES");
+ LOG(INFO) << "GC EDGES";
for (int a : *nodes) {
for (int b : *nodes) {
if (gc->HasEdge(Get(id, a), Get(id, b))) {
- ABSL_RAW_LOG(INFO, "%d %d", a, b);
+ LOG(INFO) << a << " " << b;
}
}
}
- ABSL_RAW_LOG(INFO, "---");
+ LOG(INFO) << "---";
}
static void PrintTransitiveClosure(Nodes *nodes, Edges *edges) {
- ABSL_RAW_LOG(INFO, "Transitive closure");
+ LOG(INFO) << "Transitive closure";
for (int a : *nodes) {
for (int b : *nodes) {
std::unordered_set<int> seen;
if (IsReachable(edges, a, b, &seen)) {
- ABSL_RAW_LOG(INFO, "%d %d", a, b);
+ LOG(INFO) << a << " " << b;
}
}
}
- ABSL_RAW_LOG(INFO, "---");
+ LOG(INFO) << "---";
}
static void PrintGCTransitiveClosure(Nodes *nodes, const IdMap &id,
GraphCycles *gc) {
- ABSL_RAW_LOG(INFO, "GC Transitive closure");
+ LOG(INFO) << "GC Transitive closure";
for (int a : *nodes) {
for (int b : *nodes) {
if (gc->IsReachable(Get(id, a), Get(id, b))) {
- ABSL_RAW_LOG(INFO, "%d %d", a, b);
+ LOG(INFO) << a << " " << b;
}
}
}
- ABSL_RAW_LOG(INFO, "---");
+ LOG(INFO) << "---";
}
static void CheckTransitiveClosure(Nodes *nodes, Edges *edges, const IdMap &id,
@@ -125,9 +126,8 @@
PrintGCEdges(nodes, id, gc);
PrintTransitiveClosure(nodes, edges);
PrintGCTransitiveClosure(nodes, id, gc);
- ABSL_RAW_LOG(FATAL, "gc_reachable %s reachable %s a %d b %d",
- gc_reachable ? "true" : "false",
- reachable ? "true" : "false", a, b);
+ LOG(FATAL) << "gc_reachable " << gc_reachable << " reachable "
+ << reachable << " a " << a << " b " << b;
}
}
}
@@ -142,7 +142,7 @@
if (!gc->HasEdge(Get(id, a), Get(id, b))) {
PrintEdges(edges);
PrintGCEdges(nodes, id, gc);
- ABSL_RAW_LOG(FATAL, "!gc->HasEdge(%d, %d)", a, b);
+ LOG(FATAL) << "!gc->HasEdge(" << a << ", " << b << ")";
}
}
for (const auto &a : *nodes) {
@@ -155,13 +155,12 @@
if (count != edges->size()) {
PrintEdges(edges);
PrintGCEdges(nodes, id, gc);
- ABSL_RAW_LOG(FATAL, "edges->size() %zu count %d", edges->size(), count);
+ LOG(FATAL) << "edges->size() " << edges->size() << " count " << count;
}
}
static void CheckInvariants(const GraphCycles &gc) {
- if (ABSL_PREDICT_FALSE(!gc.CheckInvariants()))
- ABSL_RAW_LOG(FATAL, "CheckInvariants");
+ CHECK(gc.CheckInvariants()) << "CheckInvariants";
}
// Returns the index of a randomly chosen node in *nodes.
@@ -309,7 +308,7 @@
break;
default:
- ABSL_RAW_LOG(FATAL, "op %d", op);
+ LOG(FATAL) << "op " << op;
}
// Very rarely, test graph expansion by adding then removing many nodes.
diff --git a/absl/synchronization/internal/kernel_timeout.cc b/absl/synchronization/internal/kernel_timeout.cc
new file mode 100644
index 0000000..48ea628
--- /dev/null
+++ b/absl/synchronization/internal/kernel_timeout.cc
@@ -0,0 +1,225 @@
+// Copyright 2023 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/synchronization/internal/kernel_timeout.h"
+
+#ifndef _WIN32
+#include <sys/types.h>
+#endif
+
+#include <algorithm>
+#include <chrono> // NOLINT(build/c++11)
+#include <cstdint>
+#include <cstdlib>
+#include <cstring>
+#include <ctime>
+#include <limits>
+
+#include "absl/base/attributes.h"
+#include "absl/base/call_once.h"
+#include "absl/base/config.h"
+#include "absl/time/time.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace synchronization_internal {
+
+#ifdef ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL
+constexpr uint64_t KernelTimeout::kNoTimeout;
+constexpr int64_t KernelTimeout::kMaxNanos;
+#endif
+
+int64_t KernelTimeout::SteadyClockNow() {
+ if (!SupportsSteadyClock()) {
+ return absl::GetCurrentTimeNanos();
+ }
+ return std::chrono::duration_cast<std::chrono::nanoseconds>(
+ std::chrono::steady_clock::now().time_since_epoch())
+ .count();
+}
+
+KernelTimeout::KernelTimeout(absl::Time t) {
+ // `absl::InfiniteFuture()` is a common "no timeout" value and cheaper to
+ // compare than convert.
+ if (t == absl::InfiniteFuture()) {
+ rep_ = kNoTimeout;
+ return;
+ }
+
+ int64_t unix_nanos = absl::ToUnixNanos(t);
+
+ // A timeout that lands before the unix epoch is converted to 0.
+ // In theory implementations should expire these timeouts immediately.
+ if (unix_nanos < 0) {
+ unix_nanos = 0;
+ }
+
+ // Values greater than or equal to kMaxNanos are converted to infinite.
+ if (unix_nanos >= kMaxNanos) {
+ rep_ = kNoTimeout;
+ return;
+ }
+
+ rep_ = static_cast<uint64_t>(unix_nanos) << 1;
+}
+
+KernelTimeout::KernelTimeout(absl::Duration d) {
+ // `absl::InfiniteDuration()` is a common "no timeout" value and cheaper to
+ // compare than convert.
+ if (d == absl::InfiniteDuration()) {
+ rep_ = kNoTimeout;
+ return;
+ }
+
+ int64_t nanos = absl::ToInt64Nanoseconds(d);
+
+ // Negative durations are normalized to 0.
+ // In theory implementations should expire these timeouts immediately.
+ if (nanos < 0) {
+ nanos = 0;
+ }
+
+ int64_t now = SteadyClockNow();
+ if (nanos > kMaxNanos - now) {
+ // Durations that would be greater than kMaxNanos are converted to infinite.
+ rep_ = kNoTimeout;
+ return;
+ }
+
+ nanos += now;
+ rep_ = (static_cast<uint64_t>(nanos) << 1) | uint64_t{1};
+}
+
+int64_t KernelTimeout::MakeAbsNanos() const {
+ if (!has_timeout()) {
+ return kMaxNanos;
+ }
+
+ int64_t nanos = RawAbsNanos();
+
+ if (is_relative_timeout()) {
+ // We need to change epochs, because the relative timeout might be
+ // represented by an absolute timestamp from another clock.
+ nanos = std::max<int64_t>(nanos - SteadyClockNow(), 0);
+ int64_t now = absl::GetCurrentTimeNanos();
+ if (nanos > kMaxNanos - now) {
+ // Overflow.
+ nanos = kMaxNanos;
+ } else {
+ nanos += now;
+ }
+ } else if (nanos == 0) {
+ // Some callers have assumed that 0 means no timeout, so instead we return a
+ // time of 1 nanosecond after the epoch.
+ nanos = 1;
+ }
+
+ return nanos;
+}
+
+int64_t KernelTimeout::InNanosecondsFromNow() const {
+ if (!has_timeout()) {
+ return kMaxNanos;
+ }
+
+ int64_t nanos = RawAbsNanos();
+ if (is_absolute_timeout()) {
+ return std::max<int64_t>(nanos - absl::GetCurrentTimeNanos(), 0);
+ }
+ return std::max<int64_t>(nanos - SteadyClockNow(), 0);
+}
+
+struct timespec KernelTimeout::MakeAbsTimespec() const {
+ return absl::ToTimespec(absl::Nanoseconds(MakeAbsNanos()));
+}
+
+struct timespec KernelTimeout::MakeRelativeTimespec() const {
+ return absl::ToTimespec(absl::Nanoseconds(InNanosecondsFromNow()));
+}
+
+#ifndef _WIN32
+struct timespec KernelTimeout::MakeClockAbsoluteTimespec(clockid_t c) const {
+ if (!has_timeout()) {
+ return absl::ToTimespec(absl::Nanoseconds(kMaxNanos));
+ }
+
+ int64_t nanos = RawAbsNanos();
+ if (is_absolute_timeout()) {
+ nanos -= absl::GetCurrentTimeNanos();
+ } else {
+ nanos -= SteadyClockNow();
+ }
+
+ struct timespec now;
+ ABSL_RAW_CHECK(clock_gettime(c, &now) == 0, "clock_gettime() failed");
+ absl::Duration from_clock_epoch =
+ absl::DurationFromTimespec(now) + absl::Nanoseconds(nanos);
+ if (from_clock_epoch <= absl::ZeroDuration()) {
+ // Some callers have assumed that 0 means no timeout, so instead we return a
+ // time of 1 nanosecond after the epoch. For safety we also do not return
+ // negative values.
+ return absl::ToTimespec(absl::Nanoseconds(1));
+ }
+ return absl::ToTimespec(from_clock_epoch);
+}
+#endif
+
+KernelTimeout::DWord KernelTimeout::InMillisecondsFromNow() const {
+ constexpr DWord kInfinite = std::numeric_limits<DWord>::max();
+
+ if (!has_timeout()) {
+ return kInfinite;
+ }
+
+ constexpr uint64_t kNanosInMillis = uint64_t{1'000'000};
+ constexpr uint64_t kMaxValueNanos =
+ std::numeric_limits<int64_t>::max() - kNanosInMillis + 1;
+
+ uint64_t ns_from_now = static_cast<uint64_t>(InNanosecondsFromNow());
+ if (ns_from_now >= kMaxValueNanos) {
+ // Rounding up would overflow.
+ return kInfinite;
+ }
+ // Convert to milliseconds, always rounding up.
+ uint64_t ms_from_now = (ns_from_now + kNanosInMillis - 1) / kNanosInMillis;
+ if (ms_from_now > kInfinite) {
+ return kInfinite;
+ }
+ return static_cast<DWord>(ms_from_now);
+}
+
+std::chrono::time_point<std::chrono::system_clock>
+KernelTimeout::ToChronoTimePoint() const {
+ if (!has_timeout()) {
+ return std::chrono::time_point<std::chrono::system_clock>::max();
+ }
+
+ // The cast to std::microseconds is because (on some platforms) the
+ // std::ratio used by std::chrono::steady_clock doesn't convert to
+ // std::nanoseconds, so it doesn't compile.
+ auto micros = std::chrono::duration_cast<std::chrono::microseconds>(
+ std::chrono::nanoseconds(MakeAbsNanos()));
+ return std::chrono::system_clock::from_time_t(0) + micros;
+}
+
+std::chrono::nanoseconds KernelTimeout::ToChronoDuration() const {
+ if (!has_timeout()) {
+ return std::chrono::nanoseconds::max();
+ }
+ return std::chrono::nanoseconds(InNanosecondsFromNow());
+}
+
+} // namespace synchronization_internal
+ABSL_NAMESPACE_END
+} // namespace absl
diff --git a/absl/synchronization/internal/kernel_timeout.h b/absl/synchronization/internal/kernel_timeout.h
index bbd4d2d..06404a7 100644
--- a/absl/synchronization/internal/kernel_timeout.h
+++ b/absl/synchronization/internal/kernel_timeout.h
@@ -11,25 +11,21 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
-//
-
-// An optional absolute timeout, with nanosecond granularity,
-// compatible with absl::Time. Suitable for in-register
-// parameter-passing (e.g. syscalls.)
-// Constructible from a absl::Time (for a timeout to be respected) or {}
-// (for "no timeout".)
-// This is a private low-level API for use by a handful of low-level
-// components that are friends of this class. Higher-level components
-// should build APIs based on absl::Time and absl::Duration.
#ifndef ABSL_SYNCHRONIZATION_INTERNAL_KERNEL_TIMEOUT_H_
#define ABSL_SYNCHRONIZATION_INTERNAL_KERNEL_TIMEOUT_H_
-#include <time.h>
+#ifndef _WIN32
+#include <sys/types.h>
+#endif
#include <algorithm>
+#include <chrono> // NOLINT(build/c++11)
+#include <cstdint>
+#include <ctime>
#include <limits>
+#include "absl/base/config.h"
#include "absl/base/internal/raw_logging.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
@@ -38,56 +34,73 @@
ABSL_NAMESPACE_BEGIN
namespace synchronization_internal {
-class Futex;
-class Waiter;
-
+// An optional timeout, with nanosecond granularity.
+//
+// This is a private low-level API for use by a handful of low-level
+// components. Higher-level components should build APIs based on
+// absl::Time and absl::Duration.
class KernelTimeout {
public:
- // A timeout that should expire at <t>. Any value, in the full
- // InfinitePast() to InfiniteFuture() range, is valid here and will be
- // respected.
- explicit KernelTimeout(absl::Time t) : ns_(MakeNs(t)) {}
- // No timeout.
- KernelTimeout() : ns_(0) {}
+ // Construct an absolute timeout that should expire at `t`.
+ explicit KernelTimeout(absl::Time t);
- // A more explicit factory for those who prefer it. Equivalent to {}.
- static KernelTimeout Never() { return {}; }
+ // Construct a relative timeout that should expire after `d`.
+ explicit KernelTimeout(absl::Duration d);
- // We explicitly do not support other custom formats: timespec, int64_t nanos.
- // Unify on this and absl::Time, please.
+ // Infinite timeout.
+ constexpr KernelTimeout() : rep_(kNoTimeout) {}
- bool has_timeout() const { return ns_ != 0; }
+ // A more explicit factory for those who prefer it.
+ // Equivalent to `KernelTimeout()`.
+ static constexpr KernelTimeout Never() { return KernelTimeout(); }
- // Convert to parameter for sem_timedwait/futex/similar. Only for approved
- // users. Do not call if !has_timeout.
- struct timespec MakeAbsTimespec();
+ // Returns true if there is a timeout that will eventually expire.
+ // Returns false if the timeout is infinite.
+ bool has_timeout() const { return rep_ != kNoTimeout; }
- private:
- // internal rep, not user visible: ns after unix epoch.
- // zero = no timeout.
- // Negative we treat as an unlikely (and certainly expired!) but valid
- // timeout.
- int64_t ns_;
+ // If `has_timeout()` is true, returns true if the timeout was provided as an
+ // `absl::Time`. The return value is undefined if `has_timeout()` is false
+ // because all indefinite timeouts are equivalent.
+ bool is_absolute_timeout() const { return (rep_ & 1) == 0; }
- static int64_t MakeNs(absl::Time t) {
- // optimization--InfiniteFuture is common "no timeout" value
- // and cheaper to compare than convert.
- if (t == absl::InfiniteFuture()) return 0;
- int64_t x = ToUnixNanos(t);
+ // If `has_timeout()` is true, returns true if the timeout was provided as an
+ // `absl::Duration`. The return value is undefined if `has_timeout()` is false
+ // because all indefinite timeouts are equivalent.
+ bool is_relative_timeout() const { return (rep_ & 1) == 1; }
- // A timeout that lands exactly on the epoch (x=0) needs to be respected,
- // so we alter it unnoticably to 1. Negative timeouts are in
- // theory supported, but handled poorly by the kernel (long
- // delays) so push them forward too; since all such times have
- // already passed, it's indistinguishable.
- if (x <= 0) x = 1;
- // A time larger than what can be represented to the kernel is treated
- // as no timeout.
- if (x == (std::numeric_limits<int64_t>::max)()) x = 0;
- return x;
- }
+ // Convert to `struct timespec` for interfaces that expect an absolute
+ // timeout. If !has_timeout() or is_relative_timeout(), attempts to convert to
+ // a reasonable absolute timeout, but callers should to test has_timeout() and
+ // is_relative_timeout() and prefer to use a more appropriate interface.
+ struct timespec MakeAbsTimespec() const;
-#ifdef _WIN32
+ // Convert to `struct timespec` for interfaces that expect a relative
+ // timeout. If !has_timeout() or is_absolute_timeout(), attempts to convert to
+ // a reasonable relative timeout, but callers should to test has_timeout() and
+ // is_absolute_timeout() and prefer to use a more appropriate interface. Since
+ // the return value is a relative duration, it should be recomputed by calling
+ // this method in the case of a spurious wakeup.
+ struct timespec MakeRelativeTimespec() const;
+
+#ifndef _WIN32
+ // Convert to `struct timespec` for interfaces that expect an absolute timeout
+ // on a specific clock `c`. This is similar to `MakeAbsTimespec()`, but
+ // callers usually want to use this method with `CLOCK_MONOTONIC` when
+ // relative timeouts are requested, and when the appropriate interface expects
+ // an absolute timeout relative to a specific clock (for example,
+ // pthread_cond_clockwait() or sem_clockwait()). If !has_timeout(), attempts
+ // to convert to a reasonable absolute timeout, but callers should to test
+ // has_timeout() prefer to use a more appropriate interface.
+ struct timespec MakeClockAbsoluteTimespec(clockid_t c) const;
+#endif
+
+ // Convert to unix epoch nanos for interfaces that expect an absolute timeout
+ // in nanoseconds. If !has_timeout() or is_relative_timeout(), attempts to
+ // convert to a reasonable absolute timeout, but callers should to test
+ // has_timeout() and is_relative_timeout() and prefer to use a more
+ // appropriate interface.
+ int64_t MakeAbsNanos() const;
+
// Converts to milliseconds from now, or INFINITE when
// !has_timeout(). For use by SleepConditionVariableSRW on
// Windows. Callers should recognize that the return value is a
@@ -97,58 +110,67 @@
// so we define our own DWORD and INFINITE instead of getting them from
// <intsafe.h> and <WinBase.h>.
typedef unsigned long DWord; // NOLINT
- DWord InMillisecondsFromNow() const {
- constexpr DWord kInfinite = (std::numeric_limits<DWord>::max)();
- if (!has_timeout()) {
- return kInfinite;
- }
- // The use of absl::Now() to convert from absolute time to
- // relative time means that absl::Now() cannot use anything that
- // depends on KernelTimeout (for example, Mutex) on Windows.
- int64_t now = ToUnixNanos(absl::Now());
- if (ns_ >= now) {
- // Round up so that Now() + ms_from_now >= ns_.
- constexpr uint64_t max_nanos =
- (std::numeric_limits<int64_t>::max)() - 999999u;
- uint64_t ms_from_now =
- (std::min<uint64_t>(max_nanos, ns_ - now) + 999999u) / 1000000u;
- if (ms_from_now > kInfinite) {
- return kInfinite;
- }
- return static_cast<DWord>(ms_from_now);
- }
- return 0;
- }
-#endif
+ DWord InMillisecondsFromNow() const;
- friend class Futex;
- friend class Waiter;
+ // Convert to std::chrono::time_point for interfaces that expect an absolute
+ // timeout, like std::condition_variable::wait_until(). If !has_timeout() or
+ // is_relative_timeout(), attempts to convert to a reasonable absolute
+ // timeout, but callers should test has_timeout() and is_relative_timeout()
+ // and prefer to use a more appropriate interface.
+ std::chrono::time_point<std::chrono::system_clock> ToChronoTimePoint() const;
+
+ // Convert to std::chrono::time_point for interfaces that expect a relative
+ // timeout, like std::condition_variable::wait_for(). If !has_timeout() or
+ // is_absolute_timeout(), attempts to convert to a reasonable relative
+ // timeout, but callers should test has_timeout() and is_absolute_timeout()
+ // and prefer to use a more appropriate interface. Since the return value is a
+ // relative duration, it should be recomputed by calling this method in the
+ // case of a spurious wakeup.
+ std::chrono::nanoseconds ToChronoDuration() const;
+
+ // Returns true if steady (aka monotonic) clocks are supported by the system.
+ // This method exists because go/btm requires synchronized clocks, and
+ // thus requires we use the system (aka walltime) clock.
+ static constexpr bool SupportsSteadyClock() { return true; }
+
+ private:
+ // Returns the current time, expressed as a count of nanoseconds since the
+ // epoch used by an arbitrary clock. The implementation tries to use a steady
+ // (monotonic) clock if one is available.
+ static int64_t SteadyClockNow();
+
+ // Internal representation.
+ // - If the value is kNoTimeout, then the timeout is infinite, and
+ // has_timeout() will return true.
+ // - If the low bit is 0, then the high 63 bits is the number of nanoseconds
+ // after the unix epoch.
+ // - If the low bit is 1, then the high 63 bits is the number of nanoseconds
+ // after the epoch used by SteadyClockNow().
+ //
+ // In all cases the time is stored as an absolute time, the only difference is
+ // the clock epoch. The use of absolute times is important since in the case
+ // of a relative timeout with a spurious wakeup, the program would have to
+ // restart the wait, and thus needs a way of recomputing the remaining time.
+ uint64_t rep_;
+
+ // Returns the number of nanoseconds stored in the internal representation.
+ // When combined with the clock epoch indicated by the low bit (which is
+ // accessed through is_absolute_timeout() and is_relative_timeout()), the
+ // return value is used to compute when the timeout should occur.
+ int64_t RawAbsNanos() const { return static_cast<int64_t>(rep_ >> 1); }
+
+ // Converts to nanoseconds from now. Since the return value is a relative
+ // duration, it should be recomputed by calling this method in the case of a
+ // spurious wakeup.
+ int64_t InNanosecondsFromNow() const;
+
+ // A value that represents no timeout (or an infinite timeout).
+ static constexpr uint64_t kNoTimeout = (std::numeric_limits<uint64_t>::max)();
+
+ // The maximum value that can be stored in the high 63 bits.
+ static constexpr int64_t kMaxNanos = (std::numeric_limits<int64_t>::max)();
};
-inline struct timespec KernelTimeout::MakeAbsTimespec() {
- int64_t n = ns_;
- static const int64_t kNanosPerSecond = 1000 * 1000 * 1000;
- if (n == 0) {
- ABSL_RAW_LOG(
- ERROR, "Tried to create a timespec from a non-timeout; never do this.");
- // But we'll try to continue sanely. no-timeout ~= saturated timeout.
- n = (std::numeric_limits<int64_t>::max)();
- }
-
- // Kernel APIs validate timespecs as being at or after the epoch,
- // despite the kernel time type being signed. However, no one can
- // tell the difference between a timeout at or before the epoch (since
- // all such timeouts have expired!)
- if (n < 0) n = 0;
-
- struct timespec abstime;
- int64_t seconds = (std::min)(n / kNanosPerSecond,
- int64_t{(std::numeric_limits<time_t>::max)()});
- abstime.tv_sec = static_cast<time_t>(seconds);
- abstime.tv_nsec = static_cast<decltype(abstime.tv_nsec)>(n % kNanosPerSecond);
- return abstime;
-}
-
} // namespace synchronization_internal
ABSL_NAMESPACE_END
} // namespace absl
diff --git a/absl/synchronization/internal/kernel_timeout_test.cc b/absl/synchronization/internal/kernel_timeout_test.cc
new file mode 100644
index 0000000..bc54671
--- /dev/null
+++ b/absl/synchronization/internal/kernel_timeout_test.cc
@@ -0,0 +1,393 @@
+// Copyright 2023 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/synchronization/internal/kernel_timeout.h"
+
+#include <ctime>
+#include <chrono> // NOLINT(build/c++11)
+#include <limits>
+
+#include "absl/base/config.h"
+#include "absl/random/random.h"
+#include "absl/time/clock.h"
+#include "absl/time/time.h"
+#include "gtest/gtest.h"
+
+// Test go/btm support by randomizing the value of clock_gettime() for
+// CLOCK_MONOTONIC. This works by overriding a weak symbol in glibc.
+// We should be resistant to this randomization when !SupportsSteadyClock().
+#if defined(__GOOGLE_GRTE_VERSION__) && \
+ !defined(ABSL_HAVE_ADDRESS_SANITIZER) && \
+ !defined(ABSL_HAVE_MEMORY_SANITIZER) && \
+ !defined(ABSL_HAVE_THREAD_SANITIZER)
+extern "C" int __clock_gettime(clockid_t c, struct timespec* ts);
+
+extern "C" int clock_gettime(clockid_t c, struct timespec* ts) {
+ if (c == CLOCK_MONOTONIC &&
+ !absl::synchronization_internal::KernelTimeout::SupportsSteadyClock()) {
+ absl::SharedBitGen gen;
+ ts->tv_sec = absl::Uniform(gen, 0, 1'000'000'000);
+ ts->tv_nsec = absl::Uniform(gen, 0, 1'000'000'000);
+ return 0;
+ }
+ return __clock_gettime(c, ts);
+}
+#endif
+
+namespace {
+
+#if defined(ABSL_HAVE_ADDRESS_SANITIZER) || \
+ defined(ABSL_HAVE_MEMORY_SANITIZER) || \
+ defined(ABSL_HAVE_THREAD_SANITIZER) || defined(__ANDROID__) || \
+ defined(__APPLE__) || defined(_WIN32) || defined(_WIN64)
+constexpr absl::Duration kTimingBound = absl::Milliseconds(5);
+#else
+constexpr absl::Duration kTimingBound = absl::Microseconds(250);
+#endif
+
+using absl::synchronization_internal::KernelTimeout;
+
+TEST(KernelTimeout, FiniteTimes) {
+ constexpr absl::Duration kDurationsToTest[] = {
+ absl::ZeroDuration(),
+ absl::Nanoseconds(1),
+ absl::Microseconds(1),
+ absl::Milliseconds(1),
+ absl::Seconds(1),
+ absl::Minutes(1),
+ absl::Hours(1),
+ absl::Hours(1000),
+ -absl::Nanoseconds(1),
+ -absl::Microseconds(1),
+ -absl::Milliseconds(1),
+ -absl::Seconds(1),
+ -absl::Minutes(1),
+ -absl::Hours(1),
+ -absl::Hours(1000),
+ };
+
+ for (auto duration : kDurationsToTest) {
+ const absl::Time now = absl::Now();
+ const absl::Time when = now + duration;
+ SCOPED_TRACE(duration);
+ KernelTimeout t(when);
+ EXPECT_TRUE(t.has_timeout());
+ EXPECT_TRUE(t.is_absolute_timeout());
+ EXPECT_FALSE(t.is_relative_timeout());
+ EXPECT_EQ(absl::TimeFromTimespec(t.MakeAbsTimespec()), when);
+#ifndef _WIN32
+ EXPECT_LE(
+ absl::AbsDuration(absl::Now() + duration -
+ absl::TimeFromTimespec(
+ t.MakeClockAbsoluteTimespec(CLOCK_REALTIME))),
+ absl::Milliseconds(10));
+#endif
+ EXPECT_LE(
+ absl::AbsDuration(absl::DurationFromTimespec(t.MakeRelativeTimespec()) -
+ std::max(duration, absl::ZeroDuration())),
+ kTimingBound);
+ EXPECT_EQ(absl::FromUnixNanos(t.MakeAbsNanos()), when);
+ EXPECT_LE(absl::AbsDuration(absl::Milliseconds(t.InMillisecondsFromNow()) -
+ std::max(duration, absl::ZeroDuration())),
+ absl::Milliseconds(5));
+ EXPECT_LE(absl::AbsDuration(absl::FromChrono(t.ToChronoTimePoint()) - when),
+ absl::Microseconds(1));
+ EXPECT_LE(absl::AbsDuration(absl::FromChrono(t.ToChronoDuration()) -
+ std::max(duration, absl::ZeroDuration())),
+ kTimingBound);
+ }
+}
+
+TEST(KernelTimeout, InfiniteFuture) {
+ KernelTimeout t(absl::InfiniteFuture());
+ EXPECT_FALSE(t.has_timeout());
+ // Callers are expected to check has_timeout() instead of using the methods
+ // below, but we do try to do something reasonable if they don't. We may not
+ // be able to round-trip back to absl::InfiniteDuration() or
+ // absl::InfiniteFuture(), but we should return a very large value.
+ EXPECT_GT(absl::TimeFromTimespec(t.MakeAbsTimespec()),
+ absl::Now() + absl::Hours(100000));
+#ifndef _WIN32
+ EXPECT_GT(absl::TimeFromTimespec(t.MakeClockAbsoluteTimespec(CLOCK_REALTIME)),
+ absl::Now() + absl::Hours(100000));
+#endif
+ EXPECT_GT(absl::DurationFromTimespec(t.MakeRelativeTimespec()),
+ absl::Hours(100000));
+ EXPECT_GT(absl::FromUnixNanos(t.MakeAbsNanos()),
+ absl::Now() + absl::Hours(100000));
+ EXPECT_EQ(t.InMillisecondsFromNow(),
+ std::numeric_limits<KernelTimeout::DWord>::max());
+ EXPECT_EQ(t.ToChronoTimePoint(),
+ std::chrono::time_point<std::chrono::system_clock>::max());
+ EXPECT_GE(t.ToChronoDuration(), std::chrono::nanoseconds::max());
+}
+
+TEST(KernelTimeout, DefaultConstructor) {
+ // The default constructor is equivalent to absl::InfiniteFuture().
+ KernelTimeout t;
+ EXPECT_FALSE(t.has_timeout());
+ // Callers are expected to check has_timeout() instead of using the methods
+ // below, but we do try to do something reasonable if they don't. We may not
+ // be able to round-trip back to absl::InfiniteDuration() or
+ // absl::InfiniteFuture(), but we should return a very large value.
+ EXPECT_GT(absl::TimeFromTimespec(t.MakeAbsTimespec()),
+ absl::Now() + absl::Hours(100000));
+#ifndef _WIN32
+ EXPECT_GT(absl::TimeFromTimespec(t.MakeClockAbsoluteTimespec(CLOCK_REALTIME)),
+ absl::Now() + absl::Hours(100000));
+#endif
+ EXPECT_GT(absl::DurationFromTimespec(t.MakeRelativeTimespec()),
+ absl::Hours(100000));
+ EXPECT_GT(absl::FromUnixNanos(t.MakeAbsNanos()),
+ absl::Now() + absl::Hours(100000));
+ EXPECT_EQ(t.InMillisecondsFromNow(),
+ std::numeric_limits<KernelTimeout::DWord>::max());
+ EXPECT_EQ(t.ToChronoTimePoint(),
+ std::chrono::time_point<std::chrono::system_clock>::max());
+ EXPECT_GE(t.ToChronoDuration(), std::chrono::nanoseconds::max());
+}
+
+TEST(KernelTimeout, TimeMaxNanos) {
+ // Time >= kMaxNanos should behave as no timeout.
+ KernelTimeout t(absl::FromUnixNanos(std::numeric_limits<int64_t>::max()));
+ EXPECT_FALSE(t.has_timeout());
+ // Callers are expected to check has_timeout() instead of using the methods
+ // below, but we do try to do something reasonable if they don't. We may not
+ // be able to round-trip back to absl::InfiniteDuration() or
+ // absl::InfiniteFuture(), but we should return a very large value.
+ EXPECT_GT(absl::TimeFromTimespec(t.MakeAbsTimespec()),
+ absl::Now() + absl::Hours(100000));
+#ifndef _WIN32
+ EXPECT_GT(absl::TimeFromTimespec(t.MakeClockAbsoluteTimespec(CLOCK_REALTIME)),
+ absl::Now() + absl::Hours(100000));
+#endif
+ EXPECT_GT(absl::DurationFromTimespec(t.MakeRelativeTimespec()),
+ absl::Hours(100000));
+ EXPECT_GT(absl::FromUnixNanos(t.MakeAbsNanos()),
+ absl::Now() + absl::Hours(100000));
+ EXPECT_EQ(t.InMillisecondsFromNow(),
+ std::numeric_limits<KernelTimeout::DWord>::max());
+ EXPECT_EQ(t.ToChronoTimePoint(),
+ std::chrono::time_point<std::chrono::system_clock>::max());
+ EXPECT_GE(t.ToChronoDuration(), std::chrono::nanoseconds::max());
+}
+
+TEST(KernelTimeout, Never) {
+ // KernelTimeout::Never() is equivalent to absl::InfiniteFuture().
+ KernelTimeout t = KernelTimeout::Never();
+ EXPECT_FALSE(t.has_timeout());
+ // Callers are expected to check has_timeout() instead of using the methods
+ // below, but we do try to do something reasonable if they don't. We may not
+ // be able to round-trip back to absl::InfiniteDuration() or
+ // absl::InfiniteFuture(), but we should return a very large value.
+ EXPECT_GT(absl::TimeFromTimespec(t.MakeAbsTimespec()),
+ absl::Now() + absl::Hours(100000));
+#ifndef _WIN32
+ EXPECT_GT(absl::TimeFromTimespec(t.MakeClockAbsoluteTimespec(CLOCK_REALTIME)),
+ absl::Now() + absl::Hours(100000));
+#endif
+ EXPECT_GT(absl::DurationFromTimespec(t.MakeRelativeTimespec()),
+ absl::Hours(100000));
+ EXPECT_GT(absl::FromUnixNanos(t.MakeAbsNanos()),
+ absl::Now() + absl::Hours(100000));
+ EXPECT_EQ(t.InMillisecondsFromNow(),
+ std::numeric_limits<KernelTimeout::DWord>::max());
+ EXPECT_EQ(t.ToChronoTimePoint(),
+ std::chrono::time_point<std::chrono::system_clock>::max());
+ EXPECT_GE(t.ToChronoDuration(), std::chrono::nanoseconds::max());
+}
+
+TEST(KernelTimeout, InfinitePast) {
+ KernelTimeout t(absl::InfinitePast());
+ EXPECT_TRUE(t.has_timeout());
+ EXPECT_TRUE(t.is_absolute_timeout());
+ EXPECT_FALSE(t.is_relative_timeout());
+ EXPECT_LE(absl::TimeFromTimespec(t.MakeAbsTimespec()),
+ absl::FromUnixNanos(1));
+#ifndef _WIN32
+ EXPECT_LE(absl::TimeFromTimespec(t.MakeClockAbsoluteTimespec(CLOCK_REALTIME)),
+ absl::FromUnixSeconds(1));
+#endif
+ EXPECT_EQ(absl::DurationFromTimespec(t.MakeRelativeTimespec()),
+ absl::ZeroDuration());
+ EXPECT_LE(absl::FromUnixNanos(t.MakeAbsNanos()), absl::FromUnixNanos(1));
+ EXPECT_EQ(t.InMillisecondsFromNow(), KernelTimeout::DWord{0});
+ EXPECT_LT(t.ToChronoTimePoint(), std::chrono::system_clock::from_time_t(0) +
+ std::chrono::seconds(1));
+ EXPECT_EQ(t.ToChronoDuration(), std::chrono::nanoseconds(0));
+}
+
+TEST(KernelTimeout, FiniteDurations) {
+ constexpr absl::Duration kDurationsToTest[] = {
+ absl::ZeroDuration(),
+ absl::Nanoseconds(1),
+ absl::Microseconds(1),
+ absl::Milliseconds(1),
+ absl::Seconds(1),
+ absl::Minutes(1),
+ absl::Hours(1),
+ absl::Hours(1000),
+ };
+
+ for (auto duration : kDurationsToTest) {
+ SCOPED_TRACE(duration);
+ KernelTimeout t(duration);
+ EXPECT_TRUE(t.has_timeout());
+ EXPECT_FALSE(t.is_absolute_timeout());
+ EXPECT_TRUE(t.is_relative_timeout());
+ EXPECT_LE(absl::AbsDuration(absl::Now() + duration -
+ absl::TimeFromTimespec(t.MakeAbsTimespec())),
+ absl::Milliseconds(5));
+#ifndef _WIN32
+ EXPECT_LE(
+ absl::AbsDuration(absl::Now() + duration -
+ absl::TimeFromTimespec(
+ t.MakeClockAbsoluteTimespec(CLOCK_REALTIME))),
+ absl::Milliseconds(5));
+#endif
+ EXPECT_LE(
+ absl::AbsDuration(absl::DurationFromTimespec(t.MakeRelativeTimespec()) -
+ duration),
+ kTimingBound);
+ EXPECT_LE(absl::AbsDuration(absl::Now() + duration -
+ absl::FromUnixNanos(t.MakeAbsNanos())),
+ absl::Milliseconds(5));
+ EXPECT_LE(absl::Milliseconds(t.InMillisecondsFromNow()) - duration,
+ absl::Milliseconds(5));
+ EXPECT_LE(absl::AbsDuration(absl::Now() + duration -
+ absl::FromChrono(t.ToChronoTimePoint())),
+ kTimingBound);
+ EXPECT_LE(
+ absl::AbsDuration(absl::FromChrono(t.ToChronoDuration()) - duration),
+ kTimingBound);
+ }
+}
+
+TEST(KernelTimeout, NegativeDurations) {
+ constexpr absl::Duration kDurationsToTest[] = {
+ -absl::ZeroDuration(),
+ -absl::Nanoseconds(1),
+ -absl::Microseconds(1),
+ -absl::Milliseconds(1),
+ -absl::Seconds(1),
+ -absl::Minutes(1),
+ -absl::Hours(1),
+ -absl::Hours(1000),
+ -absl::InfiniteDuration(),
+ };
+
+ for (auto duration : kDurationsToTest) {
+ // Negative durations should all be converted to zero durations or "now".
+ SCOPED_TRACE(duration);
+ KernelTimeout t(duration);
+ EXPECT_TRUE(t.has_timeout());
+ EXPECT_FALSE(t.is_absolute_timeout());
+ EXPECT_TRUE(t.is_relative_timeout());
+ EXPECT_LE(absl::AbsDuration(absl::Now() -
+ absl::TimeFromTimespec(t.MakeAbsTimespec())),
+ absl::Milliseconds(5));
+#ifndef _WIN32
+ EXPECT_LE(absl::AbsDuration(absl::Now() - absl::TimeFromTimespec(
+ t.MakeClockAbsoluteTimespec(
+ CLOCK_REALTIME))),
+ absl::Milliseconds(5));
+#endif
+ EXPECT_EQ(absl::DurationFromTimespec(t.MakeRelativeTimespec()),
+ absl::ZeroDuration());
+ EXPECT_LE(
+ absl::AbsDuration(absl::Now() - absl::FromUnixNanos(t.MakeAbsNanos())),
+ absl::Milliseconds(5));
+ EXPECT_EQ(t.InMillisecondsFromNow(), KernelTimeout::DWord{0});
+ EXPECT_LE(absl::AbsDuration(absl::Now() -
+ absl::FromChrono(t.ToChronoTimePoint())),
+ absl::Milliseconds(5));
+ EXPECT_EQ(t.ToChronoDuration(), std::chrono::nanoseconds(0));
+ }
+}
+
+TEST(KernelTimeout, InfiniteDuration) {
+ KernelTimeout t(absl::InfiniteDuration());
+ EXPECT_FALSE(t.has_timeout());
+ // Callers are expected to check has_timeout() instead of using the methods
+ // below, but we do try to do something reasonable if they don't. We may not
+ // be able to round-trip back to absl::InfiniteDuration() or
+ // absl::InfiniteFuture(), but we should return a very large value.
+ EXPECT_GT(absl::TimeFromTimespec(t.MakeAbsTimespec()),
+ absl::Now() + absl::Hours(100000));
+#ifndef _WIN32
+ EXPECT_GT(absl::TimeFromTimespec(t.MakeClockAbsoluteTimespec(CLOCK_REALTIME)),
+ absl::Now() + absl::Hours(100000));
+#endif
+ EXPECT_GT(absl::DurationFromTimespec(t.MakeRelativeTimespec()),
+ absl::Hours(100000));
+ EXPECT_GT(absl::FromUnixNanos(t.MakeAbsNanos()),
+ absl::Now() + absl::Hours(100000));
+ EXPECT_EQ(t.InMillisecondsFromNow(),
+ std::numeric_limits<KernelTimeout::DWord>::max());
+ EXPECT_EQ(t.ToChronoTimePoint(),
+ std::chrono::time_point<std::chrono::system_clock>::max());
+ EXPECT_GE(t.ToChronoDuration(), std::chrono::nanoseconds::max());
+}
+
+TEST(KernelTimeout, DurationMaxNanos) {
+ // Duration >= kMaxNanos should behave as no timeout.
+ KernelTimeout t(absl::Nanoseconds(std::numeric_limits<int64_t>::max()));
+ EXPECT_FALSE(t.has_timeout());
+ // Callers are expected to check has_timeout() instead of using the methods
+ // below, but we do try to do something reasonable if they don't. We may not
+ // be able to round-trip back to absl::InfiniteDuration() or
+ // absl::InfiniteFuture(), but we should return a very large value.
+ EXPECT_GT(absl::TimeFromTimespec(t.MakeAbsTimespec()),
+ absl::Now() + absl::Hours(100000));
+#ifndef _WIN32
+ EXPECT_GT(absl::TimeFromTimespec(t.MakeClockAbsoluteTimespec(CLOCK_REALTIME)),
+ absl::Now() + absl::Hours(100000));
+#endif
+ EXPECT_GT(absl::DurationFromTimespec(t.MakeRelativeTimespec()),
+ absl::Hours(100000));
+ EXPECT_GT(absl::FromUnixNanos(t.MakeAbsNanos()),
+ absl::Now() + absl::Hours(100000));
+ EXPECT_EQ(t.InMillisecondsFromNow(),
+ std::numeric_limits<KernelTimeout::DWord>::max());
+ EXPECT_EQ(t.ToChronoTimePoint(),
+ std::chrono::time_point<std::chrono::system_clock>::max());
+ EXPECT_GE(t.ToChronoDuration(), std::chrono::nanoseconds::max());
+}
+
+TEST(KernelTimeout, OverflowNanos) {
+ // Test what happens when KernelTimeout is constructed with an absl::Duration
+ // that would overflow now_nanos + duration.
+ int64_t now_nanos = absl::ToUnixNanos(absl::Now());
+ int64_t limit = std::numeric_limits<int64_t>::max() - now_nanos;
+ absl::Duration duration = absl::Nanoseconds(limit) + absl::Seconds(1);
+ KernelTimeout t(duration);
+ // Timeouts should still be far in the future.
+ EXPECT_GT(absl::TimeFromTimespec(t.MakeAbsTimespec()),
+ absl::Now() + absl::Hours(100000));
+#ifndef _WIN32
+ EXPECT_GT(absl::TimeFromTimespec(t.MakeClockAbsoluteTimespec(CLOCK_REALTIME)),
+ absl::Now() + absl::Hours(100000));
+#endif
+ EXPECT_GT(absl::DurationFromTimespec(t.MakeRelativeTimespec()),
+ absl::Hours(100000));
+ EXPECT_GT(absl::FromUnixNanos(t.MakeAbsNanos()),
+ absl::Now() + absl::Hours(100000));
+ EXPECT_LE(absl::Milliseconds(t.InMillisecondsFromNow()) - duration,
+ absl::Milliseconds(5));
+ EXPECT_GT(t.ToChronoTimePoint(),
+ std::chrono::system_clock::now() + std::chrono::hours(100000));
+ EXPECT_GT(t.ToChronoDuration(), std::chrono::hours(100000));
+}
+
+} // namespace
diff --git a/absl/synchronization/internal/per_thread_sem.cc b/absl/synchronization/internal/per_thread_sem.cc
index a603178..c9b8dc1 100644
--- a/absl/synchronization/internal/per_thread_sem.cc
+++ b/absl/synchronization/internal/per_thread_sem.cc
@@ -40,17 +40,6 @@
return identity->blocked_count_ptr;
}
-void PerThreadSem::Init(base_internal::ThreadIdentity *identity) {
- new (Waiter::GetWaiter(identity)) Waiter();
- identity->ticker.store(0, std::memory_order_relaxed);
- identity->wait_start.store(0, std::memory_order_relaxed);
- identity->is_idle.store(false, std::memory_order_relaxed);
-}
-
-void PerThreadSem::Destroy(base_internal::ThreadIdentity *identity) {
- Waiter::GetWaiter(identity)->~Waiter();
-}
-
void PerThreadSem::Tick(base_internal::ThreadIdentity *identity) {
const int ticker =
identity->ticker.fetch_add(1, std::memory_order_relaxed) + 1;
@@ -58,7 +47,7 @@
const bool is_idle = identity->is_idle.load(std::memory_order_relaxed);
if (wait_start && (ticker - wait_start > Waiter::kIdlePeriods) && !is_idle) {
// Wakeup the waiting thread since it is time for it to become idle.
- Waiter::GetWaiter(identity)->Poke();
+ ABSL_INTERNAL_C_SYMBOL(AbslInternalPerThreadSemPoke)(identity);
}
}
@@ -68,11 +57,22 @@
extern "C" {
+ABSL_ATTRIBUTE_WEAK void ABSL_INTERNAL_C_SYMBOL(AbslInternalPerThreadSemInit)(
+ absl::base_internal::ThreadIdentity *identity) {
+ new (absl::synchronization_internal::Waiter::GetWaiter(identity))
+ absl::synchronization_internal::Waiter();
+}
+
ABSL_ATTRIBUTE_WEAK void ABSL_INTERNAL_C_SYMBOL(AbslInternalPerThreadSemPost)(
absl::base_internal::ThreadIdentity *identity) {
absl::synchronization_internal::Waiter::GetWaiter(identity)->Post();
}
+ABSL_ATTRIBUTE_WEAK void ABSL_INTERNAL_C_SYMBOL(AbslInternalPerThreadSemPoke)(
+ absl::base_internal::ThreadIdentity *identity) {
+ absl::synchronization_internal::Waiter::GetWaiter(identity)->Poke();
+}
+
ABSL_ATTRIBUTE_WEAK bool ABSL_INTERNAL_C_SYMBOL(AbslInternalPerThreadSemWait)(
absl::synchronization_internal::KernelTimeout t) {
bool timeout = false;
diff --git a/absl/synchronization/internal/per_thread_sem.h b/absl/synchronization/internal/per_thread_sem.h
index 7beae8e..144ab3c 100644
--- a/absl/synchronization/internal/per_thread_sem.h
+++ b/absl/synchronization/internal/per_thread_sem.h
@@ -64,11 +64,7 @@
private:
// Create the PerThreadSem associated with "identity". Initializes count=0.
// REQUIRES: May only be called by ThreadIdentity.
- static void Init(base_internal::ThreadIdentity* identity);
-
- // Destroy the PerThreadSem associated with "identity".
- // REQUIRES: May only be called by ThreadIdentity.
- static void Destroy(base_internal::ThreadIdentity* identity);
+ static inline void Init(base_internal::ThreadIdentity* identity);
// Increments "identity"'s count.
static inline void Post(base_internal::ThreadIdentity* identity);
@@ -81,8 +77,7 @@
// Permitted callers.
friend class PerThreadSemTest;
friend class absl::Mutex;
- friend absl::base_internal::ThreadIdentity* CreateThreadIdentity();
- friend void ReclaimThreadIdentity(void* v);
+ friend void OneTimeInitThreadIdentity(absl::base_internal::ThreadIdentity*);
};
} // namespace synchronization_internal
@@ -96,12 +91,21 @@
// By changing our extension points to be extern "C", we dodge this
// check.
extern "C" {
+void ABSL_INTERNAL_C_SYMBOL(AbslInternalPerThreadSemInit)(
+ absl::base_internal::ThreadIdentity* identity);
void ABSL_INTERNAL_C_SYMBOL(AbslInternalPerThreadSemPost)(
absl::base_internal::ThreadIdentity* identity);
bool ABSL_INTERNAL_C_SYMBOL(AbslInternalPerThreadSemWait)(
absl::synchronization_internal::KernelTimeout t);
+void ABSL_INTERNAL_C_SYMBOL(AbslInternalPerThreadSemPoke)(
+ absl::base_internal::ThreadIdentity* identity);
} // extern "C"
+void absl::synchronization_internal::PerThreadSem::Init(
+ absl::base_internal::ThreadIdentity* identity) {
+ ABSL_INTERNAL_C_SYMBOL(AbslInternalPerThreadSemInit)(identity);
+}
+
void absl::synchronization_internal::PerThreadSem::Post(
absl::base_internal::ThreadIdentity* identity) {
ABSL_INTERNAL_C_SYMBOL(AbslInternalPerThreadSemPost)(identity);
diff --git a/absl/synchronization/internal/per_thread_sem_test.cc b/absl/synchronization/internal/per_thread_sem_test.cc
index db1184e..24a6b54 100644
--- a/absl/synchronization/internal/per_thread_sem_test.cc
+++ b/absl/synchronization/internal/per_thread_sem_test.cc
@@ -174,6 +174,15 @@
EXPECT_TRUE(Wait(negative_timeout));
}
+TEST_F(PerThreadSemTest, ThreadIdentityReuse) {
+ // Create a base_internal::ThreadIdentity object and keep reusing it. There
+ // should be no memory or resource leaks.
+ for (int i = 0; i < 10000; i++) {
+ std::thread t([]() { GetOrCreateCurrentThreadIdentity(); });
+ t.join();
+ }
+}
+
} // namespace
} // namespace synchronization_internal
diff --git a/absl/synchronization/internal/pthread_waiter.cc b/absl/synchronization/internal/pthread_waiter.cc
new file mode 100644
index 0000000..bf700e9
--- /dev/null
+++ b/absl/synchronization/internal/pthread_waiter.cc
@@ -0,0 +1,167 @@
+// Copyright 2023 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/synchronization/internal/pthread_waiter.h"
+
+#ifdef ABSL_INTERNAL_HAVE_PTHREAD_WAITER
+
+#include <pthread.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+#include <cassert>
+#include <cerrno>
+
+#include "absl/base/config.h"
+#include "absl/base/internal/raw_logging.h"
+#include "absl/base/internal/thread_identity.h"
+#include "absl/base/optimization.h"
+#include "absl/synchronization/internal/kernel_timeout.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace synchronization_internal {
+
+namespace {
+class PthreadMutexHolder {
+ public:
+ explicit PthreadMutexHolder(pthread_mutex_t *mu) : mu_(mu) {
+ const int err = pthread_mutex_lock(mu_);
+ if (err != 0) {
+ ABSL_RAW_LOG(FATAL, "pthread_mutex_lock failed: %d", err);
+ }
+ }
+
+ PthreadMutexHolder(const PthreadMutexHolder &rhs) = delete;
+ PthreadMutexHolder &operator=(const PthreadMutexHolder &rhs) = delete;
+
+ ~PthreadMutexHolder() {
+ const int err = pthread_mutex_unlock(mu_);
+ if (err != 0) {
+ ABSL_RAW_LOG(FATAL, "pthread_mutex_unlock failed: %d", err);
+ }
+ }
+
+ private:
+ pthread_mutex_t *mu_;
+};
+} // namespace
+
+#ifdef ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL
+constexpr char PthreadWaiter::kName[];
+#endif
+
+PthreadWaiter::PthreadWaiter() : waiter_count_(0), wakeup_count_(0) {
+ const int err = pthread_mutex_init(&mu_, 0);
+ if (err != 0) {
+ ABSL_RAW_LOG(FATAL, "pthread_mutex_init failed: %d", err);
+ }
+
+ const int err2 = pthread_cond_init(&cv_, 0);
+ if (err2 != 0) {
+ ABSL_RAW_LOG(FATAL, "pthread_cond_init failed: %d", err2);
+ }
+}
+
+#ifdef __APPLE__
+#define ABSL_INTERNAL_HAS_PTHREAD_COND_TIMEDWAIT_RELATIVE_NP 1
+#endif
+
+#if defined(__GLIBC__) && \
+ (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 30))
+#define ABSL_INTERNAL_HAVE_PTHREAD_COND_CLOCKWAIT 1
+#elif defined(__ANDROID_API__) && __ANDROID_API__ >= 30
+#define ABSL_INTERNAL_HAVE_PTHREAD_COND_CLOCKWAIT 1
+#endif
+
+// Calls pthread_cond_timedwait() or possibly something else like
+// pthread_cond_timedwait_relative_np() depending on the platform and
+// KernelTimeout requested. The return value is the same as the return
+// value of pthread_cond_timedwait().
+int PthreadWaiter::TimedWait(KernelTimeout t) {
+ assert(t.has_timeout());
+ if (KernelTimeout::SupportsSteadyClock() && t.is_relative_timeout()) {
+#ifdef ABSL_INTERNAL_HAS_PTHREAD_COND_TIMEDWAIT_RELATIVE_NP
+ const auto rel_timeout = t.MakeRelativeTimespec();
+ return pthread_cond_timedwait_relative_np(&cv_, &mu_, &rel_timeout);
+#elif defined(ABSL_INTERNAL_HAVE_PTHREAD_COND_CLOCKWAIT) && \
+ defined(CLOCK_MONOTONIC)
+ const auto abs_clock_timeout = t.MakeClockAbsoluteTimespec(CLOCK_MONOTONIC);
+ return pthread_cond_clockwait(&cv_, &mu_, CLOCK_MONOTONIC,
+ &abs_clock_timeout);
+#endif
+ }
+
+ const auto abs_timeout = t.MakeAbsTimespec();
+ return pthread_cond_timedwait(&cv_, &mu_, &abs_timeout);
+}
+
+bool PthreadWaiter::Wait(KernelTimeout t) {
+ PthreadMutexHolder h(&mu_);
+ ++waiter_count_;
+ // Loop until we find a wakeup to consume or timeout.
+ // Note that, since the thread ticker is just reset, we don't need to check
+ // whether the thread is idle on the very first pass of the loop.
+ bool first_pass = true;
+ while (wakeup_count_ == 0) {
+ if (!first_pass) MaybeBecomeIdle();
+ // No wakeups available, time to wait.
+ if (!t.has_timeout()) {
+ const int err = pthread_cond_wait(&cv_, &mu_);
+ if (err != 0) {
+ ABSL_RAW_LOG(FATAL, "pthread_cond_wait failed: %d", err);
+ }
+ } else {
+ const int err = TimedWait(t);
+ if (err == ETIMEDOUT) {
+ --waiter_count_;
+ return false;
+ }
+ if (err != 0) {
+ ABSL_RAW_LOG(FATAL, "PthreadWaiter::TimedWait() failed: %d", err);
+ }
+ }
+ first_pass = false;
+ }
+ // Consume a wakeup and we're done.
+ --wakeup_count_;
+ --waiter_count_;
+ return true;
+}
+
+void PthreadWaiter::Post() {
+ PthreadMutexHolder h(&mu_);
+ ++wakeup_count_;
+ InternalCondVarPoke();
+}
+
+void PthreadWaiter::Poke() {
+ PthreadMutexHolder h(&mu_);
+ InternalCondVarPoke();
+}
+
+void PthreadWaiter::InternalCondVarPoke() {
+ if (waiter_count_ != 0) {
+ const int err = pthread_cond_signal(&cv_);
+ if (ABSL_PREDICT_FALSE(err != 0)) {
+ ABSL_RAW_LOG(FATAL, "pthread_cond_signal failed: %d", err);
+ }
+ }
+}
+
+} // namespace synchronization_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_INTERNAL_HAVE_PTHREAD_WAITER
diff --git a/absl/synchronization/internal/pthread_waiter.h b/absl/synchronization/internal/pthread_waiter.h
new file mode 100644
index 0000000..23db76a
--- /dev/null
+++ b/absl/synchronization/internal/pthread_waiter.h
@@ -0,0 +1,60 @@
+// Copyright 2023 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef ABSL_SYNCHRONIZATION_INTERNAL_PTHREAD_WAITER_H_
+#define ABSL_SYNCHRONIZATION_INTERNAL_PTHREAD_WAITER_H_
+
+#if !defined(_WIN32) && !defined(__MINGW32__)
+#include <pthread.h>
+
+#include "absl/base/config.h"
+#include "absl/synchronization/internal/kernel_timeout.h"
+#include "absl/synchronization/internal/waiter_base.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace synchronization_internal {
+
+#define ABSL_INTERNAL_HAVE_PTHREAD_WAITER 1
+
+class PthreadWaiter : public WaiterCrtp<PthreadWaiter> {
+ public:
+ PthreadWaiter();
+
+ bool Wait(KernelTimeout t);
+ void Post();
+ void Poke();
+
+ static constexpr char kName[] = "PthreadWaiter";
+
+ private:
+ int TimedWait(KernelTimeout t);
+
+ // REQUIRES: mu_ must be held.
+ void InternalCondVarPoke();
+
+ pthread_mutex_t mu_;
+ pthread_cond_t cv_;
+ int waiter_count_;
+ int wakeup_count_; // Unclaimed wakeups.
+};
+
+} // namespace synchronization_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // !defined(_WIN32) && !defined(__MINGW32__)
+
+#endif // ABSL_SYNCHRONIZATION_INTERNAL_PTHREAD_WAITER_H_
diff --git a/absl/synchronization/internal/sem_waiter.cc b/absl/synchronization/internal/sem_waiter.cc
new file mode 100644
index 0000000..d62dbdc
--- /dev/null
+++ b/absl/synchronization/internal/sem_waiter.cc
@@ -0,0 +1,122 @@
+// Copyright 2023 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/synchronization/internal/sem_waiter.h"
+
+#ifdef ABSL_INTERNAL_HAVE_SEM_WAITER
+
+#include <semaphore.h>
+
+#include <atomic>
+#include <cassert>
+#include <cstdint>
+#include <cerrno>
+
+#include "absl/base/config.h"
+#include "absl/base/internal/raw_logging.h"
+#include "absl/base/internal/thread_identity.h"
+#include "absl/base/optimization.h"
+#include "absl/synchronization/internal/kernel_timeout.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace synchronization_internal {
+
+#ifdef ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL
+constexpr char SemWaiter::kName[];
+#endif
+
+SemWaiter::SemWaiter() : wakeups_(0) {
+ if (sem_init(&sem_, 0, 0) != 0) {
+ ABSL_RAW_LOG(FATAL, "sem_init failed with errno %d\n", errno);
+ }
+}
+
+#if defined(__GLIBC__) && \
+ (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 30))
+#define ABSL_INTERNAL_HAVE_SEM_CLOCKWAIT 1
+#elif defined(__ANDROID_API__) && __ANDROID_API__ >= 30
+#define ABSL_INTERNAL_HAVE_SEM_CLOCKWAIT 1
+#endif
+
+// Calls sem_timedwait() or possibly something else like
+// sem_clockwait() depending on the platform and
+// KernelTimeout requested. The return value is the same as a call to the return
+// value to a call to sem_timedwait().
+int SemWaiter::TimedWait(KernelTimeout t) {
+ if (KernelTimeout::SupportsSteadyClock() && t.is_relative_timeout()) {
+#if defined(ABSL_INTERNAL_HAVE_SEM_CLOCKWAIT) && defined(CLOCK_MONOTONIC)
+ const auto abs_clock_timeout = t.MakeClockAbsoluteTimespec(CLOCK_MONOTONIC);
+ return sem_clockwait(&sem_, CLOCK_MONOTONIC, &abs_clock_timeout);
+#endif
+ }
+
+ const auto abs_timeout = t.MakeAbsTimespec();
+ return sem_timedwait(&sem_, &abs_timeout);
+}
+
+bool SemWaiter::Wait(KernelTimeout t) {
+ // Loop until we timeout or consume a wakeup.
+ // Note that, since the thread ticker is just reset, we don't need to check
+ // whether the thread is idle on the very first pass of the loop.
+ bool first_pass = true;
+ while (true) {
+ int x = wakeups_.load(std::memory_order_relaxed);
+ while (x != 0) {
+ if (!wakeups_.compare_exchange_weak(x, x - 1,
+ std::memory_order_acquire,
+ std::memory_order_relaxed)) {
+ continue; // Raced with someone, retry.
+ }
+ // Successfully consumed a wakeup, we're done.
+ return true;
+ }
+
+ if (!first_pass) MaybeBecomeIdle();
+ // Nothing to consume, wait (looping on EINTR).
+ while (true) {
+ if (!t.has_timeout()) {
+ if (sem_wait(&sem_) == 0) break;
+ if (errno == EINTR) continue;
+ ABSL_RAW_LOG(FATAL, "sem_wait failed: %d", errno);
+ } else {
+ if (TimedWait(t) == 0) break;
+ if (errno == EINTR) continue;
+ if (errno == ETIMEDOUT) return false;
+ ABSL_RAW_LOG(FATAL, "SemWaiter::TimedWait() failed: %d", errno);
+ }
+ }
+ first_pass = false;
+ }
+}
+
+void SemWaiter::Post() {
+ // Post a wakeup.
+ if (wakeups_.fetch_add(1, std::memory_order_release) == 0) {
+ // We incremented from 0, need to wake a potential waiter.
+ Poke();
+ }
+}
+
+void SemWaiter::Poke() {
+ if (sem_post(&sem_) != 0) { // Wake any semaphore waiter.
+ ABSL_RAW_LOG(FATAL, "sem_post failed with errno %d\n", errno);
+ }
+}
+
+} // namespace synchronization_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_INTERNAL_HAVE_SEM_WAITER
diff --git a/absl/synchronization/internal/sem_waiter.h b/absl/synchronization/internal/sem_waiter.h
new file mode 100644
index 0000000..c22746f
--- /dev/null
+++ b/absl/synchronization/internal/sem_waiter.h
@@ -0,0 +1,65 @@
+// Copyright 2023 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef ABSL_SYNCHRONIZATION_INTERNAL_SEM_WAITER_H_
+#define ABSL_SYNCHRONIZATION_INTERNAL_SEM_WAITER_H_
+
+#include "absl/base/config.h"
+
+#ifdef ABSL_HAVE_SEMAPHORE_H
+#include <semaphore.h>
+
+#include <atomic>
+#include <cstdint>
+
+#include "absl/base/internal/thread_identity.h"
+#include "absl/synchronization/internal/futex.h"
+#include "absl/synchronization/internal/kernel_timeout.h"
+#include "absl/synchronization/internal/waiter_base.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace synchronization_internal {
+
+#define ABSL_INTERNAL_HAVE_SEM_WAITER 1
+
+class SemWaiter : public WaiterCrtp<SemWaiter> {
+ public:
+ SemWaiter();
+
+ bool Wait(KernelTimeout t);
+ void Post();
+ void Poke();
+
+ static constexpr char kName[] = "SemWaiter";
+
+ private:
+ int TimedWait(KernelTimeout t);
+
+ sem_t sem_;
+
+ // This seems superfluous, but for Poke() we need to cause spurious
+ // wakeups on the semaphore. Hence we can't actually use the
+ // semaphore's count.
+ std::atomic<int> wakeups_;
+};
+
+} // namespace synchronization_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_HAVE_SEMAPHORE_H
+
+#endif // ABSL_SYNCHRONIZATION_INTERNAL_SEM_WAITER_H_
diff --git a/absl/synchronization/internal/stdcpp_waiter.cc b/absl/synchronization/internal/stdcpp_waiter.cc
new file mode 100644
index 0000000..355718a
--- /dev/null
+++ b/absl/synchronization/internal/stdcpp_waiter.cc
@@ -0,0 +1,91 @@
+// Copyright 2023 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/synchronization/internal/stdcpp_waiter.h"
+
+#ifdef ABSL_INTERNAL_HAVE_STDCPP_WAITER
+
+#include <chrono> // NOLINT(build/c++11)
+#include <condition_variable> // NOLINT(build/c++11)
+#include <mutex> // NOLINT(build/c++11)
+
+#include "absl/base/config.h"
+#include "absl/base/internal/raw_logging.h"
+#include "absl/base/internal/thread_identity.h"
+#include "absl/base/optimization.h"
+#include "absl/synchronization/internal/kernel_timeout.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace synchronization_internal {
+
+#ifdef ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL
+constexpr char StdcppWaiter::kName[];
+#endif
+
+StdcppWaiter::StdcppWaiter() : waiter_count_(0), wakeup_count_(0) {}
+
+bool StdcppWaiter::Wait(KernelTimeout t) {
+ std::unique_lock<std::mutex> lock(mu_);
+ ++waiter_count_;
+
+ // Loop until we find a wakeup to consume or timeout.
+ // Note that, since the thread ticker is just reset, we don't need to check
+ // whether the thread is idle on the very first pass of the loop.
+ bool first_pass = true;
+ while (wakeup_count_ == 0) {
+ if (!first_pass) MaybeBecomeIdle();
+ // No wakeups available, time to wait.
+ if (!t.has_timeout()) {
+ cv_.wait(lock);
+ } else {
+ auto wait_result = t.SupportsSteadyClock() && t.is_relative_timeout()
+ ? cv_.wait_for(lock, t.ToChronoDuration())
+ : cv_.wait_until(lock, t.ToChronoTimePoint());
+ if (wait_result == std::cv_status::timeout) {
+ --waiter_count_;
+ return false;
+ }
+ }
+ first_pass = false;
+ }
+
+ // Consume a wakeup and we're done.
+ --wakeup_count_;
+ --waiter_count_;
+ return true;
+}
+
+void StdcppWaiter::Post() {
+ std::lock_guard<std::mutex> lock(mu_);
+ ++wakeup_count_;
+ InternalCondVarPoke();
+}
+
+void StdcppWaiter::Poke() {
+ std::lock_guard<std::mutex> lock(mu_);
+ InternalCondVarPoke();
+}
+
+void StdcppWaiter::InternalCondVarPoke() {
+ if (waiter_count_ != 0) {
+ cv_.notify_one();
+ }
+}
+
+} // namespace synchronization_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_INTERNAL_HAVE_STDCPP_WAITER
diff --git a/absl/synchronization/internal/stdcpp_waiter.h b/absl/synchronization/internal/stdcpp_waiter.h
new file mode 100644
index 0000000..e592a27
--- /dev/null
+++ b/absl/synchronization/internal/stdcpp_waiter.h
@@ -0,0 +1,56 @@
+// Copyright 2023 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef ABSL_SYNCHRONIZATION_INTERNAL_STDCPP_WAITER_H_
+#define ABSL_SYNCHRONIZATION_INTERNAL_STDCPP_WAITER_H_
+
+#include <condition_variable> // NOLINT(build/c++11)
+#include <mutex> // NOLINT(build/c++11)
+
+#include "absl/base/config.h"
+#include "absl/synchronization/internal/kernel_timeout.h"
+#include "absl/synchronization/internal/waiter_base.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace synchronization_internal {
+
+#define ABSL_INTERNAL_HAVE_STDCPP_WAITER 1
+
+class StdcppWaiter : public WaiterCrtp<StdcppWaiter> {
+ public:
+ StdcppWaiter();
+
+ bool Wait(KernelTimeout t);
+ void Post();
+ void Poke();
+
+ static constexpr char kName[] = "StdcppWaiter";
+
+ private:
+ // REQUIRES: mu_ must be held.
+ void InternalCondVarPoke();
+
+ std::mutex mu_;
+ std::condition_variable cv_;
+ int waiter_count_;
+ int wakeup_count_; // Unclaimed wakeups.
+};
+
+} // namespace synchronization_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_SYNCHRONIZATION_INTERNAL_STDCPP_WAITER_H_
diff --git a/absl/synchronization/internal/thread_pool.h b/absl/synchronization/internal/thread_pool.h
index 0cb96da..5eb0bb6 100644
--- a/absl/synchronization/internal/thread_pool.h
+++ b/absl/synchronization/internal/thread_pool.h
@@ -20,9 +20,11 @@
#include <functional>
#include <queue>
#include <thread> // NOLINT(build/c++11)
+#include <utility>
#include <vector>
#include "absl/base/thread_annotations.h"
+#include "absl/functional/any_invocable.h"
#include "absl/synchronization/mutex.h"
namespace absl {
@@ -33,6 +35,7 @@
class ThreadPool {
public:
explicit ThreadPool(int num_threads) {
+ threads_.reserve(num_threads);
for (int i = 0; i < num_threads; ++i) {
threads_.push_back(std::thread(&ThreadPool::WorkLoop, this));
}
@@ -54,7 +57,7 @@
}
// Schedule a function to be run on a ThreadPool thread immediately.
- void Schedule(std::function<void()> func) {
+ void Schedule(absl::AnyInvocable<void()> func) {
assert(func != nullptr);
absl::MutexLock l(&mu_);
queue_.push(std::move(func));
@@ -67,7 +70,7 @@
void WorkLoop() {
while (true) {
- std::function<void()> func;
+ absl::AnyInvocable<void()> func;
{
absl::MutexLock l(&mu_);
mu_.Await(absl::Condition(this, &ThreadPool::WorkAvailable));
@@ -82,7 +85,7 @@
}
absl::Mutex mu_;
- std::queue<std::function<void()>> queue_ ABSL_GUARDED_BY(mu_);
+ std::queue<absl::AnyInvocable<void()>> queue_ ABSL_GUARDED_BY(mu_);
std::vector<std::thread> threads_;
};
diff --git a/absl/synchronization/internal/waiter.cc b/absl/synchronization/internal/waiter.cc
deleted file mode 100644
index 28ef311..0000000
--- a/absl/synchronization/internal/waiter.cc
+++ /dev/null
@@ -1,428 +0,0 @@
-// Copyright 2017 The Abseil Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "absl/synchronization/internal/waiter.h"
-
-#include "absl/base/config.h"
-
-#ifdef _WIN32
-#include <windows.h>
-#else
-#include <pthread.h>
-#include <sys/time.h>
-#include <unistd.h>
-#endif
-
-#ifdef __linux__
-#include <linux/futex.h>
-#include <sys/syscall.h>
-#endif
-
-#ifdef ABSL_HAVE_SEMAPHORE_H
-#include <semaphore.h>
-#endif
-
-#include <errno.h>
-#include <stdio.h>
-#include <time.h>
-
-#include <atomic>
-#include <cassert>
-#include <cstdint>
-#include <new>
-#include <type_traits>
-
-#include "absl/base/internal/raw_logging.h"
-#include "absl/base/internal/thread_identity.h"
-#include "absl/base/optimization.h"
-#include "absl/synchronization/internal/kernel_timeout.h"
-
-
-namespace absl {
-ABSL_NAMESPACE_BEGIN
-namespace synchronization_internal {
-
-static void MaybeBecomeIdle() {
- base_internal::ThreadIdentity *identity =
- base_internal::CurrentThreadIdentityIfPresent();
- assert(identity != nullptr);
- const bool is_idle = identity->is_idle.load(std::memory_order_relaxed);
- const int ticker = identity->ticker.load(std::memory_order_relaxed);
- const int wait_start = identity->wait_start.load(std::memory_order_relaxed);
- if (!is_idle && ticker - wait_start > Waiter::kIdlePeriods) {
- identity->is_idle.store(true, std::memory_order_relaxed);
- }
-}
-
-#if ABSL_WAITER_MODE == ABSL_WAITER_MODE_FUTEX
-
-Waiter::Waiter() {
- futex_.store(0, std::memory_order_relaxed);
-}
-
-Waiter::~Waiter() = default;
-
-bool Waiter::Wait(KernelTimeout t) {
- // Loop until we can atomically decrement futex from a positive
- // value, waiting on a futex while we believe it is zero.
- // Note that, since the thread ticker is just reset, we don't need to check
- // whether the thread is idle on the very first pass of the loop.
- bool first_pass = true;
-
- while (true) {
- int32_t x = futex_.load(std::memory_order_relaxed);
- while (x != 0) {
- if (!futex_.compare_exchange_weak(x, x - 1,
- std::memory_order_acquire,
- std::memory_order_relaxed)) {
- continue; // Raced with someone, retry.
- }
- return true; // Consumed a wakeup, we are done.
- }
-
- if (!first_pass) MaybeBecomeIdle();
- const int err = Futex::WaitUntil(&futex_, 0, t);
- if (err != 0) {
- if (err == -EINTR || err == -EWOULDBLOCK) {
- // Do nothing, the loop will retry.
- } else if (err == -ETIMEDOUT) {
- return false;
- } else {
- ABSL_RAW_LOG(FATAL, "Futex operation failed with error %d\n", err);
- }
- }
- first_pass = false;
- }
-}
-
-void Waiter::Post() {
- if (futex_.fetch_add(1, std::memory_order_release) == 0) {
- // We incremented from 0, need to wake a potential waiter.
- Poke();
- }
-}
-
-void Waiter::Poke() {
- // Wake one thread waiting on the futex.
- const int err = Futex::Wake(&futex_, 1);
- if (ABSL_PREDICT_FALSE(err < 0)) {
- ABSL_RAW_LOG(FATAL, "Futex operation failed with error %d\n", err);
- }
-}
-
-#elif ABSL_WAITER_MODE == ABSL_WAITER_MODE_CONDVAR
-
-class PthreadMutexHolder {
- public:
- explicit PthreadMutexHolder(pthread_mutex_t *mu) : mu_(mu) {
- const int err = pthread_mutex_lock(mu_);
- if (err != 0) {
- ABSL_RAW_LOG(FATAL, "pthread_mutex_lock failed: %d", err);
- }
- }
-
- PthreadMutexHolder(const PthreadMutexHolder &rhs) = delete;
- PthreadMutexHolder &operator=(const PthreadMutexHolder &rhs) = delete;
-
- ~PthreadMutexHolder() {
- const int err = pthread_mutex_unlock(mu_);
- if (err != 0) {
- ABSL_RAW_LOG(FATAL, "pthread_mutex_unlock failed: %d", err);
- }
- }
-
- private:
- pthread_mutex_t *mu_;
-};
-
-Waiter::Waiter() {
- const int err = pthread_mutex_init(&mu_, 0);
- if (err != 0) {
- ABSL_RAW_LOG(FATAL, "pthread_mutex_init failed: %d", err);
- }
-
- const int err2 = pthread_cond_init(&cv_, 0);
- if (err2 != 0) {
- ABSL_RAW_LOG(FATAL, "pthread_cond_init failed: %d", err2);
- }
-
- waiter_count_ = 0;
- wakeup_count_ = 0;
-}
-
-Waiter::~Waiter() {
- const int err = pthread_mutex_destroy(&mu_);
- if (err != 0) {
- ABSL_RAW_LOG(FATAL, "pthread_mutex_destroy failed: %d", err);
- }
-
- const int err2 = pthread_cond_destroy(&cv_);
- if (err2 != 0) {
- ABSL_RAW_LOG(FATAL, "pthread_cond_destroy failed: %d", err2);
- }
-}
-
-bool Waiter::Wait(KernelTimeout t) {
- struct timespec abs_timeout;
- if (t.has_timeout()) {
- abs_timeout = t.MakeAbsTimespec();
- }
-
- PthreadMutexHolder h(&mu_);
- ++waiter_count_;
- // Loop until we find a wakeup to consume or timeout.
- // Note that, since the thread ticker is just reset, we don't need to check
- // whether the thread is idle on the very first pass of the loop.
- bool first_pass = true;
- while (wakeup_count_ == 0) {
- if (!first_pass) MaybeBecomeIdle();
- // No wakeups available, time to wait.
- if (!t.has_timeout()) {
- const int err = pthread_cond_wait(&cv_, &mu_);
- if (err != 0) {
- ABSL_RAW_LOG(FATAL, "pthread_cond_wait failed: %d", err);
- }
- } else {
- const int err = pthread_cond_timedwait(&cv_, &mu_, &abs_timeout);
- if (err == ETIMEDOUT) {
- --waiter_count_;
- return false;
- }
- if (err != 0) {
- ABSL_RAW_LOG(FATAL, "pthread_cond_timedwait failed: %d", err);
- }
- }
- first_pass = false;
- }
- // Consume a wakeup and we're done.
- --wakeup_count_;
- --waiter_count_;
- return true;
-}
-
-void Waiter::Post() {
- PthreadMutexHolder h(&mu_);
- ++wakeup_count_;
- InternalCondVarPoke();
-}
-
-void Waiter::Poke() {
- PthreadMutexHolder h(&mu_);
- InternalCondVarPoke();
-}
-
-void Waiter::InternalCondVarPoke() {
- if (waiter_count_ != 0) {
- const int err = pthread_cond_signal(&cv_);
- if (ABSL_PREDICT_FALSE(err != 0)) {
- ABSL_RAW_LOG(FATAL, "pthread_cond_signal failed: %d", err);
- }
- }
-}
-
-#elif ABSL_WAITER_MODE == ABSL_WAITER_MODE_SEM
-
-Waiter::Waiter() {
- if (sem_init(&sem_, 0, 0) != 0) {
- ABSL_RAW_LOG(FATAL, "sem_init failed with errno %d\n", errno);
- }
- wakeups_.store(0, std::memory_order_relaxed);
-}
-
-Waiter::~Waiter() {
- if (sem_destroy(&sem_) != 0) {
- ABSL_RAW_LOG(FATAL, "sem_destroy failed with errno %d\n", errno);
- }
-}
-
-bool Waiter::Wait(KernelTimeout t) {
- struct timespec abs_timeout;
- if (t.has_timeout()) {
- abs_timeout = t.MakeAbsTimespec();
- }
-
- // Loop until we timeout or consume a wakeup.
- // Note that, since the thread ticker is just reset, we don't need to check
- // whether the thread is idle on the very first pass of the loop.
- bool first_pass = true;
- while (true) {
- int x = wakeups_.load(std::memory_order_relaxed);
- while (x != 0) {
- if (!wakeups_.compare_exchange_weak(x, x - 1,
- std::memory_order_acquire,
- std::memory_order_relaxed)) {
- continue; // Raced with someone, retry.
- }
- // Successfully consumed a wakeup, we're done.
- return true;
- }
-
- if (!first_pass) MaybeBecomeIdle();
- // Nothing to consume, wait (looping on EINTR).
- while (true) {
- if (!t.has_timeout()) {
- if (sem_wait(&sem_) == 0) break;
- if (errno == EINTR) continue;
- ABSL_RAW_LOG(FATAL, "sem_wait failed: %d", errno);
- } else {
- if (sem_timedwait(&sem_, &abs_timeout) == 0) break;
- if (errno == EINTR) continue;
- if (errno == ETIMEDOUT) return false;
- ABSL_RAW_LOG(FATAL, "sem_timedwait failed: %d", errno);
- }
- }
- first_pass = false;
- }
-}
-
-void Waiter::Post() {
- // Post a wakeup.
- if (wakeups_.fetch_add(1, std::memory_order_release) == 0) {
- // We incremented from 0, need to wake a potential waiter.
- Poke();
- }
-}
-
-void Waiter::Poke() {
- if (sem_post(&sem_) != 0) { // Wake any semaphore waiter.
- ABSL_RAW_LOG(FATAL, "sem_post failed with errno %d\n", errno);
- }
-}
-
-#elif ABSL_WAITER_MODE == ABSL_WAITER_MODE_WIN32
-
-class Waiter::WinHelper {
- public:
- static SRWLOCK *GetLock(Waiter *w) {
- return reinterpret_cast<SRWLOCK *>(&w->mu_storage_);
- }
-
- static CONDITION_VARIABLE *GetCond(Waiter *w) {
- return reinterpret_cast<CONDITION_VARIABLE *>(&w->cv_storage_);
- }
-
- static_assert(sizeof(SRWLOCK) == sizeof(void *),
- "`mu_storage_` does not have the same size as SRWLOCK");
- static_assert(alignof(SRWLOCK) == alignof(void *),
- "`mu_storage_` does not have the same alignment as SRWLOCK");
-
- static_assert(sizeof(CONDITION_VARIABLE) == sizeof(void *),
- "`ABSL_CONDITION_VARIABLE_STORAGE` does not have the same size "
- "as `CONDITION_VARIABLE`");
- static_assert(
- alignof(CONDITION_VARIABLE) == alignof(void *),
- "`cv_storage_` does not have the same alignment as `CONDITION_VARIABLE`");
-
- // The SRWLOCK and CONDITION_VARIABLE types must be trivially constructible
- // and destructible because we never call their constructors or destructors.
- static_assert(std::is_trivially_constructible<SRWLOCK>::value,
- "The `SRWLOCK` type must be trivially constructible");
- static_assert(
- std::is_trivially_constructible<CONDITION_VARIABLE>::value,
- "The `CONDITION_VARIABLE` type must be trivially constructible");
- static_assert(std::is_trivially_destructible<SRWLOCK>::value,
- "The `SRWLOCK` type must be trivially destructible");
- static_assert(std::is_trivially_destructible<CONDITION_VARIABLE>::value,
- "The `CONDITION_VARIABLE` type must be trivially destructible");
-};
-
-class LockHolder {
- public:
- explicit LockHolder(SRWLOCK* mu) : mu_(mu) {
- AcquireSRWLockExclusive(mu_);
- }
-
- LockHolder(const LockHolder&) = delete;
- LockHolder& operator=(const LockHolder&) = delete;
-
- ~LockHolder() {
- ReleaseSRWLockExclusive(mu_);
- }
-
- private:
- SRWLOCK* mu_;
-};
-
-Waiter::Waiter() {
- auto *mu = ::new (static_cast<void *>(&mu_storage_)) SRWLOCK;
- auto *cv = ::new (static_cast<void *>(&cv_storage_)) CONDITION_VARIABLE;
- InitializeSRWLock(mu);
- InitializeConditionVariable(cv);
- waiter_count_ = 0;
- wakeup_count_ = 0;
-}
-
-// SRW locks and condition variables do not need to be explicitly destroyed.
-// https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-initializesrwlock
-// https://stackoverflow.com/questions/28975958/why-does-windows-have-no-deleteconditionvariable-function-to-go-together-with
-Waiter::~Waiter() = default;
-
-bool Waiter::Wait(KernelTimeout t) {
- SRWLOCK *mu = WinHelper::GetLock(this);
- CONDITION_VARIABLE *cv = WinHelper::GetCond(this);
-
- LockHolder h(mu);
- ++waiter_count_;
-
- // Loop until we find a wakeup to consume or timeout.
- // Note that, since the thread ticker is just reset, we don't need to check
- // whether the thread is idle on the very first pass of the loop.
- bool first_pass = true;
- while (wakeup_count_ == 0) {
- if (!first_pass) MaybeBecomeIdle();
- // No wakeups available, time to wait.
- if (!SleepConditionVariableSRW(cv, mu, t.InMillisecondsFromNow(), 0)) {
- // GetLastError() returns a Win32 DWORD, but we assign to
- // unsigned long to simplify the ABSL_RAW_LOG case below. The uniform
- // initialization guarantees this is not a narrowing conversion.
- const unsigned long err{GetLastError()}; // NOLINT(runtime/int)
- if (err == ERROR_TIMEOUT) {
- --waiter_count_;
- return false;
- } else {
- ABSL_RAW_LOG(FATAL, "SleepConditionVariableSRW failed: %lu", err);
- }
- }
- first_pass = false;
- }
- // Consume a wakeup and we're done.
- --wakeup_count_;
- --waiter_count_;
- return true;
-}
-
-void Waiter::Post() {
- LockHolder h(WinHelper::GetLock(this));
- ++wakeup_count_;
- InternalCondVarPoke();
-}
-
-void Waiter::Poke() {
- LockHolder h(WinHelper::GetLock(this));
- InternalCondVarPoke();
-}
-
-void Waiter::InternalCondVarPoke() {
- if (waiter_count_ != 0) {
- WakeConditionVariable(WinHelper::GetCond(this));
- }
-}
-
-#else
-#error Unknown ABSL_WAITER_MODE
-#endif
-
-} // namespace synchronization_internal
-ABSL_NAMESPACE_END
-} // namespace absl
diff --git a/absl/synchronization/internal/waiter.h b/absl/synchronization/internal/waiter.h
index be3df18..6ba204b 100644
--- a/absl/synchronization/internal/waiter.h
+++ b/absl/synchronization/internal/waiter.h
@@ -17,136 +17,50 @@
#define ABSL_SYNCHRONIZATION_INTERNAL_WAITER_H_
#include "absl/base/config.h"
-
-#ifdef _WIN32
-#include <sdkddkver.h>
-#else
-#include <pthread.h>
-#endif
-
-#ifdef __linux__
-#include <linux/futex.h>
-#endif
-
-#ifdef ABSL_HAVE_SEMAPHORE_H
-#include <semaphore.h>
-#endif
-
-#include <atomic>
-#include <cstdint>
-
-#include "absl/base/internal/thread_identity.h"
-#include "absl/synchronization/internal/futex.h"
-#include "absl/synchronization/internal/kernel_timeout.h"
+#include "absl/synchronization/internal/futex_waiter.h"
+#include "absl/synchronization/internal/pthread_waiter.h"
+#include "absl/synchronization/internal/sem_waiter.h"
+#include "absl/synchronization/internal/stdcpp_waiter.h"
+#include "absl/synchronization/internal/win32_waiter.h"
// May be chosen at compile time via -DABSL_FORCE_WAITER_MODE=<index>
#define ABSL_WAITER_MODE_FUTEX 0
#define ABSL_WAITER_MODE_SEM 1
#define ABSL_WAITER_MODE_CONDVAR 2
#define ABSL_WAITER_MODE_WIN32 3
+#define ABSL_WAITER_MODE_STDCPP 4
#if defined(ABSL_FORCE_WAITER_MODE)
#define ABSL_WAITER_MODE ABSL_FORCE_WAITER_MODE
-#elif defined(_WIN32) && _WIN32_WINNT >= _WIN32_WINNT_VISTA
+#elif defined(ABSL_INTERNAL_HAVE_WIN32_WAITER)
#define ABSL_WAITER_MODE ABSL_WAITER_MODE_WIN32
-#elif defined(ABSL_INTERNAL_HAVE_FUTEX)
+#elif defined(ABSL_INTERNAL_HAVE_FUTEX_WAITER)
#define ABSL_WAITER_MODE ABSL_WAITER_MODE_FUTEX
-#elif defined(ABSL_HAVE_SEMAPHORE_H)
+#elif defined(ABSL_INTERNAL_HAVE_SEM_WAITER)
#define ABSL_WAITER_MODE ABSL_WAITER_MODE_SEM
-#else
+#elif defined(ABSL_INTERNAL_HAVE_PTHREAD_WAITER)
#define ABSL_WAITER_MODE ABSL_WAITER_MODE_CONDVAR
+#elif defined(ABSL_INTERNAL_HAVE_STDCPP_WAITER)
+#define ABSL_WAITER_MODE ABSL_WAITER_MODE_STDCPP
+#else
+#error ABSL_WAITER_MODE is undefined
#endif
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace synchronization_internal {
-// Waiter is an OS-specific semaphore.
-class Waiter {
- public:
- // Prepare any data to track waits.
- Waiter();
-
- // Not copyable or movable
- Waiter(const Waiter&) = delete;
- Waiter& operator=(const Waiter&) = delete;
-
- // Destroy any data to track waits.
- ~Waiter();
-
- // Blocks the calling thread until a matching call to `Post()` or
- // `t` has passed. Returns `true` if woken (`Post()` called),
- // `false` on timeout.
- bool Wait(KernelTimeout t);
-
- // Restart the caller of `Wait()` as with a normal semaphore.
- void Post();
-
- // If anyone is waiting, wake them up temporarily and cause them to
- // call `MaybeBecomeIdle()`. They will then return to waiting for a
- // `Post()` or timeout.
- void Poke();
-
- // Returns the Waiter associated with the identity.
- static Waiter* GetWaiter(base_internal::ThreadIdentity* identity) {
- static_assert(
- sizeof(Waiter) <= sizeof(base_internal::ThreadIdentity::WaiterState),
- "Insufficient space for Waiter");
- return reinterpret_cast<Waiter*>(identity->waiter_state.data);
- }
-
- // How many periods to remain idle before releasing resources
-#ifndef ABSL_HAVE_THREAD_SANITIZER
- static constexpr int kIdlePeriods = 60;
-#else
- // Memory consumption under ThreadSanitizer is a serious concern,
- // so we release resources sooner. The value of 1 leads to 1 to 2 second
- // delay before marking a thread as idle.
- static const int kIdlePeriods = 1;
-#endif
-
- private:
#if ABSL_WAITER_MODE == ABSL_WAITER_MODE_FUTEX
- // Futexes are defined by specification to be 32-bits.
- // Thus std::atomic<int32_t> must be just an int32_t with lockfree methods.
- std::atomic<int32_t> futex_;
- static_assert(sizeof(int32_t) == sizeof(futex_), "Wrong size for futex");
-
-#elif ABSL_WAITER_MODE == ABSL_WAITER_MODE_CONDVAR
- // REQUIRES: mu_ must be held.
- void InternalCondVarPoke();
-
- pthread_mutex_t mu_;
- pthread_cond_t cv_;
- int waiter_count_;
- int wakeup_count_; // Unclaimed wakeups.
-
+using Waiter = FutexWaiter;
#elif ABSL_WAITER_MODE == ABSL_WAITER_MODE_SEM
- sem_t sem_;
- // This seems superfluous, but for Poke() we need to cause spurious
- // wakeups on the semaphore. Hence we can't actually use the
- // semaphore's count.
- std::atomic<int> wakeups_;
-
+using Waiter = SemWaiter;
+#elif ABSL_WAITER_MODE == ABSL_WAITER_MODE_CONDVAR
+using Waiter = PthreadWaiter;
#elif ABSL_WAITER_MODE == ABSL_WAITER_MODE_WIN32
- // WinHelper - Used to define utilities for accessing the lock and
- // condition variable storage once the types are complete.
- class WinHelper;
-
- // REQUIRES: WinHelper::GetLock(this) must be held.
- void InternalCondVarPoke();
-
- // We can't include Windows.h in our headers, so we use aligned charachter
- // buffers to define the storage of SRWLOCK and CONDITION_VARIABLE.
- alignas(void*) unsigned char mu_storage_[sizeof(void*)];
- alignas(void*) unsigned char cv_storage_[sizeof(void*)];
- int waiter_count_;
- int wakeup_count_;
-
-#else
- #error Unknown ABSL_WAITER_MODE
+using Waiter = Win32Waiter;
+#elif ABSL_WAITER_MODE == ABSL_WAITER_MODE_STDCPP
+using Waiter = StdcppWaiter;
#endif
-};
} // namespace synchronization_internal
ABSL_NAMESPACE_END
diff --git a/absl/synchronization/internal/waiter_base.cc b/absl/synchronization/internal/waiter_base.cc
new file mode 100644
index 0000000..46928b4
--- /dev/null
+++ b/absl/synchronization/internal/waiter_base.cc
@@ -0,0 +1,42 @@
+// Copyright 2023 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/synchronization/internal/waiter_base.h"
+
+#include "absl/base/config.h"
+#include "absl/base/internal/thread_identity.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace synchronization_internal {
+
+#ifdef ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL
+constexpr int WaiterBase::kIdlePeriods;
+#endif
+
+void WaiterBase::MaybeBecomeIdle() {
+ base_internal::ThreadIdentity *identity =
+ base_internal::CurrentThreadIdentityIfPresent();
+ assert(identity != nullptr);
+ const bool is_idle = identity->is_idle.load(std::memory_order_relaxed);
+ const int ticker = identity->ticker.load(std::memory_order_relaxed);
+ const int wait_start = identity->wait_start.load(std::memory_order_relaxed);
+ if (!is_idle && ticker - wait_start > kIdlePeriods) {
+ identity->is_idle.store(true, std::memory_order_relaxed);
+ }
+}
+
+} // namespace synchronization_internal
+ABSL_NAMESPACE_END
+} // namespace absl
diff --git a/absl/synchronization/internal/waiter_base.h b/absl/synchronization/internal/waiter_base.h
new file mode 100644
index 0000000..cf17548
--- /dev/null
+++ b/absl/synchronization/internal/waiter_base.h
@@ -0,0 +1,90 @@
+// Copyright 2023 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef ABSL_SYNCHRONIZATION_INTERNAL_WAITER_BASE_H_
+#define ABSL_SYNCHRONIZATION_INTERNAL_WAITER_BASE_H_
+
+#include "absl/base/config.h"
+#include "absl/base/internal/thread_identity.h"
+#include "absl/synchronization/internal/kernel_timeout.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace synchronization_internal {
+
+// `Waiter` is a platform specific semaphore implementation that `PerThreadSem`
+// waits on to implement blocking in `absl::Mutex`. Implementations should
+// inherit from `WaiterCrtp` and must implement `Wait()`, `Post()`, and `Poke()`
+// as described in `WaiterBase`. `waiter.h` selects the implementation and uses
+// static-dispatch for performance.
+class WaiterBase {
+ public:
+ WaiterBase() = default;
+
+ // Not copyable or movable
+ WaiterBase(const WaiterBase&) = delete;
+ WaiterBase& operator=(const WaiterBase&) = delete;
+
+ // Blocks the calling thread until a matching call to `Post()` or
+ // `t` has passed. Returns `true` if woken (`Post()` called),
+ // `false` on timeout.
+ //
+ // bool Wait(KernelTimeout t);
+
+ // Restart the caller of `Wait()` as with a normal semaphore.
+ //
+ // void Post();
+
+ // If anyone is waiting, wake them up temporarily and cause them to
+ // call `MaybeBecomeIdle()`. They will then return to waiting for a
+ // `Post()` or timeout.
+ //
+ // void Poke();
+
+ // Returns the name of this implementation. Used only for debugging.
+ //
+ // static constexpr char kName[];
+
+ // How many periods to remain idle before releasing resources
+#ifndef ABSL_HAVE_THREAD_SANITIZER
+ static constexpr int kIdlePeriods = 60;
+#else
+ // Memory consumption under ThreadSanitizer is a serious concern,
+ // so we release resources sooner. The value of 1 leads to 1 to 2 second
+ // delay before marking a thread as idle.
+ static constexpr int kIdlePeriods = 1;
+#endif
+
+ protected:
+ static void MaybeBecomeIdle();
+};
+
+template <typename T>
+class WaiterCrtp : public WaiterBase {
+ public:
+ // Returns the Waiter associated with the identity.
+ static T* GetWaiter(base_internal::ThreadIdentity* identity) {
+ static_assert(
+ sizeof(T) <= sizeof(base_internal::ThreadIdentity::WaiterState),
+ "Insufficient space for Waiter");
+ return reinterpret_cast<T*>(identity->waiter_state.data);
+ }
+};
+
+} // namespace synchronization_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_SYNCHRONIZATION_INTERNAL_WAITER_BASE_H_
diff --git a/absl/synchronization/internal/waiter_test.cc b/absl/synchronization/internal/waiter_test.cc
new file mode 100644
index 0000000..992db29
--- /dev/null
+++ b/absl/synchronization/internal/waiter_test.cc
@@ -0,0 +1,180 @@
+// Copyright 2023 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/synchronization/internal/waiter.h"
+
+#include <ctime>
+#include <iostream>
+#include <ostream>
+
+#include "absl/base/config.h"
+#include "absl/random/random.h"
+#include "absl/synchronization/internal/create_thread_identity.h"
+#include "absl/synchronization/internal/futex_waiter.h"
+#include "absl/synchronization/internal/kernel_timeout.h"
+#include "absl/synchronization/internal/pthread_waiter.h"
+#include "absl/synchronization/internal/sem_waiter.h"
+#include "absl/synchronization/internal/stdcpp_waiter.h"
+#include "absl/synchronization/internal/thread_pool.h"
+#include "absl/synchronization/internal/win32_waiter.h"
+#include "absl/time/clock.h"
+#include "absl/time/time.h"
+#include "gtest/gtest.h"
+
+// Test go/btm support by randomizing the value of clock_gettime() for
+// CLOCK_MONOTONIC. This works by overriding a weak symbol in glibc.
+// We should be resistant to this randomization when !SupportsSteadyClock().
+#if defined(__GOOGLE_GRTE_VERSION__) && \
+ !defined(ABSL_HAVE_ADDRESS_SANITIZER) && \
+ !defined(ABSL_HAVE_MEMORY_SANITIZER) && \
+ !defined(ABSL_HAVE_THREAD_SANITIZER)
+extern "C" int __clock_gettime(clockid_t c, struct timespec* ts);
+
+extern "C" int clock_gettime(clockid_t c, struct timespec* ts) {
+ if (c == CLOCK_MONOTONIC &&
+ !absl::synchronization_internal::KernelTimeout::SupportsSteadyClock()) {
+ absl::SharedBitGen gen;
+ ts->tv_sec = absl::Uniform(gen, 0, 1'000'000'000);
+ ts->tv_nsec = absl::Uniform(gen, 0, 1'000'000'000);
+ return 0;
+ }
+ return __clock_gettime(c, ts);
+}
+#endif
+
+namespace {
+
+TEST(Waiter, PrintPlatformImplementation) {
+ // Allows us to verify that the platform is using the expected implementation.
+ std::cout << absl::synchronization_internal::Waiter::kName << std::endl;
+}
+
+template <typename T>
+class WaiterTest : public ::testing::Test {
+ public:
+ // Waiter implementations assume that a ThreadIdentity has already been
+ // created.
+ WaiterTest() {
+ absl::synchronization_internal::GetOrCreateCurrentThreadIdentity();
+ }
+};
+
+TYPED_TEST_SUITE_P(WaiterTest);
+
+absl::Duration WithTolerance(absl::Duration d) { return d * 0.95; }
+
+TYPED_TEST_P(WaiterTest, WaitNoTimeout) {
+ absl::synchronization_internal::ThreadPool tp(1);
+ TypeParam waiter;
+ tp.Schedule([&]() {
+ // Include some `Poke()` calls to ensure they don't cause `waiter` to return
+ // from `Wait()`.
+ waiter.Poke();
+ absl::SleepFor(absl::Seconds(1));
+ waiter.Poke();
+ absl::SleepFor(absl::Seconds(1));
+ waiter.Post();
+ });
+ absl::Time start = absl::Now();
+ EXPECT_TRUE(
+ waiter.Wait(absl::synchronization_internal::KernelTimeout::Never()));
+ absl::Duration waited = absl::Now() - start;
+ EXPECT_GE(waited, WithTolerance(absl::Seconds(2)));
+}
+
+TYPED_TEST_P(WaiterTest, WaitDurationWoken) {
+ absl::synchronization_internal::ThreadPool tp(1);
+ TypeParam waiter;
+ tp.Schedule([&]() {
+ // Include some `Poke()` calls to ensure they don't cause `waiter` to return
+ // from `Wait()`.
+ waiter.Poke();
+ absl::SleepFor(absl::Milliseconds(500));
+ waiter.Post();
+ });
+ absl::Time start = absl::Now();
+ EXPECT_TRUE(waiter.Wait(
+ absl::synchronization_internal::KernelTimeout(absl::Seconds(10))));
+ absl::Duration waited = absl::Now() - start;
+ EXPECT_GE(waited, WithTolerance(absl::Milliseconds(500)));
+ EXPECT_LT(waited, absl::Seconds(2));
+}
+
+TYPED_TEST_P(WaiterTest, WaitTimeWoken) {
+ absl::synchronization_internal::ThreadPool tp(1);
+ TypeParam waiter;
+ tp.Schedule([&]() {
+ // Include some `Poke()` calls to ensure they don't cause `waiter` to return
+ // from `Wait()`.
+ waiter.Poke();
+ absl::SleepFor(absl::Milliseconds(500));
+ waiter.Post();
+ });
+ absl::Time start = absl::Now();
+ EXPECT_TRUE(waiter.Wait(absl::synchronization_internal::KernelTimeout(
+ start + absl::Seconds(10))));
+ absl::Duration waited = absl::Now() - start;
+ EXPECT_GE(waited, WithTolerance(absl::Milliseconds(500)));
+ EXPECT_LT(waited, absl::Seconds(2));
+}
+
+TYPED_TEST_P(WaiterTest, WaitDurationReached) {
+ TypeParam waiter;
+ absl::Time start = absl::Now();
+ EXPECT_FALSE(waiter.Wait(
+ absl::synchronization_internal::KernelTimeout(absl::Milliseconds(500))));
+ absl::Duration waited = absl::Now() - start;
+ EXPECT_GE(waited, WithTolerance(absl::Milliseconds(500)));
+ EXPECT_LT(waited, absl::Seconds(1));
+}
+
+TYPED_TEST_P(WaiterTest, WaitTimeReached) {
+ TypeParam waiter;
+ absl::Time start = absl::Now();
+ EXPECT_FALSE(waiter.Wait(absl::synchronization_internal::KernelTimeout(
+ start + absl::Milliseconds(500))));
+ absl::Duration waited = absl::Now() - start;
+ EXPECT_GE(waited, WithTolerance(absl::Milliseconds(500)));
+ EXPECT_LT(waited, absl::Seconds(1));
+}
+
+REGISTER_TYPED_TEST_SUITE_P(WaiterTest,
+ WaitNoTimeout,
+ WaitDurationWoken,
+ WaitTimeWoken,
+ WaitDurationReached,
+ WaitTimeReached);
+
+#ifdef ABSL_INTERNAL_HAVE_FUTEX_WAITER
+INSTANTIATE_TYPED_TEST_SUITE_P(Futex, WaiterTest,
+ absl::synchronization_internal::FutexWaiter);
+#endif
+#ifdef ABSL_INTERNAL_HAVE_PTHREAD_WAITER
+INSTANTIATE_TYPED_TEST_SUITE_P(Pthread, WaiterTest,
+ absl::synchronization_internal::PthreadWaiter);
+#endif
+#ifdef ABSL_INTERNAL_HAVE_SEM_WAITER
+INSTANTIATE_TYPED_TEST_SUITE_P(Sem, WaiterTest,
+ absl::synchronization_internal::SemWaiter);
+#endif
+#ifdef ABSL_INTERNAL_HAVE_WIN32_WAITER
+INSTANTIATE_TYPED_TEST_SUITE_P(Win32, WaiterTest,
+ absl::synchronization_internal::Win32Waiter);
+#endif
+#ifdef ABSL_INTERNAL_HAVE_STDCPP_WAITER
+INSTANTIATE_TYPED_TEST_SUITE_P(Stdcpp, WaiterTest,
+ absl::synchronization_internal::StdcppWaiter);
+#endif
+
+} // namespace
diff --git a/absl/synchronization/internal/win32_waiter.cc b/absl/synchronization/internal/win32_waiter.cc
new file mode 100644
index 0000000..bd95ff0
--- /dev/null
+++ b/absl/synchronization/internal/win32_waiter.cc
@@ -0,0 +1,151 @@
+// Copyright 2023 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/synchronization/internal/win32_waiter.h"
+
+#ifdef ABSL_INTERNAL_HAVE_WIN32_WAITER
+
+#include <windows.h>
+
+#include "absl/base/config.h"
+#include "absl/base/internal/raw_logging.h"
+#include "absl/base/internal/thread_identity.h"
+#include "absl/base/optimization.h"
+#include "absl/synchronization/internal/kernel_timeout.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace synchronization_internal {
+
+#ifdef ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL
+constexpr char Win32Waiter::kName[];
+#endif
+
+class Win32Waiter::WinHelper {
+ public:
+ static SRWLOCK *GetLock(Win32Waiter *w) {
+ return reinterpret_cast<SRWLOCK *>(&w->mu_storage_);
+ }
+
+ static CONDITION_VARIABLE *GetCond(Win32Waiter *w) {
+ return reinterpret_cast<CONDITION_VARIABLE *>(&w->cv_storage_);
+ }
+
+ static_assert(sizeof(SRWLOCK) == sizeof(void *),
+ "`mu_storage_` does not have the same size as SRWLOCK");
+ static_assert(alignof(SRWLOCK) == alignof(void *),
+ "`mu_storage_` does not have the same alignment as SRWLOCK");
+
+ static_assert(sizeof(CONDITION_VARIABLE) == sizeof(void *),
+ "`ABSL_CONDITION_VARIABLE_STORAGE` does not have the same size "
+ "as `CONDITION_VARIABLE`");
+ static_assert(
+ alignof(CONDITION_VARIABLE) == alignof(void *),
+ "`cv_storage_` does not have the same alignment as `CONDITION_VARIABLE`");
+
+ // The SRWLOCK and CONDITION_VARIABLE types must be trivially constructible
+ // and destructible because we never call their constructors or destructors.
+ static_assert(std::is_trivially_constructible<SRWLOCK>::value,
+ "The `SRWLOCK` type must be trivially constructible");
+ static_assert(
+ std::is_trivially_constructible<CONDITION_VARIABLE>::value,
+ "The `CONDITION_VARIABLE` type must be trivially constructible");
+ static_assert(std::is_trivially_destructible<SRWLOCK>::value,
+ "The `SRWLOCK` type must be trivially destructible");
+ static_assert(std::is_trivially_destructible<CONDITION_VARIABLE>::value,
+ "The `CONDITION_VARIABLE` type must be trivially destructible");
+};
+
+class LockHolder {
+ public:
+ explicit LockHolder(SRWLOCK* mu) : mu_(mu) {
+ AcquireSRWLockExclusive(mu_);
+ }
+
+ LockHolder(const LockHolder&) = delete;
+ LockHolder& operator=(const LockHolder&) = delete;
+
+ ~LockHolder() {
+ ReleaseSRWLockExclusive(mu_);
+ }
+
+ private:
+ SRWLOCK* mu_;
+};
+
+Win32Waiter::Win32Waiter() {
+ auto *mu = ::new (static_cast<void *>(&mu_storage_)) SRWLOCK;
+ auto *cv = ::new (static_cast<void *>(&cv_storage_)) CONDITION_VARIABLE;
+ InitializeSRWLock(mu);
+ InitializeConditionVariable(cv);
+ waiter_count_ = 0;
+ wakeup_count_ = 0;
+}
+
+bool Win32Waiter::Wait(KernelTimeout t) {
+ SRWLOCK *mu = WinHelper::GetLock(this);
+ CONDITION_VARIABLE *cv = WinHelper::GetCond(this);
+
+ LockHolder h(mu);
+ ++waiter_count_;
+
+ // Loop until we find a wakeup to consume or timeout.
+ // Note that, since the thread ticker is just reset, we don't need to check
+ // whether the thread is idle on the very first pass of the loop.
+ bool first_pass = true;
+ while (wakeup_count_ == 0) {
+ if (!first_pass) MaybeBecomeIdle();
+ // No wakeups available, time to wait.
+ if (!SleepConditionVariableSRW(cv, mu, t.InMillisecondsFromNow(), 0)) {
+ // GetLastError() returns a Win32 DWORD, but we assign to
+ // unsigned long to simplify the ABSL_RAW_LOG case below. The uniform
+ // initialization guarantees this is not a narrowing conversion.
+ const unsigned long err{GetLastError()}; // NOLINT(runtime/int)
+ if (err == ERROR_TIMEOUT) {
+ --waiter_count_;
+ return false;
+ } else {
+ ABSL_RAW_LOG(FATAL, "SleepConditionVariableSRW failed: %lu", err);
+ }
+ }
+ first_pass = false;
+ }
+ // Consume a wakeup and we're done.
+ --wakeup_count_;
+ --waiter_count_;
+ return true;
+}
+
+void Win32Waiter::Post() {
+ LockHolder h(WinHelper::GetLock(this));
+ ++wakeup_count_;
+ InternalCondVarPoke();
+}
+
+void Win32Waiter::Poke() {
+ LockHolder h(WinHelper::GetLock(this));
+ InternalCondVarPoke();
+}
+
+void Win32Waiter::InternalCondVarPoke() {
+ if (waiter_count_ != 0) {
+ WakeConditionVariable(WinHelper::GetCond(this));
+ }
+}
+
+} // namespace synchronization_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_INTERNAL_HAVE_WIN32_WAITER
diff --git a/absl/synchronization/internal/win32_waiter.h b/absl/synchronization/internal/win32_waiter.h
new file mode 100644
index 0000000..fdab264
--- /dev/null
+++ b/absl/synchronization/internal/win32_waiter.h
@@ -0,0 +1,72 @@
+// Copyright 2023 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef ABSL_SYNCHRONIZATION_INTERNAL_WIN32_WAITER_H_
+#define ABSL_SYNCHRONIZATION_INTERNAL_WIN32_WAITER_H_
+
+#ifdef _WIN32
+#include <sdkddkver.h>
+#endif
+
+#if defined(_WIN32) && !defined(__MINGW32__) && \
+ _WIN32_WINNT >= _WIN32_WINNT_VISTA
+
+#include "absl/base/config.h"
+#include "absl/synchronization/internal/kernel_timeout.h"
+#include "absl/synchronization/internal/waiter_base.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace synchronization_internal {
+
+#define ABSL_INTERNAL_HAVE_WIN32_WAITER 1
+
+class Win32Waiter : public WaiterCrtp<Win32Waiter> {
+ public:
+ Win32Waiter();
+
+ bool Wait(KernelTimeout t);
+ void Post();
+ void Poke();
+
+ static constexpr char kName[] = "Win32Waiter";
+
+ private:
+ // WinHelper - Used to define utilities for accessing the lock and
+ // condition variable storage once the types are complete.
+ class WinHelper;
+
+ // REQUIRES: WinHelper::GetLock(this) must be held.
+ void InternalCondVarPoke();
+
+ // We can't include Windows.h in our headers, so we use aligned character
+ // buffers to define the storage of SRWLOCK and CONDITION_VARIABLE.
+ // SRW locks and condition variables do not need to be explicitly destroyed.
+ // https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-initializesrwlock
+ // https://stackoverflow.com/questions/28975958/why-does-windows-have-no-deleteconditionvariable-function-to-go-together-with
+ alignas(void*) unsigned char mu_storage_[sizeof(void*)];
+ alignas(void*) unsigned char cv_storage_[sizeof(void*)];
+ int waiter_count_;
+ int wakeup_count_;
+};
+
+} // namespace synchronization_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // defined(_WIN32) && !defined(__MINGW32__) &&
+ // _WIN32_WINNT >= _WIN32_WINNT_VISTA
+
+#endif // ABSL_SYNCHRONIZATION_INTERNAL_WIN32_WAITER_H_
diff --git a/absl/synchronization/lifetime_test.cc b/absl/synchronization/lifetime_test.cc
index cc973a3..4c4cff6 100644
--- a/absl/synchronization/lifetime_test.cc
+++ b/absl/synchronization/lifetime_test.cc
@@ -18,8 +18,8 @@
#include "absl/base/attributes.h"
#include "absl/base/const_init.h"
-#include "absl/base/internal/raw_logging.h"
#include "absl/base/thread_annotations.h"
+#include "absl/log/check.h"
#include "absl/synchronization/mutex.h"
#include "absl/synchronization/notification.h"
@@ -35,20 +35,20 @@
// Thread two waits on 'notification', then sets 'state' inside the 'mutex',
// signalling the change via 'condvar'.
//
-// These tests use ABSL_RAW_CHECK to validate invariants, rather than EXPECT or
-// ASSERT from gUnit, because we need to invoke them during global destructors,
-// when gUnit teardown would have already begun.
+// These tests use CHECK to validate invariants, rather than EXPECT or ASSERT
+// from gUnit, because we need to invoke them during global destructors, when
+// gUnit teardown would have already begun.
void ThreadOne(absl::Mutex* mutex, absl::CondVar* condvar,
absl::Notification* notification, bool* state) {
// Test that the notification is in a valid initial state.
- ABSL_RAW_CHECK(!notification->HasBeenNotified(), "invalid Notification");
- ABSL_RAW_CHECK(*state == false, "*state not initialized");
+ CHECK(!notification->HasBeenNotified()) << "invalid Notification";
+ CHECK(!*state) << "*state not initialized";
{
absl::MutexLock lock(mutex);
notification->Notify();
- ABSL_RAW_CHECK(notification->HasBeenNotified(), "invalid Notification");
+ CHECK(notification->HasBeenNotified()) << "invalid Notification";
while (*state == false) {
condvar->Wait(mutex);
@@ -58,11 +58,11 @@
void ThreadTwo(absl::Mutex* mutex, absl::CondVar* condvar,
absl::Notification* notification, bool* state) {
- ABSL_RAW_CHECK(*state == false, "*state not initialized");
+ CHECK(!*state) << "*state not initialized";
// Wake thread one
notification->WaitForNotification();
- ABSL_RAW_CHECK(notification->HasBeenNotified(), "invalid Notification");
+ CHECK(notification->HasBeenNotified()) << "invalid Notification";
{
absl::MutexLock lock(mutex);
*state = true;
@@ -123,10 +123,9 @@
};
// These tests require that the compiler correctly supports C++11 constant
-// initialization... but MSVC has a known regression since v19.10:
+// initialization... but MSVC has a known regression (since v19.10) till v19.25:
// https://developercommunity.visualstudio.com/content/problem/336946/class-with-constexpr-constructor-not-using-static.html
-// TODO(epastor): Limit the affected range once MSVC fixes this bug.
-#if defined(__clang__) || !(defined(_MSC_VER) && _MSC_VER > 1900)
+#if defined(__clang__) || !(defined(_MSC_VER) && _MSC_VER < 1925)
// kConstInit
// Test early usage. (Declaration comes first; definitions must appear after
// the test runner.)
diff --git a/absl/synchronization/mutex.cc b/absl/synchronization/mutex.cc
index 76ad41f..cb3c7e7 100644
--- a/absl/synchronization/mutex.cc
+++ b/absl/synchronization/mutex.cc
@@ -35,7 +35,9 @@
#include <algorithm>
#include <atomic>
-#include <cinttypes>
+#include <cstddef>
+#include <cstdlib>
+#include <cstring>
#include <thread> // NOLINT(build/c++11)
#include "absl/base/attributes.h"
@@ -51,7 +53,7 @@
#include "absl/base/internal/sysinfo.h"
#include "absl/base/internal/thread_identity.h"
#include "absl/base/internal/tsan_mutex_interface.h"
-#include "absl/base/port.h"
+#include "absl/base/optimization.h"
#include "absl/debugging/stacktrace.h"
#include "absl/debugging/symbolize.h"
#include "absl/synchronization/internal/graphcycles.h"
@@ -59,6 +61,7 @@
#include "absl/time/time.h"
using absl::base_internal::CurrentThreadIdentityIfPresent;
+using absl::base_internal::CycleClock;
using absl::base_internal::PerThreadSynch;
using absl::base_internal::SchedulingGuard;
using absl::base_internal::ThreadIdentity;
@@ -94,38 +97,31 @@
absl::base_internal::AtomicHook<void (*)(int64_t wait_cycles)>
submit_profile_data;
ABSL_INTERNAL_ATOMIC_HOOK_ATTRIBUTES absl::base_internal::AtomicHook<void (*)(
- const char *msg, const void *obj, int64_t wait_cycles)>
+ const char* msg, const void* obj, int64_t wait_cycles)>
mutex_tracer;
ABSL_INTERNAL_ATOMIC_HOOK_ATTRIBUTES
- absl::base_internal::AtomicHook<void (*)(const char *msg, const void *cv)>
- cond_var_tracer;
-ABSL_INTERNAL_ATOMIC_HOOK_ATTRIBUTES absl::base_internal::AtomicHook<
- bool (*)(const void *pc, char *out, int out_size)>
- symbolizer(absl::Symbolize);
+absl::base_internal::AtomicHook<void (*)(const char* msg, const void* cv)>
+ cond_var_tracer;
} // namespace
-static inline bool EvalConditionAnnotated(const Condition *cond, Mutex *mu,
+static inline bool EvalConditionAnnotated(const Condition* cond, Mutex* mu,
bool locking, bool trylock,
bool read_lock);
-void RegisterMutexProfiler(void (*fn)(int64_t wait_timestamp)) {
+void RegisterMutexProfiler(void (*fn)(int64_t wait_cycles)) {
submit_profile_data.Store(fn);
}
-void RegisterMutexTracer(void (*fn)(const char *msg, const void *obj,
+void RegisterMutexTracer(void (*fn)(const char* msg, const void* obj,
int64_t wait_cycles)) {
mutex_tracer.Store(fn);
}
-void RegisterCondVarTracer(void (*fn)(const char *msg, const void *cv)) {
+void RegisterCondVarTracer(void (*fn)(const char* msg, const void* cv)) {
cond_var_tracer.Store(fn);
}
-void RegisterSymbolizer(bool (*fn)(const void *pc, char *out, int out_size)) {
- symbolizer.Store(fn);
-}
-
namespace {
// Represents the strategy for spin and yield.
// See the comment in GetMutexGlobals() for more information.
@@ -133,29 +129,46 @@
struct ABSL_CACHELINE_ALIGNED MutexGlobals {
absl::once_flag once;
- int spinloop_iterations = 0;
- int32_t mutex_sleep_limit[2] = {};
+ // Note: this variable is initialized separately in Mutex::LockSlow,
+ // so that Mutex::Lock does not have a stack frame in optimized build.
+ std::atomic<int> spinloop_iterations{0};
+ int32_t mutex_sleep_spins[2] = {};
+ absl::Duration mutex_sleep_time;
};
-const MutexGlobals &GetMutexGlobals() {
- ABSL_CONST_INIT static MutexGlobals data;
- absl::base_internal::LowLevelCallOnce(&data.once, [&]() {
- const int num_cpus = absl::base_internal::NumCPUs();
- data.spinloop_iterations = num_cpus > 1 ? 1500 : 0;
- // If this a uniprocessor, only yield/sleep. Otherwise, if the mode is
- // aggressive then spin many times before yielding. If the mode is
- // gentle then spin only a few times before yielding. Aggressive spinning
- // is used to ensure that an Unlock() call, which must get the spin lock
- // for any thread to make progress gets it without undue delay.
- if (num_cpus > 1) {
- data.mutex_sleep_limit[AGGRESSIVE] = 5000;
- data.mutex_sleep_limit[GENTLE] = 250;
+ABSL_CONST_INIT static MutexGlobals globals;
+
+absl::Duration MeasureTimeToYield() {
+ absl::Time before = absl::Now();
+ ABSL_INTERNAL_C_SYMBOL(AbslInternalMutexYield)();
+ return absl::Now() - before;
+}
+
+const MutexGlobals& GetMutexGlobals() {
+ absl::base_internal::LowLevelCallOnce(&globals.once, [&]() {
+ if (absl::base_internal::NumCPUs() > 1) {
+ // If the mode is aggressive then spin many times before yielding.
+ // If the mode is gentle then spin only a few times before yielding.
+ // Aggressive spinning is used to ensure that an Unlock() call,
+ // which must get the spin lock for any thread to make progress gets it
+ // without undue delay.
+ globals.mutex_sleep_spins[AGGRESSIVE] = 5000;
+ globals.mutex_sleep_spins[GENTLE] = 250;
+ globals.mutex_sleep_time = absl::Microseconds(10);
} else {
- data.mutex_sleep_limit[AGGRESSIVE] = 0;
- data.mutex_sleep_limit[GENTLE] = 0;
+ // If this a uniprocessor, only yield/sleep. Real-time threads are often
+ // unable to yield, so the sleep time needs to be long enough to keep
+ // the calling thread asleep until scheduling happens.
+ globals.mutex_sleep_spins[AGGRESSIVE] = 0;
+ globals.mutex_sleep_spins[GENTLE] = 0;
+ globals.mutex_sleep_time = MeasureTimeToYield() * 5;
+ globals.mutex_sleep_time =
+ std::min(globals.mutex_sleep_time, absl::Milliseconds(1));
+ globals.mutex_sleep_time =
+ std::max(globals.mutex_sleep_time, absl::Microseconds(10));
}
});
- return data;
+ return globals;
}
} // namespace
@@ -163,7 +176,8 @@
// Returns the Mutex delay on iteration `c` depending on the given `mode`.
// The returned value should be used as `c` for the next call to `MutexDelay`.
int MutexDelay(int32_t c, int mode) {
- const int32_t limit = GetMutexGlobals().mutex_sleep_limit[mode];
+ const int32_t limit = GetMutexGlobals().mutex_sleep_spins[mode];
+ const absl::Duration sleep_time = GetMutexGlobals().mutex_sleep_time;
if (c < limit) {
// Spin.
c++;
@@ -176,7 +190,7 @@
c++;
} else {
// Then wait.
- absl::SleepFor(absl::Microseconds(10));
+ absl::SleepFor(sleep_time);
c = 0;
}
ABSL_TSAN_MUTEX_POST_DIVERT(nullptr, 0);
@@ -189,33 +203,23 @@
// Ensure that "(*pv & bits) == bits" by doing an atomic update of "*pv" to
// "*pv | bits" if necessary. Wait until (*pv & wait_until_clear)==0
// before making any change.
+// Returns true if bits were previously unset and set by the call.
// This is used to set flags in mutex and condition variable words.
-static void AtomicSetBits(std::atomic<intptr_t>* pv, intptr_t bits,
+static bool AtomicSetBits(std::atomic<intptr_t>* pv, intptr_t bits,
intptr_t wait_until_clear) {
- intptr_t v;
- do {
- v = pv->load(std::memory_order_relaxed);
- } while ((v & bits) != bits &&
- ((v & wait_until_clear) != 0 ||
- !pv->compare_exchange_weak(v, v | bits,
- std::memory_order_release,
- std::memory_order_relaxed)));
-}
-
-// Ensure that "(*pv & bits) == 0" by doing an atomic update of "*pv" to
-// "*pv & ~bits" if necessary. Wait until (*pv & wait_until_clear)==0
-// before making any change.
-// This is used to unset flags in mutex and condition variable words.
-static void AtomicClearBits(std::atomic<intptr_t>* pv, intptr_t bits,
- intptr_t wait_until_clear) {
- intptr_t v;
- do {
- v = pv->load(std::memory_order_relaxed);
- } while ((v & bits) != 0 &&
- ((v & wait_until_clear) != 0 ||
- !pv->compare_exchange_weak(v, v & ~bits,
- std::memory_order_release,
- std::memory_order_relaxed)));
+ for (;;) {
+ intptr_t v = pv->load(std::memory_order_relaxed);
+ if ((v & bits) == bits) {
+ return false;
+ }
+ if ((v & wait_until_clear) != 0) {
+ continue;
+ }
+ if (pv->compare_exchange_weak(v, v | bits, std::memory_order_release,
+ std::memory_order_relaxed)) {
+ return true;
+ }
+ }
}
//------------------------------------------------------------------
@@ -225,7 +229,7 @@
absl::kConstInit, base_internal::SCHEDULE_KERNEL_ONLY);
// Graph used to detect deadlocks.
-ABSL_CONST_INIT static GraphCycles *deadlock_graph
+ABSL_CONST_INIT static GraphCycles* deadlock_graph
ABSL_GUARDED_BY(deadlock_graph_mu) ABSL_PT_GUARDED_BY(deadlock_graph_mu);
//------------------------------------------------------------------
@@ -269,7 +273,7 @@
// Properties of the events.
static const struct {
int flags;
- const char *msg;
+ const char* msg;
} event_properties[] = {
{SYNCH_F_LCK_W | SYNCH_F_TRY, "TryLock succeeded "},
{0, "TryLock failed "},
@@ -294,12 +298,12 @@
// Can't be too small, as it's used for deadlock detection information.
static constexpr uint32_t kNSynchEvent = 1031;
-static struct SynchEvent { // this is a trivial hash table for the events
+static struct SynchEvent { // this is a trivial hash table for the events
// struct is freed when refcount reaches 0
int refcount ABSL_GUARDED_BY(synch_event_mu);
// buckets have linear, 0-terminated chains
- SynchEvent *next ABSL_GUARDED_BY(synch_event_mu);
+ SynchEvent* next ABSL_GUARDED_BY(synch_event_mu);
// Constant after initialization
uintptr_t masked_addr; // object at this address is called "name"
@@ -307,13 +311,13 @@
// No explicit synchronization used. Instead we assume that the
// client who enables/disables invariants/logging on a Mutex does so
// while the Mutex is not being concurrently accessed by others.
- void (*invariant)(void *arg); // called on each event
- void *arg; // first arg to (*invariant)()
- bool log; // logging turned on
+ void (*invariant)(void* arg); // called on each event
+ void* arg; // first arg to (*invariant)()
+ bool log; // logging turned on
// Constant after initialization
- char name[1]; // actually longer---NUL-terminated string
-} * synch_event[kNSynchEvent] ABSL_GUARDED_BY(synch_event_mu);
+ char name[1]; // actually longer---NUL-terminated string
+}* synch_event[kNSynchEvent] ABSL_GUARDED_BY(synch_event_mu);
// Ensure that the object at "addr" has a SynchEvent struct associated with it,
// set "bits" in the word there (waiting until lockbit is clear before doing
@@ -322,88 +326,94 @@
// the string name is copied into it.
// When used with a mutex, the caller should also ensure that kMuEvent
// is set in the mutex word, and similarly for condition variables and kCVEvent.
-static SynchEvent *EnsureSynchEvent(std::atomic<intptr_t> *addr,
- const char *name, intptr_t bits,
+static SynchEvent* EnsureSynchEvent(std::atomic<intptr_t>* addr,
+ const char* name, intptr_t bits,
intptr_t lockbit) {
- uint32_t h = reinterpret_cast<intptr_t>(addr) % kNSynchEvent;
- SynchEvent *e;
- // first look for existing SynchEvent struct..
+ uint32_t h = reinterpret_cast<uintptr_t>(addr) % kNSynchEvent;
synch_event_mu.Lock();
- for (e = synch_event[h];
- e != nullptr && e->masked_addr != base_internal::HidePtr(addr);
- e = e->next) {
+ // When a Mutex/CondVar is destroyed, we don't remove the associated
+ // SynchEvent to keep destructors empty in release builds for performance
+ // reasons. If the current call is the first to set bits (kMuEvent/kCVEvent),
+ // we don't look up the existing even because (if it exists, it must be for
+ // the previous Mutex/CondVar that existed at the same address).
+ // The leaking events must not be a problem for tests, which should create
+ // bounded amount of events. And debug logging is not supposed to be enabled
+ // in production. However, if it's accidentally enabled, or briefly enabled
+ // for some debugging, we don't want to crash the program. Instead we drop
+ // all events, if we accumulated too many of them. Size of a single event
+ // is ~48 bytes, so 100K events is ~5 MB.
+ // Additionally we could delete the old event for the same address,
+ // but it would require a better hashmap (if we accumulate too many events,
+ // linked lists will grow and traversing them will be very slow).
+ constexpr size_t kMaxSynchEventCount = 100 << 10;
+ // Total number of live synch events.
+ static size_t synch_event_count ABSL_GUARDED_BY(synch_event_mu);
+ if (++synch_event_count > kMaxSynchEventCount) {
+ synch_event_count = 0;
+ ABSL_RAW_LOG(ERROR,
+ "Accumulated %zu Mutex debug objects. If you see this"
+ " in production, it may mean that the production code"
+ " accidentally calls "
+ "Mutex/CondVar::EnableDebugLog/EnableInvariantDebugging.",
+ kMaxSynchEventCount);
+ for (auto*& head : synch_event) {
+ for (auto* e = head; e != nullptr;) {
+ SynchEvent* next = e->next;
+ if (--(e->refcount) == 0) {
+ base_internal::LowLevelAlloc::Free(e);
+ }
+ e = next;
+ }
+ head = nullptr;
+ }
+ }
+ SynchEvent* e = nullptr;
+ if (!AtomicSetBits(addr, bits, lockbit)) {
+ for (e = synch_event[h];
+ e != nullptr && e->masked_addr != base_internal::HidePtr(addr);
+ e = e->next) {
+ }
}
if (e == nullptr) { // no SynchEvent struct found; make one.
if (name == nullptr) {
name = "";
}
size_t l = strlen(name);
- e = reinterpret_cast<SynchEvent *>(
+ e = reinterpret_cast<SynchEvent*>(
base_internal::LowLevelAlloc::Alloc(sizeof(*e) + l));
- e->refcount = 2; // one for return value, one for linked list
+ e->refcount = 2; // one for return value, one for linked list
e->masked_addr = base_internal::HidePtr(addr);
e->invariant = nullptr;
e->arg = nullptr;
e->log = false;
strcpy(e->name, name); // NOLINT(runtime/printf)
e->next = synch_event[h];
- AtomicSetBits(addr, bits, lockbit);
synch_event[h] = e;
} else {
- e->refcount++; // for return value
+ e->refcount++; // for return value
}
synch_event_mu.Unlock();
return e;
}
-// Deallocate the SynchEvent *e, whose refcount has fallen to zero.
-static void DeleteSynchEvent(SynchEvent *e) {
- base_internal::LowLevelAlloc::Free(e);
-}
-
// Decrement the reference count of *e, or do nothing if e==null.
-static void UnrefSynchEvent(SynchEvent *e) {
+static void UnrefSynchEvent(SynchEvent* e) {
if (e != nullptr) {
synch_event_mu.Lock();
bool del = (--(e->refcount) == 0);
synch_event_mu.Unlock();
if (del) {
- DeleteSynchEvent(e);
+ base_internal::LowLevelAlloc::Free(e);
}
}
}
-// Forget the mapping from the object (Mutex or CondVar) at address addr
-// to SynchEvent object, and clear "bits" in its word (waiting until lockbit
-// is clear before doing so).
-static void ForgetSynchEvent(std::atomic<intptr_t> *addr, intptr_t bits,
- intptr_t lockbit) {
- uint32_t h = reinterpret_cast<intptr_t>(addr) % kNSynchEvent;
- SynchEvent **pe;
- SynchEvent *e;
- synch_event_mu.Lock();
- for (pe = &synch_event[h];
- (e = *pe) != nullptr && e->masked_addr != base_internal::HidePtr(addr);
- pe = &e->next) {
- }
- bool del = false;
- if (e != nullptr) {
- *pe = e->next;
- del = (--(e->refcount) == 0);
- }
- AtomicClearBits(addr, bits, lockbit);
- synch_event_mu.Unlock();
- if (del) {
- DeleteSynchEvent(e);
- }
-}
-
// Return a refcounted reference to the SynchEvent of the object at address
// "addr", if any. The pointer returned is valid until the UnrefSynchEvent() is
// called.
-static SynchEvent *GetSynchEvent(const void *addr) {
- uint32_t h = reinterpret_cast<intptr_t>(addr) % kNSynchEvent;
- SynchEvent *e;
+static SynchEvent* GetSynchEvent(const void* addr) {
+ uint32_t h = reinterpret_cast<uintptr_t>(addr) % kNSynchEvent;
+ SynchEvent* e;
synch_event_mu.Lock();
for (e = synch_event[h];
e != nullptr && e->masked_addr != base_internal::HidePtr(addr);
@@ -418,19 +428,25 @@
// Called when an event "ev" occurs on a Mutex of CondVar "obj"
// if event recording is on
-static void PostSynchEvent(void *obj, int ev) {
- SynchEvent *e = GetSynchEvent(obj);
+static void PostSynchEvent(void* obj, int ev) {
+ SynchEvent* e = GetSynchEvent(obj);
// logging is on if event recording is on and either there's no event struct,
// or it explicitly says to log
if (e == nullptr || e->log) {
- void *pcs[40];
+ void* pcs[40];
int n = absl::GetStackTrace(pcs, ABSL_ARRAYSIZE(pcs), 1);
// A buffer with enough space for the ASCII for all the PCs, even on a
// 64-bit machine.
char buffer[ABSL_ARRAYSIZE(pcs) * 24];
- int pos = snprintf(buffer, sizeof (buffer), " @");
+ int pos = snprintf(buffer, sizeof(buffer), " @");
for (int i = 0; i != n; i++) {
- pos += snprintf(&buffer[pos], sizeof (buffer) - pos, " %p", pcs[i]);
+ int b = snprintf(&buffer[pos], sizeof(buffer) - static_cast<size_t>(pos),
+ " %p", pcs[i]);
+ if (b < 0 ||
+ static_cast<size_t>(b) >= sizeof(buffer) - static_cast<size_t>(pos)) {
+ break;
+ }
+ pos += b;
}
ABSL_RAW_LOG(INFO, "%s%p %s %s", event_properties[ev].msg, obj,
(e == nullptr ? "" : e->name), buffer);
@@ -444,13 +460,13 @@
// get false positive race reports later.
// Reuse EvalConditionAnnotated to properly call into user code.
struct local {
- static bool pred(SynchEvent *ev) {
+ static bool pred(SynchEvent* ev) {
(*ev->invariant)(ev->arg);
return false;
}
};
Condition cond(&local::pred, e);
- Mutex *mu = static_cast<Mutex *>(obj);
+ Mutex* mu = static_cast<Mutex*>(obj);
const bool locking = (flags & SYNCH_F_UNLOCK) == 0;
const bool trylock = (flags & SYNCH_F_TRY) != 0;
const bool read_lock = (flags & SYNCH_F_R) != 0;
@@ -476,43 +492,45 @@
// PerThreadSynch struct points at the most recent SynchWaitParams struct when
// the thread is on a Mutex's waiter queue.
struct SynchWaitParams {
- SynchWaitParams(Mutex::MuHow how_arg, const Condition *cond_arg,
- KernelTimeout timeout_arg, Mutex *cvmu_arg,
- PerThreadSynch *thread_arg,
- std::atomic<intptr_t> *cv_word_arg)
+ SynchWaitParams(Mutex::MuHow how_arg, const Condition* cond_arg,
+ KernelTimeout timeout_arg, Mutex* cvmu_arg,
+ PerThreadSynch* thread_arg,
+ std::atomic<intptr_t>* cv_word_arg)
: how(how_arg),
cond(cond_arg),
timeout(timeout_arg),
cvmu(cvmu_arg),
thread(thread_arg),
cv_word(cv_word_arg),
- contention_start_cycles(base_internal::CycleClock::Now()) {}
+ contention_start_cycles(CycleClock::Now()),
+ should_submit_contention_data(false) {}
const Mutex::MuHow how; // How this thread needs to wait.
- const Condition *cond; // The condition that this thread is waiting for.
- // In Mutex, this field is set to zero if a timeout
- // expires.
+ const Condition* cond; // The condition that this thread is waiting for.
+ // In Mutex, this field is set to zero if a timeout
+ // expires.
KernelTimeout timeout; // timeout expiry---absolute time
// In Mutex, this field is set to zero if a timeout
// expires.
- Mutex *const cvmu; // used for transfer from cond var to mutex
- PerThreadSynch *const thread; // thread that is waiting
+ Mutex* const cvmu; // used for transfer from cond var to mutex
+ PerThreadSynch* const thread; // thread that is waiting
// If not null, thread should be enqueued on the CondVar whose state
// word is cv_word instead of queueing normally on the Mutex.
- std::atomic<intptr_t> *cv_word;
+ std::atomic<intptr_t>* cv_word;
int64_t contention_start_cycles; // Time (in cycles) when this thread started
// to contend for the mutex.
+ bool should_submit_contention_data;
};
struct SynchLocksHeld {
- int n; // number of valid entries in locks[]
- bool overflow; // true iff we overflowed the array at some point
+ int n; // number of valid entries in locks[]
+ bool overflow; // true iff we overflowed the array at some point
struct {
- Mutex *mu; // lock acquired
- int32_t count; // times acquired
- GraphId id; // deadlock_graph id of acquired lock
+ Mutex* mu; // lock acquired
+ int32_t count; // times acquired
+ GraphId id; // deadlock_graph id of acquired lock
} locks[40];
// If a thread overfills the array during deadlock detection, we
// continue, discarding information as needed. If no overflow has
@@ -522,11 +540,11 @@
// A sentinel value in lists that is not 0.
// A 0 value is used to mean "not on a list".
-static PerThreadSynch *const kPerThreadSynchNull =
- reinterpret_cast<PerThreadSynch *>(1);
+static PerThreadSynch* const kPerThreadSynchNull =
+ reinterpret_cast<PerThreadSynch*>(1);
-static SynchLocksHeld *LocksHeldAlloc() {
- SynchLocksHeld *ret = reinterpret_cast<SynchLocksHeld *>(
+static SynchLocksHeld* LocksHeldAlloc() {
+ SynchLocksHeld* ret = reinterpret_cast<SynchLocksHeld*>(
base_internal::LowLevelAlloc::Alloc(sizeof(SynchLocksHeld)));
ret->n = 0;
ret->overflow = false;
@@ -534,24 +552,24 @@
}
// Return the PerThreadSynch-struct for this thread.
-static PerThreadSynch *Synch_GetPerThread() {
- ThreadIdentity *identity = GetOrCreateCurrentThreadIdentity();
+static PerThreadSynch* Synch_GetPerThread() {
+ ThreadIdentity* identity = GetOrCreateCurrentThreadIdentity();
return &identity->per_thread_synch;
}
-static PerThreadSynch *Synch_GetPerThreadAnnotated(Mutex *mu) {
+static PerThreadSynch* Synch_GetPerThreadAnnotated(Mutex* mu) {
if (mu) {
ABSL_TSAN_MUTEX_PRE_DIVERT(mu, 0);
}
- PerThreadSynch *w = Synch_GetPerThread();
+ PerThreadSynch* w = Synch_GetPerThread();
if (mu) {
ABSL_TSAN_MUTEX_POST_DIVERT(mu, 0);
}
return w;
}
-static SynchLocksHeld *Synch_GetAllLocks() {
- PerThreadSynch *s = Synch_GetPerThread();
+static SynchLocksHeld* Synch_GetAllLocks() {
+ PerThreadSynch* s = Synch_GetPerThread();
if (s->all_locks == nullptr) {
s->all_locks = LocksHeldAlloc(); // Freed by ReclaimThreadIdentity.
}
@@ -559,27 +577,26 @@
}
// Post on "w"'s associated PerThreadSem.
-void Mutex::IncrementSynchSem(Mutex *mu, PerThreadSynch *w) {
- if (mu) {
- ABSL_TSAN_MUTEX_PRE_DIVERT(mu, 0);
- }
+void Mutex::IncrementSynchSem(Mutex* mu, PerThreadSynch* w) {
+ static_cast<void>(mu); // Prevent unused param warning in non-TSAN builds.
+ ABSL_TSAN_MUTEX_PRE_DIVERT(mu, 0);
+ // We miss synchronization around passing PerThreadSynch between threads
+ // since it happens inside of the Mutex code, so we need to ignore all
+ // accesses to the object.
+ ABSL_ANNOTATE_IGNORE_READS_AND_WRITES_BEGIN();
PerThreadSem::Post(w->thread_identity());
- if (mu) {
- ABSL_TSAN_MUTEX_POST_DIVERT(mu, 0);
- }
+ ABSL_ANNOTATE_IGNORE_READS_AND_WRITES_END();
+ ABSL_TSAN_MUTEX_POST_DIVERT(mu, 0);
}
// Wait on "w"'s associated PerThreadSem; returns false if timeout expired.
-bool Mutex::DecrementSynchSem(Mutex *mu, PerThreadSynch *w, KernelTimeout t) {
- if (mu) {
- ABSL_TSAN_MUTEX_PRE_DIVERT(mu, 0);
- }
+bool Mutex::DecrementSynchSem(Mutex* mu, PerThreadSynch* w, KernelTimeout t) {
+ static_cast<void>(mu); // Prevent unused param warning in non-TSAN builds.
+ ABSL_TSAN_MUTEX_PRE_DIVERT(mu, 0);
assert(w == Synch_GetPerThread());
static_cast<void>(w);
bool res = PerThreadSem::Wait(t);
- if (mu) {
- ABSL_TSAN_MUTEX_POST_DIVERT(mu, 0);
- }
+ ABSL_TSAN_MUTEX_POST_DIVERT(mu, 0);
return res;
}
@@ -591,7 +608,7 @@
// Mutex code checking that the "waitp" field has not been reused.
void Mutex::InternalAttemptToUseMutexInFatalSignalHandler() {
// Fix the per-thread state only if it exists.
- ThreadIdentity *identity = CurrentThreadIdentityIfPresent();
+ ThreadIdentity* identity = CurrentThreadIdentityIfPresent();
if (identity != nullptr) {
identity->per_thread_synch.suppress_fatal_errors = true;
}
@@ -600,21 +617,6 @@
std::memory_order_release);
}
-// --------------------------time support
-
-// Return the current time plus the timeout. Use the same clock as
-// PerThreadSem::Wait() for consistency. Unfortunately, we don't have
-// such a choice when a deadline is given directly.
-static absl::Time DeadlineFromTimeout(absl::Duration timeout) {
-#ifndef _WIN32
- struct timeval tv;
- gettimeofday(&tv, nullptr);
- return absl::TimeFromTimeval(tv) + timeout;
-#else
- return absl::Now() + timeout;
-#endif
-}
-
// --------------------------Mutexes
// In the layout below, the msb of the bottom byte is currently unused. Also,
@@ -625,24 +627,29 @@
// bit-twiddling trick in Mutex::Unlock().
// o kMuWriter / kMuReader == kMuWrWait / kMuWait,
// to enable the bit-twiddling trick in CheckForMutexCorruption().
-static const intptr_t kMuReader = 0x0001L; // a reader holds the lock
-static const intptr_t kMuDesig = 0x0002L; // there's a designated waker
-static const intptr_t kMuWait = 0x0004L; // threads are waiting
-static const intptr_t kMuWriter = 0x0008L; // a writer holds the lock
-static const intptr_t kMuEvent = 0x0010L; // record this mutex's events
+static const intptr_t kMuReader = 0x0001L; // a reader holds the lock
+// There's a designated waker.
// INVARIANT1: there's a thread that was blocked on the mutex, is
// no longer, yet has not yet acquired the mutex. If there's a
// designated waker, all threads can avoid taking the slow path in
// unlock because the designated waker will subsequently acquire
// the lock and wake someone. To maintain INVARIANT1 the bit is
// set when a thread is unblocked(INV1a), and threads that were
-// unblocked reset the bit when they either acquire or re-block
-// (INV1b).
-static const intptr_t kMuWrWait = 0x0020L; // runnable writer is waiting
- // for a reader
-static const intptr_t kMuSpin = 0x0040L; // spinlock protects wait list
-static const intptr_t kMuLow = 0x00ffL; // mask all mutex bits
-static const intptr_t kMuHigh = ~kMuLow; // mask pointer/reader count
+// unblocked reset the bit when they either acquire or re-block (INV1b).
+static const intptr_t kMuDesig = 0x0002L;
+static const intptr_t kMuWait = 0x0004L; // threads are waiting
+static const intptr_t kMuWriter = 0x0008L; // a writer holds the lock
+static const intptr_t kMuEvent = 0x0010L; // record this mutex's events
+// Runnable writer is waiting for a reader.
+// If set, new readers will not lock the mutex to avoid writer starvation.
+// Note: if a reader has higher priority than the writer, it will still lock
+// the mutex ahead of the waiting writer, but in a very inefficient manner:
+// the reader will first queue itself and block, but then the last unlocking
+// reader will wake it.
+static const intptr_t kMuWrWait = 0x0020L;
+static const intptr_t kMuSpin = 0x0040L; // spinlock protects wait list
+static const intptr_t kMuLow = 0x00ffL; // mask all mutex bits
+static const intptr_t kMuHigh = ~kMuLow; // mask pointer/reader count
// Hack to make constant values available to gdb pretty printer
enum {
@@ -668,6 +675,7 @@
// flags passed to Enqueue and LockSlow{,WithTimeout,Loop}
static const int kMuHasBlocked = 0x01; // already blocked (MUST == 1)
static const int kMuIsCond = 0x02; // conditional waiter (CV or Condition)
+static const int kMuIsFer = 0x04; // wait morphing from a CondVar
static_assert(PerThreadSynch::kAlignment > kMuLow,
"PerThreadSynch::kAlignment must be greater than kMuLow");
@@ -723,40 +731,59 @@
}
#endif
-static bool DebugOnlyIsExiting() {
- return false;
-}
+#if defined(__APPLE__) || defined(ABSL_BUILD_DLL)
+// When building a dll symbol export lists may reference the destructor
+// and want it to be an exported symbol rather than an inline function.
+// Some apple builds also do dynamic library build but don't say it explicitly.
+Mutex::~Mutex() { Dtor(); }
+#endif
-Mutex::~Mutex() {
- intptr_t v = mu_.load(std::memory_order_relaxed);
- if ((v & kMuEvent) != 0 && !DebugOnlyIsExiting()) {
- ForgetSynchEvent(&this->mu_, kMuEvent, kMuSpin);
- }
+#if !defined(NDEBUG) || defined(ABSL_HAVE_THREAD_SANITIZER)
+void Mutex::Dtor() {
if (kDebugMode) {
this->ForgetDeadlockInfo();
}
ABSL_TSAN_MUTEX_DESTROY(this, __tsan_mutex_not_static);
}
+#endif
-void Mutex::EnableDebugLog(const char *name) {
- SynchEvent *e = EnsureSynchEvent(&this->mu_, name, kMuEvent, kMuSpin);
+void Mutex::EnableDebugLog(const char* name) {
+ // Need to disable writes here and in EnableInvariantDebugging to prevent
+ // false race reports on SynchEvent objects. TSan ignores synchronization
+ // on synch_event_mu in Lock/Unlock/etc methods due to mutex annotations,
+ // but it sees few accesses to SynchEvent in EvalConditionAnnotated.
+ // If we don't ignore accesses here, it can result in false races
+ // between EvalConditionAnnotated and SynchEvent reuse in EnsureSynchEvent.
+ ABSL_ANNOTATE_IGNORE_WRITES_BEGIN();
+ SynchEvent* e = EnsureSynchEvent(&this->mu_, name, kMuEvent, kMuSpin);
e->log = true;
UnrefSynchEvent(e);
+ // This prevents "error: undefined symbol: absl::Mutex::~Mutex()"
+ // in a release build (NDEBUG defined) when a test does "#undef NDEBUG"
+ // to use assert macro. In such case, the test does not get the dtor
+ // definition because it's supposed to be outline when NDEBUG is not defined,
+ // and this source file does not define one either because NDEBUG is defined.
+ // Since it's not possible to take address of a destructor, we move the
+ // actual destructor code into the separate Dtor function and force the
+ // compiler to emit this function even if it's inline by taking its address.
+ ABSL_ATTRIBUTE_UNUSED volatile auto dtor = &Mutex::Dtor;
+ ABSL_ANNOTATE_IGNORE_WRITES_END();
}
void EnableMutexInvariantDebugging(bool enabled) {
synch_check_invariants.store(enabled, std::memory_order_release);
}
-void Mutex::EnableInvariantDebugging(void (*invariant)(void *),
- void *arg) {
+void Mutex::EnableInvariantDebugging(void (*invariant)(void*), void* arg) {
+ ABSL_ANNOTATE_IGNORE_WRITES_BEGIN();
if (synch_check_invariants.load(std::memory_order_acquire) &&
invariant != nullptr) {
- SynchEvent *e = EnsureSynchEvent(&this->mu_, nullptr, kMuEvent, kMuSpin);
+ SynchEvent* e = EnsureSynchEvent(&this->mu_, nullptr, kMuEvent, kMuSpin);
e->invariant = invariant;
e->arg = arg;
UnrefSynchEvent(e);
}
+ ABSL_ANNOTATE_IGNORE_WRITES_END();
}
void SetMutexDeadlockDetectionMode(OnDeadlockCycle mode) {
@@ -768,15 +795,15 @@
// waiters with the same condition, type of lock, and thread priority.
//
// Requires that x and y be waiting on the same Mutex queue.
-static bool MuEquivalentWaiter(PerThreadSynch *x, PerThreadSynch *y) {
+static bool MuEquivalentWaiter(PerThreadSynch* x, PerThreadSynch* y) {
return x->waitp->how == y->waitp->how && x->priority == y->priority &&
Condition::GuaranteedEqual(x->waitp->cond, y->waitp->cond);
}
// Given the contents of a mutex word containing a PerThreadSynch pointer,
// return the pointer.
-static inline PerThreadSynch *GetPerThreadSynch(intptr_t v) {
- return reinterpret_cast<PerThreadSynch *>(v & kMuHigh);
+static inline PerThreadSynch* GetPerThreadSynch(intptr_t v) {
+ return reinterpret_cast<PerThreadSynch*>(v & kMuHigh);
}
// The next several routines maintain the per-thread next and skip fields
@@ -834,17 +861,17 @@
// except those in the added node and the former "head" node. This implies
// that the new node is added after head, and so must be the new head or the
// new front of the queue.
-static PerThreadSynch *Skip(PerThreadSynch *x) {
- PerThreadSynch *x0 = nullptr;
- PerThreadSynch *x1 = x;
- PerThreadSynch *x2 = x->skip;
+static PerThreadSynch* Skip(PerThreadSynch* x) {
+ PerThreadSynch* x0 = nullptr;
+ PerThreadSynch* x1 = x;
+ PerThreadSynch* x2 = x->skip;
if (x2 != nullptr) {
// Each iteration attempts to advance sequence (x0,x1,x2) to next sequence
// such that x1 == x0->skip && x2 == x1->skip
while ((x0 = x1, x1 = x2, x2 = x2->skip) != nullptr) {
- x0->skip = x2; // short-circuit skip from x0 to x2
+ x0->skip = x2; // short-circuit skip from x0 to x2
}
- x->skip = x1; // short-circuit skip from x to result
+ x->skip = x1; // short-circuit skip from x to result
}
return x1;
}
@@ -853,7 +880,7 @@
// The latter is going to be removed out of order, because of a timeout.
// Check whether "ancestor" has a skip field pointing to "to_be_removed",
// and fix it if it does.
-static void FixSkip(PerThreadSynch *ancestor, PerThreadSynch *to_be_removed) {
+static void FixSkip(PerThreadSynch* ancestor, PerThreadSynch* to_be_removed) {
if (ancestor->skip == to_be_removed) { // ancestor->skip left dangling
if (to_be_removed->skip != nullptr) {
ancestor->skip = to_be_removed->skip; // can skip past to_be_removed
@@ -865,7 +892,7 @@
}
}
-static void CondVarEnqueue(SynchWaitParams *waitp);
+static void CondVarEnqueue(SynchWaitParams* waitp);
// Enqueue thread "waitp->thread" on a waiter queue.
// Called with mutex spinlock held if head != nullptr
@@ -886,8 +913,8 @@
// returned. This mechanism is used by CondVar to queue a thread on the
// condition variable queue instead of the mutex queue in implementing Wait().
// In this case, Enqueue() can return nullptr (if head==nullptr).
-static PerThreadSynch *Enqueue(PerThreadSynch *head,
- SynchWaitParams *waitp, intptr_t mu, int flags) {
+static PerThreadSynch* Enqueue(PerThreadSynch* head, SynchWaitParams* waitp,
+ intptr_t mu, int flags) {
// If we have been given a cv_word, call CondVarEnqueue() and return
// the previous head of the Mutex waiter queue.
if (waitp->cv_word != nullptr) {
@@ -895,30 +922,25 @@
return head;
}
- PerThreadSynch *s = waitp->thread;
+ PerThreadSynch* s = waitp->thread;
ABSL_RAW_CHECK(
s->waitp == nullptr || // normal case
s->waitp == waitp || // Fer()---transfer from condition variable
s->suppress_fatal_errors,
"detected illegal recursion into Mutex code");
s->waitp = waitp;
- s->skip = nullptr; // maintain skip invariant (see above)
- s->may_skip = true; // always true on entering queue
- s->wake = false; // not being woken
+ s->skip = nullptr; // maintain skip invariant (see above)
+ s->may_skip = true; // always true on entering queue
+ s->wake = false; // not being woken
s->cond_waiter = ((flags & kMuIsCond) != 0);
- if (head == nullptr) { // s is the only waiter
- s->next = s; // it's the only entry in the cycle
- s->readers = mu; // reader count is from mu word
- s->maybe_unlocking = false; // no one is searching an empty list
- head = s; // s is new head
- } else {
- PerThreadSynch *enqueue_after = nullptr; // we'll put s after this element
#ifdef ABSL_HAVE_PTHREAD_GETSCHEDPARAM
- int64_t now_cycles = base_internal::CycleClock::Now();
+ if ((flags & kMuIsFer) == 0) {
+ assert(s == Synch_GetPerThread());
+ int64_t now_cycles = CycleClock::Now();
if (s->next_priority_read_cycles < now_cycles) {
// Every so often, update our idea of the thread's priority.
// pthread_getschedparam() is 5% of the block/wakeup time;
- // base_internal::CycleClock::Now() is 0.5%.
+ // CycleClock::Now() is 0.5%.
int policy;
struct sched_param param;
const int err = pthread_getschedparam(pthread_self(), &policy, ¶m);
@@ -927,10 +949,19 @@
} else {
s->priority = param.sched_priority;
s->next_priority_read_cycles =
- now_cycles +
- static_cast<int64_t>(base_internal::CycleClock::Frequency());
+ now_cycles + static_cast<int64_t>(CycleClock::Frequency());
}
}
+ }
+#endif
+ if (head == nullptr) { // s is the only waiter
+ s->next = s; // it's the only entry in the cycle
+ s->readers = mu; // reader count is from mu word
+ s->maybe_unlocking = false; // no one is searching an empty list
+ head = s; // s is new head
+ } else {
+ PerThreadSynch* enqueue_after = nullptr; // we'll put s after this element
+#ifdef ABSL_HAVE_PTHREAD_GETSCHEDPARAM
if (s->priority > head->priority) { // s's priority is above head's
// try to put s in priority-fifo order, or failing that at the front.
if (!head->maybe_unlocking) {
@@ -940,20 +971,19 @@
// Within a skip chain, all waiters have the same priority, so we can
// skip forward through the chains until we find one with a lower
// priority than the waiter to be enqueued.
- PerThreadSynch *advance_to = head; // next value of enqueue_after
+ PerThreadSynch* advance_to = head; // next value of enqueue_after
do {
enqueue_after = advance_to;
// (side-effect: optimizes skip chain)
advance_to = Skip(enqueue_after->next);
} while (s->priority <= advance_to->priority);
- // termination guaranteed because s->priority > head->priority
- // and head is the end of a skip chain
- } else if (waitp->how == kExclusive &&
- Condition::GuaranteedEqual(waitp->cond, nullptr)) {
+ // termination guaranteed because s->priority > head->priority
+ // and head is the end of a skip chain
+ } else if (waitp->how == kExclusive && waitp->cond == nullptr) {
// An unlocker could be scanning the queue, but we know it will recheck
// the queue front for writers that have no condition, which is what s
// is, so an insert at front is safe.
- enqueue_after = head; // add after head, at front
+ enqueue_after = head; // add after head, at front
}
}
#endif
@@ -978,12 +1008,30 @@
enqueue_after->skip = enqueue_after->next;
}
if (MuEquivalentWaiter(s, s->next)) { // s->may_skip is known to be true
- s->skip = s->next; // s may skip to its successor
+ s->skip = s->next; // s may skip to its successor
}
- } else { // enqueue not done any other way, so
- // we're inserting s at the back
+ } else if ((flags & kMuHasBlocked) &&
+ (s->priority >= head->next->priority) &&
+ (!head->maybe_unlocking ||
+ (waitp->how == kExclusive &&
+ Condition::GuaranteedEqual(waitp->cond, nullptr)))) {
+ // This thread has already waited, then was woken, then failed to acquire
+ // the mutex and now tries to requeue. Try to requeue it at head,
+ // otherwise it can suffer bad latency (wait whole queue several times).
+ // However, we need to be conservative. First, we need to ensure that we
+ // respect priorities. Then, we need to be careful to not break wait
+ // queue invariants: we require either that unlocker is not scanning
+ // the queue or that the current thread is a writer with no condition
+ // (unlocker will recheck the queue for such waiters).
+ s->next = head->next;
+ head->next = s;
+ if (MuEquivalentWaiter(s, s->next)) { // s->may_skip is known to be true
+ s->skip = s->next; // s may skip to its successor
+ }
+ } else { // enqueue not done any other way, so
+ // we're inserting s at the back
// s will become new head; copy data from head into it
- s->next = head->next; // add s after head
+ s->next = head->next; // add s after head
head->next = s;
s->readers = head->readers; // reader count is from previous head
s->maybe_unlocking = head->maybe_unlocking; // same for unlock hint
@@ -1002,17 +1050,17 @@
// whose last element is head. The new head element is returned, or null
// if the list is made empty.
// Dequeue is called with both spinlock and Mutex held.
-static PerThreadSynch *Dequeue(PerThreadSynch *head, PerThreadSynch *pw) {
- PerThreadSynch *w = pw->next;
- pw->next = w->next; // snip w out of list
- if (head == w) { // we removed the head
+static PerThreadSynch* Dequeue(PerThreadSynch* head, PerThreadSynch* pw) {
+ PerThreadSynch* w = pw->next;
+ pw->next = w->next; // snip w out of list
+ if (head == w) { // we removed the head
head = (pw == w) ? nullptr : pw; // either emptied list, or pw is new head
} else if (pw != head && MuEquivalentWaiter(pw, pw->next)) {
// pw can skip to its new successor
if (pw->next->skip !=
nullptr) { // either skip to its successors skip target
pw->skip = pw->next->skip;
- } else { // or to pw's successor
+ } else { // or to pw's successor
pw->skip = pw->next;
}
}
@@ -1025,27 +1073,27 @@
// singly-linked list wake_list in the order found. Assumes that
// there is only one such element if the element has how == kExclusive.
// Return the new head.
-static PerThreadSynch *DequeueAllWakeable(PerThreadSynch *head,
- PerThreadSynch *pw,
- PerThreadSynch **wake_tail) {
- PerThreadSynch *orig_h = head;
- PerThreadSynch *w = pw->next;
+static PerThreadSynch* DequeueAllWakeable(PerThreadSynch* head,
+ PerThreadSynch* pw,
+ PerThreadSynch** wake_tail) {
+ PerThreadSynch* orig_h = head;
+ PerThreadSynch* w = pw->next;
bool skipped = false;
do {
- if (w->wake) { // remove this element
+ if (w->wake) { // remove this element
ABSL_RAW_CHECK(pw->skip == nullptr, "bad skip in DequeueAllWakeable");
// we're removing pw's successor so either pw->skip is zero or we should
// already have removed pw since if pw->skip!=null, pw has the same
// condition as w.
head = Dequeue(head, pw);
- w->next = *wake_tail; // keep list terminated
- *wake_tail = w; // add w to wake_list;
- wake_tail = &w->next; // next addition to end
+ w->next = *wake_tail; // keep list terminated
+ *wake_tail = w; // add w to wake_list;
+ wake_tail = &w->next; // next addition to end
if (w->waitp->how == kExclusive) { // wake at most 1 writer
break;
}
- } else { // not waking this one; skip
- pw = Skip(w); // skip as much as possible
+ } else { // not waking this one; skip
+ pw = Skip(w); // skip as much as possible
skipped = true;
}
w = pw->next;
@@ -1063,7 +1111,7 @@
// Try to remove thread s from the list of waiters on this mutex.
// Does nothing if s is not on the waiter list.
-void Mutex::TryRemove(PerThreadSynch *s) {
+void Mutex::TryRemove(PerThreadSynch* s) {
SchedulingGuard::ScopedDisable disable_rescheduling;
intptr_t v = mu_.load(std::memory_order_relaxed);
// acquire spinlock & lock
@@ -1071,16 +1119,16 @@
mu_.compare_exchange_strong(v, v | kMuSpin | kMuWriter,
std::memory_order_acquire,
std::memory_order_relaxed)) {
- PerThreadSynch *h = GetPerThreadSynch(v);
+ PerThreadSynch* h = GetPerThreadSynch(v);
if (h != nullptr) {
- PerThreadSynch *pw = h; // pw is w's predecessor
- PerThreadSynch *w;
+ PerThreadSynch* pw = h; // pw is w's predecessor
+ PerThreadSynch* w;
if ((w = pw->next) != s) { // search for thread,
do { // processing at least one element
// If the current element isn't equivalent to the waiter to be
// removed, we can skip the entire chain.
if (!MuEquivalentWaiter(s, w)) {
- pw = Skip(w); // so skip all that won't match
+ pw = Skip(w); // so skip all that won't match
// we don't have to worry about dangling skip fields
// in the threads we skipped; none can point to s
// because they are in a different equivalence class.
@@ -1092,7 +1140,7 @@
// process the first thread again.
} while ((w = pw->next) != s && pw != h);
}
- if (w == s) { // found thread; remove it
+ if (w == s) { // found thread; remove it
// pw->skip may be non-zero here; the loop above ensured that
// no ancestor of s can skip to s, so removal is safe anyway.
h = Dequeue(h, pw);
@@ -1101,16 +1149,15 @@
}
}
intptr_t nv;
- do { // release spinlock and lock
+ do { // release spinlock and lock
v = mu_.load(std::memory_order_relaxed);
nv = v & (kMuDesig | kMuEvent);
if (h != nullptr) {
nv |= kMuWait | reinterpret_cast<intptr_t>(h);
- h->readers = 0; // we hold writer lock
+ h->readers = 0; // we hold writer lock
h->maybe_unlocking = false; // finished unlocking
}
- } while (!mu_.compare_exchange_weak(v, nv,
- std::memory_order_release,
+ } while (!mu_.compare_exchange_weak(v, nv, std::memory_order_release,
std::memory_order_relaxed));
}
}
@@ -1120,7 +1167,7 @@
// if the wait extends past the absolute time specified, even if "s" is still
// on the mutex queue. In this case, remove "s" from the queue and return
// true, otherwise return false.
-ABSL_XRAY_LOG_ARGS(1) void Mutex::Block(PerThreadSynch *s) {
+void Mutex::Block(PerThreadSynch* s) {
while (s->state.load(std::memory_order_acquire) == PerThreadSynch::kQueued) {
if (!DecrementSynchSem(this, s, s->waitp->timeout)) {
// After a timeout, we go into a spin loop until we remove ourselves
@@ -1139,7 +1186,7 @@
// is not on the queue.
this->TryRemove(s);
}
- s->waitp->timeout = KernelTimeout::Never(); // timeout is satisfied
+ s->waitp->timeout = KernelTimeout::Never(); // timeout is satisfied
s->waitp->cond = nullptr; // condition no longer relevant for wakeups
}
}
@@ -1149,8 +1196,8 @@
}
// Wake thread w, and return the next thread in the list.
-PerThreadSynch *Mutex::Wakeup(PerThreadSynch *w) {
- PerThreadSynch *next = w->next;
+PerThreadSynch* Mutex::Wakeup(PerThreadSynch* w) {
+ PerThreadSynch* next = w->next;
w->next = nullptr;
w->state.store(PerThreadSynch::kAvailable, std::memory_order_release);
IncrementSynchSem(this, w);
@@ -1158,7 +1205,7 @@
return next;
}
-static GraphId GetGraphIdLocked(Mutex *mu)
+static GraphId GetGraphIdLocked(Mutex* mu)
ABSL_EXCLUSIVE_LOCKS_REQUIRED(deadlock_graph_mu) {
if (!deadlock_graph) { // (re)create the deadlock graph.
deadlock_graph =
@@ -1168,7 +1215,7 @@
return deadlock_graph->GetId(mu);
}
-static GraphId GetGraphId(Mutex *mu) ABSL_LOCKS_EXCLUDED(deadlock_graph_mu) {
+static GraphId GetGraphId(Mutex* mu) ABSL_LOCKS_EXCLUDED(deadlock_graph_mu) {
deadlock_graph_mu.Lock();
GraphId id = GetGraphIdLocked(mu);
deadlock_graph_mu.Unlock();
@@ -1178,7 +1225,7 @@
// Record a lock acquisition. This is used in debug mode for deadlock
// detection. The held_locks pointer points to the relevant data
// structure for each case.
-static void LockEnter(Mutex* mu, GraphId id, SynchLocksHeld *held_locks) {
+static void LockEnter(Mutex* mu, GraphId id, SynchLocksHeld* held_locks) {
int n = held_locks->n;
int i = 0;
while (i != n && held_locks->locks[i].id != id) {
@@ -1202,7 +1249,7 @@
// eventually followed by a call to LockLeave(mu, id, x) by the same thread.
// It does not process the event if is not needed when deadlock detection is
// disabled.
-static void LockLeave(Mutex* mu, GraphId id, SynchLocksHeld *held_locks) {
+static void LockLeave(Mutex* mu, GraphId id, SynchLocksHeld* held_locks) {
int n = held_locks->n;
int i = 0;
while (i != n && held_locks->locks[i].id != id) {
@@ -1217,11 +1264,11 @@
i++;
}
if (i == n) { // mu missing means releasing unheld lock
- SynchEvent *mu_events = GetSynchEvent(mu);
+ SynchEvent* mu_events = GetSynchEvent(mu);
ABSL_RAW_LOG(FATAL,
"thread releasing lock it does not hold: %p %s; "
,
- static_cast<void *>(mu),
+ static_cast<void*>(mu),
mu_events == nullptr ? "" : mu_events->name);
}
}
@@ -1238,7 +1285,7 @@
}
// Call LockEnter() if in debug mode and deadlock detection is enabled.
-static inline void DebugOnlyLockEnter(Mutex *mu) {
+static inline void DebugOnlyLockEnter(Mutex* mu) {
if (kDebugMode) {
if (synch_deadlock_detection.load(std::memory_order_acquire) !=
OnDeadlockCycle::kIgnore) {
@@ -1248,7 +1295,7 @@
}
// Call LockEnter() if in debug mode and deadlock detection is enabled.
-static inline void DebugOnlyLockEnter(Mutex *mu, GraphId id) {
+static inline void DebugOnlyLockEnter(Mutex* mu, GraphId id) {
if (kDebugMode) {
if (synch_deadlock_detection.load(std::memory_order_acquire) !=
OnDeadlockCycle::kIgnore) {
@@ -1258,7 +1305,7 @@
}
// Call LockLeave() if in debug mode and deadlock detection is enabled.
-static inline void DebugOnlyLockLeave(Mutex *mu) {
+static inline void DebugOnlyLockLeave(Mutex* mu) {
if (kDebugMode) {
if (synch_deadlock_detection.load(std::memory_order_acquire) !=
OnDeadlockCycle::kIgnore) {
@@ -1267,36 +1314,40 @@
}
}
-static char *StackString(void **pcs, int n, char *buf, int maxlen,
+static char* StackString(void** pcs, int n, char* buf, int maxlen,
bool symbolize) {
- static const int kSymLen = 200;
+ static constexpr int kSymLen = 200;
char sym[kSymLen];
int len = 0;
for (int i = 0; i != n; i++) {
+ if (len >= maxlen)
+ return buf;
+ size_t count = static_cast<size_t>(maxlen - len);
if (symbolize) {
- if (!symbolizer(pcs[i], sym, kSymLen)) {
+ if (!absl::Symbolize(pcs[i], sym, kSymLen)) {
sym[0] = '\0';
}
- snprintf(buf + len, maxlen - len, "%s\t@ %p %s\n",
- (i == 0 ? "\n" : ""),
- pcs[i], sym);
+ snprintf(buf + len, count, "%s\t@ %p %s\n", (i == 0 ? "\n" : ""), pcs[i],
+ sym);
} else {
- snprintf(buf + len, maxlen - len, " %p", pcs[i]);
+ snprintf(buf + len, count, " %p", pcs[i]);
}
len += strlen(&buf[len]);
}
return buf;
}
-static char *CurrentStackString(char *buf, int maxlen, bool symbolize) {
- void *pcs[40];
+static char* CurrentStackString(char* buf, int maxlen, bool symbolize) {
+ void* pcs[40];
return StackString(pcs, absl::GetStackTrace(pcs, ABSL_ARRAYSIZE(pcs), 2), buf,
maxlen, symbolize);
}
namespace {
-enum { kMaxDeadlockPathLen = 10 }; // maximum length of a deadlock cycle;
- // a path this long would be remarkable
+enum {
+ kMaxDeadlockPathLen = 10
+}; // maximum length of a deadlock cycle;
+ // a path this long would be remarkable
// Buffers required to report a deadlock.
// We do not allocate them on stack to avoid large stack frame.
struct DeadlockReportBuffers {
@@ -1306,11 +1357,11 @@
struct ScopedDeadlockReportBuffers {
ScopedDeadlockReportBuffers() {
- b = reinterpret_cast<DeadlockReportBuffers *>(
+ b = reinterpret_cast<DeadlockReportBuffers*>(
base_internal::LowLevelAlloc::Alloc(sizeof(*b)));
}
~ScopedDeadlockReportBuffers() { base_internal::LowLevelAlloc::Free(b); }
- DeadlockReportBuffers *b;
+ DeadlockReportBuffers* b;
};
// Helper to pass to GraphCycles::UpdateStackTrace.
@@ -1321,13 +1372,13 @@
// Called in debug mode when a thread is about to acquire a lock in a way that
// may block.
-static GraphId DeadlockCheck(Mutex *mu) {
+static GraphId DeadlockCheck(Mutex* mu) {
if (synch_deadlock_detection.load(std::memory_order_acquire) ==
OnDeadlockCycle::kIgnore) {
return InvalidGraphId();
}
- SynchLocksHeld *all_locks = Synch_GetAllLocks();
+ SynchLocksHeld* all_locks = Synch_GetAllLocks();
absl::base_internal::SpinLockHolder lock(&deadlock_graph_mu);
const GraphId mu_id = GetGraphIdLocked(mu);
@@ -1349,8 +1400,8 @@
// For each other mutex already held by this thread:
for (int i = 0; i != all_locks->n; i++) {
const GraphId other_node_id = all_locks->locks[i].id;
- const Mutex *other =
- static_cast<const Mutex *>(deadlock_graph->Ptr(other_node_id));
+ const Mutex* other =
+ static_cast<const Mutex*>(deadlock_graph->Ptr(other_node_id));
if (other == nullptr) {
// Ignore stale lock
continue;
@@ -1359,48 +1410,51 @@
// Add the acquired-before edge to the graph.
if (!deadlock_graph->InsertEdge(other_node_id, mu_id)) {
ScopedDeadlockReportBuffers scoped_buffers;
- DeadlockReportBuffers *b = scoped_buffers.b;
+ DeadlockReportBuffers* b = scoped_buffers.b;
static int number_of_reported_deadlocks = 0;
number_of_reported_deadlocks++;
// Symbolize only 2 first deadlock report to avoid huge slowdowns.
bool symbolize = number_of_reported_deadlocks <= 2;
ABSL_RAW_LOG(ERROR, "Potential Mutex deadlock: %s",
CurrentStackString(b->buf, sizeof (b->buf), symbolize));
- int len = 0;
+ size_t len = 0;
for (int j = 0; j != all_locks->n; j++) {
void* pr = deadlock_graph->Ptr(all_locks->locks[j].id);
if (pr != nullptr) {
- snprintf(b->buf + len, sizeof (b->buf) - len, " %p", pr);
- len += static_cast<int>(strlen(&b->buf[len]));
+ snprintf(b->buf + len, sizeof(b->buf) - len, " %p", pr);
+ len += strlen(&b->buf[len]);
}
}
ABSL_RAW_LOG(ERROR,
"Acquiring absl::Mutex %p while holding %s; a cycle in the "
"historical lock ordering graph has been observed",
- static_cast<void *>(mu), b->buf);
+ static_cast<void*>(mu), b->buf);
ABSL_RAW_LOG(ERROR, "Cycle: ");
- int path_len = deadlock_graph->FindPath(
- mu_id, other_node_id, ABSL_ARRAYSIZE(b->path), b->path);
- for (int j = 0; j != path_len; j++) {
+ int path_len = deadlock_graph->FindPath(mu_id, other_node_id,
+ ABSL_ARRAYSIZE(b->path), b->path);
+ for (int j = 0; j != path_len && j != ABSL_ARRAYSIZE(b->path); j++) {
GraphId id = b->path[j];
- Mutex *path_mu = static_cast<Mutex *>(deadlock_graph->Ptr(id));
+ Mutex* path_mu = static_cast<Mutex*>(deadlock_graph->Ptr(id));
if (path_mu == nullptr) continue;
void** stack;
int depth = deadlock_graph->GetStackTrace(id, &stack);
snprintf(b->buf, sizeof(b->buf),
- "mutex@%p stack: ", static_cast<void *>(path_mu));
+ "mutex@%p stack: ", static_cast<void*>(path_mu));
StackString(stack, depth, b->buf + strlen(b->buf),
static_cast<int>(sizeof(b->buf) - strlen(b->buf)),
symbolize);
ABSL_RAW_LOG(ERROR, "%s", b->buf);
}
+ if (path_len > static_cast<int>(ABSL_ARRAYSIZE(b->path))) {
+ ABSL_RAW_LOG(ERROR, "(long cycle; list truncated)");
+ }
if (synch_deadlock_detection.load(std::memory_order_acquire) ==
OnDeadlockCycle::kAbort) {
deadlock_graph_mu.Unlock(); // avoid deadlock in fatal sighandler
ABSL_RAW_LOG(FATAL, "dying due to potential deadlock");
return mu_id;
}
- break; // report at most one potential deadlock per acquisition
+ break; // report at most one potential deadlock per acquisition
}
}
@@ -1409,7 +1463,7 @@
// Invoke DeadlockCheck() iff we're in debug mode and
// deadlock checking has been enabled.
-static inline GraphId DebugOnlyDeadlockCheck(Mutex *mu) {
+static inline GraphId DebugOnlyDeadlockCheck(Mutex* mu) {
if (kDebugMode && synch_deadlock_detection.load(std::memory_order_acquire) !=
OnDeadlockCycle::kIgnore) {
return DeadlockCheck(mu);
@@ -1436,13 +1490,13 @@
(mu_.load(std::memory_order_relaxed) & (kMuWriter | kMuReader)) != 0 &&
synch_deadlock_detection.load(std::memory_order_acquire) !=
OnDeadlockCycle::kIgnore) {
- GraphId id = GetGraphId(const_cast<Mutex *>(this));
- SynchLocksHeld *locks = Synch_GetAllLocks();
+ GraphId id = GetGraphId(const_cast<Mutex*>(this));
+ SynchLocksHeld* locks = Synch_GetAllLocks();
for (int i = 0; i != locks->n; i++) {
if (locks->locks[i].id == id) {
- SynchEvent *mu_events = GetSynchEvent(this);
+ SynchEvent* mu_events = GetSynchEvent(this);
ABSL_RAW_LOG(FATAL, "thread should not hold mutex %p %s",
- static_cast<const void *>(this),
+ static_cast<const void*>(this),
(mu_events == nullptr ? "" : mu_events->name));
}
}
@@ -1452,11 +1506,11 @@
// Attempt to acquire *mu, and return whether successful. The implementation
// may spin for a short while if the lock cannot be acquired immediately.
static bool TryAcquireWithSpinning(std::atomic<intptr_t>* mu) {
- int c = GetMutexGlobals().spinloop_iterations;
+ int c = globals.spinloop_iterations.load(std::memory_order_relaxed);
do { // do/while somewhat faster on AMD
intptr_t v = mu->load(std::memory_order_relaxed);
- if ((v & (kMuReader|kMuEvent)) != 0) {
- return false; // a reader or tracing -> give up
+ if ((v & (kMuReader | kMuEvent)) != 0) {
+ return false; // a reader or tracing -> give up
} else if (((v & kMuWriter) == 0) && // no holder -> try to acquire
mu->compare_exchange_strong(v, kMuWriter | v,
std::memory_order_acquire,
@@ -1467,17 +1521,17 @@
return false;
}
-ABSL_XRAY_LOG_ARGS(1) void Mutex::Lock() {
+void Mutex::Lock() {
ABSL_TSAN_MUTEX_PRE_LOCK(this, 0);
GraphId id = DebugOnlyDeadlockCheck(this);
intptr_t v = mu_.load(std::memory_order_relaxed);
// try fast acquire, then spin loop
- if ((v & (kMuWriter | kMuReader | kMuEvent)) != 0 ||
- !mu_.compare_exchange_strong(v, kMuWriter | v,
- std::memory_order_acquire,
- std::memory_order_relaxed)) {
+ if (ABSL_PREDICT_FALSE((v & (kMuWriter | kMuReader | kMuEvent)) != 0) ||
+ ABSL_PREDICT_FALSE(!mu_.compare_exchange_strong(
+ v, kMuWriter | v, std::memory_order_acquire,
+ std::memory_order_relaxed))) {
// try spin acquire, then slow loop
- if (!TryAcquireWithSpinning(&this->mu_)) {
+ if (ABSL_PREDICT_FALSE(!TryAcquireWithSpinning(&this->mu_))) {
this->LockSlow(kExclusive, nullptr, 0);
}
}
@@ -1485,187 +1539,159 @@
ABSL_TSAN_MUTEX_POST_LOCK(this, 0, 0);
}
-ABSL_XRAY_LOG_ARGS(1) void Mutex::ReaderLock() {
+void Mutex::ReaderLock() {
ABSL_TSAN_MUTEX_PRE_LOCK(this, __tsan_mutex_read_lock);
GraphId id = DebugOnlyDeadlockCheck(this);
intptr_t v = mu_.load(std::memory_order_relaxed);
- // try fast acquire, then slow loop
- if ((v & (kMuWriter | kMuWait | kMuEvent)) != 0 ||
- !mu_.compare_exchange_strong(v, (kMuReader | v) + kMuOne,
- std::memory_order_acquire,
- std::memory_order_relaxed)) {
- this->LockSlow(kShared, nullptr, 0);
+ for (;;) {
+ // If there are non-readers holding the lock, use the slow loop.
+ if (ABSL_PREDICT_FALSE(v & (kMuWriter | kMuWait | kMuEvent)) != 0) {
+ this->LockSlow(kShared, nullptr, 0);
+ break;
+ }
+ // We can avoid the loop and only use the CAS when the lock is free or
+ // only held by readers.
+ if (ABSL_PREDICT_TRUE(mu_.compare_exchange_weak(
+ v, (kMuReader | v) + kMuOne, std::memory_order_acquire,
+ std::memory_order_relaxed))) {
+ break;
+ }
}
DebugOnlyLockEnter(this, id);
ABSL_TSAN_MUTEX_POST_LOCK(this, __tsan_mutex_read_lock, 0);
}
-void Mutex::LockWhen(const Condition &cond) {
- ABSL_TSAN_MUTEX_PRE_LOCK(this, 0);
+bool Mutex::LockWhenCommon(const Condition& cond,
+ synchronization_internal::KernelTimeout t,
+ bool write) {
+ MuHow how = write ? kExclusive : kShared;
+ ABSL_TSAN_MUTEX_PRE_LOCK(this, TsanFlags(how));
GraphId id = DebugOnlyDeadlockCheck(this);
- this->LockSlow(kExclusive, &cond, 0);
+ bool res = LockSlowWithDeadline(how, &cond, t, 0);
DebugOnlyLockEnter(this, id);
- ABSL_TSAN_MUTEX_POST_LOCK(this, 0, 0);
-}
-
-bool Mutex::LockWhenWithTimeout(const Condition &cond, absl::Duration timeout) {
- return LockWhenWithDeadline(cond, DeadlineFromTimeout(timeout));
-}
-
-bool Mutex::LockWhenWithDeadline(const Condition &cond, absl::Time deadline) {
- ABSL_TSAN_MUTEX_PRE_LOCK(this, 0);
- GraphId id = DebugOnlyDeadlockCheck(this);
- bool res = LockSlowWithDeadline(kExclusive, &cond,
- KernelTimeout(deadline), 0);
- DebugOnlyLockEnter(this, id);
- ABSL_TSAN_MUTEX_POST_LOCK(this, 0, 0);
+ ABSL_TSAN_MUTEX_POST_LOCK(this, TsanFlags(how), 0);
return res;
}
-void Mutex::ReaderLockWhen(const Condition &cond) {
- ABSL_TSAN_MUTEX_PRE_LOCK(this, __tsan_mutex_read_lock);
- GraphId id = DebugOnlyDeadlockCheck(this);
- this->LockSlow(kShared, &cond, 0);
- DebugOnlyLockEnter(this, id);
- ABSL_TSAN_MUTEX_POST_LOCK(this, __tsan_mutex_read_lock, 0);
-}
-
-bool Mutex::ReaderLockWhenWithTimeout(const Condition &cond,
- absl::Duration timeout) {
- return ReaderLockWhenWithDeadline(cond, DeadlineFromTimeout(timeout));
-}
-
-bool Mutex::ReaderLockWhenWithDeadline(const Condition &cond,
- absl::Time deadline) {
- ABSL_TSAN_MUTEX_PRE_LOCK(this, __tsan_mutex_read_lock);
- GraphId id = DebugOnlyDeadlockCheck(this);
- bool res = LockSlowWithDeadline(kShared, &cond, KernelTimeout(deadline), 0);
- DebugOnlyLockEnter(this, id);
- ABSL_TSAN_MUTEX_POST_LOCK(this, __tsan_mutex_read_lock, 0);
- return res;
-}
-
-void Mutex::Await(const Condition &cond) {
- if (cond.Eval()) { // condition already true; nothing to do
- if (kDebugMode) {
- this->AssertReaderHeld();
- }
- } else { // normal case
- ABSL_RAW_CHECK(this->AwaitCommon(cond, KernelTimeout::Never()),
- "condition untrue on return from Await");
+bool Mutex::AwaitCommon(const Condition& cond, KernelTimeout t) {
+ if (kDebugMode) {
+ this->AssertReaderHeld();
}
-}
-
-bool Mutex::AwaitWithTimeout(const Condition &cond, absl::Duration timeout) {
- return AwaitWithDeadline(cond, DeadlineFromTimeout(timeout));
-}
-
-bool Mutex::AwaitWithDeadline(const Condition &cond, absl::Time deadline) {
- if (cond.Eval()) { // condition already true; nothing to do
- if (kDebugMode) {
- this->AssertReaderHeld();
- }
+ if (cond.Eval()) { // condition already true; nothing to do
return true;
}
-
- KernelTimeout t{deadline};
- bool res = this->AwaitCommon(cond, t);
+ MuHow how =
+ (mu_.load(std::memory_order_relaxed) & kMuWriter) ? kExclusive : kShared;
+ ABSL_TSAN_MUTEX_PRE_UNLOCK(this, TsanFlags(how));
+ SynchWaitParams waitp(how, &cond, t, nullptr /*no cvmu*/,
+ Synch_GetPerThreadAnnotated(this),
+ nullptr /*no cv_word*/);
+ this->UnlockSlow(&waitp);
+ this->Block(waitp.thread);
+ ABSL_TSAN_MUTEX_POST_UNLOCK(this, TsanFlags(how));
+ ABSL_TSAN_MUTEX_PRE_LOCK(this, TsanFlags(how));
+ this->LockSlowLoop(&waitp, kMuHasBlocked | kMuIsCond);
+ bool res = waitp.cond != nullptr || // => cond known true from LockSlowLoop
+ EvalConditionAnnotated(&cond, this, true, false, how == kShared);
+ ABSL_TSAN_MUTEX_POST_LOCK(this, TsanFlags(how), 0);
ABSL_RAW_CHECK(res || t.has_timeout(),
"condition untrue on return from Await");
return res;
}
-bool Mutex::AwaitCommon(const Condition &cond, KernelTimeout t) {
- this->AssertReaderHeld();
- MuHow how =
- (mu_.load(std::memory_order_relaxed) & kMuWriter) ? kExclusive : kShared;
- ABSL_TSAN_MUTEX_PRE_UNLOCK(this, TsanFlags(how));
- SynchWaitParams waitp(
- how, &cond, t, nullptr /*no cvmu*/, Synch_GetPerThreadAnnotated(this),
- nullptr /*no cv_word*/);
- int flags = kMuHasBlocked;
- if (!Condition::GuaranteedEqual(&cond, nullptr)) {
- flags |= kMuIsCond;
- }
- this->UnlockSlow(&waitp);
- this->Block(waitp.thread);
- ABSL_TSAN_MUTEX_POST_UNLOCK(this, TsanFlags(how));
- ABSL_TSAN_MUTEX_PRE_LOCK(this, TsanFlags(how));
- this->LockSlowLoop(&waitp, flags);
- bool res = waitp.cond != nullptr || // => cond known true from LockSlowLoop
- EvalConditionAnnotated(&cond, this, true, false, how == kShared);
- ABSL_TSAN_MUTEX_POST_LOCK(this, TsanFlags(how), 0);
- return res;
-}
-
-ABSL_XRAY_LOG_ARGS(1) bool Mutex::TryLock() {
+bool Mutex::TryLock() {
ABSL_TSAN_MUTEX_PRE_LOCK(this, __tsan_mutex_try_lock);
intptr_t v = mu_.load(std::memory_order_relaxed);
- if ((v & (kMuWriter | kMuReader | kMuEvent)) == 0 && // try fast acquire
- mu_.compare_exchange_strong(v, kMuWriter | v,
- std::memory_order_acquire,
- std::memory_order_relaxed)) {
- DebugOnlyLockEnter(this);
- ABSL_TSAN_MUTEX_POST_LOCK(this, __tsan_mutex_try_lock, 0);
- return true;
- }
- if ((v & kMuEvent) != 0) { // we're recording events
- if ((v & kExclusive->slow_need_zero) == 0 && // try fast acquire
- mu_.compare_exchange_strong(
- v, (kExclusive->fast_or | v) + kExclusive->fast_add,
- std::memory_order_acquire, std::memory_order_relaxed)) {
+ // Try fast acquire.
+ if (ABSL_PREDICT_TRUE((v & (kMuWriter | kMuReader | kMuEvent)) == 0)) {
+ if (ABSL_PREDICT_TRUE(mu_.compare_exchange_strong(
+ v, kMuWriter | v, std::memory_order_acquire,
+ std::memory_order_relaxed))) {
DebugOnlyLockEnter(this);
- PostSynchEvent(this, SYNCH_EV_TRYLOCK_SUCCESS);
ABSL_TSAN_MUTEX_POST_LOCK(this, __tsan_mutex_try_lock, 0);
return true;
- } else {
- PostSynchEvent(this, SYNCH_EV_TRYLOCK_FAILED);
}
+ } else if (ABSL_PREDICT_FALSE((v & kMuEvent) != 0)) {
+ // We're recording events.
+ return TryLockSlow();
}
ABSL_TSAN_MUTEX_POST_LOCK(
this, __tsan_mutex_try_lock | __tsan_mutex_try_lock_failed, 0);
return false;
}
-ABSL_XRAY_LOG_ARGS(1) bool Mutex::ReaderTryLock() {
+ABSL_ATTRIBUTE_NOINLINE bool Mutex::TryLockSlow() {
+ intptr_t v = mu_.load(std::memory_order_relaxed);
+ if ((v & kExclusive->slow_need_zero) == 0 && // try fast acquire
+ mu_.compare_exchange_strong(
+ v, (kExclusive->fast_or | v) + kExclusive->fast_add,
+ std::memory_order_acquire, std::memory_order_relaxed)) {
+ DebugOnlyLockEnter(this);
+ PostSynchEvent(this, SYNCH_EV_TRYLOCK_SUCCESS);
+ ABSL_TSAN_MUTEX_POST_LOCK(this, __tsan_mutex_try_lock, 0);
+ return true;
+ }
+ PostSynchEvent(this, SYNCH_EV_TRYLOCK_FAILED);
+ ABSL_TSAN_MUTEX_POST_LOCK(
+ this, __tsan_mutex_try_lock | __tsan_mutex_try_lock_failed, 0);
+ return false;
+}
+
+bool Mutex::ReaderTryLock() {
ABSL_TSAN_MUTEX_PRE_LOCK(this,
__tsan_mutex_read_lock | __tsan_mutex_try_lock);
intptr_t v = mu_.load(std::memory_order_relaxed);
+ // Clang tends to unroll the loop when compiling with optimization.
+ // But in this case it just unnecessary increases code size.
+ // If CAS is failing due to contention, the jump cost is negligible.
+#if defined(__clang__)
+#pragma nounroll
+#endif
// The while-loops (here and below) iterate only if the mutex word keeps
- // changing (typically because the reader count changes) under the CAS. We
- // limit the number of attempts to avoid having to think about livelock.
- int loop_limit = 5;
- while ((v & (kMuWriter|kMuWait|kMuEvent)) == 0 && loop_limit != 0) {
- if (mu_.compare_exchange_strong(v, (kMuReader | v) + kMuOne,
- std::memory_order_acquire,
- std::memory_order_relaxed)) {
+ // changing (typically because the reader count changes) under the CAS.
+ // We limit the number of attempts to avoid having to think about livelock.
+ for (int loop_limit = 5; loop_limit != 0; loop_limit--) {
+ if (ABSL_PREDICT_FALSE((v & (kMuWriter | kMuWait | kMuEvent)) != 0)) {
+ break;
+ }
+ if (ABSL_PREDICT_TRUE(mu_.compare_exchange_strong(
+ v, (kMuReader | v) + kMuOne, std::memory_order_acquire,
+ std::memory_order_relaxed))) {
DebugOnlyLockEnter(this);
ABSL_TSAN_MUTEX_POST_LOCK(
this, __tsan_mutex_read_lock | __tsan_mutex_try_lock, 0);
return true;
}
- loop_limit--;
- v = mu_.load(std::memory_order_relaxed);
}
- if ((v & kMuEvent) != 0) { // we're recording events
- loop_limit = 5;
- while ((v & kShared->slow_need_zero) == 0 && loop_limit != 0) {
- if (mu_.compare_exchange_strong(v, (kMuReader | v) + kMuOne,
- std::memory_order_acquire,
- std::memory_order_relaxed)) {
- DebugOnlyLockEnter(this);
- PostSynchEvent(this, SYNCH_EV_READERTRYLOCK_SUCCESS);
- ABSL_TSAN_MUTEX_POST_LOCK(
- this, __tsan_mutex_read_lock | __tsan_mutex_try_lock, 0);
- return true;
- }
- loop_limit--;
- v = mu_.load(std::memory_order_relaxed);
- }
- if ((v & kMuEvent) != 0) {
- PostSynchEvent(this, SYNCH_EV_READERTRYLOCK_FAILED);
+ if (ABSL_PREDICT_TRUE((v & kMuEvent) == 0)) {
+ ABSL_TSAN_MUTEX_POST_LOCK(this,
+ __tsan_mutex_read_lock | __tsan_mutex_try_lock |
+ __tsan_mutex_try_lock_failed,
+ 0);
+ return false;
+ }
+ // we're recording events
+ return ReaderTryLockSlow();
+}
+
+ABSL_ATTRIBUTE_NOINLINE bool Mutex::ReaderTryLockSlow() {
+ intptr_t v = mu_.load(std::memory_order_relaxed);
+#if defined(__clang__)
+#pragma nounroll
+#endif
+ for (int loop_limit = 5; loop_limit != 0; loop_limit--) {
+ if ((v & kShared->slow_need_zero) == 0 &&
+ mu_.compare_exchange_strong(v, (kMuReader | v) + kMuOne,
+ std::memory_order_acquire,
+ std::memory_order_relaxed)) {
+ DebugOnlyLockEnter(this);
+ PostSynchEvent(this, SYNCH_EV_READERTRYLOCK_SUCCESS);
+ ABSL_TSAN_MUTEX_POST_LOCK(
+ this, __tsan_mutex_read_lock | __tsan_mutex_try_lock, 0);
+ return true;
}
}
+ PostSynchEvent(this, SYNCH_EV_READERTRYLOCK_FAILED);
ABSL_TSAN_MUTEX_POST_LOCK(this,
__tsan_mutex_read_lock | __tsan_mutex_try_lock |
__tsan_mutex_try_lock_failed,
@@ -1673,7 +1699,7 @@
return false;
}
-ABSL_XRAY_LOG_ARGS(1) void Mutex::Unlock() {
+void Mutex::Unlock() {
ABSL_TSAN_MUTEX_PRE_UNLOCK(this, 0);
DebugOnlyLockLeave(this);
intptr_t v = mu_.load(std::memory_order_relaxed);
@@ -1686,7 +1712,7 @@
// should_try_cas is whether we'll try a compare-and-swap immediately.
// NOTE: optimized out when kDebugMode is false.
bool should_try_cas = ((v & (kMuEvent | kMuWriter)) == kMuWriter &&
- (v & (kMuWait | kMuDesig)) != kMuWait);
+ (v & (kMuWait | kMuDesig)) != kMuWait);
// But, we can use an alternate computation of it, that compilers
// currently don't find on their own. When that changes, this function
// can be simplified.
@@ -1703,10 +1729,9 @@
static_cast<long long>(v), static_cast<long long>(x),
static_cast<long long>(y));
}
- if (x < y &&
- mu_.compare_exchange_strong(v, v & ~(kMuWrWait | kMuWriter),
- std::memory_order_release,
- std::memory_order_relaxed)) {
+ if (x < y && mu_.compare_exchange_strong(v, v & ~(kMuWrWait | kMuWriter),
+ std::memory_order_release,
+ std::memory_order_relaxed)) {
// fast writer release (writer with no waiters or with designated waker)
} else {
this->UnlockSlow(nullptr /*no waitp*/); // take slow path
@@ -1716,7 +1741,7 @@
// Requires v to represent a reader-locked state.
static bool ExactlyOneReader(intptr_t v) {
- assert((v & (kMuWriter|kMuReader)) == kMuReader);
+ assert((v & (kMuWriter | kMuReader)) == kMuReader);
assert((v & kMuHigh) != 0);
// The more straightforward "(v & kMuHigh) == kMuOne" also works, but
// on some architectures the following generates slightly smaller code.
@@ -1725,53 +1750,82 @@
return (v & kMuMultipleWaitersMask) == 0;
}
-ABSL_XRAY_LOG_ARGS(1) void Mutex::ReaderUnlock() {
+void Mutex::ReaderUnlock() {
ABSL_TSAN_MUTEX_PRE_UNLOCK(this, __tsan_mutex_read_lock);
DebugOnlyLockLeave(this);
intptr_t v = mu_.load(std::memory_order_relaxed);
- assert((v & (kMuWriter|kMuReader)) == kMuReader);
- if ((v & (kMuReader|kMuWait|kMuEvent)) == kMuReader) {
+ assert((v & (kMuWriter | kMuReader)) == kMuReader);
+ for (;;) {
+ if (ABSL_PREDICT_FALSE((v & (kMuReader | kMuWait | kMuEvent)) !=
+ kMuReader)) {
+ this->UnlockSlow(nullptr /*no waitp*/); // take slow path
+ break;
+ }
// fast reader release (reader with no waiters)
- intptr_t clear = ExactlyOneReader(v) ? kMuReader|kMuOne : kMuOne;
- if (mu_.compare_exchange_strong(v, v - clear,
- std::memory_order_release,
- std::memory_order_relaxed)) {
- ABSL_TSAN_MUTEX_POST_UNLOCK(this, __tsan_mutex_read_lock);
- return;
+ intptr_t clear = ExactlyOneReader(v) ? kMuReader | kMuOne : kMuOne;
+ if (ABSL_PREDICT_TRUE(
+ mu_.compare_exchange_strong(v, v - clear, std::memory_order_release,
+ std::memory_order_relaxed))) {
+ break;
}
}
- this->UnlockSlow(nullptr /*no waitp*/); // take slow path
ABSL_TSAN_MUTEX_POST_UNLOCK(this, __tsan_mutex_read_lock);
}
-// The zap_desig_waker bitmask is used to clear the designated waker flag in
-// the mutex if this thread has blocked, and therefore may be the designated
-// waker.
-static const intptr_t zap_desig_waker[] = {
- ~static_cast<intptr_t>(0), // not blocked
- ~static_cast<intptr_t>(
- kMuDesig) // blocked; turn off the designated waker bit
-};
+// Clears the designated waker flag in the mutex if this thread has blocked, and
+// therefore may be the designated waker.
+static intptr_t ClearDesignatedWakerMask(int flag) {
+ assert(flag >= 0);
+ assert(flag <= 1);
+ switch (flag) {
+ case 0: // not blocked
+ return ~static_cast<intptr_t>(0);
+ case 1: // blocked; turn off the designated waker bit
+ return ~static_cast<intptr_t>(kMuDesig);
+ }
+ ABSL_UNREACHABLE();
+}
-// The ignore_waiting_writers bitmask is used to ignore the existence
-// of waiting writers if a reader that has already blocked once
-// wakes up.
-static const intptr_t ignore_waiting_writers[] = {
- ~static_cast<intptr_t>(0), // not blocked
- ~static_cast<intptr_t>(
- kMuWrWait) // blocked; pretend there are no waiting writers
-};
+// Conditionally ignores the existence of waiting writers if a reader that has
+// already blocked once wakes up.
+static intptr_t IgnoreWaitingWritersMask(int flag) {
+ assert(flag >= 0);
+ assert(flag <= 1);
+ switch (flag) {
+ case 0: // not blocked
+ return ~static_cast<intptr_t>(0);
+ case 1: // blocked; pretend there are no waiting writers
+ return ~static_cast<intptr_t>(kMuWrWait);
+ }
+ ABSL_UNREACHABLE();
+}
// Internal version of LockWhen(). See LockSlowWithDeadline()
-ABSL_ATTRIBUTE_NOINLINE void Mutex::LockSlow(MuHow how, const Condition *cond,
+ABSL_ATTRIBUTE_NOINLINE void Mutex::LockSlow(MuHow how, const Condition* cond,
int flags) {
+ // Note: we specifically initialize spinloop_iterations after the first use
+ // in TryAcquireWithSpinning so that Lock function does not have any non-tail
+ // calls and consequently a stack frame. It's fine to have spinloop_iterations
+ // uninitialized (meaning no spinning) in all initial uncontended Lock calls
+ // and in the first contended call. After that we will have
+ // spinloop_iterations properly initialized.
+ if (ABSL_PREDICT_FALSE(
+ globals.spinloop_iterations.load(std::memory_order_relaxed) == 0)) {
+ if (absl::base_internal::NumCPUs() > 1) {
+ // If this is multiprocessor, allow spinning.
+ globals.spinloop_iterations.store(1500, std::memory_order_relaxed);
+ } else {
+ // If this a uniprocessor, only yield/sleep.
+ globals.spinloop_iterations.store(-1, std::memory_order_relaxed);
+ }
+ }
ABSL_RAW_CHECK(
this->LockSlowWithDeadline(how, cond, KernelTimeout::Never(), flags),
"condition untrue on return from LockSlow");
}
// Compute cond->Eval() and tell race detectors that we do it under mutex mu.
-static inline bool EvalConditionAnnotated(const Condition *cond, Mutex *mu,
+static inline bool EvalConditionAnnotated(const Condition* cond, Mutex* mu,
bool locking, bool trylock,
bool read_lock) {
// Delicate annotation dance.
@@ -1780,8 +1834,8 @@
// operation tsan considers that we've already released the mutex.
bool res = false;
#ifdef ABSL_INTERNAL_HAVE_TSAN_INTERFACE
- const int flags = read_lock ? __tsan_mutex_read_lock : 0;
- const int tryflags = flags | (trylock ? __tsan_mutex_try_lock : 0);
+ const uint32_t flags = read_lock ? __tsan_mutex_read_lock : 0;
+ const uint32_t tryflags = flags | (trylock ? __tsan_mutex_try_lock : 0);
#endif
if (locking) {
// For lock we pretend that we have finished the operation,
@@ -1821,7 +1875,7 @@
// tsan). As the result there is no tsan-visible synchronization between the
// addition and this thread. So if we would enable race detection here,
// it would race with the predicate initialization.
-static inline bool EvalConditionIgnored(Mutex *mu, const Condition *cond) {
+static inline bool EvalConditionIgnored(Mutex* mu, const Condition* cond) {
// Memory accesses are already ignored inside of lock/unlock operations,
// but synchronization operations are also ignored. When we evaluate the
// predicate we must ignore only memory accesses but not synchronization,
@@ -1846,14 +1900,16 @@
// obstruct this call
// - kMuIsCond indicates that this is a conditional acquire (condition variable,
// Await, LockWhen) so contention profiling should be suppressed.
-bool Mutex::LockSlowWithDeadline(MuHow how, const Condition *cond,
+bool Mutex::LockSlowWithDeadline(MuHow how, const Condition* cond,
KernelTimeout t, int flags) {
intptr_t v = mu_.load(std::memory_order_relaxed);
bool unlock = false;
if ((v & how->fast_need_zero) == 0 && // try fast acquire
mu_.compare_exchange_strong(
- v, (how->fast_or | (v & zap_desig_waker[flags & kMuHasBlocked])) +
- how->fast_add,
+ v,
+ (how->fast_or |
+ (v & ClearDesignatedWakerMask(flags & kMuHasBlocked))) +
+ how->fast_add,
std::memory_order_acquire, std::memory_order_relaxed)) {
if (cond == nullptr ||
EvalConditionAnnotated(cond, this, true, false, how == kShared)) {
@@ -1861,10 +1917,10 @@
}
unlock = true;
}
- SynchWaitParams waitp(
- how, cond, t, nullptr /*no cvmu*/, Synch_GetPerThreadAnnotated(this),
- nullptr /*no cv_word*/);
- if (!Condition::GuaranteedEqual(cond, nullptr)) {
+ SynchWaitParams waitp(how, cond, t, nullptr /*no cvmu*/,
+ Synch_GetPerThreadAnnotated(this),
+ nullptr /*no cv_word*/);
+ if (cond != nullptr) {
flags |= kMuIsCond;
}
if (unlock) {
@@ -1892,7 +1948,7 @@
// Test for either of two situations that should not occur in v:
// kMuWriter and kMuReader
// kMuWrWait and !kMuWait
- const uintptr_t w = v ^ kMuWait;
+ const uintptr_t w = static_cast<uintptr_t>(v ^ kMuWait);
// By flipping that bit, we can now test for:
// kMuWriter and kMuReader in w
// kMuWrWait and kMuWait in w
@@ -1904,20 +1960,20 @@
if (ABSL_PREDICT_TRUE((w & (w << 3) & (kMuWriter | kMuWrWait)) == 0)) return;
RAW_CHECK_FMT((v & (kMuWriter | kMuReader)) != (kMuWriter | kMuReader),
"%s: Mutex corrupt: both reader and writer lock held: %p",
- label, reinterpret_cast<void *>(v));
+ label, reinterpret_cast<void*>(v));
RAW_CHECK_FMT((v & (kMuWait | kMuWrWait)) != kMuWrWait,
- "%s: Mutex corrupt: waiting writer with no waiters: %p",
- label, reinterpret_cast<void *>(v));
+ "%s: Mutex corrupt: waiting writer with no waiters: %p", label,
+ reinterpret_cast<void*>(v));
assert(false);
}
-void Mutex::LockSlowLoop(SynchWaitParams *waitp, int flags) {
+void Mutex::LockSlowLoop(SynchWaitParams* waitp, int flags) {
SchedulingGuard::ScopedDisable disable_rescheduling;
int c = 0;
intptr_t v = mu_.load(std::memory_order_relaxed);
if ((v & kMuEvent) != 0) {
- PostSynchEvent(this,
- waitp->how == kExclusive? SYNCH_EV_LOCK: SYNCH_EV_READERLOCK);
+ PostSynchEvent(
+ this, waitp->how == kExclusive ? SYNCH_EV_LOCK : SYNCH_EV_READERLOCK);
}
ABSL_RAW_CHECK(
waitp->thread->waitp == nullptr || waitp->thread->suppress_fatal_errors,
@@ -1927,9 +1983,10 @@
CheckForMutexCorruption(v, "Lock");
if ((v & waitp->how->slow_need_zero) == 0) {
if (mu_.compare_exchange_strong(
- v, (waitp->how->fast_or |
- (v & zap_desig_waker[flags & kMuHasBlocked])) +
- waitp->how->fast_add,
+ v,
+ (waitp->how->fast_or |
+ (v & ClearDesignatedWakerMask(flags & kMuHasBlocked))) +
+ waitp->how->fast_add,
std::memory_order_acquire, std::memory_order_relaxed)) {
if (waitp->cond == nullptr ||
EvalConditionAnnotated(waitp->cond, this, true, false,
@@ -1941,13 +1998,14 @@
flags |= kMuHasBlocked;
c = 0;
}
- } else { // need to access waiter list
+ } else { // need to access waiter list
bool dowait = false;
- if ((v & (kMuSpin|kMuWait)) == 0) { // no waiters
+ if ((v & (kMuSpin | kMuWait)) == 0) { // no waiters
// This thread tries to become the one and only waiter.
- PerThreadSynch *new_h = Enqueue(nullptr, waitp, v, flags);
- intptr_t nv = (v & zap_desig_waker[flags & kMuHasBlocked] & kMuLow) |
- kMuWait;
+ PerThreadSynch* new_h = Enqueue(nullptr, waitp, v, flags);
+ intptr_t nv =
+ (v & ClearDesignatedWakerMask(flags & kMuHasBlocked) & kMuLow) |
+ kMuWait;
ABSL_RAW_CHECK(new_h != nullptr, "Enqueue to empty list failed");
if (waitp->how == kExclusive && (v & kMuReader) != 0) {
nv |= kMuWrWait;
@@ -1956,21 +2014,22 @@
v, reinterpret_cast<intptr_t>(new_h) | nv,
std::memory_order_release, std::memory_order_relaxed)) {
dowait = true;
- } else { // attempted Enqueue() failed
+ } else { // attempted Enqueue() failed
// zero out the waitp field set by Enqueue()
waitp->thread->waitp = nullptr;
}
} else if ((v & waitp->how->slow_inc_need_zero &
- ignore_waiting_writers[flags & kMuHasBlocked]) == 0) {
+ IgnoreWaitingWritersMask(flags & kMuHasBlocked)) == 0) {
// This is a reader that needs to increment the reader count,
// but the count is currently held in the last waiter.
if (mu_.compare_exchange_strong(
- v, (v & zap_desig_waker[flags & kMuHasBlocked]) | kMuSpin |
- kMuReader,
+ v,
+ (v & ClearDesignatedWakerMask(flags & kMuHasBlocked)) |
+ kMuSpin | kMuReader,
std::memory_order_acquire, std::memory_order_relaxed)) {
- PerThreadSynch *h = GetPerThreadSynch(v);
- h->readers += kMuOne; // inc reader count in waiter
- do { // release spinlock
+ PerThreadSynch* h = GetPerThreadSynch(v);
+ h->readers += kMuOne; // inc reader count in waiter
+ do { // release spinlock
v = mu_.load(std::memory_order_relaxed);
} while (!mu_.compare_exchange_weak(v, (v & ~kMuSpin) | kMuReader,
std::memory_order_release,
@@ -1980,28 +2039,30 @@
waitp->how == kShared)) {
break; // we timed out, or condition true, so return
}
- this->UnlockSlow(waitp); // got lock but condition false
+ this->UnlockSlow(waitp); // got lock but condition false
this->Block(waitp->thread);
flags |= kMuHasBlocked;
c = 0;
}
} else if ((v & kMuSpin) == 0 && // attempt to queue ourselves
mu_.compare_exchange_strong(
- v, (v & zap_desig_waker[flags & kMuHasBlocked]) | kMuSpin |
- kMuWait,
+ v,
+ (v & ClearDesignatedWakerMask(flags & kMuHasBlocked)) |
+ kMuSpin | kMuWait,
std::memory_order_acquire, std::memory_order_relaxed)) {
- PerThreadSynch *h = GetPerThreadSynch(v);
- PerThreadSynch *new_h = Enqueue(h, waitp, v, flags);
+ PerThreadSynch* h = GetPerThreadSynch(v);
+ PerThreadSynch* new_h = Enqueue(h, waitp, v, flags);
intptr_t wr_wait = 0;
ABSL_RAW_CHECK(new_h != nullptr, "Enqueue to list failed");
if (waitp->how == kExclusive && (v & kMuReader) != 0) {
- wr_wait = kMuWrWait; // give priority to a waiting writer
+ wr_wait = kMuWrWait; // give priority to a waiting writer
}
- do { // release spinlock
+ do { // release spinlock
v = mu_.load(std::memory_order_relaxed);
} while (!mu_.compare_exchange_weak(
- v, (v & (kMuLow & ~kMuSpin)) | kMuWait | wr_wait |
- reinterpret_cast<intptr_t>(new_h),
+ v,
+ (v & (kMuLow & ~kMuSpin)) | kMuWait | wr_wait |
+ reinterpret_cast<intptr_t>(new_h),
std::memory_order_release, std::memory_order_relaxed));
dowait = true;
}
@@ -2021,9 +2082,9 @@
waitp->thread->waitp == nullptr || waitp->thread->suppress_fatal_errors,
"detected illegal recursion into Mutex code");
if ((v & kMuEvent) != 0) {
- PostSynchEvent(this,
- waitp->how == kExclusive? SYNCH_EV_LOCK_RETURNING :
- SYNCH_EV_READERLOCK_RETURNING);
+ PostSynchEvent(this, waitp->how == kExclusive
+ ? SYNCH_EV_LOCK_RETURNING
+ : SYNCH_EV_READERLOCK_RETURNING);
}
}
@@ -2032,28 +2093,27 @@
// which holds the lock but is not runnable because its condition is false
// or it is in the process of blocking on a condition variable; it must requeue
// itself on the mutex/condvar to wait for its condition to become true.
-ABSL_ATTRIBUTE_NOINLINE void Mutex::UnlockSlow(SynchWaitParams *waitp) {
+ABSL_ATTRIBUTE_NOINLINE void Mutex::UnlockSlow(SynchWaitParams* waitp) {
SchedulingGuard::ScopedDisable disable_rescheduling;
intptr_t v = mu_.load(std::memory_order_relaxed);
this->AssertReaderHeld();
CheckForMutexCorruption(v, "Unlock");
if ((v & kMuEvent) != 0) {
- PostSynchEvent(this,
- (v & kMuWriter) != 0? SYNCH_EV_UNLOCK: SYNCH_EV_READERUNLOCK);
+ PostSynchEvent(
+ this, (v & kMuWriter) != 0 ? SYNCH_EV_UNLOCK : SYNCH_EV_READERUNLOCK);
}
int c = 0;
// the waiter under consideration to wake, or zero
- PerThreadSynch *w = nullptr;
+ PerThreadSynch* w = nullptr;
// the predecessor to w or zero
- PerThreadSynch *pw = nullptr;
+ PerThreadSynch* pw = nullptr;
// head of the list searched previously, or zero
- PerThreadSynch *old_h = nullptr;
+ PerThreadSynch* old_h = nullptr;
// a condition that's known to be false.
- const Condition *known_false = nullptr;
- PerThreadSynch *wake_list = kPerThreadSynchNull; // list of threads to wake
- intptr_t wr_wait = 0; // set to kMuWrWait if we wake a reader and a
- // later writer could have acquired the lock
- // (starvation avoidance)
+ PerThreadSynch* wake_list = kPerThreadSynchNull; // list of threads to wake
+ intptr_t wr_wait = 0; // set to kMuWrWait if we wake a reader and a
+ // later writer could have acquired the lock
+ // (starvation avoidance)
ABSL_RAW_CHECK(waitp == nullptr || waitp->thread->waitp == nullptr ||
waitp->thread->suppress_fatal_errors,
"detected illegal recursion into Mutex code");
@@ -2073,8 +2133,7 @@
} else if ((v & (kMuReader | kMuWait)) == kMuReader && waitp == nullptr) {
// fast reader release (reader with no waiters)
intptr_t clear = ExactlyOneReader(v) ? kMuReader | kMuOne : kMuOne;
- if (mu_.compare_exchange_strong(v, v - clear,
- std::memory_order_release,
+ if (mu_.compare_exchange_strong(v, v - clear, std::memory_order_release,
std::memory_order_relaxed)) {
return;
}
@@ -2082,16 +2141,16 @@
mu_.compare_exchange_strong(v, v | kMuSpin,
std::memory_order_acquire,
std::memory_order_relaxed)) {
- if ((v & kMuWait) == 0) { // no one to wake
+ if ((v & kMuWait) == 0) { // no one to wake
intptr_t nv;
bool do_enqueue = true; // always Enqueue() the first time
ABSL_RAW_CHECK(waitp != nullptr,
"UnlockSlow is confused"); // about to sleep
- do { // must loop to release spinlock as reader count may change
+ do { // must loop to release spinlock as reader count may change
v = mu_.load(std::memory_order_relaxed);
// decrement reader count if there are readers
- intptr_t new_readers = (v >= kMuOne)? v - kMuOne : v;
- PerThreadSynch *new_h = nullptr;
+ intptr_t new_readers = (v >= kMuOne) ? v - kMuOne : v;
+ PerThreadSynch* new_h = nullptr;
if (do_enqueue) {
// If we are enqueuing on a CondVar (waitp->cv_word != nullptr) then
// we must not retry here. The initial attempt will always have
@@ -2115,21 +2174,20 @@
}
// release spinlock & our lock; retry if reader-count changed
// (writer count cannot change since we hold lock)
- } while (!mu_.compare_exchange_weak(v, nv,
- std::memory_order_release,
+ } while (!mu_.compare_exchange_weak(v, nv, std::memory_order_release,
std::memory_order_relaxed));
break;
}
// There are waiters.
// Set h to the head of the circular waiter list.
- PerThreadSynch *h = GetPerThreadSynch(v);
+ PerThreadSynch* h = GetPerThreadSynch(v);
if ((v & kMuReader) != 0 && (h->readers & kMuHigh) > kMuOne) {
// a reader but not the last
- h->readers -= kMuOne; // release our lock
- intptr_t nv = v; // normally just release spinlock
+ h->readers -= kMuOne; // release our lock
+ intptr_t nv = v; // normally just release spinlock
if (waitp != nullptr) { // but waitp!=nullptr => must queue ourselves
- PerThreadSynch *new_h = Enqueue(h, waitp, v, kMuIsCond);
+ PerThreadSynch* new_h = Enqueue(h, waitp, v, kMuIsCond);
ABSL_RAW_CHECK(new_h != nullptr,
"waiters disappeared during Enqueue()!");
nv &= kMuLow;
@@ -2147,17 +2205,17 @@
// The lock is becoming free, and there's a waiter
if (old_h != nullptr &&
- !old_h->may_skip) { // we used old_h as a terminator
- old_h->may_skip = true; // allow old_h to skip once more
+ !old_h->may_skip) { // we used old_h as a terminator
+ old_h->may_skip = true; // allow old_h to skip once more
ABSL_RAW_CHECK(old_h->skip == nullptr, "illegal skip from head");
if (h != old_h && MuEquivalentWaiter(old_h, old_h->next)) {
old_h->skip = old_h->next; // old_h not head & can skip to successor
}
}
if (h->next->waitp->how == kExclusive &&
- Condition::GuaranteedEqual(h->next->waitp->cond, nullptr)) {
+ h->next->waitp->cond == nullptr) {
// easy case: writer with no condition; no need to search
- pw = h; // wake w, the successor of h (=pw)
+ pw = h; // wake w, the successor of h (=pw)
w = h->next;
w->wake = true;
// We are waking up a writer. This writer may be racing against
@@ -2180,13 +2238,13 @@
// waiter has a condition or is a reader. We avoid searching over
// waiters we've searched on previous iterations by starting at
// old_h if it's set. If old_h==h, there's no one to wakeup at all.
- if (old_h == h) { // we've searched before, and nothing's new
- // so there's no one to wake.
- intptr_t nv = (v & ~(kMuReader|kMuWriter|kMuWrWait));
+ if (old_h == h) { // we've searched before, and nothing's new
+ // so there's no one to wake.
+ intptr_t nv = (v & ~(kMuReader | kMuWriter | kMuWrWait));
h->readers = 0;
- h->maybe_unlocking = false; // finished unlocking
- if (waitp != nullptr) { // we must queue ourselves and sleep
- PerThreadSynch *new_h = Enqueue(h, waitp, v, kMuIsCond);
+ h->maybe_unlocking = false; // finished unlocking
+ if (waitp != nullptr) { // we must queue ourselves and sleep
+ PerThreadSynch* new_h = Enqueue(h, waitp, v, kMuIsCond);
nv &= kMuLow;
if (new_h != nullptr) {
nv |= kMuWait | reinterpret_cast<intptr_t>(new_h);
@@ -2200,12 +2258,12 @@
}
// set up to walk the list
- PerThreadSynch *w_walk; // current waiter during list walk
- PerThreadSynch *pw_walk; // previous waiter during list walk
+ PerThreadSynch* w_walk; // current waiter during list walk
+ PerThreadSynch* pw_walk; // previous waiter during list walk
if (old_h != nullptr) { // we've searched up to old_h before
pw_walk = old_h;
w_walk = old_h->next;
- } else { // no prior search, start at beginning
+ } else { // no prior search, start at beginning
pw_walk =
nullptr; // h->next's predecessor may change; don't record it
w_walk = h->next;
@@ -2231,36 +2289,32 @@
// to walk the path from w_walk to h inclusive. (TryRemove() can remove
// a waiter anywhere, but it acquires both the spinlock and the Mutex)
- old_h = h; // remember we searched to here
+ old_h = h; // remember we searched to here
// Walk the path upto and including h looking for waiters we can wake.
while (pw_walk != h) {
w_walk->wake = false;
if (w_walk->waitp->cond ==
nullptr || // no condition => vacuously true OR
- (w_walk->waitp->cond != known_false &&
- // this thread's condition is not known false, AND
- // is in fact true
- EvalConditionIgnored(this, w_walk->waitp->cond))) {
+ // this thread's condition is true
+ EvalConditionIgnored(this, w_walk->waitp->cond)) {
if (w == nullptr) {
- w_walk->wake = true; // can wake this waiter
+ w_walk->wake = true; // can wake this waiter
w = w_walk;
pw = pw_walk;
if (w_walk->waitp->how == kExclusive) {
wr_wait = kMuWrWait;
- break; // bail if waking this writer
+ break; // bail if waking this writer
}
} else if (w_walk->waitp->how == kShared) { // wake if a reader
w_walk->wake = true;
- } else { // writer with true condition
+ } else { // writer with true condition
wr_wait = kMuWrWait;
}
- } else { // can't wake; condition false
- known_false = w_walk->waitp->cond; // remember last false condition
}
- if (w_walk->wake) { // we're waking reader w_walk
- pw_walk = w_walk; // don't skip similar waiters
- } else { // not waking; skip as much as possible
+ if (w_walk->wake) { // we're waking reader w_walk
+ pw_walk = w_walk; // don't skip similar waiters
+ } else { // not waking; skip as much as possible
pw_walk = Skip(w_walk);
}
// If pw_walk == h, then load of pw_walk->next can race with
@@ -2287,8 +2341,8 @@
h = DequeueAllWakeable(h, pw, &wake_list);
intptr_t nv = (v & kMuEvent) | kMuDesig;
- // assume no waiters left,
- // set kMuDesig for INV1a
+ // assume no waiters left,
+ // set kMuDesig for INV1a
if (waitp != nullptr) { // we must queue ourselves and sleep
h = Enqueue(h, waitp, v, kMuIsCond);
@@ -2301,7 +2355,7 @@
if (h != nullptr) { // there are waiters left
h->readers = 0;
- h->maybe_unlocking = false; // finished unlocking
+ h->maybe_unlocking = false; // finished unlocking
nv |= wr_wait | kMuWait | reinterpret_cast<intptr_t>(h);
}
@@ -2312,22 +2366,29 @@
}
// aggressive here; no one can proceed till we do
c = synchronization_internal::MutexDelay(c, AGGRESSIVE);
- } // end of for(;;)-loop
+ } // end of for(;;)-loop
if (wake_list != kPerThreadSynchNull) {
- int64_t enqueue_timestamp = wake_list->waitp->contention_start_cycles;
- bool cond_waiter = wake_list->cond_waiter;
+ int64_t total_wait_cycles = 0;
+ int64_t max_wait_cycles = 0;
+ int64_t now = CycleClock::Now();
do {
- wake_list = Wakeup(wake_list); // wake waiters
+ // Profile lock contention events only if the waiter was trying to acquire
+ // the lock, not waiting on a condition variable or Condition.
+ if (!wake_list->cond_waiter) {
+ int64_t cycles_waited =
+ (now - wake_list->waitp->contention_start_cycles);
+ total_wait_cycles += cycles_waited;
+ if (max_wait_cycles == 0) max_wait_cycles = cycles_waited;
+ wake_list->waitp->contention_start_cycles = now;
+ wake_list->waitp->should_submit_contention_data = true;
+ }
+ wake_list = Wakeup(wake_list); // wake waiters
} while (wake_list != kPerThreadSynchNull);
- if (!cond_waiter) {
- // Sample lock contention events only if the (first) waiter was trying to
- // acquire the lock, not waiting on a condition variable or Condition.
- int64_t wait_cycles =
- base_internal::CycleClock::Now() - enqueue_timestamp;
- mutex_tracer("slow release", this, wait_cycles);
+ if (total_wait_cycles > 0) {
+ mutex_tracer("slow release", this, total_wait_cycles);
ABSL_TSAN_MUTEX_PRE_DIVERT(this, 0);
- submit_profile_data(enqueue_timestamp);
+ submit_profile_data(total_wait_cycles);
ABSL_TSAN_MUTEX_POST_DIVERT(this, 0);
}
}
@@ -2350,15 +2411,15 @@
// condition variable. If this mutex is free, we simply wake the thread.
// It will later acquire the mutex with high probability. Otherwise, we
// enqueue thread w on this mutex.
-void Mutex::Fer(PerThreadSynch *w) {
+void Mutex::Fer(PerThreadSynch* w) {
SchedulingGuard::ScopedDisable disable_rescheduling;
int c = 0;
ABSL_RAW_CHECK(w->waitp->cond == nullptr,
"Mutex::Fer while waiting on Condition");
- ABSL_RAW_CHECK(!w->waitp->timeout.has_timeout(),
- "Mutex::Fer while in timed wait");
ABSL_RAW_CHECK(w->waitp->cv_word == nullptr,
"Mutex::Fer with pending CondVar queueing");
+ // The CondVar timeout is not relevant for the Mutex wait.
+ w->waitp->timeout = {};
for (;;) {
intptr_t v = mu_.load(std::memory_order_relaxed);
// Note: must not queue if the mutex is unlocked (nobody will wake it).
@@ -2375,9 +2436,10 @@
IncrementSynchSem(this, w);
return;
} else {
- if ((v & (kMuSpin|kMuWait)) == 0) { // no waiters
+ if ((v & (kMuSpin | kMuWait)) == 0) { // no waiters
// This thread tries to become the one and only waiter.
- PerThreadSynch *new_h = Enqueue(nullptr, w->waitp, v, kMuIsCond);
+ PerThreadSynch* new_h =
+ Enqueue(nullptr, w->waitp, v, kMuIsCond | kMuIsFer);
ABSL_RAW_CHECK(new_h != nullptr,
"Enqueue failed"); // we must queue ourselves
if (mu_.compare_exchange_strong(
@@ -2387,8 +2449,8 @@
}
} else if ((v & kMuSpin) == 0 &&
mu_.compare_exchange_strong(v, v | kMuSpin | kMuWait)) {
- PerThreadSynch *h = GetPerThreadSynch(v);
- PerThreadSynch *new_h = Enqueue(h, w->waitp, v, kMuIsCond);
+ PerThreadSynch* h = GetPerThreadSynch(v);
+ PerThreadSynch* new_h = Enqueue(h, w->waitp, v, kMuIsCond | kMuIsFer);
ABSL_RAW_CHECK(new_h != nullptr,
"Enqueue failed"); // we must queue ourselves
do {
@@ -2407,19 +2469,18 @@
void Mutex::AssertHeld() const {
if ((mu_.load(std::memory_order_relaxed) & kMuWriter) == 0) {
- SynchEvent *e = GetSynchEvent(this);
+ SynchEvent* e = GetSynchEvent(this);
ABSL_RAW_LOG(FATAL, "thread should hold write lock on Mutex %p %s",
- static_cast<const void *>(this),
- (e == nullptr ? "" : e->name));
+ static_cast<const void*>(this), (e == nullptr ? "" : e->name));
}
}
void Mutex::AssertReaderHeld() const {
if ((mu_.load(std::memory_order_relaxed) & (kMuReader | kMuWriter)) == 0) {
- SynchEvent *e = GetSynchEvent(this);
- ABSL_RAW_LOG(
- FATAL, "thread should hold at least a read lock on Mutex %p %s",
- static_cast<const void *>(this), (e == nullptr ? "" : e->name));
+ SynchEvent* e = GetSynchEvent(this);
+ ABSL_RAW_LOG(FATAL,
+ "thread should hold at least a read lock on Mutex %p %s",
+ static_cast<const void*>(this), (e == nullptr ? "" : e->name));
}
}
@@ -2430,42 +2491,38 @@
static const intptr_t kCvLow = 0x0003L; // low order bits of CV
// Hack to make constant values available to gdb pretty printer
-enum { kGdbCvSpin = kCvSpin, kGdbCvEvent = kCvEvent, kGdbCvLow = kCvLow, };
+enum {
+ kGdbCvSpin = kCvSpin,
+ kGdbCvEvent = kCvEvent,
+ kGdbCvLow = kCvLow,
+};
static_assert(PerThreadSynch::kAlignment > kCvLow,
"PerThreadSynch::kAlignment must be greater than kCvLow");
-void CondVar::EnableDebugLog(const char *name) {
- SynchEvent *e = EnsureSynchEvent(&this->cv_, name, kCvEvent, kCvSpin);
+void CondVar::EnableDebugLog(const char* name) {
+ SynchEvent* e = EnsureSynchEvent(&this->cv_, name, kCvEvent, kCvSpin);
e->log = true;
UnrefSynchEvent(e);
}
-CondVar::~CondVar() {
- if ((cv_.load(std::memory_order_relaxed) & kCvEvent) != 0) {
- ForgetSynchEvent(&this->cv_, kCvEvent, kCvSpin);
- }
-}
-
-
// Remove thread s from the list of waiters on this condition variable.
-void CondVar::Remove(PerThreadSynch *s) {
+void CondVar::Remove(PerThreadSynch* s) {
SchedulingGuard::ScopedDisable disable_rescheduling;
intptr_t v;
int c = 0;
for (v = cv_.load(std::memory_order_relaxed);;
v = cv_.load(std::memory_order_relaxed)) {
if ((v & kCvSpin) == 0 && // attempt to acquire spinlock
- cv_.compare_exchange_strong(v, v | kCvSpin,
- std::memory_order_acquire,
+ cv_.compare_exchange_strong(v, v | kCvSpin, std::memory_order_acquire,
std::memory_order_relaxed)) {
- PerThreadSynch *h = reinterpret_cast<PerThreadSynch *>(v & ~kCvLow);
+ PerThreadSynch* h = reinterpret_cast<PerThreadSynch*>(v & ~kCvLow);
if (h != nullptr) {
- PerThreadSynch *w = h;
+ PerThreadSynch* w = h;
while (w->next != s && w->next != h) { // search for thread
w = w->next;
}
- if (w->next == s) { // found thread; remove it
+ if (w->next == s) { // found thread; remove it
w->next = s->next;
if (h == s) {
h = (w == s) ? nullptr : w;
@@ -2474,7 +2531,7 @@
s->state.store(PerThreadSynch::kAvailable, std::memory_order_release);
}
}
- // release spinlock
+ // release spinlock
cv_.store((v & kCvEvent) | reinterpret_cast<intptr_t>(h),
std::memory_order_release);
return;
@@ -2492,19 +2549,19 @@
// before calling Mutex::UnlockSlow(), the Mutex code might be re-entered (via
// the logging code, or via a Condition function) and might potentially attempt
// to block this thread. That would be a problem if the thread were already on
-// a the condition variable waiter queue. Thus, we use the waitp->cv_word
-// to tell the unlock code to call CondVarEnqueue() to queue the thread on the
-// condition variable queue just before the mutex is to be unlocked, and (most
+// a condition variable waiter queue. Thus, we use the waitp->cv_word to tell
+// the unlock code to call CondVarEnqueue() to queue the thread on the condition
+// variable queue just before the mutex is to be unlocked, and (most
// importantly) after any call to an external routine that might re-enter the
// mutex code.
-static void CondVarEnqueue(SynchWaitParams *waitp) {
+static void CondVarEnqueue(SynchWaitParams* waitp) {
// This thread might be transferred to the Mutex queue by Fer() when
// we are woken. To make sure that is what happens, Enqueue() doesn't
// call CondVarEnqueue() again but instead uses its normal code. We
// must do this before we queue ourselves so that cv_word will be null
// when seen by the dequeuer, who may wish immediately to requeue
// this thread on another queue.
- std::atomic<intptr_t> *cv_word = waitp->cv_word;
+ std::atomic<intptr_t>* cv_word = waitp->cv_word;
waitp->cv_word = nullptr;
intptr_t v = cv_word->load(std::memory_order_relaxed);
@@ -2517,8 +2574,8 @@
v = cv_word->load(std::memory_order_relaxed);
}
ABSL_RAW_CHECK(waitp->thread->waitp == nullptr, "waiting when shouldn't be");
- waitp->thread->waitp = waitp; // prepare ourselves for waiting
- PerThreadSynch *h = reinterpret_cast<PerThreadSynch *>(v & ~kCvLow);
+ waitp->thread->waitp = waitp; // prepare ourselves for waiting
+ PerThreadSynch* h = reinterpret_cast<PerThreadSynch*>(v & ~kCvLow);
if (h == nullptr) { // add this thread to waiter list
waitp->thread->next = waitp->thread;
} else {
@@ -2531,8 +2588,8 @@
std::memory_order_release);
}
-bool CondVar::WaitCommon(Mutex *mutex, KernelTimeout t) {
- bool rc = false; // return value; true iff we timed-out
+bool CondVar::WaitCommon(Mutex* mutex, KernelTimeout t) {
+ bool rc = false; // return value; true iff we timed-out
intptr_t mutex_v = mutex->mu_.load(std::memory_order_relaxed);
Mutex::MuHow mutex_how = ((mutex_v & kMuWriter) != 0) ? kExclusive : kShared;
@@ -2557,6 +2614,23 @@
while (waitp.thread->state.load(std::memory_order_acquire) ==
PerThreadSynch::kQueued) {
if (!Mutex::DecrementSynchSem(mutex, waitp.thread, t)) {
+ // DecrementSynchSem returned due to timeout.
+ // Now we will either (1) remove ourselves from the wait list in Remove
+ // below, in which case Remove will set thread.state = kAvailable and
+ // we will not call DecrementSynchSem again; or (2) Signal/SignalAll
+ // has removed us concurrently and is calling Wakeup, which will set
+ // thread.state = kAvailable and post to the semaphore.
+ // It's important to reset the timeout for the case (2) because otherwise
+ // we can live-lock in this loop since DecrementSynchSem will always
+ // return immediately due to timeout, but Signal/SignalAll is not
+ // necessary set thread.state = kAvailable yet (and is not scheduled
+ // due to thread priorities or other scheduler artifacts).
+ // Note this could also be resolved if Signal/SignalAll would set
+ // thread.state = kAvailable while holding the wait list spin lock.
+ // But this can't be easily done for SignalAll since it grabs the whole
+ // wait list with a single compare-exchange and does not really grab
+ // the spin lock.
+ t = KernelTimeout::Never();
this->Remove(waitp.thread);
rc = true;
}
@@ -2582,35 +2656,6 @@
return rc;
}
-bool CondVar::WaitWithTimeout(Mutex *mu, absl::Duration timeout) {
- return WaitWithDeadline(mu, DeadlineFromTimeout(timeout));
-}
-
-bool CondVar::WaitWithDeadline(Mutex *mu, absl::Time deadline) {
- return WaitCommon(mu, KernelTimeout(deadline));
-}
-
-void CondVar::Wait(Mutex *mu) {
- WaitCommon(mu, KernelTimeout::Never());
-}
-
-// Wake thread w
-// If it was a timed wait, w will be waiting on w->cv
-// Otherwise, if it was not a Mutex mutex, w will be waiting on w->sem
-// Otherwise, w is transferred to the Mutex mutex via Mutex::Fer().
-void CondVar::Wakeup(PerThreadSynch *w) {
- if (w->waitp->timeout.has_timeout() || w->waitp->cvmu == nullptr) {
- // The waiting thread only needs to observe "w->state == kAvailable" to be
- // released, we must cache "cvmu" before clearing "next".
- Mutex *mu = w->waitp->cvmu;
- w->next = nullptr;
- w->state.store(PerThreadSynch::kAvailable, std::memory_order_release);
- Mutex::IncrementSynchSem(mu, w);
- } else {
- w->waitp->cvmu->Fer(w);
- }
-}
-
void CondVar::Signal() {
SchedulingGuard::ScopedDisable disable_rescheduling;
ABSL_TSAN_MUTEX_PRE_SIGNAL(nullptr, 0);
@@ -2619,11 +2664,10 @@
for (v = cv_.load(std::memory_order_relaxed); v != 0;
v = cv_.load(std::memory_order_relaxed)) {
if ((v & kCvSpin) == 0 && // attempt to acquire spinlock
- cv_.compare_exchange_strong(v, v | kCvSpin,
- std::memory_order_acquire,
+ cv_.compare_exchange_strong(v, v | kCvSpin, std::memory_order_acquire,
std::memory_order_relaxed)) {
- PerThreadSynch *h = reinterpret_cast<PerThreadSynch *>(v & ~kCvLow);
- PerThreadSynch *w = nullptr;
+ PerThreadSynch* h = reinterpret_cast<PerThreadSynch*>(v & ~kCvLow);
+ PerThreadSynch* w = nullptr;
if (h != nullptr) { // remove first waiter
w = h->next;
if (w == h) {
@@ -2632,11 +2676,11 @@
h->next = w->next;
}
}
- // release spinlock
+ // release spinlock
cv_.store((v & kCvEvent) | reinterpret_cast<intptr_t>(h),
std::memory_order_release);
if (w != nullptr) {
- CondVar::Wakeup(w); // wake waiter, if there was one
+ w->waitp->cvmu->Fer(w); // wake waiter, if there was one
cond_var_tracer("Signal wakeup", this);
}
if ((v & kCvEvent) != 0) {
@@ -2651,7 +2695,7 @@
ABSL_TSAN_MUTEX_POST_SIGNAL(nullptr, 0);
}
-void CondVar::SignalAll () {
+void CondVar::SignalAll() {
ABSL_TSAN_MUTEX_PRE_SIGNAL(nullptr, 0);
intptr_t v;
int c = 0;
@@ -2665,14 +2709,14 @@
if ((v & kCvSpin) == 0 &&
cv_.compare_exchange_strong(v, v & kCvEvent, std::memory_order_acquire,
std::memory_order_relaxed)) {
- PerThreadSynch *h = reinterpret_cast<PerThreadSynch *>(v & ~kCvLow);
+ PerThreadSynch* h = reinterpret_cast<PerThreadSynch*>(v & ~kCvLow);
if (h != nullptr) {
- PerThreadSynch *w;
- PerThreadSynch *n = h->next;
- do { // for every thread, wake it up
+ PerThreadSynch* w;
+ PerThreadSynch* n = h->next;
+ do { // for every thread, wake it up
w = n;
n = n->next;
- CondVar::Wakeup(w);
+ w->waitp->cvmu->Fer(w);
} while (w != h);
cond_var_tracer("SignalAll wakeup", this);
}
@@ -2697,54 +2741,54 @@
}
#ifdef ABSL_HAVE_THREAD_SANITIZER
-extern "C" void __tsan_read1(void *addr);
+extern "C" void __tsan_read1(void* addr);
#else
#define __tsan_read1(addr) // do nothing if TSan not enabled
#endif
// A function that just returns its argument, dereferenced
-static bool Dereference(void *arg) {
+static bool Dereference(void* arg) {
// ThreadSanitizer does not instrument this file for memory accesses.
// This function dereferences a user variable that can participate
// in a data race, so we need to manually tell TSan about this memory access.
__tsan_read1(arg);
- return *(static_cast<bool *>(arg));
+ return *(static_cast<bool*>(arg));
}
-Condition::Condition() {} // null constructor, used for kTrue only
-const Condition Condition::kTrue;
+ABSL_CONST_INIT const Condition Condition::kTrue;
-Condition::Condition(bool (*func)(void *), void *arg)
- : eval_(&CallVoidPtrFunction),
- function_(func),
- method_(nullptr),
- arg_(arg) {}
-
-bool Condition::CallVoidPtrFunction(const Condition *c) {
- return (*c->function_)(c->arg_);
+Condition::Condition(bool (*func)(void*), void* arg)
+ : eval_(&CallVoidPtrFunction), arg_(arg) {
+ static_assert(sizeof(&func) <= sizeof(callback_),
+ "An overlarge function pointer passed to Condition.");
+ StoreCallback(func);
}
-Condition::Condition(const bool *cond)
+bool Condition::CallVoidPtrFunction(const Condition* c) {
+ using FunctionPointer = bool (*)(void*);
+ FunctionPointer function_pointer;
+ std::memcpy(&function_pointer, c->callback_, sizeof(function_pointer));
+ return (*function_pointer)(c->arg_);
+}
+
+Condition::Condition(const bool* cond)
: eval_(CallVoidPtrFunction),
- function_(Dereference),
- method_(nullptr),
// const_cast is safe since Dereference does not modify arg
- arg_(const_cast<bool *>(cond)) {}
-
-bool Condition::Eval() const {
- // eval_ == null for kTrue
- return (this->eval_ == nullptr) || (*this->eval_)(this);
+ arg_(const_cast<bool*>(cond)) {
+ using FunctionPointer = bool (*)(void*);
+ const FunctionPointer dereference = Dereference;
+ StoreCallback(dereference);
}
-bool Condition::GuaranteedEqual(const Condition *a, const Condition *b) {
- if (a == nullptr) {
- return b == nullptr || b->eval_ == nullptr;
+bool Condition::Eval() const { return (*this->eval_)(this); }
+
+bool Condition::GuaranteedEqual(const Condition* a, const Condition* b) {
+ if (a == nullptr || b == nullptr) {
+ return a == b;
}
- if (b == nullptr || b->eval_ == nullptr) {
- return a->eval_ == nullptr;
- }
- return a->eval_ == b->eval_ && a->function_ == b->function_ &&
- a->arg_ == b->arg_ && a->method_ == b->method_;
+ // Check equality of the representative fields.
+ return a->eval_ == b->eval_ && a->arg_ == b->arg_ &&
+ !memcmp(a->callback_, b->callback_, sizeof(a->callback_));
}
ABSL_NAMESPACE_END
diff --git a/absl/synchronization/mutex.h b/absl/synchronization/mutex.h
index f49e0c8..d53a22b 100644
--- a/absl/synchronization/mutex.h
+++ b/absl/synchronization/mutex.h
@@ -60,8 +60,11 @@
#include <atomic>
#include <cstdint>
+#include <cstring>
+#include <iterator>
#include <string>
+#include "absl/base/attributes.h"
#include "absl/base/const_init.h"
#include "absl/base/internal/identity.h"
#include "absl/base/internal/low_level_alloc.h"
@@ -90,26 +93,42 @@
//
// A `Mutex` has two basic operations: `Mutex::Lock()` and `Mutex::Unlock()`.
// The `Lock()` operation *acquires* a `Mutex` (in a state known as an
-// *exclusive* -- or write -- lock), while the `Unlock()` operation *releases* a
+// *exclusive* -- or *write* -- lock), and the `Unlock()` operation *releases* a
// Mutex. During the span of time between the Lock() and Unlock() operations,
-// a mutex is said to be *held*. By design all mutexes support exclusive/write
+// a mutex is said to be *held*. By design, all mutexes support exclusive/write
// locks, as this is the most common way to use a mutex.
//
+// Mutex operations are only allowed under certain conditions; otherwise an
+// operation is "invalid", and disallowed by the API. The conditions concern
+// both the current state of the mutex and the identity of the threads that
+// are performing the operations.
+//
// The `Mutex` state machine for basic lock/unlock operations is quite simple:
//
-// | | Lock() | Unlock() |
-// |----------------+------------+----------|
-// | Free | Exclusive | invalid |
-// | Exclusive | blocks | Free |
+// | | Lock() | Unlock() |
+// |----------------+------------------------+----------|
+// | Free | Exclusive | invalid |
+// | Exclusive | blocks, then exclusive | Free |
//
-// Attempts to `Unlock()` must originate from the thread that performed the
-// corresponding `Lock()` operation.
+// The full conditions are as follows.
//
-// An "invalid" operation is disallowed by the API. The `Mutex` implementation
-// is allowed to do anything on an invalid call, including but not limited to
+// * 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.
+//
+// * In other words, the state of being "held" has both a temporal component
+// (from `Lock()` until `Unlock()`) as well as a thread identity component:
+// the mutex is held *by a particular thread*.
+//
+// An "invalid" operation has undefined behavior. The `Mutex` implementation
+// is allowed to do anything on an invalid call, including, but not limited to,
// crashing with a useful error message, silently succeeding, or corrupting
-// data structures. In debug mode, the implementation attempts to crash with a
-// useful error message.
+// data structures. In debug mode, the implementation may crash with a useful
+// error message.
//
// `Mutex` is not guaranteed to be "fair" in prioritizing waiting threads; it
// is, however, approximately fair over long periods, and starvation-free for
@@ -123,8 +142,9 @@
// issues that could potentially result in race conditions and deadlocks.
//
// For more information about the lock annotations, please see
-// [Thread Safety Analysis](http://clang.llvm.org/docs/ThreadSafetyAnalysis.html)
-// in the Clang documentation.
+// [Thread Safety
+// Analysis](http://clang.llvm.org/docs/ThreadSafetyAnalysis.html) in the Clang
+// documentation.
//
// See also `MutexLock`, below, for scoped `Mutex` acquisition.
@@ -174,9 +194,12 @@
// Mutex::AssertHeld()
//
- // Return immediately if this thread holds the `Mutex` exclusively (in write
- // mode). Otherwise, may report an error (typically by crashing with a
- // diagnostic), or may return immediately.
+ // Require that the mutex be held exclusively (write mode) by this thread.
+ //
+ // If the mutex is not currently held by this thread, this function may report
+ // an error (typically by crashing with a diagnostic) or it may do nothing.
+ // This function is intended only as a tool to assist debugging; it doesn't
+ // guarantee correctness.
void AssertHeld() const ABSL_ASSERT_EXCLUSIVE_LOCK();
// ---------------------------------------------------------------------------
@@ -236,9 +259,13 @@
// Mutex::AssertReaderHeld()
//
- // Returns immediately if this thread holds the `Mutex` in at least shared
- // mode (read mode). Otherwise, may report an error (typically by
- // crashing with a diagnostic), or may return immediately.
+ // Require that the mutex be held at least in shared mode (read mode) by this
+ // thread.
+ //
+ // If the mutex is not currently held by this thread, this function may report
+ // an error (typically by crashing with a diagnostic) or it may do nothing.
+ // This function is intended only as a tool to assist debugging; it doesn't
+ // guarantee correctness.
void AssertReaderHeld() const ABSL_ASSERT_SHARED_LOCK();
// Mutex::WriterLock()
@@ -248,7 +275,7 @@
// Aliases for `Mutex::Lock()`, `Mutex::Unlock()`, and `Mutex::TryLock()`.
//
// These methods may be used (along with the complementary `Reader*()`
- // methods) to distingish simple exclusive `Mutex` usage (`Lock()`,
+ // methods) to distinguish simple exclusive `Mutex` usage (`Lock()`,
// etc.) from reader/writer lock usage.
void WriterLock() ABSL_EXCLUSIVE_LOCK_FUNCTION() { this->Lock(); }
@@ -298,7 +325,9 @@
// `true`, `Await()` *may* skip the release/re-acquire step.
//
// `Await()` requires that this thread holds this `Mutex` in some mode.
- void Await(const Condition &cond);
+ void Await(const Condition& cond) {
+ AwaitCommon(cond, synchronization_internal::KernelTimeout::Never());
+ }
// Mutex::LockWhen()
// Mutex::ReaderLockWhen()
@@ -308,11 +337,17 @@
// be acquired, then atomically acquires this `Mutex`. `LockWhen()` is
// logically equivalent to `*Lock(); Await();` though they may have different
// performance characteristics.
- void LockWhen(const Condition &cond) ABSL_EXCLUSIVE_LOCK_FUNCTION();
+ void LockWhen(const Condition& cond) ABSL_EXCLUSIVE_LOCK_FUNCTION() {
+ LockWhenCommon(cond, synchronization_internal::KernelTimeout::Never(),
+ true);
+ }
- void ReaderLockWhen(const Condition &cond) ABSL_SHARED_LOCK_FUNCTION();
+ void ReaderLockWhen(const Condition& cond) ABSL_SHARED_LOCK_FUNCTION() {
+ LockWhenCommon(cond, synchronization_internal::KernelTimeout::Never(),
+ false);
+ }
- void WriterLockWhen(const Condition &cond) ABSL_EXCLUSIVE_LOCK_FUNCTION() {
+ void WriterLockWhen(const Condition& cond) ABSL_EXCLUSIVE_LOCK_FUNCTION() {
this->LockWhen(cond);
}
@@ -337,9 +372,13 @@
// Negative timeouts are equivalent to a zero timeout.
//
// This method requires that this thread holds this `Mutex` in some mode.
- bool AwaitWithTimeout(const Condition &cond, absl::Duration timeout);
+ bool AwaitWithTimeout(const Condition& cond, absl::Duration timeout) {
+ return AwaitCommon(cond, synchronization_internal::KernelTimeout{timeout});
+ }
- bool AwaitWithDeadline(const Condition &cond, absl::Time deadline);
+ bool AwaitWithDeadline(const Condition& cond, absl::Time deadline) {
+ return AwaitCommon(cond, synchronization_internal::KernelTimeout{deadline});
+ }
// Mutex::LockWhenWithTimeout()
// Mutex::ReaderLockWhenWithTimeout()
@@ -352,11 +391,17 @@
// `true` on return.
//
// Negative timeouts are equivalent to a zero timeout.
- bool LockWhenWithTimeout(const Condition &cond, absl::Duration timeout)
- ABSL_EXCLUSIVE_LOCK_FUNCTION();
- bool ReaderLockWhenWithTimeout(const Condition &cond, absl::Duration timeout)
- ABSL_SHARED_LOCK_FUNCTION();
- bool WriterLockWhenWithTimeout(const Condition &cond, absl::Duration timeout)
+ bool LockWhenWithTimeout(const Condition& cond, absl::Duration timeout)
+ ABSL_EXCLUSIVE_LOCK_FUNCTION() {
+ return LockWhenCommon(
+ cond, synchronization_internal::KernelTimeout{timeout}, true);
+ }
+ bool ReaderLockWhenWithTimeout(const Condition& cond, absl::Duration timeout)
+ ABSL_SHARED_LOCK_FUNCTION() {
+ return LockWhenCommon(
+ cond, synchronization_internal::KernelTimeout{timeout}, false);
+ }
+ bool WriterLockWhenWithTimeout(const Condition& cond, absl::Duration timeout)
ABSL_EXCLUSIVE_LOCK_FUNCTION() {
return this->LockWhenWithTimeout(cond, timeout);
}
@@ -372,11 +417,17 @@
// on return.
//
// Deadlines in the past are equivalent to an immediate deadline.
- bool LockWhenWithDeadline(const Condition &cond, absl::Time deadline)
- ABSL_EXCLUSIVE_LOCK_FUNCTION();
- bool ReaderLockWhenWithDeadline(const Condition &cond, absl::Time deadline)
- ABSL_SHARED_LOCK_FUNCTION();
- bool WriterLockWhenWithDeadline(const Condition &cond, absl::Time deadline)
+ bool LockWhenWithDeadline(const Condition& cond, absl::Time deadline)
+ ABSL_EXCLUSIVE_LOCK_FUNCTION() {
+ return LockWhenCommon(
+ cond, synchronization_internal::KernelTimeout{deadline}, true);
+ }
+ bool ReaderLockWhenWithDeadline(const Condition& cond, absl::Time deadline)
+ ABSL_SHARED_LOCK_FUNCTION() {
+ return LockWhenCommon(
+ cond, synchronization_internal::KernelTimeout{deadline}, false);
+ }
+ bool WriterLockWhenWithDeadline(const Condition& cond, absl::Time deadline)
ABSL_EXCLUSIVE_LOCK_FUNCTION() {
return this->LockWhenWithDeadline(cond, deadline);
}
@@ -398,7 +449,7 @@
// substantially reduce `Mutex` performance; it should be set only for
// non-production runs. Optimization options may also disable invariant
// checks.
- void EnableInvariantDebugging(void (*invariant)(void *), void *arg);
+ void EnableInvariantDebugging(void (*invariant)(void*), void* arg);
// Mutex::EnableDebugLog()
//
@@ -407,7 +458,7 @@
// call to `EnableInvariantDebugging()` or `EnableDebugLog()` has been made.
//
// Note: This method substantially reduces `Mutex` performance.
- void EnableDebugLog(const char *name);
+ void EnableDebugLog(const char* name);
// Deadlock detection
@@ -435,7 +486,7 @@
// A `MuHow` is a constant that indicates how a lock should be acquired.
// Internal implementation detail. Clients should ignore.
- typedef const struct MuHowS *MuHow;
+ typedef const struct MuHowS* MuHow;
// Mutex::InternalAttemptToUseMutexInFatalSignalHandler()
//
@@ -457,37 +508,44 @@
// Post()/Wait() versus associated PerThreadSem; in class for required
// friendship with PerThreadSem.
- static void IncrementSynchSem(Mutex *mu, base_internal::PerThreadSynch *w);
- static bool DecrementSynchSem(Mutex *mu, base_internal::PerThreadSynch *w,
+ static void IncrementSynchSem(Mutex* mu, base_internal::PerThreadSynch* w);
+ static bool DecrementSynchSem(Mutex* mu, base_internal::PerThreadSynch* w,
synchronization_internal::KernelTimeout t);
// slow path acquire
- void LockSlowLoop(SynchWaitParams *waitp, int flags);
+ void LockSlowLoop(SynchWaitParams* waitp, int flags);
// wrappers around LockSlowLoop()
- bool LockSlowWithDeadline(MuHow how, const Condition *cond,
+ bool LockSlowWithDeadline(MuHow how, const Condition* cond,
synchronization_internal::KernelTimeout t,
int flags);
- void LockSlow(MuHow how, const Condition *cond,
+ void LockSlow(MuHow how, const Condition* cond,
int flags) ABSL_ATTRIBUTE_COLD;
// slow path release
- void UnlockSlow(SynchWaitParams *waitp) ABSL_ATTRIBUTE_COLD;
+ void UnlockSlow(SynchWaitParams* waitp) ABSL_ATTRIBUTE_COLD;
+ // TryLock slow path.
+ bool TryLockSlow();
+ // ReaderTryLock slow path.
+ bool ReaderTryLockSlow();
// Common code between Await() and AwaitWithTimeout/Deadline()
- bool AwaitCommon(const Condition &cond,
+ bool AwaitCommon(const Condition& cond,
synchronization_internal::KernelTimeout t);
+ bool LockWhenCommon(const Condition& cond,
+ synchronization_internal::KernelTimeout t, bool write);
// Attempt to remove thread s from queue.
- void TryRemove(base_internal::PerThreadSynch *s);
+ void TryRemove(base_internal::PerThreadSynch* s);
// Block a thread on mutex.
- void Block(base_internal::PerThreadSynch *s);
+ void Block(base_internal::PerThreadSynch* s);
// Wake a thread; return successor.
- base_internal::PerThreadSynch *Wakeup(base_internal::PerThreadSynch *w);
+ base_internal::PerThreadSynch* Wakeup(base_internal::PerThreadSynch* w);
+ void Dtor();
friend class CondVar; // for access to Trans()/Fer().
void Trans(MuHow how); // used for CondVar->Mutex transfer
void Fer(
- base_internal::PerThreadSynch *w); // used for CondVar->Mutex transfer
+ base_internal::PerThreadSynch* w); // used for CondVar->Mutex transfer
// Catch the error of writing Mutex when intending MutexLock.
- Mutex(const volatile Mutex * /*ignored*/) {} // NOLINT(runtime/explicit)
+ explicit Mutex(const volatile Mutex* /*ignored*/) {}
Mutex(const Mutex&) = delete;
Mutex& operator=(const Mutex&) = delete;
@@ -522,28 +580,28 @@
// Calls `mu->Lock()` and returns when that call returns. That is, `*mu` is
// guaranteed to be locked when this object is constructed. Requires that
// `mu` be dereferenceable.
- explicit MutexLock(Mutex *mu) ABSL_EXCLUSIVE_LOCK_FUNCTION(mu) : mu_(mu) {
+ explicit MutexLock(Mutex* mu) ABSL_EXCLUSIVE_LOCK_FUNCTION(mu) : mu_(mu) {
this->mu_->Lock();
}
// Like above, but calls `mu->LockWhen(cond)` instead. That is, in addition to
// the above, the condition given by `cond` is also guaranteed to hold when
// this object is constructed.
- explicit MutexLock(Mutex *mu, const Condition &cond)
+ explicit MutexLock(Mutex* mu, const Condition& cond)
ABSL_EXCLUSIVE_LOCK_FUNCTION(mu)
: mu_(mu) {
this->mu_->LockWhen(cond);
}
- MutexLock(const MutexLock &) = delete; // NOLINT(runtime/mutex)
- MutexLock(MutexLock&&) = delete; // NOLINT(runtime/mutex)
+ MutexLock(const MutexLock&) = delete; // NOLINT(runtime/mutex)
+ MutexLock(MutexLock&&) = delete; // NOLINT(runtime/mutex)
MutexLock& operator=(const MutexLock&) = delete;
MutexLock& operator=(MutexLock&&) = delete;
~MutexLock() ABSL_UNLOCK_FUNCTION() { this->mu_->Unlock(); }
private:
- Mutex *const mu_;
+ Mutex* const mu_;
};
// ReaderMutexLock
@@ -552,11 +610,11 @@
// releases a shared lock on a `Mutex` via RAII.
class ABSL_SCOPED_LOCKABLE ReaderMutexLock {
public:
- explicit ReaderMutexLock(Mutex *mu) ABSL_SHARED_LOCK_FUNCTION(mu) : mu_(mu) {
+ explicit ReaderMutexLock(Mutex* mu) ABSL_SHARED_LOCK_FUNCTION(mu) : mu_(mu) {
mu->ReaderLock();
}
- explicit ReaderMutexLock(Mutex *mu, const Condition &cond)
+ explicit ReaderMutexLock(Mutex* mu, const Condition& cond)
ABSL_SHARED_LOCK_FUNCTION(mu)
: mu_(mu) {
mu->ReaderLockWhen(cond);
@@ -570,7 +628,7 @@
~ReaderMutexLock() ABSL_UNLOCK_FUNCTION() { this->mu_->ReaderUnlock(); }
private:
- Mutex *const mu_;
+ Mutex* const mu_;
};
// WriterMutexLock
@@ -579,12 +637,12 @@
// releases a write (exclusive) lock on a `Mutex` via RAII.
class ABSL_SCOPED_LOCKABLE WriterMutexLock {
public:
- explicit WriterMutexLock(Mutex *mu) ABSL_EXCLUSIVE_LOCK_FUNCTION(mu)
+ explicit WriterMutexLock(Mutex* mu) ABSL_EXCLUSIVE_LOCK_FUNCTION(mu)
: mu_(mu) {
mu->WriterLock();
}
- explicit WriterMutexLock(Mutex *mu, const Condition &cond)
+ explicit WriterMutexLock(Mutex* mu, const Condition& cond)
ABSL_EXCLUSIVE_LOCK_FUNCTION(mu)
: mu_(mu) {
mu->WriterLockWhen(cond);
@@ -598,19 +656,19 @@
~WriterMutexLock() ABSL_UNLOCK_FUNCTION() { this->mu_->WriterUnlock(); }
private:
- Mutex *const mu_;
+ Mutex* const mu_;
};
// -----------------------------------------------------------------------------
// Condition
// -----------------------------------------------------------------------------
//
-// As noted above, `Mutex` contains a number of member functions which take a
-// `Condition` as an argument; clients can wait for conditions to become `true`
-// before attempting to acquire the mutex. These sections are known as
-// "condition critical" sections. To use a `Condition`, you simply need to
-// construct it, and use within an appropriate `Mutex` member function;
-// everything else in the `Condition` class is an implementation detail.
+// `Mutex` contains a number of member functions which take a `Condition` as an
+// argument; clients can wait for conditions to become `true` before attempting
+// to acquire the mutex. These sections are known as "condition critical"
+// sections. To use a `Condition`, you simply need to construct it, and use
+// within an appropriate `Mutex` member function; everything else in the
+// `Condition` class is an implementation detail.
//
// A `Condition` is specified as a function pointer which returns a boolean.
// `Condition` functions should be pure functions -- their results should depend
@@ -656,7 +714,7 @@
class Condition {
public:
// A Condition that returns the result of "(*func)(arg)"
- Condition(bool (*func)(void *), void *arg);
+ Condition(bool (*func)(void*), void* arg);
// Templated version for people who are averse to casts.
//
@@ -667,27 +725,43 @@
// Note: lambdas in this case must contain no bound variables.
//
// See class comment for performance advice.
- template<typename T>
- Condition(bool (*func)(T *), T *arg);
+ template <typename T>
+ Condition(bool (*func)(T*), T* arg);
+
+ // Same as above, but allows for cases where `arg` comes from a pointer that
+ // is convertible to the function parameter type `T*` but not an exact match.
+ //
+ // For example, the argument might be `X*` but the function takes `const X*`,
+ // or the argument might be `Derived*` while the function takes `Base*`, and
+ // so on for cases where the argument pointer can be implicitly converted.
+ //
+ // Implementation notes: This constructor overload is required in addition to
+ // the one above to allow deduction of `T` from `arg` for cases such as where
+ // a function template is passed as `func`. Also, the dummy `typename = void`
+ // template parameter exists just to work around a MSVC mangling bug.
+ template <typename T, typename = void>
+ Condition(bool (*func)(T*),
+ typename absl::internal::type_identity<T>::type* arg);
// Templated version for invoking a method that returns a `bool`.
//
// `Condition(object, &Class::Method)` constructs a `Condition` that evaluates
// `object->Method()`.
//
- // Implementation Note: `absl::internal::identity` is used to allow methods to
- // come from base classes. A simpler signature like
+ // Implementation Note: `absl::internal::type_identity` is used to allow
+ // methods to come from base classes. A simpler signature like
// `Condition(T*, bool (T::*)())` does not suffice.
- template<typename T>
- Condition(T *object, bool (absl::internal::identity<T>::type::* method)());
+ template <typename T>
+ Condition(T* object,
+ bool (absl::internal::type_identity<T>::type::*method)());
// Same as above, for const members
- template<typename T>
- Condition(const T *object,
- bool (absl::internal::identity<T>::type::* method)() const);
+ template <typename T>
+ Condition(const T* object,
+ bool (absl::internal::type_identity<T>::type::*method)() const);
// A Condition that returns the value of `*cond`
- explicit Condition(const bool *cond);
+ explicit Condition(const bool* cond);
// Templated version for invoking a functor that returns a `bool`.
// This approach accepts pointers to non-mutable lambdas, `std::function`,
@@ -714,13 +788,23 @@
// Implementation note: The second template parameter ensures that this
// constructor doesn't participate in overload resolution if T doesn't have
// `bool operator() const`.
- template <typename T, typename E = decltype(
- static_cast<bool (T::*)() const>(&T::operator()))>
- explicit Condition(const T *obj)
+ template <typename T, typename E = decltype(static_cast<bool (T::*)() const>(
+ &T::operator()))>
+ explicit Condition(const T* obj)
: Condition(obj, static_cast<bool (T::*)() const>(&T::operator())) {}
// A Condition that always returns `true`.
- static const Condition kTrue;
+ // kTrue is only useful in a narrow set of circumstances, mostly when
+ // it's passed conditionally. For example:
+ //
+ // mu.LockWhen(some_flag ? kTrue : SomeOtherCondition);
+ //
+ // Note: {LockWhen,Await}With{Deadline,Timeout} methods with kTrue condition
+ // don't return immediately when the timeout happens, they still block until
+ // the Mutex becomes available. The return value of these methods does
+ // not indicate if the timeout was reached; rather it indicates whether or
+ // not the condition is true.
+ ABSL_CONST_INIT static const Condition kTrue;
// Evaluates the condition.
bool Eval() const;
@@ -732,25 +816,61 @@
// Two `Condition` values are guaranteed equal if both their `func` and `arg`
// components are the same. A null pointer is equivalent to a `true`
// condition.
- static bool GuaranteedEqual(const Condition *a, const Condition *b);
+ static bool GuaranteedEqual(const Condition* a, const Condition* b);
private:
- typedef bool (*InternalFunctionType)(void * arg);
- typedef bool (Condition::*InternalMethodType)();
- typedef bool (*InternalMethodCallerType)(void * arg,
- InternalMethodType internal_method);
+ // Sizing an allocation for a method pointer can be subtle. In the Itanium
+ // specifications, a method pointer has a predictable, uniform size. On the
+ // other hand, MSVC ABI, method pointer sizes vary based on the
+ // inheritance of the class. Specifically, method pointers from classes with
+ // multiple inheritance are bigger than those of classes with single
+ // inheritance. Other variations also exist.
- bool (*eval_)(const Condition*); // Actual evaluator
- InternalFunctionType function_; // function taking pointer returning bool
- InternalMethodType method_; // method returning bool
- void *arg_; // arg of function_ or object of method_
+#ifndef _MSC_VER
+ // Allocation for a function pointer or method pointer.
+ // The {0} initializer ensures that all unused bytes of this buffer are
+ // always zeroed out. This is necessary, because GuaranteedEqual() compares
+ // all of the bytes, unaware of which bytes are relevant to a given `eval_`.
+ using MethodPtr = bool (Condition::*)();
+ char callback_[sizeof(MethodPtr)] = {0};
+#else
+ // It is well known that the larget MSVC pointer-to-member is 24 bytes. This
+ // may be the largest known pointer-to-member of any platform. For this
+ // reason we will allocate 24 bytes for MSVC platform toolchains.
+ char callback_[24] = {0};
+#endif
- Condition(); // null constructor used only to create kTrue
+ // Function with which to evaluate callbacks and/or arguments.
+ bool (*eval_)(const Condition*) = nullptr;
+
+ // Either an argument for a function call or an object for a method call.
+ void* arg_ = nullptr;
// Various functions eval_ can point to:
static bool CallVoidPtrFunction(const Condition*);
- template <typename T> static bool CastAndCallFunction(const Condition* c);
- template <typename T> static bool CastAndCallMethod(const Condition* c);
+ template <typename T>
+ static bool CastAndCallFunction(const Condition* c);
+ template <typename T, typename ConditionMethodPtr>
+ static bool CastAndCallMethod(const Condition* c);
+
+ // Helper methods for storing, validating, and reading callback arguments.
+ template <typename T>
+ inline void StoreCallback(T callback) {
+ static_assert(
+ sizeof(callback) <= sizeof(callback_),
+ "An overlarge pointer was passed as a callback to Condition.");
+ std::memcpy(callback_, &callback, sizeof(callback));
+ }
+
+ template <typename T>
+ inline void ReadCallback(T* callback) const {
+ std::memcpy(callback, callback_, sizeof(*callback));
+ }
+
+ static bool AlwaysTrue(const Condition*) { return true; }
+
+ // Used only to create kTrue.
+ constexpr Condition() : eval_(AlwaysTrue), arg_(nullptr) {}
};
// -----------------------------------------------------------------------------
@@ -778,9 +898,9 @@
//
// Usage to wake T is:
// mu.Lock();
-// // process data, possibly establishing C
-// if (C) { cv->Signal(); }
-// mu.Unlock();
+// // process data, possibly establishing C
+// if (C) { cv->Signal(); }
+// mu.Unlock();
//
// If C may be useful to more than one waiter, use `SignalAll()` instead of
// `Signal()`.
@@ -793,7 +913,6 @@
// A `CondVar` allocated on the heap or on the stack can use the this
// constructor.
CondVar();
- ~CondVar();
// CondVar::Wait()
//
@@ -802,7 +921,9 @@
// spurious wakeup), then reacquires the `Mutex` and returns.
//
// Requires and ensures that the current thread holds the `Mutex`.
- void Wait(Mutex *mu);
+ void Wait(Mutex* mu) {
+ WaitCommon(mu, synchronization_internal::KernelTimeout::Never());
+ }
// CondVar::WaitWithTimeout()
//
@@ -817,7 +938,9 @@
// to return `true` or `false`.
//
// Requires and ensures that the current thread holds the `Mutex`.
- bool WaitWithTimeout(Mutex *mu, absl::Duration timeout);
+ bool WaitWithTimeout(Mutex* mu, absl::Duration timeout) {
+ return WaitCommon(mu, synchronization_internal::KernelTimeout(timeout));
+ }
// CondVar::WaitWithDeadline()
//
@@ -834,7 +957,9 @@
// to return `true` or `false`.
//
// Requires and ensures that the current thread holds the `Mutex`.
- bool WaitWithDeadline(Mutex *mu, absl::Time deadline);
+ bool WaitWithDeadline(Mutex* mu, absl::Time deadline) {
+ return WaitCommon(mu, synchronization_internal::KernelTimeout(deadline));
+ }
// CondVar::Signal()
//
@@ -851,18 +976,16 @@
// Causes all subsequent uses of this `CondVar` to be logged via
// `ABSL_RAW_LOG(INFO)`. Log entries are tagged with `name` if `name != 0`.
// Note: this method substantially reduces `CondVar` performance.
- void EnableDebugLog(const char *name);
+ void EnableDebugLog(const char* name);
private:
- bool WaitCommon(Mutex *mutex, synchronization_internal::KernelTimeout t);
- void Remove(base_internal::PerThreadSynch *s);
- void Wakeup(base_internal::PerThreadSynch *w);
+ bool WaitCommon(Mutex* mutex, synchronization_internal::KernelTimeout t);
+ void Remove(base_internal::PerThreadSynch* s);
std::atomic<intptr_t> cv_; // Condition variable state.
CondVar(const CondVar&) = delete;
CondVar& operator=(const CondVar&) = delete;
};
-
// Variants of MutexLock.
//
// If you find yourself using one of these, consider instead using
@@ -873,14 +996,14 @@
// MutexLockMaybe is like MutexLock, but is a no-op when mu is null.
class ABSL_SCOPED_LOCKABLE MutexLockMaybe {
public:
- explicit MutexLockMaybe(Mutex *mu) ABSL_EXCLUSIVE_LOCK_FUNCTION(mu)
+ explicit MutexLockMaybe(Mutex* mu) ABSL_EXCLUSIVE_LOCK_FUNCTION(mu)
: mu_(mu) {
if (this->mu_ != nullptr) {
this->mu_->Lock();
}
}
- explicit MutexLockMaybe(Mutex *mu, const Condition &cond)
+ explicit MutexLockMaybe(Mutex* mu, const Condition& cond)
ABSL_EXCLUSIVE_LOCK_FUNCTION(mu)
: mu_(mu) {
if (this->mu_ != nullptr) {
@@ -889,11 +1012,13 @@
}
~MutexLockMaybe() ABSL_UNLOCK_FUNCTION() {
- if (this->mu_ != nullptr) { this->mu_->Unlock(); }
+ if (this->mu_ != nullptr) {
+ this->mu_->Unlock();
+ }
}
private:
- Mutex *const mu_;
+ Mutex* const mu_;
MutexLockMaybe(const MutexLockMaybe&) = delete;
MutexLockMaybe(MutexLockMaybe&&) = delete;
MutexLockMaybe& operator=(const MutexLockMaybe&) = delete;
@@ -906,25 +1031,27 @@
// mutex before destruction. `Release()` may be called at most once.
class ABSL_SCOPED_LOCKABLE ReleasableMutexLock {
public:
- explicit ReleasableMutexLock(Mutex *mu) ABSL_EXCLUSIVE_LOCK_FUNCTION(mu)
+ explicit ReleasableMutexLock(Mutex* mu) ABSL_EXCLUSIVE_LOCK_FUNCTION(mu)
: mu_(mu) {
this->mu_->Lock();
}
- explicit ReleasableMutexLock(Mutex *mu, const Condition &cond)
+ explicit ReleasableMutexLock(Mutex* mu, const Condition& cond)
ABSL_EXCLUSIVE_LOCK_FUNCTION(mu)
: mu_(mu) {
this->mu_->LockWhen(cond);
}
~ReleasableMutexLock() ABSL_UNLOCK_FUNCTION() {
- if (this->mu_ != nullptr) { this->mu_->Unlock(); }
+ if (this->mu_ != nullptr) {
+ this->mu_->Unlock();
+ }
}
void Release() ABSL_UNLOCK_FUNCTION();
private:
- Mutex *mu_;
+ Mutex* mu_;
ReleasableMutexLock(const ReleasableMutexLock&) = delete;
ReleasableMutexLock(ReleasableMutexLock&&) = delete;
ReleasableMutexLock& operator=(const ReleasableMutexLock&) = delete;
@@ -937,61 +1064,87 @@
inline constexpr Mutex::Mutex(absl::ConstInitType) : mu_(0) {}
+#if !defined(__APPLE__) && !defined(ABSL_BUILD_DLL)
+ABSL_ATTRIBUTE_ALWAYS_INLINE
+inline Mutex::~Mutex() { Dtor(); }
+#endif
+
+#if defined(NDEBUG) && !defined(ABSL_HAVE_THREAD_SANITIZER)
+// Use default (empty) destructor in release build for performance reasons.
+// We need to mark both Dtor and ~Mutex as always inline for inconsistent
+// builds that use both NDEBUG and !NDEBUG with dynamic libraries. In these
+// cases we want the empty functions to dissolve entirely rather than being
+// exported from dynamic libraries and potentially override the non-empty ones.
+ABSL_ATTRIBUTE_ALWAYS_INLINE
+inline void Mutex::Dtor() {}
+#endif
+
inline CondVar::CondVar() : cv_(0) {}
// static
-template <typename T>
-bool Condition::CastAndCallMethod(const Condition *c) {
- typedef bool (T::*MemberType)();
- MemberType rm = reinterpret_cast<MemberType>(c->method_);
- T *x = static_cast<T *>(c->arg_);
- return (x->*rm)();
+template <typename T, typename ConditionMethodPtr>
+bool Condition::CastAndCallMethod(const Condition* c) {
+ T* object = static_cast<T*>(c->arg_);
+ ConditionMethodPtr condition_method_pointer;
+ c->ReadCallback(&condition_method_pointer);
+ return (object->*condition_method_pointer)();
}
// static
template <typename T>
-bool Condition::CastAndCallFunction(const Condition *c) {
- typedef bool (*FuncType)(T *);
- FuncType fn = reinterpret_cast<FuncType>(c->function_);
- T *x = static_cast<T *>(c->arg_);
- return (*fn)(x);
+bool Condition::CastAndCallFunction(const Condition* c) {
+ bool (*function)(T*);
+ c->ReadCallback(&function);
+ T* argument = static_cast<T*>(c->arg_);
+ return (*function)(argument);
}
template <typename T>
-inline Condition::Condition(bool (*func)(T *), T *arg)
+inline Condition::Condition(bool (*func)(T*), T* arg)
: eval_(&CastAndCallFunction<T>),
- function_(reinterpret_cast<InternalFunctionType>(func)),
- method_(nullptr),
- arg_(const_cast<void *>(static_cast<const void *>(arg))) {}
+ arg_(const_cast<void*>(static_cast<const void*>(arg))) {
+ static_assert(sizeof(&func) <= sizeof(callback_),
+ "An overlarge function pointer was passed to Condition.");
+ StoreCallback(func);
+}
+
+template <typename T, typename>
+inline Condition::Condition(
+ bool (*func)(T*), typename absl::internal::type_identity<T>::type* arg)
+ // Just delegate to the overload above.
+ : Condition(func, arg) {}
template <typename T>
-inline Condition::Condition(T *object,
- bool (absl::internal::identity<T>::type::*method)())
- : eval_(&CastAndCallMethod<T>),
- function_(nullptr),
- method_(reinterpret_cast<InternalMethodType>(method)),
- arg_(object) {}
+inline Condition::Condition(
+ T* object, bool (absl::internal::type_identity<T>::type::*method)())
+ : eval_(&CastAndCallMethod<T, decltype(method)>), arg_(object) {
+ static_assert(sizeof(&method) <= sizeof(callback_),
+ "An overlarge method pointer was passed to Condition.");
+ StoreCallback(method);
+}
template <typename T>
-inline Condition::Condition(const T *object,
- bool (absl::internal::identity<T>::type::*method)()
- const)
- : eval_(&CastAndCallMethod<T>),
- function_(nullptr),
- method_(reinterpret_cast<InternalMethodType>(method)),
- arg_(reinterpret_cast<void *>(const_cast<T *>(object))) {}
+inline Condition::Condition(
+ const T* object,
+ bool (absl::internal::type_identity<T>::type::*method)() const)
+ : eval_(&CastAndCallMethod<const T, decltype(method)>),
+ arg_(reinterpret_cast<void*>(const_cast<T*>(object))) {
+ StoreCallback(method);
+}
-// Register a hook for profiling support.
+// Register hooks for profiling support.
//
// The function pointer registered here will be called whenever a mutex is
-// contended. The callback is given the absl/base/cycleclock.h timestamp when
-// waiting began.
+// contended. The callback is given the cycles for which waiting happened (as
+// measured by //absl/base/internal/cycleclock.h, and which may not
+// be real "cycle" counts.)
//
-// Calls to this function do not race or block, but there is no ordering
-// guaranteed between calls to this function and call to the provided hook.
-// In particular, the previously registered hook may still be called for some
-// time after this function returns.
-void RegisterMutexProfiler(void (*fn)(int64_t wait_timestamp));
+// There is no ordering guarantee between when the hook is registered and when
+// callbacks will begin. Only a single profiler can be installed in a running
+// binary; if this function is called a second time with a different function
+// pointer, the value is ignored (and will cause an assertion failure in debug
+// mode.)
+void RegisterMutexProfiler(void (*fn)(int64_t wait_cycles));
// Register a hook for Mutex tracing.
//
@@ -1003,13 +1156,11 @@
//
// The only event name currently sent is "slow release".
//
-// This has the same memory ordering concerns as RegisterMutexProfiler() above.
-void RegisterMutexTracer(void (*fn)(const char *msg, const void *obj,
+// This has the same ordering and single-use limitations as
+// RegisterMutexProfiler() above.
+void RegisterMutexTracer(void (*fn)(const char* msg, const void* obj,
int64_t wait_cycles));
-// TODO(gfalcon): Combine RegisterMutexProfiler() and RegisterMutexTracer()
-// into a single interface, since they are only ever called in pairs.
-
// Register a hook for CondVar tracing.
//
// The function pointer registered here will be called here on various CondVar
@@ -1020,24 +1171,9 @@
// Events that can be sent are "Wait", "Unwait", "Signal wakeup", and
// "SignalAll wakeup".
//
-// This has the same memory ordering concerns as RegisterMutexProfiler() above.
-void RegisterCondVarTracer(void (*fn)(const char *msg, const void *cv));
-
-// Register a hook for symbolizing stack traces in deadlock detector reports.
-//
-// 'pc' is the program counter being symbolized, 'out' is the buffer to write
-// into, and 'out_size' is the size of the buffer. This function can return
-// false if symbolizing failed, or true if a NUL-terminated symbol was written
-// to 'out.'
-//
-// This has the same memory ordering concerns as RegisterMutexProfiler() above.
-//
-// DEPRECATED: The default symbolizer function is absl::Symbolize() and the
-// ability to register a different hook for symbolizing stack traces will be
-// removed on or after 2023-05-01.
-ABSL_DEPRECATED("absl::RegisterSymbolizer() is deprecated and will be removed "
- "on or after 2023-05-01")
-void RegisterSymbolizer(bool (*fn)(const void *pc, char *out, int out_size));
+// This has the same ordering and single-use limitations as
+// RegisterMutexProfiler() above.
+void RegisterCondVarTracer(void (*fn)(const char* msg, const void* cv));
// EnableMutexInvariantDebugging()
//
@@ -1054,7 +1190,7 @@
enum class OnDeadlockCycle {
kIgnore, // Neither report on nor attempt to track cycles in lock ordering
kReport, // Report lock cycles to stderr when detected
- kAbort, // Report lock cycles to stderr when detected, then abort
+ kAbort, // Report lock cycles to stderr when detected, then abort
};
// SetMutexDeadlockDetectionMode()
diff --git a/absl/synchronization/mutex_benchmark.cc b/absl/synchronization/mutex_benchmark.cc
index e35aed8..06888df 100644
--- a/absl/synchronization/mutex_benchmark.cc
+++ b/absl/synchronization/mutex_benchmark.cc
@@ -19,6 +19,7 @@
#include "absl/base/config.h"
#include "absl/base/internal/cycleclock.h"
#include "absl/base/internal/spinlock.h"
+#include "absl/base/no_destructor.h"
#include "absl/synchronization/blocking_counter.h"
#include "absl/synchronization/internal/thread_pool.h"
#include "absl/synchronization/mutex.h"
@@ -27,13 +28,41 @@
namespace {
void BM_Mutex(benchmark::State& state) {
- static absl::Mutex* mu = new absl::Mutex;
+ static absl::NoDestructor<absl::Mutex> mu;
for (auto _ : state) {
- absl::MutexLock lock(mu);
+ absl::MutexLock lock(mu.get());
}
}
BENCHMARK(BM_Mutex)->UseRealTime()->Threads(1)->ThreadPerCpu();
+void BM_ReaderLock(benchmark::State& state) {
+ static absl::NoDestructor<absl::Mutex> mu;
+ for (auto _ : state) {
+ absl::ReaderMutexLock lock(mu.get());
+ }
+}
+BENCHMARK(BM_ReaderLock)->UseRealTime()->Threads(1)->ThreadPerCpu();
+
+void BM_TryLock(benchmark::State& state) {
+ absl::Mutex mu;
+ for (auto _ : state) {
+ if (mu.TryLock()) {
+ mu.Unlock();
+ }
+ }
+}
+BENCHMARK(BM_TryLock);
+
+void BM_ReaderTryLock(benchmark::State& state) {
+ static absl::NoDestructor<absl::Mutex> mu;
+ for (auto _ : state) {
+ if (mu->ReaderTryLock()) {
+ mu->ReaderUnlock();
+ }
+ }
+}
+BENCHMARK(BM_ReaderTryLock)->UseRealTime()->Threads(1)->ThreadPerCpu();
+
static void DelayNs(int64_t ns, int* data) {
int64_t end = absl::base_internal::CycleClock::Now() +
ns * absl::base_internal::CycleClock::Frequency() / 1e9;
@@ -97,7 +126,7 @@
// Mutex queueing behavior is modified.
const bool multiple_priorities = state.range(0);
ScopedThreadMutexPriority priority_setter(
- (multiple_priorities && state.thread_index != 0) ? 1 : 0);
+ (multiple_priorities && state.thread_index() != 0) ? 1 : 0);
struct Shared {
absl::Mutex mu;
@@ -105,7 +134,7 @@
std::atomic<int> blocked_threads{0};
std::atomic<bool> thread_has_mutex{false};
};
- static Shared* shared = new Shared;
+ static absl::NoDestructor<Shared> shared;
// Set up 'blocked_threads' to count how many threads are currently blocked
// in Abseil synchronization code.
@@ -176,14 +205,14 @@
template <typename MutexType>
void BM_Contended(benchmark::State& state) {
- int priority = state.thread_index % state.range(1);
+ int priority = state.thread_index() % state.range(1);
ScopedThreadMutexPriority priority_setter(priority);
struct Shared {
MutexType mu;
int data = 0;
};
- static auto* shared = new Shared;
+ static absl::NoDestructor<Shared> shared;
int local = 0;
for (auto _ : state) {
// Here we model both local work outside of the critical section as well as
@@ -196,7 +225,7 @@
// To achieve this amount of local work is multiplied by number of threads
// to keep ratio between local work and critical section approximately
// equal regardless of number of threads.
- DelayNs(100 * state.threads, &local);
+ DelayNs(100 * state.threads(), &local);
RaiiLocker<MutexType> locker(&shared->mu);
DelayNs(state.range(0), &shared->data);
}
diff --git a/absl/synchronization/mutex_method_pointer_test.cc b/absl/synchronization/mutex_method_pointer_test.cc
new file mode 100644
index 0000000..f4c82d2
--- /dev/null
+++ b/absl/synchronization/mutex_method_pointer_test.cc
@@ -0,0 +1,138 @@
+// Copyright 2017 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/synchronization/mutex.h"
+
+#include <cstdlib>
+#include <string>
+
+#include "gtest/gtest.h"
+#include "absl/base/config.h"
+
+namespace {
+
+class IncompleteClass;
+
+#ifdef _MSC_VER
+// These tests verify expectations about sizes of MSVC pointers to methods.
+// Pointers to methods are distinguished by whether their class hierarchies
+// contain single inheritance, multiple inheritance, or virtual inheritance.
+
+// Declare classes of the various MSVC inheritance types.
+class __single_inheritance SingleInheritance{};
+class __multiple_inheritance MultipleInheritance;
+class __virtual_inheritance VirtualInheritance;
+
+TEST(MutexMethodPointerTest, MicrosoftMethodPointerSize) {
+ void (SingleInheritance::*single_inheritance_method_pointer)();
+ void (MultipleInheritance::*multiple_inheritance_method_pointer)();
+ void (VirtualInheritance::*virtual_inheritance_method_pointer)();
+
+#if defined(_M_IX86) || defined(_M_ARM)
+ static_assert(sizeof(single_inheritance_method_pointer) == 4,
+ "Unexpected sizeof(single_inheritance_method_pointer).");
+ static_assert(sizeof(multiple_inheritance_method_pointer) == 8,
+ "Unexpected sizeof(multiple_inheritance_method_pointer).");
+ static_assert(sizeof(virtual_inheritance_method_pointer) == 12,
+ "Unexpected sizeof(virtual_inheritance_method_pointer).");
+#elif defined(_M_X64) || defined(__aarch64__)
+ static_assert(sizeof(single_inheritance_method_pointer) == 8,
+ "Unexpected sizeof(single_inheritance_method_pointer).");
+ static_assert(sizeof(multiple_inheritance_method_pointer) == 16,
+ "Unexpected sizeof(multiple_inheritance_method_pointer).");
+ static_assert(sizeof(virtual_inheritance_method_pointer) == 16,
+ "Unexpected sizeof(virtual_inheritance_method_pointer).");
+#endif
+ void (IncompleteClass::*incomplete_class_method_pointer)();
+ static_assert(sizeof(incomplete_class_method_pointer) >=
+ sizeof(virtual_inheritance_method_pointer),
+ "Failed invariant: sizeof(incomplete_class_method_pointer) >= "
+ "sizeof(virtual_inheritance_method_pointer)!");
+}
+
+class Callback {
+ bool x = true;
+
+ public:
+ Callback() {}
+ bool method() {
+ x = !x;
+ return x;
+ }
+};
+
+class M2 {
+ bool x = true;
+
+ public:
+ M2() {}
+ bool method2() {
+ x = !x;
+ return x;
+ }
+};
+
+class MultipleInheritance : public Callback, public M2 {};
+
+TEST(MutexMethodPointerTest, ConditionWithMultipleInheritanceMethod) {
+ // This test ensures that Condition can deal with method pointers from classes
+ // with multiple inheritance.
+ MultipleInheritance object = MultipleInheritance();
+ absl::Condition condition(&object, &MultipleInheritance::method);
+ EXPECT_FALSE(condition.Eval());
+ EXPECT_TRUE(condition.Eval());
+}
+
+class __virtual_inheritance VirtualInheritance : virtual public Callback {
+ bool x = false;
+
+ public:
+ VirtualInheritance() {}
+ bool method() {
+ x = !x;
+ return x;
+ }
+};
+
+TEST(MutexMethodPointerTest, ConditionWithVirtualInheritanceMethod) {
+ // This test ensures that Condition can deal with method pointers from classes
+ // with virtual inheritance.
+ VirtualInheritance object = VirtualInheritance();
+ absl::Condition condition(&object, &VirtualInheritance::method);
+ EXPECT_TRUE(condition.Eval());
+ EXPECT_FALSE(condition.Eval());
+}
+#endif // #ifdef _MSC_VER
+
+TEST(MutexMethodPointerTest, ConditionWithIncompleteClassMethod) {
+ using IncompleteClassMethodPointer = void (IncompleteClass::*)();
+
+ union CallbackSlot {
+ void (*anonymous_function_pointer)();
+ IncompleteClassMethodPointer incomplete_class_method_pointer;
+ };
+
+ static_assert(sizeof(CallbackSlot) >= sizeof(IncompleteClassMethodPointer),
+ "The callback slot is not big enough for method pointers.");
+ static_assert(
+ sizeof(CallbackSlot) == sizeof(IncompleteClassMethodPointer),
+ "The callback slot is not big enough for anonymous function pointers.");
+
+#if defined(_MSC_VER)
+ static_assert(sizeof(IncompleteClassMethodPointer) <= 24,
+ "The pointer to a method of an incomplete class is too big.");
+#endif
+}
+
+} // namespace
diff --git a/absl/synchronization/mutex_test.cc b/absl/synchronization/mutex_test.cc
index f8fbf94..bb93abc 100644
--- a/absl/synchronization/mutex_test.cc
+++ b/absl/synchronization/mutex_test.cc
@@ -26,18 +26,26 @@
#include <random>
#include <string>
#include <thread> // NOLINT(build/c++11)
+#include <type_traits>
#include <vector>
#include "gtest/gtest.h"
#include "absl/base/attributes.h"
#include "absl/base/config.h"
-#include "absl/base/internal/raw_logging.h"
#include "absl/base/internal/sysinfo.h"
+#include "absl/log/check.h"
+#include "absl/log/log.h"
#include "absl/memory/memory.h"
+#include "absl/synchronization/internal/create_thread_identity.h"
#include "absl/synchronization/internal/thread_pool.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
+#ifdef ABSL_HAVE_PTHREAD_GETSCHEDPARAM
+#include <pthread.h>
+#include <string.h>
+#endif
+
namespace {
// TODO(dmauro): Replace with a commandline flag.
@@ -64,6 +72,11 @@
});
}
+struct ScopedInvariantDebugging {
+ ScopedInvariantDebugging() { absl::EnableMutexInvariantDebugging(true); }
+ ~ScopedInvariantDebugging() { absl::EnableMutexInvariantDebugging(false); }
+};
+
struct TestContext {
int iterations;
int threads;
@@ -86,7 +99,7 @@
static void CheckSumG0G1(void *v) {
TestContext *cxt = static_cast<TestContext *>(v);
- ABSL_RAW_CHECK(cxt->g0 == -cxt->g1, "Error in CheckSumG0G1");
+ CHECK_EQ(cxt->g0, -cxt->g1) << "Error in CheckSumG0G1";
SetInvariantChecked(true);
}
@@ -131,7 +144,7 @@
} else {
for (int i = 0; i != cxt->iterations; i++) {
absl::ReaderMutexLock l(&cxt->mu);
- ABSL_RAW_CHECK(cxt->g0 == -cxt->g1, "Error in TestRW");
+ CHECK_EQ(cxt->g0, -cxt->g1) << "Error in TestRW";
cxt->mu.AssertReaderHeld();
}
}
@@ -156,7 +169,7 @@
cxt->mu.AssertHeld();
while (cxt->g0 < cxt->iterations) {
cxt->mu.Await(absl::Condition(&mc, &MyContext::MyTurn));
- ABSL_RAW_CHECK(mc.MyTurn(), "Error in TestAwait");
+ CHECK(mc.MyTurn()) << "Error in TestAwait";
cxt->mu.AssertHeld();
if (cxt->g0 < cxt->iterations) {
int a = cxt->g0 + 1;
@@ -184,7 +197,7 @@
}
static void TestSignal(TestContext *cxt, int c) {
- ABSL_RAW_CHECK(cxt->threads == 2, "TestSignal should use 2 threads");
+ CHECK_EQ(cxt->threads, 2) << "TestSignal should use 2 threads";
int target = c;
absl::MutexLock l(&cxt->mu);
cxt->mu.AssertHeld();
@@ -221,8 +234,8 @@
static bool G0GE2(TestContext *cxt) { return cxt->g0 >= 2; }
static void TestTime(TestContext *cxt, int c, bool use_cv) {
- ABSL_RAW_CHECK(cxt->iterations == 1, "TestTime should only use 1 iteration");
- ABSL_RAW_CHECK(cxt->threads > 2, "TestTime should use more than 2 threads");
+ CHECK_EQ(cxt->iterations, 1) << "TestTime should only use 1 iteration";
+ CHECK_GT(cxt->threads, 2) << "TestTime should use more than 2 threads";
const bool kFalse = false;
absl::Condition false_cond(&kFalse);
absl::Condition g0ge2(G0GE2, cxt);
@@ -233,26 +246,24 @@
if (use_cv) {
cxt->cv.WaitWithTimeout(&cxt->mu, absl::Seconds(1));
} else {
- ABSL_RAW_CHECK(!cxt->mu.AwaitWithTimeout(false_cond, absl::Seconds(1)),
- "TestTime failed");
+ CHECK(!cxt->mu.AwaitWithTimeout(false_cond, absl::Seconds(1)))
+ << "TestTime failed";
}
absl::Duration elapsed = absl::Now() - start;
- ABSL_RAW_CHECK(
- absl::Seconds(0.9) <= elapsed && elapsed <= absl::Seconds(2.0),
- "TestTime failed");
- ABSL_RAW_CHECK(cxt->g0 == 1, "TestTime failed");
+ CHECK(absl::Seconds(0.9) <= elapsed && elapsed <= absl::Seconds(2.0))
+ << "TestTime failed";
+ CHECK_EQ(cxt->g0, 1) << "TestTime failed";
start = absl::Now();
if (use_cv) {
cxt->cv.WaitWithTimeout(&cxt->mu, absl::Seconds(1));
} else {
- ABSL_RAW_CHECK(!cxt->mu.AwaitWithTimeout(false_cond, absl::Seconds(1)),
- "TestTime failed");
+ CHECK(!cxt->mu.AwaitWithTimeout(false_cond, absl::Seconds(1)))
+ << "TestTime failed";
}
elapsed = absl::Now() - start;
- ABSL_RAW_CHECK(
- absl::Seconds(0.9) <= elapsed && elapsed <= absl::Seconds(2.0),
- "TestTime failed");
+ CHECK(absl::Seconds(0.9) <= elapsed && elapsed <= absl::Seconds(2.0))
+ << "TestTime failed";
cxt->g0++;
if (use_cv) {
cxt->cv.Signal();
@@ -262,26 +273,24 @@
if (use_cv) {
cxt->cv.WaitWithTimeout(&cxt->mu, absl::Seconds(4));
} else {
- ABSL_RAW_CHECK(!cxt->mu.AwaitWithTimeout(false_cond, absl::Seconds(4)),
- "TestTime failed");
+ CHECK(!cxt->mu.AwaitWithTimeout(false_cond, absl::Seconds(4)))
+ << "TestTime failed";
}
elapsed = absl::Now() - start;
- ABSL_RAW_CHECK(
- absl::Seconds(3.9) <= elapsed && elapsed <= absl::Seconds(6.0),
- "TestTime failed");
- ABSL_RAW_CHECK(cxt->g0 >= 3, "TestTime failed");
+ CHECK(absl::Seconds(3.9) <= elapsed && elapsed <= absl::Seconds(6.0))
+ << "TestTime failed";
+ CHECK_GE(cxt->g0, 3) << "TestTime failed";
start = absl::Now();
if (use_cv) {
cxt->cv.WaitWithTimeout(&cxt->mu, absl::Seconds(1));
} else {
- ABSL_RAW_CHECK(!cxt->mu.AwaitWithTimeout(false_cond, absl::Seconds(1)),
- "TestTime failed");
+ CHECK(!cxt->mu.AwaitWithTimeout(false_cond, absl::Seconds(1)))
+ << "TestTime failed";
}
elapsed = absl::Now() - start;
- ABSL_RAW_CHECK(
- absl::Seconds(0.9) <= elapsed && elapsed <= absl::Seconds(2.0),
- "TestTime failed");
+ CHECK(absl::Seconds(0.9) <= elapsed && elapsed <= absl::Seconds(2.0))
+ << "TestTime failed";
if (use_cv) {
cxt->cv.SignalAll();
}
@@ -290,13 +299,13 @@
if (use_cv) {
cxt->cv.WaitWithTimeout(&cxt->mu, absl::Seconds(1));
} else {
- ABSL_RAW_CHECK(!cxt->mu.AwaitWithTimeout(false_cond, absl::Seconds(1)),
- "TestTime failed");
+ CHECK(!cxt->mu.AwaitWithTimeout(false_cond, absl::Seconds(1)))
+ << "TestTime failed";
}
elapsed = absl::Now() - start;
- ABSL_RAW_CHECK(absl::Seconds(0.9) <= elapsed &&
- elapsed <= absl::Seconds(2.0), "TestTime failed");
- ABSL_RAW_CHECK(cxt->g0 == cxt->threads, "TestTime failed");
+ CHECK(absl::Seconds(0.9) <= elapsed && elapsed <= absl::Seconds(2.0))
+ << "TestTime failed";
+ CHECK_EQ(cxt->g0, cxt->threads) << "TestTime failed";
} else if (c == 1) {
absl::MutexLock l(&cxt->mu);
@@ -304,14 +313,12 @@
if (use_cv) {
cxt->cv.WaitWithTimeout(&cxt->mu, absl::Milliseconds(500));
} else {
- ABSL_RAW_CHECK(
- !cxt->mu.AwaitWithTimeout(false_cond, absl::Milliseconds(500)),
- "TestTime failed");
+ CHECK(!cxt->mu.AwaitWithTimeout(false_cond, absl::Milliseconds(500)))
+ << "TestTime failed";
}
const absl::Duration elapsed = absl::Now() - start;
- ABSL_RAW_CHECK(
- absl::Seconds(0.4) <= elapsed && elapsed <= absl::Seconds(0.9),
- "TestTime failed");
+ CHECK(absl::Seconds(0.4) <= elapsed && elapsed <= absl::Seconds(0.9))
+ << "TestTime failed";
cxt->g0++;
} else if (c == 2) {
absl::MutexLock l(&cxt->mu);
@@ -320,8 +327,8 @@
cxt->cv.WaitWithTimeout(&cxt->mu, absl::Seconds(100));
}
} else {
- ABSL_RAW_CHECK(cxt->mu.AwaitWithTimeout(g0ge2, absl::Seconds(100)),
- "TestTime failed");
+ CHECK(cxt->mu.AwaitWithTimeout(g0ge2, absl::Seconds(100)))
+ << "TestTime failed";
}
cxt->g0++;
} else {
@@ -342,7 +349,7 @@
static void TestCVTime(TestContext *cxt, int c) { TestTime(cxt, c, true); }
static void EndTest(int *c0, int *c1, absl::Mutex *mu, absl::CondVar *cv,
- const std::function<void(int)>& cb) {
+ const std::function<void(int)> &cb) {
mu->Lock();
int c = (*c0)++;
mu->Unlock();
@@ -365,9 +372,9 @@
cxt->threads = threads;
absl::synchronization_internal::ThreadPool tp(threads);
for (int i = 0; i != threads; i++) {
- tp.Schedule(std::bind(&EndTest, &c0, &c1, &mu2, &cv2,
- std::function<void(int)>(
- std::bind(test, cxt, std::placeholders::_1))));
+ tp.Schedule(std::bind(
+ &EndTest, &c0, &c1, &mu2, &cv2,
+ std::function<void(int)>(std::bind(test, cxt, std::placeholders::_1))));
}
mu2.Lock();
while (c1 != threads) {
@@ -393,13 +400,12 @@
int threads, int iterations,
int operations,
void (*invariant)(void *)) {
- absl::EnableMutexInvariantDebugging(true);
+ ScopedInvariantDebugging scoped_debugging;
SetInvariantChecked(false);
TestContext cxt;
cxt.mu.EnableInvariantDebugging(invariant, &cxt);
int ret = RunTestCommon(&cxt, test, threads, iterations, operations);
- ABSL_RAW_CHECK(GetInvariantChecked(), "Invariant not checked");
- absl::EnableMutexInvariantDebugging(false); // Restore.
+ CHECK(GetInvariantChecked()) << "Invariant not checked";
return ret;
}
#endif
@@ -681,14 +687,14 @@
bool waiting = false;
};
-static bool LockWhenTestIsCond(LockWhenTestStruct* s) {
+static bool LockWhenTestIsCond(LockWhenTestStruct *s) {
s->mu2.Lock();
s->waiting = true;
s->mu2.Unlock();
return s->cond;
}
-static void LockWhenTestWaitForIsCond(LockWhenTestStruct* s) {
+static void LockWhenTestWaitForIsCond(LockWhenTestStruct *s) {
s->mu1.LockWhen(absl::Condition(&LockWhenTestIsCond, s));
s->mu1.Unlock();
}
@@ -870,31 +876,118 @@
}
}
-// --------------------------------------------------------
-// Test for bug with pattern of readers using a condvar. The bug was that if a
-// reader went to sleep on a condition variable while one or more other readers
-// held the lock, but there were no waiters, the reader count (held in the
-// mutex word) would be lost. (This is because Enqueue() had at one time
-// always placed the thread on the Mutex queue. Later (CL 4075610), to
-// tolerate re-entry into Mutex from a Condition predicate, Enqueue() was
-// changed so that it could also place a thread on a condition-variable. This
-// introduced the case where Enqueue() returned with an empty queue, and this
-// case was handled incorrectly in one place.)
+// Some functions taking pointers to non-const.
+bool Equals42(int *p) { return *p == 42; }
+bool Equals43(int *p) { return *p == 43; }
-static void ReaderForReaderOnCondVar(absl::Mutex *mu, absl::CondVar *cv,
- int *running) {
- std::random_device dev;
- std::mt19937 gen(dev());
- std::uniform_int_distribution<int> random_millis(0, 15);
- mu->ReaderLock();
- while (*running == 3) {
- absl::SleepFor(absl::Milliseconds(random_millis(gen)));
- cv->WaitWithTimeout(mu, absl::Milliseconds(random_millis(gen)));
- }
- mu->ReaderUnlock();
- mu->Lock();
- (*running)--;
- mu->Unlock();
+// Some functions taking pointers to const.
+bool ConstEquals42(const int *p) { return *p == 42; }
+bool ConstEquals43(const int *p) { return *p == 43; }
+
+// Some function templates taking pointers. Note it's possible for `T` to be
+// deduced as non-const or const, which creates the potential for ambiguity,
+// but which the implementation is careful to avoid.
+template <typename T>
+bool TemplateEquals42(T *p) {
+ return *p == 42;
+}
+template <typename T>
+bool TemplateEquals43(T *p) {
+ return *p == 43;
+}
+
+TEST(Mutex, FunctionPointerCondition) {
+ // Some arguments.
+ int x = 42;
+ const int const_x = 42;
+
+ // Parameter non-const, argument non-const.
+ EXPECT_TRUE(absl::Condition(Equals42, &x).Eval());
+ EXPECT_FALSE(absl::Condition(Equals43, &x).Eval());
+
+ // Parameter const, argument non-const.
+ EXPECT_TRUE(absl::Condition(ConstEquals42, &x).Eval());
+ EXPECT_FALSE(absl::Condition(ConstEquals43, &x).Eval());
+
+ // Parameter const, argument const.
+ EXPECT_TRUE(absl::Condition(ConstEquals42, &const_x).Eval());
+ EXPECT_FALSE(absl::Condition(ConstEquals43, &const_x).Eval());
+
+ // Parameter type deduced, argument non-const.
+ EXPECT_TRUE(absl::Condition(TemplateEquals42, &x).Eval());
+ EXPECT_FALSE(absl::Condition(TemplateEquals43, &x).Eval());
+
+ // Parameter type deduced, argument const.
+ EXPECT_TRUE(absl::Condition(TemplateEquals42, &const_x).Eval());
+ EXPECT_FALSE(absl::Condition(TemplateEquals43, &const_x).Eval());
+
+ // Parameter non-const, argument const is not well-formed.
+ EXPECT_FALSE((std::is_constructible<absl::Condition, decltype(Equals42),
+ decltype(&const_x)>::value));
+ // Validate use of is_constructible by contrasting to a well-formed case.
+ EXPECT_TRUE((std::is_constructible<absl::Condition, decltype(ConstEquals42),
+ decltype(&const_x)>::value));
+}
+
+// Example base and derived class for use in predicates and test below. Not a
+// particularly realistic example, but it suffices for testing purposes.
+struct Base {
+ explicit Base(int v) : value(v) {}
+ int value;
+};
+struct Derived : Base {
+ explicit Derived(int v) : Base(v) {}
+};
+
+// Some functions taking pointer to non-const `Base`.
+bool BaseEquals42(Base *p) { return p->value == 42; }
+bool BaseEquals43(Base *p) { return p->value == 43; }
+
+// Some functions taking pointer to const `Base`.
+bool ConstBaseEquals42(const Base *p) { return p->value == 42; }
+bool ConstBaseEquals43(const Base *p) { return p->value == 43; }
+
+TEST(Mutex, FunctionPointerConditionWithDerivedToBaseConversion) {
+ // Some arguments.
+ Derived derived(42);
+ const Derived const_derived(42);
+
+ // Parameter non-const base, argument derived non-const.
+ EXPECT_TRUE(absl::Condition(BaseEquals42, &derived).Eval());
+ EXPECT_FALSE(absl::Condition(BaseEquals43, &derived).Eval());
+
+ // Parameter const base, argument derived non-const.
+ EXPECT_TRUE(absl::Condition(ConstBaseEquals42, &derived).Eval());
+ EXPECT_FALSE(absl::Condition(ConstBaseEquals43, &derived).Eval());
+
+ // Parameter const base, argument derived const.
+ EXPECT_TRUE(absl::Condition(ConstBaseEquals42, &const_derived).Eval());
+ EXPECT_FALSE(absl::Condition(ConstBaseEquals43, &const_derived).Eval());
+
+ // Parameter const base, argument derived const.
+ EXPECT_TRUE(absl::Condition(ConstBaseEquals42, &const_derived).Eval());
+ EXPECT_FALSE(absl::Condition(ConstBaseEquals43, &const_derived).Eval());
+
+ // Parameter derived, argument base is not well-formed.
+ bool (*derived_pred)(const Derived *) = [](const Derived *) { return true; };
+ EXPECT_FALSE((std::is_constructible<absl::Condition, decltype(derived_pred),
+ Base *>::value));
+ EXPECT_FALSE((std::is_constructible<absl::Condition, decltype(derived_pred),
+ const Base *>::value));
+ // Validate use of is_constructible by contrasting to well-formed cases.
+ EXPECT_TRUE((std::is_constructible<absl::Condition, decltype(derived_pred),
+ Derived *>::value));
+ EXPECT_TRUE((std::is_constructible<absl::Condition, decltype(derived_pred),
+ const Derived *>::value));
+}
+
+struct Constable {
+ bool WotsAllThisThen() const { return true; }
+};
+
+TEST(Mutex, FunctionPointerConditionWithConstMethod) {
+ const Constable chapman;
+ EXPECT_TRUE(absl::Condition(&chapman, &Constable::WotsAllThisThen).Eval());
}
struct True {
@@ -945,6 +1038,46 @@
}
}
+TEST(Mutex, ConditionSwap) {
+ // Ensure that Conditions can be swap'ed.
+ bool b1 = true;
+ absl::Condition c1(&b1);
+ bool b2 = false;
+ absl::Condition c2(&b2);
+ EXPECT_TRUE(c1.Eval());
+ EXPECT_FALSE(c2.Eval());
+ std::swap(c1, c2);
+ EXPECT_FALSE(c1.Eval());
+ EXPECT_TRUE(c2.Eval());
+}
+
+// --------------------------------------------------------
+// Test for bug with pattern of readers using a condvar. The bug was that if a
+// reader went to sleep on a condition variable while one or more other readers
+// held the lock, but there were no waiters, the reader count (held in the
+// mutex word) would be lost. (This is because Enqueue() had at one time
+// always placed the thread on the Mutex queue. Later (CL 4075610), to
+// tolerate re-entry into Mutex from a Condition predicate, Enqueue() was
+// changed so that it could also place a thread on a condition-variable. This
+// introduced the case where Enqueue() returned with an empty queue, and this
+// case was handled incorrectly in one place.)
+
+static void ReaderForReaderOnCondVar(absl::Mutex *mu, absl::CondVar *cv,
+ int *running) {
+ std::random_device dev;
+ std::mt19937 gen(dev());
+ std::uniform_int_distribution<int> random_millis(0, 15);
+ mu->ReaderLock();
+ while (*running == 3) {
+ absl::SleepFor(absl::Milliseconds(random_millis(gen)));
+ cv->WaitWithTimeout(mu, absl::Milliseconds(random_millis(gen)));
+ }
+ mu->ReaderUnlock();
+ mu->Lock();
+ (*running)--;
+ mu->Unlock();
+}
+
static bool IntIsZero(int *x) { return *x == 0; }
// Test for reader waiting condition variable when there are other readers
@@ -986,7 +1119,7 @@
absl::Milliseconds(100));
x->mu1.Unlock();
}
- ABSL_RAW_CHECK(x->value < 4, "should not be invoked a fourth time");
+ CHECK_LT(x->value, 4) << "should not be invoked a fourth time";
// We arrange for the condition to return true on only the 2nd and 3rd calls.
return x->value == 2 || x->value == 3;
@@ -1129,6 +1262,25 @@
absl::SetMutexDeadlockDetectionMode(absl::OnDeadlockCycle::kAbort);
}
+TEST(Mutex, DeadlockDetectorLongCycle) {
+ absl::SetMutexDeadlockDetectionMode(absl::OnDeadlockCycle::kReport);
+
+ // This test generates a warning if it passes, and crashes otherwise.
+ // Cause bazel to ignore the warning.
+ ScopedDisableBazelTestWarnings disable_bazel_test_warnings;
+
+ // Check that we survive a deadlock with a lock cycle.
+ std::vector<absl::Mutex> mutex(100);
+ for (size_t i = 0; i != mutex.size(); i++) {
+ mutex[i].Lock();
+ mutex[(i + 1) % mutex.size()].Lock();
+ mutex[i].Unlock();
+ mutex[(i + 1) % mutex.size()].Unlock();
+ }
+
+ absl::SetMutexDeadlockDetectionMode(absl::OnDeadlockCycle::kAbort);
+}
+
// This test is tagged with NO_THREAD_SAFETY_ANALYSIS because the
// annotation-based static thread-safety analysis is not currently
// predicate-aware and cannot tell if the two for-loops that acquire and
@@ -1214,11 +1366,9 @@
// different clock than absl::Now(), but these cases should be handled by the
// the retry mechanism in each TimeoutTest.
if (actual_delay < expected_delay) {
- ABSL_RAW_LOG(WARNING,
- "Actual delay %s was too short, expected %s (difference %s)",
- absl::FormatDuration(actual_delay).c_str(),
- absl::FormatDuration(expected_delay).c_str(),
- absl::FormatDuration(actual_delay - expected_delay).c_str());
+ LOG(WARNING) << "Actual delay " << actual_delay
+ << " was too short, expected " << expected_delay
+ << " (difference " << actual_delay - expected_delay << ")";
pass = false;
}
// If the expected delay is <= zero then allow a small error tolerance, since
@@ -1229,11 +1379,9 @@
? absl::Milliseconds(10)
: TimeoutTestAllowedSchedulingDelay();
if (actual_delay > expected_delay + tolerance) {
- ABSL_RAW_LOG(WARNING,
- "Actual delay %s was too long, expected %s (difference %s)",
- absl::FormatDuration(actual_delay).c_str(),
- absl::FormatDuration(expected_delay).c_str(),
- absl::FormatDuration(actual_delay - expected_delay).c_str());
+ LOG(WARNING) << "Actual delay " << actual_delay
+ << " was too long, expected " << expected_delay
+ << " (difference " << actual_delay - expected_delay << ")";
pass = false;
}
return pass;
@@ -1283,12 +1431,6 @@
<< " expected_delay: " << param.expected_delay;
}
-std::string FormatString(const TimeoutTestParam ¶m) {
- std::ostringstream os;
- os << param;
- return os.str();
-}
-
// Like `thread::Executor::ScheduleAt` except:
// a) Delays zero or negative are executed immediately in the current thread.
// b) Infinite delays are never scheduled.
@@ -1418,13 +1560,13 @@
TEST_P(TimeoutTest, Await) {
const TimeoutTestParam params = GetParam();
- ABSL_RAW_LOG(INFO, "Params: %s", FormatString(params).c_str());
+ LOG(INFO) << "Params: " << params;
// Because this test asserts bounds on scheduling delays it is flaky. To
// compensate it loops forever until it passes. Failures express as test
// timeouts, in which case the test log can be used to diagnose the issue.
for (int attempt = 1;; ++attempt) {
- ABSL_RAW_LOG(INFO, "Attempt %d", attempt);
+ LOG(INFO) << "Attempt " << attempt;
absl::Mutex mu;
bool value = false; // condition value (under mu)
@@ -1452,13 +1594,13 @@
TEST_P(TimeoutTest, LockWhen) {
const TimeoutTestParam params = GetParam();
- ABSL_RAW_LOG(INFO, "Params: %s", FormatString(params).c_str());
+ LOG(INFO) << "Params: " << params;
// Because this test asserts bounds on scheduling delays it is flaky. To
// compensate it loops forever until it passes. Failures express as test
// timeouts, in which case the test log can be used to diagnose the issue.
for (int attempt = 1;; ++attempt) {
- ABSL_RAW_LOG(INFO, "Attempt %d", attempt);
+ LOG(INFO) << "Attempt " << attempt;
absl::Mutex mu;
bool value = false; // condition value (under mu)
@@ -1487,13 +1629,13 @@
TEST_P(TimeoutTest, ReaderLockWhen) {
const TimeoutTestParam params = GetParam();
- ABSL_RAW_LOG(INFO, "Params: %s", FormatString(params).c_str());
+ LOG(INFO) << "Params: " << params;
// Because this test asserts bounds on scheduling delays it is flaky. To
// compensate it loops forever until it passes. Failures express as test
// timeouts, in which case the test log can be used to diagnose the issue.
for (int attempt = 0;; ++attempt) {
- ABSL_RAW_LOG(INFO, "Attempt %d", attempt);
+ LOG(INFO) << "Attempt " << attempt;
absl::Mutex mu;
bool value = false; // condition value (under mu)
@@ -1523,13 +1665,13 @@
TEST_P(TimeoutTest, Wait) {
const TimeoutTestParam params = GetParam();
- ABSL_RAW_LOG(INFO, "Params: %s", FormatString(params).c_str());
+ LOG(INFO) << "Params: " << params;
// Because this test asserts bounds on scheduling delays it is flaky. To
// compensate it loops forever until it passes. Failures express as test
// timeouts, in which case the test log can be used to diagnose the issue.
for (int attempt = 0;; ++attempt) {
- ABSL_RAW_LOG(INFO, "Attempt %d", attempt);
+ LOG(INFO) << "Attempt " << attempt;
absl::Mutex mu;
bool value = false; // condition value (under mu)
@@ -1580,6 +1722,61 @@
logged_cv.SignalAll();
}
+TEST(Mutex, LoggingAddressReuse) {
+ // Repeatedly re-create a Mutex with debug logging at the same address.
+ ScopedInvariantDebugging scoped_debugging;
+ alignas(absl::Mutex) char storage[sizeof(absl::Mutex)];
+ auto invariant =
+ +[](void *alive) { EXPECT_TRUE(*static_cast<bool *>(alive)); };
+ constexpr size_t kIters = 10;
+ bool alive[kIters] = {};
+ for (size_t i = 0; i < kIters; ++i) {
+ absl::Mutex *mu = new (storage) absl::Mutex;
+ alive[i] = true;
+ mu->EnableDebugLog("Mutex");
+ mu->EnableInvariantDebugging(invariant, &alive[i]);
+ mu->Lock();
+ mu->Unlock();
+ mu->~Mutex();
+ alive[i] = false;
+ }
+}
+
+TEST(Mutex, LoggingBankrupcy) {
+ // Test the case with too many live Mutexes with debug logging.
+ ScopedInvariantDebugging scoped_debugging;
+ std::vector<absl::Mutex> mus(1 << 20);
+ for (auto &mu : mus) {
+ mu.EnableDebugLog("Mutex");
+ }
+}
+
+TEST(Mutex, SynchEventRace) {
+ // Regression test for a false TSan race report in
+ // EnableInvariantDebugging/EnableDebugLog related to SynchEvent reuse.
+ ScopedInvariantDebugging scoped_debugging;
+ std::vector<std::thread> threads;
+ for (size_t i = 0; i < 5; i++) {
+ threads.emplace_back([&] {
+ for (size_t j = 0; j < (1 << 17); j++) {
+ {
+ absl::Mutex mu;
+ mu.EnableInvariantDebugging([](void *) {}, nullptr);
+ mu.Lock();
+ mu.Unlock();
+ }
+ {
+ absl::Mutex mu;
+ mu.EnableDebugLog("Mutex");
+ }
+ }
+ });
+ }
+ for (auto &thread : threads) {
+ thread.join();
+ }
+}
+
// --------------------------------------------------------
// Generate the vector of thread counts for tests parameterized on thread count.
@@ -1693,8 +1890,7 @@
TEST(Mutex, CVTime) {
int threads = 10; // Use a fixed thread count of 10
int iterations = 1;
- EXPECT_EQ(RunTest(&TestCVTime, threads, iterations, 1),
- threads * iterations);
+ EXPECT_EQ(RunTest(&TestCVTime, threads, iterations, 1), threads * iterations);
}
TEST(Mutex, MuTime) {
@@ -1703,4 +1899,139 @@
EXPECT_EQ(RunTest(&TestMuTime, threads, iterations, 1), threads * iterations);
}
+TEST(Mutex, SignalExitedThread) {
+ // 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
+#else
+ constexpr int kThreads = 100;
+#endif
+ std::vector<std::thread> top;
+ for (unsigned i = 0; i < 2 * std::thread::hardware_concurrency(); i++) {
+ top.emplace_back([&]() {
+ for (int i = 0; i < kThreads; i++) {
+ absl::Mutex mu;
+ std::thread t([&]() {
+ mu.Lock();
+ mu.Unlock();
+ });
+ mu.Lock();
+ mu.Unlock();
+ t.join();
+ }
+ });
+ }
+ for (auto &th : top) th.join();
+}
+
+TEST(Mutex, WriterPriority) {
+ absl::Mutex mu;
+ bool wrote = false;
+ std::atomic<bool> saw_wrote{false};
+ auto readfunc = [&]() {
+ for (size_t i = 0; i < 10; ++i) {
+ absl::ReaderMutexLock lock(&mu);
+ if (wrote) {
+ saw_wrote = true;
+ break;
+ }
+ absl::SleepFor(absl::Seconds(1));
+ }
+ };
+ std::thread t1(readfunc);
+ absl::SleepFor(absl::Milliseconds(500));
+ std::thread t2(readfunc);
+ // Note: this test guards against a bug that was related to an uninit
+ // PerThreadSynch::priority, so the writer intentionally runs on a new thread.
+ std::thread t3([&]() {
+ // The writer should be able squeeze between the two alternating readers.
+ absl::MutexLock lock(&mu);
+ wrote = true;
+ });
+ t1.join();
+ t2.join();
+ t3.join();
+ EXPECT_TRUE(saw_wrote.load());
+}
+
+#ifdef ABSL_HAVE_PTHREAD_GETSCHEDPARAM
+TEST(Mutex, CondVarPriority) {
+ // A regression test for a bug in condition variable wait morphing,
+ // which resulted in the waiting thread getting priority of the waking thread.
+ int err = 0;
+ sched_param param;
+ param.sched_priority = 7;
+ std::thread test([&]() {
+ err = pthread_setschedparam(pthread_self(), SCHED_FIFO, ¶m);
+ });
+ test.join();
+ if (err) {
+ // Setting priority usually requires special privileges.
+ GTEST_SKIP() << "failed to set priority: " << strerror(err);
+ }
+ absl::Mutex mu;
+ absl::CondVar cv;
+ bool locked = false;
+ bool notified = false;
+ bool waiting = false;
+ bool morph = false;
+ std::thread th([&]() {
+ EXPECT_EQ(0, pthread_setschedparam(pthread_self(), SCHED_FIFO, ¶m));
+ mu.Lock();
+ locked = true;
+ mu.Await(absl::Condition(¬ified));
+ mu.Unlock();
+ EXPECT_EQ(absl::synchronization_internal::GetOrCreateCurrentThreadIdentity()
+ ->per_thread_synch.priority,
+ param.sched_priority);
+ mu.Lock();
+ mu.Await(absl::Condition(&waiting));
+ morph = true;
+ absl::SleepFor(absl::Seconds(1));
+ cv.Signal();
+ mu.Unlock();
+ });
+ mu.Lock();
+ mu.Await(absl::Condition(&locked));
+ notified = true;
+ mu.Unlock();
+ mu.Lock();
+ waiting = true;
+ while (!morph) {
+ cv.Wait(&mu);
+ }
+ mu.Unlock();
+ th.join();
+ EXPECT_NE(absl::synchronization_internal::GetOrCreateCurrentThreadIdentity()
+ ->per_thread_synch.priority,
+ param.sched_priority);
+}
+#endif
+
+TEST(Mutex, LockWhenWithTimeoutResult) {
+ // Check various corner cases for Await/LockWhen return value
+ // with always true/always false conditions.
+ absl::Mutex mu;
+ const bool kAlwaysTrue = true, kAlwaysFalse = false;
+ const absl::Condition kTrueCond(&kAlwaysTrue), kFalseCond(&kAlwaysFalse);
+ EXPECT_TRUE(mu.LockWhenWithTimeout(kTrueCond, absl::Milliseconds(1)));
+ mu.Unlock();
+ EXPECT_FALSE(mu.LockWhenWithTimeout(kFalseCond, absl::Milliseconds(1)));
+ EXPECT_TRUE(mu.AwaitWithTimeout(kTrueCond, absl::Milliseconds(1)));
+ EXPECT_FALSE(mu.AwaitWithTimeout(kFalseCond, absl::Milliseconds(1)));
+ std::thread th1([&]() {
+ EXPECT_TRUE(mu.LockWhenWithTimeout(kTrueCond, absl::Milliseconds(1)));
+ mu.Unlock();
+ });
+ std::thread th2([&]() {
+ EXPECT_FALSE(mu.LockWhenWithTimeout(kFalseCond, absl::Milliseconds(1)));
+ mu.Unlock();
+ });
+ absl::SleepFor(absl::Milliseconds(100));
+ mu.Unlock();
+ th1.join();
+ th2.join();
+}
+
} // namespace
diff --git a/absl/synchronization/notification.cc b/absl/synchronization/notification.cc
index e91b903..165ba66 100644
--- a/absl/synchronization/notification.cc
+++ b/absl/synchronization/notification.cc
@@ -16,7 +16,6 @@
#include <atomic>
-#include "absl/base/attributes.h"
#include "absl/base/internal/raw_logging.h"
#include "absl/synchronization/mutex.h"
#include "absl/time/time.h"
diff --git a/absl/synchronization/notification.h b/absl/synchronization/notification.h
index 9a354ca..8986d9a 100644
--- a/absl/synchronization/notification.h
+++ b/absl/synchronization/notification.h
@@ -22,7 +22,7 @@
// The `Notification` object maintains a private boolean "notified" state that
// transitions to `true` at most once. The `Notification` class provides the
// following primary member functions:
-// * `HasBeenNotified() `to query its state
+// * `HasBeenNotified()` to query its state
// * `WaitForNotification*()` to have threads wait until the "notified" state
// is `true`.
// * `Notify()` to set the notification's "notified" state to `true` and
@@ -52,7 +52,7 @@
#include <atomic>
-#include "absl/base/macros.h"
+#include "absl/base/attributes.h"
#include "absl/synchronization/mutex.h"
#include "absl/time/time.h"
@@ -74,7 +74,7 @@
// Notification::HasBeenNotified()
//
// Returns the value of the notification's internal "notified" state.
- bool HasBeenNotified() const {
+ ABSL_MUST_USE_RESULT bool HasBeenNotified() const {
return HasBeenNotifiedInternal(&this->notified_yet_);
}
diff --git a/absl/synchronization/notification_test.cc b/absl/synchronization/notification_test.cc
index 100ea76..49ce61a 100644
--- a/absl/synchronization/notification_test.cc
+++ b/absl/synchronization/notification_test.cc
@@ -79,7 +79,7 @@
// Allow for a slight early return, to account for quality of implementation
// issues on various platforms.
- const absl::Duration slop = absl::Microseconds(200);
+ const absl::Duration slop = absl::Milliseconds(5);
EXPECT_LE(delay - slop, elapsed)
<< "WaitForNotificationWithTimeout returned " << delay - elapsed
<< " early (with " << slop << " slop), start time was " << start;
diff --git a/absl/time/BUILD.bazel b/absl/time/BUILD.bazel
index 3e25ca2..05f1f2f 100644
--- a/absl/time/BUILD.bazel
+++ b/absl/time/BUILD.bazel
@@ -14,7 +14,6 @@
# limitations under the License.
#
-load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test")
load(
"//absl:copts/configure_copts.bzl",
"ABSL_DEFAULT_COPTS",
@@ -22,7 +21,14 @@
"ABSL_TEST_COPTS",
)
-package(default_visibility = ["//visibility:public"])
+package(
+ default_visibility = ["//visibility:public"],
+ features = [
+ "header_modules",
+ "layering_check",
+ "parse_headers",
+ ],
+)
licenses(["notice"])
@@ -46,34 +52,29 @@
linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
"//absl/base",
+ "//absl/base:config",
"//absl/base:core_headers",
"//absl/base:raw_logging_internal",
"//absl/numeric:int128",
"//absl/strings",
"//absl/time/internal/cctz:civil_time",
"//absl/time/internal/cctz:time_zone",
+ "//absl/types:optional",
],
)
cc_library(
name = "test_util",
- testonly = 1,
- srcs = [
- "internal/test_util.cc",
- "internal/zoneinfo.inc",
- ],
+ testonly = True,
+ srcs = ["internal/test_util.cc"],
hdrs = ["internal/test_util.h"],
copts = ABSL_DEFAULT_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
- visibility = [
- "//absl/time:__pkg__",
- ],
+ visibility = ["//visibility:private"],
deps = [
":time",
"//absl/base:config",
"//absl/base:raw_logging_internal",
- "//absl/time/internal/cctz:time_zone",
- "@com_google_googletest//:gtest",
],
)
@@ -88,6 +89,8 @@
"time_zone_test.cc",
],
copts = ABSL_TEST_COPTS,
+ data = ["//absl/time/internal/cctz:zoneinfo"],
+ env = {"TZDIR": "absl/time/internal/cctz/testdata/zoneinfo"},
linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
":test_util",
@@ -95,7 +98,34 @@
"//absl/base:config",
"//absl/base:core_headers",
"//absl/numeric:int128",
+ "//absl/strings:str_format",
"//absl/time/internal/cctz:time_zone",
+ "@com_google_googletest//:gtest",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "flag_test",
+ srcs = [
+ "flag_test.cc",
+ ],
+ copts = ABSL_TEST_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ tags = [
+ "no_test_android_arm",
+ "no_test_android_arm64",
+ "no_test_android_x86",
+ "no_test_ios_x86_64",
+ "no_test_lexan",
+ "no_test_loonix",
+ "no_test_wasm",
+ ],
+ deps = [
+ ":time",
+ "//absl/flags:flag",
+ "//absl/flags:reflection",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -110,6 +140,8 @@
"time_benchmark.cc",
],
copts = ABSL_TEST_COPTS,
+ data = ["//absl/time/internal/cctz:zoneinfo"],
+ env = {"TZDIR": "absl/time/internal/cctz/testdata/zoneinfo"},
linkopts = ABSL_DEFAULT_LINKOPTS,
tags = [
"benchmark",
diff --git a/absl/time/CMakeLists.txt b/absl/time/CMakeLists.txt
index f6ff8bd..e1ade7a 100644
--- a/absl/time/CMakeLists.txt
+++ b/absl/time/CMakeLists.txt
@@ -54,10 +54,6 @@
${ABSL_DEFAULT_COPTS}
)
-if(APPLE)
- find_library(CoreFoundation CoreFoundation)
-endif()
-
absl_cc_library(
NAME
time_zone
@@ -84,9 +80,13 @@
COPTS
${ABSL_DEFAULT_COPTS}
DEPS
- $<$<PLATFORM_ID:Darwin>:${CoreFoundation}>
+ Threads::Threads
+ # TODO(#1495): Use $<LINK_LIBRARY:FRAMEWORK,CoreFoundation> once our
+ # minimum CMake version >= 3.24
+ $<$<PLATFORM_ID:Darwin>:-Wl,-framework,CoreFoundation>
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
time_internal_test_util
@@ -94,7 +94,6 @@
"internal/test_util.h"
SRCS
"internal/test_util.cc"
- "internal/zoneinfo.inc"
COPTS
${ABSL_DEFAULT_COPTS}
DEPS
@@ -102,7 +101,6 @@
absl::config
absl::raw_logging_internal
absl::time_zone
- GTest::gmock
TESTONLY
)
@@ -123,6 +121,21 @@
absl::time
absl::config
absl::core_headers
+ absl::strings
+ absl::str_format
absl::time_zone
GTest::gmock_main
)
+
+absl_cc_test(
+ NAME
+ flag_test
+ SRCS
+ "flag_test.cc"
+ COPTS
+ ${ABSL_TEST_COPTS}
+ DEPS
+ absl::flags
+ absl::flags_reflection
+ GTest::gmock_main
+)
diff --git a/absl/time/civil_time.cc b/absl/time/civil_time.cc
index 6a231ed..65df39d 100644
--- a/absl/time/civil_time.cc
+++ b/absl/time/civil_time.cc
@@ -15,6 +15,7 @@
#include "absl/time/civil_time.h"
#include <cstdlib>
+#include <ostream>
#include <string>
#include "absl/strings/str_cat.h"
@@ -167,6 +168,31 @@
return os << FormatCivilTime(s);
}
+bool AbslParseFlag(string_view s, CivilSecond* c, std::string*) {
+ return ParseLenientCivilTime(s, c);
+}
+bool AbslParseFlag(string_view s, CivilMinute* c, std::string*) {
+ return ParseLenientCivilTime(s, c);
+}
+bool AbslParseFlag(string_view s, CivilHour* c, std::string*) {
+ return ParseLenientCivilTime(s, c);
+}
+bool AbslParseFlag(string_view s, CivilDay* c, std::string*) {
+ return ParseLenientCivilTime(s, c);
+}
+bool AbslParseFlag(string_view s, CivilMonth* c, std::string*) {
+ return ParseLenientCivilTime(s, c);
+}
+bool AbslParseFlag(string_view s, CivilYear* c, std::string*) {
+ return ParseLenientCivilTime(s, c);
+}
+std::string AbslUnparseFlag(CivilSecond c) { return FormatCivilTime(c); }
+std::string AbslUnparseFlag(CivilMinute c) { return FormatCivilTime(c); }
+std::string AbslUnparseFlag(CivilHour c) { return FormatCivilTime(c); }
+std::string AbslUnparseFlag(CivilDay c) { return FormatCivilTime(c); }
+std::string AbslUnparseFlag(CivilMonth c) { return FormatCivilTime(c); }
+std::string AbslUnparseFlag(CivilYear c) { return FormatCivilTime(c); }
+
} // namespace time_internal
ABSL_NAMESPACE_END
diff --git a/absl/time/civil_time.h b/absl/time/civil_time.h
index bb46004..3e904a1 100644
--- a/absl/time/civil_time.h
+++ b/absl/time/civil_time.h
@@ -70,8 +70,10 @@
#ifndef ABSL_TIME_CIVIL_TIME_H_
#define ABSL_TIME_CIVIL_TIME_H_
+#include <iosfwd>
#include <string>
+#include "absl/base/config.h"
#include "absl/strings/string_view.h"
#include "absl/time/internal/cctz/include/cctz/civil_time.h"
@@ -460,6 +462,32 @@
std::string FormatCivilTime(CivilMonth c);
std::string FormatCivilTime(CivilYear c);
+// Support for StrFormat(), StrCat(), etc
+template <typename Sink>
+void AbslStringify(Sink& sink, CivilSecond c) {
+ sink.Append(FormatCivilTime(c));
+}
+template <typename Sink>
+void AbslStringify(Sink& sink, CivilMinute c) {
+ sink.Append(FormatCivilTime(c));
+}
+template <typename Sink>
+void AbslStringify(Sink& sink, CivilHour c) {
+ sink.Append(FormatCivilTime(c));
+}
+template <typename Sink>
+void AbslStringify(Sink& sink, CivilDay c) {
+ sink.Append(FormatCivilTime(c));
+}
+template <typename Sink>
+void AbslStringify(Sink& sink, CivilMonth c) {
+ sink.Append(FormatCivilTime(c));
+}
+template <typename Sink>
+void AbslStringify(Sink& sink, CivilYear c) {
+ sink.Append(FormatCivilTime(c));
+}
+
// absl::ParseCivilTime()
//
// Parses a civil-time value from the specified `absl::string_view` into the
@@ -530,6 +558,29 @@
std::ostream& operator<<(std::ostream& os, CivilMinute m);
std::ostream& operator<<(std::ostream& os, CivilSecond s);
+// AbslParseFlag()
+//
+// Parses the command-line flag string representation `s` into a civil-time
+// value. Flags must be specified in a format that is valid for
+// `absl::ParseLenientCivilTime()`.
+bool AbslParseFlag(absl::string_view s, CivilSecond* c, std::string* error);
+bool AbslParseFlag(absl::string_view s, CivilMinute* c, std::string* error);
+bool AbslParseFlag(absl::string_view s, CivilHour* c, std::string* error);
+bool AbslParseFlag(absl::string_view s, CivilDay* c, std::string* error);
+bool AbslParseFlag(absl::string_view s, CivilMonth* c, std::string* error);
+bool AbslParseFlag(absl::string_view s, CivilYear* c, std::string* error);
+
+// AbslUnparseFlag()
+//
+// Unparses a civil-time value into a command-line string representation using
+// the format specified by `absl::ParseCivilTime()`.
+std::string AbslUnparseFlag(CivilSecond c);
+std::string AbslUnparseFlag(CivilMinute c);
+std::string AbslUnparseFlag(CivilHour c);
+std::string AbslUnparseFlag(CivilDay c);
+std::string AbslUnparseFlag(CivilMonth c);
+std::string AbslUnparseFlag(CivilYear c);
+
} // namespace time_internal
ABSL_NAMESPACE_END
diff --git a/absl/time/civil_time_benchmark.cc b/absl/time/civil_time_benchmark.cc
index f04dbe2..2de0233 100644
--- a/absl/time/civil_time_benchmark.cc
+++ b/absl/time/civil_time_benchmark.cc
@@ -14,7 +14,9 @@
#include "absl/time/civil_time.h"
+#include <cstddef>
#include <numeric>
+#include <string>
#include <vector>
#include "absl/hash/hash.h"
@@ -42,7 +44,7 @@
const absl::CivilDay c(2014, 8, 22);
const absl::CivilDay epoch(1970, 1, 1);
while (state.KeepRunning()) {
- const absl::civil_diff_t n = c - epoch;
+ absl::civil_diff_t n = c - epoch;
benchmark::DoNotOptimize(n);
}
}
@@ -60,7 +62,7 @@
void BM_Format(benchmark::State& state) {
const absl::CivilSecond c(2014, 1, 2, 3, 4, 5);
while (state.KeepRunning()) {
- const std::string s = absl::FormatCivilTime(c);
+ std::string s = absl::FormatCivilTime(c);
benchmark::DoNotOptimize(s);
}
}
@@ -70,7 +72,7 @@
const std::string f = "2014-01-02T03:04:05";
absl::CivilSecond c;
while (state.KeepRunning()) {
- const bool b = absl::ParseCivilTime(f, &c);
+ bool b = absl::ParseCivilTime(f, &c);
benchmark::DoNotOptimize(b);
}
}
@@ -80,7 +82,7 @@
const absl::CivilSecond c(2014, 1, 2, 3, 4, 5);
absl::CivilSecond out;
while (state.KeepRunning()) {
- const bool b = absl::ParseCivilTime(absl::FormatCivilTime(c), &out);
+ bool b = absl::ParseCivilTime(absl::FormatCivilTime(c), &out);
benchmark::DoNotOptimize(b);
}
}
@@ -95,7 +97,8 @@
absl::Hash<T> absl_hasher;
while (state.KeepRunningBatch(kSize)) {
for (const T civil_time : civil_times) {
- benchmark::DoNotOptimize(absl_hasher(civil_time));
+ size_t hash = absl_hasher(civil_time);
+ benchmark::DoNotOptimize(hash);
}
}
}
diff --git a/absl/time/civil_time_test.cc b/absl/time/civil_time_test.cc
index 0ebd97a..19292a9 100644
--- a/absl/time/civil_time_test.cc
+++ b/absl/time/civil_time_test.cc
@@ -14,12 +14,14 @@
#include "absl/time/civil_time.h"
+#include <iomanip>
#include <limits>
#include <sstream>
#include <type_traits>
-#include "absl/base/macros.h"
#include "gtest/gtest.h"
+#include "absl/base/macros.h"
+#include "absl/strings/str_format.h"
namespace {
@@ -868,6 +870,23 @@
EXPECT_FALSE(absl::ParseLenientCivilTime("9223372036854775808", &y)) << y;
}
+TEST(CivilTime, AbslStringify) {
+ EXPECT_EQ("2015-01-02T03:04:05",
+ absl::StrFormat("%v", absl::CivilSecond(2015, 1, 2, 3, 4, 5)));
+
+ EXPECT_EQ("2015-01-02T03:04",
+ absl::StrFormat("%v", absl::CivilMinute(2015, 1, 2, 3, 4)));
+
+ EXPECT_EQ("2015-01-02T03",
+ absl::StrFormat("%v", absl::CivilHour(2015, 1, 2, 3)));
+
+ EXPECT_EQ("2015-01-02", absl::StrFormat("%v", absl::CivilDay(2015, 1, 2)));
+
+ EXPECT_EQ("2015-01", absl::StrFormat("%v", absl::CivilMonth(2015, 1)));
+
+ EXPECT_EQ("2015", absl::StrFormat("%v", absl::CivilYear(2015)));
+}
+
TEST(CivilTime, OutputStream) {
absl::CivilSecond cs(2016, 2, 3, 4, 5, 6);
{
@@ -1228,7 +1247,7 @@
EXPECT_EQ(0, day_floor.hour()); // 09:09:09 is floored
EXPECT_EQ(absl::CivilDay(2015, 1, 2), day_floor);
- // Unspecified fields default to their minium value
+ // Unspecified fields default to their minimum value
absl::CivilDay day_default(2015); // Defaults to Jan 1
EXPECT_EQ(absl::CivilDay(2015, 1, 1), day_default);
diff --git a/absl/time/clock.cc b/absl/time/clock.cc
index 7b204c4..ecd539e 100644
--- a/absl/time/clock.cc
+++ b/absl/time/clock.cc
@@ -48,17 +48,16 @@
ABSL_NAMESPACE_END
} // namespace absl
-// Decide if we should use the fast GetCurrentTimeNanos() algorithm
-// based on the cyclecounter, otherwise just get the time directly
-// from the OS on every call. This can be chosen at compile-time via
+// Decide if we should use the fast GetCurrentTimeNanos() algorithm based on the
+// cyclecounter, otherwise just get the time directly from the OS on every call.
+// By default, the fast algorithm based on the cyclecount is disabled because in
+// certain situations, for example, if the OS enters a "sleep" mode, it may
+// produce incorrect values immediately upon waking.
+// This can be chosen at compile-time via
// -DABSL_USE_CYCLECLOCK_FOR_GET_CURRENT_TIME_NANOS=[0|1]
#ifndef ABSL_USE_CYCLECLOCK_FOR_GET_CURRENT_TIME_NANOS
-#if ABSL_USE_UNSCALED_CYCLECLOCK
-#define ABSL_USE_CYCLECLOCK_FOR_GET_CURRENT_TIME_NANOS 1
-#else
#define ABSL_USE_CYCLECLOCK_FOR_GET_CURRENT_TIME_NANOS 0
#endif
-#endif
#if defined(__APPLE__) || defined(_WIN32)
#include "absl/time/internal/get_current_time_chrono.inc"
@@ -89,11 +88,25 @@
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace time_internal {
+
+// On some processors, consecutive reads of the cycle counter may yield the
+// same value (weakly-increasing). In debug mode, clear the least significant
+// bits to discourage depending on a strictly-increasing Now() value.
+// In x86-64's debug mode, discourage depending on a strictly-increasing Now()
+// value.
+#if !defined(NDEBUG) && defined(__x86_64__)
+constexpr int64_t kCycleClockNowMask = ~int64_t{0xff};
+#else
+constexpr int64_t kCycleClockNowMask = ~int64_t{0};
+#endif
+
// This is a friend wrapper around UnscaledCycleClock::Now()
// (needed to access UnscaledCycleClock).
class UnscaledCycleClockWrapperForGetCurrentTime {
public:
- static int64_t Now() { return base_internal::UnscaledCycleClock::Now(); }
+ static int64_t Now() {
+ return base_internal::UnscaledCycleClock::Now() & kCycleClockNowMask;
+ }
};
} // namespace time_internal
@@ -196,7 +209,7 @@
absl::base_internal::SpinLock lock{absl::kConstInit,
base_internal::SCHEDULE_KERNEL_ONLY};
};
-ABSL_CONST_INIT static TimeState time_state{};
+ABSL_CONST_INIT static TimeState time_state;
// Return the time in ns as told by the kernel interface. Place in *cycleclock
// the value of the cycleclock at about the time of the syscall.
@@ -217,9 +230,11 @@
uint64_t elapsed_cycles;
int loops = 0;
do {
- before_cycles = GET_CURRENT_TIME_NANOS_CYCLECLOCK_NOW();
+ before_cycles =
+ static_cast<uint64_t>(GET_CURRENT_TIME_NANOS_CYCLECLOCK_NOW());
current_time_nanos_from_system = GET_CURRENT_TIME_NANOS_FROM_SYSTEM();
- after_cycles = GET_CURRENT_TIME_NANOS_CYCLECLOCK_NOW();
+ after_cycles =
+ static_cast<uint64_t>(GET_CURRENT_TIME_NANOS_CYCLECLOCK_NOW());
// elapsed_cycles is unsigned, so is large on overflow
elapsed_cycles = after_cycles - before_cycles;
if (elapsed_cycles >= local_approx_syscall_time_in_cycles &&
@@ -316,7 +331,8 @@
// contribute to register pressure - reading it early before initializing
// the other pieces of the calculation minimizes spill/restore instructions,
// minimizing icache cost.
- uint64_t now_cycles = GET_CURRENT_TIME_NANOS_CYCLECLOCK_NOW();
+ uint64_t now_cycles =
+ static_cast<uint64_t>(GET_CURRENT_TIME_NANOS_CYCLECLOCK_NOW());
// Acquire pairs with the barrier in SeqRelease - if this load sees that
// store, the shared-data reads necessarily see that SeqRelease's updates
@@ -356,7 +372,8 @@
uint64_t delta_cycles;
if (seq_read0 == seq_read1 && (seq_read0 & 1) == 0 &&
(delta_cycles = now_cycles - base_cycles) < min_cycles_per_sample) {
- return base_ns + ((delta_cycles * nsscaled_per_cycle) >> kScale);
+ return static_cast<int64_t>(
+ base_ns + ((delta_cycles * nsscaled_per_cycle) >> kScale));
}
return GetCurrentTimeNanosSlowPath();
}
@@ -404,8 +421,8 @@
// Sample the kernel time base. This is the definition of
// "now" if we take the slow path.
uint64_t now_cycles;
- uint64_t now_ns =
- GetCurrentTimeNanosFromKernel(time_state.last_now_cycles, &now_cycles);
+ uint64_t now_ns = static_cast<uint64_t>(
+ GetCurrentTimeNanosFromKernel(time_state.last_now_cycles, &now_cycles));
time_state.last_now_cycles = now_cycles;
uint64_t estimated_base_ns;
@@ -432,7 +449,7 @@
time_state.lock.Unlock();
- return estimated_base_ns;
+ return static_cast<int64_t>(estimated_base_ns);
}
// Main part of the algorithm. Locks out readers, updates the approximation
@@ -489,7 +506,8 @@
uint64_t assumed_next_sample_delta_cycles =
SafeDivideAndScale(kMinNSBetweenSamples, measured_nsscaled_per_cycle);
- int64_t diff_ns = now_ns - estimated_base_ns; // estimate low by this much
+ // Estimate low by this much.
+ int64_t diff_ns = static_cast<int64_t>(now_ns - estimated_base_ns);
// We want to set nsscaled_per_cycle so that our estimate of the ns time
// at the assumed cycle time is the assumed ns time.
@@ -500,7 +518,8 @@
// of our current error, by solving:
// kMinNSBetweenSamples + diff_ns - (diff_ns / 16) ==
// (assumed_next_sample_delta_cycles * nsscaled_per_cycle) >> kScale
- ns = kMinNSBetweenSamples + diff_ns - (diff_ns / 16);
+ ns = static_cast<uint64_t>(static_cast<int64_t>(kMinNSBetweenSamples) +
+ diff_ns - (diff_ns / 16));
uint64_t new_nsscaled_per_cycle =
SafeDivideAndScale(ns, assumed_next_sample_delta_cycles);
if (new_nsscaled_per_cycle != 0 &&
@@ -558,7 +577,7 @@
// REQUIRES: to_sleep <= MaxSleep().
void SleepOnce(absl::Duration to_sleep) {
#ifdef _WIN32
- Sleep(to_sleep / absl::Milliseconds(1));
+ Sleep(static_cast<DWORD>(to_sleep / absl::Milliseconds(1)));
#else
struct timespec sleep_time = absl::ToTimespec(to_sleep);
while (nanosleep(&sleep_time, &sleep_time) != 0 && errno == EINTR) {
diff --git a/absl/time/clock.h b/absl/time/clock.h
index 5fe244d..41d2cf2 100644
--- a/absl/time/clock.h
+++ b/absl/time/clock.h
@@ -22,6 +22,9 @@
#ifndef ABSL_TIME_CLOCK_H_
#define ABSL_TIME_CLOCK_H_
+#include <cstdint>
+
+#include "absl/base/config.h"
#include "absl/base/macros.h"
#include "absl/time/time.h"
@@ -64,7 +67,8 @@
// By changing our extension points to be extern "C", we dodge this
// check.
extern "C" {
-void ABSL_INTERNAL_C_SYMBOL(AbslInternalSleepFor)(absl::Duration duration);
+ABSL_DLL void ABSL_INTERNAL_C_SYMBOL(AbslInternalSleepFor)(
+ absl::Duration duration);
} // extern "C"
inline void absl::SleepFor(absl::Duration duration) {
diff --git a/absl/time/clock_test.cc b/absl/time/clock_test.cc
index 4bcfc6b..bc77dbc 100644
--- a/absl/time/clock_test.cc
+++ b/absl/time/clock_test.cc
@@ -18,6 +18,10 @@
#if defined(ABSL_HAVE_ALARM)
#include <signal.h>
#include <unistd.h>
+#ifdef _AIX
+// sig_t is not defined in AIX.
+typedef void (*sig_t)(int);
+#endif
#elif defined(__linux__) || defined(__APPLE__)
#error all known Linux and Apple targets have alarm
#endif
diff --git a/absl/time/duration.cc b/absl/time/duration.cc
index 4443109..8d0b66f 100644
--- a/absl/time/duration.cc
+++ b/absl/time/duration.cc
@@ -55,8 +55,7 @@
#include <algorithm>
#include <cassert>
-#include <cctype>
-#include <cerrno>
+#include <chrono> // NOLINT(build/c++11)
#include <cmath>
#include <cstdint>
#include <cstdlib>
@@ -66,8 +65,9 @@
#include <limits>
#include <string>
+#include "absl/base/attributes.h"
#include "absl/base/casts.h"
-#include "absl/base/macros.h"
+#include "absl/base/config.h"
#include "absl/numeric/int128.h"
#include "absl/strings/string_view.h"
#include "absl/strings/strip.h"
@@ -96,13 +96,6 @@
return d != 0.0;
}
-// Can't use std::round() because it is only available in C++11.
-// Note that we ignore the possibility of floating-point over/underflow.
-template <typename Double>
-inline double Round(Double d) {
- return d < 0 ? std::ceil(d - 0.5) : std::floor(d + 0.5);
-}
-
// *sec may be positive or negative. *ticks must be in the range
// -kTicksPerSecond < *ticks < kTicksPerSecond. If *ticks is negative it
// will be normalized to a positive value by adjusting *sec accordingly.
@@ -226,7 +219,7 @@
? static_cast<uint128>(Uint128Low64(a) * Uint128Low64(b))
: a * b;
}
- return b == 0 ? b : (a > kuint128max / b) ? kuint128max : a * b;
+ return b == 0 ? b : (a > Uint128Max() / b) ? Uint128Max() : a * b;
}
};
@@ -260,7 +253,7 @@
double lo_frac = std::modf(lo_doub, &lo_int);
// Rolls lo into hi if necessary.
- int64_t lo64 = Round(lo_frac * kTicksPerSecond);
+ int64_t lo64 = std::round(lo_frac * kTicksPerSecond);
Duration ans;
if (!SafeAddRepHi(hi_int, lo_int, &ans)) return ans;
@@ -287,33 +280,35 @@
int64_t den_hi = time_internal::GetRepHi(den);
uint32_t den_lo = time_internal::GetRepLo(den);
- if (den_hi == 0 && den_lo == kTicksPerNanosecond) {
- // Dividing by 1ns
- if (num_hi >= 0 && num_hi < (kint64max - kTicksPerSecond) / 1000000000) {
- *q = num_hi * 1000000000 + num_lo / kTicksPerNanosecond;
- *rem = time_internal::MakeDuration(0, num_lo % den_lo);
- return true;
- }
- } else if (den_hi == 0 && den_lo == 100 * kTicksPerNanosecond) {
- // Dividing by 100ns (common when converting to Universal time)
- if (num_hi >= 0 && num_hi < (kint64max - kTicksPerSecond) / 10000000) {
- *q = num_hi * 10000000 + num_lo / (100 * kTicksPerNanosecond);
- *rem = time_internal::MakeDuration(0, num_lo % den_lo);
- return true;
- }
- } else if (den_hi == 0 && den_lo == 1000 * kTicksPerNanosecond) {
- // Dividing by 1us
- if (num_hi >= 0 && num_hi < (kint64max - kTicksPerSecond) / 1000000) {
- *q = num_hi * 1000000 + num_lo / (1000 * kTicksPerNanosecond);
- *rem = time_internal::MakeDuration(0, num_lo % den_lo);
- return true;
- }
- } else if (den_hi == 0 && den_lo == 1000000 * kTicksPerNanosecond) {
- // Dividing by 1ms
- if (num_hi >= 0 && num_hi < (kint64max - kTicksPerSecond) / 1000) {
- *q = num_hi * 1000 + num_lo / (1000000 * kTicksPerNanosecond);
- *rem = time_internal::MakeDuration(0, num_lo % den_lo);
- return true;
+ if (den_hi == 0) {
+ if (den_lo == kTicksPerNanosecond) {
+ // Dividing by 1ns
+ if (num_hi >= 0 && num_hi < (kint64max - kTicksPerSecond) / 1000000000) {
+ *q = num_hi * 1000000000 + num_lo / kTicksPerNanosecond;
+ *rem = time_internal::MakeDuration(0, num_lo % den_lo);
+ return true;
+ }
+ } else if (den_lo == 100 * kTicksPerNanosecond) {
+ // Dividing by 100ns (common when converting to Universal time)
+ if (num_hi >= 0 && num_hi < (kint64max - kTicksPerSecond) / 10000000) {
+ *q = num_hi * 10000000 + num_lo / (100 * kTicksPerNanosecond);
+ *rem = time_internal::MakeDuration(0, num_lo % den_lo);
+ return true;
+ }
+ } else if (den_lo == 1000 * kTicksPerNanosecond) {
+ // Dividing by 1us
+ if (num_hi >= 0 && num_hi < (kint64max - kTicksPerSecond) / 1000000) {
+ *q = num_hi * 1000000 + num_lo / (1000 * kTicksPerNanosecond);
+ *rem = time_internal::MakeDuration(0, num_lo % den_lo);
+ return true;
+ }
+ } else if (den_lo == 1000000 * kTicksPerNanosecond) {
+ // Dividing by 1ms
+ if (num_hi >= 0 && num_hi < (kint64max - kTicksPerSecond) / 1000) {
+ *q = num_hi * 1000 + num_lo / (1000000 * kTicksPerNanosecond);
+ *rem = time_internal::MakeDuration(0, num_lo % den_lo);
+ return true;
+ }
}
} else if (den_hi > 0 && den_lo == 0) {
// Dividing by positive multiple of 1s
@@ -349,19 +344,10 @@
} // namespace
-namespace time_internal {
+namespace {
-// The 'satq' argument indicates whether the quotient should saturate at the
-// bounds of int64_t. If it does saturate, the difference will spill over to
-// the remainder. If it does not saturate, the remainder remain accurate,
-// but the returned quotient will over/underflow int64_t and should not be used.
-int64_t IDivDuration(bool satq, const Duration num, const Duration den,
+int64_t IDivSlowPath(bool satq, const Duration num, const Duration den,
Duration* rem) {
- int64_t q = 0;
- if (IDivFastPath(num, den, &q, rem)) {
- return q;
- }
-
const bool num_neg = num < ZeroDuration();
const bool den_neg = den < ZeroDuration();
const bool quotient_neg = num_neg != den_neg;
@@ -398,7 +384,27 @@
return -static_cast<int64_t>(Uint128Low64(quotient128 - 1) & kint64max) - 1;
}
-} // namespace time_internal
+// The 'satq' argument indicates whether the quotient should saturate at the
+// bounds of int64_t. If it does saturate, the difference will spill over to
+// the remainder. If it does not saturate, the remainder remain accurate,
+// but the returned quotient will over/underflow int64_t and should not be used.
+ABSL_ATTRIBUTE_ALWAYS_INLINE inline int64_t IDivDurationImpl(bool satq,
+ const Duration num,
+ const Duration den,
+ Duration* rem) {
+ int64_t q = 0;
+ if (IDivFastPath(num, den, &q, rem)) {
+ return q;
+ }
+ return IDivSlowPath(satq, num, den, rem);
+}
+
+} // namespace
+
+int64_t IDivDuration(Duration num, Duration den, Duration* rem) {
+ return IDivDurationImpl(true, num, den,
+ rem); // trunc towards zero
+}
//
// Additive operators.
@@ -407,16 +413,18 @@
Duration& Duration::operator+=(Duration rhs) {
if (time_internal::IsInfiniteDuration(*this)) return *this;
if (time_internal::IsInfiniteDuration(rhs)) return *this = rhs;
- const int64_t orig_rep_hi = rep_hi_;
- rep_hi_ =
- DecodeTwosComp(EncodeTwosComp(rep_hi_) + EncodeTwosComp(rhs.rep_hi_));
+ const int64_t orig_rep_hi = rep_hi_.Get();
+ rep_hi_ = DecodeTwosComp(EncodeTwosComp(rep_hi_.Get()) +
+ EncodeTwosComp(rhs.rep_hi_.Get()));
if (rep_lo_ >= kTicksPerSecond - rhs.rep_lo_) {
- rep_hi_ = DecodeTwosComp(EncodeTwosComp(rep_hi_) + 1);
+ rep_hi_ = DecodeTwosComp(EncodeTwosComp(rep_hi_.Get()) + 1);
rep_lo_ -= kTicksPerSecond;
}
rep_lo_ += rhs.rep_lo_;
- if (rhs.rep_hi_ < 0 ? rep_hi_ > orig_rep_hi : rep_hi_ < orig_rep_hi) {
- return *this = rhs.rep_hi_ < 0 ? -InfiniteDuration() : InfiniteDuration();
+ if (rhs.rep_hi_.Get() < 0 ? rep_hi_.Get() > orig_rep_hi
+ : rep_hi_.Get() < orig_rep_hi) {
+ return *this =
+ rhs.rep_hi_.Get() < 0 ? -InfiniteDuration() : InfiniteDuration();
}
return *this;
}
@@ -424,18 +432,21 @@
Duration& Duration::operator-=(Duration rhs) {
if (time_internal::IsInfiniteDuration(*this)) return *this;
if (time_internal::IsInfiniteDuration(rhs)) {
- return *this = rhs.rep_hi_ >= 0 ? -InfiniteDuration() : InfiniteDuration();
+ return *this = rhs.rep_hi_.Get() >= 0 ? -InfiniteDuration()
+ : InfiniteDuration();
}
- const int64_t orig_rep_hi = rep_hi_;
- rep_hi_ =
- DecodeTwosComp(EncodeTwosComp(rep_hi_) - EncodeTwosComp(rhs.rep_hi_));
+ const int64_t orig_rep_hi = rep_hi_.Get();
+ rep_hi_ = DecodeTwosComp(EncodeTwosComp(rep_hi_.Get()) -
+ EncodeTwosComp(rhs.rep_hi_.Get()));
if (rep_lo_ < rhs.rep_lo_) {
- rep_hi_ = DecodeTwosComp(EncodeTwosComp(rep_hi_) - 1);
+ rep_hi_ = DecodeTwosComp(EncodeTwosComp(rep_hi_.Get()) - 1);
rep_lo_ += kTicksPerSecond;
}
rep_lo_ -= rhs.rep_lo_;
- if (rhs.rep_hi_ < 0 ? rep_hi_ < orig_rep_hi : rep_hi_ > orig_rep_hi) {
- return *this = rhs.rep_hi_ >= 0 ? -InfiniteDuration() : InfiniteDuration();
+ if (rhs.rep_hi_.Get() < 0 ? rep_hi_.Get() < orig_rep_hi
+ : rep_hi_.Get() > orig_rep_hi) {
+ return *this = rhs.rep_hi_.Get() >= 0 ? -InfiniteDuration()
+ : InfiniteDuration();
}
return *this;
}
@@ -446,7 +457,7 @@
Duration& Duration::operator*=(int64_t r) {
if (time_internal::IsInfiniteDuration(*this)) {
- const bool is_neg = (r < 0) != (rep_hi_ < 0);
+ const bool is_neg = (r < 0) != (rep_hi_.Get() < 0);
return *this = is_neg ? -InfiniteDuration() : InfiniteDuration();
}
return *this = ScaleFixed<SafeMultiply>(*this, r);
@@ -454,7 +465,7 @@
Duration& Duration::operator*=(double r) {
if (time_internal::IsInfiniteDuration(*this) || !IsFinite(r)) {
- const bool is_neg = (std::signbit(r) != 0) != (rep_hi_ < 0);
+ const bool is_neg = std::signbit(r) != (rep_hi_.Get() < 0);
return *this = is_neg ? -InfiniteDuration() : InfiniteDuration();
}
return *this = ScaleDouble<std::multiplies>(*this, r);
@@ -462,7 +473,7 @@
Duration& Duration::operator/=(int64_t r) {
if (time_internal::IsInfiniteDuration(*this) || r == 0) {
- const bool is_neg = (r < 0) != (rep_hi_ < 0);
+ const bool is_neg = (r < 0) != (rep_hi_.Get() < 0);
return *this = is_neg ? -InfiniteDuration() : InfiniteDuration();
}
return *this = ScaleFixed<std::divides>(*this, r);
@@ -470,14 +481,14 @@
Duration& Duration::operator/=(double r) {
if (time_internal::IsInfiniteDuration(*this) || !IsValidDivisor(r)) {
- const bool is_neg = (std::signbit(r) != 0) != (rep_hi_ < 0);
+ const bool is_neg = std::signbit(r) != (rep_hi_.Get() < 0);
return *this = is_neg ? -InfiniteDuration() : InfiniteDuration();
}
return *this = ScaleDouble<std::divides>(*this, r);
}
Duration& Duration::operator%=(Duration rhs) {
- time_internal::IDivDuration(false, *this, rhs, this);
+ IDivDurationImpl(false, *this, rhs, this);
return *this;
}
@@ -503,9 +514,7 @@
// Trunc/Floor/Ceil.
//
-Duration Trunc(Duration d, Duration unit) {
- return d - (d % unit);
-}
+Duration Trunc(Duration d, Duration unit) { return d - (d % unit); }
Duration Floor(const Duration d, const Duration unit) {
const absl::Duration td = Trunc(d, unit);
@@ -593,15 +602,9 @@
double ToDoubleMilliseconds(Duration d) {
return FDivDuration(d, Milliseconds(1));
}
-double ToDoubleSeconds(Duration d) {
- return FDivDuration(d, Seconds(1));
-}
-double ToDoubleMinutes(Duration d) {
- return FDivDuration(d, Minutes(1));
-}
-double ToDoubleHours(Duration d) {
- return FDivDuration(d, Hours(1));
-}
+double ToDoubleSeconds(Duration d) { return FDivDuration(d, Seconds(1)); }
+double ToDoubleMinutes(Duration d) { return FDivDuration(d, Minutes(1)); }
+double ToDoubleHours(Duration d) { return FDivDuration(d, Hours(1)); }
timespec ToTimespec(Duration d) {
timespec ts;
@@ -617,7 +620,7 @@
rep_lo -= kTicksPerSecond;
}
}
- ts.tv_sec = rep_hi;
+ ts.tv_sec = static_cast<decltype(ts.tv_sec)>(rep_hi);
if (ts.tv_sec == rep_hi) { // no time_t narrowing
ts.tv_nsec = rep_lo / kTicksPerNanosecond;
return ts;
@@ -645,7 +648,7 @@
ts.tv_nsec -= 1000 * 1000 * 1000;
}
}
- tv.tv_sec = ts.tv_sec;
+ tv.tv_sec = static_cast<decltype(tv.tv_sec)>(ts.tv_sec);
if (tv.tv_sec != ts.tv_sec) { // narrowing
if (ts.tv_sec < 0) {
tv.tv_sec = std::numeric_limits<decltype(tv.tv_sec)>::min();
@@ -691,7 +694,7 @@
char* Format64(char* ep, int width, int64_t v) {
do {
--width;
- *--ep = '0' + (v % 10); // contiguous digits
+ *--ep = static_cast<char>('0' + (v % 10)); // contiguous digits
} while (v /= 10);
while (--width >= 0) *--ep = '0'; // zero pad
return ep;
@@ -728,7 +731,7 @@
char* const ep = buf + sizeof(buf);
char* bp = Format64(ep, 0, n);
if (*bp != '0' || bp + 1 != ep) {
- out->append(bp, ep - bp);
+ out->append(bp, static_cast<size_t>(ep - bp));
out->append(unit.abbr.data(), unit.abbr.size());
}
}
@@ -741,16 +744,16 @@
char buf[kBufferSize]; // also large enough to hold integer part
char* ep = buf + sizeof(buf);
double d = 0;
- int64_t frac_part = Round(std::modf(n, &d) * unit.pow10);
+ int64_t frac_part = std::round(std::modf(n, &d) * unit.pow10);
int64_t int_part = d;
if (int_part != 0 || frac_part != 0) {
char* bp = Format64(ep, 0, int_part); // always < 1000
- out->append(bp, ep - bp);
+ out->append(bp, static_cast<size_t>(ep - bp));
if (frac_part != 0) {
out->push_back('.');
bp = Format64(ep, prec, frac_part);
while (ep[-1] == '0') --ep;
- out->append(bp, ep - bp);
+ out->append(bp, static_cast<size_t>(ep - bp));
}
out->append(unit.abbr.data(), unit.abbr.size());
}
@@ -766,13 +769,14 @@
// is non-zero.
// Unlike Go, we format the zero duration as 0, with no unit.
std::string FormatDuration(Duration d) {
- const Duration min_duration = Seconds(kint64min);
- if (d == min_duration) {
+ constexpr Duration kMinDuration = Seconds(kint64min);
+ std::string s;
+ if (d == kMinDuration) {
// Avoid needing to negate kint64min by directly returning what the
// following code should produce in that case.
- return "-2562047788015215h30m8s";
+ s = "-2562047788015215h30m8s";
+ return s;
}
- std::string s;
if (d < ZeroDuration()) {
s.append("-");
d = -d;
@@ -840,7 +844,7 @@
// in "*unit". The given string pointer is modified to point to the first
// unconsumed char.
bool ConsumeDurationUnit(const char** start, const char* end, Duration* unit) {
- size_t size = end - *start;
+ size_t size = static_cast<size_t>(end - *start);
switch (size) {
case 0:
return false;
diff --git a/absl/time/duration_benchmark.cc b/absl/time/duration_benchmark.cc
index 56820f3..fdb26bb 100644
--- a/absl/time/duration_benchmark.cc
+++ b/absl/time/duration_benchmark.cc
@@ -290,6 +290,26 @@
}
BENCHMARK(BM_Duration_IDivDuration_Hours);
+void BM_Duration_Modulo(benchmark::State& state) {
+ int i = 0;
+ while (state.KeepRunning()) {
+ auto mod = absl::Seconds(i) % absl::Nanoseconds(12345);
+ benchmark::DoNotOptimize(mod);
+ ++i;
+ }
+}
+BENCHMARK(BM_Duration_Modulo);
+
+void BM_Duration_Modulo_FastPath(benchmark::State& state) {
+ int i = 0;
+ while (state.KeepRunning()) {
+ auto mod = absl::Seconds(i) % absl::Milliseconds(1);
+ benchmark::DoNotOptimize(mod);
+ ++i;
+ }
+}
+BENCHMARK(BM_Duration_Modulo_FastPath);
+
void BM_Duration_ToInt64Nanoseconds(benchmark::State& state) {
absl::Duration d = absl::Seconds(100000);
while (state.KeepRunning()) {
diff --git a/absl/time/duration_test.cc b/absl/time/duration_test.cc
index b7209e1..dcf7aad 100644
--- a/absl/time/duration_test.cc
+++ b/absl/time/duration_test.cc
@@ -16,8 +16,9 @@
#include <winsock2.h> // for timeval
#endif
-#include <chrono> // NOLINT(build/c++11)
+#include <array>
#include <cfloat>
+#include <chrono> // NOLINT(build/c++11)
#include <cmath>
#include <cstdint>
#include <ctime>
@@ -28,6 +29,7 @@
#include "gmock/gmock.h"
#include "gtest/gtest.h"
+#include "absl/strings/str_format.h"
#include "absl/time/time.h"
namespace {
@@ -1818,4 +1820,18 @@
#undef TEST_PARSE_ROUNDTRIP
}
+TEST(Duration, AbslStringify) {
+ // FormatDuration is already well tested, so just use one test case here to
+ // verify that StrFormat("%v", d) works as expected.
+ absl::Duration d = absl::Seconds(1);
+ EXPECT_EQ(absl::StrFormat("%v", d), absl::FormatDuration(d));
+}
+
+TEST(Duration, NoPadding) {
+ // Should match the size of a struct with uint32_t alignment and no padding.
+ using NoPadding = std::array<uint32_t, 3>;
+ EXPECT_EQ(sizeof(NoPadding), sizeof(absl::Duration));
+ EXPECT_EQ(alignof(NoPadding), alignof(absl::Duration));
+}
+
} // namespace
diff --git a/absl/time/flag_test.cc b/absl/time/flag_test.cc
new file mode 100644
index 0000000..8f8532b
--- /dev/null
+++ b/absl/time/flag_test.cc
@@ -0,0 +1,147 @@
+// Copyright 2018 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/flags/flag.h"
+
+#include <string>
+
+#include "gtest/gtest.h"
+#include "absl/flags/reflection.h"
+#include "absl/time/civil_time.h"
+#include "absl/time/time.h"
+
+ABSL_FLAG(absl::CivilSecond, test_flag_civil_second,
+ absl::CivilSecond(2015, 1, 2, 3, 4, 5), "");
+ABSL_FLAG(absl::CivilMinute, test_flag_civil_minute,
+ absl::CivilMinute(2015, 1, 2, 3, 4), "");
+ABSL_FLAG(absl::CivilHour, test_flag_civil_hour, absl::CivilHour(2015, 1, 2, 3),
+ "");
+ABSL_FLAG(absl::CivilDay, test_flag_civil_day, absl::CivilDay(2015, 1, 2), "");
+ABSL_FLAG(absl::CivilMonth, test_flag_civil_month, absl::CivilMonth(2015, 1),
+ "");
+ABSL_FLAG(absl::CivilYear, test_flag_civil_year, absl::CivilYear(2015), "");
+
+ABSL_FLAG(absl::Duration, test_duration_flag, absl::Seconds(5),
+ "For testing support for Duration flags");
+ABSL_FLAG(absl::Time, test_time_flag, absl::InfinitePast(),
+ "For testing support for Time flags");
+
+namespace {
+
+bool SetFlagValue(absl::string_view flag_name, absl::string_view value) {
+ auto* flag = absl::FindCommandLineFlag(flag_name);
+ if (!flag) return false;
+ std::string err;
+ return flag->ParseFrom(value, &err);
+}
+
+bool GetFlagValue(absl::string_view flag_name, std::string& value) {
+ auto* flag = absl::FindCommandLineFlag(flag_name);
+ if (!flag) return false;
+ value = flag->CurrentValue();
+ return true;
+}
+
+TEST(CivilTime, FlagSupport) {
+ // Tests the default setting of the flags.
+ const absl::CivilSecond kDefaultSec(2015, 1, 2, 3, 4, 5);
+ EXPECT_EQ(absl::CivilSecond(kDefaultSec),
+ absl::GetFlag(FLAGS_test_flag_civil_second));
+ EXPECT_EQ(absl::CivilMinute(kDefaultSec),
+ absl::GetFlag(FLAGS_test_flag_civil_minute));
+ EXPECT_EQ(absl::CivilHour(kDefaultSec),
+ absl::GetFlag(FLAGS_test_flag_civil_hour));
+ EXPECT_EQ(absl::CivilDay(kDefaultSec),
+ absl::GetFlag(FLAGS_test_flag_civil_day));
+ EXPECT_EQ(absl::CivilMonth(kDefaultSec),
+ absl::GetFlag(FLAGS_test_flag_civil_month));
+ EXPECT_EQ(absl::CivilYear(kDefaultSec),
+ absl::GetFlag(FLAGS_test_flag_civil_year));
+
+ // Sets flags to a new value.
+ const absl::CivilSecond kNewSec(2016, 6, 7, 8, 9, 10);
+ absl::SetFlag(&FLAGS_test_flag_civil_second, absl::CivilSecond(kNewSec));
+ absl::SetFlag(&FLAGS_test_flag_civil_minute, absl::CivilMinute(kNewSec));
+ absl::SetFlag(&FLAGS_test_flag_civil_hour, absl::CivilHour(kNewSec));
+ absl::SetFlag(&FLAGS_test_flag_civil_day, absl::CivilDay(kNewSec));
+ absl::SetFlag(&FLAGS_test_flag_civil_month, absl::CivilMonth(kNewSec));
+ absl::SetFlag(&FLAGS_test_flag_civil_year, absl::CivilYear(kNewSec));
+
+ EXPECT_EQ(absl::CivilSecond(kNewSec),
+ absl::GetFlag(FLAGS_test_flag_civil_second));
+ EXPECT_EQ(absl::CivilMinute(kNewSec),
+ absl::GetFlag(FLAGS_test_flag_civil_minute));
+ EXPECT_EQ(absl::CivilHour(kNewSec),
+ absl::GetFlag(FLAGS_test_flag_civil_hour));
+ EXPECT_EQ(absl::CivilDay(kNewSec), absl::GetFlag(FLAGS_test_flag_civil_day));
+ EXPECT_EQ(absl::CivilMonth(kNewSec),
+ absl::GetFlag(FLAGS_test_flag_civil_month));
+ EXPECT_EQ(absl::CivilYear(kNewSec),
+ absl::GetFlag(FLAGS_test_flag_civil_year));
+}
+
+TEST(Duration, FlagSupport) {
+ EXPECT_EQ(absl::Seconds(5), absl::GetFlag(FLAGS_test_duration_flag));
+
+ absl::SetFlag(&FLAGS_test_duration_flag, absl::Seconds(10));
+ EXPECT_EQ(absl::Seconds(10), absl::GetFlag(FLAGS_test_duration_flag));
+
+ EXPECT_TRUE(SetFlagValue("test_duration_flag", "20s"));
+ EXPECT_EQ(absl::Seconds(20), absl::GetFlag(FLAGS_test_duration_flag));
+
+ std::string current_flag_value;
+ EXPECT_TRUE(GetFlagValue("test_duration_flag", current_flag_value));
+ EXPECT_EQ("20s", current_flag_value);
+}
+
+TEST(Time, FlagSupport) {
+ EXPECT_EQ(absl::InfinitePast(), absl::GetFlag(FLAGS_test_time_flag));
+
+ const absl::Time t = absl::FromCivil(absl::CivilSecond(2016, 1, 2, 3, 4, 5),
+ absl::UTCTimeZone());
+ absl::SetFlag(&FLAGS_test_time_flag, t);
+ EXPECT_EQ(t, absl::GetFlag(FLAGS_test_time_flag));
+
+ // Successful parse
+ EXPECT_TRUE(SetFlagValue("test_time_flag", "2016-01-02T03:04:06Z"));
+ EXPECT_EQ(t + absl::Seconds(1), absl::GetFlag(FLAGS_test_time_flag));
+ EXPECT_TRUE(SetFlagValue("test_time_flag", "2016-01-02T03:04:07.0Z"));
+ EXPECT_EQ(t + absl::Seconds(2), absl::GetFlag(FLAGS_test_time_flag));
+ EXPECT_TRUE(SetFlagValue("test_time_flag", "2016-01-02T03:04:08.000Z"));
+ EXPECT_EQ(t + absl::Seconds(3), absl::GetFlag(FLAGS_test_time_flag));
+ EXPECT_TRUE(SetFlagValue("test_time_flag", "2016-01-02T03:04:09+00:00"));
+ EXPECT_EQ(t + absl::Seconds(4), absl::GetFlag(FLAGS_test_time_flag));
+ EXPECT_TRUE(SetFlagValue("test_time_flag", "2016-01-02T03:04:05.123+00:00"));
+ EXPECT_EQ(t + absl::Milliseconds(123), absl::GetFlag(FLAGS_test_time_flag));
+ EXPECT_TRUE(SetFlagValue("test_time_flag", "2016-01-02T03:04:05.123+08:00"));
+ EXPECT_EQ(t + absl::Milliseconds(123) - absl::Hours(8),
+ absl::GetFlag(FLAGS_test_time_flag));
+ EXPECT_TRUE(SetFlagValue("test_time_flag", "infinite-future"));
+ EXPECT_EQ(absl::InfiniteFuture(), absl::GetFlag(FLAGS_test_time_flag));
+ EXPECT_TRUE(SetFlagValue("test_time_flag", "infinite-past"));
+ EXPECT_EQ(absl::InfinitePast(), absl::GetFlag(FLAGS_test_time_flag));
+
+ EXPECT_FALSE(SetFlagValue("test_time_flag", "2016-01-02T03:04:06"));
+ EXPECT_FALSE(SetFlagValue("test_time_flag", "2016-01-02"));
+ EXPECT_FALSE(SetFlagValue("test_time_flag", "2016-01-02Z"));
+ EXPECT_FALSE(SetFlagValue("test_time_flag", "2016-01-02+00:00"));
+ EXPECT_FALSE(SetFlagValue("test_time_flag", "2016-99-99T03:04:06Z"));
+
+ EXPECT_TRUE(SetFlagValue("test_time_flag", "2016-01-02T03:04:05Z"));
+ std::string current_flag_value;
+ EXPECT_TRUE(GetFlagValue("test_time_flag", current_flag_value));
+ EXPECT_EQ("2016-01-02T03:04:05+00:00", current_flag_value);
+}
+
+} // namespace
diff --git a/absl/time/format.cc b/absl/time/format.cc
index 4005fb7..15a26b1 100644
--- a/absl/time/format.cc
+++ b/absl/time/format.cc
@@ -64,7 +64,8 @@
// details about rep_hi and rep_lo.
absl::Time Join(const cctz_parts& parts) {
const int64_t rep_hi = (parts.sec - unix_epoch()).count();
- const uint32_t rep_lo = parts.fem.count() / (1000 * 1000 / 4);
+ const uint32_t rep_lo =
+ static_cast<uint32_t>(parts.fem.count() / (1000 * 1000 / 4));
const auto d = time_internal::MakeDuration(rep_hi, rep_lo);
return time_internal::FromUnixDuration(d);
}
diff --git a/absl/time/internal/cctz/BUILD.bazel b/absl/time/internal/cctz/BUILD.bazel
index 45a9529..0b43bb1 100644
--- a/absl/time/internal/cctz/BUILD.bazel
+++ b/absl/time/internal/cctz/BUILD.bazel
@@ -12,31 +12,14 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test")
-
-package(features = ["-parse_headers"])
+package(features = [
+ "header_modules",
+ "layering_check",
+ "parse_headers",
+])
licenses(["notice"])
-filegroup(
- name = "zoneinfo",
- srcs = glob(["testdata/zoneinfo/**"]),
-)
-
-config_setting(
- name = "osx",
- constraint_values = [
- "@bazel_tools//platforms:osx",
- ],
-)
-
-config_setting(
- name = "ios",
- constraint_values = [
- "@bazel_tools//platforms:ios",
- ],
-)
-
### libraries
cc_library(
@@ -75,19 +58,19 @@
"include/cctz/zone_info_source.h",
],
linkopts = select({
- ":osx": [
- "-framework Foundation",
- ],
- ":ios": [
- "-framework Foundation",
- ],
+ "@platforms//os:osx": ["-Wl,-framework,CoreFoundation"],
+ "@platforms//os:ios": ["-Wl,-framework,CoreFoundation"],
"//conditions:default": [],
}),
visibility = ["//visibility:public"],
deps = [
":civil_time",
"//absl/base:config",
- ],
+ ] + select(
+ {
+ "//conditions:default": [],
+ },
+ ),
)
### tests
@@ -104,6 +87,7 @@
deps = [
":civil_time",
"//absl/base:config",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -113,15 +97,18 @@
size = "small",
srcs = ["src/time_zone_format_test.cc"],
data = [":zoneinfo"],
+ env = {"TZDIR": "absl/time/internal/cctz/testdata/zoneinfo"},
tags = [
"no_test_android_arm",
"no_test_android_arm64",
"no_test_android_x86",
+ "no_test_wasm",
],
deps = [
":civil_time",
":time_zone",
"//absl/base:config",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -132,15 +119,18 @@
timeout = "moderate",
srcs = ["src/time_zone_lookup_test.cc"],
data = [":zoneinfo"],
+ env = {"TZDIR": "absl/time/internal/cctz/testdata/zoneinfo"},
tags = [
"no_test_android_arm",
"no_test_android_arm64",
"no_test_android_x86",
+ "no_test_wasm",
],
deps = [
":civil_time",
":time_zone",
"//absl/base:config",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -166,6 +156,12 @@
],
)
+filegroup(
+ name = "zoneinfo",
+ srcs = glob(["testdata/zoneinfo/**"]),
+ visibility = ["//absl/time:__subpackages__"],
+)
+
### examples
### binaries
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 8aadde5..2b0aed5 100644
--- a/absl/time/internal/cctz/include/cctz/civil_time_detail.h
+++ b/absl/time/internal/cctz/include/cctz/civil_time_detail.h
@@ -84,14 +84,13 @@
return y % 4 == 0 && (y % 100 != 0 || y % 400 == 0);
}
CONSTEXPR_F int year_index(year_t y, month_t m) noexcept {
- return (static_cast<int>((y + (m > 2)) % 400) + 400) % 400;
+ const int yi = static_cast<int>((y + (m > 2)) % 400);
+ return yi < 0 ? yi + 400 : yi;
}
-CONSTEXPR_F int days_per_century(year_t y, month_t m) noexcept {
- const int yi = year_index(y, m);
+CONSTEXPR_F int days_per_century(int yi) noexcept {
return 36524 + (yi == 0 || yi > 300);
}
-CONSTEXPR_F int days_per_4years(year_t y, month_t m) noexcept {
- const int yi = year_index(y, m);
+CONSTEXPR_F int days_per_4years(int yi) noexcept {
return 1460 + (yi == 0 || yi > 300 || (yi - 1) % 100 < 96);
}
CONSTEXPR_F int days_per_year(year_t y, month_t m) noexcept {
@@ -133,17 +132,22 @@
}
}
if (d > 365) {
+ int yi = year_index(ey, m); // Index into Gregorian 400 year cycle.
for (;;) {
- int n = days_per_century(ey, m);
+ int n = days_per_century(yi);
if (d <= n) break;
d -= n;
ey += 100;
+ yi += 100;
+ if (yi >= 400) yi -= 400;
}
for (;;) {
- int n = days_per_4years(ey, m);
+ int n = days_per_4years(yi);
if (d <= n) break;
d -= n;
ey += 4;
+ yi += 4;
+ if (yi >= 400) yi -= 400;
}
for (;;) {
int n = days_per_year(ey, m);
@@ -397,11 +401,11 @@
: civil_time(ct.f_) {}
// Factories for the maximum/minimum representable civil_time.
- static CONSTEXPR_F civil_time(max)() {
+ static CONSTEXPR_F auto(max)() -> civil_time {
const auto max_year = (std::numeric_limits<std::int_least64_t>::max)();
return civil_time(max_year, 12, 31, 23, 59, 59);
}
- static CONSTEXPR_F civil_time(min)() {
+ static CONSTEXPR_F auto(min)() -> civil_time {
const auto min_year = (std::numeric_limits<std::int_least64_t>::min)();
return civil_time(min_year, 1, 1, 0, 0, 0);
}
diff --git a/absl/time/internal/cctz/include/cctz/time_zone.h b/absl/time/internal/cctz/include/cctz/time_zone.h
index 5562a37..b2b0cf6 100644
--- a/absl/time/internal/cctz/include/cctz/time_zone.h
+++ b/absl/time/internal/cctz/include/cctz/time_zone.h
@@ -22,6 +22,8 @@
#include <chrono>
#include <cstdint>
+#include <limits>
+#include <ratio> // NOLINT: We use std::ratio in this header
#include <string>
#include <utility>
@@ -41,20 +43,9 @@
namespace detail {
template <typename D>
-inline std::pair<time_point<seconds>, D> split_seconds(
- const time_point<D>& tp) {
- auto sec = std::chrono::time_point_cast<seconds>(tp);
- auto sub = tp - sec;
- if (sub.count() < 0) {
- sec -= seconds(1);
- sub += seconds(1);
- }
- return {sec, std::chrono::duration_cast<D>(sub)};
-}
-inline std::pair<time_point<seconds>, seconds> split_seconds(
- const time_point<seconds>& tp) {
- return {tp, seconds::zero()};
-}
+std::pair<time_point<seconds>, D> split_seconds(const time_point<D>& tp);
+std::pair<time_point<seconds>, seconds> split_seconds(
+ const time_point<seconds>& tp);
} // namespace detail
// cctz::time_zone is an opaque, small, value-type class representing a
@@ -279,6 +270,20 @@
const femtoseconds&, const time_zone&);
bool parse(const std::string&, const std::string&, const time_zone&,
time_point<seconds>*, femtoseconds*, std::string* err = nullptr);
+template <typename Rep, std::intmax_t Denom>
+bool join_seconds(
+ const time_point<seconds>& sec, const femtoseconds& fs,
+ time_point<std::chrono::duration<Rep, std::ratio<1, Denom>>>* tpp);
+template <typename Rep, std::intmax_t Num>
+bool join_seconds(
+ const time_point<seconds>& sec, const femtoseconds& fs,
+ time_point<std::chrono::duration<Rep, std::ratio<Num, 1>>>* tpp);
+template <typename Rep>
+bool join_seconds(
+ const time_point<seconds>& sec, const femtoseconds& fs,
+ time_point<std::chrono::duration<Rep, std::ratio<1, 1>>>* tpp);
+bool join_seconds(const time_point<seconds>& sec, const femtoseconds&,
+ time_point<seconds>* tpp);
} // namespace detail
// Formats the given time_point in the given cctz::time_zone according to
@@ -369,15 +374,84 @@
const time_zone& tz, time_point<D>* tpp) {
time_point<seconds> sec;
detail::femtoseconds fs;
- const bool b = detail::parse(fmt, input, tz, &sec, &fs);
- if (b) {
- // TODO: Return false if unrepresentable as a time_point<D>.
- *tpp = std::chrono::time_point_cast<D>(sec);
- *tpp += std::chrono::duration_cast<D>(fs);
- }
- return b;
+ return detail::parse(fmt, input, tz, &sec, &fs) &&
+ detail::join_seconds(sec, fs, tpp);
}
+namespace detail {
+
+// Split a time_point<D> into a time_point<seconds> and a D subseconds.
+// Undefined behavior if time_point<seconds> is not of sufficient range.
+// Note that this means it is UB to call cctz::time_zone::lookup(tp) or
+// cctz::format(fmt, tp, tz) with a time_point that is outside the range
+// of a 64-bit std::time_t.
+template <typename D>
+std::pair<time_point<seconds>, D> split_seconds(const time_point<D>& tp) {
+ auto sec = std::chrono::time_point_cast<seconds>(tp);
+ auto sub = tp - sec;
+ if (sub.count() < 0) {
+ sec -= seconds(1);
+ sub += seconds(1);
+ }
+ return {sec, std::chrono::duration_cast<D>(sub)};
+}
+
+inline std::pair<time_point<seconds>, seconds> split_seconds(
+ const time_point<seconds>& tp) {
+ return {tp, seconds::zero()};
+}
+
+// Join a time_point<seconds> and femto subseconds into a time_point<D>.
+// Floors to the resolution of time_point<D>. Returns false if time_point<D>
+// is not of sufficient range.
+template <typename Rep, std::intmax_t Denom>
+bool join_seconds(
+ const time_point<seconds>& sec, const femtoseconds& fs,
+ time_point<std::chrono::duration<Rep, std::ratio<1, Denom>>>* tpp) {
+ using D = std::chrono::duration<Rep, std::ratio<1, Denom>>;
+ // TODO(#199): Return false if result unrepresentable as a time_point<D>.
+ *tpp = std::chrono::time_point_cast<D>(sec);
+ *tpp += std::chrono::duration_cast<D>(fs);
+ return true;
+}
+
+template <typename Rep, std::intmax_t Num>
+bool join_seconds(
+ const time_point<seconds>& sec, const femtoseconds&,
+ time_point<std::chrono::duration<Rep, std::ratio<Num, 1>>>* tpp) {
+ using D = std::chrono::duration<Rep, std::ratio<Num, 1>>;
+ auto count = sec.time_since_epoch().count();
+ if (count >= 0 || count % Num == 0) {
+ count /= Num;
+ } else {
+ count /= Num;
+ count -= 1;
+ }
+ if (count > (std::numeric_limits<Rep>::max)()) return false;
+ if (count < (std::numeric_limits<Rep>::min)()) return false;
+ *tpp = time_point<D>() + D{static_cast<Rep>(count)};
+ return true;
+}
+
+template <typename Rep>
+bool join_seconds(
+ const time_point<seconds>& sec, const femtoseconds&,
+ time_point<std::chrono::duration<Rep, std::ratio<1, 1>>>* tpp) {
+ using D = std::chrono::duration<Rep, std::ratio<1, 1>>;
+ auto count = sec.time_since_epoch().count();
+ if (count > (std::numeric_limits<Rep>::max)()) return false;
+ if (count < (std::numeric_limits<Rep>::min)()) return false;
+ *tpp = time_point<D>() + D{static_cast<Rep>(count)};
+ return true;
+}
+
+inline bool join_seconds(const time_point<seconds>& sec, const femtoseconds&,
+ time_point<seconds>* tpp) {
+ *tpp = sec;
+ return true;
+}
+
+} // namespace detail
} // namespace cctz
} // namespace time_internal
ABSL_NAMESPACE_END
diff --git a/absl/time/internal/cctz/src/cctz_benchmark.cc b/absl/time/internal/cctz/src/cctz_benchmark.cc
index 4e39188..11f9ba6 100644
--- a/absl/time/internal/cctz/src/cctz_benchmark.cc
+++ b/absl/time/internal/cctz/src/cctz_benchmark.cc
@@ -110,7 +110,6 @@
"Africa/Addis_Ababa",
"Africa/Algiers",
"Africa/Asmara",
- "Africa/Asmera",
"Africa/Bamako",
"Africa/Bangui",
"Africa/Banjul",
@@ -166,7 +165,6 @@
"America/Araguaina",
"America/Argentina/Buenos_Aires",
"America/Argentina/Catamarca",
- "America/Argentina/ComodRivadavia",
"America/Argentina/Cordoba",
"America/Argentina/Jujuy",
"America/Argentina/La_Rioja",
@@ -190,18 +188,16 @@
"America/Boa_Vista",
"America/Bogota",
"America/Boise",
- "America/Buenos_Aires",
"America/Cambridge_Bay",
"America/Campo_Grande",
"America/Cancun",
"America/Caracas",
- "America/Catamarca",
"America/Cayenne",
"America/Cayman",
"America/Chicago",
"America/Chihuahua",
+ "America/Ciudad_Juarez",
"America/Coral_Harbour",
- "America/Cordoba",
"America/Costa_Rica",
"America/Creston",
"America/Cuiaba",
@@ -217,7 +213,6 @@
"America/El_Salvador",
"America/Ensenada",
"America/Fort_Nelson",
- "America/Fort_Wayne",
"America/Fortaleza",
"America/Glace_Bay",
"America/Godthab",
@@ -239,20 +234,16 @@
"America/Indiana/Vevay",
"America/Indiana/Vincennes",
"America/Indiana/Winamac",
- "America/Indianapolis",
"America/Inuvik",
"America/Iqaluit",
"America/Jamaica",
- "America/Jujuy",
"America/Juneau",
"America/Kentucky/Louisville",
"America/Kentucky/Monticello",
- "America/Knox_IN",
"America/Kralendijk",
"America/La_Paz",
"America/Lima",
"America/Los_Angeles",
- "America/Louisville",
"America/Lower_Princes",
"America/Maceio",
"America/Managua",
@@ -261,7 +252,6 @@
"America/Martinique",
"America/Matamoros",
"America/Mazatlan",
- "America/Mendoza",
"America/Menominee",
"America/Merida",
"America/Metlakatla",
@@ -298,7 +288,6 @@
"America/Regina",
"America/Resolute",
"America/Rio_Branco",
- "America/Rosario",
"America/Santa_Isabel",
"America/Santarem",
"America/Santiago",
@@ -334,7 +323,6 @@
"Antarctica/McMurdo",
"Antarctica/Palmer",
"Antarctica/Rothera",
- "Antarctica/South_Pole",
"Antarctica/Syowa",
"Antarctica/Troll",
"Antarctica/Vostok",
@@ -346,7 +334,6 @@
"Asia/Aqtau",
"Asia/Aqtobe",
"Asia/Ashgabat",
- "Asia/Ashkhabad",
"Asia/Atyrau",
"Asia/Baghdad",
"Asia/Bahrain",
@@ -356,13 +343,10 @@
"Asia/Beirut",
"Asia/Bishkek",
"Asia/Brunei",
- "Asia/Calcutta",
"Asia/Chita",
"Asia/Choibalsan",
"Asia/Chongqing",
- "Asia/Chungking",
"Asia/Colombo",
- "Asia/Dacca",
"Asia/Damascus",
"Asia/Dhaka",
"Asia/Dili",
@@ -385,14 +369,12 @@
"Asia/Karachi",
"Asia/Kashgar",
"Asia/Kathmandu",
- "Asia/Katmandu",
"Asia/Khandyga",
"Asia/Kolkata",
"Asia/Krasnoyarsk",
"Asia/Kuala_Lumpur",
"Asia/Kuching",
"Asia/Kuwait",
- "Asia/Macao",
"Asia/Macau",
"Asia/Magadan",
"Asia/Makassar",
@@ -409,9 +391,7 @@
"Asia/Qatar",
"Asia/Qostanay",
"Asia/Qyzylorda",
- "Asia/Rangoon",
"Asia/Riyadh",
- "Asia/Saigon",
"Asia/Sakhalin",
"Asia/Samarkand",
"Asia/Seoul",
@@ -423,13 +403,10 @@
"Asia/Tbilisi",
"Asia/Tehran",
"Asia/Tel_Aviv",
- "Asia/Thimbu",
"Asia/Thimphu",
"Asia/Tokyo",
"Asia/Tomsk",
- "Asia/Ujung_Pandang",
"Asia/Ulaanbaatar",
- "Asia/Ulan_Bator",
"Asia/Urumqi",
"Asia/Ust-Nera",
"Asia/Vientiane",
@@ -442,7 +419,6 @@
"Atlantic/Bermuda",
"Atlantic/Canary",
"Atlantic/Cape_Verde",
- "Atlantic/Faeroe",
"Atlantic/Faroe",
"Atlantic/Jan_Mayen",
"Atlantic/Madeira",
@@ -450,7 +426,6 @@
"Atlantic/South_Georgia",
"Atlantic/St_Helena",
"Atlantic/Stanley",
- "Australia/ACT",
"Australia/Adelaide",
"Australia/Brisbane",
"Australia/Broken_Hill",
@@ -459,42 +434,12 @@
"Australia/Darwin",
"Australia/Eucla",
"Australia/Hobart",
- "Australia/LHI",
"Australia/Lindeman",
"Australia/Lord_Howe",
"Australia/Melbourne",
- "Australia/NSW",
- "Australia/North",
"Australia/Perth",
- "Australia/Queensland",
- "Australia/South",
"Australia/Sydney",
- "Australia/Tasmania",
- "Australia/Victoria",
- "Australia/West",
"Australia/Yancowinna",
- "Brazil/Acre",
- "Brazil/DeNoronha",
- "Brazil/East",
- "Brazil/West",
- "CET",
- "CST6CDT",
- "Canada/Atlantic",
- "Canada/Central",
- "Canada/Eastern",
- "Canada/Mountain",
- "Canada/Newfoundland",
- "Canada/Pacific",
- "Canada/Saskatchewan",
- "Canada/Yukon",
- "Chile/Continental",
- "Chile/EasterIsland",
- "Cuba",
- "EET",
- "EST",
- "EST5EDT",
- "Egypt",
- "Eire",
"Etc/GMT",
"Etc/GMT+0",
"Etc/GMT+1",
@@ -552,8 +497,8 @@
"Europe/Istanbul",
"Europe/Jersey",
"Europe/Kaliningrad",
- "Europe/Kiev",
"Europe/Kirov",
+ "Europe/Kyiv",
"Europe/Lisbon",
"Europe/Ljubljana",
"Europe/London",
@@ -583,7 +528,6 @@
"Europe/Tirane",
"Europe/Tiraspol",
"Europe/Ulyanovsk",
- "Europe/Uzhgorod",
"Europe/Vaduz",
"Europe/Vatican",
"Europe/Vienna",
@@ -591,18 +535,8 @@
"Europe/Volgograd",
"Europe/Warsaw",
"Europe/Zagreb",
- "Europe/Zaporozhye",
"Europe/Zurich",
- "GB",
- "GB-Eire",
- "GMT",
- "GMT+0",
- "GMT-0",
- "GMT0",
- "Greenwich",
- "HST",
- "Hongkong",
- "Iceland",
+ "Factory",
"Indian/Antananarivo",
"Indian/Chagos",
"Indian/Christmas",
@@ -614,23 +548,6 @@
"Indian/Mauritius",
"Indian/Mayotte",
"Indian/Reunion",
- "Iran",
- "Israel",
- "Jamaica",
- "Japan",
- "Kwajalein",
- "Libya",
- "MET",
- "MST",
- "MST7MDT",
- "Mexico/BajaNorte",
- "Mexico/BajaSur",
- "Mexico/General",
- "NZ",
- "NZ-CHAT",
- "Navajo",
- "PRC",
- "PST8PDT",
"Pacific/Apia",
"Pacific/Auckland",
"Pacific/Bougainville",
@@ -638,7 +555,6 @@
"Pacific/Chuuk",
"Pacific/Easter",
"Pacific/Efate",
- "Pacific/Enderbury",
"Pacific/Fakaofo",
"Pacific/Fiji",
"Pacific/Funafuti",
@@ -648,6 +564,7 @@
"Pacific/Guam",
"Pacific/Honolulu",
"Pacific/Johnston",
+ "Pacific/Kanton",
"Pacific/Kiritimati",
"Pacific/Kosrae",
"Pacific/Kwajalein",
@@ -662,7 +579,6 @@
"Pacific/Palau",
"Pacific/Pitcairn",
"Pacific/Pohnpei",
- "Pacific/Ponape",
"Pacific/Port_Moresby",
"Pacific/Rarotonga",
"Pacific/Saipan",
@@ -670,34 +586,10 @@
"Pacific/Tahiti",
"Pacific/Tarawa",
"Pacific/Tongatapu",
- "Pacific/Truk",
"Pacific/Wake",
"Pacific/Wallis",
"Pacific/Yap",
- "Poland",
- "Portugal",
- "ROC",
- "ROK",
- "Singapore",
- "Turkey",
- "UCT",
- "US/Alaska",
- "US/Aleutian",
- "US/Arizona",
- "US/Central",
- "US/East-Indiana",
- "US/Eastern",
- "US/Hawaii",
- "US/Indiana-Starke",
- "US/Michigan",
- "US/Mountain",
- "US/Pacific",
- "US/Samoa",
"UTC",
- "Universal",
- "W-SU",
- "WET",
- "Zulu",
nullptr};
std::vector<std::string> AllTimeZoneNames() {
diff --git a/absl/time/internal/cctz/src/time_zone_fixed.cc b/absl/time/internal/cctz/src/time_zone_fixed.cc
index f2b3294..e09654e 100644
--- a/absl/time/internal/cctz/src/time_zone_fixed.cc
+++ b/absl/time/internal/cctz/src/time_zone_fixed.cc
@@ -105,7 +105,7 @@
offset_minutes %= 60;
const std::size_t prefix_len = sizeof(kFixedZonePrefix) - 1;
char buf[prefix_len + sizeof("-24:00:00")];
- char* ep = std::copy(kFixedZonePrefix, kFixedZonePrefix + prefix_len, buf);
+ char* ep = std::copy_n(kFixedZonePrefix, prefix_len, buf);
*ep++ = sign;
ep = Format02d(ep, offset_hours);
*ep++ = ':';
diff --git a/absl/time/internal/cctz/src/time_zone_format.cc b/absl/time/internal/cctz/src/time_zone_format.cc
index d8cb047..e7e30a2 100644
--- a/absl/time/internal/cctz/src/time_zone_format.cc
+++ b/absl/time/internal/cctz/src/time_zone_format.cc
@@ -13,14 +13,14 @@
// limitations under the License.
#if !defined(HAS_STRPTIME)
-#if !defined(_MSC_VER) && !defined(__MINGW32__)
-#define HAS_STRPTIME 1 // assume everyone has strptime() except windows
+#if !defined(_MSC_VER) && !defined(__MINGW32__) && !defined(__VXWORKS__)
+#define HAS_STRPTIME 1 // Assume everyone else has strptime().
#endif
#endif
#if defined(HAS_STRPTIME) && HAS_STRPTIME
-#if !defined(_XOPEN_SOURCE)
-#define _XOPEN_SOURCE // Definedness suffices for strptime.
+#if !defined(_XOPEN_SOURCE) && !defined(__FreeBSD__) && !defined(__OpenBSD__)
+#define _XOPEN_SOURCE 500 // Exposes definitions for SUSv2 (UNIX 98).
#endif
#endif
diff --git a/absl/time/internal/cctz/src/time_zone_format_test.cc b/absl/time/internal/cctz/src/time_zone_format_test.cc
index 294f2e2..4a6c71f 100644
--- a/absl/time/internal/cctz/src/time_zone_format_test.cc
+++ b/absl/time/internal/cctz/src/time_zone_format_test.cc
@@ -13,15 +13,20 @@
// limitations under the License.
#include <chrono>
+#include <cstdint>
#include <iomanip>
#include <sstream>
#include <string>
+#include "absl/base/config.h"
+#include "absl/time/internal/cctz/include/cctz/time_zone.h"
+#if defined(__linux__)
+#include <features.h>
+#endif
+
#include "gmock/gmock.h"
#include "gtest/gtest.h"
-#include "absl/base/config.h"
#include "absl/time/internal/cctz/include/cctz/civil_time.h"
-#include "absl/time/internal/cctz/include/cctz/time_zone.h"
namespace chrono = std::chrono;
@@ -59,10 +64,13 @@
template <typename D>
void TestFormatSpecifier(time_point<D> tp, time_zone tz, const std::string& fmt,
const std::string& ans) {
- EXPECT_EQ(ans, format(fmt, tp, tz)) << fmt;
- EXPECT_EQ("xxx " + ans, format("xxx " + fmt, tp, tz));
- EXPECT_EQ(ans + " yyy", format(fmt + " yyy", tp, tz));
- EXPECT_EQ("xxx " + ans + " yyy", format("xxx " + fmt + " yyy", tp, tz));
+ EXPECT_EQ(ans, absl::time_internal::cctz::format(fmt, tp, tz)) << fmt;
+ EXPECT_EQ("xxx " + ans,
+ absl::time_internal::cctz::format("xxx " + fmt, tp, tz));
+ EXPECT_EQ(ans + " yyy",
+ absl::time_internal::cctz::format(fmt + " yyy", tp, tz));
+ EXPECT_EQ("xxx " + ans + " yyy",
+ absl::time_internal::cctz::format("xxx " + fmt + " yyy", tp, tz));
}
} // namespace
@@ -78,26 +86,29 @@
chrono::system_clock::from_time_t(1420167845) +
chrono::milliseconds(123) + chrono::microseconds(456) +
chrono::nanoseconds(789);
- EXPECT_EQ(
- "03:04:05.123456789",
- format(kFmt, chrono::time_point_cast<chrono::nanoseconds>(t0), utc));
- EXPECT_EQ(
- "03:04:05.123456",
- format(kFmt, chrono::time_point_cast<chrono::microseconds>(t0), utc));
- EXPECT_EQ(
- "03:04:05.123",
- format(kFmt, chrono::time_point_cast<chrono::milliseconds>(t0), utc));
+ EXPECT_EQ("03:04:05.123456789",
+ absl::time_internal::cctz::format(
+ kFmt, chrono::time_point_cast<chrono::nanoseconds>(t0), utc));
+ EXPECT_EQ("03:04:05.123456",
+ absl::time_internal::cctz::format(
+ kFmt, chrono::time_point_cast<chrono::microseconds>(t0), utc));
+ EXPECT_EQ("03:04:05.123",
+ absl::time_internal::cctz::format(
+ kFmt, chrono::time_point_cast<chrono::milliseconds>(t0), utc));
EXPECT_EQ("03:04:05",
- format(kFmt, chrono::time_point_cast<chrono::seconds>(t0), utc));
+ absl::time_internal::cctz::format(
+ kFmt, chrono::time_point_cast<chrono::seconds>(t0), utc));
EXPECT_EQ(
"03:04:05",
- format(kFmt,
- chrono::time_point_cast<absl::time_internal::cctz::seconds>(t0),
- utc));
+ absl::time_internal::cctz::format(
+ kFmt, chrono::time_point_cast<absl::time_internal::cctz::seconds>(t0),
+ utc));
EXPECT_EQ("03:04:00",
- format(kFmt, chrono::time_point_cast<chrono::minutes>(t0), utc));
+ absl::time_internal::cctz::format(
+ kFmt, chrono::time_point_cast<chrono::minutes>(t0), utc));
EXPECT_EQ("03:00:00",
- format(kFmt, chrono::time_point_cast<chrono::hours>(t0), utc));
+ absl::time_internal::cctz::format(
+ kFmt, chrono::time_point_cast<chrono::hours>(t0), utc));
}
TEST(Format, TimePointExtendedResolution) {
@@ -132,24 +143,28 @@
time_point<chrono::nanoseconds> tp = chrono::system_clock::from_time_t(0);
// Starts with a couple basic edge cases.
- EXPECT_EQ("", format("", tp, tz));
- EXPECT_EQ(" ", format(" ", tp, tz));
- EXPECT_EQ(" ", format(" ", tp, tz));
- EXPECT_EQ("xxx", format("xxx", tp, tz));
+ EXPECT_EQ("", absl::time_internal::cctz::format("", tp, tz));
+ EXPECT_EQ(" ", absl::time_internal::cctz::format(" ", tp, tz));
+ EXPECT_EQ(" ", absl::time_internal::cctz::format(" ", tp, tz));
+ EXPECT_EQ("xxx", absl::time_internal::cctz::format("xxx", tp, tz));
std::string big(128, 'x');
- EXPECT_EQ(big, format(big, tp, tz));
+ EXPECT_EQ(big, absl::time_internal::cctz::format(big, tp, tz));
// Cause the 1024-byte buffer to grow.
std::string bigger(100000, 'x');
- EXPECT_EQ(bigger, format(bigger, tp, tz));
+ EXPECT_EQ(bigger, absl::time_internal::cctz::format(bigger, tp, tz));
tp += chrono::hours(13) + chrono::minutes(4) + chrono::seconds(5);
tp += chrono::milliseconds(6) + chrono::microseconds(7) +
chrono::nanoseconds(8);
- EXPECT_EQ("1970-01-01", format("%Y-%m-%d", tp, tz));
- EXPECT_EQ("13:04:05", format("%H:%M:%S", tp, tz));
- EXPECT_EQ("13:04:05.006", format("%H:%M:%E3S", tp, tz));
- EXPECT_EQ("13:04:05.006007", format("%H:%M:%E6S", tp, tz));
- EXPECT_EQ("13:04:05.006007008", format("%H:%M:%E9S", tp, tz));
+ EXPECT_EQ("1970-01-01",
+ absl::time_internal::cctz::format("%Y-%m-%d", tp, tz));
+ EXPECT_EQ("13:04:05", absl::time_internal::cctz::format("%H:%M:%S", tp, tz));
+ EXPECT_EQ("13:04:05.006",
+ absl::time_internal::cctz::format("%H:%M:%E3S", tp, tz));
+ EXPECT_EQ("13:04:05.006007",
+ absl::time_internal::cctz::format("%H:%M:%E6S", tp, tz));
+ EXPECT_EQ("13:04:05.006007008",
+ absl::time_internal::cctz::format("%H:%M:%E9S", tp, tz));
}
TEST(Format, PosixConversions) {
@@ -182,8 +197,10 @@
TestFormatSpecifier(tp, tz, "%F", "1970-01-01");
TestFormatSpecifier(tp, tz, "%g", "70");
TestFormatSpecifier(tp, tz, "%G", "1970");
+#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, "%t", "\t");
@@ -204,7 +221,8 @@
TestFormatSpecifier(tp, tz, "%B", "January");
// %c should at least produce the numeric year and time-of-day.
- const std::string s = format("%c", tp, utc_time_zone());
+ const std::string s =
+ absl::time_internal::cctz::format("%c", tp, utc_time_zone());
EXPECT_THAT(s, testing::HasSubstr("1970"));
EXPECT_THAT(s, testing::HasSubstr("00:00:00"));
@@ -215,7 +233,9 @@
#if defined(__linux__)
// SU/C99/TZ extensions
TestFormatSpecifier(tp, tz, "%h", "Jan"); // Same as %b
+#if defined(__GLIBC__)
TestFormatSpecifier(tp, tz, "%P", "am");
+#endif
TestFormatSpecifier(tp, tz, "%r", "12:00:00 AM");
// Modified conversion specifiers %E_
@@ -268,49 +288,61 @@
// No subseconds.
time_point<chrono::nanoseconds> tp = chrono::system_clock::from_time_t(0);
tp += chrono::seconds(5);
- EXPECT_EQ("05", format("%E*S", tp, tz));
- EXPECT_EQ("05", format("%E0S", tp, tz));
- EXPECT_EQ("05.0", format("%E1S", tp, tz));
- EXPECT_EQ("05.00", format("%E2S", tp, tz));
- EXPECT_EQ("05.000", format("%E3S", tp, tz));
- EXPECT_EQ("05.0000", format("%E4S", tp, tz));
- EXPECT_EQ("05.00000", format("%E5S", tp, tz));
- EXPECT_EQ("05.000000", format("%E6S", tp, tz));
- EXPECT_EQ("05.0000000", format("%E7S", tp, tz));
- EXPECT_EQ("05.00000000", format("%E8S", tp, tz));
- EXPECT_EQ("05.000000000", format("%E9S", tp, tz));
- EXPECT_EQ("05.0000000000", format("%E10S", tp, tz));
- EXPECT_EQ("05.00000000000", format("%E11S", tp, tz));
- EXPECT_EQ("05.000000000000", format("%E12S", tp, tz));
- EXPECT_EQ("05.0000000000000", format("%E13S", tp, tz));
- EXPECT_EQ("05.00000000000000", format("%E14S", tp, tz));
- EXPECT_EQ("05.000000000000000", format("%E15S", tp, tz));
+ EXPECT_EQ("05", absl::time_internal::cctz::format("%E*S", tp, tz));
+ EXPECT_EQ("05", absl::time_internal::cctz::format("%E0S", tp, tz));
+ EXPECT_EQ("05.0", absl::time_internal::cctz::format("%E1S", tp, tz));
+ EXPECT_EQ("05.00", absl::time_internal::cctz::format("%E2S", tp, tz));
+ EXPECT_EQ("05.000", absl::time_internal::cctz::format("%E3S", tp, tz));
+ EXPECT_EQ("05.0000", absl::time_internal::cctz::format("%E4S", tp, tz));
+ EXPECT_EQ("05.00000", absl::time_internal::cctz::format("%E5S", tp, tz));
+ EXPECT_EQ("05.000000", absl::time_internal::cctz::format("%E6S", tp, tz));
+ EXPECT_EQ("05.0000000", absl::time_internal::cctz::format("%E7S", tp, tz));
+ EXPECT_EQ("05.00000000", absl::time_internal::cctz::format("%E8S", tp, tz));
+ EXPECT_EQ("05.000000000", absl::time_internal::cctz::format("%E9S", tp, tz));
+ EXPECT_EQ("05.0000000000",
+ absl::time_internal::cctz::format("%E10S", tp, tz));
+ EXPECT_EQ("05.00000000000",
+ absl::time_internal::cctz::format("%E11S", tp, tz));
+ EXPECT_EQ("05.000000000000",
+ absl::time_internal::cctz::format("%E12S", tp, tz));
+ EXPECT_EQ("05.0000000000000",
+ absl::time_internal::cctz::format("%E13S", tp, tz));
+ EXPECT_EQ("05.00000000000000",
+ absl::time_internal::cctz::format("%E14S", tp, tz));
+ EXPECT_EQ("05.000000000000000",
+ absl::time_internal::cctz::format("%E15S", tp, tz));
// With subseconds.
tp += chrono::milliseconds(6) + chrono::microseconds(7) +
chrono::nanoseconds(8);
- EXPECT_EQ("05.006007008", format("%E*S", tp, tz));
- EXPECT_EQ("05", format("%E0S", tp, tz));
- EXPECT_EQ("05.0", format("%E1S", tp, tz));
- EXPECT_EQ("05.00", format("%E2S", tp, tz));
- EXPECT_EQ("05.006", format("%E3S", tp, tz));
- EXPECT_EQ("05.0060", format("%E4S", tp, tz));
- EXPECT_EQ("05.00600", format("%E5S", tp, tz));
- EXPECT_EQ("05.006007", format("%E6S", tp, tz));
- EXPECT_EQ("05.0060070", format("%E7S", tp, tz));
- EXPECT_EQ("05.00600700", format("%E8S", tp, tz));
- EXPECT_EQ("05.006007008", format("%E9S", tp, tz));
- EXPECT_EQ("05.0060070080", format("%E10S", tp, tz));
- EXPECT_EQ("05.00600700800", format("%E11S", tp, tz));
- EXPECT_EQ("05.006007008000", format("%E12S", tp, tz));
- EXPECT_EQ("05.0060070080000", format("%E13S", tp, tz));
- EXPECT_EQ("05.00600700800000", format("%E14S", tp, tz));
- EXPECT_EQ("05.006007008000000", format("%E15S", tp, tz));
+ EXPECT_EQ("05.006007008", absl::time_internal::cctz::format("%E*S", tp, tz));
+ EXPECT_EQ("05", absl::time_internal::cctz::format("%E0S", tp, tz));
+ EXPECT_EQ("05.0", absl::time_internal::cctz::format("%E1S", tp, tz));
+ EXPECT_EQ("05.00", absl::time_internal::cctz::format("%E2S", tp, tz));
+ EXPECT_EQ("05.006", absl::time_internal::cctz::format("%E3S", tp, tz));
+ EXPECT_EQ("05.0060", absl::time_internal::cctz::format("%E4S", tp, tz));
+ EXPECT_EQ("05.00600", absl::time_internal::cctz::format("%E5S", tp, tz));
+ EXPECT_EQ("05.006007", absl::time_internal::cctz::format("%E6S", tp, tz));
+ EXPECT_EQ("05.0060070", absl::time_internal::cctz::format("%E7S", tp, tz));
+ EXPECT_EQ("05.00600700", absl::time_internal::cctz::format("%E8S", tp, tz));
+ EXPECT_EQ("05.006007008", absl::time_internal::cctz::format("%E9S", tp, tz));
+ EXPECT_EQ("05.0060070080",
+ absl::time_internal::cctz::format("%E10S", tp, tz));
+ EXPECT_EQ("05.00600700800",
+ absl::time_internal::cctz::format("%E11S", tp, tz));
+ EXPECT_EQ("05.006007008000",
+ absl::time_internal::cctz::format("%E12S", tp, tz));
+ EXPECT_EQ("05.0060070080000",
+ absl::time_internal::cctz::format("%E13S", tp, tz));
+ EXPECT_EQ("05.00600700800000",
+ absl::time_internal::cctz::format("%E14S", tp, tz));
+ EXPECT_EQ("05.006007008000000",
+ absl::time_internal::cctz::format("%E15S", tp, tz));
// Times before the Unix epoch.
tp = chrono::system_clock::from_time_t(0) + chrono::microseconds(-1);
EXPECT_EQ("1969-12-31 23:59:59.999999",
- format("%Y-%m-%d %H:%M:%E*S", tp, tz));
+ absl::time_internal::cctz::format("%Y-%m-%d %H:%M:%E*S", tp, tz));
// Here is a "%E*S" case we got wrong for a while. While the first
// instant below is correctly rendered as "...:07.333304", the second
@@ -318,10 +350,10 @@
tp = chrono::system_clock::from_time_t(0) +
chrono::microseconds(1395024427333304);
EXPECT_EQ("2014-03-17 02:47:07.333304",
- format("%Y-%m-%d %H:%M:%E*S", tp, tz));
+ absl::time_internal::cctz::format("%Y-%m-%d %H:%M:%E*S", tp, tz));
tp += chrono::microseconds(1);
EXPECT_EQ("2014-03-17 02:47:07.333305",
- format("%Y-%m-%d %H:%M:%E*S", tp, tz));
+ absl::time_internal::cctz::format("%Y-%m-%d %H:%M:%E*S", tp, tz));
}
TEST(Format, ExtendedSubeconds) {
@@ -330,60 +362,69 @@
// No subseconds.
time_point<chrono::nanoseconds> tp = chrono::system_clock::from_time_t(0);
tp += chrono::seconds(5);
- EXPECT_EQ("0", format("%E*f", tp, tz));
- EXPECT_EQ("", format("%E0f", tp, tz));
- EXPECT_EQ("0", format("%E1f", tp, tz));
- EXPECT_EQ("00", format("%E2f", tp, tz));
- EXPECT_EQ("000", format("%E3f", tp, tz));
- EXPECT_EQ("0000", format("%E4f", tp, tz));
- EXPECT_EQ("00000", format("%E5f", tp, tz));
- EXPECT_EQ("000000", format("%E6f", tp, tz));
- EXPECT_EQ("0000000", format("%E7f", tp, tz));
- EXPECT_EQ("00000000", format("%E8f", tp, tz));
- EXPECT_EQ("000000000", format("%E9f", tp, tz));
- EXPECT_EQ("0000000000", format("%E10f", tp, tz));
- EXPECT_EQ("00000000000", format("%E11f", tp, tz));
- EXPECT_EQ("000000000000", format("%E12f", tp, tz));
- EXPECT_EQ("0000000000000", format("%E13f", tp, tz));
- EXPECT_EQ("00000000000000", format("%E14f", tp, tz));
- EXPECT_EQ("000000000000000", format("%E15f", tp, tz));
+ EXPECT_EQ("0", absl::time_internal::cctz::format("%E*f", tp, tz));
+ EXPECT_EQ("", absl::time_internal::cctz::format("%E0f", tp, tz));
+ EXPECT_EQ("0", absl::time_internal::cctz::format("%E1f", tp, tz));
+ EXPECT_EQ("00", absl::time_internal::cctz::format("%E2f", tp, tz));
+ EXPECT_EQ("000", absl::time_internal::cctz::format("%E3f", tp, tz));
+ EXPECT_EQ("0000", absl::time_internal::cctz::format("%E4f", tp, tz));
+ EXPECT_EQ("00000", absl::time_internal::cctz::format("%E5f", tp, tz));
+ EXPECT_EQ("000000", absl::time_internal::cctz::format("%E6f", tp, tz));
+ EXPECT_EQ("0000000", absl::time_internal::cctz::format("%E7f", tp, tz));
+ EXPECT_EQ("00000000", absl::time_internal::cctz::format("%E8f", tp, tz));
+ EXPECT_EQ("000000000", absl::time_internal::cctz::format("%E9f", tp, tz));
+ EXPECT_EQ("0000000000", absl::time_internal::cctz::format("%E10f", tp, tz));
+ EXPECT_EQ("00000000000", absl::time_internal::cctz::format("%E11f", tp, tz));
+ EXPECT_EQ("000000000000", absl::time_internal::cctz::format("%E12f", tp, tz));
+ EXPECT_EQ("0000000000000",
+ absl::time_internal::cctz::format("%E13f", tp, tz));
+ EXPECT_EQ("00000000000000",
+ absl::time_internal::cctz::format("%E14f", tp, tz));
+ EXPECT_EQ("000000000000000",
+ absl::time_internal::cctz::format("%E15f", tp, tz));
// With subseconds.
tp += chrono::milliseconds(6) + chrono::microseconds(7) +
chrono::nanoseconds(8);
- EXPECT_EQ("006007008", format("%E*f", tp, tz));
- EXPECT_EQ("", format("%E0f", tp, tz));
- EXPECT_EQ("0", format("%E1f", tp, tz));
- EXPECT_EQ("00", format("%E2f", tp, tz));
- EXPECT_EQ("006", format("%E3f", tp, tz));
- EXPECT_EQ("0060", format("%E4f", tp, tz));
- EXPECT_EQ("00600", format("%E5f", tp, tz));
- EXPECT_EQ("006007", format("%E6f", tp, tz));
- EXPECT_EQ("0060070", format("%E7f", tp, tz));
- EXPECT_EQ("00600700", format("%E8f", tp, tz));
- EXPECT_EQ("006007008", format("%E9f", tp, tz));
- EXPECT_EQ("0060070080", format("%E10f", tp, tz));
- EXPECT_EQ("00600700800", format("%E11f", tp, tz));
- EXPECT_EQ("006007008000", format("%E12f", tp, tz));
- EXPECT_EQ("0060070080000", format("%E13f", tp, tz));
- EXPECT_EQ("00600700800000", format("%E14f", tp, tz));
- EXPECT_EQ("006007008000000", format("%E15f", tp, tz));
+ EXPECT_EQ("006007008", absl::time_internal::cctz::format("%E*f", tp, tz));
+ EXPECT_EQ("", absl::time_internal::cctz::format("%E0f", tp, tz));
+ EXPECT_EQ("0", absl::time_internal::cctz::format("%E1f", tp, tz));
+ EXPECT_EQ("00", absl::time_internal::cctz::format("%E2f", tp, tz));
+ EXPECT_EQ("006", absl::time_internal::cctz::format("%E3f", tp, tz));
+ EXPECT_EQ("0060", absl::time_internal::cctz::format("%E4f", tp, tz));
+ EXPECT_EQ("00600", absl::time_internal::cctz::format("%E5f", tp, tz));
+ EXPECT_EQ("006007", absl::time_internal::cctz::format("%E6f", tp, tz));
+ EXPECT_EQ("0060070", absl::time_internal::cctz::format("%E7f", tp, tz));
+ EXPECT_EQ("00600700", absl::time_internal::cctz::format("%E8f", tp, tz));
+ EXPECT_EQ("006007008", absl::time_internal::cctz::format("%E9f", tp, tz));
+ EXPECT_EQ("0060070080", absl::time_internal::cctz::format("%E10f", tp, tz));
+ EXPECT_EQ("00600700800", absl::time_internal::cctz::format("%E11f", tp, tz));
+ EXPECT_EQ("006007008000", absl::time_internal::cctz::format("%E12f", tp, tz));
+ EXPECT_EQ("0060070080000",
+ absl::time_internal::cctz::format("%E13f", tp, tz));
+ EXPECT_EQ("00600700800000",
+ absl::time_internal::cctz::format("%E14f", tp, tz));
+ EXPECT_EQ("006007008000000",
+ absl::time_internal::cctz::format("%E15f", tp, tz));
// Times before the Unix epoch.
tp = chrono::system_clock::from_time_t(0) + chrono::microseconds(-1);
- EXPECT_EQ("1969-12-31 23:59:59.999999",
- format("%Y-%m-%d %H:%M:%S.%E*f", tp, tz));
+ EXPECT_EQ(
+ "1969-12-31 23:59:59.999999",
+ absl::time_internal::cctz::format("%Y-%m-%d %H:%M:%S.%E*f", tp, tz));
// Here is a "%E*S" case we got wrong for a while. While the first
// instant below is correctly rendered as "...:07.333304", the second
// one used to appear as "...:07.33330499999999999".
tp = chrono::system_clock::from_time_t(0) +
chrono::microseconds(1395024427333304);
- EXPECT_EQ("2014-03-17 02:47:07.333304",
- format("%Y-%m-%d %H:%M:%S.%E*f", tp, tz));
+ EXPECT_EQ(
+ "2014-03-17 02:47:07.333304",
+ absl::time_internal::cctz::format("%Y-%m-%d %H:%M:%S.%E*f", tp, tz));
tp += chrono::microseconds(1);
- EXPECT_EQ("2014-03-17 02:47:07.333305",
- format("%Y-%m-%d %H:%M:%S.%E*f", tp, tz));
+ EXPECT_EQ(
+ "2014-03-17 02:47:07.333305",
+ absl::time_internal::cctz::format("%Y-%m-%d %H:%M:%S.%E*f", tp, tz));
}
TEST(Format, CompareExtendSecondsVsSubseconds) {
@@ -399,15 +440,17 @@
time_point<chrono::nanoseconds> tp = chrono::system_clock::from_time_t(0);
tp += chrono::seconds(5);
// ... %E*S and %S.%E*f are different.
- EXPECT_EQ("05", format(fmt_A("*"), tp, tz));
- EXPECT_EQ("05.0", format(fmt_B("*"), tp, tz));
+ EXPECT_EQ("05", absl::time_internal::cctz::format(fmt_A("*"), tp, tz));
+ EXPECT_EQ("05.0", absl::time_internal::cctz::format(fmt_B("*"), tp, tz));
// ... %E0S and %S.%E0f are different.
- EXPECT_EQ("05", format(fmt_A("0"), tp, tz));
- EXPECT_EQ("05.", format(fmt_B("0"), tp, tz));
+ EXPECT_EQ("05", absl::time_internal::cctz::format(fmt_A("0"), tp, tz));
+ EXPECT_EQ("05.", absl::time_internal::cctz::format(fmt_B("0"), tp, tz));
// ... %E<prec>S and %S.%E<prec>f are the same for prec in [1:15].
for (int prec = 1; prec <= 15; ++prec) {
- const std::string a = format(fmt_A(std::to_string(prec)), tp, tz);
- const std::string b = format(fmt_B(std::to_string(prec)), tp, tz);
+ const std::string a =
+ absl::time_internal::cctz::format(fmt_A(std::to_string(prec)), tp, tz);
+ const std::string b =
+ absl::time_internal::cctz::format(fmt_B(std::to_string(prec)), tp, tz);
EXPECT_EQ(a, b) << "prec=" << prec;
}
@@ -415,15 +458,19 @@
// ... %E*S and %S.%E*f are the same.
tp += chrono::milliseconds(6) + chrono::microseconds(7) +
chrono::nanoseconds(8);
- EXPECT_EQ("05.006007008", format(fmt_A("*"), tp, tz));
- EXPECT_EQ("05.006007008", format(fmt_B("*"), tp, tz));
+ EXPECT_EQ("05.006007008",
+ absl::time_internal::cctz::format(fmt_A("*"), tp, tz));
+ EXPECT_EQ("05.006007008",
+ absl::time_internal::cctz::format(fmt_B("*"), tp, tz));
// ... %E0S and %S.%E0f are different.
- EXPECT_EQ("05", format(fmt_A("0"), tp, tz));
- EXPECT_EQ("05.", format(fmt_B("0"), tp, tz));
+ EXPECT_EQ("05", absl::time_internal::cctz::format(fmt_A("0"), tp, tz));
+ EXPECT_EQ("05.", absl::time_internal::cctz::format(fmt_B("0"), tp, tz));
// ... %E<prec>S and %S.%E<prec>f are the same for prec in [1:15].
for (int prec = 1; prec <= 15; ++prec) {
- const std::string a = format(fmt_A(std::to_string(prec)), tp, tz);
- const std::string b = format(fmt_B(std::to_string(prec)), tp, tz);
+ const std::string a =
+ absl::time_internal::cctz::format(fmt_A(std::to_string(prec)), tp, tz);
+ const std::string b =
+ absl::time_internal::cctz::format(fmt_B(std::to_string(prec)), tp, tz);
EXPECT_EQ(a, b) << "prec=" << prec;
}
}
@@ -596,31 +643,31 @@
// %E4Y zero-pads the year to produce at least 4 chars, including the sign.
auto tp = convert(civil_second(-999, 11, 27, 0, 0, 0), utc);
- EXPECT_EQ("-9991127", format(e4y_fmt, tp, utc));
+ EXPECT_EQ("-9991127", absl::time_internal::cctz::format(e4y_fmt, tp, utc));
tp = convert(civil_second(-99, 11, 27, 0, 0, 0), utc);
- EXPECT_EQ("-0991127", format(e4y_fmt, tp, utc));
+ EXPECT_EQ("-0991127", absl::time_internal::cctz::format(e4y_fmt, tp, utc));
tp = convert(civil_second(-9, 11, 27, 0, 0, 0), utc);
- EXPECT_EQ("-0091127", format(e4y_fmt, tp, utc));
+ EXPECT_EQ("-0091127", absl::time_internal::cctz::format(e4y_fmt, tp, utc));
tp = convert(civil_second(-1, 11, 27, 0, 0, 0), utc);
- EXPECT_EQ("-0011127", format(e4y_fmt, tp, utc));
+ EXPECT_EQ("-0011127", absl::time_internal::cctz::format(e4y_fmt, tp, utc));
tp = convert(civil_second(0, 11, 27, 0, 0, 0), utc);
- EXPECT_EQ("00001127", format(e4y_fmt, tp, utc));
+ EXPECT_EQ("00001127", absl::time_internal::cctz::format(e4y_fmt, tp, utc));
tp = convert(civil_second(1, 11, 27, 0, 0, 0), utc);
- EXPECT_EQ("00011127", format(e4y_fmt, tp, utc));
+ EXPECT_EQ("00011127", absl::time_internal::cctz::format(e4y_fmt, tp, utc));
tp = convert(civil_second(9, 11, 27, 0, 0, 0), utc);
- EXPECT_EQ("00091127", format(e4y_fmt, tp, utc));
+ EXPECT_EQ("00091127", absl::time_internal::cctz::format(e4y_fmt, tp, utc));
tp = convert(civil_second(99, 11, 27, 0, 0, 0), utc);
- EXPECT_EQ("00991127", format(e4y_fmt, tp, utc));
+ EXPECT_EQ("00991127", absl::time_internal::cctz::format(e4y_fmt, tp, utc));
tp = convert(civil_second(999, 11, 27, 0, 0, 0), utc);
- EXPECT_EQ("09991127", format(e4y_fmt, tp, utc));
+ EXPECT_EQ("09991127", absl::time_internal::cctz::format(e4y_fmt, tp, utc));
tp = convert(civil_second(9999, 11, 27, 0, 0, 0), utc);
- EXPECT_EQ("99991127", format(e4y_fmt, tp, utc));
+ EXPECT_EQ("99991127", absl::time_internal::cctz::format(e4y_fmt, tp, utc));
// When the year is outside [-999:9999], more than 4 chars are produced.
tp = convert(civil_second(-1000, 11, 27, 0, 0, 0), utc);
- EXPECT_EQ("-10001127", format(e4y_fmt, tp, utc));
+ EXPECT_EQ("-10001127", absl::time_internal::cctz::format(e4y_fmt, tp, utc));
tp = convert(civil_second(10000, 11, 27, 0, 0, 0), utc);
- EXPECT_EQ("100001127", format(e4y_fmt, tp, utc));
+ EXPECT_EQ("100001127", absl::time_internal::cctz::format(e4y_fmt, tp, utc));
}
TEST(Format, RFC3339Format) {
@@ -629,45 +676,64 @@
time_point<chrono::nanoseconds> tp =
convert(civil_second(1977, 6, 28, 9, 8, 7), tz);
- EXPECT_EQ("1977-06-28T09:08:07-07:00", format(RFC3339_full, tp, tz));
- EXPECT_EQ("1977-06-28T09:08:07-07:00", format(RFC3339_sec, tp, tz));
+ EXPECT_EQ("1977-06-28T09:08:07-07:00",
+ absl::time_internal::cctz::format(RFC3339_full, tp, tz));
+ EXPECT_EQ("1977-06-28T09:08:07-07:00",
+ absl::time_internal::cctz::format(RFC3339_sec, tp, tz));
tp += chrono::milliseconds(100);
- EXPECT_EQ("1977-06-28T09:08:07.1-07:00", format(RFC3339_full, tp, tz));
- EXPECT_EQ("1977-06-28T09:08:07-07:00", format(RFC3339_sec, tp, tz));
+ EXPECT_EQ("1977-06-28T09:08:07.1-07:00",
+ absl::time_internal::cctz::format(RFC3339_full, tp, tz));
+ EXPECT_EQ("1977-06-28T09:08:07-07:00",
+ absl::time_internal::cctz::format(RFC3339_sec, tp, tz));
tp += chrono::milliseconds(20);
- EXPECT_EQ("1977-06-28T09:08:07.12-07:00", format(RFC3339_full, tp, tz));
- EXPECT_EQ("1977-06-28T09:08:07-07:00", format(RFC3339_sec, tp, tz));
+ EXPECT_EQ("1977-06-28T09:08:07.12-07:00",
+ absl::time_internal::cctz::format(RFC3339_full, tp, tz));
+ EXPECT_EQ("1977-06-28T09:08:07-07:00",
+ absl::time_internal::cctz::format(RFC3339_sec, tp, tz));
tp += chrono::milliseconds(3);
- EXPECT_EQ("1977-06-28T09:08:07.123-07:00", format(RFC3339_full, tp, tz));
- EXPECT_EQ("1977-06-28T09:08:07-07:00", format(RFC3339_sec, tp, tz));
+ EXPECT_EQ("1977-06-28T09:08:07.123-07:00",
+ absl::time_internal::cctz::format(RFC3339_full, tp, tz));
+ EXPECT_EQ("1977-06-28T09:08:07-07:00",
+ absl::time_internal::cctz::format(RFC3339_sec, tp, tz));
tp += chrono::microseconds(400);
- EXPECT_EQ("1977-06-28T09:08:07.1234-07:00", format(RFC3339_full, tp, tz));
- EXPECT_EQ("1977-06-28T09:08:07-07:00", format(RFC3339_sec, tp, tz));
+ EXPECT_EQ("1977-06-28T09:08:07.1234-07:00",
+ absl::time_internal::cctz::format(RFC3339_full, tp, tz));
+ EXPECT_EQ("1977-06-28T09:08:07-07:00",
+ absl::time_internal::cctz::format(RFC3339_sec, tp, tz));
tp += chrono::microseconds(50);
- EXPECT_EQ("1977-06-28T09:08:07.12345-07:00", format(RFC3339_full, tp, tz));
- EXPECT_EQ("1977-06-28T09:08:07-07:00", format(RFC3339_sec, tp, tz));
+ EXPECT_EQ("1977-06-28T09:08:07.12345-07:00",
+ absl::time_internal::cctz::format(RFC3339_full, tp, tz));
+ EXPECT_EQ("1977-06-28T09:08:07-07:00",
+ absl::time_internal::cctz::format(RFC3339_sec, tp, tz));
tp += chrono::microseconds(6);
- EXPECT_EQ("1977-06-28T09:08:07.123456-07:00", format(RFC3339_full, tp, tz));
- EXPECT_EQ("1977-06-28T09:08:07-07:00", format(RFC3339_sec, tp, tz));
+ EXPECT_EQ("1977-06-28T09:08:07.123456-07:00",
+ absl::time_internal::cctz::format(RFC3339_full, tp, tz));
+ EXPECT_EQ("1977-06-28T09:08:07-07:00",
+ absl::time_internal::cctz::format(RFC3339_sec, tp, tz));
tp += chrono::nanoseconds(700);
- EXPECT_EQ("1977-06-28T09:08:07.1234567-07:00", format(RFC3339_full, tp, tz));
- EXPECT_EQ("1977-06-28T09:08:07-07:00", format(RFC3339_sec, tp, tz));
+ EXPECT_EQ("1977-06-28T09:08:07.1234567-07:00",
+ absl::time_internal::cctz::format(RFC3339_full, tp, tz));
+ EXPECT_EQ("1977-06-28T09:08:07-07:00",
+ absl::time_internal::cctz::format(RFC3339_sec, tp, tz));
tp += chrono::nanoseconds(80);
- EXPECT_EQ("1977-06-28T09:08:07.12345678-07:00", format(RFC3339_full, tp, tz));
- EXPECT_EQ("1977-06-28T09:08:07-07:00", format(RFC3339_sec, tp, tz));
+ EXPECT_EQ("1977-06-28T09:08:07.12345678-07:00",
+ absl::time_internal::cctz::format(RFC3339_full, tp, tz));
+ EXPECT_EQ("1977-06-28T09:08:07-07:00",
+ absl::time_internal::cctz::format(RFC3339_sec, tp, tz));
tp += chrono::nanoseconds(9);
EXPECT_EQ("1977-06-28T09:08:07.123456789-07:00",
- format(RFC3339_full, tp, tz));
- EXPECT_EQ("1977-06-28T09:08:07-07:00", format(RFC3339_sec, tp, tz));
+ absl::time_internal::cctz::format(RFC3339_full, tp, tz));
+ EXPECT_EQ("1977-06-28T09:08:07-07:00",
+ absl::time_internal::cctz::format(RFC3339_sec, tp, tz));
}
TEST(Format, RFC1123Format) { // locale specific
@@ -675,36 +741,50 @@
EXPECT_TRUE(load_time_zone("America/Los_Angeles", &tz));
auto tp = convert(civil_second(1977, 6, 28, 9, 8, 7), tz);
- EXPECT_EQ("Tue, 28 Jun 1977 09:08:07 -0700", format(RFC1123_full, tp, tz));
- EXPECT_EQ("28 Jun 1977 09:08:07 -0700", format(RFC1123_no_wday, tp, tz));
+ EXPECT_EQ("Tue, 28 Jun 1977 09:08:07 -0700",
+ absl::time_internal::cctz::format(RFC1123_full, tp, tz));
+ EXPECT_EQ("28 Jun 1977 09:08:07 -0700",
+ absl::time_internal::cctz::format(RFC1123_no_wday, tp, tz));
}
TEST(Format, Week) {
const time_zone utc = utc_time_zone();
auto tp = convert(civil_second(2017, 1, 1, 0, 0, 0), utc);
- EXPECT_EQ("2017-01-7", format("%Y-%U-%u", tp, utc));
- EXPECT_EQ("2017-00-0", format("%Y-%W-%w", tp, utc));
+ EXPECT_EQ("2017-01-7",
+ absl::time_internal::cctz::format("%Y-%U-%u", tp, utc));
+ EXPECT_EQ("2017-00-0",
+ absl::time_internal::cctz::format("%Y-%W-%w", tp, utc));
tp = convert(civil_second(2017, 12, 31, 0, 0, 0), utc);
- EXPECT_EQ("2017-53-7", format("%Y-%U-%u", tp, utc));
- EXPECT_EQ("2017-52-0", format("%Y-%W-%w", tp, utc));
+ EXPECT_EQ("2017-53-7",
+ absl::time_internal::cctz::format("%Y-%U-%u", tp, utc));
+ EXPECT_EQ("2017-52-0",
+ absl::time_internal::cctz::format("%Y-%W-%w", tp, utc));
tp = convert(civil_second(2018, 1, 1, 0, 0, 0), utc);
- EXPECT_EQ("2018-00-1", format("%Y-%U-%u", tp, utc));
- EXPECT_EQ("2018-01-1", format("%Y-%W-%w", tp, utc));
+ EXPECT_EQ("2018-00-1",
+ absl::time_internal::cctz::format("%Y-%U-%u", tp, utc));
+ EXPECT_EQ("2018-01-1",
+ absl::time_internal::cctz::format("%Y-%W-%w", tp, utc));
tp = convert(civil_second(2018, 12, 31, 0, 0, 0), utc);
- EXPECT_EQ("2018-52-1", format("%Y-%U-%u", tp, utc));
- EXPECT_EQ("2018-53-1", format("%Y-%W-%w", tp, utc));
+ EXPECT_EQ("2018-52-1",
+ absl::time_internal::cctz::format("%Y-%U-%u", tp, utc));
+ EXPECT_EQ("2018-53-1",
+ absl::time_internal::cctz::format("%Y-%W-%w", tp, utc));
tp = convert(civil_second(2019, 1, 1, 0, 0, 0), utc);
- EXPECT_EQ("2019-00-2", format("%Y-%U-%u", tp, utc));
- EXPECT_EQ("2019-00-2", format("%Y-%W-%w", tp, utc));
+ EXPECT_EQ("2019-00-2",
+ absl::time_internal::cctz::format("%Y-%U-%u", tp, utc));
+ EXPECT_EQ("2019-00-2",
+ absl::time_internal::cctz::format("%Y-%W-%w", tp, utc));
tp = convert(civil_second(2019, 12, 31, 0, 0, 0), utc);
- EXPECT_EQ("2019-52-2", format("%Y-%U-%u", tp, utc));
- EXPECT_EQ("2019-52-2", format("%Y-%W-%w", tp, utc));
+ EXPECT_EQ("2019-52-2",
+ absl::time_internal::cctz::format("%Y-%U-%u", tp, utc));
+ EXPECT_EQ("2019-52-2",
+ absl::time_internal::cctz::format("%Y-%W-%w", tp, utc));
}
//
@@ -717,39 +797,46 @@
time_point<chrono::nanoseconds> tp_ns;
EXPECT_TRUE(parse(kFmt, "03:04:05.123456789", utc, &tp_ns));
- EXPECT_EQ("03:04:05.123456789", format(kFmt, tp_ns, utc));
+ EXPECT_EQ("03:04:05.123456789",
+ absl::time_internal::cctz::format(kFmt, tp_ns, utc));
EXPECT_TRUE(parse(kFmt, "03:04:05.123456", utc, &tp_ns));
- EXPECT_EQ("03:04:05.123456", format(kFmt, tp_ns, utc));
+ EXPECT_EQ("03:04:05.123456",
+ absl::time_internal::cctz::format(kFmt, tp_ns, utc));
time_point<chrono::microseconds> tp_us;
EXPECT_TRUE(parse(kFmt, "03:04:05.123456789", utc, &tp_us));
- EXPECT_EQ("03:04:05.123456", format(kFmt, tp_us, utc));
+ EXPECT_EQ("03:04:05.123456",
+ absl::time_internal::cctz::format(kFmt, tp_us, utc));
EXPECT_TRUE(parse(kFmt, "03:04:05.123456", utc, &tp_us));
- EXPECT_EQ("03:04:05.123456", format(kFmt, tp_us, utc));
+ EXPECT_EQ("03:04:05.123456",
+ absl::time_internal::cctz::format(kFmt, tp_us, utc));
EXPECT_TRUE(parse(kFmt, "03:04:05.123", utc, &tp_us));
- EXPECT_EQ("03:04:05.123", format(kFmt, tp_us, utc));
+ EXPECT_EQ("03:04:05.123",
+ absl::time_internal::cctz::format(kFmt, tp_us, utc));
time_point<chrono::milliseconds> tp_ms;
EXPECT_TRUE(parse(kFmt, "03:04:05.123456", utc, &tp_ms));
- EXPECT_EQ("03:04:05.123", format(kFmt, tp_ms, utc));
+ EXPECT_EQ("03:04:05.123",
+ absl::time_internal::cctz::format(kFmt, tp_ms, utc));
EXPECT_TRUE(parse(kFmt, "03:04:05.123", utc, &tp_ms));
- EXPECT_EQ("03:04:05.123", format(kFmt, tp_ms, utc));
+ EXPECT_EQ("03:04:05.123",
+ absl::time_internal::cctz::format(kFmt, tp_ms, utc));
EXPECT_TRUE(parse(kFmt, "03:04:05", utc, &tp_ms));
- EXPECT_EQ("03:04:05", format(kFmt, tp_ms, utc));
+ EXPECT_EQ("03:04:05", absl::time_internal::cctz::format(kFmt, tp_ms, utc));
time_point<chrono::seconds> tp_s;
EXPECT_TRUE(parse(kFmt, "03:04:05.123", utc, &tp_s));
- EXPECT_EQ("03:04:05", format(kFmt, tp_s, utc));
+ EXPECT_EQ("03:04:05", absl::time_internal::cctz::format(kFmt, tp_s, utc));
EXPECT_TRUE(parse(kFmt, "03:04:05", utc, &tp_s));
- EXPECT_EQ("03:04:05", format(kFmt, tp_s, utc));
+ EXPECT_EQ("03:04:05", absl::time_internal::cctz::format(kFmt, tp_s, utc));
time_point<chrono::minutes> tp_m;
EXPECT_TRUE(parse(kFmt, "03:04:05", utc, &tp_m));
- EXPECT_EQ("03:04:00", format(kFmt, tp_m, utc));
+ EXPECT_EQ("03:04:00", absl::time_internal::cctz::format(kFmt, tp_m, utc));
time_point<chrono::hours> tp_h;
EXPECT_TRUE(parse(kFmt, "03:04:05", utc, &tp_h));
- EXPECT_EQ("03:00:00", format(kFmt, tp_h, utc));
+ EXPECT_EQ("03:00:00", absl::time_internal::cctz::format(kFmt, tp_h, utc));
}
TEST(Parse, TimePointExtendedResolution) {
@@ -1044,9 +1131,11 @@
EXPECT_TRUE(parse("%h", "Feb", tz, &tp));
EXPECT_EQ(2, convert(tp, tz).month()); // Equivalent to %b
+#if defined(__GLIBC__)
tp = reset;
EXPECT_TRUE(parse("%l %p", "5 PM", tz, &tp));
EXPECT_EQ(17, convert(tp, tz).hour());
+#endif
tp = reset;
EXPECT_TRUE(parse("%r", "03:44:55 PM", tz, &tp));
@@ -1054,6 +1143,7 @@
EXPECT_EQ(44, convert(tp, tz).minute());
EXPECT_EQ(55, convert(tp, tz).second());
+#if defined(__GLIBC__)
tp = reset;
EXPECT_TRUE(parse("%Ec", "Tue Nov 19 05:06:07 2013", tz, &tp));
EXPECT_EQ(convert(civil_second(2013, 11, 19, 5, 6, 7), tz), tp);
@@ -1125,6 +1215,7 @@
EXPECT_TRUE(parse("%Oy", "04", tz, &tp));
EXPECT_EQ(2004, convert(tp, tz).year());
#endif
+#endif
}
TEST(Parse, ExtendedSeconds) {
@@ -1504,7 +1595,7 @@
parse(RFC3339_sec, "292277026596-12-04T14:30:07-01:00", utc, &tp));
EXPECT_EQ(tp, time_point<absl::time_internal::cctz::seconds>::max());
EXPECT_FALSE(
- parse(RFC3339_sec, "292277026596-12-04T15:30:07-01:00", utc, &tp));
+ parse(RFC3339_sec, "292277026596-12-04T14:30:08-01:00", utc, &tp));
// tests the lower limit using +00:00 offset
EXPECT_TRUE(
@@ -1525,10 +1616,86 @@
parse(RFC3339_sec, "9223372036854775807-12-31T23:59:59-00:01", utc, &tp));
EXPECT_FALSE(parse(RFC3339_sec, "-9223372036854775808-01-01T00:00:00+00:01",
utc, &tp));
+}
- // TODO: Add tests that parsing times with fractional seconds overflow
- // appropriately. This can't be done until cctz::parse() properly detects
- // overflow when combining the chrono seconds and femto.
+TEST(Parse, TimePointOverflow) {
+ const time_zone utc = utc_time_zone();
+
+ using D = chrono::duration<std::int64_t, std::nano>;
+ time_point<D> tp;
+
+ EXPECT_TRUE(
+ parse(RFC3339_full, "2262-04-11T23:47:16.8547758079+00:00", utc, &tp));
+ EXPECT_EQ(tp, time_point<D>::max());
+ EXPECT_EQ("2262-04-11T23:47:16.854775807+00:00",
+ absl::time_internal::cctz::format(RFC3339_full, tp, utc));
+#if 0
+ // TODO(#199): Will fail until cctz::parse() properly detects overflow.
+ EXPECT_FALSE(
+ parse(RFC3339_full, "2262-04-11T23:47:16.8547758080+00:00", utc, &tp));
+ EXPECT_TRUE(
+ parse(RFC3339_full, "1677-09-21T00:12:43.1452241920+00:00", utc, &tp));
+ EXPECT_EQ(tp, time_point<D>::min());
+ EXPECT_EQ("1677-09-21T00:12:43.145224192+00:00",
+ absl::time_internal::cctz::format(RFC3339_full, tp, utc));
+ EXPECT_FALSE(
+ parse(RFC3339_full, "1677-09-21T00:12:43.1452241919+00:00", utc, &tp));
+#endif
+
+ using DS = chrono::duration<std::int8_t, chrono::seconds::period>;
+ time_point<DS> stp;
+
+ EXPECT_TRUE(parse(RFC3339_full, "1970-01-01T00:02:07.9+00:00", utc, &stp));
+ EXPECT_EQ(stp, time_point<DS>::max());
+ EXPECT_EQ("1970-01-01T00:02:07+00:00",
+ absl::time_internal::cctz::format(RFC3339_full, stp, utc));
+ EXPECT_FALSE(parse(RFC3339_full, "1970-01-01T00:02:08+00:00", utc, &stp));
+
+ EXPECT_TRUE(parse(RFC3339_full, "1969-12-31T23:57:52+00:00", utc, &stp));
+ EXPECT_EQ(stp, time_point<DS>::min());
+ EXPECT_EQ("1969-12-31T23:57:52+00:00",
+ absl::time_internal::cctz::format(RFC3339_full, stp, utc));
+ EXPECT_FALSE(parse(RFC3339_full, "1969-12-31T23:57:51.9+00:00", utc, &stp));
+
+ using DM = chrono::duration<std::int8_t, chrono::minutes::period>;
+ time_point<DM> mtp;
+
+ EXPECT_TRUE(parse(RFC3339_full, "1970-01-01T02:07:59+00:00", utc, &mtp));
+ EXPECT_EQ(mtp, time_point<DM>::max());
+ EXPECT_EQ("1970-01-01T02:07:00+00:00",
+ absl::time_internal::cctz::format(RFC3339_full, mtp, utc));
+ EXPECT_FALSE(parse(RFC3339_full, "1970-01-01T02:08:00+00:00", utc, &mtp));
+
+ EXPECT_TRUE(parse(RFC3339_full, "1969-12-31T21:52:00+00:00", utc, &mtp));
+ EXPECT_EQ(mtp, time_point<DM>::min());
+ EXPECT_EQ("1969-12-31T21:52:00+00:00",
+ absl::time_internal::cctz::format(RFC3339_full, mtp, utc));
+ EXPECT_FALSE(parse(RFC3339_full, "1969-12-31T21:51:59+00:00", utc, &mtp));
+}
+
+TEST(Parse, TimePointOverflowFloor) {
+ const time_zone utc = utc_time_zone();
+
+ using D = chrono::duration<std::int64_t, std::micro>;
+ time_point<D> tp;
+
+ EXPECT_TRUE(
+ parse(RFC3339_full, "294247-01-10T04:00:54.7758079+00:00", utc, &tp));
+ EXPECT_EQ(tp, time_point<D>::max());
+ EXPECT_EQ("294247-01-10T04:00:54.775807+00:00",
+ absl::time_internal::cctz::format(RFC3339_full, tp, utc));
+#if 0
+ // TODO(#199): Will fail until cctz::parse() properly detects overflow.
+ EXPECT_FALSE(
+ parse(RFC3339_full, "294247-01-10T04:00:54.7758080+00:00", utc, &tp));
+ EXPECT_TRUE(
+ parse(RFC3339_full, "-290308-12-21T19:59:05.2241920+00:00", utc, &tp));
+ EXPECT_EQ(tp, time_point<D>::min());
+ EXPECT_EQ("-290308-12-21T19:59:05.224192+00:00",
+ absl::time_internal::cctz::format(RFC3339_full, tp, utc));
+ EXPECT_FALSE(
+ parse(RFC3339_full, "-290308-12-21T19:59:05.2241919+00:00", utc, &tp));
+#endif
}
//
@@ -1544,7 +1711,8 @@
// RFC3339, which renders subseconds.
{
time_point<chrono::nanoseconds> out;
- const std::string s = format(RFC3339_full, in + subseconds, lax);
+ const std::string s =
+ absl::time_internal::cctz::format(RFC3339_full, in + subseconds, lax);
EXPECT_TRUE(parse(RFC3339_full, s, lax, &out)) << s;
EXPECT_EQ(in + subseconds, out); // RFC3339_full includes %Ez
}
@@ -1552,7 +1720,8 @@
// RFC1123, which only does whole seconds.
{
time_point<chrono::nanoseconds> out;
- const std::string s = format(RFC1123_full, in, lax);
+ const std::string s =
+ absl::time_internal::cctz::format(RFC1123_full, in, lax);
EXPECT_TRUE(parse(RFC1123_full, s, lax, &out)) << s;
EXPECT_EQ(in, out); // RFC1123_full includes %z
}
@@ -1570,7 +1739,7 @@
{
time_point<chrono::nanoseconds> out;
time_zone utc = utc_time_zone();
- const std::string s = format("%c", in, utc);
+ const std::string s = absl::time_internal::cctz::format("%c", in, utc);
EXPECT_TRUE(parse("%c", s, utc, &out)) << s;
EXPECT_EQ(in, out);
}
@@ -1581,7 +1750,8 @@
const time_zone utc = utc_time_zone();
const time_point<absl::time_internal::cctz::seconds> in =
time_point<absl::time_internal::cctz::seconds>::max();
- const std::string s = format(RFC3339_full, in, utc);
+ const std::string s =
+ absl::time_internal::cctz::format(RFC3339_full, in, utc);
time_point<absl::time_internal::cctz::seconds> out;
EXPECT_TRUE(parse(RFC3339_full, s, utc, &out)) << s;
EXPECT_EQ(in, out);
@@ -1591,7 +1761,8 @@
const time_zone utc = utc_time_zone();
const time_point<absl::time_internal::cctz::seconds> in =
time_point<absl::time_internal::cctz::seconds>::min();
- const std::string s = format(RFC3339_full, in, utc);
+ const std::string s =
+ absl::time_internal::cctz::format(RFC3339_full, in, utc);
time_point<absl::time_internal::cctz::seconds> out;
EXPECT_TRUE(parse(RFC3339_full, s, utc, &out)) << s;
EXPECT_EQ(in, out);
diff --git a/absl/time/internal/cctz/src/time_zone_if.cc b/absl/time/internal/cctz/src/time_zone_if.cc
index 0319b2f..0e65cd9 100644
--- a/absl/time/internal/cctz/src/time_zone_if.cc
+++ b/absl/time/internal/cctz/src/time_zone_if.cc
@@ -23,17 +23,19 @@
namespace time_internal {
namespace cctz {
-std::unique_ptr<TimeZoneIf> TimeZoneIf::Load(const std::string& name) {
+std::unique_ptr<TimeZoneIf> TimeZoneIf::UTC() { return TimeZoneInfo::UTC(); }
+
+std::unique_ptr<TimeZoneIf> TimeZoneIf::Make(const std::string& name) {
// Support "libc:localtime" and "libc:*" to access the legacy
// localtime and UTC support respectively from the C library.
+ // NOTE: The "libc:*" zones are internal, test-only interfaces, and
+ // are subject to change/removal without notice. Do not use them.
if (name.compare(0, 5, "libc:") == 0) {
- return std::unique_ptr<TimeZoneIf>(new TimeZoneLibC(name.substr(5)));
+ return TimeZoneLibC::Make(name.substr(5));
}
- // Otherwise use the "zoneinfo" implementation by default.
- std::unique_ptr<TimeZoneInfo> tz(new TimeZoneInfo);
- if (!tz->Load(name)) tz.reset();
- return std::unique_ptr<TimeZoneIf>(tz.release());
+ // Otherwise use the "zoneinfo" implementation.
+ return TimeZoneInfo::Make(name);
}
// Defined out-of-line to avoid emitting a weak vtable in all TUs.
diff --git a/absl/time/internal/cctz/src/time_zone_if.h b/absl/time/internal/cctz/src/time_zone_if.h
index 32c0891..bec9beb 100644
--- a/absl/time/internal/cctz/src/time_zone_if.h
+++ b/absl/time/internal/cctz/src/time_zone_if.h
@@ -33,8 +33,9 @@
// Subclasses implement the functions for civil-time conversions in the zone.
class TimeZoneIf {
public:
- // A factory function for TimeZoneIf implementations.
- static std::unique_ptr<TimeZoneIf> Load(const std::string& name);
+ // Factory functions for TimeZoneIf implementations.
+ static std::unique_ptr<TimeZoneIf> UTC(); // never fails
+ static std::unique_ptr<TimeZoneIf> Make(const std::string& name);
virtual ~TimeZoneIf();
@@ -51,12 +52,15 @@
virtual std::string Description() const = 0;
protected:
- TimeZoneIf() {}
+ TimeZoneIf() = default;
+ TimeZoneIf(const TimeZoneIf&) = delete;
+ TimeZoneIf& operator=(const TimeZoneIf&) = delete;
};
// Convert between time_point<seconds> and a count of seconds since the
// Unix epoch. We assume that the std::chrono::system_clock and the
-// Unix clock are second aligned, but not that they share an epoch.
+// Unix clock are second aligned, and that the results are representable.
+// (That is, that they share an epoch, which is required since C++20.)
inline std::int_fast64_t ToUnixSeconds(const time_point<seconds>& tp) {
return (tp - std::chrono::time_point_cast<seconds>(
std::chrono::system_clock::from_time_t(0)))
diff --git a/absl/time/internal/cctz/src/time_zone_impl.cc b/absl/time/internal/cctz/src/time_zone_impl.cc
index f34e3ae..aadbb77 100644
--- a/absl/time/internal/cctz/src/time_zone_impl.cc
+++ b/absl/time/internal/cctz/src/time_zone_impl.cc
@@ -99,11 +99,13 @@
}
}
+time_zone::Impl::Impl() : name_("UTC"), zone_(TimeZoneIf::UTC()) {}
+
time_zone::Impl::Impl(const std::string& name)
- : name_(name), zone_(TimeZoneIf::Load(name_)) {}
+ : name_(name), zone_(TimeZoneIf::Make(name_)) {}
const time_zone::Impl* time_zone::Impl::UTCImpl() {
- static const Impl* utc_impl = new Impl("UTC"); // never fails
+ static const Impl* utc_impl = new Impl;
return utc_impl;
}
diff --git a/absl/time/internal/cctz/src/time_zone_impl.h b/absl/time/internal/cctz/src/time_zone_impl.h
index 7d747ba..8308a3b 100644
--- a/absl/time/internal/cctz/src/time_zone_impl.h
+++ b/absl/time/internal/cctz/src/time_zone_impl.h
@@ -78,7 +78,11 @@
std::string Description() const { return zone_->Description(); }
private:
+ Impl();
explicit Impl(const std::string& name);
+ Impl(const Impl&) = delete;
+ Impl& operator=(const Impl&) = delete;
+
static const Impl* UTCImpl();
const std::string name_;
diff --git a/absl/time/internal/cctz/src/time_zone_info.cc b/absl/time/internal/cctz/src/time_zone_info.cc
index f2777d9..b7178a6 100644
--- a/absl/time/internal/cctz/src/time_zone_info.cc
+++ b/absl/time/internal/cctz/src/time_zone_info.cc
@@ -39,11 +39,13 @@
#include <cstdio>
#include <cstdlib>
#include <cstring>
+#include <fstream>
#include <functional>
#include <memory>
#include <sstream>
#include <string>
#include <utility>
+#include <vector>
#include "absl/base/config.h"
#include "absl/time/internal/cctz/include/cctz/civil_time.h"
@@ -133,6 +135,64 @@
return static_cast<std::int_fast64_t>(v - s64maxU - 1) - s64max - 1;
}
+struct Header { // counts of:
+ std::size_t timecnt; // transition times
+ std::size_t typecnt; // transition types
+ std::size_t charcnt; // zone abbreviation characters
+ std::size_t leapcnt; // leap seconds (we expect none)
+ std::size_t ttisstdcnt; // UTC/local indicators (unused)
+ std::size_t ttisutcnt; // standard/wall indicators (unused)
+
+ bool Build(const tzhead& tzh);
+ std::size_t DataLength(std::size_t time_len) const;
+};
+
+// Builds the in-memory header using the raw bytes from the file.
+bool Header::Build(const tzhead& tzh) {
+ std::int_fast32_t v;
+ if ((v = Decode32(tzh.tzh_timecnt)) < 0) return false;
+ timecnt = static_cast<std::size_t>(v);
+ if ((v = Decode32(tzh.tzh_typecnt)) < 0) return false;
+ typecnt = static_cast<std::size_t>(v);
+ if ((v = Decode32(tzh.tzh_charcnt)) < 0) return false;
+ charcnt = static_cast<std::size_t>(v);
+ if ((v = Decode32(tzh.tzh_leapcnt)) < 0) return false;
+ leapcnt = static_cast<std::size_t>(v);
+ if ((v = Decode32(tzh.tzh_ttisstdcnt)) < 0) return false;
+ ttisstdcnt = static_cast<std::size_t>(v);
+ if ((v = Decode32(tzh.tzh_ttisutcnt)) < 0) return false;
+ ttisutcnt = static_cast<std::size_t>(v);
+ return true;
+}
+
+// How many bytes of data are associated with this header. The result
+// depends upon whether this is a section with 4-byte or 8-byte times.
+std::size_t Header::DataLength(std::size_t time_len) const {
+ std::size_t len = 0;
+ len += (time_len + 1) * timecnt; // unix_time + type_index
+ len += (4 + 1 + 1) * typecnt; // utc_offset + is_dst + abbr_index
+ len += 1 * charcnt; // abbreviations
+ len += (time_len + 4) * leapcnt; // leap-time + TAI-UTC
+ len += 1 * ttisstdcnt; // UTC/local indicators
+ len += 1 * ttisutcnt; // standard/wall indicators
+ return len;
+}
+
+// Does the rule for future transitions call for year-round daylight time?
+// See tz/zic.c:stringzone() for the details on how such rules are encoded.
+bool AllYearDST(const PosixTimeZone& posix) {
+ if (posix.dst_start.date.fmt != PosixTransition::N) return false;
+ if (posix.dst_start.date.n.day != 0) return false;
+ if (posix.dst_start.time.offset != 0) return false;
+
+ if (posix.dst_end.date.fmt != PosixTransition::J) return false;
+ if (posix.dst_end.date.j.day != kDaysPerYear[0]) return false;
+ const auto offset = posix.std_offset - posix.dst_offset;
+ if (posix.dst_end.time.offset + offset != kSecsPerDay) return false;
+
+ return true;
+}
+
// Generate a year-relative offset for a PosixTransition.
std::int_fast64_t TransOffset(bool leap_year, int jan1_weekday,
const PosixTransition& pt) {
@@ -201,6 +261,329 @@
} // namespace
+// Find/make a transition type with these attributes.
+bool TimeZoneInfo::GetTransitionType(std::int_fast32_t utc_offset, bool is_dst,
+ const std::string& abbr,
+ std::uint_least8_t* index) {
+ std::size_t type_index = 0;
+ std::size_t abbr_index = abbreviations_.size();
+ for (; type_index != transition_types_.size(); ++type_index) {
+ const TransitionType& tt(transition_types_[type_index]);
+ const char* tt_abbr = &abbreviations_[tt.abbr_index];
+ if (tt_abbr == abbr) abbr_index = tt.abbr_index;
+ if (tt.utc_offset == utc_offset && tt.is_dst == is_dst) {
+ if (abbr_index == tt.abbr_index) break; // reuse
+ }
+ }
+ if (type_index > 255 || abbr_index > 255) {
+ // No index space (8 bits) available for a new type or abbreviation.
+ return false;
+ }
+ if (type_index == transition_types_.size()) {
+ TransitionType& tt(*transition_types_.emplace(transition_types_.end()));
+ tt.utc_offset = static_cast<std::int_least32_t>(utc_offset);
+ tt.is_dst = is_dst;
+ if (abbr_index == abbreviations_.size()) {
+ abbreviations_.append(abbr);
+ abbreviations_.append(1, '\0');
+ }
+ tt.abbr_index = static_cast<std::uint_least8_t>(abbr_index);
+ }
+ *index = static_cast<std::uint_least8_t>(type_index);
+ return true;
+}
+
+// zic(8) can generate no-op transitions when a zone changes rules at an
+// instant when there is actually no discontinuity. So we check whether
+// two transitions have equivalent types (same offset/is_dst/abbr).
+bool TimeZoneInfo::EquivTransitions(std::uint_fast8_t tt1_index,
+ std::uint_fast8_t tt2_index) const {
+ if (tt1_index == tt2_index) return true;
+ const TransitionType& tt1(transition_types_[tt1_index]);
+ const TransitionType& tt2(transition_types_[tt2_index]);
+ if (tt1.utc_offset != tt2.utc_offset) return false;
+ if (tt1.is_dst != tt2.is_dst) return false;
+ if (tt1.abbr_index != tt2.abbr_index) return false;
+ return true;
+}
+
+// Use the POSIX-TZ-environment-variable-style string to handle times
+// in years after the last transition stored in the zoneinfo data.
+bool TimeZoneInfo::ExtendTransitions() {
+ extended_ = false;
+ if (future_spec_.empty()) return true; // last transition prevails
+
+ PosixTimeZone posix;
+ if (!ParsePosixSpec(future_spec_, &posix)) return false;
+
+ // Find transition type for the future std specification.
+ std::uint_least8_t std_ti;
+ if (!GetTransitionType(posix.std_offset, false, posix.std_abbr, &std_ti))
+ return false;
+
+ if (posix.dst_abbr.empty()) { // std only
+ // The future specification should match the last transition, and
+ // that means that handling the future will fall out naturally.
+ return EquivTransitions(transitions_.back().type_index, std_ti);
+ }
+
+ // Find transition type for the future dst specification.
+ std::uint_least8_t dst_ti;
+ if (!GetTransitionType(posix.dst_offset, true, posix.dst_abbr, &dst_ti))
+ return false;
+
+ if (AllYearDST(posix)) { // dst only
+ // The future specification should match the last transition, and
+ // that means that handling the future will fall out naturally.
+ return EquivTransitions(transitions_.back().type_index, dst_ti);
+ }
+
+ // Extend the transitions for an additional 401 years using the future
+ // specification. Years beyond those can be handled by mapping back to
+ // a cycle-equivalent year within that range. Note that we need 401
+ // (well, at least the first transition in the 401st year) so that the
+ // end of the 400th year is mapped back to an extended year. And first
+ // we may also need two additional transitions for the current year.
+ transitions_.reserve(transitions_.size() + 2 + 401 * 2);
+ extended_ = true;
+
+ const Transition& last(transitions_.back());
+ const std::int_fast64_t last_time = last.unix_time;
+ const TransitionType& last_tt(transition_types_[last.type_index]);
+ last_year_ = LocalTime(last_time, last_tt).cs.year();
+ bool leap_year = IsLeap(last_year_);
+ const civil_second jan1(last_year_);
+ std::int_fast64_t jan1_time = jan1 - civil_second();
+ int jan1_weekday = ToPosixWeekday(get_weekday(jan1));
+
+ Transition dst = {0, dst_ti, civil_second(), civil_second()};
+ Transition std = {0, std_ti, civil_second(), civil_second()};
+ for (const year_t limit = last_year_ + 401;; ++last_year_) {
+ auto dst_trans_off = TransOffset(leap_year, jan1_weekday, posix.dst_start);
+ auto std_trans_off = TransOffset(leap_year, jan1_weekday, posix.dst_end);
+ dst.unix_time = jan1_time + dst_trans_off - posix.std_offset;
+ std.unix_time = jan1_time + std_trans_off - posix.dst_offset;
+ const auto* ta = dst.unix_time < std.unix_time ? &dst : &std;
+ const auto* tb = dst.unix_time < std.unix_time ? &std : &dst;
+ if (last_time < tb->unix_time) {
+ if (last_time < ta->unix_time) transitions_.push_back(*ta);
+ transitions_.push_back(*tb);
+ }
+ if (last_year_ == limit) break;
+ jan1_time += kSecsPerYear[leap_year];
+ jan1_weekday = (jan1_weekday + kDaysPerYear[leap_year]) % 7;
+ leap_year = !leap_year && IsLeap(last_year_ + 1);
+ }
+
+ return true;
+}
+
+namespace {
+
+using FilePtr = std::unique_ptr<FILE, int (*)(FILE*)>;
+
+// fopen(3) adaptor.
+inline FilePtr FOpen(const char* path, const char* mode) {
+#if defined(_MSC_VER)
+ FILE* fp;
+ if (fopen_s(&fp, path, mode) != 0) fp = nullptr;
+ return FilePtr(fp, fclose);
+#else
+ // TODO: Enable the close-on-exec flag.
+ return FilePtr(fopen(path, mode), fclose);
+#endif
+}
+
+// A stdio(3)-backed implementation of ZoneInfoSource.
+class FileZoneInfoSource : public ZoneInfoSource {
+ public:
+ static std::unique_ptr<ZoneInfoSource> Open(const std::string& name);
+
+ std::size_t Read(void* ptr, std::size_t size) override {
+ size = std::min(size, len_);
+ std::size_t nread = fread(ptr, 1, size, fp_.get());
+ len_ -= nread;
+ return nread;
+ }
+ int Skip(std::size_t offset) override {
+ offset = std::min(offset, len_);
+ int rc = fseek(fp_.get(), static_cast<long>(offset), SEEK_CUR);
+ if (rc == 0) len_ -= offset;
+ return rc;
+ }
+ std::string Version() const override {
+ // TODO: It would nice if the zoneinfo data included the tzdb version.
+ return std::string();
+ }
+
+ protected:
+ explicit FileZoneInfoSource(
+ FilePtr fp, std::size_t len = std::numeric_limits<std::size_t>::max())
+ : fp_(std::move(fp)), len_(len) {}
+
+ private:
+ FilePtr fp_;
+ std::size_t len_;
+};
+
+std::unique_ptr<ZoneInfoSource> FileZoneInfoSource::Open(
+ const std::string& name) {
+ // Use of the "file:" prefix is intended for testing purposes only.
+ const std::size_t pos = (name.compare(0, 5, "file:") == 0) ? 5 : 0;
+
+ // Map the time-zone name to a path name.
+ std::string path;
+ if (pos == name.size() || name[pos] != '/') {
+ const char* tzdir = "/usr/share/zoneinfo";
+ char* tzdir_env = nullptr;
+#if defined(_MSC_VER)
+ _dupenv_s(&tzdir_env, nullptr, "TZDIR");
+#else
+ tzdir_env = std::getenv("TZDIR");
+#endif
+ if (tzdir_env && *tzdir_env) tzdir = tzdir_env;
+ path += tzdir;
+ path += '/';
+#if defined(_MSC_VER)
+ free(tzdir_env);
+#endif
+ }
+ path.append(name, pos, std::string::npos);
+
+ // Open the zoneinfo file.
+ auto fp = FOpen(path.c_str(), "rb");
+ if (fp == nullptr) return nullptr;
+ return std::unique_ptr<ZoneInfoSource>(new FileZoneInfoSource(std::move(fp)));
+}
+
+class AndroidZoneInfoSource : public FileZoneInfoSource {
+ public:
+ static std::unique_ptr<ZoneInfoSource> Open(const std::string& name);
+ std::string Version() const override { return version_; }
+
+ private:
+ explicit AndroidZoneInfoSource(FilePtr fp, std::size_t len,
+ std::string version)
+ : FileZoneInfoSource(std::move(fp), len), version_(std::move(version)) {}
+ std::string version_;
+};
+
+std::unique_ptr<ZoneInfoSource> AndroidZoneInfoSource::Open(
+ const std::string& name) {
+ // Use of the "file:" prefix is intended for testing purposes only.
+ const std::size_t pos = (name.compare(0, 5, "file:") == 0) ? 5 : 0;
+
+ // See Android's libc/tzcode/bionic.cpp for additional information.
+ for (const char* tzdata : {"/apex/com.android.tzdata/etc/tz/tzdata",
+ "/data/misc/zoneinfo/current/tzdata",
+ "/system/usr/share/zoneinfo/tzdata"}) {
+ auto fp = FOpen(tzdata, "rb");
+ if (fp == nullptr) continue;
+
+ char hbuf[24]; // covers header.zonetab_offset too
+ if (fread(hbuf, 1, sizeof(hbuf), fp.get()) != sizeof(hbuf)) continue;
+ if (strncmp(hbuf, "tzdata", 6) != 0) continue;
+ const char* vers = (hbuf[11] == '\0') ? hbuf + 6 : "";
+ const std::int_fast32_t index_offset = Decode32(hbuf + 12);
+ const std::int_fast32_t data_offset = Decode32(hbuf + 16);
+ if (index_offset < 0 || data_offset < index_offset) continue;
+ if (fseek(fp.get(), static_cast<long>(index_offset), SEEK_SET) != 0)
+ continue;
+
+ char ebuf[52]; // covers entry.unused too
+ const std::size_t index_size =
+ static_cast<std::size_t>(data_offset - index_offset);
+ const std::size_t zonecnt = index_size / sizeof(ebuf);
+ if (zonecnt * sizeof(ebuf) != index_size) continue;
+ for (std::size_t i = 0; i != zonecnt; ++i) {
+ if (fread(ebuf, 1, sizeof(ebuf), fp.get()) != sizeof(ebuf)) break;
+ const std::int_fast32_t start = data_offset + Decode32(ebuf + 40);
+ const std::int_fast32_t length = Decode32(ebuf + 44);
+ if (start < 0 || length < 0) break;
+ ebuf[40] = '\0'; // ensure zone name is NUL terminated
+ if (strcmp(name.c_str() + pos, ebuf) == 0) {
+ if (fseek(fp.get(), static_cast<long>(start), SEEK_SET) != 0) break;
+ return std::unique_ptr<ZoneInfoSource>(new AndroidZoneInfoSource(
+ std::move(fp), static_cast<std::size_t>(length), vers));
+ }
+ }
+ }
+
+ return nullptr;
+}
+
+// A zoneinfo source for use inside Fuchsia components. This attempts to
+// read zoneinfo files from one of several known paths in a component's
+// incoming namespace. [Config data][1] is preferred, but package-specific
+// resources are also supported.
+//
+// Fuchsia's implementation supports `FileZoneInfoSource::Version()`.
+//
+// [1]:
+// https://fuchsia.dev/fuchsia-src/development/components/data#using_config_data_in_your_component
+class FuchsiaZoneInfoSource : public FileZoneInfoSource {
+ public:
+ static std::unique_ptr<ZoneInfoSource> Open(const std::string& name);
+ std::string Version() const override { return version_; }
+
+ private:
+ explicit FuchsiaZoneInfoSource(FilePtr fp, std::string version)
+ : FileZoneInfoSource(std::move(fp)), version_(std::move(version)) {}
+ std::string version_;
+};
+
+std::unique_ptr<ZoneInfoSource> FuchsiaZoneInfoSource::Open(
+ const std::string& name) {
+ // Use of the "file:" prefix is intended for testing purposes only.
+ const std::size_t pos = (name.compare(0, 5, "file:") == 0) ? 5 : 0;
+
+ // Prefixes where a Fuchsia component might find zoneinfo files,
+ // in descending order of preference.
+ const auto kTzdataPrefixes = {
+ // The tzdata from `config-data`.
+ "/config/data/tzdata/",
+ // The tzdata bundled in the component's package.
+ "/pkg/data/tzdata/",
+ // General data storage.
+ "/data/tzdata/",
+ // The recommended path for routed-in tzdata files.
+ // See for details:
+ // https://fuchsia.dev/fuchsia-src/concepts/process/namespaces?hl=en#typical_directory_structure
+ "/config/tzdata/",
+ };
+ const auto kEmptyPrefix = {""};
+ const bool name_absolute = (pos != name.size() && name[pos] == '/');
+ const auto prefixes = name_absolute ? kEmptyPrefix : kTzdataPrefixes;
+
+ // Fuchsia builds place zoneinfo files at "<prefix><format><name>".
+ for (const std::string prefix : prefixes) {
+ std::string path = prefix;
+ if (!prefix.empty()) path += "zoneinfo/tzif2/"; // format
+ path.append(name, pos, std::string::npos);
+
+ auto fp = FOpen(path.c_str(), "rb");
+ if (fp == nullptr) continue;
+
+ std::string version;
+ if (!prefix.empty()) {
+ // Fuchsia builds place the version in "<prefix>revision.txt".
+ std::ifstream version_stream(prefix + "revision.txt");
+ if (version_stream.is_open()) {
+ // revision.txt should contain no newlines, but to be
+ // defensive we read just the first line.
+ std::getline(version_stream, version);
+ }
+ }
+
+ return std::unique_ptr<ZoneInfoSource>(
+ new FuchsiaZoneInfoSource(std::move(fp), std::move(version)));
+ }
+
+ return nullptr;
+}
+
+} // namespace
+
// What (no leap-seconds) UTC+seconds zoneinfo would look like.
bool TimeZoneInfo::ResetToBuiltinUTC(const seconds& offset) {
transition_types_.resize(1);
@@ -248,146 +631,6 @@
return true;
}
-// Builds the in-memory header using the raw bytes from the file.
-bool TimeZoneInfo::Header::Build(const tzhead& tzh) {
- std::int_fast32_t v;
- if ((v = Decode32(tzh.tzh_timecnt)) < 0) return false;
- timecnt = static_cast<std::size_t>(v);
- if ((v = Decode32(tzh.tzh_typecnt)) < 0) return false;
- typecnt = static_cast<std::size_t>(v);
- if ((v = Decode32(tzh.tzh_charcnt)) < 0) return false;
- charcnt = static_cast<std::size_t>(v);
- if ((v = Decode32(tzh.tzh_leapcnt)) < 0) return false;
- leapcnt = static_cast<std::size_t>(v);
- if ((v = Decode32(tzh.tzh_ttisstdcnt)) < 0) return false;
- ttisstdcnt = static_cast<std::size_t>(v);
- if ((v = Decode32(tzh.tzh_ttisutcnt)) < 0) return false;
- ttisutcnt = static_cast<std::size_t>(v);
- return true;
-}
-
-// How many bytes of data are associated with this header. The result
-// depends upon whether this is a section with 4-byte or 8-byte times.
-std::size_t TimeZoneInfo::Header::DataLength(std::size_t time_len) const {
- std::size_t len = 0;
- len += (time_len + 1) * timecnt; // unix_time + type_index
- len += (4 + 1 + 1) * typecnt; // utc_offset + is_dst + abbr_index
- len += 1 * charcnt; // abbreviations
- len += (time_len + 4) * leapcnt; // leap-time + TAI-UTC
- len += 1 * ttisstdcnt; // UTC/local indicators
- len += 1 * ttisutcnt; // standard/wall indicators
- return len;
-}
-
-// zic(8) can generate no-op transitions when a zone changes rules at an
-// instant when there is actually no discontinuity. So we check whether
-// two transitions have equivalent types (same offset/is_dst/abbr).
-bool TimeZoneInfo::EquivTransitions(std::uint_fast8_t tt1_index,
- std::uint_fast8_t tt2_index) const {
- if (tt1_index == tt2_index) return true;
- const TransitionType& tt1(transition_types_[tt1_index]);
- const TransitionType& tt2(transition_types_[tt2_index]);
- if (tt1.utc_offset != tt2.utc_offset) return false;
- if (tt1.is_dst != tt2.is_dst) return false;
- if (tt1.abbr_index != tt2.abbr_index) return false;
- return true;
-}
-
-// Find/make a transition type with these attributes.
-bool TimeZoneInfo::GetTransitionType(std::int_fast32_t utc_offset, bool is_dst,
- const std::string& abbr,
- std::uint_least8_t* index) {
- std::size_t type_index = 0;
- std::size_t abbr_index = abbreviations_.size();
- for (; type_index != transition_types_.size(); ++type_index) {
- const TransitionType& tt(transition_types_[type_index]);
- const char* tt_abbr = &abbreviations_[tt.abbr_index];
- if (tt_abbr == abbr) abbr_index = tt.abbr_index;
- if (tt.utc_offset == utc_offset && tt.is_dst == is_dst) {
- if (abbr_index == tt.abbr_index) break; // reuse
- }
- }
- if (type_index > 255 || abbr_index > 255) {
- // No index space (8 bits) available for a new type or abbreviation.
- return false;
- }
- if (type_index == transition_types_.size()) {
- TransitionType& tt(*transition_types_.emplace(transition_types_.end()));
- tt.utc_offset = static_cast<std::int_least32_t>(utc_offset);
- tt.is_dst = is_dst;
- if (abbr_index == abbreviations_.size()) {
- abbreviations_.append(abbr);
- abbreviations_.append(1, '\0');
- }
- tt.abbr_index = static_cast<std::uint_least8_t>(abbr_index);
- }
- *index = static_cast<std::uint_least8_t>(type_index);
- return true;
-}
-
-// Use the POSIX-TZ-environment-variable-style string to handle times
-// in years after the last transition stored in the zoneinfo data.
-bool TimeZoneInfo::ExtendTransitions() {
- extended_ = false;
- if (future_spec_.empty()) return true; // last transition prevails
-
- PosixTimeZone posix;
- if (!ParsePosixSpec(future_spec_, &posix)) return false;
-
- // Find transition type for the future std specification.
- std::uint_least8_t std_ti;
- if (!GetTransitionType(posix.std_offset, false, posix.std_abbr, &std_ti))
- return false;
-
- if (posix.dst_abbr.empty()) { // std only
- // The future specification should match the last transition, and
- // that means that handling the future will fall out naturally.
- return EquivTransitions(transitions_.back().type_index, std_ti);
- }
-
- // Find transition type for the future dst specification.
- std::uint_least8_t dst_ti;
- if (!GetTransitionType(posix.dst_offset, true, posix.dst_abbr, &dst_ti))
- return false;
-
- // Extend the transitions for an additional 400 years using the
- // future specification. Years beyond those can be handled by
- // mapping back to a cycle-equivalent year within that range.
- // We may need two additional transitions for the current year.
- transitions_.reserve(transitions_.size() + 400 * 2 + 2);
- extended_ = true;
-
- const Transition& last(transitions_.back());
- const std::int_fast64_t last_time = last.unix_time;
- const TransitionType& last_tt(transition_types_[last.type_index]);
- last_year_ = LocalTime(last_time, last_tt).cs.year();
- bool leap_year = IsLeap(last_year_);
- const civil_second jan1(last_year_);
- std::int_fast64_t jan1_time = jan1 - civil_second();
- int jan1_weekday = ToPosixWeekday(get_weekday(jan1));
-
- Transition dst = {0, dst_ti, civil_second(), civil_second()};
- Transition std = {0, std_ti, civil_second(), civil_second()};
- for (const year_t limit = last_year_ + 400;; ++last_year_) {
- auto dst_trans_off = TransOffset(leap_year, jan1_weekday, posix.dst_start);
- auto std_trans_off = TransOffset(leap_year, jan1_weekday, posix.dst_end);
- dst.unix_time = jan1_time + dst_trans_off - posix.std_offset;
- std.unix_time = jan1_time + std_trans_off - posix.dst_offset;
- const auto* ta = dst.unix_time < std.unix_time ? &dst : &std;
- const auto* tb = dst.unix_time < std.unix_time ? &std : &dst;
- if (last_time < tb->unix_time) {
- if (last_time < ta->unix_time) transitions_.push_back(*ta);
- transitions_.push_back(*tb);
- }
- if (last_year_ == limit) break;
- jan1_time += kSecsPerYear[leap_year];
- jan1_weekday = (jan1_weekday + kDaysPerYear[leap_year]) % 7;
- leap_year = !leap_year && IsLeap(last_year_ + 1);
- }
-
- return true;
-}
-
bool TimeZoneInfo::Load(ZoneInfoSource* zip) {
// Read and validate the header.
tzhead tzh;
@@ -480,9 +723,9 @@
// encoded zoneinfo. The ttisstd/ttisgmt indicators only apply when
// interpreting a POSIX spec that does not include start/end rules, and
// that isn't the case here (see "zic -p").
- bp += (8 + 4) * hdr.leapcnt; // leap-time + TAI-UTC
- bp += 1 * hdr.ttisstdcnt; // UTC/local indicators
- bp += 1 * hdr.ttisutcnt; // standard/wall indicators
+ bp += (time_len + 4) * hdr.leapcnt; // leap-time + TAI-UTC
+ bp += 1 * hdr.ttisstdcnt; // UTC/local indicators
+ bp += 1 * hdr.ttisutcnt; // standard/wall indicators
assert(bp == tbuf.data() + tbuf.size());
future_spec_.clear();
@@ -509,19 +752,6 @@
version_ = zip->Version();
}
- // Trim redundant transitions. zic may have added these to work around
- // differences between the glibc and reference implementations (see
- // zic.c:dontmerge) and the Qt library (see zic.c:WORK_AROUND_QTBUG_53071).
- // For us, they just get in the way when we do future_spec_ extension.
- while (hdr.timecnt > 1) {
- if (!EquivTransitions(transitions_[hdr.timecnt - 1].type_index,
- transitions_[hdr.timecnt - 2].type_index)) {
- break;
- }
- hdr.timecnt -= 1;
- }
- transitions_.resize(hdr.timecnt);
-
// Ensure that there is always a transition in the first half of the
// time line (the second half is handled below) so that the signed
// difference between a civil_second and the civil_second of its
@@ -575,141 +805,6 @@
return true;
}
-namespace {
-
-using FilePtr = std::unique_ptr<FILE, int (*)(FILE*)>;
-
-// fopen(3) adaptor.
-inline FilePtr FOpen(const char* path, const char* mode) {
-#if defined(_MSC_VER)
- FILE* fp;
- if (fopen_s(&fp, path, mode) != 0) fp = nullptr;
- return FilePtr(fp, fclose);
-#else
- // TODO: Enable the close-on-exec flag.
- return FilePtr(fopen(path, mode), fclose);
-#endif
-}
-
-// A stdio(3)-backed implementation of ZoneInfoSource.
-class FileZoneInfoSource : public ZoneInfoSource {
- public:
- static std::unique_ptr<ZoneInfoSource> Open(const std::string& name);
-
- std::size_t Read(void* ptr, std::size_t size) override {
- size = std::min(size, len_);
- std::size_t nread = fread(ptr, 1, size, fp_.get());
- len_ -= nread;
- return nread;
- }
- int Skip(std::size_t offset) override {
- offset = std::min(offset, len_);
- int rc = fseek(fp_.get(), static_cast<long>(offset), SEEK_CUR);
- if (rc == 0) len_ -= offset;
- return rc;
- }
- std::string Version() const override {
- // TODO: It would nice if the zoneinfo data included the tzdb version.
- return std::string();
- }
-
- protected:
- explicit FileZoneInfoSource(
- FilePtr fp, std::size_t len = std::numeric_limits<std::size_t>::max())
- : fp_(std::move(fp)), len_(len) {}
-
- private:
- FilePtr fp_;
- std::size_t len_;
-};
-
-std::unique_ptr<ZoneInfoSource> FileZoneInfoSource::Open(
- const std::string& name) {
- // Use of the "file:" prefix is intended for testing purposes only.
- const std::size_t pos = (name.compare(0, 5, "file:") == 0) ? 5 : 0;
-
- // Map the time-zone name to a path name.
- std::string path;
- if (pos == name.size() || name[pos] != '/') {
- const char* tzdir = "/usr/share/zoneinfo";
- char* tzdir_env = nullptr;
-#if defined(_MSC_VER)
- _dupenv_s(&tzdir_env, nullptr, "TZDIR");
-#else
- tzdir_env = std::getenv("TZDIR");
-#endif
- if (tzdir_env && *tzdir_env) tzdir = tzdir_env;
- path += tzdir;
- path += '/';
-#if defined(_MSC_VER)
- free(tzdir_env);
-#endif
- }
- path.append(name, pos, std::string::npos);
-
- // Open the zoneinfo file.
- auto fp = FOpen(path.c_str(), "rb");
- if (fp.get() == nullptr) return nullptr;
- return std::unique_ptr<ZoneInfoSource>(new FileZoneInfoSource(std::move(fp)));
-}
-
-class AndroidZoneInfoSource : public FileZoneInfoSource {
- public:
- static std::unique_ptr<ZoneInfoSource> Open(const std::string& name);
- std::string Version() const override { return version_; }
-
- private:
- explicit AndroidZoneInfoSource(FilePtr fp, std::size_t len,
- std::string version)
- : FileZoneInfoSource(std::move(fp), len), version_(std::move(version)) {}
- std::string version_;
-};
-
-std::unique_ptr<ZoneInfoSource> AndroidZoneInfoSource::Open(
- const std::string& name) {
- // Use of the "file:" prefix is intended for testing purposes only.
- const std::size_t pos = (name.compare(0, 5, "file:") == 0) ? 5 : 0;
-
- // See Android's libc/tzcode/bionic.cpp for additional information.
- for (const char* tzdata : {"/data/misc/zoneinfo/current/tzdata",
- "/system/usr/share/zoneinfo/tzdata"}) {
- auto fp = FOpen(tzdata, "rb");
- if (fp.get() == nullptr) continue;
-
- char hbuf[24]; // covers header.zonetab_offset too
- if (fread(hbuf, 1, sizeof(hbuf), fp.get()) != sizeof(hbuf)) continue;
- if (strncmp(hbuf, "tzdata", 6) != 0) continue;
- const char* vers = (hbuf[11] == '\0') ? hbuf + 6 : "";
- const std::int_fast32_t index_offset = Decode32(hbuf + 12);
- const std::int_fast32_t data_offset = Decode32(hbuf + 16);
- if (index_offset < 0 || data_offset < index_offset) continue;
- if (fseek(fp.get(), static_cast<long>(index_offset), SEEK_SET) != 0)
- continue;
-
- char ebuf[52]; // covers entry.unused too
- const std::size_t index_size =
- static_cast<std::size_t>(data_offset - index_offset);
- const std::size_t zonecnt = index_size / sizeof(ebuf);
- if (zonecnt * sizeof(ebuf) != index_size) continue;
- for (std::size_t i = 0; i != zonecnt; ++i) {
- if (fread(ebuf, 1, sizeof(ebuf), fp.get()) != sizeof(ebuf)) break;
- const std::int_fast32_t start = data_offset + Decode32(ebuf + 40);
- const std::int_fast32_t length = Decode32(ebuf + 44);
- if (start < 0 || length < 0) break;
- ebuf[40] = '\0'; // ensure zone name is NUL terminated
- if (strcmp(name.c_str() + pos, ebuf) == 0) {
- if (fseek(fp.get(), static_cast<long>(start), SEEK_SET) != 0) break;
- return std::unique_ptr<ZoneInfoSource>(new AndroidZoneInfoSource(
- std::move(fp), static_cast<std::size_t>(length), vers));
- }
- }
- }
-
- return nullptr;
-}
-
-} // namespace
-
bool TimeZoneInfo::Load(const std::string& name) {
// We can ensure that the loading of UTC or any other fixed-offset
// zone never fails because the simple, fixed-offset state can be
@@ -725,11 +820,24 @@
name, [](const std::string& n) -> std::unique_ptr<ZoneInfoSource> {
if (auto z = FileZoneInfoSource::Open(n)) return z;
if (auto z = AndroidZoneInfoSource::Open(n)) return z;
+ if (auto z = FuchsiaZoneInfoSource::Open(n)) return z;
return nullptr;
});
return zip != nullptr && Load(zip.get());
}
+std::unique_ptr<TimeZoneInfo> TimeZoneInfo::UTC() {
+ auto tz = std::unique_ptr<TimeZoneInfo>(new TimeZoneInfo);
+ tz->ResetToBuiltinUTC(seconds::zero());
+ return tz;
+}
+
+std::unique_ptr<TimeZoneInfo> TimeZoneInfo::Make(const std::string& name) {
+ auto tz = std::unique_ptr<TimeZoneInfo>(new TimeZoneInfo);
+ if (!tz->Load(name)) tz.reset(); // fallback to UTC
+ return tz;
+}
+
// BreakTime() translation for a particular transition type.
time_zone::absolute_lookup TimeZoneInfo::LocalTime(
std::int_fast64_t unix_time, const TransitionType& tt) const {
diff --git a/absl/time/internal/cctz/src/time_zone_info.h b/absl/time/internal/cctz/src/time_zone_info.h
index 2467ff5..689df6f 100644
--- a/absl/time/internal/cctz/src/time_zone_info.h
+++ b/absl/time/internal/cctz/src/time_zone_info.h
@@ -18,6 +18,7 @@
#include <atomic>
#include <cstddef>
#include <cstdint>
+#include <memory>
#include <string>
#include <vector>
@@ -64,12 +65,9 @@
// A time zone backed by the IANA Time Zone Database (zoneinfo).
class TimeZoneInfo : public TimeZoneIf {
public:
- TimeZoneInfo() = default;
- TimeZoneInfo(const TimeZoneInfo&) = delete;
- TimeZoneInfo& operator=(const TimeZoneInfo&) = delete;
-
- // Loads the zoneinfo for the given name, returning true if successful.
- bool Load(const std::string& name);
+ // Factories.
+ static std::unique_ptr<TimeZoneInfo> UTC(); // never fails
+ static std::unique_ptr<TimeZoneInfo> Make(const std::string& name);
// TimeZoneIf implementations.
time_zone::absolute_lookup BreakTime(
@@ -83,17 +81,9 @@
std::string Description() const override;
private:
- struct Header { // counts of:
- std::size_t timecnt; // transition times
- std::size_t typecnt; // transition types
- std::size_t charcnt; // zone abbreviation characters
- std::size_t leapcnt; // leap seconds (we expect none)
- std::size_t ttisstdcnt; // UTC/local indicators (unused)
- std::size_t ttisutcnt; // standard/wall indicators (unused)
-
- bool Build(const tzhead& tzh);
- std::size_t DataLength(std::size_t time_len) const;
- };
+ TimeZoneInfo() = default;
+ TimeZoneInfo(const TimeZoneInfo&) = delete;
+ TimeZoneInfo& operator=(const TimeZoneInfo&) = delete;
bool GetTransitionType(std::int_fast32_t utc_offset, bool is_dst,
const std::string& abbr, std::uint_least8_t* index);
@@ -102,6 +92,7 @@
bool ExtendTransitions();
bool ResetToBuiltinUTC(const seconds& offset);
+ bool Load(const std::string& name);
bool Load(ZoneInfoSource* zip);
// Helpers for BreakTime() and MakeTime().
diff --git a/absl/time/internal/cctz/src/time_zone_libc.cc b/absl/time/internal/cctz/src/time_zone_libc.cc
index 887dd09..d014612 100644
--- a/absl/time/internal/cctz/src/time_zone_libc.cc
+++ b/absl/time/internal/cctz/src/time_zone_libc.cc
@@ -62,7 +62,7 @@
}
#elif defined(__native_client__) || defined(__myriad2__) || \
defined(__EMSCRIPTEN__)
-// Uses the globals: 'timezone' and 'tzname'.
+// Uses the globals: '_timezone' and 'tzname'.
auto tm_gmtoff(const std::tm& tm) -> decltype(_timezone + 0) {
const bool is_dst = tm.tm_isdst > 0;
return _timezone + (is_dst ? 60 * 60 : 0);
@@ -71,6 +71,16 @@
const bool is_dst = tm.tm_isdst > 0;
return tzname[is_dst];
}
+#elif defined(__VXWORKS__)
+// Uses the globals: 'timezone' and 'tzname'.
+auto tm_gmtoff(const std::tm& tm) -> decltype(timezone + 0) {
+ const bool is_dst = tm.tm_isdst > 0;
+ return timezone + (is_dst ? 60 * 60 : 0);
+}
+auto tm_zone(const std::tm& tm) -> decltype(tzname[0]) {
+ const bool is_dst = tm.tm_isdst > 0;
+ return tzname[is_dst];
+}
#else
// Adapt to different spellings of the struct std::tm extension fields.
#if defined(tm_gmtoff)
@@ -108,6 +118,7 @@
}
#endif // tm_zone
#endif
+using tm_gmtoff_t = decltype(tm_gmtoff(std::tm{}));
inline std::tm* gm_time(const std::time_t* timep, std::tm* result) {
#if defined(_WIN32) || defined(_WIN64)
@@ -125,37 +136,36 @@
#endif
}
-// Converts a civil second and "dst" flag into a time_t and UTC offset.
+// Converts a civil second and "dst" flag into a time_t and a struct tm.
// Returns false if time_t cannot represent the requested civil second.
// Caller must have already checked that cs.year() will fit into a tm_year.
-bool make_time(const civil_second& cs, int is_dst, std::time_t* t, int* off) {
- std::tm tm;
- tm.tm_year = static_cast<int>(cs.year() - year_t{1900});
- tm.tm_mon = cs.month() - 1;
- tm.tm_mday = cs.day();
- tm.tm_hour = cs.hour();
- tm.tm_min = cs.minute();
- tm.tm_sec = cs.second();
- tm.tm_isdst = is_dst;
- *t = std::mktime(&tm);
+bool make_time(const civil_second& cs, int is_dst, std::time_t* t,
+ std::tm* tm) {
+ tm->tm_year = static_cast<int>(cs.year() - year_t{1900});
+ tm->tm_mon = cs.month() - 1;
+ tm->tm_mday = cs.day();
+ tm->tm_hour = cs.hour();
+ tm->tm_min = cs.minute();
+ tm->tm_sec = cs.second();
+ tm->tm_isdst = is_dst;
+ *t = std::mktime(tm);
if (*t == std::time_t{-1}) {
std::tm tm2;
const std::tm* tmp = local_time(t, &tm2);
- if (tmp == nullptr || tmp->tm_year != tm.tm_year ||
- tmp->tm_mon != tm.tm_mon || tmp->tm_mday != tm.tm_mday ||
- tmp->tm_hour != tm.tm_hour || tmp->tm_min != tm.tm_min ||
- tmp->tm_sec != tm.tm_sec) {
+ if (tmp == nullptr || tmp->tm_year != tm->tm_year ||
+ tmp->tm_mon != tm->tm_mon || tmp->tm_mday != tm->tm_mday ||
+ tmp->tm_hour != tm->tm_hour || tmp->tm_min != tm->tm_min ||
+ tmp->tm_sec != tm->tm_sec) {
// A true error (not just one second before the epoch).
return false;
}
}
- *off = static_cast<int>(tm_gmtoff(tm));
return true;
}
// Find the least time_t in [lo:hi] where local time matches offset, given:
// (1) lo doesn't match, (2) hi does, and (3) there is only one transition.
-std::time_t find_trans(std::time_t lo, std::time_t hi, int offset) {
+std::time_t find_trans(std::time_t lo, std::time_t hi, tm_gmtoff_t offset) {
std::tm tm;
while (lo + 1 != hi) {
const std::time_t mid = lo + (hi - lo) / 2;
@@ -183,8 +193,9 @@
} // namespace
-TimeZoneLibC::TimeZoneLibC(const std::string& name)
- : local_(name == "localtime") {}
+std::unique_ptr<TimeZoneLibC> TimeZoneLibC::Make(const std::string& name) {
+ return std::unique_ptr<TimeZoneLibC>(new TimeZoneLibC(name));
+}
time_zone::absolute_lookup TimeZoneLibC::BreakTime(
const time_point<seconds>& tp) const {
@@ -254,33 +265,37 @@
// We probe with "is_dst" values of 0 and 1 to try to distinguish unique
// civil seconds from skipped or repeated ones. This is not always possible
// however, as the "dst" flag does not change over some offset transitions.
- // We are also subject to the vagaries of mktime() implementations.
+ // We are also subject to the vagaries of mktime() implementations. For
+ // example, some implementations treat "tm_isdst" as a demand (useless),
+ // and some as a disambiguator (useful).
std::time_t t0, t1;
- int offset0, offset1;
- if (make_time(cs, 0, &t0, &offset0) && make_time(cs, 1, &t1, &offset1)) {
- if (t0 == t1) {
+ std::tm tm0, tm1;
+ if (make_time(cs, 0, &t0, &tm0) && make_time(cs, 1, &t1, &tm1)) {
+ if (tm0.tm_isdst == tm1.tm_isdst) {
// The civil time was singular (pre == trans == post).
- const time_point<seconds> tp = FromUnixSeconds(t0);
+ const time_point<seconds> tp = FromUnixSeconds(tm0.tm_isdst ? t1 : t0);
return {time_zone::civil_lookup::UNIQUE, tp, tp, tp};
}
- if (t0 > t1) {
+ tm_gmtoff_t offset = tm_gmtoff(tm0);
+ if (t0 < t1) { // negative DST
std::swap(t0, t1);
- std::swap(offset0, offset1);
+ offset = tm_gmtoff(tm1);
}
- const std::time_t tt = find_trans(t0, t1, offset1);
+
+ const std::time_t tt = find_trans(t1, t0, offset);
const time_point<seconds> trans = FromUnixSeconds(tt);
- if (offset0 < offset1) {
+ if (tm0.tm_isdst) {
// The civil time did not exist (pre >= trans > post).
- const time_point<seconds> pre = FromUnixSeconds(t1);
- const time_point<seconds> post = FromUnixSeconds(t0);
+ const time_point<seconds> pre = FromUnixSeconds(t0);
+ const time_point<seconds> post = FromUnixSeconds(t1);
return {time_zone::civil_lookup::SKIPPED, pre, trans, post};
}
// The civil time was ambiguous (pre < trans <= post).
- const time_point<seconds> pre = FromUnixSeconds(t0);
- const time_point<seconds> post = FromUnixSeconds(t1);
+ const time_point<seconds> pre = FromUnixSeconds(t1);
+ const time_point<seconds> post = FromUnixSeconds(t0);
return {time_zone::civil_lookup::REPEATED, pre, trans, post};
}
@@ -309,6 +324,9 @@
return local_ ? "localtime" : "UTC";
}
+TimeZoneLibC::TimeZoneLibC(const std::string& name)
+ : local_(name == "localtime") {}
+
} // namespace cctz
} // namespace time_internal
ABSL_NAMESPACE_END
diff --git a/absl/time/internal/cctz/src/time_zone_libc.h b/absl/time/internal/cctz/src/time_zone_libc.h
index 1da9039..ae21073 100644
--- a/absl/time/internal/cctz/src/time_zone_libc.h
+++ b/absl/time/internal/cctz/src/time_zone_libc.h
@@ -15,6 +15,7 @@
#ifndef ABSL_TIME_INTERNAL_CCTZ_TIME_ZONE_LIBC_H_
#define ABSL_TIME_INTERNAL_CCTZ_TIME_ZONE_LIBC_H_
+#include <memory>
#include <string>
#include "absl/base/config.h"
@@ -27,10 +28,10 @@
// A time zone backed by gmtime_r(3), localtime_r(3), and mktime(3),
// and which therefore only supports UTC and the local time zone.
-// TODO: Add support for fixed offsets from UTC.
class TimeZoneLibC : public TimeZoneIf {
public:
- explicit TimeZoneLibC(const std::string& name);
+ // Factory.
+ static std::unique_ptr<TimeZoneLibC> Make(const std::string& name);
// TimeZoneIf implementations.
time_zone::absolute_lookup BreakTime(
@@ -44,6 +45,10 @@
std::string Description() const override;
private:
+ explicit TimeZoneLibC(const std::string& name);
+ TimeZoneLibC(const TimeZoneLibC&) = delete;
+ TimeZoneLibC& operator=(const TimeZoneLibC&) = delete;
+
const bool local_; // localtime or UTC
};
diff --git a/absl/time/internal/cctz/src/time_zone_lookup.cc b/absl/time/internal/cctz/src/time_zone_lookup.cc
index efdea64..8979174 100644
--- a/absl/time/internal/cctz/src/time_zone_lookup.cc
+++ b/absl/time/internal/cctz/src/time_zone_lookup.cc
@@ -17,9 +17,6 @@
#if defined(__ANDROID__)
#include <sys/system_properties.h>
-#if defined(__ANDROID_API__) && __ANDROID_API__ >= 21
-#include <dlfcn.h>
-#endif
#endif
#if defined(__APPLE__)
@@ -28,6 +25,31 @@
#include <vector>
#endif
+#if defined(__Fuchsia__)
+#include <fuchsia/intl/cpp/fidl.h>
+#include <lib/async-loop/cpp/loop.h>
+#include <lib/fdio/directory.h>
+#include <zircon/types.h>
+#endif
+
+#if defined(_WIN32)
+#include <sdkddkver.h>
+// Include only when the SDK is for Windows 10 (and later), and the binary is
+// targeted for Windows XP and later.
+// Note: The Windows SDK added windows.globalization.h file for Windows 10, but
+// MinGW did not add it until NTDDI_WIN10_NI (SDK version 10.0.22621.0).
+#if ((defined(_WIN32_WINNT_WIN10) && !defined(__MINGW32__)) || \
+ (defined(NTDDI_WIN10_NI) && NTDDI_VERSION >= NTDDI_WIN10_NI)) && \
+ (_WIN32_WINNT >= _WIN32_WINNT_WINXP)
+#define USE_WIN32_LOCAL_TIME_ZONE
+#include <roapi.h>
+#include <tchar.h>
+#include <wchar.h>
+#include <windows.globalization.h>
+#include <windows.h>
+#endif
+#endif
+
#include <cstdlib>
#include <cstring>
#include <string>
@@ -40,34 +62,83 @@
namespace time_internal {
namespace cctz {
-#if defined(__ANDROID__) && defined(__ANDROID_API__) && __ANDROID_API__ >= 21
namespace {
-// Android 'L' removes __system_property_get() from the NDK, however
-// it is still a hidden symbol in libc so we use dlsym() to access it.
-// See Chromium's base/sys_info_android.cc for a similar example.
-
-using property_get_func = int (*)(const char*, char*);
-
-property_get_func LoadSystemPropertyGet() {
- int flag = RTLD_LAZY | RTLD_GLOBAL;
-#if defined(RTLD_NOLOAD)
- flag |= RTLD_NOLOAD; // libc.so should already be resident
-#endif
- if (void* handle = dlopen("libc.so", flag)) {
- void* sym = dlsym(handle, "__system_property_get");
- dlclose(handle);
- return reinterpret_cast<property_get_func>(sym);
+#if defined(USE_WIN32_LOCAL_TIME_ZONE)
+// Calls the WinRT Calendar.GetTimeZone method to obtain the IANA ID of the
+// local time zone. Returns an empty vector in case of an error.
+std::string win32_local_time_zone(const HMODULE combase) {
+ std::string result;
+ const auto ro_activate_instance =
+ reinterpret_cast<decltype(&RoActivateInstance)>(
+ GetProcAddress(combase, "RoActivateInstance"));
+ if (!ro_activate_instance) {
+ return result;
}
- return nullptr;
-}
+ const auto windows_create_string_reference =
+ reinterpret_cast<decltype(&WindowsCreateStringReference)>(
+ GetProcAddress(combase, "WindowsCreateStringReference"));
+ if (!windows_create_string_reference) {
+ return result;
+ }
+ const auto windows_delete_string =
+ reinterpret_cast<decltype(&WindowsDeleteString)>(
+ GetProcAddress(combase, "WindowsDeleteString"));
+ if (!windows_delete_string) {
+ return result;
+ }
+ const auto windows_get_string_raw_buffer =
+ reinterpret_cast<decltype(&WindowsGetStringRawBuffer)>(
+ GetProcAddress(combase, "WindowsGetStringRawBuffer"));
+ if (!windows_get_string_raw_buffer) {
+ return result;
+ }
-int __system_property_get(const char* name, char* value) {
- static property_get_func system_property_get = LoadSystemPropertyGet();
- return system_property_get ? system_property_get(name, value) : -1;
-}
+ // The string returned by WindowsCreateStringReference doesn't need to be
+ // deleted.
+ HSTRING calendar_class_id;
+ HSTRING_HEADER calendar_class_id_header;
+ HRESULT hr = windows_create_string_reference(
+ RuntimeClass_Windows_Globalization_Calendar,
+ sizeof(RuntimeClass_Windows_Globalization_Calendar) / sizeof(wchar_t) - 1,
+ &calendar_class_id_header, &calendar_class_id);
+ if (FAILED(hr)) {
+ return result;
+ }
-} // namespace
+ IInspectable* calendar;
+ hr = ro_activate_instance(calendar_class_id, &calendar);
+ if (FAILED(hr)) {
+ return result;
+ }
+
+ ABI::Windows::Globalization::ITimeZoneOnCalendar* time_zone;
+ hr = calendar->QueryInterface(IID_PPV_ARGS(&time_zone));
+ if (FAILED(hr)) {
+ calendar->Release();
+ return result;
+ }
+
+ HSTRING tz_hstr;
+ hr = time_zone->GetTimeZone(&tz_hstr);
+ if (SUCCEEDED(hr)) {
+ UINT32 wlen;
+ const PCWSTR tz_wstr = windows_get_string_raw_buffer(tz_hstr, &wlen);
+ if (tz_wstr) {
+ const int size =
+ WideCharToMultiByte(CP_UTF8, 0, tz_wstr, static_cast<int>(wlen),
+ nullptr, 0, nullptr, nullptr);
+ result.resize(static_cast<size_t>(size));
+ WideCharToMultiByte(CP_UTF8, 0, tz_wstr, static_cast<int>(wlen),
+ &result[0], size, nullptr, nullptr);
+ }
+ windows_delete_string(tz_hstr);
+ }
+ time_zone->Release();
+ calendar->Release();
+ return result;
+}
#endif
+} // namespace
std::string time_zone::name() const { return effective_impl().Name(); }
@@ -133,13 +204,89 @@
if (CFStringRef tz_name = CFTimeZoneGetName(tz_default)) {
CFStringEncoding encoding = kCFStringEncodingUTF8;
CFIndex length = CFStringGetLength(tz_name);
- buffer.resize(CFStringGetMaximumSizeForEncoding(length, encoding) + 1);
- if (CFStringGetCString(tz_name, &buffer[0], buffer.size(), encoding)) {
+ CFIndex max_size = CFStringGetMaximumSizeForEncoding(length, encoding) + 1;
+ buffer.resize(static_cast<size_t>(max_size));
+ if (CFStringGetCString(tz_name, &buffer[0], max_size, encoding)) {
zone = &buffer[0];
}
}
CFRelease(tz_default);
#endif
+#if defined(__Fuchsia__)
+ std::string primary_tz;
+ [&]() {
+ // Note: We can't use the synchronous FIDL API here because it doesn't
+ // allow timeouts; if the FIDL call failed, local_time_zone() would never
+ // return.
+
+ const zx::duration kTimeout = zx::msec(500);
+
+ // Don't attach to the thread because otherwise the thread's dispatcher
+ // would be set to null when the loop is destroyed, causing any other FIDL
+ // code running on the same thread to crash.
+ async::Loop loop(&kAsyncLoopConfigNeverAttachToThread);
+
+ fuchsia::intl::PropertyProviderHandle handle;
+ zx_status_t status = fdio_service_connect_by_name(
+ fuchsia::intl::PropertyProvider::Name_,
+ handle.NewRequest().TakeChannel().release());
+ if (status != ZX_OK) {
+ return;
+ }
+
+ fuchsia::intl::PropertyProviderPtr intl_provider;
+ status = intl_provider.Bind(std::move(handle), loop.dispatcher());
+ if (status != ZX_OK) {
+ return;
+ }
+
+ intl_provider->GetProfile(
+ [&loop, &primary_tz](fuchsia::intl::Profile profile) {
+ if (!profile.time_zones().empty()) {
+ primary_tz = profile.time_zones()[0].id;
+ }
+ loop.Quit();
+ });
+ loop.Run(zx::deadline_after(kTimeout));
+ }();
+
+ if (!primary_tz.empty()) {
+ zone = primary_tz.c_str();
+ }
+#endif
+#if defined(USE_WIN32_LOCAL_TIME_ZONE)
+ // Use the WinRT Calendar class to get the local time zone. This feature is
+ // available on Windows 10 and later. The library is dynamically linked to
+ // maintain binary compatibility with Windows XP - Windows 7. On Windows 8,
+ // The combase.dll API functions are available but the RoActivateInstance
+ // call will fail for the Calendar class.
+ std::string winrt_tz;
+ const HMODULE combase =
+ LoadLibraryEx(_T("combase.dll"), nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32);
+ if (combase) {
+ const auto ro_initialize = reinterpret_cast<decltype(&::RoInitialize)>(
+ GetProcAddress(combase, "RoInitialize"));
+ const auto ro_uninitialize = reinterpret_cast<decltype(&::RoUninitialize)>(
+ GetProcAddress(combase, "RoUninitialize"));
+ if (ro_initialize && ro_uninitialize) {
+ const HRESULT hr = ro_initialize(RO_INIT_MULTITHREADED);
+ // RPC_E_CHANGED_MODE means that a previous RoInitialize call specified
+ // a different concurrency model. The WinRT runtime is initialized and
+ // should work for our purpose here, but we should *not* call
+ // RoUninitialize because it's a failure.
+ if (SUCCEEDED(hr) || hr == RPC_E_CHANGED_MODE) {
+ winrt_tz = win32_local_time_zone(combase);
+ if (SUCCEEDED(hr)) {
+ ro_uninitialize();
+ }
+ }
+ }
+ FreeLibrary(combase);
+ }
+ if (!winrt_tz.empty()) {
+ zone = winrt_tz.c_str();
+ }
+#endif
// Allow ${TZ} to override to default zone.
char* tz_env = nullptr;
diff --git a/absl/time/internal/cctz/src/time_zone_lookup_test.cc b/absl/time/internal/cctz/src/time_zone_lookup_test.cc
index 6948c3e..6f7e5cf 100644
--- a/absl/time/internal/cctz/src/time_zone_lookup_test.cc
+++ b/absl/time/internal/cctz/src/time_zone_lookup_test.cc
@@ -21,10 +21,14 @@
#include <thread>
#include <vector>
-#include "gtest/gtest.h"
#include "absl/base/config.h"
-#include "absl/time/internal/cctz/include/cctz/civil_time.h"
#include "absl/time/internal/cctz/include/cctz/time_zone.h"
+#if defined(__linux__)
+#include <features.h>
+#endif
+
+#include "gtest/gtest.h"
+#include "absl/time/internal/cctz/include/cctz/civil_time.h"
namespace chrono = std::chrono;
@@ -41,7 +45,6 @@
"Africa/Addis_Ababa",
"Africa/Algiers",
"Africa/Asmara",
- "Africa/Asmera",
"Africa/Bamako",
"Africa/Bangui",
"Africa/Banjul",
@@ -97,7 +100,6 @@
"America/Araguaina",
"America/Argentina/Buenos_Aires",
"America/Argentina/Catamarca",
- "America/Argentina/ComodRivadavia",
"America/Argentina/Cordoba",
"America/Argentina/Jujuy",
"America/Argentina/La_Rioja",
@@ -121,18 +123,16 @@
"America/Boa_Vista",
"America/Bogota",
"America/Boise",
- "America/Buenos_Aires",
"America/Cambridge_Bay",
"America/Campo_Grande",
"America/Cancun",
"America/Caracas",
- "America/Catamarca",
"America/Cayenne",
"America/Cayman",
"America/Chicago",
"America/Chihuahua",
+ "America/Ciudad_Juarez",
"America/Coral_Harbour",
- "America/Cordoba",
"America/Costa_Rica",
"America/Creston",
"America/Cuiaba",
@@ -148,7 +148,6 @@
"America/El_Salvador",
"America/Ensenada",
"America/Fort_Nelson",
- "America/Fort_Wayne",
"America/Fortaleza",
"America/Glace_Bay",
"America/Godthab",
@@ -170,20 +169,16 @@
"America/Indiana/Vevay",
"America/Indiana/Vincennes",
"America/Indiana/Winamac",
- "America/Indianapolis",
"America/Inuvik",
"America/Iqaluit",
"America/Jamaica",
- "America/Jujuy",
"America/Juneau",
"America/Kentucky/Louisville",
"America/Kentucky/Monticello",
- "America/Knox_IN",
"America/Kralendijk",
"America/La_Paz",
"America/Lima",
"America/Los_Angeles",
- "America/Louisville",
"America/Lower_Princes",
"America/Maceio",
"America/Managua",
@@ -192,7 +187,6 @@
"America/Martinique",
"America/Matamoros",
"America/Mazatlan",
- "America/Mendoza",
"America/Menominee",
"America/Merida",
"America/Metlakatla",
@@ -229,7 +223,6 @@
"America/Regina",
"America/Resolute",
"America/Rio_Branco",
- "America/Rosario",
"America/Santa_Isabel",
"America/Santarem",
"America/Santiago",
@@ -265,7 +258,6 @@
"Antarctica/McMurdo",
"Antarctica/Palmer",
"Antarctica/Rothera",
- "Antarctica/South_Pole",
"Antarctica/Syowa",
"Antarctica/Troll",
"Antarctica/Vostok",
@@ -277,7 +269,6 @@
"Asia/Aqtau",
"Asia/Aqtobe",
"Asia/Ashgabat",
- "Asia/Ashkhabad",
"Asia/Atyrau",
"Asia/Baghdad",
"Asia/Bahrain",
@@ -287,13 +278,10 @@
"Asia/Beirut",
"Asia/Bishkek",
"Asia/Brunei",
- "Asia/Calcutta",
"Asia/Chita",
"Asia/Choibalsan",
"Asia/Chongqing",
- "Asia/Chungking",
"Asia/Colombo",
- "Asia/Dacca",
"Asia/Damascus",
"Asia/Dhaka",
"Asia/Dili",
@@ -316,14 +304,12 @@
"Asia/Karachi",
"Asia/Kashgar",
"Asia/Kathmandu",
- "Asia/Katmandu",
"Asia/Khandyga",
"Asia/Kolkata",
"Asia/Krasnoyarsk",
"Asia/Kuala_Lumpur",
"Asia/Kuching",
"Asia/Kuwait",
- "Asia/Macao",
"Asia/Macau",
"Asia/Magadan",
"Asia/Makassar",
@@ -340,9 +326,7 @@
"Asia/Qatar",
"Asia/Qostanay",
"Asia/Qyzylorda",
- "Asia/Rangoon",
"Asia/Riyadh",
- "Asia/Saigon",
"Asia/Sakhalin",
"Asia/Samarkand",
"Asia/Seoul",
@@ -354,13 +338,10 @@
"Asia/Tbilisi",
"Asia/Tehran",
"Asia/Tel_Aviv",
- "Asia/Thimbu",
"Asia/Thimphu",
"Asia/Tokyo",
"Asia/Tomsk",
- "Asia/Ujung_Pandang",
"Asia/Ulaanbaatar",
- "Asia/Ulan_Bator",
"Asia/Urumqi",
"Asia/Ust-Nera",
"Asia/Vientiane",
@@ -373,7 +354,6 @@
"Atlantic/Bermuda",
"Atlantic/Canary",
"Atlantic/Cape_Verde",
- "Atlantic/Faeroe",
"Atlantic/Faroe",
"Atlantic/Jan_Mayen",
"Atlantic/Madeira",
@@ -381,7 +361,6 @@
"Atlantic/South_Georgia",
"Atlantic/St_Helena",
"Atlantic/Stanley",
- "Australia/ACT",
"Australia/Adelaide",
"Australia/Brisbane",
"Australia/Broken_Hill",
@@ -390,42 +369,12 @@
"Australia/Darwin",
"Australia/Eucla",
"Australia/Hobart",
- "Australia/LHI",
"Australia/Lindeman",
"Australia/Lord_Howe",
"Australia/Melbourne",
- "Australia/NSW",
- "Australia/North",
"Australia/Perth",
- "Australia/Queensland",
- "Australia/South",
"Australia/Sydney",
- "Australia/Tasmania",
- "Australia/Victoria",
- "Australia/West",
"Australia/Yancowinna",
- "Brazil/Acre",
- "Brazil/DeNoronha",
- "Brazil/East",
- "Brazil/West",
- "CET",
- "CST6CDT",
- "Canada/Atlantic",
- "Canada/Central",
- "Canada/Eastern",
- "Canada/Mountain",
- "Canada/Newfoundland",
- "Canada/Pacific",
- "Canada/Saskatchewan",
- "Canada/Yukon",
- "Chile/Continental",
- "Chile/EasterIsland",
- "Cuba",
- "EET",
- "EST",
- "EST5EDT",
- "Egypt",
- "Eire",
"Etc/GMT",
"Etc/GMT+0",
"Etc/GMT+1",
@@ -483,8 +432,8 @@
"Europe/Istanbul",
"Europe/Jersey",
"Europe/Kaliningrad",
- "Europe/Kiev",
"Europe/Kirov",
+ "Europe/Kyiv",
"Europe/Lisbon",
"Europe/Ljubljana",
"Europe/London",
@@ -514,7 +463,6 @@
"Europe/Tirane",
"Europe/Tiraspol",
"Europe/Ulyanovsk",
- "Europe/Uzhgorod",
"Europe/Vaduz",
"Europe/Vatican",
"Europe/Vienna",
@@ -522,18 +470,8 @@
"Europe/Volgograd",
"Europe/Warsaw",
"Europe/Zagreb",
- "Europe/Zaporozhye",
"Europe/Zurich",
- "GB",
- "GB-Eire",
- "GMT",
- "GMT+0",
- "GMT-0",
- "GMT0",
- "Greenwich",
- "HST",
- "Hongkong",
- "Iceland",
+ "Factory",
"Indian/Antananarivo",
"Indian/Chagos",
"Indian/Christmas",
@@ -545,23 +483,6 @@
"Indian/Mauritius",
"Indian/Mayotte",
"Indian/Reunion",
- "Iran",
- "Israel",
- "Jamaica",
- "Japan",
- "Kwajalein",
- "Libya",
- "MET",
- "MST",
- "MST7MDT",
- "Mexico/BajaNorte",
- "Mexico/BajaSur",
- "Mexico/General",
- "NZ",
- "NZ-CHAT",
- "Navajo",
- "PRC",
- "PST8PDT",
"Pacific/Apia",
"Pacific/Auckland",
"Pacific/Bougainville",
@@ -569,7 +490,6 @@
"Pacific/Chuuk",
"Pacific/Easter",
"Pacific/Efate",
- "Pacific/Enderbury",
"Pacific/Fakaofo",
"Pacific/Fiji",
"Pacific/Funafuti",
@@ -579,6 +499,7 @@
"Pacific/Guam",
"Pacific/Honolulu",
"Pacific/Johnston",
+ "Pacific/Kanton",
"Pacific/Kiritimati",
"Pacific/Kosrae",
"Pacific/Kwajalein",
@@ -593,7 +514,6 @@
"Pacific/Palau",
"Pacific/Pitcairn",
"Pacific/Pohnpei",
- "Pacific/Ponape",
"Pacific/Port_Moresby",
"Pacific/Rarotonga",
"Pacific/Saipan",
@@ -601,34 +521,10 @@
"Pacific/Tahiti",
"Pacific/Tarawa",
"Pacific/Tongatapu",
- "Pacific/Truk",
"Pacific/Wake",
"Pacific/Wallis",
"Pacific/Yap",
- "Poland",
- "Portugal",
- "ROC",
- "ROK",
- "Singapore",
- "Turkey",
- "UCT",
- "US/Alaska",
- "US/Aleutian",
- "US/Arizona",
- "US/Central",
- "US/East-Indiana",
- "US/Eastern",
- "US/Hawaii",
- "US/Indiana-Starke",
- "US/Michigan",
- "US/Mountain",
- "US/Pacific",
- "US/Samoa",
"UTC",
- "Universal",
- "W-SU",
- "WET",
- "Zulu",
nullptr};
// Helper to return a loaded time zone by value (UTC on error).
@@ -727,6 +623,10 @@
time_zone loaded_utc0;
EXPECT_TRUE(load_time_zone("UTC0", &loaded_utc0));
EXPECT_EQ(loaded_utc0, utc);
+
+ time_zone loaded_bad;
+ EXPECT_FALSE(load_time_zone("Invalid/TimeZone", &loaded_bad));
+ EXPECT_EQ(loaded_bad, utc);
}
TEST(TimeZone, NamedTimeZones) {
@@ -904,19 +804,19 @@
const time_zone utc = utc_time_zone();
const time_point<chrono::nanoseconds> tp_ns =
convert(civil_second(2015, 1, 2, 3, 4, 5), utc);
- EXPECT_EQ("04:05", format("%M:%E*S", tp_ns, utc));
+ EXPECT_EQ("04:05", absl::time_internal::cctz::format("%M:%E*S", tp_ns, utc));
const time_point<chrono::microseconds> tp_us =
convert(civil_second(2015, 1, 2, 3, 4, 5), utc);
- EXPECT_EQ("04:05", format("%M:%E*S", tp_us, utc));
+ EXPECT_EQ("04:05", absl::time_internal::cctz::format("%M:%E*S", tp_us, utc));
const time_point<chrono::milliseconds> tp_ms =
convert(civil_second(2015, 1, 2, 3, 4, 5), utc);
- EXPECT_EQ("04:05", format("%M:%E*S", tp_ms, utc));
+ EXPECT_EQ("04:05", absl::time_internal::cctz::format("%M:%E*S", tp_ms, utc));
const time_point<chrono::seconds> tp_s =
convert(civil_second(2015, 1, 2, 3, 4, 5), utc);
- EXPECT_EQ("04:05", format("%M:%E*S", tp_s, utc));
+ EXPECT_EQ("04:05", absl::time_internal::cctz::format("%M:%E*S", tp_s, utc));
const time_point<absl::time_internal::cctz::seconds> tp_s64 =
convert(civil_second(2015, 1, 2, 3, 4, 5), utc);
- EXPECT_EQ("04:05", format("%M:%E*S", tp_s64, utc));
+ EXPECT_EQ("04:05", absl::time_internal::cctz::format("%M:%E*S", tp_s64, utc));
// These next two require chrono::time_point_cast because the conversion
// from a resolution of seconds (the return value of convert()) to a
@@ -924,10 +824,10 @@
const time_point<chrono::minutes> tp_m =
chrono::time_point_cast<chrono::minutes>(
convert(civil_second(2015, 1, 2, 3, 4, 5), utc));
- EXPECT_EQ("04:00", format("%M:%E*S", tp_m, utc));
+ EXPECT_EQ("04:00", absl::time_internal::cctz::format("%M:%E*S", tp_m, utc));
const time_point<chrono::hours> tp_h = chrono::time_point_cast<chrono::hours>(
convert(civil_second(2015, 1, 2, 3, 4, 5), utc));
- EXPECT_EQ("00:00", format("%M:%E*S", tp_h, utc));
+ EXPECT_EQ("00:00", absl::time_internal::cctz::format("%M:%E*S", tp_h, utc));
}
TEST(MakeTime, Normalization) {
@@ -953,9 +853,11 @@
// Approach the maximal time_point<cctz::seconds> value from below.
tp = convert(civil_second(292277026596, 12, 4, 15, 30, 6), utc);
- EXPECT_EQ("292277026596-12-04T15:30:06+00:00", format(RFC3339, tp, utc));
+ EXPECT_EQ("292277026596-12-04T15:30:06+00:00",
+ absl::time_internal::cctz::format(RFC3339, tp, utc));
tp = convert(civil_second(292277026596, 12, 4, 15, 30, 7), utc);
- EXPECT_EQ("292277026596-12-04T15:30:07+00:00", format(RFC3339, tp, utc));
+ EXPECT_EQ("292277026596-12-04T15:30:07+00:00",
+ absl::time_internal::cctz::format(RFC3339, tp, utc));
EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::max(), tp);
tp = convert(civil_second(292277026596, 12, 4, 15, 30, 8), utc);
EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::max(), tp);
@@ -964,7 +866,8 @@
// Checks that we can also get the maximal value for a far-east zone.
tp = convert(civil_second(292277026596, 12, 5, 5, 30, 7), east);
- EXPECT_EQ("292277026596-12-05T05:30:07+14:00", format(RFC3339, tp, east));
+ EXPECT_EQ("292277026596-12-05T05:30:07+14:00",
+ absl::time_internal::cctz::format(RFC3339, tp, east));
EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::max(), tp);
tp = convert(civil_second(292277026596, 12, 5, 5, 30, 8), east);
EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::max(), tp);
@@ -973,7 +876,8 @@
// Checks that we can also get the maximal value for a far-west zone.
tp = convert(civil_second(292277026596, 12, 4, 1, 30, 7), west);
- EXPECT_EQ("292277026596-12-04T01:30:07-14:00", format(RFC3339, tp, west));
+ EXPECT_EQ("292277026596-12-04T01:30:07-14:00",
+ absl::time_internal::cctz::format(RFC3339, tp, west));
EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::max(), tp);
tp = convert(civil_second(292277026596, 12, 4, 7, 30, 8), west);
EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::max(), tp);
@@ -982,9 +886,11 @@
// Approach the minimal time_point<cctz::seconds> value from above.
tp = convert(civil_second(-292277022657, 1, 27, 8, 29, 53), utc);
- EXPECT_EQ("-292277022657-01-27T08:29:53+00:00", format(RFC3339, tp, utc));
+ EXPECT_EQ("-292277022657-01-27T08:29:53+00:00",
+ absl::time_internal::cctz::format(RFC3339, tp, utc));
tp = convert(civil_second(-292277022657, 1, 27, 8, 29, 52), utc);
- EXPECT_EQ("-292277022657-01-27T08:29:52+00:00", format(RFC3339, tp, utc));
+ EXPECT_EQ("-292277022657-01-27T08:29:52+00:00",
+ absl::time_internal::cctz::format(RFC3339, tp, utc));
EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::min(), tp);
tp = convert(civil_second(-292277022657, 1, 27, 8, 29, 51), utc);
EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::min(), tp);
@@ -993,7 +899,8 @@
// Checks that we can also get the minimal value for a far-east zone.
tp = convert(civil_second(-292277022657, 1, 27, 22, 29, 52), east);
- EXPECT_EQ("-292277022657-01-27T22:29:52+14:00", format(RFC3339, tp, east));
+ EXPECT_EQ("-292277022657-01-27T22:29:52+14:00",
+ absl::time_internal::cctz::format(RFC3339, tp, east));
EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::min(), tp);
tp = convert(civil_second(-292277022657, 1, 27, 22, 29, 51), east);
EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::min(), tp);
@@ -1002,7 +909,8 @@
// Checks that we can also get the minimal value for a far-west zone.
tp = convert(civil_second(-292277022657, 1, 26, 18, 29, 52), west);
- EXPECT_EQ("-292277022657-01-26T18:29:52-14:00", format(RFC3339, tp, west));
+ EXPECT_EQ("-292277022657-01-26T18:29:52-14:00",
+ absl::time_internal::cctz::format(RFC3339, tp, west));
EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::min(), tp);
tp = convert(civil_second(-292277022657, 1, 26, 18, 29, 51), west);
EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::min(), tp);
@@ -1019,14 +927,20 @@
const time_zone cut = LoadZone("libc:UTC");
const year_t max_tm_year = year_t{std::numeric_limits<int>::max()} + 1900;
tp = convert(civil_second(max_tm_year, 12, 31, 23, 59, 59), cut);
-#if defined(__FreeBSD__) || defined(__OpenBSD__)
- // The BSD gmtime_r() fails on extreme positive tm_year values.
+#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__EMSCRIPTEN__)
+ // Some gmtime_r() impls fail on extreme positive values.
#else
- EXPECT_EQ("2147485547-12-31T23:59:59+00:00", format(RFC3339, tp, cut));
+ EXPECT_EQ("2147485547-12-31T23:59:59+00:00",
+ absl::time_internal::cctz::format(RFC3339, tp, cut));
#endif
const year_t min_tm_year = year_t{std::numeric_limits<int>::min()} + 1900;
tp = convert(civil_second(min_tm_year, 1, 1, 0, 0, 0), cut);
- EXPECT_EQ("-2147481748-01-01T00:00:00+00:00", format(RFC3339, tp, cut));
+#if defined(__Fuchsia__) || defined(__EMSCRIPTEN__)
+ // Some gmtime_r() impls fail on extreme negative values (fxbug.dev/78527).
+#else
+ EXPECT_EQ("-2147481748-01-01T00:00:00+00:00",
+ absl::time_internal::cctz::format(RFC3339, tp, cut));
+#endif
#endif
}
}
@@ -1038,7 +952,7 @@
// 1) we know how to change the time zone used by localtime()/mktime(),
// 2) cctz and localtime()/mktime() will use similar-enough tzdata, and
// 3) we have some idea about how mktime() behaves during transitions.
-#if defined(__linux__) && !defined(__ANDROID__)
+#if defined(__linux__) && defined(__GLIBC__) && !defined(__ANDROID__)
const char* const ep = getenv("TZ");
std::string tz_name = (ep != nullptr) ? ep : "";
for (const char* const* np = kTimeZoneNames; *np != nullptr; ++np) {
@@ -1051,7 +965,7 @@
tp = zi.lookup(transition.to).trans) {
const auto fcl = zi.lookup(transition.from);
const auto tcl = zi.lookup(transition.to);
- civil_second cs; // compare cs in zi and lc
+ civil_second cs, us; // compare cs and us in zi and lc
if (fcl.kind == time_zone::civil_lookup::UNIQUE) {
if (tcl.kind == time_zone::civil_lookup::UNIQUE) {
// Both unique; must be an is_dst or abbr change.
@@ -1067,12 +981,14 @@
}
ASSERT_EQ(time_zone::civil_lookup::REPEATED, tcl.kind);
cs = transition.to;
+ us = transition.from;
} else {
ASSERT_EQ(time_zone::civil_lookup::UNIQUE, tcl.kind);
ASSERT_EQ(time_zone::civil_lookup::SKIPPED, fcl.kind);
cs = transition.from;
+ us = transition.to;
}
- if (cs.year() > 2037) break; // limit test time (and to 32-bit time_t)
+ if (us.year() > 2037) break; // limit test time (and to 32-bit time_t)
const auto cl_zi = zi.lookup(cs);
if (zi.lookup(cl_zi.pre).is_dst == zi.lookup(cl_zi.post).is_dst) {
// The "libc" implementation cannot correctly classify transitions
@@ -1104,6 +1020,13 @@
EXPECT_EQ(cl_zi.pre, cl_lc.pre);
EXPECT_EQ(cl_zi.trans, cl_lc.trans);
EXPECT_EQ(cl_zi.post, cl_lc.post);
+ const auto ucl_zi = zi.lookup(us);
+ const auto ucl_lc = lc.lookup(us);
+ SCOPED_TRACE(testing::Message() << "For " << us << " in " << *np);
+ EXPECT_EQ(ucl_zi.kind, ucl_lc.kind);
+ EXPECT_EQ(ucl_zi.pre, ucl_lc.pre);
+ EXPECT_EQ(ucl_zi.trans, ucl_lc.trans);
+ EXPECT_EQ(ucl_zi.post, ucl_lc.post);
}
}
if (ep == nullptr) {
@@ -1177,6 +1100,45 @@
// We have a transition but we don't know which one.
}
+TEST(NextTransition, Scan) {
+ for (const char* const* np = kTimeZoneNames; *np != nullptr; ++np) {
+ SCOPED_TRACE(testing::Message() << "In " << *np);
+ time_zone tz;
+ // EXPECT_TRUE(load_time_zone(*np, &tz));
+ if (!load_time_zone(*np, &tz)) {
+ continue; // tolerate kTimeZoneNames/zoneinfo skew
+ }
+
+ auto tp = time_point<absl::time_internal::cctz::seconds>::min();
+ time_zone::civil_transition trans;
+ while (tz.next_transition(tp, &trans)) {
+ time_zone::civil_lookup from_cl = tz.lookup(trans.from);
+ EXPECT_NE(from_cl.kind, time_zone::civil_lookup::REPEATED);
+ time_zone::civil_lookup to_cl = tz.lookup(trans.to);
+ EXPECT_NE(to_cl.kind, time_zone::civil_lookup::SKIPPED);
+
+ auto trans_tp = to_cl.trans;
+ time_zone::absolute_lookup trans_al = tz.lookup(trans_tp);
+ EXPECT_EQ(trans_al.cs, trans.to);
+ auto pre_trans_tp = trans_tp - absl::time_internal::cctz::seconds(1);
+ time_zone::absolute_lookup pre_trans_al = tz.lookup(pre_trans_tp);
+ EXPECT_EQ(pre_trans_al.cs + 1, trans.from);
+
+ auto offset_delta = trans_al.offset - pre_trans_al.offset;
+ EXPECT_EQ(offset_delta, trans.to - trans.from);
+ if (offset_delta == 0) {
+ // This "transition" is only an is_dst or abbr change.
+ EXPECT_EQ(to_cl.kind, time_zone::civil_lookup::UNIQUE);
+ if (trans_al.is_dst == pre_trans_al.is_dst) {
+ EXPECT_STRNE(trans_al.abbr, pre_trans_al.abbr);
+ }
+ }
+
+ tp = trans_tp; // continue scan from transition
+ }
+ }
+}
+
TEST(TimeZoneEdgeCase, AmericaNewYork) {
const time_zone tz = LoadZone("America/New_York");
diff --git a/absl/time/internal/cctz/src/time_zone_posix.h b/absl/time/internal/cctz/src/time_zone_posix.h
index 0cf2905..7fd2b9e 100644
--- a/absl/time/internal/cctz/src/time_zone_posix.h
+++ b/absl/time/internal/cctz/src/time_zone_posix.h
@@ -104,7 +104,7 @@
// The entirety of a POSIX-string specified time-zone rule. The standard
// abbreviation and offset are always given. If the time zone includes
-// daylight saving, then the daylight abbrevation is non-empty and the
+// daylight saving, then the daylight abbreviation is non-empty and the
// remaining fields are also valid. Note that the start/end transitions
// are not ordered---in the southern hemisphere the transition to end
// daylight time occurs first in any particular year.
diff --git a/absl/time/internal/cctz/src/tzfile.h b/absl/time/internal/cctz/src/tzfile.h
index 269fa36..2be3bb8 100644
--- a/absl/time/internal/cctz/src/tzfile.h
+++ b/absl/time/internal/cctz/src/tzfile.h
@@ -21,14 +21,6 @@
** Information about time zone files.
*/
-#ifndef TZDIR
-#define TZDIR "/usr/share/zoneinfo" /* Time zone object file directory */
-#endif /* !defined TZDIR */
-
-#ifndef TZDEFAULT
-#define TZDEFAULT "/etc/localtime"
-#endif /* !defined TZDEFAULT */
-
#ifndef TZDEFRULES
#define TZDEFRULES "posixrules"
#endif /* !defined TZDEFRULES */
@@ -43,7 +35,7 @@
struct tzhead {
char tzh_magic[4]; /* TZ_MAGIC */
- char tzh_version[1]; /* '\0' or '2' or '3' as of 2013 */
+ char tzh_version[1]; /* '\0' or '2'-'4' as of 2021 */
char tzh_reserved[15]; /* reserved; must be zero */
char tzh_ttisutcnt[4]; /* coded number of trans. time flags */
char tzh_ttisstdcnt[4]; /* coded number of trans. time flags */
@@ -85,11 +77,11 @@
** time uses 8 rather than 4 chars,
** then a POSIX-TZ-environment-variable-style string for use in handling
** instants after the last transition time stored in the file
-** (with nothing between the newlines if there is no POSIX representation for
-** such instants).
+** (with nothing between the newlines if there is no POSIX.1-2017
+** representation for such instants).
**
** If tz_version is '3' or greater, the above is extended as follows.
-** First, the POSIX TZ string's hour offset may range from -167
+** First, the TZ string's hour offset may range from -167
** through 167 as compared to the POSIX-required 0 through 24.
** Second, its DST start time may be January 1 at 00:00 and its stop
** time December 31 at 24:00 plus the difference between DST and
@@ -102,20 +94,24 @@
*/
#ifndef TZ_MAX_TIMES
+/* This must be at least 242 for Europe/London with 'zic -b fat'. */
#define TZ_MAX_TIMES 2000
#endif /* !defined TZ_MAX_TIMES */
#ifndef TZ_MAX_TYPES
-/* This must be at least 17 for Europe/Samara and Europe/Vilnius. */
+/* This must be at least 18 for Europe/Vilnius with 'zic -b fat'. */
#define TZ_MAX_TYPES 256 /* Limited by what (unsigned char)'s can hold */
#endif /* !defined TZ_MAX_TYPES */
#ifndef TZ_MAX_CHARS
+/* This must be at least 40 for America/Anchorage. */
#define TZ_MAX_CHARS 50 /* Maximum number of abbreviation characters */
/* (limited by what unsigned chars can hold) */
#endif /* !defined TZ_MAX_CHARS */
#ifndef TZ_MAX_LEAPS
+/* This must be at least 27 for leap seconds from 1972 through mid-2023.
+ There's a plan to discontinue leap seconds by 2035. */
#define TZ_MAX_LEAPS 50 /* Maximum number of leap second corrections */
#endif /* !defined TZ_MAX_LEAPS */
diff --git a/absl/time/internal/cctz/src/zone_info_source.cc b/absl/time/internal/cctz/src/zone_info_source.cc
index 7209533..9bc8197 100644
--- a/absl/time/internal/cctz/src/zone_info_source.cc
+++ b/absl/time/internal/cctz/src/zone_info_source.cc
@@ -58,14 +58,15 @@
// MinGW is GCC on Windows, so while it asserts __has_attribute(weak), the
// Windows linker cannot handle that. Nor does the MinGW compiler know how to
// pass "#pragma comment(linker, ...)" to the Windows linker.
-#if (__has_attribute(weak) || defined(__GNUC__)) && !defined(__MINGW32__)
+#if (__has_attribute(weak) || defined(__GNUC__)) && !defined(__MINGW32__) && \
+ !defined(__CYGWIN__)
ZoneInfoSourceFactory zone_info_source_factory __attribute__((weak)) =
DefaultFactory;
#elif defined(_MSC_VER) && !defined(__MINGW32__) && !defined(_LIBCPP_VERSION)
extern ZoneInfoSourceFactory zone_info_source_factory;
extern ZoneInfoSourceFactory default_factory;
ZoneInfoSourceFactory default_factory = DefaultFactory;
-#if defined(_M_IX86)
+#if defined(_M_IX86) || defined(_M_ARM)
#pragma comment( \
linker, \
"/alternatename:?zone_info_source_factory@cctz_extension@time_internal@" ABSL_INTERNAL_MANGLED_NS \
@@ -83,8 +84,7 @@
"@@U?$default_delete@VZoneInfoSource@cctz@time_internal@" ABSL_INTERNAL_MANGLED_NS \
"@@@std@@@std@@ABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@2@@Z@" ABSL_INTERNAL_MANGLED_BACKREFERENCE \
"@@ZA")
-#elif defined(_M_IA_64) || defined(_M_AMD64) || defined(_M_ARM) || \
- defined(_M_ARM64)
+#elif defined(_M_IA_64) || defined(_M_AMD64) || defined(_M_ARM64)
#pragma comment( \
linker, \
"/alternatename:?zone_info_source_factory@cctz_extension@time_internal@" ABSL_INTERNAL_MANGLED_NS \
diff --git a/absl/time/internal/cctz/testdata/README.zoneinfo b/absl/time/internal/cctz/testdata/README.zoneinfo
index 95fb4a9..0fe5585 100644
--- a/absl/time/internal/cctz/testdata/README.zoneinfo
+++ b/absl/time/internal/cctz/testdata/README.zoneinfo
@@ -21,7 +21,8 @@
REDO=posix_only \
LOCALTIME=Factory \
TZDATA_TEXT= \
- ZONETABLES=zone1970.tab
+ PACKRATDATA=backzone PACKRATLIST=zone.tab \
+ ZONETABLES=zone1970.tab\ zonenow.tab
tar --create --dereference --hard-dereference --file tzfile.tar \
--directory=tz tzfile.h
tar --create --dereference --hard-dereference --file zoneinfo.tar \
diff --git a/absl/time/internal/cctz/testdata/version b/absl/time/internal/cctz/testdata/version
index 1d59095..04fe674 100644
--- a/absl/time/internal/cctz/testdata/version
+++ b/absl/time/internal/cctz/testdata/version
@@ -1 +1 @@
-2021a
+2024a
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Addis_Ababa b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Addis_Ababa
index 5f4ebcb..4e8951f 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Addis_Ababa
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Addis_Ababa
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Asmara b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Asmara
index 5f4ebcb..194e986 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Asmara
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Asmara
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Asmera b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Asmera
index 5f4ebcb..194e986 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Asmera
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Asmera
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Bamako b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Bamako
index 8906e88..3cb875f 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Bamako
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Bamako
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Bangui b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Bangui
index 3d7a71b..0021d2d 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Bangui
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Bangui
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Banjul b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Banjul
index 8906e88..b235744 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Banjul
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Banjul
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Blantyre b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Blantyre
index 651e5cf..d7bca1e 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Blantyre
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Blantyre
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Brazzaville b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Brazzaville
index 3d7a71b..57a723b 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Brazzaville
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Brazzaville
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Bujumbura b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Bujumbura
index 651e5cf..90b8679 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Bujumbura
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Bujumbura
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Cairo b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Cairo
index ea38c97..1e6d48d 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Cairo
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Cairo
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Casablanca b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Casablanca
index 0263c90..240ebb2 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Casablanca
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Casablanca
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Conakry b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Conakry
index 8906e88..c22c328 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Conakry
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Conakry
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Dakar b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Dakar
index 8906e88..1f04c58 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Dakar
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Dakar
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Dar_es_Salaam b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Dar_es_Salaam
index 5f4ebcb..b37c2b4 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Dar_es_Salaam
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Dar_es_Salaam
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Djibouti b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Djibouti
index 5f4ebcb..e9bbc7a 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Djibouti
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Djibouti
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Douala b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Douala
index 3d7a71b..65001f6 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Douala
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Douala
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/El_Aaiun b/absl/time/internal/cctz/testdata/zoneinfo/Africa/El_Aaiun
index 772e23c..909c5f9 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/El_Aaiun
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/El_Aaiun
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Freetown b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Freetown
index 8906e88..8431ed6 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Freetown
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Freetown
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Gaborone b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Gaborone
index 651e5cf..e442098 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Gaborone
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Gaborone
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Harare b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Harare
index 651e5cf..c4a502c 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Harare
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Harare
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Kampala b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Kampala
index 5f4ebcb..3021d84 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Kampala
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Kampala
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Kigali b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Kigali
index 651e5cf..b2eff57 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Kigali
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Kigali
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Kinshasa b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Kinshasa
index 3d7a71b..8d6f2a8 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Kinshasa
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Kinshasa
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Libreville b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Libreville
index 3d7a71b..1544cf5 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Libreville
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Libreville
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Lome b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Lome
index 8906e88..8e2b700 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Lome
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Lome
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Luanda b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Luanda
index 3d7a71b..226d87f 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Luanda
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Luanda
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Lubumbashi b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Lubumbashi
index 651e5cf..14e1ee1 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Lubumbashi
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Lubumbashi
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Lusaka b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Lusaka
index 651e5cf..18fcb16 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Lusaka
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Lusaka
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Malabo b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Malabo
index 3d7a71b..8a3f4e9 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Malabo
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Malabo
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Maseru b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Maseru
index bada063..820d852 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Maseru
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Maseru
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Mbabane b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Mbabane
index bada063..d57a53c 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Mbabane
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Mbabane
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Mogadishu b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Mogadishu
index 5f4ebcb..25a5973 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Mogadishu
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Mogadishu
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Niamey b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Niamey
index 3d7a71b..bdf222a 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Niamey
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Niamey
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Nouakchott b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Nouakchott
index 8906e88..faa6f32 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Nouakchott
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Nouakchott
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Ouagadougou b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Ouagadougou
index 8906e88..f4e55ae 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Ouagadougou
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Ouagadougou
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Porto-Novo b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Porto-Novo
index 3d7a71b..a869ec3 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Porto-Novo
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Porto-Novo
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Timbuktu b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Timbuktu
index 8906e88..3cb875f 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Timbuktu
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Timbuktu
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Anguilla b/absl/time/internal/cctz/testdata/zoneinfo/America/Anguilla
index f4fe590..d057735 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/America/Anguilla
+++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Anguilla
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Antigua b/absl/time/internal/cctz/testdata/zoneinfo/America/Antigua
index f4fe590..7ef2cc9 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/America/Antigua
+++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Antigua
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Aruba b/absl/time/internal/cctz/testdata/zoneinfo/America/Aruba
index d6ddf7d..6158ca5 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/America/Aruba
+++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Aruba
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Bahia_Banderas b/absl/time/internal/cctz/testdata/zoneinfo/America/Bahia_Banderas
index cbe22a7..48faea2 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/America/Bahia_Banderas
+++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Bahia_Banderas
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Barbados b/absl/time/internal/cctz/testdata/zoneinfo/America/Barbados
index 9d3afa6..720c986 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/America/Barbados
+++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Barbados
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Bogota b/absl/time/internal/cctz/testdata/zoneinfo/America/Bogota
index 6cb53d4..85b9033 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/America/Bogota
+++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Bogota
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Cambridge_Bay b/absl/time/internal/cctz/testdata/zoneinfo/America/Cambridge_Bay
index 0a22252..1092f4b 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/America/Cambridge_Bay
+++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Cambridge_Bay
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Cayman b/absl/time/internal/cctz/testdata/zoneinfo/America/Cayman
index 9154643..8be5515 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/America/Cayman
+++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Cayman
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Chihuahua b/absl/time/internal/cctz/testdata/zoneinfo/America/Chihuahua
index e1780a5..5e0a54f 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/America/Chihuahua
+++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Chihuahua
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Ciudad_Juarez b/absl/time/internal/cctz/testdata/zoneinfo/America/Ciudad_Juarez
new file mode 100644
index 0000000..f636ee6
--- /dev/null
+++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Ciudad_Juarez
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Dominica b/absl/time/internal/cctz/testdata/zoneinfo/America/Dominica
index f4fe590..7c7cebf 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/America/Dominica
+++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Dominica
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Ensenada b/absl/time/internal/cctz/testdata/zoneinfo/America/Ensenada
index 19ccd35..42087af 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/Godthab b/absl/time/internal/cctz/testdata/zoneinfo/America/Godthab
index 4ddc99d..310774e 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/America/Godthab
+++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Godthab
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Goose_Bay b/absl/time/internal/cctz/testdata/zoneinfo/America/Goose_Bay
index 820e0dd..e2cc3ee 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/America/Goose_Bay
+++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Goose_Bay
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Grenada b/absl/time/internal/cctz/testdata/zoneinfo/America/Grenada
index f4fe590..a58e63a 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/America/Grenada
+++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Grenada
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Guadeloupe b/absl/time/internal/cctz/testdata/zoneinfo/America/Guadeloupe
index f4fe590..7174738 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/America/Guadeloupe
+++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Guadeloupe
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Guyana b/absl/time/internal/cctz/testdata/zoneinfo/America/Guyana
index ebd85d0..bcc6688 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/America/Guyana
+++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Guyana
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Hermosillo b/absl/time/internal/cctz/testdata/zoneinfo/America/Hermosillo
index 8283239..5c92e29 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/America/Hermosillo
+++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Hermosillo
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Indiana/Winamac b/absl/time/internal/cctz/testdata/zoneinfo/America/Indiana/Winamac
index 8700ed9..679d321 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/America/Indiana/Winamac
+++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Indiana/Winamac
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Inuvik b/absl/time/internal/cctz/testdata/zoneinfo/America/Inuvik
index af3107d..86639f6 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/America/Inuvik
+++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Inuvik
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Iqaluit b/absl/time/internal/cctz/testdata/zoneinfo/America/Iqaluit
index eb2c99c..95e055c 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/America/Iqaluit
+++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Iqaluit
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Matamoros b/absl/time/internal/cctz/testdata/zoneinfo/America/Matamoros
index 722751b..993ac47 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/America/Matamoros
+++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Matamoros
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Mazatlan b/absl/time/internal/cctz/testdata/zoneinfo/America/Mazatlan
index 4c819fa..97d4d36 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/America/Mazatlan
+++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Mazatlan
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Merida b/absl/time/internal/cctz/testdata/zoneinfo/America/Merida
index d3b0ca1..e5de113 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/America/Merida
+++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Merida
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Metlakatla b/absl/time/internal/cctz/testdata/zoneinfo/America/Metlakatla
index 9fefee3..71b0eab 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/America/Metlakatla
+++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Metlakatla
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Mexico_City b/absl/time/internal/cctz/testdata/zoneinfo/America/Mexico_City
index ffcf8be..80a415c 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/America/Mexico_City
+++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Mexico_City
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Miquelon b/absl/time/internal/cctz/testdata/zoneinfo/America/Miquelon
index 3b62585..ba95cb0 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/America/Miquelon
+++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Miquelon
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Moncton b/absl/time/internal/cctz/testdata/zoneinfo/America/Moncton
index ecb69ef..020e33d 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/America/Moncton
+++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Moncton
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Monterrey b/absl/time/internal/cctz/testdata/zoneinfo/America/Monterrey
index dea9e3f..a5822e2 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/America/Monterrey
+++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Monterrey
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Montreal b/absl/time/internal/cctz/testdata/zoneinfo/America/Montreal
index fe6be8e..668e70d 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/America/Montreal
+++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Montreal
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Montserrat b/absl/time/internal/cctz/testdata/zoneinfo/America/Montserrat
index f4fe590..41bf898 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/America/Montserrat
+++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Montserrat
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Nipigon b/absl/time/internal/cctz/testdata/zoneinfo/America/Nipigon
index b9f67a9..668e70d 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/America/Nipigon
+++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Nipigon
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Nuuk b/absl/time/internal/cctz/testdata/zoneinfo/America/Nuuk
index 4ddc99d..310774e 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/America/Nuuk
+++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Nuuk
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Ojinaga b/absl/time/internal/cctz/testdata/zoneinfo/America/Ojinaga
index da0909c..f7e40c0 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/America/Ojinaga
+++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Ojinaga
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Pangnirtung b/absl/time/internal/cctz/testdata/zoneinfo/America/Pangnirtung
index 5be6f9b..95e055c 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/America/Pangnirtung
+++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Pangnirtung
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Punta_Arenas b/absl/time/internal/cctz/testdata/zoneinfo/America/Punta_Arenas
index 5c9a20b..aa839ea 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/America/Punta_Arenas
+++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Punta_Arenas
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Rainy_River b/absl/time/internal/cctz/testdata/zoneinfo/America/Rainy_River
index d6ddda4..7e646d1 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/America/Rainy_River
+++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Rainy_River
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Rankin_Inlet b/absl/time/internal/cctz/testdata/zoneinfo/America/Rankin_Inlet
index 92e2ed2..6d1d90d 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/America/Rankin_Inlet
+++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Rankin_Inlet
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Resolute b/absl/time/internal/cctz/testdata/zoneinfo/America/Resolute
index a84d1df..97eb8a9 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/America/Resolute
+++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Resolute
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 19ccd35..42087af 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/Santiago b/absl/time/internal/cctz/testdata/zoneinfo/America/Santiago
index 8d60322..d3fc9b8 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/America/Santiago
+++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Santiago
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Scoresbysund b/absl/time/internal/cctz/testdata/zoneinfo/America/Scoresbysund
index 6db4912..fc1b11c 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/America/Scoresbysund
+++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Scoresbysund
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/St_Johns b/absl/time/internal/cctz/testdata/zoneinfo/America/St_Johns
index e5f2aec..94d790b 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/America/St_Johns
+++ b/absl/time/internal/cctz/testdata/zoneinfo/America/St_Johns
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/St_Kitts b/absl/time/internal/cctz/testdata/zoneinfo/America/St_Kitts
index f4fe590..6170b6c 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/America/St_Kitts
+++ b/absl/time/internal/cctz/testdata/zoneinfo/America/St_Kitts
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/St_Lucia b/absl/time/internal/cctz/testdata/zoneinfo/America/St_Lucia
index f4fe590..e265baf 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/America/St_Lucia
+++ b/absl/time/internal/cctz/testdata/zoneinfo/America/St_Lucia
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/St_Thomas b/absl/time/internal/cctz/testdata/zoneinfo/America/St_Thomas
index f4fe590..0e62d30 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/America/St_Thomas
+++ b/absl/time/internal/cctz/testdata/zoneinfo/America/St_Thomas
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/St_Vincent b/absl/time/internal/cctz/testdata/zoneinfo/America/St_Vincent
index f4fe590..64cbf90 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/America/St_Vincent
+++ b/absl/time/internal/cctz/testdata/zoneinfo/America/St_Vincent
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Thunder_Bay b/absl/time/internal/cctz/testdata/zoneinfo/America/Thunder_Bay
index fcb0328..668e70d 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/America/Thunder_Bay
+++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Thunder_Bay
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Tijuana b/absl/time/internal/cctz/testdata/zoneinfo/America/Tijuana
index 19ccd35..42087af 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/America/Toronto b/absl/time/internal/cctz/testdata/zoneinfo/America/Toronto
index fe6be8e..668e70d 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/America/Toronto
+++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Toronto
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Tortola b/absl/time/internal/cctz/testdata/zoneinfo/America/Tortola
index f4fe590..a0a5d60 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/America/Tortola
+++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Tortola
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Virgin b/absl/time/internal/cctz/testdata/zoneinfo/America/Virgin
index f4fe590..0e62d30 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/America/Virgin
+++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Virgin
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Whitehorse b/absl/time/internal/cctz/testdata/zoneinfo/America/Whitehorse
index 878b6a9..40baa9a 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/America/Whitehorse
+++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Whitehorse
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Yellowknife b/absl/time/internal/cctz/testdata/zoneinfo/America/Yellowknife
index c779cef..645ee94 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/America/Yellowknife
+++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Yellowknife
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Antarctica/Casey b/absl/time/internal/cctz/testdata/zoneinfo/Antarctica/Casey
index 30315cc..84f1c61 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Antarctica/Casey
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Antarctica/Casey
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Antarctica/Macquarie b/absl/time/internal/cctz/testdata/zoneinfo/Antarctica/Macquarie
index 3fc1f23..99a8e60 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Antarctica/Macquarie
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Antarctica/Macquarie
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Antarctica/McMurdo b/absl/time/internal/cctz/testdata/zoneinfo/Antarctica/McMurdo
index afb3929..ea1f8f8 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Antarctica/McMurdo
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Antarctica/McMurdo
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Antarctica/South_Pole b/absl/time/internal/cctz/testdata/zoneinfo/Antarctica/South_Pole
index afb3929..ea1f8f8 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Antarctica/South_Pole
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Antarctica/South_Pole
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Antarctica/Troll b/absl/time/internal/cctz/testdata/zoneinfo/Antarctica/Troll
index 4e31aff..2359c44 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Antarctica/Troll
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Antarctica/Troll
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Antarctica/Vostok b/absl/time/internal/cctz/testdata/zoneinfo/Antarctica/Vostok
index 6e32907..4ce8f74 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Antarctica/Vostok
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Antarctica/Vostok
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Aden b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Aden
index 01c47cc..ac57147 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Aden
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Aden
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Almaty b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Almaty
index 3ec4fc8..02f047d 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Almaty
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Almaty
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Amman b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Amman
index 1bd09fe..a3f9dff 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Amman
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Amman
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Bahrain b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Bahrain
index 7409d74..33f7a20 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Bahrain
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Bahrain
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Damascus b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Damascus
index 168ef9b..bd1624d 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Damascus
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Damascus
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Gaza b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Gaza
index 58e9fdf..0d79662 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Gaza
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Gaza
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Hebron b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Hebron
index aeda06b..53a3c14 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Hebron
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Hebron
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Ho_Chi_Minh b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Ho_Chi_Minh
index 7ca9972..86e21b0 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Ho_Chi_Minh
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Ho_Chi_Minh
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Kuala_Lumpur b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Kuala_Lumpur
index e93dd51..b396dec 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Kuala_Lumpur
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Kuala_Lumpur
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Kuwait b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Kuwait
index 01c47cc..5c7f106 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Kuwait
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Kuwait
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Muscat b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Muscat
index 58d75bc..cce5e19 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Muscat
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Muscat
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Nicosia b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Nicosia
index c210d0a..390347f 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Nicosia
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Nicosia
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Phnom_Penh b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Phnom_Penh
index ed687d2..c49800e 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Phnom_Penh
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Phnom_Penh
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Qostanay b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Qostanay
index ff6fe61..109fe41 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Qostanay
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Qostanay
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Saigon b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Saigon
index 7ca9972..86e21b0 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Saigon
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Saigon
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Singapore b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Singapore
index 350d77e..dbbdea3 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Singapore
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Singapore
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Tehran b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Tehran
index f1555f0..824acb0 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Tehran
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Tehran
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Vientiane b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Vientiane
index ed687d2..659e511 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Vientiane
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Vientiane
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Atlantic/Azores b/absl/time/internal/cctz/testdata/zoneinfo/Atlantic/Azores
index b7f75a9..e6e2616 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Atlantic/Azores
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Atlantic/Azores
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Atlantic/Madeira b/absl/time/internal/cctz/testdata/zoneinfo/Atlantic/Madeira
index 7c3a49c..cf965c3 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Atlantic/Madeira
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Atlantic/Madeira
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Atlantic/St_Helena b/absl/time/internal/cctz/testdata/zoneinfo/Atlantic/St_Helena
index 8906e88..6f75068 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Atlantic/St_Helena
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Atlantic/St_Helena
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Canada/Eastern b/absl/time/internal/cctz/testdata/zoneinfo/Canada/Eastern
index fe6be8e..668e70d 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Canada/Eastern
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Canada/Eastern
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Canada/Newfoundland b/absl/time/internal/cctz/testdata/zoneinfo/Canada/Newfoundland
index e5f2aec..94d790b 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Canada/Newfoundland
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Canada/Newfoundland
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Canada/Yukon b/absl/time/internal/cctz/testdata/zoneinfo/Canada/Yukon
index 878b6a9..40baa9a 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Canada/Yukon
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Canada/Yukon
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Chile/Continental b/absl/time/internal/cctz/testdata/zoneinfo/Chile/Continental
index 8d60322..d3fc9b8 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Chile/Continental
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Chile/Continental
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Chile/EasterIsland b/absl/time/internal/cctz/testdata/zoneinfo/Chile/EasterIsland
index d29bcd6..54dff00 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Chile/EasterIsland
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Chile/EasterIsland
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Egypt b/absl/time/internal/cctz/testdata/zoneinfo/Egypt
index ea38c97..1e6d48d 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Egypt
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Egypt
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Eire b/absl/time/internal/cctz/testdata/zoneinfo/Eire
index 4a45ea8..17d2b15 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Eire
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Eire
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Belfast b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Belfast
index 323cd38..b9e95d9 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Belfast
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Belfast
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Bucharest b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Bucharest
index efa689b..c4a391e 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Bucharest
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Bucharest
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Chisinau b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Chisinau
index 6970b14..9152e68 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Chisinau
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Chisinau
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Dublin b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Dublin
index 4a45ea8..17d2b15 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Dublin
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Dublin
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Guernsey b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Guernsey
index 323cd38..f2d87c6 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Guernsey
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Guernsey
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Isle_of_Man b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Isle_of_Man
index 323cd38..9f5aaff 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Isle_of_Man
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Isle_of_Man
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Jersey b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Jersey
index 323cd38..c83814d 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Jersey
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Jersey
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Kiev b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Kiev
index 8f83cef..753a6c8 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Kiev
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Kiev
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Kirov b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Kirov
index d1c93c5..bfac561 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Kirov
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Kirov
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Kyiv b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Kyiv
new file mode 100644
index 0000000..753a6c8
--- /dev/null
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Kyiv
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Lisbon b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Lisbon
index 6484166..f0c70b6 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Lisbon
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Lisbon
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Ljubljana b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Ljubljana
index a1bf928..fdb9e86 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Ljubljana
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Ljubljana
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Europe/London b/absl/time/internal/cctz/testdata/zoneinfo/Europe/London
index 323cd38..b9e95d9 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Europe/London
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Europe/London
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Nicosia b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Nicosia
index c210d0a..390347f 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Nicosia
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Nicosia
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Riga b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Riga
index 26af4c9..d99170b 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Riga
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Riga
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Sarajevo b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Sarajevo
index a1bf928..53db056 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Sarajevo
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Sarajevo
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Simferopol b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Simferopol
index 88a6f3b..298b832 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Simferopol
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Simferopol
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Skopje b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Skopje
index a1bf928..036361c 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Skopje
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Skopje
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Sofia b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Sofia
index eabc972..8945068 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Sofia
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Sofia
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Tallinn b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Tallinn
index 5321bbd..fbebdc6 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Tallinn
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Tallinn
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Tiraspol b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Tiraspol
index 6970b14..9152e68 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Tiraspol
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Tiraspol
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Uzhgorod b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Uzhgorod
index a575568..753a6c8 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Uzhgorod
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Uzhgorod
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Vaduz b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Vaduz
index 388df29..28465d8 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Vaduz
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Vaduz
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Vilnius b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Vilnius
index 75b2eeb..43c3d7f 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Vilnius
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Vilnius
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Volgograd b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Volgograd
index c517002..0715d58 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Volgograd
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Volgograd
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Zagreb b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Zagreb
index a1bf928..8e13ede 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Zagreb
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Zagreb
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Zaporozhye b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Zaporozhye
index 4ea8dae..753a6c8 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Zaporozhye
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Zaporozhye
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/GB b/absl/time/internal/cctz/testdata/zoneinfo/GB
index 323cd38..b9e95d9 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/GB
+++ b/absl/time/internal/cctz/testdata/zoneinfo/GB
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/GB-Eire b/absl/time/internal/cctz/testdata/zoneinfo/GB-Eire
index 323cd38..b9e95d9 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/GB-Eire
+++ b/absl/time/internal/cctz/testdata/zoneinfo/GB-Eire
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Indian/Antananarivo b/absl/time/internal/cctz/testdata/zoneinfo/Indian/Antananarivo
index 5f4ebcb..0bf86f0 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Indian/Antananarivo
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Indian/Antananarivo
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Indian/Comoro b/absl/time/internal/cctz/testdata/zoneinfo/Indian/Comoro
index 5f4ebcb..640b3e8 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Indian/Comoro
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Indian/Comoro
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Indian/Mayotte b/absl/time/internal/cctz/testdata/zoneinfo/Indian/Mayotte
index 5f4ebcb..7a009c3 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Indian/Mayotte
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Indian/Mayotte
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Iran b/absl/time/internal/cctz/testdata/zoneinfo/Iran
index f1555f0..824acb0 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Iran
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Iran
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Mexico/BajaNorte b/absl/time/internal/cctz/testdata/zoneinfo/Mexico/BajaNorte
index 19ccd35..42087af 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/Mexico/BajaSur b/absl/time/internal/cctz/testdata/zoneinfo/Mexico/BajaSur
index 4c819fa..97d4d36 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Mexico/BajaSur
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Mexico/BajaSur
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Mexico/General b/absl/time/internal/cctz/testdata/zoneinfo/Mexico/General
index ffcf8be..80a415c 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Mexico/General
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Mexico/General
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Apia b/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Apia
index 244af26..a6b835a 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Apia
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Apia
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Easter b/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Easter
index d29bcd6..54dff00 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Easter
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Easter
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Enderbury b/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Enderbury
index b22ab14..2b6a060 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Enderbury
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Enderbury
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Fiji b/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Fiji
index e3934e4..610b850 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Fiji
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Fiji
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Kanton b/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Kanton
new file mode 100644
index 0000000..2b6a060
--- /dev/null
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Kanton
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Midway b/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Midway
index 001289c..b25364c 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Midway
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Midway
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Niue b/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Niue
index 7b35793..be874e2 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Niue
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Niue
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Norfolk b/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Norfolk
index 79e2a94..0c0bdbd 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Norfolk
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Norfolk
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Rarotonga b/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Rarotonga
index 143a188..7220bda 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Rarotonga
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Rarotonga
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Saipan b/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Saipan
index bf9a2d9..9539353 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Saipan
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Saipan
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Tongatapu b/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Tongatapu
index 54aeb0f..f28c840 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Tongatapu
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Tongatapu
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Portugal b/absl/time/internal/cctz/testdata/zoneinfo/Portugal
index 6484166..f0c70b6 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Portugal
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Portugal
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Singapore b/absl/time/internal/cctz/testdata/zoneinfo/Singapore
index 350d77e..dbbdea3 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/Singapore
+++ b/absl/time/internal/cctz/testdata/zoneinfo/Singapore
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/iso3166.tab b/absl/time/internal/cctz/testdata/zoneinfo/iso3166.tab
index a4ff61a..402c015 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/iso3166.tab
+++ b/absl/time/internal/cctz/testdata/zoneinfo/iso3166.tab
@@ -3,17 +3,22 @@
# This file is in the public domain, so clarified as of
# 2009-05-17 by Arthur David Olson.
#
-# From Paul Eggert (2015-05-02):
+# From Paul Eggert (2023-09-06):
# This file contains a table of two-letter country codes. Columns are
# separated by a single tab. Lines beginning with '#' are comments.
# All text uses UTF-8 encoding. The columns of the table are as follows:
#
# 1. ISO 3166-1 alpha-2 country code, current as of
-# ISO 3166-1 N976 (2018-11-06). See: Updates on ISO 3166-1
-# https://isotc.iso.org/livelink/livelink/Open/16944257
-# 2. The usual English name for the coded region,
-# chosen so that alphabetic sorting of subsets produces helpful lists.
-# This is not the same as the English name in the ISO 3166 tables.
+# ISO/TC 46 N1108 (2023-04-05). See: ISO/TC 46 Documents
+# https://www.iso.org/committee/48750.html?view=documents
+# 2. The usual English name for the coded region. This sometimes
+# departs from ISO-listed names, sometimes so that sorted subsets
+# of names are useful (e.g., "Samoa (American)" and "Samoa
+# (western)" rather than "American Samoa" and "Samoa"),
+# sometimes to avoid confusion among non-experts (e.g.,
+# "Czech Republic" and "Turkey" rather than "Czechia" and "Türkiye"),
+# and sometimes to omit needless detail or churn (e.g., "Netherlands"
+# rather than "Netherlands (the)" or "Netherlands (Kingdom of the)").
#
# The table is sorted by country code.
#
@@ -238,7 +243,7 @@
SZ Eswatini (Swaziland)
TC Turks & Caicos Is
TD Chad
-TF French Southern & Antarctic Lands
+TF French S. Terr.
TG Togo
TH Thailand
TJ Tajikistan
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/localtime b/absl/time/internal/cctz/testdata/zoneinfo/localtime
deleted file mode 100644
index afeeb88..0000000
--- a/absl/time/internal/cctz/testdata/zoneinfo/localtime
+++ /dev/null
Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/zone1970.tab b/absl/time/internal/cctz/testdata/zoneinfo/zone1970.tab
index 396e4d3..abd9489 100644
--- a/absl/time/internal/cctz/testdata/zoneinfo/zone1970.tab
+++ b/absl/time/internal/cctz/testdata/zoneinfo/zone1970.tab
@@ -18,7 +18,10 @@
# Please see the theory.html file for how these names are chosen.
# If multiple timezones overlap a country, each has a row in the
# table, with each column 1 containing the country code.
-# 4. Comments; present if and only if a country has multiple timezones.
+# 4. Comments; present if and only if countries have multiple timezones,
+# and useful only for those countries. For example, the comments
+# for the row with countries CH,DE,LI and name Europe/Zurich
+# are useful only for DE, since CH and LI have no other timezones.
#
# If a timezone covers multiple countries, the most-populous city is used,
# and that country is listed first in column 1; any other countries
@@ -34,32 +37,30 @@
#country-
#codes coordinates TZ comments
AD +4230+00131 Europe/Andorra
-AE,OM +2518+05518 Asia/Dubai
+AE,OM,RE,SC,TF +2518+05518 Asia/Dubai Crozet
AF +3431+06912 Asia/Kabul
AL +4120+01950 Europe/Tirane
AM +4011+04430 Asia/Yerevan
AQ -6617+11031 Antarctica/Casey Casey
AQ -6835+07758 Antarctica/Davis Davis
-AQ -6640+14001 Antarctica/DumontDUrville Dumont-d'Urville
AQ -6736+06253 Antarctica/Mawson Mawson
AQ -6448-06406 Antarctica/Palmer Palmer
AQ -6734-06808 Antarctica/Rothera Rothera
-AQ -690022+0393524 Antarctica/Syowa Syowa
AQ -720041+0023206 Antarctica/Troll Troll
AQ -7824+10654 Antarctica/Vostok Vostok
AR -3436-05827 America/Argentina/Buenos_Aires Buenos Aires (BA, CF)
-AR -3124-06411 America/Argentina/Cordoba Argentina (most areas: CB, CC, CN, ER, FM, MN, SE, SF)
+AR -3124-06411 America/Argentina/Cordoba most areas: CB, CC, CN, ER, FM, MN, SE, SF
AR -2447-06525 America/Argentina/Salta Salta (SA, LP, NQ, RN)
AR -2411-06518 America/Argentina/Jujuy Jujuy (JY)
AR -2649-06513 America/Argentina/Tucuman Tucumán (TM)
-AR -2828-06547 America/Argentina/Catamarca Catamarca (CT); Chubut (CH)
+AR -2828-06547 America/Argentina/Catamarca Catamarca (CT), Chubut (CH)
AR -2926-06651 America/Argentina/La_Rioja La Rioja (LR)
AR -3132-06831 America/Argentina/San_Juan San Juan (SJ)
AR -3253-06849 America/Argentina/Mendoza Mendoza (MZ)
AR -3319-06621 America/Argentina/San_Luis San Luis (SL)
AR -5138-06913 America/Argentina/Rio_Gallegos Santa Cruz (SC)
AR -5448-06818 America/Argentina/Ushuaia Tierra del Fuego (TF)
-AS,UM -1416-17042 Pacific/Pago_Pago Samoa, Midway
+AS,UM -1416-17042 Pacific/Pago_Pago Midway
AT +4813+01620 Europe/Vienna
AU -3133+15905 Australia/Lord_Howe Lord Howe Island
AU -5430+15857 Antarctica/Macquarie Macquarie Island
@@ -76,13 +77,12 @@
AZ +4023+04951 Asia/Baku
BB +1306-05937 America/Barbados
BD +2343+09025 Asia/Dhaka
-BE +5050+00420 Europe/Brussels
+BE,LU,NL +5050+00420 Europe/Brussels
BG +4241+02319 Europe/Sofia
BM +3217-06446 Atlantic/Bermuda
-BN +0456+11455 Asia/Brunei
BO -1630-06809 America/La_Paz
BR -0351-03225 America/Noronha Atlantic islands
-BR -0127-04829 America/Belem Pará (east); Amapá
+BR -0127-04829 America/Belem Pará (east), Amapá
BR -0343-03830 America/Fortaleza Brazil (northeast: MA, PI, CE, RN, PB)
BR -0803-03454 America/Recife Pernambuco
BR -0712-04812 America/Araguaina Tocantins
@@ -97,43 +97,33 @@
BR -0308-06001 America/Manaus Amazonas (east)
BR -0640-06952 America/Eirunepe Amazonas (west)
BR -0958-06748 America/Rio_Branco Acre
-BS +2505-07721 America/Nassau
BT +2728+08939 Asia/Thimphu
BY +5354+02734 Europe/Minsk
BZ +1730-08812 America/Belize
-CA +4734-05243 America/St_Johns Newfoundland; Labrador (southeast)
-CA +4439-06336 America/Halifax Atlantic - NS (most areas); PE
+CA +4734-05243 America/St_Johns Newfoundland, Labrador (SE)
+CA +4439-06336 America/Halifax Atlantic - NS (most areas), PE
CA +4612-05957 America/Glace_Bay Atlantic - NS (Cape Breton)
CA +4606-06447 America/Moncton Atlantic - New Brunswick
CA +5320-06025 America/Goose_Bay Atlantic - Labrador (most areas)
-CA +5125-05707 America/Blanc-Sablon AST - QC (Lower North Shore)
-CA +4339-07923 America/Toronto Eastern - ON, QC (most areas)
-CA +4901-08816 America/Nipigon Eastern - ON, QC (no DST 1967-73)
-CA +4823-08915 America/Thunder_Bay Eastern - ON (Thunder Bay)
-CA +6344-06828 America/Iqaluit Eastern - NU (most east areas)
-CA +6608-06544 America/Pangnirtung Eastern - NU (Pangnirtung)
-CA +484531-0913718 America/Atikokan EST - ON (Atikokan); NU (Coral H)
-CA +4953-09709 America/Winnipeg Central - ON (west); Manitoba
-CA +4843-09434 America/Rainy_River Central - ON (Rainy R, Ft Frances)
+CA,BS +4339-07923 America/Toronto Eastern - ON & QC (most areas)
+CA +6344-06828 America/Iqaluit Eastern - NU (most areas)
+CA +4953-09709 America/Winnipeg Central - ON (west), Manitoba
CA +744144-0944945 America/Resolute Central - NU (Resolute)
CA +624900-0920459 America/Rankin_Inlet Central - NU (central)
CA +5024-10439 America/Regina CST - SK (most areas)
CA +5017-10750 America/Swift_Current CST - SK (midwest)
-CA +5333-11328 America/Edmonton Mountain - AB; BC (E); SK (W)
+CA +5333-11328 America/Edmonton Mountain - AB, BC(E), NT(E), SK(W)
CA +690650-1050310 America/Cambridge_Bay Mountain - NU (west)
-CA +6227-11421 America/Yellowknife Mountain - NT (central)
CA +682059-1334300 America/Inuvik Mountain - NT (west)
-CA +4906-11631 America/Creston MST - BC (Creston)
-CA +5946-12014 America/Dawson_Creek MST - BC (Dawson Cr, Ft St John)
+CA +5546-12014 America/Dawson_Creek MST - BC (Dawson Cr, Ft St John)
CA +5848-12242 America/Fort_Nelson MST - BC (Ft Nelson)
CA +6043-13503 America/Whitehorse MST - Yukon (east)
CA +6404-13925 America/Dawson MST - Yukon (west)
CA +4916-12307 America/Vancouver Pacific - BC (most areas)
-CC -1210+09655 Indian/Cocos
-CH,DE,LI +4723+00832 Europe/Zurich Swiss time
-CI,BF,GM,GN,ML,MR,SH,SL,SN,TG +0519-00402 Africa/Abidjan
+CH,DE,LI +4723+00832 Europe/Zurich Büsingen
+CI,BF,GH,GM,GN,IS,ML,MR,SH,SL,SN,TG +0519-00402 Africa/Abidjan
CK -2114-15946 Pacific/Rarotonga
-CL -3327-07040 America/Santiago Chile (most areas)
+CL -3327-07040 America/Santiago most of Chile
CL -5309-07055 America/Punta_Arenas Region of Magallanes
CL -2709-10926 Pacific/Easter Easter Island
CN +3114+12128 Asia/Shanghai Beijing Time
@@ -142,13 +132,10 @@
CR +0956-08405 America/Costa_Rica
CU +2308-08222 America/Havana
CV +1455-02331 Atlantic/Cape_Verde
-CW,AW,BQ,SX +1211-06900 America/Curacao
-CX -1025+10543 Indian/Christmas
-CY +3510+03322 Asia/Nicosia Cyprus (most areas)
+CY +3510+03322 Asia/Nicosia most of Cyprus
CY +3507+03357 Asia/Famagusta Northern Cyprus
CZ,SK +5005+01426 Europe/Prague
-DE +5230+01322 Europe/Berlin Germany (most areas)
-DK +5540+01235 Europe/Copenhagen
+DE,DK,NO,SE,SJ +5230+01322 Europe/Berlin most of Germany
DO +1828-06954 America/Santo_Domingo
DZ +3647+00303 Africa/Algiers
EC -0210-07950 America/Guayaquil Ecuador (mainland)
@@ -162,17 +149,14 @@
FI,AX +6010+02458 Europe/Helsinki
FJ -1808+17825 Pacific/Fiji
FK -5142-05751 Atlantic/Stanley
-FM +0725+15147 Pacific/Chuuk Chuuk/Truk, Yap
-FM +0658+15813 Pacific/Pohnpei Pohnpei/Ponape
FM +0519+16259 Pacific/Kosrae Kosrae
FO +6201-00646 Atlantic/Faroe
-FR +4852+00220 Europe/Paris
+FR,MC +4852+00220 Europe/Paris
GB,GG,IM,JE +513030-0000731 Europe/London
GE +4143+04449 Asia/Tbilisi
GF +0456-05220 America/Cayenne
-GH +0533-00013 Africa/Accra
GI +3608-00521 Europe/Gibraltar
-GL +6411-05144 America/Nuuk Greenland (most areas)
+GL +6411-05144 America/Nuuk most of Greenland
GL +7646-01840 America/Danmarkshavn National Park (east coast)
GL +7029-02158 America/Scoresbysund Scoresbysund/Ittoqqortoormiit
GL +7634-06847 America/Thule Thule/Pituffik
@@ -188,27 +172,26 @@
HU +4730+01905 Europe/Budapest
ID -0610+10648 Asia/Jakarta Java, Sumatra
ID -0002+10920 Asia/Pontianak Borneo (west, central)
-ID -0507+11924 Asia/Makassar Borneo (east, south); Sulawesi/Celebes, Bali, Nusa Tengarra; Timor (west)
-ID -0232+14042 Asia/Jayapura New Guinea (West Papua / Irian Jaya); Malukus/Moluccas
+ID -0507+11924 Asia/Makassar Borneo (east, south), Sulawesi/Celebes, Bali, Nusa Tengarra, Timor (west)
+ID -0232+14042 Asia/Jayapura New Guinea (West Papua / Irian Jaya), Malukus/Moluccas
IE +5320-00615 Europe/Dublin
IL +314650+0351326 Asia/Jerusalem
IN +2232+08822 Asia/Kolkata
IO -0720+07225 Indian/Chagos
IQ +3321+04425 Asia/Baghdad
IR +3540+05126 Asia/Tehran
-IS +6409-02151 Atlantic/Reykjavik
IT,SM,VA +4154+01229 Europe/Rome
JM +175805-0764736 America/Jamaica
JO +3157+03556 Asia/Amman
JP +353916+1394441 Asia/Tokyo
KE,DJ,ER,ET,KM,MG,SO,TZ,UG,YT -0117+03649 Africa/Nairobi
KG +4254+07436 Asia/Bishkek
-KI +0125+17300 Pacific/Tarawa Gilbert Islands
-KI -0308-17105 Pacific/Enderbury Phoenix Islands
+KI,MH,TV,UM,WF +0125+17300 Pacific/Tarawa Gilberts, Marshalls, Wake
+KI -0247-17143 Pacific/Kanton Phoenix Islands
KI +0152-15720 Pacific/Kiritimati Line Islands
KP +3901+12545 Asia/Pyongyang
KR +3733+12658 Asia/Seoul
-KZ +4315+07657 Asia/Almaty Kazakhstan (most areas)
+KZ +4315+07657 Asia/Almaty most of Kazakhstan
KZ +4448+06528 Asia/Qyzylorda Qyzylorda/Kyzylorda/Kzyl-Orda
KZ +5312+06337 Asia/Qostanay Qostanay/Kostanay/Kustanay
KZ +5017+05710 Asia/Aqtobe Aqtöbe/Aktobe
@@ -219,62 +202,57 @@
LK +0656+07951 Asia/Colombo
LR +0618-01047 Africa/Monrovia
LT +5441+02519 Europe/Vilnius
-LU +4936+00609 Europe/Luxembourg
LV +5657+02406 Europe/Riga
LY +3254+01311 Africa/Tripoli
MA +3339-00735 Africa/Casablanca
-MC +4342+00723 Europe/Monaco
MD +4700+02850 Europe/Chisinau
-MH +0709+17112 Pacific/Majuro Marshall Islands (most areas)
MH +0905+16720 Pacific/Kwajalein Kwajalein
-MM +1647+09610 Asia/Yangon
-MN +4755+10653 Asia/Ulaanbaatar Mongolia (most areas)
+MM,CC +1647+09610 Asia/Yangon
+MN +4755+10653 Asia/Ulaanbaatar most of Mongolia
MN +4801+09139 Asia/Hovd Bayan-Ölgii, Govi-Altai, Hovd, Uvs, Zavkhan
MN +4804+11430 Asia/Choibalsan Dornod, Sükhbaatar
MO +221150+1133230 Asia/Macau
MQ +1436-06105 America/Martinique
MT +3554+01431 Europe/Malta
MU -2010+05730 Indian/Mauritius
-MV +0410+07330 Indian/Maldives
-MX +1924-09909 America/Mexico_City Central Time
-MX +2105-08646 America/Cancun Eastern Standard Time - Quintana Roo
-MX +2058-08937 America/Merida Central Time - Campeche, Yucatán
-MX +2540-10019 America/Monterrey Central Time - Durango; Coahuila, Nuevo León, Tamaulipas (most areas)
-MX +2550-09730 America/Matamoros Central Time US - Coahuila, Nuevo León, Tamaulipas (US border)
-MX +2313-10625 America/Mazatlan Mountain Time - Baja California Sur, Nayarit, Sinaloa
-MX +2838-10605 America/Chihuahua Mountain Time - Chihuahua (most areas)
-MX +2934-10425 America/Ojinaga Mountain Time US - Chihuahua (US border)
-MX +2904-11058 America/Hermosillo Mountain Standard Time - Sonora
-MX +3232-11701 America/Tijuana Pacific Time US - Baja California
-MX +2048-10515 America/Bahia_Banderas Central Time - Bahía de Banderas
-MY +0310+10142 Asia/Kuala_Lumpur Malaysia (peninsula)
-MY +0133+11020 Asia/Kuching Sabah, Sarawak
+MV,TF +0410+07330 Indian/Maldives Kerguelen, St Paul I, Amsterdam I
+MX +1924-09909 America/Mexico_City Central Mexico
+MX +2105-08646 America/Cancun Quintana Roo
+MX +2058-08937 America/Merida Campeche, Yucatán
+MX +2540-10019 America/Monterrey Durango; Coahuila, Nuevo León, Tamaulipas (most areas)
+MX +2550-09730 America/Matamoros Coahuila, Nuevo León, Tamaulipas (US border)
+MX +2838-10605 America/Chihuahua Chihuahua (most areas)
+MX +3144-10629 America/Ciudad_Juarez Chihuahua (US border - west)
+MX +2934-10425 America/Ojinaga Chihuahua (US border - east)
+MX +2313-10625 America/Mazatlan Baja California Sur, Nayarit (most areas), Sinaloa
+MX +2048-10515 America/Bahia_Banderas Bahía de Banderas
+MX +2904-11058 America/Hermosillo Sonora
+MX +3232-11701 America/Tijuana Baja California
+MY,BN +0133+11020 Asia/Kuching Sabah, Sarawak
MZ,BI,BW,CD,MW,RW,ZM,ZW -2558+03235 Africa/Maputo Central Africa Time
NA -2234+01706 Africa/Windhoek
NC -2216+16627 Pacific/Noumea
NF -2903+16758 Pacific/Norfolk
NG,AO,BJ,CD,CF,CG,CM,GA,GQ,NE +0627+00324 Africa/Lagos West Africa Time
NI +1209-08617 America/Managua
-NL +5222+00454 Europe/Amsterdam
-NO,SJ +5955+01045 Europe/Oslo
NP +2743+08519 Asia/Kathmandu
NR -0031+16655 Pacific/Nauru
NU -1901-16955 Pacific/Niue
NZ,AQ -3652+17446 Pacific/Auckland New Zealand time
NZ -4357-17633 Pacific/Chatham Chatham Islands
-PA,KY +0858-07932 America/Panama
+PA,CA,KY +0858-07932 America/Panama EST - ON (Atikokan), NU (Coral H)
PE -1203-07703 America/Lima
PF -1732-14934 Pacific/Tahiti Society Islands
PF -0900-13930 Pacific/Marquesas Marquesas Islands
PF -2308-13457 Pacific/Gambier Gambier Islands
-PG -0930+14710 Pacific/Port_Moresby Papua New Guinea (most areas)
+PG,AQ,FM -0930+14710 Pacific/Port_Moresby Papua New Guinea (most areas), Chuuk, Yap, Dumont d'Urville
PG -0613+15534 Pacific/Bougainville Bougainville
PH +1435+12100 Asia/Manila
PK +2452+06703 Asia/Karachi
PL +5215+02100 Europe/Warsaw
PM +4703-05620 America/Miquelon
PN -2504-13005 Pacific/Pitcairn
-PR +182806-0660622 America/Puerto_Rico
+PR,AG,CA,AI,AW,BL,BQ,CW,DM,GD,GP,KN,LC,MF,MS,SX,TT,VC,VG,VI +182806-0660622 America/Puerto_Rico AST - QC (Lower North Shore)
PS +3130+03428 Asia/Gaza Gaza Strip
PS +313200+0350542 Asia/Hebron West Bank
PT +3843-00908 Europe/Lisbon Portugal (mainland)
@@ -283,7 +261,6 @@
PW +0720+13429 Pacific/Palau
PY -2516-05740 America/Asuncion
QA,BH +2517+05132 Asia/Qatar
-RE,TF -2052+05528 Indian/Reunion Réunion, Crozet, Scattered Islands
RO +4426+02606 Europe/Bucharest
RS,BA,HR,ME,MK,SI +4450+02030 Europe/Belgrade
RU +5443+02030 Europe/Kaliningrad MSK-01 - Kaliningrad
@@ -311,15 +288,13 @@
RU +643337+1431336 Asia/Ust-Nera MSK+07 - Oymyakonsky
RU +5934+15048 Asia/Magadan MSK+08 - Magadan
RU +4658+14242 Asia/Sakhalin MSK+08 - Sakhalin Island
-RU +6728+15343 Asia/Srednekolymsk MSK+08 - Sakha (E); North Kuril Is
+RU +6728+15343 Asia/Srednekolymsk MSK+08 - Sakha (E), N Kuril Is
RU +5301+15839 Asia/Kamchatka MSK+09 - Kamchatka
RU +6445+17729 Asia/Anadyr MSK+09 - Bering Sea
-SA,KW,YE +2438+04643 Asia/Riyadh
-SB -0932+16012 Pacific/Guadalcanal
-SC -0440+05528 Indian/Mahe
+SA,AQ,KW,YE +2438+04643 Asia/Riyadh Syowa
+SB,FM -0932+16012 Pacific/Guadalcanal Pohnpei
SD +1536+03232 Africa/Khartoum
-SE +5920+01803 Europe/Stockholm
-SG +0117+10351 Asia/Singapore
+SG,MY +0117+10351 Asia/Singapore peninsular Malaysia
SR +0550-05510 America/Paramaribo
SS +0451+03137 Africa/Juba
ST +0020+00644 Africa/Sao_Tome
@@ -327,22 +302,16 @@
SY +3330+03618 Asia/Damascus
TC +2128-07108 America/Grand_Turk
TD +1207+01503 Africa/Ndjamena
-TF -492110+0701303 Indian/Kerguelen Kerguelen, St Paul Island, Amsterdam Island
-TH,KH,LA,VN +1345+10031 Asia/Bangkok Indochina (most areas)
+TH,CX,KH,LA,VN +1345+10031 Asia/Bangkok north Vietnam
TJ +3835+06848 Asia/Dushanbe
TK -0922-17114 Pacific/Fakaofo
TL -0833+12535 Asia/Dili
TM +3757+05823 Asia/Ashgabat
TN +3648+01011 Africa/Tunis
-TO -2110-17510 Pacific/Tongatapu
+TO -210800-1751200 Pacific/Tongatapu
TR +4101+02858 Europe/Istanbul
-TT,AG,AI,BL,DM,GD,GP,KN,LC,MF,MS,VC,VG,VI +1039-06131 America/Port_of_Spain
-TV -0831+17913 Pacific/Funafuti
TW +2503+12130 Asia/Taipei
-UA +5026+03031 Europe/Kiev Ukraine (most areas)
-UA +4837+02218 Europe/Uzhgorod Transcarpathia
-UA +4750+03510 Europe/Zaporozhye Zaporozhye and east Lugansk
-UM +1917+16637 Pacific/Wake Wake Island
+UA +5026+03031 Europe/Kyiv most of Ukraine
US +404251-0740023 America/New_York Eastern (most areas)
US +421953-0830245 America/Detroit Eastern - MI (most areas)
US +381515-0854534 America/Kentucky/Louisville Eastern - KY (Louisville area)
@@ -361,8 +330,8 @@
US +465042-1012439 America/North_Dakota/New_Salem Central - ND (Morton rural)
US +471551-1014640 America/North_Dakota/Beulah Central - ND (Mercer)
US +394421-1045903 America/Denver Mountain (most areas)
-US +433649-1161209 America/Boise Mountain - ID (south); OR (east)
-US +332654-1120424 America/Phoenix MST - Arizona (except Navajo)
+US +433649-1161209 America/Boise Mountain - ID (south), OR (east)
+US,CA +332654-1120424 America/Phoenix MST - AZ (most areas), Creston BC
US +340308-1181434 America/Los_Angeles Pacific
US +611305-1495401 America/Anchorage Alaska (most areas)
US +581807-1342511 America/Juneau Alaska - Juneau area
@@ -370,14 +339,37 @@
US +550737-1313435 America/Metlakatla Alaska - Annette Island
US +593249-1394338 America/Yakutat Alaska - Yakutat
US +643004-1652423 America/Nome Alaska (west)
-US +515248-1763929 America/Adak Aleutian Islands
-US,UM +211825-1575130 Pacific/Honolulu Hawaii
+US +515248-1763929 America/Adak Alaska - western Aleutians
+US +211825-1575130 Pacific/Honolulu Hawaii
UY -345433-0561245 America/Montevideo
UZ +3940+06648 Asia/Samarkand Uzbekistan (west)
UZ +4120+06918 Asia/Tashkent Uzbekistan (east)
VE +1030-06656 America/Caracas
-VN +1045+10640 Asia/Ho_Chi_Minh Vietnam (south)
+VN +1045+10640 Asia/Ho_Chi_Minh south Vietnam
VU -1740+16825 Pacific/Efate
-WF -1318-17610 Pacific/Wallis
WS -1350-17144 Pacific/Apia
ZA,LS,SZ -2615+02800 Africa/Johannesburg
+#
+# The next section contains experimental tab-separated comments for
+# use by user agents like tzselect that identify continents and oceans.
+#
+# For example, the comment "#@AQ<tab>Antarctica/" means the country code
+# AQ is in the continent Antarctica regardless of the Zone name,
+# so Pacific/Auckland should be listed under Antarctica as well as
+# under the Pacific because its line's country codes include AQ.
+#
+# If more than one country code is affected each is listed separated
+# by commas, e.g., #@IS,SH<tab>Atlantic/". If a country code is in
+# more than one continent or ocean, each is listed separated by
+# commas, e.g., the second column of "#@CY,TR<tab>Asia/,Europe/".
+#
+# These experimental comments are present only for country codes where
+# the continent or ocean is not already obvious from the Zone name.
+# For example, there is no such comment for RU since it already
+# corresponds to Zone names starting with both "Europe/" and "Asia/".
+#
+#@AQ Antarctica/
+#@IS,SH Atlantic/
+#@CY,TR Asia/,Europe/
+#@SJ Arctic/
+#@CC,CX,KM,MG,YT Indian/
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/zonenow.tab b/absl/time/internal/cctz/testdata/zoneinfo/zonenow.tab
new file mode 100644
index 0000000..b6f2910
--- /dev/null
+++ b/absl/time/internal/cctz/testdata/zoneinfo/zonenow.tab
@@ -0,0 +1,303 @@
+# tzdb timezone descriptions, for users who do not care about old timestamps
+#
+# This file is in the public domain.
+#
+# From Paul Eggert (2023-12-18):
+# This file contains a table where each row stands for a timezone
+# where civil timestamps are predicted to agree from now on.
+# This file is like zone1970.tab (see zone1970.tab's coments),
+# but with the following changes:
+#
+# 1. Each timezone corresponds to a set of clocks that are planned
+# to agree from now on. This is a larger set of clocks than in
+# zone1970.tab, where each timezone's clocks must agree from 1970 on.
+# 2. The first column is irrelevant and ignored.
+# 3. The table is sorted in a different way:
+# first by standard time UTC offset;
+# then, if DST is used, by daylight saving UTC offset;
+# then by time zone abbreviation.
+# 4. Every timezone has a nonempty comments column, with wording
+# distinguishing the timezone only from other timezones with the
+# same UTC offset at some point during the year.
+#
+# The format of this table is experimental, and may change in future versions.
+#
+# This table is intended as an aid for users, to help them select timezones
+# appropriate for their practical needs. It is not intended to take or
+# endorse any position on legal or territorial claims.
+#
+#XX coordinates TZ comments
+#
+# -11 - SST
+XX -1416-17042 Pacific/Pago_Pago Midway; Samoa ("SST")
+#
+# -11
+XX -1901-16955 Pacific/Niue Niue
+#
+# -10 - HST
+XX +211825-1575130 Pacific/Honolulu Hawaii ("HST")
+#
+# -10
+XX -1732-14934 Pacific/Tahiti Tahiti; Cook Islands
+#
+# -10/-09 - HST / HDT (North America DST)
+XX +515248-1763929 America/Adak western Aleutians in Alaska ("HST/HDT")
+#
+# -09:30
+XX -0900-13930 Pacific/Marquesas Marquesas
+#
+# -09
+XX -2308-13457 Pacific/Gambier Gambier
+#
+# -09/-08 - AKST/AKDT (North America DST)
+XX +611305-1495401 America/Anchorage most of Alaska ("AKST/AKDT")
+#
+# -08
+XX -2504-13005 Pacific/Pitcairn Pitcairn
+#
+# -08/-07 - PST/PDT (North America DST)
+XX +340308-1181434 America/Los_Angeles Pacific ("PST/PDT") - US & Canada; Mexico near US border
+#
+# -07 - MST
+XX +332654-1120424 America/Phoenix Mountain Standard ("MST") - Arizona; western Mexico; Yukon
+#
+# -07/-06 - MST/MDT (North America DST)
+XX +394421-1045903 America/Denver Mountain ("MST/MDT") - US & Canada; Mexico near US border
+#
+# -06
+XX -0054-08936 Pacific/Galapagos Galápagos
+#
+# -06 - CST
+XX +1924-09909 America/Mexico_City Central Standard ("CST") - Saskatchewan; central Mexico; Central America
+#
+# -06/-05 (Chile DST)
+XX -2709-10926 Pacific/Easter Easter Island
+#
+# -06/-05 - CST/CDT (North America DST)
+XX +415100-0873900 America/Chicago Central ("CST/CDT") - US & Canada; Mexico near US border
+#
+# -05
+XX -1203-07703 America/Lima eastern South America
+#
+# -05 - EST
+XX +175805-0764736 America/Jamaica Eastern Standard ("EST") - Caymans; Jamaica; eastern Mexico; Panama
+#
+# -05/-04 - CST/CDT (Cuba DST)
+XX +2308-08222 America/Havana Cuba
+#
+# -05/-04 - EST/EDT (North America DST)
+XX +404251-0740023 America/New_York Eastern ("EST/EDT") - US & Canada
+#
+# -04
+XX +1030-06656 America/Caracas western South America
+#
+# -04 - AST
+XX +1828-06954 America/Santo_Domingo Atlantic Standard ("AST") - eastern Caribbean
+#
+# -04/-03 (Chile DST)
+XX -3327-07040 America/Santiago most of Chile
+#
+# -04/-03 (Paraguay DST)
+XX -2516-05740 America/Asuncion Paraguay
+#
+# -04/-03 - AST/ADT (North America DST)
+XX +4439-06336 America/Halifax Atlantic ("AST/ADT") - Canada; Bermuda
+#
+# -03:30/-02:30 - NST/NDT (North America DST)
+XX +4734-05243 America/St_Johns Newfoundland ("NST/NDT")
+#
+# -03
+XX -2332-04637 America/Sao_Paulo eastern South America
+#
+# -03/-02 (North America DST)
+XX +4703-05620 America/Miquelon St Pierre & Miquelon
+#
+# -02
+XX -0351-03225 America/Noronha Fernando de Noronha; South Georgia
+#
+# -02/-01 (EU DST)
+XX +6411-05144 America/Nuuk most of Greenland
+#
+# -01
+XX +1455-02331 Atlantic/Cape_Verde Cape Verde
+#
+# -01/+00 (EU DST)
+XX +3744-02540 Atlantic/Azores Azores
+# -01/+00 (EU DST) until 2024-03-31; then -02/-01 (EU DST)
+XX +7029-02158 America/Scoresbysund Ittoqqortoormiit
+#
+# +00 - GMT
+XX +0519-00402 Africa/Abidjan far western Africa; Iceland ("GMT")
+#
+# +00/+01 - GMT/BST (EU DST)
+XX +513030-0000731 Europe/London United Kingdom ("GMT/BST")
+#
+# +00/+01 - WET/WEST (EU DST)
+XX +3843-00908 Europe/Lisbon western Europe ("WET/WEST")
+#
+# +00/+02 - Troll DST
+XX -720041+0023206 Antarctica/Troll Troll Station in Antarctica
+#
+# +01 - CET
+XX +3647+00303 Africa/Algiers Algeria, Tunisia ("CET")
+#
+# +01 - WAT
+XX +0627+00324 Africa/Lagos western Africa ("WAT")
+#
+# +01/+00 - IST/GMT (EU DST in reverse)
+XX +5320-00615 Europe/Dublin Ireland ("IST/GMT")
+#
+# +01/+00 - (Morocco DST)
+XX +3339-00735 Africa/Casablanca Morocco
+#
+# +01/+02 - CET/CEST (EU DST)
+XX +4852+00220 Europe/Paris central Europe ("CET/CEST")
+#
+# +02 - CAT
+XX -2558+03235 Africa/Maputo central Africa ("CAT")
+#
+# +02 - EET
+XX +3254+01311 Africa/Tripoli Libya; Kaliningrad ("EET")
+#
+# +02 - SAST
+XX -2615+02800 Africa/Johannesburg southern Africa ("SAST")
+#
+# +02/+03 - EET/EEST (EU DST)
+XX +3758+02343 Europe/Athens eastern Europe ("EET/EEST")
+#
+# +02/+03 - EET/EEST (Egypt DST)
+XX +3003+03115 Africa/Cairo Egypt
+#
+# +02/+03 - EET/EEST (Lebanon DST)
+XX +3353+03530 Asia/Beirut Lebanon
+#
+# +02/+03 - EET/EEST (Moldova DST)
+XX +4700+02850 Europe/Chisinau Moldova
+#
+# +02/+03 - EET/EEST (Palestine DST)
+XX +3130+03428 Asia/Gaza Palestine
+#
+# +02/+03 - IST/IDT (Israel DST)
+XX +314650+0351326 Asia/Jerusalem Israel
+#
+# +03
+XX +4101+02858 Europe/Istanbul Near East; Belarus
+#
+# +03 - EAT
+XX -0117+03649 Africa/Nairobi eastern Africa ("EAT")
+#
+# +03 - MSK
+XX +554521+0373704 Europe/Moscow Moscow ("MSK")
+#
+# +03:30
+XX +3540+05126 Asia/Tehran Iran
+#
+# +04
+XX +2518+05518 Asia/Dubai Russia; Caucasus; Persian Gulf; Seychelles; Réunion
+#
+# +04:30
+XX +3431+06912 Asia/Kabul Afghanistan
+#
+# +05
+XX +4120+06918 Asia/Tashkent Russia; west Kazakhstan; Tajikistan; Turkmenistan; Uzbekistan; Maldives
+#
+# +05 - PKT
+XX +2452+06703 Asia/Karachi Pakistan ("PKT")
+#
+# +05:30
+XX +0656+07951 Asia/Colombo Sri Lanka
+#
+# +05:30 - IST
+XX +2232+08822 Asia/Kolkata India ("IST")
+#
+# +05:45
+XX +2743+08519 Asia/Kathmandu Nepal
+#
+# +06
+XX +2343+09025 Asia/Dhaka Russia; Kyrgyzstan; Bhutan; Bangladesh; Chagos
+# +06 until 2024-03-01; then +05
+XX +4315+07657 Asia/Almaty Kazakhstan (except western areas)
+#
+# +06:30
+XX +1647+09610 Asia/Yangon Myanmar; Cocos
+#
+# +07
+XX +1345+10031 Asia/Bangkok Russia; Indochina; Christmas Island
+#
+# +07 - WIB
+XX -0610+10648 Asia/Jakarta Indonesia ("WIB")
+#
+# +08
+XX +0117+10351 Asia/Singapore Russia; Brunei; Malaysia; Singapore
+#
+# +08 - AWST
+XX -3157+11551 Australia/Perth Western Australia ("AWST")
+#
+# +08 - CST
+XX +3114+12128 Asia/Shanghai China ("CST")
+#
+# +08 - HKT
+XX +2217+11409 Asia/Hong_Kong Hong Kong ("HKT")
+#
+# +08 - PHT
+XX +1435+12100 Asia/Manila Philippines ("PHT")
+#
+# +08 - WITA
+XX -0507+11924 Asia/Makassar Indonesia ("WITA")
+#
+# +08:45
+XX -3143+12852 Australia/Eucla Eucla
+#
+# +09
+XX +5203+11328 Asia/Chita Russia; Palau; East Timor
+#
+# +09 - JST
+XX +353916+1394441 Asia/Tokyo Japan ("JST")
+#
+# +09 - KST
+XX +3733+12658 Asia/Seoul Korea ("KST")
+#
+# +09 - WIT
+XX -0232+14042 Asia/Jayapura Indonesia ("WIT")
+#
+# +09:30 - ACST
+XX -1228+13050 Australia/Darwin Northern Territory ("ACST")
+#
+# +09:30/+10:30 - ACST/ACDT (Australia DST)
+XX -3455+13835 Australia/Adelaide South Australia ("ACST/ACDT")
+#
+# +10
+XX +4310+13156 Asia/Vladivostok Russia; Yap; Chuuk; Papua New Guinea; Dumont d'Urville
+#
+# +10 - AEST
+XX -2728+15302 Australia/Brisbane Queensland ("AEST")
+#
+# +10 - ChST
+XX +1328+14445 Pacific/Guam Mariana Islands ("ChST")
+#
+# +10/+11 - AEST/AEDT (Australia DST)
+XX -3352+15113 Australia/Sydney southeast Australia ("AEST/AEDT")
+#
+# +10:30/+11
+XX -3133+15905 Australia/Lord_Howe Lord Howe Island
+#
+# +11
+XX -0613+15534 Pacific/Bougainville Russia; Kosrae; Bougainville; Solomons
+#
+# +11/+12 (Australia DST)
+XX -2903+16758 Pacific/Norfolk Norfolk Island
+#
+# +12
+XX +5301+15839 Asia/Kamchatka Russia; Tuvalu; Fiji; etc.
+#
+# +12/+13 (New Zealand DST)
+XX -3652+17446 Pacific/Auckland New Zealand ("NZST/NZDT")
+#
+# +12:45/+13:45 (Chatham DST)
+XX -4357-17633 Pacific/Chatham Chatham Islands
+#
+# +13
+XX -210800-1751200 Pacific/Tongatapu Kanton; Tokelau; Samoa (western); Tonga
+#
+# +14
+XX +0152-15720 Pacific/Kiritimati Kiritimati
diff --git a/absl/time/internal/test_util.cc b/absl/time/internal/test_util.cc
index 9a485a0..3e2452e 100644
--- a/absl/time/internal/test_util.cc
+++ b/absl/time/internal/test_util.cc
@@ -14,15 +14,8 @@
#include "absl/time/internal/test_util.h"
-#include <algorithm>
-#include <cstddef>
-#include <cstring>
-
#include "absl/base/config.h"
#include "absl/base/internal/raw_logging.h"
-#include "absl/time/internal/cctz/include/cctz/zone_info_source.h"
-
-namespace cctz = absl::time_internal::cctz;
namespace absl {
ABSL_NAMESPACE_BEGIN
@@ -37,95 +30,3 @@
} // namespace time_internal
ABSL_NAMESPACE_END
} // namespace absl
-
-namespace absl {
-ABSL_NAMESPACE_BEGIN
-namespace time_internal {
-namespace cctz_extension {
-namespace {
-
-// Embed the zoneinfo data for time zones used during tests and benchmarks.
-// The data was generated using "xxd -i zoneinfo-file". There is no need
-// to update the data as long as the tests do not depend on recent changes
-// (and the past rules remain the same).
-#include "absl/time/internal/zoneinfo.inc"
-
-const struct ZoneInfo {
- const char* name;
- const char* data;
- std::size_t length;
-} kZoneInfo[] = {
- // The three real time zones used by :time_test and :time_benchmark.
- {"America/Los_Angeles", //
- reinterpret_cast<char*>(America_Los_Angeles), America_Los_Angeles_len},
- {"America/New_York", //
- reinterpret_cast<char*>(America_New_York), America_New_York_len},
- {"Australia/Sydney", //
- reinterpret_cast<char*>(Australia_Sydney), Australia_Sydney_len},
-
- // Other zones named in tests but which should fail to load.
- {"Invalid/TimeZone", nullptr, 0},
- {"", nullptr, 0},
-
- // Also allow for loading the local time zone under TZ=US/Pacific.
- {"US/Pacific", //
- reinterpret_cast<char*>(America_Los_Angeles), America_Los_Angeles_len},
-
- // Allows use of the local time zone from a system-specific location.
-#ifdef _MSC_VER
- {"localtime", //
- reinterpret_cast<char*>(America_Los_Angeles), America_Los_Angeles_len},
-#else
- {"/etc/localtime", //
- reinterpret_cast<char*>(America_Los_Angeles), America_Los_Angeles_len},
-#endif
-};
-
-class TestZoneInfoSource : public cctz::ZoneInfoSource {
- public:
- TestZoneInfoSource(const char* data, std::size_t size)
- : data_(data), end_(data + size) {}
-
- std::size_t Read(void* ptr, std::size_t size) override {
- const std::size_t len = std::min<std::size_t>(size, end_ - data_);
- memcpy(ptr, data_, len);
- data_ += len;
- return len;
- }
-
- int Skip(std::size_t offset) override {
- data_ += std::min<std::size_t>(offset, end_ - data_);
- return 0;
- }
-
- private:
- const char* data_;
- const char* const end_;
-};
-
-std::unique_ptr<cctz::ZoneInfoSource> TestFactory(
- const std::string& name,
- const std::function<std::unique_ptr<cctz::ZoneInfoSource>(
- const std::string& name)>& /*fallback_factory*/) {
- for (const ZoneInfo& zoneinfo : kZoneInfo) {
- if (name == zoneinfo.name) {
- if (zoneinfo.data == nullptr) return nullptr;
- return std::unique_ptr<cctz::ZoneInfoSource>(
- new TestZoneInfoSource(zoneinfo.data, zoneinfo.length));
- }
- }
- ABSL_RAW_LOG(FATAL, "Unexpected time zone \"%s\" in test", name.c_str());
- return nullptr;
-}
-
-} // namespace
-
-#if !defined(__MINGW32__)
-// MinGW does not support the weak symbol extension mechanism.
-ZoneInfoSourceFactory zone_info_source_factory = TestFactory;
-#endif
-
-} // namespace cctz_extension
-} // namespace time_internal
-ABSL_NAMESPACE_END
-} // namespace absl
diff --git a/absl/time/internal/zoneinfo.inc b/absl/time/internal/zoneinfo.inc
deleted file mode 100644
index bfed829..0000000
--- a/absl/time/internal/zoneinfo.inc
+++ /dev/null
@@ -1,729 +0,0 @@
-unsigned char America_Los_Angeles[] = {
- 0x54, 0x5a, 0x69, 0x66, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05,
- 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xba,
- 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x14, 0x80, 0x00, 0x00, 0x00,
- 0x9e, 0xa6, 0x48, 0xa0, 0x9f, 0xbb, 0x15, 0x90, 0xa0, 0x86, 0x2a, 0xa0,
- 0xa1, 0x9a, 0xf7, 0x90, 0xcb, 0x89, 0x1a, 0xa0, 0xd2, 0x23, 0xf4, 0x70,
- 0xd2, 0x61, 0x26, 0x10, 0xd6, 0xfe, 0x74, 0x5c, 0xd8, 0x80, 0xad, 0x90,
- 0xda, 0xfe, 0xc3, 0x90, 0xdb, 0xc0, 0x90, 0x10, 0xdc, 0xde, 0xa5, 0x90,
- 0xdd, 0xa9, 0xac, 0x90, 0xde, 0xbe, 0x87, 0x90, 0xdf, 0x89, 0x8e, 0x90,
- 0xe0, 0x9e, 0x69, 0x90, 0xe1, 0x69, 0x70, 0x90, 0xe2, 0x7e, 0x4b, 0x90,
- 0xe3, 0x49, 0x52, 0x90, 0xe4, 0x5e, 0x2d, 0x90, 0xe5, 0x29, 0x34, 0x90,
- 0xe6, 0x47, 0x4a, 0x10, 0xe7, 0x12, 0x51, 0x10, 0xe8, 0x27, 0x2c, 0x10,
- 0xe8, 0xf2, 0x33, 0x10, 0xea, 0x07, 0x0e, 0x10, 0xea, 0xd2, 0x15, 0x10,
- 0xeb, 0xe6, 0xf0, 0x10, 0xec, 0xb1, 0xf7, 0x10, 0xed, 0xc6, 0xd2, 0x10,
- 0xee, 0x91, 0xd9, 0x10, 0xef, 0xaf, 0xee, 0x90, 0xf0, 0x71, 0xbb, 0x10,
- 0xf1, 0x8f, 0xd0, 0x90, 0xf2, 0x7f, 0xc1, 0x90, 0xf3, 0x6f, 0xb2, 0x90,
- 0xf4, 0x5f, 0xa3, 0x90, 0xf5, 0x4f, 0x94, 0x90, 0xf6, 0x3f, 0x85, 0x90,
- 0xf7, 0x2f, 0x76, 0x90, 0xf8, 0x28, 0xa2, 0x10, 0xf9, 0x0f, 0x58, 0x90,
- 0xfa, 0x08, 0x84, 0x10, 0xfa, 0xf8, 0x83, 0x20, 0xfb, 0xe8, 0x66, 0x10,
- 0xfc, 0xd8, 0x65, 0x20, 0xfd, 0xc8, 0x48, 0x10, 0xfe, 0xb8, 0x47, 0x20,
- 0xff, 0xa8, 0x2a, 0x10, 0x00, 0x98, 0x29, 0x20, 0x01, 0x88, 0x0c, 0x10,
- 0x02, 0x78, 0x0b, 0x20, 0x03, 0x71, 0x28, 0x90, 0x04, 0x61, 0x27, 0xa0,
- 0x05, 0x51, 0x0a, 0x90, 0x06, 0x41, 0x09, 0xa0, 0x07, 0x30, 0xec, 0x90,
- 0x07, 0x8d, 0x43, 0xa0, 0x09, 0x10, 0xce, 0x90, 0x09, 0xad, 0xbf, 0x20,
- 0x0a, 0xf0, 0xb0, 0x90, 0x0b, 0xe0, 0xaf, 0xa0, 0x0c, 0xd9, 0xcd, 0x10,
- 0x0d, 0xc0, 0x91, 0xa0, 0x0e, 0xb9, 0xaf, 0x10, 0x0f, 0xa9, 0xae, 0x20,
- 0x10, 0x99, 0x91, 0x10, 0x11, 0x89, 0x90, 0x20, 0x12, 0x79, 0x73, 0x10,
- 0x13, 0x69, 0x72, 0x20, 0x14, 0x59, 0x55, 0x10, 0x15, 0x49, 0x54, 0x20,
- 0x16, 0x39, 0x37, 0x10, 0x17, 0x29, 0x36, 0x20, 0x18, 0x22, 0x53, 0x90,
- 0x19, 0x09, 0x18, 0x20, 0x1a, 0x02, 0x35, 0x90, 0x1a, 0xf2, 0x34, 0xa0,
- 0x1b, 0xe2, 0x17, 0x90, 0x1c, 0xd2, 0x16, 0xa0, 0x1d, 0xc1, 0xf9, 0x90,
- 0x1e, 0xb1, 0xf8, 0xa0, 0x1f, 0xa1, 0xdb, 0x90, 0x20, 0x76, 0x2b, 0x20,
- 0x21, 0x81, 0xbd, 0x90, 0x22, 0x56, 0x0d, 0x20, 0x23, 0x6a, 0xda, 0x10,
- 0x24, 0x35, 0xef, 0x20, 0x25, 0x4a, 0xbc, 0x10, 0x26, 0x15, 0xd1, 0x20,
- 0x27, 0x2a, 0x9e, 0x10, 0x27, 0xfe, 0xed, 0xa0, 0x29, 0x0a, 0x80, 0x10,
- 0x29, 0xde, 0xcf, 0xa0, 0x2a, 0xea, 0x62, 0x10, 0x2b, 0xbe, 0xb1, 0xa0,
- 0x2c, 0xd3, 0x7e, 0x90, 0x2d, 0x9e, 0x93, 0xa0, 0x2e, 0xb3, 0x60, 0x90,
- 0x2f, 0x7e, 0x75, 0xa0, 0x30, 0x93, 0x42, 0x90, 0x31, 0x67, 0x92, 0x20,
- 0x32, 0x73, 0x24, 0x90, 0x33, 0x47, 0x74, 0x20, 0x34, 0x53, 0x06, 0x90,
- 0x35, 0x27, 0x56, 0x20, 0x36, 0x32, 0xe8, 0x90, 0x37, 0x07, 0x38, 0x20,
- 0x38, 0x1c, 0x05, 0x10, 0x38, 0xe7, 0x1a, 0x20, 0x39, 0xfb, 0xe7, 0x10,
- 0x3a, 0xc6, 0xfc, 0x20, 0x3b, 0xdb, 0xc9, 0x10, 0x3c, 0xb0, 0x18, 0xa0,
- 0x3d, 0xbb, 0xab, 0x10, 0x3e, 0x8f, 0xfa, 0xa0, 0x3f, 0x9b, 0x8d, 0x10,
- 0x40, 0x6f, 0xdc, 0xa0, 0x41, 0x84, 0xa9, 0x90, 0x42, 0x4f, 0xbe, 0xa0,
- 0x43, 0x64, 0x8b, 0x90, 0x44, 0x2f, 0xa0, 0xa0, 0x45, 0x44, 0x6d, 0x90,
- 0x45, 0xf3, 0xd3, 0x20, 0x47, 0x2d, 0x8a, 0x10, 0x47, 0xd3, 0xb5, 0x20,
- 0x49, 0x0d, 0x6c, 0x10, 0x49, 0xb3, 0x97, 0x20, 0x4a, 0xed, 0x4e, 0x10,
- 0x4b, 0x9c, 0xb3, 0xa0, 0x4c, 0xd6, 0x6a, 0x90, 0x4d, 0x7c, 0x95, 0xa0,
- 0x4e, 0xb6, 0x4c, 0x90, 0x4f, 0x5c, 0x77, 0xa0, 0x50, 0x96, 0x2e, 0x90,
- 0x51, 0x3c, 0x59, 0xa0, 0x52, 0x76, 0x10, 0x90, 0x53, 0x1c, 0x3b, 0xa0,
- 0x54, 0x55, 0xf2, 0x90, 0x54, 0xfc, 0x1d, 0xa0, 0x56, 0x35, 0xd4, 0x90,
- 0x56, 0xe5, 0x3a, 0x20, 0x58, 0x1e, 0xf1, 0x10, 0x58, 0xc5, 0x1c, 0x20,
- 0x59, 0xfe, 0xd3, 0x10, 0x5a, 0xa4, 0xfe, 0x20, 0x5b, 0xde, 0xb5, 0x10,
- 0x5c, 0x84, 0xe0, 0x20, 0x5d, 0xbe, 0x97, 0x10, 0x5e, 0x64, 0xc2, 0x20,
- 0x5f, 0x9e, 0x79, 0x10, 0x60, 0x4d, 0xde, 0xa0, 0x61, 0x87, 0x95, 0x90,
- 0x62, 0x2d, 0xc0, 0xa0, 0x63, 0x67, 0x77, 0x90, 0x64, 0x0d, 0xa2, 0xa0,
- 0x65, 0x47, 0x59, 0x90, 0x65, 0xed, 0x84, 0xa0, 0x67, 0x27, 0x3b, 0x90,
- 0x67, 0xcd, 0x66, 0xa0, 0x69, 0x07, 0x1d, 0x90, 0x69, 0xad, 0x48, 0xa0,
- 0x6a, 0xe6, 0xff, 0x90, 0x6b, 0x96, 0x65, 0x20, 0x6c, 0xd0, 0x1c, 0x10,
- 0x6d, 0x76, 0x47, 0x20, 0x6e, 0xaf, 0xfe, 0x10, 0x6f, 0x56, 0x29, 0x20,
- 0x70, 0x8f, 0xe0, 0x10, 0x71, 0x36, 0x0b, 0x20, 0x72, 0x6f, 0xc2, 0x10,
- 0x73, 0x15, 0xed, 0x20, 0x74, 0x4f, 0xa4, 0x10, 0x74, 0xff, 0x09, 0xa0,
- 0x76, 0x38, 0xc0, 0x90, 0x76, 0xde, 0xeb, 0xa0, 0x78, 0x18, 0xa2, 0x90,
- 0x78, 0xbe, 0xcd, 0xa0, 0x79, 0xf8, 0x84, 0x90, 0x7a, 0x9e, 0xaf, 0xa0,
- 0x7b, 0xd8, 0x66, 0x90, 0x7c, 0x7e, 0x91, 0xa0, 0x7d, 0xb8, 0x48, 0x90,
- 0x7e, 0x5e, 0x73, 0xa0, 0x7f, 0x98, 0x2a, 0x90, 0x02, 0x01, 0x02, 0x01,
- 0x02, 0x03, 0x04, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02,
- 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02,
- 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02,
- 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02,
- 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02,
- 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02,
- 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02,
- 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02,
- 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02,
- 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02,
- 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02,
- 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02,
- 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02,
- 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02,
- 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02,
- 0x01, 0x02, 0xff, 0xff, 0x91, 0x26, 0x00, 0x00, 0xff, 0xff, 0x9d, 0x90,
- 0x01, 0x04, 0xff, 0xff, 0x8f, 0x80, 0x00, 0x08, 0xff, 0xff, 0x9d, 0x90,
- 0x01, 0x0c, 0xff, 0xff, 0x9d, 0x90, 0x01, 0x10, 0x4c, 0x4d, 0x54, 0x00,
- 0x50, 0x44, 0x54, 0x00, 0x50, 0x53, 0x54, 0x00, 0x50, 0x57, 0x54, 0x00,
- 0x50, 0x50, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
- 0x00, 0x01, 0x54, 0x5a, 0x69, 0x66, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0xbb, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x14, 0xf8, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x5e, 0x04,
- 0x1a, 0xc0, 0xff, 0xff, 0xff, 0xff, 0x9e, 0xa6, 0x48, 0xa0, 0xff, 0xff,
- 0xff, 0xff, 0x9f, 0xbb, 0x15, 0x90, 0xff, 0xff, 0xff, 0xff, 0xa0, 0x86,
- 0x2a, 0xa0, 0xff, 0xff, 0xff, 0xff, 0xa1, 0x9a, 0xf7, 0x90, 0xff, 0xff,
- 0xff, 0xff, 0xcb, 0x89, 0x1a, 0xa0, 0xff, 0xff, 0xff, 0xff, 0xd2, 0x23,
- 0xf4, 0x70, 0xff, 0xff, 0xff, 0xff, 0xd2, 0x61, 0x26, 0x10, 0xff, 0xff,
- 0xff, 0xff, 0xd6, 0xfe, 0x74, 0x5c, 0xff, 0xff, 0xff, 0xff, 0xd8, 0x80,
- 0xad, 0x90, 0xff, 0xff, 0xff, 0xff, 0xda, 0xfe, 0xc3, 0x90, 0xff, 0xff,
- 0xff, 0xff, 0xdb, 0xc0, 0x90, 0x10, 0xff, 0xff, 0xff, 0xff, 0xdc, 0xde,
- 0xa5, 0x90, 0xff, 0xff, 0xff, 0xff, 0xdd, 0xa9, 0xac, 0x90, 0xff, 0xff,
- 0xff, 0xff, 0xde, 0xbe, 0x87, 0x90, 0xff, 0xff, 0xff, 0xff, 0xdf, 0x89,
- 0x8e, 0x90, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x9e, 0x69, 0x90, 0xff, 0xff,
- 0xff, 0xff, 0xe1, 0x69, 0x70, 0x90, 0xff, 0xff, 0xff, 0xff, 0xe2, 0x7e,
- 0x4b, 0x90, 0xff, 0xff, 0xff, 0xff, 0xe3, 0x49, 0x52, 0x90, 0xff, 0xff,
- 0xff, 0xff, 0xe4, 0x5e, 0x2d, 0x90, 0xff, 0xff, 0xff, 0xff, 0xe5, 0x29,
- 0x34, 0x90, 0xff, 0xff, 0xff, 0xff, 0xe6, 0x47, 0x4a, 0x10, 0xff, 0xff,
- 0xff, 0xff, 0xe7, 0x12, 0x51, 0x10, 0xff, 0xff, 0xff, 0xff, 0xe8, 0x27,
- 0x2c, 0x10, 0xff, 0xff, 0xff, 0xff, 0xe8, 0xf2, 0x33, 0x10, 0xff, 0xff,
- 0xff, 0xff, 0xea, 0x07, 0x0e, 0x10, 0xff, 0xff, 0xff, 0xff, 0xea, 0xd2,
- 0x15, 0x10, 0xff, 0xff, 0xff, 0xff, 0xeb, 0xe6, 0xf0, 0x10, 0xff, 0xff,
- 0xff, 0xff, 0xec, 0xb1, 0xf7, 0x10, 0xff, 0xff, 0xff, 0xff, 0xed, 0xc6,
- 0xd2, 0x10, 0xff, 0xff, 0xff, 0xff, 0xee, 0x91, 0xd9, 0x10, 0xff, 0xff,
- 0xff, 0xff, 0xef, 0xaf, 0xee, 0x90, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x71,
- 0xbb, 0x10, 0xff, 0xff, 0xff, 0xff, 0xf1, 0x8f, 0xd0, 0x90, 0xff, 0xff,
- 0xff, 0xff, 0xf2, 0x7f, 0xc1, 0x90, 0xff, 0xff, 0xff, 0xff, 0xf3, 0x6f,
- 0xb2, 0x90, 0xff, 0xff, 0xff, 0xff, 0xf4, 0x5f, 0xa3, 0x90, 0xff, 0xff,
- 0xff, 0xff, 0xf5, 0x4f, 0x94, 0x90, 0xff, 0xff, 0xff, 0xff, 0xf6, 0x3f,
- 0x85, 0x90, 0xff, 0xff, 0xff, 0xff, 0xf7, 0x2f, 0x76, 0x90, 0xff, 0xff,
- 0xff, 0xff, 0xf8, 0x28, 0xa2, 0x10, 0xff, 0xff, 0xff, 0xff, 0xf9, 0x0f,
- 0x58, 0x90, 0xff, 0xff, 0xff, 0xff, 0xfa, 0x08, 0x84, 0x10, 0xff, 0xff,
- 0xff, 0xff, 0xfa, 0xf8, 0x83, 0x20, 0xff, 0xff, 0xff, 0xff, 0xfb, 0xe8,
- 0x66, 0x10, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xd8, 0x65, 0x20, 0xff, 0xff,
- 0xff, 0xff, 0xfd, 0xc8, 0x48, 0x10, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xb8,
- 0x47, 0x20, 0xff, 0xff, 0xff, 0xff, 0xff, 0xa8, 0x2a, 0x10, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x98, 0x29, 0x20, 0x00, 0x00, 0x00, 0x00, 0x01, 0x88,
- 0x0c, 0x10, 0x00, 0x00, 0x00, 0x00, 0x02, 0x78, 0x0b, 0x20, 0x00, 0x00,
- 0x00, 0x00, 0x03, 0x71, 0x28, 0x90, 0x00, 0x00, 0x00, 0x00, 0x04, 0x61,
- 0x27, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x05, 0x51, 0x0a, 0x90, 0x00, 0x00,
- 0x00, 0x00, 0x06, 0x41, 0x09, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x07, 0x30,
- 0xec, 0x90, 0x00, 0x00, 0x00, 0x00, 0x07, 0x8d, 0x43, 0xa0, 0x00, 0x00,
- 0x00, 0x00, 0x09, 0x10, 0xce, 0x90, 0x00, 0x00, 0x00, 0x00, 0x09, 0xad,
- 0xbf, 0x20, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xf0, 0xb0, 0x90, 0x00, 0x00,
- 0x00, 0x00, 0x0b, 0xe0, 0xaf, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x0c, 0xd9,
- 0xcd, 0x10, 0x00, 0x00, 0x00, 0x00, 0x0d, 0xc0, 0x91, 0xa0, 0x00, 0x00,
- 0x00, 0x00, 0x0e, 0xb9, 0xaf, 0x10, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xa9,
- 0xae, 0x20, 0x00, 0x00, 0x00, 0x00, 0x10, 0x99, 0x91, 0x10, 0x00, 0x00,
- 0x00, 0x00, 0x11, 0x89, 0x90, 0x20, 0x00, 0x00, 0x00, 0x00, 0x12, 0x79,
- 0x73, 0x10, 0x00, 0x00, 0x00, 0x00, 0x13, 0x69, 0x72, 0x20, 0x00, 0x00,
- 0x00, 0x00, 0x14, 0x59, 0x55, 0x10, 0x00, 0x00, 0x00, 0x00, 0x15, 0x49,
- 0x54, 0x20, 0x00, 0x00, 0x00, 0x00, 0x16, 0x39, 0x37, 0x10, 0x00, 0x00,
- 0x00, 0x00, 0x17, 0x29, 0x36, 0x20, 0x00, 0x00, 0x00, 0x00, 0x18, 0x22,
- 0x53, 0x90, 0x00, 0x00, 0x00, 0x00, 0x19, 0x09, 0x18, 0x20, 0x00, 0x00,
- 0x00, 0x00, 0x1a, 0x02, 0x35, 0x90, 0x00, 0x00, 0x00, 0x00, 0x1a, 0xf2,
- 0x34, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x1b, 0xe2, 0x17, 0x90, 0x00, 0x00,
- 0x00, 0x00, 0x1c, 0xd2, 0x16, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x1d, 0xc1,
- 0xf9, 0x90, 0x00, 0x00, 0x00, 0x00, 0x1e, 0xb1, 0xf8, 0xa0, 0x00, 0x00,
- 0x00, 0x00, 0x1f, 0xa1, 0xdb, 0x90, 0x00, 0x00, 0x00, 0x00, 0x20, 0x76,
- 0x2b, 0x20, 0x00, 0x00, 0x00, 0x00, 0x21, 0x81, 0xbd, 0x90, 0x00, 0x00,
- 0x00, 0x00, 0x22, 0x56, 0x0d, 0x20, 0x00, 0x00, 0x00, 0x00, 0x23, 0x6a,
- 0xda, 0x10, 0x00, 0x00, 0x00, 0x00, 0x24, 0x35, 0xef, 0x20, 0x00, 0x00,
- 0x00, 0x00, 0x25, 0x4a, 0xbc, 0x10, 0x00, 0x00, 0x00, 0x00, 0x26, 0x15,
- 0xd1, 0x20, 0x00, 0x00, 0x00, 0x00, 0x27, 0x2a, 0x9e, 0x10, 0x00, 0x00,
- 0x00, 0x00, 0x27, 0xfe, 0xed, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x29, 0x0a,
- 0x80, 0x10, 0x00, 0x00, 0x00, 0x00, 0x29, 0xde, 0xcf, 0xa0, 0x00, 0x00,
- 0x00, 0x00, 0x2a, 0xea, 0x62, 0x10, 0x00, 0x00, 0x00, 0x00, 0x2b, 0xbe,
- 0xb1, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x2c, 0xd3, 0x7e, 0x90, 0x00, 0x00,
- 0x00, 0x00, 0x2d, 0x9e, 0x93, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x2e, 0xb3,
- 0x60, 0x90, 0x00, 0x00, 0x00, 0x00, 0x2f, 0x7e, 0x75, 0xa0, 0x00, 0x00,
- 0x00, 0x00, 0x30, 0x93, 0x42, 0x90, 0x00, 0x00, 0x00, 0x00, 0x31, 0x67,
- 0x92, 0x20, 0x00, 0x00, 0x00, 0x00, 0x32, 0x73, 0x24, 0x90, 0x00, 0x00,
- 0x00, 0x00, 0x33, 0x47, 0x74, 0x20, 0x00, 0x00, 0x00, 0x00, 0x34, 0x53,
- 0x06, 0x90, 0x00, 0x00, 0x00, 0x00, 0x35, 0x27, 0x56, 0x20, 0x00, 0x00,
- 0x00, 0x00, 0x36, 0x32, 0xe8, 0x90, 0x00, 0x00, 0x00, 0x00, 0x37, 0x07,
- 0x38, 0x20, 0x00, 0x00, 0x00, 0x00, 0x38, 0x1c, 0x05, 0x10, 0x00, 0x00,
- 0x00, 0x00, 0x38, 0xe7, 0x1a, 0x20, 0x00, 0x00, 0x00, 0x00, 0x39, 0xfb,
- 0xe7, 0x10, 0x00, 0x00, 0x00, 0x00, 0x3a, 0xc6, 0xfc, 0x20, 0x00, 0x00,
- 0x00, 0x00, 0x3b, 0xdb, 0xc9, 0x10, 0x00, 0x00, 0x00, 0x00, 0x3c, 0xb0,
- 0x18, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x3d, 0xbb, 0xab, 0x10, 0x00, 0x00,
- 0x00, 0x00, 0x3e, 0x8f, 0xfa, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x9b,
- 0x8d, 0x10, 0x00, 0x00, 0x00, 0x00, 0x40, 0x6f, 0xdc, 0xa0, 0x00, 0x00,
- 0x00, 0x00, 0x41, 0x84, 0xa9, 0x90, 0x00, 0x00, 0x00, 0x00, 0x42, 0x4f,
- 0xbe, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x43, 0x64, 0x8b, 0x90, 0x00, 0x00,
- 0x00, 0x00, 0x44, 0x2f, 0xa0, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x45, 0x44,
- 0x6d, 0x90, 0x00, 0x00, 0x00, 0x00, 0x45, 0xf3, 0xd3, 0x20, 0x00, 0x00,
- 0x00, 0x00, 0x47, 0x2d, 0x8a, 0x10, 0x00, 0x00, 0x00, 0x00, 0x47, 0xd3,
- 0xb5, 0x20, 0x00, 0x00, 0x00, 0x00, 0x49, 0x0d, 0x6c, 0x10, 0x00, 0x00,
- 0x00, 0x00, 0x49, 0xb3, 0x97, 0x20, 0x00, 0x00, 0x00, 0x00, 0x4a, 0xed,
- 0x4e, 0x10, 0x00, 0x00, 0x00, 0x00, 0x4b, 0x9c, 0xb3, 0xa0, 0x00, 0x00,
- 0x00, 0x00, 0x4c, 0xd6, 0x6a, 0x90, 0x00, 0x00, 0x00, 0x00, 0x4d, 0x7c,
- 0x95, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x4e, 0xb6, 0x4c, 0x90, 0x00, 0x00,
- 0x00, 0x00, 0x4f, 0x5c, 0x77, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x50, 0x96,
- 0x2e, 0x90, 0x00, 0x00, 0x00, 0x00, 0x51, 0x3c, 0x59, 0xa0, 0x00, 0x00,
- 0x00, 0x00, 0x52, 0x76, 0x10, 0x90, 0x00, 0x00, 0x00, 0x00, 0x53, 0x1c,
- 0x3b, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x54, 0x55, 0xf2, 0x90, 0x00, 0x00,
- 0x00, 0x00, 0x54, 0xfc, 0x1d, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x56, 0x35,
- 0xd4, 0x90, 0x00, 0x00, 0x00, 0x00, 0x56, 0xe5, 0x3a, 0x20, 0x00, 0x00,
- 0x00, 0x00, 0x58, 0x1e, 0xf1, 0x10, 0x00, 0x00, 0x00, 0x00, 0x58, 0xc5,
- 0x1c, 0x20, 0x00, 0x00, 0x00, 0x00, 0x59, 0xfe, 0xd3, 0x10, 0x00, 0x00,
- 0x00, 0x00, 0x5a, 0xa4, 0xfe, 0x20, 0x00, 0x00, 0x00, 0x00, 0x5b, 0xde,
- 0xb5, 0x10, 0x00, 0x00, 0x00, 0x00, 0x5c, 0x84, 0xe0, 0x20, 0x00, 0x00,
- 0x00, 0x00, 0x5d, 0xbe, 0x97, 0x10, 0x00, 0x00, 0x00, 0x00, 0x5e, 0x64,
- 0xc2, 0x20, 0x00, 0x00, 0x00, 0x00, 0x5f, 0x9e, 0x79, 0x10, 0x00, 0x00,
- 0x00, 0x00, 0x60, 0x4d, 0xde, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x61, 0x87,
- 0x95, 0x90, 0x00, 0x00, 0x00, 0x00, 0x62, 0x2d, 0xc0, 0xa0, 0x00, 0x00,
- 0x00, 0x00, 0x63, 0x67, 0x77, 0x90, 0x00, 0x00, 0x00, 0x00, 0x64, 0x0d,
- 0xa2, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x65, 0x47, 0x59, 0x90, 0x00, 0x00,
- 0x00, 0x00, 0x65, 0xed, 0x84, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x67, 0x27,
- 0x3b, 0x90, 0x00, 0x00, 0x00, 0x00, 0x67, 0xcd, 0x66, 0xa0, 0x00, 0x00,
- 0x00, 0x00, 0x69, 0x07, 0x1d, 0x90, 0x00, 0x00, 0x00, 0x00, 0x69, 0xad,
- 0x48, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x6a, 0xe6, 0xff, 0x90, 0x00, 0x00,
- 0x00, 0x00, 0x6b, 0x96, 0x65, 0x20, 0x00, 0x00, 0x00, 0x00, 0x6c, 0xd0,
- 0x1c, 0x10, 0x00, 0x00, 0x00, 0x00, 0x6d, 0x76, 0x47, 0x20, 0x00, 0x00,
- 0x00, 0x00, 0x6e, 0xaf, 0xfe, 0x10, 0x00, 0x00, 0x00, 0x00, 0x6f, 0x56,
- 0x29, 0x20, 0x00, 0x00, 0x00, 0x00, 0x70, 0x8f, 0xe0, 0x10, 0x00, 0x00,
- 0x00, 0x00, 0x71, 0x36, 0x0b, 0x20, 0x00, 0x00, 0x00, 0x00, 0x72, 0x6f,
- 0xc2, 0x10, 0x00, 0x00, 0x00, 0x00, 0x73, 0x15, 0xed, 0x20, 0x00, 0x00,
- 0x00, 0x00, 0x74, 0x4f, 0xa4, 0x10, 0x00, 0x00, 0x00, 0x00, 0x74, 0xff,
- 0x09, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x76, 0x38, 0xc0, 0x90, 0x00, 0x00,
- 0x00, 0x00, 0x76, 0xde, 0xeb, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x78, 0x18,
- 0xa2, 0x90, 0x00, 0x00, 0x00, 0x00, 0x78, 0xbe, 0xcd, 0xa0, 0x00, 0x00,
- 0x00, 0x00, 0x79, 0xf8, 0x84, 0x90, 0x00, 0x00, 0x00, 0x00, 0x7a, 0x9e,
- 0xaf, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x7b, 0xd8, 0x66, 0x90, 0x00, 0x00,
- 0x00, 0x00, 0x7c, 0x7e, 0x91, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x7d, 0xb8,
- 0x48, 0x90, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x5e, 0x73, 0xa0, 0x00, 0x00,
- 0x00, 0x00, 0x7f, 0x98, 0x2a, 0x90, 0x00, 0x02, 0x01, 0x02, 0x01, 0x02,
- 0x03, 0x04, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01,
- 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01,
- 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01,
- 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01,
- 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01,
- 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01,
- 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01,
- 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01,
- 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01,
- 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01,
- 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01,
- 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01,
- 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01,
- 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01,
- 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01,
- 0x02, 0xff, 0xff, 0x91, 0x26, 0x00, 0x00, 0xff, 0xff, 0x9d, 0x90, 0x01,
- 0x04, 0xff, 0xff, 0x8f, 0x80, 0x00, 0x08, 0xff, 0xff, 0x9d, 0x90, 0x01,
- 0x0c, 0xff, 0xff, 0x9d, 0x90, 0x01, 0x10, 0x4c, 0x4d, 0x54, 0x00, 0x50,
- 0x44, 0x54, 0x00, 0x50, 0x53, 0x54, 0x00, 0x50, 0x57, 0x54, 0x00, 0x50,
- 0x50, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x0a, 0x50, 0x53, 0x54, 0x38, 0x50, 0x44, 0x54, 0x2c, 0x4d, 0x33,
- 0x2e, 0x32, 0x2e, 0x30, 0x2c, 0x4d, 0x31, 0x31, 0x2e, 0x31, 0x2e, 0x30,
- 0x0a
-};
-unsigned int America_Los_Angeles_len = 2845;
-unsigned char America_New_York[] = {
- 0x54, 0x5a, 0x69, 0x66, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05,
- 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xec,
- 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x14, 0x80, 0x00, 0x00, 0x00,
- 0x9e, 0xa6, 0x1e, 0x70, 0x9f, 0xba, 0xeb, 0x60, 0xa0, 0x86, 0x00, 0x70,
- 0xa1, 0x9a, 0xcd, 0x60, 0xa2, 0x65, 0xe2, 0x70, 0xa3, 0x83, 0xe9, 0xe0,
- 0xa4, 0x6a, 0xae, 0x70, 0xa5, 0x35, 0xa7, 0x60, 0xa6, 0x53, 0xca, 0xf0,
- 0xa7, 0x15, 0x89, 0x60, 0xa8, 0x33, 0xac, 0xf0, 0xa8, 0xfe, 0xa5, 0xe0,
- 0xaa, 0x13, 0x8e, 0xf0, 0xaa, 0xde, 0x87, 0xe0, 0xab, 0xf3, 0x70, 0xf0,
- 0xac, 0xbe, 0x69, 0xe0, 0xad, 0xd3, 0x52, 0xf0, 0xae, 0x9e, 0x4b, 0xe0,
- 0xaf, 0xb3, 0x34, 0xf0, 0xb0, 0x7e, 0x2d, 0xe0, 0xb1, 0x9c, 0x51, 0x70,
- 0xb2, 0x67, 0x4a, 0x60, 0xb3, 0x7c, 0x33, 0x70, 0xb4, 0x47, 0x2c, 0x60,
- 0xb5, 0x5c, 0x15, 0x70, 0xb6, 0x27, 0x0e, 0x60, 0xb7, 0x3b, 0xf7, 0x70,
- 0xb8, 0x06, 0xf0, 0x60, 0xb9, 0x1b, 0xd9, 0x70, 0xb9, 0xe6, 0xd2, 0x60,
- 0xbb, 0x04, 0xf5, 0xf0, 0xbb, 0xc6, 0xb4, 0x60, 0xbc, 0xe4, 0xd7, 0xf0,
- 0xbd, 0xaf, 0xd0, 0xe0, 0xbe, 0xc4, 0xb9, 0xf0, 0xbf, 0x8f, 0xb2, 0xe0,
- 0xc0, 0xa4, 0x9b, 0xf0, 0xc1, 0x6f, 0x94, 0xe0, 0xc2, 0x84, 0x7d, 0xf0,
- 0xc3, 0x4f, 0x76, 0xe0, 0xc4, 0x64, 0x5f, 0xf0, 0xc5, 0x2f, 0x58, 0xe0,
- 0xc6, 0x4d, 0x7c, 0x70, 0xc7, 0x0f, 0x3a, 0xe0, 0xc8, 0x2d, 0x5e, 0x70,
- 0xc8, 0xf8, 0x57, 0x60, 0xca, 0x0d, 0x40, 0x70, 0xca, 0xd8, 0x39, 0x60,
- 0xcb, 0x88, 0xf0, 0x70, 0xd2, 0x23, 0xf4, 0x70, 0xd2, 0x60, 0xfb, 0xe0,
- 0xd3, 0x75, 0xe4, 0xf0, 0xd4, 0x40, 0xdd, 0xe0, 0xd5, 0x55, 0xc6, 0xf0,
- 0xd6, 0x20, 0xbf, 0xe0, 0xd7, 0x35, 0xa8, 0xf0, 0xd8, 0x00, 0xa1, 0xe0,
- 0xd9, 0x15, 0x8a, 0xf0, 0xd9, 0xe0, 0x83, 0xe0, 0xda, 0xfe, 0xa7, 0x70,
- 0xdb, 0xc0, 0x65, 0xe0, 0xdc, 0xde, 0x89, 0x70, 0xdd, 0xa9, 0x82, 0x60,
- 0xde, 0xbe, 0x6b, 0x70, 0xdf, 0x89, 0x64, 0x60, 0xe0, 0x9e, 0x4d, 0x70,
- 0xe1, 0x69, 0x46, 0x60, 0xe2, 0x7e, 0x2f, 0x70, 0xe3, 0x49, 0x28, 0x60,
- 0xe4, 0x5e, 0x11, 0x70, 0xe5, 0x57, 0x2e, 0xe0, 0xe6, 0x47, 0x2d, 0xf0,
- 0xe7, 0x37, 0x10, 0xe0, 0xe8, 0x27, 0x0f, 0xf0, 0xe9, 0x16, 0xf2, 0xe0,
- 0xea, 0x06, 0xf1, 0xf0, 0xea, 0xf6, 0xd4, 0xe0, 0xeb, 0xe6, 0xd3, 0xf0,
- 0xec, 0xd6, 0xb6, 0xe0, 0xed, 0xc6, 0xb5, 0xf0, 0xee, 0xbf, 0xd3, 0x60,
- 0xef, 0xaf, 0xd2, 0x70, 0xf0, 0x9f, 0xb5, 0x60, 0xf1, 0x8f, 0xb4, 0x70,
- 0xf2, 0x7f, 0x97, 0x60, 0xf3, 0x6f, 0x96, 0x70, 0xf4, 0x5f, 0x79, 0x60,
- 0xf5, 0x4f, 0x78, 0x70, 0xf6, 0x3f, 0x5b, 0x60, 0xf7, 0x2f, 0x5a, 0x70,
- 0xf8, 0x28, 0x77, 0xe0, 0xf9, 0x0f, 0x3c, 0x70, 0xfa, 0x08, 0x59, 0xe0,
- 0xfa, 0xf8, 0x58, 0xf0, 0xfb, 0xe8, 0x3b, 0xe0, 0xfc, 0xd8, 0x3a, 0xf0,
- 0xfd, 0xc8, 0x1d, 0xe0, 0xfe, 0xb8, 0x1c, 0xf0, 0xff, 0xa7, 0xff, 0xe0,
- 0x00, 0x97, 0xfe, 0xf0, 0x01, 0x87, 0xe1, 0xe0, 0x02, 0x77, 0xe0, 0xf0,
- 0x03, 0x70, 0xfe, 0x60, 0x04, 0x60, 0xfd, 0x70, 0x05, 0x50, 0xe0, 0x60,
- 0x06, 0x40, 0xdf, 0x70, 0x07, 0x30, 0xc2, 0x60, 0x07, 0x8d, 0x19, 0x70,
- 0x09, 0x10, 0xa4, 0x60, 0x09, 0xad, 0x94, 0xf0, 0x0a, 0xf0, 0x86, 0x60,
- 0x0b, 0xe0, 0x85, 0x70, 0x0c, 0xd9, 0xa2, 0xe0, 0x0d, 0xc0, 0x67, 0x70,
- 0x0e, 0xb9, 0x84, 0xe0, 0x0f, 0xa9, 0x83, 0xf0, 0x10, 0x99, 0x66, 0xe0,
- 0x11, 0x89, 0x65, 0xf0, 0x12, 0x79, 0x48, 0xe0, 0x13, 0x69, 0x47, 0xf0,
- 0x14, 0x59, 0x2a, 0xe0, 0x15, 0x49, 0x29, 0xf0, 0x16, 0x39, 0x0c, 0xe0,
- 0x17, 0x29, 0x0b, 0xf0, 0x18, 0x22, 0x29, 0x60, 0x19, 0x08, 0xed, 0xf0,
- 0x1a, 0x02, 0x0b, 0x60, 0x1a, 0xf2, 0x0a, 0x70, 0x1b, 0xe1, 0xed, 0x60,
- 0x1c, 0xd1, 0xec, 0x70, 0x1d, 0xc1, 0xcf, 0x60, 0x1e, 0xb1, 0xce, 0x70,
- 0x1f, 0xa1, 0xb1, 0x60, 0x20, 0x76, 0x00, 0xf0, 0x21, 0x81, 0x93, 0x60,
- 0x22, 0x55, 0xe2, 0xf0, 0x23, 0x6a, 0xaf, 0xe0, 0x24, 0x35, 0xc4, 0xf0,
- 0x25, 0x4a, 0x91, 0xe0, 0x26, 0x15, 0xa6, 0xf0, 0x27, 0x2a, 0x73, 0xe0,
- 0x27, 0xfe, 0xc3, 0x70, 0x29, 0x0a, 0x55, 0xe0, 0x29, 0xde, 0xa5, 0x70,
- 0x2a, 0xea, 0x37, 0xe0, 0x2b, 0xbe, 0x87, 0x70, 0x2c, 0xd3, 0x54, 0x60,
- 0x2d, 0x9e, 0x69, 0x70, 0x2e, 0xb3, 0x36, 0x60, 0x2f, 0x7e, 0x4b, 0x70,
- 0x30, 0x93, 0x18, 0x60, 0x31, 0x67, 0x67, 0xf0, 0x32, 0x72, 0xfa, 0x60,
- 0x33, 0x47, 0x49, 0xf0, 0x34, 0x52, 0xdc, 0x60, 0x35, 0x27, 0x2b, 0xf0,
- 0x36, 0x32, 0xbe, 0x60, 0x37, 0x07, 0x0d, 0xf0, 0x38, 0x1b, 0xda, 0xe0,
- 0x38, 0xe6, 0xef, 0xf0, 0x39, 0xfb, 0xbc, 0xe0, 0x3a, 0xc6, 0xd1, 0xf0,
- 0x3b, 0xdb, 0x9e, 0xe0, 0x3c, 0xaf, 0xee, 0x70, 0x3d, 0xbb, 0x80, 0xe0,
- 0x3e, 0x8f, 0xd0, 0x70, 0x3f, 0x9b, 0x62, 0xe0, 0x40, 0x6f, 0xb2, 0x70,
- 0x41, 0x84, 0x7f, 0x60, 0x42, 0x4f, 0x94, 0x70, 0x43, 0x64, 0x61, 0x60,
- 0x44, 0x2f, 0x76, 0x70, 0x45, 0x44, 0x43, 0x60, 0x45, 0xf3, 0xa8, 0xf0,
- 0x47, 0x2d, 0x5f, 0xe0, 0x47, 0xd3, 0x8a, 0xf0, 0x49, 0x0d, 0x41, 0xe0,
- 0x49, 0xb3, 0x6c, 0xf0, 0x4a, 0xed, 0x23, 0xe0, 0x4b, 0x9c, 0x89, 0x70,
- 0x4c, 0xd6, 0x40, 0x60, 0x4d, 0x7c, 0x6b, 0x70, 0x4e, 0xb6, 0x22, 0x60,
- 0x4f, 0x5c, 0x4d, 0x70, 0x50, 0x96, 0x04, 0x60, 0x51, 0x3c, 0x2f, 0x70,
- 0x52, 0x75, 0xe6, 0x60, 0x53, 0x1c, 0x11, 0x70, 0x54, 0x55, 0xc8, 0x60,
- 0x54, 0xfb, 0xf3, 0x70, 0x56, 0x35, 0xaa, 0x60, 0x56, 0xe5, 0x0f, 0xf0,
- 0x58, 0x1e, 0xc6, 0xe0, 0x58, 0xc4, 0xf1, 0xf0, 0x59, 0xfe, 0xa8, 0xe0,
- 0x5a, 0xa4, 0xd3, 0xf0, 0x5b, 0xde, 0x8a, 0xe0, 0x5c, 0x84, 0xb5, 0xf0,
- 0x5d, 0xbe, 0x6c, 0xe0, 0x5e, 0x64, 0x97, 0xf0, 0x5f, 0x9e, 0x4e, 0xe0,
- 0x60, 0x4d, 0xb4, 0x70, 0x61, 0x87, 0x6b, 0x60, 0x62, 0x2d, 0x96, 0x70,
- 0x63, 0x67, 0x4d, 0x60, 0x64, 0x0d, 0x78, 0x70, 0x65, 0x47, 0x2f, 0x60,
- 0x65, 0xed, 0x5a, 0x70, 0x67, 0x27, 0x11, 0x60, 0x67, 0xcd, 0x3c, 0x70,
- 0x69, 0x06, 0xf3, 0x60, 0x69, 0xad, 0x1e, 0x70, 0x6a, 0xe6, 0xd5, 0x60,
- 0x6b, 0x96, 0x3a, 0xf0, 0x6c, 0xcf, 0xf1, 0xe0, 0x6d, 0x76, 0x1c, 0xf0,
- 0x6e, 0xaf, 0xd3, 0xe0, 0x6f, 0x55, 0xfe, 0xf0, 0x70, 0x8f, 0xb5, 0xe0,
- 0x71, 0x35, 0xe0, 0xf0, 0x72, 0x6f, 0x97, 0xe0, 0x73, 0x15, 0xc2, 0xf0,
- 0x74, 0x4f, 0x79, 0xe0, 0x74, 0xfe, 0xdf, 0x70, 0x76, 0x38, 0x96, 0x60,
- 0x76, 0xde, 0xc1, 0x70, 0x78, 0x18, 0x78, 0x60, 0x78, 0xbe, 0xa3, 0x70,
- 0x79, 0xf8, 0x5a, 0x60, 0x7a, 0x9e, 0x85, 0x70, 0x7b, 0xd8, 0x3c, 0x60,
- 0x7c, 0x7e, 0x67, 0x70, 0x7d, 0xb8, 0x1e, 0x60, 0x7e, 0x5e, 0x49, 0x70,
- 0x7f, 0x98, 0x00, 0x60, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01,
- 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01,
- 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01,
- 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01,
- 0x02, 0x01, 0x02, 0x01, 0x02, 0x03, 0x04, 0x02, 0x01, 0x02, 0x01, 0x02,
- 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02,
- 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02,
- 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02,
- 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02,
- 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02,
- 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02,
- 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02,
- 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02,
- 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02,
- 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02,
- 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02,
- 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02,
- 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02,
- 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02,
- 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02,
- 0xff, 0xff, 0xba, 0x9e, 0x00, 0x00, 0xff, 0xff, 0xc7, 0xc0, 0x01, 0x04,
- 0xff, 0xff, 0xb9, 0xb0, 0x00, 0x08, 0xff, 0xff, 0xc7, 0xc0, 0x01, 0x0c,
- 0xff, 0xff, 0xc7, 0xc0, 0x01, 0x10, 0x4c, 0x4d, 0x54, 0x00, 0x45, 0x44,
- 0x54, 0x00, 0x45, 0x53, 0x54, 0x00, 0x45, 0x57, 0x54, 0x00, 0x45, 0x50,
- 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01,
- 0x54, 0x5a, 0x69, 0x66, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05,
- 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xed,
- 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x14, 0xf8, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x5e, 0x03, 0xf0, 0x90,
- 0xff, 0xff, 0xff, 0xff, 0x9e, 0xa6, 0x1e, 0x70, 0xff, 0xff, 0xff, 0xff,
- 0x9f, 0xba, 0xeb, 0x60, 0xff, 0xff, 0xff, 0xff, 0xa0, 0x86, 0x00, 0x70,
- 0xff, 0xff, 0xff, 0xff, 0xa1, 0x9a, 0xcd, 0x60, 0xff, 0xff, 0xff, 0xff,
- 0xa2, 0x65, 0xe2, 0x70, 0xff, 0xff, 0xff, 0xff, 0xa3, 0x83, 0xe9, 0xe0,
- 0xff, 0xff, 0xff, 0xff, 0xa4, 0x6a, 0xae, 0x70, 0xff, 0xff, 0xff, 0xff,
- 0xa5, 0x35, 0xa7, 0x60, 0xff, 0xff, 0xff, 0xff, 0xa6, 0x53, 0xca, 0xf0,
- 0xff, 0xff, 0xff, 0xff, 0xa7, 0x15, 0x89, 0x60, 0xff, 0xff, 0xff, 0xff,
- 0xa8, 0x33, 0xac, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xa8, 0xfe, 0xa5, 0xe0,
- 0xff, 0xff, 0xff, 0xff, 0xaa, 0x13, 0x8e, 0xf0, 0xff, 0xff, 0xff, 0xff,
- 0xaa, 0xde, 0x87, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xab, 0xf3, 0x70, 0xf0,
- 0xff, 0xff, 0xff, 0xff, 0xac, 0xbe, 0x69, 0xe0, 0xff, 0xff, 0xff, 0xff,
- 0xad, 0xd3, 0x52, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xae, 0x9e, 0x4b, 0xe0,
- 0xff, 0xff, 0xff, 0xff, 0xaf, 0xb3, 0x34, 0xf0, 0xff, 0xff, 0xff, 0xff,
- 0xb0, 0x7e, 0x2d, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xb1, 0x9c, 0x51, 0x70,
- 0xff, 0xff, 0xff, 0xff, 0xb2, 0x67, 0x4a, 0x60, 0xff, 0xff, 0xff, 0xff,
- 0xb3, 0x7c, 0x33, 0x70, 0xff, 0xff, 0xff, 0xff, 0xb4, 0x47, 0x2c, 0x60,
- 0xff, 0xff, 0xff, 0xff, 0xb5, 0x5c, 0x15, 0x70, 0xff, 0xff, 0xff, 0xff,
- 0xb6, 0x27, 0x0e, 0x60, 0xff, 0xff, 0xff, 0xff, 0xb7, 0x3b, 0xf7, 0x70,
- 0xff, 0xff, 0xff, 0xff, 0xb8, 0x06, 0xf0, 0x60, 0xff, 0xff, 0xff, 0xff,
- 0xb9, 0x1b, 0xd9, 0x70, 0xff, 0xff, 0xff, 0xff, 0xb9, 0xe6, 0xd2, 0x60,
- 0xff, 0xff, 0xff, 0xff, 0xbb, 0x04, 0xf5, 0xf0, 0xff, 0xff, 0xff, 0xff,
- 0xbb, 0xc6, 0xb4, 0x60, 0xff, 0xff, 0xff, 0xff, 0xbc, 0xe4, 0xd7, 0xf0,
- 0xff, 0xff, 0xff, 0xff, 0xbd, 0xaf, 0xd0, 0xe0, 0xff, 0xff, 0xff, 0xff,
- 0xbe, 0xc4, 0xb9, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xbf, 0x8f, 0xb2, 0xe0,
- 0xff, 0xff, 0xff, 0xff, 0xc0, 0xa4, 0x9b, 0xf0, 0xff, 0xff, 0xff, 0xff,
- 0xc1, 0x6f, 0x94, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xc2, 0x84, 0x7d, 0xf0,
- 0xff, 0xff, 0xff, 0xff, 0xc3, 0x4f, 0x76, 0xe0, 0xff, 0xff, 0xff, 0xff,
- 0xc4, 0x64, 0x5f, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xc5, 0x2f, 0x58, 0xe0,
- 0xff, 0xff, 0xff, 0xff, 0xc6, 0x4d, 0x7c, 0x70, 0xff, 0xff, 0xff, 0xff,
- 0xc7, 0x0f, 0x3a, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xc8, 0x2d, 0x5e, 0x70,
- 0xff, 0xff, 0xff, 0xff, 0xc8, 0xf8, 0x57, 0x60, 0xff, 0xff, 0xff, 0xff,
- 0xca, 0x0d, 0x40, 0x70, 0xff, 0xff, 0xff, 0xff, 0xca, 0xd8, 0x39, 0x60,
- 0xff, 0xff, 0xff, 0xff, 0xcb, 0x88, 0xf0, 0x70, 0xff, 0xff, 0xff, 0xff,
- 0xd2, 0x23, 0xf4, 0x70, 0xff, 0xff, 0xff, 0xff, 0xd2, 0x60, 0xfb, 0xe0,
- 0xff, 0xff, 0xff, 0xff, 0xd3, 0x75, 0xe4, 0xf0, 0xff, 0xff, 0xff, 0xff,
- 0xd4, 0x40, 0xdd, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xd5, 0x55, 0xc6, 0xf0,
- 0xff, 0xff, 0xff, 0xff, 0xd6, 0x20, 0xbf, 0xe0, 0xff, 0xff, 0xff, 0xff,
- 0xd7, 0x35, 0xa8, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xd8, 0x00, 0xa1, 0xe0,
- 0xff, 0xff, 0xff, 0xff, 0xd9, 0x15, 0x8a, 0xf0, 0xff, 0xff, 0xff, 0xff,
- 0xd9, 0xe0, 0x83, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xda, 0xfe, 0xa7, 0x70,
- 0xff, 0xff, 0xff, 0xff, 0xdb, 0xc0, 0x65, 0xe0, 0xff, 0xff, 0xff, 0xff,
- 0xdc, 0xde, 0x89, 0x70, 0xff, 0xff, 0xff, 0xff, 0xdd, 0xa9, 0x82, 0x60,
- 0xff, 0xff, 0xff, 0xff, 0xde, 0xbe, 0x6b, 0x70, 0xff, 0xff, 0xff, 0xff,
- 0xdf, 0x89, 0x64, 0x60, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x9e, 0x4d, 0x70,
- 0xff, 0xff, 0xff, 0xff, 0xe1, 0x69, 0x46, 0x60, 0xff, 0xff, 0xff, 0xff,
- 0xe2, 0x7e, 0x2f, 0x70, 0xff, 0xff, 0xff, 0xff, 0xe3, 0x49, 0x28, 0x60,
- 0xff, 0xff, 0xff, 0xff, 0xe4, 0x5e, 0x11, 0x70, 0xff, 0xff, 0xff, 0xff,
- 0xe5, 0x57, 0x2e, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xe6, 0x47, 0x2d, 0xf0,
- 0xff, 0xff, 0xff, 0xff, 0xe7, 0x37, 0x10, 0xe0, 0xff, 0xff, 0xff, 0xff,
- 0xe8, 0x27, 0x0f, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xe9, 0x16, 0xf2, 0xe0,
- 0xff, 0xff, 0xff, 0xff, 0xea, 0x06, 0xf1, 0xf0, 0xff, 0xff, 0xff, 0xff,
- 0xea, 0xf6, 0xd4, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xeb, 0xe6, 0xd3, 0xf0,
- 0xff, 0xff, 0xff, 0xff, 0xec, 0xd6, 0xb6, 0xe0, 0xff, 0xff, 0xff, 0xff,
- 0xed, 0xc6, 0xb5, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xee, 0xbf, 0xd3, 0x60,
- 0xff, 0xff, 0xff, 0xff, 0xef, 0xaf, 0xd2, 0x70, 0xff, 0xff, 0xff, 0xff,
- 0xf0, 0x9f, 0xb5, 0x60, 0xff, 0xff, 0xff, 0xff, 0xf1, 0x8f, 0xb4, 0x70,
- 0xff, 0xff, 0xff, 0xff, 0xf2, 0x7f, 0x97, 0x60, 0xff, 0xff, 0xff, 0xff,
- 0xf3, 0x6f, 0x96, 0x70, 0xff, 0xff, 0xff, 0xff, 0xf4, 0x5f, 0x79, 0x60,
- 0xff, 0xff, 0xff, 0xff, 0xf5, 0x4f, 0x78, 0x70, 0xff, 0xff, 0xff, 0xff,
- 0xf6, 0x3f, 0x5b, 0x60, 0xff, 0xff, 0xff, 0xff, 0xf7, 0x2f, 0x5a, 0x70,
- 0xff, 0xff, 0xff, 0xff, 0xf8, 0x28, 0x77, 0xe0, 0xff, 0xff, 0xff, 0xff,
- 0xf9, 0x0f, 0x3c, 0x70, 0xff, 0xff, 0xff, 0xff, 0xfa, 0x08, 0x59, 0xe0,
- 0xff, 0xff, 0xff, 0xff, 0xfa, 0xf8, 0x58, 0xf0, 0xff, 0xff, 0xff, 0xff,
- 0xfb, 0xe8, 0x3b, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xd8, 0x3a, 0xf0,
- 0xff, 0xff, 0xff, 0xff, 0xfd, 0xc8, 0x1d, 0xe0, 0xff, 0xff, 0xff, 0xff,
- 0xfe, 0xb8, 0x1c, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xa7, 0xff, 0xe0,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x97, 0xfe, 0xf0, 0x00, 0x00, 0x00, 0x00,
- 0x01, 0x87, 0xe1, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x02, 0x77, 0xe0, 0xf0,
- 0x00, 0x00, 0x00, 0x00, 0x03, 0x70, 0xfe, 0x60, 0x00, 0x00, 0x00, 0x00,
- 0x04, 0x60, 0xfd, 0x70, 0x00, 0x00, 0x00, 0x00, 0x05, 0x50, 0xe0, 0x60,
- 0x00, 0x00, 0x00, 0x00, 0x06, 0x40, 0xdf, 0x70, 0x00, 0x00, 0x00, 0x00,
- 0x07, 0x30, 0xc2, 0x60, 0x00, 0x00, 0x00, 0x00, 0x07, 0x8d, 0x19, 0x70,
- 0x00, 0x00, 0x00, 0x00, 0x09, 0x10, 0xa4, 0x60, 0x00, 0x00, 0x00, 0x00,
- 0x09, 0xad, 0x94, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xf0, 0x86, 0x60,
- 0x00, 0x00, 0x00, 0x00, 0x0b, 0xe0, 0x85, 0x70, 0x00, 0x00, 0x00, 0x00,
- 0x0c, 0xd9, 0xa2, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x0d, 0xc0, 0x67, 0x70,
- 0x00, 0x00, 0x00, 0x00, 0x0e, 0xb9, 0x84, 0xe0, 0x00, 0x00, 0x00, 0x00,
- 0x0f, 0xa9, 0x83, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x10, 0x99, 0x66, 0xe0,
- 0x00, 0x00, 0x00, 0x00, 0x11, 0x89, 0x65, 0xf0, 0x00, 0x00, 0x00, 0x00,
- 0x12, 0x79, 0x48, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x13, 0x69, 0x47, 0xf0,
- 0x00, 0x00, 0x00, 0x00, 0x14, 0x59, 0x2a, 0xe0, 0x00, 0x00, 0x00, 0x00,
- 0x15, 0x49, 0x29, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x16, 0x39, 0x0c, 0xe0,
- 0x00, 0x00, 0x00, 0x00, 0x17, 0x29, 0x0b, 0xf0, 0x00, 0x00, 0x00, 0x00,
- 0x18, 0x22, 0x29, 0x60, 0x00, 0x00, 0x00, 0x00, 0x19, 0x08, 0xed, 0xf0,
- 0x00, 0x00, 0x00, 0x00, 0x1a, 0x02, 0x0b, 0x60, 0x00, 0x00, 0x00, 0x00,
- 0x1a, 0xf2, 0x0a, 0x70, 0x00, 0x00, 0x00, 0x00, 0x1b, 0xe1, 0xed, 0x60,
- 0x00, 0x00, 0x00, 0x00, 0x1c, 0xd1, 0xec, 0x70, 0x00, 0x00, 0x00, 0x00,
- 0x1d, 0xc1, 0xcf, 0x60, 0x00, 0x00, 0x00, 0x00, 0x1e, 0xb1, 0xce, 0x70,
- 0x00, 0x00, 0x00, 0x00, 0x1f, 0xa1, 0xb1, 0x60, 0x00, 0x00, 0x00, 0x00,
- 0x20, 0x76, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x21, 0x81, 0x93, 0x60,
- 0x00, 0x00, 0x00, 0x00, 0x22, 0x55, 0xe2, 0xf0, 0x00, 0x00, 0x00, 0x00,
- 0x23, 0x6a, 0xaf, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x24, 0x35, 0xc4, 0xf0,
- 0x00, 0x00, 0x00, 0x00, 0x25, 0x4a, 0x91, 0xe0, 0x00, 0x00, 0x00, 0x00,
- 0x26, 0x15, 0xa6, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x27, 0x2a, 0x73, 0xe0,
- 0x00, 0x00, 0x00, 0x00, 0x27, 0xfe, 0xc3, 0x70, 0x00, 0x00, 0x00, 0x00,
- 0x29, 0x0a, 0x55, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x29, 0xde, 0xa5, 0x70,
- 0x00, 0x00, 0x00, 0x00, 0x2a, 0xea, 0x37, 0xe0, 0x00, 0x00, 0x00, 0x00,
- 0x2b, 0xbe, 0x87, 0x70, 0x00, 0x00, 0x00, 0x00, 0x2c, 0xd3, 0x54, 0x60,
- 0x00, 0x00, 0x00, 0x00, 0x2d, 0x9e, 0x69, 0x70, 0x00, 0x00, 0x00, 0x00,
- 0x2e, 0xb3, 0x36, 0x60, 0x00, 0x00, 0x00, 0x00, 0x2f, 0x7e, 0x4b, 0x70,
- 0x00, 0x00, 0x00, 0x00, 0x30, 0x93, 0x18, 0x60, 0x00, 0x00, 0x00, 0x00,
- 0x31, 0x67, 0x67, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x32, 0x72, 0xfa, 0x60,
- 0x00, 0x00, 0x00, 0x00, 0x33, 0x47, 0x49, 0xf0, 0x00, 0x00, 0x00, 0x00,
- 0x34, 0x52, 0xdc, 0x60, 0x00, 0x00, 0x00, 0x00, 0x35, 0x27, 0x2b, 0xf0,
- 0x00, 0x00, 0x00, 0x00, 0x36, 0x32, 0xbe, 0x60, 0x00, 0x00, 0x00, 0x00,
- 0x37, 0x07, 0x0d, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x38, 0x1b, 0xda, 0xe0,
- 0x00, 0x00, 0x00, 0x00, 0x38, 0xe6, 0xef, 0xf0, 0x00, 0x00, 0x00, 0x00,
- 0x39, 0xfb, 0xbc, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x3a, 0xc6, 0xd1, 0xf0,
- 0x00, 0x00, 0x00, 0x00, 0x3b, 0xdb, 0x9e, 0xe0, 0x00, 0x00, 0x00, 0x00,
- 0x3c, 0xaf, 0xee, 0x70, 0x00, 0x00, 0x00, 0x00, 0x3d, 0xbb, 0x80, 0xe0,
- 0x00, 0x00, 0x00, 0x00, 0x3e, 0x8f, 0xd0, 0x70, 0x00, 0x00, 0x00, 0x00,
- 0x3f, 0x9b, 0x62, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x40, 0x6f, 0xb2, 0x70,
- 0x00, 0x00, 0x00, 0x00, 0x41, 0x84, 0x7f, 0x60, 0x00, 0x00, 0x00, 0x00,
- 0x42, 0x4f, 0x94, 0x70, 0x00, 0x00, 0x00, 0x00, 0x43, 0x64, 0x61, 0x60,
- 0x00, 0x00, 0x00, 0x00, 0x44, 0x2f, 0x76, 0x70, 0x00, 0x00, 0x00, 0x00,
- 0x45, 0x44, 0x43, 0x60, 0x00, 0x00, 0x00, 0x00, 0x45, 0xf3, 0xa8, 0xf0,
- 0x00, 0x00, 0x00, 0x00, 0x47, 0x2d, 0x5f, 0xe0, 0x00, 0x00, 0x00, 0x00,
- 0x47, 0xd3, 0x8a, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x49, 0x0d, 0x41, 0xe0,
- 0x00, 0x00, 0x00, 0x00, 0x49, 0xb3, 0x6c, 0xf0, 0x00, 0x00, 0x00, 0x00,
- 0x4a, 0xed, 0x23, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x4b, 0x9c, 0x89, 0x70,
- 0x00, 0x00, 0x00, 0x00, 0x4c, 0xd6, 0x40, 0x60, 0x00, 0x00, 0x00, 0x00,
- 0x4d, 0x7c, 0x6b, 0x70, 0x00, 0x00, 0x00, 0x00, 0x4e, 0xb6, 0x22, 0x60,
- 0x00, 0x00, 0x00, 0x00, 0x4f, 0x5c, 0x4d, 0x70, 0x00, 0x00, 0x00, 0x00,
- 0x50, 0x96, 0x04, 0x60, 0x00, 0x00, 0x00, 0x00, 0x51, 0x3c, 0x2f, 0x70,
- 0x00, 0x00, 0x00, 0x00, 0x52, 0x75, 0xe6, 0x60, 0x00, 0x00, 0x00, 0x00,
- 0x53, 0x1c, 0x11, 0x70, 0x00, 0x00, 0x00, 0x00, 0x54, 0x55, 0xc8, 0x60,
- 0x00, 0x00, 0x00, 0x00, 0x54, 0xfb, 0xf3, 0x70, 0x00, 0x00, 0x00, 0x00,
- 0x56, 0x35, 0xaa, 0x60, 0x00, 0x00, 0x00, 0x00, 0x56, 0xe5, 0x0f, 0xf0,
- 0x00, 0x00, 0x00, 0x00, 0x58, 0x1e, 0xc6, 0xe0, 0x00, 0x00, 0x00, 0x00,
- 0x58, 0xc4, 0xf1, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x59, 0xfe, 0xa8, 0xe0,
- 0x00, 0x00, 0x00, 0x00, 0x5a, 0xa4, 0xd3, 0xf0, 0x00, 0x00, 0x00, 0x00,
- 0x5b, 0xde, 0x8a, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x5c, 0x84, 0xb5, 0xf0,
- 0x00, 0x00, 0x00, 0x00, 0x5d, 0xbe, 0x6c, 0xe0, 0x00, 0x00, 0x00, 0x00,
- 0x5e, 0x64, 0x97, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x5f, 0x9e, 0x4e, 0xe0,
- 0x00, 0x00, 0x00, 0x00, 0x60, 0x4d, 0xb4, 0x70, 0x00, 0x00, 0x00, 0x00,
- 0x61, 0x87, 0x6b, 0x60, 0x00, 0x00, 0x00, 0x00, 0x62, 0x2d, 0x96, 0x70,
- 0x00, 0x00, 0x00, 0x00, 0x63, 0x67, 0x4d, 0x60, 0x00, 0x00, 0x00, 0x00,
- 0x64, 0x0d, 0x78, 0x70, 0x00, 0x00, 0x00, 0x00, 0x65, 0x47, 0x2f, 0x60,
- 0x00, 0x00, 0x00, 0x00, 0x65, 0xed, 0x5a, 0x70, 0x00, 0x00, 0x00, 0x00,
- 0x67, 0x27, 0x11, 0x60, 0x00, 0x00, 0x00, 0x00, 0x67, 0xcd, 0x3c, 0x70,
- 0x00, 0x00, 0x00, 0x00, 0x69, 0x06, 0xf3, 0x60, 0x00, 0x00, 0x00, 0x00,
- 0x69, 0xad, 0x1e, 0x70, 0x00, 0x00, 0x00, 0x00, 0x6a, 0xe6, 0xd5, 0x60,
- 0x00, 0x00, 0x00, 0x00, 0x6b, 0x96, 0x3a, 0xf0, 0x00, 0x00, 0x00, 0x00,
- 0x6c, 0xcf, 0xf1, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x6d, 0x76, 0x1c, 0xf0,
- 0x00, 0x00, 0x00, 0x00, 0x6e, 0xaf, 0xd3, 0xe0, 0x00, 0x00, 0x00, 0x00,
- 0x6f, 0x55, 0xfe, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x70, 0x8f, 0xb5, 0xe0,
- 0x00, 0x00, 0x00, 0x00, 0x71, 0x35, 0xe0, 0xf0, 0x00, 0x00, 0x00, 0x00,
- 0x72, 0x6f, 0x97, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x73, 0x15, 0xc2, 0xf0,
- 0x00, 0x00, 0x00, 0x00, 0x74, 0x4f, 0x79, 0xe0, 0x00, 0x00, 0x00, 0x00,
- 0x74, 0xfe, 0xdf, 0x70, 0x00, 0x00, 0x00, 0x00, 0x76, 0x38, 0x96, 0x60,
- 0x00, 0x00, 0x00, 0x00, 0x76, 0xde, 0xc1, 0x70, 0x00, 0x00, 0x00, 0x00,
- 0x78, 0x18, 0x78, 0x60, 0x00, 0x00, 0x00, 0x00, 0x78, 0xbe, 0xa3, 0x70,
- 0x00, 0x00, 0x00, 0x00, 0x79, 0xf8, 0x5a, 0x60, 0x00, 0x00, 0x00, 0x00,
- 0x7a, 0x9e, 0x85, 0x70, 0x00, 0x00, 0x00, 0x00, 0x7b, 0xd8, 0x3c, 0x60,
- 0x00, 0x00, 0x00, 0x00, 0x7c, 0x7e, 0x67, 0x70, 0x00, 0x00, 0x00, 0x00,
- 0x7d, 0xb8, 0x1e, 0x60, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x5e, 0x49, 0x70,
- 0x00, 0x00, 0x00, 0x00, 0x7f, 0x98, 0x00, 0x60, 0x00, 0x02, 0x01, 0x02,
- 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02,
- 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02,
- 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02,
- 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x03, 0x04,
- 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01,
- 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01,
- 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01,
- 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01,
- 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01,
- 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01,
- 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01,
- 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01,
- 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01,
- 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01,
- 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01,
- 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01,
- 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01,
- 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01,
- 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01,
- 0x02, 0x01, 0x02, 0x01, 0x02, 0xff, 0xff, 0xba, 0x9e, 0x00, 0x00, 0xff,
- 0xff, 0xc7, 0xc0, 0x01, 0x04, 0xff, 0xff, 0xb9, 0xb0, 0x00, 0x08, 0xff,
- 0xff, 0xc7, 0xc0, 0x01, 0x0c, 0xff, 0xff, 0xc7, 0xc0, 0x01, 0x10, 0x4c,
- 0x4d, 0x54, 0x00, 0x45, 0x44, 0x54, 0x00, 0x45, 0x53, 0x54, 0x00, 0x45,
- 0x57, 0x54, 0x00, 0x45, 0x50, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
- 0x00, 0x00, 0x00, 0x00, 0x01, 0x0a, 0x45, 0x53, 0x54, 0x35, 0x45, 0x44,
- 0x54, 0x2c, 0x4d, 0x33, 0x2e, 0x32, 0x2e, 0x30, 0x2c, 0x4d, 0x31, 0x31,
- 0x2e, 0x31, 0x2e, 0x30, 0x0a
-};
-unsigned int America_New_York_len = 3545;
-unsigned char Australia_Sydney[] = {
- 0x54, 0x5a, 0x69, 0x66, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05,
- 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8e,
- 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x0e, 0x80, 0x00, 0x00, 0x00,
- 0x9c, 0x4e, 0xa6, 0x9c, 0x9c, 0xbc, 0x20, 0xf0, 0xcb, 0x54, 0xb3, 0x00,
- 0xcb, 0xc7, 0x57, 0x70, 0xcc, 0xb7, 0x56, 0x80, 0xcd, 0xa7, 0x39, 0x70,
- 0xce, 0xa0, 0x73, 0x00, 0xcf, 0x87, 0x1b, 0x70, 0x03, 0x70, 0x39, 0x80,
- 0x04, 0x0d, 0x1c, 0x00, 0x05, 0x50, 0x1b, 0x80, 0x05, 0xf6, 0x38, 0x80,
- 0x07, 0x2f, 0xfd, 0x80, 0x07, 0xd6, 0x1a, 0x80, 0x09, 0x0f, 0xdf, 0x80,
- 0x09, 0xb5, 0xfc, 0x80, 0x0a, 0xef, 0xc1, 0x80, 0x0b, 0x9f, 0x19, 0x00,
- 0x0c, 0xd8, 0xde, 0x00, 0x0d, 0x7e, 0xfb, 0x00, 0x0e, 0xb8, 0xc0, 0x00,
- 0x0f, 0x5e, 0xdd, 0x00, 0x10, 0x98, 0xa2, 0x00, 0x11, 0x3e, 0xbf, 0x00,
- 0x12, 0x78, 0x84, 0x00, 0x13, 0x1e, 0xa1, 0x00, 0x14, 0x58, 0x66, 0x00,
- 0x14, 0xfe, 0x83, 0x00, 0x16, 0x38, 0x48, 0x00, 0x17, 0x0c, 0x89, 0x80,
- 0x18, 0x21, 0x64, 0x80, 0x18, 0xc7, 0x81, 0x80, 0x1a, 0x01, 0x46, 0x80,
- 0x1a, 0xa7, 0x63, 0x80, 0x1b, 0xe1, 0x28, 0x80, 0x1c, 0x87, 0x45, 0x80,
- 0x1d, 0xc1, 0x0a, 0x80, 0x1e, 0x79, 0x9c, 0x80, 0x1f, 0x97, 0xb2, 0x00,
- 0x20, 0x59, 0x7e, 0x80, 0x21, 0x80, 0xce, 0x80, 0x22, 0x42, 0x9b, 0x00,
- 0x23, 0x69, 0xeb, 0x00, 0x24, 0x22, 0x7d, 0x00, 0x25, 0x49, 0xcd, 0x00,
- 0x25, 0xef, 0xea, 0x00, 0x27, 0x29, 0xaf, 0x00, 0x27, 0xcf, 0xcc, 0x00,
- 0x29, 0x09, 0x91, 0x00, 0x29, 0xaf, 0xae, 0x00, 0x2a, 0xe9, 0x73, 0x00,
- 0x2b, 0x98, 0xca, 0x80, 0x2c, 0xd2, 0x8f, 0x80, 0x2d, 0x78, 0xac, 0x80,
- 0x2e, 0xb2, 0x71, 0x80, 0x2f, 0x58, 0x8e, 0x80, 0x30, 0x92, 0x53, 0x80,
- 0x31, 0x5d, 0x5a, 0x80, 0x32, 0x72, 0x35, 0x80, 0x33, 0x3d, 0x3c, 0x80,
- 0x34, 0x52, 0x17, 0x80, 0x35, 0x1d, 0x1e, 0x80, 0x36, 0x31, 0xf9, 0x80,
- 0x36, 0xfd, 0x00, 0x80, 0x38, 0x1b, 0x16, 0x00, 0x38, 0xdc, 0xe2, 0x80,
- 0x39, 0xa7, 0xe9, 0x80, 0x3a, 0xbc, 0xc4, 0x80, 0x3b, 0xda, 0xda, 0x00,
- 0x3c, 0xa5, 0xe1, 0x00, 0x3d, 0xba, 0xbc, 0x00, 0x3e, 0x85, 0xc3, 0x00,
- 0x3f, 0x9a, 0x9e, 0x00, 0x40, 0x65, 0xa5, 0x00, 0x41, 0x83, 0xba, 0x80,
- 0x42, 0x45, 0x87, 0x00, 0x43, 0x63, 0x9c, 0x80, 0x44, 0x2e, 0xa3, 0x80,
- 0x45, 0x43, 0x7e, 0x80, 0x46, 0x05, 0x4b, 0x00, 0x47, 0x23, 0x60, 0x80,
- 0x47, 0xf7, 0xa2, 0x00, 0x48, 0xe7, 0x93, 0x00, 0x49, 0xd7, 0x84, 0x00,
- 0x4a, 0xc7, 0x75, 0x00, 0x4b, 0xb7, 0x66, 0x00, 0x4c, 0xa7, 0x57, 0x00,
- 0x4d, 0x97, 0x48, 0x00, 0x4e, 0x87, 0x39, 0x00, 0x4f, 0x77, 0x2a, 0x00,
- 0x50, 0x70, 0x55, 0x80, 0x51, 0x60, 0x46, 0x80, 0x52, 0x50, 0x37, 0x80,
- 0x53, 0x40, 0x28, 0x80, 0x54, 0x30, 0x19, 0x80, 0x55, 0x20, 0x0a, 0x80,
- 0x56, 0x0f, 0xfb, 0x80, 0x56, 0xff, 0xec, 0x80, 0x57, 0xef, 0xdd, 0x80,
- 0x58, 0xdf, 0xce, 0x80, 0x59, 0xcf, 0xbf, 0x80, 0x5a, 0xbf, 0xb0, 0x80,
- 0x5b, 0xb8, 0xdc, 0x00, 0x5c, 0xa8, 0xcd, 0x00, 0x5d, 0x98, 0xbe, 0x00,
- 0x5e, 0x88, 0xaf, 0x00, 0x5f, 0x78, 0xa0, 0x00, 0x60, 0x68, 0x91, 0x00,
- 0x61, 0x58, 0x82, 0x00, 0x62, 0x48, 0x73, 0x00, 0x63, 0x38, 0x64, 0x00,
- 0x64, 0x28, 0x55, 0x00, 0x65, 0x18, 0x46, 0x00, 0x66, 0x11, 0x71, 0x80,
- 0x67, 0x01, 0x62, 0x80, 0x67, 0xf1, 0x53, 0x80, 0x68, 0xe1, 0x44, 0x80,
- 0x69, 0xd1, 0x35, 0x80, 0x6a, 0xc1, 0x26, 0x80, 0x6b, 0xb1, 0x17, 0x80,
- 0x6c, 0xa1, 0x08, 0x80, 0x6d, 0x90, 0xf9, 0x80, 0x6e, 0x80, 0xea, 0x80,
- 0x6f, 0x70, 0xdb, 0x80, 0x70, 0x6a, 0x07, 0x00, 0x71, 0x59, 0xf8, 0x00,
- 0x72, 0x49, 0xe9, 0x00, 0x73, 0x39, 0xda, 0x00, 0x74, 0x29, 0xcb, 0x00,
- 0x75, 0x19, 0xbc, 0x00, 0x76, 0x09, 0xad, 0x00, 0x76, 0xf9, 0x9e, 0x00,
- 0x77, 0xe9, 0x8f, 0x00, 0x78, 0xd9, 0x80, 0x00, 0x79, 0xc9, 0x71, 0x00,
- 0x7a, 0xb9, 0x62, 0x00, 0x7b, 0xb2, 0x8d, 0x80, 0x7c, 0xa2, 0x7e, 0x80,
- 0x7d, 0x92, 0x6f, 0x80, 0x7e, 0x82, 0x60, 0x80, 0x7f, 0x72, 0x51, 0x80,
- 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x03, 0x04, 0x03,
- 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03,
- 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03,
- 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03,
- 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03,
- 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03,
- 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03,
- 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03,
- 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03,
- 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03,
- 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03,
- 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x00, 0x00,
- 0x8d, 0xc4, 0x00, 0x00, 0x00, 0x00, 0x9a, 0xb0, 0x01, 0x04, 0x00, 0x00,
- 0x8c, 0xa0, 0x00, 0x09, 0x00, 0x00, 0x9a, 0xb0, 0x01, 0x04, 0x00, 0x00,
- 0x8c, 0xa0, 0x00, 0x09, 0x4c, 0x4d, 0x54, 0x00, 0x41, 0x45, 0x44, 0x54,
- 0x00, 0x41, 0x45, 0x53, 0x54, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x54, 0x5a, 0x69, 0x66, 0x32, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x8f, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x0e,
- 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
- 0x73, 0x16, 0x7f, 0x3c, 0xff, 0xff, 0xff, 0xff, 0x9c, 0x4e, 0xa6, 0x9c,
- 0xff, 0xff, 0xff, 0xff, 0x9c, 0xbc, 0x20, 0xf0, 0xff, 0xff, 0xff, 0xff,
- 0xcb, 0x54, 0xb3, 0x00, 0xff, 0xff, 0xff, 0xff, 0xcb, 0xc7, 0x57, 0x70,
- 0xff, 0xff, 0xff, 0xff, 0xcc, 0xb7, 0x56, 0x80, 0xff, 0xff, 0xff, 0xff,
- 0xcd, 0xa7, 0x39, 0x70, 0xff, 0xff, 0xff, 0xff, 0xce, 0xa0, 0x73, 0x00,
- 0xff, 0xff, 0xff, 0xff, 0xcf, 0x87, 0x1b, 0x70, 0x00, 0x00, 0x00, 0x00,
- 0x03, 0x70, 0x39, 0x80, 0x00, 0x00, 0x00, 0x00, 0x04, 0x0d, 0x1c, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x05, 0x50, 0x1b, 0x80, 0x00, 0x00, 0x00, 0x00,
- 0x05, 0xf6, 0x38, 0x80, 0x00, 0x00, 0x00, 0x00, 0x07, 0x2f, 0xfd, 0x80,
- 0x00, 0x00, 0x00, 0x00, 0x07, 0xd6, 0x1a, 0x80, 0x00, 0x00, 0x00, 0x00,
- 0x09, 0x0f, 0xdf, 0x80, 0x00, 0x00, 0x00, 0x00, 0x09, 0xb5, 0xfc, 0x80,
- 0x00, 0x00, 0x00, 0x00, 0x0a, 0xef, 0xc1, 0x80, 0x00, 0x00, 0x00, 0x00,
- 0x0b, 0x9f, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0xd8, 0xde, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x0d, 0x7e, 0xfb, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x0e, 0xb8, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x5e, 0xdd, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x10, 0x98, 0xa2, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x11, 0x3e, 0xbf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x78, 0x84, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x13, 0x1e, 0xa1, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x14, 0x58, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0xfe, 0x83, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x16, 0x38, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x17, 0x0c, 0x89, 0x80, 0x00, 0x00, 0x00, 0x00, 0x18, 0x21, 0x64, 0x80,
- 0x00, 0x00, 0x00, 0x00, 0x18, 0xc7, 0x81, 0x80, 0x00, 0x00, 0x00, 0x00,
- 0x1a, 0x01, 0x46, 0x80, 0x00, 0x00, 0x00, 0x00, 0x1a, 0xa7, 0x63, 0x80,
- 0x00, 0x00, 0x00, 0x00, 0x1b, 0xe1, 0x28, 0x80, 0x00, 0x00, 0x00, 0x00,
- 0x1c, 0x87, 0x45, 0x80, 0x00, 0x00, 0x00, 0x00, 0x1d, 0xc1, 0x0a, 0x80,
- 0x00, 0x00, 0x00, 0x00, 0x1e, 0x79, 0x9c, 0x80, 0x00, 0x00, 0x00, 0x00,
- 0x1f, 0x97, 0xb2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x59, 0x7e, 0x80,
- 0x00, 0x00, 0x00, 0x00, 0x21, 0x80, 0xce, 0x80, 0x00, 0x00, 0x00, 0x00,
- 0x22, 0x42, 0x9b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0x69, 0xeb, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x24, 0x22, 0x7d, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x25, 0x49, 0xcd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x25, 0xef, 0xea, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x27, 0x29, 0xaf, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x27, 0xcf, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x29, 0x09, 0x91, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x29, 0xaf, 0xae, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x2a, 0xe9, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2b, 0x98, 0xca, 0x80,
- 0x00, 0x00, 0x00, 0x00, 0x2c, 0xd2, 0x8f, 0x80, 0x00, 0x00, 0x00, 0x00,
- 0x2d, 0x78, 0xac, 0x80, 0x00, 0x00, 0x00, 0x00, 0x2e, 0xb2, 0x71, 0x80,
- 0x00, 0x00, 0x00, 0x00, 0x2f, 0x58, 0x8e, 0x80, 0x00, 0x00, 0x00, 0x00,
- 0x30, 0x92, 0x53, 0x80, 0x00, 0x00, 0x00, 0x00, 0x31, 0x5d, 0x5a, 0x80,
- 0x00, 0x00, 0x00, 0x00, 0x32, 0x72, 0x35, 0x80, 0x00, 0x00, 0x00, 0x00,
- 0x33, 0x3d, 0x3c, 0x80, 0x00, 0x00, 0x00, 0x00, 0x34, 0x52, 0x17, 0x80,
- 0x00, 0x00, 0x00, 0x00, 0x35, 0x1d, 0x1e, 0x80, 0x00, 0x00, 0x00, 0x00,
- 0x36, 0x31, 0xf9, 0x80, 0x00, 0x00, 0x00, 0x00, 0x36, 0xfd, 0x00, 0x80,
- 0x00, 0x00, 0x00, 0x00, 0x38, 0x1b, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x38, 0xdc, 0xe2, 0x80, 0x00, 0x00, 0x00, 0x00, 0x39, 0xa7, 0xe9, 0x80,
- 0x00, 0x00, 0x00, 0x00, 0x3a, 0xbc, 0xc4, 0x80, 0x00, 0x00, 0x00, 0x00,
- 0x3b, 0xda, 0xda, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0xa5, 0xe1, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x3d, 0xba, 0xbc, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x3e, 0x85, 0xc3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x9a, 0x9e, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x40, 0x65, 0xa5, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x41, 0x83, 0xba, 0x80, 0x00, 0x00, 0x00, 0x00, 0x42, 0x45, 0x87, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x43, 0x63, 0x9c, 0x80, 0x00, 0x00, 0x00, 0x00,
- 0x44, 0x2e, 0xa3, 0x80, 0x00, 0x00, 0x00, 0x00, 0x45, 0x43, 0x7e, 0x80,
- 0x00, 0x00, 0x00, 0x00, 0x46, 0x05, 0x4b, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x47, 0x23, 0x60, 0x80, 0x00, 0x00, 0x00, 0x00, 0x47, 0xf7, 0xa2, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x48, 0xe7, 0x93, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x49, 0xd7, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4a, 0xc7, 0x75, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x4b, 0xb7, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x4c, 0xa7, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4d, 0x97, 0x48, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x4e, 0x87, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x4f, 0x77, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x70, 0x55, 0x80,
- 0x00, 0x00, 0x00, 0x00, 0x51, 0x60, 0x46, 0x80, 0x00, 0x00, 0x00, 0x00,
- 0x52, 0x50, 0x37, 0x80, 0x00, 0x00, 0x00, 0x00, 0x53, 0x40, 0x28, 0x80,
- 0x00, 0x00, 0x00, 0x00, 0x54, 0x30, 0x19, 0x80, 0x00, 0x00, 0x00, 0x00,
- 0x55, 0x20, 0x0a, 0x80, 0x00, 0x00, 0x00, 0x00, 0x56, 0x0f, 0xfb, 0x80,
- 0x00, 0x00, 0x00, 0x00, 0x56, 0xff, 0xec, 0x80, 0x00, 0x00, 0x00, 0x00,
- 0x57, 0xef, 0xdd, 0x80, 0x00, 0x00, 0x00, 0x00, 0x58, 0xdf, 0xce, 0x80,
- 0x00, 0x00, 0x00, 0x00, 0x59, 0xcf, 0xbf, 0x80, 0x00, 0x00, 0x00, 0x00,
- 0x5a, 0xbf, 0xb0, 0x80, 0x00, 0x00, 0x00, 0x00, 0x5b, 0xb8, 0xdc, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x5c, 0xa8, 0xcd, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x5d, 0x98, 0xbe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5e, 0x88, 0xaf, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x5f, 0x78, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x60, 0x68, 0x91, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0x58, 0x82, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x62, 0x48, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x63, 0x38, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x28, 0x55, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x65, 0x18, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x66, 0x11, 0x71, 0x80, 0x00, 0x00, 0x00, 0x00, 0x67, 0x01, 0x62, 0x80,
- 0x00, 0x00, 0x00, 0x00, 0x67, 0xf1, 0x53, 0x80, 0x00, 0x00, 0x00, 0x00,
- 0x68, 0xe1, 0x44, 0x80, 0x00, 0x00, 0x00, 0x00, 0x69, 0xd1, 0x35, 0x80,
- 0x00, 0x00, 0x00, 0x00, 0x6a, 0xc1, 0x26, 0x80, 0x00, 0x00, 0x00, 0x00,
- 0x6b, 0xb1, 0x17, 0x80, 0x00, 0x00, 0x00, 0x00, 0x6c, 0xa1, 0x08, 0x80,
- 0x00, 0x00, 0x00, 0x00, 0x6d, 0x90, 0xf9, 0x80, 0x00, 0x00, 0x00, 0x00,
- 0x6e, 0x80, 0xea, 0x80, 0x00, 0x00, 0x00, 0x00, 0x6f, 0x70, 0xdb, 0x80,
- 0x00, 0x00, 0x00, 0x00, 0x70, 0x6a, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x71, 0x59, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x72, 0x49, 0xe9, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x73, 0x39, 0xda, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x74, 0x29, 0xcb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x75, 0x19, 0xbc, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x76, 0x09, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x76, 0xf9, 0x9e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0xe9, 0x8f, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x78, 0xd9, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x79, 0xc9, 0x71, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7a, 0xb9, 0x62, 0x00,
- 0x00, 0x00, 0x00, 0x00, 0x7b, 0xb2, 0x8d, 0x80, 0x00, 0x00, 0x00, 0x00,
- 0x7c, 0xa2, 0x7e, 0x80, 0x00, 0x00, 0x00, 0x00, 0x7d, 0x92, 0x6f, 0x80,
- 0x00, 0x00, 0x00, 0x00, 0x7e, 0x82, 0x60, 0x80, 0x00, 0x00, 0x00, 0x00,
- 0x7f, 0x72, 0x51, 0x80, 0x00, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02,
- 0x01, 0x02, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04,
- 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04,
- 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04,
- 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04,
- 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04,
- 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04,
- 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04,
- 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04,
- 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04,
- 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04,
- 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04,
- 0x03, 0x04, 0x03, 0x00, 0x00, 0x8d, 0xc4, 0x00, 0x00, 0x00, 0x00, 0x9a,
- 0xb0, 0x01, 0x04, 0x00, 0x00, 0x8c, 0xa0, 0x00, 0x09, 0x00, 0x00, 0x9a,
- 0xb0, 0x01, 0x04, 0x00, 0x00, 0x8c, 0xa0, 0x00, 0x09, 0x4c, 0x4d, 0x54,
- 0x00, 0x41, 0x45, 0x44, 0x54, 0x00, 0x41, 0x45, 0x53, 0x54, 0x00, 0x00,
- 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x41, 0x45,
- 0x53, 0x54, 0x2d, 0x31, 0x30, 0x41, 0x45, 0x44, 0x54, 0x2c, 0x4d, 0x31,
- 0x30, 0x2e, 0x31, 0x2e, 0x30, 0x2c, 0x4d, 0x34, 0x2e, 0x31, 0x2e, 0x30,
- 0x2f, 0x33, 0x0a
-};
-unsigned int Australia_Sydney_len = 2223;
diff --git a/absl/time/time.cc b/absl/time/time.cc
index 1ec2026..d983c12 100644
--- a/absl/time/time.cc
+++ b/absl/time/time.cc
@@ -66,6 +66,7 @@
: q - 1;
}
+ABSL_INTERNAL_DISABLE_DEPRECATED_DECLARATION_WARNING
inline absl::Time::Breakdown InfiniteFutureBreakdown() {
absl::Time::Breakdown bd;
bd.year = std::numeric_limits<int64_t>::max();
@@ -99,6 +100,7 @@
bd.zone_abbr = "-00";
return bd;
}
+ABSL_INTERNAL_RESTORE_DEPRECATED_DECLARATION_WARNING
inline absl::TimeZone::CivilInfo InfiniteFutureCivilInfo() {
TimeZone::CivilInfo ci;
@@ -120,6 +122,7 @@
return ci;
}
+ABSL_INTERNAL_DISABLE_DEPRECATED_DECLARATION_WARNING
inline absl::TimeConversion InfiniteFutureTimeConversion() {
absl::TimeConversion tc;
tc.pre = tc.trans = tc.post = absl::InfiniteFuture();
@@ -135,9 +138,10 @@
tc.normalized = true;
return tc;
}
+ABSL_INTERNAL_RESTORE_DEPRECATED_DECLARATION_WARNING
// Makes a Time from sec, overflowing to InfiniteFuture/InfinitePast as
-// necessary. If sec is min/max, then consult cs+tz to check for overlow.
+// necessary. If sec is min/max, then consult cs+tz to check for overflow.
Time MakeTimeWithOverflow(const cctz::time_point<cctz::seconds>& sec,
const cctz::civil_second& cs,
const cctz::time_zone& tz,
@@ -203,6 +207,7 @@
// Time
//
+ABSL_INTERNAL_DISABLE_DEPRECATED_DECLARATION_WARNING
absl::Time::Breakdown Time::In(absl::TimeZone tz) const {
if (*this == absl::InfiniteFuture()) return InfiniteFutureBreakdown();
if (*this == absl::InfinitePast()) return InfinitePastBreakdown();
@@ -227,6 +232,7 @@
bd.zone_abbr = al.abbr;
return bd;
}
+ABSL_INTERNAL_RESTORE_DEPRECATED_DECLARATION_WARNING
//
// Conversions from/to other time types.
@@ -297,7 +303,7 @@
timespec ts;
absl::Duration d = time_internal::ToUnixDuration(t);
if (!time_internal::IsInfiniteDuration(d)) {
- ts.tv_sec = time_internal::GetRepHi(d);
+ ts.tv_sec = static_cast<decltype(ts.tv_sec)>(time_internal::GetRepHi(d));
if (ts.tv_sec == time_internal::GetRepHi(d)) { // no time_t narrowing
ts.tv_nsec = time_internal::GetRepLo(d) / 4; // floor
return ts;
@@ -316,7 +322,7 @@
timeval ToTimeval(Time t) {
timeval tv;
timespec ts = absl::ToTimespec(t);
- tv.tv_sec = ts.tv_sec;
+ tv.tv_sec = static_cast<decltype(tv.tv_sec)>(ts.tv_sec);
if (tv.tv_sec != ts.tv_sec) { // narrowing
if (ts.tv_sec < 0) {
tv.tv_sec = std::numeric_limits<decltype(tv.tv_sec)>::min();
@@ -398,7 +404,7 @@
//
// Conversions involving time zones.
//
-
+ABSL_INTERNAL_DISABLE_DEPRECATED_DECLARATION_WARNING
absl::TimeConversion ConvertDateTime(int64_t year, int mon, int day, int hour,
int min, int sec, TimeZone tz) {
// Avoids years that are too extreme for CivilSecond to normalize.
@@ -430,6 +436,7 @@
}
return tc;
}
+ABSL_INTERNAL_RESTORE_DEPRECATED_DECLARATION_WARNING
absl::Time FromTM(const struct tm& tm, absl::TimeZone tz) {
civil_year_t tm_year = tm.tm_year;
diff --git a/absl/time/time.h b/absl/time/time.h
index e9cbce8..d367ace 100644
--- a/absl/time/time.h
+++ b/absl/time/time.h
@@ -78,11 +78,13 @@
#include <cmath>
#include <cstdint>
#include <ctime>
+#include <limits>
#include <ostream>
#include <string>
#include <type_traits>
#include <utility>
+#include "absl/base/config.h"
#include "absl/base/macros.h"
#include "absl/strings/string_view.h"
#include "absl/time/civil_time.h"
@@ -96,20 +98,24 @@
class TimeZone; // Defined below
namespace time_internal {
-int64_t IDivDuration(bool satq, Duration num, Duration den, Duration* rem);
-constexpr Time FromUnixDuration(Duration d);
-constexpr Duration ToUnixDuration(Time t);
-constexpr int64_t GetRepHi(Duration d);
-constexpr uint32_t GetRepLo(Duration d);
-constexpr Duration MakeDuration(int64_t hi, uint32_t lo);
-constexpr Duration MakeDuration(int64_t hi, int64_t lo);
-inline Duration MakePosDoubleDuration(double n);
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Time FromUnixDuration(Duration d);
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Duration ToUnixDuration(Time t);
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr int64_t GetRepHi(Duration d);
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr uint32_t GetRepLo(Duration d);
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Duration MakeDuration(int64_t hi,
+ uint32_t lo);
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Duration MakeDuration(int64_t hi,
+ int64_t lo);
+ABSL_ATTRIBUTE_CONST_FUNCTION inline Duration MakePosDoubleDuration(double n);
constexpr int64_t kTicksPerNanosecond = 4;
constexpr int64_t kTicksPerSecond = 1000 * 1000 * 1000 * kTicksPerNanosecond;
template <std::intmax_t N>
-constexpr Duration FromInt64(int64_t v, std::ratio<1, N>);
-constexpr Duration FromInt64(int64_t v, std::ratio<60>);
-constexpr Duration FromInt64(int64_t v, std::ratio<3600>);
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Duration FromInt64(int64_t v,
+ std::ratio<1, N>);
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Duration FromInt64(int64_t v,
+ std::ratio<60>);
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Duration FromInt64(int64_t v,
+ std::ratio<3600>);
template <typename T>
using EnableIfIntegral = typename std::enable_if<
std::is_integral<T>::value || std::is_enum<T>::value, int>::type;
@@ -120,7 +126,7 @@
// Duration
//
-// The `absl::Duration` class represents a signed, fixed-length span of time.
+// The `absl::Duration` class represents a signed, fixed-length amount of time.
// A `Duration` is generated using a unit-specific factory function, or is
// the result of subtracting one `absl::Time` from another. Durations behave
// like unit-safe integers and they support all the natural integer-like
@@ -162,7 +168,7 @@
constexpr Duration() : rep_hi_(0), rep_lo_(0) {} // zero-length duration
// Copyable.
-#if !defined(__clang__) && defined(_MSC_VER) && _MSC_VER < 1910
+#if !defined(__clang__) && defined(_MSC_VER) && _MSC_VER < 1930
// Explicitly defining the constexpr copy constructor avoids an MSVC bug.
constexpr Duration(const Duration& d)
: rep_hi_(d.rep_hi_), rep_lo_(d.rep_lo_) {}
@@ -181,7 +187,12 @@
Duration& operator%=(Duration rhs);
// Overloads that forward to either the int64_t or double overloads above.
- // Integer operands must be representable as int64_t.
+ // Integer operands must be representable as int64_t. Integer division is
+ // truncating, so values less than the resolution will be returned as zero.
+ // Floating-point multiplication and division is rounding (halfway cases
+ // rounding away from zero), so values less than the resolution may be
+ // returned as either the resolution or zero. In particular, `d / 2.0`
+ // can produce `d` when it is the resolution and "even".
template <typename T, time_internal::EnableIfIntegral<T> = 0>
Duration& operator*=(T r) {
int64_t x = r;
@@ -208,7 +219,7 @@
template <typename H>
friend H AbslHashValue(H h, Duration d) {
- return H::combine(std::move(h), d.rep_hi_, d.rep_lo_);
+ return H::combine(std::move(h), d.rep_hi_.Get(), d.rep_lo_);
}
private:
@@ -217,42 +228,114 @@
friend constexpr Duration time_internal::MakeDuration(int64_t hi,
uint32_t lo);
constexpr Duration(int64_t hi, uint32_t lo) : rep_hi_(hi), rep_lo_(lo) {}
- int64_t rep_hi_;
+
+ // We store `rep_hi_` 4-byte rather than 8-byte aligned to avoid 4 bytes of
+ // tail padding.
+ class HiRep {
+ public:
+ // Default constructor default-initializes `hi_`, which has the same
+ // semantics as default-initializing an `int64_t` (undetermined value).
+ HiRep() = default;
+
+ HiRep(const HiRep&) = default;
+ HiRep& operator=(const HiRep&) = default;
+
+ explicit constexpr HiRep(const int64_t value)
+ : // C++17 forbids default-initialization in constexpr contexts. We can
+ // remove this in C++20.
+#if defined(ABSL_IS_BIG_ENDIAN) && ABSL_IS_BIG_ENDIAN
+ hi_(0),
+ lo_(0)
+#else
+ lo_(0),
+ hi_(0)
+#endif
+ {
+ *this = value;
+ }
+
+ constexpr int64_t Get() const {
+ const uint64_t unsigned_value =
+ (static_cast<uint64_t>(hi_) << 32) | static_cast<uint64_t>(lo_);
+ // `static_cast<int64_t>(unsigned_value)` is implementation-defined
+ // before c++20. On all supported platforms the behaviour is that mandated
+ // by c++20, i.e. "If the destination type is signed, [...] the result is
+ // the unique value of the destination type equal to the source value
+ // modulo 2^n, where n is the number of bits used to represent the
+ // destination type."
+ static_assert(
+ (static_cast<int64_t>((std::numeric_limits<uint64_t>::max)()) ==
+ int64_t{-1}) &&
+ (static_cast<int64_t>(static_cast<uint64_t>(
+ (std::numeric_limits<int64_t>::max)()) +
+ 1) ==
+ (std::numeric_limits<int64_t>::min)()),
+ "static_cast<int64_t>(uint64_t) does not have c++20 semantics");
+ return static_cast<int64_t>(unsigned_value);
+ }
+
+ constexpr HiRep& operator=(const int64_t value) {
+ // "If the destination type is unsigned, the resulting value is the
+ // smallest unsigned value equal to the source value modulo 2^n
+ // where `n` is the number of bits used to represent the destination
+ // type".
+ const auto unsigned_value = static_cast<uint64_t>(value);
+ hi_ = static_cast<uint32_t>(unsigned_value >> 32);
+ lo_ = static_cast<uint32_t>(unsigned_value);
+ return *this;
+ }
+
+ private:
+ // Notes:
+ // - Ideally we would use a `char[]` and `std::bitcast`, but the latter
+ // does not exist (and is not constexpr in `absl`) before c++20.
+ // - Order is optimized depending on endianness so that the compiler can
+ // turn `Get()` (resp. `operator=()`) into a single 8-byte load (resp.
+ // store).
+#if defined(ABSL_IS_BIG_ENDIAN) && ABSL_IS_BIG_ENDIAN
+ uint32_t hi_;
+ uint32_t lo_;
+#else
+ uint32_t lo_;
+ uint32_t hi_;
+#endif
+ };
+ HiRep rep_hi_;
uint32_t rep_lo_;
};
// Relational Operators
-constexpr bool operator<(Duration lhs, Duration rhs);
-constexpr bool operator>(Duration lhs, Duration rhs) { return rhs < lhs; }
-constexpr bool operator>=(Duration lhs, Duration rhs) { return !(lhs < rhs); }
-constexpr bool operator<=(Duration lhs, Duration rhs) { return !(rhs < lhs); }
-constexpr bool operator==(Duration lhs, Duration rhs);
-constexpr bool operator!=(Duration lhs, Duration rhs) { return !(lhs == rhs); }
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr bool operator<(Duration lhs,
+ Duration rhs);
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr bool operator>(Duration lhs,
+ Duration rhs) {
+ return rhs < lhs;
+}
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr bool operator>=(Duration lhs,
+ Duration rhs) {
+ return !(lhs < rhs);
+}
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr bool operator<=(Duration lhs,
+ Duration rhs) {
+ return !(rhs < lhs);
+}
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr bool operator==(Duration lhs,
+ Duration rhs);
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr bool operator!=(Duration lhs,
+ Duration rhs) {
+ return !(lhs == rhs);
+}
// Additive Operators
-constexpr Duration operator-(Duration d);
-inline Duration operator+(Duration lhs, Duration rhs) { return lhs += rhs; }
-inline Duration operator-(Duration lhs, Duration rhs) { return lhs -= rhs; }
-
-// Multiplicative Operators
-// Integer operands must be representable as int64_t.
-template <typename T>
-Duration operator*(Duration lhs, T rhs) {
- return lhs *= rhs;
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Duration operator-(Duration d);
+ABSL_ATTRIBUTE_CONST_FUNCTION inline Duration operator+(Duration lhs,
+ Duration rhs) {
+ return lhs += rhs;
}
-template <typename T>
-Duration operator*(T lhs, Duration rhs) {
- return rhs *= lhs;
+ABSL_ATTRIBUTE_CONST_FUNCTION inline Duration operator-(Duration lhs,
+ Duration rhs) {
+ return lhs -= rhs;
}
-template <typename T>
-Duration operator/(Duration lhs, T rhs) {
- return lhs /= rhs;
-}
-inline int64_t operator/(Duration lhs, Duration rhs) {
- return time_internal::IDivDuration(true, lhs, rhs,
- &lhs); // trunc towards zero
-}
-inline Duration operator%(Duration lhs, Duration rhs) { return lhs %= rhs; }
// IDivDuration()
//
@@ -282,10 +365,7 @@
// // Here, q would overflow int64_t, so rem accounts for the difference.
// int64_t q = absl::IDivDuration(a, b, &rem);
// // q == std::numeric_limits<int64_t>::max(), rem == a - b * q
-inline int64_t IDivDuration(Duration num, Duration den, Duration* rem) {
- return time_internal::IDivDuration(true, num, den,
- rem); // trunc towards zero
-}
+int64_t IDivDuration(Duration num, Duration den, Duration* rem);
// FDivDuration()
//
@@ -299,18 +379,44 @@
//
// double d = absl::FDivDuration(absl::Milliseconds(1500), absl::Seconds(1));
// // d == 1.5
-double FDivDuration(Duration num, Duration den);
+ABSL_ATTRIBUTE_CONST_FUNCTION double FDivDuration(Duration num, Duration den);
+
+// Multiplicative Operators
+// Integer operands must be representable as int64_t.
+template <typename T>
+ABSL_ATTRIBUTE_CONST_FUNCTION Duration operator*(Duration lhs, T rhs) {
+ return lhs *= rhs;
+}
+template <typename T>
+ABSL_ATTRIBUTE_CONST_FUNCTION Duration operator*(T lhs, Duration rhs) {
+ return rhs *= lhs;
+}
+template <typename T>
+ABSL_ATTRIBUTE_CONST_FUNCTION Duration operator/(Duration lhs, T rhs) {
+ return lhs /= rhs;
+}
+ABSL_ATTRIBUTE_CONST_FUNCTION inline int64_t operator/(Duration lhs,
+ Duration rhs) {
+ return IDivDuration(lhs, rhs,
+ &lhs); // trunc towards zero
+}
+ABSL_ATTRIBUTE_CONST_FUNCTION inline Duration operator%(Duration lhs,
+ Duration rhs) {
+ return lhs %= rhs;
+}
// ZeroDuration()
//
// Returns a zero-length duration. This function behaves just like the default
// constructor, but the name helps make the semantics clear at call sites.
-constexpr Duration ZeroDuration() { return Duration(); }
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Duration ZeroDuration() {
+ return Duration();
+}
// AbsDuration()
//
// Returns the absolute value of a duration.
-inline Duration AbsDuration(Duration d) {
+ABSL_ATTRIBUTE_CONST_FUNCTION inline Duration AbsDuration(Duration d) {
return (d < ZeroDuration()) ? -d : d;
}
@@ -322,7 +428,7 @@
//
// absl::Duration d = absl::Nanoseconds(123456789);
// absl::Duration a = absl::Trunc(d, absl::Microseconds(1)); // 123456us
-Duration Trunc(Duration d, Duration unit);
+ABSL_ATTRIBUTE_CONST_FUNCTION Duration Trunc(Duration d, Duration unit);
// Floor()
//
@@ -333,7 +439,7 @@
//
// absl::Duration d = absl::Nanoseconds(123456789);
// absl::Duration b = absl::Floor(d, absl::Microseconds(1)); // 123456us
-Duration Floor(Duration d, Duration unit);
+ABSL_ATTRIBUTE_CONST_FUNCTION Duration Floor(Duration d, Duration unit);
// Ceil()
//
@@ -344,7 +450,7 @@
//
// absl::Duration d = absl::Nanoseconds(123456789);
// absl::Duration c = absl::Ceil(d, absl::Microseconds(1)); // 123457us
-Duration Ceil(Duration d, Duration unit);
+ABSL_ATTRIBUTE_CONST_FUNCTION Duration Ceil(Duration d, Duration unit);
// InfiniteDuration()
//
@@ -380,7 +486,7 @@
//
// The examples involving the `/` operator above also apply to `IDivDuration()`
// and `FDivDuration()`.
-constexpr Duration InfiniteDuration();
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Duration InfiniteDuration();
// Nanoseconds()
// Microseconds()
@@ -404,27 +510,27 @@
// absl::Duration a = absl::Seconds(60);
// absl::Duration b = absl::Minutes(1); // b == a
template <typename T, time_internal::EnableIfIntegral<T> = 0>
-constexpr Duration Nanoseconds(T n) {
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Duration Nanoseconds(T n) {
return time_internal::FromInt64(n, std::nano{});
}
template <typename T, time_internal::EnableIfIntegral<T> = 0>
-constexpr Duration Microseconds(T n) {
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Duration Microseconds(T n) {
return time_internal::FromInt64(n, std::micro{});
}
template <typename T, time_internal::EnableIfIntegral<T> = 0>
-constexpr Duration Milliseconds(T n) {
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Duration Milliseconds(T n) {
return time_internal::FromInt64(n, std::milli{});
}
template <typename T, time_internal::EnableIfIntegral<T> = 0>
-constexpr Duration Seconds(T n) {
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Duration Seconds(T n) {
return time_internal::FromInt64(n, std::ratio<1>{});
}
template <typename T, time_internal::EnableIfIntegral<T> = 0>
-constexpr Duration Minutes(T n) {
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Duration Minutes(T n) {
return time_internal::FromInt64(n, std::ratio<60>{});
}
template <typename T, time_internal::EnableIfIntegral<T> = 0>
-constexpr Duration Hours(T n) {
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Duration Hours(T n) {
return time_internal::FromInt64(n, std::ratio<3600>{});
}
@@ -438,19 +544,19 @@
// auto a = absl::Seconds(1.5); // OK
// auto b = absl::Milliseconds(1500); // BETTER
template <typename T, time_internal::EnableIfFloat<T> = 0>
-Duration Nanoseconds(T n) {
+ABSL_ATTRIBUTE_CONST_FUNCTION Duration Nanoseconds(T n) {
return n * Nanoseconds(1);
}
template <typename T, time_internal::EnableIfFloat<T> = 0>
-Duration Microseconds(T n) {
+ABSL_ATTRIBUTE_CONST_FUNCTION Duration Microseconds(T n) {
return n * Microseconds(1);
}
template <typename T, time_internal::EnableIfFloat<T> = 0>
-Duration Milliseconds(T n) {
+ABSL_ATTRIBUTE_CONST_FUNCTION Duration Milliseconds(T n) {
return n * Milliseconds(1);
}
template <typename T, time_internal::EnableIfFloat<T> = 0>
-Duration Seconds(T n) {
+ABSL_ATTRIBUTE_CONST_FUNCTION Duration Seconds(T n) {
if (n >= 0) { // Note: `NaN >= 0` is false.
if (n >= static_cast<T>((std::numeric_limits<int64_t>::max)())) {
return InfiniteDuration();
@@ -464,11 +570,11 @@
}
}
template <typename T, time_internal::EnableIfFloat<T> = 0>
-Duration Minutes(T n) {
+ABSL_ATTRIBUTE_CONST_FUNCTION Duration Minutes(T n) {
return n * Minutes(1);
}
template <typename T, time_internal::EnableIfFloat<T> = 0>
-Duration Hours(T n) {
+ABSL_ATTRIBUTE_CONST_FUNCTION Duration Hours(T n) {
return n * Hours(1);
}
@@ -480,21 +586,22 @@
// ToInt64Hours()
//
// Helper functions that convert a Duration to an integral count of the
-// indicated unit. These functions are shorthand for the `IDivDuration()`
-// function above; see its documentation for details about overflow, etc.
+// indicated unit. These return the same results as the `IDivDuration()`
+// function, though they usually do so more efficiently; see the
+// documentation of `IDivDuration()` for details about overflow, etc.
//
// Example:
//
// absl::Duration d = absl::Milliseconds(1500);
// int64_t isec = absl::ToInt64Seconds(d); // isec == 1
-ABSL_ATTRIBUTE_PURE_FUNCTION int64_t ToInt64Nanoseconds(Duration d);
-ABSL_ATTRIBUTE_PURE_FUNCTION int64_t ToInt64Microseconds(Duration d);
-ABSL_ATTRIBUTE_PURE_FUNCTION int64_t ToInt64Milliseconds(Duration d);
-ABSL_ATTRIBUTE_PURE_FUNCTION int64_t ToInt64Seconds(Duration d);
-ABSL_ATTRIBUTE_PURE_FUNCTION int64_t ToInt64Minutes(Duration d);
-ABSL_ATTRIBUTE_PURE_FUNCTION int64_t ToInt64Hours(Duration d);
+ABSL_ATTRIBUTE_CONST_FUNCTION int64_t ToInt64Nanoseconds(Duration d);
+ABSL_ATTRIBUTE_CONST_FUNCTION int64_t ToInt64Microseconds(Duration d);
+ABSL_ATTRIBUTE_CONST_FUNCTION int64_t ToInt64Milliseconds(Duration d);
+ABSL_ATTRIBUTE_CONST_FUNCTION int64_t ToInt64Seconds(Duration d);
+ABSL_ATTRIBUTE_CONST_FUNCTION int64_t ToInt64Minutes(Duration d);
+ABSL_ATTRIBUTE_CONST_FUNCTION int64_t ToInt64Hours(Duration d);
-// ToDoubleNanoSeconds()
+// ToDoubleNanoseconds()
// ToDoubleMicroseconds()
// ToDoubleMilliseconds()
// ToDoubleSeconds()
@@ -509,12 +616,12 @@
//
// absl::Duration d = absl::Milliseconds(1500);
// double dsec = absl::ToDoubleSeconds(d); // dsec == 1.5
-ABSL_ATTRIBUTE_PURE_FUNCTION double ToDoubleNanoseconds(Duration d);
-ABSL_ATTRIBUTE_PURE_FUNCTION double ToDoubleMicroseconds(Duration d);
-ABSL_ATTRIBUTE_PURE_FUNCTION double ToDoubleMilliseconds(Duration d);
-ABSL_ATTRIBUTE_PURE_FUNCTION double ToDoubleSeconds(Duration d);
-ABSL_ATTRIBUTE_PURE_FUNCTION double ToDoubleMinutes(Duration d);
-ABSL_ATTRIBUTE_PURE_FUNCTION double ToDoubleHours(Duration d);
+ABSL_ATTRIBUTE_CONST_FUNCTION double ToDoubleNanoseconds(Duration d);
+ABSL_ATTRIBUTE_CONST_FUNCTION double ToDoubleMicroseconds(Duration d);
+ABSL_ATTRIBUTE_CONST_FUNCTION double ToDoubleMilliseconds(Duration d);
+ABSL_ATTRIBUTE_CONST_FUNCTION double ToDoubleSeconds(Duration d);
+ABSL_ATTRIBUTE_CONST_FUNCTION double ToDoubleMinutes(Duration d);
+ABSL_ATTRIBUTE_CONST_FUNCTION double ToDoubleHours(Duration d);
// FromChrono()
//
@@ -524,12 +631,18 @@
//
// std::chrono::milliseconds ms(123);
// absl::Duration d = absl::FromChrono(ms);
-constexpr Duration FromChrono(const std::chrono::nanoseconds& d);
-constexpr Duration FromChrono(const std::chrono::microseconds& d);
-constexpr Duration FromChrono(const std::chrono::milliseconds& d);
-constexpr Duration FromChrono(const std::chrono::seconds& d);
-constexpr Duration FromChrono(const std::chrono::minutes& d);
-constexpr Duration FromChrono(const std::chrono::hours& d);
+ABSL_ATTRIBUTE_PURE_FUNCTION constexpr Duration FromChrono(
+ const std::chrono::nanoseconds& d);
+ABSL_ATTRIBUTE_PURE_FUNCTION constexpr Duration FromChrono(
+ const std::chrono::microseconds& d);
+ABSL_ATTRIBUTE_PURE_FUNCTION constexpr Duration FromChrono(
+ const std::chrono::milliseconds& d);
+ABSL_ATTRIBUTE_PURE_FUNCTION constexpr Duration FromChrono(
+ const std::chrono::seconds& d);
+ABSL_ATTRIBUTE_PURE_FUNCTION constexpr Duration FromChrono(
+ const std::chrono::minutes& d);
+ABSL_ATTRIBUTE_PURE_FUNCTION constexpr Duration FromChrono(
+ const std::chrono::hours& d);
// ToChronoNanoseconds()
// ToChronoMicroseconds()
@@ -549,24 +662,33 @@
// auto y = absl::ToChronoNanoseconds(d); // x == y
// auto z = absl::ToChronoSeconds(absl::InfiniteDuration());
// // z == std::chrono::seconds::max()
-std::chrono::nanoseconds ToChronoNanoseconds(Duration d);
-std::chrono::microseconds ToChronoMicroseconds(Duration d);
-std::chrono::milliseconds ToChronoMilliseconds(Duration d);
-std::chrono::seconds ToChronoSeconds(Duration d);
-std::chrono::minutes ToChronoMinutes(Duration d);
-std::chrono::hours ToChronoHours(Duration d);
+ABSL_ATTRIBUTE_CONST_FUNCTION std::chrono::nanoseconds ToChronoNanoseconds(
+ Duration d);
+ABSL_ATTRIBUTE_CONST_FUNCTION std::chrono::microseconds ToChronoMicroseconds(
+ Duration d);
+ABSL_ATTRIBUTE_CONST_FUNCTION std::chrono::milliseconds ToChronoMilliseconds(
+ Duration d);
+ABSL_ATTRIBUTE_CONST_FUNCTION std::chrono::seconds ToChronoSeconds(Duration d);
+ABSL_ATTRIBUTE_CONST_FUNCTION std::chrono::minutes ToChronoMinutes(Duration d);
+ABSL_ATTRIBUTE_CONST_FUNCTION std::chrono::hours ToChronoHours(Duration d);
// FormatDuration()
//
// Returns a string representing the duration in the form "72h3m0.5s".
// Returns "inf" or "-inf" for +/- `InfiniteDuration()`.
-std::string FormatDuration(Duration d);
+ABSL_ATTRIBUTE_CONST_FUNCTION std::string FormatDuration(Duration d);
// Output stream operator.
inline std::ostream& operator<<(std::ostream& os, Duration d) {
return os << FormatDuration(d);
}
+// Support for StrFormat(), StrCat() etc.
+template <typename Sink>
+void AbslStringify(Sink& sink, Duration d) {
+ sink.Append(FormatDuration(d));
+}
+
// ParseDuration()
//
// Parses a duration string consisting of a possibly signed sequence of
@@ -578,7 +700,7 @@
// AbslParseFlag()
//
-// Parses a command-line flag string representation `text` into a a Duration
+// Parses a command-line flag string representation `text` into a Duration
// value. Duration flags must be specified in a format that is valid input for
// `absl::ParseDuration()`.
bool AbslParseFlag(absl::string_view text, Duration* dst, std::string* error);
@@ -676,8 +798,7 @@
// `absl::TimeZone`.
//
// Deprecated. Use `absl::TimeZone::CivilInfo`.
- struct
- Breakdown {
+ struct ABSL_DEPRECATED("Use `absl::TimeZone::CivilInfo`.") Breakdown {
int64_t year; // year (e.g., 2013)
int month; // month of year [1:12]
int day; // day of month [1:31]
@@ -703,7 +824,10 @@
// Returns the breakdown of this instant in the given TimeZone.
//
// Deprecated. Use `absl::TimeZone::At(Time)`.
+ ABSL_INTERNAL_DISABLE_DEPRECATED_DECLARATION_WARNING
+ ABSL_DEPRECATED("Use `absl::TimeZone::At(Time)`.")
Breakdown In(TimeZone tz) const;
+ ABSL_INTERNAL_RESTORE_DEPRECATED_DECLARATION_WARNING
template <typename H>
friend H AbslHashValue(H h, Time t) {
@@ -724,48 +848,69 @@
};
// Relational Operators
-constexpr bool operator<(Time lhs, Time rhs) { return lhs.rep_ < rhs.rep_; }
-constexpr bool operator>(Time lhs, Time rhs) { return rhs < lhs; }
-constexpr bool operator>=(Time lhs, Time rhs) { return !(lhs < rhs); }
-constexpr bool operator<=(Time lhs, Time rhs) { return !(rhs < lhs); }
-constexpr bool operator==(Time lhs, Time rhs) { return lhs.rep_ == rhs.rep_; }
-constexpr bool operator!=(Time lhs, Time rhs) { return !(lhs == rhs); }
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr bool operator<(Time lhs, Time rhs) {
+ return lhs.rep_ < rhs.rep_;
+}
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr bool operator>(Time lhs, Time rhs) {
+ return rhs < lhs;
+}
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr bool operator>=(Time lhs, Time rhs) {
+ return !(lhs < rhs);
+}
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr bool operator<=(Time lhs, Time rhs) {
+ return !(rhs < lhs);
+}
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr bool operator==(Time lhs, Time rhs) {
+ return lhs.rep_ == rhs.rep_;
+}
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr bool operator!=(Time lhs, Time rhs) {
+ return !(lhs == rhs);
+}
// Additive Operators
-inline Time operator+(Time lhs, Duration rhs) { return lhs += rhs; }
-inline Time operator+(Duration lhs, Time rhs) { return rhs += lhs; }
-inline Time operator-(Time lhs, Duration rhs) { return lhs -= rhs; }
-inline Duration operator-(Time lhs, Time rhs) { return lhs.rep_ - rhs.rep_; }
+ABSL_ATTRIBUTE_CONST_FUNCTION inline Time operator+(Time lhs, Duration rhs) {
+ return lhs += rhs;
+}
+ABSL_ATTRIBUTE_CONST_FUNCTION inline Time operator+(Duration lhs, Time rhs) {
+ return rhs += lhs;
+}
+ABSL_ATTRIBUTE_CONST_FUNCTION inline Time operator-(Time lhs, Duration rhs) {
+ return lhs -= rhs;
+}
+ABSL_ATTRIBUTE_CONST_FUNCTION inline Duration operator-(Time lhs, Time rhs) {
+ return lhs.rep_ - rhs.rep_;
+}
// UnixEpoch()
//
// Returns the `absl::Time` representing "1970-01-01 00:00:00.0 +0000".
-constexpr Time UnixEpoch() { return Time(); }
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Time UnixEpoch() { return Time(); }
// UniversalEpoch()
//
// Returns the `absl::Time` representing "0001-01-01 00:00:00.0 +0000", the
// epoch of the ICU Universal Time Scale.
-constexpr Time UniversalEpoch() {
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Time UniversalEpoch() {
// 719162 is the number of days from 0001-01-01 to 1970-01-01,
// assuming the Gregorian calendar.
- return Time(time_internal::MakeDuration(-24 * 719162 * int64_t{3600}, 0U));
+ return Time(
+ time_internal::MakeDuration(-24 * 719162 * int64_t{3600}, uint32_t{0}));
}
// InfiniteFuture()
//
// Returns an `absl::Time` that is infinitely far in the future.
-constexpr Time InfiniteFuture() {
- return Time(
- time_internal::MakeDuration((std::numeric_limits<int64_t>::max)(), ~0U));
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Time InfiniteFuture() {
+ return Time(time_internal::MakeDuration((std::numeric_limits<int64_t>::max)(),
+ ~uint32_t{0}));
}
// InfinitePast()
//
// Returns an `absl::Time` that is infinitely far in the past.
-constexpr Time InfinitePast() {
- return Time(
- time_internal::MakeDuration((std::numeric_limits<int64_t>::min)(), ~0U));
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Time InfinitePast() {
+ return Time(time_internal::MakeDuration((std::numeric_limits<int64_t>::min)(),
+ ~uint32_t{0}));
}
// FromUnixNanos()
@@ -776,14 +921,15 @@
// FromUDate()
// FromUniversal()
//
-// Creates an `absl::Time` from a variety of other representations.
-constexpr Time FromUnixNanos(int64_t ns);
-constexpr Time FromUnixMicros(int64_t us);
-constexpr Time FromUnixMillis(int64_t ms);
-constexpr Time FromUnixSeconds(int64_t s);
-constexpr Time FromTimeT(time_t t);
-Time FromUDate(double udate);
-Time FromUniversal(int64_t universal);
+// Creates an `absl::Time` from a variety of other representations. See
+// https://unicode-org.github.io/icu/userguide/datetime/universaltimescale.html
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Time FromUnixNanos(int64_t ns);
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Time FromUnixMicros(int64_t us);
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Time FromUnixMillis(int64_t ms);
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Time FromUnixSeconds(int64_t s);
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Time FromTimeT(time_t t);
+ABSL_ATTRIBUTE_CONST_FUNCTION Time FromUDate(double udate);
+ABSL_ATTRIBUTE_CONST_FUNCTION Time FromUniversal(int64_t universal);
// ToUnixNanos()
// ToUnixMicros()
@@ -793,17 +939,19 @@
// ToUDate()
// ToUniversal()
//
-// Converts an `absl::Time` to a variety of other representations. Note that
-// these operations round down toward negative infinity where necessary to
-// adjust to the resolution of the result type. Beware of possible time_t
-// over/underflow in ToTime{T,val,spec}() on 32-bit platforms.
-int64_t ToUnixNanos(Time t);
-int64_t ToUnixMicros(Time t);
-int64_t ToUnixMillis(Time t);
-int64_t ToUnixSeconds(Time t);
-time_t ToTimeT(Time t);
-double ToUDate(Time t);
-int64_t ToUniversal(Time t);
+// Converts an `absl::Time` to a variety of other representations. See
+// https://unicode-org.github.io/icu/userguide/datetime/universaltimescale.html
+//
+// Note that these operations round down toward negative infinity where
+// necessary to adjust to the resolution of the result type. Beware of
+// possible time_t over/underflow in ToTime{T,val,spec}() on 32-bit platforms.
+ABSL_ATTRIBUTE_CONST_FUNCTION int64_t ToUnixNanos(Time t);
+ABSL_ATTRIBUTE_CONST_FUNCTION int64_t ToUnixMicros(Time t);
+ABSL_ATTRIBUTE_CONST_FUNCTION int64_t ToUnixMillis(Time t);
+ABSL_ATTRIBUTE_CONST_FUNCTION int64_t ToUnixSeconds(Time t);
+ABSL_ATTRIBUTE_CONST_FUNCTION time_t ToTimeT(Time t);
+ABSL_ATTRIBUTE_CONST_FUNCTION double ToUDate(Time t);
+ABSL_ATTRIBUTE_CONST_FUNCTION int64_t ToUniversal(Time t);
// DurationFromTimespec()
// DurationFromTimeval()
@@ -819,14 +967,14 @@
// and gettimeofday(2)), so conversion functions are provided for both cases.
// The "to timespec/val" direction is easily handled via overloading, but
// for "from timespec/val" the desired type is part of the function name.
-Duration DurationFromTimespec(timespec ts);
-Duration DurationFromTimeval(timeval tv);
-timespec ToTimespec(Duration d);
-timeval ToTimeval(Duration d);
-Time TimeFromTimespec(timespec ts);
-Time TimeFromTimeval(timeval tv);
-timespec ToTimespec(Time t);
-timeval ToTimeval(Time t);
+ABSL_ATTRIBUTE_CONST_FUNCTION Duration DurationFromTimespec(timespec ts);
+ABSL_ATTRIBUTE_CONST_FUNCTION Duration DurationFromTimeval(timeval tv);
+ABSL_ATTRIBUTE_CONST_FUNCTION timespec ToTimespec(Duration d);
+ABSL_ATTRIBUTE_CONST_FUNCTION timeval ToTimeval(Duration d);
+ABSL_ATTRIBUTE_CONST_FUNCTION Time TimeFromTimespec(timespec ts);
+ABSL_ATTRIBUTE_CONST_FUNCTION Time TimeFromTimeval(timeval tv);
+ABSL_ATTRIBUTE_CONST_FUNCTION timespec ToTimespec(Time t);
+ABSL_ATTRIBUTE_CONST_FUNCTION timeval ToTimeval(Time t);
// FromChrono()
//
@@ -837,7 +985,8 @@
// auto tp = std::chrono::system_clock::from_time_t(123);
// absl::Time t = absl::FromChrono(tp);
// // t == absl::FromTimeT(123)
-Time FromChrono(const std::chrono::system_clock::time_point& tp);
+ABSL_ATTRIBUTE_PURE_FUNCTION Time
+FromChrono(const std::chrono::system_clock::time_point& tp);
// ToChronoTime()
//
@@ -850,7 +999,8 @@
// absl::Time t = absl::FromTimeT(123);
// auto tp = absl::ToChronoTime(t);
// // tp == std::chrono::system_clock::from_time_t(123);
-std::chrono::system_clock::time_point ToChronoTime(Time);
+ABSL_ATTRIBUTE_CONST_FUNCTION std::chrono::system_clock::time_point
+ ToChronoTime(Time);
// AbslParseFlag()
//
@@ -1122,22 +1272,25 @@
// absl::Time t = ...;
// absl::TimeZone tz = ...;
// const auto cd = absl::ToCivilDay(t, tz);
-inline CivilSecond ToCivilSecond(Time t, TimeZone tz) {
+ABSL_ATTRIBUTE_PURE_FUNCTION inline CivilSecond ToCivilSecond(Time t,
+ TimeZone tz) {
return tz.At(t).cs; // already a CivilSecond
}
-inline CivilMinute ToCivilMinute(Time t, TimeZone tz) {
+ABSL_ATTRIBUTE_PURE_FUNCTION inline CivilMinute ToCivilMinute(Time t,
+ TimeZone tz) {
return CivilMinute(tz.At(t).cs);
}
-inline CivilHour ToCivilHour(Time t, TimeZone tz) {
+ABSL_ATTRIBUTE_PURE_FUNCTION inline CivilHour ToCivilHour(Time t, TimeZone tz) {
return CivilHour(tz.At(t).cs);
}
-inline CivilDay ToCivilDay(Time t, TimeZone tz) {
+ABSL_ATTRIBUTE_PURE_FUNCTION inline CivilDay ToCivilDay(Time t, TimeZone tz) {
return CivilDay(tz.At(t).cs);
}
-inline CivilMonth ToCivilMonth(Time t, TimeZone tz) {
+ABSL_ATTRIBUTE_PURE_FUNCTION inline CivilMonth ToCivilMonth(Time t,
+ TimeZone tz) {
return CivilMonth(tz.At(t).cs);
}
-inline CivilYear ToCivilYear(Time t, TimeZone tz) {
+ABSL_ATTRIBUTE_PURE_FUNCTION inline CivilYear ToCivilYear(Time t, TimeZone tz) {
return CivilYear(tz.At(t).cs);
}
@@ -1153,7 +1306,8 @@
// being when two non-existent civil times map to the same transition time.
//
// Note: Accepts civil times of any alignment.
-inline Time FromCivil(CivilSecond ct, TimeZone tz) {
+ABSL_ATTRIBUTE_PURE_FUNCTION inline Time FromCivil(CivilSecond ct,
+ TimeZone tz) {
const auto ti = tz.At(ct);
if (ti.kind == TimeZone::TimeInfo::SKIPPED) return ti.trans;
return ti.pre;
@@ -1167,8 +1321,7 @@
// `absl::ConvertDateTime()`. Legacy version of `absl::TimeZone::TimeInfo`.
//
// Deprecated. Use `absl::TimeZone::TimeInfo`.
-struct
- TimeConversion {
+struct ABSL_DEPRECATED("Use `absl::TimeZone::TimeInfo`.") TimeConversion {
Time pre; // time calculated using the pre-transition offset
Time trans; // when the civil-time discontinuity occurred
Time post; // time calculated using the post-transition offset
@@ -1202,8 +1355,11 @@
// // absl::ToCivilDay(tc.pre, tz).day() == 1
//
// Deprecated. Use `absl::TimeZone::At(CivilSecond)`.
+ABSL_INTERNAL_DISABLE_DEPRECATED_DECLARATION_WARNING
+ABSL_DEPRECATED("Use `absl::TimeZone::At(CivilSecond)`.")
TimeConversion ConvertDateTime(int64_t year, int mon, int day, int hour,
int min, int sec, TimeZone tz);
+ABSL_INTERNAL_RESTORE_DEPRECATED_DECLARATION_WARNING
// FromDateTime()
//
@@ -1220,9 +1376,12 @@
// Deprecated. Use `absl::FromCivil(CivilSecond, TimeZone)`. Note that the
// behavior of `FromCivil()` differs from `FromDateTime()` for skipped civil
// times. If you care about that see `absl::TimeZone::At(absl::CivilSecond)`.
-inline Time FromDateTime(int64_t year, int mon, int day, int hour,
- int min, int sec, TimeZone tz) {
+ABSL_DEPRECATED("Use `absl::FromCivil(CivilSecond, TimeZone)`.")
+inline Time FromDateTime(int64_t year, int mon, int day, int hour, int min,
+ int sec, TimeZone tz) {
+ ABSL_INTERNAL_DISABLE_DEPRECATED_DECLARATION_WARNING
return ConvertDateTime(year, mon, day, hour, min, sec, tz).pre;
+ ABSL_INTERNAL_RESTORE_DEPRECATED_DECLARATION_WARNING
}
// FromTM()
@@ -1238,13 +1397,13 @@
// instant, so `tm_isdst != 0` returns the DST instant, and `tm_isdst == 0`
// returns the non-DST instant, that would have matched if the transition never
// happened.
-Time FromTM(const struct tm& tm, TimeZone tz);
+ABSL_ATTRIBUTE_PURE_FUNCTION Time FromTM(const struct tm& tm, TimeZone tz);
// ToTM()
//
// Converts the given `absl::Time` to a struct tm using the given time zone.
// See ctime(3) for a description of the values of the tm fields.
-struct tm ToTM(Time t, TimeZone tz);
+ABSL_ATTRIBUTE_PURE_FUNCTION struct tm ToTM(Time t, TimeZone tz);
// RFC3339_full
// RFC3339_sec
@@ -1303,19 +1462,26 @@
// `absl::InfinitePast()`, the returned string will be exactly "infinite-past".
// In both cases the given format string and `absl::TimeZone` are ignored.
//
-std::string FormatTime(absl::string_view format, Time t, TimeZone tz);
+ABSL_ATTRIBUTE_PURE_FUNCTION std::string FormatTime(absl::string_view format,
+ Time t, TimeZone tz);
// Convenience functions that format the given time using the RFC3339_full
// format. The first overload uses the provided TimeZone, while the second
// uses LocalTimeZone().
-std::string FormatTime(Time t, TimeZone tz);
-std::string FormatTime(Time t);
+ABSL_ATTRIBUTE_PURE_FUNCTION std::string FormatTime(Time t, TimeZone tz);
+ABSL_ATTRIBUTE_PURE_FUNCTION std::string FormatTime(Time t);
// Output stream operator.
inline std::ostream& operator<<(std::ostream& os, Time t) {
return os << FormatTime(t);
}
+// Support for StrFormat(), StrCat() etc.
+template <typename Sink>
+void AbslStringify(Sink& sink, Time t) {
+ sink.Append(FormatTime(t));
+}
+
// ParseTime()
//
// Parses an input string according to the provided format string and
@@ -1387,18 +1553,20 @@
// Creates a Duration with a given representation.
// REQUIRES: hi,lo is a valid representation of a Duration as specified
// in time/duration.cc.
-constexpr Duration MakeDuration(int64_t hi, uint32_t lo = 0) {
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Duration MakeDuration(int64_t hi,
+ uint32_t lo = 0) {
return Duration(hi, lo);
}
-constexpr Duration MakeDuration(int64_t hi, int64_t lo) {
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Duration MakeDuration(int64_t hi,
+ int64_t lo) {
return MakeDuration(hi, static_cast<uint32_t>(lo));
}
// Make a Duration value from a floating-point number, as long as that number
// is in the range [ 0 .. numeric_limits<int64_t>::max ), that is, as long as
// it's positive and can be converted to int64_t without risk of UB.
-inline Duration MakePosDoubleDuration(double n) {
+ABSL_ATTRIBUTE_CONST_FUNCTION inline Duration MakePosDoubleDuration(double n) {
const int64_t int_secs = static_cast<int64_t>(n);
const uint32_t ticks = static_cast<uint32_t>(
std::round((n - static_cast<double>(int_secs)) * kTicksPerSecond));
@@ -1411,28 +1579,37 @@
// pair. sec may be positive or negative. ticks must be in the range
// -kTicksPerSecond < *ticks < kTicksPerSecond. If ticks is negative it
// will be normalized to a positive value in the resulting Duration.
-constexpr Duration MakeNormalizedDuration(int64_t sec, int64_t ticks) {
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Duration MakeNormalizedDuration(
+ int64_t sec, int64_t ticks) {
return (ticks < 0) ? MakeDuration(sec - 1, ticks + kTicksPerSecond)
: MakeDuration(sec, ticks);
}
// Provide access to the Duration representation.
-constexpr int64_t GetRepHi(Duration d) { return d.rep_hi_; }
-constexpr uint32_t GetRepLo(Duration d) { return d.rep_lo_; }
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr int64_t GetRepHi(Duration d) {
+ return d.rep_hi_.Get();
+}
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr uint32_t GetRepLo(Duration d) {
+ return d.rep_lo_;
+}
// Returns true iff d is positive or negative infinity.
-constexpr bool IsInfiniteDuration(Duration d) { return GetRepLo(d) == ~0U; }
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr bool IsInfiniteDuration(Duration d) {
+ return GetRepLo(d) == ~uint32_t{0};
+}
// Returns an infinite Duration with the opposite sign.
// REQUIRES: IsInfiniteDuration(d)
-constexpr Duration OppositeInfinity(Duration d) {
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Duration OppositeInfinity(Duration d) {
return GetRepHi(d) < 0
- ? MakeDuration((std::numeric_limits<int64_t>::max)(), ~0U)
- : MakeDuration((std::numeric_limits<int64_t>::min)(), ~0U);
+ ? MakeDuration((std::numeric_limits<int64_t>::max)(), ~uint32_t{0})
+ : MakeDuration((std::numeric_limits<int64_t>::min)(),
+ ~uint32_t{0});
}
// Returns (-n)-1 (equivalently -(n+1)) without avoidable overflow.
-constexpr int64_t NegateAndSubtractOne(int64_t n) {
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr int64_t NegateAndSubtractOne(
+ int64_t n) {
// Note: Good compilers will optimize this expression to ~n when using
// a two's-complement representation (which is required for int64_t).
return (n < 0) ? -(n + 1) : (-n) - 1;
@@ -1442,23 +1619,30 @@
// functions depend on the above mentioned choice of the Unix epoch for the
// Time representation (and both need to be Time friends). Without this
// knowledge, we would need to add-in/subtract-out UnixEpoch() respectively.
-constexpr Time FromUnixDuration(Duration d) { return Time(d); }
-constexpr Duration ToUnixDuration(Time t) { return t.rep_; }
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Time FromUnixDuration(Duration d) {
+ return Time(d);
+}
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Duration ToUnixDuration(Time t) {
+ return t.rep_;
+}
template <std::intmax_t N>
-constexpr Duration FromInt64(int64_t v, std::ratio<1, N>) {
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Duration FromInt64(int64_t v,
+ std::ratio<1, N>) {
static_assert(0 < N && N <= 1000 * 1000 * 1000, "Unsupported ratio");
// Subsecond ratios cannot overflow.
return MakeNormalizedDuration(
v / N, v % N * kTicksPerNanosecond * 1000 * 1000 * 1000 / N);
}
-constexpr Duration FromInt64(int64_t v, std::ratio<60>) {
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Duration FromInt64(int64_t v,
+ std::ratio<60>) {
return (v <= (std::numeric_limits<int64_t>::max)() / 60 &&
v >= (std::numeric_limits<int64_t>::min)() / 60)
? MakeDuration(v * 60)
: v > 0 ? InfiniteDuration() : -InfiniteDuration();
}
-constexpr Duration FromInt64(int64_t v, std::ratio<3600>) {
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Duration FromInt64(int64_t v,
+ std::ratio<3600>) {
return (v <= (std::numeric_limits<int64_t>::max)() / 3600 &&
v >= (std::numeric_limits<int64_t>::min)() / 3600)
? MakeDuration(v * 3600)
@@ -1478,40 +1662,44 @@
// Converts a std::chrono::duration to an absl::Duration.
template <typename Rep, typename Period>
-constexpr Duration FromChrono(const std::chrono::duration<Rep, Period>& d) {
+ABSL_ATTRIBUTE_PURE_FUNCTION constexpr Duration FromChrono(
+ const std::chrono::duration<Rep, Period>& d) {
static_assert(IsValidRep64<Rep>(0), "duration::rep is invalid");
return FromInt64(int64_t{d.count()}, Period{});
}
template <typename Ratio>
-int64_t ToInt64(Duration d, Ratio) {
+ABSL_ATTRIBUTE_CONST_FUNCTION int64_t ToInt64(Duration d, Ratio) {
// Note: This may be used on MSVC, which may have a system_clock period of
// std::ratio<1, 10 * 1000 * 1000>
return ToInt64Seconds(d * Ratio::den / Ratio::num);
}
// Fastpath implementations for the 6 common duration units.
-inline int64_t ToInt64(Duration d, std::nano) {
+ABSL_ATTRIBUTE_CONST_FUNCTION inline int64_t ToInt64(Duration d, std::nano) {
return ToInt64Nanoseconds(d);
}
-inline int64_t ToInt64(Duration d, std::micro) {
+ABSL_ATTRIBUTE_CONST_FUNCTION inline int64_t ToInt64(Duration d, std::micro) {
return ToInt64Microseconds(d);
}
-inline int64_t ToInt64(Duration d, std::milli) {
+ABSL_ATTRIBUTE_CONST_FUNCTION inline int64_t ToInt64(Duration d, std::milli) {
return ToInt64Milliseconds(d);
}
-inline int64_t ToInt64(Duration d, std::ratio<1>) {
+ABSL_ATTRIBUTE_CONST_FUNCTION inline int64_t ToInt64(Duration d,
+ std::ratio<1>) {
return ToInt64Seconds(d);
}
-inline int64_t ToInt64(Duration d, std::ratio<60>) {
+ABSL_ATTRIBUTE_CONST_FUNCTION inline int64_t ToInt64(Duration d,
+ std::ratio<60>) {
return ToInt64Minutes(d);
}
-inline int64_t ToInt64(Duration d, std::ratio<3600>) {
+ABSL_ATTRIBUTE_CONST_FUNCTION inline int64_t ToInt64(Duration d,
+ std::ratio<3600>) {
return ToInt64Hours(d);
}
// Converts an absl::Duration to a chrono duration of type T.
template <typename T>
-T ToChronoDuration(Duration d) {
+ABSL_ATTRIBUTE_CONST_FUNCTION T ToChronoDuration(Duration d) {
using Rep = typename T::rep;
using Period = typename T::period;
static_assert(IsValidRep64<Rep>(0), "duration::rep is invalid");
@@ -1525,7 +1713,8 @@
} // namespace time_internal
-constexpr bool operator<(Duration lhs, Duration rhs) {
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr bool operator<(Duration lhs,
+ Duration rhs) {
return time_internal::GetRepHi(lhs) != time_internal::GetRepHi(rhs)
? time_internal::GetRepHi(lhs) < time_internal::GetRepHi(rhs)
: time_internal::GetRepHi(lhs) == (std::numeric_limits<int64_t>::min)()
@@ -1534,12 +1723,13 @@
: time_internal::GetRepLo(lhs) < time_internal::GetRepLo(rhs);
}
-constexpr bool operator==(Duration lhs, Duration rhs) {
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr bool operator==(Duration lhs,
+ Duration rhs) {
return time_internal::GetRepHi(lhs) == time_internal::GetRepHi(rhs) &&
time_internal::GetRepLo(lhs) == time_internal::GetRepLo(rhs);
}
-constexpr Duration operator-(Duration d) {
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Duration operator-(Duration d) {
// This is a little interesting because of the special cases.
//
// If rep_lo_ is zero, we have it easy; it's safe to negate rep_hi_, we're
@@ -1565,47 +1755,53 @@
time_internal::GetRepLo(d));
}
-constexpr Duration InfiniteDuration() {
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Duration InfiniteDuration() {
return time_internal::MakeDuration((std::numeric_limits<int64_t>::max)(),
- ~0U);
+ ~uint32_t{0});
}
-constexpr Duration FromChrono(const std::chrono::nanoseconds& d) {
+ABSL_ATTRIBUTE_PURE_FUNCTION constexpr Duration FromChrono(
+ const std::chrono::nanoseconds& d) {
return time_internal::FromChrono(d);
}
-constexpr Duration FromChrono(const std::chrono::microseconds& d) {
+ABSL_ATTRIBUTE_PURE_FUNCTION constexpr Duration FromChrono(
+ const std::chrono::microseconds& d) {
return time_internal::FromChrono(d);
}
-constexpr Duration FromChrono(const std::chrono::milliseconds& d) {
+ABSL_ATTRIBUTE_PURE_FUNCTION constexpr Duration FromChrono(
+ const std::chrono::milliseconds& d) {
return time_internal::FromChrono(d);
}
-constexpr Duration FromChrono(const std::chrono::seconds& d) {
+ABSL_ATTRIBUTE_PURE_FUNCTION constexpr Duration FromChrono(
+ const std::chrono::seconds& d) {
return time_internal::FromChrono(d);
}
-constexpr Duration FromChrono(const std::chrono::minutes& d) {
+ABSL_ATTRIBUTE_PURE_FUNCTION constexpr Duration FromChrono(
+ const std::chrono::minutes& d) {
return time_internal::FromChrono(d);
}
-constexpr Duration FromChrono(const std::chrono::hours& d) {
+ABSL_ATTRIBUTE_PURE_FUNCTION constexpr Duration FromChrono(
+ const std::chrono::hours& d) {
return time_internal::FromChrono(d);
}
-constexpr Time FromUnixNanos(int64_t ns) {
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Time FromUnixNanos(int64_t ns) {
return time_internal::FromUnixDuration(Nanoseconds(ns));
}
-constexpr Time FromUnixMicros(int64_t us) {
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Time FromUnixMicros(int64_t us) {
return time_internal::FromUnixDuration(Microseconds(us));
}
-constexpr Time FromUnixMillis(int64_t ms) {
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Time FromUnixMillis(int64_t ms) {
return time_internal::FromUnixDuration(Milliseconds(ms));
}
-constexpr Time FromUnixSeconds(int64_t s) {
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Time FromUnixSeconds(int64_t s) {
return time_internal::FromUnixDuration(Seconds(s));
}
-constexpr Time FromTimeT(time_t t) {
+ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Time FromTimeT(time_t t) {
return time_internal::FromUnixDuration(Seconds(t));
}
diff --git a/absl/time/time_benchmark.cc b/absl/time/time_benchmark.cc
index 99e6279..93a7c41 100644
--- a/absl/time/time_benchmark.cc
+++ b/absl/time/time_benchmark.cc
@@ -185,9 +185,11 @@
int i = 0;
while (state.KeepRunning()) {
if ((i & 1) == 0) {
- absl::FromCivil(absl::CivilSecond(2014, 12, 18, 20, 16, 18), tz);
+ benchmark::DoNotOptimize(
+ absl::FromCivil(absl::CivilSecond(2014, 12, 18, 20, 16, 18), tz));
} else {
- absl::FromCivil(absl::CivilSecond(2013, 11, 15, 18, 30, 27), tz);
+ benchmark::DoNotOptimize(
+ absl::FromCivil(absl::CivilSecond(2013, 11, 15, 18, 30, 27), tz));
}
++i;
}
@@ -224,7 +226,8 @@
void BM_Time_FromCivilUTC_Absl(benchmark::State& state) {
const absl::TimeZone tz = absl::UTCTimeZone();
while (state.KeepRunning()) {
- absl::FromCivil(absl::CivilSecond(2014, 12, 18, 20, 16, 18), tz);
+ benchmark::DoNotOptimize(
+ absl::FromCivil(absl::CivilSecond(2014, 12, 18, 20, 16, 18), tz));
}
}
BENCHMARK(BM_Time_FromCivilUTC_Absl);
@@ -235,9 +238,11 @@
int i = 0;
while (state.KeepRunning()) {
if ((i & 1) == 0) {
- absl::FromCivil(absl::CivilSecond(2014, 12, 0, 20, 16, 18), tz);
+ benchmark::DoNotOptimize(
+ absl::FromCivil(absl::CivilSecond(2014, 12, 0, 20, 16, 18), tz));
} else {
- absl::FromCivil(absl::CivilSecond(2013, 11, 0, 18, 30, 27), tz);
+ benchmark::DoNotOptimize(
+ absl::FromCivil(absl::CivilSecond(2013, 11, 0, 18, 30, 27), tz));
}
++i;
}
diff --git a/absl/time/time_test.cc b/absl/time/time_test.cc
index cde9423..bcf4f2a 100644
--- a/absl/time/time_test.cc
+++ b/absl/time/time_test.cc
@@ -28,6 +28,7 @@
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/numeric/int128.h"
+#include "absl/strings/str_format.h"
#include "absl/time/clock.h"
#include "absl/time/internal/test_util.h"
@@ -1277,4 +1278,11 @@
// We have a transition but we don't know which one.
}
+TEST(Time, AbslStringify) {
+ // FormatTime is already well tested, so just use one test case here to
+ // verify that StrFormat("%v", t) works as expected.
+ absl::Time t = absl::Now();
+ EXPECT_EQ(absl::StrFormat("%v", t), absl::FormatTime(t));
+}
+
} // namespace
diff --git a/absl/types/BUILD.bazel b/absl/types/BUILD.bazel
index 83be936..ce8f605 100644
--- a/absl/types/BUILD.bazel
+++ b/absl/types/BUILD.bazel
@@ -13,7 +13,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test")
load(
"//absl:copts/configure_copts.bzl",
"ABSL_DEFAULT_COPTS",
@@ -21,7 +20,14 @@
"ABSL_TEST_COPTS",
)
-package(default_visibility = ["//visibility:public"])
+package(
+ default_visibility = ["//visibility:public"],
+ features = [
+ "header_modules",
+ "layering_check",
+ "parse_headers",
+ ],
+)
licenses(["notice"])
@@ -78,8 +84,9 @@
":any",
"//absl/base:config",
"//absl/base:exception_testing",
- "//absl/base:raw_logging_internal",
"//absl/container:test_instance_tracker",
+ "//absl/log",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -93,6 +100,7 @@
":any",
"//absl/base:config",
"//absl/base:exception_safety_testing",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -110,6 +118,7 @@
deps = [
"//absl/algorithm",
"//absl/base:core_headers",
+ "//absl/base:nullability",
"//absl/base:throw_delegate",
"//absl/meta:type_traits",
],
@@ -130,6 +139,7 @@
"//absl/container:inlined_vector",
"//absl/hash:hash_testing",
"//absl/strings",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -145,6 +155,7 @@
"//absl/base:base_internal",
"//absl/base:config",
"//absl/base:core_headers",
+ "//absl/base:nullability",
"//absl/memory",
"//absl/meta:type_traits",
"//absl/utility",
@@ -186,9 +197,10 @@
deps = [
":optional",
"//absl/base:config",
- "//absl/base:raw_logging_internal",
+ "//absl/log",
"//absl/meta:type_traits",
"//absl/strings",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -204,44 +216,7 @@
":optional",
"//absl/base:config",
"//absl/base:exception_safety_testing",
- "@com_google_googletest//:gtest_main",
- ],
-)
-
-cc_library(
- name = "conformance_testing",
- testonly = 1,
- hdrs = [
- "internal/conformance_aliases.h",
- "internal/conformance_archetype.h",
- "internal/conformance_profile.h",
- "internal/conformance_testing.h",
- "internal/conformance_testing_helpers.h",
- "internal/parentheses.h",
- "internal/transform_args.h",
- ],
- copts = ABSL_TEST_COPTS,
- linkopts = ABSL_DEFAULT_LINKOPTS,
- deps = [
- "//absl/algorithm:container",
- "//absl/meta:type_traits",
- "//absl/strings",
- "//absl/utility",
"@com_google_googletest//:gtest",
- ],
-)
-
-cc_test(
- name = "conformance_testing_test",
- size = "small",
- srcs = [
- "internal/conformance_testing_test.cc",
- ],
- copts = ABSL_TEST_COPTS,
- linkopts = ABSL_DEFAULT_LINKOPTS,
- deps = [
- ":conformance_testing",
- "//absl/meta:type_traits",
"@com_google_googletest//:gtest_main",
],
)
@@ -275,6 +250,7 @@
"//absl/memory",
"//absl/meta:type_traits",
"//absl/strings",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -291,6 +267,7 @@
":variant",
"//absl/utility",
"@com_github_google_benchmark//:benchmark_main",
+ "@com_google_googletest//:gtest",
],
)
@@ -307,6 +284,7 @@
"//absl/base:config",
"//absl/base:exception_safety_testing",
"//absl/memory",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
@@ -315,7 +293,9 @@
name = "compare",
hdrs = ["compare.h"],
copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
+ "//absl/base:config",
"//absl/base:core_headers",
"//absl/meta:type_traits",
],
@@ -331,6 +311,7 @@
deps = [
":compare",
"//absl/base",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
diff --git a/absl/types/CMakeLists.txt b/absl/types/CMakeLists.txt
index d7e8614..92b4ae4 100644
--- a/absl/types/CMakeLists.txt
+++ b/absl/types/CMakeLists.txt
@@ -43,6 +43,7 @@
PUBLIC
)
+# Internal-only target, do not depend on directly.
absl_cc_library(
NAME
bad_any_cast_impl
@@ -67,7 +68,7 @@
absl::any
absl::config
absl::exception_testing
- absl::raw_logging_internal
+ absl::log
absl::test_instance_tracker
GTest::gmock_main
)
@@ -114,6 +115,7 @@
DEPS
absl::algorithm
absl::core_headers
+ absl::nullability
absl::throw_delegate
absl::type_traits
PUBLIC
@@ -174,6 +176,7 @@
absl::config
absl::core_headers
absl::memory
+ absl::nullability
absl::type_traits
absl::utility
PUBLIC
@@ -219,7 +222,7 @@
DEPS
absl::optional
absl::config
- absl::raw_logging_internal
+ absl::log
absl::strings
absl::type_traits
GTest::gmock_main
@@ -241,58 +244,6 @@
absl_cc_library(
NAME
- conformance_testing
- HDRS
- "internal/conformance_aliases.h"
- "internal/conformance_archetype.h"
- "internal/conformance_profile.h"
- "internal/conformance_testing.h"
- "internal/conformance_testing_helpers.h"
- "internal/parentheses.h"
- "internal/transform_args.h"
- COPTS
- ${ABSL_DEFAULT_COPTS}
- DEPS
- absl::algorithm
- absl::debugging
- absl::type_traits
- absl::strings
- absl::utility
- GTest::gmock_main
- TESTONLY
-)
-
-absl_cc_test(
- NAME
- conformance_testing_test
- SRCS
- "internal/conformance_testing_test.cc"
- COPTS
- ${ABSL_TEST_COPTS}
- ${ABSL_EXCEPTIONS_FLAG}
- LINKOPTS
- ${ABSL_EXCEPTIONS_FLAG_LINKOPTS}
- DEPS
- absl::conformance_testing
- absl::type_traits
- GTest::gmock_main
-)
-
-absl_cc_test(
- NAME
- conformance_testing_test_no_exceptions
- SRCS
- "internal/conformance_testing_test.cc"
- COPTS
- ${ABSL_TEST_COPTS}
- DEPS
- absl::conformance_testing
- absl::type_traits
- GTest::gmock_main
-)
-
-absl_cc_library(
- NAME
variant
HDRS
"variant.h"
@@ -335,6 +286,7 @@
COPTS
${ABSL_DEFAULT_COPTS}
DEPS
+ absl::config
absl::core_headers
absl::type_traits
PUBLIC
diff --git a/absl/types/any.h b/absl/types/any.h
index fc5a074..61f071f 100644
--- a/absl/types/any.h
+++ b/absl/types/any.h
@@ -53,6 +53,7 @@
#ifndef ABSL_TYPES_ANY_H_
#define ABSL_TYPES_ANY_H_
+#include "absl/base/attributes.h"
#include "absl/base/config.h"
#include "absl/utility/utility.h"
@@ -81,18 +82,9 @@
#include <utility>
#include "absl/base/internal/fast_type_id.h"
-#include "absl/base/macros.h"
#include "absl/meta/type_traits.h"
#include "absl/types/bad_any_cast.h"
-// NOTE: This macro is an implementation detail that is undefined at the bottom
-// of the file. It is not intended for expansion directly from user code.
-#ifdef ABSL_ANY_DETAIL_HAS_RTTI
-#error ABSL_ANY_DETAIL_HAS_RTTI cannot be directly set
-#elif !defined(__GNUC__) || defined(__GXX_RTTI)
-#define ABSL_ANY_DETAIL_HAS_RTTI 1
-#endif // !defined(__GNUC__) || defined(__GXX_RTTI)
-
namespace absl {
ABSL_NAMESPACE_BEGIN
@@ -297,7 +289,7 @@
typename T, typename... Args, typename VT = absl::decay_t<T>,
absl::enable_if_t<std::is_copy_constructible<VT>::value &&
std::is_constructible<VT, Args...>::value>* = nullptr>
- VT& emplace(Args&&... args) {
+ VT& emplace(Args&&... args) ABSL_ATTRIBUTE_LIFETIME_BOUND {
reset(); // NOTE: reset() is required here even in the world of exceptions.
Obj<VT>* const object_ptr =
new Obj<VT>(in_place, std::forward<Args>(args)...);
@@ -321,7 +313,8 @@
absl::enable_if_t<std::is_copy_constructible<VT>::value &&
std::is_constructible<VT, std::initializer_list<U>&,
Args...>::value>* = nullptr>
- VT& emplace(std::initializer_list<U> ilist, Args&&... args) {
+ VT& emplace(std::initializer_list<U> ilist,
+ Args&&... args) ABSL_ATTRIBUTE_LIFETIME_BOUND {
reset(); // NOTE: reset() is required here even in the world of exceptions.
Obj<VT>* const object_ptr =
new Obj<VT>(in_place, ilist, std::forward<Args>(args)...);
@@ -348,7 +341,7 @@
// returns `false`.
bool has_value() const noexcept { return obj_ != nullptr; }
-#if ABSL_ANY_DETAIL_HAS_RTTI
+#ifdef ABSL_INTERNAL_HAS_RTTI
// Returns: typeid(T) if *this has a contained object of type T, otherwise
// typeid(void).
const std::type_info& type() const noexcept {
@@ -358,7 +351,7 @@
return typeid(void);
}
-#endif // ABSL_ANY_DETAIL_HAS_RTTI
+#endif // ABSL_INTERNAL_HAS_RTTI
private:
// Tagged type-erased abstraction for holding a cloneable object.
@@ -367,9 +360,9 @@
virtual ~ObjInterface() = default;
virtual std::unique_ptr<ObjInterface> Clone() const = 0;
virtual const void* ObjTypeId() const noexcept = 0;
-#if ABSL_ANY_DETAIL_HAS_RTTI
+#ifdef ABSL_INTERNAL_HAS_RTTI
virtual const std::type_info& Type() const noexcept = 0;
-#endif // ABSL_ANY_DETAIL_HAS_RTTI
+#endif // ABSL_INTERNAL_HAS_RTTI
};
// Hold a value of some queryable type, with an ability to Clone it.
@@ -386,9 +379,9 @@
const void* ObjTypeId() const noexcept final { return IdForType<T>(); }
-#if ABSL_ANY_DETAIL_HAS_RTTI
+#ifdef ABSL_INTERNAL_HAS_RTTI
const std::type_info& Type() const noexcept final { return typeid(T); }
-#endif // ABSL_ANY_DETAIL_HAS_RTTI
+#endif // ABSL_INTERNAL_HAS_RTTI
T value;
};
@@ -521,8 +514,6 @@
ABSL_NAMESPACE_END
} // namespace absl
-#undef ABSL_ANY_DETAIL_HAS_RTTI
-
#endif // ABSL_USES_STD_ANY
#endif // ABSL_TYPES_ANY_H_
diff --git a/absl/types/any_test.cc b/absl/types/any_test.cc
index 70e4ba2..666ea5b 100644
--- a/absl/types/any_test.cc
+++ b/absl/types/any_test.cc
@@ -25,8 +25,8 @@
#include "gtest/gtest.h"
#include "absl/base/config.h"
#include "absl/base/internal/exception_testing.h"
-#include "absl/base/internal/raw_logging.h"
#include "absl/container/internal/test_instance_tracker.h"
+#include "absl/log/log.h"
namespace {
using absl::test_internal::CopyableOnlyInstance;
@@ -704,7 +704,7 @@
#ifdef ABSL_HAVE_EXCEPTIONS
throw BadCopy();
#else
- ABSL_RAW_LOG(FATAL, "Bad copy");
+ LOG(FATAL) << "Bad copy";
#endif
}
};
@@ -754,26 +754,23 @@
// Test the guarantees regarding exceptions in emplace.
TEST(AnyTest, FailedEmplace) {
- {
- BadCopyable bad;
- absl::any target;
- ABSL_ANY_TEST_EXPECT_BAD_COPY(target.emplace<BadCopyable>(bad));
- }
+ BadCopyable bad;
+ absl::any target;
+ ABSL_ANY_TEST_EXPECT_BAD_COPY(target.emplace<BadCopyable>(bad));
+}
- {
- BadCopyable bad;
- absl::any target(absl::in_place_type<int>);
- ABSL_ANY_TEST_EXPECT_BAD_COPY(target.emplace<BadCopyable>(bad));
-#if defined(ABSL_USES_STD_ANY) && defined(__GLIBCXX__)
- // libstdc++ std::any::emplace() implementation (as of 7.2) has a bug: if an
- // exception is thrown, *this contains a value.
-#define ABSL_GLIBCXX_ANY_EMPLACE_EXCEPTION_BUG 1
+// GCC and Clang have a bug here.
+// Ine some cases, the exception seems to be thrown at the wrong time, and
+// target may contain a value.
+#ifdef __GNUC__
+TEST(AnyTest, DISABLED_FailedEmplaceInPlace) {
+#else
+TEST(AnyTest, FailedEmplaceInPlace) {
#endif
-#if defined(ABSL_HAVE_EXCEPTIONS) && \
- !defined(ABSL_GLIBCXX_ANY_EMPLACE_EXCEPTION_BUG)
- EXPECT_FALSE(target.has_value());
-#endif
- }
+ BadCopyable bad;
+ absl::any target(absl::in_place_type<int>);
+ ABSL_ANY_TEST_EXPECT_BAD_COPY(target.emplace<BadCopyable>(bad));
+ EXPECT_FALSE(target.has_value());
}
} // namespace
diff --git a/absl/types/bad_any_cast.cc b/absl/types/bad_any_cast.cc
index b0592cc..22558b4 100644
--- a/absl/types/bad_any_cast.cc
+++ b/absl/types/bad_any_cast.cc
@@ -43,4 +43,22 @@
ABSL_NAMESPACE_END
} // namespace absl
+#else
+
+// https://github.com/abseil/abseil-cpp/issues/1465
+// CMake builds on Apple platforms error when libraries are empty.
+// Our CMake configuration can avoid this error on header-only libraries,
+// but since this library is conditionally empty, including a single
+// variable is an easy workaround.
+#ifdef __APPLE__
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace types_internal {
+extern const char kAvoidEmptyBadAnyCastLibraryWarning;
+const char kAvoidEmptyBadAnyCastLibraryWarning = 0;
+} // namespace types_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+#endif // __APPLE__
+
#endif // ABSL_USES_STD_ANY
diff --git a/absl/types/bad_optional_access.cc b/absl/types/bad_optional_access.cc
index 26aca70..2552cc8 100644
--- a/absl/types/bad_optional_access.cc
+++ b/absl/types/bad_optional_access.cc
@@ -45,4 +45,22 @@
ABSL_NAMESPACE_END
} // namespace absl
+#else
+
+// https://github.com/abseil/abseil-cpp/issues/1465
+// CMake builds on Apple platforms error when libraries are empty.
+// Our CMake configuration can avoid this error on header-only libraries,
+// but since this library is conditionally empty, including a single
+// variable is an easy workaround.
+#ifdef __APPLE__
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace types_internal {
+extern const char kAvoidEmptyBadOptionalAccessLibraryWarning;
+const char kAvoidEmptyBadOptionalAccessLibraryWarning = 0;
+} // namespace types_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+#endif // __APPLE__
+
#endif // ABSL_USES_STD_OPTIONAL
diff --git a/absl/types/bad_optional_access.h b/absl/types/bad_optional_access.h
index a500286..049e72a 100644
--- a/absl/types/bad_optional_access.h
+++ b/absl/types/bad_optional_access.h
@@ -67,7 +67,7 @@
namespace optional_internal {
// throw delegator
-[[noreturn]] void throw_bad_optional_access();
+[[noreturn]] ABSL_DLL void throw_bad_optional_access();
} // namespace optional_internal
ABSL_NAMESPACE_END
diff --git a/absl/types/bad_variant_access.cc b/absl/types/bad_variant_access.cc
index 3dc88cc..a76aa80 100644
--- a/absl/types/bad_variant_access.cc
+++ b/absl/types/bad_variant_access.cc
@@ -61,4 +61,22 @@
ABSL_NAMESPACE_END
} // namespace absl
+#else
+
+// https://github.com/abseil/abseil-cpp/issues/1465
+// CMake builds on Apple platforms error when libraries are empty.
+// Our CMake configuration can avoid this error on header-only libraries,
+// but since this library is conditionally empty, including a single
+// variable is an easy workaround.
+#ifdef __APPLE__
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace types_internal {
+extern const char kAvoidEmptyBadVariantAccessLibraryWarning;
+const char kAvoidEmptyBadVariantAccessLibraryWarning = 0;
+} // namespace types_internal
+ABSL_NAMESPACE_END
+} // namespace absl
+#endif // __APPLE__
+
#endif // ABSL_USES_STD_VARIANT
diff --git a/absl/types/bad_variant_access.h b/absl/types/bad_variant_access.h
index 095969f..8ab215e 100644
--- a/absl/types/bad_variant_access.h
+++ b/absl/types/bad_variant_access.h
@@ -70,8 +70,8 @@
namespace variant_internal {
-[[noreturn]] void ThrowBadVariantAccess();
-[[noreturn]] void Rethrow();
+[[noreturn]] ABSL_DLL void ThrowBadVariantAccess();
+[[noreturn]] ABSL_DLL void Rethrow();
} // namespace variant_internal
ABSL_NAMESPACE_END
diff --git a/absl/types/compare.h b/absl/types/compare.h
index 19b076e..3cf4a91 100644
--- a/absl/types/compare.h
+++ b/absl/types/compare.h
@@ -16,57 +16,88 @@
// compare.h
// -----------------------------------------------------------------------------
//
-// This header file defines the `absl::weak_equality`, `absl::strong_equality`,
-// `absl::partial_ordering`, `absl::weak_ordering`, and `absl::strong_ordering`
-// types for storing the results of three way comparisons.
+// This header file defines the `absl::partial_ordering`, `absl::weak_ordering`,
+// and `absl::strong_ordering` types for storing the results of three way
+// comparisons.
//
// Example:
// absl::weak_ordering compare(const std::string& a, const std::string& b);
//
// These are C++11 compatible versions of the C++20 corresponding types
-// (`std::weak_equality`, etc.) and are designed to be drop-in replacements
+// (`std::partial_ordering`, etc.) and are designed to be drop-in replacements
// for code compliant with C++20.
#ifndef ABSL_TYPES_COMPARE_H_
#define ABSL_TYPES_COMPARE_H_
+#include "absl/base/config.h"
+
+#ifdef ABSL_USES_STD_ORDERING
+
+#include <compare> // IWYU pragma: export
+#include <type_traits>
+
+#include "absl/meta/type_traits.h"
+
+#else
+
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <type_traits>
#include "absl/base/attributes.h"
+#include "absl/base/macros.h"
#include "absl/meta/type_traits.h"
+#endif
+
namespace absl {
ABSL_NAMESPACE_BEGIN
+
+#ifdef ABSL_USES_STD_ORDERING
+
+using std::partial_ordering;
+using std::strong_ordering;
+using std::weak_ordering;
+
+#else
+
namespace compare_internal {
using value_type = int8_t;
-template <typename T>
-struct Fail {
- static_assert(sizeof(T) < 0, "Only literal `0` is allowed.");
-};
-
-// We need the NullPtrT template to avoid triggering the modernize-use-nullptr
-// ClangTidy warning in user code.
-template <typename NullPtrT = std::nullptr_t>
-struct OnlyLiteralZero {
- constexpr OnlyLiteralZero(NullPtrT) noexcept {} // NOLINT
+class OnlyLiteralZero {
+ public:
+#if ABSL_HAVE_ATTRIBUTE(enable_if)
+ // On clang, we can avoid triggering modernize-use-nullptr by only enabling
+ // this overload when the value is a compile time integer constant equal to 0.
+ //
+ // In c++20, this could be a static_assert in a consteval function.
+ constexpr OnlyLiteralZero(int n) // NOLINT
+ __attribute__((enable_if(n == 0, "Only literal `0` is allowed."))) {}
+#else // ABSL_HAVE_ATTRIBUTE(enable_if)
+ // Accept only literal zero since it can be implicitly converted to a pointer
+ // to member type. nullptr constants will be caught by the other constructor
+ // which accepts a nullptr_t.
+ //
+ // This constructor is not used for clang since it triggers
+ // modernize-use-nullptr.
+ constexpr OnlyLiteralZero(int OnlyLiteralZero::*) noexcept {} // NOLINT
+#endif
// Fails compilation when `nullptr` or integral type arguments other than
// `int` are passed. This constructor doesn't accept `int` because literal `0`
// has type `int`. Literal `0` arguments will be implicitly converted to
// `std::nullptr_t` and accepted by the above constructor, while other `int`
// arguments will fail to be converted and cause compilation failure.
- template <
- typename T,
- typename = typename std::enable_if<
- std::is_same<T, std::nullptr_t>::value ||
- (std::is_integral<T>::value && !std::is_same<T, int>::value)>::type,
- typename = typename Fail<T>::type>
- OnlyLiteralZero(T); // NOLINT
+ template <typename T, typename = typename std::enable_if<
+ std::is_same<T, std::nullptr_t>::value ||
+ (std::is_integral<T>::value &&
+ !std::is_same<T, int>::value)>::type>
+ OnlyLiteralZero(T) { // NOLINT
+ static_assert(sizeof(T) < 0, "Only literal `0` is allowed.");
+ }
};
enum class eq : value_type {
@@ -113,20 +144,6 @@
// in the header file (for performance) without using inline variables (which
// aren't available in C++11).
template <typename T>
-struct weak_equality_base {
- ABSL_COMPARE_INLINE_BASECLASS_DECL(equivalent);
- ABSL_COMPARE_INLINE_BASECLASS_DECL(nonequivalent);
-};
-
-template <typename T>
-struct strong_equality_base {
- ABSL_COMPARE_INLINE_BASECLASS_DECL(equal);
- ABSL_COMPARE_INLINE_BASECLASS_DECL(nonequal);
- ABSL_COMPARE_INLINE_BASECLASS_DECL(equivalent);
- ABSL_COMPARE_INLINE_BASECLASS_DECL(nonequivalent);
-};
-
-template <typename T>
struct partial_ordering_base {
ABSL_COMPARE_INLINE_BASECLASS_DECL(less);
ABSL_COMPARE_INLINE_BASECLASS_DECL(equivalent);
@@ -151,104 +168,6 @@
} // namespace compare_internal
-class weak_equality
- : public compare_internal::weak_equality_base<weak_equality> {
- explicit constexpr weak_equality(compare_internal::eq v) noexcept
- : value_(static_cast<compare_internal::value_type>(v)) {}
- friend struct compare_internal::weak_equality_base<weak_equality>;
-
- public:
- ABSL_COMPARE_INLINE_SUBCLASS_DECL(weak_equality, equivalent);
- ABSL_COMPARE_INLINE_SUBCLASS_DECL(weak_equality, nonequivalent);
-
- // Comparisons
- friend constexpr bool operator==(
- weak_equality v, compare_internal::OnlyLiteralZero<>) noexcept {
- return v.value_ == 0;
- }
- friend constexpr bool operator!=(
- weak_equality v, compare_internal::OnlyLiteralZero<>) noexcept {
- return v.value_ != 0;
- }
- friend constexpr bool operator==(compare_internal::OnlyLiteralZero<>,
- weak_equality v) noexcept {
- return 0 == v.value_;
- }
- friend constexpr bool operator!=(compare_internal::OnlyLiteralZero<>,
- weak_equality v) noexcept {
- return 0 != v.value_;
- }
- friend constexpr bool operator==(weak_equality v1,
- weak_equality v2) noexcept {
- return v1.value_ == v2.value_;
- }
- friend constexpr bool operator!=(weak_equality v1,
- weak_equality v2) noexcept {
- return v1.value_ != v2.value_;
- }
-
- private:
- compare_internal::value_type value_;
-};
-ABSL_COMPARE_INLINE_INIT(weak_equality, equivalent,
- compare_internal::eq::equivalent);
-ABSL_COMPARE_INLINE_INIT(weak_equality, nonequivalent,
- compare_internal::eq::nonequivalent);
-
-class strong_equality
- : public compare_internal::strong_equality_base<strong_equality> {
- explicit constexpr strong_equality(compare_internal::eq v) noexcept
- : value_(static_cast<compare_internal::value_type>(v)) {}
- friend struct compare_internal::strong_equality_base<strong_equality>;
-
- public:
- ABSL_COMPARE_INLINE_SUBCLASS_DECL(strong_equality, equal);
- ABSL_COMPARE_INLINE_SUBCLASS_DECL(strong_equality, nonequal);
- ABSL_COMPARE_INLINE_SUBCLASS_DECL(strong_equality, equivalent);
- ABSL_COMPARE_INLINE_SUBCLASS_DECL(strong_equality, nonequivalent);
-
- // Conversion
- constexpr operator weak_equality() const noexcept { // NOLINT
- return value_ == 0 ? weak_equality::equivalent
- : weak_equality::nonequivalent;
- }
- // Comparisons
- friend constexpr bool operator==(
- strong_equality v, compare_internal::OnlyLiteralZero<>) noexcept {
- return v.value_ == 0;
- }
- friend constexpr bool operator!=(
- strong_equality v, compare_internal::OnlyLiteralZero<>) noexcept {
- return v.value_ != 0;
- }
- friend constexpr bool operator==(compare_internal::OnlyLiteralZero<>,
- strong_equality v) noexcept {
- return 0 == v.value_;
- }
- friend constexpr bool operator!=(compare_internal::OnlyLiteralZero<>,
- strong_equality v) noexcept {
- return 0 != v.value_;
- }
- friend constexpr bool operator==(strong_equality v1,
- strong_equality v2) noexcept {
- return v1.value_ == v2.value_;
- }
- friend constexpr bool operator!=(strong_equality v1,
- strong_equality v2) noexcept {
- return v1.value_ != v2.value_;
- }
-
- private:
- compare_internal::value_type value_;
-};
-ABSL_COMPARE_INLINE_INIT(strong_equality, equal, compare_internal::eq::equal);
-ABSL_COMPARE_INLINE_INIT(strong_equality, nonequal,
- compare_internal::eq::nonequal);
-ABSL_COMPARE_INLINE_INIT(strong_equality, equivalent,
- compare_internal::eq::equivalent);
-ABSL_COMPARE_INLINE_INIT(strong_equality, nonequivalent,
- compare_internal::eq::nonequivalent);
-
class partial_ordering
: public compare_internal::partial_ordering_base<partial_ordering> {
explicit constexpr partial_ordering(compare_internal::eq v) noexcept
@@ -270,57 +189,52 @@
ABSL_COMPARE_INLINE_SUBCLASS_DECL(partial_ordering, greater);
ABSL_COMPARE_INLINE_SUBCLASS_DECL(partial_ordering, unordered);
- // Conversion
- constexpr operator weak_equality() const noexcept { // NOLINT
- return value_ == 0 ? weak_equality::equivalent
- : weak_equality::nonequivalent;
- }
// Comparisons
friend constexpr bool operator==(
- partial_ordering v, compare_internal::OnlyLiteralZero<>) noexcept {
+ partial_ordering v, compare_internal::OnlyLiteralZero) noexcept {
return v.is_ordered() && v.value_ == 0;
}
friend constexpr bool operator!=(
- partial_ordering v, compare_internal::OnlyLiteralZero<>) noexcept {
+ partial_ordering v, compare_internal::OnlyLiteralZero) noexcept {
return !v.is_ordered() || v.value_ != 0;
}
friend constexpr bool operator<(
- partial_ordering v, compare_internal::OnlyLiteralZero<>) noexcept {
+ partial_ordering v, compare_internal::OnlyLiteralZero) noexcept {
return v.is_ordered() && v.value_ < 0;
}
friend constexpr bool operator<=(
- partial_ordering v, compare_internal::OnlyLiteralZero<>) noexcept {
+ partial_ordering v, compare_internal::OnlyLiteralZero) noexcept {
return v.is_ordered() && v.value_ <= 0;
}
friend constexpr bool operator>(
- partial_ordering v, compare_internal::OnlyLiteralZero<>) noexcept {
+ partial_ordering v, compare_internal::OnlyLiteralZero) noexcept {
return v.is_ordered() && v.value_ > 0;
}
friend constexpr bool operator>=(
- partial_ordering v, compare_internal::OnlyLiteralZero<>) noexcept {
+ partial_ordering v, compare_internal::OnlyLiteralZero) noexcept {
return v.is_ordered() && v.value_ >= 0;
}
- friend constexpr bool operator==(compare_internal::OnlyLiteralZero<>,
+ friend constexpr bool operator==(compare_internal::OnlyLiteralZero,
partial_ordering v) noexcept {
return v.is_ordered() && 0 == v.value_;
}
- friend constexpr bool operator!=(compare_internal::OnlyLiteralZero<>,
+ friend constexpr bool operator!=(compare_internal::OnlyLiteralZero,
partial_ordering v) noexcept {
return !v.is_ordered() || 0 != v.value_;
}
- friend constexpr bool operator<(compare_internal::OnlyLiteralZero<>,
+ friend constexpr bool operator<(compare_internal::OnlyLiteralZero,
partial_ordering v) noexcept {
return v.is_ordered() && 0 < v.value_;
}
- friend constexpr bool operator<=(compare_internal::OnlyLiteralZero<>,
+ friend constexpr bool operator<=(compare_internal::OnlyLiteralZero,
partial_ordering v) noexcept {
return v.is_ordered() && 0 <= v.value_;
}
- friend constexpr bool operator>(compare_internal::OnlyLiteralZero<>,
+ friend constexpr bool operator>(compare_internal::OnlyLiteralZero,
partial_ordering v) noexcept {
return v.is_ordered() && 0 > v.value_;
}
- friend constexpr bool operator>=(compare_internal::OnlyLiteralZero<>,
+ friend constexpr bool operator>=(compare_internal::OnlyLiteralZero,
partial_ordering v) noexcept {
return v.is_ordered() && 0 >= v.value_;
}
@@ -358,10 +272,6 @@
ABSL_COMPARE_INLINE_SUBCLASS_DECL(weak_ordering, greater);
// Conversions
- constexpr operator weak_equality() const noexcept { // NOLINT
- return value_ == 0 ? weak_equality::equivalent
- : weak_equality::nonequivalent;
- }
constexpr operator partial_ordering() const noexcept { // NOLINT
return value_ == 0 ? partial_ordering::equivalent
: (value_ < 0 ? partial_ordering::less
@@ -369,50 +279,50 @@
}
// Comparisons
friend constexpr bool operator==(
- weak_ordering v, compare_internal::OnlyLiteralZero<>) noexcept {
+ weak_ordering v, compare_internal::OnlyLiteralZero) noexcept {
return v.value_ == 0;
}
friend constexpr bool operator!=(
- weak_ordering v, compare_internal::OnlyLiteralZero<>) noexcept {
+ weak_ordering v, compare_internal::OnlyLiteralZero) noexcept {
return v.value_ != 0;
}
friend constexpr bool operator<(
- weak_ordering v, compare_internal::OnlyLiteralZero<>) noexcept {
+ weak_ordering v, compare_internal::OnlyLiteralZero) noexcept {
return v.value_ < 0;
}
friend constexpr bool operator<=(
- weak_ordering v, compare_internal::OnlyLiteralZero<>) noexcept {
+ weak_ordering v, compare_internal::OnlyLiteralZero) noexcept {
return v.value_ <= 0;
}
friend constexpr bool operator>(
- weak_ordering v, compare_internal::OnlyLiteralZero<>) noexcept {
+ weak_ordering v, compare_internal::OnlyLiteralZero) noexcept {
return v.value_ > 0;
}
friend constexpr bool operator>=(
- weak_ordering v, compare_internal::OnlyLiteralZero<>) noexcept {
+ weak_ordering v, compare_internal::OnlyLiteralZero) noexcept {
return v.value_ >= 0;
}
- friend constexpr bool operator==(compare_internal::OnlyLiteralZero<>,
+ friend constexpr bool operator==(compare_internal::OnlyLiteralZero,
weak_ordering v) noexcept {
return 0 == v.value_;
}
- friend constexpr bool operator!=(compare_internal::OnlyLiteralZero<>,
+ friend constexpr bool operator!=(compare_internal::OnlyLiteralZero,
weak_ordering v) noexcept {
return 0 != v.value_;
}
- friend constexpr bool operator<(compare_internal::OnlyLiteralZero<>,
+ friend constexpr bool operator<(compare_internal::OnlyLiteralZero,
weak_ordering v) noexcept {
return 0 < v.value_;
}
- friend constexpr bool operator<=(compare_internal::OnlyLiteralZero<>,
+ friend constexpr bool operator<=(compare_internal::OnlyLiteralZero,
weak_ordering v) noexcept {
return 0 <= v.value_;
}
- friend constexpr bool operator>(compare_internal::OnlyLiteralZero<>,
+ friend constexpr bool operator>(compare_internal::OnlyLiteralZero,
weak_ordering v) noexcept {
return 0 > v.value_;
}
- friend constexpr bool operator>=(compare_internal::OnlyLiteralZero<>,
+ friend constexpr bool operator>=(compare_internal::OnlyLiteralZero,
weak_ordering v) noexcept {
return 0 >= v.value_;
}
@@ -449,13 +359,6 @@
ABSL_COMPARE_INLINE_SUBCLASS_DECL(strong_ordering, greater);
// Conversions
- constexpr operator weak_equality() const noexcept { // NOLINT
- return value_ == 0 ? weak_equality::equivalent
- : weak_equality::nonequivalent;
- }
- constexpr operator strong_equality() const noexcept { // NOLINT
- return value_ == 0 ? strong_equality::equal : strong_equality::nonequal;
- }
constexpr operator partial_ordering() const noexcept { // NOLINT
return value_ == 0 ? partial_ordering::equivalent
: (value_ < 0 ? partial_ordering::less
@@ -468,50 +371,50 @@
}
// Comparisons
friend constexpr bool operator==(
- strong_ordering v, compare_internal::OnlyLiteralZero<>) noexcept {
+ strong_ordering v, compare_internal::OnlyLiteralZero) noexcept {
return v.value_ == 0;
}
friend constexpr bool operator!=(
- strong_ordering v, compare_internal::OnlyLiteralZero<>) noexcept {
+ strong_ordering v, compare_internal::OnlyLiteralZero) noexcept {
return v.value_ != 0;
}
friend constexpr bool operator<(
- strong_ordering v, compare_internal::OnlyLiteralZero<>) noexcept {
+ strong_ordering v, compare_internal::OnlyLiteralZero) noexcept {
return v.value_ < 0;
}
friend constexpr bool operator<=(
- strong_ordering v, compare_internal::OnlyLiteralZero<>) noexcept {
+ strong_ordering v, compare_internal::OnlyLiteralZero) noexcept {
return v.value_ <= 0;
}
friend constexpr bool operator>(
- strong_ordering v, compare_internal::OnlyLiteralZero<>) noexcept {
+ strong_ordering v, compare_internal::OnlyLiteralZero) noexcept {
return v.value_ > 0;
}
friend constexpr bool operator>=(
- strong_ordering v, compare_internal::OnlyLiteralZero<>) noexcept {
+ strong_ordering v, compare_internal::OnlyLiteralZero) noexcept {
return v.value_ >= 0;
}
- friend constexpr bool operator==(compare_internal::OnlyLiteralZero<>,
+ friend constexpr bool operator==(compare_internal::OnlyLiteralZero,
strong_ordering v) noexcept {
return 0 == v.value_;
}
- friend constexpr bool operator!=(compare_internal::OnlyLiteralZero<>,
+ friend constexpr bool operator!=(compare_internal::OnlyLiteralZero,
strong_ordering v) noexcept {
return 0 != v.value_;
}
- friend constexpr bool operator<(compare_internal::OnlyLiteralZero<>,
+ friend constexpr bool operator<(compare_internal::OnlyLiteralZero,
strong_ordering v) noexcept {
return 0 < v.value_;
}
- friend constexpr bool operator<=(compare_internal::OnlyLiteralZero<>,
+ friend constexpr bool operator<=(compare_internal::OnlyLiteralZero,
strong_ordering v) noexcept {
return 0 <= v.value_;
}
- friend constexpr bool operator>(compare_internal::OnlyLiteralZero<>,
+ friend constexpr bool operator>(compare_internal::OnlyLiteralZero,
strong_ordering v) noexcept {
return 0 > v.value_;
}
- friend constexpr bool operator>=(compare_internal::OnlyLiteralZero<>,
+ friend constexpr bool operator>=(compare_internal::OnlyLiteralZero,
strong_ordering v) noexcept {
return 0 >= v.value_;
}
@@ -538,15 +441,17 @@
#undef ABSL_COMPARE_INLINE_SUBCLASS_DECL
#undef ABSL_COMPARE_INLINE_INIT
+#endif // ABSL_USES_STD_ORDERING
+
namespace compare_internal {
// We also provide these comparator adapter functions for internal absl use.
// Helper functions to do a boolean comparison of two keys given a boolean
// or three-way comparator.
// SFINAE prevents implicit conversions to bool (such as from int).
-template <typename Bool,
- absl::enable_if_t<std::is_same<bool, Bool>::value, int> = 0>
-constexpr bool compare_result_as_less_than(const Bool r) { return r; }
+template <typename BoolT,
+ absl::enable_if_t<std::is_same<bool, BoolT>::value, int> = 0>
+constexpr bool compare_result_as_less_than(const BoolT r) { return r; }
constexpr bool compare_result_as_less_than(const absl::weak_ordering r) {
return r < 0;
}
diff --git a/absl/types/compare_test.cc b/absl/types/compare_test.cc
index 8095baf..455cdbb 100644
--- a/absl/types/compare_test.cc
+++ b/absl/types/compare_test.cc
@@ -26,45 +26,6 @@
// to an int, which can't be converted to the unspecified zero type.
bool Identity(bool b) { return b; }
-TEST(Compare, WeakEquality) {
- EXPECT_TRUE(Identity(weak_equality::equivalent == 0));
- EXPECT_TRUE(Identity(0 == weak_equality::equivalent));
- EXPECT_TRUE(Identity(weak_equality::nonequivalent != 0));
- EXPECT_TRUE(Identity(0 != weak_equality::nonequivalent));
- const weak_equality values[] = {weak_equality::equivalent,
- weak_equality::nonequivalent};
- for (const auto& lhs : values) {
- for (const auto& rhs : values) {
- const bool are_equal = &lhs == &rhs;
- EXPECT_EQ(lhs == rhs, are_equal);
- EXPECT_EQ(lhs != rhs, !are_equal);
- }
- }
-}
-
-TEST(Compare, StrongEquality) {
- EXPECT_TRUE(Identity(strong_equality::equal == 0));
- EXPECT_TRUE(Identity(0 == strong_equality::equal));
- EXPECT_TRUE(Identity(strong_equality::nonequal != 0));
- EXPECT_TRUE(Identity(0 != strong_equality::nonequal));
- EXPECT_TRUE(Identity(strong_equality::equivalent == 0));
- EXPECT_TRUE(Identity(0 == strong_equality::equivalent));
- EXPECT_TRUE(Identity(strong_equality::nonequivalent != 0));
- EXPECT_TRUE(Identity(0 != strong_equality::nonequivalent));
- const strong_equality values[] = {strong_equality::equal,
- strong_equality::nonequal};
- for (const auto& lhs : values) {
- for (const auto& rhs : values) {
- const bool are_equal = &lhs == &rhs;
- EXPECT_EQ(lhs == rhs, are_equal);
- EXPECT_EQ(lhs != rhs, !are_equal);
- }
- }
- EXPECT_TRUE(Identity(strong_equality::equivalent == strong_equality::equal));
- EXPECT_TRUE(
- Identity(strong_equality::nonequivalent == strong_equality::nonequal));
-}
-
TEST(Compare, PartialOrdering) {
EXPECT_TRUE(Identity(partial_ordering::less < 0));
EXPECT_TRUE(Identity(0 > partial_ordering::less));
@@ -147,30 +108,6 @@
TEST(Compare, Conversions) {
EXPECT_TRUE(
- Identity(implicit_cast<weak_equality>(strong_equality::equal) == 0));
- EXPECT_TRUE(
- Identity(implicit_cast<weak_equality>(strong_equality::nonequal) != 0));
- EXPECT_TRUE(
- Identity(implicit_cast<weak_equality>(strong_equality::equivalent) == 0));
- EXPECT_TRUE(Identity(
- implicit_cast<weak_equality>(strong_equality::nonequivalent) != 0));
-
- EXPECT_TRUE(
- Identity(implicit_cast<weak_equality>(partial_ordering::less) != 0));
- EXPECT_TRUE(Identity(
- implicit_cast<weak_equality>(partial_ordering::equivalent) == 0));
- EXPECT_TRUE(
- Identity(implicit_cast<weak_equality>(partial_ordering::greater) != 0));
- EXPECT_TRUE(
- Identity(implicit_cast<weak_equality>(partial_ordering::unordered) != 0));
-
- EXPECT_TRUE(implicit_cast<weak_equality>(weak_ordering::less) != 0);
- EXPECT_TRUE(
- Identity(implicit_cast<weak_equality>(weak_ordering::equivalent) == 0));
- EXPECT_TRUE(
- Identity(implicit_cast<weak_equality>(weak_ordering::greater) != 0));
-
- EXPECT_TRUE(
Identity(implicit_cast<partial_ordering>(weak_ordering::less) != 0));
EXPECT_TRUE(
Identity(implicit_cast<partial_ordering>(weak_ordering::less) < 0));
@@ -186,24 +123,6 @@
Identity(implicit_cast<partial_ordering>(weak_ordering::greater) >= 0));
EXPECT_TRUE(
- Identity(implicit_cast<weak_equality>(strong_ordering::less) != 0));
- EXPECT_TRUE(
- Identity(implicit_cast<weak_equality>(strong_ordering::equal) == 0));
- EXPECT_TRUE(
- Identity(implicit_cast<weak_equality>(strong_ordering::equivalent) == 0));
- EXPECT_TRUE(
- Identity(implicit_cast<weak_equality>(strong_ordering::greater) != 0));
-
- EXPECT_TRUE(
- Identity(implicit_cast<strong_equality>(strong_ordering::less) != 0));
- EXPECT_TRUE(
- Identity(implicit_cast<strong_equality>(strong_ordering::equal) == 0));
- EXPECT_TRUE(Identity(
- implicit_cast<strong_equality>(strong_ordering::equivalent) == 0));
- EXPECT_TRUE(
- Identity(implicit_cast<strong_equality>(strong_ordering::greater) != 0));
-
- EXPECT_TRUE(
Identity(implicit_cast<partial_ordering>(strong_ordering::less) != 0));
EXPECT_TRUE(
Identity(implicit_cast<partial_ordering>(strong_ordering::less) < 0));
@@ -360,14 +279,6 @@
#ifdef __cpp_inline_variables
TEST(Compare, StaticAsserts) {
- static_assert(weak_equality::equivalent == 0, "");
- static_assert(weak_equality::nonequivalent != 0, "");
-
- static_assert(strong_equality::equal == 0, "");
- static_assert(strong_equality::nonequal != 0, "");
- static_assert(strong_equality::equivalent == 0, "");
- static_assert(strong_equality::nonequivalent != 0, "");
-
static_assert(partial_ordering::less < 0, "");
static_assert(partial_ordering::equivalent == 0, "");
static_assert(partial_ordering::greater > 0, "");
diff --git a/absl/types/internal/conformance_aliases.h b/absl/types/internal/conformance_aliases.h
deleted file mode 100644
index 0cc6884..0000000
--- a/absl/types/internal/conformance_aliases.h
+++ /dev/null
@@ -1,447 +0,0 @@
-// Copyright 2018 The Abseil Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-// -----------------------------------------------------------------------------
-// regularity_aliases.h
-// -----------------------------------------------------------------------------
-//
-// This file contains type aliases of common ConformanceProfiles and Archetypes
-// so that they can be directly used by name without creating them from scratch.
-
-#ifndef ABSL_TYPES_INTERNAL_CONFORMANCE_ALIASES_H_
-#define ABSL_TYPES_INTERNAL_CONFORMANCE_ALIASES_H_
-
-#include "absl/types/internal/conformance_archetype.h"
-#include "absl/types/internal/conformance_profile.h"
-
-namespace absl {
-ABSL_NAMESPACE_BEGIN
-namespace types_internal {
-
-// Creates both a Profile and a corresponding Archetype with root name "name".
-#define ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(name, ...) \
- struct name##Profile : __VA_ARGS__ {}; \
- \
- using name##Archetype = ::absl::types_internal::Archetype<name##Profile>; \
- \
- template <class AbslInternalProfileTag> \
- using name##Archetype##_ = ::absl::types_internal::Archetype< \
- ::absl::types_internal::StrongProfileTypedef<name##Profile, \
- AbslInternalProfileTag>>
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- HasTrivialDefaultConstructor,
- ConformanceProfile<default_constructible::trivial>);
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- HasNothrowDefaultConstructor,
- ConformanceProfile<default_constructible::nothrow>);
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- HasDefaultConstructor, ConformanceProfile<default_constructible::yes>);
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- HasTrivialMoveConstructor, ConformanceProfile<default_constructible::maybe,
- move_constructible::trivial>);
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- HasNothrowMoveConstructor, ConformanceProfile<default_constructible::maybe,
- move_constructible::nothrow>);
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- HasMoveConstructor,
- ConformanceProfile<default_constructible::maybe, move_constructible::yes>);
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- HasTrivialCopyConstructor,
- ConformanceProfile<default_constructible::maybe, move_constructible::maybe,
- copy_constructible::trivial>);
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- HasNothrowCopyConstructor,
- ConformanceProfile<default_constructible::maybe, move_constructible::maybe,
- copy_constructible::nothrow>);
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- HasCopyConstructor,
- ConformanceProfile<default_constructible::maybe, move_constructible::maybe,
- copy_constructible::yes>);
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- HasTrivialMoveAssign,
- ConformanceProfile<default_constructible::maybe, move_constructible::maybe,
- copy_constructible::maybe, move_assignable::trivial>);
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- HasNothrowMoveAssign,
- ConformanceProfile<default_constructible::maybe, move_constructible::maybe,
- copy_constructible::maybe, move_assignable::nothrow>);
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- HasMoveAssign,
- ConformanceProfile<default_constructible::maybe, move_constructible::maybe,
- copy_constructible::maybe, move_assignable::yes>);
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- HasTrivialCopyAssign,
- ConformanceProfile<default_constructible::maybe, move_constructible::maybe,
- copy_constructible::maybe, move_assignable::maybe,
- copy_assignable::trivial>);
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- HasNothrowCopyAssign,
- ConformanceProfile<default_constructible::maybe, move_constructible::maybe,
- copy_constructible::maybe, move_assignable::maybe,
- copy_assignable::nothrow>);
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- HasCopyAssign,
- ConformanceProfile<default_constructible::maybe, move_constructible::maybe,
- copy_constructible::maybe, move_assignable::maybe,
- copy_assignable::yes>);
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- HasTrivialDestructor,
- ConformanceProfile<default_constructible::maybe, move_constructible::maybe,
- copy_constructible::maybe, move_assignable::maybe,
- copy_assignable::maybe, destructible::trivial>);
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- HasNothrowDestructor,
- ConformanceProfile<default_constructible::maybe, move_constructible::maybe,
- copy_constructible::maybe, move_assignable::maybe,
- copy_assignable::maybe, destructible::nothrow>);
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- HasDestructor,
- ConformanceProfile<default_constructible::maybe, move_constructible::maybe,
- copy_constructible::maybe, move_assignable::maybe,
- copy_assignable::maybe, destructible::yes>);
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- HasNothrowEquality,
- ConformanceProfile<default_constructible::maybe, move_constructible::maybe,
- copy_constructible::maybe, move_assignable::maybe,
- copy_assignable::maybe, destructible::maybe,
- equality_comparable::nothrow>);
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- HasEquality,
- ConformanceProfile<default_constructible::maybe, move_constructible::maybe,
- copy_constructible::maybe, move_assignable::maybe,
- copy_assignable::maybe, destructible::maybe,
- equality_comparable::yes>);
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- HasNothrowInequality,
- ConformanceProfile<default_constructible::maybe, move_constructible::maybe,
- copy_constructible::maybe, move_assignable::maybe,
- copy_assignable::maybe, destructible::maybe,
- equality_comparable::maybe,
- inequality_comparable::nothrow>);
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- HasInequality,
- ConformanceProfile<default_constructible::maybe, move_constructible::maybe,
- copy_constructible::maybe, move_assignable::maybe,
- copy_assignable::maybe, destructible::maybe,
- equality_comparable::maybe, inequality_comparable::yes>);
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- HasNothrowLessThan,
- ConformanceProfile<default_constructible::maybe, move_constructible::maybe,
- copy_constructible::maybe, move_assignable::maybe,
- copy_assignable::maybe, destructible::maybe,
- equality_comparable::maybe, inequality_comparable::maybe,
- less_than_comparable::nothrow>);
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- HasLessThan,
- ConformanceProfile<default_constructible::maybe, move_constructible::maybe,
- copy_constructible::maybe, move_assignable::maybe,
- copy_assignable::maybe, destructible::maybe,
- equality_comparable::maybe, inequality_comparable::maybe,
- less_than_comparable::yes>);
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- HasNothrowLessEqual,
- ConformanceProfile<default_constructible::maybe, move_constructible::maybe,
- copy_constructible::maybe, move_assignable::maybe,
- copy_assignable::maybe, destructible::maybe,
- equality_comparable::maybe, inequality_comparable::maybe,
- less_than_comparable::maybe,
- less_equal_comparable::nothrow>);
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- HasLessEqual,
- ConformanceProfile<default_constructible::maybe, move_constructible::maybe,
- copy_constructible::maybe, move_assignable::maybe,
- copy_assignable::maybe, destructible::maybe,
- equality_comparable::maybe, inequality_comparable::maybe,
- less_than_comparable::maybe,
- less_equal_comparable::yes>);
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- HasNothrowGreaterEqual,
- ConformanceProfile<
- default_constructible::maybe, move_constructible::maybe,
- copy_constructible::maybe, move_assignable::maybe,
- copy_assignable::maybe, destructible::maybe, equality_comparable::maybe,
- inequality_comparable::maybe, less_than_comparable::maybe,
- less_equal_comparable::maybe, greater_equal_comparable::nothrow>);
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- HasGreaterEqual,
- ConformanceProfile<
- default_constructible::maybe, move_constructible::maybe,
- copy_constructible::maybe, move_assignable::maybe,
- copy_assignable::maybe, destructible::maybe, equality_comparable::maybe,
- inequality_comparable::maybe, less_than_comparable::maybe,
- less_equal_comparable::maybe, greater_equal_comparable::yes>);
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- HasNothrowGreaterThan,
- ConformanceProfile<
- default_constructible::maybe, move_constructible::maybe,
- copy_constructible::maybe, move_assignable::maybe,
- copy_assignable::maybe, destructible::maybe, equality_comparable::maybe,
- inequality_comparable::maybe, less_than_comparable::maybe,
- less_equal_comparable::maybe, greater_equal_comparable::maybe,
- greater_than_comparable::nothrow>);
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- HasGreaterThan,
- ConformanceProfile<
- default_constructible::maybe, move_constructible::maybe,
- copy_constructible::maybe, move_assignable::maybe,
- copy_assignable::maybe, destructible::maybe, equality_comparable::maybe,
- inequality_comparable::maybe, less_than_comparable::maybe,
- less_equal_comparable::maybe, greater_equal_comparable::maybe,
- greater_than_comparable::yes>);
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- HasNothrowSwap,
- ConformanceProfile<
- default_constructible::maybe, move_constructible::maybe,
- copy_constructible::maybe, move_assignable::maybe,
- copy_assignable::maybe, destructible::maybe, equality_comparable::maybe,
- inequality_comparable::maybe, less_than_comparable::maybe,
- less_equal_comparable::maybe, greater_equal_comparable::maybe,
- greater_than_comparable::maybe, swappable::nothrow>);
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- HasSwap,
- ConformanceProfile<
- default_constructible::maybe, move_constructible::maybe,
- copy_constructible::maybe, move_assignable::maybe,
- copy_assignable::maybe, destructible::maybe, equality_comparable::maybe,
- inequality_comparable::maybe, less_than_comparable::maybe,
- less_equal_comparable::maybe, greater_equal_comparable::maybe,
- greater_than_comparable::maybe, swappable::yes>);
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- HasStdHashSpecialization,
- ConformanceProfile<
- default_constructible::maybe, move_constructible::maybe,
- copy_constructible::maybe, move_assignable::maybe,
- copy_assignable::maybe, destructible::maybe, equality_comparable::maybe,
- inequality_comparable::maybe, less_than_comparable::maybe,
- less_equal_comparable::maybe, greater_equal_comparable::maybe,
- greater_than_comparable::maybe, swappable::maybe, hashable::yes>);
-
-////////////////////////////////////////////////////////////////////////////////
-//// The remaining aliases are combinations of the previous aliases. ////
-////////////////////////////////////////////////////////////////////////////////
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- Equatable, CombineProfiles<HasEqualityProfile, HasInequalityProfile>);
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- Comparable,
- CombineProfiles<EquatableProfile, HasLessThanProfile, HasLessEqualProfile,
- HasGreaterEqualProfile, HasGreaterThanProfile>);
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- NothrowEquatable,
- CombineProfiles<HasNothrowEqualityProfile, HasNothrowInequalityProfile>);
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- NothrowComparable,
- CombineProfiles<NothrowEquatableProfile, HasNothrowLessThanProfile,
- HasNothrowLessEqualProfile, HasNothrowGreaterEqualProfile,
- HasNothrowGreaterThanProfile>);
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- Value,
- CombineProfiles<HasNothrowMoveConstructorProfile, HasCopyConstructorProfile,
- HasNothrowMoveAssignProfile, HasCopyAssignProfile,
- HasNothrowDestructorProfile, HasNothrowSwapProfile>);
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- EquatableValue, CombineProfiles<EquatableProfile, ValueProfile>);
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- ComparableValue, CombineProfiles<ComparableProfile, ValueProfile>);
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- DefaultConstructibleValue,
- CombineProfiles<HasDefaultConstructorProfile, ValueProfile>);
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- NothrowMoveConstructible, CombineProfiles<HasNothrowMoveConstructorProfile,
- HasNothrowDestructorProfile>);
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- EquatableNothrowMoveConstructible,
- CombineProfiles<EquatableProfile, NothrowMoveConstructibleProfile>);
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- ComparableNothrowMoveConstructible,
- CombineProfiles<ComparableProfile, NothrowMoveConstructibleProfile>);
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- DefaultConstructibleNothrowMoveConstructible,
- CombineProfiles<HasDefaultConstructorProfile,
- NothrowMoveConstructibleProfile>);
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- CopyConstructible,
- CombineProfiles<HasNothrowMoveConstructorProfile, HasCopyConstructorProfile,
- HasNothrowDestructorProfile>);
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- EquatableCopyConstructible,
- CombineProfiles<EquatableProfile, CopyConstructibleProfile>);
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- ComparableCopyConstructible,
- CombineProfiles<ComparableProfile, CopyConstructibleProfile>);
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- DefaultConstructibleCopyConstructible,
- CombineProfiles<HasDefaultConstructorProfile, CopyConstructibleProfile>);
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- NothrowMovable,
- CombineProfiles<HasNothrowMoveConstructorProfile,
- HasNothrowMoveAssignProfile, HasNothrowDestructorProfile,
- HasNothrowSwapProfile>);
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- EquatableNothrowMovable,
- CombineProfiles<EquatableProfile, NothrowMovableProfile>);
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- ComparableNothrowMovable,
- CombineProfiles<ComparableProfile, NothrowMovableProfile>);
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- DefaultConstructibleNothrowMovable,
- CombineProfiles<HasDefaultConstructorProfile, NothrowMovableProfile>);
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- TrivialSpecialMemberFunctions,
- CombineProfiles<HasTrivialDefaultConstructorProfile,
- HasTrivialMoveConstructorProfile,
- HasTrivialCopyConstructorProfile,
- HasTrivialMoveAssignProfile, HasTrivialCopyAssignProfile,
- HasTrivialDestructorProfile, HasNothrowSwapProfile>);
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- TriviallyComplete,
- CombineProfiles<TrivialSpecialMemberFunctionsProfile, ComparableProfile,
- HasStdHashSpecializationProfile>);
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- HashableNothrowMoveConstructible,
- CombineProfiles<HasStdHashSpecializationProfile,
- NothrowMoveConstructibleProfile>);
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- HashableCopyConstructible,
- CombineProfiles<HasStdHashSpecializationProfile, CopyConstructibleProfile>);
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- HashableNothrowMovable,
- CombineProfiles<HasStdHashSpecializationProfile, NothrowMovableProfile>);
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- HashableValue,
- CombineProfiles<HasStdHashSpecializationProfile, ValueProfile>);
-
-ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(
- ComparableHashableValue,
- CombineProfiles<HashableValueProfile, ComparableProfile>);
-
-// The "preferred" profiles that we support in Abseil.
-template <template <class...> class Receiver>
-using ExpandBasicProfiles =
- Receiver<NothrowMoveConstructibleProfile, CopyConstructibleProfile,
- NothrowMovableProfile, ValueProfile>;
-
-// The basic profiles except that they are also all Equatable.
-template <template <class...> class Receiver>
-using ExpandBasicEquatableProfiles =
- Receiver<EquatableNothrowMoveConstructibleProfile,
- EquatableCopyConstructibleProfile, EquatableNothrowMovableProfile,
- EquatableValueProfile>;
-
-// The basic profiles except that they are also all Comparable.
-template <template <class...> class Receiver>
-using ExpandBasicComparableProfiles =
- Receiver<ComparableNothrowMoveConstructibleProfile,
- ComparableCopyConstructibleProfile,
- ComparableNothrowMovableProfile, ComparableValueProfile>;
-
-// The basic profiles except that they are also all Hashable.
-template <template <class...> class Receiver>
-using ExpandBasicHashableProfiles =
- Receiver<HashableNothrowMoveConstructibleProfile,
- HashableCopyConstructibleProfile, HashableNothrowMovableProfile,
- HashableValueProfile>;
-
-// The basic profiles except that they are also all DefaultConstructible.
-template <template <class...> class Receiver>
-using ExpandBasicDefaultConstructibleProfiles =
- Receiver<DefaultConstructibleNothrowMoveConstructibleProfile,
- DefaultConstructibleCopyConstructibleProfile,
- DefaultConstructibleNothrowMovableProfile,
- DefaultConstructibleValueProfile>;
-
-// The type profiles that we support in Abseil (all of the previous lists).
-template <template <class...> class Receiver>
-using ExpandSupportedProfiles = Receiver<
- NothrowMoveConstructibleProfile, CopyConstructibleProfile,
- NothrowMovableProfile, ValueProfile,
- EquatableNothrowMoveConstructibleProfile, EquatableCopyConstructibleProfile,
- EquatableNothrowMovableProfile, EquatableValueProfile,
- ComparableNothrowMoveConstructibleProfile,
- ComparableCopyConstructibleProfile, ComparableNothrowMovableProfile,
- ComparableValueProfile, DefaultConstructibleNothrowMoveConstructibleProfile,
- DefaultConstructibleCopyConstructibleProfile,
- DefaultConstructibleNothrowMovableProfile, DefaultConstructibleValueProfile,
- HashableNothrowMoveConstructibleProfile, HashableCopyConstructibleProfile,
- HashableNothrowMovableProfile, HashableValueProfile>;
-
-// TODO(calabrese) Include types that have throwing move constructors, since in
-// practice we still need to support them because of standard library types with
-// (potentially) non-noexcept moves.
-
-} // namespace types_internal
-ABSL_NAMESPACE_END
-} // namespace absl
-
-#undef ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS
-
-#endif // ABSL_TYPES_INTERNAL_CONFORMANCE_ALIASES_H_
diff --git a/absl/types/internal/conformance_archetype.h b/absl/types/internal/conformance_archetype.h
deleted file mode 100644
index 2349e0f..0000000
--- a/absl/types/internal/conformance_archetype.h
+++ /dev/null
@@ -1,978 +0,0 @@
-// Copyright 2019 The Abseil Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-// -----------------------------------------------------------------------------
-// conformance_archetype.h
-// -----------------------------------------------------------------------------
-//
-// This file contains a facility for generating "archetypes" of out of
-// "Conformance Profiles" (see "conformance_profiles.h" for more information
-// about Conformance Profiles). An archetype is a type that aims to support the
-// bare minimum requirements of a given Conformance Profile. For instance, an
-// archetype that corresponds to an ImmutableProfile has exactly a nothrow
-// move-constructor, a potentially-throwing copy constructor, a nothrow
-// destructor, with all other special-member-functions deleted. These archetypes
-// are useful for testing to make sure that templates are able to work with the
-// kinds of types that they claim to support (i.e. that they do not accidentally
-// under-constrain),
-//
-// The main type template in this file is the Archetype template, which takes
-// a Conformance Profile as a template argument and its instantiations are a
-// minimum-conforming model of that profile.
-
-#ifndef ABSL_TYPES_INTERNAL_CONFORMANCE_ARCHETYPE_H_
-#define ABSL_TYPES_INTERNAL_CONFORMANCE_ARCHETYPE_H_
-
-#include <cstddef>
-#include <functional>
-#include <type_traits>
-#include <utility>
-
-#include "absl/meta/type_traits.h"
-#include "absl/types/internal/conformance_profile.h"
-
-namespace absl {
-ABSL_NAMESPACE_BEGIN
-namespace types_internal {
-
-// A minimum-conforming implementation of a type with properties specified in
-// `Prof`, where `Prof` is a valid Conformance Profile.
-template <class Prof, class /*Enabler*/ = void>
-class Archetype;
-
-// Given an Archetype, obtain the properties of the profile associated with that
-// archetype.
-template <class Archetype>
-struct PropertiesOfArchetype;
-
-template <class Prof>
-struct PropertiesOfArchetype<Archetype<Prof>> {
- using type = PropertiesOfT<Prof>;
-};
-
-template <class Archetype>
-using PropertiesOfArchetypeT = typename PropertiesOfArchetype<Archetype>::type;
-
-// A metafunction to determine if a type is an `Archetype`.
-template <class T>
-struct IsArchetype : std::false_type {};
-
-template <class Prof>
-struct IsArchetype<Archetype<Prof>> : std::true_type {};
-
-// A constructor tag type used when creating an Archetype with internal state.
-struct MakeArchetypeState {};
-
-// Data stored within an archetype that is copied/compared/hashed when the
-// corresponding operations are used.
-using ArchetypeState = std::size_t;
-
-////////////////////////////////////////////////////////////////////////////////
-// This section of the file defines a chain of base classes for Archetype, //
-// where each base defines a specific special member function with the //
-// appropriate properties (deleted, noexcept(false), noexcept, or trivial). //
-////////////////////////////////////////////////////////////////////////////////
-
-// The bottom-most base, which contains the state and the default constructor.
-template <default_constructible DefaultConstructibleValue>
-struct ArchetypeStateBase {
- static_assert(DefaultConstructibleValue == default_constructible::yes ||
- DefaultConstructibleValue == default_constructible::nothrow,
- "");
-
- ArchetypeStateBase() noexcept(
- DefaultConstructibleValue ==
- default_constructible::
- nothrow) /*Vacuous archetype_state initialization*/ {}
- explicit ArchetypeStateBase(MakeArchetypeState, ArchetypeState state) noexcept
- : archetype_state(state) {}
-
- ArchetypeState archetype_state;
-};
-
-template <>
-struct ArchetypeStateBase<default_constructible::maybe> {
- explicit ArchetypeStateBase() = delete;
- explicit ArchetypeStateBase(MakeArchetypeState, ArchetypeState state) noexcept
- : archetype_state(state) {}
-
- ArchetypeState archetype_state;
-};
-
-template <>
-struct ArchetypeStateBase<default_constructible::trivial> {
- ArchetypeStateBase() = default;
- explicit ArchetypeStateBase(MakeArchetypeState, ArchetypeState state) noexcept
- : archetype_state(state) {}
-
- ArchetypeState archetype_state;
-};
-
-// The move-constructor base
-template <default_constructible DefaultConstructibleValue,
- move_constructible MoveConstructibleValue>
-struct ArchetypeMoveConstructor
- : ArchetypeStateBase<DefaultConstructibleValue> {
- static_assert(MoveConstructibleValue == move_constructible::yes ||
- MoveConstructibleValue == move_constructible::nothrow,
- "");
-
- explicit ArchetypeMoveConstructor(MakeArchetypeState,
- ArchetypeState state) noexcept
- : ArchetypeStateBase<DefaultConstructibleValue>(MakeArchetypeState(),
- state) {}
-
- ArchetypeMoveConstructor() = default;
- ArchetypeMoveConstructor(ArchetypeMoveConstructor&& other) noexcept(
- MoveConstructibleValue == move_constructible::nothrow)
- : ArchetypeStateBase<DefaultConstructibleValue>(MakeArchetypeState(),
- other.archetype_state) {}
- ArchetypeMoveConstructor(const ArchetypeMoveConstructor&) = default;
- ArchetypeMoveConstructor& operator=(ArchetypeMoveConstructor&&) = default;
- ArchetypeMoveConstructor& operator=(const ArchetypeMoveConstructor&) =
- default;
-};
-
-template <default_constructible DefaultConstructibleValue>
-struct ArchetypeMoveConstructor<DefaultConstructibleValue,
- move_constructible::trivial>
- : ArchetypeStateBase<DefaultConstructibleValue> {
- explicit ArchetypeMoveConstructor(MakeArchetypeState,
- ArchetypeState state) noexcept
- : ArchetypeStateBase<DefaultConstructibleValue>(MakeArchetypeState(),
- state) {}
-
- ArchetypeMoveConstructor() = default;
-};
-
-// The copy-constructor base
-template <default_constructible DefaultConstructibleValue,
- move_constructible MoveConstructibleValue,
- copy_constructible CopyConstructibleValue>
-struct ArchetypeCopyConstructor
- : ArchetypeMoveConstructor<DefaultConstructibleValue,
- MoveConstructibleValue> {
- static_assert(CopyConstructibleValue == copy_constructible::yes ||
- CopyConstructibleValue == copy_constructible::nothrow,
- "");
- explicit ArchetypeCopyConstructor(MakeArchetypeState,
- ArchetypeState state) noexcept
- : ArchetypeMoveConstructor<DefaultConstructibleValue,
- MoveConstructibleValue>(MakeArchetypeState(),
- state) {}
-
- ArchetypeCopyConstructor() = default;
- ArchetypeCopyConstructor(ArchetypeCopyConstructor&&) = default;
- ArchetypeCopyConstructor(const ArchetypeCopyConstructor& other) noexcept(
- CopyConstructibleValue == copy_constructible::nothrow)
- : ArchetypeMoveConstructor<DefaultConstructibleValue,
- MoveConstructibleValue>(
- MakeArchetypeState(), other.archetype_state) {}
- ArchetypeCopyConstructor& operator=(ArchetypeCopyConstructor&&) = default;
- ArchetypeCopyConstructor& operator=(const ArchetypeCopyConstructor&) =
- default;
-};
-
-template <default_constructible DefaultConstructibleValue,
- move_constructible MoveConstructibleValue>
-struct ArchetypeCopyConstructor<DefaultConstructibleValue,
- MoveConstructibleValue,
- copy_constructible::maybe>
- : ArchetypeMoveConstructor<DefaultConstructibleValue,
- MoveConstructibleValue> {
- explicit ArchetypeCopyConstructor(MakeArchetypeState,
- ArchetypeState state) noexcept
- : ArchetypeMoveConstructor<DefaultConstructibleValue,
- MoveConstructibleValue>(MakeArchetypeState(),
- state) {}
-
- ArchetypeCopyConstructor() = default;
- ArchetypeCopyConstructor(ArchetypeCopyConstructor&&) = default;
- ArchetypeCopyConstructor(const ArchetypeCopyConstructor&) = delete;
- ArchetypeCopyConstructor& operator=(ArchetypeCopyConstructor&&) = default;
- ArchetypeCopyConstructor& operator=(const ArchetypeCopyConstructor&) =
- default;
-};
-
-template <default_constructible DefaultConstructibleValue,
- move_constructible MoveConstructibleValue>
-struct ArchetypeCopyConstructor<DefaultConstructibleValue,
- MoveConstructibleValue,
- copy_constructible::trivial>
- : ArchetypeMoveConstructor<DefaultConstructibleValue,
- MoveConstructibleValue> {
- explicit ArchetypeCopyConstructor(MakeArchetypeState,
- ArchetypeState state) noexcept
- : ArchetypeMoveConstructor<DefaultConstructibleValue,
- MoveConstructibleValue>(MakeArchetypeState(),
- state) {}
-
- ArchetypeCopyConstructor() = default;
-};
-
-// The move-assign base
-template <default_constructible DefaultConstructibleValue,
- move_constructible MoveConstructibleValue,
- copy_constructible CopyConstructibleValue,
- move_assignable MoveAssignableValue>
-struct ArchetypeMoveAssign
- : ArchetypeCopyConstructor<DefaultConstructibleValue,
- MoveConstructibleValue, CopyConstructibleValue> {
- static_assert(MoveAssignableValue == move_assignable::yes ||
- MoveAssignableValue == move_assignable::nothrow,
- "");
- explicit ArchetypeMoveAssign(MakeArchetypeState,
- ArchetypeState state) noexcept
- : ArchetypeCopyConstructor<DefaultConstructibleValue,
- MoveConstructibleValue,
- CopyConstructibleValue>(MakeArchetypeState(),
- state) {}
-
- ArchetypeMoveAssign() = default;
- ArchetypeMoveAssign(ArchetypeMoveAssign&&) = default;
- ArchetypeMoveAssign(const ArchetypeMoveAssign&) = default;
- ArchetypeMoveAssign& operator=(ArchetypeMoveAssign&& other) noexcept(
- MoveAssignableValue == move_assignable::nothrow) {
- this->archetype_state = other.archetype_state;
- return *this;
- }
-
- ArchetypeMoveAssign& operator=(const ArchetypeMoveAssign&) = default;
-};
-
-template <default_constructible DefaultConstructibleValue,
- move_constructible MoveConstructibleValue,
- copy_constructible CopyConstructibleValue>
-struct ArchetypeMoveAssign<DefaultConstructibleValue, MoveConstructibleValue,
- CopyConstructibleValue, move_assignable::trivial>
- : ArchetypeCopyConstructor<DefaultConstructibleValue,
- MoveConstructibleValue, CopyConstructibleValue> {
- explicit ArchetypeMoveAssign(MakeArchetypeState,
- ArchetypeState state) noexcept
- : ArchetypeCopyConstructor<DefaultConstructibleValue,
- MoveConstructibleValue,
- CopyConstructibleValue>(MakeArchetypeState(),
- state) {}
-
- ArchetypeMoveAssign() = default;
-};
-
-// The copy-assign base
-template <default_constructible DefaultConstructibleValue,
- move_constructible MoveConstructibleValue,
- copy_constructible CopyConstructibleValue,
- move_assignable MoveAssignableValue,
- copy_assignable CopyAssignableValue>
-struct ArchetypeCopyAssign
- : ArchetypeMoveAssign<DefaultConstructibleValue, MoveConstructibleValue,
- CopyConstructibleValue, MoveAssignableValue> {
- static_assert(CopyAssignableValue == copy_assignable::yes ||
- CopyAssignableValue == copy_assignable::nothrow,
- "");
- explicit ArchetypeCopyAssign(MakeArchetypeState,
- ArchetypeState state) noexcept
- : ArchetypeMoveAssign<DefaultConstructibleValue, MoveConstructibleValue,
- CopyConstructibleValue, MoveAssignableValue>(
- MakeArchetypeState(), state) {}
-
- ArchetypeCopyAssign() = default;
- ArchetypeCopyAssign(ArchetypeCopyAssign&&) = default;
- ArchetypeCopyAssign(const ArchetypeCopyAssign&) = default;
- ArchetypeCopyAssign& operator=(ArchetypeCopyAssign&&) = default;
-
- ArchetypeCopyAssign& operator=(const ArchetypeCopyAssign& other) noexcept(
- CopyAssignableValue == copy_assignable::nothrow) {
- this->archetype_state = other.archetype_state;
- return *this;
- }
-};
-
-template <default_constructible DefaultConstructibleValue,
- move_constructible MoveConstructibleValue,
- copy_constructible CopyConstructibleValue,
- move_assignable MoveAssignableValue>
-struct ArchetypeCopyAssign<DefaultConstructibleValue, MoveConstructibleValue,
- CopyConstructibleValue, MoveAssignableValue,
- copy_assignable::maybe>
- : ArchetypeMoveAssign<DefaultConstructibleValue, MoveConstructibleValue,
- CopyConstructibleValue, MoveAssignableValue> {
- explicit ArchetypeCopyAssign(MakeArchetypeState,
- ArchetypeState state) noexcept
- : ArchetypeMoveAssign<DefaultConstructibleValue, MoveConstructibleValue,
- CopyConstructibleValue, MoveAssignableValue>(
- MakeArchetypeState(), state) {}
-
- ArchetypeCopyAssign() = default;
- ArchetypeCopyAssign(ArchetypeCopyAssign&&) = default;
- ArchetypeCopyAssign(const ArchetypeCopyAssign&) = default;
- ArchetypeCopyAssign& operator=(ArchetypeCopyAssign&&) = default;
- ArchetypeCopyAssign& operator=(const ArchetypeCopyAssign&) = delete;
-};
-
-template <default_constructible DefaultConstructibleValue,
- move_constructible MoveConstructibleValue,
- copy_constructible CopyConstructibleValue,
- move_assignable MoveAssignableValue>
-struct ArchetypeCopyAssign<DefaultConstructibleValue, MoveConstructibleValue,
- CopyConstructibleValue, MoveAssignableValue,
- copy_assignable::trivial>
- : ArchetypeMoveAssign<DefaultConstructibleValue, MoveConstructibleValue,
- CopyConstructibleValue, MoveAssignableValue> {
- explicit ArchetypeCopyAssign(MakeArchetypeState,
- ArchetypeState state) noexcept
- : ArchetypeMoveAssign<DefaultConstructibleValue, MoveConstructibleValue,
- CopyConstructibleValue, MoveAssignableValue>(
- MakeArchetypeState(), state) {}
-
- ArchetypeCopyAssign() = default;
-};
-
-// The destructor base
-template <default_constructible DefaultConstructibleValue,
- move_constructible MoveConstructibleValue,
- copy_constructible CopyConstructibleValue,
- move_assignable MoveAssignableValue,
- copy_assignable CopyAssignableValue, destructible DestructibleValue>
-struct ArchetypeDestructor
- : ArchetypeCopyAssign<DefaultConstructibleValue, MoveConstructibleValue,
- CopyConstructibleValue, MoveAssignableValue,
- CopyAssignableValue> {
- static_assert(DestructibleValue == destructible::yes ||
- DestructibleValue == destructible::nothrow,
- "");
-
- explicit ArchetypeDestructor(MakeArchetypeState,
- ArchetypeState state) noexcept
- : ArchetypeCopyAssign<DefaultConstructibleValue, MoveConstructibleValue,
- CopyConstructibleValue, MoveAssignableValue,
- CopyAssignableValue>(MakeArchetypeState(), state) {}
-
- ArchetypeDestructor() = default;
- ArchetypeDestructor(ArchetypeDestructor&&) = default;
- ArchetypeDestructor(const ArchetypeDestructor&) = default;
- ArchetypeDestructor& operator=(ArchetypeDestructor&&) = default;
- ArchetypeDestructor& operator=(const ArchetypeDestructor&) = default;
- ~ArchetypeDestructor() noexcept(DestructibleValue == destructible::nothrow) {}
-};
-
-template <default_constructible DefaultConstructibleValue,
- move_constructible MoveConstructibleValue,
- copy_constructible CopyConstructibleValue,
- move_assignable MoveAssignableValue,
- copy_assignable CopyAssignableValue>
-struct ArchetypeDestructor<DefaultConstructibleValue, MoveConstructibleValue,
- CopyConstructibleValue, MoveAssignableValue,
- CopyAssignableValue, destructible::trivial>
- : ArchetypeCopyAssign<DefaultConstructibleValue, MoveConstructibleValue,
- CopyConstructibleValue, MoveAssignableValue,
- CopyAssignableValue> {
- explicit ArchetypeDestructor(MakeArchetypeState,
- ArchetypeState state) noexcept
- : ArchetypeCopyAssign<DefaultConstructibleValue, MoveConstructibleValue,
- CopyConstructibleValue, MoveAssignableValue,
- CopyAssignableValue>(MakeArchetypeState(), state) {}
-
- ArchetypeDestructor() = default;
-};
-
-// An alias to the top of the chain of bases for special-member functions.
-// NOTE: move_constructible::maybe, move_assignable::maybe, and
-// destructible::maybe are handled in the top-level type by way of SFINAE.
-// Because of this, we never instantiate the base classes with
-// move_constructible::maybe, move_assignable::maybe, or destructible::maybe so
-// that we minimize the number of different possible type-template
-// instantiations.
-template <default_constructible DefaultConstructibleValue,
- move_constructible MoveConstructibleValue,
- copy_constructible CopyConstructibleValue,
- move_assignable MoveAssignableValue,
- copy_assignable CopyAssignableValue, destructible DestructibleValue>
-using ArchetypeSpecialMembersBase = ArchetypeDestructor<
- DefaultConstructibleValue,
- MoveConstructibleValue != move_constructible::maybe
- ? MoveConstructibleValue
- : move_constructible::nothrow,
- CopyConstructibleValue,
- MoveAssignableValue != move_assignable::maybe ? MoveAssignableValue
- : move_assignable::nothrow,
- CopyAssignableValue,
- DestructibleValue != destructible::maybe ? DestructibleValue
- : destructible::nothrow>;
-
-// A function that is used to create an archetype with some associated state.
-template <class Arch>
-Arch MakeArchetype(ArchetypeState state) noexcept {
- static_assert(IsArchetype<Arch>::value,
- "The explicit template argument to MakeArchetype is required "
- "to be an Archetype.");
- return Arch(MakeArchetypeState(), state);
-}
-
-// This is used to conditionally delete "copy" and "move" constructors in a way
-// that is consistent with what the ConformanceProfile requires and that also
-// strictly enforces the arguments to the copy/move to not come from implicit
-// conversions when dealing with the Archetype.
-template <class Prof, class T>
-constexpr bool ShouldDeleteConstructor() {
- return !((PropertiesOfT<Prof>::move_constructible_support !=
- move_constructible::maybe &&
- std::is_same<T, Archetype<Prof>>::value) ||
- (PropertiesOfT<Prof>::copy_constructible_support !=
- copy_constructible::maybe &&
- (std::is_same<T, const Archetype<Prof>&>::value ||
- std::is_same<T, Archetype<Prof>&>::value ||
- std::is_same<T, const Archetype<Prof>>::value)));
-}
-
-// This is used to conditionally delete "copy" and "move" assigns in a way
-// that is consistent with what the ConformanceProfile requires and that also
-// strictly enforces the arguments to the copy/move to not come from implicit
-// conversions when dealing with the Archetype.
-template <class Prof, class T>
-constexpr bool ShouldDeleteAssign() {
- return !(
- (PropertiesOfT<Prof>::move_assignable_support != move_assignable::maybe &&
- std::is_same<T, Archetype<Prof>>::value) ||
- (PropertiesOfT<Prof>::copy_assignable_support != copy_assignable::maybe &&
- (std::is_same<T, const Archetype<Prof>&>::value ||
- std::is_same<T, Archetype<Prof>&>::value ||
- std::is_same<T, const Archetype<Prof>>::value)));
-}
-
-// TODO(calabrese) Inherit from a chain of secondary bases to pull in the
-// associated functions of other concepts.
-template <class Prof, class Enabler>
-class Archetype : ArchetypeSpecialMembersBase<
- PropertiesOfT<Prof>::default_constructible_support,
- PropertiesOfT<Prof>::move_constructible_support,
- PropertiesOfT<Prof>::copy_constructible_support,
- PropertiesOfT<Prof>::move_assignable_support,
- PropertiesOfT<Prof>::copy_assignable_support,
- PropertiesOfT<Prof>::destructible_support> {
- static_assert(std::is_same<Enabler, void>::value,
- "An explicit type must not be passed as the second template "
- "argument to 'Archetype`.");
-
- // The cases mentioned in these static_asserts are expected to be handled in
- // the partial template specializations of Archetype that follow this
- // definition.
- static_assert(PropertiesOfT<Prof>::destructible_support !=
- destructible::maybe,
- "");
- static_assert(PropertiesOfT<Prof>::move_constructible_support !=
- move_constructible::maybe ||
- PropertiesOfT<Prof>::copy_constructible_support ==
- copy_constructible::maybe,
- "");
- static_assert(PropertiesOfT<Prof>::move_assignable_support !=
- move_assignable::maybe ||
- PropertiesOfT<Prof>::copy_assignable_support ==
- copy_assignable::maybe,
- "");
-
- public:
- Archetype() = default;
-
- // Disallow moves when requested, and disallow implicit conversions.
- template <class T, typename std::enable_if<
- ShouldDeleteConstructor<Prof, T>()>::type* = nullptr>
- Archetype(T&&) = delete;
-
- // Disallow moves when requested, and disallow implicit conversions.
- template <class T, typename std::enable_if<
- ShouldDeleteAssign<Prof, T>()>::type* = nullptr>
- Archetype& operator=(T&&) = delete;
-
- using ArchetypeSpecialMembersBase<
- PropertiesOfT<Prof>::default_constructible_support,
- PropertiesOfT<Prof>::move_constructible_support,
- PropertiesOfT<Prof>::copy_constructible_support,
- PropertiesOfT<Prof>::move_assignable_support,
- PropertiesOfT<Prof>::copy_assignable_support,
- PropertiesOfT<Prof>::destructible_support>::archetype_state;
-
- private:
- explicit Archetype(MakeArchetypeState, ArchetypeState state) noexcept
- : ArchetypeSpecialMembersBase<
- PropertiesOfT<Prof>::default_constructible_support,
- PropertiesOfT<Prof>::move_constructible_support,
- PropertiesOfT<Prof>::copy_constructible_support,
- PropertiesOfT<Prof>::move_assignable_support,
- PropertiesOfT<Prof>::copy_assignable_support,
- PropertiesOfT<Prof>::destructible_support>(MakeArchetypeState(),
- state) {}
-
- friend Archetype MakeArchetype<Archetype>(ArchetypeState) noexcept;
-};
-
-template <class Prof>
-class Archetype<Prof, typename std::enable_if<
- PropertiesOfT<Prof>::move_constructible_support !=
- move_constructible::maybe &&
- PropertiesOfT<Prof>::move_assignable_support ==
- move_assignable::maybe &&
- PropertiesOfT<Prof>::destructible_support !=
- destructible::maybe>::type>
- : ArchetypeSpecialMembersBase<
- PropertiesOfT<Prof>::default_constructible_support,
- PropertiesOfT<Prof>::move_constructible_support,
- PropertiesOfT<Prof>::copy_constructible_support,
- PropertiesOfT<Prof>::move_assignable_support,
- PropertiesOfT<Prof>::copy_assignable_support,
- PropertiesOfT<Prof>::destructible_support> {
- public:
- Archetype() = default;
- Archetype(Archetype&&) = default;
- Archetype(const Archetype&) = default;
- Archetype& operator=(Archetype&&) = delete;
- Archetype& operator=(const Archetype&) = default;
-
- // Disallow moves when requested, and disallow implicit conversions.
- template <class T, typename std::enable_if<
- ShouldDeleteConstructor<Prof, T>()>::type* = nullptr>
- Archetype(T&&) = delete;
-
- // Disallow moves when requested, and disallow implicit conversions.
- template <class T, typename std::enable_if<
- ShouldDeleteAssign<Prof, T>()>::type* = nullptr>
- Archetype& operator=(T&&) = delete;
-
- using ArchetypeSpecialMembersBase<
- PropertiesOfT<Prof>::default_constructible_support,
- PropertiesOfT<Prof>::move_constructible_support,
- PropertiesOfT<Prof>::copy_constructible_support,
- PropertiesOfT<Prof>::move_assignable_support,
- PropertiesOfT<Prof>::copy_assignable_support,
- PropertiesOfT<Prof>::destructible_support>::archetype_state;
-
- private:
- explicit Archetype(MakeArchetypeState, ArchetypeState state) noexcept
- : ArchetypeSpecialMembersBase<
- PropertiesOfT<Prof>::default_constructible_support,
- PropertiesOfT<Prof>::move_constructible_support,
- PropertiesOfT<Prof>::copy_constructible_support,
- PropertiesOfT<Prof>::move_assignable_support,
- PropertiesOfT<Prof>::copy_assignable_support,
- PropertiesOfT<Prof>::destructible_support>(MakeArchetypeState(),
- state) {}
-
- friend Archetype MakeArchetype<Archetype>(ArchetypeState) noexcept;
-};
-
-template <class Prof>
-class Archetype<Prof, typename std::enable_if<
- PropertiesOfT<Prof>::move_constructible_support ==
- move_constructible::maybe &&
- PropertiesOfT<Prof>::move_assignable_support ==
- move_assignable::maybe &&
- PropertiesOfT<Prof>::destructible_support !=
- destructible::maybe>::type>
- : ArchetypeSpecialMembersBase<
- PropertiesOfT<Prof>::default_constructible_support,
- PropertiesOfT<Prof>::move_constructible_support,
- PropertiesOfT<Prof>::copy_constructible_support,
- PropertiesOfT<Prof>::move_assignable_support,
- PropertiesOfT<Prof>::copy_assignable_support,
- PropertiesOfT<Prof>::destructible_support> {
- public:
- Archetype() = default;
- Archetype(Archetype&&) = delete;
- Archetype(const Archetype&) = default;
- Archetype& operator=(Archetype&&) = delete;
- Archetype& operator=(const Archetype&) = default;
-
- // Disallow moves when requested, and disallow implicit conversions.
- template <class T, typename std::enable_if<
- ShouldDeleteConstructor<Prof, T>()>::type* = nullptr>
- Archetype(T&&) = delete;
-
- // Disallow moves when requested, and disallow implicit conversions.
- template <class T, typename std::enable_if<
- ShouldDeleteAssign<Prof, T>()>::type* = nullptr>
- Archetype& operator=(T&&) = delete;
-
- using ArchetypeSpecialMembersBase<
- PropertiesOfT<Prof>::default_constructible_support,
- PropertiesOfT<Prof>::move_constructible_support,
- PropertiesOfT<Prof>::copy_constructible_support,
- PropertiesOfT<Prof>::move_assignable_support,
- PropertiesOfT<Prof>::copy_assignable_support,
- PropertiesOfT<Prof>::destructible_support>::archetype_state;
-
- private:
- explicit Archetype(MakeArchetypeState, ArchetypeState state) noexcept
- : ArchetypeSpecialMembersBase<
- PropertiesOfT<Prof>::default_constructible_support,
- PropertiesOfT<Prof>::move_constructible_support,
- PropertiesOfT<Prof>::copy_constructible_support,
- PropertiesOfT<Prof>::move_assignable_support,
- PropertiesOfT<Prof>::copy_assignable_support,
- PropertiesOfT<Prof>::destructible_support>(MakeArchetypeState(),
- state) {}
-
- friend Archetype MakeArchetype<Archetype>(ArchetypeState) noexcept;
-};
-
-template <class Prof>
-class Archetype<Prof, typename std::enable_if<
- PropertiesOfT<Prof>::move_constructible_support ==
- move_constructible::maybe &&
- PropertiesOfT<Prof>::move_assignable_support !=
- move_assignable::maybe &&
- PropertiesOfT<Prof>::destructible_support !=
- destructible::maybe>::type>
- : ArchetypeSpecialMembersBase<
- PropertiesOfT<Prof>::default_constructible_support,
- PropertiesOfT<Prof>::move_constructible_support,
- PropertiesOfT<Prof>::copy_constructible_support,
- PropertiesOfT<Prof>::move_assignable_support,
- PropertiesOfT<Prof>::copy_assignable_support,
- PropertiesOfT<Prof>::destructible_support> {
- public:
- Archetype() = default;
- Archetype(Archetype&&) = delete;
- Archetype(const Archetype&) = default;
- Archetype& operator=(Archetype&&) = default;
- Archetype& operator=(const Archetype&) = default;
-
- // Disallow moves when requested, and disallow implicit conversions.
- template <class T, typename std::enable_if<
- ShouldDeleteConstructor<Prof, T>()>::type* = nullptr>
- Archetype(T&&) = delete;
-
- // Disallow moves when requested, and disallow implicit conversions.
- template <class T, typename std::enable_if<
- ShouldDeleteAssign<Prof, T>()>::type* = nullptr>
- Archetype& operator=(T&&) = delete;
-
- using ArchetypeSpecialMembersBase<
- PropertiesOfT<Prof>::default_constructible_support,
- PropertiesOfT<Prof>::move_constructible_support,
- PropertiesOfT<Prof>::copy_constructible_support,
- PropertiesOfT<Prof>::move_assignable_support,
- PropertiesOfT<Prof>::copy_assignable_support,
- PropertiesOfT<Prof>::destructible_support>::archetype_state;
-
- private:
- explicit Archetype(MakeArchetypeState, ArchetypeState state) noexcept
- : ArchetypeSpecialMembersBase<
- PropertiesOfT<Prof>::default_constructible_support,
- PropertiesOfT<Prof>::move_constructible_support,
- PropertiesOfT<Prof>::copy_constructible_support,
- PropertiesOfT<Prof>::move_assignable_support,
- PropertiesOfT<Prof>::copy_assignable_support,
- PropertiesOfT<Prof>::destructible_support>(MakeArchetypeState(),
- state) {}
-
- friend Archetype MakeArchetype<Archetype>(ArchetypeState) noexcept;
-};
-
-template <class Prof>
-class Archetype<Prof, typename std::enable_if<
- PropertiesOfT<Prof>::move_constructible_support !=
- move_constructible::maybe &&
- PropertiesOfT<Prof>::move_assignable_support ==
- move_assignable::maybe &&
- PropertiesOfT<Prof>::destructible_support ==
- destructible::maybe>::type>
- : ArchetypeSpecialMembersBase<
- PropertiesOfT<Prof>::default_constructible_support,
- PropertiesOfT<Prof>::move_constructible_support,
- PropertiesOfT<Prof>::copy_constructible_support,
- PropertiesOfT<Prof>::move_assignable_support,
- PropertiesOfT<Prof>::copy_assignable_support,
- PropertiesOfT<Prof>::destructible_support> {
- public:
- Archetype() = default;
- Archetype(Archetype&&) = default;
- Archetype(const Archetype&) = default;
- Archetype& operator=(Archetype&&) = delete;
- Archetype& operator=(const Archetype&) = default;
- ~Archetype() = delete;
-
- // Disallow moves when requested, and disallow implicit conversions.
- template <class T, typename std::enable_if<
- ShouldDeleteConstructor<Prof, T>()>::type* = nullptr>
- Archetype(T&&) = delete;
-
- // Disallow moves when requested, and disallow implicit conversions.
- template <class T, typename std::enable_if<
- ShouldDeleteAssign<Prof, T>()>::type* = nullptr>
- Archetype& operator=(T&&) = delete;
-
- using ArchetypeSpecialMembersBase<
- PropertiesOfT<Prof>::default_constructible_support,
- PropertiesOfT<Prof>::move_constructible_support,
- PropertiesOfT<Prof>::copy_constructible_support,
- PropertiesOfT<Prof>::move_assignable_support,
- PropertiesOfT<Prof>::copy_assignable_support,
- PropertiesOfT<Prof>::destructible_support>::archetype_state;
-
- private:
- explicit Archetype(MakeArchetypeState, ArchetypeState state) noexcept
- : ArchetypeSpecialMembersBase<
- PropertiesOfT<Prof>::default_constructible_support,
- PropertiesOfT<Prof>::move_constructible_support,
- PropertiesOfT<Prof>::copy_constructible_support,
- PropertiesOfT<Prof>::move_assignable_support,
- PropertiesOfT<Prof>::copy_assignable_support,
- PropertiesOfT<Prof>::destructible_support>(MakeArchetypeState(),
- state) {}
-
- friend Archetype MakeArchetype<Archetype>(ArchetypeState) noexcept;
-};
-
-template <class Prof>
-class Archetype<Prof, typename std::enable_if<
- PropertiesOfT<Prof>::move_constructible_support ==
- move_constructible::maybe &&
- PropertiesOfT<Prof>::move_assignable_support ==
- move_assignable::maybe &&
- PropertiesOfT<Prof>::destructible_support ==
- destructible::maybe>::type>
- : ArchetypeSpecialMembersBase<
- PropertiesOfT<Prof>::default_constructible_support,
- PropertiesOfT<Prof>::move_constructible_support,
- PropertiesOfT<Prof>::copy_constructible_support,
- PropertiesOfT<Prof>::move_assignable_support,
- PropertiesOfT<Prof>::copy_assignable_support,
- PropertiesOfT<Prof>::destructible_support> {
- public:
- Archetype() = default;
- Archetype(Archetype&&) = delete;
- Archetype(const Archetype&) = default;
- Archetype& operator=(Archetype&&) = delete;
- Archetype& operator=(const Archetype&) = default;
- ~Archetype() = delete;
-
- // Disallow moves when requested, and disallow implicit conversions.
- template <class T, typename std::enable_if<
- ShouldDeleteConstructor<Prof, T>()>::type* = nullptr>
- Archetype(T&&) = delete;
-
- // Disallow moves when requested, and disallow implicit conversions.
- template <class T, typename std::enable_if<
- ShouldDeleteAssign<Prof, T>()>::type* = nullptr>
- Archetype& operator=(T&&) = delete;
-
- using ArchetypeSpecialMembersBase<
- PropertiesOfT<Prof>::default_constructible_support,
- PropertiesOfT<Prof>::move_constructible_support,
- PropertiesOfT<Prof>::copy_constructible_support,
- PropertiesOfT<Prof>::move_assignable_support,
- PropertiesOfT<Prof>::copy_assignable_support,
- PropertiesOfT<Prof>::destructible_support>::archetype_state;
-
- private:
- explicit Archetype(MakeArchetypeState, ArchetypeState state) noexcept
- : ArchetypeSpecialMembersBase<
- PropertiesOfT<Prof>::default_constructible_support,
- PropertiesOfT<Prof>::move_constructible_support,
- PropertiesOfT<Prof>::copy_constructible_support,
- PropertiesOfT<Prof>::move_assignable_support,
- PropertiesOfT<Prof>::copy_assignable_support,
- PropertiesOfT<Prof>::destructible_support>(MakeArchetypeState(),
- state) {}
-
- friend Archetype MakeArchetype<Archetype>(ArchetypeState) noexcept;
-};
-
-template <class Prof>
-class Archetype<Prof, typename std::enable_if<
- PropertiesOfT<Prof>::move_constructible_support ==
- move_constructible::maybe &&
- PropertiesOfT<Prof>::move_assignable_support !=
- move_assignable::maybe &&
- PropertiesOfT<Prof>::destructible_support ==
- destructible::maybe>::type>
- : ArchetypeSpecialMembersBase<
- PropertiesOfT<Prof>::default_constructible_support,
- PropertiesOfT<Prof>::move_constructible_support,
- PropertiesOfT<Prof>::copy_constructible_support,
- PropertiesOfT<Prof>::move_assignable_support,
- PropertiesOfT<Prof>::copy_assignable_support,
- PropertiesOfT<Prof>::destructible_support> {
- public:
- Archetype() = default;
- Archetype(Archetype&&) = delete;
- Archetype(const Archetype&) = default;
- Archetype& operator=(Archetype&&) = default;
- Archetype& operator=(const Archetype&) = default;
- ~Archetype() = delete;
-
- // Disallow moves when requested, and disallow implicit conversions.
- template <class T, typename std::enable_if<
- ShouldDeleteConstructor<Prof, T>()>::type* = nullptr>
- Archetype(T&&) = delete;
-
- // Disallow moves when requested, and disallow implicit conversions.
- template <class T, typename std::enable_if<
- ShouldDeleteAssign<Prof, T>()>::type* = nullptr>
- Archetype& operator=(T&&) = delete;
-
- using ArchetypeSpecialMembersBase<
- PropertiesOfT<Prof>::default_constructible_support,
- PropertiesOfT<Prof>::move_constructible_support,
- PropertiesOfT<Prof>::copy_constructible_support,
- PropertiesOfT<Prof>::move_assignable_support,
- PropertiesOfT<Prof>::copy_assignable_support,
- PropertiesOfT<Prof>::destructible_support>::archetype_state;
-
- private:
- explicit Archetype(MakeArchetypeState, ArchetypeState state) noexcept
- : ArchetypeSpecialMembersBase<
- PropertiesOfT<Prof>::default_constructible_support,
- PropertiesOfT<Prof>::move_constructible_support,
- PropertiesOfT<Prof>::copy_constructible_support,
- PropertiesOfT<Prof>::move_assignable_support,
- PropertiesOfT<Prof>::copy_assignable_support,
- PropertiesOfT<Prof>::destructible_support>(MakeArchetypeState(),
- state) {}
-
- friend Archetype MakeArchetype<Archetype>(ArchetypeState) noexcept;
-};
-
-// Explicitly deleted swap for Archetype if the profile does not require swap.
-// It is important to delete it rather than simply leave it out so that the
-// "using std::swap;" idiom will result in this deleted overload being picked.
-template <class Prof,
- absl::enable_if_t<!PropertiesOfT<Prof>::is_swappable, int> = 0>
-void swap(Archetype<Prof>&, Archetype<Prof>&) = delete; // NOLINT
-
-// A conditionally-noexcept swap implementation for Archetype when the profile
-// supports swap.
-template <class Prof,
- absl::enable_if_t<PropertiesOfT<Prof>::is_swappable, int> = 0>
-void swap(Archetype<Prof>& lhs, Archetype<Prof>& rhs) // NOLINT
- noexcept(PropertiesOfT<Prof>::swappable_support != swappable::yes) {
- std::swap(lhs.archetype_state, rhs.archetype_state);
-}
-
-// A convertible-to-bool type that is used as the return type of comparison
-// operators since the standard doesn't always require exactly bool.
-struct NothrowBool {
- explicit NothrowBool() = delete;
- ~NothrowBool() = default;
-
- // TODO(calabrese) Delete the copy constructor in C++17 mode since guaranteed
- // elision makes it not required when returning from a function.
- // NothrowBool(NothrowBool const&) = delete;
-
- NothrowBool& operator=(NothrowBool const&) = delete;
-
- explicit operator bool() const noexcept { return value; }
-
- static NothrowBool make(bool const value) noexcept {
- return NothrowBool(value);
- }
-
- private:
- explicit NothrowBool(bool const value) noexcept : value(value) {}
-
- bool value;
-};
-
-// A convertible-to-bool type that is used as the return type of comparison
-// operators since the standard doesn't always require exactly bool.
-// Note: ExceptionalBool has a conversion operator that is not noexcept, so
-// that even when a comparison operator is noexcept, that operation may still
-// potentially throw when converted to bool.
-struct ExceptionalBool {
- explicit ExceptionalBool() = delete;
- ~ExceptionalBool() = default;
-
- // TODO(calabrese) Delete the copy constructor in C++17 mode since guaranteed
- // elision makes it not required when returning from a function.
- // ExceptionalBool(ExceptionalBool const&) = delete;
-
- ExceptionalBool& operator=(ExceptionalBool const&) = delete;
-
- explicit operator bool() const { return value; } // NOLINT
-
- static ExceptionalBool make(bool const value) noexcept {
- return ExceptionalBool(value);
- }
-
- private:
- explicit ExceptionalBool(bool const value) noexcept : value(value) {}
-
- bool value;
-};
-
-// The following macro is only used as a helper in this file to stamp out
-// comparison operator definitions. It is undefined after usage.
-//
-// NOTE: Non-nothrow operators throw via their result's conversion to bool even
-// though the operation itself is noexcept.
-#define ABSL_TYPES_INTERNAL_OP(enum_name, op) \
- template <class Prof> \
- absl::enable_if_t<!PropertiesOfT<Prof>::is_##enum_name, bool> operator op( \
- const Archetype<Prof>&, const Archetype<Prof>&) = delete; \
- \
- template <class Prof> \
- typename absl::enable_if_t< \
- PropertiesOfT<Prof>::is_##enum_name, \
- std::conditional<PropertiesOfT<Prof>::enum_name##_support == \
- enum_name::nothrow, \
- NothrowBool, ExceptionalBool>>::type \
- operator op(const Archetype<Prof>& lhs, \
- const Archetype<Prof>& rhs) noexcept { \
- return absl::conditional_t< \
- PropertiesOfT<Prof>::enum_name##_support == enum_name::nothrow, \
- NothrowBool, ExceptionalBool>::make(lhs.archetype_state op \
- rhs.archetype_state); \
- }
-
-ABSL_TYPES_INTERNAL_OP(equality_comparable, ==);
-ABSL_TYPES_INTERNAL_OP(inequality_comparable, !=);
-ABSL_TYPES_INTERNAL_OP(less_than_comparable, <);
-ABSL_TYPES_INTERNAL_OP(less_equal_comparable, <=);
-ABSL_TYPES_INTERNAL_OP(greater_equal_comparable, >=);
-ABSL_TYPES_INTERNAL_OP(greater_than_comparable, >);
-
-#undef ABSL_TYPES_INTERNAL_OP
-
-// Base class for std::hash specializations when an Archetype doesn't support
-// hashing.
-struct PoisonedHash {
- PoisonedHash() = delete;
- PoisonedHash(const PoisonedHash&) = delete;
- PoisonedHash& operator=(const PoisonedHash&) = delete;
-};
-
-// Base class for std::hash specializations when an Archetype supports hashing.
-template <class Prof>
-struct EnabledHash {
- using argument_type = Archetype<Prof>;
- using result_type = std::size_t;
- result_type operator()(const argument_type& arg) const {
- return std::hash<ArchetypeState>()(arg.archetype_state);
- }
-};
-
-} // namespace types_internal
-ABSL_NAMESPACE_END
-} // namespace absl
-
-namespace std {
-
-template <class Prof> // NOLINT
-struct hash<::absl::types_internal::Archetype<Prof>>
- : conditional<::absl::types_internal::PropertiesOfT<Prof>::is_hashable,
- ::absl::types_internal::EnabledHash<Prof>,
- ::absl::types_internal::PoisonedHash>::type {};
-
-} // namespace std
-
-#endif // ABSL_TYPES_INTERNAL_CONFORMANCE_ARCHETYPE_H_
diff --git a/absl/types/internal/conformance_profile.h b/absl/types/internal/conformance_profile.h
deleted file mode 100644
index cf64ff4..0000000
--- a/absl/types/internal/conformance_profile.h
+++ /dev/null
@@ -1,931 +0,0 @@
-// Copyright 2019 The Abseil Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-// -----------------------------------------------------------------------------
-// conformance_profiles.h
-// -----------------------------------------------------------------------------
-//
-// This file contains templates for representing "Regularity Profiles" and
-// concisely-named versions of commonly used Regularity Profiles.
-//
-// A Regularity Profile is a compile-time description of the types of operations
-// that a given type supports, along with properties of those operations when
-// they do exist. For instance, a Regularity Profile may describe a type that
-// has a move-constructor that is noexcept and a copy constructor that is not
-// noexcept. This description can then be examined and passed around to other
-// templates for the purposes of asserting expectations on user-defined types
-// via a series trait checks, or for determining what kinds of run-time tests
-// are able to be performed.
-//
-// Regularity Profiles are also used when creating "archetypes," which are
-// minimum-conforming types that meet all of the requirements of a given
-// Regularity Profile. For more information regarding archetypes, see
-// "conformance_archetypes.h".
-
-#ifndef ABSL_TYPES_INTERNAL_CONFORMANCE_PROFILE_H_
-#define ABSL_TYPES_INTERNAL_CONFORMANCE_PROFILE_H_
-
-#include <set>
-#include <type_traits>
-#include <utility>
-#include <vector>
-
-#include "gtest/gtest.h"
-#include "absl/algorithm/container.h"
-#include "absl/meta/type_traits.h"
-#include "absl/strings/ascii.h"
-#include "absl/strings/str_cat.h"
-#include "absl/strings/string_view.h"
-#include "absl/types/internal/conformance_testing_helpers.h"
-#include "absl/utility/utility.h"
-
-// TODO(calabrese) Add support for extending profiles.
-
-namespace absl {
-ABSL_NAMESPACE_BEGIN
-namespace types_internal {
-
-// Converts an enum to its underlying integral value.
-template <typename Enum>
-constexpr absl::underlying_type_t<Enum> UnderlyingValue(Enum value) {
- return static_cast<absl::underlying_type_t<Enum>>(value);
-}
-
-// A tag type used in place of a matcher when checking that an assertion result
-// does not actually contain any errors.
-struct NoError {};
-
-// -----------------------------------------------------------------------------
-// ConformanceErrors
-// -----------------------------------------------------------------------------
-class ConformanceErrors {
- public:
- // Setup the error reporting mechanism by seeding it with the name of the type
- // that is being tested.
- explicit ConformanceErrors(std::string type_name)
- : assertion_result_(false), type_name_(std::move(type_name)) {
- assertion_result_ << "\n\n"
- "Assuming the following type alias:\n"
- "\n"
- " using _T = "
- << type_name_ << ";\n\n";
- outputDivider();
- }
-
- // Adds the test name to the list of successfully run tests iff it was not
- // previously reported as failing. This behavior is useful for tests that
- // have multiple parts, where failures and successes are reported individually
- // with the same test name.
- void addTestSuccess(absl::string_view test_name) {
- auto normalized_test_name = absl::AsciiStrToLower(test_name);
-
- // If the test is already reported as failing, do not add it to the list of
- // successes.
- if (test_failures_.find(normalized_test_name) == test_failures_.end()) {
- test_successes_.insert(std::move(normalized_test_name));
- }
- }
-
- // Streams a single error description into the internal buffer (a visual
- // divider is automatically inserted after the error so that multiple errors
- // are visibly distinct).
- //
- // This function increases the error count by 1.
- //
- // TODO(calabrese) Determine desired behavior when if this function throws.
- template <class... P>
- void addTestFailure(absl::string_view test_name, const P&... args) {
- // Output a message related to the test failure.
- assertion_result_ << "\n\n"
- "Failed test: "
- << test_name << "\n\n";
- addTestFailureImpl(args...);
- assertion_result_ << "\n\n";
- outputDivider();
-
- auto normalized_test_name = absl::AsciiStrToLower(test_name);
-
- // If previous parts of this test succeeded, remove it from that set.
- test_successes_.erase(normalized_test_name);
-
- // Add the test name to the list of failed tests.
- test_failures_.insert(std::move(normalized_test_name));
-
- has_error_ = true;
- }
-
- // Convert this object into a testing::AssertionResult instance such that it
- // can be used with gtest.
- ::testing::AssertionResult assertionResult() const {
- return has_error_ ? assertion_result_ : ::testing::AssertionSuccess();
- }
-
- // Convert this object into a testing::AssertionResult instance such that it
- // can be used with gtest. This overload expects errors, using the specified
- // matcher.
- ::testing::AssertionResult expectFailedTests(
- const std::set<std::string>& test_names) const {
- // Since we are expecting nonconformance, output an error message when the
- // type actually conformed to the specified profile.
- if (!has_error_) {
- return ::testing::AssertionFailure()
- << "Unexpected conformance of type:\n"
- " "
- << type_name_ << "\n\n";
- }
-
- // Get a list of all expected failures that did not actually fail
- // (or that were not run).
- std::vector<std::string> nonfailing_tests;
- absl::c_set_difference(test_names, test_failures_,
- std::back_inserter(nonfailing_tests));
-
- // Get a list of all "expected failures" that were never actually run.
- std::vector<std::string> unrun_tests;
- absl::c_set_difference(nonfailing_tests, test_successes_,
- std::back_inserter(unrun_tests));
-
- // Report when the user specified tests that were not run.
- if (!unrun_tests.empty()) {
- const bool tests_were_run =
- !(test_failures_.empty() && test_successes_.empty());
-
- // Prepare an assertion result used in the case that tests pass that were
- // expected to fail.
- ::testing::AssertionResult result = ::testing::AssertionFailure();
- result << "When testing type:\n " << type_name_
- << "\n\nThe following tests were expected to fail but were not "
- "run";
-
- if (tests_were_run) result << " (was the test name spelled correctly?)";
-
- result << ":\n\n";
-
- // List all of the tests that unexpectedly passed.
- for (const auto& test_name : unrun_tests) {
- result << " " << test_name << "\n";
- }
-
- if (!tests_were_run) result << "\nNo tests were run.";
-
- if (!test_failures_.empty()) {
- // List test failures
- result << "\nThe tests that were run and failed are:\n\n";
- for (const auto& test_name : test_failures_) {
- result << " " << test_name << "\n";
- }
- }
-
- if (!test_successes_.empty()) {
- // List test successes
- result << "\nThe tests that were run and succeeded are:\n\n";
- for (const auto& test_name : test_successes_) {
- result << " " << test_name << "\n";
- }
- }
-
- return result;
- }
-
- // If some tests passed when they were expected to fail, alert the caller.
- if (nonfailing_tests.empty()) return ::testing::AssertionSuccess();
-
- // Prepare an assertion result used in the case that tests pass that were
- // expected to fail.
- ::testing::AssertionResult unexpected_successes =
- ::testing::AssertionFailure();
- unexpected_successes << "When testing type:\n " << type_name_
- << "\n\nThe following tests passed when they were "
- "expected to fail:\n\n";
-
- // List all of the tests that unexpectedly passed.
- for (const auto& test_name : nonfailing_tests) {
- unexpected_successes << " " << test_name << "\n";
- }
-
- return unexpected_successes;
- }
-
- private:
- void outputDivider() {
- assertion_result_ << "========================================";
- }
-
- void addTestFailureImpl() {}
-
- template <class H, class... T>
- void addTestFailureImpl(const H& head, const T&... tail) {
- assertion_result_ << head;
- addTestFailureImpl(tail...);
- }
-
- ::testing::AssertionResult assertion_result_;
- std::set<std::string> test_failures_;
- std::set<std::string> test_successes_;
- std::string type_name_;
- bool has_error_ = false;
-};
-
-template <class T, class /*Enabler*/ = void>
-struct PropertiesOfImpl {};
-
-template <class T>
-struct PropertiesOfImpl<T, absl::void_t<typename T::properties>> {
- using type = typename T::properties;
-};
-
-template <class T>
-struct PropertiesOfImpl<T, absl::void_t<typename T::profile_alias_of>> {
- using type = typename PropertiesOfImpl<typename T::profile_alias_of>::type;
-};
-
-template <class T>
-struct PropertiesOf : PropertiesOfImpl<T> {};
-
-template <class T>
-using PropertiesOfT = typename PropertiesOf<T>::type;
-
-// NOTE: These enums use this naming convention to be consistent with the
-// standard trait names, which is useful since it allows us to match up each
-// enum name with a corresponding trait name in macro definitions.
-
-// An enum that describes the various expectations on an operations existence.
-enum class function_support { maybe, yes, nothrow, trivial };
-
-constexpr const char* PessimisticPropertyDescription(function_support v) {
- return v == function_support::maybe
- ? "no"
- : v == function_support::yes
- ? "yes, potentially throwing"
- : v == function_support::nothrow ? "yes, nothrow"
- : "yes, trivial";
-}
-
-// Return a string that describes the kind of property support that was
-// expected.
-inline std::string ExpectedFunctionKindList(function_support min,
- function_support max) {
- if (min == max) {
- std::string result =
- absl::StrCat("Expected:\n ",
- PessimisticPropertyDescription(
- static_cast<function_support>(UnderlyingValue(min))),
- "\n");
- return result;
- }
-
- std::string result = "Expected one of:\n";
- for (auto curr_support = UnderlyingValue(min);
- curr_support <= UnderlyingValue(max); ++curr_support) {
- absl::StrAppend(&result, " ",
- PessimisticPropertyDescription(
- static_cast<function_support>(curr_support)),
- "\n");
- }
-
- return result;
-}
-
-template <class Enum>
-void ExpectModelOfImpl(ConformanceErrors* errors, Enum min_support,
- Enum max_support, Enum kind) {
- const auto kind_value = UnderlyingValue(kind);
- const auto min_support_value = UnderlyingValue(min_support);
- const auto max_support_value = UnderlyingValue(max_support);
-
- if (!(kind_value >= min_support_value && kind_value <= max_support_value)) {
- errors->addTestFailure(
- PropertyName(kind), "**Failed property expectation**\n\n",
- ExpectedFunctionKindList(
- static_cast<function_support>(min_support_value),
- static_cast<function_support>(max_support_value)),
- '\n', "Actual:\n ",
- PessimisticPropertyDescription(
- static_cast<function_support>(kind_value)));
- } else {
- errors->addTestSuccess(PropertyName(kind));
- }
-}
-
-#define ABSL_INTERNAL_SPECIAL_MEMBER_FUNCTION_ENUM(description, name) \
- enum class name { maybe, yes, nothrow, trivial }; \
- \
- constexpr const char* PropertyName(name v) { return description; } \
- static_assert(true, "") // Force a semicolon when using this macro.
-
-ABSL_INTERNAL_SPECIAL_MEMBER_FUNCTION_ENUM("support for default construction",
- default_constructible);
-ABSL_INTERNAL_SPECIAL_MEMBER_FUNCTION_ENUM("support for move construction",
- move_constructible);
-ABSL_INTERNAL_SPECIAL_MEMBER_FUNCTION_ENUM("support for copy construction",
- copy_constructible);
-ABSL_INTERNAL_SPECIAL_MEMBER_FUNCTION_ENUM("support for move assignment",
- move_assignable);
-ABSL_INTERNAL_SPECIAL_MEMBER_FUNCTION_ENUM("support for copy assignment",
- copy_assignable);
-ABSL_INTERNAL_SPECIAL_MEMBER_FUNCTION_ENUM("support for destruction",
- destructible);
-
-#undef ABSL_INTERNAL_SPECIAL_MEMBER_FUNCTION_ENUM
-
-#define ABSL_INTERNAL_INTRINSIC_FUNCTION_ENUM(description, name) \
- enum class name { maybe, yes, nothrow }; \
- \
- constexpr const char* PropertyName(name v) { return description; } \
- static_assert(true, "") // Force a semicolon when using this macro.
-
-ABSL_INTERNAL_INTRINSIC_FUNCTION_ENUM("support for ==", equality_comparable);
-ABSL_INTERNAL_INTRINSIC_FUNCTION_ENUM("support for !=", inequality_comparable);
-ABSL_INTERNAL_INTRINSIC_FUNCTION_ENUM("support for <", less_than_comparable);
-ABSL_INTERNAL_INTRINSIC_FUNCTION_ENUM("support for <=", less_equal_comparable);
-ABSL_INTERNAL_INTRINSIC_FUNCTION_ENUM("support for >=",
- greater_equal_comparable);
-ABSL_INTERNAL_INTRINSIC_FUNCTION_ENUM("support for >", greater_than_comparable);
-
-ABSL_INTERNAL_INTRINSIC_FUNCTION_ENUM("support for swap", swappable);
-
-#undef ABSL_INTERNAL_INTRINSIC_FUNCTION_ENUM
-
-enum class hashable { maybe, yes };
-
-constexpr const char* PropertyName(hashable v) {
- return "support for std::hash";
-}
-
-template <class T>
-using AlwaysFalse = std::false_type;
-
-#define ABSL_INTERNAL_PESSIMISTIC_MODEL_OF_SPECIAL_MEMBER(name, property) \
- template <class T> \
- constexpr property property##_support_of() { \
- return std::is_##property<T>::value \
- ? std::is_nothrow_##property<T>::value \
- ? absl::is_trivially_##property<T>::value \
- ? property::trivial \
- : property::nothrow \
- : property::yes \
- : property::maybe; \
- } \
- \
- template <class T, class MinProf, class MaxProf> \
- void ExpectModelOf##name(ConformanceErrors* errors) { \
- (ExpectModelOfImpl)(errors, PropertiesOfT<MinProf>::property##_support, \
- PropertiesOfT<MaxProf>::property##_support, \
- property##_support_of<T>()); \
- }
-
-ABSL_INTERNAL_PESSIMISTIC_MODEL_OF_SPECIAL_MEMBER(DefaultConstructible,
- default_constructible);
-
-ABSL_INTERNAL_PESSIMISTIC_MODEL_OF_SPECIAL_MEMBER(MoveConstructible,
- move_constructible);
-
-ABSL_INTERNAL_PESSIMISTIC_MODEL_OF_SPECIAL_MEMBER(CopyConstructible,
- copy_constructible);
-
-ABSL_INTERNAL_PESSIMISTIC_MODEL_OF_SPECIAL_MEMBER(MoveAssignable,
- move_assignable);
-
-ABSL_INTERNAL_PESSIMISTIC_MODEL_OF_SPECIAL_MEMBER(CopyAssignable,
- copy_assignable);
-
-ABSL_INTERNAL_PESSIMISTIC_MODEL_OF_SPECIAL_MEMBER(Destructible, destructible);
-
-#undef ABSL_INTERNAL_PESSIMISTIC_MODEL_OF_SPECIAL_MEMBER
-
-void BoolFunction(bool) noexcept;
-
-////////////////////////////////////////////////////////////////////////////////
-//
-// A metafunction for checking if an operation exists through SFINAE.
-//
-// `T` is the type to test and Op is an alias containing the expression to test.
-template <class T, template <class...> class Op, class = void>
-struct IsOpableImpl : std::false_type {};
-
-template <class T, template <class...> class Op>
-struct IsOpableImpl<T, Op, absl::void_t<Op<T>>> : std::true_type {};
-
-template <template <class...> class Op>
-struct IsOpable {
- template <class T>
- using apply = typename IsOpableImpl<T, Op>::type;
-};
-//
-////////////////////////////////////////////////////////////////////////////////
-
-////////////////////////////////////////////////////////////////////////////////
-//
-// A metafunction for checking if an operation exists and is also noexcept
-// through SFINAE and the noexcept operator.
-///
-// `T` is the type to test and Op is an alias containing the expression to test.
-template <class T, template <class...> class Op, class = void>
-struct IsNothrowOpableImpl : std::false_type {};
-
-template <class T, template <class...> class Op>
-struct IsNothrowOpableImpl<T, Op, absl::enable_if_t<Op<T>::value>>
- : std::true_type {};
-
-template <template <class...> class Op>
-struct IsNothrowOpable {
- template <class T>
- using apply = typename IsNothrowOpableImpl<T, Op>::type;
-};
-//
-////////////////////////////////////////////////////////////////////////////////
-
-////////////////////////////////////////////////////////////////////////////////
-//
-// A macro that produces the necessary function for reporting what kind of
-// support a specific comparison operation has and a function for reporting an
-// error if a given type's support for that operation does not meet the expected
-// requirements.
-#define ABSL_INTERNAL_PESSIMISTIC_MODEL_OF_COMPARISON(name, property, op) \
- template <class T, \
- class Result = std::integral_constant< \
- bool, noexcept((BoolFunction)(std::declval<const T&>() op \
- std::declval<const T&>()))>> \
- using name = Result; \
- \
- template <class T> \
- constexpr property property##_support_of() { \
- return IsOpable<name>::apply<T>::value \
- ? IsNothrowOpable<name>::apply<T>::value ? property::nothrow \
- : property::yes \
- : property::maybe; \
- } \
- \
- template <class T, class MinProf, class MaxProf> \
- void ExpectModelOf##name(ConformanceErrors* errors) { \
- (ExpectModelOfImpl)(errors, PropertiesOfT<MinProf>::property##_support, \
- PropertiesOfT<MaxProf>::property##_support, \
- property##_support_of<T>()); \
- }
-//
-////////////////////////////////////////////////////////////////////////////////
-
-////////////////////////////////////////////////////////////////////////////////
-//
-// Generate the necessary support-checking and error reporting functions for
-// each of the comparison operators.
-ABSL_INTERNAL_PESSIMISTIC_MODEL_OF_COMPARISON(EqualityComparable,
- equality_comparable, ==);
-
-ABSL_INTERNAL_PESSIMISTIC_MODEL_OF_COMPARISON(InequalityComparable,
- inequality_comparable, !=);
-
-ABSL_INTERNAL_PESSIMISTIC_MODEL_OF_COMPARISON(LessThanComparable,
- less_than_comparable, <);
-
-ABSL_INTERNAL_PESSIMISTIC_MODEL_OF_COMPARISON(LessEqualComparable,
- less_equal_comparable, <=);
-
-ABSL_INTERNAL_PESSIMISTIC_MODEL_OF_COMPARISON(GreaterEqualComparable,
- greater_equal_comparable, >=);
-
-ABSL_INTERNAL_PESSIMISTIC_MODEL_OF_COMPARISON(GreaterThanComparable,
- greater_than_comparable, >);
-
-#undef ABSL_INTERNAL_PESSIMISTIC_MODEL_OF_COMPARISON
-//
-////////////////////////////////////////////////////////////////////////////////
-
-////////////////////////////////////////////////////////////////////////////////
-//
-// The necessary support-checking and error-reporting functions for swap.
-template <class T>
-constexpr swappable swappable_support_of() {
- return type_traits_internal::IsSwappable<T>::value
- ? type_traits_internal::IsNothrowSwappable<T>::value
- ? swappable::nothrow
- : swappable::yes
- : swappable::maybe;
-}
-
-template <class T, class MinProf, class MaxProf>
-void ExpectModelOfSwappable(ConformanceErrors* errors) {
- (ExpectModelOfImpl)(errors, PropertiesOfT<MinProf>::swappable_support,
- PropertiesOfT<MaxProf>::swappable_support,
- swappable_support_of<T>());
-}
-//
-////////////////////////////////////////////////////////////////////////////////
-
-////////////////////////////////////////////////////////////////////////////////
-//
-// The necessary support-checking and error-reporting functions for std::hash.
-template <class T>
-constexpr hashable hashable_support_of() {
- return type_traits_internal::IsHashable<T>::value ? hashable::yes
- : hashable::maybe;
-}
-
-template <class T, class MinProf, class MaxProf>
-void ExpectModelOfHashable(ConformanceErrors* errors) {
- (ExpectModelOfImpl)(errors, PropertiesOfT<MinProf>::hashable_support,
- PropertiesOfT<MaxProf>::hashable_support,
- hashable_support_of<T>());
-}
-//
-////////////////////////////////////////////////////////////////////////////////
-
-template <
- default_constructible DefaultConstructibleValue =
- default_constructible::maybe,
- move_constructible MoveConstructibleValue = move_constructible::maybe,
- copy_constructible CopyConstructibleValue = copy_constructible::maybe,
- move_assignable MoveAssignableValue = move_assignable::maybe,
- copy_assignable CopyAssignableValue = copy_assignable::maybe,
- destructible DestructibleValue = destructible::maybe,
- equality_comparable EqualityComparableValue = equality_comparable::maybe,
- inequality_comparable InequalityComparableValue =
- inequality_comparable::maybe,
- less_than_comparable LessThanComparableValue = less_than_comparable::maybe,
- less_equal_comparable LessEqualComparableValue =
- less_equal_comparable::maybe,
- greater_equal_comparable GreaterEqualComparableValue =
- greater_equal_comparable::maybe,
- greater_than_comparable GreaterThanComparableValue =
- greater_than_comparable::maybe,
- swappable SwappableValue = swappable::maybe,
- hashable HashableValue = hashable::maybe>
-struct ConformanceProfile {
- using properties = ConformanceProfile;
-
- static constexpr default_constructible
- default_constructible_support = // NOLINT
- DefaultConstructibleValue;
-
- static constexpr move_constructible move_constructible_support = // NOLINT
- MoveConstructibleValue;
-
- static constexpr copy_constructible copy_constructible_support = // NOLINT
- CopyConstructibleValue;
-
- static constexpr move_assignable move_assignable_support = // NOLINT
- MoveAssignableValue;
-
- static constexpr copy_assignable copy_assignable_support = // NOLINT
- CopyAssignableValue;
-
- static constexpr destructible destructible_support = // NOLINT
- DestructibleValue;
-
- static constexpr equality_comparable equality_comparable_support = // NOLINT
- EqualityComparableValue;
-
- static constexpr inequality_comparable
- inequality_comparable_support = // NOLINT
- InequalityComparableValue;
-
- static constexpr less_than_comparable
- less_than_comparable_support = // NOLINT
- LessThanComparableValue;
-
- static constexpr less_equal_comparable
- less_equal_comparable_support = // NOLINT
- LessEqualComparableValue;
-
- static constexpr greater_equal_comparable
- greater_equal_comparable_support = // NOLINT
- GreaterEqualComparableValue;
-
- static constexpr greater_than_comparable
- greater_than_comparable_support = // NOLINT
- GreaterThanComparableValue;
-
- static constexpr swappable swappable_support = SwappableValue; // NOLINT
-
- static constexpr hashable hashable_support = HashableValue; // NOLINT
-
- static constexpr bool is_default_constructible = // NOLINT
- DefaultConstructibleValue != default_constructible::maybe;
-
- static constexpr bool is_move_constructible = // NOLINT
- MoveConstructibleValue != move_constructible::maybe;
-
- static constexpr bool is_copy_constructible = // NOLINT
- CopyConstructibleValue != copy_constructible::maybe;
-
- static constexpr bool is_move_assignable = // NOLINT
- MoveAssignableValue != move_assignable::maybe;
-
- static constexpr bool is_copy_assignable = // NOLINT
- CopyAssignableValue != copy_assignable::maybe;
-
- static constexpr bool is_destructible = // NOLINT
- DestructibleValue != destructible::maybe;
-
- static constexpr bool is_equality_comparable = // NOLINT
- EqualityComparableValue != equality_comparable::maybe;
-
- static constexpr bool is_inequality_comparable = // NOLINT
- InequalityComparableValue != inequality_comparable::maybe;
-
- static constexpr bool is_less_than_comparable = // NOLINT
- LessThanComparableValue != less_than_comparable::maybe;
-
- static constexpr bool is_less_equal_comparable = // NOLINT
- LessEqualComparableValue != less_equal_comparable::maybe;
-
- static constexpr bool is_greater_equal_comparable = // NOLINT
- GreaterEqualComparableValue != greater_equal_comparable::maybe;
-
- static constexpr bool is_greater_than_comparable = // NOLINT
- GreaterThanComparableValue != greater_than_comparable::maybe;
-
- static constexpr bool is_swappable = // NOLINT
- SwappableValue != swappable::maybe;
-
- static constexpr bool is_hashable = // NOLINT
- HashableValue != hashable::maybe;
-};
-
-////////////////////////////////////////////////////////////////////////////////
-//
-// Compliant SFINAE-friendliness is not always present on the standard library
-// implementations that we support. This helper-struct (and associated enum) is
-// used as a means to conditionally check the hashability support of a type.
-enum class CheckHashability { no, yes };
-
-template <class T, CheckHashability ShouldCheckHashability>
-struct conservative_hashable_support_of;
-
-template <class T>
-struct conservative_hashable_support_of<T, CheckHashability::no> {
- static constexpr hashable Invoke() { return hashable::maybe; }
-};
-
-template <class T>
-struct conservative_hashable_support_of<T, CheckHashability::yes> {
- static constexpr hashable Invoke() { return hashable_support_of<T>(); }
-};
-//
-////////////////////////////////////////////////////////////////////////////////
-
-// The ConformanceProfile that is expected based on introspection into the type
-// by way of trait checks.
-template <class T, CheckHashability ShouldCheckHashability>
-struct SyntacticConformanceProfileOf {
- using properties = ConformanceProfile<
- default_constructible_support_of<T>(), move_constructible_support_of<T>(),
- copy_constructible_support_of<T>(), move_assignable_support_of<T>(),
- copy_assignable_support_of<T>(), destructible_support_of<T>(),
- equality_comparable_support_of<T>(),
- inequality_comparable_support_of<T>(),
- less_than_comparable_support_of<T>(),
- less_equal_comparable_support_of<T>(),
- greater_equal_comparable_support_of<T>(),
- greater_than_comparable_support_of<T>(), swappable_support_of<T>(),
- conservative_hashable_support_of<T, ShouldCheckHashability>::Invoke()>;
-};
-
-#define ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF_IMPL(type, name) \
- template <default_constructible DefaultConstructibleValue, \
- move_constructible MoveConstructibleValue, \
- copy_constructible CopyConstructibleValue, \
- move_assignable MoveAssignableValue, \
- copy_assignable CopyAssignableValue, \
- destructible DestructibleValue, \
- equality_comparable EqualityComparableValue, \
- inequality_comparable InequalityComparableValue, \
- less_than_comparable LessThanComparableValue, \
- less_equal_comparable LessEqualComparableValue, \
- greater_equal_comparable GreaterEqualComparableValue, \
- greater_than_comparable GreaterThanComparableValue, \
- swappable SwappableValue, hashable HashableValue> \
- constexpr type ConformanceProfile< \
- DefaultConstructibleValue, MoveConstructibleValue, \
- CopyConstructibleValue, MoveAssignableValue, CopyAssignableValue, \
- DestructibleValue, EqualityComparableValue, InequalityComparableValue, \
- LessThanComparableValue, LessEqualComparableValue, \
- GreaterEqualComparableValue, GreaterThanComparableValue, SwappableValue, \
- HashableValue>::name
-
-#define ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF(type) \
- ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF_IMPL(type, \
- type##_support); \
- ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF_IMPL(bool, is_##type)
-
-ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF(default_constructible);
-ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF(move_constructible);
-ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF(copy_constructible);
-ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF(move_assignable);
-ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF(copy_assignable);
-ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF(destructible);
-ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF(equality_comparable);
-ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF(inequality_comparable);
-ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF(less_than_comparable);
-ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF(less_equal_comparable);
-ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF(greater_equal_comparable);
-ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF(greater_than_comparable);
-ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF(swappable);
-ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF(hashable);
-
-#undef ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF
-#undef ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF_IMPL
-
-// Retrieve the enum with the minimum underlying value.
-// Note: std::min is not constexpr in C++11, which is why this is necessary.
-template <class H>
-constexpr H MinEnum(H head) {
- return head;
-}
-
-template <class H, class N, class... T>
-constexpr H MinEnum(H head, N next, T... tail) {
- return (UnderlyingValue)(head) < (UnderlyingValue)(next)
- ? (MinEnum)(head, tail...)
- : (MinEnum)(next, tail...);
-}
-
-template <class... Profs>
-struct MinimalProfiles {
- static constexpr default_constructible
- default_constructible_support = // NOLINT
- (MinEnum)(PropertiesOfT<Profs>::default_constructible_support...);
-
- static constexpr move_constructible move_constructible_support = // NOLINT
- (MinEnum)(PropertiesOfT<Profs>::move_constructible_support...);
-
- static constexpr copy_constructible copy_constructible_support = // NOLINT
- (MinEnum)(PropertiesOfT<Profs>::copy_constructible_support...);
-
- static constexpr move_assignable move_assignable_support = // NOLINT
- (MinEnum)(PropertiesOfT<Profs>::move_assignable_support...);
-
- static constexpr copy_assignable copy_assignable_support = // NOLINT
- (MinEnum)(PropertiesOfT<Profs>::copy_assignable_support...);
-
- static constexpr destructible destructible_support = // NOLINT
- (MinEnum)(PropertiesOfT<Profs>::destructible_support...);
-
- static constexpr equality_comparable equality_comparable_support = // NOLINT
- (MinEnum)(PropertiesOfT<Profs>::equality_comparable_support...);
-
- static constexpr inequality_comparable
- inequality_comparable_support = // NOLINT
- (MinEnum)(PropertiesOfT<Profs>::inequality_comparable_support...);
-
- static constexpr less_than_comparable
- less_than_comparable_support = // NOLINT
- (MinEnum)(PropertiesOfT<Profs>::less_than_comparable_support...);
-
- static constexpr less_equal_comparable
- less_equal_comparable_support = // NOLINT
- (MinEnum)(PropertiesOfT<Profs>::less_equal_comparable_support...);
-
- static constexpr greater_equal_comparable
- greater_equal_comparable_support = // NOLINT
- (MinEnum)(PropertiesOfT<Profs>::greater_equal_comparable_support...);
-
- static constexpr greater_than_comparable
- greater_than_comparable_support = // NOLINT
- (MinEnum)(PropertiesOfT<Profs>::greater_than_comparable_support...);
-
- static constexpr swappable swappable_support = // NOLINT
- (MinEnum)(PropertiesOfT<Profs>::swappable_support...);
-
- static constexpr hashable hashable_support = // NOLINT
- (MinEnum)(PropertiesOfT<Profs>::hashable_support...);
-
- using properties = ConformanceProfile<
- default_constructible_support, move_constructible_support,
- copy_constructible_support, move_assignable_support,
- copy_assignable_support, destructible_support,
- equality_comparable_support, inequality_comparable_support,
- less_than_comparable_support, less_equal_comparable_support,
- greater_equal_comparable_support, greater_than_comparable_support,
- swappable_support, hashable_support>;
-};
-
-// Retrieve the enum with the greatest underlying value.
-// Note: std::max is not constexpr in C++11, which is why this is necessary.
-template <class H>
-constexpr H MaxEnum(H head) {
- return head;
-}
-
-template <class H, class N, class... T>
-constexpr H MaxEnum(H head, N next, T... tail) {
- return (UnderlyingValue)(next) < (UnderlyingValue)(head)
- ? (MaxEnum)(head, tail...)
- : (MaxEnum)(next, tail...);
-}
-
-template <class... Profs>
-struct CombineProfilesImpl {
- static constexpr default_constructible
- default_constructible_support = // NOLINT
- (MaxEnum)(PropertiesOfT<Profs>::default_constructible_support...);
-
- static constexpr move_constructible move_constructible_support = // NOLINT
- (MaxEnum)(PropertiesOfT<Profs>::move_constructible_support...);
-
- static constexpr copy_constructible copy_constructible_support = // NOLINT
- (MaxEnum)(PropertiesOfT<Profs>::copy_constructible_support...);
-
- static constexpr move_assignable move_assignable_support = // NOLINT
- (MaxEnum)(PropertiesOfT<Profs>::move_assignable_support...);
-
- static constexpr copy_assignable copy_assignable_support = // NOLINT
- (MaxEnum)(PropertiesOfT<Profs>::copy_assignable_support...);
-
- static constexpr destructible destructible_support = // NOLINT
- (MaxEnum)(PropertiesOfT<Profs>::destructible_support...);
-
- static constexpr equality_comparable equality_comparable_support = // NOLINT
- (MaxEnum)(PropertiesOfT<Profs>::equality_comparable_support...);
-
- static constexpr inequality_comparable
- inequality_comparable_support = // NOLINT
- (MaxEnum)(PropertiesOfT<Profs>::inequality_comparable_support...);
-
- static constexpr less_than_comparable
- less_than_comparable_support = // NOLINT
- (MaxEnum)(PropertiesOfT<Profs>::less_than_comparable_support...);
-
- static constexpr less_equal_comparable
- less_equal_comparable_support = // NOLINT
- (MaxEnum)(PropertiesOfT<Profs>::less_equal_comparable_support...);
-
- static constexpr greater_equal_comparable
- greater_equal_comparable_support = // NOLINT
- (MaxEnum)(PropertiesOfT<Profs>::greater_equal_comparable_support...);
-
- static constexpr greater_than_comparable
- greater_than_comparable_support = // NOLINT
- (MaxEnum)(PropertiesOfT<Profs>::greater_than_comparable_support...);
-
- static constexpr swappable swappable_support = // NOLINT
- (MaxEnum)(PropertiesOfT<Profs>::swappable_support...);
-
- static constexpr hashable hashable_support = // NOLINT
- (MaxEnum)(PropertiesOfT<Profs>::hashable_support...);
-
- using properties = ConformanceProfile<
- default_constructible_support, move_constructible_support,
- copy_constructible_support, move_assignable_support,
- copy_assignable_support, destructible_support,
- equality_comparable_support, inequality_comparable_support,
- less_than_comparable_support, less_equal_comparable_support,
- greater_equal_comparable_support, greater_than_comparable_support,
- swappable_support, hashable_support>;
-};
-
-// NOTE: We use this as opposed to a direct alias of CombineProfilesImpl so that
-// when named aliases of CombineProfiles are created (such as in
-// conformance_aliases.h), we only pay for the combination algorithm on the
-// profiles that are actually used.
-template <class... Profs>
-struct CombineProfiles {
- using profile_alias_of = CombineProfilesImpl<Profs...>;
-};
-
-template <>
-struct CombineProfiles<> {
- using properties = ConformanceProfile<>;
-};
-
-template <class Profile, class Tag>
-struct StrongProfileTypedef {
- using properties = PropertiesOfT<Profile>;
-};
-
-template <class T, class /*Enabler*/ = void>
-struct IsProfileImpl : std::false_type {};
-
-template <class T>
-struct IsProfileImpl<T, absl::void_t<PropertiesOfT<T>>> : std::true_type {};
-
-template <class T>
-struct IsProfile : IsProfileImpl<T>::type {};
-
-// A tag that describes which set of properties we will check when the user
-// requires a strict match in conformance (as opposed to a loose match which
-// allows more-refined support of any given operation).
-//
-// Currently only the RegularityDomain exists and it includes all operations
-// that the conformance testing suite knows about. The intent is that if the
-// suite is expanded to support extension, such as for checking conformance of
-// concepts like Iterators or Containers, additional corresponding domains can
-// be created.
-struct RegularityDomain {};
-
-} // namespace types_internal
-ABSL_NAMESPACE_END
-} // namespace absl
-
-#endif // ABSL_TYPES_INTERNAL_CONFORMANCE_PROFILE_H_
diff --git a/absl/types/internal/conformance_testing.h b/absl/types/internal/conformance_testing.h
deleted file mode 100644
index 487b0f7..0000000
--- a/absl/types/internal/conformance_testing.h
+++ /dev/null
@@ -1,1386 +0,0 @@
-// Copyright 2019 The Abseil Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-// -----------------------------------------------------------------------------
-// conformance_testing.h
-// -----------------------------------------------------------------------------
-//
-
-#ifndef ABSL_TYPES_INTERNAL_CONFORMANCE_TESTING_H_
-#define ABSL_TYPES_INTERNAL_CONFORMANCE_TESTING_H_
-
-////////////////////////////////////////////////////////////////////////////////
-// //
-// Many templates in this file take a `T` and a `Prof` type as explicit //
-// template arguments. These are a type to be checked and a //
-// "Regularity Profile" that describes what operations that type `T` is //
-// expected to support. See "regularity_profiles.h" for more details //
-// regarding Regularity Profiles. //
-// //
-////////////////////////////////////////////////////////////////////////////////
-
-#include <cstddef>
-#include <set>
-#include <tuple>
-#include <type_traits>
-#include <utility>
-
-#include "gtest/gtest.h"
-#include "absl/meta/type_traits.h"
-#include "absl/strings/ascii.h"
-#include "absl/strings/str_cat.h"
-#include "absl/strings/string_view.h"
-#include "absl/types/internal/conformance_aliases.h"
-#include "absl/types/internal/conformance_archetype.h"
-#include "absl/types/internal/conformance_profile.h"
-#include "absl/types/internal/conformance_testing_helpers.h"
-#include "absl/types/internal/parentheses.h"
-#include "absl/types/internal/transform_args.h"
-#include "absl/utility/utility.h"
-
-namespace absl {
-ABSL_NAMESPACE_BEGIN
-namespace types_internal {
-
-// Returns true if the compiler incorrectly greedily instantiates constexpr
-// templates in any unevaluated context.
-constexpr bool constexpr_instantiation_when_unevaluated() {
-#if defined(__apple_build_version__) // TODO(calabrese) Make more specific
- return true;
-#elif defined(__clang__)
- return __clang_major__ < 4;
-#elif defined(__GNUC__)
- // TODO(calabrese) Figure out why gcc 7 fails (seems like a different bug)
- return __GNUC__ < 5 || (__GNUC__ == 5 && __GNUC_MINOR__ < 2) || __GNUC__ >= 7;
-#else
- return false;
-#endif
-}
-
-// Returns true if the standard library being used incorrectly produces an error
-// when instantiating the definition of a poisoned std::hash specialization.
-constexpr bool poisoned_hash_fails_instantiation() {
-#if defined(_MSC_VER) && !defined(_LIBCPP_VERSION)
- return _MSC_VER < 1914;
-#else
- return false;
-#endif
-}
-
-template <class Fun>
-struct GeneratorType {
- decltype(std::declval<const Fun&>()()) operator()() const
- noexcept(noexcept(std::declval<const Fun&>()())) {
- return fun();
- }
-
- Fun fun;
- const char* description;
-};
-
-// A "make" function for the GeneratorType template that deduces the function
-// object type.
-template <class Fun,
- absl::enable_if_t<IsNullaryCallable<Fun>::value>** = nullptr>
-GeneratorType<Fun> Generator(Fun fun, const char* description) {
- return GeneratorType<Fun>{absl::move(fun), description};
-}
-
-// A type that contains a set of nullary function objects that each return an
-// instance of the same type and value (though possibly different
-// representations, such as +0 and -0 or two vectors with the same elements but
-// with different capacities).
-template <class... Funs>
-struct EquivalenceClassType {
- std::tuple<GeneratorType<Funs>...> generators;
-};
-
-// A "make" function for the EquivalenceClassType template that deduces the
-// function object types and is constrained such that a user can only pass in
-// function objects that all have the same return type.
-template <class... Funs, absl::enable_if_t<AreGeneratorsWithTheSameReturnType<
- Funs...>::value>** = nullptr>
-EquivalenceClassType<Funs...> EquivalenceClass(GeneratorType<Funs>... funs) {
- return {std::make_tuple(absl::move(funs)...)};
-}
-
-// A type that contains an ordered series of EquivalenceClassTypes, from
-// smallest value to largest value.
-template <class... EqClasses>
-struct OrderedEquivalenceClasses {
- std::tuple<EqClasses...> eq_classes;
-};
-
-// An object containing the parts of a given (name, initialization expression),
-// and is capable of generating a string that describes the given.
-struct GivenDeclaration {
- std::string outputDeclaration(std::size_t width) const {
- const std::size_t indent_size = 2;
- std::string result = absl::StrCat(" ", name);
-
- if (!expression.empty()) {
- // Indent
- result.resize(indent_size + width, ' ');
- absl::StrAppend(&result, " = ", expression, ";\n");
- } else {
- absl::StrAppend(&result, ";\n");
- }
-
- return result;
- }
-
- std::string name;
- std::string expression;
-};
-
-// Produce a string that contains all of the givens of an error report.
-template <class... Decls>
-std::string PrepareGivenContext(const Decls&... decls) {
- const std::size_t width = (std::max)({decls.name.size()...});
- return absl::StrCat("Given:\n", decls.outputDeclaration(width)..., "\n");
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// Function objects that perform a check for each comparison operator //
-////////////////////////////////////////////////////////////////////////////////
-
-#define ABSL_INTERNAL_EXPECT_OP(name, op) \
- struct Expect##name { \
- template <class T> \
- void operator()(absl::string_view test_name, absl::string_view context, \
- const T& lhs, const T& rhs, absl::string_view lhs_name, \
- absl::string_view rhs_name) const { \
- if (!static_cast<bool>(lhs op rhs)) { \
- errors->addTestFailure( \
- test_name, absl::StrCat(context, \
- "**Unexpected comparison result**\n" \
- "\n" \
- "Expression:\n" \
- " ", \
- lhs_name, " " #op " ", rhs_name, \
- "\n" \
- "\n" \
- "Expected: true\n" \
- " Actual: false")); \
- } else { \
- errors->addTestSuccess(test_name); \
- } \
- } \
- \
- ConformanceErrors* errors; \
- }; \
- \
- struct ExpectNot##name { \
- template <class T> \
- void operator()(absl::string_view test_name, absl::string_view context, \
- const T& lhs, const T& rhs, absl::string_view lhs_name, \
- absl::string_view rhs_name) const { \
- if (lhs op rhs) { \
- errors->addTestFailure( \
- test_name, absl::StrCat(context, \
- "**Unexpected comparison result**\n" \
- "\n" \
- "Expression:\n" \
- " ", \
- lhs_name, " " #op " ", rhs_name, \
- "\n" \
- "\n" \
- "Expected: false\n" \
- " Actual: true")); \
- } else { \
- errors->addTestSuccess(test_name); \
- } \
- } \
- \
- ConformanceErrors* errors; \
- }
-
-ABSL_INTERNAL_EXPECT_OP(Eq, ==);
-ABSL_INTERNAL_EXPECT_OP(Ne, !=);
-ABSL_INTERNAL_EXPECT_OP(Lt, <);
-ABSL_INTERNAL_EXPECT_OP(Le, <=);
-ABSL_INTERNAL_EXPECT_OP(Ge, >=);
-ABSL_INTERNAL_EXPECT_OP(Gt, >);
-
-#undef ABSL_INTERNAL_EXPECT_OP
-
-// A function object that verifies that two objects hash to the same value by
-// way of the std::hash specialization.
-struct ExpectSameHash {
- template <class T>
- void operator()(absl::string_view test_name, absl::string_view context,
- const T& lhs, const T& rhs, absl::string_view lhs_name,
- absl::string_view rhs_name) const {
- if (std::hash<T>()(lhs) != std::hash<T>()(rhs)) {
- errors->addTestFailure(
- test_name, absl::StrCat(context,
- "**Unexpected hash result**\n"
- "\n"
- "Expression:\n"
- " std::hash<T>()(",
- lhs_name, ") == std::hash<T>()(", rhs_name,
- ")\n"
- "\n"
- "Expected: true\n"
- " Actual: false"));
- } else {
- errors->addTestSuccess(test_name);
- }
- }
-
- ConformanceErrors* errors;
-};
-
-// A function template that takes two objects and verifies that each comparison
-// operator behaves in a way that is consistent with equality. It has "OneWay"
-// in the name because the first argument will always be the left-hand operand
-// of the corresponding comparison operator and the second argument will
-// always be the right-hand operand. It will never switch that order.
-// At a higher level in the test suite, the one-way form is called once for each
-// of the two possible orders whenever lhs and rhs are not the same initializer.
-template <class T, class Prof>
-void ExpectOneWayEquality(ConformanceErrors* errors,
- absl::string_view test_name,
- absl::string_view context, const T& lhs, const T& rhs,
- absl::string_view lhs_name,
- absl::string_view rhs_name) {
- If<PropertiesOfT<Prof>::is_equality_comparable>::Invoke(
- ExpectEq{errors}, test_name, context, lhs, rhs, lhs_name, rhs_name);
-
- If<PropertiesOfT<Prof>::is_inequality_comparable>::Invoke(
- ExpectNotNe{errors}, test_name, context, lhs, rhs, lhs_name, rhs_name);
-
- If<PropertiesOfT<Prof>::is_less_than_comparable>::Invoke(
- ExpectNotLt{errors}, test_name, context, lhs, rhs, lhs_name, rhs_name);
-
- If<PropertiesOfT<Prof>::is_less_equal_comparable>::Invoke(
- ExpectLe{errors}, test_name, context, lhs, rhs, lhs_name, rhs_name);
-
- If<PropertiesOfT<Prof>::is_greater_equal_comparable>::Invoke(
- ExpectGe{errors}, test_name, context, lhs, rhs, lhs_name, rhs_name);
-
- If<PropertiesOfT<Prof>::is_greater_than_comparable>::Invoke(
- ExpectNotGt{errors}, test_name, context, lhs, rhs, lhs_name, rhs_name);
-
- If<PropertiesOfT<Prof>::is_hashable>::Invoke(
- ExpectSameHash{errors}, test_name, context, lhs, rhs, lhs_name, rhs_name);
-}
-
-// A function template that takes two objects and verifies that each comparison
-// operator behaves in a way that is consistent with equality. This function
-// differs from ExpectOneWayEquality in that this will do checks with argument
-// order reversed in addition to in-order.
-template <class T, class Prof>
-void ExpectEquality(ConformanceErrors* errors, absl::string_view test_name,
- absl::string_view context, const T& lhs, const T& rhs,
- absl::string_view lhs_name, absl::string_view rhs_name) {
- (ExpectOneWayEquality<T, Prof>)(errors, test_name, context, lhs, rhs,
- lhs_name, rhs_name);
- (ExpectOneWayEquality<T, Prof>)(errors, test_name, context, rhs, lhs,
- rhs_name, lhs_name);
-}
-
-// Given a generator, makes sure that a generated value and a moved-from
-// generated value are equal.
-template <class T, class Prof>
-struct ExpectMoveConstructOneGenerator {
- template <class Fun>
- void operator()(const Fun& generator) const {
- const T object = generator();
- const T moved_object = absl::move(generator()); // Force no elision.
-
- (ExpectEquality<T, Prof>)(errors, "Move construction",
- PrepareGivenContext(
- GivenDeclaration{"const _T object",
- generator.description},
- GivenDeclaration{"const _T moved_object",
- std::string("std::move(") +
- generator.description +
- ")"}),
- object, moved_object, "object", "moved_object");
- }
-
- ConformanceErrors* errors;
-};
-
-// Given a generator, makes sure that a generated value and a copied-from
-// generated value are equal.
-template <class T, class Prof>
-struct ExpectCopyConstructOneGenerator {
- template <class Fun>
- void operator()(const Fun& generator) const {
- const T object = generator();
- const T copied_object = static_cast<const T&>(generator());
-
- (ExpectEquality<T, Prof>)(errors, "Copy construction",
- PrepareGivenContext(
- GivenDeclaration{"const _T object",
- generator.description},
- GivenDeclaration{
- "const _T copied_object",
- std::string("static_cast<const _T&>(") +
- generator.description + ")"}),
- object, copied_object, "object", "copied_object");
- }
-
- ConformanceErrors* errors;
-};
-
-// Default-construct and do nothing before destruction.
-//
-// This is useful in exercising the codepath of default construction followed by
-// destruction, but does not explicitly test anything. An example of where this
-// might fail is a default destructor that default-initializes a scalar and a
-// destructor reads the value of that member. Sanitizers can catch this as long
-// as our test attempts to execute such a case.
-template <class T>
-struct ExpectDefaultConstructWithDestruct {
- void operator()() const {
- // Scoped so that destructor gets called before reporting success.
- {
- T object;
- static_cast<void>(object);
- }
-
- errors->addTestSuccess("Default construction");
- }
-
- ConformanceErrors* errors;
-};
-
-// Check move-assign into a default-constructed object.
-template <class T, class Prof>
-struct ExpectDefaultConstructWithMoveAssign {
- template <class Fun>
- void operator()(const Fun& generator) const {
- const T source_of_truth = generator();
- T object;
- object = generator();
-
- (ExpectEquality<T, Prof>)(errors, "Move assignment",
- PrepareGivenContext(
- GivenDeclaration{"const _T object",
- generator.description},
- GivenDeclaration{"_T object", ""},
- GivenDeclaration{"object",
- generator.description}),
- object, source_of_truth, "std::as_const(object)",
- "source_of_truth");
- }
-
- ConformanceErrors* errors;
-};
-
-// Check copy-assign into a default-constructed object.
-template <class T, class Prof>
-struct ExpectDefaultConstructWithCopyAssign {
- template <class Fun>
- void operator()(const Fun& generator) const {
- const T source_of_truth = generator();
- T object;
- object = static_cast<const T&>(generator());
-
- (ExpectEquality<T, Prof>)(errors, "Copy assignment",
- PrepareGivenContext(
- GivenDeclaration{"const _T source_of_truth",
- generator.description},
- GivenDeclaration{"_T object", ""},
- GivenDeclaration{
- "object",
- std::string("static_cast<const _T&>(") +
- generator.description + ")"}),
- object, source_of_truth, "std::as_const(object)",
- "source_of_truth");
- }
-
- ConformanceErrors* errors;
-};
-
-// Perform a self move-assign.
-template <class T, class Prof>
-struct ExpectSelfMoveAssign {
- template <class Fun>
- void operator()(const Fun& generator) const {
- T object = generator();
- object = absl::move(object);
-
- // NOTE: Self move-assign results in a valid-but-unspecified state.
-
- (ExpectEquality<T, Prof>)(errors, "Move assignment",
- PrepareGivenContext(
- GivenDeclaration{"_T object",
- generator.description},
- GivenDeclaration{"object",
- "std::move(object)"}),
- object, object, "object", "object");
- }
-
- ConformanceErrors* errors;
-};
-
-// Perform a self copy-assign.
-template <class T, class Prof>
-struct ExpectSelfCopyAssign {
- template <class Fun>
- void operator()(const Fun& generator) const {
- const T source_of_truth = generator();
- T object = generator();
- const T& const_object = object;
- object = const_object;
-
- (ExpectEquality<T, Prof>)(errors, "Copy assignment",
- PrepareGivenContext(
- GivenDeclaration{"const _T source_of_truth",
- generator.description},
- GivenDeclaration{"_T object",
- generator.description},
- GivenDeclaration{"object",
- "std::as_const(object)"}),
- const_object, source_of_truth,
- "std::as_const(object)", "source_of_truth");
- }
-
- ConformanceErrors* errors;
-};
-
-// Perform a self-swap.
-template <class T, class Prof>
-struct ExpectSelfSwap {
- template <class Fun>
- void operator()(const Fun& generator) const {
- const T source_of_truth = generator();
- T object = generator();
-
- type_traits_internal::Swap(object, object);
-
- std::string preliminary_info = absl::StrCat(
- PrepareGivenContext(
- GivenDeclaration{"const _T source_of_truth", generator.description},
- GivenDeclaration{"_T object", generator.description}),
- "After performing a self-swap:\n"
- " using std::swap;\n"
- " swap(object, object);\n"
- "\n");
-
- (ExpectEquality<T, Prof>)(errors, "Swap", std::move(preliminary_info),
- object, source_of_truth, "std::as_const(object)",
- "source_of_truth");
- }
-
- ConformanceErrors* errors;
-};
-
-// Perform each of the single-generator checks when necessary operations are
-// supported.
-template <class T, class Prof>
-struct ExpectSelfComparison {
- template <class Fun>
- void operator()(const Fun& generator) const {
- const T object = generator();
- (ExpectOneWayEquality<T, Prof>)(errors, "Comparison",
- PrepareGivenContext(GivenDeclaration{
- "const _T object",
- generator.description}),
- object, object, "object", "object");
- }
-
- ConformanceErrors* errors;
-};
-
-// Perform each of the single-generator checks when necessary operations are
-// supported.
-template <class T, class Prof>
-struct ExpectConsistency {
- template <class Fun>
- void operator()(const Fun& generator) const {
- If<PropertiesOfT<Prof>::is_move_constructible>::Invoke(
- ExpectMoveConstructOneGenerator<T, Prof>{errors}, generator);
-
- If<PropertiesOfT<Prof>::is_copy_constructible>::Invoke(
- ExpectCopyConstructOneGenerator<T, Prof>{errors}, generator);
-
- If<PropertiesOfT<Prof>::is_default_constructible &&
- PropertiesOfT<Prof>::is_move_assignable>::
- Invoke(ExpectDefaultConstructWithMoveAssign<T, Prof>{errors},
- generator);
-
- If<PropertiesOfT<Prof>::is_default_constructible &&
- PropertiesOfT<Prof>::is_copy_assignable>::
- Invoke(ExpectDefaultConstructWithCopyAssign<T, Prof>{errors},
- generator);
-
- If<PropertiesOfT<Prof>::is_move_assignable>::Invoke(
- ExpectSelfMoveAssign<T, Prof>{errors}, generator);
-
- If<PropertiesOfT<Prof>::is_copy_assignable>::Invoke(
- ExpectSelfCopyAssign<T, Prof>{errors}, generator);
-
- If<PropertiesOfT<Prof>::is_swappable>::Invoke(
- ExpectSelfSwap<T, Prof>{errors}, generator);
- }
-
- ConformanceErrors* errors;
-};
-
-// Check move-assign with two different values.
-template <class T, class Prof>
-struct ExpectMoveAssign {
- template <class Fun0, class Fun1>
- void operator()(const Fun0& generator0, const Fun1& generator1) const {
- const T source_of_truth1 = generator1();
- T object = generator0();
- object = generator1();
-
- (ExpectEquality<T, Prof>)(errors, "Move assignment",
- PrepareGivenContext(
- GivenDeclaration{"const _T source_of_truth1",
- generator1.description},
- GivenDeclaration{"_T object",
- generator0.description},
- GivenDeclaration{"object",
- generator1.description}),
- object, source_of_truth1, "std::as_const(object)",
- "source_of_truth1");
- }
-
- ConformanceErrors* errors;
-};
-
-// Check copy-assign with two different values.
-template <class T, class Prof>
-struct ExpectCopyAssign {
- template <class Fun0, class Fun1>
- void operator()(const Fun0& generator0, const Fun1& generator1) const {
- const T source_of_truth1 = generator1();
- T object = generator0();
- object = static_cast<const T&>(generator1());
-
- (ExpectEquality<T, Prof>)(errors, "Copy assignment",
- PrepareGivenContext(
- GivenDeclaration{"const _T source_of_truth1",
- generator1.description},
- GivenDeclaration{"_T object",
- generator0.description},
- GivenDeclaration{
- "object",
- std::string("static_cast<const _T&>(") +
- generator1.description + ")"}),
- object, source_of_truth1, "std::as_const(object)",
- "source_of_truth1");
- }
-
- ConformanceErrors* errors;
-};
-
-// Check swap with two different values.
-template <class T, class Prof>
-struct ExpectSwap {
- template <class Fun0, class Fun1>
- void operator()(const Fun0& generator0, const Fun1& generator1) const {
- const T source_of_truth0 = generator0();
- const T source_of_truth1 = generator1();
- T object0 = generator0();
- T object1 = generator1();
-
- type_traits_internal::Swap(object0, object1);
-
- const std::string context =
- PrepareGivenContext(
- GivenDeclaration{"const _T source_of_truth0",
- generator0.description},
- GivenDeclaration{"const _T source_of_truth1",
- generator1.description},
- GivenDeclaration{"_T object0", generator0.description},
- GivenDeclaration{"_T object1", generator1.description}) +
- "After performing a swap:\n"
- " using std::swap;\n"
- " swap(object0, object1);\n"
- "\n";
-
- (ExpectEquality<T, Prof>)(errors, "Swap", context, object0,
- source_of_truth1, "std::as_const(object0)",
- "source_of_truth1");
- (ExpectEquality<T, Prof>)(errors, "Swap", context, object1,
- source_of_truth0, "std::as_const(object1)",
- "source_of_truth0");
- }
-
- ConformanceErrors* errors;
-};
-
-// Validate that `generator0` and `generator1` produce values that are equal.
-template <class T, class Prof>
-struct ExpectEquivalenceClassComparison {
- template <class Fun0, class Fun1>
- void operator()(const Fun0& generator0, const Fun1& generator1) const {
- const T object0 = generator0();
- const T object1 = generator1();
-
- (ExpectEquality<T, Prof>)(errors, "Comparison",
- PrepareGivenContext(
- GivenDeclaration{"const _T object0",
- generator0.description},
- GivenDeclaration{"const _T object1",
- generator1.description}),
- object0, object1, "object0", "object1");
- }
-
- ConformanceErrors* errors;
-};
-
-// Validate that all objects in the same equivalence-class have the same value.
-template <class T, class Prof>
-struct ExpectEquivalenceClassConsistency {
- template <class Fun0, class Fun1>
- void operator()(const Fun0& generator0, const Fun1& generator1) const {
- If<PropertiesOfT<Prof>::is_move_assignable>::Invoke(
- ExpectMoveAssign<T, Prof>{errors}, generator0, generator1);
-
- If<PropertiesOfT<Prof>::is_copy_assignable>::Invoke(
- ExpectCopyAssign<T, Prof>{errors}, generator0, generator1);
-
- If<PropertiesOfT<Prof>::is_swappable>::Invoke(ExpectSwap<T, Prof>{errors},
- generator0, generator1);
- }
-
- ConformanceErrors* errors;
-};
-
-// Given a "lesser" object and a "greater" object, perform every combination of
-// comparison operators supported for the type, expecting consistent results.
-template <class T, class Prof>
-void ExpectOrdered(ConformanceErrors* errors, absl::string_view context,
- const T& small, const T& big, absl::string_view small_name,
- absl::string_view big_name) {
- const absl::string_view test_name = "Comparison";
-
- If<PropertiesOfT<Prof>::is_equality_comparable>::Invoke(
- ExpectNotEq{errors}, test_name, context, small, big, small_name,
- big_name);
- If<PropertiesOfT<Prof>::is_equality_comparable>::Invoke(
- ExpectNotEq{errors}, test_name, context, big, small, big_name,
- small_name);
-
- If<PropertiesOfT<Prof>::is_inequality_comparable>::Invoke(
- ExpectNe{errors}, test_name, context, small, big, small_name, big_name);
- If<PropertiesOfT<Prof>::is_inequality_comparable>::Invoke(
- ExpectNe{errors}, test_name, context, big, small, big_name, small_name);
-
- If<PropertiesOfT<Prof>::is_less_than_comparable>::Invoke(
- ExpectLt{errors}, test_name, context, small, big, small_name, big_name);
- If<PropertiesOfT<Prof>::is_less_than_comparable>::Invoke(
- ExpectNotLt{errors}, test_name, context, big, small, big_name,
- small_name);
-
- If<PropertiesOfT<Prof>::is_less_equal_comparable>::Invoke(
- ExpectLe{errors}, test_name, context, small, big, small_name, big_name);
- If<PropertiesOfT<Prof>::is_less_equal_comparable>::Invoke(
- ExpectNotLe{errors}, test_name, context, big, small, big_name,
- small_name);
-
- If<PropertiesOfT<Prof>::is_greater_equal_comparable>::Invoke(
- ExpectNotGe{errors}, test_name, context, small, big, small_name,
- big_name);
- If<PropertiesOfT<Prof>::is_greater_equal_comparable>::Invoke(
- ExpectGe{errors}, test_name, context, big, small, big_name, small_name);
-
- If<PropertiesOfT<Prof>::is_greater_than_comparable>::Invoke(
- ExpectNotGt{errors}, test_name, context, small, big, small_name,
- big_name);
- If<PropertiesOfT<Prof>::is_greater_than_comparable>::Invoke(
- ExpectGt{errors}, test_name, context, big, small, big_name, small_name);
-}
-
-// For every two elements of an equivalence class, makes sure that those two
-// elements compare equal, including checks with the same argument passed as
-// both operands.
-template <class T, class Prof>
-struct ExpectEquivalenceClassComparisons {
- template <class... Funs>
- void operator()(EquivalenceClassType<Funs...> eq_class) const {
- (ForEachTupleElement)(ExpectSelfComparison<T, Prof>{errors},
- eq_class.generators);
-
- (ForEveryTwo)(ExpectEquivalenceClassComparison<T, Prof>{errors},
- eq_class.generators);
- }
-
- ConformanceErrors* errors;
-};
-
-// For every element of an equivalence class, makes sure that the element is
-// self-consistent (in other words, if any of move/copy/swap are defined,
-// perform those operations and make such that results and operands still
-// compare equal to known values whenever it is required for that operation.
-template <class T, class Prof>
-struct ExpectEquivalenceClass {
- template <class... Funs>
- void operator()(EquivalenceClassType<Funs...> eq_class) const {
- (ForEachTupleElement)(ExpectConsistency<T, Prof>{errors},
- eq_class.generators);
-
- (ForEveryTwo)(ExpectEquivalenceClassConsistency<T, Prof>{errors},
- eq_class.generators);
- }
-
- ConformanceErrors* errors;
-};
-
-// Validate that the passed-in argument is a generator of a greater value than
-// the one produced by the "small_gen" datamember with respect to all of the
-// comparison operators that Prof requires, with both argument orders to test.
-template <class T, class Prof, class SmallGenerator>
-struct ExpectBiggerGeneratorThanComparisons {
- template <class BigGenerator>
- void operator()(BigGenerator big_gen) const {
- const T small = small_gen();
- const T big = big_gen();
-
- (ExpectOrdered<T, Prof>)(errors,
- PrepareGivenContext(
- GivenDeclaration{"const _T small",
- small_gen.description},
- GivenDeclaration{"const _T big",
- big_gen.description}),
- small, big, "small", "big");
- }
-
- SmallGenerator small_gen;
- ConformanceErrors* errors;
-};
-
-// Perform all of the move, copy, and swap checks on the value generated by
-// `small_gen` and the value generated by `big_gen`.
-template <class T, class Prof, class SmallGenerator>
-struct ExpectBiggerGeneratorThan {
- template <class BigGenerator>
- void operator()(BigGenerator big_gen) const {
- If<PropertiesOfT<Prof>::is_move_assignable>::Invoke(
- ExpectMoveAssign<T, Prof>{errors}, small_gen, big_gen);
- If<PropertiesOfT<Prof>::is_move_assignable>::Invoke(
- ExpectMoveAssign<T, Prof>{errors}, big_gen, small_gen);
-
- If<PropertiesOfT<Prof>::is_copy_assignable>::Invoke(
- ExpectCopyAssign<T, Prof>{errors}, small_gen, big_gen);
- If<PropertiesOfT<Prof>::is_copy_assignable>::Invoke(
- ExpectCopyAssign<T, Prof>{errors}, big_gen, small_gen);
-
- If<PropertiesOfT<Prof>::is_swappable>::Invoke(ExpectSwap<T, Prof>{errors},
- small_gen, big_gen);
- }
-
- SmallGenerator small_gen;
- ConformanceErrors* errors;
-};
-
-// Validate that the result of a generator is greater than the results of all
-// generators in an equivalence class with respect to comparisons.
-template <class T, class Prof, class SmallGenerator>
-struct ExpectBiggerGeneratorThanEqClassesComparisons {
- template <class BigEqClass>
- void operator()(BigEqClass big_eq_class) const {
- (ForEachTupleElement)(
- ExpectBiggerGeneratorThanComparisons<T, Prof, SmallGenerator>{small_gen,
- errors},
- big_eq_class.generators);
- }
-
- SmallGenerator small_gen;
- ConformanceErrors* errors;
-};
-
-// Validate that the non-comparison binary operations required by Prof are
-// correct for the result of each generator of big_eq_class and a generator of
-// the logically smaller value returned by small_gen.
-template <class T, class Prof, class SmallGenerator>
-struct ExpectBiggerGeneratorThanEqClasses {
- template <class BigEqClass>
- void operator()(BigEqClass big_eq_class) const {
- (ForEachTupleElement)(
- ExpectBiggerGeneratorThan<T, Prof, SmallGenerator>{small_gen, errors},
- big_eq_class.generators);
- }
-
- SmallGenerator small_gen;
- ConformanceErrors* errors;
-};
-
-// Validate that each equivalence class that is passed is logically less than
-// the equivalence classes that comes later on in the argument list.
-template <class T, class Prof>
-struct ExpectOrderedEquivalenceClassesComparisons {
- template <class... BigEqClasses>
- struct Impl {
- // Validate that the value produced by `small_gen` is less than all of the
- // values generated by those of the logically larger equivalence classes.
- template <class SmallGenerator>
- void operator()(SmallGenerator small_gen) const {
- (ForEachTupleElement)(ExpectBiggerGeneratorThanEqClassesComparisons<
- T, Prof, SmallGenerator>{small_gen, errors},
- big_eq_classes);
- }
-
- std::tuple<BigEqClasses...> big_eq_classes;
- ConformanceErrors* errors;
- };
-
- // When given no equivalence classes, no validation is necessary.
- void operator()() const {}
-
- template <class SmallEqClass, class... BigEqClasses>
- void operator()(SmallEqClass small_eq_class,
- BigEqClasses... big_eq_classes) const {
- // For each generator in the first equivalence class, make sure that it is
- // less than each of those in the logically greater equivalence classes.
- (ForEachTupleElement)(
- Impl<BigEqClasses...>{std::make_tuple(absl::move(big_eq_classes)...),
- errors},
- small_eq_class.generators);
-
- // Recurse so that all equivalence class combinations are checked.
- (*this)(absl::move(big_eq_classes)...);
- }
-
- ConformanceErrors* errors;
-};
-
-// Validate that the non-comparison binary operations required by Prof are
-// correct for the result of each generator of big_eq_classes and a generator of
-// the logically smaller value returned by small_gen.
-template <class T, class Prof>
-struct ExpectOrderedEquivalenceClasses {
- template <class... BigEqClasses>
- struct Impl {
- template <class SmallGenerator>
- void operator()(SmallGenerator small_gen) const {
- (ForEachTupleElement)(
- ExpectBiggerGeneratorThanEqClasses<T, Prof, SmallGenerator>{small_gen,
- errors},
- big_eq_classes);
- }
-
- std::tuple<BigEqClasses...> big_eq_classes;
- ConformanceErrors* errors;
- };
-
- // Check that small_eq_class is logically consistent and also is logically
- // less than all values in big_eq_classes.
- template <class SmallEqClass, class... BigEqClasses>
- void operator()(SmallEqClass small_eq_class,
- BigEqClasses... big_eq_classes) const {
- (ForEachTupleElement)(
- Impl<BigEqClasses...>{std::make_tuple(absl::move(big_eq_classes)...),
- errors},
- small_eq_class.generators);
-
- (*this)(absl::move(big_eq_classes)...);
- }
-
- // Terminating case of operator().
- void operator()() const {}
-
- ConformanceErrors* errors;
-};
-
-// Validate that a type meets the syntactic requirements of std::hash if the
-// range of profiles requires it.
-template <class T, class MinProf, class MaxProf>
-struct ExpectHashable {
- void operator()() const {
- ExpectModelOfHashable<T, MinProf, MaxProf>(errors);
- }
-
- ConformanceErrors* errors;
-};
-
-// Validate that the type `T` meets all of the requirements associated with
-// `MinProf` and without going beyond the syntactic properties of `MaxProf`.
-template <class T, class MinProf, class MaxProf>
-struct ExpectModels {
- void operator()(ConformanceErrors* errors) const {
- ExpectModelOfDefaultConstructible<T, MinProf, MaxProf>(errors);
- ExpectModelOfMoveConstructible<T, MinProf, MaxProf>(errors);
- ExpectModelOfCopyConstructible<T, MinProf, MaxProf>(errors);
- ExpectModelOfMoveAssignable<T, MinProf, MaxProf>(errors);
- ExpectModelOfCopyAssignable<T, MinProf, MaxProf>(errors);
- ExpectModelOfDestructible<T, MinProf, MaxProf>(errors);
- ExpectModelOfEqualityComparable<T, MinProf, MaxProf>(errors);
- ExpectModelOfInequalityComparable<T, MinProf, MaxProf>(errors);
- ExpectModelOfLessThanComparable<T, MinProf, MaxProf>(errors);
- ExpectModelOfLessEqualComparable<T, MinProf, MaxProf>(errors);
- ExpectModelOfGreaterEqualComparable<T, MinProf, MaxProf>(errors);
- ExpectModelOfGreaterThanComparable<T, MinProf, MaxProf>(errors);
- ExpectModelOfSwappable<T, MinProf, MaxProf>(errors);
-
- // Only check hashability on compilers that have a compliant default-hash.
- If<!poisoned_hash_fails_instantiation()>::Invoke(
- ExpectHashable<T, MinProf, MaxProf>{errors});
- }
-};
-
-// A metafunction that yields a Profile matching the set of properties that are
-// safe to be checked (lack-of-hashability is only checked on standard library
-// implementations that are standards compliant in that they provide a std::hash
-// primary template that is SFINAE-friendly)
-template <class LogicalProf, class T>
-struct MinimalCheckableProfile {
- using type =
- MinimalProfiles<PropertiesOfT<LogicalProf>,
- PropertiesOfT<SyntacticConformanceProfileOf<
- T, !PropertiesOfT<LogicalProf>::is_hashable &&
- poisoned_hash_fails_instantiation()
- ? CheckHashability::no
- : CheckHashability::yes>>>;
-};
-
-// An identity metafunction
-template <class T>
-struct Always {
- using type = T;
-};
-
-// Validate the T meets all of the necessary requirements of LogicalProf, with
-// syntactic requirements defined by the profile range [MinProf, MaxProf].
-template <class T, class LogicalProf, class MinProf, class MaxProf,
- class... EqClasses>
-ConformanceErrors ExpectRegularityImpl(
- OrderedEquivalenceClasses<EqClasses...> vals) {
- ConformanceErrors errors((NameOf<T>()));
-
- If<!constexpr_instantiation_when_unevaluated()>::Invoke(
- ExpectModels<T, MinProf, MaxProf>(), &errors);
-
- using minimal_profile = typename absl::conditional_t<
- constexpr_instantiation_when_unevaluated(), Always<LogicalProf>,
- MinimalCheckableProfile<LogicalProf, T>>::type;
-
- If<PropertiesOfT<minimal_profile>::is_default_constructible>::Invoke(
- ExpectDefaultConstructWithDestruct<T>{&errors});
-
- //////////////////////////////////////////////////////////////////////////////
- // Perform all comparison checks first, since later checks depend on their
- // correctness.
- //
- // Check all of the comparisons for all values in the same equivalence
- // class (equal with respect to comparison operators and hash the same).
- (ForEachTupleElement)(
- ExpectEquivalenceClassComparisons<T, minimal_profile>{&errors},
- vals.eq_classes);
-
- // Check all of the comparisons for each combination of values that are in
- // different equivalence classes (not equal with respect to comparison
- // operators).
- absl::apply(
- ExpectOrderedEquivalenceClassesComparisons<T, minimal_profile>{&errors},
- vals.eq_classes);
- //
- //////////////////////////////////////////////////////////////////////////////
-
- // Perform remaining checks, relying on comparisons.
- // TODO(calabrese) short circuit if any comparisons above failed.
- (ForEachTupleElement)(ExpectEquivalenceClass<T, minimal_profile>{&errors},
- vals.eq_classes);
-
- absl::apply(ExpectOrderedEquivalenceClasses<T, minimal_profile>{&errors},
- vals.eq_classes);
-
- return errors;
-}
-
-// A type that represents a range of profiles that are acceptable to be matched.
-//
-// `MinProf` is the minimum set of syntactic requirements that must be met.
-//
-// `MaxProf` is the maximum set of syntactic requirements that must be met.
-// This maximum is particularly useful for certain "strictness" checking. Some
-// examples for when this is useful:
-//
-// * Making sure that a type is move-only (rather than simply movable)
-//
-// * Making sure that a member function is *not* noexcept in cases where it
-// cannot be noexcept, such as if a dependent datamember has certain
-// operations that are not noexcept.
-//
-// * Making sure that a type tightly matches a spec, such as the standard.
-//
-// `LogicalProf` is the Profile for which run-time testing is to take place.
-//
-// Note: The reason for `LogicalProf` is because it is often the case, when
-// dealing with templates, that a declaration of a given operation is specified,
-// but whose body would fail to instantiate. Examples include the
-// copy-constructor of a standard container when the element-type is move-only,
-// or the comparison operators of a standard container when the element-type
-// does not have the necessary comparison operations defined. The `LogicalProf`
-// parameter allows us to capture the intent of what should be tested at
-// run-time, even in the cases where syntactically it might otherwise appear as
-// though the type undergoing testing supports more than it actually does.
-template <class LogicalProf, class MinProf = LogicalProf,
- class MaxProf = MinProf>
-struct ProfileRange {
- using logical_profile = LogicalProf;
- using min_profile = MinProf;
- using max_profile = MaxProf;
-};
-
-// Similar to ProfileRange except that it creates a profile range that is
-// coupled with a Domain and is used when testing that a type matches exactly
-// the "minimum" requirements of LogicalProf.
-template <class StrictnessDomain, class LogicalProf,
- class MinProf = LogicalProf, class MaxProf = MinProf>
-struct StrictProfileRange {
- // We do not yet support extension.
- static_assert(
- std::is_same<StrictnessDomain, RegularityDomain>::value,
- "Currently, the only valid StrictnessDomain is RegularityDomain.");
- using strictness_domain = StrictnessDomain;
- using logical_profile = LogicalProf;
- using min_profile = MinProf;
- using max_profile = MaxProf;
-};
-
-////////////////////////////////////////////////////////////////////////////////
-//
-// A metafunction that creates a StrictProfileRange from a Domain and either a
-// Profile or ProfileRange.
-template <class StrictnessDomain, class ProfOrRange>
-struct MakeStrictProfileRange;
-
-template <class StrictnessDomain, class LogicalProf>
-struct MakeStrictProfileRange {
- using type = StrictProfileRange<StrictnessDomain, LogicalProf>;
-};
-
-template <class StrictnessDomain, class LogicalProf, class MinProf,
- class MaxProf>
-struct MakeStrictProfileRange<StrictnessDomain,
- ProfileRange<LogicalProf, MinProf, MaxProf>> {
- using type =
- StrictProfileRange<StrictnessDomain, LogicalProf, MinProf, MaxProf>;
-};
-
-template <class StrictnessDomain, class ProfOrRange>
-using MakeStrictProfileRangeT =
- typename MakeStrictProfileRange<StrictnessDomain, ProfOrRange>::type;
-//
-////////////////////////////////////////////////////////////////////////////////
-
-// A profile in the RegularityDomain with the strongest possible requirements.
-using MostStrictProfile =
- CombineProfiles<TriviallyCompleteProfile, NothrowComparableProfile>;
-
-// Forms a ProfileRange that treats the Profile as the bare minimum requirements
-// of a type.
-template <class LogicalProf, class MinProf = LogicalProf>
-using LooseProfileRange = StrictProfileRange<RegularityDomain, LogicalProf,
- MinProf, MostStrictProfile>;
-
-template <class Prof>
-using MakeLooseProfileRangeT = Prof;
-
-////////////////////////////////////////////////////////////////////////////////
-//
-// The following classes implement the metafunction ProfileRangeOfT<T> that
-// takes either a Profile or ProfileRange and yields the ProfileRange to be
-// used during testing.
-//
-template <class T, class /*Enabler*/ = void>
-struct ProfileRangeOfImpl;
-
-template <class T>
-struct ProfileRangeOfImpl<T, absl::void_t<PropertiesOfT<T>>> {
- using type = LooseProfileRange<T>;
-};
-
-template <class T>
-struct ProfileRangeOf : ProfileRangeOfImpl<T> {};
-
-template <class StrictnessDomain, class LogicalProf, class MinProf,
- class MaxProf>
-struct ProfileRangeOf<
- StrictProfileRange<StrictnessDomain, LogicalProf, MinProf, MaxProf>> {
- using type =
- StrictProfileRange<StrictnessDomain, LogicalProf, MinProf, MaxProf>;
-};
-
-template <class T>
-using ProfileRangeOfT = typename ProfileRangeOf<T>::type;
-//
-////////////////////////////////////////////////////////////////////////////////
-
-// Extract the logical profile of a range (what will be runtime tested).
-template <class T>
-using LogicalProfileOfT = typename ProfileRangeOfT<T>::logical_profile;
-
-// Extract the minimal syntactic profile of a range (error if not at least).
-template <class T>
-using MinProfileOfT = typename ProfileRangeOfT<T>::min_profile;
-
-// Extract the maximum syntactic profile of a range (error if more than).
-template <class T>
-using MaxProfileOfT = typename ProfileRangeOfT<T>::max_profile;
-
-////////////////////////////////////////////////////////////////////////////////
-//
-template <class T>
-struct IsProfileOrProfileRange : IsProfile<T>::type {};
-
-template <class StrictnessDomain, class LogicalProf, class MinProf,
- class MaxProf>
-struct IsProfileOrProfileRange<
- StrictProfileRange<StrictnessDomain, LogicalProf, MinProf, MaxProf>>
- : std::true_type {};
-//
-////////////////////////////////////////////////////////////////////////////////
-
-// TODO(calabrese): Consider naming the functions in this class the same as
-// the macros (defined later on) so that auto-complete leads to the correct name
-// and so that a user cannot accidentally call a function rather than the macro
-// form.
-template <bool ExpectSuccess, class T, class... EqClasses>
-struct ExpectConformanceOf {
- // Add a value to be tested. Subsequent calls to this function on the same
- // object must specify logically "larger" values with respect to the
- // comparison operators of the type, if any.
- //
- // NOTE: This function should not be called directly. A stateless lambda is
- // implicitly formed and passed when using the INITIALIZER macro at the bottom
- // of this file.
- template <class Fun,
- absl::enable_if_t<std::is_same<
- ResultOfGeneratorT<GeneratorType<Fun>>, T>::value>** = nullptr>
- ABSL_MUST_USE_RESULT ExpectConformanceOf<ExpectSuccess, T, EqClasses...,
- EquivalenceClassType<Fun>>
- initializer(GeneratorType<Fun> fun) && {
- return {
- {std::tuple_cat(absl::move(ordered_vals.eq_classes),
- std::make_tuple((EquivalenceClass)(absl::move(fun))))},
- std::move(expected_failed_tests)};
- }
-
- template <class... TestNames,
- absl::enable_if_t<!ExpectSuccess && sizeof...(EqClasses) == 0 &&
- absl::conjunction<std::is_convertible<
- TestNames, absl::string_view>...>::value>** =
- nullptr>
- ABSL_MUST_USE_RESULT ExpectConformanceOf<ExpectSuccess, T, EqClasses...>
- due_to(TestNames&&... test_names) && {
- (InsertEach)(&expected_failed_tests,
- absl::AsciiStrToLower(absl::string_view(test_names))...);
-
- return {absl::move(ordered_vals), std::move(expected_failed_tests)};
- }
-
- template <class... TestNames, int = 0, // MSVC disambiguator
- absl::enable_if_t<ExpectSuccess && sizeof...(EqClasses) == 0 &&
- absl::conjunction<std::is_convertible<
- TestNames, absl::string_view>...>::value>** =
- nullptr>
- ABSL_MUST_USE_RESULT ExpectConformanceOf<ExpectSuccess, T, EqClasses...>
- due_to(TestNames&&... test_names) && {
- // TODO(calabrese) Instead have DUE_TO only exist via a CRTP base.
- // This would produce better errors messages than the static_assert.
- static_assert(!ExpectSuccess,
- "DUE_TO cannot be called when conformance is expected -- did "
- "you mean to use ASSERT_NONCONFORMANCE_OF?");
- }
-
- // Add a value to be tested. Subsequent calls to this function on the same
- // object must specify logically "larger" values with respect to the
- // comparison operators of the type, if any.
- //
- // NOTE: This function should not be called directly. A stateful lambda is
- // implicitly formed and passed when using the INITIALIZER macro at the bottom
- // of this file.
- template <class Fun,
- absl::enable_if_t<std::is_same<
- ResultOfGeneratorT<GeneratorType<Fun>>, T>::value>** = nullptr>
- ABSL_MUST_USE_RESULT ExpectConformanceOf<ExpectSuccess, T, EqClasses...,
- EquivalenceClassType<Fun>>
- dont_class_directly_stateful_initializer(GeneratorType<Fun> fun) && {
- return {
- {std::tuple_cat(absl::move(ordered_vals.eq_classes),
- std::make_tuple((EquivalenceClass)(absl::move(fun))))},
- std::move(expected_failed_tests)};
- }
-
- // Add a set of value to be tested, where each value is equal with respect to
- // the comparison operators and std::hash specialization, if defined.
- template <
- class... Funs,
- absl::void_t<absl::enable_if_t<std::is_same<
- ResultOfGeneratorT<GeneratorType<Funs>>, T>::value>...>** = nullptr>
- ABSL_MUST_USE_RESULT ExpectConformanceOf<ExpectSuccess, T, EqClasses...,
- EquivalenceClassType<Funs...>>
- equivalence_class(GeneratorType<Funs>... funs) && {
- return {{std::tuple_cat(
- absl::move(ordered_vals.eq_classes),
- std::make_tuple((EquivalenceClass)(absl::move(funs)...)))},
- std::move(expected_failed_tests)};
- }
-
- // Execute the tests for the captured set of values, strictly matching a range
- // of expected profiles in a given domain.
- template <
- class ProfRange,
- absl::enable_if_t<IsProfileOrProfileRange<ProfRange>::value>** = nullptr>
- ABSL_MUST_USE_RESULT ::testing::AssertionResult with_strict_profile(
- ProfRange /*profile*/) {
- ConformanceErrors test_result =
- (ExpectRegularityImpl<
- T, LogicalProfileOfT<ProfRange>, MinProfileOfT<ProfRange>,
- MaxProfileOfT<ProfRange>>)(absl::move(ordered_vals));
-
- return ExpectSuccess ? test_result.assertionResult()
- : test_result.expectFailedTests(expected_failed_tests);
- }
-
- // Execute the tests for the captured set of values, loosely matching a range
- // of expected profiles (loose in that an interface is allowed to be more
- // refined that a profile suggests, such as a type having a noexcept copy
- // constructor when all that is required is that the copy constructor exists).
- template <class Prof, absl::enable_if_t<IsProfile<Prof>::value>** = nullptr>
- ABSL_MUST_USE_RESULT ::testing::AssertionResult with_loose_profile(
- Prof /*profile*/) {
- ConformanceErrors test_result =
- (ExpectRegularityImpl<
- T, Prof, Prof,
- CombineProfiles<TriviallyCompleteProfile,
- NothrowComparableProfile>>)(absl::
- move(ordered_vals));
-
- return ExpectSuccess ? test_result.assertionResult()
- : test_result.expectFailedTests(expected_failed_tests);
- }
-
- OrderedEquivalenceClasses<EqClasses...> ordered_vals;
- std::set<std::string> expected_failed_tests;
-};
-
-template <class T>
-using ExpectConformanceOfType = ExpectConformanceOf</*ExpectSuccess=*/true, T>;
-
-template <class T>
-using ExpectNonconformanceOfType =
- ExpectConformanceOf</*ExpectSuccess=*/false, T>;
-
-struct EquivalenceClassMaker {
- // TODO(calabrese) Constrain to callable
- template <class Fun>
- static GeneratorType<Fun> initializer(GeneratorType<Fun> fun) {
- return fun;
- }
-};
-
-// A top-level macro that begins the builder pattern.
-//
-// The argument here takes the datatype to be tested.
-#define ABSL_INTERNAL_ASSERT_CONFORMANCE_OF(...) \
- GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
- if ABSL_INTERNAL_LPAREN \
- const ::testing::AssertionResult gtest_ar = \
- ABSL_INTERNAL_LPAREN ::absl::types_internal::ExpectConformanceOfType< \
- __VA_ARGS__>()
-
-// Akin to ASSERT_CONFORMANCE_OF except that it expects failure and tries to
-// match text.
-#define ABSL_INTERNAL_ASSERT_NONCONFORMANCE_OF(...) \
- GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
- if ABSL_INTERNAL_LPAREN \
- const ::testing::AssertionResult gtest_ar = \
- ABSL_INTERNAL_LPAREN ::absl::types_internal::ExpectNonconformanceOfType< \
- __VA_ARGS__>()
-
-////////////////////////////////////////////////////////////////////////////////
-// NOTE: The following macros look like they are recursive, but are not (macros
-// cannot recurse). These actually refer to member functions of the same name.
-// This is done intentionally so that a user cannot accidentally invoke a
-// member function of the conformance-testing suite without going through the
-// macro.
-////////////////////////////////////////////////////////////////////////////////
-
-// Specify expected test failures as comma-separated strings.
-#define DUE_TO(...) due_to(__VA_ARGS__)
-
-// Specify a value to be tested.
-//
-// Note: Internally, this takes an expression and turns it into the return value
-// of lambda that captures no data. The expression is stringized during
-// preprocessing so that it can be used in error reports.
-#define INITIALIZER(...) \
- initializer(::absl::types_internal::Generator( \
- [] { return __VA_ARGS__; }, ABSL_INTERNAL_STRINGIZE(__VA_ARGS__)))
-
-// Specify a value to be tested.
-//
-// Note: Internally, this takes an expression and turns it into the return value
-// of lambda that captures data by reference. The expression is stringized
-// during preprocessing so that it can be used in error reports.
-#define STATEFUL_INITIALIZER(...) \
- stateful_initializer(::absl::types_internal::Generator( \
- [&] { return __VA_ARGS__; }, ABSL_INTERNAL_STRINGIZE(__VA_ARGS__)))
-
-// Used in the builder-pattern.
-//
-// Takes a series of INITIALIZER and/or STATEFUL_INITIALIZER invocations and
-// forwards them along to be tested, grouping them such that the testing suite
-// knows that they are supposed to represent the same logical value (the values
-// compare the same, hash the same, etc.).
-#define EQUIVALENCE_CLASS(...) \
- equivalence_class(ABSL_INTERNAL_TRANSFORM_ARGS( \
- ABSL_INTERNAL_PREPEND_EQ_MAKER, __VA_ARGS__))
-
-// An invocation of this or WITH_STRICT_PROFILE must end the builder-pattern.
-// It takes a Profile as its argument.
-//
-// This executes the tests and allows types that are "more referined" than the
-// profile specifies, but not less. For instance, if the Profile specifies
-// noexcept copy-constructiblity, the test will fail if the copy-constructor is
-// not noexcept, however, it will succeed if the copy constructor is trivial.
-//
-// This is useful for testing that a type meets some minimum set of
-// requirements.
-#define WITH_LOOSE_PROFILE(...) \
- with_loose_profile( \
- ::absl::types_internal::MakeLooseProfileRangeT<__VA_ARGS__>()) \
- ABSL_INTERNAL_RPAREN ABSL_INTERNAL_RPAREN; \
- else GTEST_FATAL_FAILURE_(gtest_ar.failure_message()) // NOLINT
-
-// An invocation of this or WITH_STRICT_PROFILE must end the builder-pattern.
-// It takes a Domain and a Profile as its arguments.
-//
-// This executes the tests and disallows types that differ at all from the
-// properties of the Profile. For instance, if the Profile specifies noexcept
-// copy-constructiblity, the test will fail if the copy constructor is trivial.
-//
-// This is useful for testing that a type does not do anything more than a
-// specification requires, such as to minimize things like Hyrum's Law, or more
-// commonly, to prevent a type from being "accidentally" copy-constructible in
-// a way that may produce incorrect results, simply because the user forget to
-// delete that operation.
-#define WITH_STRICT_PROFILE(...) \
- with_strict_profile( \
- ::absl::types_internal::MakeStrictProfileRangeT<__VA_ARGS__>()) \
- ABSL_INTERNAL_RPAREN ABSL_INTERNAL_RPAREN; \
- else GTEST_FATAL_FAILURE_(gtest_ar.failure_message()) // NOLINT
-
-// Internal macro that is used in the internals of the EDSL when forming
-// equivalence classes.
-#define ABSL_INTERNAL_PREPEND_EQ_MAKER(arg) \
- ::absl::types_internal::EquivalenceClassMaker().arg
-
-} // namespace types_internal
-ABSL_NAMESPACE_END
-} // namespace absl
-
-#endif // ABSL_TYPES_INTERNAL_CONFORMANCE_TESTING_H_
diff --git a/absl/types/internal/conformance_testing_helpers.h b/absl/types/internal/conformance_testing_helpers.h
deleted file mode 100644
index 00775f9..0000000
--- a/absl/types/internal/conformance_testing_helpers.h
+++ /dev/null
@@ -1,391 +0,0 @@
-// Copyright 2019 The Abseil Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef ABSL_TYPES_INTERNAL_CONFORMANCE_TESTING_HELPERS_H_
-#define ABSL_TYPES_INTERNAL_CONFORMANCE_TESTING_HELPERS_H_
-
-// Checks to determine whether or not we can use abi::__cxa_demangle
-#if (defined(__ANDROID__) || defined(ANDROID)) && !defined(OS_ANDROID)
-#define ABSL_INTERNAL_OS_ANDROID
-#endif
-
-// We support certain compilers only. See demangle.h for details.
-#if defined(OS_ANDROID) && (defined(__i386__) || defined(__x86_64__))
-#define ABSL_TYPES_INTERNAL_HAS_CXA_DEMANGLE 0
-#elif (__GNUC__ >= 4 || (__GNUC__ >= 3 && __GNUC_MINOR__ >= 4)) && \
- !defined(__mips__)
-#define ABSL_TYPES_INTERNAL_HAS_CXA_DEMANGLE 1
-#elif defined(__clang__) && !defined(_MSC_VER)
-#define ABSL_TYPES_INTERNAL_HAS_CXA_DEMANGLE 1
-#else
-#define ABSL_TYPES_INTERNAL_HAS_CXA_DEMANGLE 0
-#endif
-
-#include <tuple>
-#include <type_traits>
-#include <utility>
-
-#include "absl/meta/type_traits.h"
-#include "absl/strings/string_view.h"
-#include "absl/utility/utility.h"
-
-#if ABSL_TYPES_INTERNAL_HAS_CXA_DEMANGLE
-#include <cxxabi.h>
-
-#include <cstdlib>
-#endif
-
-namespace absl {
-ABSL_NAMESPACE_BEGIN
-namespace types_internal {
-
-// Return a readable name for type T.
-template <class T>
-absl::string_view NameOfImpl() {
-// TODO(calabrese) Investigate using debugging:internal_demangle as a fallback.
-#if ABSL_TYPES_INTERNAL_HAS_CXA_DEMANGLE
- int status = 0;
- char* demangled_name = nullptr;
-
- demangled_name =
- abi::__cxa_demangle(typeid(T).name(), nullptr, nullptr, &status);
-
- if (status == 0 && demangled_name != nullptr) {
- return demangled_name;
- } else {
- return typeid(T).name();
- }
-#else
- return typeid(T).name();
-#endif
- // NOTE: We intentionally leak demangled_name so that it remains valid
- // throughout the remainder of the program.
-}
-
-// Given a type, returns as nice of a type name as we can produce (demangled).
-//
-// Note: This currently strips cv-qualifiers and references, but that is okay
-// because we only use this internally with unqualified object types.
-template <class T>
-std::string NameOf() {
- static const absl::string_view result = NameOfImpl<T>();
- return std::string(result);
-}
-
-////////////////////////////////////////////////////////////////////////////////
-//
-// Metafunction to check if a type is callable with no explicit arguments
-template <class Fun, class /*Enabler*/ = void>
-struct IsNullaryCallableImpl : std::false_type {};
-
-template <class Fun>
-struct IsNullaryCallableImpl<
- Fun, absl::void_t<decltype(std::declval<const Fun&>()())>>
- : std::true_type {
- using result_type = decltype(std::declval<const Fun&>()());
-
- template <class ValueType>
- using for_type = std::is_same<ValueType, result_type>;
-
- using void_if_true = void;
-};
-
-template <class Fun>
-struct IsNullaryCallable : IsNullaryCallableImpl<Fun> {};
-//
-////////////////////////////////////////////////////////////////////////////////
-
-// A type that contains a function object that returns an instance of a type
-// that is undergoing conformance testing. This function is required to always
-// return the same value upon invocation.
-template <class Fun>
-struct GeneratorType;
-
-// A type that contains a tuple of GeneratorType<Fun> where each Fun has the
-// same return type. The result of each of the different generators should all
-// be equal values, though the underlying object representation may differ (such
-// as if one returns 0.0 and another return -0.0, or if one returns an empty
-// vector and another returns an empty vector with a different capacity.
-template <class... Funs>
-struct EquivalenceClassType;
-
-////////////////////////////////////////////////////////////////////////////////
-//
-// A metafunction to check if a type is a specialization of EquivalenceClassType
-template <class T>
-struct IsEquivalenceClass : std::false_type {};
-
-template <>
-struct IsEquivalenceClass<EquivalenceClassType<>> : std::true_type {
- using self = IsEquivalenceClass;
-
- // A metafunction to check if this EquivalenceClassType is a valid
- // EquivalenceClassType for a type `ValueType` that is undergoing testing
- template <class ValueType>
- using for_type = std::true_type;
-};
-
-template <class Head, class... Tail>
-struct IsEquivalenceClass<EquivalenceClassType<Head, Tail...>>
- : std::true_type {
- using self = IsEquivalenceClass;
-
- // The type undergoing conformance testing that this EquivalenceClass
- // corresponds to
- using result_type = typename IsNullaryCallable<Head>::result_type;
-
- // A metafunction to check if this EquivalenceClassType is a valid
- // EquivalenceClassType for a type `ValueType` that is undergoing testing
- template <class ValueType>
- using for_type = std::is_same<ValueType, result_type>;
-};
-//
-////////////////////////////////////////////////////////////////////////////////
-
-// A type that contains an ordered series of EquivalenceClassTypes, where the
-// the function object of each underlying GeneratorType has the same return type
-//
-// These equivalence classes are required to be in a logical ascending order
-// that is consistent with comparison operators that are defined for the return
-// type of each GeneratorType, if any.
-template <class... EqClasses>
-struct OrderedEquivalenceClasses;
-
-////////////////////////////////////////////////////////////////////////////////
-//
-// A metafunction to determine the return type of the function object contained
-// in a GeneratorType specialization.
-template <class T>
-struct ResultOfGenerator {};
-
-template <class Fun>
-struct ResultOfGenerator<GeneratorType<Fun>> {
- using type = decltype(std::declval<const Fun&>()());
-};
-
-template <class Fun>
-using ResultOfGeneratorT = typename ResultOfGenerator<GeneratorType<Fun>>::type;
-//
-////////////////////////////////////////////////////////////////////////////////
-
-////////////////////////////////////////////////////////////////////////////////
-//
-// A metafunction that yields true iff each of Funs is a GeneratorType
-// specialization and they all contain functions with the same return type
-template <class /*Enabler*/, class... Funs>
-struct AreGeneratorsWithTheSameReturnTypeImpl : std::false_type {};
-
-template <>
-struct AreGeneratorsWithTheSameReturnTypeImpl<void> : std::true_type {};
-
-template <class Head, class... Tail>
-struct AreGeneratorsWithTheSameReturnTypeImpl<
- typename std::enable_if<absl::conjunction<std::is_same<
- ResultOfGeneratorT<Head>, ResultOfGeneratorT<Tail>>...>::value>::type,
- Head, Tail...> : std::true_type {};
-
-template <class... Funs>
-struct AreGeneratorsWithTheSameReturnType
- : AreGeneratorsWithTheSameReturnTypeImpl<void, Funs...>::type {};
-//
-////////////////////////////////////////////////////////////////////////////////
-
-////////////////////////////////////////////////////////////////////////////////
-//
-// A metafunction that yields true iff each of Funs is an EquivalenceClassType
-// specialization and they all contain GeneratorType specializations that have
-// the same return type
-template <class... EqClasses>
-struct AreEquivalenceClassesOfTheSameType {
- static_assert(sizeof...(EqClasses) != sizeof...(EqClasses), "");
-};
-
-template <>
-struct AreEquivalenceClassesOfTheSameType<> : std::true_type {
- using self = AreEquivalenceClassesOfTheSameType;
-
- // Metafunction to check that a type is the same as all of the equivalence
- // classes, if any.
- // Note: In this specialization there are no equivalence classes, so the
- // value type is always compatible.
- template <class /*ValueType*/>
- using for_type = std::true_type;
-};
-
-template <class... Funs>
-struct AreEquivalenceClassesOfTheSameType<EquivalenceClassType<Funs...>>
- : std::true_type {
- using self = AreEquivalenceClassesOfTheSameType;
-
- // Metafunction to check that a type is the same as all of the equivalence
- // classes, if any.
- template <class ValueType>
- using for_type = typename IsEquivalenceClass<
- EquivalenceClassType<Funs...>>::template for_type<ValueType>;
-};
-
-template <class... TailEqClasses>
-struct AreEquivalenceClassesOfTheSameType<
- EquivalenceClassType<>, EquivalenceClassType<>, TailEqClasses...>
- : AreEquivalenceClassesOfTheSameType<TailEqClasses...>::self {};
-
-template <class HeadNextFun, class... TailNextFuns, class... TailEqClasses>
-struct AreEquivalenceClassesOfTheSameType<
- EquivalenceClassType<>, EquivalenceClassType<HeadNextFun, TailNextFuns...>,
- TailEqClasses...>
- : AreEquivalenceClassesOfTheSameType<
- EquivalenceClassType<HeadNextFun, TailNextFuns...>,
- TailEqClasses...>::self {};
-
-template <class HeadHeadFun, class... TailHeadFuns, class... TailEqClasses>
-struct AreEquivalenceClassesOfTheSameType<
- EquivalenceClassType<HeadHeadFun, TailHeadFuns...>, EquivalenceClassType<>,
- TailEqClasses...>
- : AreEquivalenceClassesOfTheSameType<
- EquivalenceClassType<HeadHeadFun, TailHeadFuns...>,
- TailEqClasses...>::self {};
-
-template <class HeadHeadFun, class... TailHeadFuns, class HeadNextFun,
- class... TailNextFuns, class... TailEqClasses>
-struct AreEquivalenceClassesOfTheSameType<
- EquivalenceClassType<HeadHeadFun, TailHeadFuns...>,
- EquivalenceClassType<HeadNextFun, TailNextFuns...>, TailEqClasses...>
- : absl::conditional_t<
- IsNullaryCallable<HeadNextFun>::template for_type<
- typename IsNullaryCallable<HeadHeadFun>::result_type>::value,
- AreEquivalenceClassesOfTheSameType<
- EquivalenceClassType<HeadHeadFun, TailHeadFuns...>,
- TailEqClasses...>,
- std::false_type> {};
-//
-////////////////////////////////////////////////////////////////////////////////
-
-// Execute a function for each passed-in parameter.
-template <class Fun, class... Cases>
-void ForEachParameter(const Fun& fun, const Cases&... cases) {
- const std::initializer_list<bool> results = {
- (static_cast<void>(fun(cases)), true)...};
-
- (void)results;
-}
-
-// Execute a function on each passed-in parameter (using a bound function).
-template <class Fun>
-struct ForEachParameterFun {
- template <class... T>
- void operator()(const T&... cases) const {
- (ForEachParameter)(fun, cases...);
- }
-
- Fun fun;
-};
-
-// Execute a function on each element of a tuple.
-template <class Fun, class Tup>
-void ForEachTupleElement(const Fun& fun, const Tup& tup) {
- absl::apply(ForEachParameterFun<Fun>{fun}, tup);
-}
-
-////////////////////////////////////////////////////////////////////////////////
-//
-// Execute a function for each combination of two elements of a tuple, including
-// combinations of an element with itself.
-template <class Fun, class... T>
-struct ForEveryTwoImpl {
- template <class Lhs>
- struct WithBoundLhs {
- template <class Rhs>
- void operator()(const Rhs& rhs) const {
- fun(lhs, rhs);
- }
-
- Fun fun;
- Lhs lhs;
- };
-
- template <class Lhs>
- void operator()(const Lhs& lhs) const {
- (ForEachTupleElement)(WithBoundLhs<Lhs>{fun, lhs}, args);
- }
-
- Fun fun;
- std::tuple<T...> args;
-};
-
-template <class Fun, class... T>
-void ForEveryTwo(const Fun& fun, std::tuple<T...> args) {
- (ForEachTupleElement)(ForEveryTwoImpl<Fun, T...>{fun, args}, args);
-}
-//
-////////////////////////////////////////////////////////////////////////////////
-
-////////////////////////////////////////////////////////////////////////////////
-//
-// Insert all values into an associative container
-template<class Container>
-void InsertEach(Container* cont) {
-}
-
-template<class Container, class H, class... T>
-void InsertEach(Container* cont, H&& head, T&&... tail) {
- cont->insert(head);
- (InsertEach)(cont, tail...);
-}
-//
-////////////////////////////////////////////////////////////////////////////////
-// A template with a nested "Invoke" static-member-function that executes a
-// passed-in Callable when `Condition` is true, otherwise it ignores the
-// Callable. This is useful for executing a function object with a condition
-// that corresponds to whether or not the Callable can be safely instantiated.
-// It has some overlapping uses with C++17 `if constexpr`.
-template <bool Condition>
-struct If;
-
-template <>
-struct If</*Condition =*/false> {
- template <class Fun, class... P>
- static void Invoke(const Fun& /*fun*/, P&&... /*args*/) {}
-};
-
-template <>
-struct If</*Condition =*/true> {
- template <class Fun, class... P>
- static void Invoke(const Fun& fun, P&&... args) {
- // TODO(calabrese) Use std::invoke equivalent instead of function-call.
- fun(absl::forward<P>(args)...);
- }
-};
-
-//
-// ABSL_INTERNAL_STRINGIZE(...)
-//
-// This variadic macro transforms its arguments into a c-string literal after
-// expansion.
-//
-// Example:
-//
-// ABSL_INTERNAL_STRINGIZE(std::array<int, 10>)
-//
-// Results in:
-//
-// "std::array<int, 10>"
-#define ABSL_INTERNAL_STRINGIZE(...) ABSL_INTERNAL_STRINGIZE_IMPL((__VA_ARGS__))
-#define ABSL_INTERNAL_STRINGIZE_IMPL(arg) ABSL_INTERNAL_STRINGIZE_IMPL2 arg
-#define ABSL_INTERNAL_STRINGIZE_IMPL2(...) #__VA_ARGS__
-
-} // namespace types_internal
-ABSL_NAMESPACE_END
-} // namespace absl
-
-#endif // ABSL_TYPES_INTERNAL_CONFORMANCE_TESTING_HELPERS_H_
diff --git a/absl/types/internal/conformance_testing_test.cc b/absl/types/internal/conformance_testing_test.cc
deleted file mode 100644
index cf262fa..0000000
--- a/absl/types/internal/conformance_testing_test.cc
+++ /dev/null
@@ -1,1556 +0,0 @@
-// Copyright 2019 The Abseil Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "absl/types/internal/conformance_testing.h"
-
-#include <new>
-#include <type_traits>
-#include <utility>
-
-#include "gtest/gtest.h"
-#include "absl/meta/type_traits.h"
-#include "absl/types/internal/conformance_aliases.h"
-#include "absl/types/internal/conformance_profile.h"
-
-namespace {
-
-namespace ti = absl::types_internal;
-
-template <class T>
-using DefaultConstructibleWithNewImpl = decltype(::new (std::nothrow) T);
-
-template <class T>
-using DefaultConstructibleWithNew =
- absl::type_traits_internal::is_detected<DefaultConstructibleWithNewImpl, T>;
-
-template <class T>
-using MoveConstructibleWithNewImpl =
- decltype(::new (std::nothrow) T(std::declval<T>()));
-
-template <class T>
-using MoveConstructibleWithNew =
- absl::type_traits_internal::is_detected<MoveConstructibleWithNewImpl, T>;
-
-template <class T>
-using CopyConstructibleWithNewImpl =
- decltype(::new (std::nothrow) T(std::declval<const T&>()));
-
-template <class T>
-using CopyConstructibleWithNew =
- absl::type_traits_internal::is_detected<CopyConstructibleWithNewImpl, T>;
-
-template <class T,
- class Result =
- std::integral_constant<bool, noexcept(::new (std::nothrow) T)>>
-using NothrowDefaultConstructibleWithNewImpl =
- typename std::enable_if<Result::value>::type;
-
-template <class T>
-using NothrowDefaultConstructibleWithNew =
- absl::type_traits_internal::is_detected<
- NothrowDefaultConstructibleWithNewImpl, T>;
-
-template <class T,
- class Result = std::integral_constant<
- bool, noexcept(::new (std::nothrow) T(std::declval<T>()))>>
-using NothrowMoveConstructibleWithNewImpl =
- typename std::enable_if<Result::value>::type;
-
-template <class T>
-using NothrowMoveConstructibleWithNew =
- absl::type_traits_internal::is_detected<NothrowMoveConstructibleWithNewImpl,
- T>;
-
-template <class T,
- class Result = std::integral_constant<
- bool, noexcept(::new (std::nothrow) T(std::declval<const T&>()))>>
-using NothrowCopyConstructibleWithNewImpl =
- typename std::enable_if<Result::value>::type;
-
-template <class T>
-using NothrowCopyConstructibleWithNew =
- absl::type_traits_internal::is_detected<NothrowCopyConstructibleWithNewImpl,
- T>;
-
-// NOTE: ?: is used to verify contextually-convertible to bool and not simply
-// implicit or explicit convertibility.
-#define ABSL_INTERNAL_COMPARISON_OP_EXPR(op) \
- ((std::declval<const T&>() op std::declval<const T&>()) ? true : true)
-
-#define ABSL_INTERNAL_COMPARISON_OP_TRAIT(name, op) \
- template <class T> \
- using name##Impl = decltype(ABSL_INTERNAL_COMPARISON_OP_EXPR(op)); \
- \
- template <class T> \
- using name = absl::type_traits_internal::is_detected<name##Impl, T>; \
- \
- template <class T, \
- class Result = std::integral_constant< \
- bool, noexcept(ABSL_INTERNAL_COMPARISON_OP_EXPR(op))>> \
- using Nothrow##name##Impl = typename std::enable_if<Result::value>::type; \
- \
- template <class T> \
- using Nothrow##name = \
- absl::type_traits_internal::is_detected<Nothrow##name##Impl, T>
-
-ABSL_INTERNAL_COMPARISON_OP_TRAIT(EqualityComparable, ==);
-ABSL_INTERNAL_COMPARISON_OP_TRAIT(InequalityComparable, !=);
-ABSL_INTERNAL_COMPARISON_OP_TRAIT(LessThanComparable, <);
-ABSL_INTERNAL_COMPARISON_OP_TRAIT(LessEqualComparable, <=);
-ABSL_INTERNAL_COMPARISON_OP_TRAIT(GreaterEqualComparable, >=);
-ABSL_INTERNAL_COMPARISON_OP_TRAIT(GreaterThanComparable, >);
-
-#undef ABSL_INTERNAL_COMPARISON_OP_TRAIT
-
-template <class T>
-class ProfileTest : public ::testing::Test {};
-
-TYPED_TEST_SUITE_P(ProfileTest);
-
-TYPED_TEST_P(ProfileTest, HasAppropriateConstructionProperties) {
- using profile = typename TypeParam::profile;
- using arch = typename TypeParam::arch;
- using expected_profile = typename TypeParam::expected_profile;
-
- using props = ti::PropertiesOfT<profile>;
- using arch_props = ti::PropertiesOfArchetypeT<arch>;
- using expected_props = ti::PropertiesOfT<expected_profile>;
-
- // Make sure all of the properties are as expected.
- // There are seemingly redundant tests here to make it easier to diagnose
- // the specifics of the failure if something were to go wrong.
- EXPECT_TRUE((std::is_same<props, arch_props>::value));
- EXPECT_TRUE((std::is_same<props, expected_props>::value));
- EXPECT_TRUE((std::is_same<arch_props, expected_props>::value));
-
- EXPECT_EQ(props::default_constructible_support,
- expected_props::default_constructible_support);
-
- EXPECT_EQ(props::move_constructible_support,
- expected_props::move_constructible_support);
-
- EXPECT_EQ(props::copy_constructible_support,
- expected_props::copy_constructible_support);
-
- EXPECT_EQ(props::destructible_support, expected_props::destructible_support);
-
- // Avoid additional error message noise when profile and archetype match with
- // each other but were not what was expected.
- if (!std::is_same<props, arch_props>::value) {
- EXPECT_EQ(arch_props::default_constructible_support,
- expected_props::default_constructible_support);
-
- EXPECT_EQ(arch_props::move_constructible_support,
- expected_props::move_constructible_support);
-
- EXPECT_EQ(arch_props::copy_constructible_support,
- expected_props::copy_constructible_support);
-
- EXPECT_EQ(arch_props::destructible_support,
- expected_props::destructible_support);
- }
-
- //////////////////////////////////////////////////////////////////////////////
- // Default constructor checks //
- //////////////////////////////////////////////////////////////////////////////
- EXPECT_EQ(props::default_constructible_support,
- expected_props::default_constructible_support);
-
- switch (expected_props::default_constructible_support) {
- case ti::default_constructible::maybe:
- EXPECT_FALSE(DefaultConstructibleWithNew<arch>::value);
- EXPECT_FALSE(NothrowDefaultConstructibleWithNew<arch>::value);
-
- // Standard constructible traits depend on the destructor.
- if (std::is_destructible<arch>::value) {
- EXPECT_FALSE(std::is_default_constructible<arch>::value);
- EXPECT_FALSE(std::is_nothrow_default_constructible<arch>::value);
- EXPECT_FALSE(absl::is_trivially_default_constructible<arch>::value);
- }
- break;
- case ti::default_constructible::yes:
- EXPECT_TRUE(DefaultConstructibleWithNew<arch>::value);
- EXPECT_FALSE(NothrowDefaultConstructibleWithNew<arch>::value);
-
- // Standard constructible traits depend on the destructor.
- if (std::is_destructible<arch>::value) {
- EXPECT_TRUE(std::is_default_constructible<arch>::value);
- EXPECT_FALSE(std::is_nothrow_default_constructible<arch>::value);
- EXPECT_FALSE(absl::is_trivially_default_constructible<arch>::value);
- }
- break;
- case ti::default_constructible::nothrow:
- EXPECT_TRUE(DefaultConstructibleWithNew<arch>::value);
- EXPECT_TRUE(NothrowDefaultConstructibleWithNew<arch>::value);
-
- // Standard constructible traits depend on the destructor.
- if (std::is_destructible<arch>::value) {
- EXPECT_TRUE(std::is_default_constructible<arch>::value);
- EXPECT_TRUE(std::is_nothrow_default_constructible<arch>::value);
- EXPECT_FALSE(absl::is_trivially_default_constructible<arch>::value);
-
- // Constructor traits also check the destructor.
- if (std::is_nothrow_destructible<arch>::value) {
- EXPECT_TRUE(std::is_nothrow_default_constructible<arch>::value);
- }
- }
- break;
- case ti::default_constructible::trivial:
- EXPECT_TRUE(DefaultConstructibleWithNew<arch>::value);
- EXPECT_TRUE(NothrowDefaultConstructibleWithNew<arch>::value);
-
- // Standard constructible traits depend on the destructor.
- if (std::is_destructible<arch>::value) {
- EXPECT_TRUE(std::is_default_constructible<arch>::value);
- EXPECT_TRUE(std::is_nothrow_default_constructible<arch>::value);
-
- // Constructor triviality traits require trivially destructible types.
- if (absl::is_trivially_destructible<arch>::value) {
- EXPECT_TRUE(absl::is_trivially_default_constructible<arch>::value);
- }
- }
- break;
- }
-
- //////////////////////////////////////////////////////////////////////////////
- // Move constructor checks //
- //////////////////////////////////////////////////////////////////////////////
- EXPECT_EQ(props::move_constructible_support,
- expected_props::move_constructible_support);
-
- switch (expected_props::move_constructible_support) {
- case ti::move_constructible::maybe:
- EXPECT_FALSE(MoveConstructibleWithNew<arch>::value);
- EXPECT_FALSE(NothrowMoveConstructibleWithNew<arch>::value);
-
- // Standard constructible traits depend on the destructor.
- if (std::is_destructible<arch>::value) {
- EXPECT_FALSE(std::is_move_constructible<arch>::value);
- EXPECT_FALSE(std::is_nothrow_move_constructible<arch>::value);
- EXPECT_FALSE(absl::is_trivially_move_constructible<arch>::value);
- }
- break;
- case ti::move_constructible::yes:
- EXPECT_TRUE(MoveConstructibleWithNew<arch>::value);
- EXPECT_FALSE(NothrowMoveConstructibleWithNew<arch>::value);
-
- // Standard constructible traits depend on the destructor.
- if (std::is_destructible<arch>::value) {
- EXPECT_TRUE(std::is_move_constructible<arch>::value);
- EXPECT_FALSE(std::is_nothrow_move_constructible<arch>::value);
- EXPECT_FALSE(absl::is_trivially_move_constructible<arch>::value);
- }
- break;
- case ti::move_constructible::nothrow:
- EXPECT_TRUE(MoveConstructibleWithNew<arch>::value);
- EXPECT_TRUE(NothrowMoveConstructibleWithNew<arch>::value);
-
- // Standard constructible traits depend on the destructor.
- if (std::is_destructible<arch>::value) {
- EXPECT_TRUE(std::is_move_constructible<arch>::value);
- EXPECT_TRUE(std::is_nothrow_move_constructible<arch>::value);
- EXPECT_FALSE(absl::is_trivially_move_constructible<arch>::value);
-
- // Constructor traits also check the destructor.
- if (std::is_nothrow_destructible<arch>::value) {
- EXPECT_TRUE(std::is_nothrow_move_constructible<arch>::value);
- }
- }
- break;
- case ti::move_constructible::trivial:
- EXPECT_TRUE(MoveConstructibleWithNew<arch>::value);
- EXPECT_TRUE(NothrowMoveConstructibleWithNew<arch>::value);
-
- // Standard constructible traits depend on the destructor.
- if (std::is_destructible<arch>::value) {
- EXPECT_TRUE(std::is_move_constructible<arch>::value);
- EXPECT_TRUE(std::is_nothrow_move_constructible<arch>::value);
-
- // Constructor triviality traits require trivially destructible types.
- if (absl::is_trivially_destructible<arch>::value) {
- EXPECT_TRUE(absl::is_trivially_move_constructible<arch>::value);
- }
- }
- break;
- }
-
- //////////////////////////////////////////////////////////////////////////////
- // Copy constructor checks //
- //////////////////////////////////////////////////////////////////////////////
- EXPECT_EQ(props::copy_constructible_support,
- expected_props::copy_constructible_support);
-
- switch (expected_props::copy_constructible_support) {
- case ti::copy_constructible::maybe:
- EXPECT_FALSE(CopyConstructibleWithNew<arch>::value);
- EXPECT_FALSE(NothrowCopyConstructibleWithNew<arch>::value);
-
- // Standard constructible traits depend on the destructor.
- if (std::is_destructible<arch>::value) {
- EXPECT_FALSE(std::is_copy_constructible<arch>::value);
- EXPECT_FALSE(std::is_nothrow_copy_constructible<arch>::value);
- EXPECT_FALSE(absl::is_trivially_copy_constructible<arch>::value);
- }
- break;
- case ti::copy_constructible::yes:
- EXPECT_TRUE(CopyConstructibleWithNew<arch>::value);
- EXPECT_FALSE(NothrowCopyConstructibleWithNew<arch>::value);
-
- // Standard constructible traits depend on the destructor.
- if (std::is_destructible<arch>::value) {
- EXPECT_TRUE(std::is_copy_constructible<arch>::value);
- EXPECT_FALSE(std::is_nothrow_copy_constructible<arch>::value);
- EXPECT_FALSE(absl::is_trivially_copy_constructible<arch>::value);
- }
- break;
- case ti::copy_constructible::nothrow:
- EXPECT_TRUE(CopyConstructibleWithNew<arch>::value);
- EXPECT_TRUE(NothrowCopyConstructibleWithNew<arch>::value);
-
- // Standard constructible traits depend on the destructor.
- if (std::is_destructible<arch>::value) {
- EXPECT_TRUE(std::is_copy_constructible<arch>::value);
- EXPECT_TRUE(std::is_nothrow_copy_constructible<arch>::value);
- EXPECT_FALSE(absl::is_trivially_copy_constructible<arch>::value);
-
- // Constructor traits also check the destructor.
- if (std::is_nothrow_destructible<arch>::value) {
- EXPECT_TRUE(std::is_nothrow_copy_constructible<arch>::value);
- }
- }
- break;
- case ti::copy_constructible::trivial:
- EXPECT_TRUE(CopyConstructibleWithNew<arch>::value);
- EXPECT_TRUE(NothrowCopyConstructibleWithNew<arch>::value);
-
- // Standard constructible traits depend on the destructor.
- if (std::is_destructible<arch>::value) {
- EXPECT_TRUE(std::is_copy_constructible<arch>::value);
- EXPECT_TRUE(std::is_nothrow_copy_constructible<arch>::value);
-
- // Constructor triviality traits require trivially destructible types.
- if (absl::is_trivially_destructible<arch>::value) {
- EXPECT_TRUE(absl::is_trivially_copy_constructible<arch>::value);
- }
- }
- break;
- }
-
- //////////////////////////////////////////////////////////////////////////////
- // Destructible checks //
- //////////////////////////////////////////////////////////////////////////////
- EXPECT_EQ(props::destructible_support, expected_props::destructible_support);
-
- switch (expected_props::destructible_support) {
- case ti::destructible::maybe:
- EXPECT_FALSE(std::is_destructible<arch>::value);
- EXPECT_FALSE(std::is_nothrow_destructible<arch>::value);
- EXPECT_FALSE(absl::is_trivially_destructible<arch>::value);
- break;
- case ti::destructible::yes:
- EXPECT_TRUE(std::is_destructible<arch>::value);
- EXPECT_FALSE(std::is_nothrow_destructible<arch>::value);
- EXPECT_FALSE(absl::is_trivially_destructible<arch>::value);
- break;
- case ti::destructible::nothrow:
- EXPECT_TRUE(std::is_destructible<arch>::value);
- EXPECT_TRUE(std::is_nothrow_destructible<arch>::value);
- EXPECT_FALSE(absl::is_trivially_destructible<arch>::value);
- break;
- case ti::destructible::trivial:
- EXPECT_TRUE(std::is_destructible<arch>::value);
- EXPECT_TRUE(std::is_nothrow_destructible<arch>::value);
- EXPECT_TRUE(absl::is_trivially_destructible<arch>::value);
- break;
- }
-}
-
-TYPED_TEST_P(ProfileTest, HasAppropriateAssignmentProperties) {
- using profile = typename TypeParam::profile;
- using arch = typename TypeParam::arch;
- using expected_profile = typename TypeParam::expected_profile;
-
- using props = ti::PropertiesOfT<profile>;
- using arch_props = ti::PropertiesOfArchetypeT<arch>;
- using expected_props = ti::PropertiesOfT<expected_profile>;
-
- // Make sure all of the properties are as expected.
- // There are seemingly redundant tests here to make it easier to diagnose
- // the specifics of the failure if something were to go wrong.
- EXPECT_TRUE((std::is_same<props, arch_props>::value));
- EXPECT_TRUE((std::is_same<props, expected_props>::value));
- EXPECT_TRUE((std::is_same<arch_props, expected_props>::value));
-
- EXPECT_EQ(props::move_assignable_support,
- expected_props::move_assignable_support);
-
- EXPECT_EQ(props::copy_assignable_support,
- expected_props::copy_assignable_support);
-
- // Avoid additional error message noise when profile and archetype match with
- // each other but were not what was expected.
- if (!std::is_same<props, arch_props>::value) {
- EXPECT_EQ(arch_props::move_assignable_support,
- expected_props::move_assignable_support);
-
- EXPECT_EQ(arch_props::copy_assignable_support,
- expected_props::copy_assignable_support);
- }
-
- //////////////////////////////////////////////////////////////////////////////
- // Move assignment checks //
- //////////////////////////////////////////////////////////////////////////////
- EXPECT_EQ(props::move_assignable_support,
- expected_props::move_assignable_support);
-
- switch (expected_props::move_assignable_support) {
- case ti::move_assignable::maybe:
- EXPECT_FALSE(std::is_move_assignable<arch>::value);
- EXPECT_FALSE(std::is_nothrow_move_assignable<arch>::value);
- EXPECT_FALSE(absl::is_trivially_move_assignable<arch>::value);
- break;
- case ti::move_assignable::yes:
- EXPECT_TRUE(std::is_move_assignable<arch>::value);
- EXPECT_FALSE(std::is_nothrow_move_assignable<arch>::value);
- EXPECT_FALSE(absl::is_trivially_move_assignable<arch>::value);
- break;
- case ti::move_assignable::nothrow:
- EXPECT_TRUE(std::is_move_assignable<arch>::value);
- EXPECT_TRUE(std::is_nothrow_move_assignable<arch>::value);
- EXPECT_FALSE(absl::is_trivially_move_assignable<arch>::value);
- break;
- case ti::move_assignable::trivial:
- EXPECT_TRUE(std::is_move_assignable<arch>::value);
- EXPECT_TRUE(std::is_nothrow_move_assignable<arch>::value);
- EXPECT_TRUE(absl::is_trivially_move_assignable<arch>::value);
- break;
- }
-
- //////////////////////////////////////////////////////////////////////////////
- // Copy assignment checks //
- //////////////////////////////////////////////////////////////////////////////
- EXPECT_EQ(props::copy_assignable_support,
- expected_props::copy_assignable_support);
-
- switch (expected_props::copy_assignable_support) {
- case ti::copy_assignable::maybe:
- EXPECT_FALSE(std::is_copy_assignable<arch>::value);
- EXPECT_FALSE(std::is_nothrow_copy_assignable<arch>::value);
- EXPECT_FALSE(absl::is_trivially_copy_assignable<arch>::value);
- break;
- case ti::copy_assignable::yes:
- EXPECT_TRUE(std::is_copy_assignable<arch>::value);
- EXPECT_FALSE(std::is_nothrow_copy_assignable<arch>::value);
- EXPECT_FALSE(absl::is_trivially_copy_assignable<arch>::value);
- break;
- case ti::copy_assignable::nothrow:
- EXPECT_TRUE(std::is_copy_assignable<arch>::value);
- EXPECT_TRUE(std::is_nothrow_copy_assignable<arch>::value);
- EXPECT_FALSE(absl::is_trivially_copy_assignable<arch>::value);
- break;
- case ti::copy_assignable::trivial:
- EXPECT_TRUE(std::is_copy_assignable<arch>::value);
- EXPECT_TRUE(std::is_nothrow_copy_assignable<arch>::value);
- EXPECT_TRUE(absl::is_trivially_copy_assignable<arch>::value);
- break;
- }
-}
-
-TYPED_TEST_P(ProfileTest, HasAppropriateComparisonProperties) {
- using profile = typename TypeParam::profile;
- using arch = typename TypeParam::arch;
- using expected_profile = typename TypeParam::expected_profile;
-
- using props = ti::PropertiesOfT<profile>;
- using arch_props = ti::PropertiesOfArchetypeT<arch>;
- using expected_props = ti::PropertiesOfT<expected_profile>;
-
- // Make sure all of the properties are as expected.
- // There are seemingly redundant tests here to make it easier to diagnose
- // the specifics of the failure if something were to go wrong.
- EXPECT_TRUE((std::is_same<props, arch_props>::value));
- EXPECT_TRUE((std::is_same<props, expected_props>::value));
- EXPECT_TRUE((std::is_same<arch_props, expected_props>::value));
-
- EXPECT_EQ(props::equality_comparable_support,
- expected_props::equality_comparable_support);
-
- EXPECT_EQ(props::inequality_comparable_support,
- expected_props::inequality_comparable_support);
-
- EXPECT_EQ(props::less_than_comparable_support,
- expected_props::less_than_comparable_support);
-
- EXPECT_EQ(props::less_equal_comparable_support,
- expected_props::less_equal_comparable_support);
-
- EXPECT_EQ(props::greater_equal_comparable_support,
- expected_props::greater_equal_comparable_support);
-
- EXPECT_EQ(props::greater_than_comparable_support,
- expected_props::greater_than_comparable_support);
-
- // Avoid additional error message noise when profile and archetype match with
- // each other but were not what was expected.
- if (!std::is_same<props, arch_props>::value) {
- EXPECT_EQ(arch_props::equality_comparable_support,
- expected_props::equality_comparable_support);
-
- EXPECT_EQ(arch_props::inequality_comparable_support,
- expected_props::inequality_comparable_support);
-
- EXPECT_EQ(arch_props::less_than_comparable_support,
- expected_props::less_than_comparable_support);
-
- EXPECT_EQ(arch_props::less_equal_comparable_support,
- expected_props::less_equal_comparable_support);
-
- EXPECT_EQ(arch_props::greater_equal_comparable_support,
- expected_props::greater_equal_comparable_support);
-
- EXPECT_EQ(arch_props::greater_than_comparable_support,
- expected_props::greater_than_comparable_support);
- }
-
- //////////////////////////////////////////////////////////////////////////////
- // Equality comparable checks //
- //////////////////////////////////////////////////////////////////////////////
- switch (expected_props::equality_comparable_support) {
- case ti::equality_comparable::maybe:
- EXPECT_FALSE(EqualityComparable<arch>::value);
- EXPECT_FALSE(NothrowEqualityComparable<arch>::value);
- break;
- case ti::equality_comparable::yes:
- EXPECT_TRUE(EqualityComparable<arch>::value);
- EXPECT_FALSE(NothrowEqualityComparable<arch>::value);
- break;
- case ti::equality_comparable::nothrow:
- EXPECT_TRUE(EqualityComparable<arch>::value);
- EXPECT_TRUE(NothrowEqualityComparable<arch>::value);
- break;
- }
-
- //////////////////////////////////////////////////////////////////////////////
- // Inequality comparable checks //
- //////////////////////////////////////////////////////////////////////////////
- switch (expected_props::inequality_comparable_support) {
- case ti::inequality_comparable::maybe:
- EXPECT_FALSE(InequalityComparable<arch>::value);
- EXPECT_FALSE(NothrowInequalityComparable<arch>::value);
- break;
- case ti::inequality_comparable::yes:
- EXPECT_TRUE(InequalityComparable<arch>::value);
- EXPECT_FALSE(NothrowInequalityComparable<arch>::value);
- break;
- case ti::inequality_comparable::nothrow:
- EXPECT_TRUE(InequalityComparable<arch>::value);
- EXPECT_TRUE(NothrowInequalityComparable<arch>::value);
- break;
- }
-
- //////////////////////////////////////////////////////////////////////////////
- // Less than comparable checks //
- //////////////////////////////////////////////////////////////////////////////
- switch (expected_props::less_than_comparable_support) {
- case ti::less_than_comparable::maybe:
- EXPECT_FALSE(LessThanComparable<arch>::value);
- EXPECT_FALSE(NothrowLessThanComparable<arch>::value);
- break;
- case ti::less_than_comparable::yes:
- EXPECT_TRUE(LessThanComparable<arch>::value);
- EXPECT_FALSE(NothrowLessThanComparable<arch>::value);
- break;
- case ti::less_than_comparable::nothrow:
- EXPECT_TRUE(LessThanComparable<arch>::value);
- EXPECT_TRUE(NothrowLessThanComparable<arch>::value);
- break;
- }
-
- //////////////////////////////////////////////////////////////////////////////
- // Less equal comparable checks //
- //////////////////////////////////////////////////////////////////////////////
- switch (expected_props::less_equal_comparable_support) {
- case ti::less_equal_comparable::maybe:
- EXPECT_FALSE(LessEqualComparable<arch>::value);
- EXPECT_FALSE(NothrowLessEqualComparable<arch>::value);
- break;
- case ti::less_equal_comparable::yes:
- EXPECT_TRUE(LessEqualComparable<arch>::value);
- EXPECT_FALSE(NothrowLessEqualComparable<arch>::value);
- break;
- case ti::less_equal_comparable::nothrow:
- EXPECT_TRUE(LessEqualComparable<arch>::value);
- EXPECT_TRUE(NothrowLessEqualComparable<arch>::value);
- break;
- }
-
- //////////////////////////////////////////////////////////////////////////////
- // Greater equal comparable checks //
- //////////////////////////////////////////////////////////////////////////////
- switch (expected_props::greater_equal_comparable_support) {
- case ti::greater_equal_comparable::maybe:
- EXPECT_FALSE(GreaterEqualComparable<arch>::value);
- EXPECT_FALSE(NothrowGreaterEqualComparable<arch>::value);
- break;
- case ti::greater_equal_comparable::yes:
- EXPECT_TRUE(GreaterEqualComparable<arch>::value);
- EXPECT_FALSE(NothrowGreaterEqualComparable<arch>::value);
- break;
- case ti::greater_equal_comparable::nothrow:
- EXPECT_TRUE(GreaterEqualComparable<arch>::value);
- EXPECT_TRUE(NothrowGreaterEqualComparable<arch>::value);
- break;
- }
-
- //////////////////////////////////////////////////////////////////////////////
- // Greater than comparable checks //
- //////////////////////////////////////////////////////////////////////////////
- switch (expected_props::greater_than_comparable_support) {
- case ti::greater_than_comparable::maybe:
- EXPECT_FALSE(GreaterThanComparable<arch>::value);
- EXPECT_FALSE(NothrowGreaterThanComparable<arch>::value);
- break;
- case ti::greater_than_comparable::yes:
- EXPECT_TRUE(GreaterThanComparable<arch>::value);
- EXPECT_FALSE(NothrowGreaterThanComparable<arch>::value);
- break;
- case ti::greater_than_comparable::nothrow:
- EXPECT_TRUE(GreaterThanComparable<arch>::value);
- EXPECT_TRUE(NothrowGreaterThanComparable<arch>::value);
- break;
- }
-}
-
-TYPED_TEST_P(ProfileTest, HasAppropriateAuxilliaryProperties) {
- using profile = typename TypeParam::profile;
- using arch = typename TypeParam::arch;
- using expected_profile = typename TypeParam::expected_profile;
-
- using props = ti::PropertiesOfT<profile>;
- using arch_props = ti::PropertiesOfArchetypeT<arch>;
- using expected_props = ti::PropertiesOfT<expected_profile>;
-
- // Make sure all of the properties are as expected.
- // There are seemingly redundant tests here to make it easier to diagnose
- // the specifics of the failure if something were to go wrong.
- EXPECT_TRUE((std::is_same<props, arch_props>::value));
- EXPECT_TRUE((std::is_same<props, expected_props>::value));
- EXPECT_TRUE((std::is_same<arch_props, expected_props>::value));
-
- EXPECT_EQ(props::swappable_support, expected_props::swappable_support);
-
- EXPECT_EQ(props::hashable_support, expected_props::hashable_support);
-
- // Avoid additional error message noise when profile and archetype match with
- // each other but were not what was expected.
- if (!std::is_same<props, arch_props>::value) {
- EXPECT_EQ(arch_props::swappable_support, expected_props::swappable_support);
-
- EXPECT_EQ(arch_props::hashable_support, expected_props::hashable_support);
- }
-
- //////////////////////////////////////////////////////////////////////////////
- // Swappable checks //
- //////////////////////////////////////////////////////////////////////////////
- switch (expected_props::swappable_support) {
- case ti::swappable::maybe:
- EXPECT_FALSE(absl::type_traits_internal::IsSwappable<arch>::value);
- EXPECT_FALSE(absl::type_traits_internal::IsNothrowSwappable<arch>::value);
- break;
- case ti::swappable::yes:
- EXPECT_TRUE(absl::type_traits_internal::IsSwappable<arch>::value);
- EXPECT_FALSE(absl::type_traits_internal::IsNothrowSwappable<arch>::value);
- break;
- case ti::swappable::nothrow:
- EXPECT_TRUE(absl::type_traits_internal::IsSwappable<arch>::value);
- EXPECT_TRUE(absl::type_traits_internal::IsNothrowSwappable<arch>::value);
- break;
- }
-
- //////////////////////////////////////////////////////////////////////////////
- // Hashable checks //
- //////////////////////////////////////////////////////////////////////////////
- switch (expected_props::hashable_support) {
- case ti::hashable::maybe:
-#if ABSL_META_INTERNAL_STD_HASH_SFINAE_FRIENDLY_
- EXPECT_FALSE(absl::type_traits_internal::IsHashable<arch>::value);
-#endif // ABSL_META_INTERNAL_STD_HASH_SFINAE_FRIENDLY_
- break;
- case ti::hashable::yes:
- EXPECT_TRUE(absl::type_traits_internal::IsHashable<arch>::value);
- break;
- }
-}
-
-REGISTER_TYPED_TEST_SUITE_P(ProfileTest, HasAppropriateConstructionProperties,
- HasAppropriateAssignmentProperties,
- HasAppropriateComparisonProperties,
- HasAppropriateAuxilliaryProperties);
-
-template <class Profile, class Arch, class ExpectedProfile>
-struct ProfileAndExpectation {
- using profile = Profile;
- using arch = Arch;
- using expected_profile = ExpectedProfile;
-};
-
-using CoreProfilesToTest = ::testing::Types<
- // The terminating case of combine (all properties are "maybe").
- ProfileAndExpectation<ti::CombineProfiles<>,
- ti::Archetype<ti::CombineProfiles<>>,
- ti::ConformanceProfile<>>,
-
- // Core default constructor profiles
- ProfileAndExpectation<
- ti::HasDefaultConstructorProfile, ti::HasDefaultConstructorArchetype,
- ti::ConformanceProfile<ti::default_constructible::yes>>,
- ProfileAndExpectation<
- ti::HasNothrowDefaultConstructorProfile,
- ti::HasNothrowDefaultConstructorArchetype,
- ti::ConformanceProfile<ti::default_constructible::nothrow>>,
- ProfileAndExpectation<
- ti::HasTrivialDefaultConstructorProfile,
- ti::HasTrivialDefaultConstructorArchetype,
- ti::ConformanceProfile<ti::default_constructible::trivial>>,
-
- // Core move constructor profiles
- ProfileAndExpectation<
- ti::HasMoveConstructorProfile, ti::HasMoveConstructorArchetype,
- ti::ConformanceProfile<ti::default_constructible::maybe,
- ti::move_constructible::yes>>,
- ProfileAndExpectation<
- ti::HasNothrowMoveConstructorProfile,
- ti::HasNothrowMoveConstructorArchetype,
- ti::ConformanceProfile<ti::default_constructible::maybe,
- ti::move_constructible::nothrow>>,
- ProfileAndExpectation<
- ti::HasTrivialMoveConstructorProfile,
- ti::HasTrivialMoveConstructorArchetype,
- ti::ConformanceProfile<ti::default_constructible::maybe,
- ti::move_constructible::trivial>>,
-
- // Core copy constructor profiles
- ProfileAndExpectation<
- ti::HasCopyConstructorProfile, ti::HasCopyConstructorArchetype,
- ti::ConformanceProfile<ti::default_constructible::maybe,
- ti::move_constructible::maybe,
- ti::copy_constructible::yes>>,
- ProfileAndExpectation<
- ti::HasNothrowCopyConstructorProfile,
- ti::HasNothrowCopyConstructorArchetype,
- ti::ConformanceProfile<ti::default_constructible::maybe,
- ti::move_constructible::maybe,
- ti::copy_constructible::nothrow>>,
- ProfileAndExpectation<
- ti::HasTrivialCopyConstructorProfile,
- ti::HasTrivialCopyConstructorArchetype,
- ti::ConformanceProfile<ti::default_constructible::maybe,
- ti::move_constructible::maybe,
- ti::copy_constructible::trivial>>,
-
- // Core move assignment profiles
- ProfileAndExpectation<
- ti::HasMoveAssignProfile, ti::HasMoveAssignArchetype,
- ti::ConformanceProfile<
- ti::default_constructible::maybe, ti::move_constructible::maybe,
- ti::copy_constructible::maybe, ti::move_assignable::yes>>,
- ProfileAndExpectation<
- ti::HasNothrowMoveAssignProfile, ti::HasNothrowMoveAssignArchetype,
- ti::ConformanceProfile<
- ti::default_constructible::maybe, ti::move_constructible::maybe,
- ti::copy_constructible::maybe, ti::move_assignable::nothrow>>,
- ProfileAndExpectation<
- ti::HasTrivialMoveAssignProfile, ti::HasTrivialMoveAssignArchetype,
- ti::ConformanceProfile<
- ti::default_constructible::maybe, ti::move_constructible::maybe,
- ti::copy_constructible::maybe, ti::move_assignable::trivial>>,
-
- // Core copy assignment profiles
- ProfileAndExpectation<
- ti::HasCopyAssignProfile, ti::HasCopyAssignArchetype,
- ti::ConformanceProfile<
- ti::default_constructible::maybe, ti::move_constructible::maybe,
- ti::copy_constructible::maybe, ti::move_assignable::maybe,
- ti::copy_assignable::yes>>,
- ProfileAndExpectation<
- ti::HasNothrowCopyAssignProfile, ti::HasNothrowCopyAssignArchetype,
- ti::ConformanceProfile<
- ti::default_constructible::maybe, ti::move_constructible::maybe,
- ti::copy_constructible::maybe, ti::move_assignable::maybe,
- ti::copy_assignable::nothrow>>,
- ProfileAndExpectation<
- ti::HasTrivialCopyAssignProfile, ti::HasTrivialCopyAssignArchetype,
- ti::ConformanceProfile<
- ti::default_constructible::maybe, ti::move_constructible::maybe,
- ti::copy_constructible::maybe, ti::move_assignable::maybe,
- ti::copy_assignable::trivial>>,
-
- // Core destructor profiles
- ProfileAndExpectation<
- ti::HasDestructorProfile, ti::HasDestructorArchetype,
- ti::ConformanceProfile<
- ti::default_constructible::maybe, ti::move_constructible::maybe,
- ti::copy_constructible::maybe, ti::move_assignable::maybe,
- ti::copy_assignable::maybe, ti::destructible::yes>>,
- ProfileAndExpectation<
- ti::HasNothrowDestructorProfile, ti::HasNothrowDestructorArchetype,
- ti::ConformanceProfile<
- ti::default_constructible::maybe, ti::move_constructible::maybe,
- ti::copy_constructible::maybe, ti::move_assignable::maybe,
- ti::copy_assignable::maybe, ti::destructible::nothrow>>,
- ProfileAndExpectation<
- ti::HasTrivialDestructorProfile, ti::HasTrivialDestructorArchetype,
- ti::ConformanceProfile<
- ti::default_constructible::maybe, ti::move_constructible::maybe,
- ti::copy_constructible::maybe, ti::move_assignable::maybe,
- ti::copy_assignable::maybe, ti::destructible::trivial>>,
-
- // Core equality comparable profiles
- ProfileAndExpectation<
- ti::HasEqualityProfile, ti::HasEqualityArchetype,
- ti::ConformanceProfile<
- ti::default_constructible::maybe, ti::move_constructible::maybe,
- ti::copy_constructible::maybe, ti::move_assignable::maybe,
- ti::copy_assignable::maybe, ti::destructible::maybe,
- ti::equality_comparable::yes>>,
- ProfileAndExpectation<
- ti::HasNothrowEqualityProfile, ti::HasNothrowEqualityArchetype,
- ti::ConformanceProfile<
- ti::default_constructible::maybe, ti::move_constructible::maybe,
- ti::copy_constructible::maybe, ti::move_assignable::maybe,
- ti::copy_assignable::maybe, ti::destructible::maybe,
- ti::equality_comparable::nothrow>>,
-
- // Core inequality comparable profiles
- ProfileAndExpectation<
- ti::HasInequalityProfile, ti::HasInequalityArchetype,
- ti::ConformanceProfile<
- ti::default_constructible::maybe, ti::move_constructible::maybe,
- ti::copy_constructible::maybe, ti::move_assignable::maybe,
- ti::copy_assignable::maybe, ti::destructible::maybe,
- ti::equality_comparable::maybe, ti::inequality_comparable::yes>>,
- ProfileAndExpectation<
- ti::HasNothrowInequalityProfile, ti::HasNothrowInequalityArchetype,
- ti::ConformanceProfile<
- ti::default_constructible::maybe, ti::move_constructible::maybe,
- ti::copy_constructible::maybe, ti::move_assignable::maybe,
- ti::copy_assignable::maybe, ti::destructible::maybe,
- ti::equality_comparable::maybe,
- ti::inequality_comparable::nothrow>>,
-
- // Core less than comparable profiles
- ProfileAndExpectation<
- ti::HasLessThanProfile, ti::HasLessThanArchetype,
- ti::ConformanceProfile<
- ti::default_constructible::maybe, ti::move_constructible::maybe,
- ti::copy_constructible::maybe, ti::move_assignable::maybe,
- ti::copy_assignable::maybe, ti::destructible::maybe,
- ti::equality_comparable::maybe, ti::inequality_comparable::maybe,
- ti::less_than_comparable::yes>>,
- ProfileAndExpectation<
- ti::HasNothrowLessThanProfile, ti::HasNothrowLessThanArchetype,
- ti::ConformanceProfile<
- ti::default_constructible::maybe, ti::move_constructible::maybe,
- ti::copy_constructible::maybe, ti::move_assignable::maybe,
- ti::copy_assignable::maybe, ti::destructible::maybe,
- ti::equality_comparable::maybe, ti::inequality_comparable::maybe,
- ti::less_than_comparable::nothrow>>,
-
- // Core less equal comparable profiles
- ProfileAndExpectation<
- ti::HasLessEqualProfile, ti::HasLessEqualArchetype,
- ti::ConformanceProfile<
- ti::default_constructible::maybe, ti::move_constructible::maybe,
- ti::copy_constructible::maybe, ti::move_assignable::maybe,
- ti::copy_assignable::maybe, ti::destructible::maybe,
- ti::equality_comparable::maybe, ti::inequality_comparable::maybe,
- ti::less_than_comparable::maybe, ti::less_equal_comparable::yes>>,
- ProfileAndExpectation<
- ti::HasNothrowLessEqualProfile, ti::HasNothrowLessEqualArchetype,
- ti::ConformanceProfile<
- ti::default_constructible::maybe, ti::move_constructible::maybe,
- ti::copy_constructible::maybe, ti::move_assignable::maybe,
- ti::copy_assignable::maybe, ti::destructible::maybe,
- ti::equality_comparable::maybe, ti::inequality_comparable::maybe,
- ti::less_than_comparable::maybe,
- ti::less_equal_comparable::nothrow>>,
-
- // Core greater equal comparable profiles
- ProfileAndExpectation<
- ti::HasGreaterEqualProfile, ti::HasGreaterEqualArchetype,
- ti::ConformanceProfile<
- ti::default_constructible::maybe, ti::move_constructible::maybe,
- ti::copy_constructible::maybe, ti::move_assignable::maybe,
- ti::copy_assignable::maybe, ti::destructible::maybe,
- ti::equality_comparable::maybe, ti::inequality_comparable::maybe,
- ti::less_than_comparable::maybe, ti::less_equal_comparable::maybe,
- ti::greater_equal_comparable::yes>>,
- ProfileAndExpectation<
- ti::HasNothrowGreaterEqualProfile, ti::HasNothrowGreaterEqualArchetype,
- ti::ConformanceProfile<
- ti::default_constructible::maybe, ti::move_constructible::maybe,
- ti::copy_constructible::maybe, ti::move_assignable::maybe,
- ti::copy_assignable::maybe, ti::destructible::maybe,
- ti::equality_comparable::maybe, ti::inequality_comparable::maybe,
- ti::less_than_comparable::maybe, ti::less_equal_comparable::maybe,
- ti::greater_equal_comparable::nothrow>>,
-
- // Core greater than comparable profiles
- ProfileAndExpectation<
- ti::HasGreaterThanProfile, ti::HasGreaterThanArchetype,
- ti::ConformanceProfile<
- ti::default_constructible::maybe, ti::move_constructible::maybe,
- ti::copy_constructible::maybe, ti::move_assignable::maybe,
- ti::copy_assignable::maybe, ti::destructible::maybe,
- ti::equality_comparable::maybe, ti::inequality_comparable::maybe,
- ti::less_than_comparable::maybe, ti::less_equal_comparable::maybe,
- ti::greater_equal_comparable::maybe,
- ti::greater_than_comparable::yes>>,
- ProfileAndExpectation<
- ti::HasNothrowGreaterThanProfile, ti::HasNothrowGreaterThanArchetype,
- ti::ConformanceProfile<
- ti::default_constructible::maybe, ti::move_constructible::maybe,
- ti::copy_constructible::maybe, ti::move_assignable::maybe,
- ti::copy_assignable::maybe, ti::destructible::maybe,
- ti::equality_comparable::maybe, ti::inequality_comparable::maybe,
- ti::less_than_comparable::maybe, ti::less_equal_comparable::maybe,
- ti::greater_equal_comparable::maybe,
- ti::greater_than_comparable::nothrow>>,
-
- // Core swappable profiles
- ProfileAndExpectation<
- ti::HasSwapProfile, ti::HasSwapArchetype,
- ti::ConformanceProfile<
- ti::default_constructible::maybe, ti::move_constructible::maybe,
- ti::copy_constructible::maybe, ti::move_assignable::maybe,
- ti::copy_assignable::maybe, ti::destructible::maybe,
- ti::equality_comparable::maybe, ti::inequality_comparable::maybe,
- ti::less_than_comparable::maybe, ti::less_equal_comparable::maybe,
- ti::greater_equal_comparable::maybe,
- ti::greater_than_comparable::maybe, ti::swappable::yes>>,
- ProfileAndExpectation<
- ti::HasNothrowSwapProfile, ti::HasNothrowSwapArchetype,
- ti::ConformanceProfile<
- ti::default_constructible::maybe, ti::move_constructible::maybe,
- ti::copy_constructible::maybe, ti::move_assignable::maybe,
- ti::copy_assignable::maybe, ti::destructible::maybe,
- ti::equality_comparable::maybe, ti::inequality_comparable::maybe,
- ti::less_than_comparable::maybe, ti::less_equal_comparable::maybe,
- ti::greater_equal_comparable::maybe,
- ti::greater_than_comparable::maybe, ti::swappable::nothrow>>,
-
- // Core hashable profiles
- ProfileAndExpectation<
- ti::HasStdHashSpecializationProfile,
- ti::HasStdHashSpecializationArchetype,
- ti::ConformanceProfile<
- ti::default_constructible::maybe, ti::move_constructible::maybe,
- ti::copy_constructible::maybe, ti::move_assignable::maybe,
- ti::copy_assignable::maybe, ti::destructible::maybe,
- ti::equality_comparable::maybe, ti::inequality_comparable::maybe,
- ti::less_than_comparable::maybe, ti::less_equal_comparable::maybe,
- ti::greater_equal_comparable::maybe,
- ti::greater_than_comparable::maybe, ti::swappable::maybe,
- ti::hashable::yes>>>;
-
-using CommonProfilesToTest = ::testing::Types<
- // NothrowMoveConstructible
- ProfileAndExpectation<
- ti::NothrowMoveConstructibleProfile,
- ti::NothrowMoveConstructibleArchetype,
- ti::ConformanceProfile<
- ti::default_constructible::maybe, ti::move_constructible::nothrow,
- ti::copy_constructible::maybe, ti::move_assignable::maybe,
- ti::copy_assignable::maybe, ti::destructible::nothrow>>,
-
- // CopyConstructible
- ProfileAndExpectation<
- ti::CopyConstructibleProfile, ti::CopyConstructibleArchetype,
- ti::ConformanceProfile<
- ti::default_constructible::maybe, ti::move_constructible::nothrow,
- ti::copy_constructible::yes, ti::move_assignable::maybe,
- ti::copy_assignable::maybe, ti::destructible::nothrow>>,
-
- // NothrowMovable
- ProfileAndExpectation<
- ti::NothrowMovableProfile, ti::NothrowMovableArchetype,
- ti::ConformanceProfile<
- ti::default_constructible::maybe, ti::move_constructible::nothrow,
- ti::copy_constructible::maybe, ti::move_assignable::nothrow,
- ti::copy_assignable::maybe, ti::destructible::nothrow,
- ti::equality_comparable::maybe, ti::inequality_comparable::maybe,
- ti::less_than_comparable::maybe, ti::less_equal_comparable::maybe,
- ti::greater_equal_comparable::maybe,
- ti::greater_than_comparable::maybe, ti::swappable::nothrow>>,
-
- // Value
- ProfileAndExpectation<
- ti::ValueProfile, ti::ValueArchetype,
- ti::ConformanceProfile<
- ti::default_constructible::maybe, ti::move_constructible::nothrow,
- ti::copy_constructible::yes, ti::move_assignable::nothrow,
- ti::copy_assignable::yes, ti::destructible::nothrow,
- ti::equality_comparable::maybe, ti::inequality_comparable::maybe,
- ti::less_than_comparable::maybe, ti::less_equal_comparable::maybe,
- ti::greater_equal_comparable::maybe,
- ti::greater_than_comparable::maybe, ti::swappable::nothrow>>,
-
- ////////////////////////////////////////////////////////////////////////////
- // Common but also DefaultConstructible //
- ////////////////////////////////////////////////////////////////////////////
-
- // DefaultConstructibleNothrowMoveConstructible
- ProfileAndExpectation<
- ti::DefaultConstructibleNothrowMoveConstructibleProfile,
- ti::DefaultConstructibleNothrowMoveConstructibleArchetype,
- ti::ConformanceProfile<
- ti::default_constructible::yes, ti::move_constructible::nothrow,
- ti::copy_constructible::maybe, ti::move_assignable::maybe,
- ti::copy_assignable::maybe, ti::destructible::nothrow>>,
-
- // DefaultConstructibleCopyConstructible
- ProfileAndExpectation<
- ti::DefaultConstructibleCopyConstructibleProfile,
- ti::DefaultConstructibleCopyConstructibleArchetype,
- ti::ConformanceProfile<
- ti::default_constructible::yes, ti::move_constructible::nothrow,
- ti::copy_constructible::yes, ti::move_assignable::maybe,
- ti::copy_assignable::maybe, ti::destructible::nothrow>>,
-
- // DefaultConstructibleNothrowMovable
- ProfileAndExpectation<
- ti::DefaultConstructibleNothrowMovableProfile,
- ti::DefaultConstructibleNothrowMovableArchetype,
- ti::ConformanceProfile<
- ti::default_constructible::yes, ti::move_constructible::nothrow,
- ti::copy_constructible::maybe, ti::move_assignable::nothrow,
- ti::copy_assignable::maybe, ti::destructible::nothrow,
- ti::equality_comparable::maybe, ti::inequality_comparable::maybe,
- ti::less_than_comparable::maybe, ti::less_equal_comparable::maybe,
- ti::greater_equal_comparable::maybe,
- ti::greater_than_comparable::maybe, ti::swappable::nothrow>>,
-
- // DefaultConstructibleValue
- ProfileAndExpectation<
- ti::DefaultConstructibleValueProfile,
- ti::DefaultConstructibleValueArchetype,
- ti::ConformanceProfile<
- ti::default_constructible::yes, ti::move_constructible::nothrow,
- ti::copy_constructible::yes, ti::move_assignable::nothrow,
- ti::copy_assignable::yes, ti::destructible::nothrow,
- ti::equality_comparable::maybe, ti::inequality_comparable::maybe,
- ti::less_than_comparable::maybe, ti::less_equal_comparable::maybe,
- ti::greater_equal_comparable::maybe,
- ti::greater_than_comparable::maybe, ti::swappable::nothrow>>>;
-
-using ComparableHelpersProfilesToTest = ::testing::Types<
- // Equatable
- ProfileAndExpectation<
- ti::EquatableProfile, ti::EquatableArchetype,
- ti::ConformanceProfile<
- ti::default_constructible::maybe, ti::move_constructible::maybe,
- ti::copy_constructible::maybe, ti::move_assignable::maybe,
- ti::copy_assignable::maybe, ti::destructible::maybe,
- ti::equality_comparable::yes, ti::inequality_comparable::yes>>,
-
- // Comparable
- ProfileAndExpectation<
- ti::ComparableProfile, ti::ComparableArchetype,
- ti::ConformanceProfile<
- ti::default_constructible::maybe, ti::move_constructible::maybe,
- ti::copy_constructible::maybe, ti::move_assignable::maybe,
- ti::copy_assignable::maybe, ti::destructible::maybe,
- ti::equality_comparable::yes, ti::inequality_comparable::yes,
- ti::less_than_comparable::yes, ti::less_equal_comparable::yes,
- ti::greater_equal_comparable::yes,
- ti::greater_than_comparable::yes>>,
-
- // NothrowEquatable
- ProfileAndExpectation<
- ti::NothrowEquatableProfile, ti::NothrowEquatableArchetype,
- ti::ConformanceProfile<
- ti::default_constructible::maybe, ti::move_constructible::maybe,
- ti::copy_constructible::maybe, ti::move_assignable::maybe,
- ti::copy_assignable::maybe, ti::destructible::maybe,
- ti::equality_comparable::nothrow,
- ti::inequality_comparable::nothrow>>,
-
- // NothrowComparable
- ProfileAndExpectation<
- ti::NothrowComparableProfile, ti::NothrowComparableArchetype,
- ti::ConformanceProfile<
- ti::default_constructible::maybe, ti::move_constructible::maybe,
- ti::copy_constructible::maybe, ti::move_assignable::maybe,
- ti::copy_assignable::maybe, ti::destructible::maybe,
- ti::equality_comparable::nothrow,
- ti::inequality_comparable::nothrow,
- ti::less_than_comparable::nothrow,
- ti::less_equal_comparable::nothrow,
- ti::greater_equal_comparable::nothrow,
- ti::greater_than_comparable::nothrow>>>;
-
-using CommonComparableProfilesToTest = ::testing::Types<
- // ComparableNothrowMoveConstructible
- ProfileAndExpectation<
- ti::ComparableNothrowMoveConstructibleProfile,
- ti::ComparableNothrowMoveConstructibleArchetype,
- ti::ConformanceProfile<
- ti::default_constructible::maybe, ti::move_constructible::nothrow,
- ti::copy_constructible::maybe, ti::move_assignable::maybe,
- ti::copy_assignable::maybe, ti::destructible::nothrow,
- ti::equality_comparable::yes, ti::inequality_comparable::yes,
- ti::less_than_comparable::yes, ti::less_equal_comparable::yes,
- ti::greater_equal_comparable::yes,
- ti::greater_than_comparable::yes>>,
-
- // ComparableCopyConstructible
- ProfileAndExpectation<
- ti::ComparableCopyConstructibleProfile,
- ti::ComparableCopyConstructibleArchetype,
- ti::ConformanceProfile<
- ti::default_constructible::maybe, ti::move_constructible::nothrow,
- ti::copy_constructible::yes, ti::move_assignable::maybe,
- ti::copy_assignable::maybe, ti::destructible::nothrow,
- ti::equality_comparable::yes, ti::inequality_comparable::yes,
- ti::less_than_comparable::yes, ti::less_equal_comparable::yes,
- ti::greater_equal_comparable::yes,
- ti::greater_than_comparable::yes>>,
-
- // ComparableNothrowMovable
- ProfileAndExpectation<
- ti::ComparableNothrowMovableProfile,
- ti::ComparableNothrowMovableArchetype,
- ti::ConformanceProfile<
- ti::default_constructible::maybe, ti::move_constructible::nothrow,
- ti::copy_constructible::maybe, ti::move_assignable::nothrow,
- ti::copy_assignable::maybe, ti::destructible::nothrow,
- ti::equality_comparable::yes, ti::inequality_comparable::yes,
- ti::less_than_comparable::yes, ti::less_equal_comparable::yes,
- ti::greater_equal_comparable::yes, ti::greater_than_comparable::yes,
- ti::swappable::nothrow>>,
-
- // ComparableValue
- ProfileAndExpectation<
- ti::ComparableValueProfile, ti::ComparableValueArchetype,
- ti::ConformanceProfile<
- ti::default_constructible::maybe, ti::move_constructible::nothrow,
- ti::copy_constructible::yes, ti::move_assignable::nothrow,
- ti::copy_assignable::yes, ti::destructible::nothrow,
- ti::equality_comparable::yes, ti::inequality_comparable::yes,
- ti::less_than_comparable::yes, ti::less_equal_comparable::yes,
- ti::greater_equal_comparable::yes, ti::greater_than_comparable::yes,
- ti::swappable::nothrow>>>;
-
-using TrivialProfilesToTest = ::testing::Types<
- ProfileAndExpectation<
- ti::TrivialSpecialMemberFunctionsProfile,
- ti::TrivialSpecialMemberFunctionsArchetype,
- ti::ConformanceProfile<
- ti::default_constructible::trivial, ti::move_constructible::trivial,
- ti::copy_constructible::trivial, ti::move_assignable::trivial,
- ti::copy_assignable::trivial, ti::destructible::trivial,
- ti::equality_comparable::maybe, ti::inequality_comparable::maybe,
- ti::less_than_comparable::maybe, ti::less_equal_comparable::maybe,
- ti::greater_equal_comparable::maybe,
- ti::greater_than_comparable::maybe, ti::swappable::nothrow>>,
-
- ProfileAndExpectation<
- ti::TriviallyCompleteProfile, ti::TriviallyCompleteArchetype,
- ti::ConformanceProfile<
- ti::default_constructible::trivial, ti::move_constructible::trivial,
- ti::copy_constructible::trivial, ti::move_assignable::trivial,
- ti::copy_assignable::trivial, ti::destructible::trivial,
- ti::equality_comparable::yes, ti::inequality_comparable::yes,
- ti::less_than_comparable::yes, ti::less_equal_comparable::yes,
- ti::greater_equal_comparable::yes, ti::greater_than_comparable::yes,
- ti::swappable::nothrow, ti::hashable::yes>>>;
-
-INSTANTIATE_TYPED_TEST_SUITE_P(Core, ProfileTest, CoreProfilesToTest);
-INSTANTIATE_TYPED_TEST_SUITE_P(Common, ProfileTest, CommonProfilesToTest);
-INSTANTIATE_TYPED_TEST_SUITE_P(ComparableHelpers, ProfileTest,
- ComparableHelpersProfilesToTest);
-INSTANTIATE_TYPED_TEST_SUITE_P(CommonComparable, ProfileTest,
- CommonComparableProfilesToTest);
-INSTANTIATE_TYPED_TEST_SUITE_P(Trivial, ProfileTest, TrivialProfilesToTest);
-
-TEST(ConformanceTestingTest, Basic) {
- using profile = ti::CombineProfiles<ti::TriviallyCompleteProfile,
- ti::NothrowComparableProfile>;
-
- using lim = std::numeric_limits<float>;
-
- ABSL_INTERNAL_ASSERT_CONFORMANCE_OF(float)
- .INITIALIZER(-lim::infinity())
- .INITIALIZER(lim::lowest())
- .INITIALIZER(-1.f)
- .INITIALIZER(-lim::min())
- .EQUIVALENCE_CLASS(INITIALIZER(-0.f), INITIALIZER(0.f))
- .INITIALIZER(lim::min())
- .INITIALIZER(1.f)
- .INITIALIZER(lim::max())
- .INITIALIZER(lim::infinity())
- .WITH_STRICT_PROFILE(absl::types_internal::RegularityDomain, profile);
-}
-
-struct BadMoveConstruct {
- BadMoveConstruct() = default;
- BadMoveConstruct(BadMoveConstruct&& other) noexcept
- : value(other.value + 1) {}
- BadMoveConstruct& operator=(BadMoveConstruct&& other) noexcept = default;
- int value = 0;
-
- friend bool operator==(BadMoveConstruct const& lhs,
- BadMoveConstruct const& rhs) {
- return lhs.value == rhs.value;
- }
- friend bool operator!=(BadMoveConstruct const& lhs,
- BadMoveConstruct const& rhs) {
- return lhs.value != rhs.value;
- }
-};
-
-struct BadMoveAssign {
- BadMoveAssign() = default;
- BadMoveAssign(BadMoveAssign&& other) noexcept = default;
- BadMoveAssign& operator=(BadMoveAssign&& other) noexcept {
- int new_value = other.value + 1;
- value = new_value;
- return *this;
- }
- int value = 0;
-
- friend bool operator==(BadMoveAssign const& lhs, BadMoveAssign const& rhs) {
- return lhs.value == rhs.value;
- }
- friend bool operator!=(BadMoveAssign const& lhs, BadMoveAssign const& rhs) {
- return lhs.value != rhs.value;
- }
-};
-
-enum class WhichCompIsBad { eq, ne, lt, le, ge, gt };
-
-template <WhichCompIsBad Which>
-struct BadCompare {
- int value;
-
- friend bool operator==(BadCompare const& lhs, BadCompare const& rhs) {
- return Which == WhichCompIsBad::eq ? lhs.value != rhs.value
- : lhs.value == rhs.value;
- }
-
- friend bool operator!=(BadCompare const& lhs, BadCompare const& rhs) {
- return Which == WhichCompIsBad::ne ? lhs.value == rhs.value
- : lhs.value != rhs.value;
- }
-
- friend bool operator<(BadCompare const& lhs, BadCompare const& rhs) {
- return Which == WhichCompIsBad::lt ? lhs.value >= rhs.value
- : lhs.value < rhs.value;
- }
-
- friend bool operator<=(BadCompare const& lhs, BadCompare const& rhs) {
- return Which == WhichCompIsBad::le ? lhs.value > rhs.value
- : lhs.value <= rhs.value;
- }
-
- friend bool operator>=(BadCompare const& lhs, BadCompare const& rhs) {
- return Which == WhichCompIsBad::ge ? lhs.value < rhs.value
- : lhs.value >= rhs.value;
- }
-
- friend bool operator>(BadCompare const& lhs, BadCompare const& rhs) {
- return Which == WhichCompIsBad::gt ? lhs.value <= rhs.value
- : lhs.value > rhs.value;
- }
-};
-
-TEST(ConformanceTestingDeathTest, Failures) {
- {
- using profile = ti::CombineProfiles<ti::TriviallyCompleteProfile,
- ti::NothrowComparableProfile>;
-
- // Note: The initializers are intentionally in the wrong order.
- ABSL_INTERNAL_ASSERT_NONCONFORMANCE_OF(float)
- .INITIALIZER(1.f)
- .INITIALIZER(0.f)
- .WITH_LOOSE_PROFILE(profile);
- }
-
- {
- using profile =
- ti::CombineProfiles<ti::NothrowMovableProfile, ti::EquatableProfile>;
-
- ABSL_INTERNAL_ASSERT_NONCONFORMANCE_OF(BadMoveConstruct)
- .DUE_TO("Move construction")
- .INITIALIZER(BadMoveConstruct())
- .WITH_LOOSE_PROFILE(profile);
- }
-
- {
- using profile =
- ti::CombineProfiles<ti::NothrowMovableProfile, ti::EquatableProfile>;
-
- ABSL_INTERNAL_ASSERT_NONCONFORMANCE_OF(BadMoveAssign)
- .DUE_TO("Move assignment")
- .INITIALIZER(BadMoveAssign())
- .WITH_LOOSE_PROFILE(profile);
- }
-}
-
-TEST(ConformanceTestingDeathTest, CompFailures) {
- using profile = ti::ComparableProfile;
-
- {
- using BadComp = BadCompare<WhichCompIsBad::eq>;
-
- ABSL_INTERNAL_ASSERT_NONCONFORMANCE_OF(BadComp)
- .DUE_TO("Comparison")
- .INITIALIZER(BadComp{0})
- .INITIALIZER(BadComp{1})
- .WITH_LOOSE_PROFILE(profile);
- }
-
- {
- using BadComp = BadCompare<WhichCompIsBad::ne>;
-
- ABSL_INTERNAL_ASSERT_NONCONFORMANCE_OF(BadComp)
- .DUE_TO("Comparison")
- .INITIALIZER(BadComp{0})
- .INITIALIZER(BadComp{1})
- .WITH_LOOSE_PROFILE(profile);
- }
-
- {
- using BadComp = BadCompare<WhichCompIsBad::lt>;
-
- ABSL_INTERNAL_ASSERT_NONCONFORMANCE_OF(BadComp)
- .DUE_TO("Comparison")
- .INITIALIZER(BadComp{0})
- .INITIALIZER(BadComp{1})
- .WITH_LOOSE_PROFILE(profile);
- }
-
- {
- using BadComp = BadCompare<WhichCompIsBad::le>;
-
- ABSL_INTERNAL_ASSERT_NONCONFORMANCE_OF(BadComp)
- .DUE_TO("Comparison")
- .INITIALIZER(BadComp{0})
- .INITIALIZER(BadComp{1})
- .WITH_LOOSE_PROFILE(profile);
- }
-
- {
- using BadComp = BadCompare<WhichCompIsBad::ge>;
-
- ABSL_INTERNAL_ASSERT_NONCONFORMANCE_OF(BadComp)
- .DUE_TO("Comparison")
- .INITIALIZER(BadComp{0})
- .INITIALIZER(BadComp{1})
- .WITH_LOOSE_PROFILE(profile);
- }
-
- {
- using BadComp = BadCompare<WhichCompIsBad::gt>;
-
- ABSL_INTERNAL_ASSERT_NONCONFORMANCE_OF(BadComp)
- .DUE_TO("Comparison")
- .INITIALIZER(BadComp{0})
- .INITIALIZER(BadComp{1})
- .WITH_LOOSE_PROFILE(profile);
- }
-}
-
-struct BadSelfMove {
- BadSelfMove() = default;
- BadSelfMove(BadSelfMove&&) = default;
- BadSelfMove& operator=(BadSelfMove&& other) noexcept {
- if (this == &other) {
- broken_state = true;
- }
- return *this;
- }
-
- friend bool operator==(const BadSelfMove& lhs, const BadSelfMove& rhs) {
- return !(lhs.broken_state || rhs.broken_state);
- }
-
- friend bool operator!=(const BadSelfMove& lhs, const BadSelfMove& rhs) {
- return lhs.broken_state || rhs.broken_state;
- }
-
- bool broken_state = false;
-};
-
-TEST(ConformanceTestingDeathTest, SelfMoveFailure) {
- using profile = ti::EquatableNothrowMovableProfile;
-
- {
- ABSL_INTERNAL_ASSERT_NONCONFORMANCE_OF(BadSelfMove)
- .DUE_TO("Move assignment")
- .INITIALIZER(BadSelfMove())
- .WITH_LOOSE_PROFILE(profile);
- }
-}
-
-struct BadSelfCopy {
- BadSelfCopy() = default;
- BadSelfCopy(BadSelfCopy&&) = default;
- BadSelfCopy(const BadSelfCopy&) = default;
- BadSelfCopy& operator=(BadSelfCopy&&) = default;
- BadSelfCopy& operator=(BadSelfCopy const& other) {
- if (this == &other) {
- broken_state = true;
- }
- return *this;
- }
-
- friend bool operator==(const BadSelfCopy& lhs, const BadSelfCopy& rhs) {
- return !(lhs.broken_state || rhs.broken_state);
- }
-
- friend bool operator!=(const BadSelfCopy& lhs, const BadSelfCopy& rhs) {
- return lhs.broken_state || rhs.broken_state;
- }
-
- bool broken_state = false;
-};
-
-TEST(ConformanceTestingDeathTest, SelfCopyFailure) {
- using profile = ti::EquatableValueProfile;
-
- {
- ABSL_INTERNAL_ASSERT_NONCONFORMANCE_OF(BadSelfCopy)
- .DUE_TO("Copy assignment")
- .INITIALIZER(BadSelfCopy())
- .WITH_LOOSE_PROFILE(profile);
- }
-}
-
-struct BadSelfSwap {
- friend void swap(BadSelfSwap& lhs, BadSelfSwap& rhs) noexcept {
- if (&lhs == &rhs) lhs.broken_state = true;
- }
-
- friend bool operator==(const BadSelfSwap& lhs, const BadSelfSwap& rhs) {
- return !(lhs.broken_state || rhs.broken_state);
- }
-
- friend bool operator!=(const BadSelfSwap& lhs, const BadSelfSwap& rhs) {
- return lhs.broken_state || rhs.broken_state;
- }
-
- bool broken_state = false;
-};
-
-TEST(ConformanceTestingDeathTest, SelfSwapFailure) {
- using profile = ti::EquatableNothrowMovableProfile;
-
- {
- ABSL_INTERNAL_ASSERT_NONCONFORMANCE_OF(BadSelfSwap)
- .DUE_TO("Swap")
- .INITIALIZER(BadSelfSwap())
- .WITH_LOOSE_PROFILE(profile);
- }
-}
-
-struct BadDefaultInitializedMoveAssign {
- BadDefaultInitializedMoveAssign() : default_initialized(true) {}
- explicit BadDefaultInitializedMoveAssign(int v) : value(v) {}
- BadDefaultInitializedMoveAssign(
- BadDefaultInitializedMoveAssign&& other) noexcept
- : value(other.value) {}
- BadDefaultInitializedMoveAssign& operator=(
- BadDefaultInitializedMoveAssign&& other) noexcept {
- value = other.value;
- if (default_initialized) ++value; // Bad move if lhs is default initialized
- return *this;
- }
-
- friend bool operator==(const BadDefaultInitializedMoveAssign& lhs,
- const BadDefaultInitializedMoveAssign& rhs) {
- return lhs.value == rhs.value;
- }
-
- friend bool operator!=(const BadDefaultInitializedMoveAssign& lhs,
- const BadDefaultInitializedMoveAssign& rhs) {
- return lhs.value != rhs.value;
- }
-
- bool default_initialized = false;
- int value = 0;
-};
-
-TEST(ConformanceTestingDeathTest, DefaultInitializedMoveAssignFailure) {
- using profile =
- ti::CombineProfiles<ti::DefaultConstructibleNothrowMovableProfile,
- ti::EquatableProfile>;
-
- {
- ABSL_INTERNAL_ASSERT_NONCONFORMANCE_OF(BadDefaultInitializedMoveAssign)
- .DUE_TO("move assignment")
- .INITIALIZER(BadDefaultInitializedMoveAssign(0))
- .WITH_LOOSE_PROFILE(profile);
- }
-}
-
-struct BadDefaultInitializedCopyAssign {
- BadDefaultInitializedCopyAssign() : default_initialized(true) {}
- explicit BadDefaultInitializedCopyAssign(int v) : value(v) {}
- BadDefaultInitializedCopyAssign(
- BadDefaultInitializedCopyAssign&& other) noexcept
- : value(other.value) {}
- BadDefaultInitializedCopyAssign(const BadDefaultInitializedCopyAssign& other)
- : value(other.value) {}
-
- BadDefaultInitializedCopyAssign& operator=(
- BadDefaultInitializedCopyAssign&& other) noexcept {
- value = other.value;
- return *this;
- }
-
- BadDefaultInitializedCopyAssign& operator=(
- const BadDefaultInitializedCopyAssign& other) {
- value = other.value;
- if (default_initialized) ++value; // Bad move if lhs is default initialized
- return *this;
- }
-
- friend bool operator==(const BadDefaultInitializedCopyAssign& lhs,
- const BadDefaultInitializedCopyAssign& rhs) {
- return lhs.value == rhs.value;
- }
-
- friend bool operator!=(const BadDefaultInitializedCopyAssign& lhs,
- const BadDefaultInitializedCopyAssign& rhs) {
- return lhs.value != rhs.value;
- }
-
- bool default_initialized = false;
- int value = 0;
-};
-
-TEST(ConformanceTestingDeathTest, DefaultInitializedAssignFailure) {
- using profile = ti::CombineProfiles<ti::DefaultConstructibleValueProfile,
- ti::EquatableProfile>;
-
- {
- ABSL_INTERNAL_ASSERT_NONCONFORMANCE_OF(BadDefaultInitializedCopyAssign)
- .DUE_TO("copy assignment")
- .INITIALIZER(BadDefaultInitializedCopyAssign(0))
- .WITH_LOOSE_PROFILE(profile);
- }
-}
-
-} // namespace
diff --git a/absl/types/internal/optional.h b/absl/types/internal/optional.h
index 92932b6..a96d260 100644
--- a/absl/types/internal/optional.h
+++ b/absl/types/internal/optional.h
@@ -25,34 +25,6 @@
#include "absl/meta/type_traits.h"
#include "absl/utility/utility.h"
-// ABSL_OPTIONAL_USE_INHERITING_CONSTRUCTORS
-//
-// Inheriting constructors is supported in GCC 4.8+, Clang 3.3+ and MSVC 2015.
-// __cpp_inheriting_constructors is a predefined macro and a recommended way to
-// check for this language feature, but GCC doesn't support it until 5.0 and
-// Clang doesn't support it until 3.6.
-// Also, MSVC 2015 has a bug: it doesn't inherit the constexpr template
-// constructor. For example, the following code won't work on MSVC 2015 Update3:
-// struct Base {
-// int t;
-// template <typename T>
-// constexpr Base(T t_) : t(t_) {}
-// };
-// struct Foo : Base {
-// using Base::Base;
-// }
-// constexpr Foo foo(0); // doesn't work on MSVC 2015
-#if defined(__clang__)
-#if __has_feature(cxx_inheriting_constructors)
-#define ABSL_OPTIONAL_USE_INHERITING_CONSTRUCTORS 1
-#endif
-#elif (defined(__GNUC__) && \
- (__GNUC__ > 4 || __GNUC__ == 4 && __GNUC_MINOR__ >= 8)) || \
- (__cpp_inheriting_constructors >= 200802) || \
- (defined(_MSC_VER) && _MSC_VER >= 1910)
-#define ABSL_OPTIONAL_USE_INHERITING_CONSTRUCTORS 1
-#endif
-
namespace absl {
ABSL_NAMESPACE_BEGIN
@@ -91,7 +63,15 @@
void destruct() noexcept {
if (engaged_) {
+ // `data_` must be initialized if `engaged_` is true.
+#if ABSL_INTERNAL_HAVE_MIN_GNUC_VERSION(12, 0)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
+#endif
data_.~T();
+#if ABSL_INTERNAL_HAVE_MIN_GNUC_VERSION(12, 0)
+#pragma GCC diagnostic pop
+#endif
engaged_ = false;
}
}
@@ -137,15 +117,7 @@
class optional_data_base : public optional_data_dtor_base<T> {
protected:
using base = optional_data_dtor_base<T>;
-#ifdef ABSL_OPTIONAL_USE_INHERITING_CONSTRUCTORS
using base::base;
-#else
- optional_data_base() = default;
-
- template <typename... Args>
- constexpr explicit optional_data_base(in_place_t t, Args&&... args)
- : base(t, absl::forward<Args>(args)...) {}
-#endif
template <typename... Args>
void construct(Args&&... args) {
@@ -180,27 +152,13 @@
template <typename T>
class optional_data<T, true> : public optional_data_base<T> {
protected:
-#ifdef ABSL_OPTIONAL_USE_INHERITING_CONSTRUCTORS
using optional_data_base<T>::optional_data_base;
-#else
- optional_data() = default;
-
- template <typename... Args>
- constexpr explicit optional_data(in_place_t t, Args&&... args)
- : optional_data_base<T>(t, absl::forward<Args>(args)...) {}
-#endif
};
template <typename T>
class optional_data<T, false> : public optional_data_base<T> {
protected:
-#ifdef ABSL_OPTIONAL_USE_INHERITING_CONSTRUCTORS
using optional_data_base<T>::optional_data_base;
-#else
- template <typename... Args>
- constexpr explicit optional_data(in_place_t t, Args&&... args)
- : optional_data_base<T>(t, absl::forward<Args>(args)...) {}
-#endif
optional_data() = default;
@@ -391,6 +349,4 @@
ABSL_NAMESPACE_END
} // namespace absl
-#undef ABSL_OPTIONAL_USE_INHERITING_CONSTRUCTORS
-
#endif // ABSL_TYPES_INTERNAL_OPTIONAL_H_
diff --git a/absl/types/internal/parentheses.h b/absl/types/internal/parentheses.h
deleted file mode 100644
index 5aebee8..0000000
--- a/absl/types/internal/parentheses.h
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright 2019 The Abseil Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-// -----------------------------------------------------------------------------
-// parentheses.h
-// -----------------------------------------------------------------------------
-//
-// This file contains macros that expand to a left parenthesis and a right
-// parenthesis. These are in their own file and are generated from macros
-// because otherwise clang-format gets confused and clang-format off directives
-// do not help.
-//
-// The parentheses macros are used when wanting to require a rescan before
-// expansion of parenthesized text appearing after a function-style macro name.
-
-#ifndef ABSL_TYPES_INTERNAL_PARENTHESES_H_
-#define ABSL_TYPES_INTERNAL_PARENTHESES_H_
-
-#define ABSL_INTERNAL_LPAREN (
-
-#define ABSL_INTERNAL_RPAREN )
-
-#endif // ABSL_TYPES_INTERNAL_PARENTHESES_H_
diff --git a/absl/types/internal/span.h b/absl/types/internal/span.h
index 112612f..ab89ba3 100644
--- a/absl/types/internal/span.h
+++ b/absl/types/internal/span.h
@@ -28,10 +28,10 @@
namespace absl {
ABSL_NAMESPACE_BEGIN
-namespace span_internal {
-// A constexpr min function
-constexpr size_t Min(size_t a, size_t b) noexcept { return a < b ? a : b; }
+template <typename T>
+class Span;
+namespace span_internal {
// Wrappers for access to container data pointers.
template <typename C>
constexpr auto GetDataImpl(C& c, char) noexcept // NOLINT(runtime/references)
@@ -88,7 +88,7 @@
template <template <typename> class SpanT, typename T>
bool EqualImpl(SpanT<T> a, SpanT<T> b) {
static_assert(std::is_const<T>::value, "");
- return absl::equal(a.begin(), a.end(), b.begin(), b.end());
+ return std::equal(a.begin(), a.end(), b.begin(), b.end());
}
template <template <typename> class SpanT, typename T>
@@ -99,28 +99,39 @@
return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end());
}
-// The `IsConvertible` classes here are needed because of the
-// `std::is_convertible` bug in libcxx when compiled with GCC. This build
-// configuration is used by Android NDK toolchain. Reference link:
-// https://bugs.llvm.org/show_bug.cgi?id=27538.
-template <typename From, typename To>
-struct IsConvertibleHelper {
- private:
- static std::true_type testval(To);
- static std::false_type testval(...);
-
- public:
- using type = decltype(testval(std::declval<From>()));
-};
-
-template <typename From, typename To>
-struct IsConvertible : IsConvertibleHelper<From, To>::type {};
-
-// TODO(zhangxy): replace `IsConvertible` with `std::is_convertible` once the
-// older version of libcxx is not supported.
template <typename From, typename To>
using EnableIfConvertibleTo =
- typename std::enable_if<IsConvertible<From, To>::value>::type;
+ typename std::enable_if<std::is_convertible<From, To>::value>::type;
+
+// IsView is true for types where the return type of .data() is the same for
+// mutable and const instances. This isn't foolproof, but it's only used to
+// enable a compiler warning.
+template <typename T, typename = void, typename = void>
+struct IsView {
+ static constexpr bool value = false;
+};
+
+template <typename T>
+struct IsView<
+ T, absl::void_t<decltype(span_internal::GetData(std::declval<const T&>()))>,
+ absl::void_t<decltype(span_internal::GetData(std::declval<T&>()))>> {
+ private:
+ using Container = std::remove_const_t<T>;
+ using ConstData =
+ decltype(span_internal::GetData(std::declval<const Container&>()));
+ using MutData = decltype(span_internal::GetData(std::declval<Container&>()));
+ public:
+ static constexpr bool value = std::is_same<ConstData, MutData>::value;
+};
+
+// These enablers result in 'int' so they can be used as typenames or defaults
+// in template parameters lists.
+template <typename T>
+using EnableIfIsView = std::enable_if_t<IsView<T>::value, int>;
+
+template <typename T>
+using EnableIfNotIsView = std::enable_if_t<!IsView<T>::value, int>;
+
} // namespace span_internal
ABSL_NAMESPACE_END
} // namespace absl
diff --git a/absl/types/internal/transform_args.h b/absl/types/internal/transform_args.h
deleted file mode 100644
index 4a0ab42..0000000
--- a/absl/types/internal/transform_args.h
+++ /dev/null
@@ -1,246 +0,0 @@
-// Copyright 2019 The Abseil Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-// -----------------------------------------------------------------------------
-// transform_args.h
-// -----------------------------------------------------------------------------
-//
-// This file contains a higher-order macro that "transforms" each element of a
-// a variadic argument by a provided secondary macro.
-
-#ifndef ABSL_TYPES_INTERNAL_TRANSFORM_ARGS_H_
-#define ABSL_TYPES_INTERNAL_TRANSFORM_ARGS_H_
-
-//
-// ABSL_INTERNAL_CAT(a, b)
-//
-// This macro takes two arguments and concatenates them together via ## after
-// expansion.
-//
-// Example:
-//
-// ABSL_INTERNAL_CAT(foo_, bar)
-//
-// Results in:
-//
-// foo_bar
-#define ABSL_INTERNAL_CAT(a, b) ABSL_INTERNAL_CAT_IMPL(a, b)
-#define ABSL_INTERNAL_CAT_IMPL(a, b) a##b
-
-//
-// ABSL_INTERNAL_TRANSFORM_ARGS(m, ...)
-//
-// This macro takes another macro as an argument followed by a trailing series
-// of additional parameters (up to 32 additional arguments). It invokes the
-// passed-in macro once for each of the additional arguments, with the
-// expansions separated by commas.
-//
-// Example:
-//
-// ABSL_INTERNAL_TRANSFORM_ARGS(MY_MACRO, a, b, c)
-//
-// Results in:
-//
-// MY_MACRO(a), MY_MACRO(b), MY_MACRO(c)
-//
-// TODO(calabrese) Handle no arguments as a special case.
-#define ABSL_INTERNAL_TRANSFORM_ARGS(m, ...) \
- ABSL_INTERNAL_CAT(ABSL_INTERNAL_TRANSFORM_ARGS, \
- ABSL_INTERNAL_NUM_ARGS(__VA_ARGS__)) \
- (m, __VA_ARGS__)
-
-#define ABSL_INTERNAL_TRANSFORM_ARGS1(m, a0) m(a0)
-
-#define ABSL_INTERNAL_TRANSFORM_ARGS2(m, a0, a1) m(a0), m(a1)
-
-#define ABSL_INTERNAL_TRANSFORM_ARGS3(m, a0, a1, a2) m(a0), m(a1), m(a2)
-
-#define ABSL_INTERNAL_TRANSFORM_ARGS4(m, a0, a1, a2, a3) \
- m(a0), m(a1), m(a2), m(a3)
-
-#define ABSL_INTERNAL_TRANSFORM_ARGS5(m, a0, a1, a2, a3, a4) \
- m(a0), m(a1), m(a2), m(a3), m(a4)
-
-#define ABSL_INTERNAL_TRANSFORM_ARGS6(m, a0, a1, a2, a3, a4, a5) \
- m(a0), m(a1), m(a2), m(a3), m(a4), m(a5)
-
-#define ABSL_INTERNAL_TRANSFORM_ARGS7(m, a0, a1, a2, a3, a4, a5, a6) \
- m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6)
-
-#define ABSL_INTERNAL_TRANSFORM_ARGS8(m, a0, a1, a2, a3, a4, a5, a6, a7) \
- m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7)
-
-#define ABSL_INTERNAL_TRANSFORM_ARGS9(m, a0, a1, a2, a3, a4, a5, a6, a7, a8) \
- m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8)
-
-#define ABSL_INTERNAL_TRANSFORM_ARGS10(m, a0, a1, a2, a3, a4, a5, a6, a7, a8, \
- a9) \
- m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9)
-
-#define ABSL_INTERNAL_TRANSFORM_ARGS11(m, a0, a1, a2, a3, a4, a5, a6, a7, a8, \
- a9, a10) \
- m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), m(a10)
-
-#define ABSL_INTERNAL_TRANSFORM_ARGS12(m, a0, a1, a2, a3, a4, a5, a6, a7, a8, \
- a9, a10, a11) \
- m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \
- m(a10), m(a11)
-
-#define ABSL_INTERNAL_TRANSFORM_ARGS13(m, a0, a1, a2, a3, a4, a5, a6, a7, a8, \
- a9, a10, a11, a12) \
- m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \
- m(a10), m(a11), m(a12)
-
-#define ABSL_INTERNAL_TRANSFORM_ARGS14(m, a0, a1, a2, a3, a4, a5, a6, a7, a8, \
- a9, a10, a11, a12, a13) \
- m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \
- m(a10), m(a11), m(a12), m(a13)
-
-#define ABSL_INTERNAL_TRANSFORM_ARGS15(m, a0, a1, a2, a3, a4, a5, a6, a7, a8, \
- a9, a10, a11, a12, a13, a14) \
- m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \
- m(a10), m(a11), m(a12), m(a13), m(a14)
-
-#define ABSL_INTERNAL_TRANSFORM_ARGS16(m, a0, a1, a2, a3, a4, a5, a6, a7, a8, \
- a9, a10, a11, a12, a13, a14, a15) \
- m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \
- m(a10), m(a11), m(a12), m(a13), m(a14), m(a15)
-
-#define ABSL_INTERNAL_TRANSFORM_ARGS17(m, a0, a1, a2, a3, a4, a5, a6, a7, a8, \
- a9, a10, a11, a12, a13, a14, a15, a16) \
- m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \
- m(a10), m(a11), m(a12), m(a13), m(a14), m(a15), m(a16)
-
-#define ABSL_INTERNAL_TRANSFORM_ARGS18(m, a0, a1, a2, a3, a4, a5, a6, a7, a8, \
- a9, a10, a11, a12, a13, a14, a15, a16, \
- a17) \
- m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \
- m(a10), m(a11), m(a12), m(a13), m(a14), m(a15), m(a16), m(a17)
-
-#define ABSL_INTERNAL_TRANSFORM_ARGS19(m, a0, a1, a2, a3, a4, a5, a6, a7, a8, \
- a9, a10, a11, a12, a13, a14, a15, a16, \
- a17, a18) \
- m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \
- m(a10), m(a11), m(a12), m(a13), m(a14), m(a15), m(a16), m(a17), m(a18)
-
-#define ABSL_INTERNAL_TRANSFORM_ARGS20(m, a0, a1, a2, a3, a4, a5, a6, a7, a8, \
- a9, a10, a11, a12, a13, a14, a15, a16, \
- a17, a18, a19) \
- m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \
- m(a10), m(a11), m(a12), m(a13), m(a14), m(a15), m(a16), m(a17), m(a18), \
- m(a19)
-
-#define ABSL_INTERNAL_TRANSFORM_ARGS21(m, a0, a1, a2, a3, a4, a5, a6, a7, a8, \
- a9, a10, a11, a12, a13, a14, a15, a16, \
- a17, a18, a19, a20) \
- m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \
- m(a10), m(a11), m(a12), m(a13), m(a14), m(a15), m(a16), m(a17), m(a18), \
- m(a19), m(a20)
-
-#define ABSL_INTERNAL_TRANSFORM_ARGS22(m, a0, a1, a2, a3, a4, a5, a6, a7, a8, \
- a9, a10, a11, a12, a13, a14, a15, a16, \
- a17, a18, a19, a20, a21) \
- m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \
- m(a10), m(a11), m(a12), m(a13), m(a14), m(a15), m(a16), m(a17), m(a18), \
- m(a19), m(a20), m(a21)
-
-#define ABSL_INTERNAL_TRANSFORM_ARGS23(m, a0, a1, a2, a3, a4, a5, a6, a7, a8, \
- a9, a10, a11, a12, a13, a14, a15, a16, \
- a17, a18, a19, a20, a21, a22) \
- m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \
- m(a10), m(a11), m(a12), m(a13), m(a14), m(a15), m(a16), m(a17), m(a18), \
- m(a19), m(a20), m(a21), m(a22)
-
-#define ABSL_INTERNAL_TRANSFORM_ARGS24(m, a0, a1, a2, a3, a4, a5, a6, a7, a8, \
- a9, a10, a11, a12, a13, a14, a15, a16, \
- a17, a18, a19, a20, a21, a22, a23) \
- m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \
- m(a10), m(a11), m(a12), m(a13), m(a14), m(a15), m(a16), m(a17), m(a18), \
- m(a19), m(a20), m(a21), m(a22), m(a23)
-
-#define ABSL_INTERNAL_TRANSFORM_ARGS25(m, a0, a1, a2, a3, a4, a5, a6, a7, a8, \
- a9, a10, a11, a12, a13, a14, a15, a16, \
- a17, a18, a19, a20, a21, a22, a23, a24) \
- m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \
- m(a10), m(a11), m(a12), m(a13), m(a14), m(a15), m(a16), m(a17), m(a18), \
- m(a19), m(a20), m(a21), m(a22), m(a23), m(a24)
-
-#define ABSL_INTERNAL_TRANSFORM_ARGS26( \
- m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, \
- a16, a17, a18, a19, a20, a21, a22, a23, a24, a25) \
- m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \
- m(a10), m(a11), m(a12), m(a13), m(a14), m(a15), m(a16), m(a17), m(a18), \
- m(a19), m(a20), m(a21), m(a22), m(a23), m(a24), m(a25)
-
-#define ABSL_INTERNAL_TRANSFORM_ARGS27( \
- m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, \
- a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26) \
- m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \
- m(a10), m(a11), m(a12), m(a13), m(a14), m(a15), m(a16), m(a17), m(a18), \
- m(a19), m(a20), m(a21), m(a22), m(a23), m(a24), m(a25), m(a26)
-
-#define ABSL_INTERNAL_TRANSFORM_ARGS28( \
- m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, \
- a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27) \
- m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \
- m(a10), m(a11), m(a12), m(a13), m(a14), m(a15), m(a16), m(a17), m(a18), \
- m(a19), m(a20), m(a21), m(a22), m(a23), m(a24), m(a25), m(a26), m(a27)
-
-#define ABSL_INTERNAL_TRANSFORM_ARGS29( \
- m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, \
- a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28) \
- m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \
- m(a10), m(a11), m(a12), m(a13), m(a14), m(a15), m(a16), m(a17), m(a18), \
- m(a19), m(a20), m(a21), m(a22), m(a23), m(a24), m(a25), m(a26), m(a27), \
- m(a28)
-
-#define ABSL_INTERNAL_TRANSFORM_ARGS30( \
- m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, \
- a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29) \
- m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \
- m(a10), m(a11), m(a12), m(a13), m(a14), m(a15), m(a16), m(a17), m(a18), \
- m(a19), m(a20), m(a21), m(a22), m(a23), m(a24), m(a25), m(a26), m(a27), \
- m(a28), m(a29)
-
-#define ABSL_INTERNAL_TRANSFORM_ARGS31( \
- m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, \
- a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30) \
- m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \
- m(a10), m(a11), m(a12), m(a13), m(a14), m(a15), m(a16), m(a17), m(a18), \
- m(a19), m(a20), m(a21), m(a22), m(a23), m(a24), m(a25), m(a26), m(a27), \
- m(a28), m(a29), m(a30)
-
-#define ABSL_INTERNAL_TRANSFORM_ARGS32(m, a0, a1, a2, a3, a4, a5, a6, a7, a8, \
- a9, a10, a11, a12, a13, a14, a15, a16, \
- a17, a18, a19, a20, a21, a22, a23, a24, \
- a25, a26, a27, a28, a29, a30, a31) \
- m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \
- m(a10), m(a11), m(a12), m(a13), m(a14), m(a15), m(a16), m(a17), m(a18), \
- m(a19), m(a20), m(a21), m(a22), m(a23), m(a24), m(a25), m(a26), m(a27), \
- m(a28), m(a29), m(a30), m(a31)
-
-#define ABSL_INTERNAL_NUM_ARGS_IMPL(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, \
- a10, a11, a12, a13, a14, a15, a16, a17, \
- a18, a19, a20, a21, a22, a23, a24, a25, \
- a26, a27, a28, a29, a30, a31, result, ...) \
- result
-
-#define ABSL_INTERNAL_FORCE_EXPANSION(...) __VA_ARGS__
-
-#define ABSL_INTERNAL_NUM_ARGS(...) \
- ABSL_INTERNAL_FORCE_EXPANSION(ABSL_INTERNAL_NUM_ARGS_IMPL( \
- __VA_ARGS__, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, \
- 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, ))
-
-#endif // ABSL_TYPES_INTERNAL_TRANSFORM_ARGS_H_
diff --git a/absl/types/internal/variant.h b/absl/types/internal/variant.h
index 772008c..263d7b0 100644
--- a/absl/types/internal/variant.h
+++ b/absl/types/internal/variant.h
@@ -16,8 +16,8 @@
// separate file to avoid cluttering the top of the API header with
// implementation details.
-#ifndef ABSL_TYPES_variant_internal_H_
-#define ABSL_TYPES_variant_internal_H_
+#ifndef ABSL_TYPES_INTERNAL_VARIANT_H_
+#define ABSL_TYPES_INTERNAL_VARIANT_H_
#include <cassert>
#include <cstddef>
@@ -449,7 +449,7 @@
template <std::size_t HeadSize, std::size_t... TailSize>
struct FlattenIndices<HeadSize, TailSize...> {
- template<class... SizeType>
+ template <class... SizeType>
static constexpr std::size_t Run(std::size_t head, SizeType... tail) {
return head + HeadSize * FlattenIndices<TailSize...>::Run(tail...);
}
@@ -498,8 +498,8 @@
};
template <class Op, class... SizeType>
- static VisitIndicesResultT<Op, decltype(EndIndices)...> Run(
- Op&& op, SizeType... i) {
+ static VisitIndicesResultT<Op, decltype(EndIndices)...> Run(Op&& op,
+ SizeType... i) {
return VisitIndicesSwitch<NumCasesOfSwitch<EndIndices...>::value>::Run(
FlattenedOp<Op>{absl::forward<Op>(op)},
FlattenIndices<(EndIndices + std::size_t{1})...>::Run(
@@ -683,13 +683,13 @@
variant_internal::IndexOfConstructedType<Left, QualifiedNew>;
void operator()(SizeT<NewIndex::value> /*old_i*/
- ) const {
+ ) const {
Access<NewIndex::value>(*left) = absl::forward<QualifiedNew>(other);
}
template <std::size_t OldIndex>
void operator()(SizeT<OldIndex> /*old_i*/
- ) const {
+ ) const {
using New =
typename absl::variant_alternative<NewIndex::value, Left>::type;
if (std::is_nothrow_constructible<New, QualifiedNew>::value ||
@@ -868,18 +868,6 @@
template <class Self, std::size_t I>
struct IsNeitherSelfNorInPlace<Self, in_place_index_t<I>> : std::false_type {};
-template <class Variant, class T, class = void>
-struct ConversionIsPossibleImpl : std::false_type {};
-
-template <class Variant, class T>
-struct ConversionIsPossibleImpl<
- Variant, T,
- void_t<decltype(ImaginaryFun<Variant>::Run(std::declval<T>(), {}))>>
- : std::true_type {};
-
-template <class Variant, class T>
-struct ConversionIsPossible : ConversionIsPossibleImpl<Variant, T>::type {};
-
template <class Variant, class T>
struct IndexOfConstructedType<
Variant, T,
@@ -889,8 +877,8 @@
template <std::size_t... Is>
struct ContainsVariantNPos
: absl::negation<std::is_same< // NOLINT
- absl::integer_sequence<bool, 0 <= Is...>,
- absl::integer_sequence<bool, Is != absl::variant_npos...>>> {};
+ std::integer_sequence<bool, 0 <= Is...>,
+ std::integer_sequence<bool, Is != absl::variant_npos...>>> {};
template <class Op, class... QualifiedVariants>
using RawVisitResult =
@@ -1059,11 +1047,11 @@
std::size_t index_;
};
-using absl::internal::identity;
+using absl::internal::type_identity;
// OverloadSet::Overload() is a unary function which is overloaded to
// take any of the element types of the variant, by reference-to-const.
-// The return type of the overload on T is identity<T>, so that you
+// The return type of the overload on T is type_identity<T>, so that you
// can statically determine which overload was called.
//
// Overload() is not defined, so it can only be called in unevaluated
@@ -1074,7 +1062,7 @@
template <typename T, typename... Ts>
struct OverloadSet<T, Ts...> : OverloadSet<Ts...> {
using Base = OverloadSet<Ts...>;
- static identity<T> Overload(const T&);
+ static type_identity<T> Overload(const T&);
using Base::Overload;
};
@@ -1151,16 +1139,16 @@
// Type metafunction which returns the element type selected if
// OverloadSet::Overload() is well-formed when called with argument type U.
template <typename U>
- using BestMatch = decltype(
- variant_internal::OverloadSet<Ts...>::Overload(std::declval<U>()));
+ using BestMatch = decltype(variant_internal::OverloadSet<Ts...>::Overload(
+ std::declval<U>()));
// Type metafunction which returns true if OverloadSet::Overload() is
// well-formed when called with argument type U.
// CanAccept can't be just an alias because there is a MSVC bug on parameter
// pack expansion involving decltype.
template <typename U>
- struct CanAccept :
- std::integral_constant<bool, !std::is_void<BestMatch<U>>::value> {};
+ struct CanAccept
+ : std::integral_constant<bool, !std::is_void<BestMatch<U>>::value> {};
// Type metafunction which returns true if Other is an instantiation of
// variant, and variants's converting constructor from Other will be
@@ -1183,8 +1171,8 @@
// A union's defaulted copy/move constructor is deleted if any variant member's
// copy/move constructor is nontrivial.
template <typename T>
-struct IsTriviallyMoveConstructible:
- std::is_move_constructible<Union<T, TrivialMoveOnly>> {};
+struct IsTriviallyMoveConstructible
+ : std::is_move_constructible<Union<T, TrivialMoveOnly>> {};
// To guarantee triviality of all special-member functions that can be trivial,
// we use a chain of conditional bases for each one.
@@ -1419,14 +1407,14 @@
VariantMoveAssignBaseNontrivial& operator=(
VariantMoveAssignBaseNontrivial const&) = default;
- VariantMoveAssignBaseNontrivial&
- operator=(VariantMoveAssignBaseNontrivial&& other) noexcept(
- absl::conjunction<std::is_nothrow_move_constructible<T>...,
- std::is_nothrow_move_assignable<T>...>::value) {
- VisitIndices<sizeof...(T)>::Run(
- VariantCoreAccess::MakeMoveAssignVisitor(this, &other), other.index_);
- return *this;
- }
+ VariantMoveAssignBaseNontrivial&
+ operator=(VariantMoveAssignBaseNontrivial&& other) noexcept(
+ absl::conjunction<std::is_nothrow_move_constructible<T>...,
+ std::is_nothrow_move_assignable<T>...>::value) {
+ VisitIndices<sizeof...(T)>::Run(
+ VariantCoreAccess::MakeMoveAssignVisitor(this, &other), other.index_);
+ return *this;
+ }
protected:
using Base::index_;
@@ -1450,12 +1438,12 @@
VariantCopyAssignBaseNontrivial& operator=(
VariantCopyAssignBaseNontrivial&&) = default;
- VariantCopyAssignBaseNontrivial& operator=(
- const VariantCopyAssignBaseNontrivial& other) {
- VisitIndices<sizeof...(T)>::Run(
- VariantCoreAccess::MakeCopyAssignVisitor(this, other), other.index_);
- return *this;
- }
+ VariantCopyAssignBaseNontrivial& operator=(
+ const VariantCopyAssignBaseNontrivial& other) {
+ VisitIndices<sizeof...(T)>::Run(
+ VariantCoreAccess::MakeCopyAssignVisitor(this, other), other.index_);
+ return *this;
+ }
protected:
using Base::index_;
@@ -1643,4 +1631,4 @@
} // namespace absl
#endif // !defined(ABSL_USES_STD_VARIANT)
-#endif // ABSL_TYPES_variant_internal_H_
+#endif // ABSL_TYPES_INTERNAL_VARIANT_H_
diff --git a/absl/types/optional.h b/absl/types/optional.h
index 61540cf..395fe62 100644
--- a/absl/types/optional.h
+++ b/absl/types/optional.h
@@ -61,6 +61,7 @@
#include <utility>
#include "absl/base/attributes.h"
+#include "absl/base/nullability.h"
#include "absl/base/internal/inline_variable.h"
#include "absl/meta/type_traits.h"
#include "absl/types/bad_optional_access.h"
@@ -130,7 +131,7 @@
// Constructs an `optional` holding an empty value, NOT a default constructed
// `T`.
- constexpr optional() noexcept {}
+ constexpr optional() noexcept = default;
// Constructs an `optional` initialized with `nullopt` to hold an empty value.
constexpr optional(nullopt_t) noexcept {} // NOLINT(runtime/explicit)
@@ -282,15 +283,16 @@
optional& operator=(optional&& src) = default;
// Value assignment operators
- template <
- typename U = T,
- typename = typename std::enable_if<absl::conjunction<
- absl::negation<
- std::is_same<optional<T>, typename std::decay<U>::type>>,
- absl::negation<
- absl::conjunction<std::is_scalar<T>,
- std::is_same<T, typename std::decay<U>::type>>>,
- std::is_constructible<T, U>, std::is_assignable<T&, U>>::value>::type>
+ template <typename U = T,
+ int&..., // Workaround an internal compiler error in GCC 5 to 10.
+ typename = typename std::enable_if<absl::conjunction<
+ absl::negation<
+ std::is_same<optional<T>, typename std::decay<U>::type> >,
+ absl::negation<absl::conjunction<
+ std::is_scalar<T>,
+ std::is_same<T, typename std::decay<U>::type> > >,
+ std::is_constructible<T, U>,
+ std::is_assignable<T&, U> >::value>::type>
optional& operator=(U&& v) {
this->assign(std::forward<U>(v));
return *this;
@@ -298,13 +300,14 @@
template <
typename U,
+ int&..., // Workaround an internal compiler error in GCC 5 to 10.
typename = typename std::enable_if<absl::conjunction<
- absl::negation<std::is_same<T, U>>,
+ absl::negation<std::is_same<T, U> >,
std::is_constructible<T, const U&>, std::is_assignable<T&, const U&>,
absl::negation<
optional_internal::
is_constructible_convertible_assignable_from_optional<
- T, U>>>::value>::type>
+ T, U> > >::value>::type>
optional& operator=(const optional<U>& rhs) {
if (rhs) {
this->assign(*rhs);
@@ -315,13 +318,14 @@
}
template <typename U,
+ int&..., // Workaround an internal compiler error in GCC 5 to 10.
typename = typename std::enable_if<absl::conjunction<
- absl::negation<std::is_same<T, U>>, std::is_constructible<T, U>,
- std::is_assignable<T&, U>,
+ absl::negation<std::is_same<T, U> >,
+ std::is_constructible<T, U>, std::is_assignable<T&, U>,
absl::negation<
optional_internal::
is_constructible_convertible_assignable_from_optional<
- T, U>>>::value>::type>
+ T, U> > >::value>::type>
optional& operator=(optional<U>&& rhs) {
if (rhs) {
this->assign(std::move(*rhs));
@@ -354,7 +358,7 @@
template <typename... Args,
typename = typename std::enable_if<
std::is_constructible<T, Args&&...>::value>::type>
- T& emplace(Args&&... args) {
+ T& emplace(Args&&... args) ABSL_ATTRIBUTE_LIFETIME_BOUND {
this->destruct();
this->construct(std::forward<Args>(args)...);
return reference();
@@ -374,7 +378,8 @@
template <typename U, typename... Args,
typename = typename std::enable_if<std::is_constructible<
T, std::initializer_list<U>&, Args&&...>::value>::type>
- T& emplace(std::initializer_list<U> il, Args&&... args) {
+ T& emplace(std::initializer_list<U> il,
+ Args&&... args) ABSL_ATTRIBUTE_LIFETIME_BOUND {
this->destruct();
this->construct(il, std::forward<Args>(args)...);
return reference();
@@ -411,11 +416,11 @@
// `optional` is empty, behavior is undefined.
//
// If you need myOpt->foo in constexpr, use (*myOpt).foo instead.
- const T* operator->() const {
+ absl::Nonnull<const T*> operator->() const ABSL_ATTRIBUTE_LIFETIME_BOUND {
ABSL_HARDENING_ASSERT(this->engaged_);
return std::addressof(this->data_);
}
- T* operator->() {
+ absl::Nonnull<T*> operator->() ABSL_ATTRIBUTE_LIFETIME_BOUND {
ABSL_HARDENING_ASSERT(this->engaged_);
return std::addressof(this->data_);
}
@@ -424,17 +429,17 @@
//
// Accesses the underlying `T` value of an `optional`. If the `optional` is
// empty, behavior is undefined.
- constexpr const T& operator*() const& {
+ constexpr const T& operator*() const& ABSL_ATTRIBUTE_LIFETIME_BOUND {
return ABSL_HARDENING_ASSERT(this->engaged_), reference();
}
- T& operator*() & {
+ T& operator*() & ABSL_ATTRIBUTE_LIFETIME_BOUND {
ABSL_HARDENING_ASSERT(this->engaged_);
return reference();
}
- constexpr const T&& operator*() const && {
+ constexpr const T&& operator*() const&& ABSL_ATTRIBUTE_LIFETIME_BOUND {
return ABSL_HARDENING_ASSERT(this->engaged_), absl::move(reference());
}
- T&& operator*() && {
+ T&& operator*() && ABSL_ATTRIBUTE_LIFETIME_BOUND {
ABSL_HARDENING_ASSERT(this->engaged_);
return std::move(reference());
}
@@ -469,23 +474,24 @@
// and lvalue/rvalue-ness of the `optional` is preserved to the view of
// the `T` sub-object. Throws `absl::bad_optional_access` when the `optional`
// is empty.
- constexpr const T& value() const & {
+ constexpr const T& value() const& ABSL_ATTRIBUTE_LIFETIME_BOUND {
return static_cast<bool>(*this)
? reference()
: (optional_internal::throw_bad_optional_access(), reference());
}
- T& value() & {
+ T& value() & ABSL_ATTRIBUTE_LIFETIME_BOUND {
return static_cast<bool>(*this)
? reference()
: (optional_internal::throw_bad_optional_access(), reference());
}
- T&& value() && { // NOLINT(build/c++11)
+ T&& value() && ABSL_ATTRIBUTE_LIFETIME_BOUND { // NOLINT(build/c++11)
return std::move(
static_cast<bool>(*this)
? reference()
: (optional_internal::throw_bad_optional_access(), reference()));
}
- constexpr const T&& value() const && { // NOLINT(build/c++11)
+ constexpr const T&& value()
+ const&& ABSL_ATTRIBUTE_LIFETIME_BOUND { // NOLINT(build/c++11)
return absl::move(
static_cast<bool>(*this)
? reference()
diff --git a/absl/types/optional_test.cc b/absl/types/optional_test.cc
index 7ef142c..115e20c 100644
--- a/absl/types/optional_test.cc
+++ b/absl/types/optional_test.cc
@@ -23,10 +23,41 @@
#include "gtest/gtest.h"
#include "absl/base/config.h"
-#include "absl/base/internal/raw_logging.h"
+#include "absl/log/log.h"
#include "absl/meta/type_traits.h"
#include "absl/strings/string_view.h"
+#if defined(__cplusplus) && __cplusplus >= 202002L
+// In C++20, volatile-qualified return types are deprecated.
+#define ABSL_VOLATILE_RETURN_TYPES_DEPRECATED 1
+#endif
+
+// The following types help test an internal compiler error in GCC5 though
+// GCC10. The case OptionalTest.InternalCompilerErrorInGcc5ToGcc10 crashes the
+// compiler without a workaround. This test case should remain at the beginning
+// of the file as the internal compiler error is sensitive to other constructs
+// in this file.
+template <class T, class...>
+using GccIceHelper1 = T;
+template <typename T>
+struct GccIceHelper2 {};
+template <typename T>
+class GccIce {
+ template <typename U,
+ typename SecondTemplateArgHasToExistForSomeReason = void,
+ typename DependentType = void,
+ typename = std::is_assignable<GccIceHelper1<T, DependentType>&, U>>
+ GccIce& operator=(GccIceHelper2<U> const&) {}
+};
+
+TEST(OptionalTest, InternalCompilerErrorInGcc5ToGcc10) {
+ GccIce<int> instantiate_ice_with_same_type_as_optional;
+ static_cast<void>(instantiate_ice_with_same_type_as_optional);
+ absl::optional<int> val1;
+ absl::optional<int> val2;
+ val1 = val2;
+}
+
struct Hashable {};
namespace std {
@@ -66,9 +97,9 @@
// 4522: multiple assignment operators specified
// We wrote multiple of them to test that the correct overloads are selected.
#ifdef _MSC_VER
-#pragma warning( push )
-#pragma warning( disable : 4521)
-#pragma warning( disable : 4522)
+#pragma warning(push)
+#pragma warning(disable : 4521)
+#pragma warning(disable : 4522)
#endif
struct Listenable {
static StructorListener* listener;
@@ -102,20 +133,11 @@
~Listenable() { ++listener->destruct; }
};
#ifdef _MSC_VER
-#pragma warning( pop )
+#pragma warning(pop)
#endif
StructorListener* Listenable::listener = nullptr;
-// ABSL_HAVE_NO_CONSTEXPR_INITIALIZER_LIST is defined to 1 when the standard
-// library implementation doesn't marked initializer_list's default constructor
-// constexpr. The C++11 standard doesn't specify constexpr on it, but C++14
-// added it. However, libstdc++ 4.7 marked it constexpr.
-#if defined(_LIBCPP_VERSION) && \
- (_LIBCPP_STD_VER <= 11 || defined(_LIBCPP_HAS_NO_CXX14_CONSTEXPR))
-#define ABSL_HAVE_NO_CONSTEXPR_INITIALIZER_LIST 1
-#endif
-
struct ConstexprType {
enum CtorTypes {
kCtorDefault,
@@ -125,10 +147,8 @@
};
constexpr ConstexprType() : x(kCtorDefault) {}
constexpr explicit ConstexprType(int i) : x(kCtorInt) {}
-#ifndef ABSL_HAVE_NO_CONSTEXPR_INITIALIZER_LIST
constexpr ConstexprType(std::initializer_list<int> il)
: x(kCtorInitializerList) {}
-#endif
constexpr ConstexprType(const char*) // NOLINT(runtime/explicit)
: x(kCtorConstChar) {}
int x;
@@ -205,6 +225,7 @@
EXPECT_TRUE(opt42_copy);
EXPECT_EQ(42, *opt42_copy);
}
+#if !defined(ABSL_VOLATILE_RETURN_TYPES_DEPRECATED)
{
absl::optional<volatile int> empty, opt42 = 42;
absl::optional<volatile int> empty_copy(empty);
@@ -213,6 +234,7 @@
EXPECT_TRUE(opt42_copy);
EXPECT_EQ(42, *opt42_copy);
}
+#endif
// test copyablility
EXPECT_TRUE(std::is_copy_constructible<absl::optional<int>>::value);
EXPECT_TRUE(std::is_copy_constructible<absl::optional<Copyable>>::value);
@@ -224,18 +246,11 @@
EXPECT_FALSE(
absl::is_trivially_copy_constructible<absl::optional<Copyable>>::value);
-#if defined(ABSL_USES_STD_OPTIONAL) && defined(__GLIBCXX__)
- // libstdc++ std::optional implementation (as of 7.2) has a bug: when T is
- // trivially copyable, optional<T> is not trivially copyable (due to one of
- // its base class is unconditionally nontrivial).
-#define ABSL_GLIBCXX_OPTIONAL_TRIVIALITY_BUG 1
-#endif
-#ifndef ABSL_GLIBCXX_OPTIONAL_TRIVIALITY_BUG
EXPECT_TRUE(
absl::is_trivially_copy_constructible<absl::optional<int>>::value);
EXPECT_TRUE(
absl::is_trivially_copy_constructible<absl::optional<const int>>::value);
-#ifndef _MSC_VER
+#if !defined(_MSC_VER) && !defined(ABSL_VOLATILE_RETURN_TYPES_DEPRECATED)
// See defect report "Trivial copy/move constructor for class with volatile
// member" at
// http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#2094
@@ -244,8 +259,7 @@
// Also a cv-qualified scalar type should be trivially copyable.
EXPECT_TRUE(absl::is_trivially_copy_constructible<
absl::optional<volatile int>>::value);
-#endif // _MSC_VER
-#endif // ABSL_GLIBCXX_OPTIONAL_TRIVIALITY_BUG
+#endif // !defined(_MSC_VER) && !defined(ABSL_VOLATILE_RETURN_TYPES_DEPRECATED)
// constexpr copy constructor for trivially copyable types
{
@@ -275,17 +289,10 @@
EXPECT_TRUE(absl::is_trivially_copy_constructible<
absl::optional<const TrivialCopyable>>::value);
#endif
- // When testing with VS 2017 15.3, there seems to be a bug in MSVC
- // std::optional when T is volatile-qualified. So skipping this test.
- // Bug report:
- // https://connect.microsoft.com/VisualStudio/feedback/details/3142534
-#if defined(ABSL_USES_STD_OPTIONAL) && defined(_MSC_VER) && _MSC_VER >= 1911
-#define ABSL_MSVC_OPTIONAL_VOLATILE_COPY_BUG 1
-#endif
-#ifndef ABSL_MSVC_OPTIONAL_VOLATILE_COPY_BUG
+#if !defined(ABSL_VOLATILE_RETURN_TYPES_DEPRECATED)
EXPECT_FALSE(std::is_copy_constructible<
absl::optional<volatile TrivialCopyable>>::value);
-#endif
+#endif // !defined(ABSL_VOLATILE_RETURN_TYPES_DEPRECATED)
}
}
@@ -305,11 +312,9 @@
EXPECT_FALSE(std::is_move_constructible<absl::optional<NonMovable>>::value);
// test noexcept
EXPECT_TRUE(std::is_nothrow_move_constructible<absl::optional<int>>::value);
-#ifndef ABSL_USES_STD_OPTIONAL
EXPECT_EQ(
absl::default_allocator_is_nothrow::value,
std::is_nothrow_move_constructible<absl::optional<MoveableThrow>>::value);
-#endif
EXPECT_TRUE(std::is_nothrow_move_constructible<
absl::optional<MoveableNoThrow>>::value);
}
@@ -336,11 +341,9 @@
constexpr absl::optional<ConstexprType> opt1{absl::in_place_t(), 1};
static_assert(opt1, "");
static_assert((*opt1).x == ConstexprType::kCtorInt, "");
-#ifndef ABSL_HAVE_NO_CONSTEXPR_INITIALIZER_LIST
constexpr absl::optional<ConstexprType> opt2{absl::in_place_t(), {1, 2}};
static_assert(opt2, "");
static_assert((*opt2).x == ConstexprType::kCtorInitializerList, "");
-#endif
EXPECT_FALSE((std::is_constructible<absl::optional<ConvertsFromInPlaceT>,
absl::in_place_t>::value));
@@ -638,8 +641,7 @@
EXPECT_TRUE(absl::is_copy_assignable<NonTrivial>::value);
EXPECT_FALSE(absl::is_trivially_copy_assignable<NonTrivial>::value);
- // std::optional doesn't support volatile nontrivial types.
-#ifndef ABSL_USES_STD_OPTIONAL
+#if !defined(ABSL_VOLATILE_RETURN_TYPES_DEPRECATED)
{
StructorListener listener;
Listenable::listener = &listener;
@@ -658,7 +660,7 @@
EXPECT_EQ(1, listener.destruct);
EXPECT_EQ(1, listener.volatile_copy_assign);
}
-#endif // ABSL_USES_STD_OPTIONAL
+#endif // !defined(ABSL_VOLATILE_RETURN_TYPES_DEPRECATED)
}
TEST(optionalTest, MoveAssignment) {
@@ -681,8 +683,7 @@
EXPECT_EQ(1, listener.destruct);
EXPECT_EQ(1, listener.move_assign);
}
- // std::optional doesn't support volatile nontrivial types.
-#ifndef ABSL_USES_STD_OPTIONAL
+#if !defined(ABSL_VOLATILE_RETURN_TYPES_DEPRECATED)
{
StructorListener listener;
Listenable::listener = &listener;
@@ -702,7 +703,7 @@
EXPECT_EQ(1, listener.destruct);
EXPECT_EQ(1, listener.volatile_move_assign);
}
-#endif // ABSL_USES_STD_OPTIONAL
+#endif // !defined(ABSL_VOLATILE_RETURN_TYPES_DEPRECATED)
EXPECT_FALSE(absl::is_move_assignable<absl::optional<const int>>::value);
EXPECT_TRUE(absl::is_move_assignable<absl::optional<Copyable>>::value);
EXPECT_TRUE(absl::is_move_assignable<absl::optional<MoveableThrow>>::value);
@@ -974,45 +975,13 @@
EXPECT_EQ("foo", *opt);
const auto& opt_const = opt;
EXPECT_EQ("foo", *opt_const);
- EXPECT_EQ(opt->size(), 3);
- EXPECT_EQ(opt_const->size(), 3);
+ EXPECT_EQ(opt->size(), 3u);
+ EXPECT_EQ(opt_const->size(), 3u);
constexpr absl::optional<ConstexprType> opt1(1);
static_assert((*opt1).x == ConstexprType::kCtorInt, "");
}
-// gcc has a bug pre 4.9.1 where it doesn't do correct overload resolution
-// when overloads are const-qualified and *this is an raluve.
-// Skip that test to make the build green again when using the old compiler.
-// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=59296 is fixed in 4.9.1.
-#if defined(__GNUC__) && !defined(__clang__)
-#define GCC_VERSION (__GNUC__ * 10000 \
- + __GNUC_MINOR__ * 100 \
- + __GNUC_PATCHLEVEL__)
-#if GCC_VERSION < 40901
-#define ABSL_SKIP_OVERLOAD_TEST_DUE_TO_GCC_BUG
-#endif
-#endif
-
-// MSVC has a bug with "cv-qualifiers in class construction", fixed in 2017. See
-// https://docs.microsoft.com/en-us/cpp/cpp-conformance-improvements-2017#bug-fixes
-// The compiler some incorrectly ingores the cv-qualifier when generating a
-// class object via a constructor call. For example:
-//
-// class optional {
-// constexpr T&& value() &&;
-// constexpr const T&& value() const &&;
-// }
-//
-// using COI = const absl::optional<int>;
-// static_assert(2 == COI(2).value(), ""); // const &&
-//
-// This should invoke the "const &&" overload but since it ignores the const
-// qualifier it finds the "&&" overload the best candidate.
-#if defined(_MSC_VER) && _MSC_VER < 1910
-#define ABSL_SKIP_OVERLOAD_TEST_DUE_TO_MSVC_BUG
-#endif
-
TEST(optionalTest, Value) {
using O = absl::optional<std::string>;
using CO = const absl::optional<std::string>;
@@ -1025,19 +994,15 @@
EXPECT_EQ("lvalue_c", lvalue_c.value());
EXPECT_EQ("xvalue", O(absl::in_place, "xvalue").value());
EXPECT_EQ("xvalue_c", OC(absl::in_place, "xvalue_c").value());
-#ifndef ABSL_SKIP_OVERLOAD_TEST_DUE_TO_GCC_BUG
EXPECT_EQ("cxvalue", CO(absl::in_place, "cxvalue").value());
-#endif
EXPECT_EQ("&", TypeQuals(lvalue.value()));
EXPECT_EQ("c&", TypeQuals(clvalue.value()));
EXPECT_EQ("c&", TypeQuals(lvalue_c.value()));
EXPECT_EQ("&&", TypeQuals(O(absl::in_place, "xvalue").value()));
-#if !defined(ABSL_SKIP_OVERLOAD_TEST_DUE_TO_MSVC_BUG) && \
- !defined(ABSL_SKIP_OVERLOAD_TEST_DUE_TO_GCC_BUG)
EXPECT_EQ("c&&", TypeQuals(CO(absl::in_place, "cxvalue").value()));
-#endif
EXPECT_EQ("c&&", TypeQuals(OC(absl::in_place, "xvalue_c").value()));
+#if !defined(ABSL_VOLATILE_RETURN_TYPES_DEPRECATED)
// test on volatile type
using OV = absl::optional<volatile int>;
OV lvalue_v(absl::in_place, 42);
@@ -1045,6 +1010,7 @@
EXPECT_EQ(42, OV(42).value());
EXPECT_TRUE((std::is_same<volatile int&, decltype(lvalue_v.value())>::value));
EXPECT_TRUE((std::is_same<volatile int&&, decltype(OV(42).value())>::value));
+#endif // !defined(ABSL_VOLATILE_RETURN_TYPES_DEPRECATED)
// test exception throw on value()
absl::optional<int> empty;
@@ -1057,7 +1023,7 @@
// test constexpr value()
constexpr absl::optional<int> o1(1);
static_assert(1 == o1.value(), ""); // const &
-#if !defined(_MSC_VER) && !defined(ABSL_SKIP_OVERLOAD_TEST_DUE_TO_GCC_BUG)
+#ifndef _MSC_VER
using COI = const absl::optional<int>;
static_assert(2 == COI(2).value(), ""); // const &&
#endif
@@ -1075,18 +1041,14 @@
EXPECT_EQ("lvalue_c", *lvalue_c);
EXPECT_EQ("xvalue", *O(absl::in_place, "xvalue"));
EXPECT_EQ("xvalue_c", *OC(absl::in_place, "xvalue_c"));
-#ifndef ABSL_SKIP_OVERLOAD_TEST_DUE_TO_GCC_BUG
EXPECT_EQ("cxvalue", *CO(absl::in_place, "cxvalue"));
-#endif
EXPECT_EQ("&", TypeQuals(*lvalue));
EXPECT_EQ("c&", TypeQuals(*clvalue));
EXPECT_EQ("&&", TypeQuals(*O(absl::in_place, "xvalue")));
-#if !defined(ABSL_SKIP_OVERLOAD_TEST_DUE_TO_MSVC_BUG) && \
- !defined(ABSL_SKIP_OVERLOAD_TEST_DUE_TO_GCC_BUG)
EXPECT_EQ("c&&", TypeQuals(*CO(absl::in_place, "cxvalue")));
-#endif
EXPECT_EQ("c&&", TypeQuals(*OC(absl::in_place, "xvalue_c")));
+#if !defined(ABSL_VOLATILE_RETURN_TYPES_DEPRECATED)
// test on volatile type
using OV = absl::optional<volatile int>;
OV lvalue_v(absl::in_place, 42);
@@ -1094,6 +1056,7 @@
EXPECT_EQ(42, *OV(42));
EXPECT_TRUE((std::is_same<volatile int&, decltype(*lvalue_v)>::value));
EXPECT_TRUE((std::is_same<volatile int&&, decltype(*OV(42))>::value));
+#endif // !defined(ABSL_VOLATILE_RETURN_TYPES_DEPRECATED)
constexpr absl::optional<int> opt1(1);
static_assert(*opt1 == 1, "");
@@ -1113,11 +1076,9 @@
constexpr absl::optional<double> copt_empty, copt_set = {1.2};
static_assert(42.0 == copt_empty.value_or(42), "");
static_assert(1.2 == copt_set.value_or(42), "");
-#ifndef ABSL_SKIP_OVERLOAD_TEST_DUE_TO_MSVC_BUG
using COD = const absl::optional<double>;
static_assert(42.0 == COD().value_or(42), "");
static_assert(1.2 == COD(1.2).value_or(42), "");
-#endif
}
// make_optional cannot be constexpr until C++17
@@ -1196,7 +1157,6 @@
EXPECT_TRUE(x >= y);
}
-
template <typename T, typename U, typename V>
void TestComparisons() {
absl::optional<T> ae, a2{2}, a4{4};
@@ -1289,7 +1249,6 @@
EXPECT_TRUE(e1 == e2);
}
-
TEST(optionalTest, SwapRegression) {
StructorListener listener;
Listenable::listener = &listener;
@@ -1505,7 +1464,7 @@
for (int i = 0; i < 100; ++i) {
hashcodes.insert(hash(i));
}
- EXPECT_GT(hashcodes.size(), 90);
+ EXPECT_GT(hashcodes.size(), 90u);
static_assert(is_hash_enabled_for<absl::optional<int>>::value, "");
static_assert(is_hash_enabled_for<absl::optional<Hashable>>::value, "");
@@ -1540,8 +1499,7 @@
struct MoveMeNoThrow {
MoveMeNoThrow() : x(0) {}
[[noreturn]] MoveMeNoThrow(const MoveMeNoThrow& other) : x(other.x) {
- ABSL_RAW_LOG(FATAL, "Should not be called.");
- abort();
+ LOG(FATAL) << "Should not be called.";
}
MoveMeNoThrow(MoveMeNoThrow&& other) noexcept : x(other.x) {}
int x;
@@ -1558,12 +1516,10 @@
static_assert(
std::is_nothrow_move_constructible<absl::optional<MoveMeNoThrow>>::value,
"");
-#ifndef ABSL_USES_STD_OPTIONAL
static_assert(absl::default_allocator_is_nothrow::value ==
std::is_nothrow_move_constructible<
absl::optional<MoveMeThrow>>::value,
"");
-#endif
std::vector<absl::optional<MoveMeNoThrow>> v;
for (int i = 0; i < 10; ++i) v.emplace_back();
}
diff --git a/absl/types/span.h b/absl/types/span.h
index 41db342..c183aa8 100644
--- a/absl/types/span.h
+++ b/absl/types/span.h
@@ -40,11 +40,10 @@
// * `absl::Span` has compiler-provided move and copy constructors and
// assignment. This is due to them being specified as `constexpr`, but that
// implies const in C++11.
-// * `absl::Span` has no `element_type` typedef
// * A read-only `absl::Span<const T>` can be implicitly constructed from an
// initializer list.
// * `absl::Span` has no `bytes()`, `size_bytes()`, `as_bytes()`, or
-// `as_mutable_bytes()` methods
+// `as_writable_bytes()` methods
// * `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
@@ -61,8 +60,10 @@
#include <type_traits>
#include <utility>
+#include "absl/base/attributes.h"
#include "absl/base/internal/throw_delegate.h"
#include "absl/base/macros.h"
+#include "absl/base/nullability.h"
#include "absl/base/optimization.h"
#include "absl/base/port.h" // TODO(strel): remove this include
#include "absl/meta/type_traits.h"
@@ -161,16 +162,19 @@
// Used to SFINAE-enable a function when the slice elements are const.
template <typename U>
- using EnableIfConstView =
+ using EnableIfValueIsConst =
typename std::enable_if<std::is_const<T>::value, U>::type;
// Used to SFINAE-enable a function when the slice elements are mutable.
template <typename U>
- using EnableIfMutableView =
+ using EnableIfValueIsMutable =
typename std::enable_if<!std::is_const<T>::value, U>::type;
public:
+ using element_type = T;
using value_type = absl::remove_cv_t<T>;
+ // TODO(b/316099902) - pointer should be Nullable<T*>, but this makes it hard
+ // to recognize foreach loops as safe.
using pointer = T*;
using const_pointer = const T*;
using reference = T&;
@@ -196,13 +200,34 @@
// Explicit reference constructor for a mutable `Span<T>` type. Can be
// replaced with MakeSpan() to infer the type parameter.
template <typename V, typename = EnableIfConvertibleFrom<V>,
- typename = EnableIfMutableView<V>>
- explicit Span(V& v) noexcept // NOLINT(runtime/references)
+ typename = EnableIfValueIsMutable<V>,
+ typename = span_internal::EnableIfNotIsView<V>>
+ explicit Span(
+ V& v
+ ABSL_ATTRIBUTE_LIFETIME_BOUND) noexcept // NOLINT(runtime/references)
: Span(span_internal::GetData(v), v.size()) {}
// Implicit reference constructor for a read-only `Span<const T>` type
template <typename V, typename = EnableIfConvertibleFrom<V>,
- typename = EnableIfConstView<V>>
+ typename = EnableIfValueIsConst<V>,
+ typename = span_internal::EnableIfNotIsView<V>>
+ constexpr Span(
+ const V& v
+ ABSL_ATTRIBUTE_LIFETIME_BOUND) noexcept // NOLINT(runtime/explicit)
+ : Span(span_internal::GetData(v), v.size()) {}
+
+ // Overloads of the above two functions that are only enabled for view types.
+ // This is so we can drop the ABSL_ATTRIBUTE_LIFETIME_BOUND annotation. These
+ // overloads must be made unique by using a different template parameter list
+ // (hence the = 0 for the IsView enabler).
+ template <typename V, typename = EnableIfConvertibleFrom<V>,
+ typename = EnableIfValueIsMutable<V>,
+ span_internal::EnableIfIsView<V> = 0>
+ explicit Span(V& v) noexcept // NOLINT(runtime/references)
+ : Span(span_internal::GetData(v), v.size()) {}
+ template <typename V, typename = EnableIfConvertibleFrom<V>,
+ typename = EnableIfValueIsConst<V>,
+ span_internal::EnableIfIsView<V> = 0>
constexpr Span(const V& v) noexcept // NOLINT(runtime/explicit)
: Span(span_internal::GetData(v), v.size()) {}
@@ -242,7 +267,7 @@
// Process(ints);
//
template <typename LazyT = T,
- typename = EnableIfConstView<LazyT>>
+ typename = EnableIfValueIsConst<LazyT>>
Span(std::initializer_list<value_type> v
ABSL_ATTRIBUTE_LIFETIME_BOUND) noexcept // NOLINT(runtime/explicit)
: Span(v.begin(), v.size()) {}
@@ -274,8 +299,7 @@
//
// Returns a reference to the i'th element of this span.
constexpr reference operator[](size_type i) const noexcept {
- // MSVC 2015 accepts this as constexpr, but not ptr_[i]
- return ABSL_HARDENING_ASSERT(i < size()), *(data() + i);
+ return ABSL_HARDENING_ASSERT(i < size()), ptr_[i];
}
// Span::at()
@@ -398,7 +422,7 @@
// absl::MakeSpan(vec).subspan(5); // throws std::out_of_range
constexpr Span subspan(size_type pos = 0, size_type len = npos) const {
return (pos <= size())
- ? Span(data() + pos, span_internal::Min(size() - pos, len))
+ ? Span(data() + pos, (std::min)(size() - pos, len))
: (base_internal::ThrowStdOutOfRange("pos > size()"), Span());
}
@@ -658,13 +682,14 @@
// }
//
template <int&... ExplicitArgumentBarrier, typename T>
-constexpr Span<T> MakeSpan(T* ptr, size_t size) noexcept {
+constexpr Span<T> MakeSpan(absl::Nullable<T*> ptr, size_t size) noexcept {
return Span<T>(ptr, size);
}
template <int&... ExplicitArgumentBarrier, typename T>
-Span<T> MakeSpan(T* begin, T* end) noexcept {
- return ABSL_HARDENING_ASSERT(begin <= end), Span<T>(begin, end - begin);
+Span<T> MakeSpan(absl::Nullable<T*> begin, absl::Nullable<T*> end) noexcept {
+ return ABSL_HARDENING_ASSERT(begin <= end),
+ Span<T>(begin, static_cast<size_t>(end - begin));
}
template <int&... ExplicitArgumentBarrier, typename C>
@@ -703,12 +728,14 @@
// ProcessInts(absl::MakeConstSpan(std::vector<int>{ 0, 0, 0 }));
//
template <int&... ExplicitArgumentBarrier, typename T>
-constexpr Span<const T> MakeConstSpan(T* ptr, size_t size) noexcept {
+constexpr Span<const T> MakeConstSpan(absl::Nullable<T*> ptr,
+ size_t size) noexcept {
return Span<const T>(ptr, size);
}
template <int&... ExplicitArgumentBarrier, typename T>
-Span<const T> MakeConstSpan(T* begin, T* end) noexcept {
+Span<const T> MakeConstSpan(absl::Nullable<T*> begin,
+ absl::Nullable<T*> end) noexcept {
return ABSL_HARDENING_ASSERT(begin <= end), Span<const T>(begin, end - begin);
}
diff --git a/absl/types/span_test.cc b/absl/types/span_test.cc
index 2584339..29e8681 100644
--- a/absl/types/span_test.cc
+++ b/absl/types/span_test.cc
@@ -191,7 +191,7 @@
}
void TestInitializerList(absl::Span<const int> s, const std::vector<int>& v) {
- EXPECT_TRUE(absl::equal(s.begin(), s.end(), v.begin(), v.end()));
+ EXPECT_TRUE(std::equal(s.begin(), s.end(), v.begin(), v.end()));
}
TEST(ConstIntSpan, InitializerListConversion) {
@@ -661,6 +661,8 @@
CheckType<absl::Span<int>::const_reverse_iterator>(slice.crend());
testing::StaticAssertTypeEq<int, absl::Span<int>::value_type>();
testing::StaticAssertTypeEq<int, absl::Span<const int>::value_type>();
+ testing::StaticAssertTypeEq<int, absl::Span<int>::element_type>();
+ testing::StaticAssertTypeEq<const int, absl::Span<const int>::element_type>();
testing::StaticAssertTypeEq<int*, absl::Span<int>::pointer>();
testing::StaticAssertTypeEq<const int*, absl::Span<const int>::pointer>();
testing::StaticAssertTypeEq<int&, absl::Span<int>::reference>();
diff --git a/absl/types/variant_test.cc b/absl/types/variant_test.cc
index cf23733..4cd5b7a 100644
--- a/absl/types/variant_test.cc
+++ b/absl/types/variant_test.cc
@@ -281,7 +281,7 @@
using X = variant<int>;
constexpr variant<int> x{};
ASSERT_FALSE(x.valueless_by_exception());
- ASSERT_EQ(0, x.index());
+ ASSERT_EQ(0u, x.index());
EXPECT_EQ(0, absl::get<0>(x));
EXPECT_TRUE(std::is_nothrow_default_constructible<X>::value);
}
@@ -290,7 +290,7 @@
using X = variant<NonNoexceptDefaultConstructible>;
X x{};
ASSERT_FALSE(x.valueless_by_exception());
- ASSERT_EQ(0, x.index());
+ ASSERT_EQ(0u, x.index());
EXPECT_EQ(5, absl::get<0>(x).value);
EXPECT_FALSE(std::is_nothrow_default_constructible<X>::value);
}
@@ -299,7 +299,7 @@
using X = variant<int, NonNoexceptDefaultConstructible>;
X x{};
ASSERT_FALSE(x.valueless_by_exception());
- ASSERT_EQ(0, x.index());
+ ASSERT_EQ(0u, x.index());
EXPECT_EQ(0, absl::get<0>(x));
EXPECT_TRUE(std::is_nothrow_default_constructible<X>::value);
}
@@ -308,7 +308,7 @@
using X = variant<NonNoexceptDefaultConstructible, int>;
X x{};
ASSERT_FALSE(x.valueless_by_exception());
- ASSERT_EQ(0, x.index());
+ ASSERT_EQ(0u, x.index());
EXPECT_EQ(5, absl::get<0>(x).value);
EXPECT_FALSE(std::is_nothrow_default_constructible<X>::value);
}
@@ -480,7 +480,7 @@
ASSERT_TRUE(absl::holds_alternative<std::string>(v2));
EXPECT_EQ("ABC", absl::get<std::string>(v2));
- Var v3(in_place_type_t<std::string>(), "ABC", 2);
+ Var v3(in_place_type_t<std::string>(), "ABC", 2u);
ASSERT_TRUE(absl::holds_alternative<std::string>(v3));
EXPECT_EQ("AB", absl::get<std::string>(v3));
@@ -503,7 +503,7 @@
ASSERT_TRUE(absl::holds_alternative<std::string>(v2));
EXPECT_EQ("ABC", absl::get<std::string>(v2));
- Var v3(in_place_type<std::string>, "ABC", 2);
+ Var v3(in_place_type<std::string>, "ABC", 2u);
ASSERT_TRUE(absl::holds_alternative<std::string>(v3));
EXPECT_EQ("AB", absl::get<std::string>(v3));
@@ -544,7 +544,7 @@
ASSERT_TRUE(absl::holds_alternative<std::string>(v2));
EXPECT_EQ("ABC", absl::get<std::string>(v2));
- Var v3(in_place_index_t<1>(), "ABC", 2);
+ Var v3(in_place_index_t<1>(), "ABC", 2u);
ASSERT_TRUE(absl::holds_alternative<std::string>(v3));
EXPECT_EQ("AB", absl::get<std::string>(v3));
@@ -571,7 +571,7 @@
ASSERT_TRUE(absl::holds_alternative<std::string>(v2));
EXPECT_EQ("ABC", absl::get<std::string>(v2));
- Var v3(in_place_index<1>, "ABC", 2);
+ Var v3(in_place_index<1>, "ABC", 2u);
ASSERT_TRUE(absl::holds_alternative<std::string>(v3));
EXPECT_EQ("AB", absl::get<std::string>(v3));
@@ -688,11 +688,11 @@
EXPECT_EQ(long_str, foo);
variant<int, std::string> so = long_str;
- ASSERT_EQ(1, so.index());
+ ASSERT_EQ(1u, so.index());
EXPECT_EQ(long_str, absl::get<1>(so));
so = *&so;
- ASSERT_EQ(1, so.index());
+ ASSERT_EQ(1u, so.index());
EXPECT_EQ(long_str, absl::get<1>(so));
}
@@ -968,16 +968,16 @@
using Var = variant<int, std::string, double>;
Var v = 1;
- EXPECT_EQ(0, v.index());
+ EXPECT_EQ(0u, v.index());
v = "str";
- EXPECT_EQ(1, v.index());
+ EXPECT_EQ(1u, v.index());
v = 0.;
- EXPECT_EQ(2, v.index());
+ EXPECT_EQ(2u, v.index());
Var v2 = v;
- EXPECT_EQ(2, v2.index());
+ EXPECT_EQ(2u, v2.index());
v2.emplace<int>(3);
- EXPECT_EQ(0, v2.index());
+ EXPECT_EQ(0u, v2.index());
}
TEST(VariantTest, NotValuelessByException) {
@@ -1002,11 +1002,11 @@
using Var = variant<MoveCanThrow, std::string, double>;
Var v(absl::in_place_index<0>);
- EXPECT_EQ(0, v.index());
+ EXPECT_EQ(0u, v.index());
ToValuelessByException(v);
EXPECT_EQ(absl::variant_npos, v.index());
v = "str";
- EXPECT_EQ(1, v.index());
+ EXPECT_EQ(1u, v.index());
}
TEST(VariantTest, ValuelessByException) {
@@ -1084,18 +1084,18 @@
TEST(VariantTest, VariantSize) {
{
using Size1Variant = absl::variant<int>;
- EXPECT_EQ(1, absl::variant_size<Size1Variant>::value);
- EXPECT_EQ(1, absl::variant_size<const Size1Variant>::value);
- EXPECT_EQ(1, absl::variant_size<volatile Size1Variant>::value);
- EXPECT_EQ(1, absl::variant_size<const volatile Size1Variant>::value);
+ EXPECT_EQ(1u, absl::variant_size<Size1Variant>::value);
+ EXPECT_EQ(1u, absl::variant_size<const Size1Variant>::value);
+ EXPECT_EQ(1u, absl::variant_size<volatile Size1Variant>::value);
+ EXPECT_EQ(1u, absl::variant_size<const volatile Size1Variant>::value);
}
{
using Size3Variant = absl::variant<int, float, int>;
- EXPECT_EQ(3, absl::variant_size<Size3Variant>::value);
- EXPECT_EQ(3, absl::variant_size<const Size3Variant>::value);
- EXPECT_EQ(3, absl::variant_size<volatile Size3Variant>::value);
- EXPECT_EQ(3, absl::variant_size<const volatile Size3Variant>::value);
+ EXPECT_EQ(3u, absl::variant_size<Size3Variant>::value);
+ EXPECT_EQ(3u, absl::variant_size<const Size3Variant>::value);
+ EXPECT_EQ(3u, absl::variant_size<volatile Size3Variant>::value);
+ EXPECT_EQ(3u, absl::variant_size<const volatile Size3Variant>::value);
}
}
@@ -1799,14 +1799,14 @@
EXPECT_EQ("B", piece);
struct StrLen {
- int operator()(const char* s) const { return strlen(s); }
- int operator()(const std::string& s) const { return s.size(); }
+ size_t operator()(const char* s) const { return strlen(s); }
+ size_t operator()(const std::string& s) const { return s.size(); }
};
v = "SomeStr";
- EXPECT_EQ(7, absl::visit(StrLen{}, v));
+ EXPECT_EQ(7u, absl::visit(StrLen{}, v));
v = std::string("VeryLargeThisTime");
- EXPECT_EQ(17, absl::visit(StrLen{}, v));
+ EXPECT_EQ(17u, absl::visit(StrLen{}, v));
}
TEST(VariantTest, VisitRValue) {
@@ -1979,7 +1979,7 @@
TEST(VariantTest, VariantMonostateDefaultConstruction) {
absl::variant<absl::monostate, NonDefaultConstructible> var;
- EXPECT_EQ(var.index(), 0);
+ EXPECT_EQ(var.index(), 0u);
}
////////////////////////////////
@@ -2100,7 +2100,7 @@
for (int i = 0; i < 100; ++i) {
hashcodes.insert(hash(i));
}
- EXPECT_GT(hashcodes.size(), 90);
+ EXPECT_GT(hashcodes.size(), 90u);
// test const-qualified
static_assert(type_traits_internal::IsHashable<variant<const int>>::value,
@@ -2312,9 +2312,9 @@
EXPECT_EQ(42, absl::get<int32_t>(variant2));
variant2 =
- ConvertVariantTo<variant<int32_t, uint32_t>>(variant<uint32_t>(42));
+ ConvertVariantTo<variant<int32_t, uint32_t>>(variant<uint32_t>(42u));
ASSERT_TRUE(absl::holds_alternative<uint32_t>(variant2));
- EXPECT_EQ(42, absl::get<uint32_t>(variant2));
+ EXPECT_EQ(42u, absl::get<uint32_t>(variant2));
#endif // !ABSL_USES_STD_VARIANT
variant<Convertible1, Convertible2> variant3(
@@ -2361,10 +2361,10 @@
ASSERT_TRUE(absl::holds_alternative<int32_t>(variant2));
EXPECT_EQ(42, absl::get<int32_t>(variant2));
- variant<uint32_t> source6(42);
+ variant<uint32_t> source6(42u);
variant2 = ConvertVariantTo<variant<int32_t, uint32_t>>(source6);
ASSERT_TRUE(absl::holds_alternative<uint32_t>(variant2));
- EXPECT_EQ(42, absl::get<uint32_t>(variant2));
+ EXPECT_EQ(42u, absl::get<uint32_t>(variant2));
#endif
variant<Convertible2, Convertible1> source7((Convertible1()));
@@ -2455,8 +2455,8 @@
EXPECT_THAT(absl::get_if<int32_t>(&variant2), Pointee(42));
variant2 =
- ConvertVariantTo<variant<int32_t, uint32_t>>(variant<uint32_t>(42));
- EXPECT_THAT(absl::get_if<uint32_t>(&variant2), Pointee(42));
+ ConvertVariantTo<variant<int32_t, uint32_t>>(variant<uint32_t>(42u));
+ EXPECT_THAT(absl::get_if<uint32_t>(&variant2), Pointee(42u));
#endif
variant<Convertible1, Convertible2> variant3(
@@ -2499,9 +2499,9 @@
ConvertVariantTo<variant<int32_t, uint32_t>>(source5));
EXPECT_THAT(absl::get_if<int32_t>(&variant2), Pointee(42));
- variant<uint32_t> source6(42);
+ variant<uint32_t> source6(42u);
variant2 = ConvertVariantTo<variant<int32_t, uint32_t>>(source6);
- EXPECT_THAT(absl::get_if<uint32_t>(&variant2), Pointee(42));
+ EXPECT_THAT(absl::get_if<uint32_t>(&variant2), Pointee(42u));
#endif // !ABSL_USES_STD_VARIANT
variant<Convertible2, Convertible1> source7((Convertible1()));
@@ -2570,7 +2570,7 @@
vec.reserve(3);
auto another_vec = absl::move(vec);
// As a sanity check, verify vector contents.
- ASSERT_EQ(2, another_vec.size());
+ ASSERT_EQ(2u, another_vec.size());
EXPECT_EQ(42, *absl::get<std::unique_ptr<int>>(another_vec[0]));
EXPECT_EQ("Hello", absl::get<std::string>(another_vec[1]));
}
diff --git a/absl/utility/BUILD.bazel b/absl/utility/BUILD.bazel
index 02b2c40..1c01fc1 100644
--- a/absl/utility/BUILD.bazel
+++ b/absl/utility/BUILD.bazel
@@ -14,7 +14,6 @@
# limitations under the License.
#
-load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test")
load(
"//absl:copts/configure_copts.bzl",
"ABSL_DEFAULT_COPTS",
@@ -22,7 +21,14 @@
"ABSL_TEST_COPTS",
)
-package(default_visibility = ["//visibility:public"])
+package(
+ default_visibility = ["//visibility:public"],
+ features = [
+ "header_modules",
+ "layering_check",
+ "parse_headers",
+ ],
+)
licenses(["notice"])
@@ -50,6 +56,31 @@
"//absl/base:core_headers",
"//absl/memory",
"//absl/strings",
+ "@com_google_googletest//:gtest",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_library(
+ name = "if_constexpr",
+ hdrs = [
+ "internal/if_constexpr.h",
+ ],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ "//absl/base:config",
+ ],
+)
+
+cc_test(
+ name = "if_constexpr_test",
+ srcs = ["internal/if_constexpr_test.cc"],
+ copts = ABSL_TEST_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ ":if_constexpr",
+ "@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
)
diff --git a/absl/utility/CMakeLists.txt b/absl/utility/CMakeLists.txt
index 865b758..27ee0de 100644
--- a/absl/utility/CMakeLists.txt
+++ b/absl/utility/CMakeLists.txt
@@ -42,3 +42,27 @@
absl::strings
GTest::gmock_main
)
+
+absl_cc_library(
+ NAME
+ if_constexpr
+ HDRS
+ "internal/if_constexpr.h"
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+ DEPS
+ absl::config
+ PUBLIC
+)
+
+absl_cc_test(
+ NAME
+ if_constexpr_test
+ SRCS
+ "internal/if_constexpr_test.cc"
+ COPTS
+ ${ABSL_TEST_COPTS}
+ DEPS
+ absl::if_constexpr
+ GTest::gmock_main
+)
diff --git a/absl/utility/internal/if_constexpr.h b/absl/utility/internal/if_constexpr.h
new file mode 100644
index 0000000..7a26311
--- /dev/null
+++ b/absl/utility/internal/if_constexpr.h
@@ -0,0 +1,70 @@
+// Copyright 2023 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// The IfConstexpr and IfConstexprElse utilities in this file are meant to be
+// used to emulate `if constexpr` in pre-C++17 mode in library implementation.
+// The motivation is to allow for avoiding complex SFINAE.
+//
+// The functions passed in must depend on the type(s) of the object(s) that
+// require SFINAE. For example:
+// template<typename T>
+// int MaybeFoo(T& t) {
+// if constexpr (HasFoo<T>::value) return t.foo();
+// return 0;
+// }
+//
+// can be written in pre-C++17 as:
+//
+// template<typename T>
+// int MaybeFoo(T& t) {
+// int i = 0;
+// absl::utility_internal::IfConstexpr<HasFoo<T>::value>(
+// [&](const auto& fooer) { i = fooer.foo(); }, t);
+// return i;
+// }
+
+#ifndef ABSL_UTILITY_INTERNAL_IF_CONSTEXPR_H_
+#define ABSL_UTILITY_INTERNAL_IF_CONSTEXPR_H_
+
+#include <tuple>
+#include <utility>
+
+#include "absl/base/config.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+
+namespace utility_internal {
+
+template <bool condition, typename TrueFunc, typename FalseFunc,
+ typename... Args>
+auto IfConstexprElse(TrueFunc&& true_func, FalseFunc&& false_func,
+ Args&&... args) {
+ return std::get<condition>(std::forward_as_tuple(
+ std::forward<FalseFunc>(false_func), std::forward<TrueFunc>(true_func)))(
+ std::forward<Args>(args)...);
+}
+
+template <bool condition, typename Func, typename... Args>
+void IfConstexpr(Func&& func, Args&&... args) {
+ IfConstexprElse<condition>(std::forward<Func>(func), [](auto&&...){},
+ std::forward<Args>(args)...);
+}
+
+} // namespace utility_internal
+
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_UTILITY_INTERNAL_IF_CONSTEXPR_H_
diff --git a/absl/utility/internal/if_constexpr_test.cc b/absl/utility/internal/if_constexpr_test.cc
new file mode 100644
index 0000000..d1ee723
--- /dev/null
+++ b/absl/utility/internal/if_constexpr_test.cc
@@ -0,0 +1,79 @@
+// Copyright 2023 The Abseil Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/utility/internal/if_constexpr.h"
+
+#include <utility>
+
+#include "gtest/gtest.h"
+
+namespace {
+
+struct Empty {};
+struct HasFoo {
+ int foo() const { return 1; }
+};
+
+TEST(IfConstexpr, Basic) {
+ int i = 0;
+ absl::utility_internal::IfConstexpr<false>(
+ [&](const auto& t) { i = t.foo(); }, Empty{});
+ EXPECT_EQ(i, 0);
+
+ absl::utility_internal::IfConstexpr<false>(
+ [&](const auto& t) { i = t.foo(); }, HasFoo{});
+ EXPECT_EQ(i, 0);
+
+ absl::utility_internal::IfConstexpr<true>(
+ [&](const auto& t) { i = t.foo(); }, HasFoo{});
+ EXPECT_EQ(i, 1);
+}
+
+TEST(IfConstexprElse, Basic) {
+ EXPECT_EQ(absl::utility_internal::IfConstexprElse<false>(
+ [&](const auto& t) { return t.foo(); }, [&](const auto&) { return 2; },
+ Empty{}), 2);
+
+ EXPECT_EQ(absl::utility_internal::IfConstexprElse<false>(
+ [&](const auto& t) { return t.foo(); }, [&](const auto&) { return 2; },
+ HasFoo{}), 2);
+
+ EXPECT_EQ(absl::utility_internal::IfConstexprElse<true>(
+ [&](const auto& t) { return t.foo(); }, [&](const auto&) { return 2; },
+ HasFoo{}), 1);
+}
+
+struct HasFooRValue {
+ int foo() && { return 1; }
+};
+struct RValueFunc {
+ void operator()(HasFooRValue&& t) && { *i = std::move(t).foo(); }
+
+ int* i = nullptr;
+};
+
+TEST(IfConstexpr, RValues) {
+ int i = 0;
+ RValueFunc func = {&i};
+ absl::utility_internal::IfConstexpr<false>(
+ std::move(func), HasFooRValue{});
+ EXPECT_EQ(i, 0);
+
+ func = RValueFunc{&i};
+ absl::utility_internal::IfConstexpr<true>(
+ std::move(func), HasFooRValue{});
+ EXPECT_EQ(i, 1);
+}
+
+} // namespace
diff --git a/absl/utility/utility.h b/absl/utility/utility.h
index bf92322..fc0d1f6 100644
--- a/absl/utility/utility.h
+++ b/absl/utility/utility.h
@@ -12,17 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//
-// This header file contains C++11 versions of standard <utility> header
-// abstractions available within C++14 and C++17, and are designed to be drop-in
+// This header file contains C++14 versions of standard <utility> header
+// abstractions available within C++17, and are designed to be drop-in
// replacement for code compliant with C++14 and C++17.
//
// The following abstractions are defined:
//
-// * integer_sequence<T, Ints...> == std::integer_sequence<T, Ints...>
-// * index_sequence<Ints...> == std::index_sequence<Ints...>
-// * make_integer_sequence<T, N> == std::make_integer_sequence<T, N>
-// * make_index_sequence<N> == std::make_index_sequence<N>
-// * index_sequence_for<Ts...> == std::index_sequence_for<Ts...>
// * apply<Functor, Tuple> == std::apply<Functor, Tuple>
// * exchange<T> == std::exchange<T>
// * make_from_tuple<T> == std::make_from_tuple<T>
@@ -33,7 +28,6 @@
//
// References:
//
-// https://en.cppreference.com/w/cpp/utility/integer_sequence
// https://en.cppreference.com/w/cpp/utility/apply
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3658.html
@@ -53,68 +47,18 @@
namespace absl {
ABSL_NAMESPACE_BEGIN
-// integer_sequence
-//
-// Class template representing a compile-time integer sequence. An instantiation
-// of `integer_sequence<T, Ints...>` has a sequence of integers encoded in its
-// type through its template arguments (which is a common need when
-// working with C++11 variadic templates). `absl::integer_sequence` is designed
-// to be a drop-in replacement for C++14's `std::integer_sequence`.
-//
-// Example:
-//
-// template< class T, T... Ints >
-// void user_function(integer_sequence<T, Ints...>);
-//
-// int main()
-// {
-// // user_function's `T` will be deduced to `int` and `Ints...`
-// // will be deduced to `0, 1, 2, 3, 4`.
-// user_function(make_integer_sequence<int, 5>());
-// }
-template <typename T, T... Ints>
-struct integer_sequence {
- using value_type = T;
- static constexpr size_t size() noexcept { return sizeof...(Ints); }
-};
-
-// index_sequence
-//
-// A helper template for an `integer_sequence` of `size_t`,
-// `absl::index_sequence` is designed to be a drop-in replacement for C++14's
-// `std::index_sequence`.
-template <size_t... Ints>
-using index_sequence = integer_sequence<size_t, Ints...>;
+// Historical note: Abseil once provided implementations of these
+// abstractions for platforms that had not yet provided them. Those
+// platforms are no longer supported. New code should simply use the
+// the ones from std directly.
+using std::index_sequence;
+using std::index_sequence_for;
+using std::integer_sequence;
+using std::make_index_sequence;
+using std::make_integer_sequence;
namespace utility_internal {
-template <typename Seq, size_t SeqSize, size_t Rem>
-struct Extend;
-
-// Note that SeqSize == sizeof...(Ints). It's passed explicitly for efficiency.
-template <typename T, T... Ints, size_t SeqSize>
-struct Extend<integer_sequence<T, Ints...>, SeqSize, 0> {
- using type = integer_sequence<T, Ints..., (Ints + SeqSize)...>;
-};
-
-template <typename T, T... Ints, size_t SeqSize>
-struct Extend<integer_sequence<T, Ints...>, SeqSize, 1> {
- using type = integer_sequence<T, Ints..., (Ints + SeqSize)..., 2 * SeqSize>;
-};
-
-// Recursion helper for 'make_integer_sequence<T, N>'.
-// 'Gen<T, N>::type' is an alias for 'integer_sequence<T, 0, 1, ... N-1>'.
-template <typename T, size_t N>
-struct Gen {
- using type =
- typename Extend<typename Gen<T, N / 2>::type, N / 2, N % 2>::type;
-};
-
-template <typename T>
-struct Gen<T, 0> {
- using type = integer_sequence<T>;
-};
-
template <typename T>
struct InPlaceTypeTag {
explicit InPlaceTypeTag() = delete;
@@ -131,32 +75,6 @@
} // namespace utility_internal
-// Compile-time sequences of integers
-
-// make_integer_sequence
-//
-// This template alias is equivalent to
-// `integer_sequence<int, 0, 1, ..., N-1>`, and is designed to be a drop-in
-// replacement for C++14's `std::make_integer_sequence`.
-template <typename T, T N>
-using make_integer_sequence = typename utility_internal::Gen<T, N>::type;
-
-// make_index_sequence
-//
-// This template alias is equivalent to `index_sequence<0, 1, ..., N-1>`,
-// and is designed to be a drop-in replacement for C++14's
-// `std::make_index_sequence`.
-template <size_t N>
-using make_index_sequence = make_integer_sequence<size_t, N>;
-
-// index_sequence_for
-//
-// Converts a typename pack into an index sequence of the same length, and
-// is designed to be a drop-in replacement for C++14's
-// `std::index_sequence_for()`
-template <typename... Ts>
-using index_sequence_for = make_index_sequence<sizeof...(Ts)>;
-
// Tag types
#ifdef ABSL_USES_STD_OPTIONAL
diff --git a/absl/utility/utility_test.cc b/absl/utility/utility_test.cc
index f044ad6..1af6813 100644
--- a/absl/utility/utility_test.cc
+++ b/absl/utility/utility_test.cc
@@ -1,4 +1,4 @@
-// Copyright 2017 The Abseil Authors.
+// Copyright 2022 The Abseil Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -14,10 +14,12 @@
#include "absl/utility/utility.h"
+#include <memory>
#include <sstream>
#include <string>
#include <tuple>
#include <type_traits>
+#include <utility>
#include <vector>
#include "gmock/gmock.h"
@@ -28,139 +30,10 @@
namespace {
-#ifdef _MSC_VER
-// Warnings for unused variables in this test are false positives. On other
-// platforms, they are suppressed by ABSL_ATTRIBUTE_UNUSED, but that doesn't
-// work on MSVC.
-// Both the unused variables and the name length warnings are due to calls
-// to absl::make_index_sequence with very large values, creating very long type
-// names. The resulting warnings are so long they make build output unreadable.
-#pragma warning( push )
-#pragma warning( disable : 4503 ) // decorated name length exceeded
-#pragma warning( disable : 4101 ) // unreferenced local variable
-#endif // _MSC_VER
-
using ::testing::ElementsAre;
using ::testing::Pointee;
using ::testing::StaticAssertTypeEq;
-TEST(IntegerSequenceTest, ValueType) {
- StaticAssertTypeEq<int, absl::integer_sequence<int>::value_type>();
- StaticAssertTypeEq<char, absl::integer_sequence<char>::value_type>();
-}
-
-TEST(IntegerSequenceTest, Size) {
- EXPECT_EQ(0, (absl::integer_sequence<int>::size()));
- EXPECT_EQ(1, (absl::integer_sequence<int, 0>::size()));
- EXPECT_EQ(1, (absl::integer_sequence<int, 1>::size()));
- EXPECT_EQ(2, (absl::integer_sequence<int, 1, 2>::size()));
- EXPECT_EQ(3, (absl::integer_sequence<int, 0, 1, 2>::size()));
- EXPECT_EQ(3, (absl::integer_sequence<int, -123, 123, 456>::size()));
- constexpr size_t sz = absl::integer_sequence<int, 0, 1>::size();
- EXPECT_EQ(2, sz);
-}
-
-TEST(IntegerSequenceTest, MakeIndexSequence) {
- StaticAssertTypeEq<absl::index_sequence<>, absl::make_index_sequence<0>>();
- StaticAssertTypeEq<absl::index_sequence<0>, absl::make_index_sequence<1>>();
- StaticAssertTypeEq<absl::index_sequence<0, 1>,
- absl::make_index_sequence<2>>();
- StaticAssertTypeEq<absl::index_sequence<0, 1, 2>,
- absl::make_index_sequence<3>>();
-}
-
-TEST(IntegerSequenceTest, MakeIntegerSequence) {
- StaticAssertTypeEq<absl::integer_sequence<int>,
- absl::make_integer_sequence<int, 0>>();
- StaticAssertTypeEq<absl::integer_sequence<int, 0>,
- absl::make_integer_sequence<int, 1>>();
- StaticAssertTypeEq<absl::integer_sequence<int, 0, 1>,
- absl::make_integer_sequence<int, 2>>();
- StaticAssertTypeEq<absl::integer_sequence<int, 0, 1, 2>,
- absl::make_integer_sequence<int, 3>>();
-}
-
-template <typename... Ts>
-class Counter {};
-
-template <size_t... Is>
-void CountAll(absl::index_sequence<Is...>) {
- // We only need an alias here, but instantiate a variable to silence warnings
- // for unused typedefs in some compilers.
- ABSL_ATTRIBUTE_UNUSED Counter<absl::make_index_sequence<Is>...> seq;
-}
-
-// This test verifies that absl::make_index_sequence can handle large arguments
-// without blowing up template instantiation stack, going OOM or taking forever
-// to compile (there is hard 15 minutes limit imposed by forge).
-TEST(IntegerSequenceTest, MakeIndexSequencePerformance) {
- // O(log N) template instantiations.
- // We only need an alias here, but instantiate a variable to silence warnings
- // for unused typedefs in some compilers.
- ABSL_ATTRIBUTE_UNUSED absl::make_index_sequence<(1 << 16) - 1> seq;
- // O(N) template instantiations.
- CountAll(absl::make_index_sequence<(1 << 8) - 1>());
-}
-
-template <typename F, typename Tup, size_t... Is>
-auto ApplyFromTupleImpl(F f, const Tup& tup, absl::index_sequence<Is...>)
- -> decltype(f(std::get<Is>(tup)...)) {
- return f(std::get<Is>(tup)...);
-}
-
-template <typename Tup>
-using TupIdxSeq = absl::make_index_sequence<std::tuple_size<Tup>::value>;
-
-template <typename F, typename Tup>
-auto ApplyFromTuple(F f, const Tup& tup)
- -> decltype(ApplyFromTupleImpl(f, tup, TupIdxSeq<Tup>{})) {
- return ApplyFromTupleImpl(f, tup, TupIdxSeq<Tup>{});
-}
-
-template <typename T>
-std::string Fmt(const T& x) {
- std::ostringstream os;
- os << x;
- return os.str();
-}
-
-struct PoorStrCat {
- template <typename... Args>
- std::string operator()(const Args&... args) const {
- std::string r;
- for (const auto& e : {Fmt(args)...}) r += e;
- return r;
- }
-};
-
-template <typename Tup, size_t... Is>
-std::vector<std::string> TupStringVecImpl(const Tup& tup,
- absl::index_sequence<Is...>) {
- return {Fmt(std::get<Is>(tup))...};
-}
-
-template <typename... Ts>
-std::vector<std::string> TupStringVec(const std::tuple<Ts...>& tup) {
- return TupStringVecImpl(tup, absl::index_sequence_for<Ts...>());
-}
-
-TEST(MakeIndexSequenceTest, ApplyFromTupleExample) {
- PoorStrCat f{};
- EXPECT_EQ("12abc3.14", f(12, "abc", 3.14));
- EXPECT_EQ("12abc3.14", ApplyFromTuple(f, std::make_tuple(12, "abc", 3.14)));
-}
-
-TEST(IndexSequenceForTest, Basic) {
- StaticAssertTypeEq<absl::index_sequence<>, absl::index_sequence_for<>>();
- StaticAssertTypeEq<absl::index_sequence<0>, absl::index_sequence_for<int>>();
- StaticAssertTypeEq<absl::index_sequence<0, 1, 2, 3>,
- absl::index_sequence_for<int, void, char, int>>();
-}
-
-TEST(IndexSequenceForTest, Example) {
- EXPECT_THAT(TupStringVec(std::make_tuple(12, "abc", 3.14)),
- ElementsAre("12", "abc", "3.14"));
-}
int Function(int a, int b) { return a - b; }
@@ -227,8 +100,7 @@
}
TEST(ApplyTest, NonCopyableResult) {
- EXPECT_THAT(absl::apply(Factory, std::make_tuple(42)),
- ::testing::Pointee(42));
+ EXPECT_THAT(absl::apply(Factory, std::make_tuple(42)), Pointee(42));
}
TEST(ApplyTest, VoidResult) { absl::apply(NoOp, std::tuple<>()); }
@@ -373,4 +245,3 @@
}
} // namespace
-
diff --git a/ci/absl_alternate_options.h b/ci/absl_alternate_options.h
index 29b020d..a563859 100644
--- a/ci/absl_alternate_options.h
+++ b/ci/absl_alternate_options.h
@@ -15,15 +15,16 @@
// Alternate options.h file, used in continuous integration testing to exercise
// option settings not used by default.
-#ifndef ABSL_BASE_OPTIONS_H_
-#define ABSL_BASE_OPTIONS_H_
+#ifndef ABSL_CI_ABSL_ALTERNATE_OPTIONS_H_
+#define ABSL_CI_ABSL_ALTERNATE_OPTIONS_H_
#define ABSL_OPTION_USE_STD_ANY 0
#define ABSL_OPTION_USE_STD_OPTIONAL 0
#define ABSL_OPTION_USE_STD_STRING_VIEW 0
#define ABSL_OPTION_USE_STD_VARIANT 0
+#define ABSL_OPTION_USE_STD_ORDERING 0
#define ABSL_OPTION_USE_INLINE_NAMESPACE 1
#define ABSL_OPTION_INLINE_NAMESPACE_NAME ns
#define ABSL_OPTION_HARDENED 1
-#endif // ABSL_BASE_OPTIONS_H_
+#endif // ABSL_CI_ABSL_ALTERNATE_OPTIONS_H_
diff --git a/ci/cmake_common.sh b/ci/cmake_common.sh
index 51f3106..784b381 100644
--- a/ci/cmake_common.sh
+++ b/ci/cmake_common.sh
@@ -14,7 +14,11 @@
# The commit of GoogleTest to be used in the CMake tests in this directory.
# Keep this in sync with the commit in the WORKSPACE file.
-readonly ABSL_GOOGLETEST_COMMIT="8d51ffdfab10b3fba636ae69bc03da4b54f8c235"
+# TODO(dmauro): After the next GoogleTest release, use the stable file required
+# by Bzlmod. This means downloading a copy of the file and reuploading it to
+# avoid changing checksums if the compression is changed by GitHub. It also
+# means stop referring to it as a commit and instead use the uploaded filename.
+readonly ABSL_GOOGLETEST_COMMIT="f8d7d77c06936315286eb55f8de22cd23c188571"
# Avoid depending on GitHub by looking for a cached copy of the commit first.
if [[ -r "${KOKORO_GFILE_DIR:-}/distdir/${ABSL_GOOGLETEST_COMMIT}.zip" ]]; then
diff --git a/ci/cmake_install_test.sh b/ci/cmake_install_test.sh
index 97ed847..ab3b86f 100755
--- a/ci/cmake_install_test.sh
+++ b/ci/cmake_install_test.sh
@@ -29,6 +29,18 @@
source "${ABSEIL_ROOT}/ci/linux_docker_containers.sh"
readonly DOCKER_CONTAINER=${LINUX_GCC_LATEST_CONTAINER}
+# Verify that everything works with the standard "cmake && make && make install"
+# without building tests or requiring GoogleTest.
+time docker run \
+ --mount type=bind,source="${ABSEIL_ROOT}",target=/abseil-cpp-ro,readonly \
+ --tmpfs=/buildfs:exec \
+ --workdir=/buildfs \
+ --rm \
+ ${DOCKER_EXTRA_ARGS:-} \
+ ${DOCKER_CONTAINER} \
+ /bin/bash -c "cmake /abseil-cpp-ro && make -j$(nproc) && make install"
+
+# Verify that a more complicated project works.
for link_type in ${LINK_TYPE}; do
time docker run \
--mount type=bind,source="${ABSEIL_ROOT}",target=/abseil-cpp-ro,readonly \
diff --git a/ci/linux_arm_clang-latest_libcxx_bazel.sh b/ci/linux_arm_clang-latest_libcxx_bazel.sh
new file mode 100755
index 0000000..13f4ad1
--- /dev/null
+++ b/ci/linux_arm_clang-latest_libcxx_bazel.sh
@@ -0,0 +1,100 @@
+#!/bin/bash
+#
+# Copyright 2019 The Abseil Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# This script that can be invoked to test abseil-cpp in a hermetic environment
+# using a Docker image on Linux. You must have Docker installed to use this
+# script.
+
+set -euox pipefail
+
+if [[ -z ${ABSEIL_ROOT:-} ]]; then
+ ABSEIL_ROOT="$(realpath $(dirname ${0})/..)"
+fi
+
+if [[ -z ${STD:-} ]]; then
+ STD="c++14 c++17 c++20"
+fi
+
+if [[ -z ${COMPILATION_MODE:-} ]]; then
+ COMPILATION_MODE="fastbuild opt"
+fi
+
+if [[ -z ${EXCEPTIONS_MODE:-} ]]; then
+ EXCEPTIONS_MODE="-fno-exceptions -fexceptions"
+fi
+
+source "${ABSEIL_ROOT}/ci/linux_docker_containers.sh"
+readonly DOCKER_CONTAINER=${LINUX_ARM_CLANG_LATEST_CONTAINER}
+
+# USE_BAZEL_CACHE=1 only works on Kokoro.
+# Without access to the credentials this won't work.
+if [[ ${USE_BAZEL_CACHE:-0} -ne 0 ]]; then
+ DOCKER_EXTRA_ARGS="--mount type=bind,source=${KOKORO_KEYSTORE_DIR},target=/keystore,readonly ${DOCKER_EXTRA_ARGS:-}"
+ # Bazel doesn't track changes to tools outside of the workspace
+ # (e.g. /usr/bin/gcc), so by appending the docker container to the
+ # remote_http_cache url, we make changes to the container part of
+ # the cache key. Hashing the key is to make it shorter and url-safe.
+ container_key=$(echo ${DOCKER_CONTAINER} | sha256sum | head -c 16)
+ BAZEL_EXTRA_ARGS="--remote_cache=https://storage.googleapis.com/absl-bazel-remote-cache/${container_key} --google_credentials=/keystore/73103_absl-bazel-remote-cache ${BAZEL_EXTRA_ARGS:-}"
+fi
+
+# Avoid depending on external sites like GitHub by checking --distdir for
+# external dependencies first.
+# https://docs.bazel.build/versions/master/guide.html#distdir
+if [[ ${KOKORO_GFILE_DIR:-} ]] && [[ -d "${KOKORO_GFILE_DIR}/distdir" ]]; then
+ DOCKER_EXTRA_ARGS="--mount type=bind,source=${KOKORO_GFILE_DIR}/distdir,target=/distdir,readonly ${DOCKER_EXTRA_ARGS:-}"
+ BAZEL_EXTRA_ARGS="--distdir=/distdir ${BAZEL_EXTRA_ARGS:-}"
+fi
+
+for std in ${STD}; do
+ for compilation_mode in ${COMPILATION_MODE}; do
+ for exceptions_mode in ${EXCEPTIONS_MODE}; do
+ echo "--------------------------------------------------------------------"
+ time docker run \
+ --mount type=bind,source="${ABSEIL_ROOT}",target=/abseil-cpp-ro,readonly \
+ --tmpfs=/abseil-cpp \
+ --workdir=/abseil-cpp \
+ --cap-add=SYS_PTRACE \
+ --rm \
+ -e CC="/opt/llvm/clang/bin/clang" \
+ -e BAZEL_CXXOPTS="-std=${std}:-nostdinc++" \
+ -e BAZEL_LINKOPTS="-L/opt/llvm/clang/lib/aarch64-unknown-linux-gnu:-lc++:-lc++abi:-lm:-Wl,-rpath=/opt/llvm/clang/lib/aarch64-unknown-linux-gnu" \
+ -e CPLUS_INCLUDE_PATH="/opt/llvm/clang/include/aarch64-unknown-linux-gnu/c++/v1:/opt/llvm/clang/include/c++/v1" \
+ ${DOCKER_EXTRA_ARGS:-} \
+ ${DOCKER_CONTAINER} \
+ /bin/sh -c "
+ cp -r /abseil-cpp-ro/* /abseil-cpp/
+ if [ -n \"${ALTERNATE_OPTIONS:-}\" ]; then
+ cp ${ALTERNATE_OPTIONS:-} absl/base/options.h || exit 1
+ fi
+ /usr/local/bin/bazel test ... \
+ --compilation_mode=\"${compilation_mode}\" \
+ --copt=\"${exceptions_mode}\" \
+ --copt=\"-DGTEST_REMOVE_LEGACY_TEST_CASEAPI_=1\" \
+ --copt=-Werror \
+ --define=\"absl=1\" \
+ --enable_bzlmod=true \
+ --features=external_include_paths \
+ --keep_going \
+ --show_timestamps \
+ --test_env=\"GTEST_INSTALL_FAILURE_SIGNAL_HANDLER=1\" \
+ --test_env=\"TZDIR=/abseil-cpp/absl/time/internal/cctz/testdata/zoneinfo\" \
+ --test_output=errors \
+ --test_tag_filters=-benchmark \
+ ${BAZEL_EXTRA_ARGS:-}"
+ done
+ done
+done
diff --git a/ci/linux_clang-latest_libcxx_asan_bazel.sh b/ci/linux_clang-latest_libcxx_asan_bazel.sh
index 5245933..3153fae 100755
--- a/ci/linux_clang-latest_libcxx_asan_bazel.sh
+++ b/ci/linux_clang-latest_libcxx_asan_bazel.sh
@@ -25,7 +25,7 @@
fi
if [[ -z ${STD:-} ]]; then
- STD="c++11 c++14 c++17 c++20"
+ STD="c++14 c++17 c++20"
fi
if [[ -z ${COMPILATION_MODE:-} ]]; then
@@ -48,7 +48,7 @@
# remote_http_cache url, we make changes to the container part of
# the cache key. Hashing the key is to make it shorter and url-safe.
container_key=$(echo ${DOCKER_CONTAINER} | sha256sum | head -c 16)
- BAZEL_EXTRA_ARGS="--remote_http_cache=https://storage.googleapis.com/absl-bazel-remote-cache/${container_key} --google_credentials=/keystore/73103_absl-bazel-remote-cache ${BAZEL_EXTRA_ARGS:-}"
+ BAZEL_EXTRA_ARGS="--remote_cache=https://storage.googleapis.com/absl-bazel-remote-cache/${container_key} --google_credentials=/keystore/73103_absl-bazel-remote-cache ${BAZEL_EXTRA_ARGS:-}"
fi
# Avoid depending on external sites like GitHub by checking --distdir for
@@ -77,13 +77,15 @@
/usr/local/bin/bazel test ... \
--compilation_mode="${compilation_mode}" \
--copt="${exceptions_mode}" \
+ --copt="-DGTEST_REMOVE_LEGACY_TEST_CASEAPI_=1" \
--copt="-fsanitize=address" \
--copt="-fsanitize=float-divide-by-zero" \
--copt="-fsanitize=nullability" \
--copt="-fsanitize=undefined" \
--copt="-fno-sanitize-blacklist" \
--copt=-Werror \
- --distdir="/bazel-distdir" \
+ --enable_bzlmod=true \
+ --features=external_include_paths \
--keep_going \
--linkopt="-fsanitize=address" \
--linkopt="-fsanitize-link-c++-runtime" \
diff --git a/ci/linux_clang-latest_libcxx_bazel.sh b/ci/linux_clang-latest_libcxx_bazel.sh
index e0fe653..4f3eba4 100755
--- a/ci/linux_clang-latest_libcxx_bazel.sh
+++ b/ci/linux_clang-latest_libcxx_bazel.sh
@@ -25,7 +25,7 @@
fi
if [[ -z ${STD:-} ]]; then
- STD="c++11 c++14 c++17 c++20"
+ STD="c++14 c++17 c++20"
fi
if [[ -z ${COMPILATION_MODE:-} ]]; then
@@ -48,7 +48,7 @@
# remote_http_cache url, we make changes to the container part of
# the cache key. Hashing the key is to make it shorter and url-safe.
container_key=$(echo ${DOCKER_CONTAINER} | sha256sum | head -c 16)
- BAZEL_EXTRA_ARGS="--remote_http_cache=https://storage.googleapis.com/absl-bazel-remote-cache/${container_key} --google_credentials=/keystore/73103_absl-bazel-remote-cache ${BAZEL_EXTRA_ARGS:-}"
+ BAZEL_EXTRA_ARGS="--remote_cache=https://storage.googleapis.com/absl-bazel-remote-cache/${container_key} --google_credentials=/keystore/73103_absl-bazel-remote-cache ${BAZEL_EXTRA_ARGS:-}"
fi
# Avoid depending on external sites like GitHub by checking --distdir for
@@ -83,9 +83,11 @@
/usr/local/bin/bazel test ... \
--compilation_mode=\"${compilation_mode}\" \
--copt=\"${exceptions_mode}\" \
+ --copt=\"-DGTEST_REMOVE_LEGACY_TEST_CASEAPI_=1\" \
--copt=-Werror \
--define=\"absl=1\" \
- --distdir=\"/bazel-distdir\" \
+ --enable_bzlmod=true \
+ --features=external_include_paths \
--keep_going \
--show_timestamps \
--test_env=\"GTEST_INSTALL_FAILURE_SIGNAL_HANDLER=1\" \
diff --git a/ci/linux_clang-latest_libcxx_tsan_bazel.sh b/ci/linux_clang-latest_libcxx_tsan_bazel.sh
index 555f6b1..06f4c2e 100755
--- a/ci/linux_clang-latest_libcxx_tsan_bazel.sh
+++ b/ci/linux_clang-latest_libcxx_tsan_bazel.sh
@@ -25,7 +25,7 @@
fi
if [[ -z ${STD:-} ]]; then
- STD="c++11 c++14 c++17 c++20"
+ STD="c++14 c++17 c++20"
fi
if [[ -z ${COMPILATION_MODE:-} ]]; then
@@ -48,7 +48,7 @@
# remote_http_cache url, we make changes to the container part of
# the cache key. Hashing the key is to make it shorter and url-safe.
container_key=$(echo ${DOCKER_CONTAINER} | sha256sum | head -c 16)
- BAZEL_EXTRA_ARGS="--remote_http_cache=https://storage.googleapis.com/absl-bazel-remote-cache/${container_key} --google_credentials=/keystore/73103_absl-bazel-remote-cache ${BAZEL_EXTRA_ARGS:-}"
+ BAZEL_EXTRA_ARGS="--remote_cache=https://storage.googleapis.com/absl-bazel-remote-cache/${container_key} --google_credentials=/keystore/73103_absl-bazel-remote-cache ${BAZEL_EXTRA_ARGS:-}"
fi
# Avoid depending on external sites like GitHub by checking --distdir for
@@ -78,14 +78,15 @@
--build_tag_filters="-notsan" \
--compilation_mode="${compilation_mode}" \
--copt="${exceptions_mode}" \
+ --copt="-DGTEST_REMOVE_LEGACY_TEST_CASEAPI_=1" \
--copt="-fsanitize=thread" \
--copt="-fno-sanitize-blacklist" \
--copt=-Werror \
- --distdir="/bazel-distdir" \
+ --enable_bzlmod=true \
+ --features=external_include_paths \
--keep_going \
--linkopt="-fsanitize=thread" \
--show_timestamps \
- --test_env="TSAN_OPTIONS=report_atomic_races=0" \
--test_env="TSAN_SYMBOLIZER_PATH=/opt/llvm/clang/bin/llvm-symbolizer" \
--test_env="TZDIR=/abseil-cpp/absl/time/internal/cctz/testdata/zoneinfo" \
--test_output=errors \
diff --git a/ci/linux_clang-latest_libstdcxx_bazel.sh b/ci/linux_clang-latest_libstdcxx_bazel.sh
index 36fdf82..d499e13 100755
--- a/ci/linux_clang-latest_libstdcxx_bazel.sh
+++ b/ci/linux_clang-latest_libstdcxx_bazel.sh
@@ -25,7 +25,7 @@
fi
if [[ -z ${STD:-} ]]; then
- STD="c++11 c++14 c++17"
+ STD="c++14 c++17"
fi
if [[ -z ${COMPILATION_MODE:-} ]]; then
@@ -48,7 +48,7 @@
# remote_http_cache url, we make changes to the container part of
# the cache key. Hashing the key is to make it shorter and url-safe.
container_key=$(echo ${DOCKER_CONTAINER} | sha256sum | head -c 16)
- BAZEL_EXTRA_ARGS="--remote_http_cache=https://storage.googleapis.com/absl-bazel-remote-cache/${container_key} --google_credentials=/keystore/73103_absl-bazel-remote-cache ${BAZEL_EXTRA_ARGS:-}"
+ BAZEL_EXTRA_ARGS="--remote_cache=https://storage.googleapis.com/absl-bazel-remote-cache/${container_key} --google_credentials=/keystore/73103_absl-bazel-remote-cache ${BAZEL_EXTRA_ARGS:-}"
fi
# Avoid depending on external sites like GitHub by checking --distdir for
@@ -75,10 +75,13 @@
/usr/local/bin/bazel test ... \
--compilation_mode="${compilation_mode}" \
--copt="--gcc-toolchain=/usr/local" \
+ --copt="-DGTEST_REMOVE_LEGACY_TEST_CASEAPI_=1" \
--copt="${exceptions_mode}" \
+ --copt="-march=haswell" \
--copt=-Werror \
--define="absl=1" \
- --distdir="/bazel-distdir" \
+ --enable_bzlmod=true \
+ --features=external_include_paths \
--keep_going \
--linkopt="--gcc-toolchain=/usr/local" \
--show_timestamps \
diff --git a/ci/linux_docker_containers.sh b/ci/linux_docker_containers.sh
index 32865b8..232233d 100644
--- a/ci/linux_docker_containers.sh
+++ b/ci/linux_docker_containers.sh
@@ -15,7 +15,8 @@
# The file contains Docker container identifiers currently used by test scripts.
# Test scripts should source this file to get the identifiers.
-readonly LINUX_ALPINE_CONTAINER="gcr.io/google.com/absl-177019/alpine:20201026"
-readonly LINUX_CLANG_LATEST_CONTAINER="gcr.io/google.com/absl-177019/linux_hybrid-latest:20210617"
-readonly LINUX_GCC_LATEST_CONTAINER="gcr.io/google.com/absl-177019/linux_hybrid-latest:20210617"
-readonly LINUX_GCC_FLOOR_CONTAINER="gcr.io/google.com/absl-177019/linux_gcc-floor:20210617"
+readonly LINUX_ALPINE_CONTAINER="gcr.io/google.com/absl-177019/alpine:20230612"
+readonly LINUX_CLANG_LATEST_CONTAINER="gcr.io/google.com/absl-177019/linux_hybrid-latest:20231218"
+readonly LINUX_ARM_CLANG_LATEST_CONTAINER="gcr.io/google.com/absl-177019/linux_arm_hybrid-latest:20231219"
+readonly LINUX_GCC_LATEST_CONTAINER="gcr.io/google.com/absl-177019/linux_hybrid-latest:20231218"
+readonly LINUX_GCC_FLOOR_CONTAINER="gcr.io/google.com/absl-177019/linux_gcc-floor:20230120"
diff --git a/ci/linux_gcc-floor_libstdcxx_bazel.sh b/ci/linux_gcc-floor_libstdcxx_bazel.sh
index 54ab68a..5bd1dbf 100755
--- a/ci/linux_gcc-floor_libstdcxx_bazel.sh
+++ b/ci/linux_gcc-floor_libstdcxx_bazel.sh
@@ -25,7 +25,7 @@
fi
if [[ -z ${STD:-} ]]; then
- STD="c++11 c++14"
+ STD="c++14"
fi
if [[ -z ${COMPILATION_MODE:-} ]]; then
@@ -59,6 +59,9 @@
BAZEL_EXTRA_ARGS="--distdir=/distdir ${BAZEL_EXTRA_ARGS:-}"
fi
+# TODO(absl-team): This currently uses Bazel 5. When upgrading to a version
+# of Bazel that supports Bzlmod, add --enable_bzlmod=false to keep test
+# coverage for the old WORKSPACE dependency management.
for std in ${STD}; do
for compilation_mode in ${COMPILATION_MODE}; do
for exceptions_mode in ${EXCEPTIONS_MODE}; do
@@ -75,9 +78,11 @@
/usr/local/bin/bazel test ... \
--compilation_mode="${compilation_mode}" \
--copt="${exceptions_mode}" \
+ --copt="-DGTEST_REMOVE_LEGACY_TEST_CASEAPI_=1" \
--copt=-Werror \
--define="absl=1" \
--distdir="/bazel-distdir" \
+ --features=external_include_paths \
--keep_going \
--show_timestamps \
--test_env="GTEST_INSTALL_FAILURE_SIGNAL_HANDLER=1" \
diff --git a/ci/linux_gcc-latest_libstdcxx_bazel.sh b/ci/linux_gcc-latest_libstdcxx_bazel.sh
index 0555ece..8f77346 100755
--- a/ci/linux_gcc-latest_libstdcxx_bazel.sh
+++ b/ci/linux_gcc-latest_libstdcxx_bazel.sh
@@ -25,7 +25,7 @@
fi
if [[ -z ${STD:-} ]]; then
- STD="c++11 c++14 c++17 c++20"
+ STD="c++14 c++17 c++20"
fi
if [[ -z ${COMPILATION_MODE:-} ]]; then
@@ -48,7 +48,7 @@
# remote_http_cache url, we make changes to the container part of
# the cache key. Hashing the key is to make it shorter and url-safe.
container_key=$(echo ${DOCKER_CONTAINER} | sha256sum | head -c 16)
- BAZEL_EXTRA_ARGS="--remote_http_cache=https://storage.googleapis.com/absl-bazel-remote-cache/${container_key} --google_credentials=/keystore/73103_absl-bazel-remote-cache ${BAZEL_EXTRA_ARGS:-}"
+ BAZEL_EXTRA_ARGS="--remote_cache=https://storage.googleapis.com/absl-bazel-remote-cache/${container_key} --google_credentials=/keystore/73103_absl-bazel-remote-cache ${BAZEL_EXTRA_ARGS:-}"
fi
# Avoid depending on external sites like GitHub by checking --distdir for
@@ -81,9 +81,11 @@
/usr/local/bin/bazel test ... \
--compilation_mode=\"${compilation_mode}\" \
--copt=\"${exceptions_mode}\" \
+ --copt=\"-DGTEST_REMOVE_LEGACY_TEST_CASEAPI_=1\" \
--copt=-Werror \
--define=\"absl=1\" \
- --distdir=\"/bazel-distdir\" \
+ --enable_bzlmod=true \
+ --features=external_include_paths \
--keep_going \
--show_timestamps \
--test_env=\"GTEST_INSTALL_FAILURE_SIGNAL_HANDLER=1\" \
diff --git a/ci/linux_gcc-latest_libstdcxx_cmake.sh b/ci/linux_gcc-latest_libstdcxx_cmake.sh
index ab06aa0..1f72123 100755
--- a/ci/linux_gcc-latest_libstdcxx_cmake.sh
+++ b/ci/linux_gcc-latest_libstdcxx_cmake.sh
@@ -23,7 +23,7 @@
source "${ABSEIL_ROOT}/ci/cmake_common.sh"
if [[ -z ${ABSL_CMAKE_CXX_STANDARDS:-} ]]; then
- ABSL_CMAKE_CXX_STANDARDS="11 14 17 20"
+ ABSL_CMAKE_CXX_STANDARDS="14 17 20"
fi
if [[ -z ${ABSL_CMAKE_BUILD_TYPES:-} ]]; then
@@ -54,11 +54,12 @@
cmake /abseil-cpp \
-DABSL_GOOGLETEST_DOWNLOAD_URL=${ABSL_GOOGLETEST_DOWNLOAD_URL} \
-DBUILD_SHARED_LIBS=${build_shared} \
- -DBUILD_TESTING=ON \
+ -DABSL_BUILD_TESTING=ON \
-DCMAKE_BUILD_TYPE=${compilation_mode} \
-DCMAKE_CXX_STANDARD=${std} \
-DCMAKE_MODULE_LINKER_FLAGS=\"-Wl,--no-undefined\" && \
make -j$(nproc) && \
+ TZDIR=/abseil-cpp/absl/time/internal/cctz/testdata/zoneinfo \
ctest -j$(nproc) --output-on-failure"
done
done
diff --git a/ci/linux_gcc_alpine_cmake.sh b/ci/linux_gcc_alpine_cmake.sh
index bce27d2..b784456 100755
--- a/ci/linux_gcc_alpine_cmake.sh
+++ b/ci/linux_gcc_alpine_cmake.sh
@@ -23,7 +23,7 @@
source "${ABSEIL_ROOT}/ci/cmake_common.sh"
if [[ -z ${ABSL_CMAKE_CXX_STANDARDS:-} ]]; then
- ABSL_CMAKE_CXX_STANDARDS="11 14 17"
+ ABSL_CMAKE_CXX_STANDARDS="14 17"
fi
if [[ -z ${ABSL_CMAKE_BUILD_TYPES:-} ]]; then
@@ -53,11 +53,12 @@
/bin/sh -c "
cmake /abseil-cpp \
-DABSL_GOOGLETEST_DOWNLOAD_URL=${ABSL_GOOGLETEST_DOWNLOAD_URL} \
- -DBUILD_TESTING=ON \
+ -DABSL_BUILD_TESTING=ON \
-DCMAKE_BUILD_TYPE=${compilation_mode} \
-DCMAKE_CXX_STANDARD=${std} \
-DCMAKE_MODULE_LINKER_FLAGS=\"-Wl,--no-undefined\" && \
make -j$(nproc) && \
+ TZDIR=/abseil-cpp/absl/time/internal/cctz/testdata/zoneinfo \
ctest -j$(nproc) --output-on-failure"
done
done
diff --git a/ci/macos_xcode_bazel.sh b/ci/macos_xcode_bazel.sh
index 9e14e66..bb8fb4b 100755
--- a/ci/macos_xcode_bazel.sh
+++ b/ci/macos_xcode_bazel.sh
@@ -24,7 +24,7 @@
fi
# If we are running on Kokoro, check for a versioned Bazel binary.
-KOKORO_GFILE_BAZEL_BIN="bazel-3.7.0-darwin-x86_64"
+KOKORO_GFILE_BAZEL_BIN="bazel-7.0.0-darwin-x86_64"
if [[ ${KOKORO_GFILE_DIR:-} ]] && [[ -f ${KOKORO_GFILE_DIR}/${KOKORO_GFILE_BAZEL_BIN} ]]; then
BAZEL_BIN="${KOKORO_GFILE_DIR}/${KOKORO_GFILE_BAZEL_BIN}"
chmod +x ${BAZEL_BIN}
@@ -32,6 +32,13 @@
BAZEL_BIN="bazel"
fi
+# Avoid depending on external sites like GitHub by checking --distdir for
+# external dependencies first.
+# https://docs.bazel.build/versions/master/guide.html#distdir
+if [[ ${KOKORO_GFILE_DIR:-} ]] && [[ -d "${KOKORO_GFILE_DIR}/distdir" ]]; then
+ BAZEL_EXTRA_ARGS="--distdir=${KOKORO_GFILE_DIR}/distdir ${BAZEL_EXTRA_ARGS:-}"
+fi
+
# Print the compiler and Bazel versions.
echo "---------------"
gcc -v
@@ -46,9 +53,14 @@
fi
${BAZEL_BIN} test ... \
- --copt=-Werror \
+ --copt="-DGTEST_REMOVE_LEGACY_TEST_CASEAPI_=1" \
+ --copt="-Werror" \
+ --cxxopt="-std=c++14" \
+ --enable_bzlmod=true \
+ --features=external_include_paths \
--keep_going \
--show_timestamps \
--test_env="TZDIR=${ABSEIL_ROOT}/absl/time/internal/cctz/testdata/zoneinfo" \
--test_output=errors \
- --test_tag_filters=-benchmark
+ --test_tag_filters=-benchmark \
+ ${BAZEL_EXTRA_ARGS:-}
diff --git a/ci/macos_xcode_cmake.sh b/ci/macos_xcode_cmake.sh
index 2a870cf..690f86b 100755
--- a/ci/macos_xcode_cmake.sh
+++ b/ci/macos_xcode_cmake.sh
@@ -45,12 +45,13 @@
time cmake ${ABSEIL_ROOT} \
-GXcode \
-DBUILD_SHARED_LIBS=${build_shared} \
- -DBUILD_TESTING=ON \
+ -DABSL_BUILD_TESTING=ON \
-DCMAKE_BUILD_TYPE=${compilation_mode} \
- -DCMAKE_CXX_STANDARD=11 \
+ -DCMAKE_CXX_STANDARD=14 \
-DCMAKE_MODULE_LINKER_FLAGS="-Wl,--no-undefined" \
-DABSL_GOOGLETEST_DOWNLOAD_URL="${ABSL_GOOGLETEST_DOWNLOAD_URL}"
time cmake --build .
- time ctest -C ${compilation_mode} --output-on-failure
+ time TZDIR=${ABSEIL_ROOT}/absl/time/internal/cctz/testdata/zoneinfo \
+ ctest -C ${compilation_mode} --output-on-failure
done
done
diff --git a/ci/windows_clangcl_bazel.bat b/ci/windows_clangcl_bazel.bat
new file mode 100755
index 0000000..ac2f916
--- /dev/null
+++ b/ci/windows_clangcl_bazel.bat
@@ -0,0 +1,61 @@
+:: Copyright 2023 The Abseil Authors
+::
+:: Licensed under the Apache License, Version 2.0 (the "License");
+:: you may not use this file except in compliance with the License.
+:: You may obtain a copy of the License at
+::
+:: https://www.apache.org/licenses/LICENSE-2.0
+::
+:: Unless required by applicable law or agreed to in writing, software
+:: distributed under the License is distributed on an "AS IS" BASIS,
+:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+:: See the License for the specific language governing permissions and
+:: limitations under the License.
+
+SETLOCAL ENABLEDELAYEDEXPANSION
+
+:: Set LLVM directory.
+SET BAZEL_LLVM=C:\Program Files\LLVM
+
+:: Change directory to the root of the project.
+CD %~dp0\..
+if %errorlevel% neq 0 EXIT /B 1
+
+:: Set the standard version, [c++14|c++17|c++20|c++latest]
+:: https://msdn.microsoft.com/en-us/library/mt490614.aspx
+:: The default is c++14 if not set on command line.
+IF "%STD%"=="" SET STD=c++14
+
+:: Set the compilation_mode (fastbuild|opt|dbg)
+:: https://docs.bazel.build/versions/master/user-manual.html#flag--compilation_mode
+:: The default is fastbuild
+IF "%COMPILATION_MODE%"=="" SET COMPILATION_MODE=fastbuild
+
+:: Copy the alternate option file, if specified.
+IF NOT "%ALTERNATE_OPTIONS%"=="" copy %ALTERNATE_OPTIONS% absl\base\options.h
+
+:: To upgrade Bazel, first download a new binary from
+:: https://github.com/bazelbuild/bazel/releases and copy it to
+:: /google/data/rw/teams/absl/kokoro/windows.
+::
+:: TODO(absl-team): Remove -Wno-microsoft-cast
+%KOKORO_GFILE_DIR%\bazel-7.0.0-windows-x86_64.exe ^
+ test ... ^
+ --compilation_mode=%COMPILATION_MODE% ^
+ --compiler=clang-cl ^
+ --copt=/std:%STD% ^
+ --copt=/WX ^
+ --copt=-Wno-microsoft-cast ^
+ --define=absl=1 ^
+ --distdir=%KOKORO_GFILE_DIR%\distdir ^
+ --enable_bzlmod=true ^
+ --extra_execution_platforms=//:x64_windows-clang-cl ^
+ --extra_toolchains=@local_config_cc//:cc-toolchain-x64_windows-clang-cl ^
+ --keep_going ^
+ --test_env="GTEST_INSTALL_FAILURE_SIGNAL_HANDLER=1" ^
+ --test_env=TZDIR="%CD%\absl\time\internal\cctz\testdata\zoneinfo" ^
+ --test_output=errors ^
+ --test_tag_filters=-benchmark
+
+if %errorlevel% neq 0 EXIT /B 1
+EXIT /B 0
diff --git a/ci/windows_msvc_bazel.bat b/ci/windows_msvc_bazel.bat
new file mode 100755
index 0000000..e2acf5f
--- /dev/null
+++ b/ci/windows_msvc_bazel.bat
@@ -0,0 +1,52 @@
+:: Copyright 2023 The Abseil Authors
+::
+:: Licensed under the Apache License, Version 2.0 (the "License");
+:: you may not use this file except in compliance with the License.
+:: You may obtain a copy of the License at
+::
+:: https://www.apache.org/licenses/LICENSE-2.0
+::
+:: Unless required by applicable law or agreed to in writing, software
+:: distributed under the License is distributed on an "AS IS" BASIS,
+:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+:: See the License for the specific language governing permissions and
+:: limitations under the License.
+
+SETLOCAL ENABLEDELAYEDEXPANSION
+
+:: Change directory to the root of the project.
+CD %~dp0\..
+if %errorlevel% neq 0 EXIT /B 1
+
+:: Set the standard version, [c++14|c++latest]
+:: https://msdn.microsoft.com/en-us/library/mt490614.aspx
+:: The default is c++14 if not set on command line.
+IF "%STD%"=="" SET STD=c++14
+
+:: Set the compilation_mode (fastbuild|opt|dbg)
+:: https://docs.bazel.build/versions/master/user-manual.html#flag--compilation_mode
+:: The default is fastbuild
+IF "%COMPILATION_MODE%"=="" SET COMPILATION_MODE=fastbuild
+
+:: Copy the alternate option file, if specified.
+IF NOT "%ALTERNATE_OPTIONS%"=="" copy %ALTERNATE_OPTIONS% absl\base\options.h
+
+:: To upgrade Bazel, first download a new binary from
+:: https://github.com/bazelbuild/bazel/releases and copy it to
+:: /google/data/rw/teams/absl/kokoro/windows.
+%KOKORO_GFILE_DIR%\bazel-7.0.0-windows-x86_64.exe ^
+ test ... ^
+ --compilation_mode=%COMPILATION_MODE% ^
+ --copt=/WX ^
+ --copt=/std:%STD% ^
+ --define=absl=1 ^
+ --distdir=%KOKORO_GFILE_DIR%\distdir ^
+ --enable_bzlmod=true ^
+ --keep_going ^
+ --test_env="GTEST_INSTALL_FAILURE_SIGNAL_HANDLER=1" ^
+ --test_env=TZDIR="%CD%\absl\time\internal\cctz\testdata\zoneinfo" ^
+ --test_output=errors ^
+ --test_tag_filters=-benchmark
+
+if %errorlevel% neq 0 EXIT /B 1
+EXIT /B 0
diff --git a/ci/windows_msvc_cmake.bat b/ci/windows_msvc_cmake.bat
new file mode 100755
index 0000000..c0f1ac9
--- /dev/null
+++ b/ci/windows_msvc_cmake.bat
@@ -0,0 +1,74 @@
+:: Copyright 2023 The Abseil Authors
+::
+:: Licensed under the Apache License, Version 2.0 (the "License");
+:: you may not use this file except in compliance with the License.
+:: You may obtain a copy of the License at
+::
+:: https://www.apache.org/licenses/LICENSE-2.0
+::
+:: Unless required by applicable law or agreed to in writing, software
+:: distributed under the License is distributed on an "AS IS" BASIS,
+:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+:: See the License for the specific language governing permissions and
+:: limitations under the License.
+
+SETLOCAL ENABLEDELAYEDEXPANSION
+
+:: The commit of GoogleTest to be used in the CMake tests in this directory.
+:: Keep this in sync with the commit in the WORKSPACE file.
+:: TODO(dmauro): After the next GoogleTest release, use the stable file required
+:: by Bzlmod. This means downloading a copy of the file and reuploading it to
+:: avoid changing checksums if the compression is changed by GitHub. It also
+:: means stop referring to it as a commit and instead use the uploaded filename.
+SET ABSL_GOOGLETEST_COMMIT=f8d7d77c06936315286eb55f8de22cd23c188571
+
+IF EXIST %KOKORO_GFILE_DIR%\distdir\%ABSL_GOOGLETEST_COMMIT%.zip (
+ SET ABSL_GOOGLETEST_DOWNLOAD_URL=file://%KOKORO_GFILE_DIR%\distdir\%ABSL_GOOGLETEST_COMMIT%.zip
+) ELSE (
+ SET ABSL_GOOGLETEST_DOWNLOAD_URL=https://github.com/google/googletest/archive/%ABSL_GOOGLETEST_COMMIT%.zip
+)
+
+:: Replace '\' with '/' in Windows paths for CMake.
+:: Note that this cannot go inside the IF block above, because BAT files are weird.
+SET ABSL_GOOGLETEST_DOWNLOAD_URL=%ABSL_GOOGLETEST_DOWNLOAD_URL:\=/%
+
+IF EXIST "C:\Program Files\CMake\bin\" (
+ SET CMAKE_BIN="C:\Program Files\CMake\bin\cmake.exe"
+ SET CTEST_BIN="C:\Program Files\CMake\bin\ctest.exe"
+) ELSE (
+ SET CMAKE_BIN="cmake.exe"
+ SET CTEST_BIN="ctest.exe"
+)
+
+SET CTEST_OUTPUT_ON_FAILURE=1
+SET CMAKE_BUILD_PARALLEL_LEVEL=16
+SET CTEST_PARALLEL_LEVEL=16
+
+:: Change directory to the root of the project.
+CD %~dp0\..
+if %errorlevel% neq 0 EXIT /B 1
+
+SET TZDIR=%CD%\absl\time\internal\cctz\testdata\zoneinfo
+
+MKDIR "build"
+CD "build"
+
+SET CXXFLAGS="/WX"
+
+%CMAKE_BIN% ^
+ -DABSL_BUILD_TEST_HELPERS=ON ^
+ -DABSL_BUILD_TESTING=ON ^
+ -DABSL_GOOGLETEST_DOWNLOAD_URL=%ABSL_GOOGLETEST_DOWNLOAD_URL% ^
+ -DBUILD_SHARED_LIBS=%ABSL_CMAKE_BUILD_SHARED% ^
+ -DCMAKE_CXX_STANDARD=%ABSL_CMAKE_CXX_STANDARD% ^
+ -G "%ABSL_CMAKE_GENERATOR%" ^
+ ..
+IF %errorlevel% neq 0 EXIT /B 1
+
+%CMAKE_BIN% --build . --target ALL_BUILD --config %ABSL_CMAKE_BUILD_TYPE%
+IF %errorlevel% neq 0 EXIT /B 1
+
+%CTEST_BIN% -C %ABSL_CMAKE_BUILD_TYPE% -E "absl_lifetime_test|absl_symbolize_test"
+IF %errorlevel% neq 0 EXIT /B 1
+
+EXIT /B 0
diff --git a/conanfile.py b/conanfile.py
index 926ec5c..4bbc62e 100755
--- a/conanfile.py
+++ b/conanfile.py
@@ -30,7 +30,7 @@
raise ConanInvalidConfiguration("Abseil does not support MSVC < 14")
def build(self):
- tools.replace_in_file("CMakeLists.txt", "project(absl CXX)", "project(absl CXX)\ninclude(conanbuildinfo.cmake)\nconan_basic_setup()")
+ tools.replace_in_file("CMakeLists.txt", "project(absl LANGUAGES CXX)", "project(absl LANGUAGES CXX)\ninclude(conanbuildinfo.cmake)\nconan_basic_setup()")
cmake = CMake(self)
cmake.definitions["BUILD_TESTING"] = False
cmake.configure()
diff --git a/create_lts.py b/create_lts.py
index d5d7b28..7e5368e 100755
--- a/create_lts.py
+++ b/create_lts.py
@@ -33,7 +33,7 @@
values
Raises:
- Exception: A failure occured
+ Exception: A failure occurred
"""
f = open(filename, 'r')
content = f.read()
@@ -62,7 +62,7 @@
strip_end_tag: the end of the content to be removed
Raises:
- Exception: A failure occured
+ Exception: A failure occurred
"""
f = open(filename, 'r')
content = f.read()
@@ -96,6 +96,18 @@
# Replacement directives go here.
ReplaceStringsInFile(
+ 'MODULE.bazel', {
+ 'version = "head"':
+ 'version = "{}.0"'.format(datestamp)
+ })
+ ReplaceStringsInFile(
+ 'absl/base/config.h', {
+ '#undef ABSL_LTS_RELEASE_VERSION':
+ '#define ABSL_LTS_RELEASE_VERSION {}'.format(datestamp),
+ '#undef ABSL_LTS_RELEASE_PATCH_LEVEL':
+ '#define ABSL_LTS_RELEASE_PATCH_LEVEL 0'
+ })
+ ReplaceStringsInFile(
'absl/base/options.h', {
'#define ABSL_OPTION_USE_INLINE_NAMESPACE 0':
'#define ABSL_OPTION_USE_INLINE_NAMESPACE 1',