1
0
mirror of https://github.com/vlang/v.git synced 2023-08-10 21:13:21 +03:00

checker: add a hard to reach limit of 1_000_000 iterations for resolving all generics (usually <10 are enough) (#18477)

This commit is contained in:
Delyan Angelov 2023-06-17 22:29:36 +03:00 committed by GitHub
parent 9c5aeb62b2
commit 017cc6738b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 38 additions and 9 deletions

View File

@ -14,12 +14,14 @@ import v.errors
import v.pkgconfig
const (
int_min = int(0x80000000)
int_max = int(0x7FFFFFFF)
int_min = int(0x80000000)
int_max = int(0x7FFFFFFF)
// prevent stack overflows by restricting too deep recursion:
expr_level_cutoff_limit = 40
stmt_level_cutoff_limit = 40
iface_level_cutoff_limit = 100
expr_level_cutoff_limit = 40
stmt_level_cutoff_limit = 40
iface_level_cutoff_limit = 100
generic_fn_cutoff_limit_per_fn = 10_000 // how many times post_process_generic_fns, can visit the same function before bailing out
generic_fn_postprocess_iterations_cutoff_limit = 1000_000 // how many times the compiler will try to resolve all remaining generic functions
)
pub const (
@ -323,7 +325,7 @@ pub fn (mut c Checker) check_files(ast_files []&ast.File) {
// is needed when the generic type is auto inferred from the call argument.
// we may have to loop several times, if there were more concrete types found.
mut post_process_generic_fns_iterations := 0
for {
post_process_iterations_loop: for post_process_generic_fns_iterations <= checker.generic_fn_postprocess_iterations_cutoff_limit {
$if trace_post_process_generic_fns_loop ? {
eprintln('>>>>>>>>> recheck_generic_fns loop iteration: ${post_process_generic_fns_iterations}')
}
@ -334,7 +336,7 @@ pub fn (mut c Checker) check_files(ast_files []&ast.File) {
file.generic_fns.map(it.name).str())
}
c.change_current_file(file)
c.post_process_generic_fns()
c.post_process_generic_fns() or { break post_process_iterations_loop }
}
}
if !c.need_recheck_generic_fns {

View File

@ -2210,16 +2210,23 @@ fn (mut c Checker) set_node_expected_arg_types(mut node ast.CallExpr, func &ast.
}
}
fn (mut c Checker) post_process_generic_fns() {
fn (mut c Checker) post_process_generic_fns() ! {
mut all_generic_fns := map[string]int{}
// Loop thru each generic function concrete type.
// Check each specific fn instantiation.
for i in 0 .. c.file.generic_fns.len {
mut node := c.file.generic_fns[i]
c.mod = node.mod
fkey := node.fkey()
all_generic_fns[fkey]++
if all_generic_fns[fkey] > generic_fn_cutoff_limit_per_fn {
c.error('generic function visited more than ${generic_fn_cutoff_limit_per_fn} times',
node.pos)
return error('fkey: ${fkey}')
}
gtypes := c.table.fn_generic_types[fkey]
$if trace_post_process_generic_fns ? {
eprintln('> post_process_generic_fns ${node.mod} | ${node.name} | fkey: ${fkey} | gtypes: ${gtypes}')
eprintln('> post_process_generic_fns ${node.mod} | ${node.name} | fkey: ${fkey} | gtypes: ${gtypes} | c.file.generic_fns.len: ${c.file.generic_fns.len}')
}
for concrete_types in gtypes {
c.table.cur_concrete_types = concrete_types

View File

@ -0,0 +1,12 @@
vlib/v/checker/tests/generic_fn_infinite_loop_limit_err.vv:6:2: warning: unused variable: `ff1`
4 |
5 | fn main() {
6 | ff1 := f1 // <-- missing e.g. `[int]`
| ~~~
7 | // ff1 := f1[int] <-- is a valid usage with generic return types that are not generic structs
8 | }
vlib/v/checker/tests/generic_fn_infinite_loop_limit_err.vv:1:1: error: generic function visited more than 10000 times
1 | fn f1[T](x T, i int) T {
| ~~~~~~~~~~~~~~~~~~~~~~
2 | return x
3 | }

View File

@ -0,0 +1,8 @@
fn f1[T](x T, i int) T {
return x
}
fn main() {
ff1 := f1 // <-- missing e.g. `[int]`
// ff1 := f1[int] <-- is a valid usage with generic return types that are not generic structs
}