g_test_queue_destroy and related functions free their arguments before teardown
Submitted by Simon McVittie
Link to original bug (#763241)
Description
Consider this test:
/* gcc -o queue-free queue-free.c `pkg-config --cflags --libs glib-2.0` */
#include <glib.h>
typedef struct
{
int dummy;
} Fixture;
static gchar *
noisy_strdup (const gchar *s)
{
g_printerr ("allocating: %s\n", s);
return g_strdup (s);
}
static void
noisy_free (gpointer p)
{
g_printerr ("freeing: %s\n", (const gchar *) p);
g_free (p);
}
static void
setup (Fixture *f,
gconstpointer context)
{
g_printerr ("enter setup\n");
g_test_queue_destroy (noisy_free, noisy_strdup ("allocated during setup"));
g_printerr ("exit setup\n");
}
static void
test (Fixture *f,
gconstpointer context)
{
g_printerr ("enter test\n");
g_test_queue_destroy (noisy_free, noisy_strdup ("allocated during test"));
g_printerr ("exit test\n");
}
static void
teardown (Fixture *f,
gconstpointer context)
{
g_printerr ("enter teardown\n");
g_test_queue_destroy (noisy_free, noisy_strdup ("allocated during teardown"));
g_printerr ("exit teardown\n");
}
int
main (int argc,
char **argv)
{
g_test_init (&argc, &argv);
g_test_add ("/queue-free", Fixture, NULL, setup, test, teardown);
return g_test_run ();
}
I would expect it to free the strings after teardown().
Here is what it actually does (with GLib 2.46, but it doesn't seem to be any different in master):
/queue-free: enter setup allocating: allocated during setup exit setup enter test allocating: allocated during test exit test freeing: allocated during test freeing: allocated during setup enter teardown allocating: allocated during teardown exit teardown OK
This test clearly has a memory leak (the string allocated during teardown isn't freed). In a less contrived situation, it's also easy to get into a use-after-free situation like this.
For instance, I've written some tests that create directories in setup() and/or during the test itself, and want to delete them and assert that they have already been deleted during teardown. In my first attempt at this, the directory names came from convenience functions which concatenated path segments, passed the result to g_test_queue_free() and returned it. As a result, the checks in teardown() accessed freed memory, happened to find some sort of junk bytes (let's assume they were 0xff) followed by a NUL, and effectively did things like g_assert_false (g_file_test ("\xff\xff\xff", G_FILE_EXISTS)). That isn't the check I wanted, but will succeed in practice, which caused me to think my test coverage was better than it actually is.
The output I would have expected is that the strings (and anything else queued in the same way) would be freed after teardown, so that domain-specific teardown logic (for example cleaning up temporary files/directories) can still use them during teardown. Like this:
/queue-free: enter setup allocating: allocated during setup exit setup enter test allocating: allocated during test exit test enter teardown allocating: allocated during teardown exit teardown freeing: allocated during teardown freeing: allocated during test freeing: allocated during setup OK
Version: 2.44.x