From fc6d5cd12ed7fd580ef8d19e49885e98d59b25be Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 15 Apr 2026 05:32:26 +0300 Subject: [PATCH] checker: fix TCP server socket file descriptor corruption when using GC (fixes #18263) --- vlib/v/checker/fn.v | 21 ++++++++ .../spawn_ref_arg_stack_escape_test.v | 53 +++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 vlib/v/tests/concurrency/spawn_ref_arg_stack_escape_test.v diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v index af7f92947..1a98be1de 100644 --- a/vlib/v/checker/fn.v +++ b/vlib/v/checker/fn.v @@ -3707,6 +3707,7 @@ fn (mut c Checker) spawn_expr(mut node ast.SpawnExpr) ast.Type { c.error('option handling cannot be done in `spawn` call. Do it when calling `.wait()`', node.call_expr.or_block.pos) } + c.mark_spawn_expr_ref_args(node.call_expr) // Make sure there are no mutable arguments for arg in node.call_expr.args { if arg.is_mut && !arg.typ.is_ptr() { @@ -3735,6 +3736,26 @@ fn (mut c Checker) spawn_expr(mut node ast.SpawnExpr) ast.Type { } } +fn (mut c Checker) mark_spawn_expr_ref_args(call ast.CallExpr) { + if call.is_method && call.receiver_type.is_ptr() { + c.mark_spawn_expr_ref_arg(call.left) + } + for arg in call.args { + c.mark_spawn_expr_ref_arg(arg.expr) + } +} + +fn (mut c Checker) mark_spawn_expr_ref_arg(expr ast.Expr) { + mut clean_expr := expr + clean_expr = clean_expr.remove_par() + if mut clean_expr is ast.PrefixExpr { + if clean_expr.op == .amp { + mut referenced_expr := clean_expr.right + c.mark_as_referenced(mut &referenced_expr, false) + } + } +} + fn (mut c Checker) thread_array_wait_return_type(thread_ret_type ast.Type) ast.Type { payload_type := thread_ret_type.clear_option_and_result() if payload_type == ast.void_type { diff --git a/vlib/v/tests/concurrency/spawn_ref_arg_stack_escape_test.v b/vlib/v/tests/concurrency/spawn_ref_arg_stack_escape_test.v new file mode 100644 index 000000000..18bbcd89a --- /dev/null +++ b/vlib/v/tests/concurrency/spawn_ref_arg_stack_escape_test.v @@ -0,0 +1,53 @@ +struct SpawnRefArgStackValue { + conn int +} + +struct SpawnRefReceiverStackValue { + conn int +} + +fn read_spawned_value(v &SpawnRefArgStackValue, expected int, start chan int) bool { + _ := <-start or { return false } + return v.conn == expected +} + +fn (v &SpawnRefReceiverStackValue) read_after_start(expected int, start chan int) bool { + _ := <-start or { return false } + return v.conn == expected +} + +fn test_spawn_ref_arg_from_stack_is_auto_heap_promoted() { + thread_count := 64 + start := chan int{cap: thread_count} + mut threads := []thread bool{cap: thread_count} + for i in 0 .. thread_count { + value := SpawnRefArgStackValue{ + conn: i + } + threads << spawn read_spawned_value(&value, i, start) + } + for _ in 0 .. thread_count { + start <- 1 + } + for i, th in threads { + assert th.wait(), 'spawned thread ${i} observed a reused stack value' + } +} + +fn test_spawn_ref_receiver_from_stack_is_auto_heap_promoted() { + thread_count := 64 + start := chan int{cap: thread_count} + mut threads := []thread bool{cap: thread_count} + for i in 0 .. thread_count { + value := SpawnRefReceiverStackValue{ + conn: i + } + threads << spawn (&value).read_after_start(i, start) + } + for _ in 0 .. thread_count { + start <- 1 + } + for i, th in threads { + assert th.wait(), 'spawned method receiver ${i} observed a reused stack value' + } +} -- 2.39.5