Skip to content

Commit 257eadd

Browse files
authored
gc: add -gc boehm_leak for leak detection (#9464)
1 parent 03d5686 commit 257eadd

File tree

9 files changed

+87
-15
lines changed

9 files changed

+87
-15
lines changed

.github/workflows/ci.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,21 @@ jobs:
140140
- name: Self tests with `-gc boehm`
141141
## The test cases are run with non-gc `v` for now
142142
run: ./v -gc boehm -silent test-self
143+
- name: Test leak detector
144+
run: |
145+
./v -gc boehm_leak -o testcase_leak vlib/v/tests/testcase_leak.v
146+
./testcase_leak 2>leaks.txt
147+
grep "Found 1 leaked object" leaks.txt && grep ", sz=1000," leaks.txt
148+
- name: Test leak detector not being active for `-gc boehm`
149+
run: |
150+
./v -gc boehm -o testcase_leak vlib/v/tests/testcase_leak.v
151+
./testcase_leak 2>leaks.txt
152+
[ "$(stat -c %s leaks.txt)" = "0" ]
153+
- name: Test leak detector not being active for normal compile
154+
run: |
155+
./v -o testcase_leak vlib/v/tests/testcase_leak.v
156+
./testcase_leak 2>leaks.txt
157+
[ "$(stat -c %s leaks.txt)" = "0" ]
143158
144159
misc-tooling:
145160
runs-on: ubuntu-20.04

cmd/v/help/build-c.txt

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,18 +72,24 @@ see also `v help build`.
7272

7373
-gc <mode>
7474
Use and link an optional garbage collector.
75-
Only `-gc boehm` is supported currently. You need to install a
76-
`libgc-dev` package first, or install it manually from source:
75+
Only `-gc boehm` and `-gc boehm_leak` are supported currently.
76+
You need to install a `libgc-dev` package first, or install it manually
77+
from source:
7778
https://github.com/ivmai/bdwgc
7879

79-
Note, this option is complementary to -autofree. The Boehm garbage
80+
Note, `-gc boehm` is complementary to -autofree. The Boehm garbage
8081
collector is conservative, and it may make your program significantly
8182
slower if it does many small allocations in a loop. This option
8283
is intended *mainly* for reducing the memory usage of programs, that
8384
process large amounts of text in *batch mode* on low/limited memory
8485
environments like small VPSes, and for which a few ms of garbage
8586
collection pauses from time to time *do not matter much*.
8687

88+
The option `-gc boehm_leak` is intended for leak detection in
89+
manual memory management. The function `gc_check_leaks()`
90+
can be called to get detection results. This function is a no-op
91+
when `-gc boehm_leak` is not supplied.
92+
8793
# Miscellaneous:
8894
-printfn <fn_name>
8995
Print the content of the generated C function named fn_name.

vlib/builtin/builtin.c.v

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,9 +315,14 @@ pub fn free(ptr voidptr) {
315315
return
316316
}
317317
$if gcboehm ? {
318-
// It is better to leave it to Boehm's gc to free things.
318+
// It is generally better to leave it to Boehm's gc to free things.
319319
// Calling C.GC_FREE(ptr) was tried initially, but does not work
320320
// well with programs that do manual management themselves.
321+
//
322+
// The exception is doing leak detection for manual memory management:
323+
$if gcboehm_leak ? {
324+
C.GC_FREE(ptr)
325+
}
321326
return
322327
}
323328
C.free(ptr)

vlib/builtin/builtin_d_gcboehm.v

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,38 @@ $if windows {
99
$if macos {
1010
#pkgconfig bdw-gc
1111
}
12-
12+
$if gcboehm_leak ? {
13+
#define GC_DEBUG
14+
}
1315
#include <gc.h>
1416

1517
#flag -lgc
1618

19+
// replacements for `malloc()/calloc()`, `realloc()` and `free()`
20+
// for use with Boehm-GC
21+
// Do not use them manually. They are automatically chosen when
22+
// compiled with `-gc boehm` or `-gc boehm_leak`.
1723
fn C.GC_MALLOC(n size_t) voidptr
1824

1925
fn C.GC_REALLOC(ptr voidptr, n size_t) voidptr
2026

2127
fn C.GC_FREE(ptr voidptr)
2228

23-
fn C.GC_set_find_leak(int)
24-
25-
// fn C.CHECK_LEAKS()
29+
// explicitely perform garbage collection now! Garbage collections
30+
// are done automatically when needed, so this function is hardly needed
2631
fn C.GC_gcollect()
32+
33+
// functions to temporarily suspend/resume garbage collection
34+
fn C.GC_disable()
35+
36+
fn C.GC_enable()
37+
38+
// returns non-zero if GC is disabled
39+
fn C.GC_is_disabled() int
40+
41+
// for leak detection it is advisable to do explicit garbage collections
42+
pub fn gc_check_leaks() {
43+
$if gcboehm_leak ? {
44+
C.GC_gcollect()
45+
}
46+
}

vlib/builtin/builtin_notd_gcboehm.v

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,7 @@ fn C.GC_REALLOC(ptr voidptr, n size_t) voidptr
1010

1111
fn C.GC_FREE(ptr voidptr)
1212

13-
// fn C.CHECK_LEAKS()
14-
fn C.GC_gcollect()
13+
// provide an empty function when manual memory management is used
14+
// to simplify leak detection
15+
//
16+
pub fn gc_check_leaks() {}

vlib/v/gen/c/cgen.v

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -423,7 +423,7 @@ pub fn (mut g Gen) init() {
423423
}
424424
g.comptime_defines.writeln('')
425425
}
426-
if g.pref.gc_mode == .boehm {
426+
if g.pref.gc_mode in [.boehm, .boehm_leak] {
427427
g.comptime_defines.writeln('#define _VGCBOEHM (1)')
428428
}
429429
if g.pref.is_debug || 'debug' in g.pref.compile_defines {

vlib/v/gen/c/cmain.v

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,11 @@ fn (mut g Gen) gen_c_main_function_header() {
6767

6868
fn (mut g Gen) gen_c_main_header() {
6969
g.gen_c_main_function_header()
70-
if g.pref.gc_mode == .boehm {
70+
if g.pref.gc_mode in [.boehm, .boehm_leak] {
7171
g.writeln('#if defined(_VGCBOEHM)')
72+
if g.pref.gc_mode == .boehm_leak {
73+
g.writeln('\tGC_set_find_leak(1);')
74+
}
7275
g.writeln('\tGC_INIT();')
7376
g.writeln('#endif')
7477
}
@@ -155,8 +158,11 @@ pub fn (mut g Gen) gen_c_main_for_tests() {
155158
main_fn_start_pos := g.out.len
156159
g.writeln('')
157160
g.gen_c_main_function_header()
158-
if g.pref.gc_mode == .boehm {
161+
if g.pref.gc_mode in [.boehm, .boehm_leak] {
159162
g.writeln('#if defined(_VGCBOEHM)')
163+
if g.pref.gc_mode == .boehm_leak {
164+
g.writeln('\tGC_set_find_leak(1);')
165+
}
160166
g.writeln('\tGC_INIT();')
161167
g.writeln('#endif')
162168
}

vlib/v/pref/pref.v

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ pub enum BuildMode {
2020
pub enum GarbageCollectionMode {
2121
no_gc
2222
boehm
23+
boehm_leak
2324
}
2425

2526
pub enum OutputMode {
@@ -158,7 +159,7 @@ pub mut:
158159
build_options []string // list of options, that should be passed down to `build-module`, if needed for -usecache
159160
cache_manager vcache.CacheManager
160161
is_help bool // -h, -help or --help was passed
161-
gc_mode GarbageCollectionMode = .no_gc // .no_gc, .boehm
162+
gc_mode GarbageCollectionMode = .no_gc // .no_gc, .boehm, .boehm_leak
162163
// checker settings:
163164
checker_match_exhaustive_cutoff_limit int = 10
164165
}
@@ -230,8 +231,13 @@ pub fn parse_args(known_external_commands []string, args []string) (&Preferences
230231
res.gc_mode = .boehm
231232
parse_define(mut res, 'gcboehm')
232233
}
234+
'boehm_leak' {
235+
res.gc_mode = .boehm_leak
236+
parse_define(mut res, 'gcboehm')
237+
parse_define(mut res, 'gcboehm_leak')
238+
}
233239
else {
234-
eprintln('unknown garbage collection mode, only `-gc boehm` is supported')
240+
eprintln('unknown garbage collection mode, only `-gc boehm` and `-gc boehm_leak` are supported')
235241
exit(1)
236242
}
237243
}

vlib/v/tests/testcase_leak.v

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// This program is supposed to test the leak detector which
2+
// is integrated with `-gc boehm_leak` at compile time.
3+
//
4+
// If compiled with `-gc boehm` or without `-gc` nothing
5+
// will be reported.
6+
7+
fn main() {
8+
mut y := unsafe { malloc(1000) }
9+
// unsafe { free(y) } // leak if commented out
10+
y = voidptr(0)
11+
gc_check_leaks()
12+
}

0 commit comments

Comments
 (0)