From 1e5b3d354a97bb0096ed1128c32a3c95abb51303 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 25 Mar 2026 16:42:22 +0300 Subject: [PATCH] checker: fix arbitrary pointers in safe code (fixes #25247) --- vlib/v/checker/checker.v | 18 ++++++++++++++---- ...struct_ptr_cast_ptr_outside_unsafe_warn.out | 6 ++++++ .../struct_ptr_cast_ptr_outside_unsafe_warn.vv | 9 +++++++++ .../voidptr_cast_to_ref_outside_unsafe_err.out | 13 +++++++++++++ .../voidptr_cast_to_ref_outside_unsafe_err.vv | 5 +++++ vlib/v/tests/fns/fixed_array_on_voidptr_test.v | 2 +- 6 files changed, 48 insertions(+), 5 deletions(-) create mode 100644 vlib/v/checker/tests/struct_ptr_cast_ptr_outside_unsafe_warn.out create mode 100644 vlib/v/checker/tests/struct_ptr_cast_ptr_outside_unsafe_warn.vv create mode 100644 vlib/v/checker/tests/voidptr_cast_to_ref_outside_unsafe_err.out create mode 100644 vlib/v/checker/tests/voidptr_cast_to_ref_outside_unsafe_err.vv diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 31ba71932..80d04dcba 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -4648,6 +4648,7 @@ fn (mut c Checker) cast_expr(mut node ast.CastExpr) ast.Type { if c.table.sym(base_to_type).kind == .sum_type && node.expr is ast.ArrayInit { c.expected_type = ast.void_type } + expr_is_ident_or_cast := node.expr is ast.Ident || node.expr is ast.CastExpr node.expr_type = c.expr(mut node.expr) // type to be casted if c.rewrite_smartcast_generic_wrapper_cast(mut node, to_type) { node.expr_type = c.expr(mut node.expr) @@ -4728,10 +4729,16 @@ fn (mut c Checker) cast_expr(mut node ast.CastExpr) ast.Type { } else { ast.void_type } + enforce_safe_pointer_casts := c.file.language == .v && !c.is_builtin_mod + enforce_safe_voidptr_ref_cast := enforce_safe_pointer_casts && expr_is_ident_or_cast if to_type.has_flag(.option) && from_type == ast.none_type { // allow conversion from none to every option type } else if to_type.has_flag(.option) && from_type == inner_to_type { return to_type + } else if enforce_safe_voidptr_ref_cast && from_type == ast.voidptr_type_idx && to_type.is_ptr() + && !c.inside_unsafe && !c.pref.translated && !c.file.is_translated { + tt := c.table.type_to_str(to_type) + c.error('cannot cast voidptr to `${tt}` outside `unsafe`', node.pos) } else if to_sym.kind == .sum_type { to_sym_info := to_sym.info as ast.SumType if c.pref.skip_unused && to_sym_info.concrete_types.len > 0 { @@ -4800,16 +4807,19 @@ fn (mut c Checker) cast_expr(mut node ast.CastExpr) ast.Type { c.error('cannot cast int to a struct pointer outside `unsafe`', node.pos) } } - if from_type == ast.voidptr_type_idx && !c.inside_unsafe && !c.pref.translated - && !c.file.is_translated { - c.error('cannot cast voidptr to a struct outside `unsafe`', node.pos) - } if !from_type.is_int() && final_from_sym.kind != .enum && !from_type.is_any_kind_of_pointer() { ft := c.table.type_to_str(from_type) tt := c.table.type_to_str(to_type) c.error('cannot cast `${ft}` to `${tt}`', node.pos) } + if enforce_safe_pointer_casts && !c.inside_unsafe && to_type.is_ptr() && from_type.is_ptr() + && to_type != from_type && from_type != ast.voidptr_type_idx + && to_type.deref() != ast.char_type && from_type.deref() != ast.char_type { + ft := c.table.type_to_str(from_type) + tt := c.table.type_to_str(to_type) + c.warn('casting `${ft}` to `${tt}` is only allowed in `unsafe` code', node.pos) + } } else if !from_type.has_option_or_result() && to_is_interface { if c.type_implements(from_type, to_type, node.pos) { if !from_type.is_any_kind_of_pointer() && from_sym.kind != .interface diff --git a/vlib/v/checker/tests/struct_ptr_cast_ptr_outside_unsafe_warn.out b/vlib/v/checker/tests/struct_ptr_cast_ptr_outside_unsafe_warn.out new file mode 100644 index 000000000..19073e30a --- /dev/null +++ b/vlib/v/checker/tests/struct_ptr_cast_ptr_outside_unsafe_warn.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/struct_ptr_cast_ptr_outside_unsafe_warn.vv:8:6: warning: casting `&bool` to `&AStruct` is only allowed in `unsafe` code + 6 | fn main() { + 7 | a := false + 8 | _ = &AStruct(&a) + | ~~~~~~~~~~~~ + 9 | } diff --git a/vlib/v/checker/tests/struct_ptr_cast_ptr_outside_unsafe_warn.vv b/vlib/v/checker/tests/struct_ptr_cast_ptr_outside_unsafe_warn.vv new file mode 100644 index 000000000..cb7986525 --- /dev/null +++ b/vlib/v/checker/tests/struct_ptr_cast_ptr_outside_unsafe_warn.vv @@ -0,0 +1,9 @@ +struct AStruct { + a u64 + b u64 +} + +fn main() { + a := false + _ = &AStruct(&a) +} diff --git a/vlib/v/checker/tests/voidptr_cast_to_ref_outside_unsafe_err.out b/vlib/v/checker/tests/voidptr_cast_to_ref_outside_unsafe_err.out new file mode 100644 index 000000000..b092d10fa --- /dev/null +++ b/vlib/v/checker/tests/voidptr_cast_to_ref_outside_unsafe_err.out @@ -0,0 +1,13 @@ +vlib/v/checker/tests/voidptr_cast_to_ref_outside_unsafe_err.vv:3:6: error: cannot cast voidptr to `&u32` outside `unsafe` + 1 | fn main() { + 2 | some_ptr := voidptr(1234) + 3 | _ = &u32(some_ptr) + | ~~~~~~~~~~~~~~ + 4 | _ = &u32(voidptr(5678)) + 5 | } +vlib/v/checker/tests/voidptr_cast_to_ref_outside_unsafe_err.vv:4:6: error: cannot cast voidptr to `&u32` outside `unsafe` + 2 | some_ptr := voidptr(1234) + 3 | _ = &u32(some_ptr) + 4 | _ = &u32(voidptr(5678)) + | ~~~~~~~~~~~~~~~~~~~ + 5 | } diff --git a/vlib/v/checker/tests/voidptr_cast_to_ref_outside_unsafe_err.vv b/vlib/v/checker/tests/voidptr_cast_to_ref_outside_unsafe_err.vv new file mode 100644 index 000000000..fb55be84f --- /dev/null +++ b/vlib/v/checker/tests/voidptr_cast_to_ref_outside_unsafe_err.vv @@ -0,0 +1,5 @@ +fn main() { + some_ptr := voidptr(1234) + _ = &u32(some_ptr) + _ = &u32(voidptr(5678)) +} diff --git a/vlib/v/tests/fns/fixed_array_on_voidptr_test.v b/vlib/v/tests/fns/fixed_array_on_voidptr_test.v index f11262925..6b694d8e5 100644 --- a/vlib/v/tests/fns/fixed_array_on_voidptr_test.v +++ b/vlib/v/tests/fns/fixed_array_on_voidptr_test.v @@ -3,7 +3,7 @@ fn test_main() { b[0] = 1 b[1] = 2 mut a := unsafe { memdup(b, $if new_int ? && x64 { 16 } $else { 8 }) } - x := &int(a) + x := unsafe { &int(a) } unsafe { assert x[0] == 1 assert x[1] == 2 -- 2.39.5