Address Your Sanity with AddressSanitizer - Part Three

It took a while for me to get back in the swing of things after I returned from Japan, but I eventually got there. The first step to fixing that use-after-free bug was finding out which test was triggering the failure, which meant building a MoarVM with address sanitation (often referred to as ASan) support.

ASan (also known as AddressSanitizer) is a memory error detector developed by Google for Clang that has since been ported to GCC. It finds common memory errors, such as use-after-free, overflow, double free, or memory leaks. Building a MoarVM with ASan support isn't difficult; it's actually as simple as providing --asan when running to configure MoarVM (you probably also want --debug for useful stack traces). However, ASan needs some tweaking before you can really start using it to run the Perl 6 test suite.

Despite the team's efforts, MoarVM still has a few memory leaks. ASan treats leaks as failures, so ASan makes invocations of MoarVM fail when building Rakudo. What I needed is to figure out how to tell ASan that I don't care about memory leaks for the time being.

I soon discovered ASAN_OPTIONS, which is an environment variable that can be used to tune ASan, but I couldn't find instructions on how to actually use it. Timo Paulsen pointed out to me, which contains the advice that the possible values for ASAN_OPTIONS are displayed by setting ASAN_OPTIONS=help=1. After looking at its output, I set ASAN_OPTIONS to detect_leaks=0 exitcode=117, to disable checking for memory leaks, and to set the exitcode of MoarVM invocations to a sentinel value I didn't expect to come up naturally (117) so I could easily identify which Perl 6 scripts were triggering bad memory behavior.

After setting this environment variable and running roast on my new MoarVM, I got a few hits for tests that had less-than-ideal memory behavior. ASan helpfully pointed out the problem code, which looked like this:

/* Clean up an arguments processing context for cache. */
void MVM_args_proc_cleanup_for_cache(MVMThreadContext *tc, MVMArgProcContext *ctx) {
    /* Really, just if ctx->arg_flags, which indicates a flattening occurred. */
    if (ctx->callsite && ctx->callsite->has_flattening) {
        if (ctx->arg_flags) {
            /* Free the generated flags. */
            ctx->arg_flags = NULL;

            /* Free the generated args buffer. */
            ctx->args = NULL;

It turns out that ctx->callsite is invalid at this point (because we're cleaning up ctx), so getting the value of ctx->callsite->has_flattening results in an invalid memory access. I happened to know that arg_flags is only non-NULL if has_flattening is truthy anyway, which makes the if (ctx->callsite && ctx->callsite->has_flattening) check redundant. So I removed that, which fixed the use-after-free!

This plugged the memory leak I discovered in the previous post, and that got rid of the slowdown I was observing when spawning child processes. Huzzah! However, this isn't the end of this particular journey with memory issues; as I mentioned earlier, MoarVM still has some memory leaks. With my new-found confidence in wielding ASan, I decided to try to get rid of some of them. Tune in next time as I go through fixing more leaks with the help of the heavyweight tool in this area: Valgrind.