Skip to content

Commit b3d9745

Browse files
committed
v3: address post-merge review feedback
1 parent 8a3e17c commit b3d9745

8 files changed

Lines changed: 285 additions & 21 deletions

File tree

ci/qemu_linux_tests.sh

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,10 +119,24 @@ fi
119119
ssh_options=(
120120
-i "$key_file"
121121
-p "$ssh_port"
122-
-o "UserKnownHostsFile=${known_hosts_file}"
122+
-o "UserKnownHostsFile=\"${known_hosts_file}\""
123123
-o StrictHostKeyChecking=yes
124124
-o ConnectTimeout=5
125125
)
126+
if [[ ! "$ssh_port" =~ ^[0-9]+$ ]]; then
127+
echo "Invalid SSH port: ${ssh_port}" >&2
128+
exit 1
129+
fi
130+
if [[ "$key_file" == *"'"* || "$known_hosts_file" == *"'"* || "$known_hosts_file" == *'"'* ]]; then
131+
echo 'The QEMU SSH key and known-hosts paths cannot contain quote characters.' >&2
132+
exit 1
133+
fi
134+
# openrsync understands quoted arguments but treats Bash `%q` backslashes
135+
# literally. Preserve spaces with single-quoted arguments, and retain inner
136+
# quotes around the known-hosts value for OpenSSH's own option parser.
137+
printf -v rsync_ssh_command \
138+
"ssh -i '%s' -p '%s' '-oUserKnownHostsFile=\"%s\"' -o StrictHostKeyChecking=yes" \
139+
"$key_file" "$ssh_port" "$known_hosts_file"
126140

127141
vm_is_running() {
128142
[[ -s "$pid_file" ]] && kill -0 "$(<"$pid_file")" 2>/dev/null
@@ -235,7 +249,7 @@ EOF
235249
--from0 \
236250
--files-from=- \
237251
--exclude '.detect_tcc*' \
238-
-e "ssh -i ${key_file} -p ${ssh_port} -o UserKnownHostsFile=${known_hosts_file} -o StrictHostKeyChecking=yes" \
252+
-e "$rsync_ssh_command" \
239253
"${repo_root}/" "${guest}:${guest_repo}/"
240254
# Record only untracked paths that the rsync exclusion above allows through.
241255
git -C "$repo_root" ls-files -z --others --exclude-standard \

cmd/v/macos_v3_args.c.v

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ fn macos_v3_explicit_compilation_requested(command string, prefs &pref.Preferenc
7979
}
8080
return command in ['run', 'build'] || prefs.is_script || os.is_dir(prefs.path)
8181
|| normalized_path.ends_with('.v') || normalized_path.ends_with('.vsh')
82+
|| normalized_path.ends_with('.vv')
8283
}
8384

8485
@[markused]

cmd/v/macos_v3_test.v

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2343,6 +2343,10 @@ fn test_explicit_v3_rejects_structured_v1_only_preferences() {
23432343
new_compiler: true
23442344
path: 'main.v'
23452345
})
2346+
assert macos_v3_explicit_compilation_requested('build', &pref.Preferences{
2347+
new_compiler: true
2348+
path: 'fixture.vv'
2349+
})
23462350
assert !macos_v3_explicit_compilation_requested('fmt', &pref.Preferences{
23472351
new_compiler: true
23482352
is_quiet: true
@@ -2371,6 +2375,33 @@ fn test_explicit_v3_rejects_structured_v1_only_preferences() {
23712375
})
23722376
}
23732377

