From efbdca122103eade349594ef2c686269d4024fa9 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 15 Apr 2026 15:48:24 +0300 Subject: [PATCH] cgen: fix memory leaks when using `if var := some_map[type]` (fixes #19454) --- vlib/v/gen/c/if.v | 52 +++++++++++++++++-- ..._guard_map_missing_key_cleanup.c.must_have | 2 + .../if_guard_map_missing_key_cleanup.vv | 10 ++++ 3 files changed, 60 insertions(+), 4 deletions(-) create mode 100644 vlib/v/gen/c/testdata/if_guard_map_missing_key_cleanup.c.must_have create mode 100644 vlib/v/gen/c/testdata/if_guard_map_missing_key_cleanup.vv diff --git a/vlib/v/gen/c/if.v b/vlib/v/gen/c/if.v index d65dbd192..73439af8a 100644 --- a/vlib/v/gen/c/if.v +++ b/vlib/v/gen/c/if.v @@ -18,12 +18,39 @@ fn (mut g Gen) if_guard_var_needs_gc_pin(scope &ast.Scope, name string) bool { return false } +fn (g &Gen) if_guard_else_uses_err(node ast.IfExpr, branch_idx int) bool { + if !node.has_else || branch_idx != node.branches.len - 2 { + return false + } + else_branch := node.branches[branch_idx + 1] + if err_var := else_branch.scope.find_var('err') { + return err_var.is_used + } + return false +} + fn (mut g Gen) write_if_guard_gc_pin(scope &ast.Scope, name string, cvar_name string) { if g.if_guard_var_needs_gc_pin(scope, name) { g.writeln('\tGC_reachable_here(&${cvar_name});') } } +fn (mut g Gen) if_guard_error_cleanup(var_name string, typ ast.Type) { + cvar_name := c_name(var_name) + if typ.has_flag(.result) { + g.writeln('\tif (${cvar_name}.is_error) { builtin___v_free(${cvar_name}.err._object); }') + return + } + if typ.has_flag(.option) { + tmp_op := if var_name in g.tmp_var_ptr || typ.has_flag(.option_mut_param_t) { + '->' + } else { + '.' + } + g.writeln('\tif (${cvar_name}${tmp_op}state != 0) { builtin___v_free(${cvar_name}${tmp_op}err._object); }') + } +} + fn (mut g Gen) need_tmp_var_in_if(node ast.IfExpr) bool { if node.is_expr && (g.inside_ternary == 0 || g.is_assign_lhs) { if g.is_autofree || node.typ.has_option_or_result() || node.is_comptime || g.is_assign_lhs { @@ -374,6 +401,8 @@ fn (mut g Gen) if_expr(node ast.IfExpr) { mut is_guard := false mut guard_idx := 0 mut guard_vars := []string{} + mut guard_expr_types := []ast.Type{len: node.branches.len} + mut guard_else_uses_err := []bool{len: node.branches.len} for i, branch in node.branches { cond := branch.cond if cond is ast.IfGuardExpr { @@ -382,6 +411,7 @@ fn (mut g Gen) if_expr(node ast.IfExpr) { guard_vars = []string{len: node.branches.len} } guard_idx = i // saves the last if guard index + guard_else_uses_err[i] = g.if_guard_else_uses_err(node, i) if cond.expr !in [ast.IndexExpr, ast.PrefixExpr] { var_name := g.new_tmp_var() guard_vars[i] = var_name @@ -442,11 +472,15 @@ fn (mut g Gen) if_expr(node ast.IfExpr) { g.writeln('{') // define `err` for the last branch after a `if val := opt {...}' guard if is_guard && guard_idx == i - 1 { - if err_var := branch.scope.find_var('err') { - if err_var.is_used { - cvar_name := guard_vars[guard_idx] - g.writeln('\tIError err = ${cvar_name}.err;') + cvar_name := guard_vars[guard_idx] + if guard_else_uses_err[guard_idx] { + if err_var := branch.scope.find_var('err') { + if err_var.is_used { + g.writeln('\tIError err = ${cvar_name}.err;') + } } + } else if guard_expr_types[guard_idx] != 0 { + g.if_guard_error_cleanup(cvar_name, guard_expr_types[guard_idx]) } } } else if branch.cond is ast.IfGuardExpr { @@ -476,6 +510,7 @@ fn (mut g Gen) if_expr(node ast.IfExpr) { } } } + guard_expr_types[i] = guard_expr_type if var_name == '' { short_opt = true // we don't need a further tmp, so use the one we'll get later var_name = g.new_tmp_var() @@ -699,6 +734,15 @@ fn (mut g Gen) if_expr(node ast.IfExpr) { g.set_current_pos_as_last_stmt_pos() } } + for i, var_name in guard_vars { + if var_name == '' || guard_expr_types[i] == 0 || guard_else_uses_err[i] { + continue + } + if node.has_else && i == node.branches.len - 2 { + continue + } + g.if_guard_error_cleanup(var_name, guard_expr_types[i]) + } if needs_tmp_var { // Close the extra scopes opened between branches to isolate // condition-evaluation variables from earlier branches' gotos. diff --git a/vlib/v/gen/c/testdata/if_guard_map_missing_key_cleanup.c.must_have b/vlib/v/gen/c/testdata/if_guard_map_missing_key_cleanup.c.must_have new file mode 100644 index 000000000..576f87dd0 --- /dev/null +++ b/vlib/v/gen/c/testdata/if_guard_map_missing_key_cleanup.c.must_have @@ -0,0 +1,2 @@ +map key does not exist +builtin___v_free(_t diff --git a/vlib/v/gen/c/testdata/if_guard_map_missing_key_cleanup.vv b/vlib/v/gen/c/testdata/if_guard_map_missing_key_cleanup.vv new file mode 100644 index 000000000..04b869de5 --- /dev/null +++ b/vlib/v/gen/c/testdata/if_guard_map_missing_key_cleanup.vv @@ -0,0 +1,10 @@ +fn has_value(items map[int]bool, key int) bool { + if value := items[key] { + return value + } + return false +} + +fn main() { + assert !has_value({}, 1) +} -- 2.39.5