From 24d157205bce89f0c32515f7ddce4ccf7baa1b2d Mon Sep 17 00:00:00 2001 From: Felipe Pena Date: Wed, 20 Mar 2024 19:24:38 -0300 Subject: [PATCH] cgen: force C struct types which does not implement str() to be passed as ptr (#21054) --- vlib/builtin/wchar/wchar.c.v | 6 +- vlib/v/ast/types.v | 12 ++++ vlib/v/gen/c/auto_str_methods.v | 80 ++++++++++++++++------- vlib/v/gen/c/cgen.v | 1 - vlib/v/gen/c/dumpexpr.v | 14 +++- vlib/v/gen/c/str.v | 14 +++- vlib/v/gen/c/str_intp.v | 1 - vlib/v/tests/c_structs/cstruct.h | 8 +++ vlib/v/tests/c_structs/cstruct_str_test.v | 50 ++++++++++++++ 9 files changed, 155 insertions(+), 31 deletions(-) create mode 100644 vlib/v/tests/c_structs/cstruct_str_test.v diff --git a/vlib/builtin/wchar/wchar.c.v b/vlib/builtin/wchar/wchar.c.v index 7c3eec645c..87e55680ce 100644 --- a/vlib/builtin/wchar/wchar.c.v +++ b/vlib/builtin/wchar/wchar.c.v @@ -30,7 +30,11 @@ pub fn (a Character) == (b Character) bool { // to_rune creates a V rune, given a Character @[inline] pub fn (c Character) to_rune() rune { - return unsafe { *(&rune(&c)) } + $if windows { + return unsafe { *(&rune(&c)) } & 0xFFFF + } $else { + return unsafe { *(&rune(&c)) } + } } // from_rune creates a Character, given a V rune diff --git a/vlib/v/ast/types.v b/vlib/v/ast/types.v index 929dfe5e7e..da9637120b 100644 --- a/vlib/v/ast/types.v +++ b/vlib/v/ast/types.v @@ -934,6 +934,15 @@ pub fn (t &TypeSymbol) is_array_fixed() bool { } } +pub fn (t &TypeSymbol) is_c_struct() bool { + if t.info is Struct { + return t.language == .c + } else if t.info is Alias { + return global_table.final_sym(t.info.parent_type).is_c_struct() + } + return false +} + pub fn (t &TypeSymbol) is_array_fixed_ret() bool { if t.info is ArrayFixed { return t.info.is_fn_ret @@ -1707,6 +1716,9 @@ pub fn (t &TypeSymbol) str_method_info() (bool, bool, int) { if nr_args > 0 { expects_ptr = sym_str_method.params[0].typ.is_ptr() } + } else { + // C Struct which does not implement str() are passed as pointer to handle incomplete type + expects_ptr = t.is_c_struct() } return has_str_method, expects_ptr, nr_args } diff --git a/vlib/v/gen/c/auto_str_methods.v b/vlib/v/gen/c/auto_str_methods.v index 52a8a2c85c..9f8fd13bf9 100644 --- a/vlib/v/gen/c/auto_str_methods.v +++ b/vlib/v/gen/c/auto_str_methods.v @@ -248,19 +248,25 @@ fn (mut g Gen) gen_str_for_result(typ ast.Type, styp string, str_fn_name string) fn (mut g Gen) gen_str_for_alias(info ast.Alias, styp string, str_fn_name string) { parent_str_fn_name := g.get_str_fn(info.parent_type) - _, str_method_expects_ptr, _ := g.table.sym(info.parent_type).str_method_info() + parent_sym := g.table.sym(info.parent_type) + _, str_method_expects_ptr, _ := parent_sym.str_method_info() $if trace_autostr ? { eprintln('> gen_str_for_alias: ${parent_str_fn_name} | ${styp} | ${str_fn_name}') } mut clean_type_v_type_name := util.strip_main_name(styp.replace('__', '.')) - g.definitions.writeln('static string ${str_fn_name}(${styp} it); // auto') - g.auto_str_funcs.writeln('static string ${str_fn_name}(${styp} it) { return indent_${str_fn_name}(it, 0); }') - g.definitions.writeln('static string indent_${str_fn_name}(${styp} it, int indent_count); // auto') - g.auto_str_funcs.writeln('static string indent_${str_fn_name}(${styp} it, int indent_count) {') + + is_c_struct := parent_sym.is_c_struct() && str_method_expects_ptr + arg_def := if is_c_struct { '${styp}* it' } else { '${styp} it' } + + g.definitions.writeln('static string ${str_fn_name}(${arg_def}); // auto') + g.auto_str_funcs.writeln('static string ${str_fn_name}(${arg_def}) { return indent_${str_fn_name}(it, 0); }') + g.definitions.writeln('static string indent_${str_fn_name}(${arg_def}, int indent_count); // auto') + g.auto_str_funcs.writeln('static string indent_${str_fn_name}(${arg_def}, int indent_count) {') g.auto_str_funcs.writeln('\tstring indents = string_repeat(_SLIT(" "), indent_count);') if str_method_expects_ptr { - g.auto_str_funcs.writeln('\tstring tmp_ds = ${parent_str_fn_name}(&it);') + it_arg := if is_c_struct { 'it' } else { '&it' } + g.auto_str_funcs.writeln('\tstring tmp_ds = ${parent_str_fn_name}(${it_arg});') } else { deref, _ := deref_kind(str_method_expects_ptr, info.parent_type.is_ptr(), info.parent_type) g.auto_str_funcs.writeln('\tstring tmp_ds = ${parent_str_fn_name}(${deref}it);') @@ -882,14 +888,17 @@ fn (mut g Gen) gen_str_for_struct(info ast.Struct, lang ast.Language, styp strin eprintln('> gen_str_for_struct: ${info.parent_type.debug()} | ${styp} | ${str_fn_name}') } // _str() functions should have a single argument, the indenting ones take 2: - g.definitions.writeln('static string ${str_fn_name}(${styp} it); // auto') - g.auto_str_funcs.writeln('static string ${str_fn_name}(${styp} it) { return indent_${str_fn_name}(it, 0);}') - g.definitions.writeln('static string indent_${str_fn_name}(${styp} it, int indent_count); // auto') + is_c_struct := lang == .c + arg_def := if is_c_struct { '${styp}* it' } else { '${styp} it' } + g.definitions.writeln('static string ${str_fn_name}(${arg_def}); // auto') + g.auto_str_funcs.writeln('static string ${str_fn_name}(${arg_def}) { return indent_${str_fn_name}(it, 0);}') + g.definitions.writeln('static string indent_${str_fn_name}(${arg_def}, int indent_count); // auto') mut fn_builder := strings.new_builder(512) defer { g.auto_fn_definitions << fn_builder.str() } - fn_builder.writeln('static string indent_${str_fn_name}(${styp} it, int indent_count) {') + fn_builder.writeln('static string indent_${str_fn_name}(${arg_def}, int indent_count) {') + clean_struct_v_type_name := if info.is_anon { 'struct ' } else { util.strip_main_name(typ_str) } // generate ident / indent length = 4 spaces if info.fields.len == 0 { @@ -997,18 +1006,20 @@ fn (mut g Gen) gen_str_for_struct(info ast.Struct, lang ast.Language, styp strin field_styp_fn_name, field.name, sym_has_str_method, str_method_expects_ptr) ftyp_nr_muls := field.typ.nr_muls() field_name := if lang == .c { field.name } else { c_name(field.name) } + op := if is_c_struct { '->' } else { '.' } + it_field_name := 'it${op}${field_name}' if ftyp_nr_muls > 1 || field.typ in ast.cptr_types { if is_opt_field { } else { - func = '(voidptr) it.${field.name}' + func = '(voidptr) ${it_field_name}' caller_should_free = false } } else if ftyp_noshared.is_ptr() { // reference types can be "nil" if ftyp_noshared.has_flag(.option) { - funcprefix += 'isnil(&it.${field_name}) || isnil(&it.${field_name}.data)' + funcprefix += 'isnil(&${it_field_name}) || isnil(&${it_field_name}.data)' } else { - funcprefix += 'isnil(it.${field_name})' + funcprefix += 'isnil(${it_field_name})' } funcprefix += ' ? _SLIT("nil") : ' // struct, floats and ints have a special case through the _str function @@ -1031,9 +1042,9 @@ fn (mut g Gen) gen_str_for_struct(info ast.Struct, lang ast.Language, styp strin if is_field_array { if is_opt_field { arr_styp := g.base_type(field.typ) - fn_body.write_string('it.${field_name}.state != 2 && (*(${arr_styp}*)it.${field_name}.data).len > 0 ? ${funcprefix}_SLIT("[]") : ${funcprefix}_SLIT("[]")') + fn_body.write_string('${it_field_name}.state != 2 && (*(${arr_styp}*)${it_field_name}.data).len > 0 ? ${funcprefix}_SLIT("[]") : ${funcprefix}_SLIT("[]")') } else { - fn_body.write_string('it.${field_name}.len > 0 ? ${funcprefix}_SLIT("[]") : ${funcprefix}_SLIT("[]")') + fn_body.write_string('${it_field_name}.len > 0 ? ${funcprefix}_SLIT("[]") : ${funcprefix}_SLIT("[]")') } } else { fn_body.write_string('${funcprefix}_SLIT("")') @@ -1065,6 +1076,25 @@ fn (mut g Gen) gen_str_for_struct(info ast.Struct, lang ast.Language, styp strin fn_body.writeln('\t}));') } +// c_struct_ptr handles the C struct argument for .str() method +@[inline] +pub fn c_struct_ptr(sym &ast.TypeSymbol, typ ast.Type, expects_ptr bool) string { + if sym.is_c_struct() { + if typ.has_flag(.option) { + return '' + } + if typ.nr_muls() >= 1 { + if expects_ptr { + return '*'.repeat(typ.nr_muls() - 1) + } else { + return '*'.repeat(typ.nr_muls()) + } + } + return if expects_ptr { '&' } else { '' } + } + return '' +} + fn struct_auto_str_func(sym &ast.TypeSymbol, lang ast.Language, _field_type ast.Type, fn_name string, field_name string, has_custom_str bool, expects_ptr bool) (string, bool) { $if trace_autostr ? { eprintln('> struct_auto_str_func: ${sym.name} | field_type.debug() | ${fn_name} | ${field_name} | ${has_custom_str} | ${expects_ptr}') @@ -1073,13 +1103,15 @@ fn struct_auto_str_func(sym &ast.TypeSymbol, lang ast.Language, _field_type ast. sufix := if field_type.has_flag(.shared_f) { '->val' } else { '' } deref, _ := deref_kind(expects_ptr, field_type.is_ptr(), field_type) final_field_name := if lang == .c { field_name } else { c_name(field_name) } + op := if lang == .c { '->' } else { '.' } + prefix := if sym.is_c_struct() { c_struct_ptr(sym, _field_type, expects_ptr) } else { deref } if sym.kind == .enum_ { - return '${fn_name}(${deref}(it.${final_field_name}))', true + return '${fn_name}(${deref}(it${op}${final_field_name}))', true } else if _field_type.has_flag(.option) || should_use_indent_func(sym.kind) { - obj := '${deref}it.${final_field_name}${sufix}' + obj := '${prefix}it${op}${final_field_name}${sufix}' if has_custom_str { if sym.kind == .interface_ && (sym.info as ast.Interface).defines_method('str') { - iface_obj := 'it.${final_field_name}${sufix}' + iface_obj := '${prefix}it${op}${final_field_name}${sufix}' dot := if field_type.is_ptr() { '->' } else { '.' } return '${fn_name.trim_string_right('_str')}_name_table[${iface_obj}${dot}_typ]._method_str(${iface_obj}${dot}_object)', true } @@ -1087,24 +1119,24 @@ fn struct_auto_str_func(sym &ast.TypeSymbol, lang ast.Language, _field_type ast. } return 'indent_${fn_name}(${obj}, indent_count + 1)', true } else if sym.kind in [.array, .array_fixed, .map, .sum_type] { - obj := '${deref}it.${final_field_name}${sufix}' + obj := '${prefix}it${op}${final_field_name}${sufix}' if has_custom_str { return '${fn_name}(${obj})', true } return 'indent_${fn_name}(${obj}, indent_count + 1)', true } else if sym.kind == .function { - obj := '${deref}it.${final_field_name}${sufix}' + obj := '${deref}it${op}${final_field_name}${sufix}' return '${fn_name}(${obj})', true } else if sym.kind == .chan { - return '${fn_name}(${deref}it.${final_field_name}${sufix})', true + return '${fn_name}(${deref}it${op}${final_field_name}${sufix})', true } else if sym.kind == .thread { - return '${fn_name}(${deref}it.${final_field_name}${sufix})', false + return '${fn_name}(${deref}it${op}${final_field_name}${sufix})', false } else { mut method_str := '' if !field_type.is_ptr() && field_type.has_option_or_result() { - method_str = '(*(${sym.name}*)it.${final_field_name}.data)' + method_str = '(*(${sym.name}*)it${op}${final_field_name}.data)' } else { - method_str = 'it.${final_field_name}' + method_str = 'it${op}${final_field_name}' } if sym.kind == .bool { return '${method_str} ? _SLIT("true") : _SLIT("false")', false diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index 2fff17aa6d..43f4d3cd25 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -2458,7 +2458,6 @@ fn (mut g Gen) expr_with_cast(expr ast.Expr, got_type_raw ast.Type, expected_typ lock g.referenced_fns { g.referenced_fns[fname] = true } - fname = '/*${exp_sym}*/${fname}' if exp_sym.info.is_generic { fname = g.generic_fn_name(exp_sym.info.concrete_types, fname) } diff --git a/vlib/v/gen/c/dumpexpr.v b/vlib/v/gen/c/dumpexpr.v index 6139b6a35c..f46a679b6c 100644 --- a/vlib/v/gen/c/dumpexpr.v +++ b/vlib/v/gen/c/dumpexpr.v @@ -181,11 +181,21 @@ fn (mut g Gen) dump_expr_definitions() { surrounder.add('\tstring value = isnil(&dump_arg.data) ? _SLIT("nil") : ${to_string_fn_name}(${deref}dump_arg);', '\tstring_free(&value);') } else { - surrounder.add('\tstring value = (dump_arg == NULL) ? _SLIT("nil") : ${to_string_fn_name}(${deref}dump_arg);', + prefix := if dump_sym.is_c_struct() { + c_struct_ptr(dump_sym, dump_type, str_method_expects_ptr) + } else { + deref + } + surrounder.add('\tstring value = (dump_arg == NULL) ? _SLIT("nil") : ${to_string_fn_name}(${prefix}dump_arg);', '\tstring_free(&value);') } } else { - surrounder.add('\tstring value = ${to_string_fn_name}(${deref}dump_arg);', + prefix := if dump_sym.is_c_struct() { + c_struct_ptr(dump_sym, dump_type, str_method_expects_ptr) + } else { + deref + } + surrounder.add('\tstring value = ${to_string_fn_name}(${prefix}dump_arg);', '\tstring_free(&value);') } surrounder.add(' diff --git a/vlib/v/gen/c/str.v b/vlib/v/gen/c/str.v index 7c499269bf..803ee873b9 100644 --- a/vlib/v/gen/c/str.v +++ b/vlib/v/gen/c/str.v @@ -151,7 +151,7 @@ fn (mut g Gen) gen_expr_to_string(expr ast.Expr, etype ast.Type) { } g.write('${str_fn_name}(') if str_method_expects_ptr && !is_ptr { - if is_dump_expr { + if is_dump_expr || (g.pref.ccompiler_type != .tinyc && expr is ast.CallExpr) { g.write('ADDR(${g.typ(typ)}, ') defer { g.write(')') @@ -162,7 +162,13 @@ fn (mut g Gen) gen_expr_to_string(expr ast.Expr, etype ast.Type) { } else if is_ptr && typ.has_flag(.option) { g.write('*(${g.typ(typ)}*)&') } else if !str_method_expects_ptr && !is_shared && (is_ptr || is_var_mut) { - g.write('*'.repeat(typ.nr_muls())) + if sym.is_c_struct() { + g.write(c_struct_ptr(sym, typ, str_method_expects_ptr)) + } else { + g.write('*'.repeat(typ.nr_muls())) + } + } else if sym.is_c_struct() { + g.write(c_struct_ptr(sym, typ, str_method_expects_ptr)) } if expr is ast.ArrayInit { if expr.is_fixed { @@ -199,6 +205,10 @@ fn (mut g Gen) gen_expr_to_string(expr ast.Expr, etype ast.Type) { g.write('&') } else if (!str_method_expects_ptr && is_ptr && !is_shared) || is_var_mut { g.write('*') + } else { + if sym.is_c_struct() { + g.write(c_struct_ptr(sym, typ, str_method_expects_ptr)) + } } g.expr_with_cast(expr, typ, typ) } else if typ.has_flag(.option) { diff --git a/vlib/v/gen/c/str_intp.v b/vlib/v/gen/c/str_intp.v index f097433ed6..fb2323515a 100644 --- a/vlib/v/gen/c/str_intp.v +++ b/vlib/v/gen/c/str_intp.v @@ -61,7 +61,6 @@ fn (mut g Gen) str_format(node ast.StringInterLiteral, i int, fmts []u8) (u64, s mut remove_tail_zeros := false fspec := fmts[i] mut fmt_type := StrIntpType.si_no_str - g.write('/*${fspec} ${sym}*/') // upper cases if (fspec - `A`) <= (`Z` - `A`) { upper_case = true diff --git a/vlib/v/tests/c_structs/cstruct.h b/vlib/v/tests/c_structs/cstruct.h index 3f2f6870f3..7f7d1c6b02 100644 --- a/vlib/v/tests/c_structs/cstruct.h +++ b/vlib/v/tests/c_structs/cstruct.h @@ -2,3 +2,11 @@ struct Abc { int field; }; + +typedef struct Test2 { + int a; +} Test2; + +typedef struct Test1 { + Test2 a; +} Test1; \ No newline at end of file diff --git a/vlib/v/tests/c_structs/cstruct_str_test.v b/vlib/v/tests/c_structs/cstruct_str_test.v new file mode 100644 index 0000000000..e408c8e12e --- /dev/null +++ b/vlib/v/tests/c_structs/cstruct_str_test.v @@ -0,0 +1,50 @@ +#include "@VMODROOT/cstruct.h" + +@[typedef] +struct C.Test2 {} + +@[typedef] +struct C.Test1 { + a C.Test2 +} + +fn (s C.Test2) str() string { + return 'test2' +} + +fn (s C.Test1) get() C.Test1 { + return s +} + +fn (s C.Test1) s() string { + return '.${s.get()}.' +} + +type TestAlias = C.Test2 + +fn (s TestAlias) str() string { + return 'test_alias' +} + +fn (s TestAlias) get() TestAlias { + return s +} + +fn test_main() { + x := unsafe { &C.Test1(malloc(1024)) } + println(x) + assert dump('${x}') == '&C.Test1{ + a: test2 +}' + println('.${x.get()}.${x.s()}') + + y := unsafe { &TestAlias(malloc(1024)) } + println(y) + assert dump('${y}') == '&test_alias' + println('.${y.get()}.') + + w := TestAlias(*y) + assert dump(w.str()) == 'test_alias' + + assert true +}