From 6c9b3c8ebf7b7478e66c27ca67dc44b86f7f2f08 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 25 Mar 2026 16:42:18 +0300 Subject: [PATCH] checker: fix null pointer constructed and dereferenced in safe code (fixes #20497) --- vlib/v/checker/checker.v | 70 ++++++++++++------- .../tests/globals/cast_expr_T_type_err.out | 14 ++-- ...pointer_cast_number_outside_unsafe_err.out | 32 +++++++++ .../pointer_cast_number_outside_unsafe_err.vv | 10 +++ ...struct_ptr_cast_int_outside_unsafe_err.out | 18 ----- .../tests/struct_ptr_cast_zero_err.out | 18 ----- .../tests/unsafe_generic_call_test.out | 7 -- .../checker/tests/unsafe_generic_call_test.vv | 2 +- 8 files changed, 95 insertions(+), 76 deletions(-) create mode 100644 vlib/v/checker/tests/pointer_cast_number_outside_unsafe_err.out create mode 100644 vlib/v/checker/tests/pointer_cast_number_outside_unsafe_err.vv diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 6e5eccb16..56a761816 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -4557,6 +4557,31 @@ fn (mut c Checker) smartcast_wrapper_field_name(wrapper_type ast.Type, to_type a return none } +fn integer_literal_from_pointer_cast_expr(expr ast.Expr) ?ast.IntegerLiteral { + return match expr { + ast.IntegerLiteral { + expr + } + ast.Ident { + match expr.obj { + ast.GlobalField, ast.ConstField, ast.Var { + if expr.obj.expr is ast.IntegerLiteral { + expr.obj.expr + } else { + none + } + } + else { + none + } + } + } + else { + none + } + } +} + fn (mut c Checker) cast_expr(mut node ast.CastExpr) ast.Type { // Given: `Outside( Inside(xyz) )`, // node.expr_type: `Inside` @@ -4616,6 +4641,8 @@ fn (mut c Checker) cast_expr(mut node ast.CastExpr) ast.Type { } final_to_is_ptr := to_type.is_ptr() || final_to_type.is_ptr() + number_to_type_ref_cast_outside_unsafe := from_type.is_number() && to_type.is_ptr() + && !c.inside_unsafe && !c.pref.translated && !c.file.is_translated c.markused_castexpr(mut node, to_type, mut final_to_sym) if to_type.has_flag(.result) { c.error('casting to Result type is forbidden', node.pos) @@ -4724,29 +4751,14 @@ fn (mut c Checker) cast_expr(mut node ast.CastExpr) ast.Type { if from_sym.info is ast.Alias { from_type = from_sym.info.parent_type.derive_add_muls(from_type) } - if mut node.expr is ast.IntegerLiteral { - if node.expr.val.int() == 0 && !c.pref.translated && !c.file.is_translated { - c.error('cannot null cast a struct pointer, use &${to_sym.name}(unsafe { nil })', + if int_lit := integer_literal_from_pointer_cast_expr(node.expr) { + if int_lit.val.int() == 0 && number_to_type_ref_cast_outside_unsafe { + tt := c.table.type_to_str(to_type) + c.error('cannot null cast a struct pointer, use ${tt}(unsafe { nil })', node.pos) - } else if !c.inside_unsafe && !c.pref.translated && !c.file.is_translated { + } else if number_to_type_ref_cast_outside_unsafe { c.error('cannot cast int to a struct pointer outside `unsafe`', node.pos) } - } else if mut node.expr is ast.Ident { - match mut node.expr.obj { - ast.GlobalField, ast.ConstField, ast.Var { - if mut node.expr.obj.expr is ast.IntegerLiteral { - if node.expr.obj.expr.val.int() == 0 && !c.pref.translated - && !c.file.is_translated { - c.error('cannot null cast a struct pointer, use &${to_sym.name}(unsafe { nil })', - node.pos) - } else if !c.inside_unsafe && !c.pref.translated && !c.file.is_translated { - c.error('cannot cast int to a struct pointer outside `unsafe`', - node.pos) - } - } - } - else {} - } } if from_type == ast.voidptr_type_idx && !c.inside_unsafe && !c.pref.translated && !c.file.is_translated { @@ -4880,14 +4892,22 @@ fn (mut c Checker) cast_expr(mut node ast.CastExpr) ast.Type { // if from_type == ast.voidptr_type_idx && !c.inside_unsafe && !c.pref.translated // Do not allow `&u8(unsafe { nil })` etc, force nil or voidptr cast - if from_type.is_number() && to_type.is_ptr() && !c.inside_unsafe && !c.pref.translated - && !c.file.is_translated && to_sym.kind != .sum_type { + if number_to_type_ref_cast_outside_unsafe && !(to_sym.kind == .struct && final_to_is_ptr) + && c.table.sym(to_type.idx_type()).kind != .placeholder { if from_sym.language != .c { ne_name := node.expr.str() if !ne_name.starts_with('C.') { - // TODO make an error - c.warn('cannot cast a number to a type reference, use `nil` or a voidptr cast first: `&Type(voidptr(123))`', - node.pos) + tt := c.table.type_to_str(to_type) + if int_lit := integer_literal_from_pointer_cast_expr(node.expr) { + if int_lit.val.int() == 0 { + c.error('cannot null cast a pointer, use ${tt}(unsafe { nil })', + node.pos) + } else { + c.error('cannot cast a number to `${tt}` outside `unsafe`', node.pos) + } + } else { + c.error('cannot cast a number to `${tt}` outside `unsafe`', node.pos) + } } } } diff --git a/vlib/v/checker/tests/globals/cast_expr_T_type_err.out b/vlib/v/checker/tests/globals/cast_expr_T_type_err.out index 788f708b5..fad64b895 100644 --- a/vlib/v/checker/tests/globals/cast_expr_T_type_err.out +++ b/vlib/v/checker/tests/globals/cast_expr_T_type_err.out @@ -1,10 +1,3 @@ -vlib/v/checker/tests/globals/cast_expr_T_type_err.vv:5:8: warning: cannot cast a number to a type reference, use `nil` or a voidptr cast first: `&Type(voidptr(123))` - 3 | __global ( - 4 | fo = A(0) - 5 | fo1 = &A(0) - | ~~~~~ - 6 | ) - 7 | vlib/v/checker/tests/globals/cast_expr_T_type_err.vv:4:7: error: unknown type `A` 2 | 3 | __global ( @@ -19,3 +12,10 @@ vlib/v/checker/tests/globals/cast_expr_T_type_err.vv:5:8: error: unknown type `A | ~~~~~ 6 | ) 7 | +vlib/v/checker/tests/globals/cast_expr_T_type_err.vv:5:8: error: cannot null cast a pointer, use &A(unsafe { nil }) + 3 | __global ( + 4 | fo = A(0) + 5 | fo1 = &A(0) + | ~~~~~ + 6 | ) + 7 | diff --git a/vlib/v/checker/tests/pointer_cast_number_outside_unsafe_err.out b/vlib/v/checker/tests/pointer_cast_number_outside_unsafe_err.out new file mode 100644 index 000000000..a112f20c9 --- /dev/null +++ b/vlib/v/checker/tests/pointer_cast_number_outside_unsafe_err.out @@ -0,0 +1,32 @@ +vlib/v/checker/tests/pointer_cast_number_outside_unsafe_err.vv:6:5: error: cannot null cast a pointer, use &int(unsafe { nil }) + 4 | value := 1 + 5 | + 6 | _ = &int(0) + | ~~~~~~~ + 7 | _ = &int(zero) + 8 | _ = &int(one) +vlib/v/checker/tests/pointer_cast_number_outside_unsafe_err.vv:7:5: error: cannot null cast a pointer, use &int(unsafe { nil }) + 5 | + 6 | _ = &int(0) + 7 | _ = &int(zero) + | ~~~~~~~~~~ + 8 | _ = &int(one) + 9 | _ = &int(value) +vlib/v/checker/tests/pointer_cast_number_outside_unsafe_err.vv:8:5: error: cannot cast a number to `&int` outside `unsafe` + 6 | _ = &int(0) + 7 | _ = &int(zero) + 8 | _ = &int(one) + | ~~~~~~~~~ + 9 | _ = &int(value) + 10 | _ = &&int(0) +vlib/v/checker/tests/pointer_cast_number_outside_unsafe_err.vv:9:5: error: cannot cast a number to `&int` outside `unsafe` + 7 | _ = &int(zero) + 8 | _ = &int(one) + 9 | _ = &int(value) + | ~~~~~~~~~~~ + 10 | _ = &&int(0) +vlib/v/checker/tests/pointer_cast_number_outside_unsafe_err.vv:10:5: error: cannot null cast a pointer, use &&int(unsafe { nil }) + 8 | _ = &int(one) + 9 | _ = &int(value) + 10 | _ = &&int(0) + | ~~~~~~~~ diff --git a/vlib/v/checker/tests/pointer_cast_number_outside_unsafe_err.vv b/vlib/v/checker/tests/pointer_cast_number_outside_unsafe_err.vv new file mode 100644 index 000000000..de131290c --- /dev/null +++ b/vlib/v/checker/tests/pointer_cast_number_outside_unsafe_err.vv @@ -0,0 +1,10 @@ +const zero = 0 +const one = 1 + +value := 1 + +_ = &int(0) +_ = &int(zero) +_ = &int(one) +_ = &int(value) +_ = &&int(0) diff --git a/vlib/v/checker/tests/struct_ptr_cast_int_outside_unsafe_err.out b/vlib/v/checker/tests/struct_ptr_cast_int_outside_unsafe_err.out index 58c3d85d6..896d2f6fe 100644 --- a/vlib/v/checker/tests/struct_ptr_cast_int_outside_unsafe_err.out +++ b/vlib/v/checker/tests/struct_ptr_cast_int_outside_unsafe_err.out @@ -1,21 +1,3 @@ -vlib/v/checker/tests/struct_ptr_cast_int_outside_unsafe_err.vv:7:5: warning: cannot cast a number to a type reference, use `nil` or a voidptr cast first: `&Type(voidptr(123))` - 5 | a := 1 - 6 | - 7 | _ = &Context(a) - | ~~~~~~~~~~~ - 8 | _ = &Context(b) - 9 | _ = &Context(1) -vlib/v/checker/tests/struct_ptr_cast_int_outside_unsafe_err.vv:8:5: warning: cannot cast a number to a type reference, use `nil` or a voidptr cast first: `&Type(voidptr(123))` - 6 | - 7 | _ = &Context(a) - 8 | _ = &Context(b) - | ~~~~~~~~~~~ - 9 | _ = &Context(1) -vlib/v/checker/tests/struct_ptr_cast_int_outside_unsafe_err.vv:9:5: warning: cannot cast a number to a type reference, use `nil` or a voidptr cast first: `&Type(voidptr(123))` - 7 | _ = &Context(a) - 8 | _ = &Context(b) - 9 | _ = &Context(1) - | ~~~~~~~~~~~ vlib/v/checker/tests/struct_ptr_cast_int_outside_unsafe_err.vv:7:5: error: cannot cast int to a struct pointer outside `unsafe` 5 | a := 1 6 | diff --git a/vlib/v/checker/tests/struct_ptr_cast_zero_err.out b/vlib/v/checker/tests/struct_ptr_cast_zero_err.out index a9aa9cc25..d7d9d4dc2 100644 --- a/vlib/v/checker/tests/struct_ptr_cast_zero_err.out +++ b/vlib/v/checker/tests/struct_ptr_cast_zero_err.out @@ -1,21 +1,3 @@ -vlib/v/checker/tests/struct_ptr_cast_zero_err.vv:7:5: warning: cannot cast a number to a type reference, use `nil` or a voidptr cast first: `&Type(voidptr(123))` - 5 | b := 0 - 6 | - 7 | _ = &Context(0) - | ~~~~~~~~~~~ - 8 | _ = &Context(a) - 9 | _ = &Context(b) -vlib/v/checker/tests/struct_ptr_cast_zero_err.vv:8:5: warning: cannot cast a number to a type reference, use `nil` or a voidptr cast first: `&Type(voidptr(123))` - 6 | - 7 | _ = &Context(0) - 8 | _ = &Context(a) - | ~~~~~~~~~~~ - 9 | _ = &Context(b) -vlib/v/checker/tests/struct_ptr_cast_zero_err.vv:9:5: warning: cannot cast a number to a type reference, use `nil` or a voidptr cast first: `&Type(voidptr(123))` - 7 | _ = &Context(0) - 8 | _ = &Context(a) - 9 | _ = &Context(b) - | ~~~~~~~~~~~ vlib/v/checker/tests/struct_ptr_cast_zero_err.vv:7:5: error: cannot null cast a struct pointer, use &Context(unsafe { nil }) 5 | b := 0 6 | diff --git a/vlib/v/checker/tests/unsafe_generic_call_test.out b/vlib/v/checker/tests/unsafe_generic_call_test.out index a7926f895..1d2ab5b40 100644 --- a/vlib/v/checker/tests/unsafe_generic_call_test.out +++ b/vlib/v/checker/tests/unsafe_generic_call_test.out @@ -1,8 +1 @@ -vlib/v/checker/tests/unsafe_generic_call_test.vv:8:15: warning: cannot cast a number to a type reference, use `nil` or a voidptr cast first: `&Type(voidptr(123))` - 6 | - 7 | fn test_main() { - 8 | pointers := [&int(1234)] - | ~~~~~~~~~~ - 9 | length := 1 - 10 | array := unsafe { arrays.carray_to_varray[&Blah](pointers, length) } [&Blah{}] diff --git a/vlib/v/checker/tests/unsafe_generic_call_test.vv b/vlib/v/checker/tests/unsafe_generic_call_test.vv index 4cf2af741..fc986953a 100644 --- a/vlib/v/checker/tests/unsafe_generic_call_test.vv +++ b/vlib/v/checker/tests/unsafe_generic_call_test.vv @@ -5,7 +5,7 @@ import arrays struct Blah {} fn test_main() { - pointers := [&int(1234)] + pointers := unsafe { [&int(1234)] } length := 1 array := unsafe { arrays.carray_to_varray[&Blah](pointers, length) } println(array) -- 2.39.5