nemequ / munit

µnit is a small testing framework for C
https://nemequ.github.io/munit/
Other
553 stars 75 forks source link

Valgrind report 'still reachable' warning on test_name #57

Open perror opened 5 years ago

perror commented 5 years ago

I have strange behavior with valgrind. Basically, I defined my test suite as follow:

  /* Set the test cases */
  MunitTest test_cases[] =
    {
     { "trace/instr", test_instr, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL },
     { "trace/hash", test_hash, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL },
     { "trace/hashtable", test_hashtable, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL },
     { NULL, NULL, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL },
    };

  /* Set the test suite */
  const MunitSuite test_suite[] =
    {
     { "libafl/", test_cases, NULL, 1, MUNIT_SUITE_OPTION_NONE },
     { NULL, NULL, NULL, 0, MUNIT_SUITE_OPTION_NONE }
    };

But, the problem is that valgrind detect a "still reachable" memory area in each process. For example the last test gives:

[ OK    ] [ 0.00903018 / 0.00862850 CPU ]
libafl/trace/hashtable               ==8031== 
==8031== HEAP SUMMARY:
==8031==     in use at exit: 23 bytes in 1 blocks
==8031==   total heap usage: 21 allocs, 20 frees, 5,050 bytes allocated
==8031== 
==8031== 23 bytes in 1 blocks are still reachable in loss record 1 of 1
==8031==    at 0x483577F: malloc (vg_replace_malloc.c:299)
==8031==    by 0x4848C03: munit_maybe_concat (munit.c:1110)
==8031==    by 0x484A7D8: munit_test_runner_run_test (munit.c:1556)
==8031==    by 0x484B028: munit_test_runner_run_suite (munit.c:1677)
==8031==    by 0x484B1A0: munit_test_runner_run (munit.c:1696)
==8031==    by 0x484CAA5: munit_suite_main_custom (munit.c:2026)
==8031==    by 0x484CCCA: munit_suite_main (munit.c:2054)
==8031==    by 0x10BD45: main (test_trace.c:189)
==8031== 
==8031== LEAK SUMMARY:
==8031==    definitely lost: 0 bytes in 0 blocks
==8031==    indirectly lost: 0 bytes in 0 blocks
==8031==      possibly lost: 0 bytes in 0 blocks
==8031==    still reachable: 23 bytes in 1 blocks
==8031==         suppressed: 0 bytes in 0 blocks
==8031== 
==8031== For counts of detected and suppressed errors, rerun with: -v
==8031== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
[ OK    ] [ 0.01238986 / 0.01177837 CPU ]
3 of 3 (100%) tests successful, 0 (0%) test skipped.
==8028== 
==8028== HEAP SUMMARY:
==8028==     in use at exit: 0 bytes in 0 blocks
==8028==   total heap usage: 12 allocs, 12 frees, 4,612 bytes allocated
==8028== 
==8028== All heap blocks were freed -- no leaks are possible
==8028== 
==8028== For counts of detected and suppressed errors, rerun with: -v
==8028== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

Note that the size of the "still reachable" memory area is exactly the size of the string enclosing the name of the test (plus the \0 character at the end).

One way to make the error vanish seems to simply declare the test suite as follow:

 /* Set the test cases */
  MunitTest test_cases[] =
    {
     { "libafl/trace/instr", test_instr, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL },
     { "libafl/trace/hash", test_hash, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL },
     { "libafl/trace/hashtable", test_hashtable, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL },
     { NULL, NULL, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL },
    };

  /* Set the test suite */
  const MunitSuite test_suite[] =
    {
     { "", test_cases, NULL, 1, MUNIT_SUITE_OPTION_NONE },
     { NULL, NULL, NULL, 0, MUNIT_SUITE_OPTION_NONE }
    };

The root name of the test suite is empty and this time valgrind gives a clean report for each test:

All heap blocks were freed -- no leaks are possible

I tried to see what was the problem but couldn't find a good way to solve it (everything seems to be linked to the munit_maybe_free_concat(() function).

dargueta commented 3 months ago

I pulled master and the leak is still happening.

yetsing commented 1 month ago

This is because it uses the fork subprocess to perform testing, and after the subprocess completes the testing, it stops directly. ==8031== part is the child process of fork, while the ==8028== part is the main process.

The following is a code example to demonstrate the reason

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

void
hello() {
    int pid = fork();
    assert(pid >= 0);
    if (pid == 0) {
        // child process
        exit(0);
    }
    waitpid(pid, NULL, 0);
}

int
main() {
    char *s = malloc(32);
    printf("hello world\n");
    hello();
    free(s);
    return 0;
}

save code to hello.c.

gcc hello.c
valgrind --tool=memcheck --leak-check=full --show-leak-kinds=all ./a.out

You will see similar output below

==239150== Memcheck, a memory error detector
==239150== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==239150== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==239150== Command: ./a.out
==239150==
hello world
==239151==
==239151== HEAP SUMMARY:
==239151==     in use at exit: 32 bytes in 1 blocks
==239151==   total heap usage: 2 allocs, 1 frees, 1,056 bytes allocated
==239151==
==239151== 32 bytes in 1 blocks are still reachable in loss record 1 of 1
==239151==    at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==239151==    by 0x109287: main (in /home/ecs-user/cbdai/a.out)
==239151==
==239151== LEAK SUMMARY:
==239151==    definitely lost: 0 bytes in 0 blocks
==239151==    indirectly lost: 0 bytes in 0 blocks
==239151==      possibly lost: 0 bytes in 0 blocks
==239151==    still reachable: 32 bytes in 1 blocks
==239151==         suppressed: 0 bytes in 0 blocks
==239151==
==239151== For lists of detected and suppressed errors, rerun with: -s
==239151== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
==239150==
==239150== HEAP SUMMARY:
==239150==     in use at exit: 0 bytes in 0 blocks
==239150==   total heap usage: 2 allocs, 2 frees, 1,056 bytes allocated
==239150==
==239150== All heap blocks were freed -- no leaks are possible
==239150==
==239150== For lists of detected and suppressed errors, rerun with: -s
==239150== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)