gc: add `-gc boehm_leak` for leak detection (#9464)

pull/9468/head
Uwe Krüger 2021-03-25 16:52:33 +01:00 committed by GitHub
parent 03d56865e3
commit 257eadd2e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 87 additions and 15 deletions

View File

@ -140,6 +140,21 @@ jobs:
- name: Self tests with `-gc boehm` - name: Self tests with `-gc boehm`
## The test cases are run with non-gc `v` for now ## The test cases are run with non-gc `v` for now
run: ./v -gc boehm -silent test-self 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: misc-tooling:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04

View File

@ -72,11 +72,12 @@ see also `v help build`.
-gc <mode> -gc <mode>
Use and link an optional garbage collector. Use and link an optional garbage collector.
Only `-gc boehm` is supported currently. You need to install a Only `-gc boehm` and `-gc boehm_leak` are supported currently.
`libgc-dev` package first, or install it manually from source: You need to install a `libgc-dev` package first, or install it manually
from source:
https://github.com/ivmai/bdwgc 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 collector is conservative, and it may make your program significantly
slower if it does many small allocations in a loop. This option slower if it does many small allocations in a loop. This option
is intended *mainly* for reducing the memory usage of programs, that 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 environments like small VPSes, and for which a few ms of garbage
collection pauses from time to time *do not matter much*. 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: # Miscellaneous:
-printfn <fn_name> -printfn <fn_name>
Print the content of the generated C function named fn_name. Print the content of the generated C function named fn_name.

View File

@ -315,9 +315,14 @@ pub fn free(ptr voidptr) {
return return
} }
$if gcboehm ? { $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 // Calling C.GC_FREE(ptr) was tried initially, but does not work
// well with programs that do manual management themselves. // 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 return
} }
C.free(ptr) C.free(ptr)

View File

@ -9,18 +9,38 @@ $if windows {
$if macos { $if macos {
#pkgconfig bdw-gc #pkgconfig bdw-gc
} }
$if gcboehm_leak ? {
#define GC_DEBUG
}
#include <gc.h> #include <gc.h>
#flag -lgc #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_MALLOC(n size_t) voidptr
fn C.GC_REALLOC(ptr voidptr, n size_t) voidptr fn C.GC_REALLOC(ptr voidptr, n size_t) voidptr
fn C.GC_FREE(ptr voidptr) fn C.GC_FREE(ptr voidptr)
fn C.GC_set_find_leak(int) // explicitely perform garbage collection now! Garbage collections
// are done automatically when needed, so this function is hardly needed
// fn C.CHECK_LEAKS()
fn C.GC_gcollect() 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()
}
}

View File

@ -10,5 +10,7 @@ fn C.GC_REALLOC(ptr voidptr, n size_t) voidptr
fn C.GC_FREE(ptr voidptr) fn C.GC_FREE(ptr voidptr)
// fn C.CHECK_LEAKS() // provide an empty function when manual memory management is used
fn C.GC_gcollect() // to simplify leak detection
//
pub fn gc_check_leaks() {}

View File

@ -423,7 +423,7 @@ pub fn (mut g Gen) init() {
} }
g.comptime_defines.writeln('') 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)') g.comptime_defines.writeln('#define _VGCBOEHM (1)')
} }
if g.pref.is_debug || 'debug' in g.pref.compile_defines { if g.pref.is_debug || 'debug' in g.pref.compile_defines {

View File

@ -67,8 +67,11 @@ fn (mut g Gen) gen_c_main_function_header() {
fn (mut g Gen) gen_c_main_header() { fn (mut g Gen) gen_c_main_header() {
g.gen_c_main_function_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)') 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('\tGC_INIT();')
g.writeln('#endif') g.writeln('#endif')
} }
@ -155,8 +158,11 @@ pub fn (mut g Gen) gen_c_main_for_tests() {
main_fn_start_pos := g.out.len main_fn_start_pos := g.out.len
g.writeln('') g.writeln('')
g.gen_c_main_function_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)') 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('\tGC_INIT();')
g.writeln('#endif') g.writeln('#endif')
} }

View File

@ -20,6 +20,7 @@ pub enum BuildMode {
pub enum GarbageCollectionMode { pub enum GarbageCollectionMode {
no_gc no_gc
boehm boehm
boehm_leak
} }
pub enum OutputMode { 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 build_options []string // list of options, that should be passed down to `build-module`, if needed for -usecache
cache_manager vcache.CacheManager cache_manager vcache.CacheManager
is_help bool // -h, -help or --help was passed 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 settings:
checker_match_exhaustive_cutoff_limit int = 10 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 res.gc_mode = .boehm
parse_define(mut res, 'gcboehm') parse_define(mut res, 'gcboehm')
} }
'boehm_leak' {
res.gc_mode = .boehm_leak
parse_define(mut res, 'gcboehm')
parse_define(mut res, 'gcboehm_leak')
}
else { 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) exit(1)
} }
} }

View File

@ -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()
}