From 6e63a22212c65a49734ed499ab6d92fef84ead48 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Thu, 23 Apr 2026 18:38:26 +0300 Subject: [PATCH] checker: fix Modifying an immutable pointer indirectly is allowed (fixes #12899) --- vlib/v/checker/checker.v | 44 ++++++++++++++++--- ...gn_immutable_reference_call_result_err.out | 6 +++ ...n_immutable_reference_struct_field_err.out | 2 +- .../assign_immutable_reference_var_err.out | 7 +++ ...all_result_aliases_immutable_value_err.out | 4 +- .../immutable_pointer_selector_field_err.out | 7 +++ .../immutable_pointer_selector_field_err.vv | 22 ++++++++++ .../tests/unsafe_fixed_array_assign.out | 6 +++ 8 files changed, 88 insertions(+), 10 deletions(-) create mode 100644 vlib/v/checker/tests/immutable_pointer_selector_field_err.out create mode 100644 vlib/v/checker/tests/immutable_pointer_selector_field_err.vv diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 56b568e69..8491a6cdf 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -1420,11 +1420,20 @@ fn (mut c Checker) type_has_mutable_aliasing(typ ast.Type) bool { fn (mut c Checker) expr_is_mutable_alias_of_immutable_source(expr ast.Expr) bool { match expr { ast.Ident { - if expr.obj is ast.Var && expr.obj.is_mut - && expr.obj.expr in [ast.Ident, ast.CallExpr, ast.CastExpr, ast.AsCast, ast.ParExpr, ast.UnsafeExpr] - && c.type_has_mutable_aliasing(expr.obj.typ) { - return c.expr_is_immutable_source(expr.obj.expr) - || c.expr_is_mutable_alias_of_immutable_source(expr.obj.expr) + if expr.obj is ast.Var && expr.obj.is_mut && c.type_has_mutable_aliasing(expr.obj.typ) { + match expr.obj.expr { + ast.Ident, ast.CallExpr, ast.CastExpr, ast.AsCast, ast.ParExpr, ast.UnsafeExpr { + return c.expr_is_immutable_source(expr.obj.expr) + || c.expr_is_mutable_alias_of_immutable_source(expr.obj.expr) + } + ast.SelectorExpr { + if expr.obj.typ.is_ptr() { + return c.expr_is_immutable_source(expr.obj.expr) + || c.expr_is_mutable_alias_of_immutable_source(expr.obj.expr) + } + } + else {} + } } return false } @@ -1704,12 +1713,12 @@ fn (mut c Checker) fail_if_immutable_to_mutable(left_type ast.Type, right_type a if field_info.is_mut { if init_field.expr is ast.Ident && !init_field.expr.is_mut() && init_field.typ.is_ptr() { - c.note('`${init_field.expr.name}` is immutable, cannot have a mutable reference to an immutable object', + c.error('`${init_field.expr.name}` is immutable, cannot have a mutable reference to an immutable object', init_field.pos) } else if init_field.expr is ast.PrefixExpr { if init_field.expr.op == .amp && init_field.expr.right is ast.Ident && !init_field.expr.right.is_mut() { - c.note('`${init_field.expr.right.name}` is immutable, cannot have a mutable reference to an immutable object', + c.error('`${init_field.expr.right.name}` is immutable, cannot have a mutable reference to an immutable object', init_field.expr.right.pos) } } @@ -1920,6 +1929,13 @@ fn (mut c Checker) fail_if_immutable(mut expr ast.Expr) (string, token.Pos) { c.error('field `${expr.field_name}` of struct `${type_str}` is immutable', expr.pos) } + if field_info.is_mut && expr.expr_type.is_ptr() && !c.inside_unsafe + && c.expr_is_mutable_alias_of_immutable_source(expr.expr) { + expr_str := ast.Expr(expr).str() + c.error('`${expr_str}` aliases mutable data from an immutable value', + expr.pos) + return '', expr.pos + } to_lock, pos = c.fail_if_immutable(mut expr.expr) } if to_lock != '' { @@ -1940,6 +1956,13 @@ fn (mut c Checker) fail_if_immutable(mut expr ast.Expr) (string, token.Pos) { expr.pos) return '', expr.pos } + if expr.expr_type.is_ptr() && !c.inside_unsafe + && c.expr_is_mutable_alias_of_immutable_source(expr.expr) { + expr_str := ast.Expr(expr).str() + c.error('`${expr_str}` aliases mutable data from an immutable value', + expr.pos) + return '', expr.pos + } c.fail_if_immutable(mut expr.expr) } .sum_type { @@ -1955,6 +1978,13 @@ fn (mut c Checker) fail_if_immutable(mut expr ast.Expr) (string, token.Pos) { expr.pos) return '', expr.pos } + if expr.expr_type.is_ptr() && !c.inside_unsafe + && c.expr_is_mutable_alias_of_immutable_source(expr.expr) { + expr_str := ast.Expr(expr).str() + c.error('`${expr_str}` aliases mutable data from an immutable value', + expr.pos) + return '', expr.pos + } c.fail_if_immutable(mut expr.expr) } .array, .string { diff --git a/vlib/v/checker/tests/assign_immutable_reference_call_result_err.out b/vlib/v/checker/tests/assign_immutable_reference_call_result_err.out index 8fa1d5c53..74ef64e22 100644 --- a/vlib/v/checker/tests/assign_immutable_reference_call_result_err.out +++ b/vlib/v/checker/tests/assign_immutable_reference_call_result_err.out @@ -5,3 +5,9 @@ vlib/v/checker/tests/assign_immutable_reference_call_result_err.vv:15:16: notice | ~~ 16 | x.name = 'bar' 17 | } +vlib/v/checker/tests/assign_immutable_reference_call_result_err.vv:16:4: error: `x.name` aliases mutable data from an immutable value + 14 | ja := User{name: 'foo'} + 15 | mut x := rere(ja) + 16 | x.name = 'bar' + | ~~~~ + 17 | } diff --git a/vlib/v/checker/tests/assign_immutable_reference_struct_field_err.out b/vlib/v/checker/tests/assign_immutable_reference_struct_field_err.out index f452494ab..20e460cfd 100644 --- a/vlib/v/checker/tests/assign_immutable_reference_struct_field_err.out +++ b/vlib/v/checker/tests/assign_immutable_reference_struct_field_err.out @@ -1,4 +1,4 @@ -vlib/v/checker/tests/assign_immutable_reference_struct_field_err.vv:14:12: notice: `parent` is immutable, cannot have a mutable reference to an immutable object +vlib/v/checker/tests/assign_immutable_reference_struct_field_err.vv:14:12: error: `parent` is immutable, cannot have a mutable reference to an immutable object 12 | // taking a reference of `parent` and putting it under a `mut:` struct field 13 | mut child := Tree{ 14 | parent: &parent diff --git a/vlib/v/checker/tests/assign_immutable_reference_var_err.out b/vlib/v/checker/tests/assign_immutable_reference_var_err.out index 1e309ea8d..2beffed4f 100644 --- a/vlib/v/checker/tests/assign_immutable_reference_var_err.out +++ b/vlib/v/checker/tests/assign_immutable_reference_var_err.out @@ -5,3 +5,10 @@ vlib/v/checker/tests/assign_immutable_reference_var_err.vv:8:11: notice: `x` is | ^ 9 | m.value = 42 10 | } +vlib/v/checker/tests/assign_immutable_reference_var_err.vv:9:4: error: `m.value` aliases mutable data from an immutable value + 7 | fn y(x &Foo) { + 8 | mut m := x + 9 | m.value = 42 + | ~~~~~ + 10 | } + 11 | diff --git a/vlib/v/checker/tests/call_result_aliases_immutable_value_err.out b/vlib/v/checker/tests/call_result_aliases_immutable_value_err.out index b678d79cf..ce679f824 100644 --- a/vlib/v/checker/tests/call_result_aliases_immutable_value_err.out +++ b/vlib/v/checker/tests/call_result_aliases_immutable_value_err.out @@ -12,9 +12,9 @@ vlib/v/checker/tests/call_result_aliases_immutable_value_err.vv:19:2: error: `id | ~~~~~~~ 20 | 21 | ja := User{ -vlib/v/checker/tests/call_result_aliases_immutable_value_err.vv:24:7: error: `ja` is immutable, cannot have a mutable reference to an immutable object +vlib/v/checker/tests/call_result_aliases_immutable_value_err.vv:24:11: error: `rere(ja).name` aliases mutable data from an immutable value 22 | name: 'foo' 23 | } 24 | rere(ja).name = 'bar' - | ~~ + | ~~~~ 25 | } diff --git a/vlib/v/checker/tests/immutable_pointer_selector_field_err.out b/vlib/v/checker/tests/immutable_pointer_selector_field_err.out new file mode 100644 index 000000000..9bf5e608b --- /dev/null +++ b/vlib/v/checker/tests/immutable_pointer_selector_field_err.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/immutable_pointer_selector_field_err.vv:13:4: error: `x.str` aliases mutable data from an immutable value + 11 | fn fn_test(args MyOptions) { + 12 | mut x := args.data + 13 | x.str = 'why is this possible?' + | ~~~ + 14 | } + 15 | diff --git a/vlib/v/checker/tests/immutable_pointer_selector_field_err.vv b/vlib/v/checker/tests/immutable_pointer_selector_field_err.vv new file mode 100644 index 000000000..ab3442152 --- /dev/null +++ b/vlib/v/checker/tests/immutable_pointer_selector_field_err.vv @@ -0,0 +1,22 @@ +struct AnotherStruct { +mut: + str string +} + +@[params] +struct MyOptions { + data &AnotherStruct +} + +fn fn_test(args MyOptions) { + mut x := args.data + x.str = 'why is this possible?' +} + +fn main() { + fn_test( + data: &AnotherStruct{ + str: 'test' + } + ) +} diff --git a/vlib/v/checker/tests/unsafe_fixed_array_assign.out b/vlib/v/checker/tests/unsafe_fixed_array_assign.out index 616355c55..f29d16ba1 100644 --- a/vlib/v/checker/tests/unsafe_fixed_array_assign.out +++ b/vlib/v/checker/tests/unsafe_fixed_array_assign.out @@ -12,3 +12,9 @@ vlib/v/checker/tests/unsafe_fixed_array_assign.vv:10:7: error: assignment from o | ~~ 11 | b[0].num = 0 12 | println(a) +vlib/v/checker/tests/unsafe_fixed_array_assign.vv:11:6: error: `b[0].num` aliases mutable data from an immutable value + 9 | a := [&box]! + 10 | mut b := a + 11 | b[0].num = 0 + | ~~~ + 12 | println(a) -- 2.39.5