1use crate::golden_trace::GoldenTrace;
13use std::io::Write;
14use std::process::Command;
15use std::sync::atomic::{AtomicU64, Ordering};
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
19pub enum VerificationLevel {
20 Minimal,
22 #[default]
24 Standard,
25 Strict,
27}
28
29impl std::fmt::Display for VerificationLevel {
30 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31 match self {
32 VerificationLevel::Minimal => write!(f, "minimal"),
33 VerificationLevel::Standard => write!(f, "standard"),
34 VerificationLevel::Strict => write!(f, "strict"),
35 }
36 }
37}
38
39#[derive(Debug, Clone)]
41pub struct VerifierConfig {
42 pub level: VerificationLevel,
44 pub allow_unsafe: bool,
46 pub max_clippy_warnings: usize,
48 pub timeout_secs: u64,
50}
51
52impl Default for VerifierConfig {
53 fn default() -> Self {
54 Self {
55 level: VerificationLevel::Standard,
56 allow_unsafe: false,
57 max_clippy_warnings: 0,
58 timeout_secs: 30,
59 }
60 }
61}
62
63#[derive(Debug, Clone)]
65pub struct VerificationResult {
66 pub passed: bool,
68 pub errors: Vec<String>,
70 pub warnings: Vec<String>,
72 pub unsafe_count: usize,
74 pub compilation_time_ms: u64,
76}
77
78impl VerificationResult {
79 pub fn is_clean(&self) -> bool {
81 self.passed && self.errors.is_empty() && self.warnings.is_empty()
82 }
83}
84
85#[derive(Debug, Clone, Default)]
87pub struct VerifierStats {
88 pub total_verified: usize,
90 pub passed: usize,
92 pub failed: usize,
94 pub total_unsafe_blocks: usize,
96 pub avg_verification_time_ms: f64,
98}
99
100impl VerifierStats {
101 pub fn pass_rate(&self) -> f64 {
103 if self.total_verified == 0 {
104 0.0
105 } else {
106 self.passed as f64 / self.total_verified as f64
107 }
108 }
109}
110
111pub struct TraceVerifier {
115 config: VerifierConfig,
116 stats: VerifierStats,
117}
118
119impl Default for TraceVerifier {
120 fn default() -> Self {
121 Self::new()
122 }
123}
124
125impl TraceVerifier {
126 pub fn new() -> Self {
128 Self { config: VerifierConfig::default(), stats: VerifierStats::default() }
129 }
130
131 pub fn with_config(config: VerifierConfig) -> Self {
133 Self { config, stats: VerifierStats::default() }
134 }
135
136 pub fn config(&self) -> &VerifierConfig {
138 &self.config
139 }
140
141 pub fn stats(&self) -> &VerifierStats {
143 &self.stats
144 }
145
146 pub fn verify_compilation(&self, rust_code: &str) -> Result<(), String> {
148 static COUNTER: AtomicU64 = AtomicU64::new(0);
150 let counter = COUNTER.fetch_add(1, Ordering::SeqCst);
151 let unique_id = format!("{}_{}", std::process::id(), counter);
152
153 let temp_dir = std::env::temp_dir();
154 let rust_path = temp_dir.join(format!("decy_verify_{}.rs", unique_id));
155
156 let mut file = std::fs::File::create(&rust_path)
158 .map_err(|e| format!("Failed to create temp file: {}", e))?;
159 file.write_all(rust_code.as_bytes())
160 .map_err(|e| format!("Failed to write temp file: {}", e))?;
161
162 let output = Command::new("rustc")
164 .arg("--emit=metadata")
165 .arg("--edition=2021")
166 .arg("-o")
167 .arg(temp_dir.join(format!("decy_verify_{}.rmeta", unique_id)))
168 .arg(&rust_path)
169 .output()
170 .map_err(|e| format!("Failed to run rustc: {}", e))?;
171
172 let _ = std::fs::remove_file(&rust_path);
174 let _ = std::fs::remove_file(temp_dir.join(format!("decy_verify_{}.rmeta", unique_id)));
175
176 if output.status.success() {
177 Ok(())
178 } else {
179 let stderr = String::from_utf8_lossy(&output.stderr);
180 Err(stderr.to_string())
181 }
182 }
183
184 pub fn count_unsafe_blocks(&self, rust_code: &str) -> usize {
186 rust_code.matches("unsafe {").count() + rust_code.matches("unsafe{").count()
188 }
189
190 pub fn verify_safety(&self, rust_code: &str) -> Result<(), String> {
192 let unsafe_count = self.count_unsafe_blocks(rust_code);
193
194 if !self.config.allow_unsafe && unsafe_count > 0 {
195 return Err(format!(
196 "Code contains {} unsafe block(s) but unsafe is not allowed",
197 unsafe_count
198 ));
199 }
200
201 Ok(())
202 }
203
204 pub fn verify_trace(&mut self, trace: &GoldenTrace) -> VerificationResult {
206 let start = std::time::Instant::now();
207 let mut errors = Vec::new();
208 let warnings = Vec::new();
209
210 let rust_code = if trace.rust_snippet.contains("fn main") {
212 trace.rust_snippet.clone()
213 } else {
214 format!("fn main() {{\n{}\n}}", trace.rust_snippet)
215 };
216
217 if let Err(e) = self.verify_compilation(&rust_code) {
219 errors.push(e);
220 }
221
222 let unsafe_count = self.count_unsafe_blocks(&rust_code);
224 if !self.config.allow_unsafe && unsafe_count > 0 {
225 errors.push(format!("Contains {} unsafe block(s)", unsafe_count));
226 }
227
228 let passed = errors.is_empty();
229 let compilation_time_ms = start.elapsed().as_millis() as u64;
230
231 self.stats.total_verified += 1;
233 if passed {
234 self.stats.passed += 1;
235 } else {
236 self.stats.failed += 1;
237 }
238 self.stats.total_unsafe_blocks += unsafe_count;
239
240 let n = self.stats.total_verified as f64;
242 self.stats.avg_verification_time_ms =
243 (self.stats.avg_verification_time_ms * (n - 1.0) + compilation_time_ms as f64) / n;
244
245 VerificationResult { passed, errors, warnings, unsafe_count, compilation_time_ms }
246 }
247
248 pub fn verify_batch(&self, traces: &[GoldenTrace]) -> Vec<VerificationResult> {
250 let mut verifier = Self::with_config(self.config.clone());
251 traces.iter().map(|t| verifier.verify_trace(t)).collect()
252 }
253
254 pub fn filter_valid<'a>(&self, traces: &'a [GoldenTrace]) -> Vec<&'a GoldenTrace> {
256 let mut verifier = Self::with_config(self.config.clone());
257 traces.iter().filter(|t| verifier.verify_trace(t).passed).collect()
258 }
259}
260
261#[cfg(test)]
262mod tests {
263 use super::*;
264 use crate::golden_trace::{GoldenTrace, TraceTier};
265
266 fn make_trace(rust_code: &str) -> GoldenTrace {
267 GoldenTrace::new("int x = 0;".to_string(), rust_code.to_string(), TraceTier::P0, "test.c")
268 }
269
270 #[test]
275 fn verification_level_display() {
276 assert_eq!(VerificationLevel::Minimal.to_string(), "minimal");
277 assert_eq!(VerificationLevel::Standard.to_string(), "standard");
278 assert_eq!(VerificationLevel::Strict.to_string(), "strict");
279 }
280
281 #[test]
282 fn verification_level_default() {
283 let level = VerificationLevel::default();
284 assert_eq!(level, VerificationLevel::Standard);
285 }
286
287 #[test]
292 fn verifier_config_default() {
293 let config = VerifierConfig::default();
294 assert_eq!(config.level, VerificationLevel::Standard);
295 assert!(!config.allow_unsafe);
296 assert_eq!(config.max_clippy_warnings, 0);
297 assert_eq!(config.timeout_secs, 30);
298 }
299
300 #[test]
305 fn test_verifier_default() {
306 let verifier = TraceVerifier::new();
307 assert_eq!(verifier.config().level, VerificationLevel::Standard);
308 }
309
310 #[test]
311 fn test_count_unsafe_simple() {
312 let verifier = TraceVerifier::new();
313 let code = "unsafe { }";
314 assert_eq!(verifier.count_unsafe_blocks(code), 1);
315 }
316
317 #[test]
318 fn test_verification_result_is_clean() {
319 let result = VerificationResult {
320 passed: true,
321 errors: vec![],
322 warnings: vec![],
323 unsafe_count: 0,
324 compilation_time_ms: 0,
325 };
326 assert!(result.is_clean());
327 }
328
329 #[test]
330 fn result_is_not_clean_with_errors() {
331 let result = VerificationResult {
332 passed: false,
333 errors: vec!["err".to_string()],
334 warnings: vec![],
335 unsafe_count: 0,
336 compilation_time_ms: 0,
337 };
338 assert!(!result.is_clean());
339 }
340
341 #[test]
342 fn result_is_not_clean_with_warnings() {
343 let result = VerificationResult {
344 passed: true,
345 errors: vec![],
346 warnings: vec!["warn".to_string()],
347 unsafe_count: 0,
348 compilation_time_ms: 0,
349 };
350 assert!(!result.is_clean());
351 }
352
353 #[test]
358 fn stats_pass_rate_empty() {
359 let stats = VerifierStats::default();
360 assert!((stats.pass_rate() - 0.0).abs() < f64::EPSILON);
361 }
362
363 #[test]
364 fn stats_pass_rate_all_passed() {
365 let stats = VerifierStats {
366 total_verified: 10,
367 passed: 10,
368 failed: 0,
369 total_unsafe_blocks: 0,
370 avg_verification_time_ms: 5.0,
371 };
372 assert!((stats.pass_rate() - 1.0).abs() < f64::EPSILON);
373 }
374
375 #[test]
376 fn stats_pass_rate_mixed() {
377 let stats = VerifierStats {
378 total_verified: 4,
379 passed: 3,
380 failed: 1,
381 total_unsafe_blocks: 1,
382 avg_verification_time_ms: 10.0,
383 };
384 assert!((stats.pass_rate() - 0.75).abs() < 0.01);
385 }
386
387 #[test]
392 fn verifier_with_config() {
393 let config = VerifierConfig {
394 level: VerificationLevel::Strict,
395 allow_unsafe: true,
396 max_clippy_warnings: 5,
397 timeout_secs: 60,
398 };
399 let verifier = TraceVerifier::with_config(config);
400 assert_eq!(verifier.config().level, VerificationLevel::Strict);
401 assert!(verifier.config().allow_unsafe);
402 assert_eq!(verifier.config().max_clippy_warnings, 5);
403 }
404
405 #[test]
406 fn verifier_default_trait() {
407 let verifier = TraceVerifier::default();
408 assert_eq!(verifier.config().level, VerificationLevel::Standard);
409 }
410
411 #[test]
412 fn verifier_initial_stats() {
413 let verifier = TraceVerifier::new();
414 let stats = verifier.stats();
415 assert_eq!(stats.total_verified, 0);
416 assert_eq!(stats.passed, 0);
417 assert_eq!(stats.failed, 0);
418 }
419
420 #[test]
425 fn count_unsafe_no_unsafe() {
426 let verifier = TraceVerifier::new();
427 assert_eq!(verifier.count_unsafe_blocks("fn main() { let x = 1; }"), 0);
428 }
429
430 #[test]
431 fn count_unsafe_multiple() {
432 let verifier = TraceVerifier::new();
433 let code = "unsafe { ptr::read(p) }; unsafe { ptr::write(p, v) }";
434 assert_eq!(verifier.count_unsafe_blocks(code), 2);
435 }
436
437 #[test]
438 fn count_unsafe_no_space() {
439 let verifier = TraceVerifier::new();
440 let code = "unsafe{ ptr::read(p) }";
441 assert_eq!(verifier.count_unsafe_blocks(code), 1);
442 }
443
444 #[test]
449 fn verify_safety_no_unsafe_allowed() {
450 let verifier = TraceVerifier::new(); let result = verifier.verify_safety("unsafe { ptr::read(p) }");
452 assert!(result.is_err());
453 assert!(result.unwrap_err().contains("unsafe block"));
454 }
455
456 #[test]
457 fn verify_safety_safe_code() {
458 let verifier = TraceVerifier::new();
459 let result = verifier.verify_safety("fn main() { let x = 1; }");
460 assert!(result.is_ok());
461 }
462
463 #[test]
464 fn verify_safety_unsafe_allowed() {
465 let config = VerifierConfig { allow_unsafe: true, ..Default::default() };
466 let verifier = TraceVerifier::with_config(config);
467 let result = verifier.verify_safety("unsafe { ptr::read(p) }");
468 assert!(result.is_ok());
469 }
470
471 #[test]
476 fn verify_compilation_valid_code() {
477 let verifier = TraceVerifier::new();
478 let result = verifier.verify_compilation("fn main() {}");
480 if result.is_ok() {
482 }
484 }
486
487 #[test]
488 fn verify_compilation_invalid_code() {
489 let verifier = TraceVerifier::new();
490 let _result = verifier.verify_compilation("fn main() { let x: i32 = \"bad\"; }");
492 }
494
495 #[test]
496 fn verify_compilation_empty() {
497 let verifier = TraceVerifier::new();
498 let _result = verifier.verify_compilation("");
499 }
500
501 #[test]
506 fn verify_trace_valid_code() {
507 let mut verifier = TraceVerifier::new();
508 let trace = make_trace("let _x: i32 = 42;");
509 let result = verifier.verify_trace(&trace);
510 assert_eq!(verifier.stats().total_verified, 1);
512 if result.passed {
514 assert!(result.errors.is_empty());
515 assert_eq!(result.unsafe_count, 0);
516 }
517 }
518
519 #[test]
520 fn verify_trace_with_fn_main() {
521 let mut verifier = TraceVerifier::new();
522 let trace = make_trace("fn main() {}");
523 let _result = verifier.verify_trace(&trace);
524 assert_eq!(verifier.stats().total_verified, 1);
526 }
527
528 #[test]
529 fn verify_trace_invalid_code() {
530 let mut verifier = TraceVerifier::new();
531 let trace = make_trace("let x: i32 = \"bad\";");
532 let result = verifier.verify_trace(&trace);
533 assert_eq!(verifier.stats().total_verified, 1);
536 if !result.passed {
537 assert!(!result.errors.is_empty());
538 }
539 }
540
541 #[test]
542 fn verify_trace_with_unsafe() {
543 let mut verifier = TraceVerifier::new(); let trace = make_trace("unsafe { std::ptr::null::<i32>(); }");
545 let result = verifier.verify_trace(&trace);
546 assert!(result.unsafe_count > 0);
548 assert_eq!(verifier.stats().total_verified, 1);
549 }
550
551 #[test]
552 fn verify_trace_stats_accumulate() {
553 let mut verifier = TraceVerifier::new();
554 let trace1 = make_trace("let _x: i32 = 1;");
555 let trace2 = make_trace("let _y: i32 = 2;");
556 verifier.verify_trace(&trace1);
557 verifier.verify_trace(&trace2);
558 assert_eq!(verifier.stats().total_verified, 2);
559 assert_eq!(
561 verifier.stats().passed + verifier.stats().failed,
562 verifier.stats().total_verified
563 );
564 }
565
566 #[test]
571 fn verify_batch_returns_correct_count() {
572 let verifier = TraceVerifier::new();
573 let traces = vec![make_trace("let _x: i32 = 1;"), make_trace("let _y: i32 = 2;")];
574 let results = verifier.verify_batch(&traces);
575 assert_eq!(results.len(), 2);
576 }
577
578 #[test]
579 fn verify_batch_empty() {
580 let verifier = TraceVerifier::new();
581 let results = verifier.verify_batch(&[]);
582 assert!(results.is_empty());
583 }
584
585 #[test]
590 fn filter_valid_returns_subset() {
591 let verifier = TraceVerifier::new();
592 let traces = vec![make_trace("let _x: i32 = 1;"), make_trace("let _y: i32 = 2;")];
593 let valid = verifier.filter_valid(&traces);
594 assert!(valid.len() <= traces.len());
596 }
597
598 #[test]
599 fn filter_valid_excludes_invalid() {
600 let verifier = TraceVerifier::new();
601 let traces = vec![
602 make_trace("let _x: i32 = 1;"),
603 make_trace("let y: i32 = \"bad\";"),
604 make_trace("let _z: i32 = 3;"),
605 ];
606 let valid = verifier.filter_valid(&traces);
607 assert!(valid.len() <= traces.len());
609 }
610}