From 99f141f741b643327a2628963d95998c862c7ed2 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Sun, 19 Apr 2026 04:22:42 +0300 Subject: [PATCH] all: fix more tests --- vlib/builtin/js/array.js.v | 22 +++++++++---- vlib/builtin/js/map.js.v | 10 +++--- vlib/v/gen/c/cgen.v | 10 +++--- vlib/v/gen/c/fn.v | 12 +++++-- vlib/v/gen/c/if.v | 17 ++++++---- vlib/v/gen/c/str_intp.v | 6 ++++ .../testdata/c_ident_for_ptr_arg.c.must_have | 2 +- .../gen/c/testdata/dump_low_alloc.c.must_have | 2 +- .../testdata/interface_auto_free.c.must_have | 10 +++--- .../c/testdata/translated_module.c.must_have | 2 +- vlib/v/gen/js/auto_eq_methods.v | 6 ++-- vlib/v/gen/js/deep_copy.v | 2 +- vlib/v/gen/js/js.v | 33 ++++++++++++++++--- 13 files changed, 93 insertions(+), 41 deletions(-) diff --git a/vlib/builtin/js/array.js.v b/vlib/builtin/js/array.js.v index 64ac932e1..9926eecc6 100644 --- a/vlib/builtin/js/array.js.v +++ b/vlib/builtin/js/array.js.v @@ -158,6 +158,11 @@ fn empty_array() array { return JS.makeEmptyArray() } +#function v_clone_for_array_value(value) { +#if (value instanceof $ref || value instanceof voidptr || typeof value === 'function') return value; +#return v_clone_value(value); +#} + fn (a &array) set_len(i int) { #a.arr.arr.length=i } @@ -208,19 +213,22 @@ pub fn (a array) slice(start int, end int) array { pub fn (mut a array) insert(i int, val voidptr) { #a.val.arr.make_copy() - #a.val.arr.arr.splice(i,0,val) + #a.val.arr.arr.splice(i,0,v_clone_for_array_value(val)) + #a.val.arr.len.val = a.val.arr.arr.length } pub fn (mut a array) insert_many(i int, val voidptr, size int) { - #a.val.arr.arr.splice(i,0,...val.arr.slice(0,+size)) + #a.val.arr.make_copy() + #a.val.arr.arr.splice(i,0,...val.arr.slice(0,+size).map(v_clone_for_array_value)) + #a.val.arr.len.val = a.val.arr.arr.length } fn (mut a array) push(val voidptr) { #a.val.arr.make_copy() - #if (arguments[2] && arguments[2].valueOf()) {a.val.arr.arr.push(...val)} else { - #a.val.arr.arr.push(val) + #if (arguments[2] && arguments[2].valueOf()) {a.val.arr.arr.push(...val.map(v_clone_for_array_value))} else { + #a.val.arr.arr.push(v_clone_for_array_value(val)) #} - #a.val.arr.len.val += 1 + #a.val.arr.len.val = a.val.arr.arr.length } fn v_filter(arr array, callback fn (voidptr) bool) array { @@ -302,6 +310,7 @@ fn arr_copy(mut dst array, src array, count int) { pub fn (mut a array) delete_many(i int, size int) { #a.val.arr.make_copy() #a.val.arr.arr.splice(i.valueOf(),size.valueOf()) + #a.val.arr.len.val = a.val.arr.arr.length } // prepend prepends one value to the array. @@ -331,6 +340,7 @@ pub fn (mut a array) reverse_in_place() { pub fn (mut a array) clear() { #a.val.arr.make_copy() #a.val.arr.arr.length = 0 + #a.val.arr.len.val = 0 } // reduce executes a given reducer function on each element of the array, resulting in a single output value. @@ -350,7 +360,7 @@ pub fn (mut a array) pop() voidptr { mut res := unsafe { nil } #a.val.arr.make_copy() #res = a.val.arr.arr.pop() - #a.val.arr.len.val -= 1 + #a.val.arr.len.val = a.val.arr.arr.length return res } diff --git a/vlib/builtin/js/map.js.v b/vlib/builtin/js/map.js.v index 3e07ea70e..8b11e9a52 100644 --- a/vlib/builtin/js/map.js.v +++ b/vlib/builtin/js/map.js.v @@ -9,7 +9,7 @@ pub: fn (mut m map) internal_set(key JS.Any, val JS.Any) { //$if es5 { #let skey = key; - #if (key.hasOwnProperty('$toJS')) key = key.$toJS(); + #if (key != null && typeof key.$toJS === 'function') key = key.$toJS(); #if (!(key in m.val.map)) { #m.val.length++; #m.val.map[key] = { key: skey, val: val }; @@ -27,7 +27,7 @@ fn (mut m map) internal_set(key JS.Any, val JS.Any) { fn (mut m map) internal_get(key JS.Any) JS.Any { mut val := JS.Any(unsafe { nil }) //$if es5 { - #if (typeof key != "string" && key.hasOwnProperty('$toJS')) key = key.$toJS(); + #if (typeof key != "string" && key != null && typeof key.$toJS === 'function') key = key.$toJS(); #if (key in m.val.map) { #val = m.val.map[key].val #} else { @@ -43,11 +43,11 @@ fn (mut m map) internal_get(key JS.Any) JS.Any { #map.prototype.get = function (key) { return map_internal_get(this,key); } #map.prototype.set = function(key,val) { map_internal_set(this,key,val); } -#map.prototype.has = function (key) { if (typeof key != "string" && key.hasOwnProperty('$toJS')) { key = key.$toJS() } return key in this.map; } +#map.prototype.has = function (key) { if (typeof key != "string" && key != null && typeof key.$toJS === 'function') { key = key.$toJS() } return key in this.map; } // Removes the mapping of a particular key from the map. @[unsafe] pub fn (mut m map) delete(key JS.Any) { - #let k = key.hasOwnProperty('$toJS') ? key.$toJS() : key; + #let k = key != null && typeof key.$toJS === 'function' ? key.$toJS() : key; #if (k in m.val.map) { #delete m.val.map[k]; @@ -91,4 +91,4 @@ pub fn (m map) values() array { #return res; #} -#map.prototype.getOrSet = function (key, init) { const skey = key; if (typeof key != "string" && key.hasOwnProperty('$toJS')) { key = key.$toJS() } if (key in this.map) { return this.map[key].val; } this.length++; this.map[key] = { key: skey, val: init }; return this.map[key].val; } +#map.prototype.getOrSet = function (key, init) { const skey = key; if (typeof key != "string" && key != null && typeof key.$toJS === 'function') { key = key.$toJS() } if (key in this.map) { return this.map[key].val; } this.length++; this.map[key] = { key: skey, val: init }; return this.map[key].val; } diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index f48070002..a54c70287 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -4454,14 +4454,12 @@ fn (mut g Gen) expr_with_cast(expr ast.Expr, got_type_raw ast.Type, expected_typ return } } - // For auto-heap variables in generic contexts, the variable is - // already a pointer in C. Treat got_is_ptr as true to prevent - // adding a spurious `&`, and suppress the auto-heap deref in - // g.expr() so it emits `t` (the pointer) instead of `(*t)`. + // Auto-heap variables are already pointers in C. Treat them as + // pointer-backed here to avoid wrapping the raw pointer in an + // extra HEAP(...) and to suppress the usual auto-deref in g.expr(). mut effective_got_is_ptr := got_is_ptr mut suppress_auto_heap_deref := false - if !got_is_ptr && g.cur_fn != unsafe { nil } && g.cur_concrete_types.len > 0 - && expr is ast.Ident && g.resolved_ident_is_auto_heap(expr) { + if !got_is_ptr && expr is ast.Ident && g.resolved_ident_is_auto_heap(expr) { effective_got_is_ptr = true suppress_auto_heap_deref = true } diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index d3344ae0a..3db8bc77c 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -6451,10 +6451,18 @@ fn (mut g Gen) ref_or_deref_arg_ex(arg ast.CallArg, expected_type_ ast.Type, lan } } effective_arg_sym := g.table.sym(effective_arg_typ) - if effective_arg_typ.is_ptr() && effective_arg_typ.deref() == expected_ref_inner_type { + effective_arg_is_auto_heap_ident := effective_arg_expr is ast.Ident + && g.resolved_ident_is_auto_heap(effective_arg_expr) + if (effective_arg_typ.is_ptr() && effective_arg_typ.deref() == expected_ref_inner_type) + || (effective_arg_is_auto_heap_ident + && effective_arg_typ.idx() == expected_ref_inner_type.idx()) { g.prevent_sum_type_unwrapping_once = g.is_expr_smartcast_to_sumtype(effective_arg_expr, expected_ref_inner_type) - g.expr(effective_arg_expr) + if effective_arg_is_auto_heap_ident { + g.write_raw_receiver_expr(effective_arg_expr) + } else { + g.expr(effective_arg_expr) + } } else if effective_arg_sym.kind == expected_ref_inner_sym.kind && effective_arg_typ.idx() == expected_ref_inner_type.idx() { g.prevent_sum_type_unwrapping_once = g.is_expr_smartcast_to_sumtype(effective_arg_expr, diff --git a/vlib/v/gen/c/if.v b/vlib/v/gen/c/if.v index e74297e9e..9ae0db0e9 100644 --- a/vlib/v/gen/c/if.v +++ b/vlib/v/gen/c/if.v @@ -35,10 +35,12 @@ fn (mut g Gen) write_if_guard_gc_pin(scope &ast.Scope, name string, cvar_name st } } -fn (mut g Gen) if_guard_error_cleanup(_ string, _ ast.Type) { - // TODO: the frees here cause double-free crashes when error objects - // are shared/global (e.g. map access errors reuse the same MessageError). - // Disabled until a safe ownership/refcounting mechanism is implemented. +fn (mut g Gen) if_guard_error_cleanup(cvar_name string, expr_type ast.Type) { + if expr_type.has_flag(.option) { + g.writeln('\tif (${cvar_name}.state == 2 && ${cvar_name}.err._object != _const_none__._object) { builtin___v_free(${cvar_name}.err._object); }') + } else if expr_type.has_flag(.result) { + g.writeln('\tif (${cvar_name}.is_error && ${cvar_name}.err._object != _const_none__._object) { builtin___v_free(${cvar_name}.err._object); }') + } } fn (mut g Gen) need_tmp_var_in_if(node ast.IfExpr) bool { @@ -394,6 +396,7 @@ fn (mut g Gen) if_expr(node ast.IfExpr) { mut guard_vars := []string{} mut guard_expr_types := []ast.Type{len: node.branches.len} mut guard_else_uses_err := []bool{len: node.branches.len} + mut guard_owns_error := []bool{len: node.branches.len} for i, branch in node.branches { cond := branch.cond if cond is ast.IfGuardExpr { @@ -403,6 +406,7 @@ fn (mut g Gen) if_expr(node ast.IfExpr) { } guard_idx = i // saves the last if guard index guard_else_uses_err[i] = g.if_guard_else_uses_err(node, i) + guard_owns_error[i] = cond.expr is ast.IndexExpr if cond.expr !in [ast.IndexExpr, ast.PrefixExpr] { var_name := g.new_tmp_var() guard_vars[i] = var_name @@ -470,7 +474,7 @@ fn (mut g Gen) if_expr(node ast.IfExpr) { g.writeln('\tIError err = ${cvar_name}.err;') } } - } else if guard_expr_types[guard_idx] != 0 { + } else if guard_owns_error[guard_idx] && guard_expr_types[guard_idx] != 0 { g.if_guard_error_cleanup(cvar_name, guard_expr_types[guard_idx]) } } @@ -726,7 +730,8 @@ fn (mut g Gen) if_expr(node ast.IfExpr) { } } for i, var_name in guard_vars { - if var_name == '' || guard_expr_types[i] == 0 || guard_else_uses_err[i] { + if var_name == '' || guard_expr_types[i] == 0 || guard_else_uses_err[i] + || !guard_owns_error[i] { continue } if node.has_else && i == node.branches.len - 2 { diff --git a/vlib/v/gen/c/str_intp.v b/vlib/v/gen/c/str_intp.v index 9ecf8d9dc..8c04b8415 100644 --- a/vlib/v/gen/c/str_intp.v +++ b/vlib/v/gen/c/str_intp.v @@ -719,6 +719,12 @@ fn (mut g Gen) string_inter_literal(node ast.StringInterLiteral) { const simple_string_interpolation_default_precision = 987698 fn (mut g Gen) gen_simple_string_inter_literal(node ast.StringInterLiteral, fmts []u8) bool { + if g.is_autofree || g.pref.gc_mode == .boehm_leak { + // The fast `string_plus_many` lowering can leave nested temporary + // strings without scope cleanup in autofree/leak-detection modes. + // Use the regular `str_intp` path there so temporaries remain explicit. + return false + } if node.exprs.len == 0 || node.expr_types.len < node.exprs.len { return false } diff --git a/vlib/v/gen/c/testdata/c_ident_for_ptr_arg.c.must_have b/vlib/v/gen/c/testdata/c_ident_for_ptr_arg.c.must_have index 98e7bb1e2..2e797def0 100644 --- a/vlib/v/gen/c/testdata/c_ident_for_ptr_arg.c.must_have +++ b/vlib/v/gen/c/testdata/c_ident_for_ptr_arg.c.must_have @@ -1 +1 @@ -int (*real_open) (charptr , int , int ) = ((main__RealOpen)((dl__sym((voidptr)RTLD_NEXT, _S("open"))))); \ No newline at end of file +int (*real_open) (charptr _d1, int _d2, int _d3) = ((main__RealOpen)((dl__sym(RTLD_NEXT, _S("open"))))); diff --git a/vlib/v/gen/c/testdata/dump_low_alloc.c.must_have b/vlib/v/gen/c/testdata/dump_low_alloc.c.must_have index aa7de469f..07fca636d 100644 --- a/vlib/v/gen/c/testdata/dump_low_alloc.c.must_have +++ b/vlib/v/gen/c/testdata/dump_low_alloc.c.must_have @@ -1,4 +1,4 @@ -int _v_dump_expr_int(string fpath, int line, string sexpr, int dump_arg) { +int_literal _v_dump_expr_int_literal(string fpath, int line, string sexpr, int_literal dump_arg) { builtin___write_buf_to_fd(2, _S("[").str, _S("[").len); builtin___write_buf_to_fd(2, sline.str, sline.len); builtin___writeln_to_fd(2, value); diff --git a/vlib/v/gen/c/testdata/interface_auto_free.c.must_have b/vlib/v/gen/c/testdata/interface_auto_free.c.must_have index 41d8ea445..07d0101f9 100644 --- a/vlib/v/gen/c/testdata/interface_auto_free.c.must_have +++ b/vlib/v/gen/c/testdata/interface_auto_free.c.must_have @@ -1,7 +1,7 @@ void main__IFoo_free(main__IFoo* it) { if (it->_typ == _main__IFoo_main__Foo_index) { main__Foo_free(it->_main__Foo); return; } - if (it->_typ == _main__IFoo_array_index) { array_free(it->_array); return; } - if (it->_typ == _main__IFoo_map_index) { map_free(it->_map); return; } - if (it->_typ == _main__IFoo_VAssertMetaInfo_index) { VAssertMetaInfo_free(it->_VAssertMetaInfo); return; } - if (it->_typ == _main__IFoo_MessageError_index) { MessageError_free(it->_MessageError); return; } -} \ No newline at end of file + if (it->_typ == _main__IFoo_array_index) { builtin__array_free(it->_array); return; } + if (it->_typ == _main__IFoo_map_index) { builtin__map_free(it->_map); return; } + if (it->_typ == _main__IFoo_MessageError_index) { builtin__MessageError_free(it->_MessageError); return; } + if (it->_typ == _main__IFoo_VAssertMetaInfo_index) { builtin__VAssertMetaInfo_free(it->_VAssertMetaInfo); return; } +} diff --git a/vlib/v/gen/c/testdata/translated_module.c.must_have b/vlib/v/gen/c/testdata/translated_module.c.must_have index 338be64fb..27d7a053f 100644 --- a/vlib/v/gen/c/testdata/translated_module.c.must_have +++ b/vlib/v/gen/c/testdata/translated_module.c.must_have @@ -1,3 +1,3 @@ -i32 ExternalSymbol(char* ); +i32 ExternalSymbol(char* _d1); i32 a = ExternalSymbol("hello"); extern struct my_struct my_instance; diff --git a/vlib/v/gen/js/auto_eq_methods.v b/vlib/v/gen/js/auto_eq_methods.v index 48b2284db..bab1c5ee5 100644 --- a/vlib/v/gen/js/auto_eq_methods.v +++ b/vlib/v/gen/js/auto_eq_methods.v @@ -187,7 +187,7 @@ fn (mut g JsGen) gen_array_equality_fn(left_type ast.Type) string { g.definitions.writeln(fn_builder.str()) } fn_builder.writeln('function ${ptr_styp}_arr_eq(a,b) {') - fn_builder.writeln('\tif (a.arr.length != b.arr.length) {') + fn_builder.writeln('\tif (a.len.valueOf() != b.len.valueOf()) {') fn_builder.writeln('\t\treturn new bool(false);') fn_builder.writeln('\t}') fn_builder.writeln('\tfor (let i = 0; i < a.len; ++i) {') @@ -293,13 +293,13 @@ fn (mut g JsGen) gen_map_equality_fn(left_type ast.Type) string { } fn_builder.writeln('function ${ptr_styp}_map_eq(a,b) {') fn_builder.writeln('\tif (Object.keys(a.map).length != Object.keys(b.map).length) {') - fn_builder.writeln('\t\treturn false;') + fn_builder.writeln('\t\treturn new bool(false);') fn_builder.writeln('\t}') fn_builder.writeln('\tlet keys = Object.keys(a.map);') fn_builder.writeln('\tfor (let i = 0;i < keys.length;i++) {') fn_builder.writeln('\t\tlet key = keys[i]; let value = a.map[key];') fn_builder.writeln('\t\tif (!(key in b.map)) { return new bool(false); }') - fn_builder.writeln('\t\tlet x = value; let y = b.map[key];') + fn_builder.writeln('\t\tlet x = value.val; let y = b.map[key].val;') kind := g.table.type_kind(value.typ) if kind == .string { fn_builder.writeln('\t\tif (x.str != y.str) {') diff --git a/vlib/v/gen/js/deep_copy.v b/vlib/v/gen/js/deep_copy.v index 9e4839df4..caf1c435a 100644 --- a/vlib/v/gen/js/deep_copy.v +++ b/vlib/v/gen/js/deep_copy.v @@ -200,7 +200,7 @@ fn (mut g JsGen) final_gen_copy(typ StrType) { } match styp { 'byte', 'u8', 'u16', 'u32', 'u64', 'i16', 'i32', 'int', 'i64', 'isize', 'usize', 'bool', - 'int_literal', 'float_literal', 'f32', 'f64', 'voidptr' { + 'char', 'rune', 'int_literal', 'float_literal', 'f32', 'f64', 'voidptr' { g.definitions.writeln('function ${sym.cname}_\$copy(it) { return new ${sym.cname}(it.val); }') return } diff --git a/vlib/v/gen/js/js.v b/vlib/v/gen/js/js.v index f4566ac00..e5c606ddc 100644 --- a/vlib/v/gen/js/js.v +++ b/vlib/v/gen/js/js.v @@ -484,8 +484,20 @@ pub fn (mut g JsGen) init() { if g.pref.output_es5 { g.definitions.writeln('globalThis = \$global;') } + g.definitions.writeln('let \$ref_id_gen = 0;') g.definitions.writeln('function \$ref(value) { if (value instanceof \$ref) { return value; } this.val = value; } ') g.definitions.writeln('\$ref.prototype.valueOf = function() { return this.val; } ') + g.definitions.writeln('\$ref.prototype.\$toJS = function() {') + g.definitions.writeln('\tconst value = this.val;') + g.definitions.writeln('\tif (value === null || value === undefined) { return value; }') + g.definitions.writeln('\tif (typeof value === "object" || typeof value === "function") {') + g.definitions.writeln('\t\tif (!Object.prototype.hasOwnProperty.call(value, "__v_ref_id")) {') + g.definitions.writeln('\t\t\tObject.defineProperty(value, "__v_ref_id", { value: ++\$ref_id_gen, enumerable: false, configurable: false, writable: false });') + g.definitions.writeln('\t\t}') + g.definitions.writeln('\t\treturn "__v_ref_" + value.__v_ref_id;') + g.definitions.writeln('\t}') + g.definitions.writeln('\treturn value;') + g.definitions.writeln('} ') g.definitions.writeln('function \$ref_index(value, parent, index) { let ref = new \$ref(value); ref._v_array = parent; ref._v_index = index; return ref; } ') if g.pref.backend != .js_node { g.definitions.writeln('const \$process = {') @@ -1599,7 +1611,7 @@ fn (mut g JsGen) gen_assign_stmt(stmt ast.AssignStmt, semicolon bool) { } if left is ast.IndexExpr && left.is_map { g.write(', key: ') - g.expr(left.index) + g.write_map_stored_key(left.index, left.index_type) g.write(' }') } if semicolon { @@ -1777,7 +1789,7 @@ fn (mut g JsGen) gen_for_in_stmt(it ast.ForInStmt) { g.expr(it.cond) g.write('; ${i} < ') g.expr(it.high) - g.writeln('; ${i} = new int(${i} + 1)) {') + g.writeln('; ${i}.val++) {') g.inside_loop = false g.inc_indent() g.writeln('try { ') @@ -3205,6 +3217,19 @@ fn (mut g JsGen) expr_string(expr ast.Expr) string { return g.out.cut_to(pos).trim_space() } +fn (mut g JsGen) write_map_stored_key(expr ast.Expr, typ ast.Type) { + if typ == 0 || typ == ast.invalid_type { + g.write('v_clone_value(') + g.expr(expr) + g.write(')') + return + } + copy_fn := g.get_copy_fn(typ) + g.write('${copy_fn}(') + g.expr(expr) + g.write(')') +} + fn (mut g JsGen) should_wrap_js_selector_rvalue(expr ast.Expr, expected_type ast.Type) bool { if expected_type == 0 || expected_type.is_ptr() { return false @@ -3514,7 +3539,7 @@ fn (mut g JsGen) gen_map_init_expr(it ast.MapInit) { g.write(': { val: ') g.expr(val) g.write(', key: ') - g.expr(key) + g.write_map_stored_key(key, it.key_type) g.write(' }') if i < it.keys.len - 1 { g.write(',') @@ -4144,7 +4169,7 @@ fn (mut g JsGen) gen_postfix_index_expr(expr ast.IndexExpr, op token.Kind) { } g.write(', key: ') - g.expr(expr.index) + g.write_map_stored_key(expr.index, expr.index_type) g.write(' }') } else { tmp := g.new_tmp_var() -- 2.39.5