provable_contracts/
fuzz_gen.rs1use std::fmt::Write;
8
9use crate::schema::Contract;
10
11pub fn generate_fuzz_target(contract: &Contract, stem: &str) -> String {
17 let mut out = String::with_capacity(2048);
18
19 let _ = writeln!(out, "#![no_main]");
20 let _ = writeln!(
21 out,
22 "// Auto-generated fuzz target from contract: {}",
23 contract.metadata.description
24 );
25 let _ = writeln!(out, "// Regenerate with: pv fuzz contracts/{stem}.yaml");
26 let _ = writeln!(out);
27 let _ = writeln!(out, "use libfuzzer_sys::fuzz_target;");
28 let _ = writeln!(out);
29
30 let preconditions: Vec<&str> = contract
32 .proof_obligations
33 .iter()
34 .filter(|ob| ob.obligation_type == crate::schema::ObligationType::Precondition)
35 .filter_map(|ob| ob.formal.as_deref())
36 .collect();
37
38 let postconditions: Vec<&str> = contract
40 .proof_obligations
41 .iter()
42 .filter(|ob| ob.obligation_type == crate::schema::ObligationType::Postcondition)
43 .filter_map(|ob| ob.formal.as_deref())
44 .collect();
45
46 let _ = writeln!(out, "fuzz_target!(|data: &[u8]| {{");
47 let _ = writeln!(out, " // Minimum input size guard");
48 let _ = writeln!(out, " if data.len() < 8 {{ return; }}");
49 let _ = writeln!(out);
50
51 let _ = writeln!(out, " // Parse input: interpret bytes as f32 vector");
53 let _ = writeln!(
54 out,
55 " let len = ((data[0] as usize) % 64).saturating_add(1);"
56 );
57 let _ = writeln!(out, " let byte_len = len * 4;");
58 let _ = writeln!(out, " if data.len() < 4 + byte_len {{ return; }}");
59 let _ = writeln!(out, " let input: Vec<f32> = data[4..4 + byte_len]");
60 let _ = writeln!(out, " .chunks_exact(4)");
61 let _ = writeln!(
62 out,
63 " .map(|b| f32::from_le_bytes(b.try_into().unwrap()))"
64 );
65 let _ = writeln!(out, " .collect();");
66 let _ = writeln!(out);
67
68 if preconditions.is_empty() {
70 let _ = writeln!(
71 out,
72 " // No explicit preconditions — filter NaN/Inf for numerical stability"
73 );
74 } else {
75 let _ = writeln!(out, " // Contract precondition gates");
76 for pre in &preconditions {
77 let _ = writeln!(out, " // PRE: {pre}");
78 }
79 }
80 let _ = writeln!(
81 out,
82 " if input.is_empty() || input.iter().any(|x| x.is_nan() || x.is_infinite()) {{"
83 );
84 let _ = writeln!(out, " return;");
85 let _ = writeln!(out, " }}");
86 let _ = writeln!(out);
87
88 let fn_name = stem.replace('-', "_").replace("_v1", "");
90 let _ = writeln!(out, " // Kernel under test — must not panic");
91 let _ = writeln!(out, " let result = std::panic::catch_unwind(|| {{");
92 let _ = writeln!(out, " let mut output = vec![0.0f32; input.len()];");
93 let _ = writeln!(
94 out,
95 " // TODO: Wire up: {fn_name}(&input, &mut output);"
96 );
97 let _ = writeln!(out, " output");
98 let _ = writeln!(out, " }});");
99 let _ = writeln!(out);
100
101 if !postconditions.is_empty() {
103 let _ = writeln!(out, " // Contract postcondition checks");
104 let _ = writeln!(out, " if let Ok(output) = result {{");
105 for post in &postconditions {
106 let _ = writeln!(out, " // POST: {post}");
107 }
108 let _ = writeln!(
109 out,
110 " assert_eq!(output.len(), input.len(), \"output length mismatch\");"
111 );
112 let _ = writeln!(out, " }}");
113 }
114
115 let _ = writeln!(out, "}});");
116
117 out
118}
119
120pub fn generate_fuzz_cargo_toml(crate_name: &str) -> String {
122 let mut out = String::with_capacity(512);
123
124 let _ = writeln!(out, "[package]");
125 let _ = writeln!(out, "name = \"{crate_name}-fuzz\"");
126 let _ = writeln!(out, "version = \"0.0.0\"");
127 let _ = writeln!(out, "publish = false");
128 let _ = writeln!(out, "edition = \"2021\"");
129 let _ = writeln!(out);
130 let _ = writeln!(out, "[package.metadata]");
131 let _ = writeln!(out, "cargo-fuzz = true");
132 let _ = writeln!(out);
133 let _ = writeln!(out, "[dependencies]");
134 let _ = writeln!(out, "libfuzzer-sys = \"0.11\"");
135 let _ = writeln!(out);
136 let _ = writeln!(out, "# TODO: Add dependency on the crate under test");
137 let _ = writeln!(out, "# {crate_name} = {{ path = \"..\" }}");
138
139 out
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145 use crate::schema::parse_contract_str;
146
147 fn softmax_contract() -> Contract {
148 parse_contract_str(
149 r#"
150metadata:
151 version: "1.0.0"
152 description: "Softmax kernel"
153 references: ["Paper A"]
154equations:
155 softmax:
156 formula: "σ(x)_i = exp(x_i - max(x)) / Σ_j exp(x_j - max(x))"
157 domain: "x ∈ ℝ^n, n ≥ 1"
158proof_obligations:
159 - type: precondition
160 property: "Input vector is finite and non-empty"
161 formal: "∀i: ¬isNaN(x_i) ∧ ¬isInf(x_i) ∧ len(x) > 0"
162 - type: postcondition
163 property: "Output is a valid probability distribution"
164 formal: "len(σ(x)) = len(x) ∧ ∀i: 0 < σ(x)_i < 1 ∧ |Σ σ(x)_i - 1| < ε"
165 - type: invariant
166 property: "Output sums to 1"
167falsification_tests:
168 - id: FALSIFY-001
169 rule: "Normalization"
170 prediction: "sum ≈ 1.0"
171 if_fails: "Bug"
172kani_harnesses:
173 - id: KANI-001
174 obligation: "sums to 1"
175 bound: 8
176"#,
177 )
178 .unwrap()
179 }
180
181 #[test]
182 fn generates_fuzz_target() {
183 let contract = softmax_contract();
184 let output = generate_fuzz_target(&contract, "softmax-kernel-v1");
185
186 assert!(output.contains("#![no_main]"));
187 assert!(output.contains("use libfuzzer_sys::fuzz_target;"));
188 assert!(output.contains("fuzz_target!"));
189 assert!(output.contains("catch_unwind"));
190 assert!(output.contains("PRE:"));
191 assert!(output.contains("POST:"));
192 }
193
194 #[test]
195 fn generates_fuzz_target_no_preconditions() {
196 let contract = parse_contract_str(
197 r#"
198metadata:
199 version: "1.0.0"
200 description: "Simple kernel"
201 references: ["X"]
202equations:
203 f:
204 formula: "f(x) = x"
205proof_obligations:
206 - type: invariant
207 property: "identity"
208falsification_tests:
209 - id: F-001
210 rule: "id"
211 prediction: "f(x) = x"
212 if_fails: "bug"
213kani_harnesses:
214 - id: K-001
215 obligation: "id"
216 bound: 8
217"#,
218 )
219 .unwrap();
220
221 let output = generate_fuzz_target(&contract, "identity-v1");
222 assert!(output.contains("No explicit preconditions"));
223 assert!(output.contains("fuzz_target!"));
224 }
225
226 #[test]
227 fn generates_cargo_toml() {
228 let toml = generate_fuzz_cargo_toml("aprender");
229 assert!(toml.contains("aprender-fuzz"));
230 assert!(toml.contains("libfuzzer-sys"));
231 assert!(toml.contains("cargo-fuzz = true"));
232 }
233}