From 017cc6738ba1919ea9c8ddb520e1fce5c00065ab Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Sat, 17 Jun 2023 22:29:36 +0300 Subject: [PATCH] checker: add a hard to reach limit of 1_000_000 iterations for resolving all generics (usually <10 are enough) (#18477) --- vlib/v/checker/checker.v | 16 +++++++++------- vlib/v/checker/fn.v | 11 +++++++++-- .../tests/generic_fn_infinite_loop_limit_err.out | 12 ++++++++++++ .../tests/generic_fn_infinite_loop_limit_err.vv | 8 ++++++++ 4 files changed, 38 insertions(+), 9 deletions(-) create mode 100644 vlib/v/checker/tests/generic_fn_infinite_loop_limit_err.out create mode 100644 vlib/v/checker/tests/generic_fn_infinite_loop_limit_err.vv diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index c2f7cccc8..f2cdf3b6c 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -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 { diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v index 79009ed6e..9fd0f14a2 100644 --- a/vlib/v/checker/fn.v +++ b/vlib/v/checker/fn.v @@ -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 diff --git a/vlib/v/checker/tests/generic_fn_infinite_loop_limit_err.out b/vlib/v/checker/tests/generic_fn_infinite_loop_limit_err.out new file mode 100644 index 000000000..ae3fea784 --- /dev/null +++ b/vlib/v/checker/tests/generic_fn_infinite_loop_limit_err.out @@ -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 | } diff --git a/vlib/v/checker/tests/generic_fn_infinite_loop_limit_err.vv b/vlib/v/checker/tests/generic_fn_infinite_loop_limit_err.vv new file mode 100644 index 000000000..981afd640 --- /dev/null +++ b/vlib/v/checker/tests/generic_fn_infinite_loop_limit_err.vv @@ -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 +} -- 2.39.5