rusty_cpp/analysis/
pointer_safety.rs

1use crate::parser::{Statement, Expression, Function, MoveKind};
2use crate::parser::safety_annotations::SafetyMode;
3use std::collections::HashSet;
4
5/// Check if a type is a char pointer type (char*, const char*, wchar_t*, etc.)
6/// These types are unsafe in @safe code because they represent raw pointers
7/// that can dangle or be misused.
8fn is_char_pointer_type(type_name: &str) -> bool {
9    let normalized = type_name.replace(" ", "").to_lowercase();
10
11    // Check for various char pointer patterns
12    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    // Handle qualified names (e.g., std::char*, ::char*)
21    normalized.ends_with("::char*") ||
22    normalized.ends_with("::constchar*") ||
23    // Handle spacing variations
24    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/// Check for unsafe pointer operations in a function's AST
33#[allow(dead_code)]
34pub fn check_function_for_pointers(_function: &crate::ir::IrFunction) -> Result<Vec<String>, String> {
35    // For now, return empty - we need to check at AST level
36    // The IR doesn't preserve all the pointer operations
37    Ok(Vec::new())
38}
39
40/// Check for unsafe pointer operations in a parsed function
41pub 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    // Only @safe functions have pointer operations checked
46    // Undeclared and @unsafe functions are allowed to do pointer operations
47    let skip_pointer_checks = function_safety != SafetyMode::Safe;
48
49    // Note: We do NOT check function parameters for char* types.
50    // A @safe function CAN take const char* parameters and act as a safe wrapper.
51    // The key rule is:
52    // - Callers must pass string literals (not char* variables)
53    // - The function can internally use @unsafe blocks
54    // - Variable declarations of char* inside the function ARE flagged
55    //
56    // This enables the "safe wrapper" pattern:
57    //   void Logger::log(const char* msg) { @unsafe { internal_log(msg); } }
58
59    for stmt in &function.body {
60        // Track unsafe scope depth
61        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        // Skip checking if we're in an unsafe block OR the function is not @safe
76        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
86/// Check for std::move on references in @safe code
87///
88/// In @safe code, using std::move on a reference is forbidden because:
89/// - std::move on a reference moves the underlying object, not the reference
90/// - This differs from Rust's semantics where references are first-class types
91/// - Users should use rusty::move for Rust-like reference move semantics
92///
93/// This check tracks reference variables and reports errors when std::move is called on them.
94pub fn check_std_move_on_references(function: &Function, function_safety: SafetyMode) -> Vec<String> {
95    let mut errors = Vec::new();
96
97    // Only check @safe functions
98    if function_safety != SafetyMode::Safe {
99        return errors;
100    }
101
102    // Track reference variables (including function parameters)
103    let mut reference_vars: HashSet<String> = HashSet::new();
104
105    // Add reference parameters
106    for param in &function.parameters {
107        if param.is_reference {
108            reference_vars.insert(param.name.clone());
109        }
110    }
111
112    // Process function body
113    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        // Track unsafe scope depth
134        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        // Skip checking if we're in an unsafe block
149        if *unsafe_depth > 0 {
150            // Still need to track variable declarations
151            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                // Track reference variable declarations
162                if var.is_reference {
163                    reference_vars.insert(var.name.clone());
164                }
165                // Note: Variable struct doesn't have an initializer field to check
166                // The initialization is handled via Assignment or ReferenceBinding statements
167            }
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                // Use line 0 for returns (we don't have location info here)
187                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                // Check condition
193                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 branches
198                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            // Only check std::move, not rusty::move
224            if *kind == MoveKind::StdMove {
225                // Check if the inner expression is a reference variable
226                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            // Recursively check inner expression
238            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
267/// Process a list of statements while tracking unsafe depth for pointer safety
268fn 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        // Track unsafe scope depth
277        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
301/// Check if a parsed statement contains pointer operations
302pub fn check_parsed_statement_for_pointers(stmt: &Statement, in_unsafe_scope: bool) -> Option<String> {
303    use crate::parser::Statement;
304
305    // Skip all checks if we're in an unsafe block
306    if in_unsafe_scope {
307        return None;
308    }
309
310    match stmt {
311        Statement::Assignment { lhs, rhs, location } => {
312            // Check BOTH lhs and rhs for pointer operations
313            // e.g., `n->value_ = val` has the dereference on lhs
314            // e.g., `x = *ptr` has the dereference on rhs
315            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            // Check if this is a char* type declaration
330            // char* and const char* are unsafe in @safe code because they are raw pointers
331            // String literals are safe (handled separately), but explicit char* variables are not
332            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            // Other raw pointer declarations are allowed (dereferencing is still checked)
341            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            // Recursively check branches with proper unsafe depth tracking
370            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            // Check all statements in the block with proper unsafe depth tracking
384            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            // *this is safe in member functions - this pointer is guaranteed valid
401            if let Expression::Variable(name) = inner.as_ref() {
402                if name == "this" {
403                    return None;  // *this is safe
404                }
405            }
406            Some("dereference")
407        }
408        Expression::AddressOf(inner) => {
409            // Check what we're taking the address of
410            match inner.as_ref() {
411                // For MemberAccess, recursively check if the object contains unsafe operations
412                // e.g., &(ptr->field) has a Dereference inside which is unsafe
413                // e.g., &(static_cast<T*>(p)->field) has both Cast and Dereference
414                Expression::MemberAccess { object, .. } => contains_pointer_operation(object),
415                // &ClassName::method often appears as Variable("ClassName::method")
416                // due to how C++ qualified names are parsed - this is safe (member function pointer)
417                Expression::Variable(name) if name.contains("::") => None,
418                // &variable - taking address of a local variable is unsafe (could create dangling pointers)
419                _ => Some("address-of")
420            }
421        }
422        Expression::Variable(name) if name == "this" => {
423            // Passing 'this' as a raw pointer is unsafe - the callee might store it
424            // and cause dangling pointer issues later. While 'this' is valid during
425            // the call, we can't guarantee how the callee uses it.
426            // Note: *this (dereference) is safe, but passing 'this' itself is not.
427            Some("'this' pointer")
428        }
429        Expression::FunctionCall { args, .. } => {
430            // Check arguments recursively
431            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            // Check both sides
440            if let Some(op) = contains_pointer_operation(left) {
441                return Some(op);
442            }
443            contains_pointer_operation(right)
444        }
445        Expression::MemberAccess { object, .. } => {
446            // this->member is safe - just accessing a member through the implicit this pointer
447            // ptr->field (dereference through pointer) is handled by the parser wrapping object in Dereference
448            if let Expression::Variable(name) = object.as_ref() {
449                if name == "this" {
450                    return None;  // this->member is safe
451                }
452            }
453            // For other cases, check object for pointer operations
454            contains_pointer_operation(object)
455        }
456        Expression::Cast(inner) => {
457            // C++ casts (static_cast, dynamic_cast, reinterpret_cast, const_cast, C-style)
458            // are all considered unsafe operations in @safe code
459            // Return "cast" as the operation type, but also check inner for other violations
460            Some("cast")
461        }
462        Expression::StringLiteral(_) => {
463            // String literals have static lifetime and cannot dangle
464            // They are stored in the .rodata segment and are always safe
465            None
466        }
467        Expression::Literal(_) => {
468            // Numeric and other literals are safe
469            None
470        }
471        Expression::Lambda { .. } => {
472            // Lambda safety is checked elsewhere (capture analysis)
473            None
474        }
475        Expression::Move { inner, .. } => {
476            // Check inner expression for pointer operations
477            contains_pointer_operation(inner)
478        }
479        Expression::Variable(_) => {
480            // Regular variable references (not 'this') are safe
481            // Note: 'this' is handled above with a guard
482            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        // Test *(&x) - dereference of address-of
568        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        // Declaring a pointer variable should not trigger an error (only operations do)
587        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        // Passing 'this' as an argument should be flagged as unsafe
613        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        // *this is safe - dereferencing this in a member function is valid
634        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        // 'this' by itself (passed as pointer) is unsafe
641        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        // this->member is safe - just accessing a member through the implicit this pointer
648        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        // &ClassName::method is safe - member function pointers don't involve object lifetimes
658        // This tests the MemberAccess variant
659        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        // &ClassName::method as Variable("ClassName::method") is safe
669        // The parser produces this form for qualified member function pointers
670        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        // &x is unsafe - taking address of a local variable
677        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    // Tests for is_char_pointer_type
682    #[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")); // Not a pointer
712    }
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}