1use runmat_time::Instant;
7use std::collections::HashMap;
8use std::time::Duration;
9
10use crate::format::*;
11use crate::{Snapshot, SnapshotResult};
12
13#[derive(Default)]
15pub struct SnapshotValidator {
16 config: ValidationConfig,
18
19 stats: ValidationStats,
21}
22
23#[derive(Debug, Clone)]
25pub struct ValidationConfig {
26 pub format_validation: bool,
28
29 pub integrity_checking: bool,
31
32 pub compatibility_checking: bool,
34
35 pub performance_validation: bool,
37
38 pub max_validation_time: Duration,
40
41 pub strict_mode: bool,
43}
44
45#[derive(Debug, Default)]
47pub struct ValidationStats {
48 pub checks_performed: HashMap<String, u64>,
50
51 pub check_times: HashMap<String, Duration>,
53
54 pub total_time: Duration,
56
57 pub errors: Vec<ValidationError>,
59
60 pub warnings: Vec<ValidationWarning>,
62}
63
64#[derive(Debug, Clone)]
66pub struct ValidationError {
67 pub error_type: ValidationErrorType,
68 pub message: String,
69 pub location: Option<String>,
70 pub severity: ErrorSeverity,
71}
72
73#[derive(Debug, Clone)]
75pub struct ValidationWarning {
76 pub warning_type: ValidationWarningType,
77 pub message: String,
78 pub recommendation: Option<String>,
79}
80
81#[derive(Debug, Clone)]
83pub enum ValidationErrorType {
84 FormatError,
85 IntegrityError,
86 CompatibilityError,
87 PerformanceError,
88 ConfigurationError,
89}
90
91#[derive(Debug, Clone)]
93pub enum ValidationWarningType {
94 PerformanceWarning,
95 CompatibilityWarning,
96 ConfigurationWarning,
97 DeprecationWarning,
98}
99
100#[derive(Debug, Clone)]
102pub enum ErrorSeverity {
103 Critical,
104 High,
105 Medium,
106 Low,
107}
108
109#[derive(Debug)]
111pub struct ValidationResult {
112 pub is_valid: bool,
114
115 pub score: u8,
117
118 pub errors: Vec<ValidationError>,
120
121 pub warnings: Vec<ValidationWarning>,
123
124 pub metrics: ValidationMetrics,
126
127 pub recommendations: Vec<String>,
129}
130
131#[derive(Debug)]
133pub struct ValidationMetrics {
134 pub total_time: Duration,
135 pub checks_performed: usize,
136 pub throughput: f64, pub memory_used: usize,
138}
139
140impl SnapshotValidator {
141 pub fn new() -> Self {
143 Self::default()
144 }
145
146 pub fn with_config(config: ValidationConfig) -> Self {
148 Self {
149 config,
150 stats: ValidationStats::default(),
151 }
152 }
153
154 pub fn validate_format(&mut self, format: &SnapshotFormat) -> SnapshotResult<ValidationResult> {
156 let start = Instant::now();
157 let mut errors = Vec::new();
158 let mut warnings = Vec::new();
159
160 self.validate_header(&format.header, &mut errors, &mut warnings)?;
162
163 self.validate_data_section(format, &mut errors, &mut warnings)?;
165
166 if format.header.checksum_info.is_some() {
168 self.validate_checksum(format, &mut errors, &mut warnings)?;
169 }
170
171 let validation_time = start.elapsed();
172 self.update_stats("format_validation", validation_time);
173
174 Ok(self.create_validation_result(errors, warnings, validation_time, "format_validation"))
175 }
176
177 pub fn validate_content(&mut self, snapshot: &Snapshot) -> SnapshotResult<ValidationResult> {
179 let start = Instant::now();
180 let mut errors = Vec::new();
181 let mut warnings = Vec::new();
182
183 self.validate_builtin_registry(&snapshot.builtins, &mut errors, &mut warnings)?;
185
186 self.validate_hir_cache(&snapshot.hir_cache, &mut errors, &mut warnings)?;
188
189 self.validate_bytecode_cache(&snapshot.bytecode_cache, &mut errors, &mut warnings)?;
191
192 self.validate_gc_presets(&snapshot.gc_presets, &mut errors, &mut warnings)?;
194
195 self.validate_optimization_hints(&snapshot.optimization_hints, &mut errors, &mut warnings)?;
197
198 let validation_time = start.elapsed();
199 self.update_stats("content_validation", validation_time);
200
201 Ok(self.create_validation_result(errors, warnings, validation_time, "content_validation"))
202 }
203
204 pub fn validate_compatibility(
206 &mut self,
207 snapshot: &Snapshot,
208 ) -> SnapshotResult<ValidationResult> {
209 let start = Instant::now();
210 let mut errors = Vec::new();
211 let mut warnings = Vec::new();
212
213 if !snapshot.metadata.is_compatible() {
215 errors.push(ValidationError {
216 error_type: ValidationErrorType::CompatibilityError,
217 message: "Snapshot version is not compatible with current RunMat version"
218 .to_string(),
219 location: Some("metadata.runmat_version".to_string()),
220 severity: ErrorSeverity::High,
221 });
222 }
223
224 if !SnapshotHeader::new(snapshot.metadata.clone()).is_platform_compatible() {
226 warnings.push(ValidationWarning {
227 warning_type: ValidationWarningType::CompatibilityWarning,
228 message: "Snapshot was created for a different platform".to_string(),
229 recommendation: Some("Performance may be suboptimal".to_string()),
230 });
231 }
232
233 self.validate_feature_compatibility(&snapshot.metadata, &mut errors, &mut warnings)?;
235
236 let validation_time = start.elapsed();
237 self.update_stats("compatibility_validation", validation_time);
238
239 Ok(self.create_validation_result(
240 errors,
241 warnings,
242 validation_time,
243 "compatibility_validation",
244 ))
245 }
246
247 fn validate_header(
249 &self,
250 header: &SnapshotHeader,
251 errors: &mut Vec<ValidationError>,
252 _warnings: &mut [ValidationWarning],
253 ) -> SnapshotResult<()> {
254 if header.magic != *SNAPSHOT_MAGIC {
256 errors.push(ValidationError {
257 error_type: ValidationErrorType::FormatError,
258 message: "Invalid magic number in header".to_string(),
259 location: Some("header.magic".to_string()),
260 severity: ErrorSeverity::Critical,
261 });
262 }
263
264 if !(MIN_SUPPORTED_SNAPSHOT_VERSION..=SNAPSHOT_VERSION).contains(&header.version) {
266 errors.push(ValidationError {
267 error_type: ValidationErrorType::FormatError,
268 message: format!(
269 "Unsupported snapshot version: {} is outside supported range {}..={}",
270 header.version, MIN_SUPPORTED_SNAPSHOT_VERSION, SNAPSHOT_VERSION
271 ),
272 location: Some("header.version".to_string()),
273 severity: ErrorSeverity::High,
274 });
275 }
276
277 if header.data_info.uncompressed_size == 0 {
279 errors.push(ValidationError {
280 error_type: ValidationErrorType::FormatError,
281 message: "Data section appears to be empty".to_string(),
282 location: Some("header.data_info.uncompressed_size".to_string()),
283 severity: ErrorSeverity::Medium,
284 });
285 }
286
287 Ok(())
288 }
289
290 fn validate_data_section(
292 &self,
293 format: &SnapshotFormat,
294 errors: &mut Vec<ValidationError>,
295 warnings: &mut Vec<ValidationWarning>,
296 ) -> SnapshotResult<()> {
297 if (format.data.len() as u64) != format.header.data_info.compressed_size {
299 errors.push(ValidationError {
300 error_type: ValidationErrorType::FormatError,
301 message: "Data size mismatch between header and actual data".to_string(),
302 location: Some("data_section".to_string()),
303 severity: ErrorSeverity::High,
304 });
305 }
306
307 let compression_ratio =
309 format.data.len() as f64 / format.header.data_info.uncompressed_size as f64;
310 if compression_ratio > 1.0 {
311 warnings.push(ValidationWarning {
312 warning_type: ValidationWarningType::PerformanceWarning,
313 message: "Compression appears to have increased data size".to_string(),
314 recommendation: Some("Consider disabling compression for this data".to_string()),
315 });
316 }
317
318 Ok(())
319 }
320
321 fn validate_checksum(
323 &self,
324 format: &SnapshotFormat,
325 errors: &mut Vec<ValidationError>,
326 _warnings: &mut [ValidationWarning],
327 ) -> SnapshotResult<()> {
328 match format.validate_checksum() {
329 Ok(true) => {
330 }
332 Ok(false) => {
333 errors.push(ValidationError {
334 error_type: ValidationErrorType::IntegrityError,
335 message: "Checksum validation failed".to_string(),
336 location: Some("checksum".to_string()),
337 severity: ErrorSeverity::Critical,
338 });
339 }
340 Err(e) => {
341 errors.push(ValidationError {
342 error_type: ValidationErrorType::IntegrityError,
343 message: format!("Checksum validation error: {e}"),
344 location: Some("checksum".to_string()),
345 severity: ErrorSeverity::High,
346 });
347 }
348 }
349
350 Ok(())
351 }
352
353 fn validate_builtin_registry(
355 &self,
356 registry: &crate::BuiltinRegistry,
357 errors: &mut Vec<ValidationError>,
358 warnings: &mut Vec<ValidationWarning>,
359 ) -> SnapshotResult<()> {
360 for (name, &mapped_index) in ®istry.name_index {
362 match registry.functions.get(mapped_index) {
363 Some(function) if function.name == *name => {}
364 Some(_) => {
365 errors.push(ValidationError {
366 error_type: ValidationErrorType::FormatError,
367 message: format!(
368 "Builtin registry index mismatch: expected '{}' at position {}, found '{}'",
369 name, mapped_index, registry.functions[mapped_index].name
370 ),
371 location: Some(format!("builtins.functions[{mapped_index}]")),
372 severity: ErrorSeverity::Medium,
373 });
374 }
375 None => {
376 errors.push(ValidationError {
377 error_type: ValidationErrorType::FormatError,
378 message: format!(
379 "Builtin registry name_index points outside function list: '{}' -> {}",
380 name, mapped_index
381 ),
382 location: Some("builtins.name_index".to_string()),
383 severity: ErrorSeverity::Medium,
384 });
385 }
386 }
387 }
388
389 let essential_builtins = ["abs", "sin", "cos", "sqrt", "max", "min"];
391 for builtin in &essential_builtins {
392 if !registry.name_index.contains_key(*builtin) {
393 warnings.push(ValidationWarning {
394 warning_type: ValidationWarningType::ConfigurationWarning,
395 message: format!("Essential builtin '{builtin}' not found"),
396 recommendation: Some(
397 "Ensure all standard library components are included".to_string(),
398 ),
399 });
400 }
401 }
402
403 Ok(())
404 }
405
406 fn validate_hir_cache(
408 &self,
409 cache: &crate::HirCache,
410 _errors: &mut [ValidationError],
411 warnings: &mut Vec<ValidationWarning>,
412 ) -> SnapshotResult<()> {
413 if cache.functions.is_empty() {
415 warnings.push(ValidationWarning {
416 warning_type: ValidationWarningType::PerformanceWarning,
417 message: "HIR cache is empty".to_string(),
418 recommendation: Some(
419 "Consider caching common standard library functions".to_string(),
420 ),
421 });
422 }
423
424 if cache.patterns.is_empty() {
426 warnings.push(ValidationWarning {
427 warning_type: ValidationWarningType::PerformanceWarning,
428 message: "No HIR patterns cached".to_string(),
429 recommendation: Some("Consider caching common expression patterns".to_string()),
430 });
431 }
432
433 Ok(())
434 }
435
436 fn validate_bytecode_cache(
438 &self,
439 cache: &crate::BytecodeCache,
440 _errors: &mut [ValidationError],
441 warnings: &mut Vec<ValidationWarning>,
442 ) -> SnapshotResult<()> {
443 if cache.stdlib_bytecode.is_empty() {
445 warnings.push(ValidationWarning {
446 warning_type: ValidationWarningType::PerformanceWarning,
447 message: "Bytecode cache is empty".to_string(),
448 recommendation: Some("Consider precompiling standard library bytecode".to_string()),
449 });
450 }
451
452 if cache.hotspots.is_empty() {
454 warnings.push(ValidationWarning {
455 warning_type: ValidationWarningType::PerformanceWarning,
456 message: "No hotspot bytecode identified".to_string(),
457 recommendation: Some(
458 "Consider profiling to identify optimization candidates".to_string(),
459 ),
460 });
461 }
462
463 Ok(())
464 }
465
466 fn validate_gc_presets(
468 &self,
469 presets: &crate::GcPresetCache,
470 errors: &mut Vec<ValidationError>,
471 warnings: &mut Vec<ValidationWarning>,
472 ) -> SnapshotResult<()> {
473 if !presets.presets.contains_key(&presets.default_preset) {
475 errors.push(ValidationError {
476 error_type: ValidationErrorType::ConfigurationError,
477 message: "Default GC preset not found".to_string(),
478 location: Some("gc_presets.default_preset".to_string()),
479 severity: ErrorSeverity::Medium,
480 });
481 }
482
483 for preset_name in presets.presets.keys() {
485 if !presets.performance_profiles.contains_key(preset_name) {
486 warnings.push(ValidationWarning {
487 warning_type: ValidationWarningType::ConfigurationWarning,
488 message: format!("No performance profile for preset '{preset_name}'"),
489 recommendation: Some(
490 "Add performance characteristics for better optimization".to_string(),
491 ),
492 });
493 }
494 }
495
496 Ok(())
497 }
498
499 fn validate_optimization_hints(
501 &self,
502 hints: &crate::OptimizationHints,
503 _errors: &mut [ValidationError],
504 warnings: &mut Vec<ValidationWarning>,
505 ) -> SnapshotResult<()> {
506 if hints.jit_hints.is_empty() {
508 warnings.push(ValidationWarning {
509 warning_type: ValidationWarningType::PerformanceWarning,
510 message: "No JIT optimization hints provided".to_string(),
511 recommendation: Some(
512 "Consider analyzing code for JIT optimization opportunities".to_string(),
513 ),
514 });
515 }
516
517 if hints.memory_hints.is_empty() {
518 warnings.push(ValidationWarning {
519 warning_type: ValidationWarningType::PerformanceWarning,
520 message: "No memory optimization hints provided".to_string(),
521 recommendation: Some("Consider memory layout optimizations".to_string()),
522 });
523 }
524
525 Ok(())
526 }
527
528 fn validate_feature_compatibility(
530 &self,
531 metadata: &SnapshotMetadata,
532 _errors: &mut [ValidationError],
533 warnings: &mut Vec<ValidationWarning>,
534 ) -> SnapshotResult<()> {
535 let current_features = SnapshotMetadata::current().feature_flags;
536
537 for feature in &metadata.feature_flags {
539 if !current_features.contains(feature) {
540 warnings.push(ValidationWarning {
541 warning_type: ValidationWarningType::CompatibilityWarning,
542 message: format!("Snapshot uses feature '{feature}' which is not available"),
543 recommendation: Some("Some functionality may be disabled".to_string()),
544 });
545 }
546 }
547
548 for feature in ¤t_features {
550 if !metadata.feature_flags.contains(feature) {
551 warnings.push(ValidationWarning {
552 warning_type: ValidationWarningType::CompatibilityWarning,
553 message: format!("Current environment has feature '{feature}' not in snapshot"),
554 recommendation: Some(
555 "Consider rebuilding snapshot with current features".to_string(),
556 ),
557 });
558 }
559 }
560
561 Ok(())
562 }
563
564 fn update_stats(&mut self, check_type: &str, duration: Duration) {
566 *self
567 .stats
568 .checks_performed
569 .entry(check_type.to_string())
570 .or_insert(0) += 1;
571 self.stats
572 .check_times
573 .insert(check_type.to_string(), duration);
574 self.stats.total_time += duration;
575 }
576
577 fn create_validation_result(
579 &self,
580 errors: Vec<ValidationError>,
581 warnings: Vec<ValidationWarning>,
582 validation_time: Duration,
583 _check_type: &str,
584 ) -> ValidationResult {
585 let is_valid = errors.is_empty()
586 || (!self.config.strict_mode
587 && errors
588 .iter()
589 .all(|e| matches!(e.severity, ErrorSeverity::Low)));
590
591 let score = self.calculate_validation_score(&errors, &warnings);
592
593 let recommendations = self.generate_recommendations(&errors, &warnings);
594
595 ValidationResult {
596 is_valid,
597 score,
598 errors,
599 warnings,
600 metrics: ValidationMetrics {
601 total_time: validation_time,
602 checks_performed: 1,
603 throughput: 1.0 / validation_time.as_secs_f64(),
604 memory_used: std::mem::size_of::<Self>(),
605 },
606 recommendations,
607 }
608 }
609
610 fn calculate_validation_score(
612 &self,
613 errors: &[ValidationError],
614 warnings: &[ValidationWarning],
615 ) -> u8 {
616 let mut score = 100u8;
617
618 for error in errors {
619 let penalty = match error.severity {
620 ErrorSeverity::Critical => 50,
621 ErrorSeverity::High => 20,
622 ErrorSeverity::Medium => 10,
623 ErrorSeverity::Low => 5,
624 };
625 score = score.saturating_sub(penalty);
626 }
627
628 score = score.saturating_sub((warnings.len() as u8) * 2);
630
631 score
632 }
633
634 fn generate_recommendations(
636 &self,
637 errors: &[ValidationError],
638 warnings: &[ValidationWarning],
639 ) -> Vec<String> {
640 let mut recommendations = Vec::new();
641
642 if errors
643 .iter()
644 .any(|e| matches!(e.error_type, ValidationErrorType::IntegrityError))
645 {
646 recommendations.push("Regenerate snapshot to fix integrity issues".to_string());
647 }
648
649 if errors
650 .iter()
651 .any(|e| matches!(e.error_type, ValidationErrorType::CompatibilityError))
652 {
653 recommendations.push("Update RunMat version or regenerate snapshot".to_string());
654 }
655
656 if warnings
657 .iter()
658 .any(|w| matches!(w.warning_type, ValidationWarningType::PerformanceWarning))
659 {
660 recommendations.push("Consider optimizing snapshot for better performance".to_string());
661 }
662
663 recommendations
664 }
665
666 pub fn stats(&self) -> &ValidationStats {
668 &self.stats
669 }
670
671 pub fn reset_stats(&mut self) {
673 self.stats = ValidationStats::default();
674 }
675}
676
677impl Default for ValidationConfig {
678 fn default() -> Self {
679 Self {
680 format_validation: true,
681 integrity_checking: true,
682 compatibility_checking: true,
683 performance_validation: true,
684 max_validation_time: Duration::from_secs(30),
685 strict_mode: false,
686 }
687 }
688}
689
690impl ValidationResult {
691 pub fn is_ok(&self) -> bool {
693 self.is_valid
694 }
695
696 pub fn critical_errors(&self) -> Vec<&ValidationError> {
698 self.errors
699 .iter()
700 .filter(|e| matches!(e.severity, ErrorSeverity::Critical))
701 .collect()
702 }
703
704 pub fn performance_warnings(&self) -> Vec<&ValidationWarning> {
706 self.warnings
707 .iter()
708 .filter(|w| matches!(w.warning_type, ValidationWarningType::PerformanceWarning))
709 .collect()
710 }
711}
712
713#[cfg(test)]
714mod tests {
715 use super::*;
716
717 #[test]
718 fn test_validator_creation() {
719 let validator = SnapshotValidator::new();
720 assert!(validator.config.format_validation);
721 assert!(validator.config.integrity_checking);
722 }
723
724 #[test]
725 fn test_validation_config() {
726 let config = ValidationConfig::default();
727 assert!(config.format_validation);
728 assert!(!config.strict_mode);
729 assert!(config.max_validation_time > Duration::ZERO);
730 }
731
732 #[test]
733 fn test_validation_score_calculation() {
734 let validator = SnapshotValidator::new();
735
736 assert_eq!(validator.calculate_validation_score(&[], &[]), 100);
738
739 let critical_error = ValidationError {
741 error_type: ValidationErrorType::IntegrityError,
742 message: "Test error".to_string(),
743 location: None,
744 severity: ErrorSeverity::Critical,
745 };
746 assert_eq!(
747 validator.calculate_validation_score(&[critical_error], &[]),
748 50
749 );
750
751 let warning = ValidationWarning {
753 warning_type: ValidationWarningType::PerformanceWarning,
754 message: "Test warning".to_string(),
755 recommendation: None,
756 };
757 assert_eq!(validator.calculate_validation_score(&[], &[warning]), 98);
758 }
759
760 #[test]
761 fn test_header_validation() {
762 let validator = SnapshotValidator::new();
763 let metadata = SnapshotMetadata::current();
764 let mut header = SnapshotHeader::new(metadata);
765
766 header.data_info.uncompressed_size = 1024;
768 header.data_info.compressed_size = 512;
769 header.data_info.data_offset = 256;
770
771 let mut errors = Vec::new();
772 let mut warnings = Vec::new();
773
774 validator
775 .validate_header(&header, &mut errors, &mut warnings)
776 .unwrap();
777 assert!(errors.is_empty(), "Validation errors: {errors:?}");
778 assert!(errors.is_empty());
779 }
780
781 #[test]
782 fn test_invalid_magic_detection() {
783 let validator = SnapshotValidator::new();
784 let metadata = SnapshotMetadata::current();
785 let mut header = SnapshotHeader::new(metadata);
786 header.magic = [0; 7]; let mut errors = Vec::new();
789 let mut warnings = Vec::new();
790
791 validator
792 .validate_header(&header, &mut errors, &mut warnings)
793 .unwrap();
794 assert!(!errors.is_empty());
795 assert!(errors
796 .iter()
797 .any(|e| matches!(e.error_type, ValidationErrorType::FormatError)));
798 }
799}