From e35a1b0e4642f4a8343a1dc22582321221564bee Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 11 Mar 2026 16:13:32 +0300 Subject: [PATCH] cgen: returned value differs from value to return (fixes #26346) --- vlib/v/gen/c/cgen.v | 42 +++++++++++++---- vlib/v/tests/interface_string_ref_arg_test.v | 49 ++++++++++++++++++-- 2 files changed, 80 insertions(+), 11 deletions(-) diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index 14cd88e62..cbb4ebd2e 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -3146,6 +3146,33 @@ fn (mut g Gen) write_sumtype_casting_fn(fun SumtypeCastingFn) { g.auto_fn_definitions << sb.str() } +fn (g &Gen) interface_expr_needs_heap(expr ast.Expr) bool { + match expr { + ast.Ident { + if expr.obj is ast.Var { + return !expr.obj.typ.is_ptr() && !expr.is_auto_heap() + } + return false + } + ast.SelectorExpr { + root := expr.root_ident() or { return false } + if root.obj is ast.Var { + return !root.obj.typ.is_ptr() && !root.is_auto_heap() + } + return false + } + ast.ParExpr { + return g.interface_expr_needs_heap(expr.expr) + } + ast.UnsafeExpr { + return g.interface_expr_needs_heap(expr.expr) + } + else { + return false + } + } +} + fn (mut g Gen) call_cfn_for_casting_expr(fname string, expr ast.Expr, exp ast.Type, got ast.Type, exp_styp string, got_is_ptr bool, got_is_fn bool, got_styp string) { mut rparen_n := 1 @@ -3153,6 +3180,7 @@ fn (mut g Gen) call_cfn_for_casting_expr(fname string, expr ast.Expr, exp ast.Ty is_not_ptr_and_fn := !got_is_ptr && !got_is_fn is_sumtype_cast := !got_is_fn && fname.contains('_to_sumtype_') + is_interface_cast := !got_is_fn && fname.contains('_to_Interface_') is_comptime_variant := is_not_ptr_and_fn && expr is ast.Ident && g.comptime.is_comptime_variant_var(expr) if exp.is_ptr() { @@ -3171,19 +3199,17 @@ fn (mut g Gen) call_cfn_for_casting_expr(fname string, expr ast.Expr, exp ast.Ty is_cast_fixed_array_init := expr is ast.CastExpr && (expr.expr is ast.ArrayInit && expr.expr.is_fixed) - is_value_to_interface_needing_heap := fname.contains('_to_Interface_') && expr is ast.Ident + is_primitive_to_interface := is_interface_cast && expr is ast.Ident && g.table.sym(got).kind in [.i8, .i16, .i32, .int, .i64, .isize, .u8, .u16, .u32, .u64, .usize, .f32, .f64, .bool, .rune, .string] - // Check if the expression is a function argument (local variable) that needs heap allocation - is_fn_arg := if expr is ast.Ident && expr.obj is ast.Var { - expr.obj.is_arg - } else { - false - } + // Interface casts must not store pointers into stack-rooted lvalues such as + // local variables or fields on by-value fn args (`p.email`). + is_stack_rooted_interface_expr := is_interface_cast && g.interface_expr_needs_heap(expr) + if !is_cast_fixed_array_init && (is_comptime_variant || !expr.is_lvalue() || (expr is ast.Ident && (expr.obj.is_simple_define_const() || (expr.obj is ast.Var && expr.obj.is_index_var))) - || is_value_to_interface_needing_heap || is_fn_arg) { + || is_primitive_to_interface || is_stack_rooted_interface_expr) { // Note: the `_to_sumtype_` family of functions do call memdup internally, making // another duplicate with the HEAP macro is redundant, so use ADDR instead: if expr.is_as_cast() { diff --git a/vlib/v/tests/interface_string_ref_arg_test.v b/vlib/v/tests/interface_string_ref_arg_test.v index 4e8baec5b..f6e1fa2cc 100644 --- a/vlib/v/tests/interface_string_ref_arg_test.v +++ b/vlib/v/tests/interface_string_ref_arg_test.v @@ -4,17 +4,43 @@ import arrays interface Value {} +struct Params { + email string +} + fn generate_params(s string) []Value { mut params := []Value{} params = arrays.concat(params, s) return params } -struct Params { +fn generate_selector_params(p Params) []Value { + mut params := []Value{} + params = arrays.concat(params, p.email) + return params +} + +fn generate_local_params() []Value { + s := 'info@peony.com' + mut params := []Value{} + params = arrays.concat(params, s) + return params +} + +fn generate_local_selector_params() []Value { + p := Params{ + email: 'info@peony.com' + } + mut params := []Value{} + params = arrays.concat(params, p.email) + return params +} + +struct OptionalParams { email ?string } -fn generate_optional_params(p Params) []Value { +fn generate_optional_params(p OptionalParams) []Value { mut params := []Value{} if email := p.email { params = arrays.concat(params, email) @@ -53,8 +79,25 @@ fn test_interface_string_ref_arg() { assert params == [Value('any_string')] } +fn test_interface_string_ref_local() { + params := generate_local_params() + assert params == [Value('info@peony.com')] +} + +fn test_interface_string_ref_selector_arg() { + params := generate_selector_params(Params{ + email: 'info@peony.com' + }) + assert params == [Value('info@peony.com')] +} + +fn test_interface_string_ref_selector_local() { + params := generate_local_selector_params() + assert params == [Value('info@peony.com')] +} + fn test_interface_string_ref_arg_from_option_unwrap_forwarded_through_variadic_call() { - params := generate_optional_params(Params{ + params := generate_optional_params(OptionalParams{ email: 'info@peony.com' }) mut tx := Tx{} -- 2.39.5