Skip to content

Commit d2384e8

Browse files
committed
v3: validate fastc match and call visibility
1 parent 9754c57 commit d2384e8

2 files changed

Lines changed: 81 additions & 1 deletion

File tree

vlib/v3/gen/fastc/fastc.v

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,8 @@ struct FastcFunctionSignature {
204204
return_types []string
205205
option_type string
206206
is_variadic bool
207+
is_public bool
208+
module_name string
207209
path string
208210
}
209211

@@ -1670,6 +1672,7 @@ fn collect_function_signatures(source string, path string, header FastcSourceHea
16701672
mut tok := scan.scan()
16711673
for tok != .eof {
16721674
if tok == .key_fn && brace_depth == 0 && previous_tok != .assign {
1675+
is_public := previous_tok == .key_pub
16731676
tok = scan.scan()
16741677
mut parameter_types := []string{}
16751678
mut receiver_type := ''
@@ -1868,6 +1871,8 @@ fn collect_function_signatures(source string, path string, header FastcSourceHea
18681871
return_types: return_types
18691872
option_type: option_type
18701873
is_variadic: is_variadic
1874+
is_public: is_public || is_c_function
1875+
module_name: header.module_name
18711876
path: path
18721877
}
18731878
if previous := functions[function_key] {
@@ -2117,6 +2122,8 @@ fn collect_interface_method_signatures(source string, path string, header FastcS
21172122
return_type: return_type
21182123
return_types: return_types
21192124
option_type: option_type
2125+
is_public: true
2126+
module_name: header.module_name
21202127
path: path
21212128
}
21222129
interface_methods[interface_method_key] = true
@@ -3455,12 +3462,14 @@ fn (mut g Parser) parse_match_statement() !bool {
34553462
is_string := subject_type == 'string'
34563463
mut branch_index := 0
34573464
mut all_terminate := true
3465+
mut has_else := false
34583466
g.skip_semicolons()
34593467
for g.tok != .rcbr {
34603468
if g.tok == .eof {
34613469
return g.unsupported('unfinished match statement')
34623470
}
34633471
is_else := g.tok == .key_else
3472+
has_else = has_else || is_else
34643473
mut values := []string{}
34653474
if is_else {
34663475
g.next()
@@ -3523,7 +3532,7 @@ fn (mut g Parser) parse_match_statement() !bool {
35233532
}
35243533
g.next()
35253534
g.skip_semicolons()
3526-
return all_terminate
3535+
return has_else && all_terminate
35273536
}
35283537

35293538
fn (mut g Parser) parse_comptime_if_statement() !bool {
@@ -9017,6 +9026,11 @@ fn (g &Parser) validate_expression_calls(tokens []FastcExpressionToken) ! {
90179026
}
90189027
}
90199028
if signature := g.functions[function_key] {
9029+
if !signature.is_public && signature.module_name != ''
9030+
&& signature.module_name != g.module_name && signature.module_name != 'builtin'
9031+
&& signature.module_name in g.imports.values() {
9032+
return g.unsupported('private function `${name}` from imported module `${signature.module_name}`')
9033+
}
90209034
argument_offset := if is_method_call { 1 } else { 0 }
90219035
is_variadic := signature.is_variadic
90229036
expected_arguments := signature.parameter_types.len - argument_offset - if is_variadic {

vlib/v3/gen/fastc/fastc_test.v

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,34 @@ fn test_generate_files_resolves_modules_without_an_ast() {
102102
assert run_result.output.trim_space() == '42'
103103
}
104104

105+
fn test_generate_files_rejects_private_imported_functions() {
106+
root := os.join_path(os.vtmp_dir(), 'v3_fastc_private_import_${os.getpid()}')
107+
os.rmdir_all(root) or {}
108+
os.mkdir_all(os.join_path(root, 'secrets')) or { panic(err) }
109+
defer {
110+
os.rmdir_all(root) or {}
111+
}
112+
main_file := os.join_path(root, 'main.v')
113+
module_file := os.join_path(root, 'secrets', 'secrets.v')
114+
os.write_file(main_file,
115+
'module main\nimport secrets\nfn main() { println(secrets.secret()) }\n') or { panic(err) }
116+
os.write_file(module_file, 'module secrets\nfn secret() int { return 42 }\n') or { panic(err) }
117+
mut prefs := pref.new_preferences()
118+
prefs.module_search_paths = [root]
119+
mut message := ''
120+
_ := generate_files([main_file], prefs) or {
121+
message = err.msg()
122+
''
123+
}
124+
assert message.contains('private function `secret` from imported module `secrets`'), message
125+
126+
os.write_file(module_file, 'module secrets\npub fn secret() int { return 42 }\n') or {
127+
panic(err)
128+
}
129+
c_source := generate_files([main_file], prefs) or { panic(err) }
130+
assert c_source.contains('println(secrets__secret());'), c_source
131+
}
132+
105133
fn test_disabled_function_attributes_emit_empty_stubs() {
106134
mut prefs := pref.new_preferences()
107135
prefs.user_defines = []
@@ -391,6 +419,44 @@ fn main() {
391419
assert c_source.contains('? (7) : (9)')
392420
}
393421

422+
fn test_match_statement_without_else_does_not_terminate_function() {
423+
prefs := pref.new_preferences()
424+
mut message := ''
425+
_ := generate('module main
426+
427+
fn value(x int) int {
428+
match x {
429+
1 { return 7 }
430+
}
431+
}
432+
433+
fn main() {
434+
println(value(1))
435+
}
436+
',
437+
'non_exhaustive_match_statement.v', prefs) or {
438+
message = err.msg()
439+
''
440+
}
441+
assert message.contains('non-void function `value` that can fall through'), message
442+
443+
c_source := generate('module main
444+
445+
fn value(x int) int {
446+
match x {
447+
1 { return 7 }
448+
else { return 9 }
449+
}
450+
}
451+
452+
fn main() {
453+
println(value(1))
454+
}
455+
',
456+
'exhaustive_match_statement.v', prefs) or { panic(err) }
457+
assert c_source.contains('else {\n\t\treturn 9;'), c_source
458+
}
459+
394460
fn test_c_reserved_identifiers_are_escaped_consistently() {
395461
prefs := pref.new_preferences()
396462
c_source := generate('module main

0 commit comments

Comments
 (0)