diff --git a/.bazelrc b/.bazelrc index 71c4463..f1cc28c 100644 --- a/.bazelrc +++ b/.bazelrc @@ -1,19 +1,58 @@ +# AddressSanitizer: bazel test //phaser/... --config=asan +# Do not pass -fsanitize to host/exec tools (protoc, plugins); that breaks codegen. +# On Apple Silicon, also pass --config=apple_silicon (it adds -arch arm64, which +# is macOS-only and must not be applied on Linux). build:asan --strip=never -build:asan --copt -fsanitize=address -build:asan --copt -DADDRESS_SANITIZER -build:asan --copt -g -build:asan --copt -fno-omit-frame-pointer -build:asan --linkopt -fsanitize=address +build:asan -c dbg +build:asan --copt=-fsanitize=address +build:asan --copt=-fno-omit-frame-pointer +build:asan --linkopt=-fsanitize=address + +test:asan --test_output=errors +test:asan --test_env=ASAN_OPTIONS=abort_on_error=1:symbolize=1:fast_unwind_on_malloc=0 + +# Valgrind (memcheck): bazel test //phaser/... --config=valgrind +# Build with debug symbols (and no optimization) so valgrind reports useful +# stack traces. Do not combine with --config=asan; the two are incompatible. +build:valgrind --strip=never +build:valgrind -c dbg +build:valgrind --copt=-g +build:valgrind --copt=-fno-omit-frame-pointer + +test:valgrind --test_output=errors +# Valgrind is slow; give tests plenty of time. +test:valgrind --test_timeout=900 +# Only fail on definite leaks: protobuf/abseil keep global state that valgrind +# reports as "possibly lost"/"still reachable", which are benign false positives. +# The suppressions file silences protobuf resize-uninitialized reads surfaced +# via debug Hexdump calls. +test:valgrind --run_under='valgrind --leak-check=full --show-leak-kinds=definite --errors-for-leak-kinds=definite --error-exitcode=1 --num-callers=30 --suppressions=phaser/valgrind.supp' + +build --enable_platform_specific_config # For all builds, use C++17 build --cxxopt="-std=c++17" build --cxxopt='-Wno-sign-compare' build --host_cxxopt="-std=c++17" -# For Apple Silicon +# For Apple Silicon (required when Bazel itself is x86_64 under Rosetta). build:apple_silicon --cpu=darwin_arm64 --host_cpu=darwin_arm64 --host_cxxopt="-std=c++17" +build:apple_silicon --copt=-arch +build:apple_silicon --copt=arm64 +build:apple_silicon --linkopt=-arch +build:apple_silicon --linkopt=arm64 +build:apple_silicon --host_copt=-arch +build:apple_silicon --host_copt=arm64 +build:apple_silicon --host_linkopt=-arch +build:apple_silicon --host_linkopt=arm64 build:apple_silicon --features=oso_prefix_is_pwd +# std::filesystem requires macOS 10.15+ +build:macos --cxxopt=-mmacosx-version-min=10.15 +build:macos --linkopt=-mmacosx-version-min=10.15 +build:macos --host_cxxopt=-mmacosx-version-min=10.15 +build:macos --host_linkopt=-mmacosx-version-min=10.15 + # Common flags for Clang build:clang --action_env=BAZEL_COMPILER=clang build:clang --action_env=CC=clang --action_env=CXX=clang++ diff --git a/.bazelversion b/.bazelversion new file mode 100644 index 0000000..47da986 --- /dev/null +++ b/.bazelversion @@ -0,0 +1 @@ +9.1.0 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..9654afa --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,112 @@ +name: CI + +on: + push: + branches: [main, master] + pull_request: + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + # Build and run every test target except perf_test. + test: + name: test (${{ matrix.os }}) + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + + - name: Cache Bazel + uses: actions/cache@v4 + with: + path: ~/.cache/bazel-disk + key: bazel-disk-test-${{ matrix.os }}-${{ hashFiles('MODULE.bazel', 'MODULE.bazel.lock', '.bazelrc', '.bazelversion') }} + restore-keys: | + bazel-disk-test-${{ matrix.os }}- + + # Apple Silicon runners need the apple_silicon config (adds -arch arm64), + # which must not be applied on Linux. + - name: Select macOS config + if: runner.os == 'macOS' + run: echo "EXTRA_CONFIG=--config=apple_silicon" >> "$GITHUB_ENV" + + - name: Test (everything except perf_test) + run: | + bazel test --disk_cache="$HOME/.cache/bazel-disk" $EXTRA_CONFIG \ + -- //... -//phaser:perf_test + + # AddressSanitizer runs on macOS (Apple Silicon). + asan: + name: asan (macOS) + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + + - name: Cache Bazel + uses: actions/cache@v4 + with: + path: ~/.cache/bazel-disk + key: bazel-disk-asan-macos-${{ hashFiles('MODULE.bazel', 'MODULE.bazel.lock', '.bazelrc', '.bazelversion') }} + restore-keys: | + bazel-disk-asan-macos- + + - name: Test with ASan (everything except perf_test) + run: | + bazel test --disk_cache="$HOME/.cache/bazel-disk" \ + --config=asan --config=apple_silicon \ + -- //... -//phaser:perf_test + + # AddressSanitizer runs on Linux (no apple_silicon config). + asan-linux: + name: asan (Linux) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Cache Bazel + uses: actions/cache@v4 + with: + path: ~/.cache/bazel-disk + key: bazel-disk-asan-linux-${{ hashFiles('MODULE.bazel', 'MODULE.bazel.lock', '.bazelrc', '.bazelversion') }} + restore-keys: | + bazel-disk-asan-linux- + + - name: Test with ASan (everything except perf_test) + run: | + bazel test --disk_cache="$HOME/.cache/bazel-disk" \ + --config=asan \ + -- //... -//phaser:perf_test + + # Valgrind (memcheck) runs on Linux only. + valgrind: + name: valgrind (Linux) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install valgrind + run: | + sudo apt-get update + sudo apt-get install -y valgrind + + - name: Cache Bazel + uses: actions/cache@v4 + with: + path: ~/.cache/bazel-disk + key: bazel-disk-valgrind-linux-${{ hashFiles('MODULE.bazel', 'MODULE.bazel.lock', '.bazelrc', '.bazelversion') }} + restore-keys: | + bazel-disk-valgrind-linux- + + - name: Test under Valgrind (everything except perf_test) + run: | + bazel test --disk_cache="$HOME/.cache/bazel-disk" --config=valgrind \ + -- //... -//phaser:perf_test diff --git a/MODULE.bazel b/MODULE.bazel index 3477037..85a3c95 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -1,47 +1,35 @@ module( name = "phaser", + version = "1.1.0" ) -http_archive = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") -local_repository = use_repo_rule("@bazel_tools//tools/build_defs/repo:local.bzl", "local_repository") +bazel_dep(name = "bazel_skylib", version = "1.9.0") +bazel_dep(name = "platforms", version = "1.1.0") +bazel_dep(name = "abseil-cpp", version = "20250814.1", repo_name = "com_google_absl") +bazel_dep(name = "googletest", version = "1.17.0.bcr.2", repo_name = "com_google_googletest") -bazel_dep(name = "bazel_skylib", version = "1.7.1") -bazel_dep(name = "platforms", version = "0.0.10") -bazel_dep(name = "abseil-cpp", version = "20230802.0", repo_name = "com_google_absl") -bazel_dep(name = "googletest", version = "1.14.0", repo_name = "com_google_googletest") +# Aliased as "com_google_protobuf" to match implicit dependency within bazel_tools. +# See https://github.com/bazelbuild/bazel/issues/19973 +bazel_dep(name = "protobuf", version = "34.1", repo_name = "com_google_protobuf") -# Note, see https://github.com/bazelbuild/bazel/issues/19973 -# Protobuf must be aliased as "com_google_protobuf" to match implicit dependency within bazel_tools. -bazel_dep(name = "protobuf", version = "21.7", repo_name = "com_google_protobuf") +bazel_dep(name = "rules_cc", version = "0.2.17") +bazel_dep(name = "rules_pkg", version = "1.0.1") +bazel_dep(name = "zlib", version = "1.3.1.bcr.5") -bazel_dep(name = "rules_cc", version = "0.0.9") -bazel_dep(name = "rules_pkg", version = "0.9.1") -bazel_dep(name = "zlib", version = "1.3.1.bcr.3") +bazel_dep(name = "cpp_toolbelt", version = "2.1.2") +bazel_dep(name = "coroutines", version = "3.3.2") -http_archive( - name = "toolbelt", - integrity = "sha256-RwYojmkQeMEBe/jDW1BR9BTdGa0CVcxne46X9Qdxvh0=", - strip_prefix = "cpp_toolbelt-1.2.9", - urls = ["https://github.com/dallison/cpp_toolbelt/archive/refs/tags/1.2.9.tar.gz"], +# protobuf pulls an older rules_go via gazelle; Bazel 9 needs a current rules_go. +single_version_override( + module_name = "rules_go", + version = "0.61.0", ) -# For local debugging of toolbelt coroutine library. -# local_repository( -# name = "toolbelt", -# path = "../cpp_toolbelt", -# ) - -# Coroutines -http_archive( - name = "coroutines", - integrity = "sha256-cJ3a89VebabjRgLjHNsEsjIQE+hi+5vdtuAh4RfTXCI=", - strip_prefix = "co-1.3.7", - urls = ["https://github.com/dallison/co/archive/refs/tags/1.3.7.tar.gz"], +# protobuf -> rules_jvm_external pulls rules_android 0.6.4, whose Android SDK +# toolchain still references the removed @local_config_platform repo. Bazel 9 +# enables --incompatible_disable_native_repo_rules, so resolving that globally +# registered toolchain breaks every target (even C++). 0.7.2 is Bazel 9 ready. +single_version_override( + module_name = "rules_android", + version = "0.7.2", ) - -# For local debugging of co coroutine library. -# local_repository( -# name = "coroutines", -# path = "../co", -# ) - diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index aa25adf..343a1ac 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -1,5 +1,5 @@ { - "lockFileVersion": 18, + "lockFileVersion": 26, "registryFileHashes": { "https://bcr.bazel.build/bazel_registry.json": "8a28e4aff06ee60aed2a8c281907fb8bcbf3b753c91fb5a5c57da3215d5b3497", "https://bcr.bazel.build/modules/abseil-cpp/20210324.2/MODULE.bazel": "7cd0312e064fde87c8d1cd79ba06c876bd23630c83466e9500321be55c96ace2", @@ -9,17 +9,51 @@ "https://bcr.bazel.build/modules/abseil-cpp/20230802.0/MODULE.bazel": "d253ae36a8bd9ee3c5955384096ccb6baf16a1b1e93e858370da0a3b94f77c16", "https://bcr.bazel.build/modules/abseil-cpp/20230802.1/MODULE.bazel": "fa92e2eb41a04df73cdabeec37107316f7e5272650f81d6cc096418fe647b915", "https://bcr.bazel.build/modules/abseil-cpp/20240116.1/MODULE.bazel": "37bcdb4440fbb61df6a1c296ae01b327f19e9bb521f9b8e26ec854b6f97309ed", - "https://bcr.bazel.build/modules/abseil-cpp/20240116.1/source.json": "9be551b8d4e3ef76875c0d744b5d6a504a27e3ae67bc6b28f46415fd2d2957da", + "https://bcr.bazel.build/modules/abseil-cpp/20240116.2/MODULE.bazel": "73939767a4686cd9a520d16af5ab440071ed75cec1a876bf2fcfaf1f71987a16", + "https://bcr.bazel.build/modules/abseil-cpp/20250127.0/MODULE.bazel": "d1086e248cda6576862b4b3fe9ad76a214e08c189af5b42557a6e1888812c5d5", + "https://bcr.bazel.build/modules/abseil-cpp/20250127.1/MODULE.bazel": "c4a89e7ceb9bf1e25cf84a9f830ff6b817b72874088bf5141b314726e46a57c1", + "https://bcr.bazel.build/modules/abseil-cpp/20250512.1/MODULE.bazel": "d209fdb6f36ffaf61c509fcc81b19e81b411a999a934a032e10cd009a0226215", + "https://bcr.bazel.build/modules/abseil-cpp/20250814.1/MODULE.bazel": "51f2312901470cdab0dbdf3b88c40cd21c62a7ed58a3de45b365ddc5b11bcab2", + "https://bcr.bazel.build/modules/abseil-cpp/20250814.1/source.json": "cea3901d7e299da7320700abbaafe57a65d039f10d0d7ea601c4a66938ea4b0c", + "https://bcr.bazel.build/modules/abseil-py/2.1.0/MODULE.bazel": "5ebe5bf853769c65707e5c28f216798f7a4b1042015e6a36e6d03094d94bec8a", + "https://bcr.bazel.build/modules/abseil-py/2.1.0/source.json": "0e8fc4f088ce07099c1cd6594c20c7ddbb48b4b3c0849b7d94ba94be88ff042b", + "https://bcr.bazel.build/modules/apple_support/1.11.1/MODULE.bazel": "1843d7cd8a58369a444fc6000e7304425fba600ff641592161d9f15b179fb896", + "https://bcr.bazel.build/modules/apple_support/1.15.1/MODULE.bazel": "a0556fefca0b1bb2de8567b8827518f94db6a6e7e7d632b4c48dc5f865bc7c85", + "https://bcr.bazel.build/modules/apple_support/1.16.0/MODULE.bazel": "e785295d21ccab339c3af131752bfbe50fc33dd8215b357492d05bfad0232400", + "https://bcr.bazel.build/modules/apple_support/1.21.0/MODULE.bazel": "ac1824ed5edf17dee2fdd4927ada30c9f8c3b520be1b5fd02a5da15bc10bff3e", + "https://bcr.bazel.build/modules/apple_support/1.21.1/MODULE.bazel": "5809fa3efab15d1f3c3c635af6974044bac8a4919c62238cce06acee8a8c11f1", + "https://bcr.bazel.build/modules/apple_support/1.22.1/MODULE.bazel": "90bd1a660590f3ceffbdf524e37483094b29352d85317060b2327fff8f3f4458", + "https://bcr.bazel.build/modules/apple_support/1.24.2/MODULE.bazel": "0e62471818affb9f0b26f128831d5c40b074d32e6dda5a0d3852847215a41ca4", + "https://bcr.bazel.build/modules/apple_support/1.24.2/source.json": "2c22c9827093250406c5568da6c54e6fdf0ef06238def3d99c71b12feb057a8d", + "https://bcr.bazel.build/modules/aspect_bazel_lib/2.14.0/MODULE.bazel": "2b31ffcc9bdc8295b2167e07a757dbbc9ac8906e7028e5170a3708cecaac119f", + "https://bcr.bazel.build/modules/aspect_bazel_lib/2.19.3/MODULE.bazel": "253d739ba126f62a5767d832765b12b59e9f8d2bc88cc1572f4a73e46eb298ca", + "https://bcr.bazel.build/modules/aspect_bazel_lib/2.22.5/MODULE.bazel": "004ba890363d05372a97248c37205ae64b6fa31047629cd2c0895a9d0c7779e8", + "https://bcr.bazel.build/modules/aspect_bazel_lib/2.22.5/source.json": "ac2c3213df8f985785f1d0aeb7f0f73d5324e6e67d593d9b9470fb74a25d4a9b", + "https://bcr.bazel.build/modules/aspect_bazel_lib/2.8.1/MODULE.bazel": "812d2dd42f65dca362152101fbec418029cc8fd34cbad1a2fde905383d705838", "https://bcr.bazel.build/modules/bazel_features/1.1.1/MODULE.bazel": "27b8c79ef57efe08efccbd9dd6ef70d61b4798320b8d3c134fd571f78963dbcd", + "https://bcr.bazel.build/modules/bazel_features/1.10.0/MODULE.bazel": "f75e8807570484a99be90abcd52b5e1f390362c258bcb73106f4544957a48101", "https://bcr.bazel.build/modules/bazel_features/1.11.0/MODULE.bazel": "f9382337dd5a474c3b7d334c2f83e50b6eaedc284253334cf823044a26de03e8", + "https://bcr.bazel.build/modules/bazel_features/1.13.0/MODULE.bazel": "c14c33c7c3c730612bdbe14ebbb5e61936b6f11322ea95a6e91cd1ba962f94df", "https://bcr.bazel.build/modules/bazel_features/1.15.0/MODULE.bazel": "d38ff6e517149dc509406aca0db3ad1efdd890a85e049585b7234d04238e2a4d", "https://bcr.bazel.build/modules/bazel_features/1.17.0/MODULE.bazel": "039de32d21b816b47bd42c778e0454217e9c9caac4a3cf8e15c7231ee3ddee4d", "https://bcr.bazel.build/modules/bazel_features/1.18.0/MODULE.bazel": "1be0ae2557ab3a72a57aeb31b29be347bcdc5d2b1eb1e70f39e3851a7e97041a", "https://bcr.bazel.build/modules/bazel_features/1.19.0/MODULE.bazel": "59adcdf28230d220f0067b1f435b8537dd033bfff8db21335ef9217919c7fb58", + "https://bcr.bazel.build/modules/bazel_features/1.21.0/MODULE.bazel": "675642261665d8eea09989aa3b8afb5c37627f1be178382c320d1b46afba5e3b", + "https://bcr.bazel.build/modules/bazel_features/1.23.0/MODULE.bazel": "fd1ac84bc4e97a5a0816b7fd7d4d4f6d837b0047cf4cbd81652d616af3a6591a", + "https://bcr.bazel.build/modules/bazel_features/1.25.0/MODULE.bazel": "e2e60a10a6da64bbf533f15ca652bf61a033e41c2ed734d79a9a08ba87f68c1a", + "https://bcr.bazel.build/modules/bazel_features/1.27.0/MODULE.bazel": "621eeee06c4458a9121d1f104efb80f39d34deff4984e778359c60eaf1a8cb65", + "https://bcr.bazel.build/modules/bazel_features/1.28.0/MODULE.bazel": "4b4200e6cbf8fa335b2c3f43e1d6ef3e240319c33d43d60cc0fbd4b87ece299d", + "https://bcr.bazel.build/modules/bazel_features/1.3.0/MODULE.bazel": "cdcafe83ec318cda34e02948e81d790aab8df7a929cec6f6969f13a489ccecd9", "https://bcr.bazel.build/modules/bazel_features/1.30.0/MODULE.bazel": "a14b62d05969a293b80257e72e597c2da7f717e1e69fa8b339703ed6731bec87", - "https://bcr.bazel.build/modules/bazel_features/1.30.0/source.json": "b07e17f067fe4f69f90b03b36ef1e08fe0d1f3cac254c1241a1818773e3423bc", + "https://bcr.bazel.build/modules/bazel_features/1.33.0/MODULE.bazel": "8b8dc9d2a4c88609409c3191165bccec0e4cb044cd7a72ccbe826583303459f6", + "https://bcr.bazel.build/modules/bazel_features/1.36.0/MODULE.bazel": "596cb62090b039caf1cad1d52a8bc35cf188ca9a4e279a828005e7ee49a1bec3", "https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7", + "https://bcr.bazel.build/modules/bazel_features/1.42.1/MODULE.bazel": "275a59b5406ff18c01739860aa70ad7ccb3cfb474579411decca11c93b951080", + "https://bcr.bazel.build/modules/bazel_features/1.42.1/source.json": "fcd4396b2df85f64f2b3bb436ad870793ecf39180f1d796f913cc9276d355309", + "https://bcr.bazel.build/modules/bazel_features/1.9.0/MODULE.bazel": "885151d58d90d8d9c811eb75e3288c11f850e1d6b481a8c9f766adee4712358b", "https://bcr.bazel.build/modules/bazel_features/1.9.1/MODULE.bazel": "8f679097876a9b609ad1f60249c49d68bfab783dd9be012faf9d82547b14815a", + "https://bcr.bazel.build/modules/bazel_lib/3.0.0/MODULE.bazel": "22b70b80ac89ad3f3772526cd9feee2fa412c2b01933fea7ed13238a448d370d", + "https://bcr.bazel.build/modules/bazel_lib/3.0.0/source.json": "895f21909c6fba01d7c17914bb6c8e135982275a1b18cdaa4e62272217ef1751", "https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8", "https://bcr.bazel.build/modules/bazel_skylib/1.1.1/MODULE.bazel": "1add3e7d93ff2e6998f9e118022c84d163917d912f5afafb3058e3d2f1545b5e", "https://bcr.bazel.build/modules/bazel_skylib/1.2.0/MODULE.bazel": "44fe84260e454ed94ad326352a698422dbe372b21a1ac9f3eab76eb531223686", @@ -31,175 +65,856 @@ "https://bcr.bazel.build/modules/bazel_skylib/1.6.1/MODULE.bazel": "8fdee2dbaace6c252131c00e1de4b165dc65af02ea278476187765e1a617b917", "https://bcr.bazel.build/modules/bazel_skylib/1.7.0/MODULE.bazel": "0db596f4563de7938de764cc8deeabec291f55e8ec15299718b93c4423e9796d", "https://bcr.bazel.build/modules/bazel_skylib/1.7.1/MODULE.bazel": "3120d80c5861aa616222ec015332e5f8d3171e062e3e804a2a0253e1be26e59b", - "https://bcr.bazel.build/modules/bazel_skylib/1.7.1/source.json": "f121b43eeefc7c29efbd51b83d08631e2347297c95aac9764a701f2a6a2bb953", - "https://bcr.bazel.build/modules/buildozer/7.1.2/MODULE.bazel": "2e8dd40ede9c454042645fd8d8d0cd1527966aa5c919de86661e62953cd73d84", - "https://bcr.bazel.build/modules/buildozer/7.1.2/source.json": "c9028a501d2db85793a6996205c8de120944f50a0d570438fcae0457a5f9d1f8", + "https://bcr.bazel.build/modules/bazel_skylib/1.8.1/MODULE.bazel": "88ade7293becda963e0e3ea33e7d54d3425127e0a326e0d17da085a5f1f03ff6", + "https://bcr.bazel.build/modules/bazel_skylib/1.8.2/MODULE.bazel": "69ad6927098316848b34a9142bcc975e018ba27f08c4ff403f50c1b6e646ca67", + "https://bcr.bazel.build/modules/bazel_skylib/1.9.0/MODULE.bazel": "72997b29dfd95c3fa0d0c48322d05590418edef451f8db8db5509c57875fb4b7", + "https://bcr.bazel.build/modules/bazel_skylib/1.9.0/source.json": "7ad77c1e8c1b84222d9b3f3cae016a76639435744c19330b0b37c0a3c9da7dc0", + "https://bcr.bazel.build/modules/bazel_worker_api/0.0.10/MODULE.bazel": "a426df551b40c3997c351d05f00f0d1f86b618ddb646012f1e9c72efce8ed939", + "https://bcr.bazel.build/modules/bazel_worker_api/0.0.10/source.json": "7f220d3edfeba5d1f61535fd8400338df74f8bdeb241ebe660534c6926a5a645", + "https://bcr.bazel.build/modules/bazel_worker_api/0.0.8/MODULE.bazel": "396c1ef53835aafe3d42ce6619080531ee770648303731f16cfaa33fa056bf0c", + "https://bcr.bazel.build/modules/bazel_worker_java/0.0.10/MODULE.bazel": "538f21f715cab81c4a43f73e1a91a0c1f8accd9b263dd2a831952805fcfc1a62", + "https://bcr.bazel.build/modules/bazel_worker_java/0.0.10/source.json": "1646d3aaf5a4bc0d587ecb33d81e93ea9284ff96f5ef579006a9c054945bbe7a", + "https://bcr.bazel.build/modules/bazel_worker_java/0.0.8/MODULE.bazel": "e76479eae70bd4e8f5f4c2dfc5d03ab971cfb18750246c7b3f3454c5c2ee6629", + "https://bcr.bazel.build/modules/buildozer/8.5.1/MODULE.bazel": "a35d9561b3fc5b18797c330793e99e3b834a473d5fbd3d7d7634aafc9bdb6f8f", + "https://bcr.bazel.build/modules/buildozer/8.5.1/source.json": "e3386e6ff4529f2442800dee47ad28d3e6487f36a1f75ae39ae56c70f0cd2fbd", + "https://bcr.bazel.build/modules/coroutines/3.3.2/MODULE.bazel": "ad1395ae9758ee5d113acab755b4d972c368c4dab66513bfe31423136f3ee608", + "https://bcr.bazel.build/modules/coroutines/3.3.2/source.json": "fb47a4c3e13d730a1a58ed6a34add8f6aaeef655a47d0f179427f71bf58ba150", + "https://bcr.bazel.build/modules/cpp_toolbelt/2.1.2/MODULE.bazel": "9db22ab8e1bf493e7e449d14255dbc8f2bad5bb9b8bb2635ae53a5211cd6fcb3", + "https://bcr.bazel.build/modules/cpp_toolbelt/2.1.2/source.json": "2922fb5eb35a57a8bc599120f68843acfc23df85eb85325e50b99a8a64453f33", + "https://bcr.bazel.build/modules/gawk/5.3.2.bcr.1/MODULE.bazel": "cdf8cbe5ee750db04b78878c9633cc76e80dcf4416cbe982ac3a9222f80713c8", + "https://bcr.bazel.build/modules/gawk/5.3.2.bcr.1/source.json": "fa7b512dfcb5eafd90ce3959cf42a2a6fe96144ebbb4b3b3928054895f2afac2", + "https://bcr.bazel.build/modules/gazelle/0.45.0/MODULE.bazel": "ecd19ebe9f8e024e1ccffb6d997cc893a974bcc581f1ae08f386bdd448b10687", + "https://bcr.bazel.build/modules/gazelle/0.47.0/MODULE.bazel": "b61bb007c4efad134aa30ee7f4a8e2a39b22aa5685f005edaa022fbd1de43ebc", + "https://bcr.bazel.build/modules/gazelle/0.47.0/source.json": "aeb2e5df14b7fb298625d75d08b9c65bdb0b56014c5eb89da9e5dd0572280ae6", "https://bcr.bazel.build/modules/google_benchmark/1.8.2/MODULE.bazel": "a70cf1bba851000ba93b58ae2f6d76490a9feb74192e57ab8e8ff13c34ec50cb", "https://bcr.bazel.build/modules/googletest/1.11.0/MODULE.bazel": "3a83f095183f66345ca86aa13c58b59f9f94a2f81999c093d4eeaa2d262d12f4", "https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/MODULE.bazel": "22c31a561553727960057361aa33bf20fb2e98584bc4fec007906e27053f80c6", - "https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/source.json": "41e9e129f80d8c8bf103a7acc337b76e54fad1214ac0a7084bf24f4cd924b8b4", "https://bcr.bazel.build/modules/googletest/1.14.0/MODULE.bazel": "cfbcbf3e6eac06ef9d85900f64424708cc08687d1b527f0ef65aa7517af8118f", + "https://bcr.bazel.build/modules/googletest/1.15.2/MODULE.bazel": "6de1edc1d26cafb0ea1a6ab3f4d4192d91a312fd2d360b63adaa213cd00b2108", + "https://bcr.bazel.build/modules/googletest/1.17.0.bcr.2/MODULE.bazel": "827f54f492a3ce549c940106d73de332c2b30cebd0c20c0bc5d786aba7f116cb", + "https://bcr.bazel.build/modules/googletest/1.17.0.bcr.2/source.json": "3664514073a819992320ffbce5825e4238459df344d8b01748af2208f8d2e1eb", + "https://bcr.bazel.build/modules/googletest/1.17.0/MODULE.bazel": "dbec758171594a705933a29fcf69293d2468c49ec1f2ebca65c36f504d72df46", + "https://bcr.bazel.build/modules/jq.bzl/0.1.0/MODULE.bazel": "2ce69b1af49952cd4121a9c3055faa679e748ce774c7f1fda9657f936cae902f", + "https://bcr.bazel.build/modules/jq.bzl/0.1.0/source.json": "746bf13cac0860f091df5e4911d0c593971cd8796b5ad4e809b2f8e133eee3d5", "https://bcr.bazel.build/modules/jsoncpp/1.9.5/MODULE.bazel": "31271aedc59e815656f5736f282bb7509a97c7ecb43e927ac1a37966e0578075", - "https://bcr.bazel.build/modules/jsoncpp/1.9.5/source.json": "4108ee5085dd2885a341c7fab149429db457b3169b86eb081fa245eadf69169d", + "https://bcr.bazel.build/modules/jsoncpp/1.9.6.bcr.1/MODULE.bazel": "be2a0db3289111ad410f91ca7e9a84da2dec4441a53a4026892cb29c92a60ca7", + "https://bcr.bazel.build/modules/jsoncpp/1.9.6.bcr.1/source.json": "089bd0e9656760a0b872bb78d6e2694820170a76edd10adf368f08ab2d246360", + "https://bcr.bazel.build/modules/jsoncpp/1.9.6/MODULE.bazel": "2f8d20d3b7d54143213c4dfc3d98225c42de7d666011528dc8fe91591e2e17b0", "https://bcr.bazel.build/modules/libpfm/4.11.0/MODULE.bazel": "45061ff025b301940f1e30d2c16bea596c25b176c8b6b3087e92615adbd52902", + "https://bcr.bazel.build/modules/nlohmann_json/3.6.1/MODULE.bazel": "6f7b417dcc794d9add9e556673ad25cb3ba835224290f4f848f8e2db1e1fca74", + "https://bcr.bazel.build/modules/package_metadata/0.0.2/MODULE.bazel": "fb8d25550742674d63d7b250063d4580ca530499f045d70748b1b142081ebb92", + "https://bcr.bazel.build/modules/package_metadata/0.0.3/MODULE.bazel": "77890552ecea9e284b5424c9de827a58099348763a4359e975c359a83d4faa83", + "https://bcr.bazel.build/modules/package_metadata/0.0.5/MODULE.bazel": "ef4f9439e3270fdd6b9fd4dbc3d2f29d13888e44c529a1b243f7a31dfbc2e8e4", + "https://bcr.bazel.build/modules/package_metadata/0.0.5/source.json": "2326db2f6592578177751c3e1f74786b79382cd6008834c9d01ec865b9126a85", "https://bcr.bazel.build/modules/platforms/0.0.10/MODULE.bazel": "8cb8efaf200bdeb2150d93e162c40f388529a25852b332cec879373771e48ed5", "https://bcr.bazel.build/modules/platforms/0.0.11/MODULE.bazel": "0daefc49732e227caa8bfa834d65dc52e8cc18a2faf80df25e8caea151a9413f", - "https://bcr.bazel.build/modules/platforms/0.0.11/source.json": "f7e188b79ebedebfe75e9e1d098b8845226c7992b307e28e1496f23112e8fc29", "https://bcr.bazel.build/modules/platforms/0.0.4/MODULE.bazel": "9b328e31ee156f53f3c416a64f8491f7eb731742655a47c9eec4703a71644aee", "https://bcr.bazel.build/modules/platforms/0.0.5/MODULE.bazel": "5733b54ea419d5eaf7997054bb55f6a1d0b5ff8aedf0176fef9eea44f3acda37", "https://bcr.bazel.build/modules/platforms/0.0.6/MODULE.bazel": "ad6eeef431dc52aefd2d77ed20a4b353f8ebf0f4ecdd26a807d2da5aa8cd0615", "https://bcr.bazel.build/modules/platforms/0.0.7/MODULE.bazel": "72fd4a0ede9ee5c021f6a8dd92b503e089f46c227ba2813ff183b71616034814", "https://bcr.bazel.build/modules/platforms/0.0.8/MODULE.bazel": "9f142c03e348f6d263719f5074b21ef3adf0b139ee4c5133e2aa35664da9eb2d", + "https://bcr.bazel.build/modules/platforms/0.0.9/MODULE.bazel": "4a87a60c927b56ddd67db50c89acaa62f4ce2a1d2149ccb63ffd871d5ce29ebc", + "https://bcr.bazel.build/modules/platforms/1.0.0/MODULE.bazel": "f05feb42b48f1b3c225e4ccf351f367be0371411a803198ec34a389fb22aa580", + "https://bcr.bazel.build/modules/platforms/1.1.0/MODULE.bazel": "1c0c09f5bdcf4b3f924720d2478a3711cb39f4977019ca5988685e5b7e18b3d2", + "https://bcr.bazel.build/modules/platforms/1.1.0/source.json": "fcf351c47596c939140ab0d333dfdd08ed1ea6ce33c2fe70c12493a301cf1344", "https://bcr.bazel.build/modules/protobuf/21.7/MODULE.bazel": "a5a29bb89544f9b97edce05642fac225a808b5b7be74038ea3640fae2f8e66a7", + "https://bcr.bazel.build/modules/protobuf/23.1/MODULE.bazel": "88b393b3eb4101d18129e5db51847cd40a5517a53e81216144a8c32dfeeca52a", + "https://bcr.bazel.build/modules/protobuf/24.4/MODULE.bazel": "7bc7ce5f2abf36b3b7b7c8218d3acdebb9426aeb35c2257c96445756f970eb12", "https://bcr.bazel.build/modules/protobuf/27.0/MODULE.bazel": "7873b60be88844a0a1d8f80b9d5d20cfbd8495a689b8763e76c6372998d3f64c", "https://bcr.bazel.build/modules/protobuf/27.1/MODULE.bazel": "703a7b614728bb06647f965264967a8ef1c39e09e8f167b3ca0bb1fd80449c0d", + "https://bcr.bazel.build/modules/protobuf/27.2/MODULE.bazel": "32450b50673882e4c8c3d10a83f3bc82161b213ed2f80d17e38bece8f165c295", "https://bcr.bazel.build/modules/protobuf/29.0-rc2/MODULE.bazel": "6241d35983510143049943fc0d57937937122baf1b287862f9dc8590fc4c37df", + "https://bcr.bazel.build/modules/protobuf/29.0-rc3/MODULE.bazel": "33c2dfa286578573afc55a7acaea3cada4122b9631007c594bf0729f41c8de92", "https://bcr.bazel.build/modules/protobuf/29.0/MODULE.bazel": "319dc8bf4c679ff87e71b1ccfb5a6e90a6dbc4693501d471f48662ac46d04e4e", - "https://bcr.bazel.build/modules/protobuf/29.0/source.json": "b857f93c796750eef95f0d61ee378f3420d00ee1dd38627b27193aa482f4f981", + "https://bcr.bazel.build/modules/protobuf/29.1/MODULE.bazel": "557c3457560ff49e122ed76c0bc3397a64af9574691cb8201b4e46d4ab2ecb95", "https://bcr.bazel.build/modules/protobuf/3.19.0/MODULE.bazel": "6b5fbb433f760a99a22b18b6850ed5784ef0e9928a72668b66e4d7ccd47db9b0", + "https://bcr.bazel.build/modules/protobuf/3.19.6/MODULE.bazel": "9233edc5e1f2ee276a60de3eaa47ac4132302ef9643238f23128fea53ea12858", + "https://bcr.bazel.build/modules/protobuf/31.1/MODULE.bazel": "379a389bb330b7b8c1cdf331cc90bf3e13de5614799b3b52cdb7c6f389f6b38e", + "https://bcr.bazel.build/modules/protobuf/32.1/MODULE.bazel": "89cd2866a9cb07fee9ff74c41ceace11554f32e0d849de4e23ac55515cfada4d", + "https://bcr.bazel.build/modules/protobuf/33.1/MODULE.bazel": "982c8a0cceab4d790076f72b7677faf836b0dfadc2b66a34aab7232116c4ae39", + "https://bcr.bazel.build/modules/protobuf/33.4/MODULE.bazel": "114775b816b38b6d0ca620450d6b02550c60ceedfdc8d9a229833b34a223dc42", + "https://bcr.bazel.build/modules/protobuf/34.1/MODULE.bazel": "41b4bf342b86021f7be1ee912cd140ef300fe527c4949b435f037cda74677f90", + "https://bcr.bazel.build/modules/protobuf/34.1/source.json": "9cf0686caa5d920a7e7c0401f76f075df76ea7ad7c064df442b6c029106a6d06", "https://bcr.bazel.build/modules/pybind11_bazel/2.11.1/MODULE.bazel": "88af1c246226d87e65be78ed49ecd1e6f5e98648558c14ce99176da041dc378e", - "https://bcr.bazel.build/modules/pybind11_bazel/2.11.1/source.json": "be4789e951dd5301282729fe3d4938995dc4c1a81c2ff150afc9f1b0504c6022", + "https://bcr.bazel.build/modules/pybind11_bazel/2.12.0/MODULE.bazel": "e6f4c20442eaa7c90d7190d8dc539d0ab422f95c65a57cc59562170c58ae3d34", + "https://bcr.bazel.build/modules/pybind11_bazel/2.13.6/MODULE.bazel": "2d746fda559464b253b2b2e6073cb51643a2ac79009ca02100ebbc44b4548656", + "https://bcr.bazel.build/modules/pybind11_bazel/2.13.6/source.json": "6aa0703de8efb20cc897bbdbeb928582ee7beaf278bcd001ac253e1605bddfae", "https://bcr.bazel.build/modules/re2/2023-09-01/MODULE.bazel": "cb3d511531b16cfc78a225a9e2136007a48cf8a677e4264baeab57fe78a80206", - "https://bcr.bazel.build/modules/re2/2023-09-01/source.json": "e044ce89c2883cd957a2969a43e79f7752f9656f6b20050b62f90ede21ec6eb4", - "https://bcr.bazel.build/modules/rules_android/0.1.1/MODULE.bazel": "48809ab0091b07ad0182defb787c4c5328bd3a278938415c00a7b69b50c4d3a8", - "https://bcr.bazel.build/modules/rules_android/0.1.1/source.json": "e6986b41626ee10bdc864937ffb6d6bf275bb5b9c65120e6137d56e6331f089e", + "https://bcr.bazel.build/modules/re2/2024-07-02.bcr.1/MODULE.bazel": "b4963dda9b31080be1905ef085ecd7dd6cd47c05c79b9cdf83ade83ab2ab271a", + "https://bcr.bazel.build/modules/re2/2024-07-02/MODULE.bazel": "0eadc4395959969297cbcf31a249ff457f2f1d456228c67719480205aa306daa", + "https://bcr.bazel.build/modules/re2/2025-08-12.bcr.1/MODULE.bazel": "e09b434b122bfb786a69179f9b325e35cb1856c3f56a7a81dd61609260ed46e1", + "https://bcr.bazel.build/modules/re2/2025-08-12.bcr.1/source.json": "a8ae7c09533bf67f9f6e5122d884d5741600b09d78dca6fc0f2f8d2ee0c2d957", + "https://bcr.bazel.build/modules/rules_android/0.7.2/MODULE.bazel": "0862d727582c8d117ac09cd3451b5e93d8b91033f0f13bb61a4a10334958130d", + "https://bcr.bazel.build/modules/rules_android/0.7.2/source.json": "bf2dfc8dc72ffd2b58643c9d547f1b7faea4de65aa28ac4894687fa6afa09861", + "https://bcr.bazel.build/modules/rules_apple/3.16.0/MODULE.bazel": "0d1caf0b8375942ce98ea944be754a18874041e4e0459401d925577624d3a54a", + "https://bcr.bazel.build/modules/rules_apple/4.1.0/MODULE.bazel": "76e10fd4a48038d3fc7c5dc6e63b7063bbf5304a2e3bd42edda6ec660eebea68", "https://bcr.bazel.build/modules/rules_cc/0.0.1/MODULE.bazel": "cb2aa0747f84c6c3a78dad4e2049c154f08ab9d166b1273835a8174940365647", "https://bcr.bazel.build/modules/rules_cc/0.0.10/MODULE.bazel": "ec1705118f7eaedd6e118508d3d26deba2a4e76476ada7e0e3965211be012002", "https://bcr.bazel.build/modules/rules_cc/0.0.13/MODULE.bazel": "0e8529ed7b323dad0775ff924d2ae5af7640b23553dfcd4d34344c7e7a867191", - "https://bcr.bazel.build/modules/rules_cc/0.0.14/MODULE.bazel": "5e343a3aac88b8d7af3b1b6d2093b55c347b8eefc2e7d1442f7a02dc8fea48ac", "https://bcr.bazel.build/modules/rules_cc/0.0.15/MODULE.bazel": "6704c35f7b4a72502ee81f61bf88706b54f06b3cbe5558ac17e2e14666cd5dcc", "https://bcr.bazel.build/modules/rules_cc/0.0.16/MODULE.bazel": "7661303b8fc1b4d7f532e54e9d6565771fea666fbdf839e0a86affcd02defe87", + "https://bcr.bazel.build/modules/rules_cc/0.0.17/MODULE.bazel": "2ae1d8f4238ec67d7185d8861cb0a2cdf4bc608697c331b95bf990e69b62e64a", "https://bcr.bazel.build/modules/rules_cc/0.0.2/MODULE.bazel": "6915987c90970493ab97393024c156ea8fb9f3bea953b2f3ec05c34f19b5695c", "https://bcr.bazel.build/modules/rules_cc/0.0.6/MODULE.bazel": "abf360251023dfe3efcef65ab9d56beefa8394d4176dd29529750e1c57eaa33f", "https://bcr.bazel.build/modules/rules_cc/0.0.8/MODULE.bazel": "964c85c82cfeb6f3855e6a07054fdb159aced38e99a5eecf7bce9d53990afa3e", "https://bcr.bazel.build/modules/rules_cc/0.0.9/MODULE.bazel": "836e76439f354b89afe6a911a7adf59a6b2518fafb174483ad78a2a2fde7b1c5", "https://bcr.bazel.build/modules/rules_cc/0.1.1/MODULE.bazel": "2f0222a6f229f0bf44cd711dc13c858dad98c62d52bd51d8fc3a764a83125513", - "https://bcr.bazel.build/modules/rules_cc/0.1.1/source.json": "d61627377bd7dd1da4652063e368d9366fc9a73920bfa396798ad92172cf645c", + "https://bcr.bazel.build/modules/rules_cc/0.1.2/MODULE.bazel": "557ddc3a96858ec0d465a87c0a931054d7dcfd6583af2c7ed3baf494407fd8d0", + "https://bcr.bazel.build/modules/rules_cc/0.1.4/MODULE.bazel": "bb03a452a7527ac25a7518fb86a946ef63df860b9657d8323a0c50f8504fb0b9", + "https://bcr.bazel.build/modules/rules_cc/0.1.5/MODULE.bazel": "88dfc9361e8b5ae1008ac38f7cdfd45ad738e4fa676a3ad67d19204f045a1fd8", + "https://bcr.bazel.build/modules/rules_cc/0.2.0/MODULE.bazel": "b5c17f90458caae90d2ccd114c81970062946f49f355610ed89bebf954f5783c", + "https://bcr.bazel.build/modules/rules_cc/0.2.13/MODULE.bazel": "eecdd666eda6be16a8d9dc15e44b5c75133405e820f620a234acc4b1fdc5aa37", + "https://bcr.bazel.build/modules/rules_cc/0.2.14/MODULE.bazel": "353c99ed148887ee89c54a17d4100ae7e7e436593d104b668476019023b58df8", + "https://bcr.bazel.build/modules/rules_cc/0.2.16/MODULE.bazel": "9242fa89f950c6ef7702801ab53922e99c69b02310c39fb6e62b2bd30df2a1d4", + "https://bcr.bazel.build/modules/rules_cc/0.2.17/MODULE.bazel": "1849602c86cb60da8613d2de887f9566a6d354a6df6d7009f9d04a14402f9a84", + "https://bcr.bazel.build/modules/rules_cc/0.2.17/source.json": "3832f45d145354049137c0090df04629d9c2b5493dc5c2bf46f1834040133a07", + "https://bcr.bazel.build/modules/rules_cc/0.2.8/MODULE.bazel": "f1df20f0bf22c28192a794f29b501ee2018fa37a3862a1a2132ae2940a23a642", "https://bcr.bazel.build/modules/rules_foreign_cc/0.9.0/MODULE.bazel": "c9e8c682bf75b0e7c704166d79b599f93b72cfca5ad7477df596947891feeef6", "https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/MODULE.bazel": "40c97d1144356f52905566c55811f13b299453a14ac7769dfba2ac38192337a8", - "https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/source.json": "c8b1e2c717646f1702290959a3302a178fb639d987ab61d548105019f11e527e", + "https://bcr.bazel.build/modules/rules_go/0.61.0/MODULE.bazel": "aeccf0e379a7144723596766d3a56bebcdfc15362bd64e34601a67260c6f7ccd", + "https://bcr.bazel.build/modules/rules_go/0.61.0/source.json": "3b466ee8e8a044edf97928d91da79b86bc3543fe56639b8de4d4dfa95693e904", "https://bcr.bazel.build/modules/rules_java/4.0.0/MODULE.bazel": "5a78a7ae82cd1a33cef56dc578c7d2a46ed0dca12643ee45edbb8417899e6f74", "https://bcr.bazel.build/modules/rules_java/5.3.5/MODULE.bazel": "a4ec4f2db570171e3e5eb753276ee4b389bae16b96207e9d3230895c99644b86", "https://bcr.bazel.build/modules/rules_java/6.0.0/MODULE.bazel": "8a43b7df601a7ec1af61d79345c17b31ea1fedc6711fd4abfd013ea612978e39", + "https://bcr.bazel.build/modules/rules_java/6.3.0/MODULE.bazel": "a97c7678c19f236a956ad260d59c86e10a463badb7eb2eda787490f4c969b963", "https://bcr.bazel.build/modules/rules_java/6.4.0/MODULE.bazel": "e986a9fe25aeaa84ac17ca093ef13a4637f6107375f64667a15999f77db6c8f6", "https://bcr.bazel.build/modules/rules_java/6.5.2/MODULE.bazel": "1d440d262d0e08453fa0c4d8f699ba81609ed0e9a9a0f02cd10b3e7942e61e31", + "https://bcr.bazel.build/modules/rules_java/7.1.0/MODULE.bazel": "30d9135a2b6561c761bd67bd4990da591e6bdc128790ce3e7afd6a3558b2fb64", "https://bcr.bazel.build/modules/rules_java/7.10.0/MODULE.bazel": "530c3beb3067e870561739f1144329a21c851ff771cd752a49e06e3dc9c2e71a", "https://bcr.bazel.build/modules/rules_java/7.12.2/MODULE.bazel": "579c505165ee757a4280ef83cda0150eea193eed3bef50b1004ba88b99da6de6", "https://bcr.bazel.build/modules/rules_java/7.2.0/MODULE.bazel": "06c0334c9be61e6cef2c8c84a7800cef502063269a5af25ceb100b192453d4ab", "https://bcr.bazel.build/modules/rules_java/7.3.2/MODULE.bazel": "50dece891cfdf1741ea230d001aa9c14398062f2b7c066470accace78e412bc2", + "https://bcr.bazel.build/modules/rules_java/7.4.0/MODULE.bazel": "a592852f8a3dd539e82ee6542013bf2cadfc4c6946be8941e189d224500a8934", "https://bcr.bazel.build/modules/rules_java/7.6.1/MODULE.bazel": "2f14b7e8a1aa2f67ae92bc69d1ec0fa8d9f827c4e17ff5e5f02e91caa3b2d0fe", - "https://bcr.bazel.build/modules/rules_java/8.12.0/MODULE.bazel": "8e6590b961f2defdfc2811c089c75716cb2f06c8a4edeb9a8d85eaa64ee2a761", - "https://bcr.bazel.build/modules/rules_java/8.12.0/source.json": "cbd5d55d9d38d4008a7d00bee5b5a5a4b6031fcd4a56515c9accbcd42c7be2ba", + "https://bcr.bazel.build/modules/rules_java/8.3.2/MODULE.bazel": "7336d5511ad5af0b8615fdc7477535a2e4e723a357b6713af439fe8cf0195017", + "https://bcr.bazel.build/modules/rules_java/8.5.1/MODULE.bazel": "d8a9e38cc5228881f7055a6079f6f7821a073df3744d441978e7a43e20226939", + "https://bcr.bazel.build/modules/rules_java/8.6.0/MODULE.bazel": "9c064c434606d75a086f15ade5edb514308cccd1544c2b2a89bbac4310e41c71", + "https://bcr.bazel.build/modules/rules_java/8.6.1/MODULE.bazel": "f4808e2ab5b0197f094cabce9f4b006a27766beb6a9975931da07099560ca9c2", + "https://bcr.bazel.build/modules/rules_java/8.9.0/MODULE.bazel": "e17c876cb53dcd817b7b7f0d2985b710610169729e8c371b2221cacdcd3dce4a", + "https://bcr.bazel.build/modules/rules_java/9.1.0/MODULE.bazel": "ee63f27e36a3fada80342869361182f120a9819c74320e8e65b1e04ba0cd7a9d", + "https://bcr.bazel.build/modules/rules_java/9.3.0/MODULE.bazel": "f657c72d65ac449caae9abf2e68e66c0d36f9416848c4c4903d0b3234229e7f2", + "https://bcr.bazel.build/modules/rules_java/9.3.0/source.json": "59ae7e662c3c7042b88bbb42ad12483523e234c65ebe4c51611baa43e85cb248", "https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/MODULE.bazel": "a56b85e418c83eb1839819f0b515c431010160383306d13ec21959ac412d2fe7", "https://bcr.bazel.build/modules/rules_jvm_external/5.1/MODULE.bazel": "33f6f999e03183f7d088c9be518a63467dfd0be94a11d0055fe2d210f89aa909", "https://bcr.bazel.build/modules/rules_jvm_external/5.2/MODULE.bazel": "d9351ba35217ad0de03816ef3ed63f89d411349353077348a45348b096615036", "https://bcr.bazel.build/modules/rules_jvm_external/5.3/MODULE.bazel": "bf93870767689637164657731849fb887ad086739bd5d360d90007a581d5527d", "https://bcr.bazel.build/modules/rules_jvm_external/6.1/MODULE.bazel": "75b5fec090dbd46cf9b7d8ea08cf84a0472d92ba3585b476f44c326eda8059c4", + "https://bcr.bazel.build/modules/rules_jvm_external/6.2/MODULE.bazel": "36a6e52487a855f33cb960724eb56547fa87e2c98a0474c3acad94339d7f8e99", "https://bcr.bazel.build/modules/rules_jvm_external/6.3/MODULE.bazel": "c998e060b85f71e00de5ec552019347c8bca255062c990ac02d051bb80a38df0", - "https://bcr.bazel.build/modules/rules_jvm_external/6.3/source.json": "6f5f5a5a4419ae4e37c35a5bb0a6ae657ed40b7abc5a5189111b47fcebe43197", + "https://bcr.bazel.build/modules/rules_jvm_external/6.7/MODULE.bazel": "e717beabc4d091ecb2c803c2d341b88590e9116b8bf7947915eeb33aab4f96dd", + "https://bcr.bazel.build/modules/rules_jvm_external/6.9/MODULE.bazel": "07c5db05527db7744a54fcffd653e1550d40e0540207a7f7e6d0a4de5bef8274", + "https://bcr.bazel.build/modules/rules_jvm_external/6.9/source.json": "b12970214f3cc144b26610caeb101fa622d910f1ab3d98f0bae1058edbd00bd4", "https://bcr.bazel.build/modules/rules_kotlin/1.9.0/MODULE.bazel": "ef85697305025e5a61f395d4eaede272a5393cee479ace6686dba707de804d59", + "https://bcr.bazel.build/modules/rules_kotlin/1.9.5/MODULE.bazel": "043a16a572f610558ec2030db3ff0c9938574e7dd9f58bded1bb07c0192ef025", "https://bcr.bazel.build/modules/rules_kotlin/1.9.6/MODULE.bazel": "d269a01a18ee74d0335450b10f62c9ed81f2321d7958a2934e44272fe82dcef3", - "https://bcr.bazel.build/modules/rules_kotlin/1.9.6/source.json": "2faa4794364282db7c06600b7e5e34867a564ae91bda7cae7c29c64e9466b7d5", + "https://bcr.bazel.build/modules/rules_kotlin/2.2.2/MODULE.bazel": "00d39c5e0fa78cd86193946265bb849e7878c24e44260f9525108428852b315c", + "https://bcr.bazel.build/modules/rules_kotlin/2.2.2/source.json": "7a32c2259c79ae0c9a036121f120de825e3ba5f0f3a209ffbbdccf4dc62489b9", "https://bcr.bazel.build/modules/rules_license/0.0.3/MODULE.bazel": "627e9ab0247f7d1e05736b59dbb1b6871373de5ad31c3011880b4133cafd4bd0", - "https://bcr.bazel.build/modules/rules_license/0.0.4/MODULE.bazel": "6a88dd22800cf1f9f79ba32cacad0d3a423ed28efa2c2ed5582eaa78dd3ac1e5", "https://bcr.bazel.build/modules/rules_license/0.0.7/MODULE.bazel": "088fbeb0b6a419005b89cf93fe62d9517c0a2b8bb56af3244af65ecfe37e7d5d", "https://bcr.bazel.build/modules/rules_license/1.0.0/MODULE.bazel": "a7fda60eefdf3d8c827262ba499957e4df06f659330bbe6cdbdb975b768bb65c", "https://bcr.bazel.build/modules/rules_license/1.0.0/source.json": "a52c89e54cc311196e478f8382df91c15f7a2bfdf4c6cd0e2675cc2ff0b56efb", "https://bcr.bazel.build/modules/rules_pkg/0.7.0/MODULE.bazel": "df99f03fc7934a4737122518bb87e667e62d780b610910f0447665a7e2be62dc", - "https://bcr.bazel.build/modules/rules_pkg/0.9.1/MODULE.bazel": "af00144208c4be503bc920d043ba3284fb37385b3f6160b4a4daf4df80b4b823", "https://bcr.bazel.build/modules/rules_pkg/1.0.1/MODULE.bazel": "5b1df97dbc29623bccdf2b0dcd0f5cb08e2f2c9050aab1092fd39a41e82686ff", "https://bcr.bazel.build/modules/rules_pkg/1.0.1/source.json": "bd82e5d7b9ce2d31e380dd9f50c111d678c3bdaca190cb76b0e1c71b05e1ba8a", "https://bcr.bazel.build/modules/rules_proto/4.0.0/MODULE.bazel": "a7a7b6ce9bee418c1a760b3d84f83a299ad6952f9903c67f19e4edd964894e06", "https://bcr.bazel.build/modules/rules_proto/5.3.0-21.7/MODULE.bazel": "e8dff86b0971688790ae75528fe1813f71809b5afd57facb44dad9e8eca631b7", + "https://bcr.bazel.build/modules/rules_proto/6.0.0-rc1/MODULE.bazel": "1e5b502e2e1a9e825eef74476a5a1ee524a92297085015a052510b09a1a09483", "https://bcr.bazel.build/modules/rules_proto/6.0.2/MODULE.bazel": "ce916b775a62b90b61888052a416ccdda405212b6aaeb39522f7dc53431a5e73", "https://bcr.bazel.build/modules/rules_proto/7.0.2/MODULE.bazel": "bf81793bd6d2ad89a37a40693e56c61b0ee30f7a7fdbaf3eabbf5f39de47dea2", - "https://bcr.bazel.build/modules/rules_proto/7.0.2/source.json": "1e5e7260ae32ef4f2b52fd1d0de8d03b606a44c91b694d2f1afb1d3b28a48ce1", + "https://bcr.bazel.build/modules/rules_proto/7.1.0/MODULE.bazel": "002d62d9108f75bb807cd56245d45648f38275cb3a99dcd45dfb864c5d74cb96", + "https://bcr.bazel.build/modules/rules_proto/7.1.0/source.json": "39f89066c12c24097854e8f57ab8558929f9c8d474d34b2c00ac04630ad8940e", "https://bcr.bazel.build/modules/rules_python/0.10.2/MODULE.bazel": "cc82bc96f2997baa545ab3ce73f196d040ffb8756fd2d66125a530031cd90e5f", "https://bcr.bazel.build/modules/rules_python/0.23.1/MODULE.bazel": "49ffccf0511cb8414de28321f5fcf2a31312b47c40cc21577144b7447f2bf300", "https://bcr.bazel.build/modules/rules_python/0.25.0/MODULE.bazel": "72f1506841c920a1afec76975b35312410eea3aa7b63267436bfb1dd91d2d382", "https://bcr.bazel.build/modules/rules_python/0.28.0/MODULE.bazel": "cba2573d870babc976664a912539b320cbaa7114cd3e8f053c720171cde331ed", "https://bcr.bazel.build/modules/rules_python/0.31.0/MODULE.bazel": "93a43dc47ee570e6ec9f5779b2e64c1476a6ce921c48cc9a1678a91dd5f8fd58", + "https://bcr.bazel.build/modules/rules_python/0.33.2/MODULE.bazel": "3e036c4ad8d804a4dad897d333d8dce200d943df4827cb849840055be8d2e937", + "https://bcr.bazel.build/modules/rules_python/0.34.0/MODULE.bazel": "1d623d026e075b78c9fde483a889cda7996f5da4f36dffb24c246ab30f06513a", + "https://bcr.bazel.build/modules/rules_python/0.37.2/MODULE.bazel": "b5ffde91410745750b6c13be1c5dc4555ef5bc50562af4a89fd77807fdde626a", "https://bcr.bazel.build/modules/rules_python/0.4.0/MODULE.bazel": "9208ee05fd48bf09ac60ed269791cf17fb343db56c8226a720fbb1cdf467166c", - "https://bcr.bazel.build/modules/rules_python/0.40.0/MODULE.bazel": "9d1a3cd88ed7d8e39583d9ffe56ae8a244f67783ae89b60caafc9f5cf318ada7", - "https://bcr.bazel.build/modules/rules_python/0.40.0/source.json": "939d4bd2e3110f27bfb360292986bb79fd8dcefb874358ccd6cdaa7bda029320", + "https://bcr.bazel.build/modules/rules_python/1.0.0/MODULE.bazel": "898a3d999c22caa585eb062b600f88654bf92efb204fa346fb55f6f8edffca43", + "https://bcr.bazel.build/modules/rules_python/1.3.0/MODULE.bazel": "8361d57eafb67c09b75bf4bbe6be360e1b8f4f18118ab48037f2bd50aa2ccb13", + "https://bcr.bazel.build/modules/rules_python/1.4.1/MODULE.bazel": "8991ad45bdc25018301d6b7e1d3626afc3c8af8aaf4bc04f23d0b99c938b73a6", + "https://bcr.bazel.build/modules/rules_python/1.5.1/MODULE.bazel": "acfe65880942d44a69129d4c5c3122d57baaf3edf58ae5a6bd4edea114906bf5", + "https://bcr.bazel.build/modules/rules_python/1.6.0/MODULE.bazel": "7e04ad8f8d5bea40451cf80b1bd8262552aa73f841415d20db96b7241bd027d8", + "https://bcr.bazel.build/modules/rules_python/1.7.0/MODULE.bazel": "d01f995ecd137abf30238ad9ce97f8fc3ac57289c8b24bd0bf53324d937a14f8", + "https://bcr.bazel.build/modules/rules_python/1.7.0/source.json": "028a084b65dcf8f4dc4f82f8778dbe65df133f234b316828a82e060d81bdce32", + "https://bcr.bazel.build/modules/rules_robolectric/4.14.1.2/MODULE.bazel": "d44fec647d0aeb67b9f3b980cf68ba634976f3ae7ccd6c07d790b59b87a4f251", + "https://bcr.bazel.build/modules/rules_robolectric/4.14.1.2/source.json": "37c10335f2361c337c5c1f34ed36d2da70534c23088062b33a8bdaab68aa9dea", "https://bcr.bazel.build/modules/rules_shell/0.2.0/MODULE.bazel": "fda8a652ab3c7d8fee214de05e7a9916d8b28082234e8d2c0094505c5268ed3c", - "https://bcr.bazel.build/modules/rules_shell/0.2.0/source.json": "7f27af3c28037d9701487c4744b5448d26537cc66cdef0d8df7ae85411f8de95", + "https://bcr.bazel.build/modules/rules_shell/0.3.0/MODULE.bazel": "de4402cd12f4cc8fda2354fce179fdb068c0b9ca1ec2d2b17b3e21b24c1a937b", + "https://bcr.bazel.build/modules/rules_shell/0.4.1/MODULE.bazel": "00e501db01bbf4e3e1dd1595959092c2fadf2087b2852d3f553b5370f5633592", + "https://bcr.bazel.build/modules/rules_shell/0.6.1/MODULE.bazel": "72e76b0eea4e81611ef5452aa82b3da34caca0c8b7b5c0c9584338aa93bae26b", + "https://bcr.bazel.build/modules/rules_shell/0.6.1/source.json": "20ec05cd5e592055e214b2da8ccb283c7f2a421ea0dc2acbf1aa792e11c03d0c", + "https://bcr.bazel.build/modules/rules_swift/1.16.0/MODULE.bazel": "4a09f199545a60d09895e8281362b1ff3bb08bbde69c6fc87aff5b92fcc916ca", + "https://bcr.bazel.build/modules/rules_swift/2.1.1/MODULE.bazel": "494900a80f944fc7aa61500c2073d9729dff0b764f0e89b824eb746959bc1046", + "https://bcr.bazel.build/modules/rules_swift/2.4.0/MODULE.bazel": "1639617eb1ede28d774d967a738b4a68b0accb40650beadb57c21846beab5efd", + "https://bcr.bazel.build/modules/rules_swift/3.1.2/MODULE.bazel": "72c8f5cf9d26427cee6c76c8e3853eb46ce6b0412a081b2b6db6e8ad56267400", "https://bcr.bazel.build/modules/stardoc/0.5.1/MODULE.bazel": "1a05d92974d0c122f5ccf09291442580317cdd859f07a8655f1db9a60374f9f8", "https://bcr.bazel.build/modules/stardoc/0.5.3/MODULE.bazel": "c7f6948dae6999bf0db32c1858ae345f112cacf98f174c7a8bb707e41b974f1c", "https://bcr.bazel.build/modules/stardoc/0.5.6/MODULE.bazel": "c43dabc564990eeab55e25ed61c07a1aadafe9ece96a4efabb3f8bf9063b71ef", + "https://bcr.bazel.build/modules/stardoc/0.6.2/MODULE.bazel": "7060193196395f5dd668eda046ccbeacebfd98efc77fed418dbe2b82ffaa39fd", "https://bcr.bazel.build/modules/stardoc/0.7.0/MODULE.bazel": "05e3d6d30c099b6770e97da986c53bd31844d7f13d41412480ea265ac9e8079c", "https://bcr.bazel.build/modules/stardoc/0.7.1/MODULE.bazel": "3548faea4ee5dda5580f9af150e79d0f6aea934fc60c1cc50f4efdd9420759e7", - "https://bcr.bazel.build/modules/stardoc/0.7.1/source.json": "b6500ffcd7b48cd72c29bb67bcac781e12701cc0d6d55d266a652583cfcdab01", + "https://bcr.bazel.build/modules/stardoc/0.7.2/MODULE.bazel": "fc152419aa2ea0f51c29583fab1e8c99ddefd5b3778421845606ee628629e0e5", + "https://bcr.bazel.build/modules/stardoc/0.7.2/source.json": "58b029e5e901d6802967754adf0a9056747e8176f017cfe3607c0851f4d42216", + "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.1/MODULE.bazel": "5e463fbfba7b1701d957555ed45097d7f984211330106ccd1352c6e0af0dcf91", + "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.2/MODULE.bazel": "75aab2373a4bbe2a1260b9bf2a1ebbdbf872d3bd36f80bff058dccd82e89422f", + "https://bcr.bazel.build/modules/tar.bzl/0.2.1/MODULE.bazel": "52d1c00a80a8cc67acbd01649e83d8dd6a9dc426a6c0b754a04fe8c219c76468", + "https://bcr.bazel.build/modules/tar.bzl/0.5.1/MODULE.bazel": "7c2eb3dcfc53b0f3d6f9acdfd911ca803eaf92aadf54f8ca6e4c1f3aee288351", + "https://bcr.bazel.build/modules/tar.bzl/0.5.1/source.json": "deed3094f7cc779ed1d37a68403847b0e38d9dd9d931e03cb90825f3368b515f", "https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43", + "https://bcr.bazel.build/modules/upb/0.0.0-20230516-61a97ef/MODULE.bazel": "c0df5e35ad55e264160417fd0875932ee3c9dda63d9fccace35ac62f45e1b6f9", + "https://bcr.bazel.build/modules/yq.bzl/0.1.1/MODULE.bazel": "9039681f9bcb8958ee2c87ffc74bdafba9f4369096a2b5634b88abc0eaefa072", + "https://bcr.bazel.build/modules/yq.bzl/0.1.1/source.json": "2d2bad780a9f2b9195a4a370314d2c17ae95eaa745cefc2e12fbc49759b15aa3", "https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0", - "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.3/MODULE.bazel": "af322bc08976524477c79d1e45e241b6efbeb918c497e8840b8ab116802dda79", + "https://bcr.bazel.build/modules/zlib/1.2.12/MODULE.bazel": "3b1a8834ada2a883674be8cbd36ede1b6ec481477ada359cd2d3ddc562340b27", "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/MODULE.bazel": "eec517b5bbe5492629466e11dae908d043364302283de25581e3eb944326c4ca", "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/source.json": "22bc55c47af97246cfc093d0acf683a7869377de362b5d1c552c2c2e16b7a806", "https://bcr.bazel.build/modules/zlib/1.3.1/MODULE.bazel": "751c9940dcfe869f5f7274e1295422a34623555916eb98c174c1e945594bf198" }, "selectedYankedVersions": {}, "moduleExtensions": { - "@@rules_kotlin+//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": { + "@@protobuf+//python/dist:system_python.bzl%system_python_extension": { "general": { - "bzlTransitiveDigest": "hUTp2w+RUVdL7ma5esCXZJAFnX7vLbVfLd7FwnQI6bU=", - "usagesDigest": "QI2z8ZUR+mqtbwsf2fLqYdJAkPOHdOV+tF2yVAUgRzw=", - "recordedFileInputs": {}, - "recordedDirentsInputs": {}, - "envVariables": {}, + "bzlTransitiveDigest": "qh0n9IrXU/xS94wxKQrG1J63zrLkA1Wy2Y3BQxptPcI=", + "usagesDigest": "AF5a9lHFrJtHw1GTt3jJOs7ZYl1+N2bkn1LbPFLoguA=", + "recordedInputs": [], "generatedRepoSpecs": { - "com_github_jetbrains_kotlin_git": { - "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:compiler.bzl%kotlin_compiler_git_repository", + "system_python": { + "repoRuleId": "@@protobuf+//python/dist:system_python.bzl%system_python", "attributes": { - "urls": [ - "https://github.com/JetBrains/kotlin/releases/download/v1.9.23/kotlin-compiler-1.9.23.zip" - ], - "sha256": "93137d3aab9afa9b27cb06a824c2324195c6b6f6179d8a8653f440f5bd58be88" + "minimum_python_version": "3.9" + } + } + } + } + }, + "@@pybind11_bazel+//:internal_configure.bzl%internal_configure_extension": { + "general": { + "bzlTransitiveDigest": "Ilz4hu4VWEbx3OM4ZIpgYmYXuPq6ewOVgzv5F0ziWS8=", + "usagesDigest": "tVQNvLoXMWAbiK39am3yovKGpwINdftfn7RpDyN+JZc=", + "recordedInputs": [ + "REPO_MAPPING:pybind11_bazel+,bazel_tools bazel_tools" + ], + "generatedRepoSpecs": { + "pybind11": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "build_file": "@@pybind11_bazel+//:pybind11-BUILD.bazel", + "strip_prefix": "pybind11-2.13.6", + "url": "https://github.com/pybind/pybind11/archive/refs/tags/v2.13.6.tar.gz", + "integrity": "sha256-4Iy4f0dz2pf6e18DXeh2OrxlbYfVdz5i9toFh9Hw7CA=" + } + } + } + } + }, + "@@rules_android+//bzlmod_extensions:apksig.bzl%apksig_extension": { + "general": { + "bzlTransitiveDigest": "IiT2UgJGnHaKiyP2A1yh3U/QWN4W9g/Byolrm78hC/s=", + "usagesDigest": "0FXD4PX+vQ/jVne2oV4v3Cw5Mc9DZQ4yTcoRkAjj/X4=", + "recordedInputs": [ + "REPO_MAPPING:rules_android+,bazel_tools bazel_tools" + ], + "generatedRepoSpecs": { + "apksig": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://android.googlesource.com/platform/tools/apksig/+archive/24e3075e68ebe17c0b529bb24bfda819db5e2f3b.tar.gz", + "build_file": "@@rules_android+//bzlmod_extensions:apksig.BUILD" + } + } + } + } + }, + "@@rules_android+//rules/android_sdk_repository:rule.bzl%android_sdk_repository_extension": { + "general": { + "bzlTransitiveDigest": "qHbR00gVzVzkxX+PRtv4UGcUFMtBz7TK9CNYUWH8nIE=", + "usagesDigest": "tTIw/WMyb1t/LzacDY8lDjUznzxQ3MyXW6474WIS3WQ=", + "recordedInputs": [], + "generatedRepoSpecs": { + "androidsdk": { + "repoRuleId": "@@rules_android+//rules/android_sdk_repository:rule.bzl%_android_sdk_repository", + "attributes": {} + } + } + } + }, + "@@rules_python+//python/extensions:config.bzl%config": { + "general": { + "bzlTransitiveDigest": "iibnRYgg8LpcfmH7EAnVwYePC3jsVaJ6Id8XxUjSZps=", + "usagesDigest": "ZVSXMAGpD+xzVNPuvF1IoLBkty7TROO0+akMapt1pAg=", + "recordedInputs": [ + "REPO_MAPPING:rules_python+,bazel_tools bazel_tools", + "REPO_MAPPING:rules_python+,pypi__build rules_python++config+pypi__build", + "REPO_MAPPING:rules_python+,pypi__click rules_python++config+pypi__click", + "REPO_MAPPING:rules_python+,pypi__colorama rules_python++config+pypi__colorama", + "REPO_MAPPING:rules_python+,pypi__importlib_metadata rules_python++config+pypi__importlib_metadata", + "REPO_MAPPING:rules_python+,pypi__installer rules_python++config+pypi__installer", + "REPO_MAPPING:rules_python+,pypi__more_itertools rules_python++config+pypi__more_itertools", + "REPO_MAPPING:rules_python+,pypi__packaging rules_python++config+pypi__packaging", + "REPO_MAPPING:rules_python+,pypi__pep517 rules_python++config+pypi__pep517", + "REPO_MAPPING:rules_python+,pypi__pip rules_python++config+pypi__pip", + "REPO_MAPPING:rules_python+,pypi__pip_tools rules_python++config+pypi__pip_tools", + "REPO_MAPPING:rules_python+,pypi__pyproject_hooks rules_python++config+pypi__pyproject_hooks", + "REPO_MAPPING:rules_python+,pypi__setuptools rules_python++config+pypi__setuptools", + "REPO_MAPPING:rules_python+,pypi__tomli rules_python++config+pypi__tomli", + "REPO_MAPPING:rules_python+,pypi__wheel rules_python++config+pypi__wheel", + "REPO_MAPPING:rules_python+,pypi__zipp rules_python++config+pypi__zipp" + ], + "generatedRepoSpecs": { + "rules_python_internal": { + "repoRuleId": "@@rules_python+//python/private:internal_config_repo.bzl%internal_config_repo", + "attributes": { + "transition_setting_generators": {}, + "transition_settings": [] } }, - "com_github_jetbrains_kotlin": { - "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:compiler.bzl%kotlin_capabilities_repository", + "pypi__build": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", "attributes": { - "git_repository_name": "com_github_jetbrains_kotlin_git", - "compiler_version": "1.9.23" + "url": "https://files.pythonhosted.org/packages/e2/03/f3c8ba0a6b6e30d7d18c40faab90807c9bb5e9a1e3b2fe2008af624a9c97/build-1.2.1-py3-none-any.whl", + "sha256": "75e10f767a433d9a86e50d83f418e83efc18ede923ee5ff7df93b6cb0306c5d4", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" } }, - "com_github_google_ksp": { - "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:ksp.bzl%ksp_compiler_plugin_repository", + "pypi__click": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", "attributes": { - "urls": [ - "https://github.com/google/ksp/releases/download/1.9.23-1.0.20/artifacts.zip" - ], - "sha256": "ee0618755913ef7fd6511288a232e8fad24838b9af6ea73972a76e81053c8c2d", - "strip_version": "1.9.23-1.0.20" + "url": "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", + "sha256": "ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" } }, - "com_github_pinterest_ktlint": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_file", + "pypi__colorama": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", "attributes": { - "sha256": "01b2e0ef893383a50dbeb13970fe7fa3be36ca3e83259e01649945b09d736985", - "urls": [ - "https://github.com/pinterest/ktlint/releases/download/1.3.0/ktlint" - ], - "executable": true + "url": "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", + "sha256": "4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__importlib_metadata": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/2d/0a/679461c511447ffaf176567d5c496d1de27cbe34a87df6677d7171b2fbd4/importlib_metadata-7.1.0-py3-none-any.whl", + "sha256": "30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__installer": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/e5/ca/1172b6638d52f2d6caa2dd262ec4c811ba59eee96d54a7701930726bce18/installer-0.7.0-py3-none-any.whl", + "sha256": "05d1933f0a5ba7d8d6296bb6d5018e7c94fa473ceb10cf198a92ccea19c27b53", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__more_itertools": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/50/e2/8e10e465ee3987bb7c9ab69efb91d867d93959095f4807db102d07995d94/more_itertools-10.2.0-py3-none-any.whl", + "sha256": "686b06abe565edfab151cb8fd385a05651e1fdf8f0a14191e4439283421f8684", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" } }, - "rules_android": { + "pypi__packaging": { "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", "attributes": { - "sha256": "cd06d15dd8bb59926e4d65f9003bfc20f9da4b2519985c27e190cddc8b7a7806", - "strip_prefix": "rules_android-0.1.1", - "urls": [ - "https://github.com/bazelbuild/rules_android/archive/v0.1.1.zip" - ] + "url": "https://files.pythonhosted.org/packages/49/df/1fceb2f8900f8639e278b056416d49134fb8d84c5942ffaa01ad34782422/packaging-24.0-py3-none-any.whl", + "sha256": "2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__pep517": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/25/6e/ca4a5434eb0e502210f591b97537d322546e4833dcb4d470a48c375c5540/pep517-0.13.1-py3-none-any.whl", + "sha256": "31b206f67165b3536dd577c5c3f1518e8fbaf38cbc57efff8369a392feff1721", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__pip": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/8a/6a/19e9fe04fca059ccf770861c7d5721ab4c2aebc539889e97c7977528a53b/pip-24.0-py3-none-any.whl", + "sha256": "ba0d021a166865d2265246961bec0152ff124de910c5cc39f1156ce3fa7c69dc", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__pip_tools": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/0d/dc/38f4ce065e92c66f058ea7a368a9c5de4e702272b479c0992059f7693941/pip_tools-7.4.1-py3-none-any.whl", + "sha256": "4c690e5fbae2f21e87843e89c26191f0d9454f362d8acdbd695716493ec8b3a9", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__pyproject_hooks": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/ae/f3/431b9d5fe7d14af7a32340792ef43b8a714e7726f1d7b69cc4e8e7a3f1d7/pyproject_hooks-1.1.0-py3-none-any.whl", + "sha256": "7ceeefe9aec63a1064c18d939bdc3adf2d8aa1988a510afec15151578b232aa2", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__setuptools": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/90/99/158ad0609729111163fc1f674a5a42f2605371a4cf036d0441070e2f7455/setuptools-78.1.1-py3-none-any.whl", + "sha256": "c3a9c4211ff4c309edb8b8c4f1cbfa7ae324c4ba9f91ff254e3d305b9fd54561", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__tomli": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl", + "sha256": "939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__wheel": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/7d/cd/d7460c9a869b16c3dd4e1e403cce337df165368c71d6af229a74699622ce/wheel-0.43.0-py3-none-any.whl", + "sha256": "55c570405f142630c6b9f72fe09d9b67cf1477fcf543ae5b8dcb1f5b7377da81", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__zipp": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/da/55/a03fd7240714916507e1fcf7ae355bd9d9ed2e6db492595f1a67f61681be/zipp-3.18.2-py3-none-any.whl", + "sha256": "dce197b859eb796242b0622af1b8beb0a722d52aa2f57133ead08edd5bf5374e", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + } + } + } + }, + "@@rules_python+//python/uv:uv.bzl%uv": { + "general": { + "bzlTransitiveDigest": "ijW9KS7qsIY+yBVvJ+Nr1mzwQox09j13DnE3iIwaeTM=", + "usagesDigest": "H8dQoNZcoqP+Mu0tHZTi4KHATzvNkM5ePuEqoQdklIU=", + "recordedInputs": [ + "REPO_MAPPING:rules_python+,bazel_tools bazel_tools", + "REPO_MAPPING:rules_python+,platforms platforms" + ], + "generatedRepoSpecs": { + "uv": { + "repoRuleId": "@@rules_python+//python/uv/private:uv_toolchains_repo.bzl%uv_toolchains_repo", + "attributes": { + "toolchain_type": "'@@rules_python+//python/uv:uv_toolchain_type'", + "toolchain_names": [ + "none" + ], + "toolchain_implementations": { + "none": "'@@rules_python+//python:none'" + }, + "toolchain_compatible_with": { + "none": [ + "@platforms//:incompatible" + ] + }, + "toolchain_target_settings": {} + } + } + } + } + }, + "@@tar.bzl+//tar:extensions.bzl%toolchains": { + "general": { + "bzlTransitiveDigest": "/2afh6fPjq/rcyE/jztQDK3ierehmFFngfvmqyRv72M=", + "usagesDigest": "maF8qsAIqeH1ey8pxP0gNZbvJt34kLZvTFeQ0ntrJVA=", + "recordedInputs": [], + "generatedRepoSpecs": { + "bsd_tar_toolchains": { + "repoRuleId": "@@tar.bzl+//tar/toolchain:toolchain.bzl%tar_toolchains_repo", + "attributes": { + "user_repository_name": "bsd_tar_toolchains" + } + }, + "bsd_tar_toolchains_darwin_amd64": { + "repoRuleId": "@@tar.bzl+//tar/toolchain:platforms.bzl%bsdtar_binary_repo", + "attributes": { + "platform": "darwin_amd64" + } + }, + "bsd_tar_toolchains_darwin_arm64": { + "repoRuleId": "@@tar.bzl+//tar/toolchain:platforms.bzl%bsdtar_binary_repo", + "attributes": { + "platform": "darwin_arm64" + } + }, + "bsd_tar_toolchains_linux_amd64": { + "repoRuleId": "@@tar.bzl+//tar/toolchain:platforms.bzl%bsdtar_binary_repo", + "attributes": { + "platform": "linux_amd64" + } + }, + "bsd_tar_toolchains_linux_arm64": { + "repoRuleId": "@@tar.bzl+//tar/toolchain:platforms.bzl%bsdtar_binary_repo", + "attributes": { + "platform": "linux_arm64" + } + }, + "bsd_tar_toolchains_windows_amd64": { + "repoRuleId": "@@tar.bzl+//tar/toolchain:platforms.bzl%bsdtar_binary_repo", + "attributes": { + "platform": "windows_amd64" + } + }, + "bsd_tar_toolchains_windows_arm64": { + "repoRuleId": "@@tar.bzl+//tar/toolchain:platforms.bzl%bsdtar_binary_repo", + "attributes": { + "platform": "windows_arm64" } } - }, - "recordedRepoMappingEntries": [ - [ - "rules_kotlin+", - "bazel_tools", - "bazel_tools" - ] + } + } + } + }, + "facts": { + "@@rules_go+//go:extensions.bzl%go_sdk": { + "1.22.4": { + "aix_ppc64": [ + "go1.22.4.aix-ppc64.tar.gz", + "b9647fa9fc83a0cc5d4f092a19eaeaecf45f063a5aa7d4962fde65aeb7ae6ce1" + ], + "darwin_amd64": [ + "go1.22.4.darwin-amd64.tar.gz", + "c95967f50aa4ace34af0c236cbdb49a9a3e80ee2ad09d85775cb4462a5c19ed3" + ], + "darwin_arm64": [ + "go1.22.4.darwin-arm64.tar.gz", + "242b78dc4c8f3d5435d28a0d2cec9b4c1aa999b601fb8aa59fb4e5a1364bf827" + ], + "dragonfly_amd64": [ + "go1.22.4.dragonfly-amd64.tar.gz", + "f2fbb51af4719d3616efb482d6ed2b96579b474156f85a7ddc6f126764feec4b" + ], + "freebsd_386": [ + "go1.22.4.freebsd-386.tar.gz", + "7c54884bb9f274884651d41e61d1bc12738863ad1497e97ea19ad0e9aa6bf7b5" + ], + "freebsd_amd64": [ + "go1.22.4.freebsd-amd64.tar.gz", + "88d44500e1701dd35797619774d6dd51bf60f45a8338b0a82ddc018e4e63fb78" + ], + "freebsd_arm64": [ + "go1.22.4.freebsd-arm64.tar.gz", + "726dc093cf020277be45debf03c3b02b43c2efb3e2a5d4fba8f52579d65327dc" + ], + "freebsd_armv6l": [ + "go1.22.4.freebsd-arm.tar.gz", + "3d9efe47db142a22679aba46b1772e3900b0d87ae13bd2b3bc80dbf2ac0b2cd6" + ], + "freebsd_riscv64": [ + "go1.22.4.freebsd-riscv64.tar.gz", + "5f6b67e5e32f1d6ccb2d4dcb44934a5e2e870a877ba7443d86ec43cfc28afa71" + ], + "illumos_amd64": [ + "go1.22.4.illumos-amd64.tar.gz", + "d56ecc2f85b6418a21ef83879594d0c42ab4f65391a676bb12254870e6690d63" + ], + "linux_386": [ + "go1.22.4.linux-386.tar.gz", + "47a2a8d249a91eb8605c33bceec63aedda0441a43eac47b4721e3975ff916cec" + ], + "linux_amd64": [ + "go1.22.4.linux-amd64.tar.gz", + "ba79d4526102575196273416239cca418a651e049c2b099f3159db85e7bade7d" + ], + "linux_arm64": [ + "go1.22.4.linux-arm64.tar.gz", + "a8e177c354d2e4a1b61020aca3562e27ea3e8f8247eca3170e3fa1e0c2f9e771" + ], + "linux_armv6l": [ + "go1.22.4.linux-armv6l.tar.gz", + "e2b143fbacbc9cbd448e9ef41ac3981f0488ce849af1cf37e2341d09670661de" + ], + "linux_loong64": [ + "go1.22.4.linux-loong64.tar.gz", + "e2ff9436e4b34bf6926b06d97916e26d67a909a2effec17967245900f0816f1d" + ], + "linux_mips": [ + "go1.22.4.linux-mips.tar.gz", + "73f0dcc60458c4770593b05a7bc01cc0d31fc98f948c0c2334812c7a1f2fc3f1" + ], + "linux_mips64": [ + "go1.22.4.linux-mips64.tar.gz", + "417af97fc2630a647052375768be4c38adcc5af946352ea5b28613ea81ca5d45" + ], + "linux_mips64le": [ + "go1.22.4.linux-mips64le.tar.gz", + "7486e2d7dd8c98eb44df815ace35a7fe7f30b7c02326e3741bd934077508139b" + ], + "linux_mipsle": [ + "go1.22.4.linux-mipsle.tar.gz", + "69479c8aad301e459a8365b40cad1074a0dbba5defb9291669f94809c4c4be6e" + ], + "linux_ppc64": [ + "go1.22.4.linux-ppc64.tar.gz", + "dd238847e65bc3e2745caca475a5db6522a2fcf85cf6c38fc36a06642b19efd7" + ], + "linux_ppc64le": [ + "go1.22.4.linux-ppc64le.tar.gz", + "a3e5834657ef92523f570f798fed42f1f87bc18222a16815ec76b84169649ec4" + ], + "linux_riscv64": [ + "go1.22.4.linux-riscv64.tar.gz", + "56a827ff7dc6245bcd7a1e9288dffaa1d8b0fd7468562264c1523daf3b4f1b4a" + ], + "linux_s390x": [ + "go1.22.4.linux-s390x.tar.gz", + "7590c3e278e2dc6040aae0a39da3ca1eb2e3921673a7304cc34d588c45889eec" + ], + "netbsd_386": [ + "go1.22.4.netbsd-386.tar.gz", + "ddd2eebe34471a2502de6c5dad04ab27c9fc80cbde7a9ad5b3c66ecec4504e1d" + ], + "netbsd_amd64": [ + "go1.22.4.netbsd-amd64.tar.gz", + "33af79f6f935f6fbacc5d23876450b3567b79348fc065beef8e64081127dd234" + ], + "netbsd_arm64": [ + "go1.22.4.netbsd-arm64.tar.gz", + "c9a2971dec9f6d320c6f2b049b2353c6d0a2d35e87b8a4b2d78a2f0d62545f8e" + ], + "netbsd_armv6l": [ + "go1.22.4.netbsd-arm.tar.gz", + "fa3550ebd5375a70b3bcd342b5a71f4bd271dcbbfaf4eabefa2144ab5d8924b6" + ], + "openbsd_386": [ + "go1.22.4.openbsd-386.tar.gz", + "d21af022331bfdc2b5b161d616c3a1a4573d33cf7a30416ee509a8f3641deb47" + ], + "openbsd_amd64": [ + "go1.22.4.openbsd-amd64.tar.gz", + "72c0094c43f7e5722ec49c2a3e9dfa7a1123ac43a5f3a63eecf3e3795d3ff0ae" + ], + "openbsd_arm64": [ + "go1.22.4.openbsd-arm64.tar.gz", + "a7ab8d4e0b02bf06ed144ba42c61c0e93ee00f2b433415dfd4ad4b6e79f31650" + ], + "openbsd_armv6l": [ + "go1.22.4.openbsd-arm.tar.gz", + "1096831ea3c5ea3ca57d14251d9eda3786889531eb40d7d6775dcaa324d4b065" + ], + "openbsd_ppc64": [ + "go1.22.4.openbsd-ppc64.tar.gz", + "9716327c8a628358798898dc5148c49dbbeb5196bf2cbf088e550721a6e4f60b" + ], + "plan9_386": [ + "go1.22.4.plan9-386.tar.gz", + "a8dd4503c95c32a502a616ab78870a19889c9325fe9bd31eb16dd69346e4bfa8" + ], + "plan9_amd64": [ + "go1.22.4.plan9-amd64.tar.gz", + "5423a25808d76fe5aca8607a2e5ac5673abf45446b168cb5e9d8519ee9fe39a1" + ], + "plan9_armv6l": [ + "go1.22.4.plan9-arm.tar.gz", + "6af939ad583f5c85c09c53728ab7d38c3cc2b39167562d6c18a07c5c6608b370" + ], + "solaris_amd64": [ + "go1.22.4.solaris-amd64.tar.gz", + "e8cabe69c03085725afdb32a6f9998191a3e55a747b270d835fd05000d56abba" + ], + "windows_386": [ + "go1.22.4.windows-386.zip", + "aca4e2c37278a10f1c70dd0df142f7d66b50334fcee48978d409202d308d6d25" + ], + "windows_amd64": [ + "go1.22.4.windows-amd64.zip", + "26321c4d945a0035d8a5bc4a1965b0df401ff8ceac66ce2daadabf9030419a98" + ], + "windows_arm64": [ + "go1.22.4.windows-arm64.zip", + "8a2daa9ea28cbdafddc6171aefed384f4e5b6e714fb52116fe9ed25a132f37ed" + ], + "windows_armv6l": [ + "go1.22.4.windows-arm.zip", + "5fcd0671a49cecf39b41021621ee1b6e7aa1370f37122b72e80d4fd4185833b6" + ] + }, + "1.25.0": { + "aix_ppc64": [ + "go1.25.0.aix-ppc64.tar.gz", + "e5234a7dac67bc86c528fe9752fc9d63557918627707a733ab4cac1a6faed2d4" + ], + "darwin_amd64": [ + "go1.25.0.darwin-amd64.tar.gz", + "5bd60e823037062c2307c71e8111809865116714d6f6b410597cf5075dfd80ef" + ], + "darwin_arm64": [ + "go1.25.0.darwin-arm64.tar.gz", + "544932844156d8172f7a28f77f2ac9c15a23046698b6243f633b0a0b00c0749c" + ], + "dragonfly_amd64": [ + "go1.25.0.dragonfly-amd64.tar.gz", + "5ed3cf9a810a1483822538674f1336c06b51aa1b94d6d545a1a0319a48177120" + ], + "freebsd_386": [ + "go1.25.0.freebsd-386.tar.gz", + "abea5d5c6697e6b5c224731f2158fe87c602996a2a233ac0c4730cd57bf8374e" + ], + "freebsd_amd64": [ + "go1.25.0.freebsd-amd64.tar.gz", + "86e6fe0a29698d7601c4442052dac48bd58d532c51cccb8f1917df648138730b" + ], + "freebsd_arm": [ + "go1.25.0.freebsd-arm.tar.gz", + "d90b78e41921f72f30e8bbc81d9dec2cff7ff384a33d8d8debb24053e4336bfe" + ], + "freebsd_arm64": [ + "go1.25.0.freebsd-arm64.tar.gz", + "451d0da1affd886bfb291b7c63a6018527b269505db21ce6e14724f22ab0662e" + ], + "freebsd_riscv64": [ + "go1.25.0.freebsd-riscv64.tar.gz", + "7b565f76bd8bda46549eeaaefe0e53b251e644c230577290c0f66b1ecdb3cdbe" + ], + "illumos_amd64": [ + "go1.25.0.illumos-amd64.tar.gz", + "b1e1fdaab1ad25aa1c08d7a36c97d45d74b98b89c3f78c6d2145f77face54a2c" + ], + "linux_386": [ + "go1.25.0.linux-386.tar.gz", + "8c602dd9d99bc9453b3995d20ce4baf382cc50855900a0ece5de9929df4a993a" + ], + "linux_amd64": [ + "go1.25.0.linux-amd64.tar.gz", + "2852af0cb20a13139b3448992e69b868e50ed0f8a1e5940ee1de9e19a123b613" + ], + "linux_arm64": [ + "go1.25.0.linux-arm64.tar.gz", + "05de75d6994a2783699815ee553bd5a9327d8b79991de36e38b66862782f54ae" + ], + "linux_armv6l": [ + "go1.25.0.linux-armv6l.tar.gz", + "a5a8f8198fcf00e1e485b8ecef9ee020778bf32a408a4e8873371bfce458cd09" + ], + "linux_loong64": [ + "go1.25.0.linux-loong64.tar.gz", + "cab86b1cf761b1cb3bac86a8877cfc92e7b036fc0d3084123d77013d61432afc" + ], + "linux_mips": [ + "go1.25.0.linux-mips.tar.gz", + "d66b6fb74c3d91b9829dc95ec10ca1f047ef5e89332152f92e136cf0e2da5be1" + ], + "linux_mips64": [ + "go1.25.0.linux-mips64.tar.gz", + "4082e4381a8661bc2a839ff94ba3daf4f6cde20f8fb771b5b3d4762dc84198a2" + ], + "linux_mips64le": [ + "go1.25.0.linux-mips64le.tar.gz", + "70002c299ec7f7175ac2ef673b1b347eecfa54ae11f34416a6053c17f855afcc" + ], + "linux_mipsle": [ + "go1.25.0.linux-mipsle.tar.gz", + "b00a3a39eff099f6df9f1c7355bf28e4589d0586f42d7d4a394efb763d145a73" + ], + "linux_ppc64": [ + "go1.25.0.linux-ppc64.tar.gz", + "df166f33bd98160662560a72ff0b4ba731f969a80f088922bddcf566a88c1ec1" + ], + "linux_ppc64le": [ + "go1.25.0.linux-ppc64le.tar.gz", + "0f18a89e7576cf2c5fa0b487a1635d9bcbf843df5f110e9982c64df52a983ad0" + ], + "linux_riscv64": [ + "go1.25.0.linux-riscv64.tar.gz", + "c018ff74a2c48d55c8ca9b07c8e24163558ffec8bea08b326d6336905d956b67" + ], + "linux_s390x": [ + "go1.25.0.linux-s390x.tar.gz", + "34e5a2e19f2292fbaf8783e3a241e6e49689276aef6510a8060ea5ef54eee408" + ], + "netbsd_386": [ + "go1.25.0.netbsd-386.tar.gz", + "f8586cdb7aa855657609a5c5f6dbf523efa00c2bbd7c76d3936bec80aa6c0aba" + ], + "netbsd_amd64": [ + "go1.25.0.netbsd-amd64.tar.gz", + "ae8dc1469385b86a157a423bb56304ba45730de8a897615874f57dd096db2c2a" + ], + "netbsd_arm": [ + "go1.25.0.netbsd-arm.tar.gz", + "1ff7e4cc764425fc9dd6825eaee79d02b3c7cafffbb3691687c8d672ade76cb7" + ], + "netbsd_arm64": [ + "go1.25.0.netbsd-arm64.tar.gz", + "e1b310739f26724216aa6d7d7208c4031f9ff54c9b5b9a796ddc8bebcb4a5f16" + ], + "openbsd_386": [ + "go1.25.0.openbsd-386.tar.gz", + "4802a9b20e533da91adb84aab42e94aa56cfe3e5475d0550bed3385b182e69d8" + ], + "openbsd_amd64": [ + "go1.25.0.openbsd-amd64.tar.gz", + "c016cd984bebe317b19a4f297c4f50def120dc9788490540c89f28e42f1dabe1" + ], + "openbsd_arm": [ + "go1.25.0.openbsd-arm.tar.gz", + "a1e31d0bf22172ddde42edf5ec811ef81be43433df0948ece52fecb247ccfd8d" + ], + "openbsd_arm64": [ + "go1.25.0.openbsd-arm64.tar.gz", + "343ea8edd8c218196e15a859c6072d0dd3246fbbb168481ab665eb4c4140458d" + ], + "openbsd_ppc64": [ + "go1.25.0.openbsd-ppc64.tar.gz", + "694c14da1bcaeb5e3332d49bdc2b6d155067648f8fe1540c5de8f3cf8e157154" + ], + "openbsd_riscv64": [ + "go1.25.0.openbsd-riscv64.tar.gz", + "aa510ad25cf54c06cd9c70b6d80ded69cb20188ac6e1735655eef29ff7e7885f" + ], + "plan9_386": [ + "go1.25.0.plan9-386.tar.gz", + "46f8cef02086cf04bf186c5912776b56535178d4cb319cd19c9fdbdd29231986" + ], + "plan9_amd64": [ + "go1.25.0.plan9-amd64.tar.gz", + "29b34391d84095e44608a228f63f2f88113a37b74a79781353ec043dfbcb427b" + ], + "plan9_arm": [ + "go1.25.0.plan9-arm.tar.gz", + "0a047107d13ebe7943aaa6d54b1d7bbd2e45e68ce449b52915a818da715799c2" + ], + "solaris_amd64": [ + "go1.25.0.solaris-amd64.tar.gz", + "9977f9e4351984364a3b2b78f8b88bfd1d339812356d5237678514594b7d3611" + ], + "windows_386": [ + "go1.25.0.windows-386.zip", + "df9f39db82a803af0db639e3613a36681ab7a42866b1384b3f3a1045663961a7" + ], + "windows_amd64": [ + "go1.25.0.windows-amd64.zip", + "89efb4f9b30812eee083cc1770fdd2913c14d301064f6454851428f9707d190b" + ], + "windows_arm64": [ + "go1.25.0.windows-arm64.zip", + "27bab004c72b3d7bd05a69b6ec0fc54a309b4b78cc569dd963d8b3ec28bfdb8c" ] } } diff --git a/README.md b/README.md index a6e573c..4c0e136 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,263 @@ -# phaser -Zero-copy protobuf transport +# Phaser -This is a Google Protocol Buffers (a.k.a. `protobuf`) compiler plugin to generate zero-copy -protobuf messages for the best performance in your robot. +**Zero-copy Protocol Buffers for C++ — no serialization required.** -Please refer to [the user guide](phaser/docs/phaser_user_guide.md) +Phaser is a [Protocol Buffers](https://protobuf.dev) (`protobuf`) compiler plugin that +generates C++ message classes whose data lives directly in a memory buffer, in +wire-format, instead of in a tree of heap-allocated objects. Once a message is built, +it can be written to disk, placed in shared memory, or sent over an IPC system **without +a serialization step** — the bytes in the buffer *are* the message. +The generated API is intentionally almost identical to the standard protobuf C++ API, so +if you know protobuf, you already know Phaser. + +> 📖 For the full reference, see the **[Phaser User Guide](phaser/docs/phaser_user_guide.md)**. + +--- + +## Why Phaser? + +Every program that uses classic protobuf follows the same pattern: build messages as heap +(or arena) objects, **serialize** them into a buffer, send the buffer, then **deserialize** +on the other side. For small messages the conversion cost is negligible — but it isn't +always small: + +- **Serialization can dominate the CPU.** In data-heavy domains like robotics and + autonomous vehicles, messages carry large payloads (LIDAR scans, camera frames), and + serialization/deserialization can consume well over half of available CPU. Google has + estimated serialization at ~30% of CPU time across its data centers. +- **Deserializing messages you never read is pure waste.** Many real-time systems only act + on the most recent message and drop the rest — every message deserialized and discarded + is wasted work. +- **Shared-memory IPC makes serialization redundant.** When subscribers can read the same + physical memory directly, serializing into shared memory just to deserialize it again in + every reader burns cycles for no benefit. + +Phaser removes that overhead. By writing values directly into their final location in the +buffer, message construction can be **up to an order of magnitude faster**, and reading +costs nothing until you actually touch a field. + +### Benefits at a glance + +- **No serialization / deserialization** on the hot path — the buffer is ready to send. +- **Direct buffer access** for bulk data: write straight into a vector's backing store, + or allocate many sub-messages in a single allocation. +- **Works in shared memory** and other externally-provided buffers (fixed or resizable). +- **Protobuf version compatibility** is preserved — old and new message versions + interoperate via per-message field metadata. +- **Protobuf wire-format transcoding** is available when you *do* need it (e.g. storing in + systems like BigQuery that expect protobuf bytes). +- **Familiar API** — same accessor names as protobuf, with extra zero-copy helpers. + +## Features + +1. proto3 (primary) and proto2 IDL support +2. Message printing to `std::ostream` +3. Fixed- and variable-sized buffers +4. User-supplied per-buffer metadata +5. Full `google.protobuf.Any` support (zero-copy) +6. Enum printing and parsing +7. Message reflection +8. Field presence masks +9. Bazel build integration +10. Modern C++17 with [Abseil](https://abseil.io) + +## How it works + +Phaser runs as a plugin to `protoc`. `protoc` parses your `.proto` files and hands the +descriptors to Phaser, which emits C++ (`*.phaser.h` / `*.phaser.cc`). + +The key idea is the split between two representations: + +- The **source message** is the C++ object your code interacts with. It is lightweight — + it holds only a pointer to the runtime and an offset, *not* the field values. It can live + on the stack, the heap, anywhere. +- The **binary message** is the actual data, stored in wire-format inside a relocatable + `PayloadBuffer`. + +``` + Your code Source message PayloadBuffer (the bytes you send) + ┌──────────┐ ┌───────────────┐ ┌─────────────────────────────────┐ + │ set_x(7) │ ──────► │ offset + rt │ ──────► │ [header][metadata][fields...] │ + │ x() │ ◄────── │ (no data!) │ ◄────── │ x = 7 ... │ + └──────────┘ └───────────────┘ └─────────────────────────────────┘ +``` + +- When you call `set_x(...)`, the value is written **directly into the binary buffer**. +- When you call `x()`, the value is read back **from the binary buffer**, located via a + small per-message **field-metadata** array. That indirection is what enables protobuf's + version compatibility: a reader built with a different schema version can still find the + fields present in the data. + +The `PayloadBuffer` (from the [cpp_toolbelt](https://github.com/dallison/cpp_toolbelt) +library) is a relocatable heap — a malloc/free/realloc allocator that uses only offsets +(never raw pointers), so the whole buffer can be copied or moved anywhere. It offers a fast +**bitmap allocator** for small blocks (performance mode, the default) and a **free-list +allocator** that trades speed for compactness (size mode), selectable via +`::phaser::Tuning`. + +## Quick start + +### 1. Add a `phaser_library` to your build + +Phaser integrates with Bazel through the `phaser_library` rule. Point it at a standard +`proto_library`, much like you would a `cc_proto_library`: + +```python +load("@phaser//phaser:phaser_library.bzl", "phaser_library") + +proto_library( + name = "foo_proto", + srcs = ["Foo.proto"], +) + +phaser_library( + name = "foo_phaser", + add_namespace = "phaser", # optional: avoids clashing with protobuf classes + deps = [":foo_proto"], +) +``` + +If `Foo.proto` is in package `foo.bar`, the generated classes live in +`::foo::bar::phaser` (when `add_namespace = "phaser"`), and you include them as you would +any protobuf header: + +```c++ +#include "foo/bar/Foo.phaser.h" +``` + +### 2. Create and use a message + +Creating a message looks just like protobuf — the binary data is backed by a dynamic +buffer allocated from the heap that grows as needed: + +```c++ +foo::bar::phaser::TestMessage msg; // optional: TestMessage msg(initial_size, tuning); +msg.set_x(1234); + +// The buffer is ready to send — no serialize step. +SendMessage(msg.Data(), msg.ByteSizeLong()); +``` + +Build directly inside an externally-provided buffer (e.g. shared memory from an IPC system): + +```c++ +auto msg = foo::bar::phaser::TestMessage::CreateMutable(buffer, size); +msg.set_x(1234); +``` + +Read a message received in a read-only buffer (all field access is bounds-checked against +the buffer you provide): + +```c++ +auto msg = foo::bar::phaser::TestMessage::CreateReadonly(buffer, size); +int x = msg.x(); +``` + +### 3. Zero-copy field access + +Beyond the standard protobuf accessors, Phaser adds helpers that hand you the final storage +location so you can skip intermediate copies: + +```c++ +// Strings/bytes: allocate space and write straight into it. +absl::Span dst = msg.allocate_s(len); + +// Repeated primitives: get a mutable span over the backing store. +msg.resize_vi32(n); +absl::Span data = msg.vi32_as_mutable_span(); + +// Repeated messages: allocate many at once (one allocation). +std::vector items = msg.allocate_vm(n); +``` + +## Protobuf interoperability + +Phaser's native layout is *not* protobuf wire-format, but full transcoding is provided for +when you need to interoperate with protobuf-based systems: + +```c++ +size_t SerializedSize() const; +bool SerializeToArray(char* array, size_t size) const; +bool ParseFromArray(const char* array, size_t size); +bool SerializeToString(std::string* str) const; +std::string SerializeAsString() const; +bool ParseFromString(const std::string& str); +``` + +`google.protobuf.Any` is supported with zero-copy semantics: the `value` field holds a real +binary message you can access directly (via `Is()` / `As()` / `MutableAny()`), with +`PackFrom` / `UnpackTo` provided for protobuf-compatible copying. + +## The Phaser Bank (type-erased operations & reflection) + +The **Phaser Bank** lets you operate on messages given only their type *name* — stream, +clear, copy, transcode, allocate, and reflect over fields — without compile-time knowledge +of the type. Message libraries register themselves via static initializers, so they just +need to be linked in: + +```c++ +absl::StatusOr present = + ::phaser::PhaserBankHasField("foo.bar.TestMessage", msg, 100); + +auto field = ::phaser::PhaserBankGetFieldByNumber<::phaser::Int32Field<>>( + "foo.bar.TestMessage", msg, 100); +int value = (*field)->Get(); +``` + +See the user guide's *Phaser Bank* and *Message information* sections for the full surface +(reflection, `MessageInfo`/`FieldInfo`, protobuf transcoding helpers, etc.). + +## Building from source + +Phaser uses [Bazel](https://bazel.build) (with Bzlmod) and is developed against the version +pinned in [`.bazelversion`](.bazelversion). Dependencies (Abseil, protobuf, cpp_toolbelt, +GoogleTest, …) are declared in [`MODULE.bazel`](MODULE.bazel). + +```bash +# Build everything +bazelisk build //phaser/... + +# Run the tests +bazelisk test //phaser/... + +# AddressSanitizer build/test (see .bazelrc for the asan config) +bazelisk test --config=asan //phaser/... +``` + +On Apple Silicon, the `asan` config already pulls in the native `apple_silicon` settings; +see [`.bazelrc`](.bazelrc) for the available configurations. + +### Using Phaser without Bazel + +Phaser is a `protoc` plugin, so any build system can drive it as long as the plugin binary +and dependencies are available: + +```bash +protoc --plugin=protoc-gen-phaser=DIR/bin/phaser/compiler/phaser \ + --phaser_out=add_namespace=NS,package_name=PACKAGE,target_name=TARGET:OUTPUT_DIR \ + -I IPATH \ + FILE... +``` + +Output is written to `OUTPUT_DIR/PACKAGE/TARGET`. See the user guide for the full argument +reference. + +## Project layout + +| Path | Description | +| --- | --- | +| `phaser/compiler/` | The `protoc` plugin that generates C++ code (`gen`, `enum_gen`, `message_gen`, `main`). | +| `phaser/runtime/` | The runtime library: `Message`, fields, vectors, unions, wire-format, the Phaser Bank, and `PayloadBuffer` glue. | +| `phaser/phaser_library.bzl` | The `phaser_library` Bazel rule and supporting aspect. | +| `phaser/testdata/` | Example `.proto` files used by the tests. | +| `phaser/docs/` | Reference documentation (the user guide). | + +## Documentation + +The complete reference — message layout, buffer internals, the allocator, reflection, the +Phaser Bank, and more — is in the **[Phaser User Guide](phaser/docs/phaser_user_guide.md)**. + +## License + +Phaser is licensed under the [Apache License 2.0](LICENSE). diff --git a/phaser/BUILD.bazel b/phaser/BUILD.bazel index e92c49f..cdc0354 100644 --- a/phaser/BUILD.bazel +++ b/phaser/BUILD.bazel @@ -1,31 +1,84 @@ +load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test") +load("//phaser:copts.bzl", "PHASER_COPTS") + package(default_visibility = ["//visibility:public"]) +exports_files(["valgrind.supp"]) + +cc_library( + name = "test_helpers", + hdrs = ["test_helpers.h"], + deps = [ + "@com_google_absl//absl/status", + ], +) + cc_test( name = "phaser_test", srcs = [ "phaser_test.cc", ], + copts = PHASER_COPTS, + data = ["valgrind.supp"], deps = [ + ":test_helpers", "//phaser/runtime:phaser_runtime", "//phaser/testdata:test_message_cc_proto", "//phaser/testdata:test_message_phaser", "@com_google_absl//absl/strings:str_format", "@com_google_googletest//:gtest", - "@toolbelt//toolbelt", + "@cpp_toolbelt//toolbelt", + ], +) + +cc_test( + name = "all_types_test", + srcs = ["all_types_test.cc"], + copts = PHASER_COPTS, + data = ["valgrind.supp"], + deps = [ + ":test_helpers", + "//phaser/runtime:phaser_runtime", + "//phaser/testdata:coverage_cc_proto", + "//phaser/testdata:coverage_phaser", + "@com_google_absl//absl/status", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "stress_test", + size = "medium", + timeout = "moderate", + srcs = ["stress_test.cc"], + copts = PHASER_COPTS, + data = ["valgrind.supp"], + deps = [ + ":test_helpers", + "//phaser/runtime:phaser_runtime", + "//phaser/testdata:test_message_phaser", + "@com_google_absl//absl/status", + "@com_google_googletest//:gtest_main", ], ) cc_test( name = "perf_test", + size = "large", + timeout = "long", srcs = [ "perf_test.cc", ], + # Performance benchmark; excluded from wildcard test runs (e.g. //...). + # Run explicitly with `bazel test //phaser:perf_test`. + tags = ["manual"], + copts = PHASER_COPTS, deps = [ "//phaser/runtime:phaser_runtime", "//phaser/testdata:vision_cc_proto", "//phaser/testdata:vision_phaser", "@com_google_absl//absl/strings:str_format", "@com_google_googletest//:gtest", - "@toolbelt//toolbelt", + "@cpp_toolbelt//toolbelt", ], ) diff --git a/phaser/all_types_test.cc b/phaser/all_types_test.cc new file mode 100644 index 0000000..2762b20 --- /dev/null +++ b/phaser/all_types_test.cc @@ -0,0 +1,980 @@ +// Copyright 2024-2026 David Allison +// All Rights Reserved +// See LICENSE file for licensing information. + +#include "test_helpers.h" +#include "phaser/runtime/wireformat.h" +#include "phaser/testdata/coverage.pb.h" +#include "phaser/testdata/coverage.phaser.h" +#include "google/protobuf/any.pb.h" +#include +#include +#include + +namespace foo::bar::coverage::phaser { +namespace { + +void FillAllScalars(AllScalars &msg) { + msg.set_f_int32(-123456); + msg.set_f_int64(-9876543210LL); + msg.set_f_sint32(-42); + msg.set_f_sint64(-84); + msg.set_f_uint32(0xabcdef01u); + msg.set_f_uint64(0x123456789abcdef0ULL); + msg.set_f_fixed32(0x11223344u); + msg.set_f_fixed64(0x5566778899aabbccULL); + msg.set_f_sfixed32(-77); + msg.set_f_sfixed64(-88); + msg.set_f_float(1.25f); + msg.set_f_double(2.5); + msg.set_f_bool(true); + msg.set_f_string("scalar-string"); + msg.set_f_bytes(::phaser::test::MakePatternBytes(128)); + msg.set_f_enum(COV_BAR); +} + +void ExpectAllScalars(const AllScalars &msg) { + EXPECT_EQ(-123456, msg.f_int32()); + EXPECT_EQ(-9876543210LL, msg.f_int64()); + EXPECT_EQ(-42, msg.f_sint32()); + EXPECT_EQ(-84, msg.f_sint64()); + EXPECT_EQ(0xabcdef01u, msg.f_uint32()); + EXPECT_EQ(0x123456789abcdef0ULL, msg.f_uint64()); + EXPECT_EQ(0x11223344u, msg.f_fixed32()); + EXPECT_EQ(0x5566778899aabbccULL, msg.f_fixed64()); + EXPECT_EQ(-77, msg.f_sfixed32()); + EXPECT_EQ(-88, msg.f_sfixed64()); + EXPECT_FLOAT_EQ(1.25f, msg.f_float()); + EXPECT_DOUBLE_EQ(2.5, msg.f_double()); + EXPECT_TRUE(msg.f_bool()); + EXPECT_EQ("scalar-string", msg.f_string()); + EXPECT_EQ(::phaser::test::MakePatternBytes(128), msg.f_bytes()); + EXPECT_EQ(COV_BAR, msg.f_enum()); +} + +using PbAllScalars = ::foo::bar::coverage::AllScalars; +using PbMapHolder = ::foo::bar::coverage::MapHolder; +using PbRepeatedPacked = ::foo::bar::coverage::RepeatedPrimitivesPacked; +using PbRepeatedUnpacked = ::foo::bar::coverage::RepeatedPrimitivesUnpacked; +using PbRepeatedStrings = ::foo::bar::coverage::RepeatedStrings; +using PbRepeatedBytes = ::foo::bar::coverage::RepeatedBytes; +using PbRepeatedMessages = ::foo::bar::coverage::RepeatedMessages; +using PbOneofStress = ::foo::bar::coverage::OneofStress; +using PbCoverageInner = ::foo::bar::coverage::CoverageInner; +using PbImportsMessage = ::foo::bar::coverage::ImportsMessage; + +void ExpectAllScalarsMatch(const AllScalars &a, const AllScalars &b) { + EXPECT_EQ(a.f_int32(), b.f_int32()); + EXPECT_EQ(a.f_int64(), b.f_int64()); + EXPECT_EQ(a.f_sint32(), b.f_sint32()); + EXPECT_EQ(a.f_sint64(), b.f_sint64()); + EXPECT_EQ(a.f_uint32(), b.f_uint32()); + EXPECT_EQ(a.f_uint64(), b.f_uint64()); + EXPECT_EQ(a.f_fixed32(), b.f_fixed32()); + EXPECT_EQ(a.f_fixed64(), b.f_fixed64()); + EXPECT_EQ(a.f_sfixed32(), b.f_sfixed32()); + EXPECT_EQ(a.f_sfixed64(), b.f_sfixed64()); + EXPECT_FLOAT_EQ(a.f_float(), b.f_float()); + EXPECT_DOUBLE_EQ(a.f_double(), b.f_double()); + EXPECT_EQ(a.f_bool(), b.f_bool()); + EXPECT_EQ(a.f_string(), b.f_string()); + EXPECT_EQ(a.f_bytes(), b.f_bytes()); + EXPECT_EQ(a.f_enum(), b.f_enum()); +} + +void ExpectAllScalarsMatchPb(const PbAllScalars &pb, const AllScalars &msg) { + EXPECT_EQ(pb.f_int32(), msg.f_int32()); + EXPECT_EQ(pb.f_int64(), msg.f_int64()); + EXPECT_EQ(pb.f_sint32(), msg.f_sint32()); + EXPECT_EQ(pb.f_sint64(), msg.f_sint64()); + EXPECT_EQ(pb.f_uint32(), msg.f_uint32()); + EXPECT_EQ(pb.f_uint64(), msg.f_uint64()); + EXPECT_EQ(pb.f_fixed32(), msg.f_fixed32()); + EXPECT_EQ(pb.f_fixed64(), msg.f_fixed64()); + EXPECT_EQ(pb.f_sfixed32(), msg.f_sfixed32()); + EXPECT_EQ(pb.f_sfixed64(), msg.f_sfixed64()); + EXPECT_FLOAT_EQ(pb.f_float(), msg.f_float()); + EXPECT_DOUBLE_EQ(pb.f_double(), msg.f_double()); + EXPECT_EQ(pb.f_bool(), msg.f_bool()); + EXPECT_EQ(pb.f_string(), msg.f_string()); + EXPECT_EQ(pb.f_bytes(), msg.f_bytes()); + EXPECT_EQ(pb.f_enum(), static_cast(msg.f_enum())); +} + +void FillAllScalars(PbAllScalars &msg) { + msg.set_f_int32(-123456); + msg.set_f_int64(-9876543210LL); + msg.set_f_sint32(-42); + msg.set_f_sint64(-84); + msg.set_f_uint32(0xabcdef01u); + msg.set_f_uint64(0x123456789abcdef0ULL); + msg.set_f_fixed32(0x11223344u); + msg.set_f_fixed64(0x5566778899aabbccULL); + msg.set_f_sfixed32(-77); + msg.set_f_sfixed64(-88); + msg.set_f_float(1.25f); + msg.set_f_double(2.5); + msg.set_f_bool(true); + msg.set_f_string("scalar-string"); + msg.set_f_bytes(::phaser::test::MakePatternBytes(128)); + msg.set_f_enum(::foo::bar::coverage::COV_BAR); +} + +void FillCoverageInner(PbCoverageInner &msg) { + msg.set_str("inner"); + msg.set_f(0x0123456789abcdefULL); +} + +// Phaser -> protobuf wire -> phaser, then protobuf -> wire -> phaser. +template +void ExpectBidirectionalWireRoundTrip(FillPhaser fill_phaser, FillPb fill_pb, + ExpectPhaserMatch expect_phaser, + ExpectPbMatch expect_pb) { + PhaserMsg phaser; + fill_phaser(phaser); + + std::string phaser_wire; + ASSERT_TRUE(phaser.SerializeToString(&phaser_wire)); + PbMsg pb_from_phaser; + ASSERT_TRUE(pb_from_phaser.ParseFromString(phaser_wire)); + expect_pb(pb_from_phaser, phaser); + + PhaserMsg phaser_from_pb; + std::string pb_wire; + ASSERT_TRUE(pb_from_phaser.SerializeToString(&pb_wire)); + ASSERT_TRUE(phaser_from_pb.ParseFromString(pb_wire)); + expect_phaser(phaser, phaser_from_pb); + expect_pb(pb_from_phaser, phaser_from_pb); + + PbMsg pb_orig; + fill_pb(pb_orig); + std::string pb_orig_wire; + ASSERT_TRUE(pb_orig.SerializeToString(&pb_orig_wire)); + PhaserMsg phaser_from_orig_pb; + ASSERT_TRUE(phaser_from_orig_pb.ParseFromString(pb_orig_wire)); + expect_phaser(phaser, phaser_from_orig_pb); + + std::string phaser_roundtrip_wire; + ASSERT_TRUE(phaser_from_orig_pb.SerializeToString(&phaser_roundtrip_wire)); + PhaserMsg phaser_roundtrip; + ASSERT_TRUE(phaser_roundtrip.ParseFromString(phaser_roundtrip_wire)); + expect_phaser(phaser_from_orig_pb, phaser_roundtrip); + expect_pb(pb_orig, phaser_roundtrip); +} + +void FillMapHolder(MapHolder &msg) { + auto *a = msg.add_values(); + a->set_key("alpha"); + a->set_value(-7); + auto *b = msg.add_values(); + b->set_key("beta"); + b->set_value(42); +} + +void ExpectMapHolderMatch(const MapHolder &a, const MapHolder &b) { + ASSERT_EQ(a.values_size(), b.values_size()); + for (size_t i = 0; i < a.values_size(); i++) { + bool found = false; + for (size_t j = 0; j < b.values_size(); j++) { + if (a.values(i).key() == b.values(j).key()) { + EXPECT_EQ(a.values(i).value(), b.values(j).value()); + found = true; + break; + } + } + EXPECT_TRUE(found) << "key missing in other map: " << a.values(i).key(); + } +} + +void ExpectMapHolderMatchPb(const PbMapHolder &pb, const MapHolder &msg) { + ASSERT_EQ(static_cast(msg.values_size()), pb.values_size()); + for (size_t i = 0; i < msg.values_size(); i++) { + const auto it = pb.values().find(msg.values(i).key()); + ASSERT_NE(pb.values().end(), it); + EXPECT_EQ(it->second, msg.values(i).value()); + } +} + +void FillRepeatedPacked(RepeatedPrimitivesPacked &msg) { + for (int i = -3; i < 10; i++) { + msg.add_vi32(i); + msg.add_vi64(i * 1000LL); + msg.add_vf64(static_cast(i * 100000ULL)); + } +} + +void ExpectRepeatedPackedMatch(const RepeatedPrimitivesPacked &a, + const RepeatedPrimitivesPacked &b) { + ASSERT_EQ(a.vi32_size(), b.vi32_size()); + ASSERT_EQ(a.vi64_size(), b.vi64_size()); + ASSERT_EQ(a.vf64_size(), b.vf64_size()); + for (size_t i = 0; i < a.vi32_size(); i++) { + EXPECT_EQ(a.vi32(i), b.vi32(i)); + EXPECT_EQ(a.vi64(i), b.vi64(i)); + EXPECT_EQ(a.vf64(i), b.vf64(i)); + } +} + +void ExpectRepeatedPackedMatchPb(const PbRepeatedPacked &pb, + const RepeatedPrimitivesPacked &msg) { + ASSERT_EQ(pb.vi32_size(), static_cast(msg.vi32_size())); + ASSERT_EQ(pb.vi64_size(), static_cast(msg.vi64_size())); + ASSERT_EQ(pb.vf64_size(), static_cast(msg.vf64_size())); + for (int i = 0; i < pb.vi32_size(); i++) { + EXPECT_EQ(pb.vi32(i), msg.vi32(i)); + EXPECT_EQ(pb.vi64(i), msg.vi64(i)); + EXPECT_EQ(pb.vf64(i), msg.vf64(i)); + } +} + +void FillRepeatedUnpacked(RepeatedPrimitivesUnpacked &msg) { + for (int i = -5; i < 5; i++) { + msg.add_vi32(i * 11); + } +} + +void ExpectRepeatedUnpackedMatch(const RepeatedPrimitivesUnpacked &a, + const RepeatedPrimitivesUnpacked &b) { + ASSERT_EQ(a.vi32_size(), b.vi32_size()); + for (size_t i = 0; i < a.vi32_size(); i++) { + EXPECT_EQ(a.vi32(i), b.vi32(i)); + } +} + +void ExpectRepeatedUnpackedMatchPb(const PbRepeatedUnpacked &pb, + const RepeatedPrimitivesUnpacked &msg) { + ASSERT_EQ(pb.vi32_size(), static_cast(msg.vi32_size())); + for (int i = 0; i < pb.vi32_size(); i++) { + EXPECT_EQ(pb.vi32(i), msg.vi32(i)); + } +} + +void ExpectRepeatedUnpackedMatchPb(const PbRepeatedUnpacked &a, + const PbRepeatedUnpacked &b) { + ASSERT_EQ(a.vi32_size(), b.vi32_size()); + for (int i = 0; i < a.vi32_size(); i++) { + EXPECT_EQ(a.vi32(i), b.vi32(i)); + } +} + +void FillRepeatedStrings(RepeatedStrings &msg) { + msg.add_vstr(""); + msg.add_vstr(::phaser::test::MakePatternString(64, 's')); + msg.add_vstr("line\nbreak\t"); +} + +void ExpectRepeatedStringsMatch(const RepeatedStrings &a, + const RepeatedStrings &b) { + ASSERT_EQ(a.vstr_size(), b.vstr_size()); + for (size_t i = 0; i < a.vstr_size(); i++) { + EXPECT_EQ(a.vstr(i), b.vstr(i)); + } +} + +void ExpectRepeatedStringsMatchPb(const PbRepeatedStrings &pb, + const RepeatedStrings &msg) { + ASSERT_EQ(pb.vstr_size(), static_cast(msg.vstr_size())); + for (int i = 0; i < pb.vstr_size(); i++) { + EXPECT_EQ(pb.vstr(i), msg.vstr(i)); + } +} + +void FillRepeatedBytes(RepeatedBytes &msg) { + msg.add_vbytes(::phaser::test::MakePatternBytes(32)); + msg.add_vbytes(std::string("\0\x01\xff", 3)); +} + +void ExpectRepeatedBytesMatch(const RepeatedBytes &a, const RepeatedBytes &b) { + ASSERT_EQ(a.vbytes_size(), b.vbytes_size()); + for (size_t i = 0; i < a.vbytes_size(); i++) { + EXPECT_EQ(a.vbytes(i), b.vbytes(i)); + } +} + +void ExpectRepeatedBytesMatchPb(const PbRepeatedBytes &pb, + const RepeatedBytes &msg) { + ASSERT_EQ(pb.vbytes_size(), static_cast(msg.vbytes_size())); + for (int i = 0; i < pb.vbytes_size(); i++) { + EXPECT_EQ(pb.vbytes(i), msg.vbytes(i)); + } +} + +void FillRepeatedMessages(RepeatedMessages &msg) { + auto *m0 = msg.add_items(); + FillAllScalars(*m0); + auto *m1 = msg.add_items(); + m1->set_f_int32(99); + m1->set_f_string("nested"); +} + +void ExpectRepeatedMessagesMatch(const RepeatedMessages &a, + const RepeatedMessages &b) { + ASSERT_EQ(a.items_size(), b.items_size()); + for (size_t i = 0; i < a.items_size(); i++) { + ExpectAllScalarsMatch(a.items(i), b.items(i)); + } +} + +void ExpectRepeatedMessagesMatchPb(const PbRepeatedMessages &pb, + const RepeatedMessages &msg) { + ASSERT_EQ(pb.items_size(), static_cast(msg.items_size())); + for (int i = 0; i < pb.items_size(); i++) { + ExpectAllScalarsMatchPb(pb.items(i), msg.items(i)); + } +} + +void FillCoverageInner(CoverageInner &msg) { + msg.set_str("inner"); + msg.set_f(0x0123456789abcdefULL); +} + +void ExpectCoverageInnerMatch(const CoverageInner &a, const CoverageInner &b) { + EXPECT_EQ(a.str(), b.str()); + EXPECT_EQ(a.f(), b.f()); +} + +void ExpectCoverageInnerMatchPb(const PbCoverageInner &pb, + const CoverageInner &msg) { + EXPECT_EQ(pb.str(), msg.str()); + EXPECT_EQ(pb.f(), msg.f()); +} + +void ExpectCoverageInnerMatchPb(const PbCoverageInner &a, + const PbCoverageInner &b) { + EXPECT_EQ(a.str(), b.str()); + EXPECT_EQ(a.f(), b.f()); +} + +void FillImportsMessage(ImportsMessage &msg, bool include_any = true) { + msg.mutable_imported_foo()->set_a(-3); + msg.mutable_imported_foo()->set_b("wire-foo"); + FillCoverageInner(*msg.mutable_inner()); + msg.mutable_timestamp()->set_seconds(1700000001); + msg.mutable_timestamp()->set_nanos(456); + msg.mutable_wrapped_string()->set_value("wrapped-wire"); + msg.mutable_empty_msg(); + if (include_any) { + CoverageInner packed; + FillCoverageInner(packed); + ASSERT_TRUE(msg.mutable_any_field()->PackFrom(packed)); + } +} + +void FillImportsMessage(PbImportsMessage &msg, bool include_any = true) { + msg.mutable_imported_foo()->set_a(-3); + msg.mutable_imported_foo()->set_b("wire-foo"); + FillCoverageInner(*msg.mutable_inner()); + msg.mutable_timestamp()->set_seconds(1700000001); + msg.mutable_timestamp()->set_nanos(456); + msg.mutable_wrapped_string()->set_value("wrapped-wire"); + msg.mutable_empty_msg(); + if (include_any) { + PbCoverageInner packed; + FillCoverageInner(packed); + msg.mutable_any_field()->PackFrom(packed); + } +} + +void FillMapHolder(PbMapHolder &msg) { + (*msg.mutable_values())["alpha"] = -7; + (*msg.mutable_values())["beta"] = 42; +} + +void FillRepeatedPacked(PbRepeatedPacked &msg) { + for (int i = -3; i < 10; i++) { + msg.add_vi32(i); + msg.add_vi64(i * 1000LL); + msg.add_vf64(static_cast(i * 100000ULL)); + } +} + +void FillRepeatedUnpacked(PbRepeatedUnpacked &msg) { + for (int i = -5; i < 5; i++) { + msg.add_vi32(i * 11); + } +} + +void FillRepeatedStrings(PbRepeatedStrings &msg) { + msg.add_vstr(""); + msg.add_vstr(::phaser::test::MakePatternString(64, 's')); + msg.add_vstr("line\nbreak\t"); +} + +void FillRepeatedBytes(PbRepeatedBytes &msg) { + msg.add_vbytes(::phaser::test::MakePatternBytes(32)); + msg.add_vbytes(std::string("\0\x01\xff", 3)); +} + +void FillRepeatedMessages(PbRepeatedMessages &msg) { + FillAllScalars(*msg.add_items()); + auto *m1 = msg.add_items(); + m1->set_f_int32(99); + m1->set_f_string("nested"); +} + +void ExpectImportsMessageMatch(const ImportsMessage &a, const ImportsMessage &b, + bool check_any = true) { + EXPECT_EQ(a.imported_foo().a(), b.imported_foo().a()); + EXPECT_EQ(a.imported_foo().b(), b.imported_foo().b()); + ExpectCoverageInnerMatch(a.inner(), b.inner()); + EXPECT_EQ(a.timestamp().seconds(), b.timestamp().seconds()); + EXPECT_EQ(a.timestamp().nanos(), b.timestamp().nanos()); + EXPECT_EQ(a.wrapped_string().value(), b.wrapped_string().value()); + if (!check_any) { + return; + } + CoverageInner inner_a; + CoverageInner inner_b; + ASSERT_TRUE(a.any_field().UnpackTo(&inner_a)); + ASSERT_TRUE(b.any_field().UnpackTo(&inner_b)); + ExpectCoverageInnerMatch(inner_a, inner_b); +} + +void ExpectImportsMessageMatchPb(const PbImportsMessage &pb, + const ImportsMessage &msg, + bool check_any = true) { + EXPECT_EQ(pb.imported_foo().a(), msg.imported_foo().a()); + EXPECT_EQ(pb.imported_foo().b(), msg.imported_foo().b()); + ExpectCoverageInnerMatchPb(pb.inner(), msg.inner()); + EXPECT_EQ(pb.timestamp().seconds(), msg.timestamp().seconds()); + EXPECT_EQ(pb.timestamp().nanos(), msg.timestamp().nanos()); + EXPECT_EQ(pb.wrapped_string().value(), msg.wrapped_string().value()); + if (!check_any) { + return; + } + CoverageInner inner_msg; + ASSERT_TRUE(msg.any_field().UnpackTo(&inner_msg)); + PbCoverageInner inner_pb; + ASSERT_TRUE(pb.any_field().UnpackTo(&inner_pb)); + ExpectCoverageInnerMatchPb(inner_pb, inner_msg); +} + +void ExpectImportsMessageMatchPb(const PbImportsMessage &a, + const PbImportsMessage &b) { + EXPECT_EQ(a.imported_foo().a(), b.imported_foo().a()); + EXPECT_EQ(a.imported_foo().b(), b.imported_foo().b()); + EXPECT_EQ(a.inner().str(), b.inner().str()); + EXPECT_EQ(a.inner().f(), b.inner().f()); + EXPECT_EQ(a.timestamp().seconds(), b.timestamp().seconds()); + EXPECT_EQ(a.timestamp().nanos(), b.timestamp().nanos()); + EXPECT_EQ(a.wrapped_string().value(), b.wrapped_string().value()); + PbCoverageInner inner_a; + PbCoverageInner inner_b; + ASSERT_TRUE(a.any_field().UnpackTo(&inner_a)); + ASSERT_TRUE(b.any_field().UnpackTo(&inner_b)); + EXPECT_EQ(inner_a.str(), inner_b.str()); + EXPECT_EQ(inner_a.f(), inner_b.f()); +} + +template +void ExpectOneofWireRoundTrip(FillOneof fill, ExpectPhaserOneof expect_phaser, + ExpectPbOneof expect_pb) { + OneofStress phaser; + fill(phaser); + std::string wire; + ASSERT_TRUE(phaser.SerializeToString(&wire)); + PbOneofStress pb; + ASSERT_TRUE(pb.ParseFromString(wire)); + expect_pb(pb); + OneofStress phaser2; + ASSERT_TRUE(pb.SerializeToString(&wire)); + ASSERT_TRUE(phaser2.ParseFromString(wire)); + expect_phaser(phaser2); +} + +} // namespace + +TEST(AllTypesTest, SetAndGetEveryScalar) { + AllScalars msg; + FillAllScalars(msg); + ExpectAllScalars(msg); +} + +TEST(AllTypesTest, ClearAndResetScalars) { + AllScalars msg; + FillAllScalars(msg); + msg.clear_f_string(); + msg.clear_f_bytes(); + EXPECT_FALSE(msg.has_f_string()); + EXPECT_FALSE(msg.has_f_bytes()); + msg.set_f_string("again"); + msg.set_f_bytes("bytes"); + EXPECT_EQ("again", msg.f_string()); + EXPECT_EQ("bytes", msg.f_bytes()); +} + +TEST(AllTypesTest, ProtobufRoundTrip) { + AllScalars msg; + FillAllScalars(msg); + + std::string wire; + ASSERT_TRUE(msg.SerializeToString(&wire)); + + ::foo::bar::coverage::AllScalars pb; + ASSERT_TRUE(pb.ParseFromString(wire)); + + EXPECT_EQ(pb.f_int32(), msg.f_int32()); + EXPECT_EQ(pb.f_int64(), msg.f_int64()); + EXPECT_EQ(pb.f_sint32(), msg.f_sint32()); + EXPECT_EQ(pb.f_sint64(), msg.f_sint64()); + EXPECT_EQ(pb.f_uint32(), msg.f_uint32()); + EXPECT_EQ(pb.f_uint64(), msg.f_uint64()); + EXPECT_EQ(pb.f_fixed32(), msg.f_fixed32()); + EXPECT_EQ(pb.f_fixed64(), msg.f_fixed64()); + EXPECT_EQ(pb.f_sfixed32(), msg.f_sfixed32()); + EXPECT_EQ(pb.f_sfixed64(), msg.f_sfixed64()); + EXPECT_FLOAT_EQ(pb.f_float(), msg.f_float()); + EXPECT_DOUBLE_EQ(pb.f_double(), msg.f_double()); + EXPECT_EQ(pb.f_bool(), msg.f_bool()); + EXPECT_EQ(pb.f_string(), msg.f_string()); + EXPECT_EQ(pb.f_bytes(), msg.f_bytes()); + EXPECT_EQ(pb.f_enum(), static_cast(msg.f_enum())); +} + +// Regression test for the zigzag (sint32/sint64) wire encoding. Small +// magnitudes happened to encode correctly even with the previous, buggy +// hard-coded 31-bit sign shift, so the bug only surfaced for values outside +// the int32 range. Exercise large positive and negative 64-bit values in both +// serialization directions to confirm wire compatibility with protobuf. +TEST(AllTypesTest, LargeSintWireCompat) { + const int32_t kSint32Values[] = {INT32_MIN, -1000000, -1, 0, 1, 1000000, + INT32_MAX}; + const int64_t kSint64Values[] = { + INT64_MIN, int64_t(INT32_MIN) - 1, int64_t(-1) << 40, + -1, 0, 1, + int64_t(1) << 40, int64_t(INT32_MAX) + 1, INT64_MAX}; + + for (int32_t s32 : kSint32Values) { + for (int64_t s64 : kSint64Values) { + // phaser -> protobuf + AllScalars msg; + msg.set_f_sint32(s32); + msg.set_f_sint64(s64); + std::string wire; + ASSERT_TRUE(msg.SerializeToString(&wire)); + ::foo::bar::coverage::AllScalars pb; + ASSERT_TRUE(pb.ParseFromString(wire)); + EXPECT_EQ(s32, pb.f_sint32()); + EXPECT_EQ(s64, pb.f_sint64()); + + // protobuf -> phaser + std::string pb_wire = pb.SerializeAsString(); + AllScalars msg2; + ASSERT_TRUE(msg2.ParseFromString(pb_wire)); + EXPECT_EQ(s32, msg2.f_sint32()); + EXPECT_EQ(s64, msg2.f_sint64()); + } + } +} + +TEST(AllTypesTest, MapInsertOverwriteAndClear) { + MapHolder msg; + auto *e1 = msg.add_values(); + e1->set_key("alpha"); + e1->set_value(1); + auto *e2 = msg.add_values(); + e2->set_key("beta"); + e2->set_value(2); + + ASSERT_EQ(2u, msg.values_size()); + EXPECT_EQ("alpha", msg.values(0).key()); + EXPECT_EQ(1, msg.values(0).value()); + EXPECT_EQ("beta", msg.values(1).key()); + EXPECT_EQ(2, msg.values(1).value()); + + msg.mutable_values(0)->set_value(99); + EXPECT_EQ(99, msg.values(0).value()); + + msg.clear_values(); + EXPECT_EQ(0u, msg.values_size()); + + auto *e3 = msg.add_values(); + e3->set_key("gamma"); + e3->set_value(3); + EXPECT_EQ(1u, msg.values_size()); + EXPECT_EQ("gamma", msg.values(0).key()); +} + +TEST(AllTypesTest, RepeatedPackedInt32GrowAndResize) { + RepeatedPrimitivesPacked msg; + constexpr int kCount = 1000; + for (int i = 0; i < kCount; i++) { + msg.add_vi32(i); + } + ASSERT_EQ(static_cast(kCount), msg.vi32_size()); + for (int i = 0; i < kCount; i++) { + EXPECT_EQ(i, msg.vi32(i)); + } + msg.resize_vi32(10); + ASSERT_EQ(10u, msg.vi32_size()); + msg.set_vi32(9, 9999); + EXPECT_EQ(9999, msg.vi32(9)); +} + +TEST(AllTypesTest, RepeatedPackedInt64AndFixed64) { + RepeatedPrimitivesPacked msg; + for (int i = 0; i < 500; i++) { + msg.add_vi64(i * 10); + msg.add_vf64(static_cast(i * 100)); + } + ASSERT_EQ(500u, msg.vi64_size()); + ASSERT_EQ(500u, msg.vf64_size()); + EXPECT_EQ(4990, msg.vi64(499)); + EXPECT_EQ(static_cast(49900), msg.vf64(499)); +} + +TEST(AllTypesTest, RepeatedStringChurn) { + RepeatedStrings msg; + for (int round = 0; round < 10; round++) { + msg.clear_vstr(); + for (int i = 0; i < 100; i++) { + msg.add_vstr(::phaser::test::MakePatternString(32, static_cast('a' + (i % 26)))); + } + ASSERT_EQ(100u, msg.vstr_size()); + for (int i = 0; i < 100; i++) { + EXPECT_EQ(::phaser::test::MakePatternString(32, static_cast('a' + (i % 26))), + msg.vstr(i)); + } + } +} + +TEST(AllTypesTest, RepeatedStringShrinkAndGrow) { + RepeatedStrings msg; + msg.add_vstr(::phaser::test::MakePatternString(2048, 'L')); + msg.add_vstr("tiny"); + EXPECT_EQ(2048u, msg.vstr(0).size()); + msg.set_vstr(0, "short"); + EXPECT_EQ("short", msg.vstr(0)); + msg.set_vstr(0, ::phaser::test::MakePatternString(4096, 'H')); + EXPECT_EQ(4096u, msg.vstr(0).size()); +} + +TEST(AllTypesTest, RepeatedBytesWithNulls) { + RepeatedBytes msg; + msg.add_vbytes(::phaser::test::MakePatternBytes(256)); + msg.add_vbytes(std::string("\0\0\0", 3)); + EXPECT_EQ(256u, msg.vbytes(0).size()); + EXPECT_EQ(3u, msg.vbytes(1).size()); +} + +TEST(AllTypesTest, RepeatedMessagesSparseMutable) { + RepeatedMessages msg; + auto *m5 = msg.mutable_items(5); + m5->set_f_string("slot-five"); + auto *m0 = msg.mutable_items(0); + m0->set_f_int32(7); + EXPECT_EQ(7, msg.items(0).f_int32()); + EXPECT_EQ("slot-five", msg.items(5).f_string()); +} + +// Each arm uses a fresh message (oneof arm switching can trip PayloadBuffer +// ShrinkBlock in toolbelt when replacing a larger arm with a smaller one). +TEST(AllTypesTest, OneofIntArm) { + OneofStress msg; + msg.set_u_int(42); + EXPECT_EQ(42, msg.u_int()); +} + +TEST(AllTypesTest, OneofStringArm) { + OneofStress msg; + msg.set_u_string("oneof-string"); + EXPECT_EQ("oneof-string", msg.u_string()); +} + +TEST(AllTypesTest, OneofBytesArm) { + OneofStress msg; + msg.set_u_bytes(::phaser::test::MakePatternBytes(64)); + EXPECT_EQ(::phaser::test::MakePatternBytes(64), msg.u_bytes()); +} + +TEST(AllTypesTest, OneofNestedMessageArm) { + OneofStress msg; + auto *inner = msg.mutable_u_msg(); + inner->set_f_string("nested"); + EXPECT_EQ("nested", msg.u_msg().f_string()); +} + +TEST(AllTypesTest, ImportsCrossPackage) { + ImportsMessage msg; + msg.mutable_imported_foo()->set_a(7); + msg.mutable_imported_foo()->set_b("from-foo"); + msg.mutable_inner()->set_str("inner-coverage"); + msg.mutable_inner()->set_f(0x1234); + msg.mutable_timestamp()->set_seconds(1700000000); + msg.mutable_timestamp()->set_nanos(123); + msg.mutable_wrapped_string()->set_value("wrapped"); + msg.mutable_empty_msg(); + + EXPECT_EQ(7, msg.imported_foo().a()); + EXPECT_EQ("from-foo", msg.imported_foo().b()); + EXPECT_EQ("inner-coverage", msg.inner().str()); + EXPECT_EQ(0x1234ULL, msg.inner().f()); + EXPECT_EQ(1700000000, msg.timestamp().seconds()); + EXPECT_EQ(123, msg.timestamp().nanos()); + EXPECT_EQ("wrapped", msg.wrapped_string().value()); +} + +TEST(AllTypesTest, AnyPackInnerAndImported) { + ImportsMessage msg; + + CoverageInner inner; + inner.set_str("packed-inner"); + inner.set_f(0xdeadbeef); + ASSERT_TRUE(msg.mutable_any_field()->PackFrom(inner)); + + CoverageInner unpacked; + ASSERT_TRUE(msg.any_field().UnpackTo(&unpacked)); + EXPECT_EQ("packed-inner", unpacked.str()); + EXPECT_EQ(0xdeadbeefULL, unpacked.f()); + + foo::bar::phaser::Foo foo; + foo.set_a(99); + foo.set_b("packed-foo"); + ASSERT_TRUE(msg.mutable_any_field()->PackFrom(foo)); + + foo::bar::phaser::Foo foo_out; + ASSERT_TRUE(msg.any_field().UnpackTo(&foo_out)); + EXPECT_EQ(99, foo_out.a()); + EXPECT_EQ("packed-foo", foo_out.b()); +} + +TEST(AllTypesTest, WireFormatScalarsBidirectional) { + ExpectBidirectionalWireRoundTrip( + [](AllScalars &m) { FillAllScalars(m); }, + [](PbAllScalars &m) { FillAllScalars(m); }, + [](const AllScalars &a, const AllScalars &b) { + ExpectAllScalarsMatch(a, b); + }, + [](const PbAllScalars &pb, const AllScalars &m) { + ExpectAllScalarsMatchPb(pb, m); + }); +} + +TEST(AllTypesTest, WireFormatMapBidirectional) { + ExpectBidirectionalWireRoundTrip( + [](MapHolder &m) { FillMapHolder(m); }, + [](PbMapHolder &m) { FillMapHolder(m); }, + [](const MapHolder &a, const MapHolder &b) { ExpectMapHolderMatch(a, b); }, + [](const PbMapHolder &pb, const MapHolder &m) { + ExpectMapHolderMatchPb(pb, m); + }); +} + +TEST(AllTypesTest, WireFormatRepeatedPackedBidirectional) { + ExpectBidirectionalWireRoundTrip( + [](RepeatedPrimitivesPacked &m) { FillRepeatedPacked(m); }, + [](PbRepeatedPacked &m) { FillRepeatedPacked(m); }, + [](const RepeatedPrimitivesPacked &a, const RepeatedPrimitivesPacked &b) { + ExpectRepeatedPackedMatch(a, b); + }, + [](const PbRepeatedPacked &pb, const RepeatedPrimitivesPacked &m) { + ExpectRepeatedPackedMatchPb(pb, m); + }); +} + +// Proto has [packed=false], but phaser currently emits a length-delimited packed +// payload for scalar repeated fields. Protobuf accepts that wire; verify both +// directions through protobuf bytes. +TEST(AllTypesTest, WireFormatRepeatedUnpackedBidirectional) { + PbRepeatedUnpacked pb; + FillRepeatedUnpacked(pb); + std::string pb_wire; + ASSERT_TRUE(pb.SerializeToString(&pb_wire)); + + RepeatedPrimitivesUnpacked phaser; + ASSERT_TRUE(phaser.ParseFromString(pb_wire)); + ExpectRepeatedUnpackedMatchPb(pb, phaser); + + std::string phaser_wire; + ASSERT_TRUE(phaser.SerializeToString(&phaser_wire)); + RepeatedPrimitivesUnpacked phaser2; + ASSERT_TRUE(phaser2.ParseFromString(phaser_wire)); + ExpectRepeatedUnpackedMatch(phaser, phaser2); + + std::string pb_wire2; + ASSERT_TRUE(pb.SerializeToString(&pb_wire2)); + RepeatedPrimitivesUnpacked phaser3; + ASSERT_TRUE(phaser3.ParseFromString(pb_wire2)); + ExpectRepeatedUnpackedMatch(phaser, phaser3); +} + +TEST(AllTypesTest, WireFormatRepeatedStringsBidirectional) { + ExpectBidirectionalWireRoundTrip( + [](RepeatedStrings &m) { FillRepeatedStrings(m); }, + [](PbRepeatedStrings &m) { FillRepeatedStrings(m); }, + [](const RepeatedStrings &a, const RepeatedStrings &b) { + ExpectRepeatedStringsMatch(a, b); + }, + [](const PbRepeatedStrings &pb, const RepeatedStrings &m) { + ExpectRepeatedStringsMatchPb(pb, m); + }); +} + +TEST(AllTypesTest, WireFormatRepeatedBytesBidirectional) { + ExpectBidirectionalWireRoundTrip( + [](RepeatedBytes &m) { FillRepeatedBytes(m); }, + [](PbRepeatedBytes &m) { FillRepeatedBytes(m); }, + [](const RepeatedBytes &a, const RepeatedBytes &b) { + ExpectRepeatedBytesMatch(a, b); + }, + [](const PbRepeatedBytes &pb, const RepeatedBytes &m) { + ExpectRepeatedBytesMatchPb(pb, m); + }); +} + +TEST(AllTypesTest, WireFormatRepeatedMessagesBidirectional) { + ExpectBidirectionalWireRoundTrip( + [](RepeatedMessages &m) { FillRepeatedMessages(m); }, + [](PbRepeatedMessages &m) { FillRepeatedMessages(m); }, + [](const RepeatedMessages &a, const RepeatedMessages &b) { + ExpectRepeatedMessagesMatch(a, b); + }, + [](const PbRepeatedMessages &pb, const RepeatedMessages &m) { + ExpectRepeatedMessagesMatchPb(pb, m); + }); +} + +TEST(AllTypesTest, WireFormatImportsBidirectional) { + ExpectBidirectionalWireRoundTrip( + [](ImportsMessage &m) { FillImportsMessage(m, true); }, + [](PbImportsMessage &m) { FillImportsMessage(m, true); }, + [](const ImportsMessage &a, const ImportsMessage &b) { + ExpectImportsMessageMatch(a, b, true); + }, + [](const PbImportsMessage &pb, const ImportsMessage &m) { + ExpectImportsMessageMatchPb(pb, m, true); + }); +} + +TEST(AllTypesTest, WireFormatAnyFieldBidirectional) { + ImportsMessage phaser; + CoverageInner inner; + FillCoverageInner(inner); + ASSERT_TRUE(phaser.mutable_any_field()->PackFrom(inner)); + + std::string phaser_wire; + ASSERT_TRUE(phaser.SerializeToString(&phaser_wire)); + + PbImportsMessage pb; + ASSERT_TRUE(pb.ParseFromString(phaser_wire)); + PbCoverageInner inner_pb; + ASSERT_TRUE(pb.any_field().UnpackTo(&inner_pb)); + ExpectCoverageInnerMatchPb(inner_pb, inner); + + std::string pb_wire; + ASSERT_TRUE(pb.SerializeToString(&pb_wire)); + ImportsMessage phaser_from_pb; + ASSERT_TRUE(phaser_from_pb.ParseFromString(pb_wire)); + CoverageInner inner2; + ASSERT_TRUE(phaser_from_pb.any_field().UnpackTo(&inner2)); + ExpectCoverageInnerMatch(inner, inner2); + + std::string roundtrip_wire; + ASSERT_TRUE(phaser_from_pb.SerializeToString(&roundtrip_wire)); + PbImportsMessage pb2; + ASSERT_TRUE(pb2.ParseFromString(roundtrip_wire)); + PbCoverageInner inner3_pb; + ASSERT_TRUE(pb2.any_field().UnpackTo(&inner3_pb)); + ExpectCoverageInnerMatchPb(inner_pb, inner3_pb); +} + +TEST(AllTypesTest, WireFormatAnyFieldValueBeforeTypeUrl) { + PbCoverageInner inner_pb; + FillCoverageInner(inner_pb); + std::string inner_wire; + ASSERT_TRUE(inner_pb.SerializeToString(&inner_wire)); + + const std::string type_url = + "type.googleapis.com/foo.bar.coverage.CoverageInner"; + + // Build standard protobuf Any wire with field 2 (value) before field 1 + // (type_url). + std::string any_wire; + { + ::phaser::ProtoBuffer buf(256); + ASSERT_TRUE( + buf.SerializeLengthDelimited(2, inner_wire.data(), inner_wire.size()) + .ok()); + ASSERT_TRUE(buf.SerializeLengthDelimited(1, type_url.data(), type_url.size()) + .ok()); + any_wire = buf.AsString(); + } + + ImportsMessage phaser; + ASSERT_TRUE(phaser.mutable_any_field()->ParseFromString(any_wire)); + + CoverageInner unpacked; + ASSERT_TRUE(phaser.any_field().UnpackTo(&unpacked)); + ExpectCoverageInnerMatchPb(inner_pb, unpacked); + EXPECT_EQ(type_url, phaser.any_field().type_url()); + + std::string out_wire; + ASSERT_TRUE(phaser.any_field().SerializeToString(&out_wire)); + ::google::protobuf::Any any_out; + ASSERT_TRUE(any_out.ParseFromString(out_wire)); + PbCoverageInner inner_out_pb; + ASSERT_TRUE(any_out.UnpackTo(&inner_out_pb)); + ExpectCoverageInnerMatchPb(inner_pb, inner_out_pb); +} + +TEST(AllTypesTest, WireFormatOneofIntBidirectional) { + ExpectOneofWireRoundTrip( + [](OneofStress &m) { m.set_u_int(-99); }, + [](const OneofStress &m) { + EXPECT_TRUE(m.has_u_int()); + EXPECT_EQ(-99, m.u_int()); + }, + [](const PbOneofStress &pb) { + EXPECT_TRUE(pb.has_u_int()); + EXPECT_EQ(-99, pb.u_int()); + }); +} + +TEST(AllTypesTest, WireFormatOneofStringBidirectional) { + ExpectOneofWireRoundTrip( + [](OneofStress &m) { m.set_u_string("wire-oneof"); }, + [](const OneofStress &m) { + EXPECT_TRUE(m.has_u_string()); + EXPECT_EQ("wire-oneof", m.u_string()); + }, + [](const PbOneofStress &pb) { + EXPECT_TRUE(pb.has_u_string()); + EXPECT_EQ("wire-oneof", pb.u_string()); + }); +} + +TEST(AllTypesTest, WireFormatOneofBytesBidirectional) { + const std::string bytes = ::phaser::test::MakePatternBytes(48); + ExpectOneofWireRoundTrip( + [&](OneofStress &m) { m.set_u_bytes(bytes); }, + [&](const OneofStress &m) { + EXPECT_TRUE(m.has_u_bytes()); + EXPECT_EQ(bytes, m.u_bytes()); + }, + [&](const PbOneofStress &pb) { + EXPECT_TRUE(pb.has_u_bytes()); + EXPECT_EQ(bytes, pb.u_bytes()); + }); +} + +TEST(AllTypesTest, WireFormatOneofMessageBidirectional) { + ExpectOneofWireRoundTrip( + [](OneofStress &m) { + FillAllScalars(*m.mutable_u_msg()); + }, + [](const OneofStress &m) { + EXPECT_TRUE(m.has_u_msg()); + AllScalars expected; + FillAllScalars(expected); + ExpectAllScalarsMatch(m.u_msg(), expected); + }, + [](const PbOneofStress &pb) { + EXPECT_TRUE(pb.has_u_msg()); + EXPECT_EQ(-123456, pb.u_msg().f_int32()); + EXPECT_EQ("scalar-string", pb.u_msg().f_string()); + }); +} + +} // namespace foo::bar::coverage::phaser diff --git a/phaser/compiler/BUILD.bazel b/phaser/compiler/BUILD.bazel index 767f532..90b36a6 100644 --- a/phaser/compiler/BUILD.bazel +++ b/phaser/compiler/BUILD.bazel @@ -1,3 +1,6 @@ +load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library") +load("//phaser:copts.bzl", "PHASER_COPTS") + package(default_visibility = ["//visibility:public"]) cc_library( @@ -7,6 +10,7 @@ cc_library( "gen.cc", "message_gen.cc", ], + copts = PHASER_COPTS, hdrs = [ "enum_gen.h", "gen.h", @@ -28,6 +32,7 @@ cc_binary( srcs = [ "main.cc", ], + copts = PHASER_COPTS, deps = [ ":phaser_lib", "@com_google_absl//absl/flags:flag", diff --git a/phaser/compiler/enum_gen.cc b/phaser/compiler/enum_gen.cc index 3c01b4c..368237f 100644 --- a/phaser/compiler/enum_gen.cc +++ b/phaser/compiler/enum_gen.cc @@ -1,4 +1,4 @@ -// Copyright 2024 David Allison +// Copyright 2024-2026 David Allison // All Rights Reserved // See LICENSE file for licensing information. @@ -9,14 +9,14 @@ namespace phaser { void EnumGenerator::GenerateHeader(std::ostream &os) { - std::string name = enum_->name(); + std::string name(enum_->name()); if (enum_->containing_type() != nullptr) { - name = enum_->containing_type()->name() + "_" + name; + name = std::string(enum_->containing_type()->name()) + "_" + name; } os << "enum " << name << " : int {\n"; for (int i = 0; i < enum_->value_count(); i++) { const google::protobuf::EnumValueDescriptor *value = enum_->value(i); - std::string const_name = value->name(); + std::string const_name(value->name()); if (enum_->containing_type() != nullptr) { const_name = name + "_" + const_name; } @@ -30,7 +30,7 @@ void EnumGenerator::GenerateHeader(std::ostream &os) { os << " switch (e) {\n"; for (int i = 0; i < enum_->value_count(); i++) { const google::protobuf::EnumValueDescriptor *value = enum_->value(i); - std::string const_name = value->name(); + std::string const_name(value->name()); if (enum_->containing_type() != nullptr) { const_name = name + "_" + const_name; } @@ -47,7 +47,7 @@ void EnumGenerator::GenerateHeader(std::ostream &os) { os << " " << name << " operator()(const std::string &s) {\n"; for (int i = 0; i < enum_->value_count(); i++) { const google::protobuf::EnumValueDescriptor *value = enum_->value(i); - std::string const_name = value->name(); + std::string const_name(value->name()); if (enum_->containing_type() != nullptr) { const_name = name + "_" + const_name; } diff --git a/phaser/compiler/enum_gen.h b/phaser/compiler/enum_gen.h index e6cbc5d..608002c 100644 --- a/phaser/compiler/enum_gen.h +++ b/phaser/compiler/enum_gen.h @@ -1,4 +1,4 @@ -// Copyright 2024 David Allison +// Copyright 2024-2026 David Allison // All Rights Reserved // See LICENSE file for licensing information. diff --git a/phaser/compiler/gen.cc b/phaser/compiler/gen.cc index 7fc0fa6..29f2a1b 100644 --- a/phaser/compiler/gen.cc +++ b/phaser/compiler/gen.cc @@ -1,4 +1,4 @@ -// Copyright 2024 David Allison +// Copyright 2024-2026 David Allison // All Rights Reserved // See LICENSE file for licensing information. @@ -18,7 +18,9 @@ WriteToZeroCopyStream(const std::string &data, int size; size_t offset = 0; while (offset < data.size()) { - stream->Next(&data_buffer, &size); + if (!stream->Next(&data_buffer, &size)) { + break; + } int to_copy = std::min(size, static_cast(data.size() - offset)); std::memcpy(data_buffer, data.data() + offset, to_copy); offset += to_copy; @@ -61,7 +63,8 @@ bool CodeGenerator::Generate( Generator gen(file, added_namespace_, package_name_, target_name_); - std::string filename = GeneratedFilename(package_name_, target_name_, file->name()); + std::string filename = + GeneratedFilename(package_name_, target_name_, std::string(file->name())); std::filesystem::path hp(filename); hp.replace_extension(".phaser.h"); @@ -129,7 +132,9 @@ Generator::Generator(const google::protobuf::FileDescriptor *file, : file_(file), added_namespace_(ns), package_name_(pn), target_name_(tn) { for (int i = 0; i < file->message_type_count(); i++) { message_gens_.push_back( - std::make_unique(file->message_type(i), added_namespace_, file->package())); + std::make_unique( + file->message_type(i), added_namespace_, + std::string(file->package()))); } // Enums for (int i = 0; i < file->enum_type_count(); i++) { @@ -141,7 +146,9 @@ void Generator::GenerateHeaders(std::ostream &os) { os << "#pragma once\n"; os << "#include \"phaser/runtime/runtime.h\"\n"; for (int i = 0; i < file_->dependency_count(); i++) { - std::string base = GeneratedFilename(package_name_, target_name_, file_->dependency(i)->name()); + std::string base = GeneratedFilename( + package_name_, target_name_, + std::string(file_->dependency(i)->name())); std::filesystem::path p(base); p.replace_extension(".phaser.h"); os << "#include \"" << p.string() << "\"\n"; @@ -166,7 +173,8 @@ void Generator::GenerateHeaders(std::ostream &os) { } void Generator::GenerateSources(std::ostream &os) { - std::filesystem::path p(GeneratedFilename(package_name_, target_name_, file_->name())); + std::filesystem::path p(GeneratedFilename( + package_name_, target_name_, std::string(file_->name()))); p.replace_extension(".phaser.h"); os << "#include \"" << p.string() << "\"\n"; diff --git a/phaser/compiler/gen.h b/phaser/compiler/gen.h index 050fbfb..90043d5 100644 --- a/phaser/compiler/gen.h +++ b/phaser/compiler/gen.h @@ -1,4 +1,4 @@ -// Copyright 2024 David Allison +// Copyright 2024-2026 David Allison // All Rights Reserved // See LICENSE file for licensing information. diff --git a/phaser/compiler/main.cc b/phaser/compiler/main.cc index 93555f8..bb80d40 100644 --- a/phaser/compiler/main.cc +++ b/phaser/compiler/main.cc @@ -1,4 +1,4 @@ -// Copyright 2024 David Allison +// Copyright 2024-2026 David Allison // All Rights Reserved // See LICENSE file for licensing information. diff --git a/phaser/compiler/message_gen.cc b/phaser/compiler/message_gen.cc index be833bc..5806bd4 100644 --- a/phaser/compiler/message_gen.cc +++ b/phaser/compiler/message_gen.cc @@ -1,4 +1,4 @@ -// Copyright 2024 David Allison +// Copyright 2024-2026 David Allison // All Rights Reserved // See LICENSE file for licensing information. @@ -116,9 +116,9 @@ bool IsCppReservedWord(const std::string &s) { std::string MessageGenerator::EnumName(const google::protobuf::EnumDescriptor *desc) { - std::string name = desc->name(); + std::string name(desc->name()); if (desc->containing_type() != nullptr) { - name = desc->containing_type()->name() + "_" + name; + name = std::string(desc->containing_type()->name()) + "_" + name; } return name; } @@ -129,7 +129,7 @@ MessageGenerator::MessageName(const google::protobuf::Descriptor *desc, if (is_ref && IsAny(desc)) { return "::phaser::AnyMessage"; } - std::string full_name = desc->full_name(); + std::string full_name(desc->full_name()); // If the message is in our package, use the short name. if (full_name.find(package_name_) == std::string::npos) { std::string cpp_name = @@ -142,9 +142,9 @@ MessageGenerator::MessageName(const google::protobuf::Descriptor *desc, return cpp_name.substr(0, pos) + "::" + added_namespace_ + cpp_name.substr(pos); } - std::string name = desc->name(); + std::string name(desc->name()); if (desc->containing_type() != nullptr) { - name = desc->containing_type()->name() + "_" + name; + name = std::string(desc->containing_type()->name()) + "_" + name; } return name; } @@ -438,7 +438,7 @@ void MessageGenerator::CompileUnions() { union_info->member_type += "::phaser::" + field_type; uint32_t field_size = FieldBinarySize(field); union_info->members.push_back(std::make_shared( - field, 0, union_info->id, field->name() + "_", field_type, + field, 0, union_info->id, std::string(field->name()) + "_", field_type, FieldCType(field), field_size)); union_info->binary_size = std::max(union_info->binary_size, 4 + field_size); union_info->id++; @@ -468,7 +468,7 @@ void MessageGenerator::CompileFields() { auto it = unions_.find(oneof); if (it == unions_.end()) { auto union_info = std::make_shared( - oneof, 4, oneof->name() + "_", "UnionField"); + oneof, 4, std::string(oneof->name()) + "_", "UnionField"); unions_[oneof] = union_info; fields_in_order_.push_back(union_info); } @@ -490,7 +490,8 @@ void MessageGenerator::CompileFields() { } offset = (offset + (field_size - 1)) & ~(field_size - 1); fields_.push_back( - std::make_shared(field, offset, id, field->name() + "_", + std::make_shared(field, offset, id, + std::string(field->name()) + "_", field_type, FieldCType(field), field_size)); fields_in_order_.push_back(fields_.back()); offset += field_size; @@ -929,7 +930,7 @@ void MessageGenerator::GenerateFieldProtobufAccessors(std::ostream &os) { void MessageGenerator::GenerateFieldProtobufAccessors( std::shared_ptr field, std::shared_ptr union_field, int union_index, std::ostream &os) { - std::string field_name = field->field->name(); + std::string field_name(field->field->name()); std::string sanitized_field_name = field_name + +(IsCppReservedWord(field_name) ? "_" : ""); @@ -1216,9 +1217,10 @@ void MessageGenerator::GenerateFieldProtobufAccessors( << std::to_string(union_index) << ">();\n"; os << " }\n"; } - if (IsAny(field->field)) { - GenerateAnyProtobufAccessors(field, union_field, union_index, os); - } + // Any-typed message fields reuse the standard message accessors above + // (mutable_X() returns a phaser::AnyMessage, which exposes the full Any + // API: PackFrom/UnpackTo/Is/etc.), so no field-specific generation is + // needed here. break; case google::protobuf::FieldDescriptor::TYPE_GROUP: @@ -1229,10 +1231,6 @@ void MessageGenerator::GenerateFieldProtobufAccessors( } } -void MessageGenerator::GenerateAnyProtobufAccessors( - std::shared_ptr field, std::shared_ptr union_field, - int union_index, std::ostream &os) {} - void MessageGenerator::GenerateUnionProtobufAccessors(std::ostream &os) { for (auto & [ oneof, u ] : unions_) { os << "\n // Oneof " << oneof->name() << "\n"; @@ -1268,14 +1266,14 @@ void MessageGenerator::GenerateNestedTypes(std::ostream &os) { void MessageGenerator::GenerateFieldNumbers(std::ostream &os) { for (auto &field : fields_) { - std::string name = field->field->camelcase_name(); + std::string name(field->field->camelcase_name()); name = absl::StrFormat("k%c%s", toupper(name[0]), name.substr(1)); os << " static constexpr int " << name << "FieldNumber = " << field->field->number() << ";\n"; } for (auto & [ oneof, u ] : unions_) { for (auto &field : u->members) { - std::string name = field->field->camelcase_name(); + std::string name(field->field->camelcase_name()); name = absl::StrFormat("k%c%s", toupper(name[0]), name.substr(1)); os << " static constexpr int " << name << "FieldNumber = " << field->field->number() << ";\n"; @@ -1627,7 +1625,7 @@ void MessageGenerator::GeneratePhaserBank(std::ostream &os) { os << MessageName(message_) << "Allocate(std::shared_ptr<::phaser::MessageRuntime> runtime) {\n"; os << " void *addr = toolbelt::PayloadBuffer::Allocate(&runtime->pb, " - << MessageName(message_) << "::BinarySize(), 8, true);\n"; + << MessageName(message_) << "::BinarySize());\n"; os << " toolbelt::BufferOffset offset = runtime->pb->ToOffset(addr);\n"; os << " auto msg = new " << MessageName(message_) << "(runtime, offset);\n"; os << " msg->InstallMetadata<" << MessageName(message_) << ">();\n"; @@ -1662,8 +1660,8 @@ void MessageGenerator::GeneratePhaserBank(std::ostream &os) { os << "static bool " << MessageName(message_) << "HasField(const ::phaser::Message &msg, int number) {\n"; - os << " const " << MessageName(message_) << " *m = static_cast(&msg);\n"; + os << " [[maybe_unused]] const " << MessageName(message_) + << " *m = static_cast(&msg);\n"; os << " switch (number) {\n"; for (auto &field : fields_) { os << " case " << field->field->number() << ":\n"; @@ -1740,10 +1738,10 @@ void MessageGenerator::GeneratePhaserBank(std::ostream &os) { os << " .binary_size = " << MessageName(message_) << "BinarySize,\n"; os << " .message_info = " << MessageName(message_) << "GetMessageInfo,\n"; os << " .has_field = " << MessageName(message_) << "HasField,\n"; - os << " .get_field_by_number = " << MessageName(message_) - << "GetFieldByNumber,\n"; os << " .get_field_by_name = " << MessageName(message_) << "GetFieldByName,\n"; + os << " .get_field_by_number = " << MessageName(message_) + << "GetFieldByNumber,\n"; os << "};\n\n"; os << "static struct " << MessageName(message_) << "BankInitializer {\n"; diff --git a/phaser/compiler/message_gen.h b/phaser/compiler/message_gen.h index e28a4bc..55da5b6 100644 --- a/phaser/compiler/message_gen.h +++ b/phaser/compiler/message_gen.h @@ -1,4 +1,4 @@ -// Copyright 2024 David Allison +// Copyright 2024-2026 David Allison // All Rights Reserved // See LICENSE file for licensing information. @@ -102,9 +102,6 @@ class MessageGenerator { void GenerateStreamer(std::ostream &os); bool IsAny(const google::protobuf::Descriptor *desc); bool IsAny(const google::protobuf::FieldDescriptor *field); - void GenerateAnyProtobufAccessors(std::shared_ptr field, - std::shared_ptr union_field, - int union_index, std::ostream &os); void GenerateCopy(std::ostream &os, bool decl); void GenerateDebugString(std::ostream &os); void GeneratePhaserBank(std::ostream &os); diff --git a/phaser/copts.bzl b/phaser/copts.bzl new file mode 100644 index 0000000..94f5337 --- /dev/null +++ b/phaser/copts.bzl @@ -0,0 +1,11 @@ +"""Shared compiler warning flags for Phaser's own (hand-written) targets. + +These are intentionally applied per-target so they only affect code in this +repository, and never leak into external dependencies or generated code. +""" + +PHASER_COPTS = [ + "-Wall", + "-Wextra", + "-Wpedantic", +] diff --git a/phaser/gen.cc b/phaser/gen.cc deleted file mode 100644 index 668fdbf..0000000 --- a/phaser/gen.cc +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2024 David Allison -// All Rights Reserved -// See LICENSE file for licensing information. - -#include "phaser/compiler/gen.h" -#include "absl/strings/str_format.h" - -namespace phaser { - -bool CodeGenerator::Generate( - const google::protobuf::FileDescriptor *file, const std::string ¶meter, - google::protobuf::compiler::GeneratorContext *generator_context, - std::string *error) const { - Generator gen(file); - - if (absl::Status status = gen.GenerateHeader(std::cout); !status.ok()) { - *error = absl::StrFormat("Failed to generate header: %s", status.message()); - return false; - } - - if (absl::Status status = gen.GenerateSource(std::cout); !status.ok()) { - *error = absl::StrFormat("Failed to generate source: %s", status.message()); - return false; - } - - return true; -} - -absl::Status Generator::GenerateHeader(std::ostream &os) { - return absl::OkStatus(); -} - -absl::Status Generator::GenerateSource(std::ostream &os) { - return absl::OkStatus(); -} -} // namespace phaser diff --git a/phaser/perf_test.cc b/phaser/perf_test.cc index dd1057b..b7cbb90 100644 --- a/phaser/perf_test.cc +++ b/phaser/perf_test.cc @@ -1,4 +1,4 @@ -// Copyright 2024 David Allison +// Copyright 2024-2026 David Allison // All Rights Reserved // See LICENSE file for licensing information. diff --git a/phaser/phaser_library.bzl b/phaser/phaser_library.bzl index ea4835e..31d4ae8 100644 --- a/phaser/phaser_library.bzl +++ b/phaser/phaser_library.bzl @@ -3,8 +3,15 @@ This module provides a rule to generate phaser message files from proto_library """ load("@bazel_skylib//lib:paths.bzl", "paths") +load("@com_google_protobuf//bazel/common:proto_info.bzl", "ProtoInfo") +load("@rules_cc//cc:defs.bzl", "cc_library") -MessageInfo = provider(fields = ["direct_sources", "transitive_sources", "cpp_outputs"]) +MessageInfo = provider(fields = [ + "direct_sources", + "transitive_sources", + "cpp_outputs", + "symlink_headers", +]) def _phaser_action( ctx, @@ -59,37 +66,51 @@ def _phaser_action( # This aspect generates the MessageInfo provider containing the files we # will generate from running the Phaser plugin. +def _to_list(value): + if type(value) == "list": + return value + return value.to_list() + +def _proto_output_base(source_file): + file_path = source_file.short_path + if "_virtual_imports" in file_path: + # For a file that is not in this package, we need to generate the + # output in our package. + # The path looks like: + # ../com_google_protobuf/_virtual_imports/any_proto/google/protobuf/any.proto + # We want to declare the file as: + # google/protobuf/any.phaser.cc + v = file_path.split("_virtual_imports/") + + # Remove the first directory of v[1] to get the path relative to the package. + file_path = v[1].split("/", 1)[1] + return file_path + def _phaser_aspect_impl(target, _ctx): direct_sources = [] transitive_sources = depset() cpp_outputs = [] + symlink_headers = [] - def add_output(base): + def add_output(base, symlink): cpp_outputs.append(paths.replace_extension(base, ".phaser.cc")) - cpp_outputs.append(paths.replace_extension(base, ".phaser.h")) + header = paths.replace_extension(base, ".phaser.h") + cpp_outputs.append(header) + if symlink: + symlink_headers.append(header) if ProtoInfo in target: transitive_sources = target[ProtoInfo].transitive_sources - for s in transitive_sources.to_list(): + direct_paths = {s.path: True for s in _to_list(target[ProtoInfo].direct_sources)} + for s in _to_list(transitive_sources): direct_sources.append(s) - file_path = s.short_path - if "_virtual_imports" in file_path: - # For a file that is not in this package, we need to generate the - # output in our package. - # The path looks like: - # ../com_google_protobuf/_virtual_imports/any_proto/google/protobuf/any.proto - # We want to declare the file as:ƒ - # google/protobuf/any.phaser.cc - v = file_path.split("_virtual_imports/") - - # Remove the first directory of v[1] to get the path relative to the package. - file_path = v[1].split("/", 1)[1] - add_output(file_path) + add_output(_proto_output_base(s), s.path in direct_paths) return [MessageInfo( direct_sources = direct_sources, transitive_sources = transitive_sources, cpp_outputs = cpp_outputs, + symlink_headers = symlink_headers, )] phaser_aspect = aspect( @@ -121,7 +142,7 @@ def _phaser_impl(ctx): # #include "phaser/testdata/Test.phaser.h" # so we create the symlink: # Test.phaser.h -> phaser/testdata/phaser/testdata/Test.phaser.h - if out_file.extension == "h": + if out_file.extension == "h" and out in dep[MessageInfo].symlink_headers: prefix = paths.join(ctx.attr.target_name, package_name) symlink_name = out_file.short_path[len(prefix) + 1:] if symlink_name.startswith(package_name): @@ -233,7 +254,7 @@ def phaser_library(name, deps = [], runtime = "@phaser//phaser/runtime:phaser_ru if runtime != "": libdeps = libdeps + [runtime] - native.cc_library( + cc_library( name = name, srcs = [srcs], hdrs = [hdrs], diff --git a/phaser/phaser_test.cc b/phaser/phaser_test.cc index 9a0d0c9..12bb6d0 100644 --- a/phaser/phaser_test.cc +++ b/phaser/phaser_test.cc @@ -1,8 +1,9 @@ -// Copyright 2024 David Allison +// Copyright 2024-2026 David Allison // All Rights Reserved // See LICENSE file for licensing information. +#include "test_helpers.h" #include "absl/strings/str_format.h" #include "phaser/runtime/runtime.h" #include "phaser/testdata/TestMessage.pb.h" @@ -86,6 +87,8 @@ TEST(PhaserTest, ProtobufCompat) { std::string pb_debug_string = msg2.DebugString(); std::string phaser_debug_string = msg.DebugString(); + phaser::test::StripProtobufDebugRedaction(pb_debug_string); + phaser::test::StripProtobufDebugRedaction(phaser_debug_string); ASSERT_EQ(pb_debug_string, phaser_debug_string); } @@ -261,6 +264,8 @@ TEST(PhaserTest, Any) { std::string pb_debug_string = msg4.DebugString(); std::string phaser_debug_string = msg.DebugString(); + phaser::test::StripProtobufDebugRedaction(pb_debug_string); + phaser::test::StripProtobufDebugRedaction(phaser_debug_string); ASSERT_EQ(pb_debug_string, phaser_debug_string); } @@ -537,6 +542,34 @@ TEST(PhaserTest, Reflection) { } } +TEST(DebugRedaction, StripsMarkerLine) { + std::string s = "goo.gle/debugstr-abc123\nx: 1\ny: 2\n"; + phaser::test::StripProtobufDebugRedaction(s); + ASSERT_EQ("x: 1\ny: 2\n", s); +} + +TEST(DebugRedaction, StripsMarkerWithLeadingWhitespace) { + // Protobuf may emit a random amount of leading whitespace before the marker. + std::string s = " goo.gle/debugproto\nfield: value\n"; + phaser::test::StripProtobufDebugRedaction(s); + ASSERT_EQ("field: value\n", s); +} + +TEST(DebugRedaction, LeavesUnmarkedStringUntouched) { + std::string s = "x: 1\ny: 2\n"; + phaser::test::StripProtobufDebugRedaction(s); + ASSERT_EQ("x: 1\ny: 2\n", s); +} + +TEST(DebugRedaction, DoesNotStripMidContentMarker) { + // A marker that is not the leading prefix (real content precedes it) must be + // left alone rather than truncating legitimate output. + std::string s = "name: \"goo.gle/debugstr\"\nx: 1\n"; + std::string original = s; + phaser::test::StripProtobufDebugRedaction(s); + ASSERT_EQ(original, s); +} + int main(int argc, char **argv) { testing::InitGoogleTest(&argc, argv); diff --git a/phaser/runtime/BUILD.bazel b/phaser/runtime/BUILD.bazel index 0b518d7..51b8f12 100644 --- a/phaser/runtime/BUILD.bazel +++ b/phaser/runtime/BUILD.bazel @@ -1,3 +1,6 @@ +load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test") +load("//phaser:copts.bzl", "PHASER_COPTS") + package(default_visibility = ["//visibility:public"]) cc_library( @@ -6,6 +9,7 @@ cc_library( "message.cc", "phaser_bank.cc", ], + copts = PHASER_COPTS, hdrs = [ "any.h", "fields.h", @@ -24,7 +28,7 @@ cc_library( "@com_google_absl//absl/strings", "@com_google_absl//absl/strings:str_format", "@com_google_absl//absl/types:span", - "@toolbelt//toolbelt", + "@cpp_toolbelt//toolbelt", ], ) @@ -33,12 +37,14 @@ cc_test( srcs = [ "message_test.cc", ], + copts = PHASER_COPTS, + data = ["//phaser:valgrind.supp"], deps = [ ":phaser_runtime", "//phaser/testdata:test_message_cc_proto", "@com_google_absl//absl/strings:str_format", "@com_google_googletest//:gtest", - "@toolbelt//toolbelt", + "@cpp_toolbelt//toolbelt", ], ) @@ -47,10 +53,12 @@ cc_test( srcs = [ "wireformat_test.cc", ], + copts = PHASER_COPTS, + data = ["//phaser:valgrind.supp"], deps = [ ":phaser_runtime", "@com_google_absl//absl/strings:str_format", "@com_google_googletest//:gtest", - "@toolbelt//toolbelt", + "@cpp_toolbelt//toolbelt", ], ) diff --git a/phaser/runtime/any.h b/phaser/runtime/any.h index 7ec3ddb..81fb4e4 100644 --- a/phaser/runtime/any.h +++ b/phaser/runtime/any.h @@ -1,4 +1,4 @@ -// Copyright 2024 David Allison +// Copyright 2024-2026 David Allison // All Rights Reserved // See LICENSE file for licensing information. @@ -9,13 +9,17 @@ // 1. A string called 'type_url' that specifies the type of the message // 2. A bytes field called 'value' that contains a message. // -// In regular protobuf, the 'value' field is a serialized message, but -// in phaser, the value is the binary of the message. +// In memory, `value` holds phaser binary (zero-copy via PackFrom / As / MutableAny). +// On protobuf wire, `value` is length-delimited *protobuf* bytes of the inner +// message; Serialize/Deserialize transcode at this boundary. // #include "phaser/runtime/fields.h" #include "phaser/runtime/phaser_bank.h" #include "toolbelt/hexdump.h" #include +#include +#include +#include namespace phaser { @@ -25,7 +29,7 @@ namespace phaser { // Hand-coded message class that represents a google.protobuf.Any message. class AnyMessage : public Message { public: - AnyMessage(phaser::InternalDefault d) + AnyMessage(phaser::InternalDefault /*d*/) : type_url_(offsetof(AnyMessage, type_url_), HeaderSize() + 0, 0, 1), value_(offsetof(AnyMessage, value_), HeaderSize() + 4, 1, 2) {} @@ -97,12 +101,17 @@ class AnyMessage : public Message { size += type_url_.SerializedSize(); } if (value_.IsPresent()) { - absl::StatusOr value_size = - PhaserBankSerializedSize(std::string(type_url()), As()); - if (!value_size.ok()) { + absl::StatusOr embedded = EmbeddedMessageForWire(); + if (!embedded.ok()) { return 0; } - size += *value_size; + std::unique_ptr embedded_owner(*embedded); + absl::StatusOr inner_size = + PhaserBankSerializedSize(MessageTypeName(), **embedded); + if (!inner_size.ok()) { + return 0; + } + size += ProtoBuffer::LengthDelimitedSize(2, *inner_size); } return size; } @@ -114,8 +123,24 @@ class AnyMessage : public Message { } } if (value_.IsPresent()) { - if (absl::Status status = PhaserBankSerializeToBuffer( - std::string(type_url()), As(), buffer); + absl::StatusOr embedded = EmbeddedMessageForWire(); + if (!embedded.ok()) { + return embedded.status(); + } + std::unique_ptr embedded_owner(*embedded); + const std::string type = MessageTypeName(); + absl::StatusOr inner_size = + PhaserBankSerializedSize(type, **embedded); + if (!inner_size.ok()) { + return inner_size.status(); + } + if (absl::Status status = + buffer.SerializeLengthDelimitedHeader(2, *inner_size); + !status.ok()) { + return status; + } + if (absl::Status status = + PhaserBankSerializeToBuffer(type, **embedded, buffer); !status.ok()) { return status; } @@ -124,6 +149,12 @@ class AnyMessage : public Message { } absl::Status Deserialize(phaser::ProtoBuffer &buffer) { + clear_type_url(); + clear_value(); + + std::optional pending_type_url; + std::optional pending_wire_value; + while (!buffer.Eof()) { absl::StatusOr tag = buffer.DeserializeVarint(); @@ -132,25 +163,22 @@ class AnyMessage : public Message { } uint32_t field_number = *tag >> phaser::ProtoBuffer::kFieldIdShift; switch (field_number) { - case 1: - if (absl::Status status = type_url_.Deserialize(buffer); !status.ok()) { - return status; + case 1: { + absl::StatusOr url = buffer.DeserializeString(); + if (!url.ok()) { + return url.status(); } + pending_type_url = std::string(*url); break; + } case 2: { - std::string type = MessageTypeName(); - - absl::StatusOr d = BuildMessageInstanceInValue(); - if (!d.ok()) { - return d.status(); - } - - std::unique_ptr msg(*d); - if (absl::Status status = - PhaserBankDeserializeFromBuffer(type, *msg, buffer); - !status.ok()) { - return status; + absl::StatusOr> wire = + buffer.DeserializeLengthDelimited(); + if (!wire.ok()) { + return wire.status(); } + pending_wire_value = + std::string(wire->data(), wire->data() + wire->size()); break; } default: @@ -159,6 +187,13 @@ class AnyMessage : public Message { } } } + + if (pending_type_url) { + type_url_.Set(*pending_type_url); + } + if (pending_wire_value) { + return MaterializeValueFromProtobufWire(*pending_wire_value); + } return absl::OkStatus(); } @@ -176,23 +211,23 @@ class AnyMessage : public Message { type_url_.Set(msg.type_url()); } if (msg.has_value()) { - std::string type = MessageTypeName(); - absl::StatusOr d = BuildMessageInstanceInValue(); - if (!d.ok()) { - return d.status(); + if (!msg.has_type_url()) { + return absl::FailedPreconditionError( + "Any value without type_url cannot be cloned"); } - - std::unique_ptr dest(*d); - absl::StatusOr s = - PhaserBankMakeExisting(type, msg.runtime, msg.value().data()); - if (!s.ok()) { - return s.status(); + const std::string type = msg.MessageTypeName(); + absl::StatusOr dest = AllocateEmbeddedMessage(type); + if (!dest.ok()) { + return dest.status(); } - std::unique_ptr src(*s); - if (absl::Status status = PhaserBankCopy(type, *src, *dest); - !status.ok()) { - return status; + std::unique_ptr dest_owner(*dest); + absl::StatusOr src = + PhaserBankMakeExisting(type, msg.runtime, msg.value().data()); + if (!src.ok()) { + return src.status(); } + std::unique_ptr src_owner(*src); + return PhaserBankCopy(type, **src, **dest); } return absl::OkStatus(); } @@ -254,34 +289,77 @@ class AnyMessage : public Message { return msg; } + bool ParseFromArray(const char *array, size_t size) { + ProtoBuffer buffer(array, size); + if (absl::Status status = Deserialize(buffer); !status.ok()) { + return false; + } + return true; + } + + bool ParseFromString(const std::string &str) { + return ParseFromArray(str.data(), str.size()); + } + + bool SerializeToString(std::string *str) const { + size_t size = SerializedSize(); + str->resize(size); + if (size == 0) { + // Nothing to serialize; avoid taking the address of a zero-length buffer. + return true; + } + return SerializeToArray(&(*str)[0], size); + } + + std::string SerializeAsString() const { + std::string str; + SerializeToString(&str); + return str; + } + + bool SerializeToArray(char *array, size_t size) const { + ProtoBuffer buffer(array, size); + if (absl::Status status = Serialize(buffer); !status.ok()) { + return false; + } + return true; + } + private: - absl::StatusOr BuildMessageInstanceInValue() { - std::string type = MessageTypeName(); - // Allocate space in this payload buffer with space for the string - // length. The message data will be stored in a string field, which - // needs the length before the data. + static constexpr int kValueFieldNumber = 2; + + absl::StatusOr EmbeddedMessageForWire() const { + if (!has_type_url() || !has_value()) { + return absl::FailedPreconditionError( + "Any is missing type_url or embedded value"); + } + const std::string type = MessageTypeName(); + return PhaserBankMakeExisting(type, runtime, value().data()); + } + + absl::StatusOr AllocateEmbeddedMessage(const std::string &type) { absl::StatusOr binary_size = PhaserBankBinarySize(type); if (!binary_size.ok()) { return binary_size.status(); } - char *memory = reinterpret_cast(::toolbelt::PayloadBuffer::Allocate( - &runtime->pb, *binary_size + 4, 4, true)); - // Place the message after the string length field. - absl::StatusOr d = PhaserBankAllocateAtOffset( - type, runtime, runtime->ToOffset(memory + 4)); - if (!d.ok()) { - return d.status(); - } - - // Write the length of the string into the first 4 bytes of the - // allocatedmemory - uint32_t *string_length = reinterpret_cast(memory); - *string_length = *binary_size; - // Set the string indirect field to the address of the string length - // field. - value_.SetNoCopy(memory); + absl::Span memory = value_.Allocate(*binary_size, true); + return PhaserBankAllocateAtOffset(type, runtime, + runtime->ToOffset(memory.data())); + } - return d; + absl::Status MaterializeValueFromProtobufWire(const std::string &wire) { + if (!has_type_url()) { + return absl::InvalidArgumentError( + "Any value on wire requires type_url"); + } + const std::string type = MessageTypeName(); + absl::StatusOr embedded = AllocateEmbeddedMessage(type); + if (!embedded.ok()) { + return embedded.status(); + } + std::unique_ptr embedded_owner(*embedded); + ProtoBuffer sub(wire); + return PhaserBankDeserializeFromBuffer(type, **embedded, sub); } phaser::StringField type_url_; @@ -310,6 +388,12 @@ class AnyField : public IndirectMessageField { return msg_.UnpackTo(msg); } + bool ParseFromString(const std::string &str) { return msg_.ParseFromString(str); } + + bool SerializeToString(std::string *str) const { + return msg_.SerializeToString(str); + } + template bool Is() const { return msg_.Is(); } template void CloneFrom(const T &msg) { msg_.CloneFrom(msg); } diff --git a/phaser/runtime/fields.h b/phaser/runtime/fields.h index 31a046b..3368cb1 100644 --- a/phaser/runtime/fields.h +++ b/phaser/runtime/fields.h @@ -1,4 +1,4 @@ -// Copyright 2024 David Allison +// Copyright 2024-2026 David Allison // All Rights Reserved // See LICENSE file for licensing information. @@ -44,7 +44,7 @@ class Field { bool IsPresent(uint32_t field_id, ::toolbelt::PayloadBuffer *buffer, uint32_t binary_offset) const { - if (field_id == -1) { + if (field_id == static_cast(-1)) { return false; } return buffer->IsPresent(field_id, binary_offset); @@ -441,13 +441,12 @@ class NonEmbeddedStringField { bool IsPlaceholder() const { return msg_ == nullptr; } + // Number of bytes the raw string occupies on the wire (not including any + // field tag or length prefix). Repeated-string serialization is handled by + // StringVectorField, which writes the tag/length and bytes directly, so this + // type intentionally has no standalone Serialize() of its own. size_t SerializedSize() const { return size(); } - absl::Status Serialize(ProtoBuffer &buffer) const { - // TODO: - return absl::OkStatus(); - } - private: ::toolbelt::PayloadBuffer *GetBuffer() const { return msg_->runtime->pb; } @@ -520,7 +519,7 @@ template class IndirectMessageField : public Field { } // Allocate a new message. void *msg_addr = ::toolbelt::PayloadBuffer::Allocate( - GetBufferAddr(), MessageType::BinarySize(), 8); + GetBufferAddr(), MessageType::BinarySize()); ::toolbelt::BufferOffset msg_offset = GetRuntime()->ToOffset(msg_addr); // Assign to the message. msg_.runtime = GetRuntime(); @@ -611,7 +610,7 @@ template class IndirectMessageField : public Field { } // Allocate a new message. void *msg_addr = ::toolbelt::PayloadBuffer::Allocate( - GetBufferAddr(), MessageType::BinarySize(), 8); + GetBufferAddr(), MessageType::BinarySize()); ::toolbelt::BufferOffset msg_offset = GetRuntime()->ToOffset(msg_addr); // Assign to the message. msg_.runtime = GetRuntime(); diff --git a/phaser/runtime/iterators.h b/phaser/runtime/iterators.h index 85cb977..0ddcea4 100644 --- a/phaser/runtime/iterators.h +++ b/phaser/runtime/iterators.h @@ -1,4 +1,4 @@ -// Copyright 2024 David Allison +// Copyright 2024-2026 David Allison // All Rights Reserved // See LICENSE file for licensing information. diff --git a/phaser/runtime/message.cc b/phaser/runtime/message.cc index 585b1fc..b9157cc 100644 --- a/phaser/runtime/message.cc +++ b/phaser/runtime/message.cc @@ -1,9 +1,10 @@ -// Copyright 2024 David Allison +// Copyright 2024-2026 David Allison // All Rights Reserved // See LICENSE file for licensing information. #include "phaser/runtime/message.h" #include "toolbelt/hexdump.h" +#include namespace phaser { @@ -78,7 +79,7 @@ ::toolbelt::PayloadBuffer *NewDynamicBuffer(size_t initial_size, Tuning tuning) { absl::StatusOr<::toolbelt::PayloadBuffer *> r = NewDynamicBuffer( initial_size, [](size_t size) -> void * { return ::malloc(size); }, - [](void *p, size_t old_size, size_t new_size) -> void * { + [](void *p, size_t /*old_size*/, size_t new_size) -> void * { return ::realloc(p, new_size); }, tuning); @@ -98,6 +99,10 @@ absl::StatusOr<::toolbelt::PayloadBuffer *> NewDynamicBuffer( if (!buffer.ok()) { return buffer.status(); } + // Zero the freshly allocated buffer so that unused padding/free regions are + // initialized. This avoids spurious "uninitialised value" reports from tools + // like valgrind when the allocator scans or copies free space. + memset(*buffer, 0, initial_size); ::toolbelt::PayloadBuffer *pb = new (*buffer)::toolbelt::PayloadBuffer( initial_size, [ initial_size, realloc = std::move(realloc) ]( @@ -108,6 +113,11 @@ absl::StatusOr<::toolbelt::PayloadBuffer *> NewDynamicBuffer( << " to " << new_size << std::endl; abort(); } + // Zero the newly grown region for the same reason as above. + if (new_size > old_size) { + memset(reinterpret_cast(*r) + old_size, 0, + new_size - old_size); + } *p = reinterpret_cast<::toolbelt::PayloadBuffer *>(*r); }, tuning == Tuning::kPerformance); diff --git a/phaser/runtime/message.h b/phaser/runtime/message.h index e76dfe8..fd0b70c 100644 --- a/phaser/runtime/message.h +++ b/phaser/runtime/message.h @@ -1,4 +1,4 @@ -// Copyright 2024 David Allison +// Copyright 2024-2026 David Allison // All Rights Reserved // See LICENSE file for licensing information. @@ -26,14 +26,17 @@ namespace phaser { // FieldData is a structure that contains the field numbers and offsets for a // message. It is stored in the payload buffer. +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wc99-extensions" struct FieldData { uint32_t num; struct { uint32_t number; uint32_t offset : 24; // Offset into message. uint32_t id : 8; // Field id for presence bit mask. - } fields[]; + } fields[]; // Flexible array member; data lives in the payload buffer. }; +#pragma clang diagnostic pop enum class FieldType { kFieldInt32, @@ -61,7 +64,7 @@ struct FieldInfo { struct PrimitiveFieldInfo : public FieldInfo { PrimitiveFieldInfo(const std::string &n, FieldType t, int num, off_t off, - bool f = false, bool s = false, bool r = false, + bool f = false, bool /*s*/ = false, bool r = false, bool p = false) : FieldInfo(n, t, num, off), is_fixed(f), is_repeated(r), is_packed(p) {} PrimitiveFieldInfo(const std::string &n, FieldType t, int num, off_t off, @@ -117,9 +120,9 @@ struct MessageRuntime { // have no way to check it's valid). size_t buffer_size = 0; - virtual void AddMetadata(const std::string &name, - ::toolbelt::BufferOffset offset) {} - virtual ::toolbelt::BufferOffset GetMetadata(const std::string &name) { + virtual void AddMetadata(const std::string & /*name*/, + ::toolbelt::BufferOffset /*offset*/) {} + virtual ::toolbelt::BufferOffset GetMetadata(const std::string & /*name*/) { return 0; } @@ -169,8 +172,10 @@ struct DynamicMutableMessageRuntime : public MutableMessageRuntime { std::function free) : MutableMessageRuntime(p), free_(std::move(free)) {} ~DynamicMutableMessageRuntime() override { - if (free_ != nullptr) + if (free_ != nullptr) { + pb->~PayloadBuffer(); free_(pb); + } } std::function free_; }; @@ -229,7 +234,7 @@ struct Message { virtual std::string GetName() const { return "Message"; } virtual std::string GetFullName() const { return "phaser.Message"; } virtual void Clear() {} - virtual void CopyFrom(const Message &src) {} + virtual void CopyFrom(const Message & /*src*/) {} std::shared_ptr runtime; ::toolbelt::BufferOffset absolute_binary_offset; @@ -297,16 +302,16 @@ struct Message { } void *Allocate(size_t size, size_t alignment = 4, bool clear = true) { - return toolbelt::PayloadBuffer::Allocate(&runtime->pb, size, alignment, - clear); + (void)alignment; + return toolbelt::PayloadBuffer::Allocate(&runtime->pb, size, clear); } void Free(void *ptr) { runtime->pb->Free(ptr); } void *Realloc(void *ptr, size_t size, size_t alignment = 4, bool clear = true) { - return toolbelt::PayloadBuffer::Realloc(&runtime->pb, ptr, size, alignment, - clear); + (void)alignment; + return toolbelt::PayloadBuffer::Realloc(&runtime->pb, ptr, size, clear); } toolbelt::BufferOffset ToOffset(void *addr) { @@ -329,7 +334,7 @@ struct Message { // Allocate space for field data in the payload buffer and copy it in. void *fields = ::toolbelt::PayloadBuffer::Allocate( - &runtime->pb, sizeof(MessageType::field_data), 4, false); + &runtime->pb, sizeof(MessageType::field_data), false); memcpy(fields, &MessageType::field_data, sizeof(MessageType::field_data)); ::toolbelt::BufferOffset *header = runtime->pb->ToAddress<::toolbelt::BufferOffset>( diff --git a/phaser/runtime/message_test.cc b/phaser/runtime/message_test.cc index 4085f9f..1732c0e 100644 --- a/phaser/runtime/message_test.cc +++ b/phaser/runtime/message_test.cc @@ -1,4 +1,4 @@ -// Copyright 2024 David Allison +// Copyright 2024-2026 David Allison // All Rights Reserved // See LICENSE file for licensing information. @@ -416,7 +416,7 @@ InnerMessageAllocateAtOffset(std::shared_ptr<::phaser::MessageRuntime> runtime, static std::pair InnerMessageAllocate(std::shared_ptr<::phaser::MessageRuntime> runtime) { void *addr = toolbelt::PayloadBuffer::Allocate( - &runtime->pb, InnerMessage::BinarySize(), 8, true); + &runtime->pb, InnerMessage::BinarySize()); toolbelt::BufferOffset offset = runtime->pb->ToOffset(addr); auto msg = new InnerMessage(runtime, offset); msg->InstallMetadata(); @@ -1087,7 +1087,6 @@ struct TestMessage : public Message { void DebugDump() { runtime->pb->Dump(std::cout); - toolbelt::Hexdump(runtime->pb, runtime->pb->hwm); } }; @@ -1213,7 +1212,7 @@ TestMessageAllocateAtOffset(std::shared_ptr<::phaser::MessageRuntime> runtime, static std::pair TestMessageAllocate(std::shared_ptr<::phaser::MessageRuntime> runtime) { void *addr = toolbelt::PayloadBuffer::Allocate( - &runtime->pb, TestMessage::BinarySize(), 8, true); + &runtime->pb, TestMessage::BinarySize()); toolbelt::BufferOffset offset = runtime->pb->ToOffset(addr); auto msg = new TestMessage(runtime, offset); msg->InstallMetadata(); @@ -1260,7 +1259,7 @@ static struct TestMessageBankRegister { } TestMessage_bank_register; TEST(MessageTest, Basic) { - char *buffer = (char *)malloc(4096); + char *buffer = (char *)calloc(4096, 1); TestMessage msg = TestMessage::CreateMutable(buffer, 4096); msg.DebugDump(); @@ -1292,7 +1291,7 @@ TEST(MessageTest, Basic) { // Copy message to test reading. { - char *buffer2 = (char *)malloc(4096); + char *buffer2 = (char *)calloc(4096, 1); memcpy(buffer2, buffer, 4096); TestMessage msg = TestMessage::CreateReadonly(buffer2); @@ -1312,12 +1311,13 @@ TEST(MessageTest, Basic) { auto &inner2 = msg.m_.Get(); ASSERT_EQ("Inner message", inner2.str_.Get()); ASSERT_EQ(0xdeadbeef, inner2.f_.Get()); + free(buffer2); } free(buffer); } TEST(MessageTest, RepeatedPrimitive) { - char *buffer = (char *)malloc(4096); + char *buffer = (char *)calloc(4096, 1); TestMessage msg = TestMessage::CreateMutable(buffer, 4096); // This field is absent. @@ -1337,7 +1337,7 @@ TEST(MessageTest, RepeatedPrimitive) { // Copy message to test reading. { - char *buffer2 = (char *)malloc(4096); + char *buffer2 = (char *)calloc(4096, 1); memcpy(buffer2, buffer, 4096); TestMessage msg = TestMessage::CreateReadonly(buffer2); @@ -1348,12 +1348,13 @@ TEST(MessageTest, RepeatedPrimitive) { } ASSERT_EQ(1, msg.vi32_.Get(0)); ASSERT_EQ(2, msg.vi32_.Get(1)); + free(buffer2); } free(buffer); } TEST(MessageTest, RepeatedString) { - char *buffer = (char *)malloc(4096); + char *buffer = (char *)calloc(4096, 1); TestMessage msg = TestMessage::CreateMutable(buffer, 4096); msg.DebugDump(); @@ -1379,7 +1380,7 @@ TEST(MessageTest, RepeatedString) { // Copy message to test reading. { - char *buffer2 = (char *)malloc(4096); + char *buffer2 = (char *)calloc(4096, 1); memcpy(buffer2, buffer, 4096); TestMessage msg = TestMessage::CreateReadonly(buffer2); @@ -1392,12 +1393,13 @@ TEST(MessageTest, RepeatedString) { ASSERT_EQ("two", msg.vstr_.Get(1)); ASSERT_EQ("three", msg.vstr_.Get(2)); ASSERT_EQ("four", msg.vstr_.Get(3)); + free(buffer2); } free(buffer); } TEST(MessageTest, RepeatedMessage) { - char *buffer = (char *)malloc(4096); + char *buffer = (char *)calloc(4096, 1); TestMessage msg = TestMessage::CreateMutable(buffer, 4096); InnerMessage *inner1 = msg.vm_.Add(); @@ -1422,7 +1424,7 @@ TEST(MessageTest, RepeatedMessage) { // Copy message to test reading. { - char *buffer2 = (char *)malloc(4096); + char *buffer2 = (char *)calloc(4096, 1); memcpy(buffer2, buffer, 4096); TestMessage msg = TestMessage::CreateReadonly(buffer2); @@ -1433,12 +1435,13 @@ TEST(MessageTest, RepeatedMessage) { auto &inner2 = msg.vm_.Get(2); ASSERT_EQ("two", inner2.str_.Get()); ASSERT_EQ(0x1234, inner2.f_.Get()); + free(buffer2); } free(buffer); } TEST(MessageTest, UnionField) { - char *buffer = (char *)malloc(4096); + char *buffer = (char *)calloc(4096, 1); TestMessage msg = TestMessage::CreateMutable(buffer, 4096); msg.u1_.Set<0>(1234); @@ -1460,8 +1463,66 @@ TEST(MessageTest, UnionField) { free(buffer); } +// Exercises in-place oneof arm switching on a single, reused message. The other +// union tests use a fresh message per arm, so they never replace an already-set +// variable-length arm. Replacing one with a smaller payload reallocates the +// in-buffer storage to a smaller block, which drives toolbelt's +// PayloadBuffer::Realloc -> ShrinkBlock path. +TEST(MessageTest, UnionArmSwitchInPlace) { + char *buffer = (char *)calloc(8192, 1); + TestMessage msg = TestMessage::CreateMutable(buffer, 8192); + + // Grow then repeatedly shrink the string arm on the same message. Each shrink + // reallocates the in-buffer string to a smaller block (ShrinkBlock). + msg.set_u2b(std::string(512, 'x')); + ASSERT_EQ(110, msg.u2_case()); + ASSERT_EQ(std::string(512, 'x'), msg.u2b()); + + for (int len : {256, 64, 16, 4, 1}) { + msg.set_u2b(std::string(len, 'y')); + ASSERT_EQ(110, msg.u2_case()); + ASSERT_EQ(std::string(len, 'y'), msg.u2b()); + } + + // Switch to the scalar arm and back. The high-level setters clear the + // previously-set arm, so the string storage is released rather than leaked. + msg.set_u2a(0x1122334455667788); + ASSERT_EQ(109, msg.u2_case()); + ASSERT_EQ(0x1122334455667788, msg.u2a()); + + msg.set_u2b("back to a string arm"); + ASSERT_EQ(110, msg.u2_case()); + ASSERT_EQ("back to a string arm", msg.u2b()); + + // Message arm: populate it, switch to the scalar arm, then back again. + { + InnerMessage *inner = msg.mutable_u3b(); + inner->str_.Set("an inner message with a reasonably long string payload"); + inner->f_.Set(0xfeedface); + } + ASSERT_EQ(112, msg.u3_case()); + ASSERT_EQ("an inner message with a reasonably long string payload", + msg.u3b().str_.Get()); + + msg.set_u3a(0x0102030405060708); + ASSERT_EQ(111, msg.u3_case()); + ASSERT_EQ(0x0102030405060708, msg.u3a()); + + // Back to the message arm, then shrink the inner string in place. + { + InnerMessage *inner = msg.mutable_u3b(); + inner->str_.Set("longer inner string to allocate a sizeable block here"); + inner->str_.Set("short"); + } + ASSERT_EQ(112, msg.u3_case()); + ASSERT_EQ("short", msg.u3b().str_.Get()); + + msg.DebugDump(); + free(buffer); +} + TEST(MessageTest, ClearBasic) { - char *buffer = (char *)malloc(4096); + char *buffer = (char *)calloc(4096, 1); TestMessage msg = TestMessage::CreateMutable(buffer, 4096); msg.x_.Set(1234); @@ -1487,7 +1548,7 @@ TEST(MessageTest, ClearBasic) { } TEST(MessageTest, ClearRepeated) { - char *buffer = (char *)malloc(4096); + char *buffer = (char *)calloc(4096, 1); TestMessage msg = TestMessage::CreateMutable(buffer, 4096); msg.vi32_.Add(1); @@ -1522,7 +1583,7 @@ TEST(MessageTest, ClearRepeated) { } TEST(MessageTest, ClearUnion) { - char *buffer = (char *)malloc(4096); + char *buffer = (char *)calloc(4096, 1); TestMessage msg = TestMessage::CreateMutable(buffer, 4096); msg.u1_.Set<0>(1234); @@ -1542,7 +1603,7 @@ TEST(MessageTest, ClearUnion) { } TEST(MessageTest, ProtobufBasic) { - char *buffer = (char *)malloc(4096); + char *buffer = (char *)calloc(4096, 1); TestMessage msg = TestMessage::CreateMutable(buffer, 4096); msg.set_x(1234); @@ -1565,7 +1626,7 @@ TEST(MessageTest, ProtobufBasic) { // Copy message to test reading. { - char *buffer2 = (char *)malloc(4096); + char *buffer2 = (char *)calloc(4096, 1); memcpy(buffer2, buffer, 4096); TestMessage msg = TestMessage::CreateReadonly(buffer2); @@ -1578,12 +1639,13 @@ TEST(MessageTest, ProtobufBasic) { ASSERT_TRUE(msg.has_m()); ASSERT_EQ("Inner message", msg.m().str()); ASSERT_EQ(0xdeadbeef, msg.m().f()); + free(buffer2); } free(buffer); } TEST(MessageTest, ProtobufSerializationSizeBasic) { - char *buffer = (char *)malloc(4096); + char *buffer = (char *)calloc(4096, 1); TestMessage msg = TestMessage::CreateMutable(buffer, 4096); msg.set_x(1); // Tag 100 @@ -1616,15 +1678,13 @@ TEST(MessageTest, ProtobufSerializationSizeBasic) { pb_inner->set_f(0xdeadbeef); std::string str = pb_msg.SerializeAsString(); - toolbelt::Hexdump(str.data(), str.size()); - size_t size = msg.SerializedSize(); ASSERT_EQ(pb_msg.ByteSizeLong(), size); free(buffer); } TEST(MessageTest, ProtobufSerializationSizeUnion) { - char *buffer = (char *)malloc(4096); + char *buffer = (char *)calloc(4096, 1); TestMessage msg = TestMessage::CreateMutable(buffer, 4096); msg.set_u1a(1234); // Tag 107. @@ -1656,15 +1716,13 @@ TEST(MessageTest, ProtobufSerializationSizeUnion) { pb_inner->set_f(0xdeadbeef); std::string str = pb_msg.SerializeAsString(); - toolbelt::Hexdump(str.data(), str.size()); - size_t size = msg.SerializedSize(); ASSERT_EQ(pb_msg.ByteSizeLong(), size); free(buffer); } TEST(MessageTest, ProtobufSerializationSizeRepeatedPrimitive) { - char *buffer = (char *)malloc(4096); + char *buffer = (char *)calloc(4096, 1); TestMessage msg = TestMessage::CreateMutable(buffer, 4096); // Tag 104. @@ -1694,15 +1752,13 @@ TEST(MessageTest, ProtobufSerializationSizeRepeatedPrimitive) { pb_msg.add_vstr("two"); std::string str = pb_msg.SerializeAsString(); - toolbelt::Hexdump(str.data(), str.size()); - size_t size = msg.SerializedSize(); ASSERT_EQ(pb_msg.ByteSizeLong(), size); free(buffer); } TEST(MessageTest, ProtobufSerializationBasic) { - char *buffer = (char *)malloc(4096); + char *buffer = (char *)calloc(4096, 1); TestMessage msg = TestMessage::CreateMutable(buffer, 4096); phaser::ProtoBuffer pb; @@ -1714,8 +1770,6 @@ TEST(MessageTest, ProtobufSerializationBasic) { inner->set_f(0xdeadbeef); ASSERT_TRUE(msg.Serialize(pb).ok()); - toolbelt::Hexdump(pb.data(), pb.size()); - foo::bar::TestMessage pb_msg; pb_msg.set_x(1); pb_msg.set_y(0xffff); @@ -1725,15 +1779,13 @@ TEST(MessageTest, ProtobufSerializationBasic) { pb_inner->set_f(0xdeadbeef); std::string str = pb_msg.SerializeAsString(); - toolbelt::Hexdump(str.data(), str.size()); - std::string pb_str(pb.data(), pb.size()); ASSERT_EQ(str, pb_str); free(buffer); } TEST(MessageTest, ProtobufSerializationRepeated) { - char *buffer = (char *)malloc(4096); + char *buffer = (char *)calloc(4096, 1); TestMessage msg = TestMessage::CreateMutable(buffer, 4096); phaser::ProtoBuffer pb; @@ -1755,8 +1807,6 @@ TEST(MessageTest, ProtobufSerializationRepeated) { inner2->set_f(0x1234); ASSERT_TRUE(msg.Serialize(pb).ok()); - toolbelt::Hexdump(pb.data(), pb.size()); - foo::bar::TestMessage pb_msg; pb_msg.add_vi32(1); pb_msg.add_vi32(2); @@ -1776,15 +1826,13 @@ TEST(MessageTest, ProtobufSerializationRepeated) { pb_inner2->set_f(0x1234); std::string str = pb_msg.SerializeAsString(); - toolbelt::Hexdump(str.data(), str.size()); - std::string pb_str(pb.data(), pb.size()); ASSERT_EQ(str, pb_str); free(buffer); } TEST(MessageTest, ProtobufSerializationUnion) { - char *buffer = (char *)malloc(4096); + char *buffer = (char *)calloc(4096, 1); TestMessage msg = TestMessage::CreateMutable(buffer, 4096); phaser::ProtoBuffer pb; @@ -1796,8 +1844,6 @@ TEST(MessageTest, ProtobufSerializationUnion) { inner->set_f(0xdeadbeef); ASSERT_TRUE(msg.Serialize(pb).ok()); - toolbelt::Hexdump(pb.data(), pb.size()); - foo::bar::TestMessage pb_msg; pb_msg.set_u1a(1234); pb_msg.set_u2b("Hello, world!"); @@ -1807,8 +1853,6 @@ TEST(MessageTest, ProtobufSerializationUnion) { pb_inner->set_f(0xdeadbeef); std::string str = pb_msg.SerializeAsString(); - toolbelt::Hexdump(str.data(), str.size()); - std::string pb_str(pb.data(), pb.size()); ASSERT_EQ(str, pb_str); free(buffer); @@ -1826,9 +1870,7 @@ TEST(MessageTest, ProtobufDeserializationBasic) { pb_inner->set_f(0xdeadbeef); std::string str = pb_msg.SerializeAsString(); - toolbelt::Hexdump(str.data(), str.size()); - - char *buffer = (char *)malloc(4096); + char *buffer = (char *)calloc(4096, 1); TestMessage msg = TestMessage::CreateMutable(buffer, 4096); phaser::ProtoBuffer pb_buffer(str); @@ -1840,7 +1882,6 @@ TEST(MessageTest, ProtobufDeserializationBasic) { status = msg.Serialize(pb); ASSERT_TRUE(status.ok()); - toolbelt::Hexdump(pb.data(), pb.size()); ASSERT_EQ(str, std::string(pb.data(), pb.size())); free(buffer); } @@ -1866,9 +1907,7 @@ TEST(MessageTest, ProtobufDeserializationRepeated) { pb_inner2->set_f(0x1234); std::string str = pb_msg.SerializeAsString(); - toolbelt::Hexdump(str.data(), str.size()); - - char *buffer = (char *)malloc(4096); + char *buffer = (char *)calloc(4096, 1); TestMessage msg = TestMessage::CreateMutable(buffer, 4096); phaser::ProtoBuffer pb_buffer(str); @@ -1881,7 +1920,6 @@ TEST(MessageTest, ProtobufDeserializationRepeated) { status = msg.Serialize(pb); ASSERT_TRUE(status.ok()); - toolbelt::Hexdump(pb.data(), pb.size()); ASSERT_EQ(str, std::string(pb.data(), pb.size())); free(buffer); } @@ -1897,9 +1935,7 @@ TEST(MessageTest, ProtobufDeserializationUnion) { pb_inner->set_f(0xdeadbeef); std::string str = pb_msg.SerializeAsString(); - toolbelt::Hexdump(str.data(), str.size()); - - char *buffer = (char *)malloc(4096); + char *buffer = (char *)calloc(4096, 1); TestMessage msg = TestMessage::CreateMutable(buffer, 4096); phaser::ProtoBuffer pb_buffer(str); @@ -1911,7 +1947,6 @@ TEST(MessageTest, ProtobufDeserializationUnion) { status = msg.Serialize(pb); ASSERT_TRUE(status.ok()); - toolbelt::Hexdump(pb.data(), pb.size()); ASSERT_EQ(str, std::string(pb.data(), pb.size())); free(buffer); } @@ -1983,7 +2018,7 @@ TEST(MessageTest, Print) { } TEST(MessageTest, MessageInfo) { - char *buffer = (char *)malloc(4096); + char *buffer = (char *)calloc(4096, 1); InnerMessage msg = InnerMessage::CreateMutable(buffer, 4096); const ::phaser::MessageInfo *info = msg.GetMessageInfo(); ASSERT_EQ("foo.bar.InnerMessage", info->full_name); @@ -1994,7 +2029,7 @@ TEST(MessageTest, MessageInfo) { } TEST(MessageTest, PhaserBank) { - char *buffer = (char *)malloc(4096); + char *buffer = (char *)calloc(4096, 1); TestMessage msg = TestMessage::CreateMutable(buffer, 4096); auto status = ::phaser::PhaserBankAllocate( "foo.bar.InnerMessage", msg.runtime); @@ -2004,6 +2039,8 @@ TEST(MessageTest, PhaserBank) { inner->set_str("Inner message"); ASSERT_EQ("Inner message", msg.m().str()); + delete inner; + free(buffer); } int main(int argc, char **argv) { diff --git a/phaser/runtime/phaser_bank.cc b/phaser/runtime/phaser_bank.cc index 3a5c8c8..aa8c8a9 100644 --- a/phaser/runtime/phaser_bank.cc +++ b/phaser/runtime/phaser_bank.cc @@ -1,4 +1,4 @@ -// Copyright 2024 David Allison +// Copyright 2024-2026 David Allison // All Rights Reserved // See LICENSE file for licensing information. diff --git a/phaser/runtime/phaser_bank.h b/phaser/runtime/phaser_bank.h index 8a4d518..3253674 100644 --- a/phaser/runtime/phaser_bank.h +++ b/phaser/runtime/phaser_bank.h @@ -1,4 +1,4 @@ -// Copyright 2024 David Allison +// Copyright 2024-2026 David Allison // All Rights Reserved // See LICENSE file for licensing information. diff --git a/phaser/runtime/runtime.h b/phaser/runtime/runtime.h index 4941be6..e9f623a 100644 --- a/phaser/runtime/runtime.h +++ b/phaser/runtime/runtime.h @@ -1,4 +1,4 @@ -// Copyright 2024 David Allison +// Copyright 2024-2026 David Allison // All Rights Reserved // See LICENSE file for licensing information. @@ -38,7 +38,11 @@ DEFINE_PRIMITIVE_FIELD_STREAMER(Bool) inline std::string StringWithOctalNonPrintables(std::string_view str) { std::string result; for (char c : str) { - if (c >= 32 && c < 127) { + if (c == '\\' || c == '\'' || c == '"') { + // Printable characters that protobuf still escapes with a backslash. + result.push_back('\\'); + result.push_back(c); + } else if (c >= 32 && c < 127) { result.push_back(c); } else { // Cast to unsigned to avoid sign extension. diff --git a/phaser/runtime/union.h b/phaser/runtime/union.h index 2fe01fa..b508638 100644 --- a/phaser/runtime/union.h +++ b/phaser/runtime/union.h @@ -1,4 +1,4 @@ -// Copyright 2024 David Allison +// Copyright 2024-2026 David Allison // All Rights Reserved // See LICENSE file for licensing information. @@ -45,7 +45,7 @@ class UnionMemberField { } \ return GetBuffer(runtime)->template Get(abs_offset); \ } \ - void Print(std::ostream &os, int indent, \ + void Print(std::ostream &os, int /*indent*/, \ const std::shared_ptr &runtime, \ uint32_t abs_offset) const { \ os << Get(runtime, abs_offset); \ @@ -54,16 +54,17 @@ class UnionMemberField { uint32_t abs_offset) { \ GetBuffer(runtime)->Set(abs_offset, v); \ } \ - void SetOffset(const std::shared_ptr &runtime, \ - uint32_t abs_offset, toolbelt::BufferOffset msg_offset) {} \ + void SetOffset(const std::shared_ptr & /*runtime*/, \ + uint32_t /*abs_offset*/, \ + toolbelt::BufferOffset /*msg_offset*/) {} \ \ bool Equal(const Union##cname##Field &other, \ const std::shared_ptr &runtime, \ uint32_t abs_offset) { \ return Get(runtime, abs_offset) == other.Get(runtime, abs_offset); \ } \ - void Clear(const std::shared_ptr &runtime, \ - uint32_t abs_offset) {} \ + void Clear(const std::shared_ptr & /*runtime*/, \ + uint32_t /*abs_offset*/) {} \ size_t SerializedSize(int number, \ const std::shared_ptr &runtime, \ uint32_t abs_offset) const { \ @@ -139,7 +140,7 @@ class UnionEnumField : public UnionMemberField { abs_offset)); } - void Print(std::ostream &os, int indent, + void Print(std::ostream &os, int /*indent*/, const std::shared_ptr &runtime, uint32_t abs_offset) const { os << Stringizer()(Get(runtime, abs_offset)); @@ -151,8 +152,9 @@ class UnionEnumField : public UnionMemberField { ->template Get::type>(abs_offset); } - void SetOffset(const std::shared_ptr &runtime, uint32_t abs_offset, - toolbelt::BufferOffset msg_offset) {} + void SetOffset(const std::shared_ptr & /*runtime*/, + uint32_t /*abs_offset*/, + toolbelt::BufferOffset /*msg_offset*/) {} void Set(Enum e, const std::shared_ptr &runtime, uint32_t abs_offset) { @@ -170,8 +172,8 @@ class UnionEnumField : public UnionMemberField { uint32_t abs_offset) { return Get(runtime, abs_offset) == other.Get(runtime, abs_offset); } - void Clear(const std::shared_ptr &runtime, - uint32_t abs_offset) {} + void Clear(const std::shared_ptr & /*runtime*/, + uint32_t /*abs_offset*/) {} size_t SerializedSize(int number, const std::shared_ptr &runtime, @@ -214,7 +216,7 @@ class UnionStringField : public UnionMemberField { return GetBuffer(runtime)->GetStringView(abs_offset); } - void Print(std::ostream &os, int indent, + void Print(std::ostream &os, int /*indent*/, const std::shared_ptr &runtime, uint32_t abs_offset) const { os << "\"" << std::string(Get(runtime, abs_offset)) << "\""; @@ -227,8 +229,9 @@ class UnionStringField : public UnionMemberField { return *addr != 0; } - void SetOffset(const std::shared_ptr &runtime, - uint32_t abs_offset, toolbelt::BufferOffset msg_offset) {} + void SetOffset(const std::shared_ptr & /*runtime*/, + uint32_t /*abs_offset*/, + toolbelt::BufferOffset /*msg_offset*/) {} template void Set(Str s, const std::shared_ptr &runtime, @@ -339,7 +342,7 @@ class UnionMessageField : public UnionMemberField { } // Allocate a new message. void *msg_addr = ::toolbelt::PayloadBuffer::Allocate( - GetBufferAddr(runtime), MessageType::BinarySize(), 8); + GetBufferAddr(runtime), MessageType::BinarySize()); ::toolbelt::BufferOffset msg_offset = GetBuffer(runtime)->ToOffset(msg_addr); // Assign to the message. @@ -387,7 +390,7 @@ class UnionMessageField : public UnionMemberField { } // Allocate a new message. void *msg_addr = ::toolbelt::PayloadBuffer::Allocate( - GetBufferAddr(runtime), MessageType::BinarySize(), 8); + GetBufferAddr(runtime), MessageType::BinarySize()); ::toolbelt::BufferOffset msg_offset = GetBuffer(runtime)->ToOffset(msg_addr); // Assign to the message. @@ -447,7 +450,7 @@ class UnionMessageField : public UnionMemberField { return s.status(); } void *msg_addr = ::toolbelt::PayloadBuffer::Allocate( - GetBufferAddr(runtime), MessageType::BinarySize(), 8); + GetBufferAddr(runtime), MessageType::BinarySize()); ::toolbelt::BufferOffset msg_offset = GetBuffer(runtime)->ToOffset(msg_addr); // Assign to the message. @@ -606,6 +609,14 @@ template class UnionField : public Field { t.Clear(GetRuntime(), GetMessageBinaryStart() + relative_binary_offset_ + 4); *discrim = 0; + // Reset the buffer-offset word in the shared value slot. Scalar/enum arms + // store their value inline here and their Clear() is a no-op, so without + // this a later switch to a variable-length arm (string/message) would + // misread the leftover scalar bytes as an allocated buffer offset. + ::toolbelt::BufferOffset *slot = + GetRuntime()->template ToAddress<::toolbelt::BufferOffset>( + GetMessageBinaryStart() + relative_binary_offset_ + 4); + *slot = 0; } template size_t SerializedSize(int discriminator) const { @@ -632,7 +643,7 @@ template class UnionField : public Field { } template - absl::Status Deserialize(int discriminator, ProtoBuffer &buffer) { + absl::Status Deserialize(int /*discriminator*/, ProtoBuffer &buffer) { int32_t relative_offset = Message::GetMessage(this, source_offset_) ->FindFieldOffset(field_numbers_[Id]); if (relative_offset < 0) { // Field not present. diff --git a/phaser/runtime/vectors.h b/phaser/runtime/vectors.h index 66703cd..1ecd4be 100644 --- a/phaser/runtime/vectors.h +++ b/phaser/runtime/vectors.h @@ -1,4 +1,4 @@ -// Copyright 2024 David Allison +// Copyright 2024-2026 David Allison // All Rights Reserved // See LICENSE file for licensing information. @@ -742,7 +742,7 @@ template class MessageVectorField : public Field { return empty_; } auto hdr = Header(offset); - if (index >= hdr->num_elements) { + if (static_cast(index) >= hdr->num_elements) { return empty_; } ::toolbelt::BufferOffset *data = @@ -750,7 +750,7 @@ template class MessageVectorField : public Field { if (data[index] == 0) { return empty_; } - if (index >= msgs_.size()) { + if (static_cast(index) >= msgs_.size()) { msgs_.resize(index + 1); } if (msgs_[index].empty()) { @@ -789,7 +789,7 @@ template class MessageVectorField : public Field { T *Add() { // Allocate a new message. void *binary = ::toolbelt::PayloadBuffer::Allocate( - GetBufferAddr(), T::BinarySize(), 8, true); + GetBufferAddr(), T::BinarySize()); ::toolbelt::BufferOffset absolute_binary_offset = GetRuntime()->ToOffset(binary); ::toolbelt::PayloadBuffer::VectorPush<::toolbelt::BufferOffset>( @@ -803,7 +803,7 @@ template class MessageVectorField : public Field { const T &Get(size_t index) const { return (*this)[index].Get(); } T *Mutable(int index) { - if (index >= msgs_.size()) { + if (static_cast(index) >= msgs_.size()) { ::toolbelt::PayloadBuffer::VectorResize<::toolbelt::BufferOffset>( GetBufferAddr(), Header(), index + 1); msgs_.resize(index + 1); @@ -811,7 +811,7 @@ template class MessageVectorField : public Field { if (msgs_[index].IsPlaceholder()) { void *binary = ::toolbelt::PayloadBuffer::Allocate( - GetBufferAddr(), T::BinarySize(), 8, true); + GetBufferAddr(), T::BinarySize()); ::toolbelt::BufferOffset absolute_binary_offset = GetRuntime()->ToOffset(binary); @@ -828,7 +828,7 @@ template class MessageVectorField : public Field { } void SetOffset(int index, toolbelt::BufferOffset offset) { - if (index >= msgs_.size()) { + if (static_cast(index) >= msgs_.size()) { msgs_.resize(index + 1); } auto hdr = Header(); @@ -1080,7 +1080,7 @@ class StringVectorField : public Field { return empty_; } auto hdr = Header(offset); - if (index >= hdr->num_elements) { + if (static_cast(index) >= hdr->num_elements) { return empty_; } ::toolbelt::BufferOffset *data = @@ -1088,7 +1088,7 @@ class StringVectorField : public Field { if (data[index] == 0) { return empty_; } - if (index >= strings_.size()) { + if (static_cast(index) >= strings_.size()) { strings_.resize(index + 1); } if (strings_[index].IsPlaceholder()) { @@ -1118,7 +1118,7 @@ class StringVectorField : public Field { template void push_back(Str s) { // Allocate string header in buffer. void *str_hdr = ::toolbelt::PayloadBuffer::Allocate( - GetBufferAddr(), sizeof(toolbelt::StringHeader), 4); + GetBufferAddr(), sizeof(toolbelt::StringHeader)); ::toolbelt::BufferOffset hdr_offset = GetRuntime()->ToOffset(str_hdr); ::toolbelt::PayloadBuffer::SetString(GetBufferAddr(), s, hdr_offset); @@ -1138,7 +1138,7 @@ class StringVectorField : public Field { std::string_view Get(int index) const { return (*this)[index].Get(); } template void Set(int index, Str s) { - if (index >= strings_.size()) { + if (static_cast(index) >= strings_.size()) { ::toolbelt::PayloadBuffer::VectorResize<::toolbelt::BufferOffset>( GetBufferAddr(), Header(), index + 1); strings_.resize(index + 1); @@ -1147,7 +1147,7 @@ class StringVectorField : public Field { if (strings_[index].IsPlaceholder()) { // Allocate string header in buffer. void *str_hdr = ::toolbelt::PayloadBuffer::Allocate( - GetBufferAddr(), sizeof(toolbelt::StringHeader), 4); + GetBufferAddr(), sizeof(toolbelt::StringHeader)); ::toolbelt::BufferOffset hdr_offset = GetRuntime()->ToOffset(str_hdr); auto hdr = Header(); diff --git a/phaser/runtime/wireformat.h b/phaser/runtime/wireformat.h index d1c9b89..c01103c 100644 --- a/phaser/runtime/wireformat.h +++ b/phaser/runtime/wireformat.h @@ -1,4 +1,4 @@ -// Copyright 2024 David Allison +// Copyright 2024-2026 David Allison // All Rights Reserved // See LICENSE file for licensing information. @@ -12,6 +12,7 @@ #include #include #include +#include namespace phaser { @@ -40,6 +41,9 @@ class ProtoBuffer { if (start_ == nullptr) { abort(); } + // Initialize the buffer so unwritten regions are never read as + // uninitialised memory (avoids spurious valgrind reports). + memset(start_, 0, size_); addr_ = start_; end_ = start_ + size_; } @@ -94,12 +98,22 @@ class ProtoBuffer { end_ = start_; } + // ZigZag maps a signed integer to an unsigned representation in which small + // magnitudes (positive or negative) become small values, as required by the + // protobuf sint32/sint64 wire types. The math is done on the unsigned type so + // it is free of signed overflow/shift undefined behavior and is correct for + // any width of T (the previous implementation hard-coded a 31-bit shift and + // produced wrong results for 64-bit values). template static T ZigZag(T value) { - return (value << 1) ^ (value >> 31); + using U = std::make_unsigned_t; + constexpr unsigned kSignShift = sizeof(T) * 8 - 1; + const U u = static_cast(value); + return static_cast((u << 1) ^ static_cast(-(u >> kSignShift))); } template static T ZagZig(T value) { - const uint64_t mask = (1LL << (sizeof(T) * 8 - 1)) - 1LL; - return ((value >> 1) & static_cast(mask)) ^ -(value & 1); + using U = std::make_unsigned_t; + const U u = static_cast(value); + return static_cast((u >> 1) ^ static_cast(-(u & U(1)))); } template static constexpr WireType FixedWireType() { @@ -116,18 +130,29 @@ class ProtoBuffer { return VarintSize(MakeTag(field_number, wire_type)); } - template static size_t VarintSize(T value) { - if (Signed) { - value = ZigZag(value); + template static uint64_t ToVarintWire(T value) { + if constexpr (Signed) { + return static_cast(ZigZag(value)); + } + if constexpr (std::is_same_v) { + return static_cast(value); + } + if constexpr (std::is_signed_v) { + // Protobuf int32/int64 varints sign-extend to 64 bits when negative. + return static_cast(static_cast(value)); } + return static_cast(value); + } + + template static size_t VarintSize(T value) { + uint64_t uvalue = ToVarintWire(value); size_t size = 0; for (;;) { - if ((value & ~0x7f) == 0) { + if ((uvalue & ~uint64_t(0x7f)) == 0) { return size + 1; - } else { - size++; - value >>= 7; } + size++; + uvalue >>= 7; } } @@ -143,20 +168,18 @@ class ProtoBuffer { // Serialization functions. template absl::Status SerializeRawVarint(T value) { - if (Signed) { - value = ZigZag(value); - } - if (auto status = HasSpaceFor(VarintSize(value)); !status.ok()) { + uint64_t uvalue = ToVarintWire(value); + if (auto status = HasSpaceFor(VarintSize(uvalue)); + !status.ok()) { return status; } for (;;) { - if ((value & ~0x7f) == 0) { - *addr_++ = static_cast(value); + if ((uvalue & ~uint64_t(0x7f)) == 0) { + *addr_++ = static_cast(uvalue); break; - } else { - *addr_++ = static_cast((value & 0xfF) | 0x80); - value >>= 7; } + *addr_++ = static_cast((uvalue & 0x7f) | 0x80); + uvalue >>= 7; } return absl::OkStatus(); } @@ -283,15 +306,24 @@ class ProtoBuffer { // Tag has already been read. template absl::StatusOr DeserializeVarint() { - uint32_t value = 0; - for (int shift = 0; shift < sizeof(T) * 8; shift += 7) { + uint64_t value = 0; + for (int shift = 0; shift < 64; shift += 7) { if (absl::Status status = Check(1); !status.ok()) { return status; } - uint32_t byte = *addr_++; + uint64_t byte = static_cast(*addr_++); value |= (byte & 0x7f) << shift; if ((byte & 0x80) == 0) { - return Signed ? ZagZig(value) : value; + if constexpr (Signed) { + if constexpr (std::is_same_v) { + return static_cast(value != 0); + } else { + using ST = std::make_signed_t; + return static_cast(ZagZig(static_cast(value))); + } + } else { + return static_cast(value); + } } } return absl::InternalError("Varint too long"); @@ -360,6 +392,8 @@ class ProtoBuffer { if (new_start == nullptr) { abort(); } + // Zero the newly grown region. + memset(new_start + size_, 0, new_size - size_); size_t curr_length = addr_ - start_; start_ = new_start; addr_ = start_ + curr_length; diff --git a/phaser/runtime/wireformat_test.cc b/phaser/runtime/wireformat_test.cc index dd07510..a4487f0 100644 --- a/phaser/runtime/wireformat_test.cc +++ b/phaser/runtime/wireformat_test.cc @@ -1,4 +1,4 @@ -// Copyright 2024 David Allison +// Copyright 2024-2026 David Allison // All Rights Reserved // See LICENSE file for licensing information. @@ -6,6 +6,7 @@ #include "phaser/runtime/runtime.h" #include "phaser/runtime/wireformat.h" #include "toolbelt/hexdump.h" +#include #include #include @@ -16,6 +17,8 @@ TEST(Wireformat, Sizes) { ASSERT_EQ(1, (ProtoBuffer::VarintSize(1))); ASSERT_EQ(2, (ProtoBuffer::VarintSize(0x80))); ASSERT_EQ(3, (ProtoBuffer::VarintSize(0x8000))); + // int32 varints use unsigned encoding (10 bytes for negative values). + ASSERT_EQ(10, (ProtoBuffer::VarintSize(-123456))); ASSERT_EQ(1, ProtoBuffer::TagSize(1, WireType::kVarint)); ASSERT_EQ(1, ProtoBuffer::TagSize(0xf, WireType::kVarint)); @@ -27,6 +30,83 @@ TEST(Wireformat, Sizes) { ASSERT_EQ(7, ProtoBuffer::StringSize(1, "hello")); } +TEST(Wireformat, ZigZagKnownValues) { + // Canonical protobuf zigzag mappings. ZigZag() returns the encoded value + // reinterpreted as the signed type, so compare via the unsigned bit pattern. + EXPECT_EQ(0u, static_cast(ProtoBuffer::ZigZag(0))); + EXPECT_EQ(1u, static_cast(ProtoBuffer::ZigZag(-1))); + EXPECT_EQ(2u, static_cast(ProtoBuffer::ZigZag(1))); + EXPECT_EQ(3u, static_cast(ProtoBuffer::ZigZag(-2))); + EXPECT_EQ(4u, static_cast(ProtoBuffer::ZigZag(2))); + EXPECT_EQ(4294967294u, + static_cast(ProtoBuffer::ZigZag(INT32_MAX))); + EXPECT_EQ(4294967295u, + static_cast(ProtoBuffer::ZigZag(INT32_MIN))); + + // 64-bit values exercised the bug: the old implementation hard-coded a + // 31-bit sign shift and produced garbage for anything outside the int32 + // range. + EXPECT_EQ(1u, static_cast(ProtoBuffer::ZigZag(-1))); + EXPECT_EQ(uint64_t(1) << 32, + static_cast(ProtoBuffer::ZigZag(int64_t(1) + << 31))); + EXPECT_EQ(0xFFFFFFFFFFFFFFFEull, + static_cast(ProtoBuffer::ZigZag(INT64_MAX))); + EXPECT_EQ(0xFFFFFFFFFFFFFFFFull, + static_cast(ProtoBuffer::ZigZag(INT64_MIN))); +} + +TEST(Wireformat, ZigZagRoundTrip) { + EXPECT_EQ(0, ProtoBuffer::ZagZig(ProtoBuffer::ZigZag(0))); + for (int32_t v : + {INT32_MIN, INT32_MIN + 1, -123456, -2, -1, 0, 1, 2, 123456, + INT32_MAX - 1, INT32_MAX}) { + EXPECT_EQ(v, ProtoBuffer::ZagZig(ProtoBuffer::ZigZag(v))) + << "int32 value " << v; + } + for (int64_t v : {INT64_MIN, INT64_MIN + 1, int64_t(INT32_MIN) - 1, + int64_t(INT32_MIN), int64_t(-1) << 40, int64_t(-1), + int64_t(0), int64_t(1), int64_t(1) << 31, int64_t(1) << 40, + int64_t(INT32_MAX) + 1, INT64_MAX - 1, INT64_MAX}) { + EXPECT_EQ(v, ProtoBuffer::ZagZig(ProtoBuffer::ZigZag(v))) + << "int64 value " << v; + } +} + +TEST(Wireformat, SintVarintRoundTrip) { + // Full serialize/deserialize round trip through the varint wire format for + // the sint32/sint64 (zigzag) encodings. + auto round_trip64 = [](int64_t v) { + ProtoBuffer out; + ASSERT_TRUE((out.SerializeRawVarint(v).ok())); + // ProtoBuffer(std::string_view) does not own its storage, so the encoded + // string must outlive the reader. + std::string encoded = out.AsString(); + ProtoBuffer in(encoded); + absl::StatusOr decoded = in.DeserializeVarint(); + ASSERT_TRUE(decoded.ok()); + EXPECT_EQ(v, *decoded) << "int64 value " << v; + }; + for (int64_t v : {INT64_MIN, int64_t(INT32_MIN) - 1, int64_t(-1), + int64_t(0), int64_t(1), int64_t(1) << 33, + int64_t(INT32_MAX) + 1, INT64_MAX}) { + round_trip64(v); + } + + auto round_trip32 = [](int32_t v) { + ProtoBuffer out; + ASSERT_TRUE((out.SerializeRawVarint(v).ok())); + std::string encoded = out.AsString(); + ProtoBuffer in(encoded); + absl::StatusOr decoded = in.DeserializeVarint(); + ASSERT_TRUE(decoded.ok()); + EXPECT_EQ(v, *decoded) << "int32 value " << v; + }; + for (int32_t v : {INT32_MIN, -1, 0, 1, INT32_MAX}) { + round_trip32(v); + } +} + int main(int argc, char **argv) { testing::InitGoogleTest(&argc, argv); diff --git a/phaser/stress_test.cc b/phaser/stress_test.cc new file mode 100644 index 0000000..cbd2240 --- /dev/null +++ b/phaser/stress_test.cc @@ -0,0 +1,125 @@ +// Copyright 2024-2026 David Allison +// All Rights Reserved +// See LICENSE file for licensing information. + +#include "phaser/runtime/message.h" +#include "test_helpers.h" +#include "phaser/testdata/TestMessage.phaser.h" +#include +#include + +namespace { + +using foo::bar::phaser::TestMessage; + +// Dynamic buffer: repeated strings until a large count (exercises growth). +TEST(StressTest, DynamicStringPressure) { + TestMessage msg(512); + ASSERT_FALSE(msg.allocate_buffer(64 * 1024).empty()); + for (int i = 0; i < 200; i++) { + msg.add_vstr(::phaser::test::MakePatternString(64, 's')); + } + EXPECT_GE(msg.vstr_size(), 150u); +} + +TEST(StressTest, FixedBufferTuningSizeMode) { + constexpr size_t kBufSize = 4096; + char *buffer = static_cast(calloc(kBufSize, 1)); + ASSERT_NE(nullptr, buffer); + + TestMessage msg = TestMessage::CreateMutable(buffer, kBufSize, + ::phaser::Tuning::kSize); + for (int i = 0; i < 200; i++) { + msg.add_vi32(i); + } + EXPECT_GE(msg.vi32_size(), 100u); + free(buffer); +} + +TEST(StressTest, DynamicBufferGrowsAndPreservesScalars) { + TestMessage msg(256); + msg.set_x(1234); + msg.set_s("seed"); + ASSERT_FALSE(msg.allocate_buffer(64 * 1024).empty()); + + for (int i = 0; i < 200; i++) { + msg.add_vstr(::phaser::test::MakePatternString(48, 'v')); + } + EXPECT_EQ(1234, msg.x()); + EXPECT_EQ("seed", msg.s()); + EXPECT_GE(msg.vstr_size(), 150u); +} + +TEST(StressTest, DynamicExplicitBufferExpansion) { + TestMessage msg(512); + msg.set_s("expand-me"); + auto span = msg.allocate_buffer(32 * 1024); + ASSERT_FALSE(span.empty()); + EXPECT_GE(span.size(), 32u * 1024u); + EXPECT_EQ("expand-me", msg.s()); +} + +TEST(StressTest, AllocFailsAtStart) { + auto status = ::phaser::NewDynamicBuffer( + 1024, ::phaser::test::AllocUntilLimit(0), + ::phaser::test::ReallocAlwaysFails(), ::phaser::Tuning::kPerformance); + EXPECT_FALSE(status.ok()); +} + +TEST(StressTest, CustomAllocSucceeds) { + TestMessage msg = TestMessage::CreateDynamicMutable( + 512, ::phaser::test::AllocUntilLimit(64 * 1024), [](void *p) { free(p); }, + [](void *p, size_t, size_t new_size) -> absl::StatusOr { + void *r = realloc(p, new_size); + if (r == nullptr) { + return absl::ResourceExhaustedError("realloc failed"); + } + return r; + }); + msg.set_s("custom-alloc"); + EXPECT_EQ("custom-alloc", msg.s()); +} + +TEST(StressTest, ReallocFailureAborts) { + EXPECT_DEATH( + { + TestMessage msg = TestMessage::CreateDynamicMutable( + 256, ::phaser::test::AllocUntilLimit(1024 * 1024), + [](void *p) { free(p); }, ::phaser::test::ReallocAlwaysFails()); + for (int i = 0; i < 2000; i++) { + msg.add_vstr(::phaser::test::MakePatternString(256, 'x')); + } + }, + "Failed to resize PayloadBuffer"); +} + +TEST(StressTest, RepeatedStringResizeAndReplace) { + TestMessage msg(1024); + ASSERT_FALSE(msg.allocate_buffer(64 * 1024).empty()); + msg.resize_vstr(200); + for (size_t i = 0; i < 200; i++) { + msg.set_vstr(i, ::phaser::test::MakePatternString(16, static_cast('a' + (i % 26)))); + } + msg.clear_vstr(); + EXPECT_EQ(0u, msg.vstr_size()); + for (int i = 0; i < 200; i++) { + msg.add_vstr("x"); + } + EXPECT_EQ(200u, msg.vstr_size()); +} + +TEST(StressTest, MapManyEntries) { + TestMessage msg(4096); + ASSERT_FALSE(msg.allocate_buffer(128 * 1024).empty()); + for (int i = 0; i < 500; i++) { + auto *e = msg.add_values(); + e->set_key(::phaser::test::MakePatternString(8, static_cast('k' + (i % 10)))); + e->set_value(i); + } + ASSERT_EQ(500u, msg.values_size()); + for (int i = 0; i < 500; i++) { + EXPECT_EQ(i, msg.values(i).value()); + } +} + +} // namespace diff --git a/phaser/test_helpers.h b/phaser/test_helpers.h new file mode 100644 index 0000000..44d7e9b --- /dev/null +++ b/phaser/test_helpers.h @@ -0,0 +1,79 @@ +// Copyright 2024-2026 David Allison +// All Rights Reserved +// See LICENSE file for licensing information. + +#pragma once + +#include "absl/status/statusor.h" +#include +#include +#include +#include + +namespace phaser::test { + +inline void StripProtobufDebugRedaction(std::string &s) { + // Recent protobuf versions prepend a non-deterministic redaction marker to + // DebugString() output (e.g. "goo.gle/debugstr", "goo.gle/debugproto", + // "goo.gle/debugonly") to discourage parsing the debug format. The marker is + // emitted on its own leading line, sometimes preceded by a random amount of + // leading whitespace, so we cannot assume it sits at offset 0. Find the + // marker, confirm only whitespace precedes it on the first line, then drop + // the whole marker line so comparisons are stable. + constexpr std::string_view kMarker = "goo.gle/debug"; + const size_t marker = s.find(kMarker); + if (marker == std::string::npos) { + return; + } + for (size_t i = 0; i < marker; i++) { + if (s[i] != ' ' && s[i] != '\t') { + // Something other than whitespace precedes the marker, so it is not the + // leading redaction prefix; leave the string untouched. + return; + } + } + const size_t newline = s.find('\n', marker); + if (newline == std::string::npos) { + s.clear(); + } else { + s.erase(0, newline + 1); + } +} + +// Alloc succeeds until cumulative requested bytes exceed limit. +inline std::function(size_t)> +AllocUntilLimit(size_t limit) { + return [remaining = limit](size_t size) mutable -> absl::StatusOr { + if (size > remaining) { + return absl::ResourceExhaustedError("alloc limit exceeded"); + } + remaining -= size; + void *p = ::malloc(size); + if (p == nullptr) { + return absl::ResourceExhaustedError("malloc failed"); + } + return p; + }; +} + +inline std::function(void *, size_t, size_t)> +ReallocAlwaysFails() { + return [](void *, size_t, size_t) -> absl::StatusOr { + return absl::ResourceExhaustedError("realloc denied"); + }; +} + +inline std::string MakePatternString(size_t n, char fill = 'x') { + return std::string(n, fill); +} + +inline std::string MakePatternBytes(size_t n) { + std::string s; + s.reserve(n); + for (size_t i = 0; i < n; i++) { + s.push_back(static_cast(i & 0xff)); + } + return s; +} + +} // namespace phaser::test diff --git a/phaser/testdata/BUILD b/phaser/testdata/BUILD index 0bbafbb..3a13ddf 100644 --- a/phaser/testdata/BUILD +++ b/phaser/testdata/BUILD @@ -1,3 +1,5 @@ +load("@com_google_protobuf//bazel:cc_proto_library.bzl", "cc_proto_library") +load("@com_google_protobuf//bazel:proto_library.bzl", "proto_library") load("//phaser:phaser_library.bzl", "phaser_library") package(default_visibility = ["//visibility:public"]) @@ -54,6 +56,30 @@ phaser_library( deps = [":test_proto"], ) +proto_library( + name = "coverage_proto", + srcs = ["coverage.proto"], + deps = [ + ":foo_proto", + "@com_google_protobuf//:any_proto", + "@com_google_protobuf//:empty_proto", + "@com_google_protobuf//:timestamp_proto", + "@com_google_protobuf//:wrappers_proto", + ], +) + +cc_proto_library( + name = "coverage_cc_proto", + deps = [":coverage_proto"], +) + +phaser_library( + name = "coverage_phaser", + add_namespace = "phaser", + runtime = "//phaser/runtime:phaser_runtime", + deps = [":coverage_proto"], +) + proto_library( name = "vision_proto", srcs = ["vision.proto"], @@ -70,4 +96,3 @@ phaser_library( runtime = "//phaser/runtime:phaser_runtime", deps = [":vision_proto"], ) - diff --git a/phaser/testdata/coverage.proto b/phaser/testdata/coverage.proto new file mode 100644 index 0000000..7198d23 --- /dev/null +++ b/phaser/testdata/coverage.proto @@ -0,0 +1,83 @@ +syntax = "proto3"; + +package foo.bar.coverage; + +import "phaser/testdata/Foo.proto"; +import "google/protobuf/any.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/timestamp.proto"; +import "google/protobuf/wrappers.proto"; + +enum CoverageEnum { + COV_UNSET = 0; + COV_FOO = 0xda; + COV_BAR = 0xad; +} + +message AllScalars { + int32 f_int32 = 1; + int64 f_int64 = 2; + sint32 f_sint32 = 3; + sint64 f_sint64 = 4; + uint32 f_uint32 = 5; + uint64 f_uint64 = 6; + fixed32 f_fixed32 = 7; + fixed64 f_fixed64 = 8; + sfixed32 f_sfixed32 = 9; + sfixed64 f_sfixed64 = 10; + float f_float = 11; + double f_double = 12; + bool f_bool = 13; + string f_string = 14; + bytes f_bytes = 15; + CoverageEnum f_enum = 16; +} + +message MapHolder { + map values = 1; +} + +message RepeatedPrimitivesPacked { + repeated int32 vi32 = 1 [packed = true]; + repeated int64 vi64 = 2 [packed = true]; + repeated fixed64 vf64 = 3 [packed = true]; +} + +message RepeatedPrimitivesUnpacked { + repeated int32 vi32 = 1 [packed = false]; +} + +message RepeatedStrings { + repeated string vstr = 1; +} + +message RepeatedBytes { + repeated bytes vbytes = 1; +} + +message RepeatedMessages { + repeated AllScalars items = 1; +} + +message OneofStress { + oneof u { + int32 u_int = 1; + string u_string = 2; + AllScalars u_msg = 3; + bytes u_bytes = 4; + } +} + +message CoverageInner { + string str = 1; + fixed64 f = 2; +} + +message ImportsMessage { + foo.bar.Foo imported_foo = 1; + google.protobuf.Any any_field = 2; + google.protobuf.Timestamp timestamp = 3; + google.protobuf.StringValue wrapped_string = 4; + google.protobuf.Empty empty_msg = 5; + CoverageInner inner = 6; +} diff --git a/phaser/valgrind.supp b/phaser/valgrind.supp new file mode 100644 index 0000000..ebecd3f --- /dev/null +++ b/phaser/valgrind.supp @@ -0,0 +1,68 @@ +# Valgrind suppressions for known-benign third-party reports. +# +# Used by `bazel test --config=valgrind` (see .bazelrc). + +# toolbelt::Hexdump rounds the dump length up to a 16-byte row boundary and +# always reads full rows, so it intentionally over-reads up to 15 bytes past +# the supplied buffer (it just prints the trailing bytes). When tests dump +# protobuf's SerializeAsString() output -- a std::string allocated to its exact +# length via absl/protobuf resize-uninitialized -- this reads just past the end +# of that allocation and touches bytes protobuf left uninitialised. Both are +# benign debug-only reads in a third-party utility, so suppress reports +# originating in Hexdump. +{ + toolbelt_hexdump_invalid_read1 + Memcheck:Addr1 + ... + fun:*Hexdump* + ... +} +{ + toolbelt_hexdump_invalid_read2 + Memcheck:Addr2 + ... + fun:*Hexdump* + ... +} +{ + toolbelt_hexdump_invalid_read4 + Memcheck:Addr4 + ... + fun:*Hexdump* + ... +} +{ + toolbelt_hexdump_invalid_read8 + Memcheck:Addr8 + ... + fun:*Hexdump* + ... +} +{ + toolbelt_hexdump_uninitialised_cond + Memcheck:Cond + ... + fun:*Hexdump* + ... +} +{ + toolbelt_hexdump_uninitialised_value8 + Memcheck:Value8 + ... + fun:*Hexdump* + ... +} +{ + toolbelt_hexdump_uninitialised_value4 + Memcheck:Value4 + ... + fun:*Hexdump* + ... +} +{ + toolbelt_hexdump_uninitialised_value1 + Memcheck:Value1 + ... + fun:*Hexdump* + ... +}