I use Crystal intensively for my work, and I love the language. Simple, concise, it’s almost no bullshit whatsoever. But, I do have a very specific use of it: I make calls to a C library I’ve written. It is clearly not something a lot of people are doing, at least not directly, the crystal standard library is quite extensive.
I trust the Crystal compiler enough to get the memory stuff done, but I may have done a terrible job in the C library and I want to check.
Also, I think that valgrind
is a useful tool to prevent memory errors, like corruption or leaks, and should be used intensively.
Unfortunately, I cannot just use valgrind
to check for memory leaks inside my C library. When I do that, there are way too many errors.
Just a single example:
==21822== 25 errors in context 12 of 15:
==21822== Conditional jump or move depends on uninitialised value(s)
==21822== at 0x3CA89B: GC_push_all_eager (in ./my-application)
==21822== by 0x3DA006: GC_with_callee_saves_pushed (in ./my-application)
==21822== by 0x3CBBC6: GC_push_roots (in ./my-application)
==21822== by 0x3C8936: GC_mark_some (in ./my-application)
==21822== by 0x3C1C4C: GC_stopped_mark (in ./my-application)
==21822== by 0x3C1AF6: GC_try_to_collect_inner (in ./my-application)
==21822== by 0x3CCAFE: GC_init (in ./my-application)
==21822== by 0x1D47A2: *GC::init:Nil (boehm.cr:127)
==21822== by 0x3C12C3: *Crystal::main<Int32, Pointer(Pointer(UInt8))>:Int32 (main.cr:35)
==21822== by 0x196735: main (main.cr:114)
valgrind
produces thousands of warnings like this one with Crystal code, all of them are related to the garbage collector used by the Crystal programming language.
A lot of warnings aren’t related to my code, valgrind
has to ignore most of the warnings.
In order:
--gen-suppressions=all
Each time valgrind
encounters an error, it can produce a code snippet. Here is a minimal example:
# We start valgrind with '--gen-suppressions=all' to produce suppression code.
$ valgrind --gen-suppressions=all --leak-check=full -v ./my-application
[a lot of warnings, errors and other stuff like earlier]
==4364==
==4364== 130 errors in context 13 of 15:
==4364== Conditional jump or move depends on uninitialised value(s)
==4364== at 0x3CA896: GC_push_all_eager (in ./my-application)
==4364== by 0x3DA006: GC_with_callee_saves_pushed (in ./my-application)
==4364== by 0x3CBBC6: GC_push_roots (in ./my-application)
==4364== by 0x3C8936: GC_mark_some (in ./my-application)
==4364== by 0x3C1C4C: GC_stopped_mark (in ./my-application)
==4364== by 0x3C1AF6: GC_try_to_collect_inner (in ./my-application)
==4364== by 0x3CCAFE: GC_init (in ./my-application)
==4364== by 0x1D47A2: *GC::init:Nil (boehm.cr:127)
==4364== by 0x3C12C3: *Crystal::main<Int32, Pointer(Pointer(UInt8))>:Int32 (main.cr:35)
==4364== by 0x196735: main (main.cr:114)
==4364==
{
<insert_a_suppression_name_here>
Memcheck:Cond
fun:GC_push_all_eager
fun:GC_with_callee_saves_pushed
fun:GC_push_roots
fun:GC_mark_some
fun:GC_stopped_mark
fun:GC_try_to_collect_inner
fun:GC_init
fun:*GC::init:Nil
fun:*Crystal::main<Int32, Pointer(Pointer(UInt8))>:Int32
fun:main
}
In this example, the issued code will suppress a warning for a conditional statement with the described set of function calls.
Just using the code produced as-is is almost useless. This code is way too specific, and chances are that the warnings you want to suppress are related to a particular library, an object file, or to a set of functions with well-formatted names.
To get things right, the code has to be more generic.
As we can see, the suppression code produced by valgrind
will suppress warnings for a kind of memory problem and a specific function stack. To make the code more generic, wildcards can be used to match functions (fun) or object files (obj).
Example:
# This will suppress warnings for this specific stack but the last function on the stack
# could be any function with a name starting with GC_
{
<* wildcard usage>
Memcheck:Cond
fun:GC_*
fun:GC_with_callee_saves_pushed
fun:GC_push_roots
fun:GC_mark_some
fun:GC_stopped_mark
fun:GC_try_to_collect_inner
fun:GC_init
fun:*GC::init:Nil
fun:*Crystal::main<Int32, Pointer(Pointer(UInt8))>:Int32
fun:main
}
For my needs in practice, this is still way too specific.
I don’t want to specify the whole stack. The wildcard “…” should be used.
# This will suppress warnings for any function with a name starting with GC_
# regardless of the function stack.
# This wildcard is combined with *fun* or *obj* selectors (and other wildcards).
{
<... wildcard usage>
Memcheck:Cond
...
fun:GC_*
}
Another example:
# This will suppress warnings for any function in object files /usr/lib/blah*
{
<* wildcard usage with 'obj' instruction>
Memcheck:Cond
...
obj:/usr/lib/blah*
}
As we can see, there is a Memcheck instruction, telling the kind of error produced the warning. These errors can be Cond, Value8, Leak and Addr8. I don’t see a way to combine several kinds of errors into a single instruction.
Take your time. Be sure you don’t suppress useful warnings.
Our suppression code is written in a file, named my-application.suppr for the example.
# valgrind, only with warnings for our code.
$ valgrind --suppressions=my-application.suppr --leak-check=full -v ./my-application
In case there is still useless warnings, just add more instructions. Don’t worry, the process really is straightforward, you will manage. Have fun!
This blog post was heavily inspired by this page.
{
<Garbage Collector>
Memcheck:Cond
...
fun:GC_*
}
{
<Garbage Collector>
Memcheck:Value8
...
fun:GC_*
}
{
<Garbage Collector>
Memcheck:Leak
...
fun:GC_*
}
{
<Garbage Collector>
Memcheck:Addr8
...
fun:GC_*
}
The garbage collector library is written with care, functions start with GC_, very useful to easily match them all. I started with thousands of warnings, it was impossible to find any relevant information in the output. This fixes the problem.