1use crate::parser::{Statement, Expression, Function, MoveKind};
2use crate::parser::safety_annotations::SafetyMode;
3use std::collections::HashSet;
4
5fn is_char_pointer_type(type_name: &str) -> bool {
9 let normalized = type_name.replace(" ", "").to_lowercase();
10
11 normalized == "char*" ||
13 normalized == "constchar*" ||
14 normalized == "charconst*" ||
15 normalized == "wchar_t*" ||
16 normalized == "constwchar_t*" ||
17 normalized == "wchar_tconst*" ||
18 normalized == "char16_t*" ||
19 normalized == "char32_t*" ||
20 normalized.ends_with("::char*") ||
22 normalized.ends_with("::constchar*") ||
23 type_name.contains("char *") ||
25 type_name.contains("char const *") ||
26 type_name.contains("const char *") ||
27 type_name.contains("wchar_t *") ||
28 type_name.contains("wchar_t const *") ||
29 type_name.contains("const wchar_t *")
30}
31
32#[allow(dead_code)]
34pub fn check_function_for_pointers(_function: &crate::ir::IrFunction) -> Result<Vec<String>, String> {
35 Ok(Vec::new())
38}
39
40pub fn check_parsed_function_for_pointers(function: &Function, function_safety: SafetyMode) -> Vec<String> {
42 let mut errors = Vec::new();
43 let mut unsafe_depth = 0;
44
45 let skip_pointer_checks = function_safety != SafetyMode::Safe;
48
49 for stmt in &function.body {
60 match stmt {
62 Statement::EnterUnsafe => {
63 unsafe_depth += 1;
64 continue;
65 }
66 Statement::ExitUnsafe => {
67 if unsafe_depth > 0 {
68 unsafe_depth -= 1;
69 }
70 continue;
71 }
72 _ => {}
73 }
74
75 let in_unsafe_scope = unsafe_depth > 0 || skip_pointer_checks;
77
78 if let Some(error) = check_parsed_statement_for_pointers(stmt, in_unsafe_scope) {
79 errors.push(format!("In function '{}': {}", function.name, error));
80 }
81 }
82
83 errors
84}
85
86pub fn check_std_move_on_references(function: &Function, function_safety: SafetyMode) -> Vec<String> {
95 let mut errors = Vec::new();
96
97 if function_safety != SafetyMode::Safe {
99 return errors;
100 }
101
102 let mut reference_vars: HashSet<String> = HashSet::new();
104
105 for param in &function.parameters {
107 if param.is_reference {
108 reference_vars.insert(param.name.clone());
109 }
110 }
111
112 let mut unsafe_depth = 0;
114 check_statements_for_std_move_on_ref(
115 &function.body,
116 &function.name,
117 &mut reference_vars,
118 &mut unsafe_depth,
119 &mut errors,
120 );
121
122 errors
123}
124
125fn check_statements_for_std_move_on_ref(
126 statements: &[Statement],
127 function_name: &str,
128 reference_vars: &mut HashSet<String>,
129 unsafe_depth: &mut usize,
130 errors: &mut Vec<String>,
131) {
132 for stmt in statements {
133 match stmt {
135 Statement::EnterUnsafe => {
136 *unsafe_depth += 1;
137 continue;
138 }
139 Statement::ExitUnsafe => {
140 if *unsafe_depth > 0 {
141 *unsafe_depth -= 1;
142 }
143 continue;
144 }
145 _ => {}
146 }
147
148 if *unsafe_depth > 0 {
150 if let Statement::VariableDecl(var) = stmt {
152 if var.is_reference {
153 reference_vars.insert(var.name.clone());
154 }
155 }
156 continue;
157 }
158
159 match stmt {
160 Statement::VariableDecl(var) => {
161 if var.is_reference {
163 reference_vars.insert(var.name.clone());
164 }
165 }
168 Statement::Assignment { rhs, location, .. } => {
169 if let Some(error) = check_expression_for_std_move_on_ref(rhs, reference_vars, location.line) {
170 errors.push(format!("In function '{}': {}", function_name, error));
171 }
172 }
173 Statement::ReferenceBinding { target, location, .. } => {
174 if let Some(error) = check_expression_for_std_move_on_ref(target, reference_vars, location.line) {
175 errors.push(format!("In function '{}': {}", function_name, error));
176 }
177 }
178 Statement::FunctionCall { args, location, .. } => {
179 for arg in args {
180 if let Some(error) = check_expression_for_std_move_on_ref(arg, reference_vars, location.line) {
181 errors.push(format!("In function '{}': {}", function_name, error));
182 }
183 }
184 }
185 Statement::Return(Some(expr)) => {
186 if let Some(error) = check_expression_for_std_move_on_ref(expr, reference_vars, 0) {
188 errors.push(format!("In function '{}': {}", function_name, error));
189 }
190 }
191 Statement::If { condition, then_branch, else_branch, .. } => {
192 if let Some(error) = check_expression_for_std_move_on_ref(condition, reference_vars, 0) {
194 errors.push(format!("In function '{}': {}", function_name, error));
195 }
196
197 check_statements_for_std_move_on_ref(then_branch, function_name, reference_vars, unsafe_depth, errors);
199 if let Some(else_stmts) = else_branch {
200 check_statements_for_std_move_on_ref(else_stmts, function_name, reference_vars, unsafe_depth, errors);
201 }
202 }
203 Statement::Block(statements) => {
204 check_statements_for_std_move_on_ref(statements, function_name, reference_vars, unsafe_depth, errors);
205 }
206 Statement::ExpressionStatement { expr, location } => {
207 if let Some(error) = check_expression_for_std_move_on_ref(expr, reference_vars, location.line) {
208 errors.push(format!("In function '{}': {}", function_name, error));
209 }
210 }
211 _ => {}
212 }
213 }
214}
215
216fn check_expression_for_std_move_on_ref(
217 expr: &Expression,
218 reference_vars: &HashSet<String>,
219 line: u32,
220) -> Option<String> {
221 match expr {
222 Expression::Move { inner, kind } => {
223 if *kind == MoveKind::StdMove {
225 if let Expression::Variable(var_name) = inner.as_ref() {
227 if reference_vars.contains(var_name) {
228 return Some(format!(
229 "std::move on reference '{}' at line {}: \
230 In @safe code, std::move on references is forbidden because it moves the underlying object, not the reference. \
231 Use rusty::move for Rust-like reference semantics, or use @unsafe block if you need C++ behavior.",
232 var_name, line
233 ));
234 }
235 }
236 }
237 check_expression_for_std_move_on_ref(inner, reference_vars, line)
239 }
240 Expression::FunctionCall { args, .. } => {
241 for arg in args {
242 if let Some(error) = check_expression_for_std_move_on_ref(arg, reference_vars, line) {
243 return Some(error);
244 }
245 }
246 None
247 }
248 Expression::BinaryOp { left, right, .. } => {
249 if let Some(error) = check_expression_for_std_move_on_ref(left, reference_vars, line) {
250 return Some(error);
251 }
252 check_expression_for_std_move_on_ref(right, reference_vars, line)
253 }
254 Expression::Dereference(inner) | Expression::AddressOf(inner) => {
255 check_expression_for_std_move_on_ref(inner, reference_vars, line)
256 }
257 Expression::MemberAccess { object, .. } => {
258 check_expression_for_std_move_on_ref(object, reference_vars, line)
259 }
260 Expression::Cast(inner) => {
261 check_expression_for_std_move_on_ref(inner, reference_vars, line)
262 }
263 _ => None,
264 }
265}
266
267fn check_statements_for_pointers_with_unsafe_tracking(
269 statements: &[Statement],
270 initial_unsafe_depth: usize,
271) -> Vec<String> {
272 let mut errors = Vec::new();
273 let mut unsafe_depth = initial_unsafe_depth;
274
275 for stmt in statements {
276 match stmt {
278 Statement::EnterUnsafe => {
279 unsafe_depth += 1;
280 continue;
281 }
282 Statement::ExitUnsafe => {
283 if unsafe_depth > 0 {
284 unsafe_depth -= 1;
285 }
286 continue;
287 }
288 _ => {}
289 }
290
291 let in_unsafe_scope = unsafe_depth > 0;
292
293 if let Some(error) = check_parsed_statement_for_pointers(stmt, in_unsafe_scope) {
294 errors.push(error);
295 }
296 }
297
298 errors
299}
300
301pub fn check_parsed_statement_for_pointers(stmt: &Statement, in_unsafe_scope: bool) -> Option<String> {
303 use crate::parser::Statement;
304
305 if in_unsafe_scope {
307 return None;
308 }
309
310 match stmt {
311 Statement::Assignment { lhs, rhs, location } => {
312 if let Some(op) = contains_pointer_operation(lhs) {
316 return Some(format!(
317 "Unsafe pointer {} at line {}: pointer operations require unsafe context",
318 op, location.line
319 ));
320 }
321 if let Some(op) = contains_pointer_operation(rhs) {
322 return Some(format!(
323 "Unsafe pointer {} at line {}: pointer operations require unsafe context",
324 op, location.line
325 ));
326 }
327 }
328 Statement::VariableDecl(var) if var.is_pointer => {
329 if is_char_pointer_type(&var.type_name) {
333 return Some(format!(
334 "Cannot declare '{}' with type '{}' in @safe code at line {}. \
335 Use @unsafe block or a safe wrapper type. \
336 (String literals like \"hello\" are safe; explicit char* variables are not)",
337 var.name, var.type_name, var.location.line
338 ));
339 }
340 return None;
342 }
343 Statement::FunctionCall { args, location, .. } => {
344 for arg in args {
345 if let Some(op) = contains_pointer_operation(arg) {
346 return Some(format!(
347 "Unsafe pointer {} in function call at line {}: pointer operations require unsafe context",
348 op, location.line
349 ));
350 }
351 }
352 }
353 Statement::Return(Some(expr)) => {
354 if let Some(op) = contains_pointer_operation(expr) {
355 return Some(format!(
356 "Unsafe pointer {} in return statement: pointer operations require unsafe context",
357 op
358 ));
359 }
360 }
361 Statement::If { condition, then_branch, else_branch, location } => {
362 if let Some(op) = contains_pointer_operation(condition) {
363 return Some(format!(
364 "Unsafe pointer {} in condition at line {}: pointer operations require unsafe context",
365 op, location.line
366 ));
367 }
368
369 let then_errors = check_statements_for_pointers_with_unsafe_tracking(then_branch, 0);
371 if !then_errors.is_empty() {
372 return Some(then_errors.into_iter().next().unwrap());
373 }
374
375 if let Some(else_stmts) = else_branch {
376 let else_errors = check_statements_for_pointers_with_unsafe_tracking(else_stmts, 0);
377 if !else_errors.is_empty() {
378 return Some(else_errors.into_iter().next().unwrap());
379 }
380 }
381 }
382 Statement::Block(statements) => {
383 let block_errors = check_statements_for_pointers_with_unsafe_tracking(statements, 0);
385 if !block_errors.is_empty() {
386 return Some(block_errors.into_iter().next().unwrap());
387 }
388 }
389 _ => {}
390 }
391
392 None
393}
394
395fn contains_pointer_operation(expr: &Expression) -> Option<&'static str> {
396 use crate::parser::Expression;
397
398 match expr {
399 Expression::Dereference(inner) => {
400 if let Expression::Variable(name) = inner.as_ref() {
402 if name == "this" {
403 return None; }
405 }
406 Some("dereference")
407 }
408 Expression::AddressOf(inner) => {
409 match inner.as_ref() {
411 Expression::MemberAccess { object, .. } => contains_pointer_operation(object),
415 Expression::Variable(name) if name.contains("::") => None,
418 _ => Some("address-of")
420 }
421 }
422 Expression::Variable(name) if name == "this" => {
423 Some("'this' pointer")
428 }
429 Expression::FunctionCall { args, .. } => {
430 for arg in args {
432 if let Some(op) = contains_pointer_operation(arg) {
433 return Some(op);
434 }
435 }
436 None
437 }
438 Expression::BinaryOp { left, right, .. } => {
439 if let Some(op) = contains_pointer_operation(left) {
441 return Some(op);
442 }
443 contains_pointer_operation(right)
444 }
445 Expression::MemberAccess { object, .. } => {
446 if let Expression::Variable(name) = object.as_ref() {
449 if name == "this" {
450 return None; }
452 }
453 contains_pointer_operation(object)
455 }
456 Expression::Cast(inner) => {
457 Some("cast")
461 }
462 Expression::StringLiteral(_) => {
463 None
466 }
467 Expression::Literal(_) => {
468 None
470 }
471 Expression::Lambda { .. } => {
472 None
474 }
475 Expression::Move { inner, .. } => {
476 contains_pointer_operation(inner)
478 }
479 Expression::Variable(_) => {
480 None
483 }
484 }
485}
486
487#[cfg(test)]
488mod tests {
489 use super::*;
490 use crate::parser::{Expression, Statement, SourceLocation, Variable};
491
492 #[test]
493 fn test_detect_dereference() {
494 let expr = Expression::Dereference(Box::new(Expression::Variable("ptr".to_string())));
495 assert_eq!(contains_pointer_operation(&expr), Some("dereference"));
496 }
497
498 #[test]
499 fn test_detect_address_of() {
500 let expr = Expression::AddressOf(Box::new(Expression::Variable("x".to_string())));
501 assert_eq!(contains_pointer_operation(&expr), Some("address-of"));
502 }
503
504 #[test]
505 fn test_safe_expression() {
506 let expr = Expression::Variable("x".to_string());
507 assert_eq!(contains_pointer_operation(&expr), None);
508 }
509
510 #[test]
511 fn test_pointer_in_assignment() {
512 let stmt = Statement::Assignment {
513 lhs: crate::parser::Expression::Variable("x".to_string()),
514 rhs: Expression::Dereference(Box::new(Expression::Variable("ptr".to_string()))),
515 location: SourceLocation {
516 file: "test.cpp".to_string(),
517 line: 10,
518 column: 5,
519 },
520 };
521
522 let error = check_parsed_statement_for_pointers(&stmt, false);
523 assert!(error.is_some());
524 assert!(error.unwrap().contains("dereference"));
525 }
526
527 #[test]
528 fn test_address_of_in_assignment() {
529 let stmt = Statement::Assignment {
530 lhs: crate::parser::Expression::Variable("ptr".to_string()),
531 rhs: Expression::AddressOf(Box::new(Expression::Variable("x".to_string()))),
532 location: SourceLocation {
533 file: "test.cpp".to_string(),
534 line: 20,
535 column: 5,
536 },
537 };
538
539 let error = check_parsed_statement_for_pointers(&stmt, false);
540 assert!(error.is_some());
541 assert!(error.unwrap().contains("address-of"));
542 }
543
544 #[test]
545 fn test_pointer_in_function_call() {
546 let stmt = Statement::FunctionCall {
547 name: "process".to_string(),
548 args: vec![
549 Expression::Dereference(Box::new(Expression::Variable("ptr".to_string())))
550 ],
551 location: SourceLocation {
552 file: "test.cpp".to_string(),
553 line: 15,
554 column: 5,
555 },
556 };
557
558 let error = check_parsed_statement_for_pointers(&stmt, false);
559 assert!(error.is_some());
560 let error_msg = error.unwrap();
561 assert!(error_msg.contains("function call"));
562 assert!(error_msg.contains("dereference"));
563 }
564
565 #[test]
566 fn test_nested_pointer_operations() {
567 let expr = Expression::Dereference(Box::new(
569 Expression::AddressOf(Box::new(Expression::Variable("x".to_string())))
570 ));
571 assert_eq!(contains_pointer_operation(&expr), Some("dereference"));
572 }
573
574 #[test]
575 fn test_pointer_in_binary_op() {
576 let expr = Expression::BinaryOp {
577 left: Box::new(Expression::Dereference(Box::new(Expression::Variable("p1".to_string())))),
578 op: "+".to_string(),
579 right: Box::new(Expression::Variable("x".to_string())),
580 };
581 assert_eq!(contains_pointer_operation(&expr), Some("dereference"));
582 }
583
584 #[test]
585 fn test_pointer_declaration_allowed() {
586 let stmt = Statement::VariableDecl(Variable {
588 name: "ptr".to_string(),
589 type_name: "int*".to_string(),
590 is_reference: false,
591 is_pointer: true,
592 is_const: false,
593 is_unique_ptr: false,
594 is_shared_ptr: false,
595 is_static: false,
596 is_mutable: false,
597 location: SourceLocation {
598 file: "test.cpp".to_string(),
599 line: 5,
600 column: 5,
601 },
602 is_pack: false,
603 pack_element_type: None,
604 });
605
606 let error = check_parsed_statement_for_pointers(&stmt, false);
607 assert!(error.is_none(), "Pointer declaration should be allowed");
608 }
609
610 #[test]
611 fn test_this_pointer_in_function_call() {
612 let stmt = Statement::FunctionCall {
614 name: "register".to_string(),
615 args: vec![
616 Expression::Variable("this".to_string())
617 ],
618 location: SourceLocation {
619 file: "test.cpp".to_string(),
620 line: 25,
621 column: 5,
622 },
623 };
624
625 let error = check_parsed_statement_for_pointers(&stmt, false);
626 assert!(error.is_some(), "Passing 'this' as argument should be flagged");
627 let error_msg = error.unwrap();
628 assert!(error_msg.contains("'this' pointer"), "Error should mention 'this' pointer");
629 }
630
631 #[test]
632 fn test_this_dereference_is_safe() {
633 let expr = Expression::Dereference(Box::new(Expression::Variable("this".to_string())));
635 assert_eq!(contains_pointer_operation(&expr), None, "*this should be safe");
636 }
637
638 #[test]
639 fn test_this_as_variable_is_unsafe() {
640 let expr = Expression::Variable("this".to_string());
642 assert_eq!(contains_pointer_operation(&expr), Some("'this' pointer"));
643 }
644
645 #[test]
646 fn test_this_member_access_is_safe() {
647 let expr = Expression::MemberAccess {
649 object: Box::new(Expression::Variable("this".to_string())),
650 field: "value_".to_string(),
651 };
652 assert_eq!(contains_pointer_operation(&expr), None, "this->member should be safe");
653 }
654
655 #[test]
656 fn test_member_function_pointer_is_safe() {
657 let expr = Expression::AddressOf(Box::new(Expression::MemberAccess {
660 object: Box::new(Expression::Variable("TestService".to_string())),
661 field: "echo_wrapper".to_string(),
662 }));
663 assert_eq!(contains_pointer_operation(&expr), None, "&ClassName::method (MemberAccess) should be safe");
664 }
665
666 #[test]
667 fn test_qualified_member_function_pointer_is_safe() {
668 let expr = Expression::AddressOf(Box::new(Expression::Variable("TestService::echo_wrapper".to_string())));
671 assert_eq!(contains_pointer_operation(&expr), None, "&ClassName::method (qualified Variable) should be safe");
672 }
673
674 #[test]
675 fn test_address_of_variable_is_unsafe() {
676 let expr = Expression::AddressOf(Box::new(Expression::Variable("x".to_string())));
678 assert_eq!(contains_pointer_operation(&expr), Some("address-of"), "&variable should be unsafe");
679 }
680
681 #[test]
683 fn test_char_ptr_detection() {
684 assert!(super::is_char_pointer_type("char*"));
685 assert!(super::is_char_pointer_type("char *"));
686 assert!(super::is_char_pointer_type("const char*"));
687 assert!(super::is_char_pointer_type("const char *"));
688 assert!(super::is_char_pointer_type("char const*"));
689 assert!(super::is_char_pointer_type("char const *"));
690 }
691
692 #[test]
693 fn test_wchar_ptr_detection() {
694 assert!(super::is_char_pointer_type("wchar_t*"));
695 assert!(super::is_char_pointer_type("wchar_t *"));
696 assert!(super::is_char_pointer_type("const wchar_t*"));
697 assert!(super::is_char_pointer_type("const wchar_t *"));
698 }
699
700 #[test]
701 fn test_char16_char32_ptr_detection() {
702 assert!(super::is_char_pointer_type("char16_t*"));
703 assert!(super::is_char_pointer_type("char32_t*"));
704 }
705
706 #[test]
707 fn test_non_char_ptr_not_detected() {
708 assert!(!super::is_char_pointer_type("int*"));
709 assert!(!super::is_char_pointer_type("void*"));
710 assert!(!super::is_char_pointer_type("std::string"));
711 assert!(!super::is_char_pointer_type("char")); }
713
714 #[test]
715 fn test_string_literal_expression_is_safe() {
716 let expr = Expression::StringLiteral("hello".to_string());
717 assert_eq!(contains_pointer_operation(&expr), None, "String literal should be safe");
718 }
719}