1use serde::{Deserialize, Serialize};
12use std::fmt;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
20pub enum Severity {
21 Info,
23 Medium,
25 High,
27 Critical,
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
36pub enum OwnershipDefect {
37 PointerMisclassification,
39 LifetimeInferenceGap,
41 DanglingPointerRisk,
43 AliasViolation,
45 UnsafeMinimizationFailure,
47 ArraySliceMismatch,
49 ResourceLeakPattern,
51 MutabilityMismatch,
53}
54
55impl OwnershipDefect {
56 pub fn code(&self) -> &'static str {
58 match self {
59 Self::PointerMisclassification => "DECY-O-001",
60 Self::LifetimeInferenceGap => "DECY-O-002",
61 Self::DanglingPointerRisk => "DECY-O-003",
62 Self::AliasViolation => "DECY-O-004",
63 Self::UnsafeMinimizationFailure => "DECY-O-005",
64 Self::ArraySliceMismatch => "DECY-O-006",
65 Self::ResourceLeakPattern => "DECY-O-007",
66 Self::MutabilityMismatch => "DECY-O-008",
67 }
68 }
69
70 pub fn description(&self) -> &'static str {
72 match self {
73 Self::PointerMisclassification => {
74 "Owning pointer classified as borrowing or vice versa"
75 }
76 Self::LifetimeInferenceGap => "Missing or incorrect lifetime annotations",
77 Self::DanglingPointerRisk => "Use-after-free pattern not caught",
78 Self::AliasViolation => "Multiple mutable aliases generated",
79 Self::UnsafeMinimizationFailure => "Unnecessary unsafe blocks in output",
80 Self::ArraySliceMismatch => "Array vs slice semantics error",
81 Self::ResourceLeakPattern => "Allocation without corresponding deallocation",
82 Self::MutabilityMismatch => "Const pointer vs mutable reference error",
83 }
84 }
85
86 pub fn severity(&self) -> Severity {
88 match self {
89 Self::DanglingPointerRisk | Self::AliasViolation => Severity::Critical,
91 Self::PointerMisclassification
93 | Self::LifetimeInferenceGap
94 | Self::MutabilityMismatch => Severity::High,
95 Self::UnsafeMinimizationFailure
97 | Self::ArraySliceMismatch
98 | Self::ResourceLeakPattern => Severity::Medium,
99 }
100 }
101
102 pub fn from_code(code: &str) -> Option<Self> {
104 match code {
105 "DECY-O-001" => Some(Self::PointerMisclassification),
106 "DECY-O-002" => Some(Self::LifetimeInferenceGap),
107 "DECY-O-003" => Some(Self::DanglingPointerRisk),
108 "DECY-O-004" => Some(Self::AliasViolation),
109 "DECY-O-005" => Some(Self::UnsafeMinimizationFailure),
110 "DECY-O-006" => Some(Self::ArraySliceMismatch),
111 "DECY-O-007" => Some(Self::ResourceLeakPattern),
112 "DECY-O-008" => Some(Self::MutabilityMismatch),
113 _ => None,
114 }
115 }
116}
117
118impl fmt::Display for OwnershipDefect {
119 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120 write!(f, "[{}] {}", self.code(), self.description())
121 }
122}
123
124#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
130pub enum AllocationKind {
131 Malloc,
133 Calloc,
135 Realloc,
137 Stack,
139 Static,
141 Parameter,
143 #[default]
145 Unknown,
146}
147
148#[derive(Debug, Clone, Default, Serialize, Deserialize)]
154pub struct OwnershipFeatures {
155 pub pointer_depth: u8,
158 pub is_const: bool,
160 pub is_array_decay: bool,
162 pub has_size_param: bool,
164
165 pub allocation_site: AllocationKind,
168 pub deallocation_count: u8,
170 pub alias_count: u8,
172 pub escape_scope: bool,
174
175 pub read_count: u32,
178 pub write_count: u32,
180 pub arithmetic_ops: u8,
182 pub null_checks: u8,
184}
185
186impl OwnershipFeatures {
187 pub const DIMENSION: usize = 142;
191
192 pub fn builder() -> OwnershipFeaturesBuilder {
194 OwnershipFeaturesBuilder::default()
195 }
196
197 pub fn to_vector(&self) -> Vec<f32> {
199 let mut vec = Vec::with_capacity(Self::DIMENSION);
200
201 vec.push(self.pointer_depth as f32);
203 vec.push(if self.is_const { 1.0 } else { 0.0 });
204 vec.push(if self.is_array_decay { 1.0 } else { 0.0 });
205 vec.push(if self.has_size_param { 1.0 } else { 0.0 });
206
207 vec.push(self.allocation_kind_to_f32());
209 vec.push(self.deallocation_count as f32);
210 vec.push(self.alias_count as f32);
211 vec.push(if self.escape_scope { 1.0 } else { 0.0 });
212
213 vec.push(self.read_count as f32);
215 vec.push(self.write_count as f32);
216 vec.push(self.arithmetic_ops as f32);
217 vec.push(self.null_checks as f32);
218
219 vec.resize(Self::DIMENSION, 0.0);
221
222 vec
223 }
224
225 fn allocation_kind_to_f32(&self) -> f32 {
226 match self.allocation_site {
227 AllocationKind::Malloc => 1.0,
228 AllocationKind::Calloc => 2.0,
229 AllocationKind::Realloc => 3.0,
230 AllocationKind::Stack => 4.0,
231 AllocationKind::Static => 5.0,
232 AllocationKind::Parameter => 6.0,
233 AllocationKind::Unknown => 0.0,
234 }
235 }
236}
237
238#[derive(Debug, Default)]
240pub struct OwnershipFeaturesBuilder {
241 features: OwnershipFeatures,
242}
243
244impl OwnershipFeaturesBuilder {
245 pub fn pointer_depth(mut self, depth: u8) -> Self {
247 self.features.pointer_depth = depth;
248 self
249 }
250
251 pub fn const_qualified(mut self, is_const: bool) -> Self {
253 self.features.is_const = is_const;
254 self
255 }
256
257 pub fn array_decay(mut self, is_decay: bool) -> Self {
259 self.features.is_array_decay = is_decay;
260 self
261 }
262
263 pub fn has_size_param(mut self, has_size: bool) -> Self {
265 self.features.has_size_param = has_size;
266 self
267 }
268
269 pub fn allocation_site(mut self, kind: AllocationKind) -> Self {
271 self.features.allocation_site = kind;
272 self
273 }
274
275 pub fn deallocation_count(mut self, count: u8) -> Self {
277 self.features.deallocation_count = count;
278 self
279 }
280
281 pub fn alias_count(mut self, count: u8) -> Self {
283 self.features.alias_count = count;
284 self
285 }
286
287 pub fn escape_scope(mut self, escapes: bool) -> Self {
289 self.features.escape_scope = escapes;
290 self
291 }
292
293 pub fn read_count(mut self, count: u32) -> Self {
295 self.features.read_count = count;
296 self
297 }
298
299 pub fn write_count(mut self, count: u32) -> Self {
301 self.features.write_count = count;
302 self
303 }
304
305 pub fn arithmetic_ops(mut self, count: u8) -> Self {
307 self.features.arithmetic_ops = count;
308 self
309 }
310
311 pub fn null_checks(mut self, count: u8) -> Self {
313 self.features.null_checks = count;
314 self
315 }
316
317 pub fn build(self) -> OwnershipFeatures {
319 self.features
320 }
321}
322
323#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
329pub enum InferredOwnership {
330 Owned,
332 Borrowed,
334 BorrowedMut,
336 Shared,
338 RawPointer,
340 Vec,
342 Slice,
344 SliceMut,
346}
347
348impl InferredOwnership {
349 pub fn to_rust_type(self, inner_type: &str) -> String {
351 match self {
352 Self::Owned => format!("Box<{}>", inner_type),
353 Self::Borrowed => format!("&{}", inner_type),
354 Self::BorrowedMut => format!("&mut {}", inner_type),
355 Self::Shared => format!("Rc<{}>", inner_type),
356 Self::RawPointer => format!("*const {}", inner_type),
357 Self::Vec => format!("Vec<{}>", inner_type),
358 Self::Slice => format!("&[{}]", inner_type),
359 Self::SliceMut => format!("&mut [{}]", inner_type),
360 }
361 }
362
363 pub fn requires_unsafe(&self) -> bool {
365 matches!(self, Self::RawPointer)
366 }
367}
368
369#[derive(Debug, Clone, Serialize, Deserialize)]
371pub struct OwnershipPrediction {
372 pub kind: InferredOwnership,
374 pub confidence: f32,
376 pub fallback: Option<InferredOwnership>,
378}
379
380impl OwnershipPrediction {
381 pub const CONFIDENCE_THRESHOLD: f32 = 0.65;
383
384 pub fn is_confident(&self) -> bool {
386 self.confidence >= Self::CONFIDENCE_THRESHOLD
387 }
388
389 pub fn effective_ownership(&self) -> InferredOwnership {
391 if self.is_confident() {
392 self.kind
393 } else {
394 self.fallback.unwrap_or(InferredOwnership::RawPointer)
395 }
396 }
397}
398
399impl PartialEq for OwnershipPrediction {
400 fn eq(&self, other: &Self) -> bool {
401 self.kind == other.kind && (self.confidence - other.confidence).abs() < f32::EPSILON
402 }
403}
404
405use decy_hir::{HirExpression, HirFunction, HirStatement, HirType};
410
411#[derive(Debug, Default)]
416pub struct FeatureExtractor {
417 extracted_count: u64,
419}
420
421impl FeatureExtractor {
422 pub fn new() -> Self {
424 Self { extracted_count: 0 }
425 }
426
427 pub fn extracted_count(&self) -> u64 {
429 self.extracted_count
430 }
431
432 pub fn extract_for_parameter(
434 &self,
435 func: &HirFunction,
436 param_name: &str,
437 ) -> Option<OwnershipFeatures> {
438 let param = func.parameters().iter().find(|p| p.name() == param_name)?;
440 let param_type = param.param_type();
441
442 if !self.is_pointer_like(param_type) {
444 return None;
445 }
446
447 let param_index = func
448 .parameters()
449 .iter()
450 .position(|p| p.name() == param_name)?;
451
452 Some(self.extract_features(func, param_name, param_type, param_index, true))
453 }
454
455 pub fn extract_for_variable(
457 &self,
458 func: &HirFunction,
459 var_name: &str,
460 ) -> Option<OwnershipFeatures> {
461 for stmt in func.body() {
463 if let HirStatement::VariableDeclaration {
464 name,
465 var_type,
466 initializer,
467 } = stmt
468 {
469 if name == var_name && self.is_pointer_like(var_type) {
470 let allocation = self.classify_allocation(initializer.as_ref());
471 let mut features = self.extract_features(func, var_name, var_type, 0, false);
472 features.allocation_site = allocation;
473 return Some(features);
474 }
475 }
476 }
477 None
478 }
479
480 pub fn extract_all(&self, func: &HirFunction) -> Vec<(String, OwnershipFeatures)> {
482 let mut result = Vec::new();
483
484 for (idx, param) in func.parameters().iter().enumerate() {
485 if self.is_pointer_like(param.param_type()) {
486 let features =
487 self.extract_features(func, param.name(), param.param_type(), idx, true);
488 result.push((param.name().to_string(), features));
489 }
490 }
491
492 result
493 }
494
495 fn is_pointer_like(&self, ty: &HirType) -> bool {
497 matches!(
498 ty,
499 HirType::Pointer(_) | HirType::Box(_) | HirType::Reference { .. } | HirType::Vec(_)
500 )
501 }
502
503 fn extract_features(
505 &self,
506 func: &HirFunction,
507 var_name: &str,
508 var_type: &HirType,
509 param_index: usize,
510 is_param: bool,
511 ) -> OwnershipFeatures {
512 let (reads, writes) = self.count_accesses(func.body(), var_name);
513
514 OwnershipFeatures {
515 pointer_depth: self.compute_pointer_depth(var_type),
517 is_const: self.is_const_type(var_type),
518 is_array_decay: self.detect_array_decay(func, param_index),
519 has_size_param: self.has_size_parameter(func, param_index),
520 allocation_site: if is_param {
522 AllocationKind::Parameter
523 } else {
524 AllocationKind::Unknown
525 },
526 deallocation_count: self.count_deallocations(func.body(), var_name),
527 alias_count: self.count_aliases(func.body(), var_name),
528 escape_scope: self.check_escape(func, var_name),
529 read_count: reads,
531 write_count: writes,
532 arithmetic_ops: self.count_pointer_arithmetic(func.body(), var_name),
533 null_checks: self.count_null_checks(func.body(), var_name),
534 }
535 }
536
537 fn compute_pointer_depth(&self, ty: &HirType) -> u8 {
539 match ty {
540 HirType::Pointer(inner) => 1 + self.compute_pointer_depth(inner),
541 HirType::Box(inner) => 1 + self.compute_pointer_depth(inner),
542 HirType::Reference { inner, .. } => 1 + self.compute_pointer_depth(inner),
543 _ => 0,
544 }
545 }
546
547 fn is_const_type(&self, ty: &HirType) -> bool {
549 match ty {
550 HirType::Reference { mutable, .. } => !mutable,
551 _ => false,
552 }
553 }
554
555 fn detect_array_decay(&self, func: &HirFunction, param_index: usize) -> bool {
557 let params = func.parameters();
558 if param_index + 1 >= params.len() {
559 return false;
560 }
561
562 let current = ¶ms[param_index];
564 let next = ¶ms[param_index + 1];
565
566 if !matches!(current.param_type(), HirType::Pointer(_)) {
567 return false;
568 }
569
570 if !matches!(next.param_type(), HirType::Int | HirType::UnsignedInt) {
571 return false;
572 }
573
574 let next_name = next.name().to_lowercase();
576 next_name.contains("len")
577 || next_name.contains("size")
578 || next_name.contains("count")
579 || next_name.contains("num")
580 || next_name == "n"
581 }
582
583 fn has_size_parameter(&self, func: &HirFunction, param_index: usize) -> bool {
585 self.detect_array_decay(func, param_index)
586 }
587
588 fn classify_allocation(&self, initializer: Option<&HirExpression>) -> AllocationKind {
590 match initializer {
591 Some(HirExpression::Malloc { .. }) => AllocationKind::Malloc,
592 Some(HirExpression::Calloc { .. }) => AllocationKind::Calloc,
593 Some(HirExpression::Realloc { .. }) => AllocationKind::Realloc,
594 Some(HirExpression::FunctionCall { function, .. }) if function == "malloc" => {
595 AllocationKind::Malloc
596 }
597 Some(HirExpression::FunctionCall { function, .. }) if function == "calloc" => {
598 AllocationKind::Calloc
599 }
600 Some(HirExpression::FunctionCall { function, .. }) if function == "realloc" => {
601 AllocationKind::Realloc
602 }
603 _ => AllocationKind::Unknown,
604 }
605 }
606
607 fn count_deallocations(&self, body: &[HirStatement], var_name: &str) -> u8 {
609 let mut count = 0u8;
610 for stmt in body {
611 count = count.saturating_add(self.count_free_in_stmt(stmt, var_name));
612 }
613 count
614 }
615
616 fn count_free_in_stmt(&self, stmt: &HirStatement, var_name: &str) -> u8 {
617 match stmt {
618 HirStatement::Free { pointer } => {
619 if self.expr_uses_var(pointer, var_name) {
620 1
621 } else {
622 0
623 }
624 }
625 HirStatement::If {
626 then_block,
627 else_block,
628 ..
629 } => {
630 let mut count = 0u8;
631 for s in then_block {
632 count = count.saturating_add(self.count_free_in_stmt(s, var_name));
633 }
634 if let Some(else_stmts) = else_block {
635 for s in else_stmts {
636 count = count.saturating_add(self.count_free_in_stmt(s, var_name));
637 }
638 }
639 count
640 }
641 HirStatement::While { body, .. } | HirStatement::For { body, .. } => {
642 let mut count = 0u8;
643 for s in body {
644 count = count.saturating_add(self.count_free_in_stmt(s, var_name));
645 }
646 count
647 }
648 _ => 0,
649 }
650 }
651
652 fn count_aliases(&self, _body: &[HirStatement], _var_name: &str) -> u8 {
654 0
656 }
657
658 fn check_escape(&self, func: &HirFunction, var_name: &str) -> bool {
660 for stmt in func.body() {
661 if let HirStatement::Return(Some(expr)) = stmt {
662 if self.expr_uses_var(expr, var_name) {
663 return true;
664 }
665 }
666 }
667 false
668 }
669
670 fn count_accesses(&self, body: &[HirStatement], var_name: &str) -> (u32, u32) {
672 let mut reads = 0u32;
673 let mut writes = 0u32;
674
675 for stmt in body {
676 let (r, w) = self.count_stmt_accesses(stmt, var_name);
677 reads = reads.saturating_add(r);
678 writes = writes.saturating_add(w);
679 }
680
681 (reads, writes)
682 }
683
684 fn count_stmt_accesses(&self, stmt: &HirStatement, var_name: &str) -> (u32, u32) {
685 match stmt {
686 HirStatement::Assignment { target, value } => {
687 let mut reads: u32 = if self.expr_uses_var(value, var_name) {
688 1
689 } else {
690 0
691 };
692 let writes: u32 = if target == var_name { 1 } else { 0 };
693 if target != var_name && self.expr_uses_var(value, var_name) {
695 reads += 1;
696 }
697 (reads, writes)
698 }
699 HirStatement::DerefAssignment { target, value } => {
700 let reads: u32 = if self.expr_uses_var(value, var_name)
701 || self.expr_uses_var(target, var_name)
702 {
703 1
704 } else {
705 0
706 };
707 let writes: u32 = if self.expr_uses_var(target, var_name) {
708 1
709 } else {
710 0
711 };
712 (reads, writes)
713 }
714 HirStatement::If {
715 condition,
716 then_block,
717 else_block,
718 } => {
719 let mut reads: u32 = if self.expr_uses_var(condition, var_name) {
720 1
721 } else {
722 0
723 };
724 let mut writes: u32 = 0;
725 for s in then_block {
726 let (r, w) = self.count_stmt_accesses(s, var_name);
727 reads = reads.saturating_add(r);
728 writes = writes.saturating_add(w);
729 }
730 if let Some(else_stmts) = else_block {
731 for s in else_stmts {
732 let (r, w) = self.count_stmt_accesses(s, var_name);
733 reads = reads.saturating_add(r);
734 writes = writes.saturating_add(w);
735 }
736 }
737 (reads, writes)
738 }
739 _ => (0, 0),
740 }
741 }
742
743 fn count_pointer_arithmetic(&self, _body: &[HirStatement], _var_name: &str) -> u8 {
745 0
747 }
748
749 fn count_null_checks(&self, body: &[HirStatement], var_name: &str) -> u8 {
751 let mut count = 0u8;
752 for stmt in body {
753 count = count.saturating_add(self.count_null_checks_in_stmt(stmt, var_name));
754 }
755 count
756 }
757
758 fn count_null_checks_in_stmt(&self, stmt: &HirStatement, var_name: &str) -> u8 {
759 match stmt {
760 HirStatement::If {
761 condition,
762 then_block,
763 else_block,
764 } => {
765 let mut count: u8 = if self.is_null_check(condition, var_name) {
766 1
767 } else {
768 0
769 };
770 for s in then_block {
771 count = count.saturating_add(self.count_null_checks_in_stmt(s, var_name));
772 }
773 if let Some(else_stmts) = else_block {
774 for s in else_stmts {
775 count = count.saturating_add(self.count_null_checks_in_stmt(s, var_name));
776 }
777 }
778 count
779 }
780 HirStatement::While { condition, body } => {
781 let mut count: u8 = if self.is_null_check(condition, var_name) {
782 1
783 } else {
784 0
785 };
786 for s in body {
787 count = count.saturating_add(self.count_null_checks_in_stmt(s, var_name));
788 }
789 count
790 }
791 _ => 0,
792 }
793 }
794
795 fn is_null_check(&self, expr: &HirExpression, var_name: &str) -> bool {
797 match expr {
798 HirExpression::IsNotNull(inner) => self.expr_uses_var(inner, var_name),
799 HirExpression::BinaryOp { left, right, .. } => {
800 (self.expr_uses_var(left, var_name)
802 && matches!(**right, HirExpression::NullLiteral))
803 || (self.expr_uses_var(right, var_name)
804 && matches!(**left, HirExpression::NullLiteral))
805 }
806 _ => false,
807 }
808 }
809
810 fn expr_uses_var(&self, expr: &HirExpression, var_name: &str) -> bool {
812 match expr {
813 HirExpression::Variable(name) => name == var_name,
814 HirExpression::Dereference(inner) => self.expr_uses_var(inner, var_name),
815 HirExpression::AddressOf(inner) => self.expr_uses_var(inner, var_name),
816 HirExpression::BinaryOp { left, right, .. } => {
817 self.expr_uses_var(left, var_name) || self.expr_uses_var(right, var_name)
818 }
819 HirExpression::UnaryOp { operand, .. } => self.expr_uses_var(operand, var_name),
820 HirExpression::ArrayIndex { array, index } => {
821 self.expr_uses_var(array, var_name) || self.expr_uses_var(index, var_name)
822 }
823 HirExpression::FunctionCall { arguments, .. } => arguments
824 .iter()
825 .any(|arg| self.expr_uses_var(arg, var_name)),
826 HirExpression::IsNotNull(inner) => self.expr_uses_var(inner, var_name),
827 _ => false,
828 }
829 }
830}
831
832#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
840pub enum OwnershipErrorKind {
841 PointerMisclassification,
843 LifetimeInferenceGap,
845 DanglingPointerRisk,
847 AliasViolation,
849 UnsafeMinimizationFailure,
851 ArraySliceMismatch,
853 ResourceLeakPattern,
855 MutabilityMismatch,
857}
858
859impl OwnershipErrorKind {
860 pub fn to_defect(self) -> OwnershipDefect {
862 match self {
863 Self::PointerMisclassification => OwnershipDefect::PointerMisclassification,
864 Self::LifetimeInferenceGap => OwnershipDefect::LifetimeInferenceGap,
865 Self::DanglingPointerRisk => OwnershipDefect::DanglingPointerRisk,
866 Self::AliasViolation => OwnershipDefect::AliasViolation,
867 Self::UnsafeMinimizationFailure => OwnershipDefect::UnsafeMinimizationFailure,
868 Self::ArraySliceMismatch => OwnershipDefect::ArraySliceMismatch,
869 Self::ResourceLeakPattern => OwnershipDefect::ResourceLeakPattern,
870 Self::MutabilityMismatch => OwnershipDefect::MutabilityMismatch,
871 }
872 }
873}
874
875#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Default)]
877pub enum ErrorSeverity {
878 #[default]
880 Info,
881 Warning,
883 Error,
885 Critical,
887}
888
889#[derive(Debug, Clone, Serialize, Deserialize)]
891pub struct SuggestedFix {
892 description: String,
894 code_template: String,
896 confidence: f32,
898}
899
900impl SuggestedFix {
901 pub fn new(description: impl Into<String>, code_template: impl Into<String>) -> Self {
903 Self {
904 description: description.into(),
905 code_template: code_template.into(),
906 confidence: 0.5,
907 }
908 }
909
910 pub fn with_confidence(mut self, confidence: f32) -> Self {
912 self.confidence = confidence;
913 self
914 }
915
916 pub fn description(&self) -> &str {
918 &self.description
919 }
920
921 pub fn code_template(&self) -> &str {
923 &self.code_template
924 }
925
926 pub fn confidence(&self) -> f32 {
928 self.confidence
929 }
930}
931
932#[derive(Debug, Clone, Serialize, Deserialize)]
937pub struct ErrorPattern {
938 id: String,
940 error_kind: OwnershipErrorKind,
942 description: String,
944 c_pattern: Option<String>,
946 rust_error: Option<String>,
948 suggested_fix: Option<SuggestedFix>,
950 severity: ErrorSeverity,
952 curriculum_level: u8,
954 occurrence_count: u64,
956}
957
958impl ErrorPattern {
959 pub fn new(
961 id: impl Into<String>,
962 error_kind: OwnershipErrorKind,
963 description: impl Into<String>,
964 ) -> Self {
965 Self {
966 id: id.into(),
967 error_kind,
968 description: description.into(),
969 c_pattern: None,
970 rust_error: None,
971 suggested_fix: None,
972 severity: ErrorSeverity::Error,
973 curriculum_level: 1,
974 occurrence_count: 0,
975 }
976 }
977
978 pub fn with_c_pattern(mut self, pattern: impl Into<String>) -> Self {
980 self.c_pattern = Some(pattern.into());
981 self
982 }
983
984 pub fn with_rust_error(mut self, error: impl Into<String>) -> Self {
986 self.rust_error = Some(error.into());
987 self
988 }
989
990 pub fn with_fix(mut self, fix: SuggestedFix) -> Self {
992 self.suggested_fix = Some(fix);
993 self
994 }
995
996 pub fn with_severity(mut self, severity: ErrorSeverity) -> Self {
998 self.severity = severity;
999 self
1000 }
1001
1002 pub fn with_curriculum_level(mut self, level: u8) -> Self {
1004 self.curriculum_level = level;
1005 self
1006 }
1007
1008 pub fn id(&self) -> &str {
1010 &self.id
1011 }
1012
1013 pub fn error_kind(&self) -> OwnershipErrorKind {
1015 self.error_kind
1016 }
1017
1018 pub fn description(&self) -> &str {
1020 &self.description
1021 }
1022
1023 pub fn c_pattern(&self) -> Option<&str> {
1025 self.c_pattern.as_deref()
1026 }
1027
1028 pub fn rust_error(&self) -> Option<&str> {
1030 self.rust_error.as_deref()
1031 }
1032
1033 pub fn suggested_fix(&self) -> Option<&SuggestedFix> {
1035 self.suggested_fix.as_ref()
1036 }
1037
1038 pub fn severity(&self) -> ErrorSeverity {
1040 self.severity
1041 }
1042
1043 pub fn curriculum_level(&self) -> u8 {
1045 self.curriculum_level
1046 }
1047
1048 pub fn record_occurrence(&mut self) {
1050 self.occurrence_count = self.occurrence_count.saturating_add(1);
1051 }
1052
1053 pub fn occurrence_count(&self) -> u64 {
1055 self.occurrence_count
1056 }
1057}
1058
1059#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1063pub struct PatternLibrary {
1064 patterns: std::collections::HashMap<String, ErrorPattern>,
1066}
1067
1068impl PatternLibrary {
1069 pub fn new() -> Self {
1071 Self {
1072 patterns: std::collections::HashMap::new(),
1073 }
1074 }
1075
1076 pub fn add(&mut self, pattern: ErrorPattern) {
1078 self.patterns.insert(pattern.id.clone(), pattern);
1079 }
1080
1081 pub fn get(&self, id: &str) -> Option<&ErrorPattern> {
1083 self.patterns.get(id)
1084 }
1085
1086 pub fn get_mut(&mut self, id: &str) -> Option<&mut ErrorPattern> {
1088 self.patterns.get_mut(id)
1089 }
1090
1091 pub fn get_by_error_kind(&self, kind: OwnershipErrorKind) -> Vec<&ErrorPattern> {
1093 self.patterns
1094 .values()
1095 .filter(|p| p.error_kind == kind)
1096 .collect()
1097 }
1098
1099 pub fn curriculum_ordered(&self) -> Vec<&ErrorPattern> {
1101 let mut patterns: Vec<_> = self.patterns.values().collect();
1102 patterns.sort_by_key(|p| p.curriculum_level);
1103 patterns
1104 }
1105
1106 pub fn match_rust_error(&self, error_msg: &str) -> Vec<&ErrorPattern> {
1108 self.patterns
1109 .values()
1110 .filter(|p| p.rust_error.as_ref().is_some_and(|e| error_msg.contains(e)))
1111 .collect()
1112 }
1113
1114 pub fn len(&self) -> usize {
1116 self.patterns.len()
1117 }
1118
1119 pub fn is_empty(&self) -> bool {
1121 self.patterns.is_empty()
1122 }
1123
1124 pub fn iter(&self) -> impl Iterator<Item = &ErrorPattern> {
1126 self.patterns.values()
1127 }
1128}
1129
1130pub fn default_pattern_library() -> PatternLibrary {
1154 let mut library = PatternLibrary::new();
1155
1156 library.add(
1162 ErrorPattern::new(
1163 "malloc-to-box",
1164 OwnershipErrorKind::PointerMisclassification,
1165 "Single allocation with malloc should use Box<T>",
1166 )
1167 .with_severity(ErrorSeverity::Warning)
1168 .with_curriculum_level(1)
1169 .with_c_pattern("void *ptr = malloc(sizeof(T))")
1170 .with_rust_error("E0308")
1171 .with_fix(SuggestedFix::new(
1172 "Replace raw pointer with Box",
1173 "let ptr: Box<T> = Box::new(T::default());",
1174 )),
1175 );
1176
1177 library.add(
1179 ErrorPattern::new(
1180 "array-to-vec",
1181 OwnershipErrorKind::PointerMisclassification,
1182 "Dynamic array allocation should use Vec<T>",
1183 )
1184 .with_severity(ErrorSeverity::Warning)
1185 .with_curriculum_level(1)
1186 .with_c_pattern("T *arr = malloc(n * sizeof(T))")
1187 .with_rust_error("E0308")
1188 .with_fix(SuggestedFix::new(
1189 "Replace pointer array with Vec",
1190 "let arr: Vec<T> = Vec::with_capacity(n);",
1191 )),
1192 );
1193
1194 library.add(
1196 ErrorPattern::new(
1197 "const-to-immut-ref",
1198 OwnershipErrorKind::MutabilityMismatch,
1199 "Const pointer should be immutable reference",
1200 )
1201 .with_severity(ErrorSeverity::Warning)
1202 .with_curriculum_level(1)
1203 .with_c_pattern("const T *ptr")
1204 .with_rust_error("E0596")
1205 .with_fix(SuggestedFix::new(
1206 "Use immutable reference",
1207 "fn foo(ptr: &T)",
1208 )),
1209 );
1210
1211 library.add(
1217 ErrorPattern::new(
1218 "missing-lifetime",
1219 OwnershipErrorKind::LifetimeInferenceGap,
1220 "Function returning reference needs lifetime annotation",
1221 )
1222 .with_severity(ErrorSeverity::Error)
1223 .with_curriculum_level(2)
1224 .with_c_pattern("T* get_field(Struct *s) { return &s->field; }")
1225 .with_rust_error("E0106")
1226 .with_fix(SuggestedFix::new(
1227 "Add lifetime parameter",
1228 "fn get_field<'a>(s: &'a Struct) -> &'a T",
1229 )),
1230 );
1231
1232 library.add(
1234 ErrorPattern::new(
1235 "struct-lifetime",
1236 OwnershipErrorKind::LifetimeInferenceGap,
1237 "Struct containing reference needs lifetime parameter",
1238 )
1239 .with_severity(ErrorSeverity::Error)
1240 .with_curriculum_level(2)
1241 .with_c_pattern("struct View { T *data; }")
1242 .with_rust_error("E0106")
1243 .with_fix(SuggestedFix::new(
1244 "Add lifetime to struct",
1245 "struct View<'a> { data: &'a T }",
1246 )),
1247 );
1248
1249 library.add(
1251 ErrorPattern::new(
1252 "array-param-to-slice",
1253 OwnershipErrorKind::ArraySliceMismatch,
1254 "Array parameter should be slice reference",
1255 )
1256 .with_severity(ErrorSeverity::Warning)
1257 .with_curriculum_level(2)
1258 .with_c_pattern("void process(int arr[], size_t len)")
1259 .with_rust_error("E0308")
1260 .with_fix(SuggestedFix::new(
1261 "Use slice parameter",
1262 "fn process(arr: &[i32])",
1263 )),
1264 );
1265
1266 library.add(
1272 ErrorPattern::new(
1273 "mutable-aliasing",
1274 OwnershipErrorKind::AliasViolation,
1275 "Cannot have multiple mutable references",
1276 )
1277 .with_severity(ErrorSeverity::Error)
1278 .with_curriculum_level(3)
1279 .with_c_pattern("T *a = ptr; T *b = ptr; *a = x; *b = y;")
1280 .with_rust_error("E0499")
1281 .with_fix(SuggestedFix::new(
1282 "Use single mutable reference or split borrows",
1283 "// Ensure only one &mut exists at a time",
1284 )),
1285 );
1286
1287 library.add(
1289 ErrorPattern::new(
1290 "immut-mut-aliasing",
1291 OwnershipErrorKind::AliasViolation,
1292 "Cannot have mutable reference while immutable exists",
1293 )
1294 .with_severity(ErrorSeverity::Error)
1295 .with_curriculum_level(3)
1296 .with_c_pattern("const T *r = ptr; *ptr = x; use(r);")
1297 .with_rust_error("E0502")
1298 .with_fix(SuggestedFix::new(
1299 "End immutable borrow before mutating",
1300 "let r = &*ptr; use(r); *ptr = x;",
1301 )),
1302 );
1303
1304 library.add(
1306 ErrorPattern::new(
1307 "use-after-free",
1308 OwnershipErrorKind::DanglingPointerRisk,
1309 "Use of pointer after free causes undefined behavior",
1310 )
1311 .with_severity(ErrorSeverity::Critical)
1312 .with_curriculum_level(3)
1313 .with_c_pattern("free(ptr); use(ptr);")
1314 .with_rust_error("E0382")
1315 .with_fix(SuggestedFix::new(
1316 "Use Option<Box<T>> and take() to consume",
1317 "let val = box_opt.take(); // Consumes ownership",
1318 )),
1319 );
1320
1321 library.add(
1323 ErrorPattern::new(
1324 "return-local-ref",
1325 OwnershipErrorKind::DanglingPointerRisk,
1326 "Returning pointer to local variable is undefined behavior",
1327 )
1328 .with_severity(ErrorSeverity::Critical)
1329 .with_curriculum_level(3)
1330 .with_c_pattern("int* foo() { int x = 1; return &x; }")
1331 .with_rust_error("E0515")
1332 .with_fix(SuggestedFix::new(
1333 "Return owned value or use parameter lifetime",
1334 "fn foo() -> i32 { 1 } // Return by value",
1335 )),
1336 );
1337
1338 library.add(
1344 ErrorPattern::new(
1345 "missing-free",
1346 OwnershipErrorKind::ResourceLeakPattern,
1347 "Allocated memory not freed causes leak",
1348 )
1349 .with_severity(ErrorSeverity::Warning)
1350 .with_curriculum_level(4)
1351 .with_c_pattern("void* p = malloc(...); return; // leak!")
1352 .with_fix(SuggestedFix::new(
1353 "Use RAII with Box/Vec for automatic cleanup",
1354 "let p = Box::new(...); // Automatically freed",
1355 )),
1356 );
1357
1358 library.add(
1360 ErrorPattern::new(
1361 "file-handle-leak",
1362 OwnershipErrorKind::ResourceLeakPattern,
1363 "File handle not closed causes resource leak",
1364 )
1365 .with_severity(ErrorSeverity::Warning)
1366 .with_curriculum_level(4)
1367 .with_c_pattern("FILE *f = fopen(...); return; // leak!")
1368 .with_fix(SuggestedFix::new(
1369 "Use File type with automatic Drop",
1370 "let f = File::open(...)?; // Closed on drop",
1371 )),
1372 );
1373
1374 library.add(
1376 ErrorPattern::new(
1377 "unnecessary-unsafe",
1378 OwnershipErrorKind::UnsafeMinimizationFailure,
1379 "Safe alternative exists for this unsafe operation",
1380 )
1381 .with_severity(ErrorSeverity::Warning)
1382 .with_curriculum_level(4)
1383 .with_c_pattern("*(ptr + i) = value; // pointer arithmetic")
1384 .with_fix(SuggestedFix::new(
1385 "Use safe slice indexing",
1386 "slice[i] = value;",
1387 )),
1388 );
1389
1390 library.add(
1392 ErrorPattern::new(
1393 "null-check-to-option",
1394 OwnershipErrorKind::UnsafeMinimizationFailure,
1395 "Null pointer check should use Option<T>",
1396 )
1397 .with_severity(ErrorSeverity::Warning)
1398 .with_curriculum_level(4)
1399 .with_c_pattern("if (ptr != NULL) { use(ptr); }")
1400 .with_fix(SuggestedFix::new(
1401 "Use Option<T> with if let or match",
1402 "if let Some(val) = opt { use(val); }",
1403 )),
1404 );
1405
1406 library.add(
1412 ErrorPattern::new(
1413 "self-referential-struct",
1414 OwnershipErrorKind::AliasViolation,
1415 "Self-referential struct needs Pin or unsafe",
1416 )
1417 .with_severity(ErrorSeverity::Error)
1418 .with_curriculum_level(5)
1419 .with_c_pattern("struct Node { struct Node *next; int data; }")
1420 .with_rust_error("E0597")
1421 .with_fix(SuggestedFix::new(
1422 "Use Box for indirection or Pin for self-reference",
1423 "struct Node { next: Option<Box<Node>>, data: i32 }",
1424 )),
1425 );
1426
1427 library.add(
1429 ErrorPattern::new(
1430 "multiple-lifetimes",
1431 OwnershipErrorKind::LifetimeInferenceGap,
1432 "Function with multiple reference params needs explicit lifetimes",
1433 )
1434 .with_severity(ErrorSeverity::Error)
1435 .with_curriculum_level(5)
1436 .with_c_pattern("T* pick(T *a, T *b, int cond)")
1437 .with_rust_error("E0106")
1438 .with_fix(SuggestedFix::new(
1439 "Add explicit lifetime bounds",
1440 "fn pick<'a>(a: &'a T, b: &'a T, cond: bool) -> &'a T",
1441 )),
1442 );
1443
1444 library.add(
1446 ErrorPattern::new(
1447 "interior-mutability",
1448 OwnershipErrorKind::MutabilityMismatch,
1449 "Mutation through shared reference needs Cell/RefCell",
1450 )
1451 .with_severity(ErrorSeverity::Warning)
1452 .with_curriculum_level(5)
1453 .with_c_pattern("void inc(Counter *c) { c->count++; } // called via const ptr")
1454 .with_rust_error("E0596")
1455 .with_fix(SuggestedFix::new(
1456 "Use Cell<T> or RefCell<T> for interior mutability",
1457 "struct Counter { count: Cell<i32> }",
1458 )),
1459 );
1460
1461 library
1462}
1463
1464#[cfg(test)]
1465#[path = "ml_features_coverage_tests.rs"]
1466mod ml_features_coverage_tests;