From 26ed1f73bf37184b83bd2bb6de2f10f5b69b508e Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 3 Jun 2026 17:01:03 +0300 Subject: [PATCH] builder: send the V source line that caused a C error in bug reports (#27332) --- vlib/v/builder/builder.v | 1 + vlib/v/builder/c_error_report.v | 49 +++++++++++++++++++++++++++- vlib/v/builder/c_error_report_test.v | 26 +++++++++++++++ vlib/v/builder/cc.v | 1 + vlib/v/help/build/build-c.txt | 2 ++ 5 files changed, 78 insertions(+), 1 deletion(-) diff --git a/vlib/v/builder/builder.v b/vlib/v/builder/builder.v index 0f414fc97..bf7fb8043 100644 --- a/vlib/v/builder/builder.v +++ b/vlib/v/builder/builder.v @@ -57,6 +57,7 @@ pub mut: crun_cache_keys []string // target executable + top level source files; filled in by Builder.should_rebuild executable_exists bool // if the executable already exists, don't remove new executable after `v run` str_args string // for parallel_cc mode only, to know which cc args to use (like -I etc) + last_cc_cmd string // the most recently executed C compiler command; reused to regenerate a #line annotated report disable_flto bool } diff --git a/vlib/v/builder/c_error_report.v b/vlib/v/builder/c_error_report.v index 81d334b65..62e227e70 100644 --- a/vlib/v/builder/c_error_report.v +++ b/vlib/v/builder/c_error_report.v @@ -3,6 +3,7 @@ module builder import os import strings import v.pref +import v.gen.c as cgen import v.util.version const default_c_error_bug_report_url = 'https://bugs.vlang.io/bug-report' @@ -43,7 +44,15 @@ fn (mut v Builder) submit_c_error_bug_report(ccompiler string, c_output string) if !should_submit_c_error_bug_report(v.pref.c_error_bug_report_url) { return } - raw_report := v.new_c_error_bug_report(ccompiler, c_output) + mut raw_report := v.new_c_error_bug_report(ccompiler, c_output) + if raw_report.v_file == '' { + // The default `.tmp.c` has no `#line` directives, so the C error could not be + // traced back to a V line. Regenerate the C with `#line` info (as `-g` would), + // recompile, and reuse the richer report when it does map to a V source line. + if vlines_report := v.new_c_error_bug_report_with_vlines(ccompiler) { + raw_report = vlines_report + } + } report := bounded_c_error_bug_report(raw_report, c_error_bug_report_max_body_bytes) report_url := c_error_bug_report_url(v.pref.c_error_bug_report_url) tool_output := send_c_error_bug_report(report, report_url) or { @@ -97,6 +106,44 @@ fn (mut v Builder) new_c_error_bug_report(ccompiler string, c_output string) CEr } } +// new_c_error_bug_report_with_vlines regenerates the program's C source with `#line` +// directives enabled (the same information `-g` would add), recompiles it with the +// previously used C compiler command, and builds a report from the recompiled output. +// Because the regenerated C carries `#line` annotations, the C error can be mapped back +// to the exact V source line that produced it. It returns none when the V mapping still +// cannot be produced, so the caller keeps the original, C-only report. +fn (mut v Builder) new_c_error_bug_report_with_vlines(ccompiler string) ?CErrorBugReport { + if v.pref.is_vlines || v.pref.parallel_cc || v.pref.generate_c_project != '' + || v.last_cc_cmd == '' || v.parsed_files.len == 0 || v.out_name_c == '' { + return none + } + old_is_vlines := v.pref.is_vlines + v.pref.is_vlines = true + defer { + v.pref.is_vlines = old_is_vlines + } + // Regenerate the C source, now with `#line` directives, into the same `.tmp.c` file, + // so that the recorded compiler command recompiles exactly the annotated source. + // Keep the original `.tmp.c` so that it can be restored afterwards (e.g. for `-keepc`). + original_c := os.read_file(v.out_name_c) or { return none } + goutput := cgen.gen(v.parsed_files, mut v.table, v.pref) + mut c_builder := goutput.res_builder + c_builder = cgen.fix_reset_dbg_line(c_builder, v.out_name_c) + os.write_file_array(v.out_name_c, c_builder) or { return none } + vdir := os.dir(pref.vexe_path()) + original_pwd := os.getwd() + os.chdir(vdir) or {} + recompiled := os.execute(v.last_cc_cmd) + os.chdir(original_pwd) or {} + report := v.new_c_error_bug_report(ccompiler, recompiled.output) + // Restore the C source that the user actually compiled, now that the report is built. + os.write_file(v.out_name_c, original_c) or {} + if report.v_file == '' { + return none + } + return report +} + fn c_error_bug_report_url(flag_url string) string { trimmed_flag_url := flag_url.trim_space() if trimmed_flag_url != '' { diff --git a/vlib/v/builder/c_error_report_test.v b/vlib/v/builder/c_error_report_test.v index d0372abd4..d79ed1043 100644 --- a/vlib/v/builder/c_error_report_test.v +++ b/vlib/v/builder/c_error_report_test.v @@ -1,6 +1,7 @@ module builder import os +import v.pref fn restore_env_var(name string, old_value ?string) { if value := old_value { @@ -249,3 +250,28 @@ fn test_truncated_report_text_preserves_start_and_end_when_space_allows() { assert truncated.contains('report truncated before upload') assert truncated.ends_with('-end') } + +fn test_new_c_error_bug_report_with_vlines_is_skipped_when_already_vlines() { + // When the program is already compiled with -g, the original `.tmp.c` already has + // `#line` directives, so there is nothing to regenerate. + mut b := Builder{ + pref: &pref.Preferences{ + is_vlines: true + } + } + if _ := b.new_c_error_bug_report_with_vlines('cc') { + assert false, 'expected none when the C source is already #line annotated' + } +} + +fn test_new_c_error_bug_report_with_vlines_is_skipped_without_a_recorded_command() { + // Without a recorded C compiler command (e.g. -parallel-cc, or a Windows MSVC build), + // there is no command to rerun, so no V mapping can be produced this way. + mut b := Builder{ + pref: &pref.Preferences{} + last_cc_cmd: '' + } + if _ := b.new_c_error_bug_report_with_vlines('cc') { + assert false, 'expected none when no C compiler command was recorded' + } +} diff --git a/vlib/v/builder/cc.v b/vlib/v/builder/cc.v index 05dc36db0..905922187 100644 --- a/vlib/v/builder/cc.v +++ b/vlib/v/builder/cc.v @@ -1580,6 +1580,7 @@ pub fn (mut v Builder) cc() { // os.chdir(vdir) or {} tried_compilation_commands << cmd + v.last_cc_cmd = cmd v.show_cc(cmd, response_file, response_file_content) // Run ccompiler_label := 'C ${os.file_name(ccompiler):3}' diff --git a/vlib/v/help/build/build-c.txt b/vlib/v/help/build/build-c.txt index e9a5493ad..ea9c6aed9 100644 --- a/vlib/v/help/build/build-c.txt +++ b/vlib/v/help/build/build-c.txt @@ -324,6 +324,8 @@ see also `v help build`. -bug-report-url url Override where automatic C compiler bug reports are submitted. Useful with `v bug-report --host localhost --port 8080` for local testing. + The report includes both the failing generated C line and the V source line + that produced it (V code is regenerated with `#line` info if needed to map it). Automatic reports to bugs.vlang.io are skipped when running in GitHub CI. Set V_C_ERROR_BUG_REPORT_DISABLED=1 to disable automatic report submission. -- 2.39.5