2378+
fn test_embedded_v3_explicit_vv_build_is_rejected() {
2379+
$if macos || linux {
2380+
root := os.join_path(os.real_path(os.vtmp_dir()), 'v3_explicit_vv_${os.getpid()}')
2381+
os.rmdir_all(root) or {}
2382+
os.mkdir_all(root) or { panic(err) }
2383+
defer {
2384+
os.rmdir_all(root) or {}
2385+
}
2386+
source := os.join_path(root, 'fixture.vv')
2387+
os.write_file(source, 'fn main() {}\n')!
2388+
mut environment := os.environ()
2389+
environment['VFLAGS'] = ''
2390+
environment['VOSARGS'] = ''
2391+
mut process := os.new_process(@VEXE)
2392+
process.set_args(['-new-compiler', source])
2393+
process.set_environment(environment)
2394+
process.set_redirect_stdio()
2395+
process.run()
2396+
process.wait()
2397+
output := process.stdout_slurp() + process.stderr_slurp()
2398+
exit_code := process.code
2399+
process.close()
2400+
assert exit_code == 1, output
2401+
assert output.contains('options that require the established compiler'), output
2402+
}
2403+
}
2404+
23742405
fn test_explicit_v3_options_do_not_reject_external_tools() {
23752406
root := os.join_path(os.real_path(os.vtmp_dir()), 'v3_external_tool_${os.getpid()}')
23762407
os.rmdir_all(root) or {}

doc/docs.md

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,10 @@ On macOS and Linux, `v` compiles your program with the experimental **V3**
7979
compiler (a newer implementation of the V compiler, whose source lives in
8080
`vlib/v3`) by default. On other platforms, and for C builds that select a
8181
target OS different from the host, the established compiler in `vlib/v` is
82-
used. V scripts (`.vsh`, including `v run script.vsh`) and debug builds selected
83-
with `-g`/`-debug` also remain on the established compiler. Same-OS
84-
cross-architecture builds can still use V3 when the target is supported.
82+
used. V scripts (`.vsh`, including `v run script.vsh`), the `crun` and
83+
`build-module` commands, and debug builds selected with `-g`/`-debug` also
84+
remain on the established compiler. Same-OS cross-architecture builds can still
85+
use V3 when the target is supported.
8586

8687
You normally do not need to do anything: when V3 cannot yet build an eligible
8788
program, `v` automatically falls back to the established compiler, so your build
@@ -118,9 +119,11 @@ An internal V3 compiler error on a short program (and any directory build such a
118119
file, so it can still upload a strict-subset excerpt of that file plus a few lines
119120
of context around the failing line, even when the file is short. Inline-assembly
120121
fallbacks are notice-only and do not submit a report; reporting is also skipped
121-
for test compilations and to the default endpoint in GitHub CI. A custom endpoint
122-
set with `-bug-report-url` or `V_C_ERROR_BUG_REPORT_URL` remains active in CI. You
123-
can turn reporting off entirely by setting `V_C_ERROR_BUG_REPORT_DISABLED=1`.
122+
for test compilations and to the default endpoint in GitHub CI. A custom fallback
123+
endpoint set with `V_C_ERROR_BUG_REPORT_URL` remains active in CI. The
124+
`-bug-report-url` option selects the established compiler and configures only its
125+
reports. You can turn reporting off entirely by setting
126+
`V_C_ERROR_BUG_REPORT_DISABLED=1`.
124127

125128
## Packaging V for distribution
126129
See the [notes on how to prepare a package for V](packaging_v_for_distributions.md) .

vlib/v/help/build/build.txt

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,13 @@ The default compiler:
2727
such as `v .` submits metadata only, while a generated-C error maps to a V file
2828
and can still upload a strict-subset excerpt of it, even for a short file.
2929
Inline-assembly fallbacks are notice-only; test compilations and GitHub CI also
30-
submit no report to the default endpoint. An explicitly configured custom bug
31-
report URL remains active in CI. Pass `-old-compiler` (see `v help build-c`) to always use the
32-
established compiler and skip the V3 attempt and its fallback reporting. The
33-
established compiler can still report its own eligible generated-C failures;
34-
set V_C_ERROR_BUG_REPORT_DISABLED=1 to disable all such submissions.
30+
submit no report to the default endpoint. A custom fallback URL set with
31+
V_C_ERROR_BUG_REPORT_URL remains active in CI. The -bug-report-url option
32+
selects the established compiler and configures only its reports. Pass
33+
`-old-compiler` (see `v help build-c`) to always use the established compiler
34+
and skip the V3 attempt and its fallback reporting. The established compiler
35+
can still report its own eligible generated-C failures; set
36+
V_C_ERROR_BUG_REPORT_DISABLED=1 to disable all such submissions.
3537

3638
Use `v build script.vsh` to compile a `.vsh` script without running it.
3739
`v -skip-running script.vsh` works too.

vlib/v3/gen/c/cleanc.v

Lines changed: 189 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4530,11 +4530,12 @@ fn (mut g FlatGen) collect_preserved_header_file(path string, include_dirs []str
45304530
}
45314531
g.preserved_header_files_seen[real_path] = true
45324532
text := os.read_file(real_path) or { return }
4533-
g.collect_inlined_c_structs(text)
4534-
g.collect_inlined_c_fns(text)
4535-
g.collect_inlined_c_declared_fns(text)
4533+
possible_text := c_header_possible_active_text(text, g.c_flags, g.c99_mode, g.target)
4534+
g.collect_inlined_c_structs(possible_text)
4535+
g.collect_inlined_c_fns(possible_text)
4536+
g.collect_inlined_c_declared_fns(possible_text)
45364537
mut in_block_comment := false
4537-
for line in text.split_into_lines() {
4538+
for line in possible_text.split_into_lines() {
45384539
clean, next_in_block_comment := c_preprocessor_directive_scan_line(line, in_block_comment)
45394540
in_block_comment = next_in_block_comment
45404541
if c_directive_name(clean) !in ['include', 'import'] {
@@ -4558,6 +4559,186 @@ fn (mut g FlatGen) collect_preserved_header_file(path string, include_dirs []str
45584559
}
45594560
}
45604561

4562+
// c_header_possible_active_text removes declarations from preprocessor branches
4563+
// known to be inactive for the current target and C flags. Unknown branches stay
4564+
// in the scan so a declaration that the real compiler may see remains authoritative.
4565+
fn c_header_possible_active_text(text string, flags []string, c99_mode bool, target pref.Target) string {
4566+
mut defined := map[string]bool{}
4567+
mut undefined := map[string]bool{}
4568+
mut uncertain := map[string]bool{}
4569+
// A preserved header inherits macros from its including source and parent
4570+
// headers. That context is not carried through this lightweight recursive
4571+
// scan, so an otherwise unknown macro must keep both branches possible.
4572+
mut external_macros_possible := true
4573+
mut i := 0
4574+
for i < flags.len {
4575+
clean := trimmed_space(flags[i])
4576+
mut definition := ''
4577+
mut is_undef := false
4578+
if clean == '-D' && i + 1 < flags.len {
4579+
definition = trimmed_space(flags[i + 1])
4580+
i++
4581+
} else if clean.starts_with('-D') {
4582+
definition = clean[2..]
4583+
} else if clean == '-U' && i + 1 < flags.len {
4584+
definition = trimmed_space(flags[i + 1])
4585+
is_undef = true
4586+
i++
4587+
} else if clean.starts_with('-U') {
4588+
definition = clean[2..]
4589+
is_undef = true
4590+
}
4591+
name := definition.all_before('=').trim_space()
4592+
if name.len > 0 {
4593+
if is_undef {
4594+
defined.delete(name)
4595+
undefined[name] = true
4596+
} else {
4597+
undefined.delete(name)
4598+
defined[name] = true
4599+
}
4600+
}
4601+
i++
4602+
}
4603+
strict_iso_mode := c_effective_strict_iso_mode(flags, c99_mode)
4604+
mut condition_known := []bool{}
4605+
mut condition_active := []bool{}
4606+
mut condition_taken_known := []bool{}
4607+
mut condition_taken := []bool{}
4608+
mut output := strings.new_builder(text.len)
4609+
mut in_block_comment := false
4610+
for line in c_join_continued_lines(text) {
4611+
clean, next_in_block_comment := c_preprocessor_directive_scan_line(line, in_block_comment)
4612+
in_block_comment = next_in_block_comment
4613+
name := c_directive_name(clean)
4614+
if name in ['ifdef', 'ifndef'] {
4615+
macro_name := c_directive_arg(clean).fields()[0] or { '' }
4616+
known, mut active := c_preprocessor_ifdef_macro_state(macro_name, defined, undefined,
4617+
uncertain, external_macros_possible, strict_iso_mode, target)
4618+
if name == 'ifndef' {
4619+
active = !active
4620+
}
4621+
condition_known << known
4622+
condition_active << (if known { active } else { true })
4623+
condition_taken_known << known
4624+
condition_taken << (if known { active } else { true })
4625+
output.writeln('')
4626+
continue
4627+
}
4628+
if name == 'if' {
4629+
known, active := c_preprocessor_condition_state(c_directive_arg(clean), defined,
4630+
undefined, uncertain, external_macros_possible, strict_iso_mode, target)
4631+
condition_known << known
4632+
condition_active << (if known { active } else { true })
4633+
condition_taken_known << known
4634+
condition_taken << (if known { active } else { true })
4635+
output.writeln('')
4636+
continue
4637+
}
4638+
if name == 'elif' && condition_known.len > 0 {
4639+
last := condition_known.len - 1
4640+
prior_known := condition_taken_known[last]
4641+
prior_taken := condition_taken[last]
4642+
known, active := c_preprocessor_condition_state(c_directive_arg(clean), defined,
4643+
undefined, uncertain, external_macros_possible, strict_iso_mode, target)
4644+
if (prior_known && prior_taken) || (known && !active) {
4645+
condition_known[last] = true
4646+
condition_active[last] = false
4647+
} else if prior_known && known {
4648+
condition_known[last] = true
4649+
condition_active[last] = true
4650+
} else {
4651+
condition_known[last] = false
4652+
condition_active[last] = true
4653+
}
4654+
if (prior_known && prior_taken) || (known && active) {
4655+
condition_taken_known[last] = true
4656+
condition_taken[last] = true
4657+
} else if prior_known && known {
4658+
condition_taken_known[last] = true
4659+
condition_taken[last] = false
4660+
} else {
4661+
condition_taken_known[last] = false
4662+
condition_taken[last] = true
4663+
}
4664+
output.writeln('')
4665+
continue
4666+
}
4667+
if name == 'else' && condition_known.len > 0 {
4668+
last := condition_known.len - 1
4669+
condition_known[last] = condition_taken_known[last]
4670+
condition_active[last] = if condition_taken_known[last] {
4671+
!condition_taken[last]
4672+
} else {
4673+
true
4674+
}
4675+
condition_taken_known[last] = true
4676+
condition_taken[last] = true
4677+
output.writeln('')
4678+
continue
4679+
}
4680+
if name == 'endif' && condition_known.len > 0 {
4681+
condition_known.delete_last()
4682+
condition_active.delete_last()
4683+
condition_taken_known.delete_last()
4684+
condition_taken.delete_last()
4685+
output.writeln('')
4686+
continue
4687+
}
4688+
mut possibly_active := true
4689+
mut definitely_active := true
4690+
for depth in 0 .. condition_known.len {
4691+
if condition_known[depth] && !condition_active[depth] {
4692+
possibly_active = false
4693+
definitely_active = false
4694+
break
4695+
}
4696+
if !condition_known[depth] {
4697+
definitely_active = false
4698+
}
4699+
}
4700+
if !possibly_active {
4701+
output.writeln('')
4702+
continue
4703+
}
4704+
if name in ['include', 'import'] {
4705+
c_preprocessor_invalidate_macro_state(mut defined, mut undefined, mut uncertain)
4706+
external_macros_possible = true
4707+
} else if name in ['define', 'undef'] {
4708+
parts := c_directive_arg(clean).fields()
4709+
if parts.len > 0 {
4710+
macro_name := parts[0].all_before('(')
4711+
if definitely_active {
4712+
uncertain.delete(macro_name)
4713+
if name == 'define' {
4714+
undefined.delete(macro_name)
4715+
defined[macro_name] = true
4716+
} else {
4717+
defined.delete(macro_name)
4718+
undefined[macro_name] = true
4719+
}
4720+
} else {
4721+
defined.delete(macro_name)
4722+
undefined.delete(macro_name)
4723+
uncertain[macro_name] = true
4724+
}
4725+
}
4726+
}
4727+
output.writeln(line)
4728+
}
4729+
return output.str()
4730+
}
4731+
4732+
fn c_preprocessor_ifdef_macro_state(name string, defined map[string]bool, undefined map[string]bool, uncertain map[string]bool, external_macros_possible bool, strict_iso_mode bool, target pref.Target) (bool, bool) {
4733+
if external_macros_possible && name !in defined && name !in undefined && name !in uncertain
4734+
&& name !in ['__linux__', '__linux', 'linux', 'unix', '__APPLE__', '__MACH__', '_WIN32', '_WIN64', '__FreeBSD__', '__OpenBSD__', '__NetBSD__'] {
4735+
// An earlier include can define an otherwise unknown macro. Keep both
4736+
// branches in that case so the declaration scan never drops active code.
4737+
return false, true
4738+
}
4739+
return c_preprocessor_macro_state(name, defined, undefined, uncertain, strict_iso_mode, target)
4740+
}
4741+
45614742
fn c_inline_header_text(include_arg string, vroot string, source_file string, include_dirs []string, translation_unit_uses_inttypes bool) ?CInlineHeader {
45624743
if replacement := c_system_include_replacement(include_arg, translation_unit_uses_inttypes) {
45634744
return CInlineHeader{
@@ -8378,8 +8559,8 @@ fn c_native_source_context_state(directives []string, flags []string, c99_mode b
83788559
name := c_directive_name(clean)
83798560
if name in ['ifdef', 'ifndef'] {
83808561
macro_name := c_directive_arg(clean).fields()[0] or { '' }
8381-
known, mut active := c_preprocessor_macro_state(macro_name, defined, undefined,
8382-
uncertain, strict_iso_mode, target)
8562+
known, mut active := c_preprocessor_ifdef_macro_state(macro_name, defined,
8563+
undefined, uncertain, external_macros_possible, strict_iso_mode, target)
83838564
if name == 'ifndef' {
83848565
active = !active
83858566
}
@@ -8624,8 +8805,8 @@ fn c_preprocessor_condition_state(raw string, defined map[string]bool, undefined
86248805
if macro_name.len == 0 || c_header_struct_tag(macro_name) != macro_name {
86258806
return false, true
86268807
}
8627-
known, mut active := c_preprocessor_macro_state(macro_name, defined, undefined, uncertain,
8628-
strict_iso_mode, target)
8808+
known, mut active := c_preprocessor_ifdef_macro_state(macro_name, defined, undefined,
8809+
uncertain, external_macros_possible, strict_iso_mode, target)
86298810
if negated {
86308811
active = !active
86318812
}

vlib/v3/gen/c/source_directive_test.v

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,34 @@ fn test_preserved_header_trees_scan_shared_files_once() {
5757
assert g.preserved_header_files_seen.len == 3
5858
}
5959

60+
fn test_preserved_header_ignores_known_inactive_declarations() {
61+
root := os.join_path(os.vtmp_dir(), 'v3_preserved_inactive_${os.getpid()}')
62+
os.rmdir_all(root) or {}
63+
os.mkdir_all(root) or { panic(err) }
64+
defer {
65+
os.rmdir_all(root) or {}
66+
}
67+
inactive_header := os.join_path(root, 'inactive.h')
68+
macro_header := os.join_path(root, 'macro.h')
69+
header := os.join_path(root, 'top.h')
70+
os.write_file(inactive_header, 'int nested_inactive_fn(void);\n')!
71+
os.write_file(macro_header,
72+
'#if defined(PARENT_HEADER_FEATURE)\nint parent_enabled_fn(void);\n#endif\n#define PRESERVED_HEADER_FEATURE 1\n')!
73+
os.write_file(header,
74+
'#if 0\nint inactive_fn(void);\n#include "inactive.h"\n#else\nint active_fn(void);\n#endif\n#define PARENT_HEADER_FEATURE 1\n#include "macro.h"\n#if defined(PRESERVED_HEADER_FEATURE)\nint include_enabled_fn(void);\n#endif\n')!
75+
76+
mut g := FlatGen.new()
77+
g.collect_preserved_header_file(header, [root])
78+
79+
assert 'inactive_fn' !in g.inlined_c_declared_fns
80+
assert 'nested_inactive_fn' !in g.inlined_c_declared_fns
81+
assert 'active_fn' in g.inlined_c_declared_fns
82+
assert 'include_enabled_fn' in g.inlined_c_declared_fns
83+
assert 'parent_enabled_fn' in g.inlined_c_declared_fns
84+
assert os.real_path(inactive_header) !in g.preserved_header_files_seen
85+
assert os.real_path(macro_header) in g.preserved_header_files_seen
86+
}
87+
6088
fn collect_external_input_tree_status(root string, entry string, ambient_ambiguous bool) (bool, []string) {
6189
mut active_paths := map[string]bool{}
6290
mut collected_paths := map[string]bool{}

0 commit comments

Comments
 (0)