Skip to content

Commit ce8d906

Browse files
medvednikovclaude
andcommitted
v3: keep V3/C fallback bug-report output off stdout so v -o - stays valid C (PR #28131 review)
send_prepared_c_error_bug_report and submit_v3_compiler_error_bug_report printed the bug-report banner, the uploaded C/V context, and status lines with println. For `v -o - source.v`, V1 has already written the generated C to stdout in builder.cc; appending the report there corrupts the documented `-o -` output for exactly the programs that needed the compatibility fallback (and the upload succeeding made it worse, since that path prints the most text). Emit all report diagnostics on stderr (eprintln), matching the fallback notice, which was already on stderr. Covers both the generated-C path (send_prepared_c_error_bug_report + print_c_error_bug_report_context + print_report_lines) and the V3 internal-error path (submit_v3_compiler_error_bug_report). Adds an e2e test that a `-o -` fallback build keeps stdout as pure generated C while the report goes to stderr. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent f8c4282 commit ce8d906

2 files changed

Lines changed: 91 additions & 13 deletions

File tree

cmd/v/macos_v3_test.v

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1647,6 +1647,76 @@ fn main() {
16471647
}
16481648
}
16491649

1650+
// End-to-end (PR #28131 review): a V3 internal-error fallback for `v -o - source.v` must
1651+
// keep stdout as pure generated C. V1 has already written the C to stdout, so the report
1652+
// banner, its context, and the fallback notice all go to stderr — never stdout — or the
1653+
// documented `-o -` output would be invalid C for exactly the programs that needed the
1654+
// fallback.
1655+
fn test_macos_v3_fallback_report_stays_off_generated_c_stdout() {
1656+
$if macos {
1657+
root := os.join_path(os.real_path(os.vtmp_dir()), 'macos_v3_stdout_c_${os.getpid()}')
1658+
os.rmdir_all(root) or {}
1659+
os.mkdir_all(root) or { panic(err) }
1660+
defer {
1661+
os.rmdir_all(root) or {}
1662+
}
1663+
source := os.join_path(root, 'gen.v')
1664+
os.write_file(source, 'struct Opt[T] {
1665+
val T
1666+
some bool
1667+
}
1668+
1669+
fn some[T](val T) Opt[T] {
1670+
return Opt[T]{
1671+
val: val
1672+
some: true
1673+
}
1674+
}
1675+
1676+
fn (f Opt[T]) map[U](op fn (T) U) Opt[U] {
1677+
if f.some {
1678+
return some[U](op(f.val))
1679+
}
1680+
return Opt[U]{}
1681+
}
1682+
1683+
fn main() {
1684+
result := some("hello").map(|s| s.len)
1685+
assert result.some && result.val == 5
1686+
}
1687+
')!
1688+
mut environment := os.environ()
1689+
// Exercise a real fallback; clear the job-level no-fallback guard CI may set.
1690+
environment.delete('V_MACOS_V3_NO_FALLBACK')
1691+
environment['V_C_ERROR_BUG_REPORT_DISABLED'] = ''
1692+
// Unroutable endpoint: the send fails fast, exercising the report-diagnostic
1693+
// output (the notice and "was not sent") without contacting a real server.
1694+
environment['V_C_ERROR_BUG_REPORT_URL'] = 'http://127.0.0.1:1/bug-report'
1695+
environment['VFLAGS'] = ''
1696+
environment['VOSARGS'] = ''
1697+
mut process := os.new_process(@VEXE)
1698+
process.set_args(['-gc', 'none', '-o', '-', source])
1699+
process.set_environment(environment)
1700+
process.set_redirect_stdio()
1701+
process.run()
1702+
process.wait()
1703+
stdout := process.stdout_slurp()
1704+
stderr := process.stderr_slurp()
1705+
exit_code := process.code
1706+
process.close()
1707+
assert exit_code == 0, stdout + stderr
1708+
// The fallback happened and its diagnostics went to stderr.
1709+
assert stderr.contains('the experimental V3 compiler could not build this program'), stderr
1710+
assert stderr.contains('bug report was not sent'), stderr
1711+
// stdout is the generated C only — valid C, with no report text of any kind.
1712+
assert stdout.contains('typedef') || stdout.contains('#define'), 'stdout is not generated C'
1713+
for leaked in ['could not build this program', 'compiler bug report',
1714+
'bug report was not sent', 'so this can be fixed', 'opt out of these automatic'] {
1715+
assert !stdout.contains(leaked), 'report text leaked onto `-o -` stdout: `${leaked}`'
1716+
}
1717+
}
1718+
}
1719+
16501720
fn clear_macos_v3_report_env() {
16511721
for suffix in ['PRESENT', 'KIND', 'CCOMPILER', 'COUTPUT', 'TAG', 'VFILE', 'VSOURCE'] {
16521722
os.unsetenv('V_MACOS_V3_REPORT_${suffix}')

vlib/v/builder/c_error_report.v

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -175,16 +175,19 @@ fn (mut v Builder) send_prepared_c_error_bug_report(raw_report CErrorBugReport,
175175
}
176176
return
177177
}
178-
println('================== C compiler bug report ==============')
178+
// Report diagnostics go to stderr, never stdout: with `v -o - source.v` the generated
179+
// C is already on stdout, so appending this banner there would corrupt the documented
180+
// `-o -` output for exactly the programs that needed the fallback.
181+
eprintln('================== C compiler bug report ==============')
179182
if is_v3_fallback {
180183
print_v3_fallback_notice(report_url, true, report_includes_v_source(report))
181184
}
182185
if tool_output != '' {
183-
println(tool_output)
186+
eprintln(tool_output)
184187
}
185-
println('V ${report.v_version}, ${report.target_os}/${report.arch}, cc: ${report.ccompiler}, build options: ${report.build_options}')
188+
eprintln('V ${report.v_version}, ${report.target_os}/${report.arch}, cc: ${report.ccompiler}, build options: ${report.build_options}')
186189
print_c_error_bug_report_context(report)
187-
println('='.repeat('================== C compiler bug report =============='.len))
190+
eprintln('='.repeat('================== C compiler bug report =============='.len))
188191
}
189192

