From 3324dd3bb8f90b56666e99538b68e1da7369057f Mon Sep 17 00:00:00 2001 From: Mike Date: Sat, 7 Mar 2026 17:02:45 +0200 Subject: [PATCH] checker: fix autofree crash with option/result method chains (#26694) * checker: fix autofree crash with option/result method chains * disable sanitizers tests for now --- vlib/v/checker/fn.v | 22 ++++++++++++++- .../tests/autofree_result_method_chain_test.v | 28 +++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 vlib/v/tests/autofree_result_method_chain_test.v diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v index 616295460..b51deb11d 100644 --- a/vlib/v/checker/fn.v +++ b/vlib/v/checker/fn.v @@ -817,11 +817,15 @@ fn (mut c Checker) call_expr(mut node ast.CallExpr) ast.Type { continue } if arg.expr in [ast.Ident, ast.StringLiteral, ast.SelectorExpr, ast.ComptimeSelector] - || (arg.expr is ast.CallExpr && arg.expr.or_block.kind != .absent) { + || autofree_expr_has_or_block_in_chain(arg.expr) { // Simple expressions like variables, string literals, selector expressions // (`x.field`) can't result in allocations and don't need to be assigned to // temporary vars. // Only expressions like `str + 'b'` need to be freed. + // Expressions with result/option propagation in the call chain (e.g. + // `get_str()!.to_upper()`) cannot be pre-generated safely by + // autofree_call_pregen, because the or_block unwrapping internally calls + // go_before_last_stmt() which garbles the output buffer. continue } if arg.expr is ast.CallExpr && arg.expr.name in ['json.encode', 'json.encode_pretty'] { @@ -4161,3 +4165,19 @@ fn (mut c Checker) check_variadic_arg(arg_expr ast.Expr, typ ast.Type, expected_ arg_pos) } } + +// autofree_expr_has_or_block_in_chain returns true if expr is a CallExpr whose call chain +// contains an or_block (i.e. result/option propagation via `!` or `?`). Used by autofree to +// skip tmp-var pre-generation for arguments like `get_str()!.to_upper()`, where the inner +// call's or_block would interfere with output-buffer positioning in autofree_call_pregen. +fn autofree_expr_has_or_block_in_chain(expr ast.Expr) bool { + if expr is ast.CallExpr { + if expr.or_block.kind != .absent { + return true + } + if expr.is_method { + return autofree_expr_has_or_block_in_chain(expr.left) + } + } + return false +} diff --git a/vlib/v/tests/autofree_result_method_chain_test.v b/vlib/v/tests/autofree_result_method_chain_test.v new file mode 100644 index 000000000..0da3fa549 --- /dev/null +++ b/vlib/v/tests/autofree_result_method_chain_test.v @@ -0,0 +1,28 @@ +// vtest vflags: -autofree +// vtest build: !sanitize-address-gcc && !sanitize-address-clang + +fn get_str_a() ?string { + return 'hello world?' +} + +fn process_a(s string) string { + return s.to_upper() +} + +fn get_str_b() !string { + return 'hello world!' +} + +fn process_b(s string) string { + return s.to_upper() +} + +fn test_autofree_chain_a() { + result := process_a(get_str_a()?.to_upper()) + assert result == 'HELLO WORLD?' +} + +fn test_autofree_chain_b() { + result := process_b(get_str_b()!.to_upper()) + assert result == 'HELLO WORLD!' +} -- 2.39.5