1use std::path::Path;
2use std::fs::File;
3use std::io::{BufRead, BufReader};
4use clang::Entity;
5use crate::debug_println;
6
7fn contains_annotation(text: &str, annotation: &str) -> bool {
12 if !text.starts_with(annotation) {
14 return false;
15 }
16
17 let after_annotation = annotation.len();
19 if after_annotation >= text.len() {
20 return true;
22 }
23
24 let next_char = text.chars().nth(after_annotation);
27 if let Some(ch) = next_char {
28 !ch.is_alphanumeric()
29 } else {
30 true
31 }
32}
33
34#[derive(Debug, Clone, Copy, PartialEq)]
35pub enum SafetyMode {
36 Safe, Unsafe, }
39
40#[derive(Debug, Clone, Copy, PartialEq)]
42pub enum ClassAnnotation {
43 Interface, Safe, Unsafe, }
47
48#[derive(Debug, Clone, PartialEq, Eq, Hash)]
50pub struct FunctionSignature {
51 pub name: String,
52 pub param_types: Option<Vec<String>>, }
54
55impl FunctionSignature {
56 fn new(name: String, param_types: Option<Vec<String>>) -> Self {
57 Self { name, param_types }
58 }
59
60 fn from_name_only(name: String) -> Self {
61 Self { name, param_types: None }
62 }
63
64 fn matches(&self, other: &FunctionSignature) -> bool {
66 if self.name != other.name {
68 return false;
69 }
70
71 match (&self.param_types, &other.param_types) {
73 (None, _) | (_, None) => true,
74 (Some(a), Some(b)) => a == b,
75 }
76 }
77}
78
79#[derive(Debug, Clone)]
80pub struct SafetyContext {
81 pub file_default: SafetyMode,
82 pub function_overrides: Vec<(FunctionSignature, SafetyMode)>, pub source_file: Option<String>, }
85
86
87impl SafetyContext {
88 pub fn new() -> Self {
89 Self {
90 file_default: SafetyMode::Unsafe,
91 function_overrides: Vec::new(),
92 source_file: None,
93 }
94 }
95
96 pub fn merge_header_annotations(&mut self, header_cache: &super::header_cache::HeaderCache) {
98 for (func_name, &safety_mode) in header_cache.safety_annotations.iter() {
101 let already_has_override = self.function_overrides.iter()
104 .any(|(sig, _)| {
105 sig.name == *func_name ||
106 sig.name.ends_with(&format!("::{}", func_name)) ||
107 func_name.ends_with(&format!("::{}", sig.name))
108 });
109
110 if !already_has_override {
111 debug_println!("DEBUG SAFETY: Adding header annotation for '{}': {:?}", func_name, safety_mode);
113 let signature = FunctionSignature::from_name_only(func_name.clone());
114 self.function_overrides.push((signature, safety_mode));
115 } else {
116 debug_println!("DEBUG SAFETY: Function '{}' already has annotation, keeping source file version", func_name);
117 }
118 }
120 }
121
122 pub fn should_check_function(&self, func_name: &str) -> bool {
124 self.get_function_safety(func_name) == SafetyMode::Safe
125 }
126
127 pub fn is_from_source_file(&self, file_path: &str) -> bool {
130 if let Some(ref source) = self.source_file {
131 file_path == source ||
134 file_path.ends_with(source) ||
135 source.ends_with(file_path) ||
136 std::path::Path::new(file_path).file_name() == std::path::Path::new(source).file_name()
138 } else {
139 true
141 }
142 }
143
144 pub fn get_function_safety(&self, func_name: &str) -> SafetyMode {
146 let query = FunctionSignature::from_name_only(func_name.to_string());
147
148 for (sig, mode) in &self.function_overrides {
150 if sig.matches(&query) {
151 return *mode;
152 }
153
154 let sig_is_qualified = sig.name.contains("::");
155 let func_is_qualified = func_name.contains("::");
156
157 if sig_is_qualified && func_is_qualified {
169 if sig.name.ends_with(&format!("::{}", func_name)) || func_name.ends_with(&format!("::{}", sig.name)) {
171 return *mode;
172 }
173 }
174 }
177
178 if func_name.contains("::") {
181 if let Some(last_colon) = func_name.rfind("::") {
183 let class_name = &func_name[..last_colon];
184
185 let class_query = FunctionSignature::from_name_only(class_name.to_string());
187 for (sig, mode) in &self.function_overrides {
188 if sig.matches(&class_query) {
189 return *mode;
190 }
191
192 let sig_is_qualified = sig.name.contains("::");
194 let class_is_qualified = class_name.contains("::");
195
196 if sig_is_qualified && class_is_qualified {
199 if sig.name.ends_with(&format!("::{}", class_name)) || class_name.ends_with(&format!("::{}", sig.name)) {
200 return *mode;
201 }
202 }
203 }
204 }
205 }
206
207 self.file_default
209 }
210
211 pub fn get_class_safety(&self, class_name: &str) -> SafetyMode {
214 let query = FunctionSignature::from_name_only(class_name.to_string());
215
216 debug_println!("DEBUG SAFETY: Looking up class '{}'", class_name);
217 debug_println!("DEBUG SAFETY: Stored overrides ({} total):", self.function_overrides.len());
218 for (sig, mode) in &self.function_overrides {
219 debug_println!("DEBUG SAFETY: - '{}' -> {:?}", sig.name, mode);
220 }
221
222 for (sig, mode) in &self.function_overrides {
224 if sig.matches(&query) {
225 debug_println!("DEBUG SAFETY: Exact match for class '{}' -> {:?}", class_name, mode);
226 return *mode;
227 }
228
229 let sig_is_qualified = sig.name.contains("::");
231 let class_is_qualified = class_name.contains("::");
232
233 if sig_is_qualified && class_is_qualified {
242 if sig.name.ends_with(&format!("::{}", class_name)) {
244 debug_println!("DEBUG SAFETY: Suffix match for class '{}' (stored as '{}') -> {:?}", class_name, sig.name, mode);
245 return *mode;
246 }
247
248 if class_name.ends_with(&format!("::{}", sig.name)) {
249 debug_println!("DEBUG SAFETY: Prefix match for class '{}' (query has more qualifiers) -> {:?}", class_name, mode);
250 return *mode;
251 }
252 }
253 }
255
256 debug_println!("DEBUG SAFETY: No match for class '{}', using file default: {:?}", class_name, self.file_default);
257 self.file_default
259 }
260
261 pub fn get_class_safety_for_file(&self, class_name: &str, class_file: &str) -> SafetyMode {
270 let query = FunctionSignature::from_name_only(class_name.to_string());
271
272 debug_println!("DEBUG SAFETY: Looking up class '{}' from file '{}'", class_name, class_file);
273
274 for (sig, mode) in &self.function_overrides {
276 if sig.matches(&query) {
277 debug_println!("DEBUG SAFETY: Exact match for class '{}' -> {:?}", class_name, mode);
278 return *mode;
279 }
280
281 let sig_is_qualified = sig.name.contains("::");
282 let class_is_qualified = class_name.contains("::");
283
284 if sig_is_qualified && class_is_qualified {
285 if sig.name.ends_with(&format!("::{}", class_name)) {
286 debug_println!("DEBUG SAFETY: Suffix match for class '{}' -> {:?}", class_name, mode);
287 return *mode;
288 }
289
290 if class_name.ends_with(&format!("::{}", sig.name)) {
291 debug_println!("DEBUG SAFETY: Prefix match for class '{}' -> {:?}", class_name, mode);
292 return *mode;
293 }
294 }
295 }
296
297 if self.is_from_source_file(class_file) {
300 debug_println!("DEBUG SAFETY: Class '{}' is from source file, using file default: {:?}",
301 class_name, self.file_default);
302 self.file_default
303 } else {
304 debug_println!("DEBUG SAFETY: Class '{}' is NOT from source file '{}', treating as Undeclared",
307 class_name, class_file);
308 SafetyMode::Unsafe
309 }
310 }
311
312 pub fn get_function_safety_for_file(&self, func_name: &str, func_file: &str) -> SafetyMode {
317 let query = FunctionSignature::from_name_only(func_name.to_string());
318
319 for (sig, mode) in &self.function_overrides {
321 if sig.matches(&query) {
322 return *mode;
323 }
324
325 let sig_is_qualified = sig.name.contains("::");
326 let func_is_qualified = func_name.contains("::");
327
328 if sig_is_qualified && func_is_qualified {
329 if sig.name.ends_with(&format!("::{}", func_name)) || func_name.ends_with(&format!("::{}", sig.name)) {
330 return *mode;
331 }
332 }
333 }
334
335 if func_name.contains("::") {
337 if let Some(last_colon) = func_name.rfind("::") {
338 let class_name = &func_name[..last_colon];
339
340 let class_query = FunctionSignature::from_name_only(class_name.to_string());
341 for (sig, mode) in &self.function_overrides {
342 if sig.matches(&class_query) {
343 return *mode;
344 }
345
346 let sig_is_qualified = sig.name.contains("::");
347 let class_is_qualified = class_name.contains("::");
348
349 if sig_is_qualified && class_is_qualified {
350 if sig.name.ends_with(&format!("::{}", class_name)) || class_name.ends_with(&format!("::{}", sig.name)) {
351 return *mode;
352 }
353 }
354 }
355 }
356 }
357
358 if self.is_from_source_file(func_file) {
361 self.file_default
362 } else {
363 SafetyMode::Unsafe
364 }
365 }
366
367 pub fn should_check_function_for_file(&self, func_name: &str, func_file: &str) -> bool {
369 self.get_function_safety_for_file(func_name, func_file) == SafetyMode::Safe
370 }
371}
372
373pub fn parse_safety_annotations(path: &Path) -> Result<SafetyContext, String> {
376 let file = File::open(path)
377 .map_err(|e| format!("Failed to open file for safety parsing: {}", e))?;
378
379 let reader = BufReader::new(file);
380 let mut context = SafetyContext::new();
381
382 context.source_file = path.to_str().map(|s| s.to_string());
385
386 let mut pending_annotation: Option<SafetyMode> = None;
387 let mut in_comment_block = false;
388 let mut _current_line = 0;
389
390 let mut accumulated_line = String::new();
391 let mut accumulating_for_annotation = false;
392
393 let mut class_context_stack: Vec<String> = Vec::new();
395 let mut brace_depth = 0;
396
397 for line_result in reader.lines() {
398 _current_line += 1;
399 let line = line_result.map_err(|e| format!("Failed to read line: {}", e))?;
400 let trimmed = line.trim();
401
402 if in_comment_block {
404 if trimmed.contains("*/") {
405 in_comment_block = false;
406 }
407 let cleaned = trimmed.trim_start_matches('*').trim();
409 if contains_annotation(cleaned, "@safe") {
410 pending_annotation = Some(SafetyMode::Safe);
411 } else if contains_annotation(cleaned, "@unsafe") {
412 pending_annotation = Some(SafetyMode::Unsafe);
413 }
414 continue;
415 }
416
417 if trimmed.starts_with("/*") {
419 in_comment_block = true;
420 if let Some(end_pos) = trimmed.find("*/") {
422 let comment_content = trimmed[2..end_pos].trim();
423 if contains_annotation(comment_content, "@safe") {
424 pending_annotation = Some(SafetyMode::Safe);
425 } else if contains_annotation(comment_content, "@unsafe") {
426 pending_annotation = Some(SafetyMode::Unsafe);
427 }
428 in_comment_block = false;
429 }
430 continue;
431 }
432
433 if trimmed.starts_with("//") {
435 let comment_text = trimmed[2..].trim();
437 if contains_annotation(comment_text, "@safe") {
438 pending_annotation = Some(SafetyMode::Safe);
439 } else if contains_annotation(comment_text, "@unsafe") {
440 pending_annotation = Some(SafetyMode::Unsafe);
441 }
442 continue;
443 }
444
445 if trimmed.is_empty() || trimmed.starts_with("#") {
447 continue;
448 }
449
450 let opens = trimmed.matches('{').count() as i32;
454 let closes = trimmed.matches('}').count() as i32;
455 brace_depth += opens - closes;
456
457 if brace_depth <= 0 && !class_context_stack.is_empty() {
459 class_context_stack.pop();
460 brace_depth = 0; }
462
463 let is_class_line = is_class_declaration(trimmed);
468 let needs_class_tracking = is_class_line && pending_annotation.is_none() && !accumulating_for_annotation;
469 if needs_class_tracking {
470 if let Some(class_name) = extract_class_name(trimmed) {
471 let class_brace_depth = trimmed.matches('{').count() as i32
473 - trimmed.matches('}').count() as i32;
474 if class_brace_depth > 0 {
478 class_context_stack.push(class_name);
479 brace_depth = class_brace_depth;
480 }
481 }
482 }
483
484 let is_namespace_line = (trimmed.starts_with("namespace ") || trimmed.contains(" namespace "))
487 && !trimmed.contains("using ")
488 && trimmed.contains('{');
489 let needs_namespace_tracking = is_namespace_line && pending_annotation.is_none() && !accumulating_for_annotation;
490 if needs_namespace_tracking {
491 if let Some(ns_name) = extract_namespace_name(trimmed) {
492 debug_println!("DEBUG SAFETY: Entering namespace '{}' for context", ns_name);
493 class_context_stack.push(ns_name);
494 brace_depth = trimmed.matches('{').count() as i32
496 - trimmed.matches('}').count() as i32;
497 }
498 }
499
500 if pending_annotation.is_some() && !accumulating_for_annotation {
502 accumulated_line.clear();
503 accumulating_for_annotation = true;
504 }
505
506 if accumulating_for_annotation {
508 if !accumulated_line.is_empty() {
509 accumulated_line.push(' ');
510 }
511 accumulated_line.push_str(trimmed);
512
513 let is_namespace_decl = accumulated_line.starts_with("namespace") ||
518 (accumulated_line.contains("namespace") && !accumulated_line.contains("using"));
519 let is_class_decl = is_class_declaration(&accumulated_line);
520 let should_check_annotation = if is_namespace_decl || is_class_decl {
521 accumulated_line.contains('{')
522 } else {
523 accumulated_line.contains('(') &&
524 (accumulated_line.contains(')') || accumulated_line.contains('{'))
525 };
526
527 let is_forward_decl = is_forward_declaration(&accumulated_line);
531
532 if is_forward_decl && pending_annotation.is_some() {
533 debug_println!("DEBUG SAFETY: Ignoring annotation on forward declaration: {}",
537 &accumulated_line);
538 pending_annotation.take(); accumulated_line.clear();
540 accumulating_for_annotation = false;
541 continue; }
543
544 if should_check_annotation {
546 if let Some(annotation) = pending_annotation.take() {
547 debug_println!("DEBUG SAFETY: Applying {:?} annotation to: {}", annotation, &accumulated_line);
548 if accumulated_line.starts_with("namespace") ||
550 (accumulated_line.contains("namespace") && !accumulated_line.contains("using")) {
551 context.file_default = annotation;
553 debug_println!("DEBUG SAFETY: Set file default to {:?} (namespace)", annotation);
554 if let Some(ns_name) = extract_namespace_name(&accumulated_line) {
556 debug_println!("DEBUG SAFETY: Entering annotated namespace '{}' for context", ns_name);
557 class_context_stack.push(ns_name);
558 brace_depth = accumulated_line.matches('{').count() as i32
559 - accumulated_line.matches('}').count() as i32;
560 }
561 } else if is_class_declaration(&accumulated_line) {
562 if let Some(class_name) = extract_class_name(&accumulated_line) {
564 let qualified_name = if class_context_stack.is_empty() {
566 class_name.clone()
567 } else {
568 format!("{}::{}", class_context_stack.join("::"), class_name)
569 };
570 let signature = FunctionSignature::from_name_only(qualified_name.clone());
571 context.function_overrides.push((signature, annotation));
572 debug_println!("DEBUG SAFETY: Set class '{}' to {:?}", qualified_name, annotation);
573
574 let class_brace_depth = accumulated_line.matches('{').count() as i32
577 - accumulated_line.matches('}').count() as i32;
578 if class_brace_depth > 0 {
579 class_context_stack.push(class_name.clone());
580 brace_depth = class_brace_depth;
581 }
582 }
583 } else if is_function_declaration(&accumulated_line) {
584 if let Some(func_name) = extract_function_name(&accumulated_line) {
586 let qualified_name = if class_context_stack.is_empty() {
588 func_name.clone()
589 } else {
590 format!("{}::{}", class_context_stack.join("::"), func_name)
591 };
592 let param_types = extract_parameter_types(&accumulated_line);
593 let signature = FunctionSignature::new(qualified_name.clone(), param_types.clone());
594 context.function_overrides.push((signature, annotation));
595
596 if let Some(ref params) = param_types {
597 debug_println!("DEBUG SAFETY: Set function '{}({})' to {:?}",
598 qualified_name, params.join(", "), annotation);
599 } else {
600 debug_println!("DEBUG SAFETY: Set function '{}' to {:?}", qualified_name, annotation);
601 }
602 }
603 } else {
604 debug_println!("DEBUG SAFETY: Annotation consumed by single statement: {}", &accumulated_line);
607 }
608 accumulated_line.clear();
609 accumulating_for_annotation = false;
610 }
611 }
612 }
613 }
614
615 Ok(context)
616}
617
618fn is_class_declaration(line: &str) -> bool {
620 let has_class = line.starts_with("class ") || line.starts_with("struct ") ||
622 line.contains(" class ") || line.contains(" struct ");
623 let has_brace = line.contains('{');
625 has_class && has_brace
626}
627
628fn is_forward_declaration(line: &str) -> bool {
631 let has_class_or_struct = line.starts_with("class ") || line.starts_with("struct ") ||
632 line.contains(" class ") || line.contains(" struct ");
633 let has_semicolon = line.trim_end().ends_with(';');
634 let has_brace = line.contains('{');
635
636 has_class_or_struct && has_semicolon && !has_brace
638}
639
640fn extract_class_name(line: &str) -> Option<String> {
642 let normalized = line.replace('\n', " ").replace('\r', " ");
645
646 let class_patterns = [
649 ("class ", "class "), ("struct ", "struct "), (" class ", " class "), (" struct ", " struct "), ];
654
655 for (search_pattern, keyword) in &class_patterns {
656 if let Some(pos) = normalized.find(search_pattern) {
657 let after_keyword = &normalized[pos + keyword.len()..];
658 let parts: Vec<&str> = after_keyword.split_whitespace().collect();
660 if let Some(name) = parts.first() {
661 let name = name.split('<').next().unwrap_or(name);
663 let name = name.split(':').next().unwrap_or(name);
664 let name = name.split('{').next().unwrap_or(name);
665 if name != "rusty" && name != "Arc" && name != "std" && !name.is_empty() {
668 return Some(name.to_string());
669 }
670 }
671 }
672 }
673 None
674}
675
676fn extract_namespace_name(line: &str) -> Option<String> {
678 let normalized = line.replace('\n', " ").replace('\r', " ");
681
682 if let Some(pos) = normalized.find("namespace ") {
684 let after_keyword = &normalized[pos + "namespace ".len()..];
685 let parts: Vec<&str> = after_keyword.split_whitespace().collect();
687 if let Some(name) = parts.first() {
688 let name = name.split('{').next().unwrap_or(name);
690 if !name.is_empty() {
691 return Some(name.to_string());
692 }
693 }
694 }
695 None
696}
697
698fn is_function_declaration(line: &str) -> bool {
700 if line.starts_with("template") || line.contains(" template") {
704 if let Some(template_pos) = line.find("template<") {
706 let mut depth = 0;
708 let mut template_end = None;
709 for (i, ch) in line[template_pos..].chars().enumerate() {
710 if ch == '<' {
711 depth += 1;
712 } else if ch == '>' {
713 depth -= 1;
714 if depth == 0 {
715 template_end = Some(template_pos + i);
716 break;
717 }
718 }
719 }
720
721 if let Some(end_pos) = template_end {
723 let after_template = &line[end_pos + 1..].trim_start();
724 if after_template.starts_with("class ") || after_template.starts_with("struct ") {
726 return false;
727 }
728 }
729 }
730 }
731
732 let has_parens = line.contains('(') && line.contains(')');
735 let has_type = line.contains("void") || line.contains("int") ||
736 line.contains("bool") || line.contains("auto") ||
737 line.contains("const") || line.contains("static");
738
739 let is_template_function = {
742 let trimmed = line.trim_start();
744 let starts_with_template_param = trimmed.len() >= 2 &&
745 trimmed.chars().next().map_or(false, |c| c.is_uppercase()) &&
746 trimmed.chars().nth(1) == Some(' ');
747
748 let has_template_syntax = line.contains("template") || line.contains('<') || line.contains('>');
750
751 starts_with_template_param || (has_template_syntax && has_parens)
752 };
753
754 has_parens && (has_type || line.contains("::") || is_template_function)
755}
756
757fn extract_function_name(line: &str) -> Option<String> {
759 if let Some(paren_pos) = line.find('(') {
761 let before_paren = &line[..paren_pos];
762 let parts: Vec<&str> = before_paren.split_whitespace().collect();
764 if let Some(last) = parts.last() {
765 let name = last.trim_start_matches('*').trim_start_matches('&');
767 if !name.is_empty() {
768 if name.contains("::") {
772 return Some(name.to_string());
774 }
775 return Some(name.to_string());
777 }
778 }
779 }
780 None
781}
782
783fn extract_parameter_types(line: &str) -> Option<Vec<String>> {
786 let open_paren = line.find('(')?;
788
789 let mut depth = 0;
791 let mut close_paren = None;
792 for (i, ch) in line[open_paren..].chars().enumerate() {
793 if ch == '(' {
794 depth += 1;
795 } else if ch == ')' {
796 depth -= 1;
797 if depth == 0 {
798 close_paren = Some(open_paren + i);
799 break;
800 }
801 }
802 }
803
804 let close_paren = close_paren?;
805 let params_str = &line[open_paren + 1..close_paren].trim();
806
807 if params_str.is_empty() {
809 return Some(Vec::new());
810 }
811
812 let mut params = Vec::new();
814 let mut current_param = String::new();
815 let mut angle_depth = 0;
816 let mut paren_depth = 0;
817
818 for ch in params_str.chars() {
819 match ch {
820 '<' => {
821 angle_depth += 1;
822 current_param.push(ch);
823 }
824 '>' => {
825 angle_depth -= 1;
826 current_param.push(ch);
827 }
828 '(' => {
829 paren_depth += 1;
830 current_param.push(ch);
831 }
832 ')' => {
833 paren_depth -= 1;
834 current_param.push(ch);
835 }
836 ',' if angle_depth == 0 && paren_depth == 0 => {
837 if !current_param.trim().is_empty() {
839 params.push(normalize_param_type(¤t_param));
840 }
841 current_param.clear();
842 }
843 _ => {
844 current_param.push(ch);
845 }
846 }
847 }
848
849 if !current_param.trim().is_empty() {
851 params.push(normalize_param_type(¤t_param));
852 }
853
854 Some(params)
855}
856
857fn normalize_param_type(param: &str) -> String {
860 let trimmed = param.trim();
861
862 let without_default = if let Some(eq_pos) = trimmed.find('=') {
864 &trimmed[..eq_pos]
865 } else {
866 trimmed
867 };
868
869 let tokens: Vec<&str> = without_default.split_whitespace().collect();
871
872 if tokens.is_empty() {
873 return String::new();
874 }
875
876 let type_tokens = if tokens.len() > 1 {
880 let last = tokens.last().unwrap();
881 let second_last = tokens[tokens.len() - 2];
882
883 if !last.contains('<') && !last.contains('>') && !last.contains("::") &&
886 !last.contains('*') && !last.contains('&') &&
887 (second_last.contains('<') || second_last.contains('>') ||
888 second_last.contains("::") || second_last.contains('*') ||
889 second_last.contains('&')) {
890 &tokens[..tokens.len() - 1]
891 } else {
892 &tokens[..]
893 }
894 } else {
895 &tokens[..]
896 };
897
898 type_tokens.join(" ")
900}
901
902fn extract_qualified_function_name(before_paren: &str) -> Option<String> {
904 let parts: Vec<&str> = before_paren.split_whitespace().collect();
907
908 for part in parts.iter().rev() {
909 if part.contains("::") {
910 let clean_name = part.trim_start_matches('*').trim_start_matches('&');
912 return Some(clean_name.to_string());
913 }
914 }
915
916 None
917}
918
919#[allow(dead_code)]
923pub fn parse_entity_safety(entity: &Entity) -> Option<SafetyMode> {
924 if let Some(comment) = entity.get_comment() {
925 for line in comment.lines() {
927 let trimmed = line.trim();
928 let content = if trimmed.starts_with("///") {
930 trimmed[3..].trim()
931 } else if trimmed.starts_with("//") {
932 trimmed[2..].trim()
933 } else if trimmed.starts_with("/*") {
934 trimmed[2..].trim()
935 } else if trimmed.starts_with("*") {
936 trimmed[1..].trim()
937 } else {
938 trimmed
939 };
940
941 if contains_annotation(content, "@safe") {
943 return Some(SafetyMode::Safe);
944 } else if contains_annotation(content, "@unsafe") {
945 return Some(SafetyMode::Unsafe);
946 }
947 }
948 None
949 } else {
950 None
951 }
952}
953
954#[allow(dead_code)]
957pub fn parse_class_annotation(entity: &Entity) -> Option<ClassAnnotation> {
958 if let Some(comment) = entity.get_comment() {
959 for line in comment.lines() {
961 let trimmed = line.trim();
962 let content = if trimmed.starts_with("///") {
964 trimmed[3..].trim()
965 } else if trimmed.starts_with("//") {
966 trimmed[2..].trim()
967 } else if trimmed.starts_with("/*") {
968 trimmed[2..].trim()
969 } else if trimmed.starts_with("*") {
970 trimmed[1..].trim()
971 } else {
972 trimmed
973 };
974
975 if contains_annotation(content, "@interface") {
977 return Some(ClassAnnotation::Interface);
978 } else if contains_annotation(content, "@safe") {
979 return Some(ClassAnnotation::Safe);
980 } else if contains_annotation(content, "@unsafe") {
981 return Some(ClassAnnotation::Unsafe);
982 }
983 }
984 None
985 } else {
986 None
987 }
988}
989
990pub fn check_class_interface_annotation(entity: &Entity) -> bool {
993 use std::fs::File;
994 use std::io::{BufRead, BufReader};
995
996 if let Some(comment) = entity.get_comment() {
998 for line in comment.lines() {
999 let trimmed = line.trim();
1000 let content = if trimmed.starts_with("///") {
1001 trimmed[3..].trim()
1002 } else if trimmed.starts_with("//") {
1003 trimmed[2..].trim()
1004 } else if trimmed.starts_with("/*") {
1005 trimmed[2..].trim()
1006 } else if trimmed.starts_with("*") {
1007 trimmed[1..].trim()
1008 } else {
1009 trimmed
1010 };
1011 if contains_annotation(content, "@interface") {
1012 return true;
1013 }
1014 }
1015 }
1016
1017 let location = match entity.get_location() {
1019 Some(loc) => loc,
1020 None => return false,
1021 };
1022
1023 let file_location = location.get_file_location();
1024 let file = match file_location.file {
1025 Some(f) => f,
1026 None => return false,
1027 };
1028
1029 let file_path = file.get_path();
1030 let entity_line = file_location.line as usize;
1031
1032 let file_handle = match File::open(&file_path) {
1033 Ok(f) => f,
1034 Err(_) => return false,
1035 };
1036
1037 let reader = BufReader::new(file_handle);
1038 let mut prev_line = String::new();
1039 let mut current_line = 0;
1040
1041 for line_result in reader.lines() {
1042 current_line += 1;
1043 let line = match line_result {
1044 Ok(l) => l,
1045 Err(_) => continue,
1046 };
1047
1048 if current_line == entity_line {
1049 let trimmed = prev_line.trim();
1051 if trimmed.starts_with("//") {
1052 let content = trimmed[2..].trim();
1053 if contains_annotation(content, "@interface") {
1054 return true;
1055 }
1056 }
1057 return false;
1058 }
1059
1060 prev_line = line;
1061 }
1062
1063 false
1064}
1065
1066pub fn check_method_safety_annotation(entity: &Entity) -> Option<SafetyMode> {
1070 use std::fs::File;
1071 use std::io::{BufRead, BufReader};
1072
1073 if let Some(comment) = entity.get_comment() {
1075 for line in comment.lines() {
1076 let trimmed = line.trim();
1077 let content = if trimmed.starts_with("///") {
1078 trimmed[3..].trim()
1079 } else if trimmed.starts_with("//") {
1080 trimmed[2..].trim()
1081 } else if trimmed.starts_with("/*") {
1082 trimmed[2..].trim()
1083 } else if trimmed.starts_with("*") {
1084 trimmed[1..].trim()
1085 } else {
1086 trimmed
1087 };
1088 if contains_annotation(content, "@safe") {
1089 return Some(SafetyMode::Safe);
1090 } else if contains_annotation(content, "@unsafe") {
1091 return Some(SafetyMode::Unsafe);
1092 }
1093 }
1094 }
1095
1096 let location = match entity.get_location() {
1098 Some(loc) => loc,
1099 None => return None,
1100 };
1101
1102 let file_location = location.get_file_location();
1103 let file = match file_location.file {
1104 Some(f) => f,
1105 None => return None,
1106 };
1107
1108 let file_path = file.get_path();
1109 let entity_line = file_location.line as usize;
1110
1111 let file_handle = match File::open(&file_path) {
1112 Ok(f) => f,
1113 Err(_) => return None,
1114 };
1115
1116 let reader = BufReader::new(file_handle);
1117 let mut prev_line = String::new();
1118 let mut current_line = 0;
1119
1120 for line_result in reader.lines() {
1121 current_line += 1;
1122 let line = match line_result {
1123 Ok(l) => l,
1124 Err(_) => continue,
1125 };
1126
1127 if current_line == entity_line {
1128 let trimmed = prev_line.trim();
1130 if trimmed.starts_with("//") {
1131 let content = trimmed[2..].trim();
1132 if contains_annotation(content, "@safe") {
1133 return Some(SafetyMode::Safe);
1134 } else if contains_annotation(content, "@unsafe") {
1135 return Some(SafetyMode::Unsafe);
1136 }
1137 }
1138 return None;
1139 }
1140
1141 prev_line = line;
1142 }
1143
1144 None
1145}
1146
1147#[cfg(test)]
1148mod tests {
1149 use super::*;
1150 use tempfile::NamedTempFile;
1151 use std::io::Write;
1152
1153 #[test]
1154 fn test_namespace_safe_annotation() {
1155 let code = r#"
1156// @safe
1157namespace myapp {
1158 void func1() {}
1159 void func2() {}
1160}
1161"#;
1162
1163 let mut file = NamedTempFile::with_suffix(".cpp").unwrap();
1164 file.write_all(code.as_bytes()).unwrap();
1165 file.flush().unwrap();
1166
1167 let context = parse_safety_annotations(file.path()).unwrap();
1168 assert_eq!(context.file_default, SafetyMode::Safe);
1169 }
1170
1171 #[test]
1172 fn test_function_safe_annotation() {
1173 let code = r#"
1174// Default is unsafe
1175void unsafe_func() {}
1176
1177// @safe
1178void safe_func() {
1179 int x = 42;
1180}
1181
1182// @unsafe
1183void explicit_unsafe() {}
1184"#;
1185
1186 let mut file = NamedTempFile::with_suffix(".cpp").unwrap();
1187 file.write_all(code.as_bytes()).unwrap();
1188 file.flush().unwrap();
1189
1190 let context = parse_safety_annotations(file.path()).unwrap();
1191
1192 assert!(!context.should_check_function("unsafe_func"));
1193 assert!(context.should_check_function("safe_func"));
1194 assert!(!context.should_check_function("explicit_unsafe"));
1195 }
1196
1197 #[test]
1198 fn test_first_code_element_annotation() {
1199 let code = r#"
1200// @safe
1201int global_var = 42;
1202
1203void func() {}
1204"#;
1205
1206 let mut file = NamedTempFile::with_suffix(".cpp").unwrap();
1207 file.write_all(code.as_bytes()).unwrap();
1208 file.flush().unwrap();
1209
1210 let context = parse_safety_annotations(file.path()).unwrap();
1211 assert_eq!(context.file_default, SafetyMode::Unsafe);
1213 }
1214}