From 8967d06c9b62c5f8251f0f93d8e3b431c66227b1 Mon Sep 17 00:00:00 2001 From: Shashank Sharma Date: Wed, 15 Jul 2026 14:46:30 -0700 Subject: [PATCH] No public description PiperOrigin-RevId: 948556488 --- .bazelrc | 6 + BUILD.bazel | 1 + Cargo.Bazel.lock | 5895 +++++++++++++++++ Cargo.lock | 832 +++ Cargo.toml | 21 + MODULE.bazel | 25 + MODULE.bazel.lock | 435 ++ centipede/BUILD | 26 +- rust/.rustfmt.toml | 3 + rust/BUILD | 44 + rust/Cargo.toml | 37 + rust/README.md | 63 + rust/coverage/BUILD | 36 + rust/coverage/Cargo.toml | 9 + rust/coverage/src/coverage.rs | 78 + rust/coverage/src/lib.rs | 15 + rust/e2e_tests/BUILD | 99 + rust/e2e_tests/gtest_registration_test.rs | 46 + rust/e2e_tests/replay_test.rs | 112 + rust/e2e_tests/run_with_centipede.sh | 75 + rust/e2e_tests/standalone_mode_test.rs | 297 + rust/e2e_tests/test_utils.rs | 66 + rust/e2e_tests/testdata/BUILD | 106 + rust/e2e_tests/testdata/fuzz_tests.rs | 171 + .../testdata/fuzz_tests_as_gtests.rs | 18 + rust/e2e_tests/testdata/replay_fuzz_tests.rs | 20 + .../testdata/standalone_fuzz_tests.rs | 21 + rust/e2e_tests/worker_test.rs | 26 + .../worker_with_centipede_sanitizer_test.rs | 49 + rust/e2e_tests/worker_with_centipede_test.rs | 121 + rust/engine/BUILD | 18 + rust/engine/Cargo.toml | 10 + rust/engine/build.rs | 29 + rust/engine/src/engine_ffi.rs | 441 ++ rust/engine/src/lib.rs | 268 + rust/fuzztest_macro/.rustfmt.toml | 3 + rust/fuzztest_macro/BUILD | 41 + rust/fuzztest_macro/Cargo.toml | 18 + rust/fuzztest_macro/src/helpers.rs | 26 + .../src/helpers/fn_item_utils.rs | 233 + .../src/helpers/fuzztest_domain.rs | 162 + .../src/helpers/test_registration.rs | 378 ++ rust/fuzztest_macro/src/lib.rs | 126 + rust/fuzztest_macro/src/test_main.rs | 4 + rust/rust-toolchain.toml | 9 + rust/src/crash_handler.rs | 38 + rust/src/domains.rs | 230 + rust/src/domains/arbitrary.rs | 763 +++ rust/src/domains/range.rs | 85 + rust/src/domains/tuple_of.rs | 6 + rust/src/domains/utility.rs | 404 ++ rust/src/internal.rs | 57 + rust/src/lib.rs | 20 + rust/src/options.rs | 840 +++ rust/src/worker.rs | 769 +++ rust/tests/macro_compiles.rs | 10 + rust/tests/trybuild.rs | 5 + 57 files changed, 13742 insertions(+), 4 deletions(-) create mode 100644 BUILD.bazel create mode 100644 Cargo.Bazel.lock create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 MODULE.bazel.lock create mode 100644 rust/.rustfmt.toml create mode 100644 rust/BUILD create mode 100644 rust/Cargo.toml create mode 100644 rust/README.md create mode 100644 rust/coverage/BUILD create mode 100644 rust/coverage/Cargo.toml create mode 100644 rust/coverage/src/coverage.rs create mode 100644 rust/coverage/src/lib.rs create mode 100644 rust/e2e_tests/BUILD create mode 100644 rust/e2e_tests/gtest_registration_test.rs create mode 100644 rust/e2e_tests/replay_test.rs create mode 100755 rust/e2e_tests/run_with_centipede.sh create mode 100644 rust/e2e_tests/standalone_mode_test.rs create mode 100644 rust/e2e_tests/test_utils.rs create mode 100644 rust/e2e_tests/testdata/BUILD create mode 100644 rust/e2e_tests/testdata/fuzz_tests.rs create mode 100644 rust/e2e_tests/testdata/fuzz_tests_as_gtests.rs create mode 100644 rust/e2e_tests/testdata/replay_fuzz_tests.rs create mode 100644 rust/e2e_tests/testdata/standalone_fuzz_tests.rs create mode 100644 rust/e2e_tests/worker_test.rs create mode 100644 rust/e2e_tests/worker_with_centipede_sanitizer_test.rs create mode 100644 rust/e2e_tests/worker_with_centipede_test.rs create mode 100644 rust/engine/BUILD create mode 100644 rust/engine/Cargo.toml create mode 100644 rust/engine/build.rs create mode 100644 rust/engine/src/engine_ffi.rs create mode 100644 rust/engine/src/lib.rs create mode 100644 rust/fuzztest_macro/.rustfmt.toml create mode 100644 rust/fuzztest_macro/BUILD create mode 100644 rust/fuzztest_macro/Cargo.toml create mode 100644 rust/fuzztest_macro/src/helpers.rs create mode 100644 rust/fuzztest_macro/src/helpers/fn_item_utils.rs create mode 100644 rust/fuzztest_macro/src/helpers/fuzztest_domain.rs create mode 100644 rust/fuzztest_macro/src/helpers/test_registration.rs create mode 100644 rust/fuzztest_macro/src/lib.rs create mode 100644 rust/fuzztest_macro/src/test_main.rs create mode 100644 rust/rust-toolchain.toml create mode 100644 rust/src/crash_handler.rs create mode 100644 rust/src/domains.rs create mode 100644 rust/src/domains/arbitrary.rs create mode 100644 rust/src/domains/range.rs create mode 100644 rust/src/domains/tuple_of.rs create mode 100644 rust/src/domains/utility.rs create mode 100644 rust/src/internal.rs create mode 100644 rust/src/lib.rs create mode 100644 rust/src/options.rs create mode 100644 rust/src/worker.rs create mode 100644 rust/tests/macro_compiles.rs create mode 100644 rust/tests/trybuild.rs diff --git a/.bazelrc b/.bazelrc index 19ff5f54d..5d9c8c38a 100644 --- a/.bazelrc +++ b/.bazelrc @@ -44,3 +44,9 @@ build:macos --macos_minimum_os=10.15 # bazel run //bazel:setup_configs > fuzztest.bazelrc # try-import %workspace%/fuzztest.bazelrc + +# Configure rules_rust to use the nightly channel toolchain by default. +build --@rules_rust//rust/toolchain/channel:channel=nightly + +# Link standard C++ library for Rust binaries on Linux +build:linux --linkopt=-lstdc++ diff --git a/BUILD.bazel b/BUILD.bazel new file mode 100644 index 000000000..762779895 --- /dev/null +++ b/BUILD.bazel @@ -0,0 +1 @@ +exports_files(["MODULE.bazel"]) diff --git a/Cargo.Bazel.lock b/Cargo.Bazel.lock new file mode 100644 index 000000000..f6ba62b68 --- /dev/null +++ b/Cargo.Bazel.lock @@ -0,0 +1,5895 @@ +{ + "checksum": "7fe8e06bf5d789f55dfeac6f39f71119f0acdbe34a8356c5c3d64f9ffb0e9d1e", + "crates": { + "aho-corasick 1.1.4": { + "name": "aho-corasick", + "version": "1.1.4", + "package_url": "https://github.com/BurntSushi/aho-corasick", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/aho-corasick/1.1.4/download", + "sha256": "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" + } + }, + "targets": [ + { + "Library": { + "crate_name": "aho_corasick", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "aho_corasick", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "perf-literal", + "std" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "memchr 2.8.3", + "target": "memchr" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "1.1.4" + }, + "license": "Unlicense OR MIT", + "license_ids": [ + "MIT", + "Unlicense" + ], + "license_file": "LICENSE-MIT" + }, + "anstream 1.0.0": { + "name": "anstream", + "version": "1.0.0", + "package_url": "https://github.com/rust-cli/anstyle.git", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/anstream/1.0.0/download", + "sha256": "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" + } + }, + "targets": [ + { + "Library": { + "crate_name": "anstream", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "anstream", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "auto", + "default", + "wincon" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "anstyle 1.0.14", + "target": "anstyle" + }, + { + "id": "anstyle-parse 1.0.0", + "target": "anstyle_parse" + }, + { + "id": "anstyle-query 1.1.5", + "target": "anstyle_query" + }, + { + "id": "colorchoice 1.0.5", + "target": "colorchoice" + }, + { + "id": "is_terminal_polyfill 1.70.2", + "target": "is_terminal_polyfill" + }, + { + "id": "utf8parse 0.2.2", + "target": "utf8parse" + } + ], + "selects": { + "x86_64-pc-windows-msvc": [ + { + "id": "anstyle-wincon 3.0.11", + "target": "anstyle_wincon" + } + ] + } + }, + "edition": "2021", + "version": "1.0.0" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "anstyle 1.0.14": { + "name": "anstyle", + "version": "1.0.14", + "package_url": "https://github.com/rust-cli/anstyle.git", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/anstyle/1.0.14/download", + "sha256": "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + } + }, + "targets": [ + { + "Library": { + "crate_name": "anstyle", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "anstyle", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "std" + ], + "selects": {} + }, + "edition": "2021", + "version": "1.0.14" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "anstyle-parse 1.0.0": { + "name": "anstyle-parse", + "version": "1.0.0", + "package_url": "https://github.com/rust-cli/anstyle.git", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/anstyle-parse/1.0.0/download", + "sha256": "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" + } + }, + "targets": [ + { + "Library": { + "crate_name": "anstyle_parse", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "anstyle_parse", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "utf8" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "utf8parse 0.2.2", + "target": "utf8parse" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "1.0.0" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "anstyle-query 1.1.5": { + "name": "anstyle-query", + "version": "1.1.5", + "package_url": "https://github.com/rust-cli/anstyle.git", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/anstyle-query/1.1.5/download", + "sha256": "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" + } + }, + "targets": [ + { + "Library": { + "crate_name": "anstyle_query", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "anstyle_query", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [], + "selects": { + "cfg(windows)": [ + { + "id": "windows-sys 0.61.2", + "target": "windows_sys" + } + ] + } + }, + "edition": "2021", + "version": "1.1.5" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "anstyle-wincon 3.0.11": { + "name": "anstyle-wincon", + "version": "3.0.11", + "package_url": "https://github.com/rust-cli/anstyle.git", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/anstyle-wincon/3.0.11/download", + "sha256": "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" + } + }, + "targets": [ + { + "Library": { + "crate_name": "anstyle_wincon", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "anstyle_wincon", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "anstyle 1.0.14", + "target": "anstyle" + } + ], + "selects": { + "cfg(windows)": [ + { + "id": "once_cell_polyfill 1.70.2", + "target": "once_cell_polyfill" + }, + { + "id": "windows-sys 0.61.2", + "target": "windows_sys" + } + ] + } + }, + "edition": "2021", + "version": "3.0.11" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "anyhow 1.0.104": { + "name": "anyhow", + "version": "1.0.104", + "package_url": "https://github.com/dtolnay/anyhow", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/anyhow/1.0.104/download", + "sha256": "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + } + }, + "targets": [ + { + "Library": { + "crate_name": "anyhow", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + }, + { + "BuildScript": { + "crate_name": "build_script_build", + "crate_root": "build.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "anyhow", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "std" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "anyhow 1.0.104", + "target": "build_script_build" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "1.0.104" + }, + "build_script_attrs": { + "compile_data_glob": [ + "**" + ], + "compile_data_glob_excludes": [ + "**/*.rs" + ], + "data_glob": [ + "**" + ] + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "atomic-polyfill 1.0.3": { + "name": "atomic-polyfill", + "version": "1.0.3", + "package_url": "https://github.com/embassy-rs/atomic-polyfill", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/atomic-polyfill/1.0.3/download", + "sha256": "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" + } + }, + "targets": [ + { + "Library": { + "crate_name": "atomic_polyfill", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + }, + { + "BuildScript": { + "crate_name": "build_script_build", + "crate_root": "build.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "atomic_polyfill", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "atomic-polyfill 1.0.3", + "target": "build_script_build" + }, + { + "id": "critical-section 1.2.0", + "target": "critical_section" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "1.0.3" + }, + "build_script_attrs": { + "compile_data_glob": [ + "**" + ], + "compile_data_glob_excludes": [ + "**/*.rs" + ], + "data_glob": [ + "**" + ] + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "autocfg 1.5.1": { + "name": "autocfg", + "version": "1.5.1", + "package_url": "https://github.com/cuviper/autocfg", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/autocfg/1.5.1/download", + "sha256": "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + } + }, + "targets": [ + { + "Library": { + "crate_name": "autocfg", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "autocfg", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2015", + "version": "1.5.1" + }, + "license": "Apache-2.0 OR MIT", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "bitflags 2.13.1": { + "name": "bitflags", + "version": "2.13.1", + "package_url": "https://github.com/bitflags/bitflags", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/bitflags/2.13.1/download", + "sha256": "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" + } + }, + "targets": [ + { + "Library": { + "crate_name": "bitflags", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "bitflags", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "std" + ], + "selects": {} + }, + "edition": "2021", + "version": "2.13.1" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "byteorder 1.5.0": { + "name": "byteorder", + "version": "1.5.0", + "package_url": "https://github.com/BurntSushi/byteorder", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/byteorder/1.5.0/download", + "sha256": "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + } + }, + "targets": [ + { + "Library": { + "crate_name": "byteorder", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "byteorder", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2021", + "version": "1.5.0" + }, + "license": "Unlicense OR MIT", + "license_ids": [ + "MIT", + "Unlicense" + ], + "license_file": "LICENSE-MIT" + }, + "cfg-if 1.0.4": { + "name": "cfg-if", + "version": "1.0.4", + "package_url": "https://github.com/rust-lang/cfg-if", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/cfg-if/1.0.4/download", + "sha256": "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + } + }, + "targets": [ + { + "Library": { + "crate_name": "cfg_if", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "cfg_if", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2018", + "version": "1.0.4" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "chacha20 0.10.1": { + "name": "chacha20", + "version": "0.10.1", + "package_url": "https://github.com/RustCrypto/stream-ciphers", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/chacha20/0.10.1/download", + "sha256": "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" + } + }, + "targets": [ + { + "Library": { + "crate_name": "chacha20", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "chacha20", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "rng" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "cfg-if 1.0.4", + "target": "cfg_if" + }, + { + "id": "rand_core 0.10.1", + "target": "rand_core" + } + ], + "selects": { + "cfg(any(target_arch = \"x86_64\", target_arch = \"x86\"))": [ + { + "id": "cpufeatures 0.3.0", + "target": "cpufeatures" + } + ] + } + }, + "edition": "2024", + "version": "0.10.1" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "clap 4.6.4": { + "name": "clap", + "version": "4.6.4", + "package_url": "https://github.com/clap-rs/clap", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/clap/4.6.4/download", + "sha256": "d91e0c145792ef73a6ad36d27c75ac09f1832222a3c209689d90f534685ee5b7" + } + }, + "targets": [ + { + "Library": { + "crate_name": "clap", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "clap", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "color", + "default", + "derive", + "env", + "error-context", + "help", + "std", + "suggestions", + "usage" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "clap_builder 4.6.2", + "target": "clap_builder" + } + ], + "selects": {} + }, + "edition": "2024", + "proc_macro_deps": { + "common": [ + { + "id": "clap_derive 4.6.4", + "target": "clap_derive" + } + ], + "selects": {} + }, + "version": "4.6.4" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "clap_builder 4.6.2": { + "name": "clap_builder", + "version": "4.6.2", + "package_url": "https://github.com/clap-rs/clap", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/clap_builder/4.6.2/download", + "sha256": "f09628afdcc538b57f3c6341e9c8e9970f18e4a481690a64974d7023bd33548b" + } + }, + "targets": [ + { + "Library": { + "crate_name": "clap_builder", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "clap_builder", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "color", + "env", + "error-context", + "help", + "std", + "suggestions", + "usage" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "anstream 1.0.0", + "target": "anstream" + }, + { + "id": "anstyle 1.0.14", + "target": "anstyle" + }, + { + "id": "clap_lex 1.1.0", + "target": "clap_lex" + }, + { + "id": "strsim 0.11.1", + "target": "strsim" + } + ], + "selects": {} + }, + "edition": "2024", + "version": "4.6.2" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "clap_derive 4.6.4": { + "name": "clap_derive", + "version": "4.6.4", + "package_url": "https://github.com/clap-rs/clap", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/clap_derive/4.6.4/download", + "sha256": "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061" + } + }, + "targets": [ + { + "ProcMacro": { + "crate_name": "clap_derive", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "clap_derive", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "heck 0.5.0", + "target": "heck" + }, + { + "id": "proc-macro2 1.0.107", + "target": "proc_macro2" + }, + { + "id": "quote 1.0.47", + "target": "quote" + }, + { + "id": "syn 3.0.3", + "target": "syn" + } + ], + "selects": {} + }, + "edition": "2024", + "version": "4.6.4" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "clap_lex 1.1.0": { + "name": "clap_lex", + "version": "1.1.0", + "package_url": "https://github.com/clap-rs/clap", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/clap_lex/1.1.0/download", + "sha256": "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + } + }, + "targets": [ + { + "Library": { + "crate_name": "clap_lex", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "clap_lex", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2024", + "version": "1.1.0" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "cobs 0.3.0": { + "name": "cobs", + "version": "0.3.0", + "package_url": "https://github.com/jamesmunns/cobs.rs", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/cobs/0.3.0/download", + "sha256": "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" + } + }, + "targets": [ + { + "Library": { + "crate_name": "cobs", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "cobs", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "thiserror 2.0.19", + "target": "thiserror" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "0.3.0" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "colorchoice 1.0.5": { + "name": "colorchoice", + "version": "1.0.5", + "package_url": "https://github.com/rust-cli/anstyle.git", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/colorchoice/1.0.5/download", + "sha256": "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + } + }, + "targets": [ + { + "Library": { + "crate_name": "colorchoice", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "colorchoice", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2021", + "version": "1.0.5" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "convert_case 0.4.0": { + "name": "convert_case", + "version": "0.4.0", + "package_url": "https://github.com/rutrum/convert-case", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/convert_case/0.4.0/download", + "sha256": "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + } + }, + "targets": [ + { + "Library": { + "crate_name": "convert_case", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "convert_case", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2018", + "version": "0.4.0" + }, + "license": "MIT", + "license_ids": [ + "MIT" + ], + "license_file": null + }, + "coverage 0.1.0": { + "name": "coverage", + "version": "0.1.0", + "package_url": null, + "repository": null, + "targets": [ + { + "Library": { + "crate_name": "coverage", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "coverage", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2024", + "version": "0.1.0" + }, + "license": null, + "license_ids": [], + "license_file": null + }, + "cpufeatures 0.3.0": { + "name": "cpufeatures", + "version": "0.3.0", + "package_url": "https://github.com/RustCrypto/utils", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/cpufeatures/0.3.0/download", + "sha256": "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" + } + }, + "targets": [ + { + "Library": { + "crate_name": "cpufeatures", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "cpufeatures", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [], + "selects": { + "cfg(all(target_arch = \"aarch64\", target_os = \"android\"))": [ + { + "id": "libc 0.2.189", + "target": "libc" + } + ], + "cfg(all(target_arch = \"aarch64\", target_os = \"linux\"))": [ + { + "id": "libc 0.2.189", + "target": "libc" + } + ], + "cfg(all(target_arch = \"aarch64\", target_vendor = \"apple\"))": [ + { + "id": "libc 0.2.189", + "target": "libc" + } + ], + "cfg(all(target_arch = \"loongarch64\", target_os = \"linux\"))": [ + { + "id": "libc 0.2.189", + "target": "libc" + } + ] + } + }, + "edition": "2024", + "version": "0.3.0" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "critical-section 1.2.0": { + "name": "critical-section", + "version": "1.2.0", + "package_url": "https://github.com/rust-embedded/critical-section", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/critical-section/1.2.0/download", + "sha256": "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + } + }, + "targets": [ + { + "Library": { + "crate_name": "critical_section", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "critical_section", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2018", + "version": "1.2.0" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "dissimilar 1.0.11": { + "name": "dissimilar", + "version": "1.0.11", + "package_url": "https://github.com/dtolnay/dissimilar", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/dissimilar/1.0.11/download", + "sha256": "aeda16ab4059c5fd2a83f2b9c9e9c981327b18aa8e3b313f7e6563799d4f093e" + } + }, + "targets": [ + { + "Library": { + "crate_name": "dissimilar", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "dissimilar", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2021", + "version": "1.0.11" + }, + "license": "Apache-2.0", + "license_ids": [ + "Apache-2.0" + ], + "license_file": "LICENSE-APACHE" + }, + "embedded-io 0.4.0": { + "name": "embedded-io", + "version": "0.4.0", + "package_url": "https://github.com/embassy-rs/embedded-io", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/embedded-io/0.4.0/download", + "sha256": "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + } + }, + "targets": [ + { + "Library": { + "crate_name": "embedded_io", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "embedded_io", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2021", + "version": "0.4.0" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "embedded-io 0.6.1": { + "name": "embedded-io", + "version": "0.6.1", + "package_url": "https://github.com/rust-embedded/embedded-hal", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/embedded-io/0.6.1/download", + "sha256": "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + } + }, + "targets": [ + { + "Library": { + "crate_name": "embedded_io", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "embedded_io", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2021", + "version": "0.6.1" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "engine-ffi 0.1.0": { + "name": "engine-ffi", + "version": "0.1.0", + "package_url": null, + "repository": null, + "targets": [ + { + "Library": { + "crate_name": "engine_ffi", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + }, + { + "BuildScript": { + "crate_name": "build_script_build", + "crate_root": "build.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "engine_ffi", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "engine-ffi 0.1.0", + "target": "build_script_build" + } + ], + "selects": {} + }, + "edition": "2024", + "version": "0.1.0" + }, + "build_script_attrs": { + "compile_data_glob": [ + "**" + ], + "compile_data_glob_excludes": [ + "**/*.rs" + ], + "data_glob": [ + "**" + ], + "links": "centipede_engine_static" + }, + "license": null, + "license_ids": [], + "license_file": null + }, + "equivalent 1.0.2": { + "name": "equivalent", + "version": "1.0.2", + "package_url": "https://github.com/indexmap-rs/equivalent", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/equivalent/1.0.2/download", + "sha256": "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + } + }, + "targets": [ + { + "Library": { + "crate_name": "equivalent", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "equivalent", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2015", + "version": "1.0.2" + }, + "license": "Apache-2.0 OR MIT", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "errno 0.3.14": { + "name": "errno", + "version": "0.3.14", + "package_url": "https://github.com/lambda-fairy/rust-errno", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/errno/0.3.14/download", + "sha256": "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" + } + }, + "targets": [ + { + "Library": { + "crate_name": "errno", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "errno", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "std" + ], + "selects": {} + }, + "deps": { + "common": [], + "selects": { + "cfg(target_os = \"hermit\")": [ + { + "id": "libc 0.2.189", + "target": "libc" + } + ], + "cfg(target_os = \"wasi\")": [ + { + "id": "libc 0.2.189", + "target": "libc" + } + ], + "cfg(unix)": [ + { + "id": "libc 0.2.189", + "target": "libc" + } + ], + "cfg(windows)": [ + { + "id": "windows-sys 0.61.2", + "target": "windows_sys" + } + ] + } + }, + "edition": "2018", + "version": "0.3.14" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "fastrand 2.5.0": { + "name": "fastrand", + "version": "2.5.0", + "package_url": "https://github.com/smol-rs/fastrand", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/fastrand/2.5.0/download", + "sha256": "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" + } + }, + "targets": [ + { + "Library": { + "crate_name": "fastrand", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "fastrand", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "alloc", + "default", + "std" + ], + "selects": {} + }, + "edition": "2018", + "version": "2.5.0" + }, + "license": "Apache-2.0 OR MIT", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "fuzztest 0.1.0": { + "name": "fuzztest", + "version": "0.1.0", + "package_url": null, + "repository": null, + "targets": [ + { + "Library": { + "crate_name": "fuzztest", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "fuzztest", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "anyhow 1.0.104", + "target": "anyhow" + }, + { + "id": "clap 4.6.4", + "target": "clap" + }, + { + "id": "humantime 2.4.0", + "target": "humantime" + }, + { + "id": "inventory 0.3.24", + "target": "inventory" + }, + { + "id": "num-traits 0.2.19", + "target": "num_traits" + }, + { + "id": "postcard 1.1.3", + "target": "postcard" + }, + { + "id": "rand 0.10.2", + "target": "rand" + }, + { + "id": "serde 1.0.229", + "target": "serde" + }, + { + "id": "spin 0.10.1", + "target": "spin" + }, + { + "id": "tempfile 3.27.0", + "target": "tempfile" + } + ], + "selects": {} + }, + "deps_dev": { + "common": [ + { + "id": "googletest 0.14.3", + "target": "googletest" + }, + { + "id": "trybuild 1.0.118", + "target": "trybuild" + } + ], + "selects": {} + }, + "edition": "2024", + "version": "0.1.0" + }, + "license": null, + "license_ids": [], + "license_file": null + }, + "fuzztest-macro 0.1.0": { + "name": "fuzztest-macro", + "version": "0.1.0", + "package_url": null, + "repository": null, + "targets": [ + { + "ProcMacro": { + "crate_name": "fuzztest_macro", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "fuzztest_macro", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "convert_case 0.4.0", + "target": "convert_case" + }, + { + "id": "proc-macro-crate 3.5.0", + "target": "proc_macro_crate" + }, + { + "id": "proc-macro2 1.0.107", + "target": "proc_macro2" + }, + { + "id": "quote 1.0.47", + "target": "quote" + }, + { + "id": "syn 2.0.119", + "target": "syn" + } + ], + "selects": {} + }, + "deps_dev": { + "common": [ + { + "id": "googletest 0.14.3", + "target": "googletest" + } + ], + "selects": {} + }, + "edition": "2024", + "version": "0.1.0" + }, + "license": null, + "license_ids": [], + "license_file": null + }, + "getrandom 0.4.3": { + "name": "getrandom", + "version": "0.4.3", + "package_url": "https://github.com/rust-random/getrandom", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/getrandom/0.4.3/download", + "sha256": "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" + } + }, + "targets": [ + { + "Library": { + "crate_name": "getrandom", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + }, + { + "BuildScript": { + "crate_name": "build_script_build", + "crate_root": "build.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "getrandom", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "std", + "sys_rng" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "cfg-if 1.0.4", + "target": "cfg_if" + }, + { + "id": "getrandom 0.4.3", + "target": "build_script_build" + }, + { + "id": "rand_core 0.10.1", + "target": "rand_core" + } + ], + "selects": { + "cfg(all(any(target_os = \"linux\", target_os = \"android\"), not(any(all(target_os = \"linux\", target_env = \"\"), getrandom_backend = \"custom\", getrandom_backend = \"linux_raw\", getrandom_backend = \"rdrand\", getrandom_backend = \"rndr\"))))": [ + { + "id": "libc 0.2.189", + "target": "libc" + } + ], + "cfg(all(target_os = \"uefi\", getrandom_backend = \"efi_rng\"))": [ + { + "id": "r-efi 6.0.0", + "target": "r_efi" + } + ], + "cfg(any(target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"hurd\", target_os = \"illumos\", target_os = \"cygwin\", all(target_os = \"horizon\", target_arch = \"arm\")))": [ + { + "id": "libc 0.2.189", + "target": "libc" + } + ], + "cfg(any(target_os = \"haiku\", target_os = \"redox\", target_os = \"nto\", target_os = \"aix\"))": [ + { + "id": "libc 0.2.189", + "target": "libc" + } + ], + "cfg(any(target_os = \"ios\", target_os = \"visionos\", target_os = \"watchos\", target_os = \"tvos\"))": [ + { + "id": "libc 0.2.189", + "target": "libc" + } + ], + "cfg(any(target_os = \"macos\", target_os = \"openbsd\", target_os = \"vita\", target_os = \"emscripten\"))": [ + { + "id": "libc 0.2.189", + "target": "libc" + } + ], + "cfg(target_os = \"netbsd\")": [ + { + "id": "libc 0.2.189", + "target": "libc" + } + ], + "cfg(target_os = \"solaris\")": [ + { + "id": "libc 0.2.189", + "target": "libc" + } + ], + "cfg(target_os = \"vxworks\")": [ + { + "id": "libc 0.2.189", + "target": "libc" + } + ] + } + }, + "edition": "2024", + "version": "0.4.3" + }, + "build_script_attrs": { + "compile_data_glob": [ + "**" + ], + "compile_data_glob_excludes": [ + "**/*.rs" + ], + "data_glob": [ + "**" + ] + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "glob 0.3.4": { + "name": "glob", + "version": "0.3.4", + "package_url": "https://github.com/rust-lang/glob", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/glob/0.3.4/download", + "sha256": "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" + } + }, + "targets": [ + { + "Library": { + "crate_name": "glob", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "glob", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2021", + "version": "0.3.4" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "googletest 0.14.3": { + "name": "googletest", + "version": "0.14.3", + "package_url": "https://github.com/google/googletest-rust", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/googletest/0.14.3/download", + "sha256": "f6b5e2f2b556b7b90297a5a35c8267dd43a537923d2b329beefdba2b4ec19d94" + } + }, + "targets": [ + { + "Library": { + "crate_name": "googletest", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "googletest", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "num-traits 0.2.19", + "target": "num_traits" + }, + { + "id": "regex 1.13.1", + "target": "regex" + } + ], + "selects": {} + }, + "edition": "2021", + "proc_macro_deps": { + "common": [ + { + "id": "googletest_macro 0.14.3", + "target": "googletest_macro" + }, + { + "id": "rustversion 1.0.23", + "target": "rustversion" + } + ], + "selects": {} + }, + "version": "0.14.3" + }, + "license": "Apache-2.0", + "license_ids": [ + "Apache-2.0" + ], + "license_file": "LICENSE" + }, + "googletest_macro 0.14.3": { + "name": "googletest_macro", + "version": "0.14.3", + "package_url": "https://github.com/google/googletest-rust", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/googletest_macro/0.14.3/download", + "sha256": "2ae6abc96141edd26bf5aeec0f119c129c44de3ced09e5073711a02cb74725d0" + } + }, + "targets": [ + { + "ProcMacro": { + "crate_name": "googletest_macro", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "googletest_macro", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "proc-macro2 1.0.107", + "target": "proc_macro2" + }, + { + "id": "quote 1.0.47", + "target": "quote" + }, + { + "id": "syn 2.0.119", + "target": "syn" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "0.14.3" + }, + "license": "Apache-2.0", + "license_ids": [ + "Apache-2.0" + ], + "license_file": "LICENSE" + }, + "hash32 0.2.1": { + "name": "hash32", + "version": "0.2.1", + "package_url": "https://github.com/japaric/hash32", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/hash32/0.2.1/download", + "sha256": "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" + } + }, + "targets": [ + { + "Library": { + "crate_name": "hash32", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "hash32", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "byteorder 1.5.0", + "target": "byteorder" + } + ], + "selects": {} + }, + "edition": "2015", + "version": "0.2.1" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "hashbrown 0.17.1": { + "name": "hashbrown", + "version": "0.17.1", + "package_url": "https://github.com/rust-lang/hashbrown", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/hashbrown/0.17.1/download", + "sha256": "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + } + }, + "targets": [ + { + "Library": { + "crate_name": "hashbrown", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "hashbrown", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2024", + "version": "0.17.1" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "heapless 0.7.17": { + "name": "heapless", + "version": "0.7.17", + "package_url": "https://github.com/japaric/heapless", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/heapless/0.7.17/download", + "sha256": "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" + } + }, + "targets": [ + { + "Library": { + "crate_name": "heapless", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + }, + { + "BuildScript": { + "crate_name": "build_script_build", + "crate_root": "build.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "heapless", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "atomic-polyfill", + "cas", + "serde" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "hash32 0.2.1", + "target": "hash32" + }, + { + "id": "heapless 0.7.17", + "target": "build_script_build" + }, + { + "id": "serde 1.0.229", + "target": "serde" + }, + { + "id": "stable_deref_trait 1.2.1", + "target": "stable_deref_trait" + } + ], + "selects": { + "cfg(target_arch = \"x86_64\")": [ + { + "id": "spin 0.9.9", + "target": "spin" + } + ], + "riscv32i-unknown-none-elf": [ + { + "id": "atomic-polyfill 1.0.3", + "target": "atomic_polyfill" + } + ], + "riscv32imc-unknown-none-elf": [ + { + "id": "atomic-polyfill 1.0.3", + "target": "atomic_polyfill" + } + ], + "xtensa-esp32s2-none-elf": [ + { + "id": "atomic-polyfill 1.0.3", + "target": "atomic_polyfill" + } + ] + } + }, + "edition": "2018", + "version": "0.7.17" + }, + "build_script_attrs": { + "compile_data_glob": [ + "**" + ], + "compile_data_glob_excludes": [ + "**/*.rs" + ], + "data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "rustc_version 0.4.1", + "target": "rustc_version" + } + ], + "selects": {} + } + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "heck 0.5.0": { + "name": "heck", + "version": "0.5.0", + "package_url": "https://github.com/withoutboats/heck", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/heck/0.5.0/download", + "sha256": "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + } + }, + "targets": [ + { + "Library": { + "crate_name": "heck", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "heck", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2021", + "version": "0.5.0" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "humantime 2.4.0": { + "name": "humantime", + "version": "2.4.0", + "package_url": "https://github.com/chronotope/humantime", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/humantime/2.4.0/download", + "sha256": "15cdd26707701c53297e2fa6afb323d55fbc1d0810c3aec078ae3ef0424c3c15" + } + }, + "targets": [ + { + "Library": { + "crate_name": "humantime", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "humantime", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2021", + "version": "2.4.0" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "indexmap 2.14.0": { + "name": "indexmap", + "version": "2.14.0", + "package_url": "https://github.com/indexmap-rs/indexmap", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/indexmap/2.14.0/download", + "sha256": "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" + } + }, + "targets": [ + { + "Library": { + "crate_name": "indexmap", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "indexmap", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "std" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "equivalent 1.0.2", + "target": "equivalent" + }, + { + "id": "hashbrown 0.17.1", + "target": "hashbrown" + } + ], + "selects": {} + }, + "edition": "2024", + "version": "2.14.0" + }, + "license": "Apache-2.0 OR MIT", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "inventory 0.3.24": { + "name": "inventory", + "version": "0.3.24", + "package_url": "https://github.com/dtolnay/inventory", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/inventory/0.3.24/download", + "sha256": "a4f0c30c76f2f4ccee3fe55a2435f691ca00c0e4bd87abe4f4a851b1d4dac39b" + } + }, + "targets": [ + { + "Library": { + "crate_name": "inventory", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "inventory", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2021", + "proc_macro_deps": { + "common": [], + "selects": { + "cfg(target_family = \"wasm\")": [ + { + "id": "rustversion 1.0.23", + "target": "rustversion" + } + ] + } + }, + "version": "0.3.24" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "is_terminal_polyfill 1.70.2": { + "name": "is_terminal_polyfill", + "version": "1.70.2", + "package_url": "https://github.com/polyfill-rs/is_terminal_polyfill", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/is_terminal_polyfill/1.70.2/download", + "sha256": "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + } + }, + "targets": [ + { + "Library": { + "crate_name": "is_terminal_polyfill", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "is_terminal_polyfill", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default" + ], + "selects": {} + }, + "edition": "2021", + "version": "1.70.2" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "itoa 1.0.18": { + "name": "itoa", + "version": "1.0.18", + "package_url": "https://github.com/dtolnay/itoa", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/itoa/1.0.18/download", + "sha256": "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + } + }, + "targets": [ + { + "Library": { + "crate_name": "itoa", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "itoa", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2021", + "version": "1.0.18" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "libc 0.2.189": { + "name": "libc", + "version": "0.2.189", + "package_url": "https://github.com/rust-lang/libc", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/libc/0.2.189/download", + "sha256": "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + } + }, + "targets": [ + { + "Library": { + "crate_name": "libc", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + }, + { + "BuildScript": { + "crate_name": "build_script_build", + "crate_root": "build.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "libc", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [], + "selects": { + "aarch64-apple-darwin": [ + "std" + ], + "wasm32-wasip1": [ + "std" + ] + } + }, + "deps": { + "common": [ + { + "id": "libc 0.2.189", + "target": "build_script_build" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "0.2.189" + }, + "build_script_attrs": { + "compile_data_glob": [ + "**" + ], + "compile_data_glob_excludes": [ + "**/*.rs" + ], + "data_glob": [ + "**" + ] + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "linux-raw-sys 0.12.1": { + "name": "linux-raw-sys", + "version": "0.12.1", + "package_url": "https://github.com/sunfishcode/linux-raw-sys", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/linux-raw-sys/0.12.1/download", + "sha256": "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + } + }, + "targets": [ + { + "Library": { + "crate_name": "linux_raw_sys", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "linux_raw_sys", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "auxvec", + "elf", + "errno", + "general", + "ioctl", + "no_std" + ], + "selects": {} + }, + "edition": "2021", + "version": "0.12.1" + }, + "license": "Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "lock_api 0.4.14": { + "name": "lock_api", + "version": "0.4.14", + "package_url": "https://github.com/Amanieu/parking_lot", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/lock_api/0.4.14/download", + "sha256": "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" + } + }, + "targets": [ + { + "Library": { + "crate_name": "lock_api", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "lock_api", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "atomic_usize", + "default" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "scopeguard 1.2.0", + "target": "scopeguard" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "0.4.14" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "memchr 2.8.3": { + "name": "memchr", + "version": "2.8.3", + "package_url": "https://github.com/BurntSushi/memchr", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/memchr/2.8.3/download", + "sha256": "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + } + }, + "targets": [ + { + "Library": { + "crate_name": "memchr", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "memchr", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "alloc", + "std" + ], + "selects": {} + }, + "edition": "2021", + "version": "2.8.3" + }, + "license": "Unlicense OR MIT", + "license_ids": [ + "MIT", + "Unlicense" + ], + "license_file": "LICENSE-MIT" + }, + "num-traits 0.2.19": { + "name": "num-traits", + "version": "0.2.19", + "package_url": "https://github.com/rust-num/num-traits", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/num-traits/0.2.19/download", + "sha256": "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" + } + }, + "targets": [ + { + "Library": { + "crate_name": "num_traits", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + }, + { + "BuildScript": { + "crate_name": "build_script_build", + "crate_root": "build.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "num_traits", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "std" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "num-traits 0.2.19", + "target": "build_script_build" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "0.2.19" + }, + "build_script_attrs": { + "compile_data_glob": [ + "**" + ], + "compile_data_glob_excludes": [ + "**/*.rs" + ], + "data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "autocfg 1.5.1", + "target": "autocfg" + } + ], + "selects": {} + } + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "once_cell 1.21.4": { + "name": "once_cell", + "version": "1.21.4", + "package_url": "https://github.com/matklad/once_cell", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/once_cell/1.21.4/download", + "sha256": "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + } + }, + "targets": [ + { + "Library": { + "crate_name": "once_cell", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "once_cell", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "alloc", + "race", + "std" + ], + "selects": {} + }, + "edition": "2021", + "version": "1.21.4" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "once_cell_polyfill 1.70.2": { + "name": "once_cell_polyfill", + "version": "1.70.2", + "package_url": "https://github.com/polyfill-rs/once_cell_polyfill", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/once_cell_polyfill/1.70.2/download", + "sha256": "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + } + }, + "targets": [ + { + "Library": { + "crate_name": "once_cell_polyfill", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "once_cell_polyfill", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default" + ], + "selects": {} + }, + "edition": "2021", + "version": "1.70.2" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "postcard 1.1.3": { + "name": "postcard", + "version": "1.1.3", + "package_url": "https://github.com/jamesmunns/postcard", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/postcard/1.1.3/download", + "sha256": "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" + } + }, + "targets": [ + { + "Library": { + "crate_name": "postcard", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "postcard", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "alloc", + "default", + "heapless", + "heapless-cas", + "use-std" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "cobs 0.3.0", + "target": "cobs" + }, + { + "id": "heapless 0.7.17", + "target": "heapless" + }, + { + "id": "serde 1.0.229", + "target": "serde" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "1.1.3" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "proc-macro-crate 3.5.0": { + "name": "proc-macro-crate", + "version": "3.5.0", + "package_url": "https://github.com/bkchr/proc-macro-crate", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/proc-macro-crate/3.5.0/download", + "sha256": "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" + } + }, + "targets": [ + { + "Library": { + "crate_name": "proc_macro_crate", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "proc_macro_crate", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "toml_edit 0.25.13+spec-1.1.0", + "target": "toml_edit" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "3.5.0" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "proc-macro2 1.0.107": { + "name": "proc-macro2", + "version": "1.0.107", + "package_url": "https://github.com/dtolnay/proc-macro2", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/proc-macro2/1.0.107/download", + "sha256": "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" + } + }, + "targets": [ + { + "Library": { + "crate_name": "proc_macro2", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + }, + { + "BuildScript": { + "crate_name": "build_script_build", + "crate_root": "build.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "proc_macro2", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "proc-macro" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "proc-macro2 1.0.107", + "target": "build_script_build" + }, + { + "id": "unicode-ident 1.0.24", + "target": "unicode_ident" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "1.0.107" + }, + "build_script_attrs": { + "compile_data_glob": [ + "**" + ], + "compile_data_glob_excludes": [ + "**/*.rs" + ], + "data_glob": [ + "**" + ] + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "quote 1.0.47": { + "name": "quote", + "version": "1.0.47", + "package_url": "https://github.com/dtolnay/quote", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/quote/1.0.47/download", + "sha256": "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" + } + }, + "targets": [ + { + "Library": { + "crate_name": "quote", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + }, + { + "BuildScript": { + "crate_name": "build_script_build", + "crate_root": "build.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "quote", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "proc-macro" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "proc-macro2 1.0.107", + "target": "proc_macro2" + }, + { + "id": "quote 1.0.47", + "target": "build_script_build" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "1.0.47" + }, + "build_script_attrs": { + "compile_data_glob": [ + "**" + ], + "compile_data_glob_excludes": [ + "**/*.rs" + ], + "data_glob": [ + "**" + ] + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "r-efi 6.0.0": { + "name": "r-efi", + "version": "6.0.0", + "package_url": "https://github.com/r-efi/r-efi", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/r-efi/6.0.0/download", + "sha256": "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + } + }, + "targets": [ + { + "Library": { + "crate_name": "r_efi", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "r_efi", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2018", + "version": "6.0.0" + }, + "license": "MIT OR Apache-2.0 OR LGPL-2.1-or-later", + "license_ids": [ + "Apache-2.0", + "LGPL-2.1", + "MIT" + ], + "license_file": null + }, + "rand 0.10.2": { + "name": "rand", + "version": "0.10.2", + "package_url": "https://github.com/rust-random/rand", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/rand/0.10.2/download", + "sha256": "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" + } + }, + "targets": [ + { + "Library": { + "crate_name": "rand", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "rand", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "alloc", + "default", + "std", + "std_rng", + "sys_rng", + "thread_rng" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "chacha20 0.10.1", + "target": "chacha20" + }, + { + "id": "getrandom 0.4.3", + "target": "getrandom" + }, + { + "id": "rand_core 0.10.1", + "target": "rand_core" + } + ], + "selects": {} + }, + "edition": "2024", + "version": "0.10.2" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "rand_core 0.10.1": { + "name": "rand_core", + "version": "0.10.1", + "package_url": "https://github.com/rust-random/rand_core", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/rand_core/0.10.1/download", + "sha256": "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + } + }, + "targets": [ + { + "Library": { + "crate_name": "rand_core", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "rand_core", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2024", + "version": "0.10.1" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "regex 1.13.1": { + "name": "regex", + "version": "1.13.1", + "package_url": "https://github.com/rust-lang/regex", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/regex/1.13.1/download", + "sha256": "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" + } + }, + "targets": [ + { + "Library": { + "crate_name": "regex", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "regex", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "perf", + "perf-backtrack", + "perf-cache", + "perf-dfa", + "perf-inline", + "perf-literal", + "perf-onepass", + "std", + "unicode", + "unicode-age", + "unicode-bool", + "unicode-case", + "unicode-gencat", + "unicode-perl", + "unicode-script", + "unicode-segment" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "aho-corasick 1.1.4", + "target": "aho_corasick" + }, + { + "id": "memchr 2.8.3", + "target": "memchr" + }, + { + "id": "regex-automata 0.4.16", + "target": "regex_automata" + }, + { + "id": "regex-syntax 0.8.11", + "target": "regex_syntax" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "1.13.1" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "regex-automata 0.4.16": { + "name": "regex-automata", + "version": "0.4.16", + "package_url": "https://github.com/rust-lang/regex", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/regex-automata/0.4.16/download", + "sha256": "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad" + } + }, + "targets": [ + { + "Library": { + "crate_name": "regex_automata", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "regex_automata", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "alloc", + "dfa-onepass", + "hybrid", + "meta", + "nfa-backtrack", + "nfa-pikevm", + "nfa-thompson", + "perf-inline", + "perf-literal", + "perf-literal-multisubstring", + "perf-literal-substring", + "std", + "syntax", + "unicode", + "unicode-age", + "unicode-bool", + "unicode-case", + "unicode-gencat", + "unicode-perl", + "unicode-script", + "unicode-segment", + "unicode-word-boundary" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "aho-corasick 1.1.4", + "target": "aho_corasick" + }, + { + "id": "memchr 2.8.3", + "target": "memchr" + }, + { + "id": "regex-syntax 0.8.11", + "target": "regex_syntax" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "0.4.16" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "regex-syntax 0.8.11": { + "name": "regex-syntax", + "version": "0.8.11", + "package_url": "https://github.com/rust-lang/regex", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/regex-syntax/0.8.11/download", + "sha256": "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + } + }, + "targets": [ + { + "Library": { + "crate_name": "regex_syntax", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "regex_syntax", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "std", + "unicode", + "unicode-age", + "unicode-bool", + "unicode-case", + "unicode-gencat", + "unicode-perl", + "unicode-script", + "unicode-segment" + ], + "selects": {} + }, + "edition": "2021", + "version": "0.8.11" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "rustc_version 0.4.1": { + "name": "rustc_version", + "version": "0.4.1", + "package_url": "https://github.com/djc/rustc-version-rs", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/rustc_version/0.4.1/download", + "sha256": "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" + } + }, + "targets": [ + { + "Library": { + "crate_name": "rustc_version", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "rustc_version", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "semver 1.0.28", + "target": "semver" + } + ], + "selects": {} + }, + "edition": "2018", + "version": "0.4.1" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "rustix 1.1.4": { + "name": "rustix", + "version": "1.1.4", + "package_url": "https://github.com/bytecodealliance/rustix", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/rustix/1.1.4/download", + "sha256": "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" + } + }, + "targets": [ + { + "Library": { + "crate_name": "rustix", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + }, + { + "BuildScript": { + "crate_name": "build_script_build", + "crate_root": "build.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "rustix", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "alloc", + "default", + "fs", + "std" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "bitflags 2.13.1", + "target": "bitflags" + }, + { + "id": "rustix 1.1.4", + "target": "build_script_build" + } + ], + "selects": { + "aarch64-apple-darwin": [ + { + "id": "errno 0.3.14", + "target": "errno", + "alias": "libc_errno" + }, + { + "id": "libc 0.2.189", + "target": "libc" + } + ], + "cfg(all(any(target_os = \"linux\", target_os = \"android\"), any(rustix_use_libc, miri, not(all(target_os = \"linux\", any(target_endian = \"little\", any(target_arch = \"s390x\", target_arch = \"powerpc\")), any(target_arch = \"arm\", all(target_arch = \"aarch64\", target_pointer_width = \"64\"), target_arch = \"riscv64\", all(rustix_use_experimental_asm, target_arch = \"powerpc\"), all(rustix_use_experimental_asm, target_arch = \"powerpc64\"), all(rustix_use_experimental_asm, target_arch = \"s390x\"), all(rustix_use_experimental_asm, target_arch = \"mips\"), all(rustix_use_experimental_asm, target_arch = \"mips32r6\"), all(rustix_use_experimental_asm, target_arch = \"mips64\"), all(rustix_use_experimental_asm, target_arch = \"mips64r6\"), target_arch = \"x86\", all(target_arch = \"x86_64\", target_pointer_width = \"64\")))))))": [ + { + "id": "linux-raw-sys 0.12.1", + "target": "linux_raw_sys" + } + ], + "cfg(all(not(rustix_use_libc), not(miri), target_os = \"linux\", any(target_endian = \"little\", any(target_arch = \"s390x\", target_arch = \"powerpc\")), any(target_arch = \"arm\", all(target_arch = \"aarch64\", target_pointer_width = \"64\"), target_arch = \"riscv64\", all(rustix_use_experimental_asm, target_arch = \"powerpc\"), all(rustix_use_experimental_asm, target_arch = \"powerpc64\"), all(rustix_use_experimental_asm, target_arch = \"s390x\"), all(rustix_use_experimental_asm, target_arch = \"mips\"), all(rustix_use_experimental_asm, target_arch = \"mips32r6\"), all(rustix_use_experimental_asm, target_arch = \"mips64\"), all(rustix_use_experimental_asm, target_arch = \"mips64r6\"), target_arch = \"x86\", all(target_arch = \"x86_64\", target_pointer_width = \"64\"))))": [ + { + "id": "linux-raw-sys 0.12.1", + "target": "linux_raw_sys" + } + ], + "cfg(all(not(windows), any(rustix_use_libc, miri, not(all(target_os = \"linux\", any(target_endian = \"little\", any(target_arch = \"s390x\", target_arch = \"powerpc\")), any(target_arch = \"arm\", all(target_arch = \"aarch64\", target_pointer_width = \"64\"), target_arch = \"riscv64\", all(rustix_use_experimental_asm, target_arch = \"powerpc\"), all(rustix_use_experimental_asm, target_arch = \"powerpc64\"), all(rustix_use_experimental_asm, target_arch = \"s390x\"), all(rustix_use_experimental_asm, target_arch = \"mips\"), all(rustix_use_experimental_asm, target_arch = \"mips32r6\"), all(rustix_use_experimental_asm, target_arch = \"mips64\"), all(rustix_use_experimental_asm, target_arch = \"mips64r6\"), target_arch = \"x86\", all(target_arch = \"x86_64\", target_pointer_width = \"64\")))))))": [ + { + "id": "errno 0.3.14", + "target": "errno", + "alias": "libc_errno" + }, + { + "id": "libc 0.2.189", + "target": "libc" + } + ], + "cfg(windows)": [ + { + "id": "errno 0.3.14", + "target": "errno", + "alias": "libc_errno" + }, + { + "id": "windows-sys 0.61.2", + "target": "windows_sys" + } + ], + "wasm32-wasip1": [ + { + "id": "errno 0.3.14", + "target": "errno", + "alias": "libc_errno" + }, + { + "id": "libc 0.2.189", + "target": "libc" + } + ] + } + }, + "edition": "2021", + "version": "1.1.4" + }, + "build_script_attrs": { + "compile_data_glob": [ + "**" + ], + "compile_data_glob_excludes": [ + "**/*.rs" + ], + "data_glob": [ + "**" + ] + }, + "license": "Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "rustversion 1.0.23": { + "name": "rustversion", + "version": "1.0.23", + "package_url": "https://github.com/dtolnay/rustversion", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/rustversion/1.0.23/download", + "sha256": "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + } + }, + "targets": [ + { + "ProcMacro": { + "crate_name": "rustversion", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + }, + { + "BuildScript": { + "crate_name": "build_script_build", + "crate_root": "build/build.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "rustversion", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "rustversion 1.0.23", + "target": "build_script_build" + } + ], + "selects": {} + }, + "edition": "2018", + "version": "1.0.23" + }, + "build_script_attrs": { + "compile_data_glob": [ + "**" + ], + "compile_data_glob_excludes": [ + "**/*.rs" + ], + "data_glob": [ + "**" + ] + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "scopeguard 1.2.0": { + "name": "scopeguard", + "version": "1.2.0", + "package_url": "https://github.com/bluss/scopeguard", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/scopeguard/1.2.0/download", + "sha256": "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + } + }, + "targets": [ + { + "Library": { + "crate_name": "scopeguard", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "scopeguard", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2015", + "version": "1.2.0" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "semver 1.0.28": { + "name": "semver", + "version": "1.0.28", + "package_url": "https://github.com/dtolnay/semver", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/semver/1.0.28/download", + "sha256": "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + } + }, + "targets": [ + { + "Library": { + "crate_name": "semver", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "semver", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "std" + ], + "selects": {} + }, + "edition": "2021", + "version": "1.0.28" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "serde 1.0.229": { + "name": "serde", + "version": "1.0.229", + "package_url": "https://github.com/serde-rs/serde", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/serde/1.0.229/download", + "sha256": "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" + } + }, + "targets": [ + { + "Library": { + "crate_name": "serde", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + }, + { + "BuildScript": { + "crate_name": "build_script_build", + "crate_root": "build.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "serde", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "alloc", + "default", + "derive", + "serde_derive", + "std" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "serde 1.0.229", + "target": "build_script_build" + }, + { + "id": "serde_core 1.0.229", + "target": "serde_core" + } + ], + "selects": {} + }, + "edition": "2021", + "proc_macro_deps": { + "common": [ + { + "id": "serde_derive 1.0.229", + "target": "serde_derive" + } + ], + "selects": {} + }, + "version": "1.0.229" + }, + "build_script_attrs": { + "compile_data_glob": [ + "**" + ], + "compile_data_glob_excludes": [ + "**/*.rs" + ], + "data_glob": [ + "**" + ] + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "serde_core 1.0.229": { + "name": "serde_core", + "version": "1.0.229", + "package_url": "https://github.com/serde-rs/serde", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/serde_core/1.0.229/download", + "sha256": "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" + } + }, + "targets": [ + { + "Library": { + "crate_name": "serde_core", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + }, + { + "BuildScript": { + "crate_name": "build_script_build", + "crate_root": "build.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "serde_core", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "alloc", + "result", + "std" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "serde_core 1.0.229", + "target": "build_script_build" + } + ], + "selects": {} + }, + "edition": "2021", + "proc_macro_deps": { + "common": [], + "selects": { + "cfg(any())": [ + { + "id": "serde_derive 1.0.229", + "target": "serde_derive" + } + ] + } + }, + "version": "1.0.229" + }, + "build_script_attrs": { + "compile_data_glob": [ + "**" + ], + "compile_data_glob_excludes": [ + "**/*.rs" + ], + "data_glob": [ + "**" + ] + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "serde_derive 1.0.229": { + "name": "serde_derive", + "version": "1.0.229", + "package_url": "https://github.com/serde-rs/serde", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/serde_derive/1.0.229/download", + "sha256": "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" + } + }, + "targets": [ + { + "ProcMacro": { + "crate_name": "serde_derive", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "serde_derive", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "proc-macro2 1.0.107", + "target": "proc_macro2" + }, + { + "id": "quote 1.0.47", + "target": "quote" + }, + { + "id": "syn 3.0.3", + "target": "syn" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "1.0.229" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "serde_json 1.0.151": { + "name": "serde_json", + "version": "1.0.151", + "package_url": "https://github.com/serde-rs/json", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/serde_json/1.0.151/download", + "sha256": "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" + } + }, + "targets": [ + { + "Library": { + "crate_name": "serde_json", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + }, + { + "BuildScript": { + "crate_name": "build_script_build", + "crate_root": "build.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "serde_json", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "std" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "itoa 1.0.18", + "target": "itoa" + }, + { + "id": "memchr 2.8.3", + "target": "memchr" + }, + { + "id": "serde_core 1.0.229", + "target": "serde_core" + }, + { + "id": "serde_json 1.0.151", + "target": "build_script_build" + }, + { + "id": "zmij 1.0.23", + "target": "zmij" + } + ], + "selects": { + "cfg(any())": [ + { + "id": "serde 1.0.229", + "target": "serde" + } + ] + } + }, + "edition": "2021", + "version": "1.0.151" + }, + "build_script_attrs": { + "compile_data_glob": [ + "**" + ], + "compile_data_glob_excludes": [ + "**/*.rs" + ], + "data_glob": [ + "**" + ] + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "serde_spanned 1.1.1": { + "name": "serde_spanned", + "version": "1.1.1", + "package_url": "https://github.com/toml-rs/toml", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/serde_spanned/1.1.1/download", + "sha256": "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" + } + }, + "targets": [ + { + "Library": { + "crate_name": "serde_spanned", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "serde_spanned", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "alloc", + "serde", + "std" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "serde_core 1.0.229", + "target": "serde_core" + } + ], + "selects": {} + }, + "edition": "2024", + "version": "1.1.1" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "spin 0.9.9": { + "name": "spin", + "version": "0.9.9", + "package_url": "https://github.com/mvdnes/spin-rs.git", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/spin/0.9.9/download", + "sha256": "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" + } + }, + "targets": [ + { + "Library": { + "crate_name": "spin", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "spin", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "barrier", + "default", + "lazy", + "lock_api", + "lock_api_crate", + "mutex", + "once", + "rwlock", + "spin_mutex" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "lock_api 0.4.14", + "target": "lock_api", + "alias": "lock_api_crate" + } + ], + "selects": {} + }, + "edition": "2015", + "version": "0.9.9" + }, + "license": "MIT", + "license_ids": [ + "MIT" + ], + "license_file": "LICENSE" + }, + "spin 0.10.1": { + "name": "spin", + "version": "0.10.1", + "package_url": "https://github.com/mvdnes/spin-rs.git", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/spin/0.10.1/download", + "sha256": "023a211cb3138dbc438680b32560ad89f699977624c9f8dbb95a47d5b4c07dd3" + } + }, + "targets": [ + { + "Library": { + "crate_name": "spin", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "spin", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "barrier", + "default", + "lazy", + "lock_api", + "mutex", + "once", + "rwlock", + "spin_mutex" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "lock_api 0.4.14", + "target": "lock_api", + "alias": "lock_api_crate" + } + ], + "selects": {} + }, + "edition": "2015", + "version": "0.10.1" + }, + "license": "MIT", + "license_ids": [ + "MIT" + ], + "license_file": "LICENSE" + }, + "stable_deref_trait 1.2.1": { + "name": "stable_deref_trait", + "version": "1.2.1", + "package_url": "https://github.com/storyyeller/stable_deref_trait", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/stable_deref_trait/1.2.1/download", + "sha256": "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + } + }, + "targets": [ + { + "Library": { + "crate_name": "stable_deref_trait", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "stable_deref_trait", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2015", + "version": "1.2.1" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "strsim 0.11.1": { + "name": "strsim", + "version": "0.11.1", + "package_url": "https://github.com/rapidfuzz/strsim-rs", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/strsim/0.11.1/download", + "sha256": "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + } + }, + "targets": [ + { + "Library": { + "crate_name": "strsim", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "strsim", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2015", + "version": "0.11.1" + }, + "license": "MIT", + "license_ids": [ + "MIT" + ], + "license_file": "LICENSE" + }, + "syn 2.0.119": { + "name": "syn", + "version": "2.0.119", + "package_url": "https://github.com/dtolnay/syn", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/syn/2.0.119/download", + "sha256": "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" + } + }, + "targets": [ + { + "Library": { + "crate_name": "syn", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "syn", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "clone-impls", + "default", + "derive", + "extra-traits", + "full", + "parsing", + "printing", + "proc-macro", + "visit-mut" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "proc-macro2 1.0.107", + "target": "proc_macro2" + }, + { + "id": "quote 1.0.47", + "target": "quote" + }, + { + "id": "unicode-ident 1.0.24", + "target": "unicode_ident" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "2.0.119" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "syn 3.0.3": { + "name": "syn", + "version": "3.0.3", + "package_url": "https://github.com/dtolnay/syn", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/syn/3.0.3/download", + "sha256": "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" + } + }, + "targets": [ + { + "Library": { + "crate_name": "syn", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "syn", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "clone-impls", + "default", + "derive", + "full", + "parsing", + "printing", + "proc-macro" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "proc-macro2 1.0.107", + "target": "proc_macro2" + }, + { + "id": "quote 1.0.47", + "target": "quote" + }, + { + "id": "unicode-ident 1.0.24", + "target": "unicode_ident" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "3.0.3" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "target-triple 1.0.1": { + "name": "target-triple", + "version": "1.0.1", + "package_url": "https://github.com/dtolnay/target-triple", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/target-triple/1.0.1/download", + "sha256": "c3a6bfce3d99adfa72d24750a61f782f3036a81e7f86d8841ee1326deaebd171" + } + }, + "targets": [ + { + "Library": { + "crate_name": "target_triple", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + }, + { + "BuildScript": { + "crate_name": "build_script_build", + "crate_root": "build.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "target_triple", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "target-triple 1.0.1", + "target": "build_script_build" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "1.0.1" + }, + "build_script_attrs": { + "compile_data_glob": [ + "**" + ], + "compile_data_glob_excludes": [ + "**/*.rs" + ], + "data_glob": [ + "**" + ] + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "tempfile 3.27.0": { + "name": "tempfile", + "version": "3.27.0", + "package_url": "https://github.com/Stebalien/tempfile", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/tempfile/3.27.0/download", + "sha256": "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" + } + }, + "targets": [ + { + "Library": { + "crate_name": "tempfile", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "tempfile", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "getrandom" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "fastrand 2.5.0", + "target": "fastrand" + }, + { + "id": "once_cell 1.21.4", + "target": "once_cell" + } + ], + "selects": { + "aarch64-apple-darwin": [ + { + "id": "getrandom 0.4.3", + "target": "getrandom" + } + ], + "aarch64-unknown-linux-gnu": [ + { + "id": "getrandom 0.4.3", + "target": "getrandom" + } + ], + "cfg(any(unix, target_os = \"wasi\"))": [ + { + "id": "rustix 1.1.4", + "target": "rustix" + } + ], + "cfg(windows)": [ + { + "id": "windows-sys 0.61.2", + "target": "windows_sys" + } + ], + "wasm32-wasip1": [ + { + "id": "getrandom 0.4.3", + "target": "getrandom" + } + ], + "x86_64-pc-windows-msvc": [ + { + "id": "getrandom 0.4.3", + "target": "getrandom" + } + ], + "x86_64-unknown-linux-gnu": [ + { + "id": "getrandom 0.4.3", + "target": "getrandom" + } + ], + "x86_64-unknown-nixos-gnu": [ + { + "id": "getrandom 0.4.3", + "target": "getrandom" + } + ] + } + }, + "edition": "2021", + "version": "3.27.0" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "termcolor 1.4.1": { + "name": "termcolor", + "version": "1.4.1", + "package_url": "https://github.com/BurntSushi/termcolor", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/termcolor/1.4.1/download", + "sha256": "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" + } + }, + "targets": [ + { + "Library": { + "crate_name": "termcolor", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "termcolor", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [], + "selects": { + "cfg(windows)": [ + { + "id": "winapi-util 0.1.11", + "target": "winapi_util" + } + ] + } + }, + "edition": "2018", + "version": "1.4.1" + }, + "license": "Unlicense OR MIT", + "license_ids": [ + "MIT", + "Unlicense" + ], + "license_file": "LICENSE-MIT" + }, + "thiserror 2.0.19": { + "name": "thiserror", + "version": "2.0.19", + "package_url": "https://github.com/dtolnay/thiserror", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/thiserror/2.0.19/download", + "sha256": "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" + } + }, + "targets": [ + { + "Library": { + "crate_name": "thiserror", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + }, + { + "BuildScript": { + "crate_name": "build_script_build", + "crate_root": "build.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "thiserror", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "thiserror 2.0.19", + "target": "build_script_build" + } + ], + "selects": {} + }, + "edition": "2021", + "proc_macro_deps": { + "common": [ + { + "id": "thiserror-impl 2.0.19", + "target": "thiserror_impl" + } + ], + "selects": {} + }, + "version": "2.0.19" + }, + "build_script_attrs": { + "compile_data_glob": [ + "**" + ], + "compile_data_glob_excludes": [ + "**/*.rs" + ], + "data_glob": [ + "**" + ] + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "thiserror-impl 2.0.19": { + "name": "thiserror-impl", + "version": "2.0.19", + "package_url": "https://github.com/dtolnay/thiserror", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/thiserror-impl/2.0.19/download", + "sha256": "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" + } + }, + "targets": [ + { + "ProcMacro": { + "crate_name": "thiserror_impl", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "thiserror_impl", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "proc-macro2 1.0.107", + "target": "proc_macro2" + }, + { + "id": "quote 1.0.47", + "target": "quote" + }, + { + "id": "syn 3.0.3", + "target": "syn" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "2.0.19" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "toml 1.1.3+spec-1.1.0": { + "name": "toml", + "version": "1.1.3+spec-1.1.0", + "package_url": "https://github.com/toml-rs/toml", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/toml/1.1.3+spec-1.1.0/download", + "sha256": "53c96ecdfa941c8fc4fcaed14f99ada8ebed502eef533015095a07e3301d4c3c" + } + }, + "targets": [ + { + "Library": { + "crate_name": "toml", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "toml", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "display", + "parse", + "serde", + "std" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "serde_core 1.0.229", + "target": "serde_core" + }, + { + "id": "serde_spanned 1.1.1", + "target": "serde_spanned" + }, + { + "id": "toml_datetime 1.1.1+spec-1.1.0", + "target": "toml_datetime" + }, + { + "id": "toml_parser 1.1.3+spec-1.1.0", + "target": "toml_parser" + }, + { + "id": "toml_writer 1.1.2+spec-1.1.0", + "target": "toml_writer" + }, + { + "id": "winnow 1.0.4", + "target": "winnow" + } + ], + "selects": {} + }, + "edition": "2024", + "version": "1.1.3+spec-1.1.0" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "toml_datetime 1.1.1+spec-1.1.0": { + "name": "toml_datetime", + "version": "1.1.1+spec-1.1.0", + "package_url": "https://github.com/toml-rs/toml", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/toml_datetime/1.1.1+spec-1.1.0/download", + "sha256": "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" + } + }, + "targets": [ + { + "Library": { + "crate_name": "toml_datetime", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "toml_datetime", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "alloc", + "default", + "serde", + "std" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "serde_core 1.0.229", + "target": "serde_core" + } + ], + "selects": {} + }, + "edition": "2024", + "version": "1.1.1+spec-1.1.0" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "toml_edit 0.25.13+spec-1.1.0": { + "name": "toml_edit", + "version": "0.25.13+spec-1.1.0", + "package_url": "https://github.com/toml-rs/toml", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/toml_edit/0.25.13+spec-1.1.0/download", + "sha256": "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" + } + }, + "targets": [ + { + "Library": { + "crate_name": "toml_edit", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "toml_edit", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "parse" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "indexmap 2.14.0", + "target": "indexmap" + }, + { + "id": "toml_datetime 1.1.1+spec-1.1.0", + "target": "toml_datetime" + }, + { + "id": "toml_parser 1.1.3+spec-1.1.0", + "target": "toml_parser" + }, + { + "id": "winnow 1.0.4", + "target": "winnow" + } + ], + "selects": {} + }, + "edition": "2024", + "version": "0.25.13+spec-1.1.0" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "toml_parser 1.1.3+spec-1.1.0": { + "name": "toml_parser", + "version": "1.1.3+spec-1.1.0", + "package_url": "https://github.com/toml-rs/toml", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/toml_parser/1.1.3+spec-1.1.0/download", + "sha256": "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" + } + }, + "targets": [ + { + "Library": { + "crate_name": "toml_parser", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "toml_parser", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "alloc", + "default", + "std" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "winnow 1.0.4", + "target": "winnow" + } + ], + "selects": {} + }, + "edition": "2024", + "version": "1.1.3+spec-1.1.0" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "toml_writer 1.1.2+spec-1.1.0": { + "name": "toml_writer", + "version": "1.1.2+spec-1.1.0", + "package_url": "https://github.com/toml-rs/toml", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/toml_writer/1.1.2+spec-1.1.0/download", + "sha256": "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" + } + }, + "targets": [ + { + "Library": { + "crate_name": "toml_writer", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "toml_writer", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "alloc", + "std" + ], + "selects": {} + }, + "edition": "2024", + "version": "1.1.2+spec-1.1.0" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "trybuild 1.0.118": { + "name": "trybuild", + "version": "1.0.118", + "package_url": "https://github.com/dtolnay/trybuild", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/trybuild/1.0.118/download", + "sha256": "06649c6f63d86604ba0c8950d5a1829fc9a17afd70fc6629f481d75b6a624c78" + } + }, + "targets": [ + { + "Library": { + "crate_name": "trybuild", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "trybuild", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "diff" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "dissimilar 1.0.11", + "target": "dissimilar" + }, + { + "id": "glob 0.3.4", + "target": "glob" + }, + { + "id": "serde 1.0.229", + "target": "serde" + }, + { + "id": "serde_json 1.0.151", + "target": "serde_json" + }, + { + "id": "target-triple 1.0.1", + "target": "target_triple" + }, + { + "id": "termcolor 1.4.1", + "target": "termcolor" + }, + { + "id": "toml 1.1.3+spec-1.1.0", + "target": "toml" + } + ], + "selects": {} + }, + "edition": "2024", + "proc_macro_deps": { + "common": [ + { + "id": "serde_derive 1.0.229", + "target": "serde_derive" + } + ], + "selects": {} + }, + "version": "1.0.118" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "unicode-ident 1.0.24": { + "name": "unicode-ident", + "version": "1.0.24", + "package_url": "https://github.com/dtolnay/unicode-ident", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/unicode-ident/1.0.24/download", + "sha256": "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + } + }, + "targets": [ + { + "Library": { + "crate_name": "unicode_ident", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "unicode_ident", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2021", + "version": "1.0.24" + }, + "license": "(MIT OR Apache-2.0) AND Unicode-3.0", + "license_ids": [ + "Apache-2.0", + "MIT", + "Unicode-3.0" + ], + "license_file": "LICENSE-APACHE" + }, + "utf8parse 0.2.2": { + "name": "utf8parse", + "version": "0.2.2", + "package_url": "https://github.com/alacritty/vte", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/utf8parse/0.2.2/download", + "sha256": "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + } + }, + "targets": [ + { + "Library": { + "crate_name": "utf8parse", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "utf8parse", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default" + ], + "selects": {} + }, + "edition": "2018", + "version": "0.2.2" + }, + "license": "Apache-2.0 OR MIT", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "LICENSE-APACHE" + }, + "winapi-util 0.1.11": { + "name": "winapi-util", + "version": "0.1.11", + "package_url": "https://github.com/BurntSushi/winapi-util", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/winapi-util/0.1.11/download", + "sha256": "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" + } + }, + "targets": [ + { + "Library": { + "crate_name": "winapi_util", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "winapi_util", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [], + "selects": { + "cfg(windows)": [ + { + "id": "windows-sys 0.61.2", + "target": "windows_sys" + } + ] + } + }, + "edition": "2021", + "version": "0.1.11" + }, + "license": "Unlicense OR MIT", + "license_ids": [ + "MIT", + "Unlicense" + ], + "license_file": "LICENSE-MIT" + }, + "windows-link 0.2.1": { + "name": "windows-link", + "version": "0.2.1", + "package_url": "https://github.com/microsoft/windows-rs", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/windows-link/0.2.1/download", + "sha256": "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + } + }, + "targets": [ + { + "Library": { + "crate_name": "windows_link", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "windows_link", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "edition": "2021", + "version": "0.2.1" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "license-apache-2.0" + }, + "windows-sys 0.61.2": { + "name": "windows-sys", + "version": "0.61.2", + "package_url": "https://github.com/microsoft/windows-rs", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/windows-sys/0.61.2/download", + "sha256": "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" + } + }, + "targets": [ + { + "Library": { + "crate_name": "windows_sys", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "windows_sys", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "Win32", + "Win32_Foundation", + "Win32_Storage", + "Win32_Storage_FileSystem", + "Win32_System", + "Win32_System_Console", + "Win32_System_SystemInformation", + "default" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "windows-link 0.2.1", + "target": "windows_link" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "0.61.2" + }, + "license": "MIT OR Apache-2.0", + "license_ids": [ + "Apache-2.0", + "MIT" + ], + "license_file": "license-apache-2.0" + }, + "winnow 1.0.4": { + "name": "winnow", + "version": "1.0.4", + "package_url": "https://github.com/winnow-rs/winnow", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/winnow/1.0.4/download", + "sha256": "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" + } + }, + "targets": [ + { + "Library": { + "crate_name": "winnow", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "winnow", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "alloc", + "ascii", + "binary", + "default", + "parser", + "std" + ], + "selects": {} + }, + "edition": "2021", + "version": "1.0.4" + }, + "license": "MIT", + "license_ids": [ + "MIT" + ], + "license_file": "LICENSE-MIT" + }, + "zmij 1.0.23": { + "name": "zmij", + "version": "1.0.23", + "package_url": "https://github.com/dtolnay/zmij", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/zmij/1.0.23/download", + "sha256": "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" + } + }, + "targets": [ + { + "Library": { + "crate_name": "zmij", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + }, + { + "BuildScript": { + "crate_name": "build_script_build", + "crate_root": "build.rs", + "srcs": { + "allow_empty": true, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "zmij", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "deps": { + "common": [ + { + "id": "zmij 1.0.23", + "target": "build_script_build" + } + ], + "selects": {} + }, + "edition": "2021", + "version": "1.0.23" + }, + "build_script_attrs": { + "compile_data_glob": [ + "**" + ], + "compile_data_glob_excludes": [ + "**/*.rs" + ], + "data_glob": [ + "**" + ] + }, + "license": "MIT", + "license_ids": [ + "MIT" + ], + "license_file": "LICENSE-MIT" + } + }, + "binary_crates": [], + "workspace_members": { + "coverage 0.1.0": "rust/coverage", + "engine-ffi 0.1.0": "rust/engine", + "fuzztest 0.1.0": "rust", + "fuzztest-macro 0.1.0": "rust/fuzztest_macro" + }, + "conditions": { + "aarch64-apple-darwin": [ + "aarch64-apple-darwin" + ], + "aarch64-unknown-linux-gnu": [ + "aarch64-unknown-linux-gnu" + ], + "cfg(all(any(target_os = \"linux\", target_os = \"android\"), any(rustix_use_libc, miri, not(all(target_os = \"linux\", any(target_endian = \"little\", any(target_arch = \"s390x\", target_arch = \"powerpc\")), any(target_arch = \"arm\", all(target_arch = \"aarch64\", target_pointer_width = \"64\"), target_arch = \"riscv64\", all(rustix_use_experimental_asm, target_arch = \"powerpc\"), all(rustix_use_experimental_asm, target_arch = \"powerpc64\"), all(rustix_use_experimental_asm, target_arch = \"s390x\"), all(rustix_use_experimental_asm, target_arch = \"mips\"), all(rustix_use_experimental_asm, target_arch = \"mips32r6\"), all(rustix_use_experimental_asm, target_arch = \"mips64\"), all(rustix_use_experimental_asm, target_arch = \"mips64r6\"), target_arch = \"x86\", all(target_arch = \"x86_64\", target_pointer_width = \"64\")))))))": [], + "cfg(all(any(target_os = \"linux\", target_os = \"android\"), not(any(all(target_os = \"linux\", target_env = \"\"), getrandom_backend = \"custom\", getrandom_backend = \"linux_raw\", getrandom_backend = \"rdrand\", getrandom_backend = \"rndr\"))))": [ + "aarch64-unknown-linux-gnu", + "x86_64-unknown-linux-gnu", + "x86_64-unknown-nixos-gnu" + ], + "cfg(all(not(rustix_use_libc), not(miri), target_os = \"linux\", any(target_endian = \"little\", any(target_arch = \"s390x\", target_arch = \"powerpc\")), any(target_arch = \"arm\", all(target_arch = \"aarch64\", target_pointer_width = \"64\"), target_arch = \"riscv64\", all(rustix_use_experimental_asm, target_arch = \"powerpc\"), all(rustix_use_experimental_asm, target_arch = \"powerpc64\"), all(rustix_use_experimental_asm, target_arch = \"s390x\"), all(rustix_use_experimental_asm, target_arch = \"mips\"), all(rustix_use_experimental_asm, target_arch = \"mips32r6\"), all(rustix_use_experimental_asm, target_arch = \"mips64\"), all(rustix_use_experimental_asm, target_arch = \"mips64r6\"), target_arch = \"x86\", all(target_arch = \"x86_64\", target_pointer_width = \"64\"))))": [ + "aarch64-unknown-linux-gnu", + "x86_64-unknown-linux-gnu", + "x86_64-unknown-nixos-gnu" + ], + "cfg(all(not(windows), any(rustix_use_libc, miri, not(all(target_os = \"linux\", any(target_endian = \"little\", any(target_arch = \"s390x\", target_arch = \"powerpc\")), any(target_arch = \"arm\", all(target_arch = \"aarch64\", target_pointer_width = \"64\"), target_arch = \"riscv64\", all(rustix_use_experimental_asm, target_arch = \"powerpc\"), all(rustix_use_experimental_asm, target_arch = \"powerpc64\"), all(rustix_use_experimental_asm, target_arch = \"s390x\"), all(rustix_use_experimental_asm, target_arch = \"mips\"), all(rustix_use_experimental_asm, target_arch = \"mips32r6\"), all(rustix_use_experimental_asm, target_arch = \"mips64\"), all(rustix_use_experimental_asm, target_arch = \"mips64r6\"), target_arch = \"x86\", all(target_arch = \"x86_64\", target_pointer_width = \"64\")))))))": [ + "aarch64-apple-darwin", + "wasm32-unknown-unknown", + "wasm32-wasip1" + ], + "cfg(all(target_arch = \"aarch64\", target_os = \"android\"))": [], + "cfg(all(target_arch = \"aarch64\", target_os = \"linux\"))": [ + "aarch64-unknown-linux-gnu" + ], + "cfg(all(target_arch = \"aarch64\", target_vendor = \"apple\"))": [ + "aarch64-apple-darwin" + ], + "cfg(all(target_arch = \"loongarch64\", target_os = \"linux\"))": [], + "cfg(all(target_os = \"uefi\", getrandom_backend = \"efi_rng\"))": [], + "cfg(any())": [], + "cfg(any(target_arch = \"x86_64\", target_arch = \"x86\"))": [ + "x86_64-pc-windows-msvc", + "x86_64-unknown-linux-gnu", + "x86_64-unknown-nixos-gnu" + ], + "cfg(any(target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"hurd\", target_os = \"illumos\", target_os = \"cygwin\", all(target_os = \"horizon\", target_arch = \"arm\")))": [], + "cfg(any(target_os = \"haiku\", target_os = \"redox\", target_os = \"nto\", target_os = \"aix\"))": [], + "cfg(any(target_os = \"ios\", target_os = \"visionos\", target_os = \"watchos\", target_os = \"tvos\"))": [], + "cfg(any(target_os = \"macos\", target_os = \"openbsd\", target_os = \"vita\", target_os = \"emscripten\"))": [ + "aarch64-apple-darwin" + ], + "cfg(any(unix, target_os = \"wasi\"))": [ + "aarch64-apple-darwin", + "aarch64-unknown-linux-gnu", + "wasm32-wasip1", + "x86_64-unknown-linux-gnu", + "x86_64-unknown-nixos-gnu" + ], + "cfg(target_arch = \"x86_64\")": [ + "x86_64-pc-windows-msvc", + "x86_64-unknown-linux-gnu", + "x86_64-unknown-nixos-gnu" + ], + "cfg(target_family = \"wasm\")": [ + "wasm32-unknown-unknown", + "wasm32-wasip1" + ], + "cfg(target_os = \"hermit\")": [], + "cfg(target_os = \"netbsd\")": [], + "cfg(target_os = \"solaris\")": [], + "cfg(target_os = \"vxworks\")": [], + "cfg(target_os = \"wasi\")": [ + "wasm32-wasip1" + ], + "cfg(unix)": [ + "aarch64-apple-darwin", + "aarch64-unknown-linux-gnu", + "x86_64-unknown-linux-gnu", + "x86_64-unknown-nixos-gnu" + ], + "cfg(windows)": [ + "x86_64-pc-windows-msvc" + ], + "riscv32i-unknown-none-elf": [], + "riscv32imc-unknown-none-elf": [], + "wasm32-unknown-unknown": [ + "wasm32-unknown-unknown" + ], + "wasm32-wasip1": [ + "wasm32-wasip1" + ], + "x86_64-pc-windows-msvc": [ + "x86_64-pc-windows-msvc" + ], + "x86_64-unknown-linux-gnu": [ + "x86_64-unknown-linux-gnu", + "x86_64-unknown-nixos-gnu" + ], + "x86_64-unknown-nixos-gnu": [ + "x86_64-unknown-nixos-gnu" + ], + "xtensa-esp32s2-none-elf": [] + }, + "direct_deps": [ + "anyhow 1.0.104", + "clap 4.6.4", + "convert_case 0.4.0", + "humantime 2.4.0", + "inventory 0.3.24", + "num-traits 0.2.19", + "postcard 1.1.3", + "proc-macro-crate 3.5.0", + "proc-macro2 1.0.107", + "quote 1.0.47", + "rand 0.10.2", + "serde 1.0.229", + "spin 0.10.1", + "syn 2.0.119", + "tempfile 3.27.0" + ], + "direct_dev_deps": [ + "googletest 0.14.3", + "trybuild 1.0.118" + ], + "unused_patches": [] +} diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 000000000..033c775be --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,832 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys", +] + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "atomic-polyfill" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" +dependencies = [ + "critical-section", +] + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chacha20" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" +dependencies = [ + "cfg-if", + "cpufeatures", + "rand_core", +] + +[[package]] +name = "clap" +version = "4.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91e0c145792ef73a6ad36d27c75ac09f1832222a3c209689d90f534685ee5b7" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f09628afdcc538b57f3c6341e9c8e9970f18e4a481690a64974d7023bd33548b" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "cobs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" +dependencies = [ + "thiserror", +] + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "coverage" +version = "0.1.0" + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "dissimilar" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aeda16ab4059c5fd2a83f2b9c9e9c981327b18aa8e3b313f7e6563799d4f093e" + +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + +[[package]] +name = "engine-ffi" +version = "0.1.0" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fastrand" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" + +[[package]] +name = "fuzztest" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap", + "coverage", + "engine-ffi", + "fuzztest-macro", + "googletest", + "humantime", + "inventory", + "num-traits", + "postcard", + "rand", + "serde", + "spin 0.10.1", + "tempfile", + "trybuild", +] + +[[package]] +name = "fuzztest-macro" +version = "0.1.0" +dependencies = [ + "convert_case", + "googletest", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "rand_core", +] + +[[package]] +name = "glob" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" + +[[package]] +name = "googletest" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6b5e2f2b556b7b90297a5a35c8267dd43a537923d2b329beefdba2b4ec19d94" +dependencies = [ + "googletest_macro", + "num-traits", + "regex", + "rustversion", +] + +[[package]] +name = "googletest_macro" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ae6abc96141edd26bf5aeec0f119c129c44de3ced09e5073711a02cb74725d0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heapless" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" +dependencies = [ + "atomic-polyfill", + "hash32", + "rustc_version", + "serde", + "spin 0.9.9", + "stable_deref_trait", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "humantime" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15cdd26707701c53297e2fa6afb323d55fbc1d0810c3aec078ae3ef0424c3c15" + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "inventory" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4f0c30c76f2f4ccee3fe55a2435f691ca00c0e4bd87abe4f4a851b1d4dac39b" +dependencies = [ + "rustversion", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "postcard" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" +dependencies = [ + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "heapless", + "serde", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" +dependencies = [ + "chacha20", + "getrandom", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +dependencies = [ + "serde_core", +] + +[[package]] +name = "spin" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spin" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "023a211cb3138dbc438680b32560ad89f699977624c9f8dbb95a47d5b4c07dd3" +dependencies = [ + "lock_api", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "target-triple" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3a6bfce3d99adfa72d24750a61f782f3036a81e7f86d8841ee1326deaebd171" + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom", + "once_cell", + "rustix", + "windows-sys", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "2.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "toml" +version = "1.1.3+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53c96ecdfa941c8fc4fcaed14f99ada8ebed502eef533015095a07e3301d4c3c" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.13+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.1.3+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_writer" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" + +[[package]] +name = "trybuild" +version = "1.0.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06649c6f63d86604ba0c8950d5a1829fc9a17afd70fc6629f481d75b6a624c78" +dependencies = [ + "dissimilar", + "glob", + "serde", + "serde_derive", + "serde_json", + "target-triple", + "termcolor", + "toml", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +dependencies = [ + "memchr", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 000000000..9ea0da474 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,21 @@ +[workspace] +members = ["rust", "rust/engine", "rust/fuzztest_macro"] +resolver = "2" + +[workspace.dependencies] +anyhow = "1.0.100" +clap = { version = "4.6.1", features = ["derive", "env"] } +convert_case = "0.4.0" +googletest = "0.14.3" +humantime = "2.4.0" +inventory = "0.3.20" +num-traits = "0.2" +postcard = { version = "1.0.0", features = ["use-std"] } +proc-macro-crate = "3.4.0" +proc-macro2 = "1.0.106" +quote = "1.0.45" +rand = { version = "0.10.1", features = ["std_rng"] } +serde = { version = "1.0", features = ["derive"] } +spin = "0.10.0" +syn = { version = "2.0.117", features = ["extra-traits", "full", "parsing", "printing", "visit-mut"] } +tempfile = "3.27.0" diff --git a/MODULE.bazel b/MODULE.bazel index 4610482c3..0a0dc3eec 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -27,6 +27,31 @@ bazel_dep( name = "rules_shell", version = "0.6.1", ) +bazel_dep( + name = "rules_rust", + version = "0.71.3", +) + +rust = use_extension("@rules_rust//rust:extensions.bzl", "rust") +rust.toolchain( + edition = "2024", + versions = ["nightly/2026-07-01"], +) +use_repo(rust, "rust_toolchains") + +register_toolchains("@rust_toolchains//:all") + +crate = use_extension("@rules_rust//crate_universe:extensions.bzl", "crate") +crate.from_cargo( + name = "crate_index", + cargo_lockfile = "//:Cargo.lock", + lockfile = "//:Cargo.Bazel.lock", + manifests = [ + "//:Cargo.toml", + ], +) +use_repo(crate, "crate_index") + bazel_dep( name = "abseil-cpp", version = "20260526.0", diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock new file mode 100644 index 000000000..945449983 --- /dev/null +++ b/MODULE.bazel.lock @@ -0,0 +1,435 @@ +{ + "lockFileVersion": 24, + "registryFileHashes": { + "https://bcr.bazel.build/bazel_registry.json": "8a28e4aff06ee60aed2a8c281907fb8bcbf3b753c91fb5a5c57da3215d5b3497", + "https://bcr.bazel.build/modules/abseil-cpp/20210324.2/MODULE.bazel": "7cd0312e064fde87c8d1cd79ba06c876bd23630c83466e9500321be55c96ace2", + "https://bcr.bazel.build/modules/abseil-cpp/20211102.0/MODULE.bazel": "70390338f7a5106231d20620712f7cccb659cd0e9d073d1991c038eb9fc57589", + "https://bcr.bazel.build/modules/abseil-cpp/20220623.1/MODULE.bazel": "73ae41b6818d423a11fd79d95aedef1258f304448193d4db4ff90e5e7a0f076c", + "https://bcr.bazel.build/modules/abseil-cpp/20230125.1/MODULE.bazel": "89047429cb0207707b2dface14ba7f8df85273d484c2572755be4bab7ce9c3a0", + "https://bcr.bazel.build/modules/abseil-cpp/20230802.0.bcr.1/MODULE.bazel": "1c8cec495288dccd14fdae6e3f95f772c1c91857047a098fad772034264cc8cb", + "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.0/MODULE.bazel": "98dc378d64c12a4e4741ad3362f87fb737ee6a0886b2d90c3cdbb4d93ea3e0bf", + "https://bcr.bazel.build/modules/abseil-cpp/20240116.1/MODULE.bazel": "37bcdb4440fbb61df6a1c296ae01b327f19e9bb521f9b8e26ec854b6f97309ed", + "https://bcr.bazel.build/modules/abseil-cpp/20240116.2/MODULE.bazel": "73939767a4686cd9a520d16af5ab440071ed75cec1a876bf2fcfaf1f71987a16", + "https://bcr.bazel.build/modules/abseil-cpp/20240722.0/MODULE.bazel": "88668a07647adbdc14cb3a7cd116fb23c9dda37a90a1681590b6c9d8339a5b84", + "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.0/MODULE.bazel": "c43c16ca2c432566cdb78913964497259903ebe8fb7d9b57b38e9f1425b427b8", + "https://bcr.bazel.build/modules/abseil-cpp/20250814.1/MODULE.bazel": "51f2312901470cdab0dbdf3b88c40cd21c62a7ed58a3de45b365ddc5b11bcab2", + "https://bcr.bazel.build/modules/abseil-cpp/20260526.0/MODULE.bazel": "b4a76a8d70ba08aa0253fd5e374ca5a5fa6b15aba985a7bbe4467c138954830f", + "https://bcr.bazel.build/modules/abseil-cpp/20260526.0/source.json": "a39a26fec1b7037bdd7ea6c25cc098b99ae80af10ff0b6ec8c4bfe0df694f451", + "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/antlr4-cpp-runtime/4.13.2/MODULE.bazel": "087a9f8cb3d9643938bacb3aa97d795492dae4b23ac4cbd3ed2cdcf66a2a38da", + "https://bcr.bazel.build/modules/antlr4-cpp-runtime/4.13.2/source.json": "524c7181de8478c1bb50d9ab135b1ba85b98986e7c1f362a41bf812bd1d8a421", + "https://bcr.bazel.build/modules/apple_support/1.11.1/MODULE.bazel": "1843d7cd8a58369a444fc6000e7304425fba600ff641592161d9f15b179fb896", + "https://bcr.bazel.build/modules/apple_support/1.13.0/MODULE.bazel": "7c8cdea7e031b7f9f67f0b497adf6d2c6a2675e9304ca93a9af6ed84eef5a524", + "https://bcr.bazel.build/modules/apple_support/1.15.1/MODULE.bazel": "a0556fefca0b1bb2de8567b8827518f94db6a6e7e7d632b4c48dc5f865bc7c85", + "https://bcr.bazel.build/modules/apple_support/1.17.1/MODULE.bazel": "655c922ab1209978a94ef6ca7d9d43e940cd97d9c172fb55f94d91ac53f8610b", + "https://bcr.bazel.build/modules/apple_support/1.22.1/MODULE.bazel": "90bd1a660590f3ceffbdf524e37483094b29352d85317060b2327fff8f3f4458", + "https://bcr.bazel.build/modules/apple_support/1.23.1/MODULE.bazel": "53763fed456a968cf919b3240427cf3a9d5481ec5466abc9d5dc51bc70087442", + "https://bcr.bazel.build/modules/apple_support/1.24.1/MODULE.bazel": "f46e8ddad60aef170ee92b2f3d00ef66c147ceafea68b6877cb45bd91737f5f8", + "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.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_rules_esbuild/0.21.0/MODULE.bazel": "77dc393c43ad79398b05865444c5200c6f1aae6765615544f2c7730b5858d533", + "https://bcr.bazel.build/modules/aspect_rules_esbuild/0.21.0/source.json": "062b1d3dba8adcfeb28fe60c185647f5a53ec0487ffe93cf0ae91566596e4b49", + "https://bcr.bazel.build/modules/aspect_rules_js/1.33.1/MODULE.bazel": "db3e7f16e471cf6827059d03af7c21859e7a0d2bc65429a3a11f005d46fc501b", + "https://bcr.bazel.build/modules/aspect_rules_js/1.39.0/MODULE.bazel": "aece421d479e3c31dc3e5f6d49a12acc2700457c03c556650ec7a0ff23fc0d95", + "https://bcr.bazel.build/modules/aspect_rules_js/2.0.0/MODULE.bazel": "b45b507574aa60a92796e3e13c195cd5744b3b8aff516a9c0cb5ae6a048161c5", + "https://bcr.bazel.build/modules/aspect_rules_js/2.3.8/MODULE.bazel": "74bf20a7a6bd5f2be09607fdb4196cfd6f203422ea271752ec2b1afe95426101", + "https://bcr.bazel.build/modules/aspect_rules_js/2.3.8/source.json": "411ec9d79d6f5fe8a083359588c21d01a5b48d88a2cbd334a4c90365015b7836", + "https://bcr.bazel.build/modules/aspect_rules_lint/0.12.0/MODULE.bazel": "e767c5dbfeb254ec03275a7701b5cfde2c4d2873676804bc7cb27ddff3728fed", + "https://bcr.bazel.build/modules/aspect_rules_ts/3.6.0/MODULE.bazel": "d0045b5eabb012be550a609589b3e5e47eba682344b19cfd9365d4d896ed07df", + "https://bcr.bazel.build/modules/aspect_rules_ts/3.6.0/source.json": "5593e3f1cd0dd5147f7748e163307fd5c2e1077913d6945b58739ad8d770a290", + "https://bcr.bazel.build/modules/bazel_features/0.1.0/MODULE.bazel": "47011d645b0f949f42ee67f2e8775188a9cf4a0a1528aa2fa4952f2fd00906fd", + "https://bcr.bazel.build/modules/bazel_features/1.1.0/MODULE.bazel": "cfd42ff3b815a5f39554d97182657f8c4b9719568eb7fded2b9135f084bf760b", + "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.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.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.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.43.0/MODULE.bazel": "defa2226f06ba20550d6548c3a2ea2a7929634437a52973869c20c225450eb91", + "https://bcr.bazel.build/modules/bazel_features/1.50.0/MODULE.bazel": "2083ef9c7a469f520890483ccf8e0189d6e71e2117e7752e15e6554433d5ae3e", + "https://bcr.bazel.build/modules/bazel_features/1.50.0/source.json": "e0ee3debde2789ff56e4452e612d126925ba9ab64d4bde79c67f099d2902df9b", + "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", + "https://bcr.bazel.build/modules/bazel_skylib/1.2.1/MODULE.bazel": "f35baf9da0efe45fa3da1696ae906eea3d615ad41e2e3def4aeb4e8bc0ef9a7a", + "https://bcr.bazel.build/modules/bazel_skylib/1.3.0/MODULE.bazel": "20228b92868bf5cfc41bda7afc8a8ba2a543201851de39d990ec957b513579c5", + "https://bcr.bazel.build/modules/bazel_skylib/1.4.1/MODULE.bazel": "a0dcb779424be33100dcae821e9e27e4f2901d9dfd5333efe5ac6a8d7ab75e1d", + "https://bcr.bazel.build/modules/bazel_skylib/1.4.2/MODULE.bazel": "3bd40978e7a1fac911d5989e6b09d8f64921865a45822d8b09e815eaa726a651", + "https://bcr.bazel.build/modules/bazel_skylib/1.5.0/MODULE.bazel": "32880f5e2945ce6a03d1fbd588e9198c0a959bb42297b2cfaf1685b7bc32e138", + "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.8.0/MODULE.bazel": "2fb3fb53675f6adfc1ca5bfbd5cfb655ae350fba4706d924a8ec7e3ba945671c", + "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/boringssl/0.0.0-20211025-d4f1ab9/MODULE.bazel": "6ee6353f8b1a701fe2178e1d925034294971350b6d3ac37e67e5a7d463267834", + "https://bcr.bazel.build/modules/boringssl/0.0.0-20230215-5c22014/MODULE.bazel": "4b03dc0d04375fa0271174badcd202ed249870c8e895b26664fd7298abea7282", + "https://bcr.bazel.build/modules/boringssl/0.0.0-20240530-2db0eb3/MODULE.bazel": "d0405b762c5e87cd445b7015f2b8da5400ef9a8dbca0bfefa6c1cea79d528a97", + "https://bcr.bazel.build/modules/boringssl/0.20240913.0/MODULE.bazel": "fcaa7503a5213290831a91ed1eb538551cf11ac0bc3a6ad92d0fef92c5bd25fb", + "https://bcr.bazel.build/modules/boringssl/0.20241024.0/MODULE.bazel": "b540cff73d948cb79cb0bc108d7cef391d2098a25adabfda5043e4ef548dbc87", + "https://bcr.bazel.build/modules/boringssl/0.20241024.0/source.json": "d843092e682b84188c043ac742965d7f96e04c846c7e338187e03238674909a9", + "https://bcr.bazel.build/modules/brotli/1.1.0/MODULE.bazel": "3b5b90488995183419c4b5c9b063a164f6c0bc4d0d6b40550a612a5e860cc0fe", + "https://bcr.bazel.build/modules/brotli/1.1.0/source.json": "098a4fd315527166e8dfe1fd1537c96a737a83764be38fc43f4da231d600f3d0", + "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/bzip2/1.0.8/MODULE.bazel": "83ee443b286b0b91566e5ee77e74ba6445895f3135467893871560f9e4ebc159", + "https://bcr.bazel.build/modules/bzip2/1.0.8/source.json": "b64f3a2f973749cf5f6ee32b3d804af56a35a746228a7845ed5daa31c8cc8af1", + "https://bcr.bazel.build/modules/c-ares/1.15.0/MODULE.bazel": "ba0a78360fdc83f02f437a9e7df0532ad1fbaa59b722f6e715c11effebaa0166", + "https://bcr.bazel.build/modules/c-ares/1.19.1.bcr.1/MODULE.bazel": "4894eaa219c932a8025c223e5dbf0826de226f8cb62bbed76466c9475598e22b", + "https://bcr.bazel.build/modules/c-ares/1.19.1.bcr.1/source.json": "fa4eb4f11c83cfdc2ea12ce9433f5a0a2c2686c60b2e469c146a05f495e9a4bd", + "https://bcr.bazel.build/modules/c-ares/1.19.1/MODULE.bazel": "73bca21720772370ff91cc8e88bbbaf14897720c6473e87c1ddc0f848284c313", + "https://bcr.bazel.build/modules/cel-spec/0.15.0/MODULE.bazel": "e1eed53d233acbdcf024b4b0bc1528116d92c29713251b5154078ab1348cb600", + "https://bcr.bazel.build/modules/cel-spec/0.15.0/source.json": "ab7dccdf21ea2261c0f809b5a5221a4d7f8b580309f285fdf1444baaca75d44a", + "https://bcr.bazel.build/modules/civetweb/1.16/MODULE.bazel": "46a38f9daeb57392e3827fce7d40926be0c802bd23cdd6bfd3a96c804de42fae", + "https://bcr.bazel.build/modules/civetweb/1.16/source.json": "ba8b9585adb8355cb51b999d57172fd05e7a762c56b8d4bac6db42c99de3beb7", + "https://bcr.bazel.build/modules/crc32c/1.1.0/MODULE.bazel": "f11439d063a2b4e0f19b56bb8da6a931f9691bf583bd1ec0718645bce6c62b06", + "https://bcr.bazel.build/modules/crc32c/1.1.0/source.json": "aabc6ce46d4b71343d500270c2ddfd45f59cff9fd171313bdd773bf620cf2a6f", + "https://bcr.bazel.build/modules/curl/8.4.0/MODULE.bazel": "0bc250aa1cb69590049383df7a9537c809591fcf876c620f5f097c58fdc9bc10", + "https://bcr.bazel.build/modules/curl/8.7.1/MODULE.bazel": "088221c35a2939c555e6e47cb31a81c15f8b59f4daa8009b1e9271a502d33485", + "https://bcr.bazel.build/modules/curl/8.8.0.bcr.3/MODULE.bazel": "df703a5a606a5bc264a95940113daa44197dc211f51230dd058323f2aa50efca", + "https://bcr.bazel.build/modules/curl/8.8.0.bcr.3/source.json": "ef03f6b660515bcfc9e284e8bdd3679895cc28afdaecd794a6059d47f22d1df1", + "https://bcr.bazel.build/modules/curl/8.8.0/MODULE.bazel": "7da3b3e79b0b4ee8f8c95d640bc6ad7b430ce66ef6e9c9d2bc29b3b5ef85f6fe", + "https://bcr.bazel.build/modules/cython/3.0.11-1/MODULE.bazel": "868b3f5c956c3657420d2302004c6bb92606bfa47e314bab7f2ba0630c7c966c", + "https://bcr.bazel.build/modules/cython/3.0.11-1/source.json": "da318be900b8ca9c3d1018839d3bebc5a8e1645620d0848fa2c696d4ecf7c296", + "https://bcr.bazel.build/modules/envoy_api/0.0.0-20241214-918efc9/MODULE.bazel": "24e05f6f52f37be63a795192848555a2c8c855e7814dbc1ed419fb04a7005464", + "https://bcr.bazel.build/modules/envoy_api/0.0.0-20250128-4de3c74/MODULE.bazel": "1fe72489212c530086e3ffb0e018b2bfef4663200ca03571570f9f006bef1d75", + "https://bcr.bazel.build/modules/envoy_api/0.0.0-20250128-4de3c74/source.json": "028519164a2e24563f4b43d810fdedc702daed90e71e7042d45ba82ad807b46f", + "https://bcr.bazel.build/modules/flatbuffers/25.12.19/MODULE.bazel": "fe3a7f7811f43264f68136ad99e64384d70b2a25245e09ab800c4bb83171da25", + "https://bcr.bazel.build/modules/flatbuffers/25.12.19/source.json": "ea0204be7a79de9141cee5fa436e58a14e88b39b5b59227b21efa0394474ebea", + "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.27.0/MODULE.bazel": "3446abd608295de6d90b4a8a118ed64a9ce11dcb3dda2dc3290a22056bd20996", + "https://bcr.bazel.build/modules/gazelle/0.30.0/MODULE.bazel": "f888a1effe338491f35f0e0e85003b47bb9d8295ccba73c37e07702d8d31c65b", + "https://bcr.bazel.build/modules/gazelle/0.32.0/MODULE.bazel": "b499f58a5d0d3537f3cf5b76d8ada18242f64ec474d8391247438bf04f58c7b8", + "https://bcr.bazel.build/modules/gazelle/0.33.0/MODULE.bazel": "a13a0f279b462b784fb8dd52a4074526c4a2afe70e114c7d09066097a46b3350", + "https://bcr.bazel.build/modules/gazelle/0.34.0/MODULE.bazel": "abdd8ce4d70978933209db92e436deb3a8b737859e9354fb5fd11fb5c2004c8a", + "https://bcr.bazel.build/modules/gazelle/0.36.0/MODULE.bazel": "e375d5d6e9a6ca59b0cb38b0540bc9a05b6aa926d322f2de268ad267a2ee74c0", + "https://bcr.bazel.build/modules/gazelle/0.37.0/MODULE.bazel": "d1327ba0907d0275ed5103bfbbb13518f6c04955b402213319d0d6c0ce9839d4", + "https://bcr.bazel.build/modules/gazelle/0.37.0/source.json": "b3adc10e2394e7f63ea88fb1d622d4894bfe9ec6961c493ae9a887723ab16831", + "https://bcr.bazel.build/modules/google_benchmark/1.8.2/MODULE.bazel": "a70cf1bba851000ba93b58ae2f6d76490a9feb74192e57ab8e8ff13c34ec50cb", + "https://bcr.bazel.build/modules/google_benchmark/1.8.4/MODULE.bazel": "c6d54a11dcf64ee63545f42561eda3fd94c1b5f5ebe1357011de63ae33739d5e", + "https://bcr.bazel.build/modules/google_benchmark/1.8.5/MODULE.bazel": "9ba9b31b984022828a950e3300410977eda2e35df35584c6b0b2d0c2e52766b7", + "https://bcr.bazel.build/modules/google_benchmark/1.8.5/source.json": "2c9c685f9b496f125b9e3a9c696c549d1ed2f33b75830a2fb6ac94fab23c0398", + "https://bcr.bazel.build/modules/google_cloud_cpp/3.0.0-rc1/MODULE.bazel": "d3dc3ee19f703239a67b5f954784706ffab28c0d5cf4dcc5253df8ee2feba8ff", + "https://bcr.bazel.build/modules/google_cloud_cpp/3.0.0-rc1/source.json": "0dfad712a3cd6843be34cd3b1b27d56741ce164a8e2ad633fa56932dab4b51b3", + "https://bcr.bazel.build/modules/googleapis-cc/1.0.0/MODULE.bazel": "cf01757e7590c56140a4b81638ff2b3e7074769e6271720bbf738fcda25b6fc2", + "https://bcr.bazel.build/modules/googleapis-cc/1.0.0/source.json": "ab0e3a2ee9968a8848f59872fbbfa3e1f768597d71d2229e6caa319d357967c7", + "https://bcr.bazel.build/modules/googleapis-grpc-cc/1.0.0/MODULE.bazel": "3553358a9d8d96026c9e28d9fb6c268574950d0be7fa9b4c0aeaf3c37c73f2d3", + "https://bcr.bazel.build/modules/googleapis-grpc-cc/1.0.0/source.json": "fa7b79043b3c82bf74f1f2fa45af289e19b247375868d0752db2c114a1c7366c", + "https://bcr.bazel.build/modules/googleapis-rules-registry/1.0.0/MODULE.bazel": "97c6a4d413b373d4cc97065da3de1b2166e22cbbb5f4cc9f05760bfa83619e24", + "https://bcr.bazel.build/modules/googleapis-rules-registry/1.0.0/source.json": "cf611c836a60e98e2e2ab2de8004f119e9f06878dcf4ea2d95a437b1b7a89fe9", + "https://bcr.bazel.build/modules/googleapis/0.0.0-20240326-1c8d509c5/MODULE.bazel": "a4b7e46393c1cdcc5a00e6f85524467c48c565256b22b5fae20f84ab4a999a68", + "https://bcr.bazel.build/modules/googleapis/0.0.0-20240819-fe8ba054a/MODULE.bazel": "117b7c7be7327ed5d6c482274533f2dbd78631313f607094d4625c28203cacdf", + "https://bcr.bazel.build/modules/googleapis/0.0.0-20250703-f9d6fe4a/MODULE.bazel": "d1a3f5d60acdc6466b2f86320855c8a5543cec1af1e4bf9d34d3115fe043c851", + "https://bcr.bazel.build/modules/googleapis/0.0.0-20250703-f9d6fe4a/source.json": "a51564703aa367b73e995ab01c8485860066ad39866065767871887c63122392", + "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/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/grpc-java/1.62.2/MODULE.bazel": "99b8771e8c7cacb130170fed2a10c9e8fed26334a93e73b42d2953250885a158", + "https://bcr.bazel.build/modules/grpc-java/1.66.0/MODULE.bazel": "86ff26209fac846adb89db11f3714b3dc0090fb2fb81575673cc74880cda4e7e", + "https://bcr.bazel.build/modules/grpc-java/1.69.0/MODULE.bazel": "53887af6a00b3b406d70175d3d07e84ea9362016ff55ea90b9185f0227bfaf98", + "https://bcr.bazel.build/modules/grpc-proto/0.0.0-20240627-ec30f58/MODULE.bazel": "88de79051e668a04726e9ea94a481ec6f1692086735fd6f488ab908b3b909238", + "https://bcr.bazel.build/modules/grpc/1.41.0/MODULE.bazel": "5bcbfc2b274dabea628f0649dc50c90cf36543b1cfc31624832538644ad1aae8", + "https://bcr.bazel.build/modules/grpc/1.56.3.bcr.1/MODULE.bazel": "cd5b1eb276b806ec5ab85032921f24acc51735a69ace781be586880af20ab33f", + "https://bcr.bazel.build/modules/grpc/1.62.1/MODULE.bazel": "2998211594b8a79a6b459c4e797cfa19f0fb8b3be3149760ec7b8c99abfd426f", + "https://bcr.bazel.build/modules/grpc/1.63.1.bcr.1/MODULE.bazel": "d7b9fef03bd175e6825237b521b18a3c29f1ac15f8aa52c8a1a0f3bd8f33d54b", + "https://bcr.bazel.build/modules/grpc/1.66.0.bcr.2/MODULE.bazel": "0fa2b0fd028ce354febf0fe90f1ed8fecfbfc33118cddd95ac0418cc283333a0", + "https://bcr.bazel.build/modules/grpc/1.66.0.bcr.3/MODULE.bazel": "f6047e89faf488f5e3e65cb2594c6f5e86992abec7487163ff6b623526e543b0", + "https://bcr.bazel.build/modules/grpc/1.69.0/MODULE.bazel": "4e26e05c9e1ef291ccbc96aad8e457b1b8abedbc141623831629da2f8168eef6", + "https://bcr.bazel.build/modules/grpc/1.70.1/MODULE.bazel": "b800cd8e3e7555c1e61cba2e02d3a2fcf0e91f66e800db286d965d3b7a6a721a", + "https://bcr.bazel.build/modules/grpc/1.72.0/MODULE.bazel": "b2a82e2678717683f918ac87364005fd0bf3ae3bfca9b0cae68e918ba42594b1", + "https://bcr.bazel.build/modules/grpc/1.72.0/source.json": "214430b7958731283a23d0aeed8b5e1fd6a08132eb98fe77d5110f5142959335", + "https://bcr.bazel.build/modules/highwayhash/0.0.0-20240305-5ad3bf8/MODULE.bazel": "5c7f29d5bd70feff14b0f65b39584957e18e4a8d555e5a29a4c36019afbb44b9", + "https://bcr.bazel.build/modules/highwayhash/0.0.0-20240305-5ad3bf8/source.json": "211c0937ef5f537da6c3c135d12e60927c71b380642e207e4a02b86d29c55e85", + "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.6/MODULE.bazel": "2f8d20d3b7d54143213c4dfc3d98225c42de7d666011528dc8fe91591e2e17b0", + "https://bcr.bazel.build/modules/jsoncpp/1.9.6/source.json": "a04756d367a2126c3541682864ecec52f92cdee80a35735a3cb249ce015ca000", + "https://bcr.bazel.build/modules/libpfm/4.11.0/MODULE.bazel": "45061ff025b301940f1e30d2c16bea596c25b176c8b6b3087e92615adbd52902", + "https://bcr.bazel.build/modules/libpfm/4.11.0/source.json": "caaffb3ac2b59b8aac456917a4ecf3167d40478ee79f15ab7a877ec9273937c9", + "https://bcr.bazel.build/modules/lz4/1.9.4/MODULE.bazel": "e3d307b1d354d70f6c809167eafecf5d622c3f27e3971ab7273410f429c7f83a", + "https://bcr.bazel.build/modules/lz4/1.9.4/source.json": "233f0bdfc21f254e3dda14683ddc487ca68c6a3a83b7d5db904c503f85bd089b", + "https://bcr.bazel.build/modules/mbedtls/3.6.0/MODULE.bazel": "8e380e4698107c5f8766264d4df92e36766248447858db28187151d884995a09", + "https://bcr.bazel.build/modules/mbedtls/3.6.0/source.json": "1dbe7eb5258050afcc3806b9d43050f71c6f539ce0175535c670df606790b30c", + "https://bcr.bazel.build/modules/nlohmann_json/3.11.3/MODULE.bazel": "87023db2f55fc3a9949c7b08dc711fae4d4be339a80a99d04453c4bb3998eefc", + "https://bcr.bazel.build/modules/nlohmann_json/3.12.0.bcr.1/MODULE.bazel": "a1c8bb07b5b91d971727c635f449d05623ac9608f6fe4f5f04254ea12f08e349", + "https://bcr.bazel.build/modules/nlohmann_json/3.12.0.bcr.1/source.json": "93f82a5ae985eb935c539bfee95e04767187818189241ac956f3ccadbdb8fb02", + "https://bcr.bazel.build/modules/nlohmann_json/3.6.1/MODULE.bazel": "6f7b417dcc794d9add9e556673ad25cb3ba835224290f4f848f8e2db1e1fca74", + "https://bcr.bazel.build/modules/opencensus-cpp/0.0.0-20230502-50eb5de/MODULE.bazel": "02201d2921dadb4ec90c4980eca4b2a02904eddcf6fa02f3da7594fb7b0d821c", + "https://bcr.bazel.build/modules/opencensus-cpp/0.0.0-20230502-50eb5de/source.json": "f50efc07822f5425bd1d3e40e977484f9c0142463052717d40ec85cd6744243e", + "https://bcr.bazel.build/modules/opencensus-proto/0.4.1/MODULE.bazel": "4a2e8b4d0b544002502474d611a5a183aa282251e14f6a01afe841c0c1b10372", + "https://bcr.bazel.build/modules/opencensus-proto/0.4.1/source.json": "a7d956700a85b833c43fc61455c0e111ab75bab40768ed17a206ee18a2bbe38f", + "https://bcr.bazel.build/modules/openssl/3.3.1.bcr.1/MODULE.bazel": "49c0c07e8fb87b480bccb842cfee1b32617f11dac590f732573c69058699a3d1", + "https://bcr.bazel.build/modules/openssl/3.3.1.bcr.1/source.json": "0c0872e048bbea052a9c541fb47019481a19201ba5555a71d762ad591bf94e1f", + "https://bcr.bazel.build/modules/opentelemetry-cpp/1.14.2/MODULE.bazel": "089a5613c2a159c7dfde098dabfc61e966889c7d6a81a98422a84c51535ed17d", + "https://bcr.bazel.build/modules/opentelemetry-cpp/1.16.0/MODULE.bazel": "b7379a140f538cea3f749179a2d481ed81942cc6f7b05a6113723eb34ac3b3e7", + "https://bcr.bazel.build/modules/opentelemetry-cpp/1.19.0/MODULE.bazel": "3455326c08b28415648a3d60d8e3c811847ebdbe64474f75b25878f25585aea1", + "https://bcr.bazel.build/modules/opentelemetry-cpp/1.19.0/source.json": "4e48137e4c3ecb99401ff99876df8fa330598d7da051869bec643446e8a8ff95", + "https://bcr.bazel.build/modules/opentelemetry-proto/1.1.0/MODULE.bazel": "a49f406e99bf05ab43ed4f5b3322fbd33adfd484b6546948929d1316299b68bf", + "https://bcr.bazel.build/modules/opentelemetry-proto/1.3.1/MODULE.bazel": "0141a50e989576ee064c11ce8dd5ec89993525bd9f9a09c5618e4dacc8df9352", + "https://bcr.bazel.build/modules/opentelemetry-proto/1.4.0.bcr.1/MODULE.bazel": "5ceaf25e11170d22eded4c8032728b4a3f273765fccda32f9e94f463755c4167", + "https://bcr.bazel.build/modules/opentelemetry-proto/1.5.0/MODULE.bazel": "7543d91a53b98e7b5b37c5a0865b93bff12c1ee022b1e322cd236b968894b030", + "https://bcr.bazel.build/modules/opentelemetry-proto/1.5.0/source.json": "046b721ce203e88cdaad44d7dd17a86b7200eab9388b663b234e72e13ff7b143", + "https://bcr.bazel.build/modules/opentracing-cpp/1.6.0/MODULE.bazel": "b3925269f63561b8b880ae7cf62ccf81f6ece55b62cd791eda9925147ae116ec", + "https://bcr.bazel.build/modules/opentracing-cpp/1.6.0/source.json": "da1cb1add160f5e5074b7272e9db6fd8f1b3336c15032cd0a653af9d2f484aed", + "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.3/source.json": "742075a428ad12a3fa18a69014c2f57f01af910c6d9d18646c990200853e641a", + "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.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/prometheus-cpp/1.2.4/MODULE.bazel": "0fbe5dcff66311947a3f6b86ebc6a6d9328e31a28413ca864debc4a043f371e5", + "https://bcr.bazel.build/modules/prometheus-cpp/1.3.0/MODULE.bazel": "ce82e086bbc0b60267e970f6a54b2ca6d0f22d3eb6633e00e2cc2899c700f3d8", + "https://bcr.bazel.build/modules/prometheus-cpp/1.3.0/source.json": "8cb66b4e535afc718e9d104a3db96ccb71a42ee816a100e50fd0d5ac843c0606", + "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/26.0.bcr.1/MODULE.bazel": "8f04d38c2da40a3715ff6bdce4d32c5981e6432557571482d43a62c31a24c2cf", + "https://bcr.bazel.build/modules/protobuf/26.0.bcr.2/MODULE.bazel": "62e0b84ca727bdeb55a6fe1ef180e6b191bbe548a58305ea1426c158067be534", + "https://bcr.bazel.build/modules/protobuf/26.0/MODULE.bazel": "8402da964092af40097f4a205eec2a33fd4a7748dc43632b7d1629bfd9a2b856", + "https://bcr.bazel.build/modules/protobuf/27.0-rc2/MODULE.bazel": "b2b0dbafd57b6bec0ca9b251da02e628c357dab53a097570aa7d79d020f107cf", + "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/28.3/MODULE.bazel": "2b3764bbab2e46703412bd3b859efcf0322638ed015e88432df3bb740507a1e9", + "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.1/MODULE.bazel": "557c3457560ff49e122ed76c0bc3397a64af9574691cb8201b4e46d4ab2ecb95", + "https://bcr.bazel.build/modules/protobuf/3.19.0/MODULE.bazel": "6b5fbb433f760a99a22b18b6850ed5784ef0e9928a72668b66e4d7ccd47db9b0", + "https://bcr.bazel.build/modules/protobuf/3.19.2/MODULE.bazel": "532ffe5f2186b69fdde039efe6df13ba726ff338c6bc82275ad433013fa10573", + "https://bcr.bazel.build/modules/protobuf/3.19.6/MODULE.bazel": "9233edc5e1f2ee276a60de3eaa47ac4132302ef9643238f23128fea53ea12858", + "https://bcr.bazel.build/modules/protobuf/30.0/MODULE.bazel": "0e736de5d52ad7824113f47e65256a26ee74b689ba859c5447a0663e5a075409", + "https://bcr.bazel.build/modules/protobuf/31.1/MODULE.bazel": "379a389bb330b7b8c1cdf331cc90bf3e13de5614799b3b52cdb7c6f389f6b38e", + "https://bcr.bazel.build/modules/protobuf/33.5/MODULE.bazel": "df58cd1c41c9d1257afa7f3110b23d970c107bf806b2e4d8c59a344d05504b0c", + "https://bcr.bazel.build/modules/protobuf/33.5/source.json": "fe53cb512afd722159c4c763f3fbbcc6ab850d45d1f389d8374f91c11e83bcd7", + "https://bcr.bazel.build/modules/protoc-gen-validate/1.0.4.bcr.2/MODULE.bazel": "c4bd2c850211ff5b7dadf9d2d0496c1c922fdedc303c775b01dfd3b3efc907ed", + "https://bcr.bazel.build/modules/protoc-gen-validate/1.0.4/MODULE.bazel": "b8913c154b16177990f6126d2d2477d187f9ddc568e95ee3e2d50fc65d2c494a", + "https://bcr.bazel.build/modules/protoc-gen-validate/1.2.1.bcr.1/MODULE.bazel": "4bf09676b62fa587ae07e073420a76ec8766dcce7545e5f8c68cfa8e484b5120", + "https://bcr.bazel.build/modules/protoc-gen-validate/1.2.1.bcr.1/source.json": "c19071ebc4b53b5f1cfab9c66eefaf6e4179eb8a998970d07b1077687e777f29", + "https://bcr.bazel.build/modules/pybind11_bazel/2.11.1/MODULE.bazel": "88af1c246226d87e65be78ed49ecd1e6f5e98648558c14ce99176da041dc378e", + "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/3.0.0/MODULE.bazel": "a2bfa6020ed603a00d944161c63173c7f109774e99bee0c2cd8dbf24159f8134", + "https://bcr.bazel.build/modules/pybind11_bazel/3.0.0/source.json": "d8f5104d4c21d272bf327ebe44366fb0b4c036cdaa1f5cceb21a408ca4ef2ef8", + "https://bcr.bazel.build/modules/rapidjson/1.1.0.bcr.20241007/MODULE.bazel": "82fbcb2e42f9e0040e76ccc74c06c3e46dfd33c64ca359293f8b84df0e6dff4c", + "https://bcr.bazel.build/modules/rapidjson/1.1.0.bcr.20241007/source.json": "5c42389ad0e21fc06b95ad7c0b730008271624a2fa3292e0eab5f30e15adeee3", + "https://bcr.bazel.build/modules/re2/2021-09-01/MODULE.bazel": "bcb6b96f3b071e6fe2d8bed9cc8ada137a105f9d2c5912e91d27528b3d123833", + "https://bcr.bazel.build/modules/re2/2023-09-01/MODULE.bazel": "cb3d511531b16cfc78a225a9e2136007a48cf8a677e4264baeab57fe78a80206", + "https://bcr.bazel.build/modules/re2/2024-05-01/MODULE.bazel": "55a3f059538f381107824e7d00df5df6d061ba1fb80e874e4909c0f0549e8f3e", + "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-11-05.bcr.1/MODULE.bazel": "3d9d4995833fc0334fc5c88b56a05288dd25d651544cd7b2233bbd6357bbeba0", + "https://bcr.bazel.build/modules/re2/2025-11-05.bcr.1/source.json": "7df1394aabda1c9bc188a302f5d54b1c657924edd04ebc57d2be29dbd7efd141", + "https://bcr.bazel.build/modules/riegeli/0.0.0-20250822-9f2744d/MODULE.bazel": "fe86a600f793402a4f5e838636a449b5cbf91289b3af5f3174f7d4fea9d4e784", + "https://bcr.bazel.build/modules/riegeli/0.0.0-20250822-9f2744d/source.json": "edc86dab694fb7c98b42145bc41a0e230107cc4f293e43149c35fd452d50daa7", + "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/rules_apple/3.13.0/MODULE.bazel": "b4559a2c6281ca3165275bb36c1f0ac74666632adc5bdb680e366de7ce845f43", + "https://bcr.bazel.build/modules/rules_apple/3.16.0/MODULE.bazel": "0d1caf0b8375942ce98ea944be754a18874041e4e0459401d925577624d3a54a", + "https://bcr.bazel.build/modules/rules_apple/3.16.0/source.json": "d8b5fe461272018cc07cfafce11fe369c7525330804c37eec5a82f84cd475366", + "https://bcr.bazel.build/modules/rules_apple/3.5.1/MODULE.bazel": "3d1bbf65ad3692003d36d8a29eff54d4e5c1c5f4bfb60f79e28646a924d9101c", + "https://bcr.bazel.build/modules/rules_buf/0.1.1/MODULE.bazel": "6189aec18a4f7caff599ad41b851ab7645d4f1e114aa6431acf9b0666eb92162", + "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.5/MODULE.bazel": "be41f87587998fe8890cd82ea4e848ed8eb799e053c224f78f3ff7fe1a1d9b74", + "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.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.2.0/MODULE.bazel": "b5c17f90458caae90d2ccd114c81970062946f49f355610ed89bebf954f5783c", + "https://bcr.bazel.build/modules/rules_cc/0.2.14/MODULE.bazel": "353c99ed148887ee89c54a17d4100ae7e7e436593d104b668476019023b58df8", + "https://bcr.bazel.build/modules/rules_cc/0.2.17/MODULE.bazel": "1849602c86cb60da8613d2de887f9566a6d354a6df6d7009f9d04a14402f9a84", + "https://bcr.bazel.build/modules/rules_cc/0.2.18/MODULE.bazel": "4460ec36adc8f722a6a2a4ac9374cb91f2acebadaa93fc37966129afb3dece87", + "https://bcr.bazel.build/modules/rules_cc/0.2.18/source.json": "abad668ff2fd63ada1ac49bf386d37e27048b89a3465a6fd968bb832b00a09d3", + "https://bcr.bazel.build/modules/rules_cc/0.2.4/MODULE.bazel": "1ff1223dfd24f3ecf8f028446d4a27608aa43c3f41e346d22838a4223980b8cc", + "https://bcr.bazel.build/modules/rules_cc/0.2.8/MODULE.bazel": "f1df20f0bf22c28192a794f29b501ee2018fa37a3862a1a2132ae2940a23a642", + "https://bcr.bazel.build/modules/rules_cc/0.2.9/MODULE.bazel": "34263f1dca62ea664265438cef714d7db124c03e1ed55ebb4f1dc860164308d1", + "https://bcr.bazel.build/modules/rules_foreign_cc/0.15.1/MODULE.bazel": "c2c60d26c79fda484acb95cdbec46e89d6b28b4845cb277160ce1e0c8622bb88", + "https://bcr.bazel.build/modules/rules_foreign_cc/0.15.1/source.json": "a161811a63ba8a859086da3b7ff3ad04f2e9c255d7727b41087103fc0eb22f55", + "https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/MODULE.bazel": "40c97d1144356f52905566c55811f13b299453a14ac7769dfba2ac38192337a8", + "https://bcr.bazel.build/modules/rules_go/0.33.0/MODULE.bazel": "a2b11b64cd24bf94f57454f53288a5dacfe6cb86453eee7761b7637728c1910c", + "https://bcr.bazel.build/modules/rules_go/0.38.1/MODULE.bazel": "fb8e73dd3b6fc4ff9d260ceacd830114891d49904f5bda1c16bc147bcc254f71", + "https://bcr.bazel.build/modules/rules_go/0.39.1/MODULE.bazel": "d34fb2a249403a5f4339c754f1e63dc9e5ad70b47c5e97faee1441fc6636cd61", + "https://bcr.bazel.build/modules/rules_go/0.41.0/MODULE.bazel": "55861d8e8bb0e62cbd2896f60ff303f62ffcb0eddb74ecb0e5c0cbe36fc292c8", + "https://bcr.bazel.build/modules/rules_go/0.42.0/MODULE.bazel": "8cfa875b9aa8c6fce2b2e5925e73c1388173ea3c32a0db4d2b4804b453c14270", + "https://bcr.bazel.build/modules/rules_go/0.45.1/MODULE.bazel": "6d7884f0edf890024eba8ab31a621faa98714df0ec9d512389519f0edff0281a", + "https://bcr.bazel.build/modules/rules_go/0.46.0/MODULE.bazel": "3477df8bdcc49e698b9d25f734c4f3a9f5931ff34ee48a2c662be168f5f2d3fd", + "https://bcr.bazel.build/modules/rules_go/0.48.0/MODULE.bazel": "d00ebcae0908ee3f5e6d53f68677a303d6d59a77beef879598700049c3980a03", + "https://bcr.bazel.build/modules/rules_go/0.50.1/MODULE.bazel": "b91a308dc5782bb0a8021ad4330c81fea5bda77f96b9e4c117b9b9c8f6665ee0", + "https://bcr.bazel.build/modules/rules_go/0.50.1/source.json": "205765fd30216c70321f84c9a967267684bdc74350af3f3c46c857d9f80a4fa2", + "https://bcr.bazel.build/modules/rules_java/4.0.0/MODULE.bazel": "5a78a7ae82cd1a33cef56dc578c7d2a46ed0dca12643ee45edbb8417899e6f74", + "https://bcr.bazel.build/modules/rules_java/5.1.0/MODULE.bazel": "324b6478b0343a3ce7a9add8586ad75d24076d6d43d2f622990b9c1cfd8a1b15", + "https://bcr.bazel.build/modules/rules_java/5.3.5/MODULE.bazel": "a4ec4f2db570171e3e5eb753276ee4b389bae16b96207e9d3230895c99644b86", + "https://bcr.bazel.build/modules/rules_java/5.5.0/MODULE.bazel": "486ad1aa15cdc881af632b4b1448b0136c76025a1fe1ad1b65c5899376b83a50", + "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.14.0/MODULE.bazel": "717717ed40cc69994596a45aec6ea78135ea434b8402fb91b009b9151dd65615", + "https://bcr.bazel.build/modules/rules_java/8.14.0/source.json": "8a88c4ca9e8759da53cddc88123880565c520503321e2566b4e33d0287a3d4bc", + "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.1/MODULE.bazel": "f4808e2ab5b0197f094cabce9f4b006a27766beb6a9975931da07099560ca9c2", + "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.0/MODULE.bazel": "37c93a5a78d32e895d52f86a8d0416176e915daabd029ccb5594db422e87c495", + "https://bcr.bazel.build/modules/rules_jvm_external/6.1/MODULE.bazel": "75b5fec090dbd46cf9b7d8ea08cf84a0472d92ba3585b476f44c326eda8059c4", + "https://bcr.bazel.build/modules/rules_jvm_external/6.3/MODULE.bazel": "c998e060b85f71e00de5ec552019347c8bca255062c990ac02d051bb80a38df0", + "https://bcr.bazel.build/modules/rules_jvm_external/6.7/MODULE.bazel": "e717beabc4d091ecb2c803c2d341b88590e9116b8bf7947915eeb33aab4f96dd", + "https://bcr.bazel.build/modules/rules_jvm_external/6.7/source.json": "5426f412d0a7fc6b611643376c7e4a82dec991491b9ce5cb1cfdd25fe2e92be4", + "https://bcr.bazel.build/modules/rules_kotlin/1.9.0/MODULE.bazel": "ef85697305025e5a61f395d4eaede272a5393cee479ace6686dba707de804d59", + "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_license/0.0.3/MODULE.bazel": "627e9ab0247f7d1e05736b59dbb1b6871373de5ad31c3011880b4133cafd4bd0", + "https://bcr.bazel.build/modules/rules_license/0.0.7/MODULE.bazel": "088fbeb0b6a419005b89cf93fe62d9517c0a2b8bb56af3244af65ecfe37e7d5d", + "https://bcr.bazel.build/modules/rules_license/0.0.8/MODULE.bazel": "5669c6fe49b5134dbf534db681ad3d67a2d49cfc197e4a95f1ca2fd7f3aebe96", + "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_nodejs/5.8.2/MODULE.bazel": "6bc03c8f37f69401b888023bf511cb6ee4781433b0cb56236b2e55a21e3a026a", + "https://bcr.bazel.build/modules/rules_nodejs/6.2.0/MODULE.bazel": "ec27907f55eb34705adb4e8257952162a2d4c3ed0f0b3b4c3c1aad1fac7be35e", + "https://bcr.bazel.build/modules/rules_nodejs/6.3.0/MODULE.bazel": "45345e4aba35dd6e4701c1eebf5a4e67af4ed708def9ebcdc6027585b34ee52d", + "https://bcr.bazel.build/modules/rules_nodejs/6.3.3/MODULE.bazel": "b66eadebd10f1f1b25f52f95ab5213a57e82c37c3f656fcd9a57ad04d2264ce7", + "https://bcr.bazel.build/modules/rules_nodejs/6.3.3/source.json": "45bd343155bdfed2543f0e39b80ff3f6840efc31975da4b5795797f4c94147ad", + "https://bcr.bazel.build/modules/rules_perl/0.2.4/MODULE.bazel": "5f5af7be4bf5fb88d91af7469518f0fd2161718aefc606188f7cd51f436ca938", + "https://bcr.bazel.build/modules/rules_perl/0.2.4/source.json": "574317d6b3c7e4843fe611b76f15e62a1889949f5570702e1ee4ad335ea3c339", + "https://bcr.bazel.build/modules/rules_pkg/0.7.0/MODULE.bazel": "df99f03fc7934a4737122518bb87e667e62d780b610910f0447665a7e2be62dc", + "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.0/MODULE.bazel": "b531d7f09f58dce456cd61b4579ce8c86b38544da75184eadaf0a7cb7966453f", + "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.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.22.0/MODULE.bazel": "b8057bafa11a9e0f4b08fc3b7cd7bee0dcbccea209ac6fc9a3ff051cd03e19e9", + "https://bcr.bazel.build/modules/rules_python/0.22.1/MODULE.bazel": "26114f0c0b5e93018c0c066d6673f1a2c3737c7e90af95eff30cfee38d0bbac7", + "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.29.0/MODULE.bazel": "2ac8cd70524b4b9ec49a0b8284c79e4cd86199296f82f6e0d5da3f783d660c82", + "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.36.0/MODULE.bazel": "a4ce1ccea92b9106c7d16ab9ee51c6183107e78ba4a37aa65055227b80cd480c", + "https://bcr.bazel.build/modules/rules_python/0.37.1/MODULE.bazel": "3faeb2d9fa0a81f8980643ee33f212308f4d93eea4b9ce6f36d0b742e71e9500", + "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/1.0.0/MODULE.bazel": "898a3d999c22caa585eb062b600f88654bf92efb204fa346fb55f6f8edffca43", + "https://bcr.bazel.build/modules/rules_python/1.2.0/MODULE.bazel": "5aeeb48b2a6c19d668b48adf2b8a2b209a6310c230db0ce77450f148a89846e4", + "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.6.3/MODULE.bazel": "a7b80c42cb3de5ee2a5fa1abc119684593704fcd2fec83165ebe615dec76574f", + "https://bcr.bazel.build/modules/rules_python/1.6.3/source.json": "f0be74977e5604a6526c8a416cda22985093ff7d5d380d41722d7e44015cc419", + "https://bcr.bazel.build/modules/rules_rust/0.45.1/MODULE.bazel": "a69d0db3a958fab2c6520961e1b2287afcc8b36690fd31bbc4f6f7391397150d", + "https://bcr.bazel.build/modules/rules_rust/0.51.0/MODULE.bazel": "2b6d1617ac8503bfdcc0e4520c20539d4bba3a691100bee01afe193ceb0310f9", + "https://bcr.bazel.build/modules/rules_rust/0.71.3/MODULE.bazel": "e2390c96f77d65f00c769bf665678c5424188e9c777239cfaae2a8d2dde7b981", + "https://bcr.bazel.build/modules/rules_rust/0.71.3/source.json": "5eb5d8068571725bc893045f8137ed7937988f23d73c53ea443470e8047598ad", + "https://bcr.bazel.build/modules/rules_shell/0.2.0/MODULE.bazel": "fda8a652ab3c7d8fee214de05e7a9916d8b28082234e8d2c0094505c5268ed3c", + "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/1.18.0/MODULE.bazel": "a6aba73625d0dc64c7b4a1e831549b6e375fbddb9d2dde9d80c9de6ec45b24c9", + "https://bcr.bazel.build/modules/rules_swift/2.1.1/MODULE.bazel": "494900a80f944fc7aa61500c2073d9729dff0b764f0e89b824eb746959bc1046", + "https://bcr.bazel.build/modules/rules_swift/2.1.1/source.json": "40fc69dfaac64deddbb75bd99cdac55f4427d9ca0afbe408576a65428427a186", + "https://bcr.bazel.build/modules/snappy/1.2.0/MODULE.bazel": "cc7a727b46089c7fdae0ede21b1fd65bdb14d01823da118ef5c48044f40b6b27", + "https://bcr.bazel.build/modules/snappy/1.2.0/source.json": "17f5527e15d30a9d9eebf79ed73b280b56cac44f8c8fea696666d99943f84c33", + "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.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.1/source.json": "32bd87e5f4d7acc57c5b2ff7c325ae3061d5e242c0c4c214ae87e0f1c13e54cb", + "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-20211020-160625a/MODULE.bazel": "6cced416be2dc5b9c05efd5b997049ba795e5e4e6fafbe1624f4587767638928", + "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/upb/0.0.0-20230907-e7430e6/MODULE.bazel": "3a7dedadf70346e678dc059dbe44d05cbf3ab17f1ce43a1c7a42edc7cbf93fd9", + "https://bcr.bazel.build/modules/xds/0.0.0-20240423-555b57e/MODULE.bazel": "cea509976a77e34131411684ef05a1d6ad194dd71a8d5816643bc5b0af16dc0f", + "https://bcr.bazel.build/modules/xds/0.0.0-20240423-555b57e/source.json": "7227e1fcad55f3f3cab1a08691ecd753cb29cc6380a47bc650851be9f9ad6d20", + "https://bcr.bazel.build/modules/xz/5.4.5.bcr.1/MODULE.bazel": "c037f75fa1b7e1ff15fbd15d807a8ce545e9b02f02df0a9777aa9aa7d8b268bb", + "https://bcr.bazel.build/modules/xz/5.4.5.bcr.1/source.json": "766f28499a16fa9ed8dc94382d50e80ceda0d0ab80b79b7b104a67074ab10e1f", + "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.2.12/MODULE.bazel": "3b1a8834ada2a883674be8cbd36ede1b6ec481477ada359cd2d3ddc562340b27", + "https://bcr.bazel.build/modules/zlib/1.2.13/MODULE.bazel": "aa6deb1b83c18ffecd940c4119aff9567cd0a671d7bba756741cb2ef043a29d5", + "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.1/MODULE.bazel": "6a9fe6e3fc865715a7be9823ce694ceb01e364c35f7a846bf0d2b34762bc066b", + "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.3/MODULE.bazel": "af322bc08976524477c79d1e45e241b6efbeb918c497e8840b8ab116802dda79", + "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/MODULE.bazel": "eec517b5bbe5492629466e11dae908d043364302283de25581e3eb944326c4ca", + "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.6/MODULE.bazel": "e937cf0a3772f93ad91f3c7af4f330b76a878bbfee06527ca1a9673b790eb896", + "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.6/source.json": "5f397158198f338129c865a4c3ae21bc5626a9664b3c3b40fa3b3c2ec1ff83bf", + "https://bcr.bazel.build/modules/zlib/1.3.1/MODULE.bazel": "751c9940dcfe869f5f7274e1295422a34623555916eb98c174c1e945594bf198", + "https://bcr.bazel.build/modules/zlib/1.3/MODULE.bazel": "6a9c02f19a24dcedb05572b2381446e27c272cd383aed11d41d99da9e3167a72", + "https://bcr.bazel.build/modules/zstd/1.5.6/MODULE.bazel": "471ebe7d3cdd8c6469390fcf623eb4779ff55fbee0a87f1dc57a1def468b96d4", + "https://bcr.bazel.build/modules/zstd/1.5.6/source.json": "02010c3333fc89b44fe861db049968decb6e688411f7f9d4f6791d74f9adfb51" + }, + "selectedYankedVersions": {}, + "moduleExtensions": {}, + "facts": {} +} diff --git a/centipede/BUILD b/centipede/BUILD index 3ad6faeea..125919227 100644 --- a/centipede/BUILD +++ b/centipede/BUILD @@ -1003,8 +1003,6 @@ cc_library( name = "engine_worker", srcs = [ "engine_worker.cc", - "runner_utils.cc", - "runner_utils.h", ], hdrs = ["engine_worker_abi.h"], deps = [ @@ -1013,6 +1011,7 @@ cc_library( ":feature", ":runner_request", ":runner_result", + ":runner_utils", ":shared_memory_blob_sequence", "@abseil-cpp//absl/base:nullability", "@com_google_fuzztest//common:defs", @@ -1240,8 +1239,6 @@ cc_library( "reverse_pc_table.h", "runner_dl_info.cc", "runner_dl_info.h", - "runner_utils.cc", - "runner_utils.h", "sancov_callbacks.cc", "sancov_interceptors.cc", "sancov_object_array.cc", @@ -1263,6 +1260,7 @@ cc_library( ":foreach_nonzero", ":int_utils", ":runner_cmp_trace", + ":runner_utils", "@abseil-cpp//absl/base:core_headers", "@abseil-cpp//absl/base:nullability", "@abseil-cpp//absl/numeric:bits", @@ -2026,3 +2024,23 @@ sh_test( ":test_util_sh", ], ) + +cc_library( + name = "runner_utils", + srcs = ["runner_utils.cc"], + hdrs = ["runner_utils.h"], + copts = DISABLE_SANCOV_COPTS, + deps = [ + "@abseil-cpp//absl/base:nullability", + ], +) + +cc_static_library( + name = "centipede_engine_static", + deps = [ + ":engine_controller_with_subprocess", + ":engine_worker", + ":sancov_runtime", + ":weak_sancov_stubs", + ], +) diff --git a/rust/.rustfmt.toml b/rust/.rustfmt.toml new file mode 100644 index 000000000..33b356d5f --- /dev/null +++ b/rust/.rustfmt.toml @@ -0,0 +1,3 @@ +use_field_init_shorthand = true +use_try_shorthand = true +edition = "2021" diff --git a/rust/BUILD b/rust/BUILD new file mode 100644 index 000000000..d8e3a1304 --- /dev/null +++ b/rust/BUILD @@ -0,0 +1,44 @@ +load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test") + +licenses(["notice"]) + +exports_files(["BUILD"]) + +rust_library( + name = "fuzztest", + srcs = glob([ + "src/**/*.rs", + ]), + edition = "2024", + proc_macro_deps = [ + "@com_google_fuzztest//rust/fuzztest_macro:fuzztest_macro", + ], + rustc_flags = ["-Zallow-features=cfg_sanitize"], + visibility = ["__subpackages__"], + deps = [ + "@crate_index//:anyhow", # v1 + "@crate_index//:clap", # v4 + "@crate_index//:humantime", # v2 + "@crate_index//:inventory", # v0_3 + "@crate_index//:num-traits", + "@crate_index//:postcard", # v1 + "@crate_index//:rand", # v0_10 + "@crate_index//:serde", # v1 + "@crate_index//:spin", # v0_10 + "@crate_index//:tempfile", # v3 + "@com_google_fuzztest//rust/coverage", + "@com_google_fuzztest//rust/engine", + ], +) + +rust_test( + name = "fuzztest_test", + # Avoid interference when setting/resetting environment variables in tests. + args = ["--test-threads=1"], + crate = ":fuzztest", + edition = "2024", + rustc_flags = ["-Zallow-features=cfg_sanitize"], + deps = [ + "@crate_index//:googletest", + ], +) diff --git a/rust/Cargo.toml b/rust/Cargo.toml new file mode 100644 index 000000000..31ce402d4 --- /dev/null +++ b/rust/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "fuzztest" +version = "0.1.0" +edition = "2024" + +[[test]] +name = "trybuild" +path = "tests/trybuild.rs" + +[dependencies] +anyhow.workspace = true +clap.workspace = true +coverage = { path = "coverage" } +engine = { path = "engine", package = "engine-ffi" } +fuzztest-macro = { path = "fuzztest_macro" } +humantime.workspace = true +inventory.workspace = true +num-traits.workspace = true +postcard.workspace = true +rand.workspace = true +serde.workspace = true +spin.workspace = true +tempfile.workspace = true + +[dev-dependencies] +googletest = "0.14.3" + +[dev-dependencies.trybuild] +version = "1.0.103" +features = ["diff"] + +[profile.fuzztest] +inherits = 'release' +panic = 'abort' +opt-level = "s" +debug = true +split-debuginfo = "packed" diff --git a/rust/README.md b/rust/README.md new file mode 100644 index 000000000..482c203a6 --- /dev/null +++ b/rust/README.md @@ -0,0 +1,63 @@ +# FuzzTest Rust + +A framework for fuzzing Rust projects using Google FuzzTest. + +## Prerequisites + +1. Clone the repository. +2. Build the C++ Centipede static library engine using Bazel (from the + repository root): + + ```bash + cd /path/to/fuzztest + bazel build //centipede:centipede_engine_static + ``` + +3. Copy the libcentipede_engine_static.a library to another location: + + ```bash + mkdir -p $HOME/.local/lib + cp /path/to/fuzztest/bazel-bin/centipede/libcentipede_engine_static.a \ + $HOME/.local/lib/ + ``` + +## Setup a Project + +1. Create a new Rust project: + + ```bash + cargo new my_fuzz_project --bin + ``` + +2. Add `fuzztest` as a dependency in your `Cargo.toml`: + + ```toml + [dependencies] + fuzztest = { path = "/path/to/fuzztest/rust" } + googletest = "0.14.3" + ``` + +## Write a Fuzz Test + +In `src/main.rs`: + +```rust +#[cfg(test)] +mod tests { + use fuzztest::domains::arbitrary::Arbitrary; + use fuzztest::fuzztest; + + #[fuzztest(a = Arbitrary::::default(), b = Arbitrary::::default())] + fn test_addition(a: i32, b: i32) { + let _ = a.wrapping_add(b); + } +} +``` + +## Build and Run Fuzz Tests + +To run the fuzz tests, you must specify: + +```bash +RUSTFLAGS="-L $HOME/.local/lib" cargo test __fuzztest_mod__::test_addition +``` diff --git a/rust/coverage/BUILD b/rust/coverage/BUILD new file mode 100644 index 000000000..4cf89ce24 --- /dev/null +++ b/rust/coverage/BUILD @@ -0,0 +1,36 @@ +load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test") + +licenses(["notice"]) + +rust_library( + name = "coverage", + srcs = [ + "src/coverage.rs", + "src/lib.rs", + ], + deps = [ + "@com_google_fuzztest//centipede:sancov_runtime", + ], + edition = "2024", + visibility = ["@com_google_fuzztest//rust:__subpackages__"], +) + +rust_test( + name = "coverage_test", + # Avoid interference on coverage collection between threads. + args = ["--test-threads=1"], + crate = ":coverage", + edition = "2024", + env = { + "CENTIPEDE_RUNNER_FLAGS": ":use_cmp_features:", + }, + # TODO: b/437896409 - Replace with global config once it's available. + rustc_flags = [ + "-Cpasses=sancov-module", + "-Cllvm-args=-sanitizer-coverage-level=1", + "-Cllvm-args=-sanitizer-coverage-trace-compares", + ], + deps = [ + "@crate_index//:googletest", + ], +) diff --git a/rust/coverage/Cargo.toml b/rust/coverage/Cargo.toml new file mode 100644 index 000000000..550a6c2f0 --- /dev/null +++ b/rust/coverage/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "coverage" +version = "0.1.0" +edition = "2024" + +[lib] +test = false + +[dependencies] diff --git a/rust/coverage/src/coverage.rs b/rust/coverage/src/coverage.rs new file mode 100644 index 000000000..258987e6a --- /dev/null +++ b/rust/coverage/src/coverage.rs @@ -0,0 +1,78 @@ +use std::slice; + +#[repr(C)] +pub struct SanCovRuntimeRawFeatureParts { + // Safety invariant: The only way to receive this type is through `SanCovRuntimeGetCoverage()` + // which guarantees that `ptr` always points to a valid slice of `size` elements. + ptr: *const u64, + size: usize, +} + +impl SanCovRuntimeRawFeatureParts { + /// # Safety + /// + /// Must not be held on when its data has to be mutated. For example, do not hold on to it + /// when calling `SanCovRuntimeGetCoverage()`. + pub unsafe fn as_slice(&self) -> &[u64] { + // Safety: self ensures that `ptr` always points to a valid slice of `size` elements. + unsafe { slice::from_raw_parts(self.ptr, self.size) } + } +} + +unsafe extern "C" { + pub safe fn SanCovRuntimeClearCoverage(full_clear: bool); + + pub safe fn SanCovRuntimeGetCoverage(reject_input: bool) -> SanCovRuntimeRawFeatureParts; + + pub safe fn SanCovRuntimePostProcessCoverage(reject_input: bool); + + // Exposed only for testing purposes. + pub safe fn __sanitizer_cov_trace_const_cmp1(Arg1: u8, Arg2: u8); +} + +#[cfg(test)] +mod test { + use super::*; + use googletest::prelude::*; + + #[gtest] + fn should_collect_features_from_sancov_callback() { + SanCovRuntimeClearCoverage(true); + + __sanitizer_cov_trace_const_cmp1(1, 1); + + let features = SanCovRuntimeGetCoverage(false); + expect_false!(features.ptr.is_null()); + expect_gt!(features.size, 0); + } + + #[gtest] + fn should_not_collect_features_from_empty_user_code() { + SanCovRuntimeClearCoverage(true); + + // An execution where sancov hooks such as + // `__sanitizer_cov_trace_const_cmp1` are not called. + + let features = SanCovRuntimeGetCoverage(false); + expect_false!(features.ptr.is_null()); + expect_eq!(features.size, 0); + } + + #[gtest] + fn should_collect_features_from_user_code() { + let x = std::env::args().count(); + + SanCovRuntimeClearCoverage(true); + + { + // An LLVM trace cmp callback is expected to be instrumented here. + if x == 2 { + std::hint::black_box(2); // To prevent dead code elimination. + } + } + + let features = SanCovRuntimeGetCoverage(false); + expect_false!(features.ptr.is_null()); + expect_gt!(features.size, 0); + } +} diff --git a/rust/coverage/src/lib.rs b/rust/coverage/src/lib.rs new file mode 100644 index 000000000..16eca13b3 --- /dev/null +++ b/rust/coverage/src/lib.rs @@ -0,0 +1,15 @@ +mod coverage; + +pub use coverage::SanCovRuntimeRawFeatureParts; + +pub fn prepare_coverage(full_clear: bool) { + coverage::SanCovRuntimeClearCoverage(full_clear) +} + +pub fn get_coverage(reject_input: bool) -> SanCovRuntimeRawFeatureParts { + coverage::SanCovRuntimeGetCoverage(reject_input) +} + +pub fn post_process_coverage(reject_input: bool) { + coverage::SanCovRuntimePostProcessCoverage(reject_input) +} diff --git a/rust/e2e_tests/BUILD b/rust/e2e_tests/BUILD new file mode 100644 index 000000000..994c2e912 --- /dev/null +++ b/rust/e2e_tests/BUILD @@ -0,0 +1,99 @@ +load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test") + +licenses(["notice"]) + +rust_test( + name = "worker_test", + srcs = ["worker_test.rs"], + data = [ + "@com_google_fuzztest//rust/e2e_tests/testdata:fuzztest_main", + ], + edition = "2024", + deps = [ + ":test_utils", + "@crate_index//:googletest", + ], +) + +rust_test( + name = "standalone_mode_test", + srcs = ["standalone_mode_test.rs"], + data = [ + "@com_google_fuzztest//centipede:centipede_uninstrumented", + "@com_google_fuzztest//rust/e2e_tests/testdata:fuzztest_main", + "@com_google_fuzztest//rust/e2e_tests/testdata:standalone_fuzz_tests_bin", + ], + edition = "2024", + deps = [ + ":test_utils", + "@crate_index//:googletest", + ], +) + +rust_test( + name = "worker_with_centipede_test", + srcs = ["worker_with_centipede_test.rs"], + data = [ + "@com_google_fuzztest//centipede:centipede_uninstrumented", + "@com_google_fuzztest//rust/e2e_tests/testdata:fuzztest_main", + ], + edition = "2024", + deps = [ + ":test_utils", + "@crate_index//:googletest", + "@crate_index//:rand", # v0_10 + ], +) + +rust_test( + name = "worker_with_centipede_sanitizer_test", + srcs = ["worker_with_centipede_sanitizer_test.rs"], + data = [ + "@com_google_fuzztest//centipede:centipede_uninstrumented", + "@com_google_fuzztest//rust/e2e_tests/testdata:fuzztest_main", + ], + edition = "2024", + rustc_flags = ["-Zallow-features=cfg_sanitize"], + deps = [ + ":test_utils", + "@crate_index//:googletest", + ], +) + +rust_library( + name = "test_utils", + testonly = 1, + srcs = ["test_utils.rs"], + edition = "2024", + deps = [ + "@crate_index//:googletest", + ], +) + +rust_test( + name = "gtest_registration_test", + srcs = ["gtest_registration_test.rs"], + data = [ + "@com_google_fuzztest//rust/e2e_tests/testdata:fuzz_tests_as_gtests", + ], + edition = "2024", + deps = [ + ":test_utils", + "@crate_index//:googletest", + ], +) + +rust_test( + name = "replay_test", + srcs = ["replay_test.rs"], + data = [ + "@com_google_fuzztest//centipede:centipede_uninstrumented", + "@com_google_fuzztest//rust/e2e_tests/testdata:replay_fuzz_tests_bin", + ], + edition = "2024", + deps = [ + ":test_utils", + "@crate_index//:googletest", + "@crate_index//:postcard", # v1 + ], +) diff --git a/rust/e2e_tests/gtest_registration_test.rs b/rust/e2e_tests/gtest_registration_test.rs new file mode 100644 index 000000000..59e0b61aa --- /dev/null +++ b/rust/e2e_tests/gtest_registration_test.rs @@ -0,0 +1,46 @@ +use googletest::matchers; +use googletest::prelude::*; +use std::env; +use std::path::PathBuf; +use std::process::Command; + +#[gtest] +fn test_fuzztests_are_registered_as_gtests() { + let src_dir_path = PathBuf::from(env::var("TEST_SRCDIR").unwrap()); + let target_binary_path = src_dir_path + .join("_main/rust/e2e_tests/testdata/fuzz_tests_as_gtests"); + + // Run the test binary with `--list` to list all registered tests. + let output = Command::new(&target_binary_path) + .arg("--list") + .output() + .expect("Failed to execute test binary"); + + assert!(output.status.success(), "Test binary failed to execute with --list"); + + let stdout = String::from_utf8_lossy(&output.stdout); + + // Verify that the expected fuzz tests are listed as regular tests. + expect_that!(stdout, matchers::contains_substring("bool_fuzz_test: test")); + expect_that!(stdout, matchers::contains_substring("multi_arg_fuzz_test: test")); +} + +#[gtest] +fn test_fuzztest_property_function_executes_in_gtest() { + let src_dir_path = PathBuf::from(env::var("TEST_SRCDIR").unwrap()); + let target_binary_path = src_dir_path + .join("_main/rust/e2e_tests/testdata/fuzz_tests_as_gtests"); + + // Run a fuzz test + let output = Command::new(&target_binary_path) + .arg("bool_fuzz_test") + .arg("--nocapture") + .output() + .expect("Failed to execute test binary"); + + assert!(output.status.success(), "Test binary failed to execute `bool_fuzz_test`"); + + let stdout = String::from_utf8_lossy(&output.stdout); + + expect_that!(stdout, matchers::contains_substring("bool_fuzz_test property function ran...")); +} diff --git a/rust/e2e_tests/replay_test.rs b/rust/e2e_tests/replay_test.rs new file mode 100644 index 000000000..f66804767 --- /dev/null +++ b/rust/e2e_tests/replay_test.rs @@ -0,0 +1,112 @@ +use googletest::matchers; +use googletest::prelude::*; +use std::process::Command; +use test_utils::EnvVars; + +#[gtest] +fn replay_by_id_reproduces_panic(fixture: &EnvVars) { + let test_name = "__fuzztest_mod__find_bug_fuzz_test::find_bug_fuzz_test"; + let normalized_test_name = test_name.replace("::", "."); + let crash_id = "my_custom_crash_id"; + + let target_binary_path = fixture + .target_binary_path + .parent() + .expect("target_binary_path must have a parent") + .join("replay_fuzz_tests_bin"); + + // use a relative path for the target-binary + let relative_target_binary = + target_binary_path.strip_prefix("/").unwrap_or(&target_binary_path); + + let db_dir = fixture.tmp_dir_path.join("corpus_db"); + let crash_file_dir = + db_dir.join(relative_target_binary).join(&normalized_test_name).join("crashing"); + + // Write the crashing input to the database + std::fs::create_dir_all(&crash_file_dir).expect("Failed to create corpus_db directory"); + let crashing_value: i32 = 10; + let serialized_input = + postcard::to_stdvec(&crashing_value).expect("Failed to serialize value using postcard"); + std::fs::write(crash_file_dir.join(crash_id), &serialized_input) + .expect("Failed to write crash file to db"); + + let process = Command::new(&target_binary_path) + .arg(test_name) + .arg("--nocapture") + .env("FUZZTEST_REPLAY_ID", crash_id) + .env("FUZZTEST_CORPUS_DB", &db_dir) + .env("FUZZTEST_CENTIPEDE_BINARY_PATH", &fixture.centipede_path) // Needed for FFI export + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .expect("Failed to spawn binary"); + + let output = process.wait_with_output().expect("Should terminate"); + let stderr = String::from_utf8_lossy(&output.stderr); + + expect_that!(output.status.success(), eq(false)); + expect_that!(stderr, matchers::contains_substring("panicked at")); + expect_that!(stderr, matchers::contains_substring("Bug found!")); + expect_that!(stderr, matchers::contains_substring("FuzzTest controller reported failure")); +} + +#[gtest] +fn replay_all_reproduces_all_failures(fixture: &EnvVars) { + let test_name = "__fuzztest_mod__find_bug_fuzz_test::find_bug_fuzz_test"; + let normalized_test_name = test_name.replace("::", "."); + + let target_binary_path = fixture + .target_binary_path + .parent() + .expect("target_binary_path must have a parent") + .join("replay_fuzz_tests_bin"); + + // use a relative path for the target-binary + let relative_target_binary = + target_binary_path.strip_prefix("/").unwrap_or(&target_binary_path); + + let db_dir = fixture.tmp_dir_path.join("corpus_db_all"); + let crash_file_dir = + db_dir.join(relative_target_binary).join(&normalized_test_name).join("crashing"); + + // Write multiple crashing inputs to the database + std::fs::create_dir_all(&crash_file_dir).expect("Failed to create corpus_db directory"); + + let crashing_value: i32 = 10; + let serialized_input = + postcard::to_stdvec(&crashing_value).expect("Failed to serialize value using postcard"); + std::fs::write(crash_file_dir.join("crash_1"), &serialized_input) + .expect("Failed to write crash_1 file to db"); + + let crashing_value: i32 = 20; + let serialized_input = + postcard::to_stdvec(&crashing_value).expect("Failed to serialize value using postcard"); + std::fs::write(crash_file_dir.join("crash_2"), &serialized_input) + .expect("Failed to write crash_2 file to db"); + + let crashing_value: i32 = 30; + let serialized_input = + postcard::to_stdvec(&crashing_value).expect("Failed to serialize value using postcard"); + std::fs::write(crash_file_dir.join("crash_3"), &serialized_input) + .expect("Failed to write crash_3 file to db"); + + let process = std::process::Command::new(&target_binary_path) + .arg(test_name) + .env("FUZZTEST_REPLAY_FINDINGS", "true") + .env("FUZZTEST_CORPUS_DB", &db_dir) + .env("FUZZTEST_CENTIPEDE_BINARY_PATH", &fixture.centipede_path) // Needed for FFI export + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .expect("Failed to spawn binary"); + + let output = process.wait_with_output().expect("Should terminate"); + let stderr = String::from_utf8_lossy(&output.stderr); + + expect_that!(output.status.success(), eq(true)); + + // Check that "Bug found!" appears exactly 3 times + let bug_count = stderr.matches("Bug found!").count(); + expect_that!(bug_count, eq(3)); +} diff --git a/rust/e2e_tests/run_with_centipede.sh b/rust/e2e_tests/run_with_centipede.sh new file mode 100755 index 000000000..b52785be7 --- /dev/null +++ b/rust/e2e_tests/run_with_centipede.sh @@ -0,0 +1,75 @@ +#!/bin/bash +# +# Use this script for when one wants to manually fuzz a test target with +# Centipede: internal only. Workdir for Centipede is the dir from where this +# script is executed. User is in charge of removing the corpus to start anew. +# +# This script will build Centipede and the fuzz target every time by default. +# If you wish to skip building Centipede (speeds up execution), set +# --build_centipede=false in the command line for subsequent runs. +# +# First argument should be the fuzz target. +# Example usage: +# +# ./third_party/googlefuzztest/rust/e2e_tests/run_with_centipede.sh \ +# //third_party/googlefuzztest/rust/e2e_tests/testdata:fuzztest_main \ +# --fuzz=find_rarer_bug_fuzz_test --fuzz_for=10s + +source gbash.sh || exit + +DEFINE_string artifact_dir "/tmp" "Build artifacts will be stored here." +DEFINE_string fuzz_for "" "How much time to run the fuzz test for" +DEFINE_string fuzz --required "" "The name of the fuzz test to run" +DEFINE_bool build_centipede true "If false, will not build Centipede." + +gbash::init_google "$@" +set -- "${GBASH_ARGV[@]}" + +readonly FUZZ_TARGET="${1}" +readonly CENTIPEDE_TARGET="//third_party/googlefuzztest/centipede/google:centipede_uninstrumented" + +readonly SYMLINK="${FLAGS_artifact_dir%/}/blaze-" +readonly CENTIPEDE_SYMLINK="${SYMLINK}centipede-" +readonly TARGET_SYMLINK="${SYMLINK}target-" +readonly CENTIPEDE_PATH="${CENTIPEDE_SYMLINK}bin/third_party/googlefuzztest/centipede/google/centipede_uninstrumented" + +get_fuzz_target_binary_path() { + # Remove leading double slashes. + local FUZZ_TARGET_PATH="${FUZZ_TARGET##//}" + # Replace last colon with a slash. + echo "${TARGET_SYMLINK}bin/${FUZZ_TARGET_PATH%:*}/${FUZZ_TARGET_PATH##*:}" +} + +build_all() { + + if (( FLAGS_build_centipede )); then + # BUILD Centipede. + bazel build -c opt --symlink_prefix="${CENTIPEDE_SYMLINK}" \ + "${CENTIPEDE_TARGET}" + fi + + # TODO: b/437896409 - Use a dedicated config for Rust FuzzTest. + # BUILD the fuzz target. + bazel build --symlink_prefix="${TARGET_SYMLINK}" "${FUZZ_TARGET}" \ + --config=rust-cov --config=asan + +} + +main() { + + build_all + + declare -a centipede_flags=( + --binary="$(get_fuzz_target_binary_path)" \ + --persistent_mode=0 --fork_server=0 --populate_binary_info=0 \ + --test_name="${FLAGS_fuzz}" \ + ) + + if [[ -n "${FLAGS_fuzz_for}" ]]; then + centipede_flags+=(--stop_after="${FLAGS_fuzz_for}") + fi + + "${CENTIPEDE_PATH}" "${centipede_flags[@]}" +} + +main diff --git a/rust/e2e_tests/standalone_mode_test.rs b/rust/e2e_tests/standalone_mode_test.rs new file mode 100644 index 000000000..adb5253a0 --- /dev/null +++ b/rust/e2e_tests/standalone_mode_test.rs @@ -0,0 +1,297 @@ +use googletest::matchers; +use googletest::prelude::*; +use std::process::Command; +use test_utils::EnvVars; + +#[gtest] +fn standalone_mode_invokes_centipede(fixture: &EnvVars) { + let target_binary_path = + fixture.target_binary_path.parent().unwrap().join("standalone_fuzz_tests_bin"); + let test_name = "__fuzztest_mod__standalone_validation_test::standalone_validation_test"; + + let process = Command::new(&target_binary_path) + .arg(test_name) + .env("FUZZTEST_FUZZ_FOR", "3s") + .env("FUZZTEST_PRINT_SUBPROCESS_LOG", "true") + .env("FUZZTEST_CENTIPEDE_BINARY_PATH", &fixture.centipede_path) + .env("RUST_TEST_NOCAPTURE", "1") + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .expect("Failed to spawn binary"); + + let output = process.wait_with_output().expect("Should terminate"); + let stderr = String::from_utf8_lossy(&output.stderr); + + // Assert that Centipede actually ran the test binary, and forwarded the worker log. + // We check for "LOG: STANDALONE_VALIDATION_WORKER_EXECUTED". The prefix "LOG: " indicates that + // this output is from centipede forwarding the output of the worker (due to --print_runner_log), + // whereas the rest is from the fuzztest itself. + expect_that!( + stderr, + matchers::contains_substring("LOG: STANDALONE_VALIDATION_WORKER_EXECUTED") + ); +} + +#[gtest] +fn standalone_mode_invokes_the_correct_fuzztest(fixture: &EnvVars) { + let target_binary_path = + fixture.target_binary_path.parent().unwrap().join("standalone_fuzz_tests_bin"); + let test_name = "__fuzztest_mod__standalone_validation_test::standalone_validation_test"; + + let process = Command::new(&target_binary_path) + .arg(test_name) + .env("FUZZTEST_FUZZ_FOR", "3s") + .env("FUZZTEST_PRINT_SUBPROCESS_LOG", "true") + .env("FUZZTEST_CENTIPEDE_BINARY_PATH", &fixture.centipede_path) + .env("RUST_TEST_NOCAPTURE", "1") + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .expect("Failed to spawn binary"); + + let output = process.wait_with_output().expect("Should terminate"); + let stderr = String::from_utf8_lossy(&output.stderr); + + // Check that the first test is executed + expect_that!( + stderr, + matchers::contains_substring("LOG: STANDALONE_VALIDATION_WORKER_EXECUTED") + ); + + // Check that the second test does not get executed + expect_that!(stderr, not(matchers::contains_substring("SECOND_VALIDATION_TEST_EXECUTED"))); +} + +#[gtest] +fn standalone_mode_handles_worker_crash(fixture: &EnvVars) { + let test_name = "__fuzztest_mod__find_bug_fuzz_test::find_bug_fuzz_test"; + + let process = Command::new(&fixture.target_binary_path) + .arg(test_name) + .env("FUZZTEST_FUZZ_FOR", "5s") + .env("FUZZTEST_PRINT_SUBPROCESS_LOG", "true") + .env("FUZZTEST_CENTIPEDE_BINARY_PATH", &fixture.centipede_path) + .env("RUST_TEST_NOCAPTURE", "1") + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .expect("Failed to spawn binary"); + + let output = process.wait_with_output().expect("Should terminate"); + let stderr = String::from_utf8_lossy(&output.stderr); + + expect_that!(stderr, matchers::contains_substring("Property function ran but crashed.")); + expect_that!(stderr, matchers::contains_regex("Signature[ \t]*: Unwinding panic")); + // Centipede prefixes logs from the crashing worker with "CRASH LOG: ". + expect_that!(stderr, matchers::contains_substring("CRASH LOG: Bug found!")); +} + +#[gtest] +fn standalone_mode_spawns_parallel_jobs(fixture: &EnvVars) { + let target_binary_path = + fixture.target_binary_path.parent().unwrap().join("standalone_fuzz_tests_bin"); + let test_name = "__fuzztest_mod__jobs_test::jobs_test"; + + let process = Command::new(&target_binary_path) + .arg(test_name) + .env("FUZZTEST_FUZZ_FOR", "5s") + .env("FUZZTEST_JOBS", "4") + .env("FUZZTEST_PRINT_SUBPROCESS_LOG", "true") + .env("FUZZTEST_CENTIPEDE_BINARY_PATH", &fixture.centipede_path) + .env("RUST_TEST_NOCAPTURE", "1") + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .expect("Failed to spawn binary"); + + let output = process.wait_with_output().expect("Should terminate"); + let stderr = String::from_utf8_lossy(&output.stderr); + + // Extract all PIDs from logs. + // Expected format: "LOG: JOBS_TEST_PID: " + use std::collections::HashSet; + let mut pids = HashSet::new(); + for line in stderr.lines() { + if let Some(pos) = line.find("LOG: JOBS_TEST_PID: ") { + let pid_str = &line[pos + "LOG: JOBS_TEST_PID: ".len()..]; + if let Ok(pid) = pid_str.parse::() { + pids.insert(pid); + } + } + } + + // We expect 4 different PIDs + expect_that!(pids.len(), eq(4)); +} + +#[gtest] +fn standalone_mode_replay_corpus_per_test_budget(fixture: &EnvVars) { + let target_binary_path = + fixture.target_binary_path.parent().unwrap().join("standalone_fuzz_tests_bin"); + + let corpus_db = fixture.tmp_dir_path.join("corpus_db_per_test"); + let work_dir_root = fixture.tmp_dir_path.join("WD_root_per_test"); + + // Centipede appends the binary identifier (relative path) to the corpus database path. + let identifier = target_binary_path.to_str().unwrap(); + let identifier_relative = identifier.strip_prefix('/').unwrap_or(identifier); + + // Create corpus dirs for tests + let test1_dir = corpus_db + .join(identifier_relative) + .join("__fuzztest_mod__standalone_validation_test.standalone_validation_test") + .join("coverage"); + let test2_dir = corpus_db + .join(identifier_relative) + .join("__fuzztest_mod__second_validation_test.second_validation_test") + .join("coverage"); + + std::fs::create_dir_all(&test1_dir).unwrap(); + std::fs::create_dir_all(&test2_dir).unwrap(); + + // Write some corpus inputs. + // Assuming Arbitrary consumes 4 bytes. + use std::fs::File; + use std::io::Write; + + let mut file1 = File::create(test1_dir.join("input1")).unwrap(); + file1.write_all(&[1, 0, 0, 0]).unwrap(); + + let mut file2 = File::create(test1_dir.join("input2")).unwrap(); + file2.write_all(&[2, 0, 0, 0]).unwrap(); + + let mut file3 = File::create(test2_dir.join("input3")).unwrap(); + file3.write_all(&[3, 0, 0, 0]).unwrap(); + + let process_per_test = Command::new(&target_binary_path) + .arg("__fuzztest_mod__") + .env("FUZZTEST_REPLAY_CORPUS_FOR", "3s") + .env("FUZZTEST_TIME_BUDGET_TYPE", "per-test") + .env("FUZZTEST_PRINT_SUBPROCESS_LOG", "true") + .env("FUZZTEST_CENTIPEDE_BINARY_PATH", &fixture.centipede_path) + .env("FUZZTEST_CORPUS_DB", corpus_db.to_str().unwrap()) + .env("FUZZTEST_WORKDIR_ROOT", work_dir_root.to_str().unwrap()) + .env("RUST_TEST_NOCAPTURE", "1") + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .expect("Failed to spawn binary"); + + let output_per_test = process_per_test.wait_with_output().expect("Should terminate"); + let stderr_per_test = String::from_utf8_lossy(&output_per_test.stderr); + + // Verify logs for per-test + expect_that!( + stderr_per_test, + matchers::contains_substring("Replaying __fuzztest_mod__standalone_validation_test.standalone_validation_test for 3s") + ); + expect_that!( + stderr_per_test, + matchers::contains_substring( + "Replaying __fuzztest_mod__second_validation_test.second_validation_test for 3s" + ) + ); + + // Count executions for per-test + let mut val1_count = 0; + let mut val2_count = 0; + for line in stderr_per_test.lines() { + if line.contains("STANDALONE_VALIDATION_INPUT:") { + val1_count += 1; + } + if line.contains("SECOND_VALIDATION_INPUT:") { + val2_count += 1; + } + } + // we had 2 inputs for first test + expect_that!(val1_count, eq(2)); + // and 1 input for second test + expect_that!(val2_count, eq(1)); +} + +#[gtest] +fn standalone_mode_replay_corpus_total_budget(fixture: &EnvVars) { + let target_binary_path = + fixture.target_binary_path.parent().unwrap().join("standalone_fuzz_tests_bin"); + + let corpus_db = fixture.tmp_dir_path.join("corpus_db_total"); + let work_dir_root = fixture.tmp_dir_path.join("WD_root_total"); + + // Centipede appends the binary identifier (relative path) to the corpus database path. + let identifier = target_binary_path.to_str().unwrap(); + let identifier_relative = identifier.strip_prefix('/').unwrap_or(identifier); + + // Create corpus dirs for tests + let test1_dir = corpus_db + .join(identifier_relative) + .join("__fuzztest_mod__standalone_validation_test.standalone_validation_test") + .join("coverage"); + let test2_dir = corpus_db + .join(identifier_relative) + .join("__fuzztest_mod__second_validation_test.second_validation_test") + .join("coverage"); + + std::fs::create_dir_all(&test1_dir).unwrap(); + std::fs::create_dir_all(&test2_dir).unwrap(); + + // Write some corpus inputs. + // Assuming Arbitrary consumes 4 bytes. + use std::fs::File; + use std::io::Write; + + let mut file1 = File::create(test1_dir.join("input1")).unwrap(); + file1.write_all(&[1, 0, 0, 0]).unwrap(); + + let mut file2 = File::create(test1_dir.join("input2")).unwrap(); + file2.write_all(&[2, 0, 0, 0]).unwrap(); + + let mut file3 = File::create(test2_dir.join("input3")).unwrap(); + file3.write_all(&[3, 0, 0, 0]).unwrap(); + + // We have 3 fuzz tests in the binary. Total 3s / 3 = 1s per test. + let process_total = Command::new(&target_binary_path) + .arg("__fuzztest_mod__") + .env("FUZZTEST_REPLAY_CORPUS_FOR", "3s") + .env("FUZZTEST_TIME_BUDGET_TYPE", "total") + .env("FUZZTEST_PRINT_SUBPROCESS_LOG", "true") + .env("FUZZTEST_CENTIPEDE_BINARY_PATH", &fixture.centipede_path) + .env("FUZZTEST_CORPUS_DB", corpus_db.to_str().unwrap()) + .env("FUZZTEST_WORKDIR_ROOT", work_dir_root.to_str().unwrap()) + .env("RUST_TEST_NOCAPTURE", "1") + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .expect("Failed to spawn binary"); + + let output_total = process_total.wait_with_output().expect("Should terminate"); + let stderr_total = String::from_utf8_lossy(&output_total.stderr); + + // Verify logs for total -> should be 1s + expect_that!( + stderr_total, + matchers::contains_substring("Replaying __fuzztest_mod__standalone_validation_test.standalone_validation_test for 1s") + ); + expect_that!( + stderr_total, + matchers::contains_substring( + "Replaying __fuzztest_mod__second_validation_test.second_validation_test for 1s" + ) + ); + + // Counts should still match because it finishes early anyway + let mut val1_count_total = 0; + let mut val2_count_total = 0; + for line in stderr_total.lines() { + if line.contains("STANDALONE_VALIDATION_INPUT:") { + val1_count_total += 1; + } + if line.contains("SECOND_VALIDATION_INPUT:") { + val2_count_total += 1; + } + } + // we had 2 inputs for first test + expect_that!(val1_count_total, eq(2)); + // and 1 input for second test + expect_that!(val2_count_total, eq(1)); +} diff --git a/rust/e2e_tests/test_utils.rs b/rust/e2e_tests/test_utils.rs new file mode 100644 index 000000000..60044e765 --- /dev/null +++ b/rust/e2e_tests/test_utils.rs @@ -0,0 +1,66 @@ +use googletest::fixtures::Fixture; +use std::env; +use std::path::PathBuf; +use std::process::Command; + +pub struct EnvVars { + pub tmp_dir_path: PathBuf, + pub target_binary_path: PathBuf, + pub centipede_path: PathBuf, +} + +impl Fixture for EnvVars { + fn set_up() -> googletest::Result { + const TARGET_BINARY: &str = + "_main/rust/e2e_tests/testdata/fuzztest_main"; + + const CENTIPEDE: &str = + "_main/centipede/centipede_uninstrumented"; + let src_dir_path = PathBuf::from(env::var("TEST_SRCDIR").unwrap()); + let tmp_dir_path = PathBuf::from(env::var("TEST_TMPDIR").unwrap()); + let target_binary_path = src_dir_path.join(PathBuf::from(TARGET_BINARY)); + let centipede_path = src_dir_path.join(PathBuf::from(CENTIPEDE)); + + Ok(EnvVars { tmp_dir_path, target_binary_path, centipede_path }) + } + fn tear_down(self) -> googletest::Result<()> { + Ok(()) + } +} + +/// Returns stderr of a Centipede process with `args` that is expected to terminate through some +/// internal flag. e.g. `--exit_on_crash` or `--stop_after`. +/// +/// Each arg of `args` should be a string that Centipede recognizes containing a flag with a +/// possible value, e.g. `--test_name=my_test_name` or `--exit_on_crash`. In addition to `args`, +/// the function will also pass `--populate_binary_info=0`, `--fork_server=0`, +/// `--persistent_mode=0`, and `--env_diff_for_binaries`. +pub fn run_centipede_with_args_expect_termination(fixture: &EnvVars, args: &[&str]) -> String { + // Disable interference from Bazel environment variables. + let env_diff = [ + "-TEST_DIAGNOSTICS_OUTPUT_DIR", + "-TEST_INFRASTRUCTURE_FAILURE_FILE", + "-TEST_LOGSPLITTER_OUTPUT_FILE", + "-TEST_PREMATURE_EXIT_FILE", + "-TEST_RANDOM_SEED", + "-TEST_RUN_NUMBER", + "-TEST_SHARD_INDEX", + "-TEST_SHARD_STATUS_FILE", + "-TEST_TOTAL_SHARDS", + "-TEST_UNDECLARED_OUTPUTS_ANNOTATIONS_DIR", + "-TEST_UNDECLARED_OUTPUTS_DIR", + "-TEST_WARNINGS_OUTPUT_FILE", + "-GTEST_OUTPUT", + "-XML_OUTPUT_FILE", + ]; + let process = Command::new(&fixture.centipede_path) + .arg("--populate_binary_info=0") + .arg("--fork_server=0") + .arg("--persistent_mode=0") + .arg(format!("--env_diff_for_binaries={}", env_diff.join(","))) + .args(args) + .output() + .expect("Centipede should have executed"); + + String::from_utf8_lossy(&process.stderr).to_string() +} diff --git a/rust/e2e_tests/testdata/BUILD b/rust/e2e_tests/testdata/BUILD new file mode 100644 index 000000000..65111b222 --- /dev/null +++ b/rust/e2e_tests/testdata/BUILD @@ -0,0 +1,106 @@ +load("@rules_rust//rust:defs.bzl", "rust_test") + +package(default_visibility = ["@com_google_fuzztest//rust/e2e_tests:__subpackages__"]) + +licenses(["notice"]) + +rust_test( + name = "fuzztest_main", + testonly = 1, + srcs = [ + "fuzz_tests.rs", + ], + edition = "2024", + # TODO: b/437896409 - Replace with global config once it's available. + rustc_flags = [ + "-Ccodegen-units=1", + "-Clink-dead-code", + "-Cpasses=sancov-module", + "-Cllvm-args=-sanitizer-coverage-level=1", + "-Cllvm-args=-sanitizer-coverage-trace-compares", + ], + tags = [ + "manual", + "notap", + ], + deps = [ + "@crate_index//:googletest", + "@crate_index//:anyhow", # v1 + "@crate_index//:inventory", # v0_3 + "@crate_index//:rand", # v0_10 + "@com_google_fuzztest//rust:fuzztest", + ], +) + +rust_test( + name = "standalone_fuzz_tests_bin", + testonly = 1, + srcs = [ + "standalone_fuzz_tests.rs", + ], + edition = "2024", + rustc_flags = [ + "-Ccodegen-units=1", + "-Clink-dead-code", + "-Cpasses=sancov-module", + "-Cllvm-args=-sanitizer-coverage-level=1", + "-Cllvm-args=-sanitizer-coverage-trace-compares", + ], + tags = [ + "manual", + "notap", + ], + deps = [ + "@crate_index//:googletest", + "@crate_index//:anyhow", # v1 + "@crate_index//:inventory", # v0_3 + "@crate_index//:rand", # v0_10 + "@com_google_fuzztest//rust:fuzztest", + ], +) + +rust_test( + name = "replay_fuzz_tests_bin", + testonly = 1, + srcs = [ + "replay_fuzz_tests.rs", + ], + edition = "2024", + rustc_flags = [ + "-Ccodegen-units=1", + "-Clink-dead-code", + "-Cpasses=sancov-module", + "-Cllvm-args=-sanitizer-coverage-level=1", + "-Cllvm-args=-sanitizer-coverage-trace-compares", + ], + tags = [ + "manual", + "notap", + ], + deps = [ + "@crate_index//:googletest", + "@crate_index//:anyhow", # v1 + "@crate_index//:inventory", # v0_3 + "@crate_index//:rand", # v0_10 + "@com_google_fuzztest//rust:fuzztest", + ], +) + +rust_test( + name = "fuzz_tests_as_gtests", + srcs = ["fuzz_tests_as_gtests.rs"], + edition = "2024", + rustc_flags = ["-Zallow-features=cfg_sanitize"], + tags = [ + "manual", + "notap", + ], + deps = [ + "@crate_index//:googletest", + "@crate_index//:anyhow", # v1 + "@crate_index//:inventory", # v0_3 + "@crate_index//:postcard", # v1 + "@crate_index//:rand", # v0_10 + "@com_google_fuzztest//rust:fuzztest", + ], +) diff --git a/rust/e2e_tests/testdata/fuzz_tests.rs b/rust/e2e_tests/testdata/fuzz_tests.rs new file mode 100644 index 000000000..1a5e8745e --- /dev/null +++ b/rust/e2e_tests/testdata/fuzz_tests.rs @@ -0,0 +1,171 @@ +use fuzztest::domains::arbitrary::Arbitrary; +use fuzztest::domains::range::InRange; +use fuzztest::domains::Domain; +use fuzztest::fuzztest; +use rand::RngExt; +use std::mem::MaybeUninit; + +struct ByteVectorDomain {} + +impl ByteVectorDomain { + pub fn new() -> Self { + Self {} + } +} + +// Test-only domain. +impl Domain for ByteVectorDomain { + type UserValue<'user> = Vec; + type CorpusValue = Vec; + + fn init(&self, rng: &mut dyn rand::Rng) -> anyhow::Result { + let mut val = vec![0u8; rng.random_range(0..100)]; + rng.fill(&mut val[..]); + Ok(val) + } + + fn mutate( + &self, + val: &mut Self::CorpusValue, + rng: &mut dyn rand::Rng, + only_shrink: bool, + ) -> anyhow::Result<()> { + if only_shrink { + if rng.random::() < 0.05 && !val.is_empty() { + val.remove(rng.random_range(..val.len())); + } + + for elem in val { + if *elem != 0 { + *elem -= 1; + } + } + + return Ok(()); + } + if rng.random::() < 0.05 { + val.insert(rng.random_range(0..=val.len()), rng.random()); + } + + if rng.random::() < 0.05 && !val.is_empty() { + val.remove(rng.random_range(..val.len())); + } + + let start = rng.random_range(0..=val.len()); + let end = rng.random_range(start..=val.len()); + rng.fill(&mut val[start..end]); + + Ok(()) + } + + fn get_user_value<'a>( + &self, + val: &'a Self::CorpusValue, + ) -> anyhow::Result> { + Ok(val.clone()) + } +} + +#[fuzztest(_a = Arbitrary::::default())] +fn bool_fuzz_test(_a: bool) {} + +#[fuzztest(_a = Arbitrary::::default())] +fn i32_fuzz_test(_a: i32) {} + +#[fuzztest( + _a = Arbitrary::::default(), + _b = Arbitrary::::default(), + _c = Arbitrary::::default(), + _d = ByteVectorDomain::new() +)] +fn multi_arg_fuzz_test(_a: i32, _b: f32, _c: bool, _d: Vec) {} + +// Designed to fail in a "rare" instance. +#[fuzztest(a = ByteVectorDomain::new())] +fn find_bug_fuzz_test(a: Vec) { + if !a.is_empty() && a[0] == 123 { + panic!("Bug found!"); + } +} + +// Designed to fail in an even rarer instance to prove that coverage is working. +#[fuzztest(a = ByteVectorDomain::new())] +fn find_rarer_bug_fuzz_test(a: Vec) { + if a.len() == 5 && a[0] == b'P' && a[1] == b'a' && a[2] == b'N' && a[3] == b'i' && a[4] == b'C' + { + panic!("Bug found!"); + } +} + +#[fuzztest(a = ByteVectorDomain::new(), b = ByteVectorDomain::new())] +fn find_rarer_bug_2_args_fuzz_test(a: Vec, b: Vec) { + if !a.is_empty() && a[0] >= 128 && !b.is_empty() && b[0] == 42 { + panic!("Bug found!"); + } +} + +#[fuzztest(a = ByteVectorDomain::new(), b = Arbitrary::::default(), c = InRange::::new(0, 100))] +fn find_rarer_bug_3_args_fuzz_test(a: Vec, b: f32, c: i32) { + if !a.is_empty() && a[0] >= 128 && b < 0.0 && c >= 95 { + panic!("Bug found!"); + } +} +#[fuzztest(a = ByteVectorDomain::new())] +fn use_after_free_asan_death_test(mut a: Vec) { + if !a.is_empty() && a[0] == 123 { + let ptr = a.as_mut_ptr(); + drop(a); + // Intentionally trigger a use-after-free bug for ASAN to detect. + unsafe { + *(std::hint::black_box(ptr)) = 1; + } + } +} + +#[fuzztest(a = ByteVectorDomain::new())] +fn msan_death_test(a: Vec) { + if !a.is_empty() && a[0] == 123 { + // Intentionally trigger an uninitialized memory bug for MSAN to detect. + unsafe { + let x: MaybeUninit = MaybeUninit::uninit(); + let y = std::hint::black_box(x).assume_init(); + println!("y: {}", y); + } + } +} + +struct FallibleDomain {} + +impl FallibleDomain { + pub fn new() -> Self { + Self {} + } +} + +impl Domain for FallibleDomain { + type UserValue<'user> = u32; + type CorpusValue = u32; + + fn init(&self, _rng: &mut dyn rand::Rng) -> anyhow::Result { + Ok(0) + } + + fn mutate( + &self, + _val: &mut Self::CorpusValue, + _rng: &mut dyn rand::Rng, + _only_shrink: bool, + ) -> anyhow::Result<()> { + anyhow::bail!("Intentional mutate failure") + } + + fn get_user_value<'a>( + &self, + val: &'a Self::CorpusValue, + ) -> anyhow::Result> { + Ok(*val) + } +} + +#[fuzztest(a = FallibleDomain::new())] +fn fallible_fuzz_test(_a: u32) {} diff --git a/rust/e2e_tests/testdata/fuzz_tests_as_gtests.rs b/rust/e2e_tests/testdata/fuzz_tests_as_gtests.rs new file mode 100644 index 000000000..4e2ed7b36 --- /dev/null +++ b/rust/e2e_tests/testdata/fuzz_tests_as_gtests.rs @@ -0,0 +1,18 @@ +use fuzztest::domains::arbitrary::Arbitrary; +use fuzztest::fuzztest; +use std::sync::Once; + +#[fuzztest(_a = Arbitrary::::default())] +fn bool_fuzz_test(_a: bool) { + static ONCE: Once = Once::new(); + ONCE.call_once(|| { + println!("bool_fuzz_test property function ran..."); + }); +} + +#[fuzztest( + _a = Arbitrary::::default(), + _b = Arbitrary::::default(), + _c = Arbitrary::::default(), +)] +fn multi_arg_fuzz_test(_a: i32, _b: f32, _c: bool) {} diff --git a/rust/e2e_tests/testdata/replay_fuzz_tests.rs b/rust/e2e_tests/testdata/replay_fuzz_tests.rs new file mode 100644 index 000000000..6a42cb83a --- /dev/null +++ b/rust/e2e_tests/testdata/replay_fuzz_tests.rs @@ -0,0 +1,20 @@ +use fuzztest::domains::arbitrary::Arbitrary; +use fuzztest::fuzztest; + +#[fuzztest(a = Arbitrary::::default())] +fn find_bug_fuzz_test(a: i32) { + println!("PROPERTY_FUNCTION_EXECUTED"); + if a == 10 || a == 20 || a == 30 { + panic!("Bug found!"); + } +} + +#[fuzztest(_a = Arbitrary::::default())] +fn untargeted_test_1(_a: i32) { + println!("UNTARGETED_TEST_1_EXECUTED"); +} + +#[fuzztest(_a = Arbitrary::::default())] +fn untargeted_test_2(_a: i32) { + println!("UNTARGETED_TEST_2_EXECUTED"); +} diff --git a/rust/e2e_tests/testdata/standalone_fuzz_tests.rs b/rust/e2e_tests/testdata/standalone_fuzz_tests.rs new file mode 100644 index 000000000..e8d7d53d8 --- /dev/null +++ b/rust/e2e_tests/testdata/standalone_fuzz_tests.rs @@ -0,0 +1,21 @@ +use fuzztest::domains::arbitrary::Arbitrary; +use fuzztest::fuzztest; + +#[fuzztest(_a = Arbitrary::::default())] +fn standalone_validation_test(_a: i32) { + // Minimal test function to validate standalone mode. + println!("STANDALONE_VALIDATION_WORKER_EXECUTED"); + println!("STANDALONE_VALIDATION_INPUT: {}", _a); +} + +#[fuzztest(_a = Arbitrary::::default())] +fn second_validation_test(_a: i32) { + println!("SECOND_VALIDATION_TEST_EXECUTED"); + println!("SECOND_VALIDATION_INPUT: {}", _a); +} + +#[fuzztest(_a = Arbitrary::::default())] +fn jobs_test(_a: i32) { + println!("JOBS_TEST_PID: {}", std::process::id()); + std::thread::sleep(std::time::Duration::from_millis(250)); +} diff --git a/rust/e2e_tests/worker_test.rs b/rust/e2e_tests/worker_test.rs new file mode 100644 index 000000000..7676af509 --- /dev/null +++ b/rust/e2e_tests/worker_test.rs @@ -0,0 +1,26 @@ +use googletest::matchers; +use googletest::prelude::*; +use std::fs; +use std::process::Command; +use test_utils::EnvVars; + +#[gtest] +fn get_binary_id_test(fixture: &EnvVars) { + let binary_id_file = fixture.tmp_dir_path.join("binary_id.txt"); + + let mut process = Command::new(&fixture.target_binary_path) + .env( + "CENTIPEDE_RUNNER_FLAGS", + format!(":dump_binary_id:binary_id_output={}:", binary_id_file.display()), + ) + .spawn() + .expect("Failed to spawn binary"); + + process.wait().expect("Should terminate"); + expect_that!( + fs::read_to_string(binary_id_file).unwrap(), + matchers::contains_substring( + (fixture.target_binary_path.file_name()).unwrap().to_string_lossy() + ) + ); +} diff --git a/rust/e2e_tests/worker_with_centipede_sanitizer_test.rs b/rust/e2e_tests/worker_with_centipede_sanitizer_test.rs new file mode 100644 index 000000000..f4c122156 --- /dev/null +++ b/rust/e2e_tests/worker_with_centipede_sanitizer_test.rs @@ -0,0 +1,49 @@ +#![feature(cfg_sanitize)] + +#[cfg(any(sanitize = "address", sanitize = "memory"))] +use googletest::matchers; +#[cfg(any(sanitize = "address", sanitize = "memory"))] +use googletest::prelude::*; +#[cfg(any(sanitize = "address", sanitize = "memory"))] +use std::fs; +#[cfg(any(sanitize = "address", sanitize = "memory"))] +use test_utils::EnvVars; + +#[gtest] +#[cfg(sanitize = "address")] +fn ensure_use_after_free_signature_with_asan(fixture: &EnvVars) { + let work_dir = fixture.tmp_dir_path.join("WD"); // For permissions + fs::create_dir_all(&work_dir).expect("Failed to create working directory"); + + let args = [ + &format!("--binary={}", fixture.target_binary_path.display()), + &format!("--workdir={}", work_dir.display()), + "--exit_on_crash", + "--test_name=__fuzztest_mod__use_after_free_asan_death_test.use_after_free_asan_death_test", + ]; + + let stderr = test_utils::run_centipede_with_args_expect_termination(fixture, &args); + + expect_that!(stderr, matchers::contains_regex("Signature[ \t]*: heap-use-after-free")); +} + +// TODO(yamilmorales): Enable this test on presubmit with --config=msan. +#[gtest] +#[cfg(sanitize = "memory")] +fn ensure_sanitizer_crash_signature_with_msan(fixture: &EnvVars) { + let work_dir = fixture.tmp_dir_path.join("WD"); // For permissions + fs::create_dir_all(&work_dir).expect("Failed to create working directory"); + + let args = [ + &format!("--binary={}", fixture.target_binary_path.display()), + &format!("--workdir={}", work_dir.display()), + "--exit_on_crash", + "--test_name=__fuzztest_mod__msan_death_test.msan_death_test", + "--use_cmp_features=0", // Prevent msan from detecting nested bugs and aborting without + // triggering the death callback. + ]; + + let stderr = test_utils::run_centipede_with_args_expect_termination(fixture, &args); + + expect_that!(stderr, matchers::contains_regex("Signature[ \t]*: Sanitizer crash")); +} diff --git a/rust/e2e_tests/worker_with_centipede_test.rs b/rust/e2e_tests/worker_with_centipede_test.rs new file mode 100644 index 000000000..4a16a7f26 --- /dev/null +++ b/rust/e2e_tests/worker_with_centipede_test.rs @@ -0,0 +1,121 @@ +use googletest::matchers; +use googletest::prelude::*; +use rand::RngExt; +use std::fs; +use test_utils::EnvVars; + +fn run_centipede_test<'a>( + fixture: &EnvVars, + test_name: &str, + extra_args: impl IntoIterator, +) -> String { + let random_suffix: String = + rand::rng().sample_iter(&rand::distr::Alphanumeric).take(10).map(char::from).collect(); + let work_dir_name = format!("WD_{}_{}", test_name, random_suffix); + let work_dir = fixture.tmp_dir_path.join(work_dir_name); + fs::create_dir_all(&work_dir).expect("Failed to create working directory"); + + let rust_test_name = test_name.replace(".", "::"); + let mut args: Vec = vec![ + format!( + "--binary={} --nocapture {} --exact", + fixture.target_binary_path.display(), + rust_test_name + ), + format!("--workdir={}", work_dir.display()), + format!("--test_name={}", test_name), + ]; + args.extend(extra_args.into_iter().map(|s| s.to_string())); + + let args: Vec<&str> = args.iter().map(|s| s.as_str()).collect(); + test_utils::run_centipede_with_args_expect_termination(fixture, &args) +} + +#[gtest] +fn find_bug_with_centipede_test(fixture: &EnvVars) { + let stderr = run_centipede_test( + fixture, + "__fuzztest_mod__find_bug_fuzz_test.find_bug_fuzz_test", + ["--exit_on_crash"], + ); + + expect_that!(stderr, matchers::contains_substring("Property function ran but crashed.")); + expect_that!(stderr, matchers::contains_regex("Signature[ \t]*: Unwinding panic")); + expect_that!(stderr, matchers::contains_substring("CRASH LOG: Bug found!")); +} + +#[gtest] +fn ensure_custom_mutator_with_centipede_test(fixture: &EnvVars) { + let stderr = run_centipede_test( + fixture, + "__fuzztest_mod__find_bug_fuzz_test.find_bug_fuzz_test", + ["--stop_after=10s"], + ); + + // In the 10 seconds that Centipede ran, the fuzzing loop should have requested to mutate. + expect_that!(stderr, matchers::contains_substring("Custom mutator detected")); +} + +#[gtest] +fn ensure_seed_emition_with_centipede_test(fixture: &EnvVars) { + let stderr = run_centipede_test( + fixture, + "__fuzztest_mod__find_bug_fuzz_test.find_bug_fuzz_test", + ["--exit_on_crash", "--require_seeds"], + ); + + // If not present, then exited early because no seeds were emitted. + expect_that!(stderr, matchers::contains_substring("Number of input seeds available")); +} + +#[gtest] +fn ensure_features_work_finding_rarer_bug(fixture: &EnvVars) { + let stderr = run_centipede_test( + fixture, + "__fuzztest_mod__find_rarer_bug_fuzz_test.find_rarer_bug_fuzz_test", + ["--exit_on_crash"], + ); + + // Centipede's passes a length-prefixed screen, so the initial \\\\x5 stands for + // that length, not any kind of hex-escaping. + expect_that!(stderr, matchers::contains_regex("Input bytes[ \t]*: \\\\x5PaNiC")); + expect_that!(stderr, matchers::contains_substring("CRASH LOG: Bug found!")); +} + +#[gtest] +fn ensure_features_work_finding_rarer_bug_2_args(fixture: &EnvVars) { + let stderr = run_centipede_test( + fixture, + "__fuzztest_mod__find_rarer_bug_2_args_fuzz_test.find_rarer_bug_2_args_fuzz_test", + ["--exit_on_crash"], + ); + + expect_that!(stderr, matchers::contains_substring("CRASH LOG: Bug found!")); +} + +#[gtest] +fn ensure_features_work_finding_rarer_bug_3_args(fixture: &EnvVars) { + let stderr = run_centipede_test( + fixture, + "__fuzztest_mod__find_rarer_bug_3_args_fuzz_test.find_rarer_bug_3_args_fuzz_test", + ["--exit_on_crash"], + ); + + expect_that!(stderr, matchers::contains_substring("CRASH LOG: Bug found!")); +} + +#[gtest] +fn ensure_emit_error_with_centipede_test(fixture: &EnvVars) { + let stderr = run_centipede_test( + fixture, + "__fuzztest_mod__fallible_fuzz_test.fallible_fuzz_test", + ["--stop_after=5s"], + ); + + expect_that!( + stderr, + matchers::contains_substring( + "Emitted failure output: Failed to mutate: Intentional mutate failure" + ) + ); +} diff --git a/rust/engine/BUILD b/rust/engine/BUILD new file mode 100644 index 000000000..8004b3bf5 --- /dev/null +++ b/rust/engine/BUILD @@ -0,0 +1,18 @@ +load("@rules_rust//rust:defs.bzl", "rust_library") + +licenses(["notice"]) + +rust_library( + name = "engine", + srcs = [ + "src/engine_ffi.rs", + "src/lib.rs", + ], + deps = [ + "@com_google_fuzztest//centipede:engine_abi", + "@com_google_fuzztest//centipede:engine_controller_with_subprocess", + "@com_google_fuzztest//centipede:engine_worker", + ], + edition = "2024", + visibility = ["@com_google_fuzztest//rust:__subpackages__"], +) diff --git a/rust/engine/Cargo.toml b/rust/engine/Cargo.toml new file mode 100644 index 000000000..c90114d2f --- /dev/null +++ b/rust/engine/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "engine-ffi" +version = "0.1.0" +edition = "2024" +links = "centipede_engine_static" +build = "build.rs" + +[dependencies] + + diff --git a/rust/engine/build.rs b/rust/engine/build.rs new file mode 100644 index 000000000..05b5942e8 --- /dev/null +++ b/rust/engine/build.rs @@ -0,0 +1,29 @@ +//! Build script for the `engine-ffi` crate. +//! +//! ## Prerequisites +//! +//! Before compiling or testing this crate using Cargo (`cargo build` or `cargo test`), +//! you must first compile the C++ Centipede dependencies using Bazel (or Blaze): +//! +//! ```bash +//! bazel build //centipede:centipede_engine_static +//! ``` +//! +//! This command generates the required static library archive `libcentipede_engine_static.a` +//! under `bazel-bin/centipede` (or `blaze-bin/centipede`). +//! +//! You must provide the path to this library via `RUSTFLAGS` when running Cargo: +//! +//! ```bash +//! RUSTFLAGS="-L bazel-bin/centipede" cargo build +//! ``` + +fn main() { + println!("cargo:rustc-link-lib=static=centipede_engine_static"); + + // Link required system libraries + println!("cargo:rustc-link-lib=dylib=stdc++"); + println!("cargo:rustc-link-lib=dylib=rt"); + println!("cargo:rustc-link-lib=dylib=dl"); + println!("cargo:rustc-link-lib=dylib=pthread"); +} diff --git a/rust/engine/src/engine_ffi.rs b/rust/engine/src/engine_ffi.rs new file mode 100644 index 000000000..69e2c2c6f --- /dev/null +++ b/rust/engine/src/engine_ffi.rs @@ -0,0 +1,441 @@ +// The source of truth for the FFI definitions in this file are these C++ header files: +// - engine_abi.h +// - engine_worker_abi.h +// - engine_controller_abi.h +// TODO(b/532255598): use crubit + +use core::ffi::c_int; + +use core::marker::PhantomData; +use core::marker::PhantomPinned; +use core::ptr; + +// Opaque handles for in-memory input objects used by the test, which +// may be serialized/deserialized by the engine. +#[repr(transparent)] +pub struct FuzzTestInputHandle(pub usize); + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct FuzzTestBytesView { + pub data: *const u8, + pub size: usize, +} + +impl FuzzTestBytesView { + pub fn from_bytes(bytes: &[u8]) -> Self { + Self { data: bytes.as_ptr(), size: bytes.len() } + } + + pub fn to_bytes(&self) -> *const [u8] { + if self.data.is_null() { + &[] + } else { + ptr::slice_from_raw_parts(self.data, self.size) + } + } +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct FuzzTestBytesViews { + pub views: *const FuzzTestBytesView, + pub count: usize, +} + +impl FuzzTestBytesViews { + pub fn from_slice(views: &[FuzzTestBytesView]) -> Self { + Self { views: views.as_ptr(), count: views.len() } + } + + pub fn as_slice(&self) -> *const [FuzzTestBytesView] { + if self.views.is_null() { + &[] + } else { + ptr::slice_from_raw_parts(self.views, self.count) + } + } +} + +#[repr(C)] +pub struct FuzzTestDiagnosticSinkCtx { + _opaque: (), + _not_send_nor_sync: PhantomData<*mut ()>, + _not_unpin: PhantomPinned, +} + +/// Sink for diagnostics during the setup and execution of the test +/// methods. Methods in this sink are async-safe and thread-safe. +#[repr(C)] +#[derive(Copy, Clone)] +pub struct FuzzTestDiagnosticSink { + pub ctx: *mut FuzzTestDiagnosticSinkCtx, + + /// Emits an unrecoverable error with a human-readable `message`. Engine would + /// propagate the error to the controller command when in the worker mode. + pub emit_error: Option< + unsafe extern "C" fn( + ctx: *mut FuzzTestDiagnosticSinkCtx, + message: *const FuzzTestBytesView, + ), + >, + + /// Emits a warning with a human-readable `message`. Engine would log + /// the warning and continue gracefully when in the worker mode. + pub emit_warning: Option< + unsafe extern "C" fn( + ctx: *mut FuzzTestDiagnosticSinkCtx, + message: *const FuzzTestBytesView, + ), + >, + + /// Emits a finding for running a test input within `Execute()` with a + /// human-readable `description`, and `signature` for deduplication. + /// + /// Must not be called if the engine is not calling `Execute()`. If called + /// multiple times within the same `Execute()` window, a random one would take + /// effect. + pub emit_finding: Option< + unsafe extern "C" fn( + ctx: *mut FuzzTestDiagnosticSinkCtx, + description: *const FuzzTestBytesView, + signature: *const FuzzTestBytesView, + ), + >, +} + +// SAFETY: The engine guarantees that the diagnostic sink and its context are +// thread-safe and async-safe. +unsafe impl Send for FuzzTestDiagnosticSink {} +unsafe impl Sync for FuzzTestDiagnosticSink {} + +#[repr(C)] +pub struct FuzzTestBytesSinkCtx { + _opaque: (), + _not_send_nor_sync: PhantomData<*mut ()>, + _not_unpin: PhantomPinned, +} + +/// Sink for bytes data. +#[repr(C)] +#[derive(Copy, Clone)] +pub struct FuzzTestBytesSink { + pub ctx: *mut FuzzTestBytesSinkCtx, + + /// Emits a byte buffer. Multiple emissions are concatenated. + pub emit: Option< + unsafe extern "C" fn(ctx: *mut FuzzTestBytesSinkCtx, view: *const FuzzTestBytesView), + >, +} + +#[repr(C)] +pub struct FuzzTestInputSinkCtx { + _opaque: (), + _not_send_nor_sync: PhantomData<*mut ()>, + _not_unpin: PhantomPinned, +} + +/// Sink for seed inputs. +#[repr(C)] +#[derive(Copy, Clone)] +pub struct FuzzTestInputSink { + pub ctx: *mut FuzzTestInputSinkCtx, + + /// Emits a test `input` to the engine. Engine would call + /// `FuzzTestAdapter::FreeInput` on the emitted input after the engine is + /// done with it. + pub emit: + Option, +} + +#[repr(C)] +pub struct FuzzTestUint64sView { + pub data: *const u64, + pub size: usize, +} + +impl FuzzTestUint64sView { + pub fn from_slice(slice: &[u64]) -> Self { + Self { data: slice.as_ptr(), size: slice.len() } + } + + pub fn as_slice(&self) -> *const [u64] { + if self.data.is_null() || self.size == 0 { + &[] + } else { + ptr::slice_from_raw_parts(self.data, self.size) + } + } + + pub fn to_bytes(&self) -> *const [u8] { + if self.data.is_null() { + &[] + } else { + ptr::slice_from_raw_parts( + self.data as *const u8, + self.size * core::mem::size_of::(), + ) + } + } +} +/// Constants for the layout of the coverage feature as a 64-bit unsigned +/// integer: +/// +/// - Bits 63..59: 5-bit domain ID of the feature. Each domain is a +/// logically independent feature namespace registered in +/// `FuzzTestAdapter::SetUpCoverageDomains`. +/// - Bits 58..32: 27-bit feature ID within the domain. +/// - Bits 31..0: 32-bit counter value of the feature. +/// +pub struct FuzzTestCoverageFeatureLayout; + +impl FuzzTestCoverageFeatureLayout { + pub const COUNTER_START_BIT: u32 = 0; + pub const COUNTER_BIT_SIZE: u32 = 32; + pub const FEATURE_ID_START_BIT: u32 = 32; + pub const FEATURE_ID_BIT_SIZE: u32 = 27; + pub const DOMAIN_ID_START_BIT: u32 = 59; + pub const DOMAIN_ID_BIT_SIZE: u32 = 5; +} + +#[repr(C)] +pub struct FuzzTestFeedbackSinkCtx { + _opaque: (), + _not_send_nor_sync: PhantomData<*mut ()>, + _not_unpin: PhantomPinned, +} + +/// Sink for execution feedback. +#[repr(C)] +#[derive(Copy, Clone)] +pub struct FuzzTestFeedbackSink { + pub ctx: *mut FuzzTestFeedbackSinkCtx, + + /// Emits an array of coverage features captured from the execution + /// inside `Execute` call. See `FuzzTestCoverageFeatureLayout` for the feature + /// layout. + /// + /// Multiple emissions are concatenated. + pub emit_coverage_features: Option< + unsafe extern "C" fn( + ctx: *mut FuzzTestFeedbackSinkCtx, + features: *const FuzzTestUint64sView, + ), + >, +} + +/// Information of a coverage domain. +#[repr(C)] +pub struct FuzzTestCoverageDomain { + /// 5-bit domain ID. + pub domain_id: u8, + /// Human-readable name of the domain for logging. + pub name: FuzzTestBytesView, + /// Number of bits used for the feature IDs in this domain, must be <= 27. + pub feature_id_bit_size: u8, + /// Number of bits used for the counter values in this domain, must be <= 32. + pub counter_bit_size: u8, +} + +#[repr(C)] +pub struct FuzzTestDomainRegistryCtx { + _opaque: (), + _not_send_nor_sync: PhantomData<*mut ()>, + _not_unpin: PhantomPinned, +} + +#[repr(C)] +#[derive(Copy, Clone)] +pub struct FuzzTestCoverageDomainRegistry { + pub ctx: *mut FuzzTestDomainRegistryCtx, + + /// Registers a new coverage `domain`. + pub register: Option< + unsafe extern "C" fn( + ctx: *mut FuzzTestDomainRegistryCtx, + domain: *const FuzzTestCoverageDomain, + ), + >, +} + +#[repr(C)] +pub struct FuzzTestAdapterCtx { + _opaque: (), + _not_send_nor_sync: PhantomData<*mut ()>, + _not_unpin: PhantomPinned, +} + +#[repr(C)] +pub struct FuzzTestAdapter { + pub ctx: *mut FuzzTestAdapterCtx, + + /// Sets up coverage domains using the domain `registry`. + /// The domain registrations must be the same for the all the test adapters of + /// the same test (identified by the test name and the binary). + pub set_up_coverage_domains: Option< + unsafe extern "C" fn( + ctx: *mut FuzzTestAdapterCtx, + registry: *const FuzzTestCoverageDomainRegistry, + ), + >, + + /// [Optional] Emits any preset seed inputs of the test using `sink`. + /// The output must be the same for the all the test adapters of the same test + /// (identified by the test name and the binary). + pub get_preset_seed_inputs: + Option, + + /// Emits a randomly generated seed input using `sink`. + pub get_random_seed_input: + Option, + + /// Mutates from `origin`, and emits the mutant using `sink`. `shrink` != 0 + /// means to generate smaller mutant. + /// + /// It should not change the content/metadata of `origin`. + pub mutate: Option< + unsafe extern "C" fn( + ctx: *mut FuzzTestAdapterCtx, + origin: FuzzTestInputHandle, + shrink: c_int, + sink: *const FuzzTestInputSink, + ), + >, + + /// [Optional] Performs cross-over mutation using `origin` and `other`, and + /// emits the mutant using `sink`. + /// + /// It should not change the content/metadata of `origin` or `other`. + pub cross_over: Option< + unsafe extern "C" fn( + ctx: *mut FuzzTestAdapterCtx, + origin: FuzzTestInputHandle, + other: FuzzTestInputHandle, + sink: *const FuzzTestInputSink, + ), + >, + + /// Executes `input` for testing and emits any feedback to `sink`. + /// + /// The `input` metadata may be updated by the adapter for further + /// mutations. The `input` content, which affects the test behavior of + /// `Execute()`, should not be changed. + pub execute: Option< + unsafe extern "C" fn( + ctx: *mut FuzzTestAdapterCtx, + input: FuzzTestInputHandle, + sink: *const FuzzTestFeedbackSink, + ), + >, + + /// Serializes the test `input` content into bytes using `sink`. + pub serialize_input_content: Option< + unsafe extern "C" fn( + ctx: *mut FuzzTestAdapterCtx, + input: FuzzTestInputHandle, + sink: *const FuzzTestBytesSink, + ), + >, + + /// Deserializes the test `input` content from serialized `content` into + /// a `FuzzTestInputHandle` using `sink`. + pub deserialize_input_content: Option< + unsafe extern "C" fn( + ctx: *mut FuzzTestAdapterCtx, + content: *const FuzzTestBytesView, + sink: *const FuzzTestInputSink, + ), + >, + + /// [Optional] Serializes the test `input` metadata into bytes using `sink`. + pub serialize_input_metadata: Option< + unsafe extern "C" fn( + ctx: *mut FuzzTestAdapterCtx, + input: FuzzTestInputHandle, + sink: *const FuzzTestBytesSink, + ), + >, + + /// [Optional] Updates the test `input` metadata from serialized `metadata`. + pub update_input_metadata: Option< + unsafe extern "C" fn( + ctx: *mut FuzzTestAdapterCtx, + metadata: *const FuzzTestBytesView, + input: FuzzTestInputHandle, + ), + >, + + /// Callback to run when the engine is done with `input`. + pub free_input: + Option, + + /// Callback to run when the engine is done with `ctx` (and the adapter). + pub free_ctx: Option, +} + +#[repr(C)] +pub struct FuzzTestAdapterManagerCtx { + _opaque: (), + _not_send_nor_sync: PhantomData<*mut ()>, + _not_unpin: PhantomPinned, +} + +#[repr(C)] +pub struct FuzzTestAdapterManager { + pub ctx: *mut FuzzTestAdapterManagerCtx, + + /// [Optional] Emits the ID for the current binary. + pub get_binary_id: Option< + unsafe extern "C" fn(ctx: *mut FuzzTestAdapterManagerCtx, sink: *const FuzzTestBytesSink), + >, + + /// Emits the test name. + pub get_test_name: Option< + unsafe extern "C" fn(ctx: *mut FuzzTestAdapterManagerCtx, sink: *const FuzzTestBytesSink), + >, + + /// Constructs an adapter of the test into `adapter_out`. Any diagnostics + /// happening during the construction or running the adapter should be emitted + /// to `diagnostic_sink`. `diagnostic_sink` is guaranteed to live until + /// `FreeCtx` is called on the adapter. + pub construct_adapter: Option< + unsafe extern "C" fn( + ctx: *mut FuzzTestAdapterManagerCtx, + diagnostic_sink: *const FuzzTestDiagnosticSink, + adapter_out: *mut FuzzTestAdapter, + ), + >, +} + +#[repr(i32)] +pub enum FuzzTestWorkerStatus { + /// Test should finish with a success. + Success = 0, + /// Test should finish with a failure. + Failure = 1, + /// Test should continue with controller commands. + NotRequired = 2, +} + +#[derive(PartialEq, Eq)] +#[repr(i32)] +pub enum FuzzTestControllerStatus { + Success = 0, + Failure = 1, +} + +#[allow(improper_ctypes)] +unsafe extern "C" { + /// Try to run as a FuzzTest worker with `manager` if needed. + pub fn FuzzTestWorkerMaybeRun(manager: *const FuzzTestAdapterManager) -> FuzzTestWorkerStatus; + + /// Returns whether the current process is running as a FuzzTest worker - + /// non-zero means yes and zero means no. It can be called outside of tests. + pub fn FuzzTestWorkerIsRequired() -> c_int; + + /// Run the FuzzTest controller with `flags` and `manager`. + pub fn FuzzTestControllerRun( + manager: *const FuzzTestAdapterManager, + flags: *const FuzzTestBytesViews, + ) -> FuzzTestControllerStatus; +} diff --git a/rust/engine/src/lib.rs b/rust/engine/src/lib.rs new file mode 100644 index 000000000..7594c504e --- /dev/null +++ b/rust/engine/src/lib.rs @@ -0,0 +1,268 @@ +pub mod engine_ffi; + +use std::marker::PhantomData; + +#[allow(improper_ctypes)] +unsafe extern "C" { + fn SanCovRuntimeSetUpCoverageDomains( + registry: *const engine_ffi::FuzzTestCoverageDomainRegistry, + ) -> usize; + + fn SanCovRuntimeEmitFeatures(sink: *const engine_ffi::FuzzTestFeedbackSink); +} + +/// A zero-sized witness token proving that a test is currently executing. +/// +/// Some of the methods on FuzzTestDiagnosticSink require that they are called only if the engine is +/// calling `Execute` callback of FuzzTestAdapter, which includes: +/// +/// * `emit_finding` +/// +/// The methods receiving this type can safely assume that the `Execute` callback is active because +/// the caller ensures this by creating this type. +pub struct ExecuteContext { + _private: PhantomData<()>, +} + +impl ExecuteContext { + /// Creates a new execution context. + /// + /// # Safety + /// + /// * The caller must ensure that it is in the `Execute` callback of FuzzTestAdapter. + pub unsafe fn new() -> Self { + Self { _private: PhantomData } + } +} + +#[derive(Clone)] +pub struct DiagnosticSink { + raw: engine_ffi::FuzzTestDiagnosticSink, +} + +impl DiagnosticSink { + /// Creates a safe wrapper from a pointer to `FuzzTestDiagnosticSink`. + /// + /// # Safety + /// + /// The caller must ensure that: + /// * `raw` is a non-null, properly aligned pointer to a valid `FuzzTestDiagnosticSink`. + /// * `(*raw).ctx` is a valid context pointer. + /// * The sink and its context remain valid for the duration of `DiagnosticSink`'s usage + /// (guaranteed by the engine until `FreeCtx` is called on the adapter). + pub unsafe fn from_raw(raw: *const engine_ffi::FuzzTestDiagnosticSink) -> Self { + // SAFETY: Caller guarantees `raw` is a valid pointer to `FuzzTestDiagnosticSink`. + let raw_sink = unsafe { *raw }; + Self { raw: raw_sink } + } + + /// Emits an unrecoverable error message to the underlying diagnostic sink. + pub fn emit_error(&self, message: &str) { + let emit = self.raw.emit_error.expect("EmitError is required by ABI"); + let view = engine_ffi::FuzzTestBytesView::from_bytes(message.as_bytes()); + // SAFETY: The presence of `self` guarantees that the context of the underlying + // `FuzzTestDiagnosticSink` remains live for the duration of this sink. + unsafe { + emit(self.raw.ctx, &view); + } + } + + /// Emits a warning message to the underlying diagnostic sink. + pub fn emit_warning(&self, message: &str) { + let emit = self.raw.emit_warning.expect("EmitWarning is required by ABI"); + let view = engine_ffi::FuzzTestBytesView::from_bytes(message.as_bytes()); + // SAFETY: The presence of `self` guarantees that the context of the underlying + // `FuzzTestDiagnosticSink` remains live for the duration of this sink. + unsafe { + emit(self.raw.ctx, &view); + } + } + + /// Emits a test finding (e.g., a crash or bug) to the underlying diagnostic sink. + /// + /// This must only be called during test execution, which is guaranteed by the + /// [`ExecuteContext`] token. + pub fn emit_finding(&self, _token: &ExecuteContext, description: &str, signature: &str) { + let emit = self.raw.emit_finding.expect("EmitFinding is required by ABI"); + let desc_view = engine_ffi::FuzzTestBytesView::from_bytes(description.as_bytes()); + let sig_view = engine_ffi::FuzzTestBytesView::from_bytes(signature.as_bytes()); + // SAFETY: The presence of `self` guarantees that the context of the underlying + // `FuzzTestDiagnosticSink` remains live for the duration of this sink. + // The witness `_token` further proves execution is currently within the `Execute` callback. + unsafe { + emit(self.raw.ctx, &desc_view, &sig_view); + } + } +} + +pub struct BytesSink { + raw: engine_ffi::FuzzTestBytesSink, +} + +impl BytesSink { + /// Creates a safe wrapper from a pointer to `FuzzTestBytesSink`. + /// + /// # Safety + /// + /// The caller must ensure that: + /// * `raw` is a non-null, properly aligned pointer to a valid `FuzzTestBytesSink`. + /// * `(*raw).ctx` is a valid context pointer. + /// * The sink and its context remain valid for the duration of `BytesSink`'s usage + /// (guaranteed by the engine until `FreeCtx` is called on the adapter). + pub unsafe fn from_raw(raw: *const engine_ffi::FuzzTestBytesSink) -> Self { + // SAFETY: Caller guarantees `raw` is a valid pointer to `FuzzTestBytesSink`. + let raw_sink = unsafe { *raw }; + Self { raw: raw_sink } + } + + /// Emits a byte slice to the underlying bytes sink. + /// + /// Multiple calls to this function will concatenate the emitted bytes. + pub fn emit(&mut self, bytes: &[u8]) { + let emit = self.raw.emit.expect("Emit is required by ABI"); + let view = engine_ffi::FuzzTestBytesView::from_bytes(bytes); + // SAFETY: The presence of `self` guarantees `from_raw` was called with a valid context + // pointer (`self.raw.ctx`) that remains live for the duration of this call. + unsafe { + emit(self.raw.ctx, &view); + } + } +} + +pub struct InputSink { + raw: engine_ffi::FuzzTestInputSink, +} + +impl InputSink { + /// Creates a safe wrapper from a pointer to `FuzzTestInputSink`. + /// + /// # Safety + /// + /// The caller must ensure that: + /// * `raw` is a non-null, properly aligned pointer to a valid `FuzzTestInputSink`. + /// * `(*raw).ctx` is a valid context pointer. + /// * The sink and its context remain valid for the duration of `InputSink`'s usage + /// (guaranteed by the engine until `FreeCtx` is called on the adapter). + pub unsafe fn from_raw(raw: *const engine_ffi::FuzzTestInputSink) -> Self { + // SAFETY: Caller guarantees `raw` is a valid pointer to `FuzzTestInputSink`. + let raw_sink = unsafe { *raw }; + Self { raw: raw_sink } + } + + /// Emits a test input handle to the underlying input sink. + /// + /// This function transfers ownership of the input represented by the handle + /// to the engine. The engine is responsible for freeing it. + pub fn emit(&mut self, input: engine_ffi::FuzzTestInputHandle) { + let emit = self.raw.emit.expect("Emit is required by ABI"); + // SAFETY: The presence of `self` guarantees `from_raw` was called with a valid context + // pointer (`self.raw.ctx`) that remains live for the duration of this call. + unsafe { + emit(self.raw.ctx, input); + } + } +} + +pub struct FeedbackSink { + raw: engine_ffi::FuzzTestFeedbackSink, +} + +impl FeedbackSink { + /// Creates a safe wrapper from a pointer to `FuzzTestFeedbackSink`. + /// + /// # Safety + /// + /// The caller must ensure that: + /// * `raw` is a non-null, properly aligned pointer to a valid `FuzzTestFeedbackSink`. + /// * `(*raw).ctx` is a valid context pointer. + /// * The sink and its context remain valid for the duration of `FeedbackSink`'s usage + /// (guaranteed by the engine until `FreeCtx` is called on the adapter). + pub unsafe fn from_raw(raw: *const engine_ffi::FuzzTestFeedbackSink) -> Self { + // SAFETY: Caller guarantees `raw` is a valid pointer to `FuzzTestFeedbackSink`. + let raw_sink = unsafe { *raw }; + Self { raw: raw_sink } + } + + /// Emits execution coverage features to the underlying feedback sink. + pub fn emit_coverage_features(&mut self, features: &[u64]) { + let emit = + self.raw.emit_coverage_features.expect("EmitCoverageFeatures is required by ABI"); + let view = engine_ffi::FuzzTestUint64sView::from_slice(features); + // SAFETY: The presence of `self` guarantees `from_raw` was called with a valid context + // pointer (`self.raw.ctx`) that remains live for the duration of this call. + unsafe { + emit(self.raw.ctx, &view); + } + } + + /// Emits SanitizerCoverage features. + pub fn emit_sancov_features(&mut self) { + // SAFETY: the sink pointer is guaranteed to be valid by the framework. + unsafe { SanCovRuntimeEmitFeatures(&self.raw) }; + } +} + +/// Information describing a coverage feature domain. +/// +/// This is a high-level wrapper for [`engine_ffi::FuzzTestCoverageDomain`], +/// which is the Rust representation of the C struct `FuzzTestCoverageDomain` +/// defined in `engine_abi.h` (the source of truth for the ABI). +/// +/// Unlike the FFI representation which uses [`engine_ffi::FuzzTestBytesView`], +/// this type uses Rust slices (`&[u8]`) for safety and convenience. It can be +/// converted to the FFI representation using `From`/`Into`. +/// +/// A coverage domain represents a logically independent feature namespace registered in +/// `CoverageDomainRegistry`. FuzzTest packs 64-bit coverage features into three parts: +/// * Bits 63..59 (5 bits): 5-bit domain ID (`domain_id`, up to 32 domains). +/// * Bits 58..32 (27 bits): Feature ID within the domain (`feature_id_bit_size` <= 27). +/// * Bits 31..0 (32 bits): Feature counter value (`counter_bit_size` <= 32). +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct CoverageDomain<'a> { + /// 5-bit unique domain identifier (must be < 32). + pub domain_id: u8, + /// Human-readable name of the domain for logging and debugging. + pub name: &'a str, + /// Number of bits used for feature IDs in this domain (must be <= 27). + pub feature_id_bit_size: u8, + /// Number of bits used for feature counter values in this domain (must be <= 32). + pub counter_bit_size: u8, +} + +impl<'a> From> for engine_ffi::FuzzTestCoverageDomain { + fn from(domain: CoverageDomain<'a>) -> Self { + Self { + domain_id: domain.domain_id, + name: engine_ffi::FuzzTestBytesView::from_bytes(domain.name.as_bytes()), + feature_id_bit_size: domain.feature_id_bit_size, + counter_bit_size: domain.counter_bit_size, + } + } +} + +pub struct CoverageDomainRegistry { + raw: engine_ffi::FuzzTestCoverageDomainRegistry, +} + +impl CoverageDomainRegistry { + /// Creates a safe wrapper from a pointer to `FuzzTestCoverageDomainRegistry`. + /// + /// # Safety + /// + /// The caller must ensure that: + /// * `raw` is a non-null, properly aligned pointer to a valid `FuzzTestCoverageDomainRegistry`. + /// * `(*raw).ctx` is a valid context pointer. + /// * The registry and its context remain valid for the duration of `CoverageDomainRegistry`'s usage + /// (guaranteed by the engine until `FreeCtx` is called on the adapter). + pub unsafe fn from_raw(raw: *const engine_ffi::FuzzTestCoverageDomainRegistry) -> Self { + // SAFETY: Caller guarantees `raw` is a valid pointer to `FuzzTestCoverageDomainRegistry`. + let raw_registry = unsafe { *raw }; + Self { raw: raw_registry } + } + + /// Sets up SanitizerCoverage domains. + pub fn set_up_sancov_domains(&self) { + // SAFETY: The registry pointer is guaranteed to be valid by the framework. + unsafe { SanCovRuntimeSetUpCoverageDomains(&self.raw) }; + } +} diff --git a/rust/fuzztest_macro/.rustfmt.toml b/rust/fuzztest_macro/.rustfmt.toml new file mode 100644 index 000000000..33b356d5f --- /dev/null +++ b/rust/fuzztest_macro/.rustfmt.toml @@ -0,0 +1,3 @@ +use_field_init_shorthand = true +use_try_shorthand = true +edition = "2021" diff --git a/rust/fuzztest_macro/BUILD b/rust/fuzztest_macro/BUILD new file mode 100644 index 000000000..13b677554 --- /dev/null +++ b/rust/fuzztest_macro/BUILD @@ -0,0 +1,41 @@ +load("@rules_rust//rust:defs.bzl", "rust_proc_macro", "rust_test") + +licenses(["notice"]) + +rust_proc_macro( + name = "fuzztest_macro", + srcs = glob( + [ + "src/**/*.rs", + ], + exclude = ["src/test_main.rs"], + ), + edition = "2024", + visibility = ["@com_google_fuzztest//rust:__subpackages__"], + deps = [ + "@crate_index//:convert_case", # v0_4 + "@crate_index//:proc-macro2", + "@crate_index//:proc-macro-crate", + "@crate_index//:quote", # v1 + "@crate_index//:syn", # v2 + ], +) + +rust_test( + name = "fuzztest_macro_test", + srcs = glob( + ["src/**/*.rs"], + exclude = ["src/lib.rs"], + ), + crate_root = "src/test_main.rs", + edition = "2024", + proc_macro_deps = [":fuzztest_macro"], + deps = [ + "@crate_index//:googletest", + "@crate_index//:convert_case", # v0_4 + "@crate_index//:proc-macro2", + "@crate_index//:proc-macro-crate", + "@crate_index//:quote", # v1 + "@crate_index//:syn", # v2 + ], +) diff --git a/rust/fuzztest_macro/Cargo.toml b/rust/fuzztest_macro/Cargo.toml new file mode 100644 index 000000000..4dfad0eab --- /dev/null +++ b/rust/fuzztest_macro/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "fuzztest-macro" +version = "0.1.0" +edition = "2024" + +[lib] +proc-macro = true + +[dependencies] +convert_case.workspace = true +proc-macro2.workspace = true +proc-macro-crate.workspace = true +quote.workspace = true +syn.workspace = true + + +[dev-dependencies] +googletest = "0.14.3" diff --git a/rust/fuzztest_macro/src/helpers.rs b/rust/fuzztest_macro/src/helpers.rs new file mode 100644 index 000000000..14578c9f0 --- /dev/null +++ b/rust/fuzztest_macro/src/helpers.rs @@ -0,0 +1,26 @@ +use proc_macro2::Span; +use proc_macro2::TokenStream; +use proc_macro_crate::FoundCrate; +use quote::quote; +use syn::Ident; + +mod fn_item_utils; +mod fuzztest_domain; +pub mod test_registration; + +#[derive(Debug, Clone)] +struct TestFnArgument<'a> { + name: &'a syn::Ident, + ty: syn::Type, +} + +fn import_fuzztest_crate() -> TokenStream { + match proc_macro_crate::crate_name("fuzztest") { + Ok(FoundCrate::Itself) => quote! { crate }, + Ok(FoundCrate::Name(name)) => { + let ident = Ident::new(&name, Span::call_site()); + quote! { #ident } + } + Err(_) => quote! { ::fuzztest }, + } +} diff --git a/rust/fuzztest_macro/src/helpers/fn_item_utils.rs b/rust/fuzztest_macro/src/helpers/fn_item_utils.rs new file mode 100644 index 000000000..0fd0f2cbe --- /dev/null +++ b/rust/fuzztest_macro/src/helpers/fn_item_utils.rs @@ -0,0 +1,233 @@ +use syn::parse_quote; +use syn::visit_mut::{self, VisitMut}; +use syn::BareFnArg; +use syn::Error; +use syn::FnArg; +use syn::Pat; +use syn::Signature; +use syn::TypeBareFn; + +use super::TestFnArgument; + +struct LifetimeReplacer<'a> { + user_value_lifetime_generic: &'a syn::Lifetime, +} + +impl<'a> VisitMut for LifetimeReplacer<'a> { + fn visit_lifetime_mut(&mut self, lifetime: &mut syn::Lifetime) { + // If the lifetime is `'static`, we don't want to replace it. + if lifetime.ident != "static" { + *lifetime = self.user_value_lifetime_generic.clone(); + } + visit_mut::visit_lifetime_mut(self, lifetime); + } +} + +/// Extracts the type of the arguments of a given function item (ie: a `syn::ItemFn`). +pub fn extract_test_function_arguments<'a>( + fn_signature: &'a Signature, + user_value_lifetime_generic: &syn::Lifetime, +) -> syn::Result>> { + fn_signature + .inputs + .iter() + .map(|arg| match arg { + FnArg::Receiver(_) => { + Err(Error::new_spanned(arg, "Receiver is not supported in property functions.")) + } + FnArg::Typed(pat_type) => match pat_type.pat.as_ref() { + Pat::Ident(pat_ident) => { + let mut ty = pat_type.ty.as_ref().clone(); + let mut lifetime_replacer = LifetimeReplacer { user_value_lifetime_generic }; + lifetime_replacer.visit_type_mut(&mut ty); + Ok(TestFnArgument { name: &pat_ident.ident, ty }) + } + pat => Err(Error::new_spanned(pat, "Expected an identifier")), + }, + }) + .collect::>>() +} + +/// Extract a bare function pointer type (`syn::TypeBareFn``) from a given +/// signature (`syn::Signature`) +/// +/// For example, passing in a `syn::Signature` representing the function +/// `fn test_function(mut a: i32, b: std::string::String)` would yield the +/// following bare function pointer type `fn(i32, std::string::String)`. +/// +/// Example usage: +/// ```markdown +/// ``` +/// # use syn::parse_quote; +/// +/// let signature = parse_quote! { +/// fn test_function(mut a: i32, b: std::string::String) +/// }; +/// +/// assert_eq!(fn_sig_to_bare_type(&signature), parse_quote!(fn(i32, std::string::String))); +/// ``` +/// ``` +/// +pub fn fn_sig_to_bare_type(fn_sig: &Signature) -> syn::Result { + let generics = &fn_sig.generics; + if generics.type_params().count() > 0 || generics.const_params().count() > 0 { + return Err(Error::new_spanned( + generics, + "Property function should not have any Type or Const generics", + )); + } + + let bare_input_args = fn_sig + .inputs + .iter() + .map(|arg| match arg { + FnArg::Receiver(_) => { + Err(Error::new_spanned(arg, "Receiver is not supported in property functions.")) + } + FnArg::Typed(pat_type) => Ok(BareFnArg { + attrs: pat_type.attrs.clone(), + name: None, + ty: pat_type.ty.as_ref().clone(), + }), + }) + .collect::>>()?; + let fn_lifetime_generics = generics.lifetimes().collect::>(); + let unsafety = &fn_sig.unsafety; + let abi = &fn_sig.abi; + let variadic = &fn_sig.variadic; + let output = &fn_sig.output; + if fn_lifetime_generics.is_empty() { + Ok(parse_quote! { + #unsafety #abi fn (#(#bare_input_args),* #variadic) #output + }) + } else { + Ok(parse_quote! { + for< #(#fn_lifetime_generics),* > #unsafety #abi fn (#(#bare_input_args),* #variadic) #output + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use googletest::prelude::*; + + #[googletest::test] + fn test_extract_test_function_arguments_succeeds() { + let fn_item: Signature = syn::parse_quote! { + fn test_function(mut a: i32, b: std::string::String) + }; + let user_value_lifetime_generic = parse_quote!('user); + + let test_fn_arguments = + extract_test_function_arguments(&fn_item, &user_value_lifetime_generic); + + expect_that!( + test_fn_arguments, + ok(elements_are![ + matches_pattern!(TestFnArgument { + name: eq(&"e::format_ident!("a")), + ty: eq(&syn::parse_quote! { i32 }), + }), + matches_pattern!(TestFnArgument { + name: eq(&"e::format_ident!("b")), + ty: eq(&syn::parse_quote! { std::string::String }), + }) + ]) + ); + } + + #[googletest::test] + fn test_extract_test_function_arguments_with_lifetimes_succeeds() { + let fn_item: Signature = syn::parse_quote! { + fn test_function<'a, 'b>(mut a: i32, b: &'a str, c: &'b Vec<&'static u8>) + }; + let user_value_lifetime_generic = parse_quote!('user); + + let test_fn_arguments = + extract_test_function_arguments(&fn_item, &user_value_lifetime_generic); + + expect_that!( + test_fn_arguments, + ok(elements_are![ + matches_pattern!(TestFnArgument { + name: eq(&"e::format_ident!("a")), + ty: eq(&syn::parse_quote! { i32 }), + }), + matches_pattern!(TestFnArgument { + name: eq(&"e::format_ident!("b")), + ty: eq(&syn::parse_quote! { &'user str }), + }), + matches_pattern!(TestFnArgument { + name: eq(&"e::format_ident!("c")), + ty: eq(&syn::parse_quote! { &'user Vec<&'static u8> }), + }) + ]) + ); + } + + #[googletest::test] + fn test_extract_test_function_arguments_fails_with_non_ident_arg() { + let fn_item: Signature = syn::parse_quote! { + fn test_function( + MyStruct { a }: MyStruct, b: std::string::String) + }; + let user_value_lifetime_generic = parse_quote!('user); + + let test_fn_arguments = + extract_test_function_arguments(&fn_item, &user_value_lifetime_generic); + + expect_that!( + test_fn_arguments, + err(displays_as(contains_substring("Expected an identifier"))) + ); + } + + #[googletest::test] + fn test_extract_test_function_arguments_fails_with_receiver_arg() { + let fn_item: Signature = syn::parse_quote! { + fn test_function(&self, b: std::string::String) + }; + let user_value_lifetime_generic = parse_quote!('user); + + let test_fn_arguments = + extract_test_function_arguments(&fn_item, &user_value_lifetime_generic); + + expect_that!( + test_fn_arguments, + err(displays_as(contains_substring( + "Receiver is not supported in property functions." + ))) + ); + } + + #[googletest::test] + fn test_convert_fn_sig_to_bare_type_fn_succeeds() { + expect_that!( + fn_sig_to_bare_type(&parse_quote! { + fn test_function(a: i32, b: std::string::String) -> bool + }), + ok(eq(&parse_quote! {fn (i32, std::string::String) -> bool })) + ) + } + + #[googletest::test] + fn test_convert_fn_sig_with_no_return_type_to_bare_type_fn_succeeds() { + expect_that!( + fn_sig_to_bare_type(&parse_quote! { + fn test_function(a: i32, b: std::string::String) + }), + ok(eq(&parse_quote! {fn (i32, std::string::String) })) + ) + } + + #[googletest::test] + fn test_convert_fn_sig_with_lifetimes_to_bare_type_fn_succeeds() { + expect_that!( + fn_sig_to_bare_type(&parse_quote! { + fn test_function<'a, 'b>(a: std::vec::Vec<&'a i32>, b: Test<'b>) + }), + ok(eq(&parse_quote! {for<'a, 'b> fn (std::vec::Vec<&'a i32>, Test<'b>) })) + ) + } +} diff --git a/rust/fuzztest_macro/src/helpers/fuzztest_domain.rs b/rust/fuzztest_macro/src/helpers/fuzztest_domain.rs new file mode 100644 index 000000000..31e334d1c --- /dev/null +++ b/rust/fuzztest_macro/src/helpers/fuzztest_domain.rs @@ -0,0 +1,162 @@ +use proc_macro2::TokenStream; +use quote::quote; +use syn::parse_quote; +use syn::Generics; +use syn::Ident; + +use super::import_fuzztest_crate; +use super::TestFnArgument; + +/// Generates a wrapper domain that will contain all domains of the test +/// function arguments. +pub fn generate_fuzztest_domain<'a, 'b: 'a>( + domain_struct_name: &Ident, + test_fn_args: impl IntoIterator>, + user_value_lifetime_generic: &syn::Lifetime, +) -> (TokenStream, Vec) { + let crate_name = import_fuzztest_crate(); + let test_fn_args = test_fn_args.into_iter().collect::>(); + + let domain_generics = + (0..test_fn_args.len()).map(|idx| quote::format_ident!("T{idx}")).collect::>(); + + let corpus_domain_generics = domain_generics + .iter() + .map(|generic| parse_quote!(#generic::CorpusValue)) + .collect::>(); + let user_value_domain_generics = domain_generics + .iter() + .map(|generic| parse_quote!(#generic::UserValue<#user_value_lifetime_generic>)) + .collect::>(); + let mut generics: Generics = parse_quote! { < #(#domain_generics),* > }; + + let test_fn_args_with_generics = + test_fn_args.into_iter().zip(generics.type_params().cloned()).collect::>(); + + let mut field_names = vec![]; + let mut domain_field_types = vec![]; + + let where_clauses = generics.make_where_clause(); + for (TestFnArgument { ty, name }, syn::TypeParam { ident: domain_gen, .. }) in + &test_fn_args_with_generics + { + where_clauses.predicates.push( + parse_quote! { + for <#user_value_lifetime_generic> #domain_gen: #crate_name::domains::Domain = #ty > + }); + where_clauses.predicates.push(parse_quote! { #domain_gen::CorpusValue: #crate_name::serde::Serialize + #crate_name::serde::de::DeserializeOwned + ::std::clone::Clone }); + field_names.push((*name).clone()); + domain_field_types.push(domain_gen); + } + + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + let serde_crate_path = format!("{}", quote!(#crate_name::serde)); + + let domain_definition_tokens = quote! { + #[derive(#crate_name::serde::Serialize, #crate_name::serde::Deserialize, ::std::fmt::Debug, ::std::clone::Clone)] + #[serde(crate = #serde_crate_path)] + struct #domain_struct_name #ty_generics { + #(#field_names : #domain_field_types),* + } + + impl #impl_generics #crate_name::domains::Domain for #domain_struct_name #ty_generics #where_clause { + type UserValue<#user_value_lifetime_generic> = #domain_struct_name <#(#user_value_domain_generics),*>; + type CorpusValue = #domain_struct_name <#(#corpus_domain_generics),*>; + + fn init(&self, rng: &mut dyn ::fuzztest::reexports::rand::Rng) -> ::fuzztest::reexports::anyhow::Result { + Ok(#domain_struct_name { + #(#field_names: self.#field_names.init(rng)?),* + }) + } + + fn mutate( + &self, + val: &mut Self::CorpusValue, + rng: &mut dyn ::fuzztest::reexports::rand::Rng, + only_shrink: bool, + ) -> ::fuzztest::reexports::anyhow::Result<()> { + #( self.#field_names.mutate(&mut val.#field_names, rng, only_shrink)?; )* + Ok(()) + } + + fn get_user_value<'a>(&self, corpus_value: &'a Self::CorpusValue) -> ::fuzztest::reexports::anyhow::Result> { + Ok(#domain_struct_name { + #(#field_names: self.#field_names.get_user_value(&corpus_value.#field_names)?),* + }) + } + } + }; + (domain_definition_tokens, field_names) +} + +#[cfg(test)] +mod tests { + use super::*; + use googletest::prelude::*; + + #[googletest::test] + fn test_generate_fuzztest_domain() { + let (domain_definition_tokens, field_names) = generate_fuzztest_domain( + "e::format_ident!("__FuzzTestTestFuzzStateWrapper"), + &[ + TestFnArgument { + name: "e::format_ident!("a"), + ty: parse_quote!(&'user Vec<&'user i32>), + }, + TestFnArgument { + name: "e::format_ident!("b"), + ty: parse_quote!(std::string::String), + }, + ], + &parse_quote!('user), + ); + expect_that!( + field_names, + elements_are![eq("e::format_ident!("a")), eq("e::format_ident!("b"))] + ); + expect_that!(domain_definition_tokens.to_string(), eq( "e! { + #[derive(::fuzztest::serde::Serialize, ::fuzztest::serde::Deserialize, ::std::fmt::Debug, ::std::clone::Clone)] + #[serde(crate = ":: fuzztest :: serde")] + struct __FuzzTestTestFuzzStateWrapper { + a: T0, + b: T1 + } + + impl ::fuzztest::domains::Domain for __FuzzTestTestFuzzStateWrapper + where for < 'user > T0: ::fuzztest::domains::Domain = &'user Vec<&'user i32> >, + T0::CorpusValue: ::fuzztest::serde::Serialize + ::fuzztest::serde::de::DeserializeOwned + ::std::clone::Clone, + for < 'user > T1: ::fuzztest::domains::Domain = std::string::String>, + T1::CorpusValue: ::fuzztest::serde::Serialize + ::fuzztest::serde::de::DeserializeOwned + ::std::clone::Clone { + type UserValue<'user> = __FuzzTestTestFuzzStateWrapper, T1::UserValue<'user> >; + type CorpusValue = __FuzzTestTestFuzzStateWrapper; + + fn init(&self, rng: &mut dyn ::fuzztest::reexports::rand::Rng) -> ::fuzztest::reexports::anyhow::Result { + Ok(__FuzzTestTestFuzzStateWrapper { + a: self.a.init(rng)?, + b: self.b.init(rng)? + }) + } + + fn mutate( + &self, + val: &mut Self::CorpusValue, + rng: &mut dyn ::fuzztest::reexports::rand::Rng, + only_shrink: bool, + ) -> ::fuzztest::reexports::anyhow::Result<()> { + self.a.mutate(&mut val.a, rng, only_shrink)?; + self.b.mutate(&mut val.b, rng, only_shrink)?; + Ok(()) + } + + fn get_user_value<'a>(&self, corpus_value: &'a Self::CorpusValue) -> ::fuzztest::reexports::anyhow::Result> { + Ok(__FuzzTestTestFuzzStateWrapper { + a: self.a.get_user_value(&corpus_value.a)?, + b: self.b.get_user_value(&corpus_value.b)? + }) + } + } + } + .to_string()) + ); + } +} diff --git a/rust/fuzztest_macro/src/helpers/test_registration.rs b/rust/fuzztest_macro/src/helpers/test_registration.rs new file mode 100644 index 000000000..d44614ccc --- /dev/null +++ b/rust/fuzztest_macro/src/helpers/test_registration.rs @@ -0,0 +1,378 @@ +use convert_case::Case; +use convert_case::Casing; +use proc_macro2::Span; +use proc_macro2::TokenStream; +use quote::quote; +use syn::parse_quote; +use syn::Expr; +use syn::Generics; +use syn::Ident; +use syn::Lifetime; +use syn::Signature; + +use super::fn_item_utils::extract_test_function_arguments; +use super::fn_item_utils::fn_sig_to_bare_type; +use super::fuzztest_domain::generate_fuzztest_domain; +use super::import_fuzztest_crate; +use super::TestFnArgument; + +const USER_VALUE_LIFETIME_GENERIC_NAME: &str = "'user"; + +#[derive(Clone, Debug)] +pub struct FuzzTestObjectDefinitionAndFactory { + pub fuzztest_object_factory_name: Ident, + pub fuzztest_object_tokenstream: TokenStream, +} + +#[derive(Clone, Debug)] +pub struct GTestDefinition { + pub gtest_tokens: TokenStream, +} + +/// A context struct holding the pre-computed information and token streams required +/// to generate the fuzz test registration, struct definitions, and integration with the test framework. +/// +/// This context is created once per property function. +pub struct FuzzTestRegistrationCtx<'a> { + test_fn_signature: &'a Signature, + test_fn_ident: &'a Ident, + prop_fn_ident: Ident, + test_fn_args: Vec>, + crate_name: TokenStream, + fuzz_test_domain_definition: TokenStream, + fuzz_test_domain_field_names: Vec, + fuzz_test_struct_instance_tokens: TokenStream, + fuzz_test_struct_factory_fn_name: Ident, +} + +impl<'a> FuzzTestRegistrationCtx<'a> { + /// Creates a new registration context from a property function signature and its domain constructors. + /// + /// This method analyzes the inputs, derives necessary identifiers and lifetime generics, and + /// pre-computes the tokenstream for the fuzz test struct instance. + pub fn new( + test_fn_signature: &'a Signature, + domain_ctors: impl IntoIterator, + ) -> syn::Result { + let user_value_lifetime_generic = + Lifetime::new(USER_VALUE_LIFETIME_GENERIC_NAME, Span::call_site()); + + let test_fn_args = + extract_test_function_arguments(test_fn_signature, &user_value_lifetime_generic)?; + if test_fn_args.is_empty() { + return Err(syn::Error::new_spanned( + test_fn_signature, + "fuzztest requires at least one argument for the property function", + )); + } + + let test_fn_generics = &test_fn_signature.generics; + if test_fn_generics.type_params().count() > 0 || test_fn_generics.const_params().count() > 0 + { + return Err(syn::Error::new_spanned( + test_fn_generics, + "Property function should not have any Type or Const generics", + )); + } + + let test_fn_ident = &test_fn_signature.ident; + let prop_fn_ident = quote::format_ident!("__property_fn__{}", test_fn_ident); + let domain_ctors = domain_ctors.into_iter().collect::>(); + let fuzz_test_struct_name = + quote::format_ident!("__FuzzTest{}", test_fn_ident.to_string().to_case(Case::Pascal)); + let crate_name = import_fuzztest_crate(); + + let domain_struct_name = quote::format_ident!( + "__FuzzTest{}StateWrapper", + test_fn_ident.to_string().to_case(Case::Pascal) + ); + + let (fuzz_test_domain_definition, fuzz_test_domain_field_names) = generate_fuzztest_domain( + &domain_struct_name, + &test_fn_args, + &user_value_lifetime_generic, + ); + + let fuzz_test_struct_instance_tokens = quote!( + #fuzz_test_struct_name { + domain: #domain_struct_name { + #(#fuzz_test_domain_field_names: #domain_ctors),* + }, + test_fn: #prop_fn_ident + } + ); + + let fuzz_test_struct_factory_fn_name = + quote::format_ident!("{}_factory", fuzz_test_struct_name); + + Ok(Self { + test_fn_signature, + test_fn_ident, + prop_fn_ident, + test_fn_args, + crate_name, + fuzz_test_domain_definition, + fuzz_test_domain_field_names, + fuzz_test_struct_instance_tokens, + fuzz_test_struct_factory_fn_name, + }) + } + + pub fn prop_fn_ident(&self) -> &Ident { + &self.prop_fn_ident + } + + /// Generates a GoogleTest-compatible test function that wraps the fuzz test. + /// + /// This allows the fuzz test to be executed as a regular tests. + pub fn generate_gtest(&self) -> syn::Result { + let gtest_name = &self.test_fn_ident; + let crate_name = &self.crate_name; + let test_fn_name = self.test_fn_ident.to_string(); + let fuzz_test_factory = &self.fuzz_test_struct_factory_fn_name; + + let tokens = quote!( + #[::googletest::prelude::gtest] + fn #gtest_name() { + // Note: In this setup, `module_path!()` returns a path that starts with the crate name + // (e.g., `crate_name::module::path`). We want to strip this prefix to make the + // test name similar to standard `libtest` paths. + let full_test_name = concat!(module_path!(), "::", #test_fn_name); + let test_name = full_test_name + .split_once("::") + .expect("Crate name is always the first path segment") + .1; + let manager = #crate_name::worker::RustFuzzTestAdapterManager { + test_name, + fuzz_test_factory: #fuzz_test_factory, + }; + #crate_name::worker::process(manager); + } + ); + + Ok(GTestDefinition { gtest_tokens: tokens }) + } + + /// Generates the test `struct` that will contain the state of the test: + /// - the domain of the property function inputs + /// - the property function itself + /// + /// The generated test `struct` object will implement the FuzzTest trait. + pub fn generate_test_registration(&self) -> syn::Result { + let crate_name = &self.crate_name; + let user_value_lifetime_generic = + Lifetime::new(USER_VALUE_LIFETIME_GENERIC_NAME, Span::call_site()); + let test_fn_args = &self.test_fn_args; + let test_fn_args_len = test_fn_args.len(); + + let test_fn_type = fn_sig_to_bare_type(self.test_fn_signature)?; + + let test_fn_ident = self.test_fn_ident; + let fuzz_test_struct_name = + quote::format_ident!("__FuzzTest{}", test_fn_ident.to_string().to_case(Case::Pascal)); + let domain_struct_name = quote::format_ident!( + "__FuzzTest{}StateWrapper", + test_fn_ident.to_string().to_case(Case::Pascal) + ); + let test_fn_name = test_fn_ident.to_string(); + let fuzz_test_struct_factory_fn_name = &self.fuzz_test_struct_factory_fn_name; + + let domain_generics = + (0..test_fn_args_len).map(|idx| quote::format_ident!("T{idx}")).collect::>(); + let corpus_generics = domain_generics + .iter() + .map(|generic| parse_quote!(#generic::CorpusValue)) + .collect::>(); + + let mut generics: Generics = parse_quote! { < #(#domain_generics),* > }; + + let test_fn_args_with_generics = test_fn_args + .iter() + .zip(generics.type_params().cloned()) + .zip(corpus_generics.iter()) + .collect::>(); + + let where_clauses = generics.make_where_clause(); + for ((TestFnArgument { ty, .. }, syn::TypeParam { ident: domain_gen, .. }), corpus_gen) in + &test_fn_args_with_generics + { + where_clauses.predicates.push( + parse_quote! { + for <#user_value_lifetime_generic> #domain_gen: #crate_name::domains::Domain = #ty > + }); + where_clauses.predicates.push(parse_quote! { #corpus_gen: 'static }); + } + + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + let fuzztest_instance = &self.fuzz_test_struct_instance_tokens; + let fuzz_test_domain_field_names = &self.fuzz_test_domain_field_names; + let fuzz_test_domain_definition = &self.fuzz_test_domain_definition; + + let fuzztest_struct_definition_and_factory = quote! { + #fuzz_test_domain_definition + + struct #fuzz_test_struct_name #generics { + domain: #domain_struct_name #generics, + test_fn: #test_fn_type + } + + impl #impl_generics #crate_name::internal::FuzzTest for #fuzz_test_struct_name #ty_generics #where_clause { + fn name(&self) -> &'static str { + #test_fn_name + } + fn activate(&mut self) { + todo!("Not implemented!") + } + fn mutate(&mut self) { + todo!("Not implemented!") + } + fn execute<'a>(&self, args: &'a #crate_name::domains::GenericCorpusValue) -> bool { + use #crate_name::domains::Domain; + + let wrapper = args + .downcast_ref::<#domain_struct_name<#(#corpus_generics),*>>() + .expect("Attempt to recover user value before testing failed."); + + let user_value = self.domain.get_user_value(wrapper).expect("Failed to get user value from corpus value"); + + let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| (self.test_fn)(#(user_value.#fuzz_test_domain_field_names),* ) )); + + result.is_ok() + } + fn print_finding_report(&self) { + todo!("Not implemented!") + } + fn domains(&self) -> &dyn #crate_name::domains::GenericDomain { + &self.domain + } + } + + fn #fuzz_test_struct_factory_fn_name() -> #crate_name::internal::BoxedFuzzTest { + ::std::boxed::Box::new(#fuzztest_instance) + } + }; + + Ok(FuzzTestObjectDefinitionAndFactory { + fuzztest_object_factory_name: fuzz_test_struct_factory_fn_name.clone(), + fuzztest_object_tokenstream: fuzztest_struct_definition_and_factory, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use googletest::matchers; + use googletest::prelude::*; + + #[googletest::test] + fn test_generate_test_registration() { + let sig: Signature = parse_quote! { fn test_fuzz(a: i32, b: std::string::String) }; + let expr1: Expr = + parse_quote! { ::fuzztest::domains::arbitrary::Arbitrary::::default() }; + let expr2: Expr = + parse_quote! { ::fuzztest::domains::arbitrary::Arbitrary::::default() }; + let context = FuzzTestRegistrationCtx::new(&sig, [&expr1, &expr2]).unwrap(); + let FuzzTestObjectDefinitionAndFactory { + fuzztest_object_factory_name, + fuzztest_object_tokenstream, + } = context.generate_test_registration().unwrap(); + expect_that!( + fuzztest_object_factory_name, + eq("e::format_ident!("__FuzzTestTestFuzz_factory")) + ); + expect_that!( + fuzztest_object_tokenstream.to_string(), ends_with( quote! { + struct __FuzzTestTestFuzz { + domain: __FuzzTestTestFuzzStateWrapper, + test_fn: fn(i32, std::string::String) + } + + impl ::fuzztest::internal::FuzzTest for __FuzzTestTestFuzz + where for <'user> T0: ::fuzztest::domains::Domain = i32>, + T0::CorpusValue: 'static, + for <'user> T1: ::fuzztest::domains::Domain = std::string::String>, + T1::CorpusValue: 'static { + fn name(&self) -> &'static str { + "test_fuzz" + } + fn activate(&mut self) { + todo!("Not implemented!") + } + fn mutate(&mut self) { + todo!("Not implemented!") + } + fn execute<'a>(&self, args: &'a ::fuzztest::domains::GenericCorpusValue) -> bool { + use ::fuzztest::domains::Domain; + + let wrapper = args + .downcast_ref::<__FuzzTestTestFuzzStateWrapper>() + .expect("Attempt to recover user value before testing failed."); + + let user_value = self.domain.get_user_value(wrapper).expect("Failed to get user value from corpus value"); + // Safety: Data is not reused after the test. + let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| (self.test_fn)(user_value.a, user_value.b) )); + + result.is_ok() + } + fn print_finding_report(&self) { + todo!("Not implemented!") + } + fn domains(&self) -> &dyn ::fuzztest::domains::GenericDomain { + &self.domain + } + } + + fn __FuzzTestTestFuzz_factory() -> ::fuzztest::internal::BoxedFuzzTest { + ::std::boxed::Box::new(__FuzzTestTestFuzz { + domain: __FuzzTestTestFuzzStateWrapper { + a: ::fuzztest::domains::arbitrary::Arbitrary::::default(), + b: ::fuzztest::domains::arbitrary::Arbitrary::::default() + }, + test_fn: __property_fn__test_fuzz + }) + } + } + .to_string()) + ); + } + + #[googletest::test] + fn test_generate_gtest() { + let sig: Signature = parse_quote! { fn test_fuzz(a: i32, b: std::string::String) }; + let expr1: Expr = + parse_quote! { ::fuzztest::domains::arbitrary::Arbitrary::::default() }; + let expr2: Expr = + parse_quote! { ::fuzztest::domains::arbitrary::Arbitrary::::default() }; + let context = FuzzTestRegistrationCtx::new(&sig, [&expr1, &expr2]).unwrap(); + let GTestDefinition { gtest_tokens } = context.generate_gtest().unwrap(); + + // test that the gtest was generated + expect_that!( + gtest_tokens.to_string(), + matchers::contains_substring( + quote! { + #[::googletest::prelude::gtest] + fn test_fuzz() + } + .to_string() + ) + ); + + // test that the test_name is the test path + expect_that!( + gtest_tokens.to_string(), + matchers::contains_substring( + quote! { + let full_test_name = concat!(module_path!(), "::", "test_fuzz"); + let test_name = full_test_name + .split_once("::") + .expect("Crate name is always the first path segment") + .1; + } + .to_string() + ) + ) + } +} diff --git a/rust/fuzztest_macro/src/lib.rs b/rust/fuzztest_macro/src/lib.rs new file mode 100644 index 000000000..19450ea66 --- /dev/null +++ b/rust/fuzztest_macro/src/lib.rs @@ -0,0 +1,126 @@ +/// Modules here cannot be public (ie: `pub`) because a proc-macro crate cannot export anything else +/// other than a proc-macro. That is to say, a function tagged with `#[proc_macro]`, +/// `#[proc_macro_attribute]`, `#[proc_macro_derive]` +mod helpers; + +use proc_macro::TokenStream; + +use quote::quote; +use syn::parse::{Parse, ParseStream, Parser}; +use syn::{parse_macro_input, Item}; + +use helpers::test_registration::FuzzTestObjectDefinitionAndFactory; +use helpers::test_registration::FuzzTestRegistrationCtx; +use helpers::test_registration::GTestDefinition; + +#[derive(Debug)] +struct FuzzTestArg { + _ident: syn::Ident, + _eq_token: syn::Token![=], + domain_ty: syn::Expr, +} + +impl Parse for FuzzTestArg { + fn parse(input: ParseStream) -> syn::Result { + Ok(Self { _ident: input.parse()?, _eq_token: input.parse()?, domain_ty: input.parse()? }) + } +} + +/// The `fuzztest` macro is used to generate and register a fuzz test object to be used by a fuzz +/// testing engine. +/// +/// The macro takes a list of arguments, where each argument is a domain object used to generate +/// values for the property function inputs. +/// +/// Example usage: +/// ```markdown +/// ``` +/// # use fuzztest::fuzztest; +/// # use fuzztest::domains::arbitrary::Arbitrary; +/// # use fuzztest::domains::range::InRange; +/// +/// #[fuzztest(a = Arbitrary::::default(), b = InRange::new(0, 10))] +/// fn fuzz_test_function(a: i32, b: i32) { +/// assert_eq!(a, b); +/// } +/// ``` +/// ``` +/// +#[proc_macro_attribute] +pub fn fuzztest(args: TokenStream, input: TokenStream) -> TokenStream { + let item = parse_macro_input!(input as Item); + + let syn::Item::Fn(mut test_fn) = item else { + return syn::Error::new_spanned(item, "not a function").into_compile_error().into(); + }; + + let original_ident = test_fn.sig.ident.clone(); + let fuzztest_name = original_ident.to_string(); + + // TODO(mathuxny-73): Support the case where there are no arguments. Probably we should return an error + // in this case as it may not make sense to have a FuzzTest without input. + let args = + syn::punctuated::Punctuated::::parse_terminated.parse(args); + + let args = match args { + Ok(args) => args, + Err(e) => return e.into_compile_error().into(), + }; + + let domain_ctors = args.iter().map(|arg| &arg.domain_ty); + + let fuzztest_registration_context = + match FuzzTestRegistrationCtx::new(&test_fn.sig, domain_ctors) { + Ok(fuzztest_registration_context) => fuzztest_registration_context, + Err(e) => return e.into_compile_error().into(), + }; + + let FuzzTestObjectDefinitionAndFactory { + fuzztest_object_factory_name, + fuzztest_object_tokenstream, + } = match fuzztest_registration_context.generate_test_registration() { + Ok(fuzztest_object) => fuzztest_object, + Err(e) => return e.into_compile_error().into(), + }; + + let GTestDefinition { gtest_tokens } = match fuzztest_registration_context.generate_gtest() { + Ok(gtest_tokens) => gtest_tokens, + Err(e) => return e.into_compile_error().into(), + }; + + test_fn.sig.ident = fuzztest_registration_context.prop_fn_ident().clone(); + + let fuzztest_mod_name = quote::format_ident!("__fuzztest_mod__{}", original_ident); + + // TODO(mathuxny-73): Extract the current module path from the invocation of the macro + // (ie: from `item.attrs`) + let tokens = quote! { + #[allow(non_snake_case)] + mod #fuzztest_mod_name { + use super::*; + + #test_fn + + #fuzztest_object_tokenstream + + const _: () = { + static FUZZTEST_INFO: ::fuzztest::internal::FuzzTestInfo = + ::fuzztest::internal::FuzzTestInfo { + name: #fuzztest_name, + file: file!(), + line: line!(), + }; + + ::fuzztest::reexports::inventory::submit! { + ::fuzztest::internal::FuzzTestRegistration { + info: &FUZZTEST_INFO, + factory: #fuzztest_object_factory_name + } + } + }; + + #gtest_tokens + } + }; + tokens.into() +} diff --git a/rust/fuzztest_macro/src/test_main.rs b/rust/fuzztest_macro/src/test_main.rs new file mode 100644 index 000000000..f01ede343 --- /dev/null +++ b/rust/fuzztest_macro/src/test_main.rs @@ -0,0 +1,4 @@ +// This file exists only as a workaround. It serves as the crate root for the `fuzztest_macro_test` +// BUILD target. +// This file should not be included in the `fuzztest_macro` crate. +mod helpers; diff --git a/rust/rust-toolchain.toml b/rust/rust-toolchain.toml new file mode 100644 index 000000000..0c3034f35 --- /dev/null +++ b/rust/rust-toolchain.toml @@ -0,0 +1,9 @@ +# This file is used to configure the Rust toolchain for FuzzTest. +# +# The channel is set to nightly as the fuzztest crate makes use of the compiler +# sanitizers, which are not yet part of the stable Rust toolchain. +# See https://doc.rust-lang.org/beta/unstable-book/compiler-flags/sanitizer.html +# +# See https://rust-lang.github.io/rustup/concepts/toolchains.html +[toolchain] +channel = "nightly" diff --git a/rust/src/crash_handler.rs b/rust/src/crash_handler.rs new file mode 100644 index 000000000..5dddf7018 --- /dev/null +++ b/rust/src/crash_handler.rs @@ -0,0 +1,38 @@ +#[cfg(any(sanitize = "address", sanitize = "memory"))] +mod callbacks { + use std::ffi::{c_char, CStr}; + + unsafe extern "C" { + pub safe fn __sanitizer_set_death_callback(callback: Option); + + pub safe fn __asan_get_report_description() -> *const c_char; + } + + pub extern "C" fn sanitizer_death_callback() { + use crate::worker; + + let (description, signature) = if cfg!(sanitize = "address") { + let char_ptr = __asan_get_report_description(); + // Safety: `ptr` points to a valid null terminated string. + let signature_cstr = unsafe { CStr::from_ptr(char_ptr) }; + ( + "Property function ran but address sanitizer caught a bug", + signature_cstr.to_str().unwrap_or("ASan crash"), + ) + } else { + ("Property function ran but a sanitizer caught a bug", "Sanitizer crash") + }; + worker::try_emit_finding(description, signature); + } +} + +/// Be able to emit failures before exiting fully from the process for non-unwinding panics and/or +/// unrecoverable crashes. +pub fn register_crash_handler() { + // TODO(yamilmorales): Consider allowing more sanitizers here, and find some other way to + // recognize sanitizers if this feature is not stabilized by the time we need to support Cargo. + #[cfg(any(sanitize = "address", sanitize = "memory"))] + { + callbacks::__sanitizer_set_death_callback(Some(callbacks::sanitizer_death_callback)); + } +} diff --git a/rust/src/domains.rs b/rust/src/domains.rs new file mode 100644 index 000000000..9180d0362 --- /dev/null +++ b/rust/src/domains.rs @@ -0,0 +1,230 @@ +pub mod arbitrary; +pub mod range; +pub mod tuple_of; +pub mod utility; + +use anyhow; +use anyhow::Context; + +use std::any::Any; + +pub trait CloneAny: Any { + fn clone_box(&self) -> Box; + fn as_any(&self) -> &dyn Any; + fn as_mut_any(&mut self) -> &mut dyn Any; +} + +impl dyn CloneAny { + /// Returns `true` if the underlying type is of type `T` + pub fn is(&self) -> bool { + self.as_any().is::() + } + + /// Returns a reference to the concrete value if it is of type `T`, or `None` otherwise. + pub fn downcast_ref(&self) -> Option<&T> { + self.as_any().downcast_ref::() + } + + /// Returns a mutable reference to the concrete value if it is of type `T`, or `None` otherwise. + pub fn downcast_mut(&mut self) -> Option<&mut T> { + self.as_mut_any().downcast_mut::() + } +} + +impl CloneAny for T { + fn clone_box(&self) -> Box { + // Prevent infinite recursion if T is a nested Box type. + if let Some(boxed) = self.as_any().downcast_ref::>() { + return (**boxed).clone_box(); + } + Box::new(self.clone()) + } + + fn as_any(&self) -> &dyn Any { + self + } + + fn as_mut_any(&mut self) -> &mut dyn Any { + self + } +} + +impl Clone for Box { + fn clone(&self) -> Self { + self.clone_box() + } +} + +/// Type alias for a type-erased corpus value. +pub type GenericCorpusValue = Box; + +/// Type alias for a type-erased user value. +pub type GenericUserValue = Box; + +/// A trait for types that represent a set of values that an input can take. +/// +/// Domain types are used to represent the domain of values that a fuzz test input can take +/// throughout the fuzzing process. +/// Domain types are used by the fuzzing engine through the APIs defined in this trait. +/// +/// ## Serialization/Deserialization of CorpusValue and UserValue relationship. +/// +/// The values drawn from a domain are always of type `CorpusValue`. These values are the +/// internal representation of the value and they are not directly usable by the fuzz property +/// function. +/// +/// The `get_user_value` method is used to retrieve the user value from the corpus value. For +/// example, if the domain outputs an `&str` then the CorpusValue could be a `String` and the +/// `get_user_value` method would be used to retrieve the `&str` from the `String`. +/// +/// The `parse_corpus` (resp. `serialize_corpus`) method is used deserialize (resp. serialize) +/// the corpus value from (to) a slice of bytes (resp. a vector of bytes). +/// +/// ### Relationship diagram +/// +/// The methods below are responsible for transforming between `UserValue`, `CorpusValue`, and the +/// serialized representation of `CorpusValue`. Here's a quick overview: +/// +/// ```text +/// +-- get_user_value() <---+ +-- parse_corpus() <---+ +/// | | | | +/// v | v | +/// UserValue<'a> CorpusValue &[u8] +/// | ^ +/// | | +/// +-> serialize_corpus() + +/// ``` +pub trait Domain { + /// The type of the values that the domain outputs. This should of the same type as the + /// parameter of the fuzz property function. + type UserValue<'user>; + /// The type of the corpus from which the values of type UserValue are drawn. + /// For example, if the domain should output an `&str` then the UserValue should be `&str` and + /// the CorpusValue could the owned data structured that the `&str` points to (eg: String). + /// The CorpusValue type should implement `serde::Serialize`, `serde::de::DeserializeOwned` and + /// `Clone`. + type CorpusValue: ::serde::Serialize + ::serde::de::DeserializeOwned + Clone; + + /// Initializes a new value drawn from the domain. + fn init(&self, rng: &mut dyn rand::Rng) -> anyhow::Result; + + /// Mutates the value in `val` to a new value drawn from the domain. + /// + /// If `only_shrink` is `true`, then the mutation must not increase the size of the corpus + /// value. Otherwise, the mutation can both shrink and grow the corpus value. + fn mutate( + &self, + val: &mut Self::CorpusValue, + rng: &mut dyn rand::Rng, + only_shrink: bool, + ) -> anyhow::Result<()>; + + /// Retrieves a UserValue from a given CorpusValue. + /// + /// This is used to convert the corpus value into the user value that can then be passed to the + /// fuzz property function. + fn get_user_value<'a>( + &self, + corpus_value: &'a Self::CorpusValue, + ) -> anyhow::Result>; + + /// Turns a slice of bytes into `CorpusValue`. + /// + /// By default, it uses Postcard to deserialize the data. + fn parse_corpus(&self, data: &[u8]) -> anyhow::Result { + postcard::from_bytes(data).context("Failed to deserialize corpus value from bytes") + } + + /// Serializes `corpus_value` to a Vec of bytes (ie, Vec). + /// + /// By default, it uses Postcard to serialize the data. + fn serialize_corpus(&self, corpus_value: &Self::CorpusValue) -> anyhow::Result> { + postcard::to_stdvec(corpus_value).context("Failed to serialize corpus value to bytes") + } +} + +/// A type-erased interface for Domain types. +/// +/// This trait is used to expose a common interface for Domain types to the fuzzing engine through +/// the `FuzzTest` trait. +/// The methods of this trait are similar to that of the `Domain` trait, but they operate on +/// a type-erased `GenericCorpusValue` instead of an explicit type. +pub trait GenericDomain { + /// Initializes a new value drawn from the domain. + /// + /// See `Domain::init` for more details. + fn init(&self, rng: &mut dyn rand::Rng) -> anyhow::Result; + + /// Mutates the value in `val` to a new value drawn from the domain. + /// + /// See `Domain::mutate` for more details. + fn mutate( + &self, + val: &mut GenericCorpusValue, + rng: &mut dyn rand::Rng, + only_shrink: bool, + ) -> anyhow::Result<()>; + + /// Parses a slice of bytes into a `GenericCorpusValue`. + /// + /// See `Domain::parse_corpus` for more details. + fn parse_corpus(&self, data: &[u8]) -> anyhow::Result; + + /// Serializes a `GenericCorpusValue` to a Vec of bytes (ie, Vec). + /// + /// See `Domain::serialize_corpus` for more details. + fn serialize_corpus(&self, val: &GenericCorpusValue) -> anyhow::Result>; +} + +/// Blanket implementation of the `GenericDomain` trait for types implementing the `Domain` trait. +/// +/// This implementation is a simple pass-through implementation that uses the methods of the +/// `Domain` trait to implement the `GenericDomain` trait. +impl GenericDomain for D +where + D: Domain, + D::CorpusValue: 'static, +{ + fn init(&self, rng: &mut dyn rand::Rng) -> anyhow::Result { + Ok(Box::new(self.init(rng)?)) + } + + /// Retrieves the underlying `Domain`'s `CorpusValue` from the `GenericCorpusValue` and calls + /// the underlying `Domain::mutate` method. + /// + /// If the `GenericCorpusValue` cannot be downcasted to the underlying `Domain`'s `CorpusValue` + /// then an Error is returned. + /// + /// See `GenericDomain::mutate` for more details. + fn mutate( + &self, + val: &mut GenericCorpusValue, + rng: &mut dyn rand::Rng, + only_shrink: bool, + ) -> anyhow::Result<()> { + self.mutate( + val.downcast_mut().context("Failed to retrieve the Corpus Value")?, + rng, + only_shrink, + ) + } + + /// Calls the underlying `Domain::parse_corpus` method with the `data` and wraps the resulting + /// `CorpusValue` in a `GenericCorpusValue`. + /// + /// See `GenericDomain::parse_corpus` for more details. + fn parse_corpus(&self, data: &[u8]) -> anyhow::Result { + Ok(Box::new(self.parse_corpus(data)?)) + } + + /// Retrieves the underlying `Domain`'s `CorpusValue` from the `GenericCorpusValue` and calls + /// the underlying `Domain::serialize_corpus` method. + /// + /// If the `GenericCorpusValue` cannot be downcasted to the underlying `Domain`'s `CorpusValue` + /// then an Error is returned. + /// + /// See `GenericDomain::serialize_corpus` for more details. + fn serialize_corpus(&self, val: &GenericCorpusValue) -> anyhow::Result> { + self.serialize_corpus(val.downcast_ref().context("Failed to retrieve the Corpus Value")?) + } +} diff --git a/rust/src/domains/arbitrary.rs b/rust/src/domains/arbitrary.rs new file mode 100644 index 000000000..07d7db580 --- /dev/null +++ b/rust/src/domains/arbitrary.rs @@ -0,0 +1,763 @@ +use super::utility::choose_value; +use super::utility::mutate_integer; +use super::utility::shrink_towards; +use super::Domain; + +use anyhow; +use rand::RngExt; + +/// Generates arbitrary values of type `T`. +/// +/// This domain is used to generate values of a specific type without any constraints +/// beyond the type's intrinsic range. For example, `Arbitrary` generates `i32` +/// values spanning the whole `i32` domain `[i32::MIN, i32::MAX]`. +/// +/// Example usage: +/// ``` +/// # use fuzztest::domains::Domain; +/// # use fuzztest::domains::arbitrary::Arbitrary; +/// # use rand::rngs::SmallRng; +/// # use rand::SeedableRng; +/// +/// let arbitrary_i32 = Arbitrary::::default(); +/// let mut rng = SmallRng::seed_from_u64(73); +/// +/// let sample = arbitrary_i32.init(&mut rng); +/// assert!(sample.is_ok()); +/// ``` + +pub struct Arbitrary { + _phantom: std::marker::PhantomData, +} + +// We cannot just use `#[derive(Default)]` because `T` might not be `Default`. +impl Default for Arbitrary { + fn default() -> Self { + Self { _phantom: std::marker::PhantomData } + } +} + +impl Arbitrary { + /// Creates a new `Arbitrary` domain for the given type `T`. + pub fn new() -> Self { + Self { _phantom: std::marker::PhantomData } + } +} + +impl Domain for Arbitrary { + type UserValue<'user> = bool; + type CorpusValue = bool; + + fn init(&self, rng: &mut dyn rand::Rng) -> anyhow::Result { + Ok(rng.random()) + } + + fn mutate( + &self, + val: &mut Self::CorpusValue, + rng: &mut dyn rand::Rng, + only_shrink: bool, + ) -> anyhow::Result<()> { + if only_shrink { + // Convention taken from the C++ fuzztest library. + *val = false; + } else { + *val = rng.random(); + } + Ok(()) + } + + fn get_user_value<'a>( + &self, + corpus_value: &'a Self::CorpusValue, + ) -> anyhow::Result> { + Ok(*corpus_value) + } +} + +macro_rules! impl_domain_for_integer { + // We need an additional "integer type" for this, because + // rand::distr::Distribution<> is not implemented for StandardUniform for isize and usize. + // so we perform the generation using integer types and cast to the size types if needed. + ($ty:ty, $int_ty:ty) => { + impl Domain for Arbitrary<$ty> { + type UserValue<'user> = $ty; + type CorpusValue = $ty; + + fn init(&self, rng: &mut dyn rand::Rng) -> anyhow::Result { + // We generate a the equivalent integer type so this works for size types. + let val: $int_ty = choose_value(rng); + Ok(val as $ty) + } + + fn mutate( + &self, + val: &mut Self::CorpusValue, + rng: &mut dyn rand::Rng, + only_shrink: bool, + ) -> anyhow::Result<()> { + if only_shrink { + *val = shrink_towards(rng, *val as $int_ty, 0) as $ty; + } else { + let original_val = *val; + loop { + *val = mutate_integer(rng, *val as $int_ty, 5, None, None) as $ty; + if *val != original_val { + break; + } + } + } + Ok(()) + } + + fn get_user_value<'a>( + &self, + corpus_value: &'a Self::CorpusValue, + ) -> anyhow::Result> { + Ok(*corpus_value) + } + } + }; +} + +impl_domain_for_integer!(i8, i8); +impl_domain_for_integer!(u8, u8); +impl_domain_for_integer!(i16, i16); +impl_domain_for_integer!(u16, u16); +impl_domain_for_integer!(i32, i32); +impl_domain_for_integer!(u32, u32); +impl_domain_for_integer!(i64, i64); +impl_domain_for_integer!(u64, u64); +impl_domain_for_integer!(i128, i128); +impl_domain_for_integer!(u128, u128); +impl_domain_for_integer!(isize, i64); +impl_domain_for_integer!(usize, u64); + +macro_rules! impl_domain_for_float { + ($ty:ty) => { + impl Domain for Arbitrary<$ty> { + type UserValue<'user> = $ty; + type CorpusValue = $ty; + + fn init(&self, rng: &mut dyn rand::Rng) -> anyhow::Result { + Ok(choose_value(rng)) + } + + fn mutate( + &self, + val: &mut Self::CorpusValue, + rng: &mut dyn rand::Rng, + only_shrink: bool, + ) -> anyhow::Result<()> { + if only_shrink { + // Shrink only finite, non-terminal values. + if val.is_finite() && *val != 0.0 { + *val = shrink_towards(rng, *val, 0.0); + } + return Ok(()); + } + // Non-shrinking mutation. + let original_val = *val; + loop { + // Loop to ensure mutation + if !val.is_finite() { + // NaN or Infinity + *val = self.init(rng)?; + } else { + match rng.random_range(0..4) { + 0 => *val /= 2.0, + 1 => *val = -*val, + 2 => *val += 1.0, + 3 => *val *= 3.0, + _ => unreachable!(), + } + } + + if (val.is_nan() && original_val.is_nan()) || *val == original_val { + continue; + } + break; + } + Ok(()) + } + + fn get_user_value<'a>( + &self, + corpus_value: &'a Self::CorpusValue, + ) -> anyhow::Result> { + Ok(*corpus_value) + } + } + }; +} + +impl_domain_for_float!(f32); +impl_domain_for_float!(f64); + +const SURROGATE_START: u32 = 0xD800; +const SURROGATE_END: u32 = 0xDFFF; +const MAX_VALID_CODEPOINT: u32 = 0x10FFFF; +const NUM_VALID_CODEPOINTS: u32 = MAX_VALID_CODEPOINT - (SURROGATE_END - SURROGATE_START); + +/// Maps a `char` to a `u32` in a contiguous range. +/// +/// Rust's `char` type represents a Unicode Scalar Value, which means it cannot be a +/// surrogate code point (U+D800 to U+DFFF). This function maps `char` values to a +/// contiguous integer range by shifting down the code points that are above the +/// surrogate range. This is useful for mutation strategies like random walks over +/// a dense integer space. +fn map_char_to_int(c: char) -> u32 { + let val = c as u32; + if val >= SURROGATE_START { + val - (SURROGATE_END - SURROGATE_START + 1) + } else { + val + } +} + +/// Maps a `u32` from the contiguous range back to a `char`. +/// +/// This is the inverse of `map_char_to_int`. It converts an integer from the +/// dense range back to a `char`. Integers that fall within the range previously +/// occupied by surrogates are shifted up to their correct code points. +fn map_int_to_char(u: u32) -> char { + assert!( + u < NUM_VALID_CODEPOINTS, + "input to map_int_to_char out of range: got {}, expected value < {}", + u, + NUM_VALID_CODEPOINTS + ); + let val = if u >= SURROGATE_START { u + (SURROGATE_END - SURROGATE_START + 1) } else { u }; + std::char::from_u32(val).unwrap() +} + +impl Domain for Arbitrary { + type UserValue<'user> = char; + type CorpusValue = char; + + fn init(&self, rng: &mut dyn rand::Rng) -> anyhow::Result { + Ok(choose_value(rng)) + } + + fn mutate( + &self, + val: &mut Self::CorpusValue, + rng: &mut dyn rand::Rng, + only_shrink: bool, + ) -> anyhow::Result<()> { + if only_shrink { + // Shrink towards the null character (0 as char). + *val = shrink_towards(rng, *val, 0 as char); + } else { + let original_val = *val; + loop { + // Map the current char to a u32 in a contiguous range. + let mapped_val = map_char_to_int(*val); + // Mutate the u32 value within the valid range of mapped code points. + let mutated_mapped_val = + mutate_integer(rng, mapped_val, 5, Some(0), Some(NUM_VALID_CODEPOINTS - 1)); + // Map the mutated u32 back to a char. + *val = map_int_to_char(mutated_mapped_val); + if *val != original_val { + break; + } + } + } + Ok(()) + } + + fn get_user_value<'a>( + &self, + corpus_value: &'a Self::CorpusValue, + ) -> anyhow::Result> { + Ok(*corpus_value) + } +} + +impl Domain for Arbitrary<()> { + type UserValue<'user> = (); + type CorpusValue = (); + + fn init(&self, _rng: &mut dyn rand::Rng) -> anyhow::Result { + Ok(()) + } + + fn mutate( + &self, + _val: &mut Self::CorpusValue, + _rng: &mut dyn rand::Rng, + _only_shrink: bool, + ) -> anyhow::Result<()> { + // No possible mutation for the unit type + Ok(()) + } + + fn get_user_value<'a>( + &self, + _corpus_value: &'a Self::CorpusValue, + ) -> anyhow::Result> { + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use std::collections::HashSet; + + use crate::domains::utility::SpecialValues; + use num_traits::{Float, One, Zero}; + use rand::distr::uniform::SampleUniform; + use rand::distr::{Distribution, StandardUniform}; + use rand::rngs::{SmallRng, SysRng}; + use rand::SeedableRng; + + use crate::domains::Domain; + + use super::*; + + type CorpusValueForArbitrary = as Domain>::CorpusValue; + + fn get_rng() -> SmallRng { + SmallRng::try_from_rng(&mut SysRng).unwrap() + } + + /// Trait to be able to treat numerical types uniformly w.r.t. finiteness and shrinking target. + trait NumTraitsExtended { + fn is_finite_value(&self) -> bool; + fn is_at_shrink_target(&self) -> bool; + } + + macro_rules! impl_num_traits_extended_for_floats { + ($($ty:ty),*) => { + $( + impl NumTraitsExtended for $ty { + fn is_finite_value(&self) -> bool { + self.is_finite() + } + fn is_at_shrink_target(&self) -> bool { + // We arbitrarily use EPSILON^2 as the accuracy margin for approximate equality. + self.abs() < <$ty>::EPSILON * <$ty>::EPSILON + } + } + )* + }; + } + + impl_num_traits_extended_for_floats!(f32, f64); + + macro_rules! impl_num_traits_extended_for_non_floats { + ($($ty:ty),*) => { + $( + impl NumTraitsExtended for $ty { + fn is_finite_value(&self) -> bool { + true + } + fn is_at_shrink_target(&self) -> bool { + *self == <$ty>::default() + } + } + )* + }; + } + + impl_num_traits_extended_for_non_floats!( + i8, + i16, + i32, + i64, + i128, + isize, + u8, + u16, + u32, + u64, + u128, + usize, + bool, + char, + () + ); + + fn test_arbitrary_mutations_change_value() + where + for<'user> Arbitrary: Domain = T>, + CorpusValueForArbitrary: + std::fmt::Debug + Default + Clone + Copy + PartialOrd + PartialEq + 'static, + { + let domain = Arbitrary::::default(); + let mut rng = get_rng(); + let mut value = domain.init(&mut rng).unwrap(); + + // Mutation is guaranteed to change the value. + for _ in 0..100 { + let original_value = value; + domain.mutate(&mut value, &mut rng, false).unwrap(); + assert_ne!( + value, + original_value, + "Mutation did not change the value for type {}", + std::any::type_name::() + ); + } + } + + fn test_arbitrary_mutations_generates_diverse_values() + where + for<'user> Arbitrary: Domain = T>, + CorpusValueForArbitrary: std::fmt::Debug + + Default + + Clone + + Copy + + PartialOrd + + PartialEq + + Eq + + std::hash::Hash + + 'static, + { + let domain = Arbitrary::::default(); + let mut rng = get_rng(); + for _ in 0..100 { + let mut value = domain.init(&mut rng).unwrap(); + let mut values = HashSet::new(); + values.insert(value); + + for _ in 0..100 { + domain.mutate(&mut value, &mut rng, false).unwrap(); + values.insert(value); + } + assert!( + values.len() >= 50, + "Mutation did not generate diverse values for type {}: found {} distinct values, expected >= 50", + std::any::type_name::(), + values.len() + ); + } + } + + fn test_arbitrary_shrinking_reduces_and_terminates() + where + for<'user> Arbitrary: Domain = T>, + CorpusValueForArbitrary: std::fmt::Debug + + Default + + Clone + + Copy + + PartialOrd + + PartialEq + + NumTraitsExtended + + 'static, + { + let domain = Arbitrary::::default(); + let mut rng = get_rng(); + + // Get a value that is not the shrink target + let shrink_target = CorpusValueForArbitrary::::default(); + let mut value; + loop { + value = domain.init(&mut rng).unwrap(); + if !value.is_at_shrink_target() && value.is_finite_value() { + break; + } + } + + for _ in 0..1000 { + let prev_value = value; + domain.mutate(&mut value, &mut rng, true).unwrap(); + + if value.is_at_shrink_target() { + // Ensure that once the shrink target is reached, further shrinking doesn't change it. + domain.mutate(&mut value, &mut rng, true).unwrap(); + assert!( + value.is_at_shrink_target(), + "Shrinking past the target changed the value for type {}", + std::any::type_name::() + ); + return; + } + + // Check progress + if prev_value > shrink_target { + assert!( + value < prev_value, + "Value should decrease when shrinking towards target from above. Type: {}, Prev: {:?}, Current: {:?}", + std::any::type_name::(), + prev_value, + value + ); + assert!( + value >= shrink_target, + "Value should not shrink past the target from above. Type: {}, Prev: {:?}, Current: {:?}, Target: {:?}", + std::any::type_name::(), + prev_value, + value, + shrink_target + ); + } else { + // prev_value < shrink_target + assert!( + value > prev_value, + "Value should increase when shrinking towards target from below. Type: {}, Prev: {:?}, Current: {:?}", + std::any::type_name::(), + prev_value, + value + ); + assert!( + value <= shrink_target, + "Value should not shrink past the target from below. Type: {}, Prev: {:?}, Current: {:?}, Target: {:?}", + std::any::type_name::(), + prev_value, + value, + shrink_target + ); + } + } + + panic!( + "Shrinking did not terminate in 1000 iterations for type {}", + std::any::type_name::() + ); + } + + fn test_arbitrary() + where + for<'user> Arbitrary: Domain = T>, + CorpusValueForArbitrary: std::fmt::Debug + + Default + + Clone + + Copy + + PartialOrd + + PartialEq + + Eq + + std::hash::Hash + + NumTraitsExtended + + 'static, + { + test_arbitrary_mutations_change_value::(); + test_arbitrary_mutations_generates_diverse_values::(); + test_arbitrary_shrinking_reduces_and_terminates::(); + } + + #[test] + fn test_i8() { + test_arbitrary::(); + } + #[test] + fn test_u8() { + test_arbitrary::(); + } + #[test] + fn test_i16() { + test_arbitrary::(); + } + #[test] + fn test_u16() { + test_arbitrary::(); + } + #[test] + fn test_i32() { + test_arbitrary::(); + } + #[test] + fn test_u32() { + test_arbitrary::(); + } + #[test] + fn test_i64() { + test_arbitrary::(); + } + #[test] + fn test_u64() { + test_arbitrary::(); + } + #[test] + fn test_i128() { + test_arbitrary::(); + } + #[test] + fn test_u128() { + test_arbitrary::(); + } + #[test] + fn test_isize() { + test_arbitrary::(); + } + #[test] + fn test_usize() { + test_arbitrary::(); + } + + #[test] + fn test_char() { + test_arbitrary::(); + } + + fn test_bool_shrink() { + let domain = Arbitrary::::default(); + let mut rng = get_rng(); + let mut value = true; + domain.mutate(&mut value, &mut rng, true).unwrap(); + assert_eq!(value, false); + domain.mutate(&mut value, &mut rng, true).unwrap(); + assert_eq!(value, false); + } + + #[test] + fn test_bool() { + test_arbitrary_shrinking_reduces_and_terminates::(); + test_bool_shrink(); + } + #[test] + fn test_unit() { + let mut rng = get_rng(); + let domain = Arbitrary::<()>::default(); + + // init() always returns () + assert_eq!(domain.init(&mut rng).unwrap(), ()); + + // mutate (non-shrinking) always returns () + let mut value = (); + domain.mutate(&mut value, &mut rng, false).unwrap(); + assert_eq!(value, ()); + + // mutate (shrinking) always returns () + let mut value = (); + domain.mutate(&mut value, &mut rng, true).unwrap(); + assert_eq!(value, ()); + } + + fn test_float_special_value_shrink() + where + for<'user> Arbitrary: Domain = T>, + CorpusValueForArbitrary: + Float + SampleUniform + std::fmt::Display + std::fmt::Debug + SpecialValues + 'static, + StandardUniform: Distribution, + { + let domain = Arbitrary::::default(); + let mut rng = get_rng(); + + // Positive. + let mut v = CorpusValueForArbitrary::::one(); + let original_v = v; + domain.mutate(&mut v, &mut rng, true).unwrap(); + assert!( + v >= CorpusValueForArbitrary::::zero() && v < original_v, + "Shrinking {} to {}", + original_v, + v + ); + + // Negative. + let mut v = -CorpusValueForArbitrary::::one(); + let original_v = v; + domain.mutate(&mut v, &mut rng, true).unwrap(); + assert!( + v > original_v && v <= CorpusValueForArbitrary::::zero(), + "Shrinking {} to {}", + original_v, + v + ); + + // Zero. + let mut v = CorpusValueForArbitrary::::zero(); + domain.mutate(&mut v, &mut rng, true).unwrap(); + assert_eq!(v, CorpusValueForArbitrary::::zero()); + + let mut v = CorpusValueForArbitrary::::neg_zero(); + domain.mutate(&mut v, &mut rng, true).unwrap(); + assert_eq!(v, CorpusValueForArbitrary::::neg_zero()); + + // NaN / Inf are not changed by this shrink implementation. + let mut v = CorpusValueForArbitrary::::nan(); + domain.mutate(&mut v, &mut rng, true).unwrap(); + assert!(v.is_nan(), "NaN should remain NaN"); + + let mut v = CorpusValueForArbitrary::::infinity(); + domain.mutate(&mut v, &mut rng, true).unwrap(); + assert!(v.is_infinite() && v.is_sign_positive(), "Infinity should remain Infinity"); + + let mut v = CorpusValueForArbitrary::::neg_infinity(); + domain.mutate(&mut v, &mut rng, true).unwrap(); + assert!(v.is_infinite() && v.is_sign_negative(), "Neg Infinity should remain Neg Infinity"); + } + + #[test] + fn test_f32() { + test_arbitrary_mutations_change_value::(); + test_arbitrary_shrinking_reduces_and_terminates::(); + test_float_special_value_shrink::(); + } + + #[test] + fn test_f64() { + test_arbitrary_mutations_change_value::(); + test_arbitrary_shrinking_reduces_and_terminates::(); + test_float_special_value_shrink::(); + } + + #[test] + fn test_char_mutate_boundaries() { + let domain = Arbitrary::::default(); + let mut rng = get_rng(); + let mut val = '\u{0000}'; + domain.mutate(&mut val, &mut rng, false).unwrap(); + assert!(val >= '\u{0000}'); + + val = '\u{10FFFF}'; + domain.mutate(&mut val, &mut rng, false).unwrap(); + assert!(val <= '\u{10FFFF}'); + + val = '\u{D7FF}'; // Before surrogate + domain.mutate(&mut val, &mut rng, false).unwrap(); + + val = '\u{E000}'; // After surrogate + domain.mutate(&mut val, &mut rng, false).unwrap(); + } + + #[test] + fn test_char_mapping() { + // Test boundary conditions and values around the surrogate range + assert_eq!(map_char_to_int('\u{0000}'), 0); + assert_eq!(map_int_to_char(0), '\u{0000}'); + + let before_surrogate = SURROGATE_START - 1; + assert_eq!( + map_char_to_int(std::char::from_u32(before_surrogate).unwrap()), + before_surrogate + ); + assert_eq!( + map_int_to_char(before_surrogate), + std::char::from_u32(before_surrogate).unwrap() + ); + + let after_surrogate = SURROGATE_END + 1; + let mapped_after_surrogate = map_char_to_int(std::char::from_u32(after_surrogate).unwrap()); + assert_eq!(mapped_after_surrogate, SURROGATE_START); + assert_eq!(map_int_to_char(SURROGATE_START), std::char::from_u32(after_surrogate).unwrap()); + + assert_eq!(map_char_to_int('\u{10FFFF}'), NUM_VALID_CODEPOINTS - 1); + assert_eq!(map_int_to_char(NUM_VALID_CODEPOINTS - 1), '\u{10FFFF}'); + } + + #[test] + fn test_char_shrink_to_null() { + for _ in 0..10 { + let domain = Arbitrary::::default(); + let mut rng = get_rng(); + let mut value = domain.init(&mut rng).unwrap(); + + let mut iterations = 0; + const MAX_ITERATIONS: u32 = 100_000; + + while value != '\0' && iterations < MAX_ITERATIONS { + domain.mutate(&mut value, &mut rng, true).unwrap(); + // Ensure that the value is always a valid char after mutation. + assert!(std::char::from_u32(value as u32).is_some()); + iterations += 1; + } + assert_eq!( + value, '\0', + "Char did not shrink to null within {} iterations. Final value: {}", + MAX_ITERATIONS, value + ); + } + } +} diff --git a/rust/src/domains/range.rs b/rust/src/domains/range.rs new file mode 100644 index 000000000..fd44e20e6 --- /dev/null +++ b/rust/src/domains/range.rs @@ -0,0 +1,85 @@ +use super::Domain; + +use anyhow; +use rand::distr::uniform::SampleUniform; +use rand::distr::uniform::UniformSampler; + +/// Generates values of type `T` in a given range. +/// +/// For example, `InRange::new(0, 100)` generates integer +/// values from the inclusive range `[0, 100]`. +/// +/// Example usage: +/// ``` +/// # use fuzztest::domains::Domain; +/// # use fuzztest::domains::range::InRange; +/// # use rand::prelude::*; +/// +/// let range_i32 = InRange::new(21i32, 73); +/// let sample = range_i32.init(&mut rand::rng()); +/// +/// assert!(sample.is_ok()); +/// let sample = sample.unwrap(); +/// assert!(sample >= 21); +/// assert!(sample <= 73); +/// ``` +pub struct InRange { + lower: T, + upper: T, +} + +impl InRange { + pub fn new(lower: i32, upper: i32) -> Self { + Self { lower, upper } + } + + pub fn get_in_range(&self, rng: &mut dyn rand::Rng) -> i32 { + ::Sampler::sample_single(self.lower, self.upper, rng) + .expect("Failed to sample from Uniform distribution") + } +} + +impl Domain for InRange { + type UserValue<'user> = i32; + type CorpusValue = i32; + + fn init(&self, rng: &mut dyn rand::Rng) -> anyhow::Result { + Ok(self.get_in_range(rng)) + } + + fn mutate( + &self, + val: &mut Self::CorpusValue, + rng: &mut dyn rand::Rng, + only_shrink: bool, + ) -> anyhow::Result<()> { + if only_shrink { + *val -= 1; + } else { + *val = self.get_in_range(rng); + } + Ok(()) + } + + fn get_user_value<'a>( + &self, + corpus_value: &'a Self::CorpusValue, + ) -> anyhow::Result> { + Ok(*corpus_value) + } +} + +#[cfg(test)] +mod tests { + use rand::SeedableRng; + + use super::*; + + #[test] + fn test_in_range() { + let in_range = InRange::::new(0, 100); + let mut rng = rand::rngs::StdRng::from_seed([73; 32]); + let in_range_val = in_range.get_in_range(&mut rng); + assert!(in_range_val >= 0 && in_range_val <= 100); + } +} diff --git a/rust/src/domains/tuple_of.rs b/rust/src/domains/tuple_of.rs new file mode 100644 index 000000000..ac639645a --- /dev/null +++ b/rust/src/domains/tuple_of.rs @@ -0,0 +1,6 @@ +// TODO(yamilmorales, tinmar, mathuxny-73): Replace generated wrapper domain (to deduplicate +// implementations for tests with the same number of parameters) with these, meaning we should +// `impl Domain` on these tuples. + +pub type TupleOf1 = (T0,); +pub type TupleOf2 = (T0, T1); diff --git a/rust/src/domains/utility.rs b/rust/src/domains/utility.rs new file mode 100644 index 000000000..2d63441cc --- /dev/null +++ b/rust/src/domains/utility.rs @@ -0,0 +1,404 @@ +use std::cmp::Ordering; + +use num_traits::PrimInt; +use rand::distr::uniform::SampleUniform; +use rand::distr::{Distribution, StandardUniform}; +use rand::RngExt; + +/// Shrinks a `val` towards a `target` value. +/// +/// The new value is chosen uniformly from the range between `val` and `target`. +/// - If `val > target`, the range is `[target, val)`. +/// - If `val < target`, the range is `(val, target]`. +/// - If `val == target`, `val` is returned. +/// +/// # Arguments +/// * `rng`: Random number generator. +/// * `val`: The current value to shrink. +/// * `target`: The value to shrink towards. +pub fn shrink_towards(rng: &mut R, val: T, target: T) -> T +where + T: SampleUniform + PartialOrd + Copy + std::fmt::Display, +{ + match val.partial_cmp(&target) { + Some(Ordering::Equal) => val, + Some(Ordering::Less) => { + // val < target + // Sample from (val, target]. + // To do that, we sample from [val, target) and return target if we draw val. + let sample = rng.random_range(val..target); + if sample == val { + target + } else { + sample + } + } + Some(Ordering::Greater) => { + // val > target + // Sample from [target, val). + rng.random_range(target..val) + } + None => val, // Cannot compare, return original + } +} + +/// Mutates an integer value using one of several strategies. +/// +/// The mutation strategies are: +/// 1. Random Walk: Add/subtract a small value within the given `range`. +/// 2. Bit Flip: Flip a single random bit. +/// 3. Random Choice: Replace with a new completely random value. +/// +/// # Arguments +/// * `rng`: Random number generator. +/// * `val`: The integer value to mutate. +/// * `range`: The maximum +/- delta for the random walk. +/// * `min_value`: Optional minimum boundary for mutation. If `None`, `T::min_value()` is used. +/// * `max_value`: Optional maximum boundary for mutation. If `None`, `T::max_value()` is used. +// REQUIRES: range > 0 +// REQUIRES: min_value <= max_value (after unwrapping) +// TODO(b/405382579): Add dictionary-based mutation strategies similar to C++ implementation +// (See `fuzztest/internal/domains/value_mutation_helpers.h`) +pub fn mutate_integer( + rng: &mut R, + val: T, + range: T, + min_value: Option, + max_value: Option, +) -> T +where + T: PrimInt + SampleUniform + std::fmt::Display, +{ + assert!(range > T::zero(), "mutate_integer: range value cannot be <= 0: {range}"); + + let min_val = min_value.unwrap_or(T::min_value()); + let max_val = max_value.unwrap_or(T::max_value()); + assert!( + min_val < max_val, + "mutate_integer: min_val must be strictly smaller than max_val: min={min_val}, max={max_val}" + ); + + match rng.random_range(0..3) { + 0 => { + // 1/3 chance: Random Walk + // Establish a window around the current value. If val > max_val, we should walk + // around max_val instead to bring the value back into a valid range. + let center = val.clamp(min_val, max_val); + let lo = center.saturating_sub(range).max(min_val); + let hi = center.saturating_add(range).min(max_val); + + // Sample uniformly within the clamped window [lo, hi]. + rng.random_range(lo..=hi) + } + 1 => { + // 1/3 chance: Flip a random bit + let num_bits = std::mem::size_of::() * 8; + let bit_index = rng.random_range(0..num_bits); + let mask = T::one() << bit_index; + let result = val ^ mask; + if result < min_val || result > max_val { + val + } else { + result + } + } + _ => { + // 1/3 chance: Replace with a new random value + rng.random_range(min_val..=max_val) + } + } +} + +pub trait SpecialValues: Sized + Copy { + fn get() -> &'static [Self]; +} + +macro_rules! impl_special_values_signed { + ($($ty:ty),*) => { + $( + impl SpecialValues for $ty { + fn get() -> &'static [Self] { + &[0, 1, -1, <$ty>::MIN, <$ty>::MAX] + } + } + )* + }; +} + +macro_rules! impl_special_values_unsigned { + ($($ty:ty),*) => { + $( + impl SpecialValues for $ty { + fn get() -> &'static [Self] { + &[0, 1, <$ty>::MAX, <$ty>::MAX >> 1] + } + } + )* + }; +} + +impl_special_values_signed!(i8, i16, i32, i64, i128); +impl_special_values_unsigned!(u8, u16, u32, u64, u128); + +macro_rules! impl_special_values_float { + ($($ty:ty),*) => { + $( + impl SpecialValues for $ty { + fn get() -> &'static [Self] { + &[0.0, -0.0, 1.0, -1.0, <$ty>::MIN, <$ty>::MAX, <$ty>::INFINITY, <$ty>::NEG_INFINITY, <$ty>::NAN] + } + } + )* + }; +} +impl_special_values_float!(f32, f64); + +impl SpecialValues for char { + fn get() -> &'static [Self] { + &[ + '\0', // Null + 'a', // Common ASCII + '~', // Last printable ASCII + '\n', // Newline + ' ', // Space + '\u{0080}', // Start of multi-byte UTF-8 + '\u{D7FF}', // Before surrogate range + '\u{E000}', // After surrogate range + '\u{10FFFF}', // Max valid Unicode scalar value + ] + } +} + +/// Chooses a value of type `T`, potentially from a set of "special values". +/// +/// This function has a higher chance of returning one of the predefined "special values" +/// for the type `T` (as defined by the `SpecialValues` trait). Otherwise, it generates +/// a completely random value of type `T`. +/// +/// # Arguments +/// * `rng`: Random number generator. +/// +/// # Type Parameters +/// * `T`: The type of the value to choose. Must implement `SpecialValues` and +/// `StandardUniform` must be able to generate values of type `T`. +pub fn choose_value(rng: &mut R) -> T +where + T: SpecialValues + 'static, + StandardUniform: Distribution, +{ + let special_values = T::get(); + let index = rng.random_range(0..=special_values.len()); + if index < special_values.len() { + special_values[index] + } else { + rng.random() + } +} + +/// Mutates a float value. +pub fn mutate_float( + rng: &mut R, + val: &mut T, + only_shrink: bool, + _range: Option<(T, T)>, // TODO: Implement range support for floats. +) -> anyhow::Result<()> +where + T: num_traits::Float + SampleUniform + std::fmt::Display + Copy + SpecialValues + 'static, + StandardUniform: Distribution, +{ + if only_shrink { + // Shrink only finite, non-terminal values. + if val.is_finite() && *val != T::zero() { + *val = shrink_towards(rng, *val, T::zero()); + } + return Ok(()); + } + // Non-shrinking mutation. + let original_val = *val; + loop { + // Loop to ensure mutation + if !val.is_finite() { + // NaN or Infinity + *val = choose_value(rng); + } else { + match rng.random_range(0..4) { + 0 => *val = *val / (T::one() + T::one()), + 1 => *val = -*val, + 2 => *val = *val + T::one(), + 3 => *val = *val * (T::one() + T::one() + T::one()), + _ => unreachable!(), + } + } + + if (val.is_nan() && original_val.is_nan()) || *val == original_val { + continue; + } + break; + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use rand::{ + rngs::{SmallRng, SysRng}, + SeedableRng, + }; + + fn get_rng() -> SmallRng { + SmallRng::try_from_rng(&mut SysRng).unwrap() + } + + fn check_shrink_towards(smaller: T, larger: T) + where + T: SampleUniform + PartialOrd + Copy + std::fmt::Display + std::fmt::Debug + PartialEq, + { + let mut rng = get_rng(); + + // val > target (e.g. larger -> smaller) + // Range should be [smaller, larger) + let mut results = Vec::new(); + for _ in 0..100 { + let sample = shrink_towards(&mut rng, larger, smaller); + assert!(sample >= smaller && sample < larger); + if !results.contains(&sample) { + results.push(sample); + } + } + assert!(results.len() > 1); + + // val < target (e.g. smaller -> larger) + // Range should be (smaller, larger] + let mut results = Vec::new(); + for _ in 0..100 { + let sample = shrink_towards(&mut rng, smaller, larger); + assert!(sample > smaller && sample <= larger); + if !results.contains(&sample) { + results.push(sample); + } + } + assert!(results.len() > 1); + + // identical target + let sample = shrink_towards(&mut rng, smaller, smaller); + assert_eq!(sample, smaller); + } + + #[test] + fn test_shrink_towards_all_types() { + check_shrink_towards::(5, 10); + check_shrink_towards::(5, 10); + check_shrink_towards::(5, 10); + check_shrink_towards::(5, 10); + check_shrink_towards::(5, 10); + check_shrink_towards::(5, 10); + check_shrink_towards::(5, 10); + check_shrink_towards::(5, 10); + check_shrink_towards::(5, 10); + check_shrink_towards::(5, 10); + check_shrink_towards::(5.0, 10.0); + check_shrink_towards::(5.0, 10.0); + } + + fn check_mutate_integer() + where + T: PrimInt + SampleUniform + std::fmt::Display + std::fmt::Debug + SpecialValues + 'static, + StandardUniform: Distribution, + { + let mut rng = get_rng(); + + let min_val = T::zero(); + let max_val = T::one() + T::one() + T::one() + T::one() + T::one(); + let range = T::one() + T::one(); + + // Bounded mutation stays in bounds + for _ in 0..100 { + let val: T = choose_value(&mut rng).clamp(min_val, max_val); + let next = mutate_integer(&mut rng, val, range, Some(min_val), Some(max_val)); + assert!( + next >= min_val && next <= max_val, + "mutate_integer bounded range check failed for value {} not in [{}, {}]", + next, + min_val, + max_val + ); + } + + // Unbounded mutation changes value + let mut val = T::zero(); + let mut changed = false; + for _ in 0..100 { + let next = mutate_integer(&mut rng, val, range, None, None); + if next != val { + changed = true; + break; + } + val = next; + } + assert!(changed, "mutate_integer did not change value"); + } + + #[test] + fn test_mutate_integer_all_types() { + check_mutate_integer::(); + check_mutate_integer::(); + check_mutate_integer::(); + check_mutate_integer::(); + check_mutate_integer::(); + check_mutate_integer::(); + check_mutate_integer::(); + check_mutate_integer::(); + check_mutate_integer::(); + check_mutate_integer::(); + } + + fn check_mutate_float() + where + T: num_traits::Float + + SampleUniform + + std::fmt::Display + + std::fmt::Debug + + Copy + + SpecialValues + + 'static, + StandardUniform: Distribution, + { + let mut rng = get_rng(); + + // Mutation changes value + let mut val = T::one(); + let mut changed = false; + for _ in 0..100 { + let mut next = val; + mutate_float(&mut rng, &mut next, false, None).unwrap(); + // Floats might mutate to NaN + if next != val || (next.is_nan() && !val.is_nan()) { + changed = true; + if next.is_nan() { + val = T::one(); + } else { + val = next; + } + } + } + assert!(changed, "mutate_float did not change value"); + + // Shrinking + let mut val = T::one() + T::one() + T::one(); + let original_abs = val.abs(); + for _ in 0..10 { + let mut next = val; + mutate_float(&mut rng, &mut next, true, None).unwrap(); + assert!(next.abs() <= val.abs(), "mutate_float shrink increased absolute value"); + val = next; + } + assert!(val.abs() < original_abs, "mutate_float did not shrink value"); + } + + #[test] + fn test_mutate_float_all_types() { + check_mutate_float::(); + check_mutate_float::(); + } +} diff --git a/rust/src/internal.rs b/rust/src/internal.rs new file mode 100644 index 000000000..d440b687f --- /dev/null +++ b/rust/src/internal.rs @@ -0,0 +1,57 @@ +use super::domains::GenericCorpusValue; +use super::domains::GenericDomain; +use std::collections::HashMap; +use std::sync::LazyLock; + +/// A trait implemented by types used to Fuzz a given property function. +/// +/// When a test is annotated with the `fuzztest` macro attribute, that test will be wrapped in a +/// FuzzTestObject that implements this trait. The FuzzTestObject is created by the macro and holds, +/// among others, the property function and the domains of its arguments. +/// The FuzzTestObject is then manipulated by Fuzzing Test Engine through the APIs of this trait. +pub trait FuzzTest { + // TODO(tinmar): refine arguments as we get a clearer picture of + // what the dispatcher interface looks like + fn name(&self) -> &'static str; + fn activate(&mut self); + fn mutate(&mut self); + /// Executes the property function with the given arguments + /// (will attempt to downcast to actual user values). + /// + /// Returns `true` if the property function holds, `false` if it crashes. + fn execute<'a>(&self, args: &'a GenericCorpusValue) -> bool; + fn print_finding_report(&self); + fn domains(&self) -> &dyn GenericDomain; +} + +/// Identifies the property function of a fuzz test. +/// +/// `FuzzTestInfo` is instantiated by the `fuzztest` macro which populates the fields with the +/// appropriate values. +pub struct FuzzTestInfo { + pub name: &'static str, + pub file: &'static str, + pub line: u32, +} + +pub type BoxedFuzzTest = Box; + +pub struct FuzzTestRegistration { + pub info: &'static FuzzTestInfo, + pub factory: fn() -> BoxedFuzzTest, +} + +inventory::collect!(FuzzTestRegistration); + +pub static FUZZ_TEST_NAME_TO_FACTORY: LazyLock BoxedFuzzTest>> = + LazyLock::new(|| { + inventory::iter + .into_iter() + .map(|&FuzzTestRegistration { info, factory }| (info.name, factory)) + .collect() + }); + +pub struct InputStateAndDomain { + pub input_state: Option, + pub domain: D, +} diff --git a/rust/src/lib.rs b/rust/src/lib.rs new file mode 100644 index 000000000..b447424de --- /dev/null +++ b/rust/src/lib.rs @@ -0,0 +1,20 @@ +#![feature(cfg_sanitize)] + +mod crash_handler; +pub mod domains; +pub mod internal; +pub mod worker; + +pub use fuzztest_macro::fuzztest; +pub use serde; + +pub mod options; +pub use options::get_fuzztest_options; +pub use options::FuzzTestOptions; + +// Re-export helper crates used in macro expansion to decouple downstream Cargo.toml files. +pub mod reexports { + pub use anyhow; + pub use inventory; + pub use rand; +} diff --git a/rust/src/options.rs b/rust/src/options.rs new file mode 100644 index 000000000..b22689b98 --- /dev/null +++ b/rust/src/options.rs @@ -0,0 +1,840 @@ +use crate::internal::FuzzTestRegistration; +use ::engine::engine_ffi; +use anyhow::Context; +use clap::{Parser, ValueEnum}; +use humantime::Duration; +use std::ffi::CString; +use std::ffi::OsString; +use std::path::Path; +use std::sync::OnceLock; +use tempfile::{NamedTempFile, TempDir}; + +/// Time budget calculation type for replay corpus mode. +#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq, Default)] +pub enum TimeBudgetType { + #[default] + PerTest, + Total, +} + +/// Parses a fuzzing duration string from `FUZZTEST_FUZZ_FOR`. +/// +/// Matches `"inf"` or `"infinite"` to [`FuzzFor::Indefinitely`]. All other values +/// are parsed as standard human-readable durations (for example, `"5s"` or `"10m"`). +fn parse_fuzz_for(s: &str) -> anyhow::Result { + let s_lower = s.trim().to_lowercase(); + if s_lower == "inf" || s_lower == "infinite" { + Ok(FuzzFor::Indefinitely) + } else { + let duration = s.parse()?; + Ok(FuzzFor::Duration(duration)) + } +} + +/// Command-line and environment variable options parsed for the FuzzTest harness. +#[derive(Parser, Debug, Clone, Default)] +pub struct FuzzTestOptions { + /// The working root directory. + #[arg(env = "FUZZTEST_WORKDIR_ROOT", long)] + pub workdir_root: Option, + + /// The duration for which each test should be fuzzed. + #[arg(env = "FUZZTEST_FUZZ_FOR", long, value_parser = parse_fuzz_for)] + pub fuzz_for: Option, + + /// If true, subprocess logs are printed after every batch. Note that crash logs are always printed + /// regardless of this flag's value. + #[arg(env = "FUZZTEST_PRINT_SUBPROCESS_LOG", long)] + pub print_subprocess_log: bool, + + /// Number of parallel jobs to run. + #[arg(env = "FUZZTEST_JOBS", long)] + pub jobs: Option, + + /// The crash ID to be replayed from the corpus database. + /// + /// If set, `corpus_db` must also be specified. This mode retrieves the crashing input + /// associated with the given ID from the database and executes the property function. + #[arg(env = "FUZZTEST_REPLAY_ID", long, requires = "corpus_db")] + pub replay_id: Option, + + /// Replay all crashing inputs from the corpus database. + #[arg(env = "FUZZTEST_REPLAY_FINDINGS", long)] + pub replay_findings: bool, + + /// Replay the corpus for a specified duration. + #[arg(env = "FUZZTEST_REPLAY_CORPUS_FOR", long)] + pub replay_corpus_for: Option, + + /// Time budget calculation type for replay corpus mode. + #[arg(env = "FUZZTEST_TIME_BUDGET_TYPE", long, value_enum, default_value_t = TimeBudgetType::PerTest)] + pub time_budget_type: TimeBudgetType, + + /// The path to the corpus database. + /// + /// If set to non-empty, updates/queries the corpus database that contains coverage, + /// regression, and crashing inputs for each test binary and fuzz test. + #[arg(env = "FUZZTEST_CORPUS_DB", long)] + pub corpus_db: Option, +} + +impl FuzzTestOptions { + /// Evaluates the raw options and maps them to a strongly-typed domain `ExecutionMode`. + /// + /// This decouples raw environment/CLI option parsing from execution mode validation. + pub fn execution_mode(&self) -> ExecutionMode { + if let Some(replay_corpus_for) = self.replay_corpus_for { + return ExecutionMode::ReplayCorpus(ReplayCorpusOptions { + replay_corpus_for, + time_budget_type: self.time_budget_type, + }); + } + + if self.replay_findings { + return ExecutionMode::ReplayAllCrashes; + } + + if let Some(replay_id) = &self.replay_id { + return ExecutionMode::ReplayCrash(ReplayCrashOptions { replay_id: replay_id.clone() }); + } + + if let Some(fuzz_for) = self.fuzz_for { + return ExecutionMode::Fuzz(FuzzOptions { fuzz_for, jobs: self.jobs.clone() }); + } + + ExecutionMode::SmokeTest + } +} + +/// Returns a lazily-initialized static reference to the global `FuzzTestOptions`. +pub fn get_fuzztest_options() -> &'static FuzzTestOptions { + static OPTIONS: OnceLock = OnceLock::new(); + // We parse FuzzTestOptions from an empty iterator so that clap would parse the options only + // from environment variables (like `FUZZTEST_FUZZ_FOR` etc.). We (currently) do not envisage + // support for passing flags on cli as the Rust's libtest harness does not support custom + // flags. + OPTIONS.get_or_init(|| FuzzTestOptions::parse_from(std::iter::empty::())) +} + +/// Strongly-typed domain execution mode for test runs. +/// +/// This enum represents the parsed high-level user intent derived from `FuzzTestOptions`. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ExecutionMode { + /// Smoke test execution (regular unit test fallback). + SmokeTest, + + /// Fuzzing mode. + Fuzz(FuzzOptions), + + /// Replay a specific crash ID from the corpus database. + ReplayCrash(ReplayCrashOptions), + + /// Replay all crashing inputs stored in the corpus database. + ReplayAllCrashes, + + /// Replay corpus inputs for a specified duration. + ReplayCorpus(ReplayCorpusOptions), +} + +/// Mode-specific options for continuous fuzzing. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct FuzzOptions { + pub fuzz_for: FuzzFor, + + /// If `jobs` is `None`, we won't specify the number of jobs while invoking Centipede and it + /// will use its own default value. + pub jobs: Option, +} + +/// The duration or limit for fuzzing. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum FuzzFor { + /// Fuzz indefinitely until it is manually stopped or a crash is found. + Indefinitely, + + /// Fuzz for a specific duration. + Duration(Duration), +} + +/// Mode-specific options for replaying a specific database crash. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ReplayCrashOptions { + pub replay_id: String, +} + +/// Mode-specific options for replaying corpus for a duration. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ReplayCorpusOptions { + pub replay_corpus_for: Duration, + pub time_budget_type: TimeBudgetType, +} + +impl ExecutionMode { + /// Prepares the concrete `ExecutionAction` payload for execution by building any required + /// `CentipedeArgs` or temporary runtime assets. + pub fn prepare_action( + &self, + options: &FuzzTestOptions, + current_test_name: Option<&str>, + ) -> anyhow::Result { + match self { + ExecutionMode::SmokeTest => Ok(ExecutionAction::SmokeTest), + ExecutionMode::Fuzz(_) + | ExecutionMode::ReplayCrash(_) + | ExecutionMode::ReplayCorpus(_) => { + let centipede_args = + CentipedeArgs::from_execution_mode(self, options, current_test_name, None)? + .context( + "while attempting to build CentipedeArgs for standalone execution mode", + )?; + Ok(ExecutionAction::Standalone(centipede_args)) + } + ExecutionMode::ReplayAllCrashes => { + let list_file = NamedTempFile::new() + .context("while attempting to create temp file for ReplayAllCrashes")?; + let centipede_args = CentipedeArgs::from_execution_mode( + self, + options, + current_test_name, + Some(list_file.path()), + )? + .context("while attempting to build CentipedeArgs for replay all crashes mode")?; + Ok(ExecutionAction::ReplayAllCrashes { args: centipede_args, list_file }) + } + } + } +} + +/// The prepared, ready-to-run execution payload passed to the test worker. +/// +/// This enum bridges parsed domain intent (`ExecutionMode`) with the concrete runtime +/// assets (`CentipedeArgs`, temporary files, workdir paths) needed to execute the test run. +#[derive(Debug)] +pub enum ExecutionAction { + /// Run as a smoke test (regular unit test) with random inputs for a short duration. + SmokeTest, + + /// Run in standalone mode, invoking the Centipede fuzzing controller. + Standalone(CentipedeArgs), + + /// Replay all crash IDs from the database (carries `list_file` for worker post-processing). + ReplayAllCrashes { args: CentipedeArgs, list_file: NamedTempFile }, +} + +/// Encapsulates command-line arguments and lifetime holders for Centipede execution. +#[derive(Debug)] +pub struct CentipedeArgs { + // Holds the owned C-compatible strings. We must keep these alive because the `views` + // field contains pointers into the memory owned by these `CString`s. + _c_strings: Vec, + + // The (optional) TempDir instance which is to be used as a workdir by centipede. + // We keep the ownership here so that it is not cleaned while the centipede execution is still + // in progress. + _temp_workdir: Option, + + // FFI-compatible views into the arguments in `_c_strings`. These are passed across + // the FFI boundary to Centipede. + views: Vec, +} + +impl CentipedeArgs { + fn new(args: Vec, temp_workdir: Option) -> Self { + let views = args + .iter() + .map(|arg| engine_ffi::FuzzTestBytesView::from_bytes(arg.as_bytes())) + .collect(); + + Self { _c_strings: args, _temp_workdir: temp_workdir, views } + } + + /// Decides if Centipede needs to be invoked based on the execution mode, + /// and constructs the appropriate arguments for it if so. + /// + /// Returns `Ok(Some(args))` if Centipede should be invoked, `Ok(None)` for `SmokeTest`, + /// or `Err` if argument generation fails. + pub fn from_execution_mode( + mode: &ExecutionMode, + options: &FuzzTestOptions, + current_test_name: Option<&str>, + list_file_path: Option<&Path>, + ) -> anyhow::Result> { + let mode_opts = match mode { + ExecutionMode::SmokeTest => return Ok(None), + other => other, + }; + + let mut args = Vec::new(); + let mut add_arg = |arg: String| -> anyhow::Result<()> { + args.push(CString::new(arg).context("while attempting to create CString arg")?); + Ok(()) + }; + + // ============================================================================== + // 1. Common Base Arguments (Required across all Centipede executions) + // ============================================================================== + let argv0 = std::env::args().next().context("while attempting to get argv[0]")?; + + if let Some(test_name) = current_test_name { + add_arg(format!("--binary={argv0} {test_name} --exact --nocapture"))?; + let normalized_test_name = test_name.replace("::", "."); + add_arg(format!("--test_name={normalized_test_name}"))?; + } else { + // We append the filter for fuzztests mod so that only fuzztests are executed and not unit tests. + add_arg(format!("--binary={argv0} __fuzztest_mod__ --nocapture"))?; + } + + // Binary identifier is the same as the binary path (stripped of leading slash) + let binary_id = argv0.strip_prefix('/').unwrap_or(&argv0); + add_arg(format!("--fuzztest_binary_identifier={binary_id}"))?; + + add_arg("--populate_binary_info=false".to_string())?; + add_arg("--fork_server=false".to_string())?; + + add_arg(format!("--print_runner_log={}", options.print_subprocess_log))?; + + // Add a dummy hash so as to avoid Centipede computing the hash of the binary. + add_arg("--binary_hash=DUMMY_HASH".to_string())?; + + // ============================================================================== + // 2. Common Working Directory & Corpus Database Resolution + // ============================================================================== + let (opt_corpusdb, opt_workdir_root, opt_workdir) = + get_corpusdb_and_workdir_root_and_workdir(options) + .context("while attempting to resolve corpus and workdir options")?; + + if let Some(corpus_db) = opt_corpusdb { + add_arg(format!("--fuzztest_corpus_database={corpus_db}"))?; + } + + if let Some(workdir_root) = opt_workdir_root { + add_arg(format!("--fuzztest_workdir_root={workdir_root}"))?; + } + + if let Some(temp_workdir) = &opt_workdir { + let workdir_path = temp_workdir + .path() + .to_str() + .context("while attempting to convert temp dir path to str")? + .to_string(); + add_arg(format!("--workdir={workdir_path}"))?; + } + + // ============================================================================== + // 3. Common Environment Variables Differential + // ============================================================================== + let env_diff = Self::default_env_diff(); + add_arg(format!("--env_diff_for_binaries={}", env_diff.join(",")))?; + + // ============================================================================== + // 4. Mode-Specific Arguments + // ============================================================================== + match mode_opts { + ExecutionMode::Fuzz(fuzz_opts) => { + match &fuzz_opts.fuzz_for { + FuzzFor::Indefinitely => { + // not specifying `--stop_after` means to run indefinitely. + } + FuzzFor::Duration(duration) => { + add_arg(format!("--stop_after={duration}"))?; + } + } + if let Some(jobs) = &fuzz_opts.jobs { + add_arg(format!("--j={jobs}"))?; + } + } + ExecutionMode::ReplayCrash(replay_opts) => { + add_arg("--replay_crash".to_string())?; + add_arg(format!("--crash_id={}", replay_opts.replay_id))?; + add_arg("--exit_on_crash".to_string())?; + } + ExecutionMode::ReplayAllCrashes => { + let path = list_file_path + .context("while attempting to get list_file path for ReplayAllCrashes")?; + add_arg("--list_crash_ids=true".to_string())?; + add_arg(format!("--list_crash_ids_file={}", path.display()))?; + } + ExecutionMode::ReplayCorpus(replay_corpus_opts) => { + let time_limit = match replay_corpus_opts.time_budget_type { + TimeBudgetType::PerTest => replay_corpus_opts.replay_corpus_for, + TimeBudgetType::Total => { + let num_tests = inventory::iter::().count(); + if num_tests == 0 { + replay_corpus_opts.replay_corpus_for + } else { + (*replay_corpus_opts.replay_corpus_for.as_ref() / (num_tests as u32)) + .into() + } + } + }; + add_arg("--fuzztest_only_replay=true".to_string())?; + add_arg("--fuzztest_replay_coverage_inputs=true".to_string())?; + add_arg("--load_shards_only=true".to_string())?; + add_arg(format!("--fuzztest_time_limit_per_test={time_limit}"))?; + } + ExecutionMode::SmokeTest => unreachable!(), + } + + Ok(Some(Self::new(args, opt_workdir))) + } + + /// Generates the default environment variable diff list. + /// + /// This replicates the logic in `GetEnvDiffForBinaries` from + /// `fuzztest/internal/centipede_adaptor.cc`. + /// + /// When Centipede spawns the test binary as a subprocess, we do not want it to inherit + /// environment variables set by the test harness (like Blaze/Bazel). Inheriting these + /// could cause the subprocess to think it's running in a sharded environment, try to + /// write to restricted output files, or trigger other harness-specific behavior that + /// interferes with fuzzing. + fn default_env_diff() -> Vec { + let mut env_diff = vec![ + "-TEST_DIAGNOSTICS_OUTPUT_DIR".to_string(), + "-TEST_INFRASTRUCTURE_FAILURE_FILE".to_string(), + "-TEST_LOGSPLITTER_OUTPUT_FILE".to_string(), + "-TEST_PREMATURE_EXIT_FILE".to_string(), + "-TEST_RANDOM_SEED".to_string(), + "-TEST_RUN_NUMBER".to_string(), + "-TEST_SHARD_INDEX".to_string(), + "-TEST_SHARD_STATUS_FILE".to_string(), + "-TEST_TOTAL_SHARDS".to_string(), + "-TEST_UNDECLARED_OUTPUTS_ANNOTATIONS_DIR".to_string(), + "-TEST_UNDECLARED_OUTPUTS_DIR".to_string(), + "-TEST_WARNINGS_OUTPUT_FILE".to_string(), + "-GTEST_OUTPUT".to_string(), + "-XML_OUTPUT_FILE".to_string(), + ]; + + env_diff + } + + /// Exposes the FFI-compatible argument views vector for C++ Centipede binding. + pub fn as_bytes_views(&self) -> engine_ffi::FuzzTestBytesViews { + engine_ffi::FuzzTestBytesViews { views: self.views.as_ptr(), count: self.views.len() } + } +} + +/// Determines the `ExecutionAction` for the given test name by inspecting global options. +pub fn determine_execution_action(current_test_name: Option<&str>) -> ExecutionAction { + determine_execution_action_internal(get_fuzztest_options(), current_test_name) +} + +fn determine_execution_action_internal( + options: &FuzzTestOptions, + current_test_name: Option<&str>, +) -> ExecutionAction { + options + .execution_mode() + .prepare_action(options, current_test_name) + .expect("failed to prepare execution action from fuzztest options") +} + +fn get_corpusdb_and_workdir_root_and_workdir( + options: &FuzzTestOptions, +) -> anyhow::Result<(Option<&str>, Option<&str>, Option)> { + let corpus_db = options.corpus_db.as_deref(); + let workdir_root = options.workdir_root.as_deref(); + + if let Some(corpus_db) = corpus_db { + if let Some(workdir_root) = workdir_root { + Ok((Some(corpus_db), Some(workdir_root), None)) + } else { + let temp_dir = + TempDir::new().context("while attempting to create temporary working directory")?; + Ok((Some(corpus_db), None, Some(temp_dir))) + } + } else { + if workdir_root.is_some() { + anyhow::bail!("NOT ALLOWED: workdir_root provided without corpus_db") + } else { + let temp_dir = + TempDir::new().context("while attempting to create temporary working directory")?; + Ok((None, None, Some(temp_dir))) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use googletest::prelude::*; + use std::ffi::OsString; + + #[gtest] + fn test_fuzz_options_parsing_env() { + // SAFETY: Testing environment parsing in single-threaded context. + unsafe { + std::env::set_var("FUZZTEST_FUZZ_FOR", "5s"); + } + + let options = FuzzTestOptions::parse_from(std::iter::empty::()); + + expect_true!(options.fuzz_for.is_some()); + expect_that!(options.execution_mode(), matches_pattern!(ExecutionMode::Fuzz(_))); + + // SAFETY: Cleaning up environment variables. + unsafe { + std::env::remove_var("FUZZTEST_FUZZ_FOR"); + } + } + + #[gtest] + fn test_fuzz_options_parsing_indefinite_env() { + // SAFETY: Testing environment parsing in single-threaded context. + unsafe { + std::env::set_var("FUZZTEST_FUZZ_FOR", "inf"); + } + + let options = FuzzTestOptions::parse_from(std::iter::empty::()); + + expect_that!(options.fuzz_for, eq(Some(FuzzFor::Indefinitely))); + let mode = options.execution_mode(); + let ExecutionMode::Fuzz(fuzz_opts) = mode else { + panic!("Expected ExecutionMode::Fuzz"); + }; + expect_that!(fuzz_opts.fuzz_for, eq(FuzzFor::Indefinitely)); + + // SAFETY: Cleaning up environment variables. + unsafe { + std::env::remove_var("FUZZTEST_FUZZ_FOR"); + } + } + + #[gtest] + fn test_determine_execution_action_standalone_indefinite() { + let options = + FuzzTestOptions { fuzz_for: Some(FuzzFor::Indefinitely), ..Default::default() }; + let action = determine_execution_action_internal(&options, Some("my_mod::my_test")); + + let ExecutionAction::Standalone(args) = action else { + panic!("Expected Standalone action"); + }; + + let args_str: Vec<&str> = + args._c_strings.iter().map(|s| s.to_str().expect("invalid utf8")).collect(); + + // When running indefinitely, --stop_after must be omitted so Centipede runs until interrupted. + expect_false!(args_str.iter().any(|s| s.starts_with("--stop_after="))); + expect_true!(args_str.contains(&"--test_name=my_mod.my_test")); + } + + #[gtest] + fn test_determine_execution_action_smoke_test() { + let options = FuzzTestOptions::default(); + let action = determine_execution_action_internal(&options, Some("my_mod::my_test")); + assert!(matches!(action, ExecutionAction::SmokeTest)); + } + + #[gtest] + fn test_determine_execution_action_standalone() { + let expected_duration = "10s".parse().unwrap(); + let options = FuzzTestOptions { + fuzz_for: Some(FuzzFor::Duration(expected_duration)), + ..Default::default() + }; + let action = determine_execution_action_internal(&options, Some("my_mod::my_test")); + + let ExecutionAction::Standalone(args) = action else { + panic!("Expected Standalone action"); + }; + + expect_that!(args._c_strings.len(), eq(args.views.len())); + + let args_str: Vec<&str> = + args._c_strings.iter().map(|s| s.to_str().expect("invalid utf8")).collect(); + + expect_true!(args_str + .iter() + .any(|s| s.starts_with("--binary=") && s.contains("my_mod::my_test --exact"))); + + expect_true!(args_str.contains(&"--stop_after=10s")); + expect_true!(args_str.contains(&"--test_name=my_mod.my_test")); + expect_true!(args_str.iter().any(|s| s.starts_with("--env_diff_for_binaries="))); + + // Verify that a temporary workdir was created and passed as an argument. + expect_true!(args._temp_workdir.is_some()); + let temp_workdir_path = args._temp_workdir.as_ref().unwrap().path().to_str().unwrap(); + let expected_workdir_arg = format!("--workdir={}", temp_workdir_path); + expect_true!(args_str.contains(&expected_workdir_arg.as_str())); + } + + #[gtest] + fn test_centipede_args_binary_identifier() { + let expected_duration = "1s".parse().unwrap(); + let options = FuzzTestOptions { + fuzz_for: Some(FuzzFor::Duration(expected_duration)), + ..Default::default() + }; + let action = determine_execution_action_internal(&options, Some("my_mod::my_test")); + + let ExecutionAction::Standalone(args) = action else { + panic!("Expected Standalone action"); + }; + + let args_str: Vec<&str> = + args._c_strings.iter().map(|s| s.to_str().expect("invalid utf8")).collect(); + + let argv0 = std::env::args().next().expect("get argv[0]"); + let binary_id = argv0.strip_prefix('/').unwrap_or(&argv0); + let expected_binary_id_arg = format!("--fuzztest_binary_identifier={binary_id}"); + + expect_true!(args_str.contains(&expected_binary_id_arg.as_str())); + } + + #[gtest] + fn test_centipede_args_jobs() { + let expected_duration = "1s".parse().expect("failed to parse duration"); + let options_no_jobs = FuzzTestOptions { + fuzz_for: Some(FuzzFor::Duration(expected_duration)), + ..Default::default() + }; + let action_no_jobs = + determine_execution_action_internal(&options_no_jobs, Some("my_mod::my_test")); + let ExecutionAction::Standalone(args_no_jobs) = action_no_jobs else { + panic!("Expected Standalone action"); + }; + let args_str_no_jobs: Vec<&str> = + args_no_jobs._c_strings.iter().map(|s| s.to_str().expect("invalid utf8")).collect(); + assert!(!args_str_no_jobs.iter().any(|s| s.starts_with("--j="))); + + let options_with_jobs = FuzzTestOptions { + fuzz_for: Some(FuzzFor::Duration(expected_duration)), + jobs: Some(4), + ..Default::default() + }; + let action_with_jobs = + determine_execution_action_internal(&options_with_jobs, Some("my_mod::my_test")); + let ExecutionAction::Standalone(args_with_jobs) = action_with_jobs else { + panic!("Expected Standalone action"); + }; + let args_str_with_jobs: Vec<&str> = + args_with_jobs._c_strings.iter().map(|s| s.to_str().expect("invalid utf8")).collect(); + assert!(args_str_with_jobs.contains(&"--j=4")); + } + + #[gtest] + fn test_default_env_diff_set() { + let duration = "1s".parse().unwrap(); + let options = + FuzzTestOptions { fuzz_for: Some(FuzzFor::Duration(duration)), ..Default::default() }; + let action = determine_execution_action_internal(&options, Some("my_mod::my_test")); + let ExecutionAction::Standalone(args) = action else { + panic!("Expected Standalone action"); + }; + + let args_str: Vec<&str> = + args._c_strings.iter().map(|s| s.to_str().expect("invalid utf8")).collect(); + + let env_diff_arg = args_str + .iter() + .find(|s| s.starts_with("--env_diff_for_binaries=")) + .expect("missing --env_diff_for_binaries arg"); + + expect_true!(env_diff_arg.contains("-TEST_SHARD_INDEX")); + expect_true!(env_diff_arg.contains("-XML_OUTPUT_FILE")); + expect_true!(env_diff_arg.contains("-GTEST_OUTPUT")); + } + + #[gtest] + fn test_determine_execution_action_replay_crash_id() { + let options = FuzzTestOptions { + replay_id: Some("my_crash_123".to_string()), + corpus_db: Some("/tmp/corpus_db".to_string()), + ..Default::default() + }; + let action = determine_execution_action_internal(&options, Some("my_mod::my_test")); + + let ExecutionAction::Standalone(args) = action else { + panic!("Expected Standalone action"); + }; + + let args_str: Vec<&str> = + args._c_strings.iter().map(|s| s.to_str().expect("invalid utf8")).collect(); + + expect_true!(args_str.contains(&"--replay_crash")); + expect_true!(args_str.contains(&"--crash_id=my_crash_123")); + expect_true!(args_str.contains(&"--fuzztest_corpus_database=/tmp/corpus_db")); + expect_true!(args_str.contains(&"--test_name=my_mod.my_test")); + expect_true!(args_str.iter().any(|s| s.starts_with("--workdir="))); + } + + #[gtest] + fn test_determine_execution_action_replay_findings() { + let options = FuzzTestOptions { + replay_findings: true, + corpus_db: Some("/tmp/corpus_db".to_string()), + ..Default::default() + }; + let action = determine_execution_action_internal(&options, Some("my_mod::my_test")); + + let ExecutionAction::ReplayAllCrashes { args, list_file } = action else { + panic!("Expected ReplayAllCrashes action"); + }; + + let args_str: Vec<&str> = + args._c_strings.iter().map(|s| s.to_str().expect("invalid utf8")).collect(); + + expect_true!(args_str.contains(&"--list_crash_ids=true")); + expect_true!(args_str + .contains(&format!("--list_crash_ids_file={}", list_file.path().display()).as_str())); + expect_true!(args_str.contains(&"--fuzztest_corpus_database=/tmp/corpus_db")); + expect_true!(args_str.iter().any(|s| s.starts_with("--workdir="))); + expect_true!(args_str.contains(&"--test_name=my_mod.my_test")); + } + + #[gtest] + fn test_determine_execution_action_replay_corpus() { + let expected_duration = "10s".parse().expect("failed to parse duration"); + let options = FuzzTestOptions { + replay_corpus_for: Some(expected_duration), + time_budget_type: TimeBudgetType::PerTest, + ..Default::default() + }; + let action = determine_execution_action_internal(&options, Some("my_mod::my_test")); + + let ExecutionAction::Standalone(args) = action else { + panic!("Expected Standalone action"); + }; + + let args_str: Vec<&str> = + args._c_strings.iter().map(|s| s.to_str().expect("invalid utf8")).collect(); + + assert!(args_str + .iter() + .any(|s| s.starts_with("--binary=") && s.contains("my_mod::my_test --exact"))); + assert!(args_str.contains(&"--fuzztest_only_replay=true")); + assert!(args_str.contains(&"--fuzztest_time_limit_per_test=10s")); + } + + #[gtest] + fn test_determine_execution_action_replay_corpus_total_budget() { + let expected_duration = "10s".parse().expect("failed to parse duration"); + let options = FuzzTestOptions { + replay_corpus_for: Some(expected_duration), + time_budget_type: TimeBudgetType::Total, + ..Default::default() + }; + let action = determine_execution_action_internal(&options, Some("my_mod::my_test")); + + let ExecutionAction::Standalone(args) = action else { + panic!("Expected Standalone action"); + }; + + let args_str: Vec<&str> = + args._c_strings.iter().map(|s| s.to_str().expect("invalid utf8")).collect(); + + let num_tests = inventory::iter::().count(); + let expected_limit = if num_tests == 0 { + expected_duration + } else { + (*expected_duration.as_ref() / (num_tests as u32)).into() + }; + let expected_limit_str = format!("--fuzztest_time_limit_per_test={}", expected_limit); + + assert!(args_str.contains(&expected_limit_str.as_str())); + } + + #[gtest] + fn test_determine_execution_action_replay_corpus_no_test_name() { + let expected_duration = "10s".parse().expect("failed to parse duration"); + let options = FuzzTestOptions { + replay_corpus_for: Some(expected_duration), + time_budget_type: TimeBudgetType::PerTest, + ..Default::default() + }; + let action = determine_execution_action_internal(&options, None); + + let ExecutionAction::Standalone(args) = action else { + panic!("Expected Standalone action"); + }; + + let args_str: Vec<&str> = + args._c_strings.iter().map(|s| s.to_str().expect("invalid utf8")).collect(); + + assert!(args_str.iter().any(|s| s.contains("__fuzztest_mod__"))); + assert!(args_str.contains(&"--fuzztest_only_replay=true")); + assert!(args_str.contains(&"--fuzztest_time_limit_per_test=10s")); + assert!(!args_str.iter().any(|s| s.starts_with("--test_name="))); + } + + #[gtest] + fn test_get_corpusdb_and_workdir_default_creates_temp_workdir() -> Result<()> { + let options = FuzzTestOptions::default(); + let (corpus_db, workdir_root, workdir) = + get_corpusdb_and_workdir_root_and_workdir(&options).or_fail()?; + expect_true!(corpus_db.is_none()); + expect_true!(workdir_root.is_none()); + expect_true!(workdir.is_some()); + Ok(()) + } + + #[gtest] + fn test_get_corpusdb_and_workdir_succeeds_with_corpusdb_and_workdir_root() -> Result<()> { + let options = FuzzTestOptions { + corpus_db: Some("/tmp/db".to_string()), + workdir_root: Some("/tmp/root".to_string()), + ..Default::default() + }; + let (corpus_db, workdir_root, workdir) = + get_corpusdb_and_workdir_root_and_workdir(&options).or_fail()?; + expect_that!(corpus_db, eq(Some("/tmp/db"))); + expect_that!(workdir_root, eq(Some("/tmp/root"))); + expect_true!(workdir.is_none()); + Ok(()) + } + + #[gtest] + fn test_get_corpusdb_and_workdir_succeeds_with_corpusdb_only() -> Result<()> { + let options = + FuzzTestOptions { corpus_db: Some("/tmp/db".to_string()), ..Default::default() }; + let (corpus_db, workdir_root, workdir) = + get_corpusdb_and_workdir_root_and_workdir(&options).or_fail()?; + expect_that!(corpus_db, eq(Some("/tmp/db"))); + expect_true!(workdir_root.is_none()); + expect_true!(workdir.is_some()); + Ok(()) + } + + #[gtest] + fn test_replay_id_requires_corpus_db() { + // SAFETY: Testing environment parsing in single-threaded context. + unsafe { + std::env::set_var("FUZZTEST_REPLAY_ID", "my_crash_123"); + std::env::remove_var("FUZZTEST_CORPUS_DB"); + } + + let result = FuzzTestOptions::try_parse_from(std::iter::empty::()); + + // SAFETY: Cleaning up environment variables. + unsafe { + std::env::remove_var("FUZZTEST_REPLAY_ID"); + } + + let err = result.expect_err("parsing should fail when corpus_db is missing"); + expect_that!(err.kind(), eq(clap::error::ErrorKind::MissingRequiredArgument)); + } + + #[gtest] + fn test_replay_id_with_corpus_db_succeeds() { + // SAFETY: Testing environment parsing in single-threaded context. + unsafe { + std::env::set_var("FUZZTEST_REPLAY_ID", "my_crash_123"); + std::env::set_var("FUZZTEST_CORPUS_DB", "/tmp/corpus_db"); + } + + let result = FuzzTestOptions::try_parse_from(std::iter::empty::()); + + // SAFETY: Cleaning up environment variables. + unsafe { + std::env::remove_var("FUZZTEST_REPLAY_ID"); + std::env::remove_var("FUZZTEST_CORPUS_DB"); + } + + let options = + result.expect("parsing should succeed when both replay_id and corpus_db are present"); + expect_that!(options.replay_id.as_deref(), eq(Some("my_crash_123"))); + expect_that!(options.corpus_db.as_deref(), eq(Some("/tmp/corpus_db"))); + } +} diff --git a/rust/src/worker.rs b/rust/src/worker.rs new file mode 100644 index 000000000..79bf31cd2 --- /dev/null +++ b/rust/src/worker.rs @@ -0,0 +1,769 @@ +use super::domains::GenericCorpusValue; +use super::internal::{BoxedFuzzTest, FuzzTest}; +use super::options::{self, CentipedeArgs, ExecutionAction}; +use ::engine::engine_ffi; +use ::engine::{ + BytesSink, CoverageDomainRegistry, DiagnosticSink, ExecuteContext, FeedbackSink, InputSink, +}; +use spin::Mutex; +use std::ffi::CString; +use std::path::Path; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::LazyLock; + +/// The DiagnosticSink provided by the engine while creating the adapter. +/// +/// Storing the `DiagnosticSink` in this global is necessary because C death callbacks (e.g., +/// sanitizer traps in `crash_handler.rs`) do not have access to the adapter instance and must rely +/// on a global lookup to report crashes. +static DIAGNOSTIC_SINK: Mutex> = Mutex::new(None); + +/// A global execution context token. +/// +/// This is set to `Some(ExecuteContext)` when the fuzz test is currently executing +/// (inside the `execute` method of `RustFuzzTestAdapter`) and `None` otherwise. +/// +/// The inner ExecuteContext is used as a witness token to prove to the fuzzing engine that findings +/// are being emitted during the execution of a test case. +/// A global is necessary because C death callbacks (e.g. sanitizer traps in `crash_handler.rs`) do +/// not have access to the adapter instance. +static EXECUTE_CONTEXT: Mutex> = Mutex::new(None); + +/// Sets the global DiagnosticSink. +pub(crate) fn set_diagnostic_sink(sink: DiagnosticSink) { + let mut guard = DIAGNOSTIC_SINK.lock(); + *guard = Some(sink); +} + +/// Clears the global DiagnosticSink, setting it to None. +pub(crate) fn clear_diagnostic_sink() { + let mut guard = DIAGNOSTIC_SINK.lock(); + *guard = None; +} + +/// Emits an error into the DiagnosticSink if the DiagnosticSink is set. +/// This function blocks till it can get the lock on the global DiagnosticSink and is not +/// signal-safe. +pub(crate) fn emit_error(message: &str) { + DIAGNOSTIC_SINK.lock().as_ref().map(|sink| sink.emit_error(message)); +} + +/// Emits a finding into the DiagnosticSink if the DiagnosticSink is set. +/// This function blocks till it can get the lock on the global DiagnosticSink and is not +/// signal-safe. Call `try_emit_finding` from a signal handler instead. +pub(crate) fn emit_finding(description: &str, signature: &str) { + let guard = EXECUTE_CONTEXT.lock(); + let token = + guard.as_ref().expect("emit_finding should only be called when a test is executing"); + DIAGNOSTIC_SINK + .lock() + .as_ref() + .expect("diagnostic sink should be present when a test is executing") + .emit_finding(token, description, signature); +} + +/// Emits a finding into the DiagnosticSink if the DiagnosticSink is set, and we are in the Execute +/// callback context. It only tries once to get the lock. The return value indicates if the finding +/// was successfully emitted. +/// +/// This function return false if: +/// - it could not get the lock on DiagnosticSink. +/// - it got the lock but the global DiagnosticSink was not set. +/// - diagnostic sink was set but the test was not executing. +#[allow(dead_code)] +pub(crate) fn try_emit_finding(description: &str, signature: &str) -> bool { + let Some(diagnostic_guard) = DIAGNOSTIC_SINK.try_lock() else { + return false; + }; + let Some(sink) = diagnostic_guard.as_ref() else { + return false; + }; + let Some(execution_guard) = EXECUTE_CONTEXT.try_lock() else { + return false; + }; + let Some(token) = execution_guard.as_ref() else { + return false; + }; + sink.emit_finding(token, description, signature); + return true; +} + +// We double-box the input because `GenericCorpusValue` is a fat pointer (`Box`), +// which cannot be directly represented as a single `usize` in `FuzzTestInputHandle` for FFI. +// By boxing it again, we get a thin pointer (`Box>`) that can be safely cast +// to `usize`. +// The memory is reclaimed when the engine calls `free_input`, which reconstructs the outer +// box using `Box::from_raw` and drops it, thereby dropping the inner box and the value. +fn pack_input(input: GenericCorpusValue) -> engine_ffi::FuzzTestInputHandle { + let ptr = Box::into_raw(Box::new(input)); + engine_ffi::FuzzTestInputHandle(ptr as usize) +} + +/// A guard that manages the global `EXECUTE_CONTEXT` state, setting it to `Some` on creation, +/// and resetting to `None` on drop. +struct ExecutionGuard; + +impl ExecutionGuard { + /// Enters the execution context by setting `EXECUTE_CONTEXT`. + /// + /// # Safety + /// The caller must ensure we are in a valid state to create an `ExecuteContext`. + unsafe fn enter() -> Self { + *EXECUTE_CONTEXT.lock() = Some(unsafe { ExecuteContext::new() }); + Self + } +} + +impl Drop for ExecutionGuard { + fn drop(&mut self) { + *EXECUTE_CONTEXT.lock() = None; + } +} + +pub struct RustFuzzTestAdapter { + fuzz_test: BoxedFuzzTest, +} + +impl RustFuzzTestAdapter { + pub fn set_up_coverage_domains(&self, registry: &mut CoverageDomainRegistry) { + registry.set_up_sancov_domains(); + } + + pub fn get_preset_seed_inputs(&self, _sink: &mut InputSink) { + // TODO(the-shank): implement this once the macro has support for specifying preset seed + // inputs + } + + pub fn get_random_seed_input(&self, sink: &mut InputSink) { + match self.fuzz_test.domains().init(&mut rand::rng()) { + Ok(val) => { + sink.emit(pack_input(val)); + } + Err(e) => { + emit_error(&format!("Failed to initialize random seed: {:?}", e)); + } + } + } + + pub fn mutate(&self, origin: &GenericCorpusValue, shrink: bool, sink: &mut InputSink) { + let mut mutant = origin.clone(); + + if let Err(e) = self.fuzz_test.domains().mutate(&mut mutant, &mut rand::rng(), shrink) { + emit_error(&format!("Failed to mutate: {:?}", e)); + return; + } + + sink.emit(pack_input(mutant)); + } + + pub fn cross_over( + &self, + origin: &GenericCorpusValue, + _other: &GenericCorpusValue, + sink: &mut InputSink, + ) { + // TODO(the-shank): implement cross_over. Currently we are just calling mutate. + self.mutate(origin, false, sink); + } + + pub fn execute(&self, input: &GenericCorpusValue, sink: &mut FeedbackSink) { + // TODO(the-shank): modify this in order to support persistent mode and batch execution. + static CLEAR_STARTUP_COVERAGE: AtomicBool = AtomicBool::new(true); + coverage::prepare_coverage(CLEAR_STARTUP_COVERAGE.swap(false, Ordering::AcqRel)); + + // SAFETY: We are currently in the `execute` callback of `RustFuzzTestAdapter`. + let _guard = unsafe { ExecutionGuard::enter() }; + + let result = self.fuzz_test.execute(input); + + coverage::post_process_coverage(false); + + sink.emit_sancov_features(); + + if !result { + emit_finding("Property function ran but crashed.", "Unwinding panic"); + } + } + + pub fn serialize_input_content(&self, input: &GenericCorpusValue, sink: &mut BytesSink) { + match self.fuzz_test.domains().serialize_corpus(input) { + Ok(serialized) => { + sink.emit(&serialized); + } + Err(e) => { + emit_error(&format!("Failed to serialize input: {:?}", e)); + } + } + } + + pub fn deserialize_input_content(&self, content: &[u8], sink: &mut InputSink) { + match self.fuzz_test.domains().parse_corpus(content) { + Ok(val) => { + sink.emit(pack_input(val)); + } + Err(e) => { + emit_error(&format!("Failed to deserialize input: {:?}", e)); + } + } + } + + pub fn serialize_input_metadata(&self, _input: &GenericCorpusValue, _sink: &mut BytesSink) { + // TODO(the-shank): to be implemented + } + + pub fn update_input_metadata(&self, _metadata: &[u8], _input: &mut GenericCorpusValue) { + // TODO(the-shank): to be implemented + } + + pub fn free_input(&self, input: engine_ffi::FuzzTestInputHandle) { + if input.0 != 0 { + // SAFETY: The engine guarantees `input` was created by `deserialize_input_content_callback` + // (or `emit` in `InputSink`) and has not been freed yet. + unsafe { + let _ = Box::from_raw(input.0 as *mut GenericCorpusValue); + } + } + } +} + +pub struct RustFuzzTestAdapterManager { + pub test_name: &'static str, + pub fuzz_test_factory: fn() -> BoxedFuzzTest, +} + +impl RustFuzzTestAdapterManager { + pub fn get_binary_id(&self, sink: &mut BytesSink) { + static ARGV0: LazyLock = LazyLock::new(|| { + CString::new( + Path::new(&std::env::args().nth(0).unwrap()) + .file_name() + .and_then(|f| f.to_str()) + .unwrap_or(""), + ) + .expect("argv[0] should not contain null bytes") + }); + sink.emit(ARGV0.as_bytes()); + } + + // Replaces "::" with "." to avoid parsing issues in Centipede. Centipede uses ":" as a + // separator in CENTIPEDE_RUNNER_FLAGS and its parser replaces all ":" with "\0", which would + // truncate Rust paths. + pub fn get_test_name(&self, sink: &mut BytesSink) { + let name = self.test_name.replace("::", "."); + sink.emit(name.as_bytes()); + } + + pub fn construct_adapter(&self) -> RustFuzzTestAdapter { + let fuzz_test = (self.fuzz_test_factory)(); + RustFuzzTestAdapter { fuzz_test } + } + + pub fn construct_fuzz_test(&self) -> BoxedFuzzTest { + (self.fuzz_test_factory)() + } +} + +/// # Safety +/// +/// The caller must ensure that: +/// * `ctx` is a valid pointer to the `RustFuzzTestAdapterManager` passed during initialization. +/// * `sink` is a valid pointer to a `FuzzTestBytesSink` whose lifetime extends for the duration +/// of this call. +pub unsafe extern "C" fn get_binary_id_callback( + ctx: *mut engine_ffi::FuzzTestAdapterManagerCtx, + sink: *const engine_ffi::FuzzTestBytesSink, +) { + // SAFETY: The engine guarantees `ctx` is a valid pointer to the `RustFuzzTestAdapterManager` + // passed during initialization. + let manager = unsafe { &*(ctx as *const RustFuzzTestAdapterManager) }; + // SAFETY: The engine guarantees `sink` is a valid pointer to a `FuzzTestBytesSink` + // whose lifetime extends for the duration of this call. + let mut safe_sink = unsafe { BytesSink::from_raw(sink) }; + manager.get_binary_id(&mut safe_sink); +} + +/// # Safety +/// +/// The caller must ensure that: +/// * `ctx` is a valid pointer to the `RustFuzzTestAdapterManager` passed during initialization. +/// * `sink` is a valid pointer to a `FuzzTestBytesSink` whose lifetime extends for the duration +/// of this call. +pub unsafe extern "C" fn get_test_name_callback( + ctx: *mut engine_ffi::FuzzTestAdapterManagerCtx, + sink: *const engine_ffi::FuzzTestBytesSink, +) { + // SAFETY: The engine guarantees `ctx` is a valid pointer to the `RustFuzzTestAdapterManager` + // passed during initialization. + let manager = unsafe { &*(ctx as *const RustFuzzTestAdapterManager) }; + // SAFETY: The engine guarantees `sink` is a valid pointer to a `FuzzTestBytesSink` + // whose lifetime extends for the duration of this call. + let mut safe_sink = unsafe { BytesSink::from_raw(sink) }; + manager.get_test_name(&mut safe_sink); +} + +/// # Safety +/// +/// The caller must ensure that: +/// * `ctx` is a valid pointer to the `RustFuzzTestAdapterManager` passed during initialization. +/// * `diagnostic_sink` is a valid pointer to a `FuzzTestDiagnosticSink` whose lifetime extends +/// until `FreeCtx` is called on the adapter. +/// * `adapter_out` is a valid pointer to write the output `FuzzTestAdapter`. +pub unsafe extern "C" fn construct_adapter_callback( + ctx: *mut engine_ffi::FuzzTestAdapterManagerCtx, + diagnostic_sink: *const engine_ffi::FuzzTestDiagnosticSink, + adapter_out: *mut engine_ffi::FuzzTestAdapter, +) { + // SAFETY: The engine guarantees `ctx` is a valid pointer to the `RustFuzzTestAdapterManager` + // passed during initialization. + let manager = unsafe { &*(ctx as *const RustFuzzTestAdapterManager) }; + + // SAFETY: The engine guarantees `diagnostic_sink` is a valid pointer to a `FuzzTestDiagnosticSink` + // whose lifetime extends until `FreeCtx` is called on the adapter. + let safe_sink = unsafe { DiagnosticSink::from_raw(diagnostic_sink) }; + + set_diagnostic_sink(safe_sink); + + // NOTE: `safe_sink` is not passed to `construct_adapter` or stored in `RustFuzzTestAdapter`. + // Instead, it is maintained in the global `DIAGNOSTIC_SINK` mutex via `set_diagnostic_sink`. + // This is necessary because C death callbacks (e.g., sanitizer traps in `crash_handler.rs`) do not + // have access to the adapter `self` pointer and must rely on a global lookup to report crashes. + let adapter = manager.construct_adapter(); + let boxed_adapter = Box::new(adapter); + // SAFETY: The engine guarantees `adapter_out` is a valid pointer to write the output adapter. + // The returned raw pointer `ctx` will be managed and eventually freed by `free_ctx_callback`. + unsafe { + *adapter_out = engine_ffi::FuzzTestAdapter { + ctx: Box::into_raw(boxed_adapter) as *mut engine_ffi::FuzzTestAdapterCtx, + set_up_coverage_domains: Some(set_up_coverage_domains_callback), + get_preset_seed_inputs: Some(get_preset_seed_inputs_callback), + get_random_seed_input: Some(get_random_seed_input_callback), + mutate: Some(mutate_callback), + cross_over: Some(cross_over_callback), + execute: Some(execute_callback), + serialize_input_content: Some(serialize_input_content_callback), + deserialize_input_content: Some(deserialize_input_content_callback), + serialize_input_metadata: Some(serialize_input_metadata_callback), + update_input_metadata: Some(update_input_metadata_callback), + free_input: Some(free_input_callback), + free_ctx: Some(free_ctx_callback), + }; + } +} + +/// # Safety +/// +/// The caller must ensure that: +/// * `ctx` is a valid pointer to the `RustFuzzTestAdapter` created by `construct_adapter_callback`. +/// * `registry` is a valid pointer to `FuzzTestCoverageDomainRegistry`. +pub unsafe extern "C" fn set_up_coverage_domains_callback( + ctx: *mut engine_ffi::FuzzTestAdapterCtx, + registry: *const engine_ffi::FuzzTestCoverageDomainRegistry, +) { + // SAFETY: The engine guarantees `ctx` is a valid pointer to the `RustFuzzTestAdapter` + // created by `construct_adapter_callback`. + let adapter = unsafe { &*(ctx as *const RustFuzzTestAdapter) }; + // SAFETY: The engine guarantees `registry` is a valid pointer to `FuzzTestCoverageDomainRegistry`. + let mut registry = unsafe { CoverageDomainRegistry::from_raw(registry) }; + adapter.set_up_coverage_domains(&mut registry); +} + +/// # Safety +/// +/// The caller must ensure that: +/// * `ctx` is a valid pointer to the `RustFuzzTestAdapter` created by `construct_adapter_callback`. +/// * `sink` is a valid pointer to `FuzzTestInputSink`. +pub unsafe extern "C" fn get_preset_seed_inputs_callback( + ctx: *mut engine_ffi::FuzzTestAdapterCtx, + sink: *const engine_ffi::FuzzTestInputSink, +) { + // SAFETY: The engine guarantees `ctx` is a valid pointer to the `RustFuzzTestAdapter` + // created by `construct_adapter_callback`. + let adapter = unsafe { &*(ctx as *const RustFuzzTestAdapter) }; + // SAFETY: The engine guarantees `sink` is a valid pointer to `FuzzTestInputSink`. + let mut safe_sink = unsafe { InputSink::from_raw(sink) }; + adapter.get_preset_seed_inputs(&mut safe_sink); +} + +/// # Safety +/// +/// The caller must ensure that: +/// * `ctx` is a valid pointer to the `RustFuzzTestAdapter` created by `construct_adapter_callback`. +/// * `sink` is a valid pointer to `FuzzTestInputSink`. +pub unsafe extern "C" fn get_random_seed_input_callback( + ctx: *mut engine_ffi::FuzzTestAdapterCtx, + sink: *const engine_ffi::FuzzTestInputSink, +) { + // SAFETY: The engine guarantees `ctx` is a valid pointer to the `RustFuzzTestAdapter` + // created by `construct_adapter_callback`. + let adapter = unsafe { &*(ctx as *const RustFuzzTestAdapter) }; + // SAFETY: The engine guarantees `sink` is a valid pointer to `FuzzTestInputSink`. + let mut safe_sink = unsafe { InputSink::from_raw(sink) }; + adapter.get_random_seed_input(&mut safe_sink); +} + +/// # Safety +/// +/// The caller must ensure that: +/// * `ctx` is a valid pointer to the `RustFuzzTestAdapter` created by `construct_adapter_callback`. +/// * `origin` is a valid `FuzzTestInputHandle` pointing to a heap-allocated `GenericCorpusValue` +/// managed by the framework. +/// * `sink` is a valid pointer to `FuzzTestInputSink`. +pub unsafe extern "C" fn mutate_callback( + ctx: *mut engine_ffi::FuzzTestAdapterCtx, + origin: engine_ffi::FuzzTestInputHandle, + shrink: std::ffi::c_int, + sink: *const engine_ffi::FuzzTestInputSink, +) { + // SAFETY: The engine guarantees `ctx` is a valid pointer to the `RustFuzzTestAdapter` + // created by `construct_adapter_callback`. + let adapter = unsafe { &*(ctx as *const RustFuzzTestAdapter) }; + // SAFETY: The engine guarantees `origin` is a valid `FuzzTestInputHandle` pointing to + // a heap-allocated `GenericCorpusValue` managed by the framework. + let origin_ref = unsafe { &*(origin.0 as *const GenericCorpusValue) }; + // SAFETY: The engine guarantees `sink` is a valid pointer to `FuzzTestInputSink`. + let mut safe_sink = unsafe { InputSink::from_raw(sink) }; + adapter.mutate(origin_ref, shrink != 0, &mut safe_sink); +} + +/// # Safety +/// +/// The caller must ensure that: +/// * `ctx` is a valid pointer to the `RustFuzzTestAdapter` created by `construct_adapter_callback`. +/// * `origin` is a valid `FuzzTestInputHandle` pointing to a heap-allocated `GenericCorpusValue` +/// managed by the framework. +/// * `other` is a valid `FuzzTestInputHandle` pointing to a heap-allocated `GenericCorpusValue` +/// managed by the framework. +/// * `sink` is a valid pointer to `FuzzTestInputSink`. +pub unsafe extern "C" fn cross_over_callback( + ctx: *mut engine_ffi::FuzzTestAdapterCtx, + origin: engine_ffi::FuzzTestInputHandle, + other: engine_ffi::FuzzTestInputHandle, + sink: *const engine_ffi::FuzzTestInputSink, +) { + // SAFETY: The engine guarantees `ctx` is a valid pointer to the `RustFuzzTestAdapter` + // created by `construct_adapter_callback`. + let adapter = unsafe { &*(ctx as *const RustFuzzTestAdapter) }; + // SAFETY: The engine guarantees `origin` is a valid `FuzzTestInputHandle` pointing to + // a heap-allocated `GenericCorpusValue` managed by the framework. + let origin_ref = unsafe { &*(origin.0 as *const GenericCorpusValue) }; + // SAFETY: The engine guarantees `other` is a valid `FuzzTestInputHandle` pointing to + // a heap-allocated `GenericCorpusValue` managed by the framework. + let other_ref = unsafe { &*(other.0 as *const GenericCorpusValue) }; + // SAFETY: The engine guarantees `sink` is a valid pointer to `FuzzTestInputSink`. + let mut safe_sink = unsafe { InputSink::from_raw(sink) }; + adapter.cross_over(origin_ref, other_ref, &mut safe_sink); +} + +/// # Safety +/// +/// The caller must ensure that: +/// * `ctx` is a valid pointer to the `RustFuzzTestAdapter` created by `construct_adapter_callback`. +/// * `input` is a valid `FuzzTestInputHandle` pointing to a heap-allocated `GenericCorpusValue` +/// managed by the framework. +/// * `sink` is a valid pointer to `FuzzTestFeedbackSink`. +pub unsafe extern "C" fn execute_callback( + ctx: *mut engine_ffi::FuzzTestAdapterCtx, + input: engine_ffi::FuzzTestInputHandle, + sink: *const engine_ffi::FuzzTestFeedbackSink, +) { + // SAFETY: The engine guarantees `ctx` is a valid pointer to the `RustFuzzTestAdapter` + // created by `construct_adapter_callback`. + let adapter = unsafe { &*(ctx as *const RustFuzzTestAdapter) }; + // SAFETY: The engine guarantees `input` is a valid `FuzzTestInputHandle` pointing to + // a heap-allocated `GenericCorpusValue` managed by the framework. + let input_ref = unsafe { &*(input.0 as *const GenericCorpusValue) }; + // SAFETY: The engine guarantees `sink` is a valid pointer to `FuzzTestFeedbackSink`. + let mut safe_sink = unsafe { FeedbackSink::from_raw(sink) }; + + adapter.execute(input_ref, &mut safe_sink); +} + +/// # Safety +/// +/// The caller must ensure that: +/// * `ctx` is a valid pointer to the `RustFuzzTestAdapter` created by `construct_adapter_callback`. +/// * `input` is a valid `FuzzTestInputHandle` pointing to a heap-allocated `GenericCorpusValue` +/// managed by the framework. +/// * `sink` is a valid pointer to `FuzzTestBytesSink`. +pub unsafe extern "C" fn serialize_input_content_callback( + ctx: *mut engine_ffi::FuzzTestAdapterCtx, + input: engine_ffi::FuzzTestInputHandle, + sink: *const engine_ffi::FuzzTestBytesSink, +) { + // SAFETY: The engine guarantees `ctx` is a valid pointer to the `RustFuzzTestAdapter` + // created by `construct_adapter_callback`. + let adapter = unsafe { &*(ctx as *const RustFuzzTestAdapter) }; + // SAFETY: The engine guarantees `input` is a valid `FuzzTestInputHandle` pointing to + // a heap-allocated `GenericCorpusValue` managed by the framework. + let input_ref = unsafe { &*(input.0 as *const GenericCorpusValue) }; + // SAFETY: The engine guarantees `sink` is a valid pointer to `FuzzTestBytesSink`. + let mut safe_sink = unsafe { BytesSink::from_raw(sink) }; + adapter.serialize_input_content(input_ref, &mut safe_sink); +} + +/// # Safety +/// +/// The caller must ensure that: +/// * `ctx` is a valid pointer to the `RustFuzzTestAdapter` created by `construct_adapter_callback`. +/// * `content` is a valid pointer to `FuzzTestBytesView` containing serialized input content. +/// * `sink` is a valid pointer to `FuzzTestInputSink`. +pub unsafe extern "C" fn deserialize_input_content_callback( + ctx: *mut engine_ffi::FuzzTestAdapterCtx, + content: *const engine_ffi::FuzzTestBytesView, + sink: *const engine_ffi::FuzzTestInputSink, +) { + // SAFETY: The engine guarantees `ctx` is a valid pointer to the `RustFuzzTestAdapter` + // created by `construct_adapter_callback`. + let adapter = unsafe { &*(ctx as *const RustFuzzTestAdapter) }; + // SAFETY: The engine guarantees `content` is a valid pointer to `FuzzTestBytesView` + // containing serialized input content. + let bytes = unsafe { &*(*content).to_bytes() }; + // SAFETY: The engine guarantees `sink` is a valid pointer to `FuzzTestInputSink`. + let mut safe_sink = unsafe { InputSink::from_raw(sink) }; + adapter.deserialize_input_content(bytes, &mut safe_sink); +} + +/// # Safety +/// +/// The caller must ensure that: +/// * `ctx` is a valid pointer to the `RustFuzzTestAdapter` created by `construct_adapter_callback`. +/// * `input` is a valid `FuzzTestInputHandle` pointing to a heap-allocated `GenericCorpusValue` +/// managed by the framework. +/// * `sink` is a valid pointer to `FuzzTestBytesSink`. +pub unsafe extern "C" fn serialize_input_metadata_callback( + ctx: *mut engine_ffi::FuzzTestAdapterCtx, + input: engine_ffi::FuzzTestInputHandle, + sink: *const engine_ffi::FuzzTestBytesSink, +) { + // SAFETY: The engine guarantees `ctx` is a valid pointer to the `RustFuzzTestAdapter` + // created by `construct_adapter_callback`. + let adapter = unsafe { &*(ctx as *const RustFuzzTestAdapter) }; + // SAFETY: The engine guarantees `input` is a valid `FuzzTestInputHandle` pointing to + // a heap-allocated `GenericCorpusValue` managed by the framework. + let input_ref = unsafe { &*(input.0 as *const GenericCorpusValue) }; + // SAFETY: The engine guarantees `sink` is a valid pointer to `FuzzTestBytesSink`. + let mut safe_sink = unsafe { BytesSink::from_raw(sink) }; + adapter.serialize_input_metadata(input_ref, &mut safe_sink); +} + +/// # Safety +/// +/// The caller must ensure that: +/// * `ctx` is a valid pointer to the `RustFuzzTestAdapter` created by `construct_adapter_callback`. +/// * `metadata` is a valid pointer to `FuzzTestBytesView` containing serialized input metadata. +/// * `input` is a valid `FuzzTestInputHandle` pointing to a heap-allocated `GenericCorpusValue` +/// managed by the framework, and the engine guarantees exclusive access to it for the call duration. +pub unsafe extern "C" fn update_input_metadata_callback( + ctx: *mut engine_ffi::FuzzTestAdapterCtx, + metadata: *const engine_ffi::FuzzTestBytesView, + input: engine_ffi::FuzzTestInputHandle, +) { + // SAFETY: The engine guarantees `ctx` is a valid pointer to the `RustFuzzTestAdapter` + // created by `construct_adapter_callback`. + let adapter = unsafe { &*(ctx as *const RustFuzzTestAdapter) }; + // SAFETY: The engine guarantees `metadata` is a valid pointer to `FuzzTestBytesView` + // containing serialized input metadata. + let bytes = unsafe { &*(*metadata).to_bytes() }; + // SAFETY: The engine guarantees `input` is a valid `FuzzTestInputHandle` pointing to + // a heap-allocated `GenericCorpusValue` managed by the framework, and provides exclusive + // access to it. + let input_ref = unsafe { &mut *(input.0 as *mut GenericCorpusValue) }; + adapter.update_input_metadata(bytes, input_ref); +} + +/// # Safety +/// +/// The caller must ensure that: +/// * `ctx` is a valid pointer to the `RustFuzzTestAdapter` created by `construct_adapter_callback`. +/// * `input` is a valid `FuzzTestInputHandle` pointing to a heap-allocated `GenericCorpusValue` +/// managed by the framework that has not been freed yet. +pub unsafe extern "C" fn free_input_callback( + ctx: *mut engine_ffi::FuzzTestAdapterCtx, + input: engine_ffi::FuzzTestInputHandle, +) { + // SAFETY: The engine guarantees `ctx` is a valid pointer to the `RustFuzzTestAdapter` + // created by `construct_adapter_callback`. + let adapter = unsafe { &*(ctx as *const RustFuzzTestAdapter) }; + adapter.free_input(input); +} + +/// # Safety +/// +/// The caller must ensure that: +/// * `ctx` is a valid pointer to the `RustFuzzTestAdapter` created by `construct_adapter_callback`. +pub unsafe extern "C" fn free_ctx_callback(ctx: *mut engine_ffi::FuzzTestAdapterCtx) { + if !ctx.is_null() { + // SAFETY: The engine guarantees `ctx` is a valid pointer to the `RustFuzzTestAdapter` + // created by `construct_adapter_callback`. + let ptr = ctx as *mut RustFuzzTestAdapter; + drop(unsafe { Box::from_raw(ptr) }); + + clear_diagnostic_sink(); + } +} + +pub fn run_smoke_test(fuzztest: &dyn FuzzTest) { + let start_time = std::time::Instant::now(); + + // TODO(the-shank): these should be configurable externally. + let smoke_test_duration = std::time::Duration::from_secs(1); + let only_shrink = false; + + // TODO(the-shank): the rng seed should be configurable + let mut rng = rand::rng(); + + let mut generic_corpus_value = fuzztest + .domains() + .init(&mut rng) + .expect("domain initialization should succeed to provide an initial corpus value"); + + let result = fuzztest.execute(&generic_corpus_value); + // DISCUSS: probably need a more "googletest" approach to handling the result here? + assert!(result); + + while start_time.elapsed() < smoke_test_duration { + fuzztest + .domains() + .mutate(&mut generic_corpus_value, &mut rng, only_shrink) + .expect("domain mutation should succeed"); + let result = fuzztest.execute(&generic_corpus_value); + // DISCUSS: probably need a more "googletest" approach to handling the result here? + assert!(result); + } +} + +pub enum WorkerStatus { + Success, + Failure, +} + +/// Processes and executes a fuzz test using the provided [`RustFuzzTestAdapterManager`]. +/// +/// This function serves as the primary entry point for running a fuzz test. It manages test +/// execution across two operational modes: +/// +/// 1. Worker Mode: Registers signal/sanitizer crash handlers and attempts to hand off execution to +/// the external fuzzing engine (e.g., Centipede) via [`maybe_run_as_worker`]. +/// - If the binary was spawned as a worker process by the fuzzing engine, control remains in the +/// engine loop until complete. +/// - Returns cleanly on [`WorkerStatus::Success`], or panics on [`WorkerStatus::Failure`] to +/// signal test failure to the harness. +/// 2. Smoke Test Mode: If worker mode is not active (e.g., during standard `blaze test` or +/// `cargo test` unit test runs), falls back to executing a short local smoke test using sample +/// inputs and mutation iterations to verify property function sanity. +pub fn process(manager: RustFuzzTestAdapterManager) { + super::crash_handler::register_crash_handler(); + + // box it to get a stable heap address. + let manager = Box::new(manager); + + if let Some(status) = maybe_run_as_worker(&manager) { + match status { + WorkerStatus::Success => { + // do nothing, the harness would continue to the next test + return; + } + WorkerStatus::Failure => { + std::process::exit(1); + } + } + } + + // worker mode was not run -- determine what to do next. + match options::determine_execution_action(Some(manager.test_name)) { + ExecutionAction::Standalone(centipede_args) => { + let status = run_standalone_mode(&manager, centipede_args); + if status == engine_ffi::FuzzTestControllerStatus::Failure { + panic!("FuzzTest controller reported failure"); + } + } + ExecutionAction::ReplayAllCrashes { args, list_file } => { + let status = run_standalone_mode(&manager, args); + if status == engine_ffi::FuzzTestControllerStatus::Failure { + panic!("FuzzTest controller reported failure"); + } + + // Now read the file and replay each crash + if let Ok(contents) = std::fs::read_to_string(list_file.path()) { + let options = options::get_fuzztest_options(); + for crash_id in contents.lines() { + let crash_id = crash_id.trim(); + if crash_id.is_empty() { + continue; + } + let replay_mode = + options::ExecutionMode::ReplayCrash(options::ReplayCrashOptions { + replay_id: crash_id.to_string(), + }); + if let Ok(Some(replay_args)) = CentipedeArgs::from_execution_mode( + &replay_mode, + options, + Some(manager.test_name), + None, + ) { + // We ignore the result here because replaying a crash is expected to fail + // (report failure). We want to continue replaying other crashes. + let _ = run_standalone_mode(&manager, replay_args); + } + } + } + } + ExecutionAction::SmokeTest => { + let fuzz_test = manager.construct_fuzz_test(); + run_smoke_test(fuzz_test.as_ref()); + } + } +} + +fn make_ffi_manager(manager: &RustFuzzTestAdapterManager) -> engine_ffi::FuzzTestAdapterManager { + let ctx = + manager as *const RustFuzzTestAdapterManager as *mut engine_ffi::FuzzTestAdapterManagerCtx; + + engine_ffi::FuzzTestAdapterManager { + ctx, + get_binary_id: Some(get_binary_id_callback), + get_test_name: Some(get_test_name_callback), + construct_adapter: Some(construct_adapter_callback), + } +} + +/// Attempts to run the fuzz test in Centipede worker mode using the FFI engine interface. +/// +/// This function constructs a C-compatible [`engine_ffi::FuzzTestAdapterManager`] wrapping the given `manager` +/// with required callback function pointers (`get_binary_id_callback`, `get_test_name_callback`, `construct_adapter_callback`), +/// and invokes [`engine_ffi::FuzzTestWorkerMaybeRun`]. +/// This function constructs a C-compatible [`engine_ffi::FuzzTestAdapterManager`] wrapping +/// the given `manager` with required callback function pointers (`get_binary_id_callback`, +/// `get_test_name_callback`, `construct_adapter_callback`), and invokes +/// [`engine_ffi::FuzzTestWorkerMaybeRun`]. +/// +/// # Returns +/// - `Some(WorkerStatus::Success)`: The process ran in worker mode and completed successfully. +/// - `Some(WorkerStatus::Failure)`: The process ran in worker mode and encountered a failure. +/// - `None`: Worker mode was not requested (`kFuzzTestWorkerNotRequired`). The caller should fall +/// back to standalone smoke-test execution. +fn maybe_run_as_worker(manager: &RustFuzzTestAdapterManager) -> Option { + let ffi_manager = make_ffi_manager(manager); + + // SAFETY: `manager` is a valid heap-allocated `RustFuzzTestAdapterManager`, + // and `ffi_manager` contains valid C-compatible function pointer callbacks. + let status = unsafe { engine_ffi::FuzzTestWorkerMaybeRun(&ffi_manager) }; + + match status { + engine_ffi::FuzzTestWorkerStatus::Success => Some(WorkerStatus::Success), + engine_ffi::FuzzTestWorkerStatus::Failure => Some(WorkerStatus::Failure), + engine_ffi::FuzzTestWorkerStatus::NotRequired => None, + } +} + +fn run_standalone_mode( + manager: &RustFuzzTestAdapterManager, + centipede_args: CentipedeArgs, +) -> engine_ffi::FuzzTestControllerStatus { + let ffi_manager = make_ffi_manager(manager); + + // SAFETY: `ffi_manager` wraps `manager` which is guaranteed to be alive. `centipede_args` is + // alive until the end of this function, since we have the ownership, ensuring that the + // pointers passed to `FuzzTestControllerRun` remain valid. + unsafe { engine_ffi::FuzzTestControllerRun(&ffi_manager, ¢ipede_args.as_bytes_views()) } +} diff --git a/rust/tests/macro_compiles.rs b/rust/tests/macro_compiles.rs new file mode 100644 index 000000000..a294458e5 --- /dev/null +++ b/rust/tests/macro_compiles.rs @@ -0,0 +1,10 @@ +use fuzztest::domains::arbitrary::Arbitrary; +use fuzztest::fuzztest; + +#[fuzztest(_a = Arbitrary::::default())] +fn fuzztest_macro_compiles(_a: i32) {} + +#[fuzztest(_a = Arbitrary::::default(), _b = Arbitrary::::default())] +fn fuzztest_macro_compiles_with_two_args(_a: i32, _b: i32) {} + +fn main() {} diff --git a/rust/tests/trybuild.rs b/rust/tests/trybuild.rs new file mode 100644 index 000000000..c2d9ebd3d --- /dev/null +++ b/rust/tests/trybuild.rs @@ -0,0 +1,5 @@ +#[test] +fn fuzztest() { + let t = trybuild::TestCases::new(); + t.pass("tests/macro_compiles.rs"); +}