190193
// submit_external_c_error_bug_report submits C diagnostics and generated source produced by
@@ -474,13 +477,16 @@ fn (mut v Builder) submit_v3_compiler_error_bug_report(v3_stage string, v3_outpu
474477
print_v3_fallback_notice('', false, false)
475478
return
476479
}
477-
println('================== V3 compiler bug report ==============')
480+
// Report diagnostics go to stderr, never stdout: with `v -o - source.v` the generated
481+
// C is already on stdout, so appending this banner there would corrupt the documented
482+
// `-o -` output for exactly the programs that needed the fallback.
483+
eprintln('================== V3 compiler bug report ==============')
478484
print_v3_fallback_notice(report_url, true, report.v_source != '')
479485
if tool_output != '' {
480-
println(tool_output)
486+
eprintln(tool_output)
481487
}
482-
println('V ${report.v_version}, ${report.target_os}/${report.arch}, build options: ${report.build_options}')
483-
println('='.repeat('================== V3 compiler bug report =============='.len))
488+
eprintln('V ${report.v_version}, ${report.target_os}/${report.arch}, build options: ${report.build_options}')
489+
eprintln('='.repeat('================== V3 compiler bug report =============='.len))
484490
}
485491

486492
// v3_report_v_source returns the bounded V source snippet uploaded for an internal
@@ -1242,25 +1248,27 @@ fn truncated_report_text(text string, max_bytes int) string {
12421248
return text[..head_bytes] + c_error_bug_report_truncation_notice + text[text.len - tail_bytes..]
12431249
}
12441250

1251+
// print_c_error_bug_report_context prints the uploaded C/V context to stderr — never
1252+
// stdout — so it cannot corrupt a `v -o - source.v` generated-C stream.
12451253
fn print_c_error_bug_report_context(report CErrorBugReport) {
1246-
println('Generated C lines sent from ${report.c_file}:${report.c_line}:')
1254+
eprintln('Generated C lines sent from ${report.c_file}:${report.c_line}:')
12471255
print_report_lines(report.c_context, report.c_line)
12481256
if report.v_file != '' {
1249-
println('Corresponding V lines sent from ${report.v_file}:${report.v_line}:')
1257+
eprintln('Corresponding V lines sent from ${report.v_file}:${report.v_line}:')
12501258
print_report_lines(report.v_context, report.v_line)
12511259
} else {
1252-
println('Corresponding V lines sent: no V source mapping was available.')
1260+
eprintln('Corresponding V lines sent: no V source mapping was available.')
12531261
}
12541262
}
12551263

12561264
fn print_report_lines(lines []CErrorReportLine, center int) {
12571265
if lines.len == 0 {
1258-
println(' (no source lines available)')
1266+
eprintln(' (no source lines available)')
12591267
return
12601268
}
12611269
for line in lines {
12621270
prefix := if line.line == center { '>' } else { ' ' }
1263-
println('${prefix} ${line.line:6} | ${line.text}')
1271+
eprintln('${prefix} ${line.line:6} | ${line.text}')
12641272
}
12651273
}
12661274

0 commit comments

Comments
 (0)