Skip to main content

memscope_rs/analysis/quality/
validator.rs

1use std::collections::HashMap;
2use std::time::{Duration, Instant};
3
4/// Code quality validator for memory analysis operations
5pub struct QualityValidator {
6    /// Active validation rules
7    rules: Vec<ValidationRule>,
8    /// Rule execution statistics
9    rule_stats: HashMap<String, RuleStats>,
10    /// Validation configuration
11    config: ValidationConfig,
12}
13
14/// Individual validation rule
15#[derive(Debug, Clone)]
16pub struct ValidationRule {
17    /// Unique rule identifier
18    pub id: String,
19    /// Human-readable name
20    pub name: String,
21    /// Rule description
22    pub description: String,
23    /// Rule category
24    pub category: RuleCategory,
25    /// Severity level if rule fails
26    pub severity: ValidationSeverity,
27    /// Whether rule is enabled
28    pub enabled: bool,
29    /// Validation function
30    pub validator: ValidationFunction,
31}
32
33/// Categories of validation rules
34#[derive(Debug, Clone, PartialEq)]
35pub enum RuleCategory {
36    /// Memory safety and correctness
37    MemorySafety,
38    /// Performance and efficiency
39    Performance,
40    /// Code style and maintainability
41    CodeStyle,
42    /// Error handling patterns
43    ErrorHandling,
44    /// Thread safety
45    ThreadSafety,
46    /// Resource management
47    ResourceManagement,
48}
49
50/// Validation severity levels
51#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
52pub enum ValidationSeverity {
53    /// Informational note
54    Info,
55    /// Style or convention issue
56    Style,
57    /// Potential problem
58    Warning,
59    /// Definite problem
60    Error,
61    /// Critical issue that must be fixed
62    Critical,
63}
64
65/// Validation function type
66pub type ValidationFunction = fn(&ValidationContext) -> Result<(), ValidationError>;
67
68/// Context provided to validation rules
69#[derive(Debug)]
70pub struct ValidationContext {
71    /// Operation being validated
72    pub operation_name: String,
73    /// Performance metrics for the operation
74    pub metrics: OperationMetrics,
75    /// Memory usage information
76    pub memory_info: MemoryInfo,
77    /// Error handling status
78    pub error_handling: ErrorHandlingInfo,
79    /// Thread safety information
80    pub thread_safety: ThreadSafetyInfo,
81}
82
83/// Metrics for a specific operation
84#[derive(Debug, Clone)]
85pub struct OperationMetrics {
86    /// Average execution time
87    pub avg_duration: Duration,
88    /// Peak memory usage during operation
89    pub peak_memory: usize,
90    /// Success rate (0.0 to 1.0)
91    pub success_rate: f64,
92    /// Number of allocations performed
93    pub allocation_count: usize,
94    /// CPU usage percentage
95    pub cpu_usage: f64,
96}
97
98/// Memory usage information
99#[derive(Debug, Clone)]
100pub struct MemoryInfo {
101    /// Current memory usage in bytes
102    pub current_usage: usize,
103    /// Peak memory usage in bytes
104    pub peak_usage: usize,
105    /// Number of active allocations
106    pub active_allocations: usize,
107    /// Memory fragmentation ratio
108    pub fragmentation_ratio: f64,
109    /// Memory growth rate (bytes per second)
110    pub growth_rate: f64,
111}
112
113/// Error handling information
114#[derive(Debug, Clone)]
115pub struct ErrorHandlingInfo {
116    /// Whether errors are properly handled
117    pub has_error_handling: bool,
118    /// Number of potential error points
119    pub error_points: usize,
120    /// Number of handled error points
121    pub handled_error_points: usize,
122    /// Whether recovery mechanisms exist
123    pub has_recovery: bool,
124}
125
126/// Thread safety information
127#[derive(Debug, Clone)]
128pub struct ThreadSafetyInfo {
129    /// Whether operation is thread-safe
130    pub is_thread_safe: bool,
131    /// Number of shared resources accessed
132    pub shared_resources: usize,
133    /// Whether proper synchronization is used
134    pub has_synchronization: bool,
135    /// Lock contention level (0.0 to 1.0)
136    pub contention_level: f64,
137}
138
139/// Validation error details
140#[derive(Debug, Clone)]
141pub struct ValidationError {
142    /// Error message
143    pub message: String,
144    /// Suggested fix
145    pub suggestion: Option<String>,
146    /// Code location if applicable
147    pub location: Option<String>,
148}
149
150/// Result of running validation rules
151#[derive(Debug, Clone)]
152pub struct ValidationResult {
153    /// Overall validation status
154    pub status: ValidationStatus,
155    /// Individual rule results
156    pub rule_results: Vec<RuleResult>,
157    /// Summary statistics
158    pub summary: ValidationSummary,
159    /// Performance impact of validation
160    pub validation_overhead: Duration,
161}
162
163/// Overall validation status
164#[derive(Debug, Clone, PartialEq)]
165pub enum ValidationStatus {
166    /// All rules passed
167    Passed,
168    /// Some warnings found
169    WarningsFound,
170    /// Errors found that should be addressed
171    ErrorsFound,
172    /// Critical issues found
173    CriticalIssuesFound,
174}
175
176/// Result of a single validation rule
177#[derive(Debug, Clone)]
178pub struct RuleResult {
179    /// Rule that was executed
180    pub rule_id: String,
181    /// Whether rule passed
182    pub passed: bool,
183    /// Error details if rule failed
184    pub error: Option<ValidationError>,
185    /// Execution time for this rule
186    pub execution_time: Duration,
187}
188
189/// Summary of validation results
190#[derive(Debug, Clone)]
191pub struct ValidationSummary {
192    /// Total rules executed
193    pub total_rules: usize,
194    /// Number of rules that passed
195    pub passed_rules: usize,
196    /// Number of rules that failed
197    pub failed_rules: usize,
198    /// Number of critical issues
199    pub critical_issues: usize,
200    /// Number of errors
201    pub errors: usize,
202    /// Number of warnings
203    pub warnings: usize,
204    /// Overall quality score (0.0 to 1.0)
205    pub quality_score: f64,
206}
207
208/// Configuration for validation behavior
209#[derive(Debug, Clone)]
210pub struct ValidationConfig {
211    /// Whether to stop on first critical error
212    pub fail_fast: bool,
213    /// Maximum time to spend on validation
214    pub max_validation_time: Duration,
215    /// Whether to enable performance-intensive checks
216    pub enable_deep_checks: bool,
217    /// Minimum severity level to report
218    pub min_severity: ValidationSeverity,
219}
220
221/// Statistics for rule execution
222#[derive(Debug, Clone)]
223pub struct RuleStats {
224    /// Number of times rule was executed
225    execution_count: usize,
226    /// Total execution time
227    total_time: Duration,
228    /// Number of times rule failed
229    failure_count: usize,
230    /// Average execution time
231    avg_time: Duration,
232}
233
234impl QualityValidator {
235    /// Create new validator with default rules
236    pub fn new() -> Self {
237        let mut validator = Self {
238            rules: Vec::new(),
239            rule_stats: HashMap::new(),
240            config: ValidationConfig::default(),
241        };
242
243        validator.add_default_rules();
244        validator
245    }
246
247    /// Create validator with custom configuration
248    pub fn with_config(config: ValidationConfig) -> Self {
249        let mut validator = Self {
250            rules: Vec::new(),
251            rule_stats: HashMap::new(),
252            config,
253        };
254
255        validator.add_default_rules();
256        validator
257    }
258
259    /// Add custom validation rule
260    pub fn add_rule(&mut self, rule: ValidationRule) {
261        self.rules.push(rule);
262    }
263
264    /// Remove validation rule by ID
265    pub fn remove_rule(&mut self, rule_id: &str) -> bool {
266        if let Some(pos) = self.rules.iter().position(|r| r.id == rule_id) {
267            self.rules.remove(pos);
268            self.rule_stats.remove(rule_id);
269            true
270        } else {
271            false
272        }
273    }
274
275    /// Enable or disable a specific rule
276    pub fn set_rule_enabled(&mut self, rule_id: &str, enabled: bool) -> bool {
277        if let Some(rule) = self.rules.iter_mut().find(|r| r.id == rule_id) {
278            rule.enabled = enabled;
279            true
280        } else {
281            false
282        }
283    }
284
285    /// Validate operation with all enabled rules
286    pub fn validate(&mut self, context: &ValidationContext) -> ValidationResult {
287        let start_time = Instant::now();
288        let mut rule_results = Vec::new();
289        let mut should_stop = false;
290
291        // Collect rule information to avoid borrowing issues
292        let rules_info: Vec<_> = self
293            .rules
294            .iter()
295            .filter(|rule| rule.enabled)
296            .map(|rule| (rule.id.clone(), rule.severity.clone(), rule.validator))
297            .collect();
298
299        for (rule_id, severity, validator) in rules_info {
300            if should_stop && self.config.fail_fast {
301                break;
302            }
303
304            let rule_start = Instant::now();
305            let result = validator(context);
306            let rule_duration = rule_start.elapsed();
307
308            // Update statistics
309            self.update_rule_stats(&rule_id, rule_duration, result.is_err());
310
311            let rule_result = RuleResult {
312                rule_id,
313                passed: result.is_ok(),
314                error: result.err(),
315                execution_time: rule_duration,
316            };
317
318            if !rule_result.passed && severity >= ValidationSeverity::Critical {
319                should_stop = true;
320            }
321
322            rule_results.push(rule_result);
323
324            // Check timeout
325            if start_time.elapsed() > self.config.max_validation_time {
326                break;
327            }
328        }
329
330        let validation_overhead = start_time.elapsed();
331        let summary = self.calculate_summary(&rule_results);
332        let status = self.determine_status(&summary);
333
334        ValidationResult {
335            status,
336            rule_results,
337            summary,
338            validation_overhead,
339        }
340    }
341
342    /// Get statistics for all rules
343    pub fn get_rule_statistics(&self) -> &HashMap<String, RuleStats> {
344        &self.rule_stats
345    }
346
347    /// Reset all statistics
348    pub fn reset_statistics(&mut self) {
349        self.rule_stats.clear();
350    }
351
352    fn add_default_rules(&mut self) {
353        // Memory safety rules
354        self.add_rule(ValidationRule {
355            id: "memory_leak_check".to_string(),
356            name: "Memory Leak Detection".to_string(),
357            description: "Check for potential memory leaks in tracking operations".to_string(),
358            category: RuleCategory::MemorySafety,
359            severity: ValidationSeverity::Critical,
360            enabled: true,
361            validator: validate_memory_leaks,
362        });
363
364        self.add_rule(ValidationRule {
365            id: "allocation_overhead_check".to_string(),
366            name: "Allocation Overhead Check".to_string(),
367            description: "Ensure allocation tracking overhead is within acceptable limits"
368                .to_string(),
369            category: RuleCategory::Performance,
370            severity: ValidationSeverity::Warning,
371            enabled: true,
372            validator: validate_allocation_overhead,
373        });
374
375        // Performance rules
376        self.add_rule(ValidationRule {
377            id: "tracking_latency_check".to_string(),
378            name: "Tracking Latency Check".to_string(),
379            description: "Verify allocation tracking latency is acceptable".to_string(),
380            category: RuleCategory::Performance,
381            severity: ValidationSeverity::Error,
382            enabled: true,
383            validator: validate_tracking_latency,
384        });
385
386        self.add_rule(ValidationRule {
387            id: "symbol_resolution_performance".to_string(),
388            name: "Symbol Resolution Performance".to_string(),
389            description: "Check symbol resolution performance metrics".to_string(),
390            category: RuleCategory::Performance,
391            severity: ValidationSeverity::Warning,
392            enabled: true,
393            validator: validate_symbol_performance,
394        });
395
396        // Error handling rules
397        self.add_rule(ValidationRule {
398            id: "error_handling_coverage".to_string(),
399            name: "Error Handling Coverage".to_string(),
400            description: "Ensure proper error handling in critical paths".to_string(),
401            category: RuleCategory::ErrorHandling,
402            severity: ValidationSeverity::Error,
403            enabled: true,
404            validator: validate_error_handling,
405        });
406
407        // Thread safety rules
408        self.add_rule(ValidationRule {
409            id: "thread_safety_check".to_string(),
410            name: "Thread Safety Check".to_string(),
411            description: "Verify thread safety of concurrent operations".to_string(),
412            category: RuleCategory::ThreadSafety,
413            severity: ValidationSeverity::Critical,
414            enabled: true,
415            validator: validate_thread_safety,
416        });
417    }
418
419    fn update_rule_stats(&mut self, rule_id: &str, duration: Duration, failed: bool) {
420        let stats = self
421            .rule_stats
422            .entry(rule_id.to_string())
423            .or_insert(RuleStats {
424                execution_count: 0,
425                total_time: Duration::ZERO,
426                failure_count: 0,
427                avg_time: Duration::ZERO,
428            });
429
430        stats.execution_count += 1;
431        stats.total_time += duration;
432        if failed {
433            stats.failure_count += 1;
434        }
435        stats.avg_time = stats.total_time / stats.execution_count as u32;
436    }
437
438    fn calculate_summary(&self, results: &[RuleResult]) -> ValidationSummary {
439        let total_rules = results.len();
440        let passed_rules = results.iter().filter(|r| r.passed).count();
441        let failed_rules = total_rules - passed_rules;
442
443        let mut critical_issues = 0;
444        let mut errors = 0;
445        let mut warnings = 0;
446
447        for result in results {
448            if !result.passed {
449                if let Some(rule) = self.rules.iter().find(|r| r.id == result.rule_id) {
450                    match rule.severity {
451                        ValidationSeverity::Critical => critical_issues += 1,
452                        ValidationSeverity::Error => errors += 1,
453                        ValidationSeverity::Warning => warnings += 1,
454                        _ => {}
455                    }
456                }
457            }
458        }
459
460        let quality_score = if total_rules > 0 {
461            passed_rules as f64 / total_rules as f64
462        } else {
463            1.0
464        };
465
466        ValidationSummary {
467            total_rules,
468            passed_rules,
469            failed_rules,
470            critical_issues,
471            errors,
472            warnings,
473            quality_score,
474        }
475    }
476
477    fn determine_status(&self, summary: &ValidationSummary) -> ValidationStatus {
478        if summary.critical_issues > 0 {
479            ValidationStatus::CriticalIssuesFound
480        } else if summary.errors > 0 {
481            ValidationStatus::ErrorsFound
482        } else if summary.warnings > 0 {
483            ValidationStatus::WarningsFound
484        } else {
485            ValidationStatus::Passed
486        }
487    }
488}
489
490// Validation rule implementations
491fn validate_memory_leaks(context: &ValidationContext) -> Result<(), ValidationError> {
492    if context.memory_info.growth_rate > 10.0 * 1024.0 * 1024.0 {
493        // 10MB/sec
494        return Err(ValidationError {
495            message: format!(
496                "High memory growth rate detected: {:.2}MB/sec",
497                context.memory_info.growth_rate / (1024.0 * 1024.0)
498            ),
499            suggestion: Some("Check for memory leaks in allocation tracking".to_string()),
500            location: Some(context.operation_name.clone()),
501        });
502    }
503
504    if context.memory_info.fragmentation_ratio > 0.5 {
505        return Err(ValidationError {
506            message: format!(
507                "High memory fragmentation: {:.1}%",
508                context.memory_info.fragmentation_ratio * 100.0
509            ),
510            suggestion: Some("Consider implementing memory compaction".to_string()),
511            location: Some(context.operation_name.clone()),
512        });
513    }
514
515    Ok(())
516}
517
518fn validate_allocation_overhead(context: &ValidationContext) -> Result<(), ValidationError> {
519    let overhead_ratio =
520        context.metrics.peak_memory as f64 / (context.memory_info.current_usage as f64).max(1.0);
521
522    if overhead_ratio > 0.1 {
523        // 10% overhead threshold
524        return Err(ValidationError {
525            message: format!("High tracking overhead: {:.1}%", overhead_ratio * 100.0),
526            suggestion: Some(
527                "Optimize tracking data structures to reduce memory overhead".to_string(),
528            ),
529            location: Some(context.operation_name.clone()),
530        });
531    }
532
533    Ok(())
534}
535
536fn validate_tracking_latency(context: &ValidationContext) -> Result<(), ValidationError> {
537    if context.metrics.avg_duration > Duration::from_micros(100) {
538        return Err(ValidationError {
539            message: format!(
540                "High tracking latency: {:.2}µs",
541                context.metrics.avg_duration.as_micros()
542            ),
543            suggestion: Some("Optimize allocation tracking path for lower latency".to_string()),
544            location: Some(context.operation_name.clone()),
545        });
546    }
547
548    Ok(())
549}
550
551fn validate_symbol_performance(context: &ValidationContext) -> Result<(), ValidationError> {
552    if context.operation_name.contains("symbol")
553        && context.metrics.avg_duration > Duration::from_millis(10)
554    {
555        return Err(ValidationError {
556            message: format!(
557                "Slow symbol resolution: {:.2}ms",
558                context.metrics.avg_duration.as_millis()
559            ),
560            suggestion: Some("Consider symbol caching or preloading".to_string()),
561            location: Some(context.operation_name.clone()),
562        });
563    }
564
565    Ok(())
566}
567
568fn validate_error_handling(context: &ValidationContext) -> Result<(), ValidationError> {
569    let coverage_ratio = if context.error_handling.error_points > 0 {
570        context.error_handling.handled_error_points as f64
571            / context.error_handling.error_points as f64
572    } else {
573        1.0
574    };
575
576    if coverage_ratio < 0.9 {
577        return Err(ValidationError {
578            message: format!(
579                "Low error handling coverage: {:.1}%",
580                coverage_ratio * 100.0
581            ),
582            suggestion: Some("Add error handling for unhandled error points".to_string()),
583            location: Some(context.operation_name.clone()),
584        });
585    }
586
587    if !context.error_handling.has_recovery && context.operation_name.contains("critical") {
588        return Err(ValidationError {
589            message: "Critical operation lacks recovery mechanism".to_string(),
590            suggestion: Some("Implement recovery strategies for critical operations".to_string()),
591            location: Some(context.operation_name.clone()),
592        });
593    }
594
595    Ok(())
596}
597
598fn validate_thread_safety(context: &ValidationContext) -> Result<(), ValidationError> {
599    if context.thread_safety.shared_resources > 0 && !context.thread_safety.is_thread_safe {
600        return Err(ValidationError {
601            message: "Operation accesses shared resources without thread safety".to_string(),
602            suggestion: Some("Add proper synchronization for shared resource access".to_string()),
603            location: Some(context.operation_name.clone()),
604        });
605    }
606
607    if context.thread_safety.contention_level > 0.3 {
608        return Err(ValidationError {
609            message: format!(
610                "High lock contention: {:.1}%",
611                context.thread_safety.contention_level * 100.0
612            ),
613            suggestion: Some(
614                "Consider lock-free alternatives or finer-grained locking".to_string(),
615            ),
616            location: Some(context.operation_name.clone()),
617        });
618    }
619
620    Ok(())
621}
622
623impl Default for ValidationConfig {
624    fn default() -> Self {
625        Self {
626            fail_fast: false,
627            max_validation_time: Duration::from_secs(10),
628            enable_deep_checks: true,
629            min_severity: ValidationSeverity::Info,
630        }
631    }
632}
633
634impl Default for QualityValidator {
635    fn default() -> Self {
636        Self::new()
637    }
638}
639
640#[cfg(test)]
641mod tests {
642    use super::*;
643
644    #[test]
645    fn test_validator_creation() {
646        let validator = QualityValidator::new();
647        assert!(!validator.rules.is_empty());
648        assert!(validator.rule_stats.is_empty());
649    }
650
651    #[test]
652    fn test_rule_management() {
653        let mut validator = QualityValidator::new();
654        let initial_count = validator.rules.len();
655
656        let custom_rule = ValidationRule {
657            id: "test_rule".to_string(),
658            name: "Test Rule".to_string(),
659            description: "Test rule description".to_string(),
660            category: RuleCategory::CodeStyle,
661            severity: ValidationSeverity::Info,
662            enabled: true,
663            validator: |_| Ok(()),
664        };
665
666        validator.add_rule(custom_rule);
667        assert_eq!(validator.rules.len(), initial_count + 1);
668
669        assert!(validator.remove_rule("test_rule"));
670        assert_eq!(validator.rules.len(), initial_count);
671        assert!(!validator.remove_rule("nonexistent_rule"));
672    }
673
674    #[test]
675    fn test_validation_context() {
676        let context = ValidationContext {
677            operation_name: "test_operation".to_string(),
678            metrics: OperationMetrics {
679                avg_duration: Duration::from_micros(50),
680                peak_memory: 1024,
681                success_rate: 0.95,
682                allocation_count: 100,
683                cpu_usage: 5.0,
684            },
685            memory_info: MemoryInfo {
686                current_usage: 1024 * 1024,
687                peak_usage: 2 * 1024 * 1024,
688                active_allocations: 100,
689                fragmentation_ratio: 0.1,
690                growth_rate: 0.0,
691            },
692            error_handling: ErrorHandlingInfo {
693                has_error_handling: true,
694                error_points: 10,
695                handled_error_points: 9,
696                has_recovery: true,
697            },
698            thread_safety: ThreadSafetyInfo {
699                is_thread_safe: true,
700                shared_resources: 2,
701                has_synchronization: true,
702                contention_level: 0.1,
703            },
704        };
705
706        let mut validator = QualityValidator::new();
707        let result = validator.validate(&context);
708
709        assert!(matches!(
710            result.status,
711            ValidationStatus::Passed | ValidationStatus::WarningsFound
712        ));
713        assert!(result.summary.quality_score >= 0.0);
714        assert!(result.summary.quality_score <= 1.0);
715    }
716}