Port Linux Bazel tests to GitHub Actions. Uses RBE and the shared lightweight Docker toolchain setup to offload compilation and test execution to GCP, mirroring the 10 Linux "latest" Bazel Kokoro jobs 1:1. PiperOrigin-RevId: 998974545
diff --git a/.bazelrc b/.bazelrc new file mode 100644 index 0000000..b1c92dd --- /dev/null +++ b/.bazelrc
@@ -0,0 +1,86 @@ +# Copyright 2026 The Abseil Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 --show_timestamps + +# Retry longer to avoid flakes when fetching external dependencies. +common --http_connector_attempts=10 +common --http_timeout_scaling=2.0 + +# Link GoogleTest against Abseil. +build --define=absl=1 + +test --keep_going +test --test_env=GTEST_INSTALL_FAILURE_SIGNAL_HANDLER=1 +test --test_env=TZDIR=absl/time/internal/cctz/testdata/zoneinfo +test --test_output=errors +test --test_tag_filters=-benchmark + +# Configure our Linux Docker toolchain setup. +common --extra_toolchains=@docker_toolchain//:all +build:linux --cxxopt=-std=c++17 --host_cxxopt=-std=c++17 +build:linux --copt=-fno-exceptions +build:linux --copt=-Werror +build:linux --copt=-DGTEST_REMOVE_LEGACY_TEST_CASEAPI_=1 +build:linux --features=external_include_paths +build:linux --per_file_copt=external/.*@-w +build:linux --host_per_file_copt=external/.*@-w + +build:gcc --config=linux --platforms=//ci:linux_gcc +build:gcc-exceptions --config=gcc --copt=-fexceptions +build:clang --config=linux --platforms=//ci:linux_clang --conlyopt=-Wno-unused-command-line-argument +build:clang-exceptions --config=clang --copt=-fexceptions +build:clang-arm --config=linux --platforms=//ci:linux_arm_clang --conlyopt=-Wno-unused-command-line-argument +build:libstdc++ --config=clang --features=libstdc++ --copt=-march=haswell + +# Sanitizer configurations. +build:ubsan --features=ubsan +build:ubsan --copt=-fsanitize=float-divide-by-zero,nullability,undefined +build:ubsan --copt=-fno-sanitize-recover=float-divide-by-zero,nullability,undefined +build:ubsan --copt=-fno-sanitize-blacklist +test:ubsan --test_env=UBSAN_OPTIONS=print_stacktrace=1 + +build:asan --config=clang --config=ubsan --features=asan +build:asan --copt=-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG +build:asan --linkopt=-fsanitize-link-c++-runtime +test:asan --test_tag_filters=-benchmark,-noasan + +build:tsan --config=clang --features=tsan +build:tsan --copt=-fno-sanitize-blacklist +build:tsan --build_tag_filters=-notsan +test:tsan --test_tag_filters=-benchmark,-notsan + +# Remote build configuration for CI. +build:remote-base --define=EXECUTOR=remote +build:remote-base --remote_timeout=1200 --bes_timeout=600s +build:remote-base --jobs=50 +build:remote-base --remote_download_minimal +# Retry up for up to 15 minutes under heavy load (exponential backoff). +build:remote-base --remote_retries=20 --remote_retry_max_delay=60s + +build:remote --config=remote-base +build:remote --extra_execution_platforms=//ci:rbe +build:remote --host_platform=//ci:rbe +build:remote --platforms=//ci:rbe +build:remote --remote_cache=grpcs://us-east1-remotebuildexecution.googleapis.com +build:remote --remote_executor=grpcs://us-east1-remotebuildexecution.googleapis.com +build:remote --remote_instance_name=projects/google.com:absl-177019/instances/absl-ci-east + +build:remote-arm --config=remote-base +build:remote-arm --extra_execution_platforms=//ci:rbe-arm +build:remote-arm --host_platform=//ci:rbe-arm +build:remote-arm --platforms=//ci:rbe-arm +build:remote-arm --remote_cache=grpcs://us-central1-remotebuildexecution.googleapis.com +build:remote-arm --remote_executor=grpcs://us-central1-remotebuildexecution.googleapis.com +build:remote-arm --remote_instance_name=projects/google.com:absl-177019/instances/gloop-ci-central
diff --git a/.github/workflows/bazel.yml b/.github/workflows/bazel.yml new file mode 100644 index 0000000..b54e910 --- /dev/null +++ b/.github/workflows/bazel.yml
@@ -0,0 +1,113 @@ +# Copyright 2026 The Abseil Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +name: Bazel Tests + +on: + workflow_call: + inputs: + os: + description: "GitHub Actions runner OS (e.g. ubuntu-24.04, macos-latest, windows-2022)" + required: true + type: string + toolchain: + description: "Bazel toolchain config (--config=<toolchain>)" + required: true + type: string + remote_config: + description: "Bazel remote config (--config=<remote_config>)" + required: false + default: "remote" + type: string + container_image: + description: "Docker container image to run in (empty string for host execution)" + required: false + default: "" + type: string + alternate_options: + description: "Whether to use ci/absl_alternate_options.h" + required: false + default: false + type: boolean + stds: + description: "JSON array of C++ standards to test" + required: false + default: '["c++17", "c++20", "c++23"]' + type: string + compilation_modes: + description: "JSON array of Bazel compilation modes to test" + required: false + default: '["opt", "fastbuild"]' + type: string + +permissions: + contents: read + +jobs: + test: + runs-on: ${{ inputs.os }} + name: (${{ matrix.std }}, ${{ matrix.compilation_mode }}) + container: + image: ${{ inputs.container_image }} + options: --cap-add=SYS_PTRACE + credentials: + # TODO(b/573304451): Switch to Workload Identity Federation to avoid the need for secrets. + username: _json_key + password: ${{ secrets.GAR_SERVICE_ACCOUNT }} + strategy: + fail-fast: false # Don't cancel all jobs if one fails. + matrix: + std: ${{ fromJSON(inputs.stds) }} + compilation_mode: ${{ fromJSON(inputs.compilation_modes) }} + defaults: + run: + shell: bash + env: + BAZEL_LLVM: C:\Program Files\LLVM + MSYS2_ARG_CONV_EXCL: "*" + USE_BAZEL_VERSION: 9.x + steps: + - name: Checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # 7.0.0 + + - name: Set up alternate options + if: ${{ inputs.alternate_options }} + run: cp ci/absl_alternate_options.h absl/base/options.h + + - name: Setup continuous run + if: ${{ github.event_name == 'schedule' }} + # Force builds and tests to run on scheduled runs. + run: echo "EXTRA_BAZEL_ARGS=--cache_test_results=no --action_env=FORCE_REBUILD=1" >> $GITHUB_ENV + + - name: Set up Bazel and caches + uses: bazel-contrib/setup-bazel@c5acdfb288317d0b5c0bbd7a396a3dc868bb0f86 # 0.19.0 + with: + cache-save: true + bazelisk-cache: true + external-cache: true + repository-cache: true + google-credentials: ${{ secrets.GAR_SERVICE_ACCOUNT }} + bazelrc: common --config=${{ inputs.remote_config }} + + - name: Fetch all external dependencies + run: bazel fetch //... + + - name: Run tests + run: > + bazel test ... + --config=${{ inputs.toolchain }} + --cxxopt=${{ startsWith(inputs.os, 'windows') && '/std:' || '-std=' }}${{ matrix.std }} + --host_cxxopt=${{ startsWith(inputs.os, 'windows') && '/std:' || '-std=' }}${{ matrix.std }} + --compilation_mode=${{ matrix.compilation_mode }} + ${{ env.EXTRA_BAZEL_ARGS }}
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..768f1ca --- /dev/null +++ b/.github/workflows/ci.yml
@@ -0,0 +1,120 @@ +# Copyright 2026 The Abseil Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +name: Tests + +on: + # continuous runs + schedule: + # Run every hour + - cron: "0 * * * *" + + # postsubmit runs + push: + branches: + - master + - lts_* + + # presubmit runs + pull_request: + branches: + - master + - lts_* + + # manual runs + workflow_dispatch: + +permissions: + contents: read + +# Avoid running twice at the same time over the same changes. +concurrency: + group: ${{ github.event_name }}-${{ github.workflow }}-${{ github.head_ref || github.ref }} + cancel-in-progress: ${{ contains(fromJSON('["pull_request", "workflow_dispatch", "schedule"]'), github.event_name) }} + +jobs: + # Mirrors the 10 Linux "latest" Bazel Kokoro jobs (parallelized to 1 job per + # Bazel invocation). Not yet covered here: + # - "Linux GCC/libstdc++ (floor) Bazel" (requires a floor GCC Docker image) + # - CMake, macOS, and Windows Kokoro jobs + linux: + name: Linux ${{ matrix.config.name }} Bazel + strategy: + fail-fast: false # Don't cancel all jobs if one fails. + matrix: + config: + # linux_clang-latest_libcxx_bazel.cfg + - name: Clang/libc++ + toolchain: clang + # linux_clang-latest_libcxx_exceptions_bazel.cfg (fastbuild only) + - name: Clang/libc++ -fexceptions + toolchain: clang-exceptions + compilation_modes: '["fastbuild"]' + # linux_clang-latest_libcxx_alternate_options_bazel.cfg + - name: Clang/libc++ alternate options + toolchain: clang + alternate_options: true + # linux_clang-latest_libcxx_asan_bazel.cfg (fastbuild only) + - name: Clang/libc++ ASAN + toolchain: asan + compilation_modes: '["fastbuild"]' + # linux_clang-latest_libcxx_tsan_bazel.cfg (fastbuild only) + - name: Clang/libc++ TSAN + toolchain: tsan + compilation_modes: '["fastbuild"]' + # linux_clang-latest_libstdcxx_bazel.cfg + - name: Clang/libstdc++ + toolchain: libstdc++ + # linux_gcc-latest_libstdcxx_bazel.cfg + - name: GCC/libstdc++ + toolchain: gcc + # linux_gcc-latest_libstdcxx_exceptions_bazel.cfg (fastbuild only) + - name: GCC/libstdc++ -fexceptions + toolchain: gcc-exceptions + compilation_modes: '["fastbuild"]' + # linux_gcc-latest_libstdcxx_alternate_options_bazel.cfg + - name: GCC/libstdc++ alternate options + toolchain: gcc + alternate_options: true + # linux_arm_clang-latest_libcxx_bazel.cfg (c++17 and c++20 only) + - name: ARM64 Clang/libc++ + toolchain: clang-arm + arch_suffix: -arm + stds: '["c++17", "c++20"]' + uses: ./.github/workflows/bazel.yml + secrets: inherit + with: + os: ubuntu-24.04${{ matrix.config.arch_suffix }} + toolchain: ${{ matrix.config.toolchain }} + remote_config: remote${{ matrix.config.arch_suffix }} + container_image: gcr.io/google.com/absl-177019/gloop-linux-toolchain-latest@sha256:a820c79d3f356a64db121a4ee16c4408df18f9d2c8bf6c6abfae21666ac003ba + alternate_options: ${{ matrix.config.alternate_options || false }} + stds: ${{ matrix.config.stds || '["c++17", "c++20", "c++23"]' }} + compilation_modes: ${{ matrix.config.compilation_modes || '["opt", "fastbuild"]' }} + + # Provide a single predictably-named job for copybara to look for + all-blocking-tests: + name: All Blocking Tests + needs: [linux] + runs-on: ubuntu-latest + steps: + - name: Check for cancellations + if: ${{ contains(join(needs.*.result, ' '), 'cancelled') }} + run: echo "One or more tests were cancelled. If this was a presubmit job cancelled by a submit job, you can ignore this." && exit 1 + - name: Check for failures + if: ${{ contains(join(needs.*.result, ' '), 'failure') }} + run: echo "One or more tests failed. Please check the logs above and fix the issues." && exit 1 + # This workflow must run even if one or more of the dependent workflows + # failed. + if: always()
diff --git a/MODULE.bazel b/MODULE.bazel index 00ab4c3..d4096d2 100644 --- a/MODULE.bazel +++ b/MODULE.bazel
@@ -50,3 +50,9 @@ version = "20260708.rc1", repo_name = "do_not_use_for_gloop_visibility_only", ) + +bazel_dep(name = "docker_toolchain", version = "0.1", dev_dependency = True) +local_path_override( + module_name = "docker_toolchain", + path = "ci/docker_toolchain", +)
diff --git a/absl/base/attributes.h b/absl/base/attributes.h index 1e4c7c3..601be76 100644 --- a/absl/base/attributes.h +++ b/absl/base/attributes.h
@@ -85,16 +85,16 @@ // Note: As the GCC manual states, "[s]ince non-static C++ methods // have an implicit 'this' argument, the arguments of such methods // should be counted from two, not one." -#if ABSL_HAVE_ATTRIBUTE(format) || (defined(__GNUC__) && !defined(__clang__)) -#define ABSL_PRINTF_ATTRIBUTE(string_index, first_to_check) \ - __attribute__((__format__(__printf__, string_index, first_to_check))) #if ABSL_HAVE_CPP_ATTRIBUTE(gnu::format) +#define ABSL_PRINTF_ATTRIBUTE(string_index, first_to_check) \ + [[gnu::format(printf, string_index, first_to_check)]] #define ABSL_SCANF_ATTRIBUTE(string_index, first_to_check) \ [[gnu::format(scanf, string_index, first_to_check)]] -#else +#elif ABSL_HAVE_ATTRIBUTE(format) || (defined(__GNUC__) && !defined(__clang__)) +#define ABSL_PRINTF_ATTRIBUTE(string_index, first_to_check) \ + __attribute__((__format__(__printf__, string_index, first_to_check))) #define ABSL_SCANF_ATTRIBUTE(string_index, first_to_check) \ __attribute__((__format__(__scanf__, string_index, first_to_check))) -#endif #else #define ABSL_PRINTF_ATTRIBUTE(string_index, first_to_check) #define ABSL_SCANF_ATTRIBUTE(string_index, first_to_check) @@ -1117,4 +1117,52 @@ #define ABSL_ATTRIBUTE_WARN_UNUSED #endif +// ABSL_UNSAFE_BUFFERS() +// +// Wraps code that performs unsafe buffer accesses or pointer arithmetic. Used +// to suppress warnings when `-Wunsafe-buffer-usage` is enabled. +// +// **WARNING**: This macro should be used when unavoidable. Applying it means +// the compiler is unable to prove that your code avoids out-of-bounds reads or +// writes, forcing reliance on manual review. Over time, code evolves and +// undocumented assumptions can silently break, introducing vulnerabilities. +// Always prioritize refactoring to safer alternatives (e.g. std::array, +// absl::Span, or bounds-checked containers) over using this suppression. +// +// When using this macro is unavoidable, restrict its scope to the smallest +// possible expression or statement. You must also add a `// SAFETY: ...` +// comment explaining why the operation is actually safe. +// Justifications include: +// * Explicit bounds checks (e.g., `CHECK()`) immediately preceding the access. +// * Invariants strictly enforced by surrounding types or functions. +// * Preconditions required of the caller (though this is heavily discouraged). +// +// Example (expression): +// +// // SAFETY: `buf` is NUL-terminated so `strdup` won't read past the end. +// return ABSL_UNSAFE_BUFFERS(strdup(buf)); +// +// Example (single-line statement): +// +// // SAFETY: `offset` is bounds-checked above to be within `buffer`. +// ABSL_UNSAFE_BUFFERS(buffer += offset;) +// +// Example (multi-line statement): +// +// // SAFETY: `offset` is bounds-checked above to be within `buffer`. +// ABSL_UNSAFE_BUFFERS( +// char* p = buffer + offset; +// p[0] = 'a'; +// ) +// +// Note: When wrapping a multi-line statement, placing a semicolon after the +// closing parenthesis may produce an extra semicolon warning. +#if defined(__clang__) +#define ABSL_UNSAFE_BUFFERS(...) \ + _Pragma("clang unsafe_buffer_usage begin") \ + __VA_ARGS__ _Pragma("clang unsafe_buffer_usage end") +#else +#define ABSL_UNSAFE_BUFFERS(...) __VA_ARGS__ +#endif + #endif // ABSL_BASE_ATTRIBUTES_H_
diff --git a/absl/base/attributes_test.cc b/absl/base/attributes_test.cc index 058d1be..e87b66e 100644 --- a/absl/base/attributes_test.cc +++ b/absl/base/attributes_test.cc
@@ -54,4 +54,22 @@ Agg good1 [[maybe_unused]] = {ImplicitlyConstructible("hello")}; } +TEST(Attributes, UnsafeBuffers) { + int buffer[10] = {0}; + int offset = 1; + + // Expression example + int* p1 = ABSL_UNSAFE_BUFFERS(buffer + offset); + EXPECT_EQ(p1, &buffer[1]); + + // Single-line statement example + int* p2 = buffer; + ABSL_UNSAFE_BUFFERS(p2 += offset;) + EXPECT_EQ(p2, &buffer[1]); + + // Multi-line statement example + ABSL_UNSAFE_BUFFERS(int* p3 = buffer + offset; p3[0] = 1;) + EXPECT_EQ(buffer[1], 1); +} + } // namespace
diff --git a/absl/crc/internal/crc_x86_arm_combined.cc b/absl/crc/internal/crc_x86_arm_combined.cc index 95221e4..d6f42d9 100644 --- a/absl/crc/internal/crc_x86_arm_combined.cc +++ b/absl/crc/internal/crc_x86_arm_combined.cc
@@ -1084,6 +1084,7 @@ 3, 1, PclmulStreamType::PCLMUL>(); case CpuType::kAmdMilan: case CpuType::kAmdGenoa: + case CpuType::kAmdRyzenV3000: case CpuType::kAmdSiena: case CpuType::kAmdTurin: case CpuType::kAmdVenice:
diff --git a/absl/functional/BUILD.bazel b/absl/functional/BUILD.bazel index d1b46c7..0cad083 100644 --- a/absl/functional/BUILD.bazel +++ b/absl/functional/BUILD.bazel
@@ -124,6 +124,7 @@ linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ ":bind_front", + "//absl/base:config", "//absl/memory", "@googletest//:gtest", "@googletest//:gtest_main",
diff --git a/absl/functional/CMakeLists.txt b/absl/functional/CMakeLists.txt index 1ddfa29..231c9dd 100644 --- a/absl/functional/CMakeLists.txt +++ b/absl/functional/CMakeLists.txt
@@ -105,6 +105,7 @@ ${ABSL_DEFAULT_COPTS} DEPS absl::bind_front + absl::config absl::memory GTest::gmock_main )
diff --git a/absl/functional/bind_front.h b/absl/functional/bind_front.h index 45ca4fc..06b6e52 100644 --- a/absl/functional/bind_front.h +++ b/absl/functional/bind_front.h
@@ -21,10 +21,11 @@ // it does not require the use of argument placeholders. The simpler syntax of // `absl::bind_front()` allows you to avoid known misuses with `std::bind()`. // -// `absl::bind_front()` is meant as a drop-in replacement for C++20's upcoming -// `std::bind_front()`, which similarly resolves these issues with -// `std::bind()`. Both `bind_front()` alternatives, unlike `std::bind()`, allow -// partial function application. (See +// `absl::bind_front()` is meant as a drop-in replacement for C++20's +// `std::bind_front(func, args...)` and C++26's +// `std::bind_front<ConstFn>(args...)`, which similarly resolve these issues +// with `std::bind()`. Both `bind_front()` alternatives, unlike `std::bind()`, +// allow partial function application. (See // https://en.wikipedia.org/wiki/Partial_application). #ifndef ABSL_FUNCTIONAL_BIND_FRONT_H_ @@ -39,17 +40,40 @@ #include <functional> // For std::bind_front. #endif // defined(__cpp_lib_bind_front) && __cpp_lib_bind_front >= 201907L +#include <type_traits> #include <utility> #include "absl/functional/internal/front_binder.h" #include "absl/utility/utility.h" +// ABSL_INTERNAL_HAVE_STD_NTTP_BIND_FRONT +// +// Defined if the standard library implements C++26 `std::bind_front<ConstFn>`, +// which `absl::bind_front<ConstFn>` then aliases. +// +// libc++ 17 through 22 define `__cpp_lib_bind_front` as 202306L in C++26 mode, +// but `std::bind_front<ConstFn>` was first implemented in libc++ 23: +// https://github.com/llvm/llvm-project/pull/165096#issuecomment-4487517016 +#ifdef ABSL_INTERNAL_HAVE_STD_NTTP_BIND_FRONT +#error ABSL_INTERNAL_HAVE_STD_NTTP_BIND_FRONT cannot be directly set +#elif defined(__cpp_lib_bind_front) && __cpp_lib_bind_front >= 202306L && \ + (!defined(_LIBCPP_VERSION) || _LIBCPP_VERSION >= 230000) +#define ABSL_INTERNAL_HAVE_STD_NTTP_BIND_FRONT 1 +#endif + namespace absl { ABSL_NAMESPACE_BEGIN // bind_front() // // Binds the first N arguments of an invocable object and stores them by value. +// The target invocable can be passed either as a runtime argument +// `absl::bind_front(func, args...)` (C++20 `std::bind_front`) or as a +// compile-time non-type template parameter `absl::bind_front<ConstFn>(args...)` +// (C++26 `std::bind_front`). +// +// Passing `ConstFn` as a non-type template parameter avoids storing a function +// pointer or pointer-to-member at runtime. // // Like `std::bind()`, `absl::bind_front()` is implicitly convertible to // `std::function`. In particular, it may be used as a simpler replacement for @@ -57,7 +81,8 @@ // 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. In C++20, `absl::bind_front` is replaced by +// mis-uses as errors. In C++20 (for runtime callables) and C++26 (for +// non-type template parameter callables), `absl::bind_front` aliases // `std::bind_front`. // // absl::bind_front(a...) can be seen as storing the results of @@ -70,6 +95,8 @@ // assert(absl::bind_front(Minus)(3, 2) == 3 - 2); // assert(absl::bind_front(Minus, 3)(2) == 3 - 2); // assert(absl::bind_front(Minus, 3, 2)() == 3 - 2); +// assert(absl::bind_front<&Minus>()(3, 2) == 3 - 2); +// assert(absl::bind_front<&Minus>(3)(2) == 3 - 2); // // Example: Binding a member function. // @@ -80,8 +107,10 @@ // Math math; // // assert(absl::bind_front(&Math::Double)(&math, 3) == 2 * 3); +// assert(absl::bind_front<&Math::Double>()(&math, 3) == 2 * 3); // // Stores a pointer to math inside the functor. // assert(absl::bind_front(&Math::Double, &math)(3) == 2 * 3); +// assert(absl::bind_front<&Math::Double>(&math)(3) == 2 * 3); // // Stores a copy of math inside the functor. // assert(absl::bind_front(&Math::Double, math)(3) == 2 * 3); // // Stores std::unique_ptr<Math> inside the functor. @@ -193,7 +222,23 @@ } #endif // defined(__cpp_lib_bind_front) && __cpp_lib_bind_front >= 201907L +#ifndef ABSL_INTERNAL_HAVE_STD_NTTP_BIND_FRONT +template <auto ConstFn, class... BoundArgs> +constexpr auto bind_front(BoundArgs&&... args) { + using F = decltype(ConstFn); + if constexpr (std::is_pointer_v<F> || std::is_member_pointer_v<F>) { + static_assert(ConstFn != nullptr, + "bind_front: ConstFn cannot be equal to nullptr"); + } + // The runtime `bind_front` checks the requirements on `BoundArgs`. + return absl::bind_front(functional_internal::ConstFnInvoker<ConstFn>{}, + std::forward<BoundArgs>(args)...); +} +#endif // ABSL_INTERNAL_HAVE_STD_NTTP_BIND_FRONT + ABSL_NAMESPACE_END } // namespace absl +#undef ABSL_INTERNAL_HAVE_STD_NTTP_BIND_FRONT + #endif // ABSL_FUNCTIONAL_BIND_FRONT_H_
diff --git a/absl/functional/bind_front_test.cc b/absl/functional/bind_front_test.cc index 6e87d43..300466c 100644 --- a/absl/functional/bind_front_test.cc +++ b/absl/functional/bind_front_test.cc
@@ -19,15 +19,17 @@ #include <functional> #include <memory> #include <string> +#include <type_traits> #include <utility> #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "absl/base/config.h" #include "absl/memory/memory.h" namespace { -char CharAt(const char* s, size_t index) { return s[index]; } +constexpr char CharAt(const char* s, size_t index) { return s[index]; } TEST(BindTest, Basics) { EXPECT_EQ('C', absl::bind_front(CharAt)("ABC", 2)); @@ -200,7 +202,7 @@ EXPECT_EQ(42, std::move(f)()); } -int Plus(int a, int b) { return a + b; } +constexpr int Plus(int a, int b) { return a + b; } TEST(BindTest, ConstExpr) { constexpr auto f = absl::bind_front(CharAt); @@ -223,4 +225,224 @@ absl::bind_front(ManglingCall{}, 1, 3.3)("A"); } +struct Math { + constexpr int Add(int a, int b) const noexcept { return base + a + b; } + int base = 10; +}; + +TEST(BindConstFnTest, Basics) { + EXPECT_EQ('C', absl::bind_front<&CharAt>()("ABC", 2)); + EXPECT_EQ('C', absl::bind_front<&CharAt>("ABC")(2)); + EXPECT_EQ('C', absl::bind_front<&CharAt>("ABC", 2)()); +} + +TEST(BindConstFnTest, MemberPointers) { + Math m{10}; + auto bound_ptr = absl::bind_front<&Math::Add>(&m); + EXPECT_EQ(15, bound_ptr(2, 3)); + + auto bound_ptr_and_arg = absl::bind_front<&Math::Add>(&m, 2); + EXPECT_EQ(15, bound_ptr_and_arg(3)); + + auto bound_zero_args = absl::bind_front<&Math::Add>(); + EXPECT_EQ(15, bound_zero_args(&m, 2, 3)); + EXPECT_EQ(15, bound_zero_args(m, 2, 3)); +} + +TEST(BindConstFnTest, StoreByCopyRefAndPointer) { + Struct s = {"hello"}; + auto by_copy = absl::bind_front<&Struct::value>(s); + EXPECT_EQ("hello", by_copy()); + EXPECT_NE(&s.value, &by_copy()); + + NonCopyable nc("hello"); + auto by_ref = absl::bind_front<&GetNonCopyableValue>(std::ref(nc)); + EXPECT_EQ("hello", by_ref()); + EXPECT_EQ(&nc.value, &by_ref()); + + auto by_cref = absl::bind_front<&GetNonCopyableValue>(std::cref(nc)); + EXPECT_EQ("hello", by_cref()); + EXPECT_EQ(&nc.value, &by_cref()); + + auto by_ptr = absl::bind_front<&NonCopyable::value>(&nc); + EXPECT_EQ("hello", by_ptr()); + EXPECT_EQ(&nc.value, &by_ptr()); + nc.value = "goodbye"; + EXPECT_EQ("goodbye", by_ref()); + EXPECT_EQ("goodbye", by_cref()); + EXPECT_EQ("goodbye", by_ptr()); +} + +TEST(BindConstFnTest, MoveOnlyArgsAndResult) { + EXPECT_EQ(42, absl::bind_front<&Sink>()(std::make_unique<int>(42))); + EXPECT_EQ(42, absl::bind_front<&Sink>(std::make_unique<int>(42))()); + EXPECT_THAT(absl::bind_front<&Factory>()(42), ::testing::Pointee(42)); + EXPECT_THAT(absl::bind_front<&Factory>(42)(), ::testing::Pointee(42)); + + FalseCopyable<std::unique_ptr<int>> x; + x.m = std::make_unique<int>(42); + auto f = absl::bind_front<&GetMember>(std::move(x)); + EXPECT_EQ(42, std::move(f)()); +} + +TEST(BindConstFnTest, ConstExpr) { + constexpr auto f = absl::bind_front<&CharAt>(); + EXPECT_EQ(f("ABC", 1), 'B'); + + constexpr auto plus5 = absl::bind_front<&Plus>(5); + EXPECT_EQ(plus5(1), 6); + + constexpr auto plus5_1 = absl::bind_front<&Plus>(5, 1); + EXPECT_EQ(plus5_1(), 6); + + constexpr Math m{10}; + constexpr auto add_m = absl::bind_front<&Math::Add>(m, 2); + EXPECT_EQ(add_m(3), 15); + + // Invoking the results is `constexpr` only where the runtime `bind_front` + // that produces them is `std::bind_front` (C++20). The pre-C++20 fallback's + // call operators and `std::invoke` are not `constexpr`. +#if defined(__cpp_lib_bind_front) && __cpp_lib_bind_front >= 201907L + static_assert(f("ABC", 1) == 'B'); + static_assert(plus5(1) == 6); + static_assert(plus5_1() == 6); + static_assert(add_m(3) == 15); +#endif // __cpp_lib_bind_front >= 201907L +} + +// The results come from the runtime `bind_front`, which propagates `noexcept` +// where it is `std::bind_front` (C++20). +#if defined(__cpp_lib_bind_front) && __cpp_lib_bind_front >= 201907L +int NoexceptPlus(int a, int b) noexcept { return a + b; } +int PotentiallyThrowingPlus(int a, int b) { return a + b; } + +TEST(BindConstFnTest, NoexceptPropagation) { + auto noexcept_0 = absl::bind_front<&NoexceptPlus>(); + static_assert(noexcept(noexcept_0(1, 2))); + + auto throwing_0 = absl::bind_front<&PotentiallyThrowingPlus>(); + static_assert(!noexcept(throwing_0(1, 2))); + + Math m{10}; + auto math_0 = absl::bind_front<&Math::Add>(); + static_assert(noexcept(math_0(m, 1, 2))); +} +#endif // __cpp_lib_bind_front >= 201907L + +// Invalid calls must be substitution failures rather than hard errors, so that +// `std::is_invocable` works on the result. +TEST(BindConstFnTest, SfinaeFriendly) { + using PlusBinder = decltype(absl::bind_front<&Plus>()); + static_assert(std::is_invocable_v<PlusBinder, int, int>); + static_assert(!std::is_invocable_v<PlusBinder, std::string, int>); + static_assert(!std::is_invocable_v<PlusBinder, int>); + + using Plus1Binder = decltype(absl::bind_front<&Plus>(1)); + static_assert(std::is_invocable_v<Plus1Binder, int>); + static_assert(!std::is_invocable_v<Plus1Binder, std::string>); +} + +// These tests pass class-type objects as template arguments (P0732R2, +// P1907R1), and `StatelessLambdaNTTP` puts a lambda in a template argument +// (P0315R4, which has no feature-test macro). +// +// We check the language version rather than +// `__cpp_nontype_template_args >= 201911L` because Clang (through at least +// 24) still defines it as 201411L: its P1907R1 support is incomplete, though +// everything used here works. Checking the feature macro would silently skip +// these tests on Clang. If a toolchain claims C++20 but can't compile these +// tests, exclude it here. +#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 202002L +TEST(BindConstFnTest, StatelessLambdaNTTP) { + constexpr auto f = absl::bind_front<[](int x, int y) { return x + y; }>(); + EXPECT_EQ(f(2, 3), 5); + + constexpr auto f2 = absl::bind_front<[](int x, int y) { return x + y; }>(2); + EXPECT_EQ(f2(3), 5); +} + +struct Adder { + constexpr int operator()(int x) const& { return base + x; } + // `ConstFn` names a template parameter object, which is a `const` lvalue + // ([temp.param]), so it must never be invoked as an rvalue. + int operator()(int) const&& = delete; + + int base; +}; + +TEST(BindConstFnTest, StatefulObjectNTTP) { + constexpr auto add5 = absl::bind_front<Adder{5}>(); + EXPECT_EQ(add5(1), 6); + + constexpr auto add5_1 = absl::bind_front<Adder{5}>(1); + EXPECT_EQ(add5_1(), 6); +} + +struct DescribeCall { + template <class... Args> + std::string operator()(Args&&...) & { + return "&"; + } + template <class... Args> + std::string operator()(Args&&...) const& { + return "const&"; + } + template <class... Args> + std::string operator()(Args&&...) && { + return "&&"; + } + template <class... Args> + std::string operator()(Args&&...) const&& { + return "const&&"; + } +}; + +// Since `ConstFn` is a `const` lvalue, it is invoked through its `const&` +// overload whatever the value category of the result. Both zero and nonzero +// bound arguments are tested, since the standard library's +// `std::bind_front<ConstFn>` may implement them differently. +TEST(BindConstFnTest, InvokesConstFnAsConstLvalue) { + auto f0 = absl::bind_front<DescribeCall{}>(); + const auto& cf0 = f0; + EXPECT_EQ("const&", f0()); + EXPECT_EQ("const&", cf0()); + EXPECT_EQ("const&", std::move(f0)()); + EXPECT_EQ("const&", std::move(cf0)()); + + auto f1 = absl::bind_front<DescribeCall{}>(1); + const auto& cf1 = f1; + EXPECT_EQ("const&", f1()); + EXPECT_EQ("const&", cf1()); + EXPECT_EQ("const&", std::move(f1)()); + EXPECT_EQ("const&", std::move(cf1)()); +} + +struct DeletedConstLvalueCall { + template <class... Args> + void operator()(Args&&...) &; + template <class... Args> + void operator()(Args&&...) const& = delete; + template <class... Args> + void operator()(Args&&...) &&; + template <class... Args> + void operator()(Args&&...) const&&; +}; + +// If `ConstFn`'s `const&` overload is deleted, the result isn't invocable, +// rather than falling back to another overload. +TEST(BindConstFnTest, NotInvocableIfConstLvalueCallDeleted) { + using F0 = decltype(absl::bind_front<DeletedConstLvalueCall{}>()); + static_assert(!std::is_invocable_v<F0&>); + static_assert(!std::is_invocable_v<const F0&>); + static_assert(!std::is_invocable_v<F0>); + static_assert(!std::is_invocable_v<const F0>); + + using F1 = decltype(absl::bind_front<DeletedConstLvalueCall{}>(1)); + static_assert(!std::is_invocable_v<F1&>); + static_assert(!std::is_invocable_v<const F1&>); + static_assert(!std::is_invocable_v<F1>); + static_assert(!std::is_invocable_v<const F1>); +} +#endif // ABSL_INTERNAL_CPLUSPLUS_LANG >= 202002L + } // namespace
diff --git a/absl/functional/internal/front_binder.h b/absl/functional/internal/front_binder.h index fcada9c..33d4fb4 100644 --- a/absl/functional/internal/front_binder.h +++ b/absl/functional/internal/front_binder.h
@@ -18,6 +18,7 @@ #define ABSL_FUNCTIONAL_INTERNAL_FRONT_BINDER_H_ #include <cstddef> +#include <functional> #include <type_traits> #include <utility> @@ -87,6 +88,34 @@ template <class F, class... BoundArgs> using bind_front_t = FrontBinder<std::decay_t<F>, std::decay_t<BoundArgs>...>; +// Stateless invoker for the compile-time constant target `ConstFn` of +// `absl::bind_front<ConstFn>(args...)` (C++26 [func.bind.front]). +// +// `operator()` has a trailing `decltype(std::invoke(ConstFn, ...))` return type +// rather than `decltype(auto)` so that an invalid call is a substitution +// failure instead of a hard error. Deducing a `decltype(auto)` return type +// instantiates the body, so merely checking whether a call is valid would fail +// to compile. That would break `std::is_invocable` and overload resolution, +// including among the call operators of a `bind_front` result wrapping the +// invoker: e.g. calling such a result as an rvalue fails if it holds a +// move-only bound argument, because its `const&` overload is also considered. +// +// The return type is `decltype(std::invoke(ConstFn, ...))` rather than +// `std::invoke_result_t<decltype(ConstFn), ...>` because for C++20 class-type +// non-type template parameters, `decltype(ConstFn)` is the unqualified type `F` +// (which `std::invoke_result_t` treats as an rvalue `F&&`), whereas the +// template parameter expression `ConstFn` is a `const F` lvalue as specified by +// [func.bind.front]. +template <auto ConstFn> +struct ConstFnInvoker { + template <class... FreeArgs> + constexpr auto operator()(FreeArgs&&... free_args) const noexcept( + noexcept(std::invoke(ConstFn, std::forward<FreeArgs>(free_args)...))) + -> decltype(std::invoke(ConstFn, std::forward<FreeArgs>(free_args)...)) { + return std::invoke(ConstFn, std::forward<FreeArgs>(free_args)...); + } +}; + } // namespace functional_internal ABSL_NAMESPACE_END } // namespace absl
diff --git a/ci/BUILD.bazel b/ci/BUILD.bazel new file mode 100644 index 0000000..607a356 --- /dev/null +++ b/ci/BUILD.bazel
@@ -0,0 +1,70 @@ +# Copyright 2026 The Abseil Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +package(default_visibility = ["//visibility:private"]) + +platform( + name = "rbe", + constraint_values = [ + "@platforms//os:linux", + "@platforms//cpu:x86_64", + ], + exec_properties = { + "OSArch": "amd64", + "cache-silo-key": "n2-highcpu-2-2026-10-14", + "container-image": "docker://gcr.io/google.com/absl-177019/gloop-linux-latest@sha256:e199623ad0b61b30d240e8bbddf473f65cb1a11da09462001d83b5066dbd9e98", + "dockerRunAsRoot": "true", + }, +) + +platform( + name = "rbe-arm", + constraint_values = [ + "@platforms//os:linux", + "@platforms//cpu:aarch64", + ], + exec_properties = { + "OSArch": "arm64", + "cache-silo-key": "t2a-standard-4-2026-10-14", + "container-image": "docker://gcr.io/google.com/absl-177019/gloop-linux-latest@sha256:e199623ad0b61b30d240e8bbddf473f65cb1a11da09462001d83b5066dbd9e98", + "dockerRunAsRoot": "true", + }, +) + +platform( + name = "linux_gcc", + constraint_values = [ + "@platforms//os:linux", + "@platforms//cpu:x86_64", + "@bazel_tools//tools/cpp:gcc", + ], +) + +platform( + name = "linux_clang", + constraint_values = [ + "@platforms//os:linux", + "@platforms//cpu:x86_64", + "@bazel_tools//tools/cpp:clang", + ], +) + +platform( + name = "linux_arm_clang", + constraint_values = [ + "@platforms//os:linux", + "@platforms//cpu:aarch64", + "@bazel_tools//tools/cpp:clang", + ], +)
diff --git a/ci/docker_toolchain/BUILD.bazel b/ci/docker_toolchain/BUILD.bazel new file mode 100644 index 0000000..ae482b0 --- /dev/null +++ b/ci/docker_toolchain/BUILD.bazel
@@ -0,0 +1,21 @@ +# Copyright 2026 The Abseil Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Standalone fallback toolchain module when building outside the Docker container. +# Inside the container, this module is overridden via /etc/bazel.bazelrc to point to an actual +# toolchain. + +package(default_visibility = ["//visibility:public"]) + +filegroup(name = "empty")
diff --git a/ci/docker_toolchain/MODULE.bazel b/ci/docker_toolchain/MODULE.bazel new file mode 100644 index 0000000..10d8650 --- /dev/null +++ b/ci/docker_toolchain/MODULE.bazel
@@ -0,0 +1,18 @@ +# Copyright 2026 The Abseil Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module( + name = "docker_toolchain", + version = "0.1", +)