1use 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
24pub 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 ], 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, attack_complexity: AttackComplexity::High, privileges_required: PrivilegesRequired::None,
78 user_interaction: UserInteraction::None,
79 },
80 },
81 }
82 }
83
84 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 fn is_tracing_macro_context(func: &MirFunction) -> bool {
92 if func.name.contains("__CALLSITE") || func.name.contains("::META") {
94 return true;
95 }
96
97 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 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 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 if Self::should_skip_function(func) {
144 continue;
145 }
146
147 if Self::is_tracing_macro_context(func) {
149 continue;
150 }
151
152 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
180fn is_derive_macro_function(func_name: &str) -> bool {
186 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
193fn is_safe_trait_method(func_name: &str, _func_signature: &str) -> bool {
195 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#[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#[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(¤t) {
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 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
927struct 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), _ => None,
1009 }
1010}
1011
1012pub 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())); }
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}