From 257eadd2e128c6ef405cb6297a18fb1e4a22cc06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kr=C3=BCger?= <45282134+UweKrueger@users.noreply.github.com> Date: Thu, 25 Mar 2021 16:52:33 +0100 Subject: [PATCH] gc: add `-gc boehm_leak` for leak detection (#9464) --- .github/workflows/ci.yml | 15 +++++++++++++++ cmd/v/help/build-c.txt | 12 +++++++++--- vlib/builtin/builtin.c.v | 7 ++++++- vlib/builtin/builtin_d_gcboehm.v | 28 ++++++++++++++++++++++++---- vlib/builtin/builtin_notd_gcboehm.v | 6 ++++-- vlib/v/gen/c/cgen.v | 2 +- vlib/v/gen/c/cmain.v | 10 ++++++++-- vlib/v/pref/pref.v | 10 ++++++++-- vlib/v/tests/testcase_leak.v | 12 ++++++++++++ 9 files changed, 87 insertions(+), 15 deletions(-) create mode 100644 vlib/v/tests/testcase_leak.v diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1ab8f1bb5b..8726a1761b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -140,6 +140,21 @@ jobs: - name: Self tests with `-gc boehm` ## The test cases are run with non-gc `v` for now run: ./v -gc boehm -silent test-self + - name: Test leak detector + run: | + ./v -gc boehm_leak -o testcase_leak vlib/v/tests/testcase_leak.v + ./testcase_leak 2>leaks.txt + grep "Found 1 leaked object" leaks.txt && grep ", sz=1000," leaks.txt + - name: Test leak detector not being active for `-gc boehm` + run: | + ./v -gc boehm -o testcase_leak vlib/v/tests/testcase_leak.v + ./testcase_leak 2>leaks.txt + [ "$(stat -c %s leaks.txt)" = "0" ] + - name: Test leak detector not being active for normal compile + run: | + ./v -o testcase_leak vlib/v/tests/testcase_leak.v + ./testcase_leak 2>leaks.txt + [ "$(stat -c %s leaks.txt)" = "0" ] misc-tooling: runs-on: ubuntu-20.04 diff --git a/cmd/v/help/build-c.txt b/cmd/v/help/build-c.txt index 7e57251cd1..7f99bf36b1 100644 --- a/cmd/v/help/build-c.txt +++ b/cmd/v/help/build-c.txt @@ -72,11 +72,12 @@ see also `v help build`. -gc Use and link an optional garbage collector. - Only `-gc boehm` is supported currently. You need to install a - `libgc-dev` package first, or install it manually from source: + Only `-gc boehm` and `-gc boehm_leak` are supported currently. + You need to install a `libgc-dev` package first, or install it manually + from source: https://github.com/ivmai/bdwgc - Note, this option is complementary to -autofree. The Boehm garbage + Note, `-gc boehm` is complementary to -autofree. The Boehm garbage collector is conservative, and it may make your program significantly slower if it does many small allocations in a loop. This option is intended *mainly* for reducing the memory usage of programs, that @@ -84,6 +85,11 @@ see also `v help build`. environments like small VPSes, and for which a few ms of garbage collection pauses from time to time *do not matter much*. + The option `-gc boehm_leak` is intended for leak detection in + manual memory management. The function `gc_check_leaks()` + can be called to get detection results. This function is a no-op + when `-gc boehm_leak` is not supplied. + # Miscellaneous: -printfn Print the content of the generated C function named fn_name. diff --git a/vlib/builtin/builtin.c.v b/vlib/builtin/builtin.c.v index 75d45e33bd..d8033f94ce 100644 --- a/vlib/builtin/builtin.c.v +++ b/vlib/builtin/builtin.c.v @@ -315,9 +315,14 @@ pub fn free(ptr voidptr) { return } $if gcboehm ? { - // It is better to leave it to Boehm's gc to free things. + // It is generally better to leave it to Boehm's gc to free things. // Calling C.GC_FREE(ptr) was tried initially, but does not work // well with programs that do manual management themselves. + // + // The exception is doing leak detection for manual memory management: + $if gcboehm_leak ? { + C.GC_FREE(ptr) + } return } C.free(ptr) diff --git a/vlib/builtin/builtin_d_gcboehm.v b/vlib/builtin/builtin_d_gcboehm.v index 27f2a82e83..2e454e1974 100644 --- a/vlib/builtin/builtin_d_gcboehm.v +++ b/vlib/builtin/builtin_d_gcboehm.v @@ -9,18 +9,38 @@ $if windows { $if macos { #pkgconfig bdw-gc } - +$if gcboehm_leak ? { + #define GC_DEBUG +} #include #flag -lgc +// replacements for `malloc()/calloc()`, `realloc()` and `free()` +// for use with Boehm-GC +// Do not use them manually. They are automatically chosen when +// compiled with `-gc boehm` or `-gc boehm_leak`. fn C.GC_MALLOC(n size_t) voidptr fn C.GC_REALLOC(ptr voidptr, n size_t) voidptr fn C.GC_FREE(ptr voidptr) -fn C.GC_set_find_leak(int) - -// fn C.CHECK_LEAKS() +// explicitely perform garbage collection now! Garbage collections +// are done automatically when needed, so this function is hardly needed fn C.GC_gcollect() + +// functions to temporarily suspend/resume garbage collection +fn C.GC_disable() + +fn C.GC_enable() + +// returns non-zero if GC is disabled +fn C.GC_is_disabled() int + +// for leak detection it is advisable to do explicit garbage collections +pub fn gc_check_leaks() { + $if gcboehm_leak ? { + C.GC_gcollect() + } +} diff --git a/vlib/builtin/builtin_notd_gcboehm.v b/vlib/builtin/builtin_notd_gcboehm.v index 57a7db9134..acaf42e98c 100644 --- a/vlib/builtin/builtin_notd_gcboehm.v +++ b/vlib/builtin/builtin_notd_gcboehm.v @@ -10,5 +10,7 @@ fn C.GC_REALLOC(ptr voidptr, n size_t) voidptr fn C.GC_FREE(ptr voidptr) -// fn C.CHECK_LEAKS() -fn C.GC_gcollect() +// provide an empty function when manual memory management is used +// to simplify leak detection +// +pub fn gc_check_leaks() {} diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index ed9cce30fa..c7a64e198c 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -423,7 +423,7 @@ pub fn (mut g Gen) init() { } g.comptime_defines.writeln('') } - if g.pref.gc_mode == .boehm { + if g.pref.gc_mode in [.boehm, .boehm_leak] { g.comptime_defines.writeln('#define _VGCBOEHM (1)') } if g.pref.is_debug || 'debug' in g.pref.compile_defines { diff --git a/vlib/v/gen/c/cmain.v b/vlib/v/gen/c/cmain.v index 61391e05ea..d08dc766f1 100644 --- a/vlib/v/gen/c/cmain.v +++ b/vlib/v/gen/c/cmain.v @@ -67,8 +67,11 @@ fn (mut g Gen) gen_c_main_function_header() { fn (mut g Gen) gen_c_main_header() { g.gen_c_main_function_header() - if g.pref.gc_mode == .boehm { + if g.pref.gc_mode in [.boehm, .boehm_leak] { g.writeln('#if defined(_VGCBOEHM)') + if g.pref.gc_mode == .boehm_leak { + g.writeln('\tGC_set_find_leak(1);') + } g.writeln('\tGC_INIT();') g.writeln('#endif') } @@ -155,8 +158,11 @@ pub fn (mut g Gen) gen_c_main_for_tests() { main_fn_start_pos := g.out.len g.writeln('') g.gen_c_main_function_header() - if g.pref.gc_mode == .boehm { + if g.pref.gc_mode in [.boehm, .boehm_leak] { g.writeln('#if defined(_VGCBOEHM)') + if g.pref.gc_mode == .boehm_leak { + g.writeln('\tGC_set_find_leak(1);') + } g.writeln('\tGC_INIT();') g.writeln('#endif') } diff --git a/vlib/v/pref/pref.v b/vlib/v/pref/pref.v index bb3831078d..e30c9c06ca 100644 --- a/vlib/v/pref/pref.v +++ b/vlib/v/pref/pref.v @@ -20,6 +20,7 @@ pub enum BuildMode { pub enum GarbageCollectionMode { no_gc boehm + boehm_leak } pub enum OutputMode { @@ -158,7 +159,7 @@ pub mut: build_options []string // list of options, that should be passed down to `build-module`, if needed for -usecache cache_manager vcache.CacheManager is_help bool // -h, -help or --help was passed - gc_mode GarbageCollectionMode = .no_gc // .no_gc, .boehm + gc_mode GarbageCollectionMode = .no_gc // .no_gc, .boehm, .boehm_leak // checker settings: checker_match_exhaustive_cutoff_limit int = 10 } @@ -230,8 +231,13 @@ pub fn parse_args(known_external_commands []string, args []string) (&Preferences res.gc_mode = .boehm parse_define(mut res, 'gcboehm') } + 'boehm_leak' { + res.gc_mode = .boehm_leak + parse_define(mut res, 'gcboehm') + parse_define(mut res, 'gcboehm_leak') + } else { - eprintln('unknown garbage collection mode, only `-gc boehm` is supported') + eprintln('unknown garbage collection mode, only `-gc boehm` and `-gc boehm_leak` are supported') exit(1) } } diff --git a/vlib/v/tests/testcase_leak.v b/vlib/v/tests/testcase_leak.v new file mode 100644 index 0000000000..fd73606a2b --- /dev/null +++ b/vlib/v/tests/testcase_leak.v @@ -0,0 +1,12 @@ +// This program is supposed to test the leak detector which +// is integrated with `-gc boehm_leak` at compile time. +// +// If compiled with `-gc boehm` or without `-gc` nothing +// will be reported. + +fn main() { + mut y := unsafe { malloc(1000) } + // unsafe { free(y) } // leak if commented out + y = voidptr(0) + gc_check_leaks() +}