From 17540949fb5d3e80bcf47c7763719d0a6d56ada6 Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Fri, 6 Feb 2026 06:03:12 +0200 Subject: [PATCH] cgen: fix -autofree used for code doing "string ${f(@VEXE)} interpolation" (#26521) --- .gitignore | 14 ++++++++-- vlib/toml/ast/types.v | 2 +- vlib/v/gen/c/cgen.v | 5 ++-- vlib/v/gen/c/fn.v | 26 +++++++++++-------- vlib/v/gen/c/str_intp.v | 26 +++++++++++++++++++ .../autofree_method_chain.c.must_have | 4 +-- ...ofree_nested_interpolation_nix.c.must_have | 6 +++++ .../autofree_nested_interpolation_nix.out | 2 ++ .../autofree_nested_interpolation_nix.vv | 9 +++++++ 9 files changed, 75 insertions(+), 19 deletions(-) create mode 100644 vlib/v/gen/c/testdata/autofree_nested_interpolation_nix.c.must_have create mode 100644 vlib/v/gen/c/testdata/autofree_nested_interpolation_nix.out create mode 100644 vlib/v/gen/c/testdata/autofree_nested_interpolation_nix.vv diff --git a/.gitignore b/.gitignore index 65b5110d3..069b39e53 100644 --- a/.gitignore +++ b/.gitignore @@ -139,9 +139,9 @@ vls.log # ignore Intellij files .idea/ /*.iml + +#ignore generated files: wasm.v -TAGS -tags # ignore large GTK *.gir files Gtk-4.0.gir @@ -150,6 +150,11 @@ Gtk-4.0.gir vlib/builtin/js/*.js vlib/v/tests/*.js +#ignore tags indexes, used by emacs/vim: +ETAGS +TAGS +tags + # ignore the old regexp based v.ctags file (people using universal ctags < 6.1.0, can still benefit from them) .ctags.d/v.ctags @@ -160,3 +165,8 @@ bench/vectors/obj autofuzz.log .project.gf .aider* + +#ignore common file names for bugs/reproductions +bug* +issue* + diff --git a/vlib/toml/ast/types.v b/vlib/toml/ast/types.v index 30d8174b6..39cf1c89a 100644 --- a/vlib/toml/ast/types.v +++ b/vlib/toml/ast/types.v @@ -13,7 +13,7 @@ pub type Key = Bare | Bool | Null | Number | Quoted // str returns the string representation of the key. This is implemented // by all the variants of Key. pub fn (k Key) str() string { - return k.text + return k.text.clone() } // Value is a sumtype representing all possible value types diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index 6b8912ffe..f110060de 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -3868,14 +3868,13 @@ fn (mut g Gen) expr(node_ ast.Expr) { } g.call_expr(node) if g.is_autofree && !g.is_builtin_mod && !g.is_js_call && g.strs_to_free0.len == 0 - && !g.inside_lambda { + && !g.is_autofree_tmp && !g.inside_lambda { // if len != 0, that means we are handling call expr inside call expr (arg) // and it'll get messed up here, since it's handled recursively in autofree_call_pregen() // so just skip it g.autofree_call_pregen(node) if g.strs_to_free0.len > 0 { - g.insert_at(stmt_before_call_expr_pos, g.strs_to_free0.join('\n') + - '/* inserted before */') + g.insert_at(stmt_before_call_expr_pos, g.strs_to_free0.join('\n')) } g.strs_to_free0 = [] } diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index 3b6d3d92d..935ca3bd6 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -2405,7 +2405,7 @@ fn (mut g Gen) autofree_call_pregen(node ast.CallExpr) { g.is_autofree = old_is_autofree g.is_autofree_tmp = false s += expr_code - s += ';// new af2 pre' + s += ';' g.strs_to_free0 << s // This tmp arg var will be freed with the rest of the vars at the end of the scope. } @@ -2630,18 +2630,22 @@ fn (mut g Gen) call_args(node ast.CallExpr) { // some c fn definitions dont have args (cfns.v) or are not updated in checker // when these are fixed we wont need this check if i < expected_types.len { - if use_tmp_var_autofree { - if arg.is_tmp_autofree { // && !g.is_js_call { - // We saved expressions in temp variables so that they can be freed later. - // `foo(str + str2) => x := str + str2; foo(x); x.free()` - // g.write('_arg_expr_${g.called_fn_name}_${i}') - // Use these variables here. - fn_name := node.name.replace('.', '_') - // name := '_tt${g.tmp_count_af}_arg_expr_${fn_name}_${i}' - name := '_arg_expr_${fn_name}_${i + 1}_${node.pos.pos}' + mut wrote_tmp_arg := false + if use_tmp_var_autofree && arg.is_tmp_autofree { // && !g.is_js_call { + // We saved expressions in temp variables so that they can be freed later. + // `foo(str + str2) => x := str + str2; foo(x); x.free()` + // g.write('_arg_expr_${g.called_fn_name}_${i}') + // Use these variables here. + fn_name := node.name.replace('.', '_') + // name := '_tt${g.tmp_count_af}_arg_expr_${fn_name}_${i}' + name := '_arg_expr_${fn_name}_${i + 1}_${node.pos.pos}' + scope := g.file.scope.innermost(node.pos.pos) + if !g.is_autofree_tmp || scope.known_var(name) { g.write('/*autofree arg*/' + name) + wrote_tmp_arg = true } - } else { + } + if !wrote_tmp_arg { g.ref_or_deref_arg(arg, expected_types[i], node.language, is_smartcast) } } else { diff --git a/vlib/v/gen/c/str_intp.v b/vlib/v/gen/c/str_intp.v index a1045195c..8a7b996bc 100644 --- a/vlib/v/gen/c/str_intp.v +++ b/vlib/v/gen/c/str_intp.v @@ -208,6 +208,32 @@ fn (mut g Gen) str_val(node ast.StringInterLiteral, i int, fmts []u8) { g.expr(expr) g.write(')') } else { + if g.is_autofree_tmp && g.is_autofree + && expr !in [ast.Ident, ast.StringLiteral, ast.SelectorExpr, ast.ComptimeSelector] { + if expr is ast.CallExpr { + old_is_autofree_tmp := g.is_autofree_tmp + g.autofree_call_pregen(expr) + g.is_autofree_tmp = old_is_autofree_tmp + } + tmp := g.new_tmp_var() + tmp_pos := expr.pos() + mut scope := g.file.scope.innermost(tmp_pos.pos) + scope.register(ast.Var{ + name: tmp + typ: ast.string_type + is_autofree_tmp: true + pos: tmp_pos + }) + pos_before := g.out.len + if expr.is_auto_deref_var() && fmt != `p` { + g.write('*') + } + g.expr(expr) + expr_code := g.out.cut_to(pos_before).trim_space() + g.strs_to_free0 << 'string ${tmp} = ${expr_code};' + g.write(tmp) + return + } if expr.is_auto_deref_var() && fmt != `p` { g.write('*') } diff --git a/vlib/v/gen/c/testdata/autofree_method_chain.c.must_have b/vlib/v/gen/c/testdata/autofree_method_chain.c.must_have index 15bd7342a..6e23e33f0 100644 --- a/vlib/v/gen/c/testdata/autofree_method_chain.c.must_have +++ b/vlib/v/gen/c/testdata/autofree_method_chain.c.must_have @@ -1,5 +1,5 @@ void main__Test_set_tags(main__Test* t, string tags) { -string _arg_expr_split_0_169 = builtin__string_trim_space(builtin__string_replace(builtin__string_to_lower(tags), _S(","), _S(" ")));// new af2 pre/* inserted before */ +string _arg_expr_split_0_169 = builtin__string_trim_space(builtin__string_replace(builtin__string_to_lower(tags), _S(","), _S(" "))); t->tags = builtin__string_split(/*af receiver arg*/_arg_expr_split_0_169, _S(" ")); builtin__string_free(&_arg_expr_split_0_169); // autofreed var main false } @@ -8,4 +8,4 @@ VV_LOC void main__main(void) { main__Test_set_tags(&test, _S("Hello, World")); string _t1 = Array_string_str(test.tags); builtin__println(_t1); builtin__string_free(&_t1); ; -} \ No newline at end of file +} diff --git a/vlib/v/gen/c/testdata/autofree_nested_interpolation_nix.c.must_have b/vlib/v/gen/c/testdata/autofree_nested_interpolation_nix.c.must_have new file mode 100644 index 000000000..17e8d912f --- /dev/null +++ b/vlib/v/gen/c/testdata/autofree_nested_interpolation_nix.c.must_have @@ -0,0 +1,6 @@ +string _arg_expr_os_execute_1_ += builtin__str_intp +os__Result res = os__execute(/*autofree arg*/_arg_expr_os_execute_1_ +string _arg_expr_println_1_ += builtin__string_trim_space(res.output); +builtin__println(/*autofree arg*/_arg_expr_println_1_ diff --git a/vlib/v/gen/c/testdata/autofree_nested_interpolation_nix.out b/vlib/v/gen/c/testdata/autofree_nested_interpolation_nix.out new file mode 100644 index 000000000..7dcea96d4 --- /dev/null +++ b/vlib/v/gen/c/testdata/autofree_nested_interpolation_nix.out @@ -0,0 +1,2 @@ +0 +123 diff --git a/vlib/v/gen/c/testdata/autofree_nested_interpolation_nix.vv b/vlib/v/gen/c/testdata/autofree_nested_interpolation_nix.vv new file mode 100644 index 000000000..355360519 --- /dev/null +++ b/vlib/v/gen/c/testdata/autofree_nested_interpolation_nix.vv @@ -0,0 +1,9 @@ +// vtest vflags: -autofree +import os + +fn main() { + name := ' println(123)' + res := os.execute('${os.quoted_path(@VEXE)} -e "${name[2..]}" ') + println(res.exit_code) + println(res.output.trim_space()) +} -- 2.39.5