1use std::collections::HashMap;
15
16#[derive(Debug, Clone, PartialEq, Eq, Hash)]
18pub enum TemporalPattern {
19 PairedOperation {
21 open_method: String,
22 close_method: String,
23 },
24 LifecycleSequence {
26 phase: LifecyclePhase,
27 method_name: String,
28 },
29 StateCheck {
31 check_method: String,
32 implied_prerequisite: String,
33 },
34 RustDropImpl { type_name: String },
36 RustGuardPattern {
38 guard_type: String,
39 resource: String,
40 },
41 RustAsyncSpawnWithoutJoin,
43 RustUnsafeManualResource { operation: String },
45 RustBuilderPattern {
47 type_name: String,
48 required_methods: Vec<String>,
49 },
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
54pub enum LifecyclePhase {
55 Create = 0,
57 Configure = 1,
59 Initialize = 2,
61 Start = 3,
63 Active = 4,
65 Stop = 5,
67 Cleanup = 6,
69}
70
71impl LifecyclePhase {
72 pub fn description(&self) -> &'static str {
73 match self {
74 LifecyclePhase::Create => "Object creation",
75 LifecyclePhase::Configure => "Configuration",
76 LifecyclePhase::Initialize => "Initialization",
77 LifecyclePhase::Start => "Start/Connect",
78 LifecyclePhase::Active => "Active operation",
79 LifecyclePhase::Stop => "Stop/Disconnect",
80 LifecyclePhase::Cleanup => "Cleanup/Destroy",
81 }
82 }
83}
84
85#[derive(Debug, Clone)]
87pub struct TemporalCouplingInstance {
88 pub pattern: TemporalPattern,
90 pub source: String,
92 pub severity: f64,
94 pub description: String,
96 pub suggestion: String,
98}
99
100#[derive(Debug, Clone)]
102struct PairedOp {
103 open: &'static str,
104 close: &'static str,
105 severity: f64,
106}
107
108#[derive(Debug, Clone, Default)]
110pub struct TemporalCouplingStats {
111 pub paired_operations: HashMap<String, PairedOperationStats>,
113 pub lifecycle_methods: HashMap<LifecyclePhase, Vec<String>>,
115 pub state_checks: Vec<String>,
117 pub total_issues: usize,
119 pub drop_impls: Vec<String>,
121 pub guard_patterns: Vec<String>,
123 pub async_spawns: usize,
125 pub async_joins: usize,
127 pub unsafe_allocations: Vec<String>,
129 pub builder_patterns: Vec<String>,
131}
132
133#[derive(Debug, Clone, Default)]
135pub struct PairedOperationStats {
136 pub open_count: usize,
137 pub close_count: usize,
138 pub locations: Vec<String>,
139}
140
141#[derive(Debug, Default)]
143pub struct TemporalAnalyzer {
144 pub instances: Vec<TemporalCouplingInstance>,
146 pub stats: TemporalCouplingStats,
148 current_module: String,
150 method_calls: HashMap<String, Vec<String>>,
152 function_defs: Vec<(String, String)>, drop_impls: Vec<(String, String)>, guard_usages: Vec<(String, String)>, async_spawns: Vec<String>,
160 async_joins: Vec<String>,
162 unsafe_allocs: Vec<(String, String)>, builder_types: Vec<(String, Vec<String>)>, }
167
168impl TemporalAnalyzer {
169 const PAIRED_OPS: &'static [PairedOp] = &[
171 PairedOp {
172 open: "open",
173 close: "close",
174 severity: 0.8,
175 },
176 PairedOp {
177 open: "lock",
178 close: "unlock",
179 severity: 0.9,
180 },
181 PairedOp {
182 open: "acquire",
183 close: "release",
184 severity: 0.9,
185 },
186 PairedOp {
187 open: "begin",
188 close: "commit",
189 severity: 0.7,
190 },
191 PairedOp {
192 open: "begin",
193 close: "end",
194 severity: 0.6,
195 },
196 PairedOp {
197 open: "start",
198 close: "stop",
199 severity: 0.7,
200 },
201 PairedOp {
202 open: "connect",
203 close: "disconnect",
204 severity: 0.8,
205 },
206 PairedOp {
207 open: "enter",
208 close: "exit",
209 severity: 0.7,
210 },
211 PairedOp {
212 open: "push",
213 close: "pop",
214 severity: 0.5,
215 },
216 PairedOp {
217 open: "subscribe",
218 close: "unsubscribe",
219 severity: 0.6,
220 },
221 PairedOp {
222 open: "register",
223 close: "unregister",
224 severity: 0.6,
225 },
226 PairedOp {
227 open: "enable",
228 close: "disable",
229 severity: 0.5,
230 },
231 PairedOp {
232 open: "activate",
233 close: "deactivate",
234 severity: 0.6,
235 },
236 PairedOp {
237 open: "attach",
238 close: "detach",
239 severity: 0.6,
240 },
241 PairedOp {
242 open: "bind",
243 close: "unbind",
244 severity: 0.7,
245 },
246 PairedOp {
247 open: "mount",
248 close: "unmount",
249 severity: 0.8,
250 },
251 PairedOp {
252 open: "init",
253 close: "deinit",
254 severity: 0.7,
255 },
256 PairedOp {
257 open: "setup",
258 close: "teardown",
259 severity: 0.7,
260 },
261 PairedOp {
262 open: "create",
263 close: "destroy",
264 severity: 0.7,
265 },
266 PairedOp {
267 open: "alloc",
268 close: "free",
269 severity: 0.9,
270 },
271 PairedOp {
272 open: "malloc",
273 close: "free",
274 severity: 0.9,
275 },
276 PairedOp {
277 open: "borrow",
278 close: "return",
279 severity: 0.6,
280 },
281 PairedOp {
282 open: "checkout",
283 close: "checkin",
284 severity: 0.6,
285 },
286 ];
287
288 const LIFECYCLE_PATTERNS: &'static [(LifecyclePhase, &'static [&'static str])] = &[
290 (
291 LifecyclePhase::Create,
292 &["new", "create", "build", "construct", "make"],
293 ),
294 (
295 LifecyclePhase::Configure,
296 &[
297 "configure",
298 "config",
299 "set_config",
300 "with_config",
301 "options",
302 ],
303 ),
304 (
305 LifecyclePhase::Initialize,
306 &["init", "initialize", "setup", "prepare", "bootstrap"],
307 ),
308 (
309 LifecyclePhase::Start,
310 &[
311 "start", "begin", "run", "launch", "open", "connect", "activate",
312 ],
313 ),
314 (
315 LifecyclePhase::Active,
316 &["process", "execute", "handle", "perform", "do_work"],
317 ),
318 (
319 LifecyclePhase::Stop,
320 &[
321 "stop",
322 "end",
323 "halt",
324 "pause",
325 "close",
326 "disconnect",
327 "deactivate",
328 ],
329 ),
330 (
331 LifecyclePhase::Cleanup,
332 &[
333 "cleanup", "clean", "dispose", "destroy", "drop", "finalize", "shutdown",
334 "teardown",
335 ],
336 ),
337 ];
338
339 const STATE_CHECK_PATTERNS: &'static [(&'static str, &'static str)] = &[
341 ("is_initialized", "init/initialize"),
342 ("is_connected", "connect"),
343 ("is_open", "open"),
344 ("is_started", "start"),
345 ("is_ready", "init/prepare"),
346 ("is_running", "start/run"),
347 ("is_active", "activate/start"),
348 ("is_configured", "configure"),
349 ("is_setup", "setup"),
350 ("has_started", "start"),
351 ("was_initialized", "init"),
352 ("check_initialized", "init"),
353 ("ensure_initialized", "init"),
354 ("assert_initialized", "init"),
355 ("require_connection", "connect"),
356 ];
357
358 const RUST_GUARD_TYPES: &'static [&'static str] = &[
360 "MutexGuard",
361 "RwLockReadGuard",
362 "RwLockWriteGuard",
363 "RefCell",
364 "Ref",
365 "RefMut",
366 "ScopedJoinHandle",
367 "Guard",
368 "ScopeGuard",
369 "Entered", ];
371
372 const RUST_UNSAFE_ALLOC_PATTERNS: &'static [&'static str] = &[
374 "alloc",
375 "dealloc",
376 "realloc",
377 "Box::from_raw",
378 "Box::into_raw",
379 "Vec::from_raw_parts",
380 "String::from_raw_parts",
381 "ptr::read",
382 "ptr::write",
383 "ManuallyDrop",
384 "mem::forget",
385 "mem::transmute",
386 ];
387
388 const RUST_ASYNC_SPAWN_PATTERNS: &'static [&'static str] = &[
390 "spawn",
391 "spawn_blocking",
392 "spawn_local",
393 "task::spawn",
394 "tokio::spawn",
395 "async_std::spawn",
396 "rayon::spawn",
397 ];
398
399 const RUST_ASYNC_JOIN_PATTERNS: &'static [&'static str] =
401 &["join", "join_all", "await", "block_on", "JoinHandle"];
402
403 pub fn new() -> Self {
404 Self::default()
405 }
406
407 pub fn set_module(&mut self, module: String) {
409 self.current_module = module;
410 }
411
412 pub fn record_call(&mut self, method_name: &str) {
414 let name = method_name.to_lowercase();
415 self.method_calls
416 .entry(name)
417 .or_default()
418 .push(self.current_module.clone());
419 }
420
421 pub fn record_function_def(&mut self, function_name: &str) {
423 self.function_defs
424 .push((self.current_module.clone(), function_name.to_lowercase()));
425 }
426
427 pub fn record_drop_impl(&mut self, type_name: &str) {
429 self.drop_impls
430 .push((self.current_module.clone(), type_name.to_string()));
431 }
432
433 pub fn record_guard_usage(&mut self, guard_type: &str) {
435 self.guard_usages
436 .push((self.current_module.clone(), guard_type.to_string()));
437 }
438
439 pub fn record_async_spawn(&mut self, spawn_type: &str) {
441 self.async_spawns
442 .push(format!("{}::{}", self.current_module, spawn_type));
443 }
444
445 pub fn record_async_join(&mut self, join_type: &str) {
447 self.async_joins
448 .push(format!("{}::{}", self.current_module, join_type));
449 }
450
451 pub fn record_unsafe_alloc(&mut self, operation: &str) {
453 self.unsafe_allocs
454 .push((self.current_module.clone(), operation.to_string()));
455 }
456
457 pub fn record_builder_pattern(&mut self, type_name: &str, methods: Vec<String>) {
459 self.builder_types.push((type_name.to_string(), methods));
460 }
461
462 pub fn analyze(&mut self) {
464 self.detect_paired_operation_imbalance();
465 self.detect_lifecycle_patterns();
466 self.detect_state_checks();
467 self.detect_rust_patterns();
468 }
469
470 fn detect_paired_operation_imbalance(&mut self) {
472 for paired in Self::PAIRED_OPS {
473 let open_calls = self
474 .method_calls
475 .get(paired.open)
476 .map(|v| v.len())
477 .unwrap_or(0);
478 let close_calls = self
479 .method_calls
480 .get(paired.close)
481 .map(|v| v.len())
482 .unwrap_or(0);
483
484 if open_calls > 0 || close_calls > 0 {
485 let stats = self
486 .stats
487 .paired_operations
488 .entry(format!("{}/{}", paired.open, paired.close))
489 .or_default();
490 stats.open_count = open_calls;
491 stats.close_count = close_calls;
492
493 if let Some(locs) = self.method_calls.get(paired.open) {
495 stats.locations.extend(locs.iter().cloned());
496 }
497 if let Some(locs) = self.method_calls.get(paired.close) {
498 stats.locations.extend(locs.iter().cloned());
499 }
500
501 if open_calls != close_calls && open_calls > 0 && close_calls > 0 {
503 let (description, suggestion) = if open_calls > close_calls {
504 (
505 format!(
506 "More {}() calls ({}) than {}() calls ({})",
507 paired.open, open_calls, paired.close, close_calls
508 ),
509 format!(
510 "Ensure every {}() has a matching {}(). Consider using RAII pattern or Drop trait.",
511 paired.open, paired.close
512 ),
513 )
514 } else {
515 (
516 format!(
517 "More {}() calls ({}) than {}() calls ({})",
518 paired.close, close_calls, paired.open, open_calls
519 ),
520 format!(
521 "Check if {}() is called without prior {}()",
522 paired.close, paired.open
523 ),
524 )
525 };
526
527 self.instances.push(TemporalCouplingInstance {
528 pattern: TemporalPattern::PairedOperation {
529 open_method: paired.open.to_string(),
530 close_method: paired.close.to_string(),
531 },
532 source: "project-wide".to_string(),
533 severity: paired.severity,
534 description,
535 suggestion,
536 });
537 self.stats.total_issues += 1;
538 }
539 }
540 }
541 }
542
543 fn detect_lifecycle_patterns(&mut self) {
545 let mut found_phases: Vec<(LifecyclePhase, String, String)> = Vec::new();
546
547 for (module, func_name) in &self.function_defs {
548 for (phase, patterns) in Self::LIFECYCLE_PATTERNS {
549 for pattern in *patterns {
550 if func_name.contains(pattern) {
551 found_phases.push((*phase, module.clone(), func_name.clone()));
552 self.stats
553 .lifecycle_methods
554 .entry(*phase)
555 .or_default()
556 .push(format!("{}::{}", module, func_name));
557 break;
558 }
559 }
560 }
561 }
562
563 let has_init = self
565 .stats
566 .lifecycle_methods
567 .contains_key(&LifecyclePhase::Initialize);
568 let has_cleanup = self
569 .stats
570 .lifecycle_methods
571 .contains_key(&LifecyclePhase::Cleanup);
572 let has_start = self
573 .stats
574 .lifecycle_methods
575 .contains_key(&LifecyclePhase::Start);
576 let has_stop = self
577 .stats
578 .lifecycle_methods
579 .contains_key(&LifecyclePhase::Stop);
580
581 if has_init && !has_cleanup {
583 self.instances.push(TemporalCouplingInstance {
584 pattern: TemporalPattern::LifecycleSequence {
585 phase: LifecyclePhase::Initialize,
586 method_name: "init*".to_string(),
587 },
588 source: "project-wide".to_string(),
589 severity: 0.5,
590 description: "Initialization methods found but no cleanup/teardown methods"
591 .to_string(),
592 suggestion: "Consider adding cleanup methods to properly release resources"
593 .to_string(),
594 });
595 self.stats.total_issues += 1;
596 }
597
598 if has_start && !has_stop {
600 self.instances.push(TemporalCouplingInstance {
601 pattern: TemporalPattern::LifecycleSequence {
602 phase: LifecyclePhase::Start,
603 method_name: "start*".to_string(),
604 },
605 source: "project-wide".to_string(),
606 severity: 0.5,
607 description: "Start methods found but no stop methods".to_string(),
608 suggestion: "Consider adding stop/shutdown methods for graceful termination"
609 .to_string(),
610 });
611 self.stats.total_issues += 1;
612 }
613 }
614
615 fn detect_state_checks(&mut self) {
617 for (module, func_name) in &self.function_defs {
618 for (check_pattern, prerequisite) in Self::STATE_CHECK_PATTERNS {
619 if func_name.contains(check_pattern) {
620 self.stats
621 .state_checks
622 .push(format!("{}::{}", module, func_name));
623
624 self.instances.push(TemporalCouplingInstance {
625 pattern: TemporalPattern::StateCheck {
626 check_method: func_name.clone(),
627 implied_prerequisite: prerequisite.to_string(),
628 },
629 source: module.clone(),
630 severity: 0.4,
631 description: format!(
632 "State check '{}' implies temporal dependency on {}",
633 func_name, prerequisite
634 ),
635 suggestion: "Document the required call order or use type-state pattern to enforce it at compile time".to_string(),
636 });
637 self.stats.total_issues += 1;
638 break;
639 }
640 }
641 }
642 }
643
644 fn detect_rust_patterns(&mut self) {
646 for (module, type_name) in &self.drop_impls {
648 self.stats
649 .drop_impls
650 .push(format!("{}::{}", module, type_name));
651 }
652
653 for (module, guard_type) in &self.guard_usages {
655 self.stats
656 .guard_patterns
657 .push(format!("{}::{}", module, guard_type));
658 }
659
660 self.stats.async_spawns = self.async_spawns.len();
662 self.stats.async_joins = self.async_joins.len();
663
664 if self.stats.async_spawns > 0 && self.stats.async_joins == 0 {
665 self.instances.push(TemporalCouplingInstance {
666 pattern: TemporalPattern::RustAsyncSpawnWithoutJoin,
667 source: "project-wide".to_string(),
668 severity: 0.6,
669 description: format!(
670 "Found {} async spawn(s) but no explicit join/await. Tasks may be orphaned.",
671 self.stats.async_spawns
672 ),
673 suggestion: "Ensure spawned tasks are awaited or their JoinHandles are collected"
674 .to_string(),
675 });
676 self.stats.total_issues += 1;
677 }
678
679 for (module, operation) in &self.unsafe_allocs {
681 self.stats
682 .unsafe_allocations
683 .push(format!("{}::{}", module, operation));
684
685 let has_dealloc = self.unsafe_allocs.iter().any(|(_, op)| {
687 op.contains("dealloc") || op.contains("free") || op.contains("drop")
688 });
689
690 if operation.contains("alloc") && !has_dealloc {
691 self.instances.push(TemporalCouplingInstance {
692 pattern: TemporalPattern::RustUnsafeManualResource {
693 operation: operation.clone(),
694 },
695 source: module.clone(),
696 severity: 0.9,
697 description: format!(
698 "Unsafe allocation '{}' detected without corresponding deallocation",
699 operation
700 ),
701 suggestion: "Ensure manual allocations have corresponding deallocations, or use safe wrappers".to_string(),
702 });
703 self.stats.total_issues += 1;
704 }
705 }
706
707 for (type_name, methods) in &self.builder_types {
709 self.stats
710 .builder_patterns
711 .push(format!("{} ({})", type_name, methods.join(" -> ")));
712
713 if methods.len() >= 3 {
714 self.instances.push(TemporalCouplingInstance {
715 pattern: TemporalPattern::RustBuilderPattern {
716 type_name: type_name.clone(),
717 required_methods: methods.clone(),
718 },
719 source: "project-wide".to_string(),
720 severity: 0.3,
721 description: format!(
722 "Builder pattern for '{}' has {} methods that may require specific order",
723 type_name,
724 methods.len()
725 ),
726 suggestion:
727 "Consider using type-state pattern to enforce build order at compile time"
728 .to_string(),
729 });
730 }
731 }
732 }
733
734 pub fn summary(&self) -> String {
736 let mut report = String::new();
737 report.push_str("## Temporal Coupling Analysis\n\n");
738
739 if self.instances.is_empty()
740 && self.stats.drop_impls.is_empty()
741 && self.stats.guard_patterns.is_empty()
742 {
743 report.push_str("No temporal coupling patterns detected.\n");
744 return report;
745 }
746
747 report.push_str(&format!(
748 "**Total Issues**: {}\n\n",
749 self.stats.total_issues
750 ));
751
752 if !self.stats.drop_impls.is_empty() || !self.stats.guard_patterns.is_empty() {
754 report.push_str("### Rust RAII Patterns (Positive)\n\n");
755 report.push_str("These patterns help prevent temporal coupling issues:\n\n");
756
757 if !self.stats.drop_impls.is_empty() {
758 report.push_str(&format!(
759 "- **Drop implementations**: {} types with automatic cleanup\n",
760 self.stats.drop_impls.len()
761 ));
762 }
763 if !self.stats.guard_patterns.is_empty() {
764 report.push_str(&format!(
765 "- **Guard patterns**: {} auto-release guards used\n",
766 self.stats.guard_patterns.len()
767 ));
768 }
769 report.push('\n');
770 }
771
772 if !self.stats.paired_operations.is_empty() {
774 report.push_str("### Paired Operations\n\n");
775 report.push_str("| Operation | Open | Close | Status |\n");
776 report.push_str("|-----------|------|-------|--------|\n");
777
778 for (op, stats) in &self.stats.paired_operations {
779 let status = if stats.open_count == stats.close_count {
780 "Balanced"
781 } else {
782 "Imbalanced"
783 };
784 report.push_str(&format!(
785 "| {} | {} | {} | {} |\n",
786 op, stats.open_count, stats.close_count, status
787 ));
788 }
789 report.push('\n');
790 }
791
792 if self.stats.async_spawns > 0 || self.stats.async_joins > 0 {
794 report.push_str("### Async Task Management\n\n");
795 report.push_str("| Metric | Count |\n");
796 report.push_str("|--------|-------|\n");
797 report.push_str(&format!("| Spawns | {} |\n", self.stats.async_spawns));
798 report.push_str(&format!("| Joins/Awaits | {} |\n", self.stats.async_joins));
799 if self.stats.async_spawns > self.stats.async_joins {
800 report.push_str("\n**Warning**: More spawns than joins detected.\n");
801 }
802 report.push('\n');
803 }
804
805 if !self.stats.lifecycle_methods.is_empty() {
807 report.push_str("### Lifecycle Methods\n\n");
808 report.push_str("| Phase | Methods |\n");
809 report.push_str("|-------|--------|\n");
810
811 let mut phases: Vec<_> = self.stats.lifecycle_methods.iter().collect();
812 phases.sort_by_key(|(phase, _)| **phase);
813
814 for (phase, methods) in phases {
815 let method_list = if methods.len() > 3 {
816 format!("{}, ... ({} total)", methods[..3].join(", "), methods.len())
817 } else {
818 methods.join(", ")
819 };
820 report.push_str(&format!("| {} | {} |\n", phase.description(), method_list));
821 }
822 report.push('\n');
823 }
824
825 if !self.stats.unsafe_allocations.is_empty() {
827 report.push_str("### Unsafe Manual Resource Management\n\n");
828 report.push_str(
829 "**Warning**: Manual memory management detected. Ensure proper cleanup.\n\n",
830 );
831 for alloc in &self.stats.unsafe_allocations {
832 report.push_str(&format!("- `{}`\n", alloc));
833 }
834 report.push('\n');
835 }
836
837 let high_severity: Vec<_> = self
839 .instances
840 .iter()
841 .filter(|i| i.severity >= 0.6)
842 .collect();
843
844 if !high_severity.is_empty() {
845 report.push_str("### Issues Detected\n\n");
846 for instance in high_severity {
847 let severity_label = if instance.severity >= 0.8 {
848 "Critical"
849 } else if instance.severity >= 0.6 {
850 "High"
851 } else {
852 "Medium"
853 };
854 report.push_str(&format!(
855 "- **[{}]** {}\n",
856 severity_label, instance.description
857 ));
858 report.push_str(&format!(" - Suggestion: {}\n", instance.suggestion));
859 }
860 report.push('\n');
861 }
862
863 report
864 }
865
866 pub fn high_severity_instances(&self) -> Vec<&TemporalCouplingInstance> {
868 self.instances
869 .iter()
870 .filter(|i| i.severity >= 0.6)
871 .collect()
872 }
873}
874
875pub fn analyze_temporal_patterns(content: &str, module_name: &str) -> TemporalAnalyzer {
877 let mut analyzer = TemporalAnalyzer::new();
878 analyzer.set_module(module_name.to_string());
879
880 let fn_regex = regex_lite::Regex::new(r"fn\s+([a-z_][a-z0-9_]*)\s*[<(]").unwrap();
885 for cap in fn_regex.captures_iter(content) {
886 if let Some(name) = cap.get(1) {
887 analyzer.record_function_def(name.as_str());
888 }
889 }
890
891 let method_regex = regex_lite::Regex::new(r"\.([a-z_][a-z0-9_]*)\s*\(").unwrap();
893 for cap in method_regex.captures_iter(content) {
894 if let Some(name) = cap.get(1) {
895 let name_str = name.as_str();
896 analyzer.record_call(name_str);
897
898 for spawn_pattern in TemporalAnalyzer::RUST_ASYNC_SPAWN_PATTERNS {
900 if name_str.contains(spawn_pattern) {
901 analyzer.record_async_spawn(name_str);
902 break;
903 }
904 }
905
906 for join_pattern in TemporalAnalyzer::RUST_ASYNC_JOIN_PATTERNS {
908 if name_str.contains(join_pattern) {
909 analyzer.record_async_join(name_str);
910 break;
911 }
912 }
913 }
914 }
915
916 let call_regex = regex_lite::Regex::new(r"([a-z_][a-z0-9_]*)\s*\(").unwrap();
918 for cap in call_regex.captures_iter(content) {
919 if let Some(name) = cap.get(1) {
920 let name_str = name.as_str();
921 if ![
923 "if", "while", "for", "match", "fn", "let", "return", "Some", "None", "Ok", "Err",
924 ]
925 .contains(&name_str)
926 {
927 analyzer.record_call(name_str);
928 }
929 }
930 }
931
932 let drop_regex = regex_lite::Regex::new(r"impl\s+Drop\s+for\s+([A-Z][a-zA-Z0-9_]*)").unwrap();
934 for cap in drop_regex.captures_iter(content) {
935 if let Some(name) = cap.get(1) {
936 analyzer.record_drop_impl(name.as_str());
937 }
938 }
939
940 for guard_type in TemporalAnalyzer::RUST_GUARD_TYPES {
942 if content.contains(guard_type) {
943 analyzer.record_guard_usage(guard_type);
944 }
945 }
946
947 for pattern in TemporalAnalyzer::RUST_UNSAFE_ALLOC_PATTERNS {
949 if content.contains(pattern) {
950 analyzer.record_unsafe_alloc(pattern);
951 }
952 }
953
954 analyzer
955}
956
957#[cfg(test)]
958mod tests {
959 use super::*;
960
961 #[test]
962 fn test_paired_operation_detection() {
963 let mut analyzer = TemporalAnalyzer::new();
964 analyzer.set_module("test".to_string());
965
966 analyzer.record_call("open");
968 analyzer.record_call("open");
969 analyzer.record_call("close");
971
972 analyzer.analyze();
973
974 assert_eq!(
975 analyzer
976 .stats
977 .paired_operations
978 .get("open/close")
979 .unwrap()
980 .open_count,
981 2
982 );
983 assert_eq!(
984 analyzer
985 .stats
986 .paired_operations
987 .get("open/close")
988 .unwrap()
989 .close_count,
990 1
991 );
992 assert!(analyzer.stats.total_issues > 0);
993 }
994
995 #[test]
996 fn test_lifecycle_detection() {
997 let mut analyzer = TemporalAnalyzer::new();
998 analyzer.set_module("test".to_string());
999
1000 analyzer.record_function_def("initialize");
1001 analyzer.record_function_def("start_server");
1002 analyzer.record_function_def("process_request");
1003
1004 analyzer.analyze();
1005
1006 assert!(
1007 analyzer
1008 .stats
1009 .lifecycle_methods
1010 .contains_key(&LifecyclePhase::Initialize)
1011 );
1012 assert!(
1013 analyzer
1014 .stats
1015 .lifecycle_methods
1016 .contains_key(&LifecyclePhase::Start)
1017 );
1018 }
1019
1020 #[test]
1021 fn test_state_check_detection() {
1022 let mut analyzer = TemporalAnalyzer::new();
1023 analyzer.set_module("test".to_string());
1024
1025 analyzer.record_function_def("is_initialized");
1026 analyzer.record_function_def("check_connected");
1027
1028 analyzer.analyze();
1029
1030 assert!(!analyzer.stats.state_checks.is_empty());
1031 }
1032
1033 #[test]
1034 fn test_balanced_operations() {
1035 let mut analyzer = TemporalAnalyzer::new();
1036 analyzer.set_module("test".to_string());
1037
1038 analyzer.record_call("lock");
1039 analyzer.record_call("unlock");
1040 analyzer.record_call("lock");
1041 analyzer.record_call("unlock");
1042
1043 analyzer.analyze();
1044
1045 let stats = analyzer.stats.paired_operations.get("lock/unlock").unwrap();
1046 assert_eq!(stats.open_count, stats.close_count);
1047 }
1048
1049 #[test]
1050 fn test_analyze_temporal_patterns() {
1051 let code = r#"
1052 fn initialize(&mut self) {
1053 self.ready = true;
1054 }
1055
1056 fn process(&mut self) {
1057 if self.is_initialized() {
1058 self.handle_request();
1059 }
1060 }
1061
1062 fn cleanup(&mut self) {
1063 self.close();
1064 }
1065 "#;
1066
1067 let analyzer = analyze_temporal_patterns(code, "test_module");
1068 assert!(!analyzer.function_defs.is_empty());
1069 }
1070}