From bc05b2d7d2982cea963d4da3d2786f85dec7382f Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 25 Mar 2026 16:42:23 +0300 Subject: [PATCH] cgen: fix plugin creation not being called properly (fixes #20047) --- vlib/v/ast/types.v | 6 +- vlib/v/gen/c/cgen.v | 25 ++++-- vlib/v/gen/c/fn.v | 7 +- ...face_dispatch_uses_methods_ptr.c.must_have | 3 + .../interface_dispatch_uses_methods_ptr.vv | 20 +++++ .../plugin_interface_shared_library_test.v | 81 +++++++++++++++++++ 6 files changed, 129 insertions(+), 13 deletions(-) create mode 100644 vlib/v/gen/c/testdata/interface_dispatch_uses_methods_ptr.c.must_have create mode 100644 vlib/v/gen/c/testdata/interface_dispatch_uses_methods_ptr.vv create mode 100644 vlib/v/tests/plugin_interface_shared_library_test.v diff --git a/vlib/v/ast/types.v b/vlib/v/ast/types.v index 26cb6b4c7..6fe56a098 100644 --- a/vlib/v/ast/types.v +++ b/vlib/v/ast/types.v @@ -1430,11 +1430,13 @@ pub fn (t &Table) type_size(typ Type) (int, int) { align = t.pointer_size } Interface { - size = (sym.info.fields.len + 2) * t.pointer_size + interface_header_size := round_up(t.pointer_size + 4, t.pointer_size) + + t.pointer_size + size = interface_header_size + sym.info.fields.len * t.pointer_size align = t.pointer_size for etyp in sym.info.embeds { esize, _ := t.type_size(etyp) - size += esize - 2 * t.pointer_size + size += esize - interface_header_size } } else { diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index 5fbbc8350..620949b3f 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -2346,6 +2346,7 @@ pub fn (mut g Gen) write_interface_typesymbol_declaration(sym ast.TypeSymbol) { } g.type_definitions.writeln('\t};') g.type_definitions.writeln('\tu32 _typ;') + g.type_definitions.writeln('\tvoid* _methods;') for field in info.fields { styp := g.styp(field.typ) cname := c_name(field.name) @@ -2354,6 +2355,11 @@ pub fn (mut g Gen) write_interface_typesymbol_declaration(sym ast.TypeSymbol) { g.type_definitions.writeln('};') } +fn (mut g Gen) interface_methods_struct_name(typ ast.Type) string { + interface_sym := g.table.final_sym(g.table.unaliased_type(g.unwrap_generic(typ))) + return 'struct _${interface_sym.cname}_interface_methods' +} + pub fn (mut g Gen) write_fn_typesymbol_declaration(sym ast.TypeSymbol) { info := sym.info as ast.FnType func := info.func @@ -5721,10 +5727,9 @@ fn (mut g Gen) selector_expr(node ast.SelectorExpr) { if !has_embeds { if !node.has_hidden_receiver { if node.expr is ast.Ident && sym.info is ast.Interface { - left_cc_type := g.cc_type(g.table.unaliased_type(node.expr_type), - false) - left_type_name := util.no_dots(left_cc_type) - g.write('${c_name(left_type_name)}_name_table[${node.expr.name}${g.dot_or_ptr(node.expr_type)}_typ]._method_${m.name}') + methods_struct_name := g.interface_methods_struct_name(node.expr_type) + dot := g.dot_or_ptr(node.expr_type) + g.write('((${methods_struct_name}*)(${node.expr.name}${dot}_methods))->_method_${m.name}') } else { g.write('${g.styp(node.expr_type.idx_type())}_${m.name}') } @@ -6122,9 +6127,8 @@ fn (mut g Gen) gen_closure_fn(expr_styp string, m ast.Fn, name string) { } } if rec_sym.info is ast.Interface && rec_sym.info.get_methods().contains(method_name) { - left_cc_type := g.cc_type(g.table.unaliased_type(receiver.typ), false) - left_type_name := util.no_dots(left_cc_type) - sb.write_string('${c_name(left_type_name)}_name_table[a0->_typ]._method_${method_name}(') + methods_struct_name := g.interface_methods_struct_name(receiver.typ) + sb.write_string('((${methods_struct_name}*)a0->_methods)->_method_${method_name}(') } else { mut full_method_name := '${expr_styp}_${method_name}' if !expr_styp.starts_with('builtin__') { @@ -10223,6 +10227,12 @@ fn (mut g Gen) interface_table() string { cast_struct.writeln('(${interface_name}) {') cast_struct.writeln('\t\t._${cctype2} = x,') cast_struct.writeln('\t\t._typ = ${interface_index_name},') + methods_ptr := if inter_methods.len > 0 { + '&${interface_name}_name_table[${interface_index_name}]' + } else { + '0' + } + cast_struct.writeln('\t\t._methods = ${methods_ptr},') if cctype == cctype2 { for field in inter_info.fields { cname := c_name(field.name) @@ -10283,6 +10293,7 @@ return ${cast_struct_str}; cast_shared_struct.writeln('\t\t.val = {') cast_shared_struct.writeln('\t\t\t._${cctype} = &x->val,') cast_shared_struct.writeln('\t\t\t._typ = ${interface_index_name},') + cast_shared_struct.writeln('\t\t\t._methods = ${methods_ptr},') cast_shared_struct.writeln('\t\t}') cast_shared_struct.write_string('\t}') cast_shared_struct_str := cast_shared_struct.str() diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index 836ca151c..2aba50c35 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -3544,9 +3544,8 @@ fn (mut g Gen) method_call(node ast.CallExpr) { eprintln('>>> interface typ_sym.name: ${typ_sym.name} | receiver_type_name: ${receiver_type_name} | pos: ${node.pos}') } - left_cc_type := g.cc_type(g.table.unaliased_type(left_type), false) - left_type_name := util.no_dots(left_cc_type) - g.write('${c_name(left_type_name)}_name_table[') + methods_struct_name := g.interface_methods_struct_name(left_type) + g.write('((${methods_struct_name}*)(') if node.left.is_auto_deref_var() && left_type.nr_muls() > 1 { g.write2('(', '*'.repeat(left_type.nr_muls() - 1)) g.expr(node.left) @@ -3556,7 +3555,7 @@ fn (mut g Gen) method_call(node ast.CallExpr) { } dot := g.dot_or_ptr(left_type) mname := c_fn_name(method_name) - g.write('${dot}_typ]._method_${mname}(') + g.write('${dot}_methods))->_method_${mname}(') if node.left.is_auto_deref_var() && left_type.nr_muls() > 1 { g.write2('(', '*'.repeat(left_type.nr_muls() - 1)) g.expr(node.left) diff --git a/vlib/v/gen/c/testdata/interface_dispatch_uses_methods_ptr.c.must_have b/vlib/v/gen/c/testdata/interface_dispatch_uses_methods_ptr.c.must_have new file mode 100644 index 000000000..472a925b0 --- /dev/null +++ b/vlib/v/gen/c/testdata/interface_dispatch_uses_methods_ptr.c.must_have @@ -0,0 +1,3 @@ +void* _methods; +._methods = &main__Plugin_name_table[_main__Plugin_main__MyPlugin_index], +((struct _main__Plugin_interface_methods*)(plugin._methods))->_method_print_msg(plugin._object) diff --git a/vlib/v/gen/c/testdata/interface_dispatch_uses_methods_ptr.vv b/vlib/v/gen/c/testdata/interface_dispatch_uses_methods_ptr.vv new file mode 100644 index 000000000..56edadcd8 --- /dev/null +++ b/vlib/v/gen/c/testdata/interface_dispatch_uses_methods_ptr.vv @@ -0,0 +1,20 @@ +interface Plugin { + print_msg() +} + +struct MyPlugin {} + +fn (p MyPlugin) print_msg() {} + +fn create_plugin() Plugin { + return MyPlugin{} +} + +fn use_plugin(plugin Plugin) { + plugin.print_msg() +} + +fn main() { + plugin := create_plugin() + use_plugin(plugin) +} diff --git a/vlib/v/tests/plugin_interface_shared_library_test.v b/vlib/v/tests/plugin_interface_shared_library_test.v new file mode 100644 index 000000000..3a7f5384b --- /dev/null +++ b/vlib/v/tests/plugin_interface_shared_library_test.v @@ -0,0 +1,81 @@ +import dl +import os +import rand + +const vexe = @VEXE + +fn test_interface_from_shared_library_can_call_methods() { + if os.user_os() != 'linux' { + return + } + workdir := os.join_path(os.vtmp_dir(), 'v_plugin_interface_shared_${rand.ulid()}') + os.mkdir_all(workdir) or { panic(err) } + defer { + os.rmdir_all(workdir) or {} + } + lib_src := os.join_path(workdir, 'plugin.v') + host_src := os.join_path(workdir, 'host.v') + lib_bin := os.join_path(workdir, 'plugin') + lib_so := os.join_path(workdir, 'plugin${dl.dl_ext}') + lib_path := lib_so.replace('\\', '\\\\') + library_code := [ + 'module main', + '', + 'pub interface Plugin {', + '\tprint_msg()', + '}', + '', + 'pub struct MyPlugin {}', + '', + 'pub fn (p MyPlugin) print_msg() {', + "\tprintln('Hello, World!')", + '}', + '', + "@[export: 'create_plugin']", + 'pub fn create_plugin() Plugin {', + '\treturn MyPlugin{}', + '}', + ].join('\n') + host_code := [ + 'module main', + '', + 'import dl.loader', + '', + 'pub interface Plugin {', + '\tprint_msg()', + '}', + '', + 'type CreatePlugin = fn () Plugin', + '', + "const lib_path = '${lib_path}'", + '', + 'fn main() {', + '\tmut dl_loader := loader.get_or_create_dynamic_lib_loader(', + '\t\tkey: lib_path', + '\t\tpaths: [lib_path]', + '\t) or { panic(err) }', + '\tdefer {', + '\t\tdl_loader.unregister()', + '\t}', + "\tcreate_plugin_sym := dl_loader.get_sym('create_plugin') or { panic(err) }", + '\tcreate_plugin := CreatePlugin(create_plugin_sym)', + '\tplugin := create_plugin()', + '\tplugin.print_msg()', + '}', + ].join('\n') + os.write_file(lib_src, library_code) or { panic(err) } + os.write_file(host_src, host_code) or { panic(err) } + compile_lib_cmd := '${os.quoted_path(vexe)} -d no_backtrace -shared -o ${os.quoted_path(lib_bin)} ${os.quoted_path(lib_src)}' + run_cmd(compile_lib_cmd) or { panic(err) } + run_host_cmd := '${os.quoted_path(vexe)} -d no_backtrace run ${os.quoted_path(host_src)}' + res := run_cmd(run_host_cmd) or { panic(err) } + assert res.output.contains('Hello, World!') +} + +fn run_cmd(cmd string) !os.Result { + res := os.execute(cmd) + if res.exit_code != 0 { + return error('command failed:\n${cmd}\n${res.output}') + } + return res +} -- 2.39.5