Skip to main content

mir_extractor/rules/
advanced_memory.rs

1//! Advanced memory safety rules.
2//!
3//! These rules perform deep dataflow analysis on MIR to detect:
4//! - Use-after-free (dangling pointer dereference)
5//! - Null pointer dereference
6//! - Misaligned pointer access
7//! - Pointer escapes from stack allocation
8//! - Overlapping ptr::copy_nonoverlapping calls
9//!
10//! Migrated from mir-advanced-rules crate to use the standard Rule trait.
11
12use std::collections::{HashMap, HashSet};
13use std::str::FromStr;
14
15use once_cell::sync::Lazy;
16use regex::Regex;
17
18use crate::{
19    interprocedural::InterProceduralAnalysis, AttackComplexity, AttackVector, Confidence,
20    Exploitability, Finding, MirFunction, MirPackage, PrivilegesRequired, Rule, RuleMetadata,
21    RuleOrigin, Severity, UserInteraction,
22};
23
24// ============================================================================
25// ADV001: Dangling Pointer / Use-After-Free Detection
26// ============================================================================
27
28/// Advanced memory safety rule: Detects use of pointers after their memory has been freed.
29///
30/// Approach:
31/// - Track pointer allocations (Box, Vec, raw pointer creation)
32/// - Track explicit deallocation (drop, free, Box::from_raw, etc.)
33/// - Flag any dereference or use of a pointer after its memory has been freed
34/// - Focus on unsafe blocks and FFI boundaries
35pub struct DanglingPointerUseAfterFreeRule {
36    metadata: RuleMetadata,
37}
38
39impl Default for DanglingPointerUseAfterFreeRule {
40    fn default() -> Self {
41        Self::new()
42    }
43}
44
45impl DanglingPointerUseAfterFreeRule {
46    pub fn new() -> Self {
47        Self {
48            metadata: RuleMetadata {
49                id: "RUSTCOLA200".to_string(),
50                name: "dangling-pointer-use-after-free".to_string(),
51                short_description: "Detects use of pointers after their memory has been freed"
52                    .to_string(),
53                full_description: "This rule performs dataflow analysis to track pointer \
54                    allocations and deallocations. It flags dereferences of pointers after \
55                    their backing memory has been freed (use-after-free), null pointer \
56                    dereferences, misaligned pointer access, and pointers that escape their \
57                    stack allocation scope."
58                    .to_string(),
59                help_uri: None,
60                default_severity: Severity::Critical,
61                origin: RuleOrigin::BuiltIn,
62                cwe_ids: vec![
63                    "CWE-416".to_string(),
64                    "CWE-476".to_string(),
65                    "CWE-825".to_string(),
66                ], // Use After Free, NULL Pointer Deref, Expired Pointer Deref
67                fix_suggestion: Some(
68                    "Ensure pointers are not used after their backing memory is freed. \
69                    Consider using safe Rust abstractions like references with proper lifetimes, \
70                    Rc/Arc for shared ownership, or ensure raw pointers are only dereferenced \
71                    while the owner is still alive."
72                        .to_string(),
73                ),
74                exploitability: Exploitability {
75                    attack_vector: AttackVector::Network, // Can often be triggered remotely
76                    attack_complexity: AttackComplexity::High, // Requires specific memory state
77                    privileges_required: PrivilegesRequired::None,
78                    user_interaction: UserInteraction::None,
79                },
80            },
81        }
82    }
83
84    /// Check if function should be skipped (derive macro or safe trait method)
85    fn should_skip_function(func: &MirFunction) -> bool {
86        is_derive_macro_function(&func.name) || is_safe_trait_method(&func.name, &func.signature)
87    }
88
89    /// Check if function is primarily tracing macro infrastructure
90    /// These generate complex temporary references that appear as UaF but are safe
91    fn is_tracing_macro_context(func: &MirFunction) -> bool {
92        // Check function name patterns
93        if func.name.contains("__CALLSITE") || func.name.contains("::META") {
94            return true;
95        }
96
97        // Check if this is a tracing macro closure (signature contains tracing macro path and ValueSet)
98        // Pattern: closures from tracing-*/src/macros.rs taking ValueSet<'_>
99        if func.signature.contains("tracing") && func.signature.contains("macros.rs") {
100            return true;
101        }
102        if func.signature.contains("ValueSet<'_>") {
103            return true;
104        }
105
106        // Check if body is dominated by tracing infrastructure
107        let tracing_lines = func
108            .body
109            .iter()
110            .filter(|line| {
111                line.contains("tracing::")
112                    || line.contains("observability_deps::tracing")
113                    || line.contains("__CALLSITE")
114                    || line.contains("tracing::Metadata")
115                    || line.contains("tracing::field::")
116                    || line.contains("LevelFilter")
117            })
118            .count();
119
120        // If more than 30% of the function is tracing code, skip it (lowered from 50%)
121        if func.body.len() > 3 && tracing_lines as f64 / func.body.len() as f64 > 0.3 {
122            return true;
123        }
124
125        false
126    }
127}
128
129impl Rule for DanglingPointerUseAfterFreeRule {
130    fn metadata(&self) -> &RuleMetadata {
131        &self.metadata
132    }
133
134    fn evaluate(
135        &self,
136        package: &MirPackage,
137        _inter_analysis: Option<&InterProceduralAnalysis>,
138    ) -> Vec<Finding> {
139        let mut findings = Vec::new();
140
141        for func in &package.functions {
142            // Skip derive macro functions and safe trait methods to reduce false positives
143            if Self::should_skip_function(func) {
144                continue;
145            }
146
147            // Skip tracing macro infrastructure - generates safe temporaries that look like UaF
148            if Self::is_tracing_macro_context(func) {
149                continue;
150            }
151
152            // Reconstruct MIR text from the function body
153            let mir_text = format!("fn {}() {{\n{}\n}}", func.name, func.body.join("\n"));
154
155            let analyzer = PointerAnalyzer::default();
156            let raw_findings = analyzer.analyze(&mir_text);
157
158            for msg in raw_findings {
159                findings.push(Finding {
160                    rule_id: self.metadata.id.clone(),
161                    rule_name: self.metadata.name.clone(),
162                    severity: self.metadata.default_severity,
163                    confidence: Confidence::High,
164                    message: msg,
165                    function: func.name.clone(),
166                    function_signature: func.signature.clone(),
167                    evidence: func.body.clone(),
168                    span: func.span.clone(),
169                    exploitability: self.metadata.exploitability.clone(),
170                    exploitability_score: self.metadata.exploitability.score(),
171                    ..Default::default()
172                });
173            }
174        }
175
176        findings
177    }
178}
179
180// ============================================================================
181// Helper Functions for Filtering
182// ============================================================================
183
184/// Detect derive macro generated functions by name pattern
185fn is_derive_macro_function(func_name: &str) -> bool {
186    // Derive macro functions have names like "<impl at src/lib.rs:10:5: 12:6>::eq"
187    static RE_DERIVE: Lazy<Regex> = Lazy::new(|| {
188        Regex::new(r"<impl at [^>]+:\d+:\d+:\s*\d+:\d+>::").expect("derive macro regex")
189    });
190    RE_DERIVE.is_match(func_name)
191}
192
193/// Detect safe trait methods that commonly take references
194fn is_safe_trait_method(func_name: &str, _func_signature: &str) -> bool {
195    // These trait methods commonly take &self and are safe patterns
196    let safe_methods = [
197        "::eq",
198        "::ne",
199        "::partial_cmp",
200        "::cmp",
201        "::hash",
202        "::fmt",
203        "::clone",
204        "::default",
205        "::drop",
206    ];
207    safe_methods.iter().any(|m| func_name.ends_with(m))
208}
209
210// ============================================================================
211// Pointer Analyzer (migrated from mir-advanced-rules)
212// ============================================================================
213
214#[derive(Debug, Clone, Copy, PartialEq, Eq)]
215enum OwnerKind {
216    Stack,
217    Heap,
218    Unknown,
219}
220
221#[derive(Debug, Clone, Copy, PartialEq, Eq)]
222enum InvalidationKind {
223    Drop,
224    Reallocate,
225}
226
227impl std::fmt::Display for InvalidationKind {
228    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
229        match self {
230            Self::Drop => write!(f, "drop"),
231            Self::Reallocate => write!(f, "reallocation"),
232        }
233    }
234}
235
236#[derive(Debug, Clone)]
237struct Invalidation {
238    line_index: usize,
239    line: String,
240    kind: InvalidationKind,
241}
242
243#[derive(Debug, Clone)]
244struct OwnerState {
245    kind: OwnerKind,
246    leaked: bool,
247    invalidation: Option<Invalidation>,
248}
249
250impl OwnerState {
251    fn new(kind: OwnerKind) -> Self {
252        Self {
253            kind,
254            leaked: false,
255            invalidation: None,
256        }
257    }
258
259    fn note_invalidation(&mut self, inv: Invalidation) {
260        if self.leaked {
261            return;
262        }
263
264        match &self.invalidation {
265            Some(current) if current.line_index >= inv.line_index => {}
266            _ => self.invalidation = Some(inv),
267        }
268    }
269}
270
271#[derive(Debug, Clone)]
272struct PointerInfo {
273    owner: String,
274    owner_kind: OwnerKind,
275    creation_line: String,
276    creation_index: usize,
277}
278
279#[derive(Debug, Clone, Copy, PartialEq, Eq)]
280enum PointerValidityKind {
281    Null,
282    Misaligned { alignment: usize, address: u128 },
283}
284
285#[derive(Debug, Clone)]
286struct PointerValidityInfo {
287    kind: PointerValidityKind,
288    line: String,
289}
290
291/// Captures the MIR locals used as the source and destination operands of a
292/// `ptr::copy_nonoverlapping` call so we can reason about aliasing.
293#[derive(Debug, Clone)]
294struct CopyNonOverlappingCall {
295    src: String,
296    dst: String,
297}
298
299#[derive(Debug, Default)]
300struct PointerAnalyzer {
301    pointers: HashMap<String, PointerInfo>,
302    aliases: HashMap<String, String>,
303    owners: HashMap<String, OwnerState>,
304    findings: Vec<String>,
305    reported: HashSet<(String, usize)>,
306    pointer_validities: HashMap<String, PointerValidityInfo>,
307}
308
309impl PointerAnalyzer {
310    fn analyze(mut self, mir: &str) -> Vec<String> {
311        let lines: Vec<&str> = mir.lines().collect();
312
313        for (idx, line) in lines.iter().enumerate() {
314            let trimmed = line.trim();
315            if trimmed.is_empty() {
316                continue;
317            }
318
319            if let Some((alias, base)) = Self::detect_alias_assignment(trimmed) {
320                let base_resolved = self.resolve_alias(&base);
321                self.aliases.insert(alias, base_resolved);
322            }
323
324            if let Some(pointer) = Self::detect_null_pointer_assignment(trimmed) {
325                self.record_null_pointer(pointer, idx, trimmed);
326            }
327
328            if let Some(event) = Self::detect_const_pointer_assignment(trimmed) {
329                self.record_const_pointer(event, idx, trimmed);
330            }
331
332            if let Some(event) = Self::detect_pointer_creation(trimmed) {
333                self.record_pointer_creation(idx, event);
334            }
335
336            if let Some(call) = Self::detect_copy_nonoverlapping(trimmed) {
337                self.evaluate_copy_nonoverlapping(idx, trimmed, call);
338            }
339
340            if let Some((owner_raw, kind)) = Self::detect_owner_invalidation(trimmed) {
341                let mut owner = normalize_owner(&owner_raw);
342                owner = self.resolve_alias(&owner);
343
344                let mut inferred_kind = OwnerKind::Unknown;
345                if let Some(info) = self.pointers.get(&owner) {
346                    inferred_kind = info.owner_kind;
347                    owner = info.owner.clone();
348                }
349
350                if owner.is_empty() {
351                    continue;
352                }
353
354                let entry = self
355                    .owners
356                    .entry(owner.clone())
357                    .or_insert_with(|| OwnerState::new(inferred_kind));
358
359                if entry.kind == OwnerKind::Unknown {
360                    entry.kind = inferred_kind;
361                }
362
363                entry.note_invalidation(Invalidation {
364                    line_index: idx,
365                    line: trimmed.to_string(),
366                    kind,
367                });
368            }
369
370            for ptr_var in Self::detect_pointer_dereference(trimmed) {
371                self.evaluate_pointer_use(idx, trimmed, &ptr_var, "dereference after owner drop");
372                self.evaluate_invalid_pointer_use(idx, trimmed, &ptr_var);
373            }
374
375            for ptr_var in Self::detect_return_aggregate_pointers(trimmed) {
376                self.evaluate_pointer_escape(idx, trimmed, &ptr_var, "returned inside aggregate");
377            }
378
379            for ptr_var in Self::detect_pointer_store(trimmed) {
380                self.evaluate_pointer_escape(idx, trimmed, &ptr_var, "stored through pointer");
381            }
382
383            if let Some(ptr_var) = Self::detect_return_pointer(trimmed) {
384                self.evaluate_pointer_escape(idx, trimmed, &ptr_var, "returned to caller");
385            }
386        }
387
388        self.findings
389    }
390
391    fn record_pointer_creation(&mut self, line_index: usize, event: PointerCreationEvent) {
392        let mut owner = normalize_owner(&event.owner);
393        owner = self.resolve_alias(&owner);
394
395        let mut owner_kind = event.owner_kind;
396
397        if let Some(existing) = self.pointers.get(&owner) {
398            owner = existing.owner.clone();
399            owner_kind = existing.owner_kind;
400        }
401
402        if owner.is_empty() {
403            return;
404        }
405
406        let entry = self
407            .owners
408            .entry(owner.clone())
409            .or_insert_with(|| OwnerState::new(owner_kind));
410
411        if entry.kind == OwnerKind::Unknown {
412            entry.kind = owner_kind;
413        }
414
415        if event.leaked {
416            entry.leaked = true;
417            entry.invalidation = None;
418        }
419
420        let info = PointerInfo {
421            owner: owner.clone(),
422            owner_kind: entry.kind,
423            creation_line: event.line.to_string(),
424            creation_index: line_index,
425        };
426
427        self.pointers.insert(event.pointer.clone(), info);
428        self.aliases.remove(&event.pointer);
429        self.pointer_validities.remove(&event.pointer);
430    }
431
432    fn reset_pointer_state(&mut self, pointer: &str) {
433        self.aliases.remove(pointer);
434        self.pointers.remove(pointer);
435    }
436
437    fn record_null_pointer(&mut self, pointer: String, _line_index: usize, line: &str) {
438        self.reset_pointer_state(&pointer);
439        let info = PointerValidityInfo {
440            kind: PointerValidityKind::Null,
441            line: line.to_string(),
442        };
443        self.pointer_validities.insert(pointer, info);
444    }
445
446    fn record_const_pointer(&mut self, event: ConstPointerEvent, _line_index: usize, line: &str) {
447        self.reset_pointer_state(&event.pointer);
448
449        if event.value == 0 {
450            let info = PointerValidityInfo {
451                kind: PointerValidityKind::Null,
452                line: line.to_string(),
453            };
454            self.pointer_validities.insert(event.pointer, info);
455            return;
456        }
457
458        if let Some(align) = alignment_for_type(&event.pointee) {
459            if align > 1 && (event.value % align as u128 != 0) {
460                let info = PointerValidityInfo {
461                    kind: PointerValidityKind::Misaligned {
462                        alignment: align,
463                        address: event.value,
464                    },
465                    line: line.to_string(),
466                };
467                self.pointer_validities.insert(event.pointer, info);
468                return;
469            }
470        }
471
472        self.pointer_validities.remove(&event.pointer);
473    }
474
475    fn evaluate_pointer_use(&mut self, line_index: usize, line: &str, ptr_var: &str, reason: &str) {
476        if let Some((pointer_key, info)) = self.lookup_pointer(ptr_var) {
477            let (owner_leaked, owner_invalidation) = match self.owners.get(&info.owner) {
478                Some(state) => (state.leaked, state.invalidation.clone()),
479                None => return,
480            };
481
482            if owner_leaked {
483                return;
484            }
485
486            if let Some(invalidation) = owner_invalidation {
487                if invalidation.line_index < line_index
488                    && invalidation.line_index >= info.creation_index
489                    && self.reported.insert((pointer_key.clone(), line_index))
490                {
491                    let message = format!(
492                        "Potential dangling pointer: `{}` {} after {} of `{}`.\n  \
493                        creation: `{}`\n  invalidation: `{}`\n  use: `{}`",
494                        pointer_key,
495                        reason,
496                        invalidation.kind,
497                        info.owner,
498                        info.creation_line.trim(),
499                        invalidation.line.trim(),
500                        line.trim()
501                    );
502                    self.findings.push(message);
503                }
504            }
505        }
506    }
507
508    fn evaluate_invalid_pointer_use(&mut self, line_index: usize, line: &str, ptr_var: &str) {
509        let pointer_key = self.resolve_alias(ptr_var);
510        if pointer_key.is_empty() {
511            return;
512        }
513
514        let info = match self.pointer_validities.get(&pointer_key) {
515            Some(info) => info.clone(),
516            None => return,
517        };
518
519        let display = if pointer_key == ptr_var {
520            pointer_key.clone()
521        } else {
522            format!("{} (alias of {})", ptr_var, pointer_key)
523        };
524
525        let reported_key = format!("invalid:{}", pointer_key);
526        if !self.reported.insert((reported_key, line_index)) {
527            return;
528        }
529
530        let message = match info.kind {
531            PointerValidityKind::Null => format!(
532                "Invalid pointer dereference: `{}` is null.\n  \
533                assignment: `{}`\n  use: `{}`",
534                display,
535                info.line.trim(),
536                line.trim()
537            ),
538            PointerValidityKind::Misaligned { alignment, address } => format!(
539                "Invalid pointer dereference: `{}` has address 0x{:x} which is not aligned \
540                to {} bytes.\n  assignment: `{}`\n  use: `{}`",
541                display,
542                address,
543                alignment,
544                info.line.trim(),
545                line.trim()
546            ),
547        };
548
549        self.findings.push(message);
550    }
551
552    fn evaluate_pointer_escape(
553        &mut self,
554        line_index: usize,
555        line: &str,
556        ptr_var: &str,
557        reason: &str,
558    ) {
559        if let Some((pointer_key, info)) = self.lookup_pointer(ptr_var) {
560            if info.owner_kind == OwnerKind::Stack
561                && self.reported.insert((pointer_key.clone(), line_index))
562            {
563                let message = format!(
564                    "Pointer `{}` escapes stack allocation: {}.\n  \
565                    creation: `{}`\n  escape: `{}`",
566                    pointer_key,
567                    reason,
568                    info.creation_line.trim(),
569                    line.trim()
570                );
571                self.findings.push(message);
572            }
573        }
574    }
575
576    fn evaluate_copy_nonoverlapping(
577        &mut self,
578        line_index: usize,
579        line: &str,
580        call: CopyNonOverlappingCall,
581    ) {
582        let src_candidates = self.pointer_equivalence_set(&call.src);
583        let dst_candidates = self.pointer_equivalence_set(&call.dst);
584
585        if src_candidates.is_empty() || dst_candidates.is_empty() {
586            return;
587        }
588
589        let overlap = src_candidates
590            .intersection(&dst_candidates)
591            .any(|candidate| !candidate.is_empty());
592
593        if !overlap {
594            return;
595        }
596
597        let src_key = self.resolve_alias(&call.src);
598        let dst_key = self.resolve_alias(&call.dst);
599
600        let reported_key = format!("copy:{}->{}", src_key, dst_key);
601        if !self.reported.insert((reported_key, line_index)) {
602            return;
603        }
604
605        let src_origin = self
606            .pointers
607            .get(&src_key)
608            .map(|info| info.creation_line.trim().to_string());
609        let dst_origin = self
610            .pointers
611            .get(&dst_key)
612            .map(|info| info.creation_line.trim().to_string());
613
614        let src_display = if call.src == src_key {
615            call.src.clone()
616        } else {
617            format!("{} (resolves to {})", call.src, src_key)
618        };
619
620        let dst_display = if call.dst == dst_key {
621            call.dst.clone()
622        } else {
623            format!("{} (resolves to {})", call.dst, dst_key)
624        };
625
626        let mut message = format!(
627            "Unsafe ptr::copy_nonoverlapping: src `{}` and dst `{}` may overlap.\n  \
628            call: `{}`",
629            src_display,
630            dst_display,
631            line.trim()
632        );
633
634        if let Some(origin) = src_origin {
635            message.push_str(&format!("\n  src origin: `{}`", origin));
636        }
637
638        if let Some(origin) = dst_origin {
639            message.push_str(&format!("\n  dst origin: `{}`", origin));
640        }
641
642        self.findings.push(message);
643    }
644
645    fn lookup_pointer(&self, var: &str) -> Option<(String, PointerInfo)> {
646        let pointer_key = self.resolve_alias(var);
647        self.pointers
648            .get(&pointer_key)
649            .cloned()
650            .map(|info| (pointer_key, info))
651    }
652
653    fn resolve_alias(&self, var: &str) -> String {
654        let mut current = var.trim().to_string();
655        let mut visited = HashSet::new();
656
657        while let Some(next) = self.aliases.get(&current) {
658            if !visited.insert(current.clone()) {
659                break;
660            }
661            current = next.clone();
662        }
663
664        current
665    }
666
667    fn pointer_equivalence_set(&self, var: &str) -> HashSet<String> {
668        let mut set = HashSet::new();
669
670        let trimmed = var.trim();
671        if trimmed.is_empty() {
672            return set;
673        }
674
675        set.insert(trimmed.to_string());
676
677        let resolved = self.resolve_alias(trimmed);
678        if !resolved.is_empty() {
679            set.insert(resolved.clone());
680
681            if let Some(info) = self.pointers.get(&resolved) {
682                if !info.owner.is_empty() {
683                    set.insert(info.owner.clone());
684                }
685            }
686        }
687
688        set
689    }
690
691    fn detect_alias_assignment(line: &str) -> Option<(String, String)> {
692        static RE_ALIAS_SIMPLE: Lazy<Regex> = Lazy::new(|| {
693            Regex::new(r"^(_\d+)\s*=\s*(?:copy|move)\s+(_\d+)\s*;").expect("alias regex")
694        });
695        static RE_ALIAS_INDEX: Lazy<Regex> = Lazy::new(|| {
696            Regex::new(r"^(_\d+)\s*=.*::index\(\s*(?:move|copy)\s+(_\d+),")
697                .expect("index alias regex")
698        });
699
700        if let Some(caps) = RE_ALIAS_SIMPLE.captures(line) {
701            return Some((caps[1].to_string(), caps[2].to_string()));
702        }
703
704        if let Some(caps) = RE_ALIAS_INDEX.captures(line) {
705            return Some((caps[1].to_string(), caps[2].to_string()));
706        }
707
708        None
709    }
710
711    fn detect_null_pointer_assignment(line: &str) -> Option<String> {
712        static RE_NULL: Lazy<Regex> = Lazy::new(|| {
713            Regex::new(r"^(_\d+)\s*=\s*null(?:_mut)?::<[^>]+>\(\)\s*->")
714                .expect("null pointer regex")
715        });
716
717        RE_NULL.captures(line).map(|caps| caps[1].to_string())
718    }
719
720    fn detect_const_pointer_assignment(line: &str) -> Option<ConstPointerEvent> {
721        static RE_CONST_PTR: Lazy<Regex> = Lazy::new(|| {
722            Regex::new(
723                r"^(_\d+)\s*=\s*const\s+([0-9_]+)_(?:[iu](?:size|8|16|32|64|128))\s+as\s+\*(?:const|mut)\s+([A-Za-z0-9_]+)",
724            )
725            .expect("const pointer regex")
726        });
727
728        let caps = RE_CONST_PTR.captures(line)?;
729        let pointer = caps[1].to_string();
730        let value_raw = caps[2].replace('_', "");
731        let value = u128::from_str(&value_raw).ok()?;
732        let pointee = caps[3].to_ascii_lowercase();
733
734        Some(ConstPointerEvent {
735            pointer,
736            value,
737            pointee,
738        })
739    }
740
741    fn detect_copy_nonoverlapping(line: &str) -> Option<CopyNonOverlappingCall> {
742        static RE_COPY: Lazy<Regex> = Lazy::new(|| {
743            Regex::new(
744                r"copy_nonoverlapping::<[^>]+>\((?:move|copy)\s+(_\d+)[^,]*,\s*(?:move|copy)\s+(_\d+)",
745            )
746            .expect("copy_nonoverlapping regex")
747        });
748
749        let caps = RE_COPY.captures(line)?;
750        Some(CopyNonOverlappingCall {
751            src: caps[1].to_string(),
752            dst: caps[2].to_string(),
753        })
754    }
755
756    fn detect_pointer_creation(line: &str) -> Option<PointerCreationEvent<'_>> {
757        static RE_ADDR_OF: Lazy<Regex> = Lazy::new(|| {
758            Regex::new(r"^(_\d+)\s*=\s*&raw\s+(?:const|mut)\s+\(\*([^\)]+)\);")
759                .expect("addr-of regex")
760        });
761        static RE_REF: Lazy<Regex> =
762            Lazy::new(|| Regex::new(r"^(_\d+)\s*=\s*&(?:mut\s+)?(_\d+)\s*;").expect("ref regex"));
763        static RE_INTO_RAW: Lazy<Regex> = Lazy::new(|| {
764            Regex::new(r"^(_\d+)\s*=.*::into_raw\(\s*move\s+(_\d+)\s*\).*").expect("into_raw regex")
765        });
766        static RE_BOX_LEAK: Lazy<Regex> = Lazy::new(|| {
767            Regex::new(r"^(_\d+)\s*=.*Box::leak\(\s*move\s+(_\d+)\s*\).*").expect("box leak regex")
768        });
769        static RE_AS_PTR: Lazy<Regex> = Lazy::new(|| {
770            Regex::new(r"^(_\d+)\s*=\s*copy\s+(_\d+)\s+as\s+\*const").expect("as_ptr regex")
771        });
772
773        if let Some(caps) = RE_ADDR_OF.captures(line) {
774            return Some(PointerCreationEvent::stack(
775                caps[1].to_string(),
776                caps[2].to_string(),
777                line,
778            ));
779        }
780
781        if let Some(caps) = RE_REF.captures(line) {
782            return Some(PointerCreationEvent::stack(
783                caps[1].to_string(),
784                caps[2].to_string(),
785                line,
786            ));
787        }
788
789        if let Some(caps) = RE_BOX_LEAK.captures(line) {
790            return Some(PointerCreationEvent::leaked_heap(
791                caps[1].to_string(),
792                caps[2].to_string(),
793                line,
794            ));
795        }
796
797        if let Some(caps) = RE_INTO_RAW.captures(line) {
798            return Some(PointerCreationEvent::heap(
799                caps[1].to_string(),
800                caps[2].to_string(),
801                line,
802            ));
803        }
804
805        if let Some(caps) = RE_AS_PTR.captures(line) {
806            return Some(PointerCreationEvent::heap(
807                caps[1].to_string(),
808                caps[2].to_string(),
809                line,
810            ));
811        }
812
813        None
814    }
815
816    fn detect_owner_invalidation(line: &str) -> Option<(String, InvalidationKind)> {
817        static RE_DROP: Lazy<Regex> =
818            Lazy::new(|| Regex::new(r"drop\(\s*([^\)]+)\)").expect("drop regex"));
819        static RE_STORAGE_DEAD: Lazy<Regex> =
820            Lazy::new(|| Regex::new(r"StorageDead\(\s*([^\)]+)\)").expect("storage dead regex"));
821        static RE_DROP_IN_PLACE: Lazy<Regex> = Lazy::new(|| {
822            Regex::new(r"drop_in_place::<[^>]+>\(\s*(?:move\s+)?([^\)]+)\)")
823                .expect("drop_in_place regex")
824        });
825        static RE_DEALLOC: Lazy<Regex> = Lazy::new(|| {
826            Regex::new(r"dealloc\(\s*(?:move\s+)?([^,\s\)]+)").expect("dealloc regex")
827        });
828        static RE_VEC_REALLOC: Lazy<Regex> = Lazy::new(|| {
829            Regex::new(
830                r"Vec::<[^>]+>::(?:push|append|extend|insert|reserve|reserve_exact|resize|resize_with|shrink_to_fit|shrink_to|truncate|clear)\(\s*(?:move|copy)?\s*(_\d+)",
831            )
832            .expect("vec realloc regex")
833        });
834
835        if let Some(caps) = RE_DROP.captures(line) {
836            return Some((caps[1].to_string(), InvalidationKind::Drop));
837        }
838
839        if let Some(caps) = RE_STORAGE_DEAD.captures(line) {
840            return Some((caps[1].to_string(), InvalidationKind::Drop));
841        }
842
843        if let Some(caps) = RE_DROP_IN_PLACE.captures(line) {
844            return Some((caps[1].to_string(), InvalidationKind::Drop));
845        }
846
847        if let Some(caps) = RE_DEALLOC.captures(line) {
848            return Some((caps[1].to_string(), InvalidationKind::Drop));
849        }
850
851        if let Some(caps) = RE_VEC_REALLOC.captures(line) {
852            return Some((caps[1].to_string(), InvalidationKind::Reallocate));
853        }
854
855        None
856    }
857
858    fn detect_pointer_dereference(line: &str) -> Vec<String> {
859        static RE_DEREF: Lazy<Regex> =
860            Lazy::new(|| Regex::new(r"\*\s*\(?\s*(_\d+)").expect("deref regex"));
861        static RE_PTR_READ: Lazy<Regex> = Lazy::new(|| {
862            Regex::new(r"ptr::(?:read|write)(?:_unchecked)?::<[^>]+>\(\s*(?:move\s+)?(_\d+)\)")
863                .expect("ptr::read regex")
864        });
865
866        let mut vars: Vec<String> = RE_DEREF
867            .captures_iter(line)
868            .map(|caps| caps[1].to_string())
869            .collect();
870
871        for caps in RE_PTR_READ.captures_iter(line) {
872            vars.push(caps[1].to_string());
873        }
874
875        vars
876    }
877
878    fn detect_return_aggregate_pointers(line: &str) -> Vec<String> {
879        let trimmed = line.trim_start();
880        if !trimmed.starts_with("_0") {
881            return Vec::new();
882        }
883
884        // Skip function/method calls - these consume the reference, not return it
885        // Pattern: `_0 = <Type>::method(move _3, ...) -> [return: bb1, ...]`
886        // The `->` indicates control flow from a function call
887        if trimmed.contains("->") {
888            return Vec::new();
889        }
890
891        if !(trimmed.contains('{') || trimmed.contains('(')) {
892            return Vec::new();
893        }
894
895        static RE_MOVE: Lazy<Regex> =
896            Lazy::new(|| Regex::new(r"(?:move|copy)\s+(_\d+)").expect("aggregate move regex"));
897
898        let mut vars = HashSet::new();
899
900        for caps in RE_MOVE.captures_iter(line) {
901            vars.insert(caps[1].to_string());
902        }
903
904        vars.into_iter().collect()
905    }
906
907    fn detect_pointer_store(line: &str) -> Vec<String> {
908        static RE_STORE: Lazy<Regex> = Lazy::new(|| {
909            Regex::new(r"\(\*[^\)]+\)\s*=\s*(?:move|copy)\s+(_\d+)").expect("pointer store regex")
910        });
911
912        RE_STORE
913            .captures_iter(line)
914            .map(|caps| caps[1].to_string())
915            .collect()
916    }
917
918    fn detect_return_pointer(line: &str) -> Option<String> {
919        static RE_RETURN: Lazy<Regex> = Lazy::new(|| {
920            Regex::new(r"^_0\s*=\s*(?:copy|move)\s+(_\d+)\s*;").expect("return regex")
921        });
922
923        RE_RETURN.captures(line).map(|caps| caps[1].to_string())
924    }
925}
926
927// ============================================================================
928// Supporting Types
929// ============================================================================
930
931struct PointerCreationEvent<'a> {
932    pointer: String,
933    owner: String,
934    owner_kind: OwnerKind,
935    leaked: bool,
936    line: &'a str,
937}
938
939struct ConstPointerEvent {
940    pointer: String,
941    value: u128,
942    pointee: String,
943}
944
945impl<'a> PointerCreationEvent<'a> {
946    fn stack(pointer: String, owner: String, line: &'a str) -> Self {
947        Self {
948            pointer,
949            owner,
950            owner_kind: OwnerKind::Stack,
951            leaked: false,
952            line,
953        }
954    }
955
956    fn heap(pointer: String, owner: String, line: &'a str) -> Self {
957        Self {
958            pointer,
959            owner,
960            owner_kind: OwnerKind::Heap,
961            leaked: false,
962            line,
963        }
964    }
965
966    fn leaked_heap(pointer: String, owner: String, line: &'a str) -> Self {
967        Self {
968            pointer,
969            owner,
970            owner_kind: OwnerKind::Heap,
971            leaked: true,
972            line,
973        }
974    }
975}
976
977fn normalize_owner(raw: &str) -> String {
978    let mut text = raw.trim().trim_end_matches(';').trim();
979
980    while text.starts_with('(') && text.ends_with(')') && text.len() > 2 {
981        text = &text[1..text.len() - 1];
982        text = text.trim();
983    }
984
985    if let Some(stripped) = text.strip_prefix('*') {
986        text = stripped.trim();
987    }
988
989    if let Some(stripped) = text.strip_prefix("move ") {
990        text = stripped.trim();
991    }
992
993    if let Some(stripped) = text.strip_prefix("copy ") {
994        text = stripped.trim();
995    }
996
997    text.to_string()
998}
999
1000fn alignment_for_type(pointee: &str) -> Option<usize> {
1001    match pointee {
1002        "u8" | "i8" => Some(1),
1003        "u16" | "i16" => Some(2),
1004        "u32" | "i32" | "f32" => Some(4),
1005        "u64" | "i64" | "f64" => Some(8),
1006        "u128" | "i128" => Some(16),
1007        "usize" | "isize" => Some(8), // Assume 64-bit
1008        _ => None,
1009    }
1010}
1011
1012/// Register all advanced memory rules with the rule engine.
1013pub fn register_advanced_memory_rules(engine: &mut crate::RuleEngine) {
1014    engine.register_rule(Box::new(DanglingPointerUseAfterFreeRule::new()));
1015}
1016
1017#[cfg(test)]
1018mod tests {
1019    use super::*;
1020
1021    #[test]
1022    fn test_dangling_pointer_rule_metadata() {
1023        let rule = DanglingPointerUseAfterFreeRule::new();
1024        assert_eq!(rule.metadata().id, "RUSTCOLA200");
1025        assert_eq!(rule.metadata().default_severity, Severity::Critical);
1026        assert!(rule.metadata().cwe_ids.contains(&"CWE-416".to_string())); // Use After Free
1027    }
1028
1029    #[test]
1030    fn test_derive_macro_detection() {
1031        assert!(is_derive_macro_function(
1032            "<impl at src/lib.rs:10:5: 12:6>::eq"
1033        ));
1034        assert!(!is_derive_macro_function("my_module::my_function"));
1035    }
1036
1037    #[test]
1038    fn test_safe_trait_method_detection() {
1039        assert!(is_safe_trait_method(
1040            "MyType::eq",
1041            "fn eq(&self, other: &Self) -> bool"
1042        ));
1043        assert!(is_safe_trait_method(
1044            "MyType::clone",
1045            "fn clone(&self) -> Self"
1046        ));
1047        assert!(!is_safe_trait_method(
1048            "MyType::process",
1049            "fn process(&self)"
1050        ));
1051    }
1052}