From 82d3ac2ca3d3b5188c8789c3acbafa035f8e75ea Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 11 Mar 2026 14:09:12 +0300 Subject: [PATCH] checker: invalid memory access with array of empty interface return type (fixes #24864) --- vlib/v/checker/containers.v | 14 +++++- vlib/v/gen/c/infix.v | 10 +++- ...f_empty_interface_result_regression_test.v | 48 +++++++++++++++++++ 3 files changed, 68 insertions(+), 4 deletions(-) create mode 100644 vlib/v/tests/interfaces/array_of_empty_interface_result_regression_test.v diff --git a/vlib/v/checker/containers.v b/vlib/v/checker/containers.v index 63ccd548d..8d28d734f 100644 --- a/vlib/v/checker/containers.v +++ b/vlib/v/checker/containers.v @@ -857,8 +857,18 @@ fn (mut c Checker) check_append(mut node ast.InfixExpr, left_type ast.Type, righ } } } else { - // []Animal << []Cat - c.type_implements(c.table.value_type(right_type), left_value_type, right_pos) + if c.table.does_type_implement_interface(c.unwrap_generic(right_type), left_value_type) { + // `[]Any << []int` should append the array as a single interface value. + if c.type_implements(right_type, left_value_type, right_pos) { + if !right_type.is_any_kind_of_pointer() && !c.inside_unsafe + && right_sym.kind != .interface { + c.mark_as_referenced(mut &node.right, true) + } + } + } else { + // []Animal << []Cat + c.type_implements(c.table.value_type(right_type), left_value_type, right_pos) + } } return ast.void_type } else if left_value_sym.kind == .sum_type { diff --git a/vlib/v/gen/c/infix.v b/vlib/v/gen/c/infix.v index a4c0351cf..8a3d73c00 100644 --- a/vlib/v/gen/c/infix.v +++ b/vlib/v/gen/c/infix.v @@ -1110,11 +1110,18 @@ fn (mut g Gen) infix_expr_left_shift_op(node ast.InfixExpr) { // arr << val tmp_var := g.new_tmp_var() array_info := left.unaliased_sym.info as ast.Array + elem_sym := g.table.final_sym(array_info.elem_type) noscan := g.check_noscan(array_info.elem_type) elem_is_option := array_info.elem_type.has_flag(.option) + right_implements_elem_interface := elem_sym.kind == .interface + && g.table.does_type_implement_interface(g.unwrap_generic(node.right_type), array_info.elem_type) mut prevent_push_many := g.table.sumtype_has_variant(array_info.elem_type, node.right_type, false) - if prevent_push_many && node.right is ast.CallExpr { + if right_implements_elem_interface { + // `[]Any << []int` should append the array as one interface value. + prevent_push_many = true + } + if prevent_push_many && node.right is ast.CallExpr && !right_implements_elem_interface { // Allow concatenation for array-returning calls; avoids nesting for common builder APIs. prevent_push_many = false } @@ -1156,7 +1163,6 @@ fn (mut g Gen) infix_expr_left_shift_op(node ast.InfixExpr) { } else { // push a single element elem_type_str := g.styp(array_info.elem_type) - elem_sym := g.table.final_sym(array_info.elem_type) elem_is_array_var := !elem_is_option && elem_sym.kind in [.array, .array_fixed] && node.right is ast.Ident g.write('builtin__array_push${noscan}((array*)') diff --git a/vlib/v/tests/interfaces/array_of_empty_interface_result_regression_test.v b/vlib/v/tests/interfaces/array_of_empty_interface_result_regression_test.v new file mode 100644 index 000000000..4f6c430c5 --- /dev/null +++ b/vlib/v/tests/interfaces/array_of_empty_interface_result_regression_test.v @@ -0,0 +1,48 @@ +interface Value {} + +fn empty_interface_bytes() []u8 { + return [u8(1), 2, 3, 4] +} + +fn test_array_value_is_not_spread_into_empty_interface_array() { + mut values := []Value{} + values << empty_interface_bytes() + assert values.len == 1 + assert values[0] as []u8 == empty_interface_bytes() +} + +fn test_array_variable_value_is_not_spread_into_empty_interface_array() { + bytes := empty_interface_bytes() + mut values := []Value{} + values << bytes + assert values.len == 1 + assert values[0] as []u8 == bytes +} + +fn create_empty_interface_data() !(string, []Value) { + name := 'hello' + bytes := empty_interface_bytes() + flag := true + count := 7 + mut values := []Value{} + values << name + values << bytes + values << flag + values << count + return 'ok', values +} + +fn test_array_of_empty_interface_result_regression() { + status, values := create_empty_interface_data() or { panic(err) } + assert status == 'ok' + assert values.len == 4 + rendered := '${values}' + assert rendered.contains("Value('hello')") + assert rendered.contains('Value([1, 2, 3, 4])') + assert rendered.contains('Value(true)') + assert rendered.contains('Value(7)') + assert values[0] as string == 'hello' + assert values[1] as []u8 == [u8(1), 2, 3, 4] + assert values[2] as bool == true + assert values[3] as int == 7 +} -- 2.39.5