diff options
Diffstat (limited to 'v_windows/v/vlib/v/tests/valgrind')
18 files changed, 731 insertions, 0 deletions
diff --git a/v_windows/v/vlib/v/tests/valgrind/1.strings_and_arrays.v b/v_windows/v/vlib/v/tests/valgrind/1.strings_and_arrays.v new file mode 100644 index 0000000..1802eb2 --- /dev/null +++ b/v_windows/v/vlib/v/tests/valgrind/1.strings_and_arrays.v @@ -0,0 +1,410 @@ +import os +import strings + +// This program is built and run via Valgrind to ensure there are no leaks with -autofree +fn simple() { + nums := [1, 2, 3] // local array must be freed + println(nums) + nums_copy := nums.clone() // array assignments call .clone() + println(nums_copy) + name := 'Peter' // string literals mustn't be freed + str_inter := 'hello, $name' // concatenated strings must be freed + // nums.free() // this should result in a double free and a CI error + if true { + // test the freeing of local vars in a new scope + nums2 := [4, 5, 6] + str_inter2 := 'hello, $name' + println(nums2) + } + arr := return_array([]) + println(arr) +} + +fn return_array(array_arg []string) []int { // array argument must not be freed + s := [1, 2, 3] // escaping array must not be freed + return s +} + +fn handle_strings(s string, p string) int { + return 0 +} + +fn handle_int(n int) { +} + +fn add_strings(a string, b string) string { + return a + b +} + +fn str_tmp_expr() { + println('a' + 'b') // tmp expression result must be freed + handle_strings('c' + 'd', 'e' + 'f') // multiple tmp expressions must be freed + handle_int(handle_strings('x' + 'y', 'f')) // exprs 2 levels deep must bee freed + handle_strings('1', add_strings('2', '3')) // test a fn that returns a string +} + +fn str_tmp_expr_advanced() { + // t1 = 'c' + 'd' + // t2 = 'e + f' + // t3 = add_strings(t2, 'g') + // handle_strings(t1, t3) + // t1.free() + // t2.free() + // t3.free() + // + handle_strings('c' + 'd', add_strings('e' + 'f', 'g')) // both lvl 1 and lvl2 exprs must be freed +} + +fn str_tmp_expr_advanced_var_decl() { + a := handle_strings('c' + 'd', add_strings('e' + 'f', 'g')) // both lvl 1 and lvl2 exprs must be freed + println(a) +} + +struct Foo { + a int + b string +} + +fn str_inter() { + a := 10 + println('a = $a') + // foo := Foo{10, 'x' + 'x'} + // println('foo = $foo') // TODO +} + +fn str_replace() { + mut s := 'hello world' + s = s.replace('hello', 'hi') // s can't be freed as usual before the assignment, since it's used in the right expr + println(s) + // + mut s2 := 'aa' + 'bb' + s2 = s2.replace('a', 'c') + println(s2) + /* + r := s.replace('hello', 'hi') + cloned := s.replace('hello', 'hi').clone() + cloned2 := r.clone() + println(s) + println(r) + */ +} + +fn fooo(s string) { +} + +fn str_replace2() { + mut s := 'hello world' + s = s.replace('hello', 'hi').replace('world', 'planet') + println(s) +} + +fn reassign_str() { + mut z := '1' + '2' + if true { + println('KEK') + z = 'foo' + } + mut x := 'a' + x = 'b' // nothing has to be freed here + // + mut s := 'a' + 'b' + s = 'x' + 'y' // 'a' + 'b' must be freed before the re-assignment + s = s + '!' // old s ref must be copied and freed after the assignment, since s is still used in the right expr +} + +struct Foo2 { +mut: + nums []int +} + +fn reassign_arr() { + mut x := [1, 2, 3] + // x must be freed before the re-assignment + x = [4, 5, 6] + mut foo := Foo2{[10, 20, 30]} + foo.nums = [40, 50, 60] // same with struct fields + foo.nums = [70, 80, 90] + // TODO remove this once structs are freed automatically + foo.nums.free() +} + +fn match_expr() string { + x := 2 + res := match x { + 1 { 'one' } + 2 { 'two' } + else { 'unknown' } + } + return res +} + +fn opt(s string) ?int { + return 1 +} + +fn optional_str() { + q := 'select' + s := 'query: select' + // optional fn args must be freed + pos2 := opt('query:$q') or { + // pos := s.index('query: $q') or { + println('exiting') + return + } + println(pos2 + 1) + // optional method args must be freed + pos := s.index('query: $q') or { + println('exiting') + return + } + println(pos + 1) + // test assigning an optional to an existing var + mut p := 0 + for { + p = opt('query:$q') or { break } + break + } +} + +fn return_error_with_freed_expr() ?string { + if true { + msg := 'oops' + return error('hm $msg') + } + return 'ok' +} + +fn optional_return() { + return_error_with_freed_expr() or { return } +} + +fn handle_string(s string) bool { + return true +} + +fn if_cond() { + // handle_string('1' + '2') + if handle_string('1' + '2') { + // if '1' + '2' == '12' { + println('yes') + } else { + println('no') + } +} + +fn addition_with_tmp_expr() { + x := 1 + handle_strings('a' + 'b', 'c') + println(x) +} + +fn tt() { + // time.parse_rfc2822('1234') +} + +fn get_string(s string) string { + return s.clone() // TODO handle returning the argument without clone() +} + +fn if_expr() string { + a := if true { get_string('a' + 'b') } else { get_string('c' + 'd') } + return a +} + +fn return_if_expr() string { + return if true { get_string('a' + 'b') } else { get_string('c' + 'd') } +} + +fn loop_map() { + m := { + 'UK': 'London' + 'France': 'Paris' + } + // keys must be freed + for country, capital in m { + println(country + capital) + } +} + +fn free_map() { + nums := [1, 2, 3] + /* + nums2 := nums.map(it + handle_strings('a' + 'b', 'c')) + println(nums2) + */ +} + +fn free_inside_opt_block() { + x := opt('a' + 'b') or { + get_string('c' + 'd') // c+d must be freed before a+b + return + } +} + +fn free_before_return() { + s := 'a' + 'b' + println(s) + if true { + return + } +} + +fn free_before_return_bool() bool { + s := 'a' + 'b' + println(s) + return true +} + +fn free_before_break() { + s := 'a' + 'b' + for { + q := [1, 2, 3] + break + } + for { + aa := [1, 2, 3] + if true { + // breaking should free only vars in the closest for loop's scope + // `qq`, not `s` + break + } + // nested 1 + for { + bb := [4, 5, 6] + if true { + break + } + // nested 2 + for { + cc := [7, 8, 9] + if true { + if true { + break + } + break + } + } + } + } + mut i := 0 + for { + i++ + qq := [1, 2, 3] + if i > 10 { + break + } + if true { + continue + } + } + x := ['1', '2', '3'] + for n in x { + f := 'f' + println('$n => $f') + } +} + +struct User { + name string + age int +} + +fn free_array_except_returned_element() { + user := get_user() + println(user) +} + +fn get_user() User { + users := [User{'Peter', 25}, User{'Alice', 21}] + user := users[0] // has to be cloned, since `users` are going to be freed at the end of the function + return user +} + +fn get_user2() User { + users := [User{'Peter', 25}, User{'Alice', 21}] + return users[0] // has to be cloned, since `users` are going to be freed at the end of the function +} + +fn string_array_get() { + s := ['a', 'b', 'c'] + x := s[0] + println(s) +} + +fn comp_if() { + // compif pos used to be 0, if it was the first statement in a block, vars wouldn't be freed + $if macos { + println('macos') + } + s := 'a' + 'b' + println(s) +} + +fn anon_fn() { +} + +fn return_sb_str() string { + mut sb := strings.new_builder(100) + sb.write_string('hello') + return sb.str() // sb should be freed, but only after .str() is called +} + +fn parse_header0(s string) ?string { + if !s.contains(':') { + return error('missing colon in header') + } + words := s.split_nth(':', 2) + x := words[0] + return x +} + +fn parse_header1(s string) ?string { + if !s.contains(':') { + return error('missing colon in header') + } + words := s.split_nth(':', 2) + return words[0] +} + +fn advanced_optionals() { + s := parse_header0('foo:bar') or { return } + s2 := parse_header1('foo:bar') or { return } +} + +fn main() { + println('start') + simple() + reassign_str() + reassign_arr() + str_tmp_expr() + str_tmp_expr_advanced() + str_tmp_expr_advanced_var_decl() + str_inter() + match_expr() + optional_str() + // optional_return() + str_replace() + str_replace2() + if_cond() + addition_with_tmp_expr() + q := if_expr() + s := return_if_expr() + free_inside_opt_block() + comp_if() + free_before_return() + free_before_return_bool() + free_before_break() + s2 := return_sb_str() + // free_map() + // loop_map() + advanced_optionals() + free_array_except_returned_element() + println('end') +} + +/* +s := s.replace().replace() +tmp := s.replace() +s.free() +s = tmp.replace() +tmp.free() +*/ diff --git a/v_windows/v/vlib/v/tests/valgrind/array_init_with_string_variable.v b/v_windows/v/vlib/v/tests/valgrind/array_init_with_string_variable.v new file mode 100644 index 0000000..8c8f4f3 --- /dev/null +++ b/v_windows/v/vlib/v/tests/valgrind/array_init_with_string_variable.v @@ -0,0 +1,11 @@ +module main + +fn main() { + a := 'Hello, ' + b := 'World!' + c := a + b + + d := [a, b, c] + + println(d.len) +} diff --git a/v_windows/v/vlib/v/tests/valgrind/base64.v b/v_windows/v/vlib/v/tests/valgrind/base64.v new file mode 100644 index 0000000..14acce2 --- /dev/null +++ b/v_windows/v/vlib/v/tests/valgrind/base64.v @@ -0,0 +1,59 @@ +import encoding.base64 + +fn main() { + repeats := 1000 + input_size := 3000 + + s_original := []byte{len: input_size, init: `a`} + s_encoded := base64.encode(s_original) + s_encoded_bytes := s_encoded.bytes() + s_decoded := base64.decode(s_encoded) + + assert s_encoded.len > s_original.len + assert s_original == s_decoded + + ebuffer := unsafe { malloc(s_encoded.len) } + dbuffer := unsafe { malloc(s_decoded.len) } + defer { + unsafe { free(ebuffer) } + unsafe { free(dbuffer) } + } + // + encoded_size := base64.encode_in_buffer(s_original, ebuffer) + mut encoded_in_buf := []byte{len: encoded_size} + unsafe { C.memcpy(encoded_in_buf.data, ebuffer, encoded_size) } + assert input_size * 4 / 3 == encoded_size + assert encoded_in_buf[0] == `Y` + assert encoded_in_buf[1] == `W` + assert encoded_in_buf[2] == `F` + assert encoded_in_buf[3] == `h` + + assert encoded_in_buf[encoded_size - 4] == `Y` + assert encoded_in_buf[encoded_size - 3] == `W` + assert encoded_in_buf[encoded_size - 2] == `F` + assert encoded_in_buf[encoded_size - 1] == `h` + + assert encoded_in_buf == s_encoded_bytes + + decoded_size := base64.decode_in_buffer(s_encoded, dbuffer) + assert decoded_size == input_size + mut decoded_in_buf := []byte{len: decoded_size} + unsafe { C.memcpy(decoded_in_buf.data, dbuffer, decoded_size) } + assert decoded_in_buf == s_original + + mut s := 0 + for _ in 0 .. repeats { + resultsize := base64.encode_in_buffer(s_original, ebuffer) + s += resultsize + assert resultsize == s_encoded.len + } + + for _ in 0 .. repeats { + resultsize := base64.decode_in_buffer(s_encoded, dbuffer) + s += resultsize + assert resultsize == s_decoded.len + } + + println('Final s: $s') + // assert s == 39147008 +} diff --git a/v_windows/v/vlib/v/tests/valgrind/buffer_passed_in_fn_that_uses_tos_on_it.v b/v_windows/v/vlib/v/tests/valgrind/buffer_passed_in_fn_that_uses_tos_on_it.v new file mode 100644 index 0000000..24b5344 --- /dev/null +++ b/v_windows/v/vlib/v/tests/valgrind/buffer_passed_in_fn_that_uses_tos_on_it.v @@ -0,0 +1,7 @@ +fn main() { + unsafe { + mut buffer := malloc_noscan(5) + s := utf32_to_str_no_malloc(77, buffer) + println(s) + } +} diff --git a/v_windows/v/vlib/v/tests/valgrind/fn_returning_string_param.v b/v_windows/v/vlib/v/tests/valgrind/fn_returning_string_param.v new file mode 100644 index 0000000..d040c9d --- /dev/null +++ b/v_windows/v/vlib/v/tests/valgrind/fn_returning_string_param.v @@ -0,0 +1,13 @@ +fn identity(s string) string { + return s +} + +fn main() { + sa := 'abc' + sb := 'def' + sc := sa + sb + sd := identity(sc) + if sc != sd { + exit(1) + } +} diff --git a/v_windows/v/vlib/v/tests/valgrind/fn_with_return_should_free_local_vars.v b/v_windows/v/vlib/v/tests/valgrind/fn_with_return_should_free_local_vars.v new file mode 100644 index 0000000..35ca7a9 --- /dev/null +++ b/v_windows/v/vlib/v/tests/valgrind/fn_with_return_should_free_local_vars.v @@ -0,0 +1,22 @@ +fn many_strings() []string { + mut res := []string{} + for i in 0 .. 10000 { + res << '${i:05}' + } + return res +} + +fn abc() int { + x := many_strings() + n := x.len + // NB: the local `x` is no longer used + // it should be freed *before* the return + return n +} + +fn main() { + for i in 0 .. 5 { + nstrings := abc() + eprintln('nstrings ${i:10}: $nstrings') + } +} diff --git a/v_windows/v/vlib/v/tests/valgrind/if_expr.v b/v_windows/v/vlib/v/tests/valgrind/if_expr.v new file mode 100644 index 0000000..e741f86 --- /dev/null +++ b/v_windows/v/vlib/v/tests/valgrind/if_expr.v @@ -0,0 +1,11 @@ +fn main() { + mut a := true + b := a && if true { + a = false + true + } else { + false + } + println(b) + assert b == true +} diff --git a/v_windows/v/vlib/v/tests/valgrind/if_expr_skip.v b/v_windows/v/vlib/v/tests/valgrind/if_expr_skip.v new file mode 100644 index 0000000..be1d65d --- /dev/null +++ b/v_windows/v/vlib/v/tests/valgrind/if_expr_skip.v @@ -0,0 +1,13 @@ +fn main() { + mut a := false + b := a && if true { + a = true + true + } else { + false + } + println(b) + assert b == false + println(a) + assert a == false +} diff --git a/v_windows/v/vlib/v/tests/valgrind/logging.v b/v_windows/v/vlib/v/tests/valgrind/logging.v new file mode 100644 index 0000000..ec2f016 --- /dev/null +++ b/v_windows/v/vlib/v/tests/valgrind/logging.v @@ -0,0 +1,18 @@ +import log +import time + +fn main() { + mut l := log.Log{} + defer { + l.close() + } + l.set_level(.info) + l.info('info') + l.warn('warn') + l.error('an error') + l.debug('some debug info') + for i := 0; i < 100; i++ { + l.info('123456') + time.sleep(1 * time.millisecond) + } +} diff --git a/v_windows/v/vlib/v/tests/valgrind/option_reassigned.v b/v_windows/v/vlib/v/tests/valgrind/option_reassigned.v new file mode 100644 index 0000000..50812e2 --- /dev/null +++ b/v_windows/v/vlib/v/tests/valgrind/option_reassigned.v @@ -0,0 +1,7 @@ +import os + +fn main() { + mut a := 'abc' + a = os.find_abs_path_of_executable('ls') or { '' } + eprintln(a) +} diff --git a/v_windows/v/vlib/v/tests/valgrind/option_simple.v b/v_windows/v/vlib/v/tests/valgrind/option_simple.v new file mode 100644 index 0000000..5b9d8fc --- /dev/null +++ b/v_windows/v/vlib/v/tests/valgrind/option_simple.v @@ -0,0 +1,6 @@ +import os + +fn main() { + a := os.find_abs_path_of_executable('ls') or { '' } + eprintln(a) +} diff --git a/v_windows/v/vlib/v/tests/valgrind/rune_str_method.v b/v_windows/v/vlib/v/tests/valgrind/rune_str_method.v new file mode 100644 index 0000000..e04c78b --- /dev/null +++ b/v_windows/v/vlib/v/tests/valgrind/rune_str_method.v @@ -0,0 +1,4 @@ +fn main() { + a := `Y`.str() + println(a) +} diff --git a/v_windows/v/vlib/v/tests/valgrind/simple_interpolation.v b/v_windows/v/vlib/v/tests/valgrind/simple_interpolation.v new file mode 100644 index 0000000..16acab5 --- /dev/null +++ b/v_windows/v/vlib/v/tests/valgrind/simple_interpolation.v @@ -0,0 +1,5 @@ +fn main() { + v := 't' + s := '${v}.tmp' + println(s) +} diff --git a/v_windows/v/vlib/v/tests/valgrind/simple_interpolation_script_mode.v b/v_windows/v/vlib/v/tests/valgrind/simple_interpolation_script_mode.v new file mode 100644 index 0000000..54ea943 --- /dev/null +++ b/v_windows/v/vlib/v/tests/valgrind/simple_interpolation_script_mode.v @@ -0,0 +1,4 @@ +// TODO: @medvednikov: autofree on script mode does not work when first variable is on position 0 due to the code in `cgen.v:1115` +v := 't' +s := '${v}.tmp' +println(s) diff --git a/v_windows/v/vlib/v/tests/valgrind/simple_interpolation_script_mode_more_scopes.v b/v_windows/v/vlib/v/tests/valgrind/simple_interpolation_script_mode_more_scopes.v new file mode 100644 index 0000000..0856646 --- /dev/null +++ b/v_windows/v/vlib/v/tests/valgrind/simple_interpolation_script_mode_more_scopes.v @@ -0,0 +1,7 @@ +{ + { + v := 't' + s := '${v}.tmp' + println(s) + } +} diff --git a/v_windows/v/vlib/v/tests/valgrind/string_str_method.v b/v_windows/v/vlib/v/tests/valgrind/string_str_method.v new file mode 100644 index 0000000..111e6b1 --- /dev/null +++ b/v_windows/v/vlib/v/tests/valgrind/string_str_method.v @@ -0,0 +1,4 @@ +fn main() { + a := 'Y'.str() + println(a) +} diff --git a/v_windows/v/vlib/v/tests/valgrind/struct_field.v b/v_windows/v/vlib/v/tests/valgrind/struct_field.v new file mode 100644 index 0000000..6972818 --- /dev/null +++ b/v_windows/v/vlib/v/tests/valgrind/struct_field.v @@ -0,0 +1,17 @@ +struct LeakStruct { +mut: + some_bytes []byte +} + +fn (mut l LeakStruct) free() { + unsafe { + l.some_bytes.free() + } +} + +fn main() { + z := &LeakStruct{ + some_bytes: []byte{len: 1000} + } + println(z.some_bytes.len) +} diff --git a/v_windows/v/vlib/v/tests/valgrind/valgrind_test.v b/v_windows/v/vlib/v/tests/valgrind/valgrind_test.v new file mode 100644 index 0000000..fc18276 --- /dev/null +++ b/v_windows/v/vlib/v/tests/valgrind/valgrind_test.v @@ -0,0 +1,113 @@ +import os +import term +import benchmark +import v.util.vtest + +const turn_off_vcolors = os.setenv('VCOLORS', 'never', true) + +fn bold(s string) string { + return term.colorize(term.bold, s) +} + +// +// NB: skip_compile_files can be used for totally skipping .v files temporarily. +// .v files in skip_valgrind_files will be compiled, but will not be run under +// valgrind. This ensures that at least the generated code does not have C syntax +// errors. +// Use: `./v -d noskipcompile vlib/v/tests/valgrind/valgrind_test.v` to ignore the +// skip_compile_files list. +// Use: `./v -d noskip vlib/v/tests/valgrind/valgrind_test.v` to ignore skip_valgrind_files +// Use: `./v -d noskipcompile -d noskip vlib/v/tests/valgrind/valgrind_test.v` to ignore both +// +const skip_compile_files = [ + 'vlib/v/tests/valgrind/option_reassigned.v', +] + +const skip_valgrind_files = [ + 'vlib/v/tests/valgrind/struct_field.v', + 'vlib/v/tests/valgrind/fn_returning_string_param.v', + 'vlib/v/tests/valgrind/fn_with_return_should_free_local_vars.v', + 'vlib/v/tests/valgrind/option_simple.v', +] + +fn vprintln(s string) { + $if verbose ? { + eprintln(s) + } +} + +fn test_all() { + if os.user_os() != 'linux' && os.getenv('FORCE_VALGRIND_TEST').len == 0 { + eprintln('Valgrind tests can only be run reliably on Linux for now.') + eprintln('You can still do it by setting FORCE_VALGRIND_TEST=1 .') + exit(0) + } + if os.getenv('V_CI_UBUNTU_MUSL').len > 0 { + eprintln('This test is disabled for musl.') + exit(0) + } + bench_message := 'memory leak checking with valgrind' + mut bench := benchmark.new_benchmark() + eprintln(term.header(bench_message, '-')) + vexe := os.getenv('VEXE') + vroot := os.dir(vexe) + valgrind_test_path := 'vlib/v/tests/valgrind' + dir := os.join_path(vroot, valgrind_test_path) + files := os.ls(dir) or { panic(err) } + // + wrkdir := os.join_path(os.temp_dir(), 'vtests', 'valgrind') + os.mkdir_all(wrkdir) or { panic(err) } + os.chdir(wrkdir) or {} + // + only_ordinary_v_files := files.filter(it.ends_with('.v') && !it.ends_with('_test.v')) + tests := vtest.filter_vtest_only(only_ordinary_v_files, basepath: valgrind_test_path) + bench.set_total_expected_steps(tests.len) + for test in tests { + bench.step() + if test in skip_compile_files { + $if !noskipcompile ? { + bench.skip() + eprintln(bench.step_message_skip(test)) + continue + } + } + // + exe_filename := '$wrkdir/x' + full_path_to_source_file := os.join_path(vroot, test) + compile_cmd := '$vexe -o $exe_filename -cg -cflags "-w" -autofree "$full_path_to_source_file"' + vprintln('compile cmd: ${bold(compile_cmd)}') + res := os.execute(compile_cmd) + if res.exit_code != 0 { + bench.fail() + eprintln(bench.step_message_fail('file: $test could not be compiled.')) + eprintln(res.output) + eprintln('You can reproduce the failure with:\n$compile_cmd') + continue + } + if test in skip_valgrind_files { + $if !noskip ? { + bench.skip() + eprintln(bench.step_message_skip(test)) + continue + } + } + valgrind_cmd := 'valgrind --error-exitcode=1 --leak-check=full $exe_filename' + vprintln('valgrind cmd: ${bold(valgrind_cmd)}') + valgrind_res := os.execute(valgrind_cmd) + if valgrind_res.exit_code != 0 { + bench.fail() + eprintln(bench.step_message_fail('failed valgrind check for ${bold(test)}')) + eprintln(valgrind_res.output) + eprintln('You can reproduce the failure with:\n$compile_cmd && $valgrind_cmd') + continue + } + bench.ok() + eprintln(bench.step_message_ok(test)) + } + bench.stop() + eprintln(term.h_divider('-')) + eprintln(bench.total_message(bench_message)) + if bench.nfail > 0 { + exit(1) + } +} |