From 8fa8354b237390008a7a07801a45461693de716d Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Sat, 20 Sep 2025 17:44:20 +0300 Subject: [PATCH] v: support `__global C.Name = i16(123)` and `@[c_extern] __global C.stdout &C.FILE` declarations (in addition to `@[c_extern] fn C.name()`), to allow for more precise C API error checking (fix #25319) (#25331) --- vlib/v/ast/ast.v | 6 +++ vlib/v/checker/checker.v | 4 +- vlib/v/gen/c/consts_and_globals.v | 31 +++++++----- ...xtern_on_C_global_declarations.c.must_have | 18 +++++++ .../c_extern_on_C_global_declarations.out | 15 ++++++ .../c_extern_on_C_global_declarations.vv | 49 +++++++++++++++++++ vlib/v/gen/c/testdata/c_file_for_c_extern.c | 4 ++ vlib/v/parser/parser.v | 11 ++++- 8 files changed, 125 insertions(+), 13 deletions(-) create mode 100644 vlib/v/gen/c/testdata/c_extern_on_C_global_declarations.c.must_have create mode 100644 vlib/v/gen/c/testdata/c_extern_on_C_global_declarations.out create mode 100644 vlib/v/gen/c/testdata/c_extern_on_C_global_declarations.vv create mode 100644 vlib/v/gen/c/testdata/c_file_for_c_extern.c diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index d5f094a73..6456a1397 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -967,6 +967,12 @@ pub: is_exported bool // an explicit `@[export]` tag; the global will NOT be removed by `-skip-unused` is_weak bool is_hidden bool + // The following fields, are relevant for non V globals, for example `__global C.stdout &C.FILE`: + language Language // for C.stdout, it will be .c . + is_extern bool // true, if an explicit `@[c_extern]` tag was used. It is suitable for globals, that are not initialised by V, + // but come from the external linked objects/libs, like C.stdout etc, and that *are not* declared in included .h files . + // Without an explicit `@[c_extern]` tag, V will avoid emiting `extern CType CName;` lines. + // V will still know, that the type of C.stdout, is not the default `int`, but &C.FILE, and thus will do more checks on it. pub mut: expr Expr typ Type diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 27e1569d1..1b6f8a1cb 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -2529,7 +2529,9 @@ fn (mut c Checker) global_decl(mut node ast.GlobalDecl) { } } for mut field in node.fields { - c.check_valid_snake_case(field.name, 'global name', field.pos) + if field.language != .c { + c.check_valid_snake_case(field.name, 'global name', field.pos) + } if field.name in ast.global_reserved_type_names { c.error('invalid use of reserved type `${field.name}` as a global name', field.pos) diff --git a/vlib/v/gen/c/consts_and_globals.v b/vlib/v/gen/c/consts_and_globals.v index b684d33ea..d44f049eb 100644 --- a/vlib/v/gen/c/consts_and_globals.v +++ b/vlib/v/gen/c/consts_and_globals.v @@ -434,7 +434,6 @@ fn (mut g Gen) global_decl(node ast.GlobalDecl) { g.inside_cinit = false g.inside_global_decl = false } - cextern := node.attrs.contains('c_extern') should_init := (!g.pref.use_cache && g.pref.build_mode != .build_module) || (g.pref.build_mode == .build_module && g.module_built == node.mod) mut attributes := '' @@ -475,11 +474,11 @@ fn (mut g Gen) global_decl(node ast.GlobalDecl) { } mut def_builder := strings.new_builder(100) mut init := '' - extern := if cextern { 'extern ' } else { '' } + extern := if field.is_extern { 'extern ' } else { '' } modifier := if field.is_volatile { ' volatile ' } else { '' } - def_builder.write_string('${extern}${visibility_kw}${modifier}${styp} ${attributes}${field.name}') - if cextern { - def_builder.writeln('; // global 2') + final_c_name := field.name.all_after('C.') + if field.is_extern { + def_builder.writeln('${extern}${visibility_kw}${modifier}${styp} ${attributes}${final_c_name}; // global 2') g.global_const_defs[name] = GlobalConstDef{ mod: node.mod def: def_builder.str() @@ -487,6 +486,11 @@ fn (mut g Gen) global_decl(node ast.GlobalDecl) { } continue } + mut needs_ending_semicolon := false + if field.language != .c || field.has_expr { + def_builder.write_string('${extern}${visibility_kw}${modifier}${styp} ${attributes}${final_c_name}') + needs_ending_semicolon = true + } if field.has_expr || cinit { // `__global x = unsafe { nil }` should still use the simple direct initialisation, `g_main_argv` needs it. mut is_simple_unsafe_expr := false @@ -510,29 +514,34 @@ fn (mut g Gen) global_decl(node ast.GlobalDecl) { // More complex expressions need to be moved to `_vinit()` // e.g. `__global ( mygblobal = 'hello ' + world' )` if field.name in ['g_main_argc', 'g_main_argv'] { - init = '\t// skipping ${field.name}, it was initialised in main' + init = '\t// skipping ${final_c_name}, it was initialised in main' } else { - init = '\t${field.name} = ${g.expr_string(field.expr)}; // global 3' + init = '\t${final_c_name} = ${g.expr_string(field.expr)}; // global 3' } } - } else if !g.pref.translated { // don't zero globals from C code + } else if !g.pref.translated && field.language == .v { + // don't zero globals from C code g.type_default_vars.clear() default_initializer := g.type_default(field.typ) if default_initializer == '{0}' && should_init { def_builder.write_string(' = {0}') } else if default_initializer == '{E_STRUCT}' && should_init { - init = '\tmemcpy(${field.name}, (${styp}){${default_initializer}}, sizeof(${styp})); // global 4' + init = '\tmemcpy(${final_c_name}, (${styp}){${default_initializer}}, sizeof(${styp})); // global 4' } else { if field.name !in ['as_cast_type_indexes', 'g_memory_block', 'global_allocator'] { decls := g.type_default_vars.str() if decls != '' { init = '\t${decls}' } - init += '\t${field.name} = *(${styp}*)&((${styp}[]){${default_initializer}}[0]); // global 5' + init += '\t${final_c_name} = *(${styp}*)&((${styp}[]){${default_initializer}}[0]); // global 5' } } + } else { + def_builder.writeln('/* skip C global: ${final_c_name} */') + } + if needs_ending_semicolon { + def_builder.writeln('; // global 6') } - def_builder.writeln('; // global 6') g.global_const_defs[name] = GlobalConstDef{ mod: node.mod def: def_builder.str() diff --git a/vlib/v/gen/c/testdata/c_extern_on_C_global_declarations.c.must_have b/vlib/v/gen/c/testdata/c_extern_on_C_global_declarations.c.must_have new file mode 100644 index 000000000..d96d5d52b --- /dev/null +++ b/vlib/v/gen/c/testdata/c_extern_on_C_global_declarations.c.must_have @@ -0,0 +1,18 @@ +/* skip C global: stdout */ +/* skip C global: stderr */ +builtin__println(_S("&C.FILE")); +builtin__FILE_str(stdout) +builtin__FILE_str(stderr) +fprintf(stdout, "Hello world on stdout.\n"); +fprintf(stderr, "This should be on stderr.\n"); + +extern u64 My_C_u64_global; +extern i32 My_C_i32_global; +fprintf(stdout, "My_C_u64_global: %ld\n", My_C_u64_global); +fprintf(stdout, "My_C_i32_global: %d\n", My_C_i32_global); +builtin__u64_str(My_C_u64_global) +builtin__i32_str(My_C_i32_global) + +i16 My_non_extern_C_global = ((i16)(-9876)); +fprintf(stdout, "C.My_non_extern_C_global: %d\n", My_non_extern_C_global); +builtin__i16_str(My_non_extern_C_global) diff --git a/vlib/v/gen/c/testdata/c_extern_on_C_global_declarations.out b/vlib/v/gen/c/testdata/c_extern_on_C_global_declarations.out new file mode 100644 index 000000000..3d3030e5e --- /dev/null +++ b/vlib/v/gen/c/testdata/c_extern_on_C_global_declarations.out @@ -0,0 +1,15 @@ +types: +&C.FILE +&C.FILE +values: +&C.FILE{} +&C.FILE{} +This should be on stderr. +Hello world on stdout. +My_C_u64_global: 456 +My_C_i32_global: -123 +C.My_non_extern_C_global: -9876 +u64 +i32 +i16 +-9876 diff --git a/vlib/v/gen/c/testdata/c_extern_on_C_global_declarations.vv b/vlib/v/gen/c/testdata/c_extern_on_C_global_declarations.vv new file mode 100644 index 000000000..42181fbc0 --- /dev/null +++ b/vlib/v/gen/c/testdata/c_extern_on_C_global_declarations.vv @@ -0,0 +1,49 @@ +@[has_globals] +module main + +#include "@DIR/c_file_for_c_extern.c" + +// These could be macros, but without the @[c_extern] tag, they should still work. +// V should treat their type as `&C.FILE`, and *NOT* as the default `int` type for C names. +__global C.stdout &C.FILE +__global C.stderr &C.FILE + +// This should generate: `i16 My_non_extern_C_global = -9876;` . Note the lack of `extern `. +__global C.My_non_extern_C_global = i16(-9876) + +// These are defined in the included c_file_for_c_extern.c . Declaring them here, +// should make V recognize their type. + +@[c_extern] +__global C.My_C_u64_global u64 +// This should generate: `extern u64 My_C_u64_global;` + +@[c_extern] +__global C.My_C_i32_global i32 +// This should generate: `extern i32 My_C_i32_global;` + +unbuffer_stdout() +println('types:') +println(typeof(C.stdout).name) +println(typeof(C.stderr).name) +println('values:') +println(C.stdout) +println(C.stderr) +// println(voidptr(C.stdout)) +// println(voidptr(C.stderr)) +C.fflush(C.stderr) +C.fflush(C.stdout) +C.fprintf(C.stderr, c'This should be on stderr.\n') +C.fflush(C.stderr) +C.fprintf(C.stdout, c'Hello world on stdout.\n') +C.fprintf(C.stdout, c'My_C_u64_global: %ld\n', C.My_C_u64_global) +C.fprintf(C.stdout, c'My_C_i32_global: %d\n', C.My_C_i32_global) +C.fprintf(C.stdout, c'C.My_non_extern_C_global: %d\n', C.My_non_extern_C_global) +C.fflush(C.stdout) + +assert C.My_C_u64_global == 456 +assert C.My_C_i32_global == -123 +println(typeof(C.My_C_u64_global).name) +println(typeof(C.My_C_i32_global).name) +println(typeof(C.My_non_extern_C_global).name) +println(C.My_non_extern_C_global) diff --git a/vlib/v/gen/c/testdata/c_file_for_c_extern.c b/vlib/v/gen/c/testdata/c_file_for_c_extern.c new file mode 100644 index 000000000..24fb60d26 --- /dev/null +++ b/vlib/v/gen/c/testdata/c_file_for_c_extern.c @@ -0,0 +1,4 @@ + +u64 My_C_u64_global = 456; + +i32 My_C_i32_global = -123; diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index f21c0e629..b9a83a1b7 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -2608,12 +2608,14 @@ fn (mut p Parser) global_decl() ast.GlobalDecl { mut is_exported := false mut is_weak := false mut is_hidden := false + mut is_extern := false for ga in attrs { match ga.name { 'export' { is_exported = true } 'markused' { is_markused = true } 'weak' { is_weak = true } 'hidden' { is_hidden = true } + 'c_extern' { is_extern = true } else {} } } @@ -2648,8 +2650,10 @@ fn (mut p Parser) global_decl() ast.GlobalDecl { if p.tok.kind == .rpar { break } + language := p.parse_language() + pos := p.tok.pos() - name := p.check_name() + mut name := p.check_name() has_expr := p.tok.kind == .assign mut expr := ast.empty_expr mut typ := ast.void_type @@ -2687,6 +2691,9 @@ fn (mut p Parser) global_decl() ast.GlobalDecl { typ_pos = p.tok.pos() typ = p.parse_type() } + if language == .c { + name = 'C.' + name + } field := ast.GlobalField{ name: name has_expr: has_expr @@ -2700,6 +2707,8 @@ fn (mut p Parser) global_decl() ast.GlobalDecl { is_exported: is_exported is_weak: is_weak is_hidden: is_hidden + is_extern: is_extern + language: language } fields << field if name !in ast.global_reserved_type_names { -- 2.39.5