Repository: trailofbits/test-fuzz (language: rust)

Issue:
Add option to automatically apply `cast_checks`
https://github.com/trailofbits/cast_checks


Discussion / hints:

Fix patch:
diff --git a/README.md b/README.md
index a4d7933b..1834a9db 100644
--- a/README.md
+++ b/README.md
@@ -401,6 +401,10 @@ test-fuzz = { version = "*", features = ["self_ty_in_mod_name"] }
 
 The `test-fuzz` package currently supports the following features:
 
+### `cast_checks`
+
+Use [`cast_checks`] to automatically check target functions for invalid casts.
+
 ### `self_ty_in_mod_name`
 
 Incorporate an `impl`'s `Self` type into the names of modules generated for the `impl`. Expansion of the `test_fuzz` macro adds a module definition to the enclosing scope. By default, the module is named `target_fuzz`, where `target` is the name of the target. If the target appears in an `impl` block, then use of this feature causes the module to instead be named `path_target_fuzz`, where `path` is the path of the `impl`'s `Self` type converted to snake case and joined with `_`. (See also [`rename`] above.)
@@ -521,6 +525,7 @@ These options are incompatible in the following sense. If a fuzz target's argume
 [`cargo test-fuzz` command]: #cargo-test-fuzz-command
 [`cargo test-fuzz`]: #cargo-test-fuzz-command
 [`cargo-clone`]: https://github.com/JanLikar/cargo-clone
+[`cast_checks`]: https://github.com/trailofbits/cast_checks
 [`convert`]: #convert--x-y
 [`core::ops::Add`]: https://doc.rust-lang.org/beta/core/ops/trait.Add.html
 [`core::ops::Div`]: https://doc.rust-lang.org/beta/core/ops/trait.Div.html
diff --git a/macro/Cargo.toml b/macro/Cargo.toml
index 312b87bb..ca453610 100644
--- a/macro/Cargo.toml
+++ b/macro/Cargo.toml
@@ -23,6 +23,7 @@ quote = "1.0"
 syn = { version = "2.0", features = ["full", "parsing", "visit", "visit-mut"] }
 
 [features]
+__cast_checks = []
 __persistent = []
 __self_ty_in_mod_name = ["heck"]
 
diff --git a/macro/src/lib.rs b/macro/src/lib.rs
index af050a1e..5b98c7af 100644
--- a/macro/src/lib.rs
+++ b/macro/src/lib.rs
@@ -294,6 +294,18 @@ fn map_method_or_fn(
         }
     }
 
+    let mut attrs = attrs.clone();
+    let maybe_use_cast_checks = if cfg!(feature = "__cast_checks") {
+        attrs.push(parse_quote! {
+            #[test_fuzz::cast_checks::enable]
+        });
+        quote! {
+            use test_fuzz::cast_checks;
+        }
+    } else {
+        quote! {}
+    };
+
     let impl_ty_idents = type_idents(generics);
     let ty_idents = type_idents(&sig.generics);
     let combined_type_idents = [impl_ty_idents.clone(), ty_idents.clone()].concat();
@@ -724,6 +736,8 @@ fn map_method_or_fn(
     (
         parse_quote! {
             #(#attrs)* #vis #defaultness #sig {
+                #maybe_use_cast_checks
+
                 #write_generic_args_and_args
 
                 #in_production_write_generic_args_and_args


Test patch:
diff --git a/cargo-test-fuzz/tests/fuzz_cast.rs b/cargo-test-fuzz/tests/fuzz_cast.rs
new file mode 100644
index 00000000..fec999df
--- /dev/null
+++ b/cargo-test-fuzz/tests/fuzz_cast.rs
@@ -0,0 +1,35 @@
+use predicates::prelude::*;
+use testing::{examples, retry, CommandExt};
+
+const MAX_TOTAL_TIME: &str = "60";
+
+#[test]
+fn fuzz_cast() {
+    examples::test("cast", "test")
+        .unwrap()
+        .logged_assert()
+        .success();
+
+    for use_cast_checks in [false, true] {
+        let mut args = vec![
+            "--exit-code",
+            "--run-until-crash",
+            "--max-total-time",
+            MAX_TOTAL_TIME,
+        ];
+        let code = if use_cast_checks {
+            args.push("--features=test-fuzz/cast_checks");
+            1
+        } else {
+            0
+        };
+        retry(3, || {
+            examples::test_fuzz("cast", "target")
+                .unwrap()
+                .args(&args)
+                .logged_assert()
+                .try_code(predicate::eq(code))
+        })
+        .unwrap();
+    }
+}
diff --git a/examples/tests/cast.rs b/examples/tests/cast.rs
new file mode 100644
index 00000000..18d9a62c
--- /dev/null
+++ b/examples/tests/cast.rs
@@ -0,0 +1,9 @@
+#[test_fuzz::test_fuzz]
+fn target(x: u64) {
+    let _ = x as u32;
+}
+
+#[test]
+fn test() {
+    target(0);
+}
diff --git a/test-fuzz/Cargo.toml b/test-fuzz/Cargo.toml
index e7ffe507..f9c719f6 100644
--- a/test-fuzz/Cargo.toml
+++ b/test-fuzz/Cargo.toml
@@ -11,6 +11,7 @@ repository = "https://github.com/trailofbits/test-fuzz"
 
 [dependencies]
 afl = { version = "0.15", optional = true }
+cast_checks = { version = "0.1", optional = true }
 serde = "1.0"
 
 internal = { path = "../internal", package = "test-fuzz-internal", version = "=5.0.0" }
@@ -34,6 +35,7 @@ testing = { path = "../testing", package = "test-fuzz-testing" }
 # https://github.com/djkoloski/rust_serialization_benchmark
 
 [features]
+cast_checks = ["dep:cast_checks", "test-fuzz-macro/__cast_checks"]
 self_ty_in_mod_name = ["test-fuzz-macro/__self_ty_in_mod_name"]
 serde_bincode = ["internal/__serde_bincode"]
 serde_cbor = ["internal/__serde_cbor"]
diff --git a/test-fuzz/src/lib.rs b/test-fuzz/src/lib.rs
index 00f193ef..b80dd3bc 100644
--- a/test-fuzz/src/lib.rs
+++ b/test-fuzz/src/lib.rs
@@ -6,6 +6,10 @@ pub use test_fuzz_macro::{test_fuzz, test_fuzz_impl};
 #[cfg(feature = "__persistent")]
 pub use afl;
 
+// smoelius: Do the same for `cast_checks`.
+#[cfg(feature = "cast_checks")]
+pub use cast_checks;
+
 // smoelius: Unfortunately, the same trick doesn't work for serde.
 // https://github.com/serde-rs/serde/issues/1465