Skip to content

Commit 9b9c848

Browse files
committed
v3: reject unresolved fastc names
1 parent ecdaffc commit 9b9c848

3 files changed

Lines changed: 129 additions & 23 deletions

File tree

vlib/v3/gen/fastc/fastc.v

Lines changed: 78 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -55,35 +55,69 @@ static void v_fastc_println_unsigned(unsigned long long value) { printf("%llu\n"
5555
struct Parser {
5656
path string
5757
mut:
58-
s scanner.Scanner
59-
tok token.Token
60-
lit string
61-
out strings.Builder
62-
protos strings.Builder
63-
indent int
64-
in_main bool
65-
has_main bool
66-
temp_id int
67-
locals map[string]bool
58+
s scanner.Scanner
59+
tok token.Token
60+
lit string
61+
out strings.Builder
62+
protos strings.Builder
63+
indent int
64+
in_main bool
65+
has_main bool
66+
temp_id int
67+
locals map[string]bool
68+
functions map[string]bool
6869
}
6970

7071
// generate scans V source and emits C as each declaration and statement is consumed. It does
7172
// not construct an AST or invoke semantic type checking. Unsupported syntax is returned as an
7273
// error; FastC never retries through an AST-based backend.
7374
pub fn generate(source string, path string, prefs &pref.Preferences) !string {
75+
functions := collect_function_names(source, path, prefs)!
7476
mut file_set := token.FileSet.new()
7577
mut file := file_set.add_file(path, source.len)
7678
file.index_lines(source)
7779
mut gen := Parser{
78-
path: path
79-
s: scanner.new_scanner(prefs, .normal)
80-
out: strings.new_builder(source.len)
81-
protos: strings.new_builder(256)
80+
path: path
81+
s: scanner.new_scanner(prefs, .normal)
82+
out: strings.new_builder(source.len)
83+
protos: strings.new_builder(256)
84+
functions: functions
8285
}
8386
gen.s.init(file, source)
8487
return gen.run()
8588
}
8689

90+
fn collect_function_names(source string, path string, prefs &pref.Preferences) !map[string]bool {
91+
mut file_set := token.FileSet.new()
92+
mut file := file_set.add_file(path, source.len)
93+
file.index_lines(source)
94+
mut scan := scanner.new_scanner(prefs, .normal)
95+
scan.init(file, source)
96+
mut functions := map[string]bool{}
97+
mut brace_depth := 0
98+
mut tok := scan.scan()
99+
for tok != .eof {
100+
if tok == .key_fn && brace_depth == 0 {
101+
tok = scan.scan()
102+
if tok == .name {
103+
name := scan.lit
104+
if name in functions {
105+
return error('fastc parser does not support duplicate function `${name}` in ${path}')
106+
}
107+
functions[name] = true
108+
}
109+
continue
110+
}
111+
if tok == .lcbr {
112+
brace_depth++
113+
} else if tok == .rcbr && brace_depth > 0 {
114+
brace_depth--
115+
}
116+
tok = scan.scan()
117+
}
118+
return functions
119+
}
120+
87121
fn (mut g Parser) run() !string {
88122
g.next()
89123
for g.tok != .eof {
@@ -417,6 +451,9 @@ fn (mut g Parser) parse_for() ! {
417451
name := g.lit
418452
g.next()
419453
if g.tok == .key_in {
454+
if name in g.locals {
455+
return g.unsupported('redeclaration of `${name}`')
456+
}
420457
g.next()
421458
start := g.read_expression([token.Token.dotdot])!
422459
g.expect(.dotdot)!
@@ -428,16 +465,22 @@ fn (mut g Parser) parse_for() ! {
428465
g.write_line('__typeof__((${start})) ${start_name} = (${start});')
429466
g.write_line('__typeof__((${end})) ${end_name} = (${end});')
430467
g.write_line('for (__typeof__((${start_name})) ${name} = (${start_name}); ${name} < (${end_name}); ${name}++) {')
468+
g.locals[name] = false
431469
g.indent++
432470
g.parse_block_body()!
433471
g.indent--
472+
g.locals.delete(name)
434473
g.write_line('}')
435474
return
436475
}
437476
if g.tok == .decl_assign {
477+
if name in g.locals {
478+
return g.unsupported('redeclaration of `${name}`')
479+
}
438480
g.next()
439481
initial := g.read_expression([token.Token.semicolon])!
440482
g.expect(.semicolon)!
483+
g.locals[name] = true
441484
condition := g.read_expression([token.Token.semicolon])!
442485
g.expect(.semicolon)!
443486
update := g.read_expression([token.Token.lcbr])!
@@ -446,9 +489,11 @@ fn (mut g Parser) parse_for() ! {
446489
g.indent++
447490
g.parse_block_body()!
448491
g.indent--
492+
g.locals.delete(name)
449493
g.write_line('}')
450494
return
451495
}
496+
g.validate_expression_name(name, .unknown)!
452497
condition := g.read_expression_with_prefix(name, [token.Token.lcbr])!
453498
g.expect(.lcbr)!
454499
g.write_line('while (${condition}) {')
@@ -507,6 +552,7 @@ fn (mut g Parser) parse_simple_statement() ! {
507552
&& (name !in g.locals || !g.locals[name]) {
508553
return g.unsupported('mutation of immutable or unknown name `${name}`')
509554
}
555+
g.validate_expression_name(name, .unknown)!
510556
expression :=
511557
g.read_expression_with_prefix(name, [token.Token.semicolon, token.Token.rcbr])!
512558
g.consume_statement_end()
@@ -564,6 +610,7 @@ fn (mut g Parser) read_expression_with_prefix(prefix string, stops []token.Token
564610
mut has_and_operator := false
565611
mut has_pipe_operator := false
566612
mut has_xor_operator := false
613+
mut previous_token := token.Token.unknown
567614
for g.tok != .eof {
568615
if paren_depth == 0 && g.tok in stops {
569616
break
@@ -629,7 +676,7 @@ fn (mut g Parser) read_expression_with_prefix(prefix string, stops []token.Token
629676
// levels and also orders + and - above &. Reject ambiguous token streams.
630677
return g.unsupported('mixed operator precedence')
631678
}
632-
piece := g.expression_token()!
679+
piece := g.expression_token(previous_token)!
633680
if result.len > 0 && fastc_needs_space(result.last(), piece) {
634681
result.write_u8(` `)
635682
}
@@ -646,6 +693,7 @@ fn (mut g Parser) read_expression_with_prefix(prefix string, stops []token.Token
646693
}
647694
else {}
648695
}
696+
previous_token = g.tok
649697
g.next()
650698
}
651699
if paren_depth != 0 {
@@ -654,9 +702,9 @@ fn (mut g Parser) read_expression_with_prefix(prefix string, stops []token.Token
654702
return result.str().trim_space()
655703
}
656704

657-
fn (g &Parser) expression_token() !string {
705+
fn (g &Parser) expression_token(previous token.Token) !string {
658706
return match g.tok {
659-
.name { g.expression_name()! }
707+
.name { g.expression_name(previous)! }
660708
.number { fastc_c_number(g.lit)! }
661709
.string { fastc_c_string(g.lit)! }
662710
.char { g.unsupported('rune or C character literals') }
@@ -671,14 +719,23 @@ fn (g &Parser) expression_token() !string {
671719
}
672720
}
673721

674-
fn (g &Parser) expression_name() !string {
675-
if g.lit == 'charptr' {
722+
fn (g &Parser) expression_name(previous token.Token) !string {
723+
g.validate_expression_name(g.lit, previous)!
724+
return g.lit
725+
}
726+
727+
fn (g &Parser) validate_expression_name(name string, previous token.Token) ! {
728+
if name == 'charptr' {
676729
return g.unsupported('charptr expressions')
677730
}
678-
if g.lit == 'rune' {
731+
if name == 'rune' {
679732
return g.unsupported('rune expressions')
680733
}
681-
return g.lit
734+
if previous == .dot || name in g.locals || name in g.functions
735+
|| name in ['print', 'println', 'bool', 'byte', 'char', 'f32', 'f64', 'i8', 'i16', 'i32', 'i64', 'int', 'isize', 'string', 'u8', 'u16', 'u32', 'u64', 'uint', 'usize', 'voidptr', 'byteptr'] {
736+
return
737+
}
738+
return g.unsupported('unresolved name `${name}`')
682739
}
683740

684741
fn fastc_nondecimal_literal_is_type_sensitive(literal string) bool {

vlib/v3/gen/fastc/fastc_test.v

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,38 @@ fn test_unsupported_import_is_rejected() {
7070
assert failed
7171
}
7272

73+
fn test_unresolved_names_are_rejected_before_c_emission() {
74+
prefs := pref.new_preferences()
75+
for source in [
76+
"module main\nfn main() { puts('hello') }\n",
77+
'module main\nfn main() { printf("hello") }\n',
78+
'module main\nfn main() { value := stdout; println(value) }\n',
79+
] {
80+
mut message := ''
81+
_ := generate(source, 'unresolved_name.v', prefs) or {
82+
message = err.msg()
83+
''
84+
}
85+
assert message.contains('fastc parser does not support unresolved name'), message
86+
}
87+
}
88+
89+
fn test_declared_names_are_available_without_an_ast() {
90+
prefs := pref.new_preferences()
91+
c_source := generate('module main
92+
93+
fn main() {
94+
println(later(2))
95+
}
96+
97+
fn later(value int) int {
98+
return value + 1
99+
}
100+
',
101+
'declared_names.v', prefs) or { panic(err) }
102+
assert c_source.contains('println(later(2));')
103+
}
104+
73105
fn test_bare_return_from_main_emits_zero() {
74106
prefs := pref.new_preferences()
75107
c_source := generate('module main

vlib/v3/tests/fastc_backend_test.v

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,22 @@ fn main() {
122122
assert !os.exists(invalid_binary)
123123
assert !os.exists(invalid_binary + '.c')
124124

125+
preamble_name_source := os.join_path(root, 'preamble_name.v')
126+
write_fastc_test_source(preamble_name_source, "module main
127+
128+
fn main() {
129+
puts('hello')
130+
}
131+
")
132+
preamble_name_binary := os.join_path(root, 'preamble_name')
133+
preamble_name_compile := cmdexec.run(v3_bin, ['-silent', '-b', 'fastc', '-o',
134+
preamble_name_binary, preamble_name_source])
135+
assert preamble_name_compile.exit_code != 0
136+
assert preamble_name_compile.output.contains('fastc parser does not support unresolved name `puts`'), preamble_name_compile.output
137+
138+
assert !os.exists(preamble_name_binary)
139+
assert !os.exists(preamble_name_binary + '.c')
140+
125141
for invocation in [
126142
UnsupportedFastCInvocation{
127143
args: ['-silent', '-prod', '-b', 'fastc', '-o', os.join_path(root, 'prod'),
@@ -134,8 +150,9 @@ fn main() {
134150
expected: 'fastc parser does not support compiler self-hosting'
135151
},
136152
UnsupportedFastCInvocation{
137-
args: ['-silent', '-b', 'fastc', '-d', 'no_main', '-o',
138-
os.join_path(root, 'no_main.c'), valid_source]
153+
args: ['-silent', '-b', 'fastc', '-d', 'no_main', '-o', os.join_path(root,
154+
'no_main.c'),
155+
valid_source]
139156
expected: 'fastc parser does not support `-d no_main`'
140157
},
141158
UnsupportedFastCInvocation{

0 commit comments

Comments
 (0)