Skip to main content

runmat_snapshot/
validation.rs

1//! Snapshot validation and integrity checking
2//!
3//! Provides comprehensive validation for snapshot files including
4//! format validation, integrity checks, and compatibility verification.
5
6use runmat_time::Instant;
7use std::collections::HashMap;
8use std::time::Duration;
9
10use crate::format::*;
11use crate::{Snapshot, SnapshotResult};
12
13/// Snapshot validator with comprehensive checks
14#[derive(Default)]
15pub struct SnapshotValidator {
16    /// Validation configuration
17    config: ValidationConfig,
18
19    /// Validation statistics
20    stats: ValidationStats,
21}
22
23/// Validation configuration
24#[derive(Debug, Clone)]
25pub struct ValidationConfig {
26    /// Enable format validation
27    pub format_validation: bool,
28
29    /// Enable integrity checking
30    pub integrity_checking: bool,
31
32    /// Enable compatibility checking
33    pub compatibility_checking: bool,
34
35    /// Enable performance validation
36    pub performance_validation: bool,
37
38    /// Maximum validation time
39    pub max_validation_time: Duration,
40
41    /// Strict mode (fail on warnings)
42    pub strict_mode: bool,
43}
44
45/// Validation statistics
46#[derive(Debug, Default)]
47pub struct ValidationStats {
48    /// Checks performed
49    pub checks_performed: HashMap<String, u64>,
50
51    /// Validation time by check type
52    pub check_times: HashMap<String, Duration>,
53
54    /// Total validation time
55    pub total_time: Duration,
56
57    /// Errors found
58    pub errors: Vec<ValidationError>,
59
60    /// Warnings found
61    pub warnings: Vec<ValidationWarning>,
62}
63
64/// Validation error
65#[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/// Validation warning
74#[derive(Debug, Clone)]
75pub struct ValidationWarning {
76    pub warning_type: ValidationWarningType,
77    pub message: String,
78    pub recommendation: Option<String>,
79}
80
81/// Validation error types
82#[derive(Debug, Clone)]
83pub enum ValidationErrorType {
84    FormatError,
85    IntegrityError,
86    CompatibilityError,
87    PerformanceError,
88    ConfigurationError,
89}
90
91/// Validation warning types
92#[derive(Debug, Clone)]
93pub enum ValidationWarningType {
94    PerformanceWarning,
95    CompatibilityWarning,
96    ConfigurationWarning,
97    DeprecationWarning,
98}
99
100/// Error severity levels
101#[derive(Debug, Clone)]
102pub enum ErrorSeverity {
103    Critical,
104    High,
105    Medium,
106    Low,
107}
108
109/// Validation result
110#[derive(Debug)]
111pub struct ValidationResult {
112    /// Overall validation success
113    pub is_valid: bool,
114
115    /// Validation score (0-100)
116    pub score: u8,
117
118    /// Errors found
119    pub errors: Vec<ValidationError>,
120
121    /// Warnings found
122    pub warnings: Vec<ValidationWarning>,
123
124    /// Performance metrics
125    pub metrics: ValidationMetrics,
126
127    /// Recommendations
128    pub recommendations: Vec<String>,
129}
130
131/// Validation performance metrics
132#[derive(Debug)]
133pub struct ValidationMetrics {
134    pub total_time: Duration,
135    pub checks_performed: usize,
136    pub throughput: f64, // checks per second
137    pub memory_used: usize,
138}
139
140impl SnapshotValidator {
141    /// Create a new snapshot validator
142    pub fn new() -> Self {
143        Self::default()
144    }
145
146    /// Create validator with custom configuration
147    pub fn with_config(config: ValidationConfig) -> Self {
148        Self {
149            config,
150            stats: ValidationStats::default(),
151        }
152    }
153
154    /// Validate snapshot format
155    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        // Validate header
161        self.validate_header(&format.header, &mut errors, &mut warnings)?;
162
163        // Validate data section
164        self.validate_data_section(format, &mut errors, &mut warnings)?;
165
166        // Validate checksum if present
167        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    /// Validate snapshot content
178    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        // Validate builtin registry
184        self.validate_builtin_registry(&snapshot.builtins, &mut errors, &mut warnings)?;
185
186        // Validate HIR cache
187        self.validate_hir_cache(&snapshot.hir_cache, &mut errors, &mut warnings)?;
188
189        // Validate bytecode cache
190        self.validate_bytecode_cache(&snapshot.bytecode_cache, &mut errors, &mut warnings)?;
191
192        // Validate GC presets
193        self.validate_gc_presets(&snapshot.gc_presets, &mut errors, &mut warnings)?;
194
195        // Validate optimization hints
196        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    /// Validate compatibility with current environment
205    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        // Check version compatibility
214        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        // Check platform compatibility
225        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        // Check feature compatibility
234        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    /// Validate header structure
248    fn validate_header(
249        &self,
250        header: &SnapshotHeader,
251        errors: &mut Vec<ValidationError>,
252        _warnings: &mut [ValidationWarning],
253    ) -> SnapshotResult<()> {
254        // Validate magic number
255        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        // Validate version
265        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        // Validate data section info
278        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    /// Validate data section
291    fn validate_data_section(
292        &self,
293        format: &SnapshotFormat,
294        errors: &mut Vec<ValidationError>,
295        warnings: &mut Vec<ValidationWarning>,
296    ) -> SnapshotResult<()> {
297        // Check data size consistency
298        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        // Check compression ratio
308        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    /// Validate checksum
322    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                // Checksum is valid
331            }
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    /// Validate builtin registry
354    fn validate_builtin_registry(
355        &self,
356        registry: &crate::BuiltinRegistry,
357        errors: &mut Vec<ValidationError>,
358        warnings: &mut Vec<ValidationWarning>,
359    ) -> SnapshotResult<()> {
360        // Validate that the name_index map points to matching entries within the functions list.
361        for (name, &mapped_index) in &registry.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        // Check for essential builtins
390        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    /// Validate HIR cache
407    fn validate_hir_cache(
408        &self,
409        cache: &crate::HirCache,
410        _errors: &mut [ValidationError],
411        warnings: &mut Vec<ValidationWarning>,
412    ) -> SnapshotResult<()> {
413        // Check cache effectiveness
414        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        // Check pattern effectiveness
425        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    /// Validate bytecode cache
437    fn validate_bytecode_cache(
438        &self,
439        cache: &crate::BytecodeCache,
440        _errors: &mut [ValidationError],
441        warnings: &mut Vec<ValidationWarning>,
442    ) -> SnapshotResult<()> {
443        // Check cache content
444        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        // Check hotspot identification
453        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    /// Validate GC presets
467    fn validate_gc_presets(
468        &self,
469        presets: &crate::GcPresetCache,
470        errors: &mut Vec<ValidationError>,
471        warnings: &mut Vec<ValidationWarning>,
472    ) -> SnapshotResult<()> {
473        // Check default preset exists
474        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        // Check performance profiles
484        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    /// Validate optimization hints
500    fn validate_optimization_hints(
501        &self,
502        hints: &crate::OptimizationHints,
503        _errors: &mut [ValidationError],
504        warnings: &mut Vec<ValidationWarning>,
505    ) -> SnapshotResult<()> {
506        // Check hint completeness
507        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    /// Validate feature compatibility
529    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        // Check for missing features
538        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        // Check for additional features
549        for feature in &current_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    /// Update validation statistics
565    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    /// Create validation result
578    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    /// Calculate validation score
611    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        // Warnings reduce score by 2 each
629        score = score.saturating_sub((warnings.len() as u8) * 2);
630
631        score
632    }
633
634    /// Generate recommendations based on errors and warnings
635    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    /// Get validation statistics
667    pub fn stats(&self) -> &ValidationStats {
668        &self.stats
669    }
670
671    /// Reset validation statistics
672    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    /// Check if validation passed
692    pub fn is_ok(&self) -> bool {
693        self.is_valid
694    }
695
696    /// Get critical errors
697    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    /// Get performance warnings
705    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        // No errors or warnings = perfect score
737        assert_eq!(validator.calculate_validation_score(&[], &[]), 100);
738
739        // Critical error
740        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        // Warning
752        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        // Set up proper data info to avoid validation errors
767        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]; // Invalid magic
787
788        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}