gcleanup: Implementation of GCleanupScope and associated macros

Add a new type GCleanupScope that stores a list of things to "clean up"
when g_cleanup_clean() is called.

More importantly, define some macros (G_CLEANUP, etc) that
facilitate conditionally building a per-library/executable cleanup list
if G_DEBUG=cleanup is specified.  The cleanup list is run at destructor
time.

-DG_CLEANUP_SCOPE defines the name of the cleanup list and enables the
feature for a given module.

Concept and initial work: Ryan Lortie <desrt@desrt.ca>

https://bugzilla.gnome.org/show_bug.cgi?id=627423
This commit is contained in:
Stef Walter
2013-11-13 08:54:52 +01:00
parent 21cf219cf6
commit 81a2133a72
10 changed files with 995 additions and 0 deletions

View File

@@ -3241,3 +3241,18 @@ g_hostname_is_ascii_encoded
<SUBSECTION>
g_hostname_is_ip_address
</SECTION>
<SECTION>
<FILE>gcleanup</FILE>
<TITLE>Cleanup</FILE>
G_CLEANUP_SCOPE
G_CLEANUP_DEFINE
G_CLEANUP
G_CLEANUP_IN_PHASE
G_CLEANUP_FUNC
G_CLEANUP_FUNC_IN_PHASE
g_cleanup_is_enabled
g_cleanup_list_push
g_cleanup_list_remove
g_cleanup_list_clean
</SECTION>

View File

@@ -115,6 +115,7 @@ libglib_2_0_la_SOURCES = \
gcharset.c \
gcharsetprivate.h \
gchecksum.c \
gcleanup.c \
gconvert.c \
gdataset.c \
gdatasetprivate.h \
@@ -251,6 +252,7 @@ glibsubinclude_HEADERS = \
gbytes.h \
gcharset.h \
gchecksum.h \
gcleanup.h \
gconstructor.h \
gconvert.h \
gdataset.h \

620
glib/gcleanup.c Normal file
View File

@@ -0,0 +1,620 @@
/*
* Copyright © 2013 Canonical Limited
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the licence, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*
* Author: Ryan Lortie <desrt@desrt.ca>
*/
#include "config.h"
#include "gcleanup.h"
#include "glib-init.h"
#include "glib-private.h"
#include "gatomic.h"
#include "gbitlock.h"
#include "ghash.h"
#include "gmacros.h"
#include "gtestutils.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/**
* SECTION:gcleanup
* @short_description: Cleanup on Exit
*
* The cleanup facilities allow GLib based libraries to clean up their global
* variables on exit or unloading of the module. This is useful for verifying
* that no memory leaks are present, and works well in conjuction with tools
* like valgrind.
*
* To use cleanup, define %G_CLEANUP_SCOPE either in your make files or
* in a non-public header. This declares the #GCleanupScope that cleanup items
* will be added to.
*
* Create the cleanup scope using the %G_CLEANUP_DEFINE macro in a source
* file. To push items for cleanup use %G_CLEANUP or %G_CLEANUP_FUNC.
*
* The <literal>G_DEBUG</literal> environment variable must contain the word
* '<literal>cleanup</literal>' for the cleanup to occur.
*
* The cleanup is ordered in phases. Cleanup items in lower numbered phases
* are run before those in higher numbered phases. Several phases are
* predefined, but you are free to define your own between the integers
* -1000 and 1000.
*
* It is permissible to add or remove other cleanup items at any time, and
* from any thread.
*
* Note that cleanup items registered by a library will run before the items
* of the libraries it depends on. The phases are only respected within a
* single %G_CLEANUP_SCOPE.
*/
/**
* G_CLEANUP_SCOPE:
*
* Defines the cleanup scope.
*
* Applications or libraries should define this if they wish to make use
* of the cleanup facilities. If not defined, then cleanup functions will
* do nothing. Be careful not to define it in any public header files.
*
* For example, you might use this in its Makefile.am:
* |[
* INCLUDES = -DG_CLEANUP_SCOPE=my_app_cleanup
* ]|
*
* If %G_CLEANUP_SCOPE is not defined, then it will automatically be treated
* as %NULL, and cleanup facilities will not be compiled in.
*
* You can pass %G_CLEANUP_SCOPE as a #GCleanupScope to g_cleanup_list_push()
* and g_cleanup_list_remove() if you need to.
*/
/**
* G_CLEANUP_PHASE_EARLY:
*
* Cleanup items that run before the main phase. This might be used for cleanup
* items that stop worker threads.
*/
/**
* G_CLEANUP_PHASE_DEFAULT:
*
* The main set of cleanup items.
*/
/**
* G_CLEANUP_PHASE_LATE:
*
* Cleanup items that run after the main phase. This is used to cleanup items
* that the main cleanup phase still depends on.
*/
/**
* G_CLEANUP_PHASE_GRAVEYARD:
*
* Special extremely late cleanup items. Rarely used outside of GLib. By
* convention, cleanup items running in this phase should not only use lower
* level facilitities, and not run other parts of the library's code.
*/
/*
* NOTE: most glib functions are off limits in this function without
* careful consideration. In particular logging functions,
* use various locks, which would cause issues during cleanup.
*/
/* As good a place as any to put this... */
G_CLEANUP_DEFINE;
static gint marked = 0;
/* GCleanupNode flags */
enum {
DEREF_CLEAR_POINTER = 1 << 16,
PHASE_MASK = 0xFFFF
};
typedef struct _GCleanupNode GCleanupNode;
struct _GCleanupNode
{
/* Lower 16 bits is phase, higher bits are flags */
guint phase_and_flags;
/* @func is accessed atomically. If NULL, then node is removed */
GCleanupFunc func;
gpointer data;
/* May drop this if we drop debugging */
const gchar *func_name;
GCleanupNode *next;
};
typedef struct {
gint flags;
GCleanupNode *nodes;
gint lock;
gint swept;
} GRealCleanup;
static gboolean
check_if_verbose (void)
{
const gchar *env = getenv ("G_MESSAGES_DEBUG");
return (env && (strstr (env, "GLib-Cleanup") || strcmp (env, "all") == 0));
}
/**
* g_cleanup_is_enabled:
*
* Checks if the program should attempt to cleanup allocated memory at
* exit.
*
* This function will return true if the G_DEBUG variable is set to or
* includes 'cleanup'.
*
* See G_CLEANUP() and %G_CLEANUP_DEFINE for the recommended way to
* deal with memory cleanup.
*
* Returns: %TRUE if memory cleanup is enabled
*
* Since: 2.40
**/
gboolean
g_cleanup_is_enabled (void)
{
return g_cleanup_enabled;
}
static gpointer
cleanup_scope_push (GRealCleanup *cleanup,
gint phase,
guint flags,
GCleanupFunc cleanup_func,
gpointer user_data)
{
GCleanupNode *node;
GCleanupNode **ptr;
node = NULL;
/*
* We use the bit locks, as they don't need cleanup themselves. In theory
* we could perform all the needed operations in a lock-less manner, but
* using a simple lock should be more efficient.
*/
g_bit_lock (&cleanup->lock, 1);
/*
* Item removal is optimized for removal during cleanup. However in the case
* of repeated removal/push during the source of the process (ie: before
* cleanup has begun), we don't want the GCleanupScope to become a memory
* leak.
*
* So we reuse removed nodes here.
*/
marked = g_atomic_int_get (&marked);
if (marked != cleanup->swept)
{
for (ptr = &cleanup->nodes; (node = *ptr) != NULL; ptr = &(*ptr)->next)
{
/* If the node->func is NULL, steal the node */
if (g_atomic_pointer_compare_and_exchange (&node->func, NULL, cleanup_func))
{
g_atomic_int_add (&marked, -1);
*ptr = node->next;
break;
}
}
/* A full pass found nothing, don't try again */
if (node == NULL)
cleanup->swept = marked;
}
/* Allocate a new one */
if (node == NULL)
node = calloc (1, sizeof (GCleanupNode));
if (node != NULL)
{
node->func = cleanup_func;
node->data = user_data;
/*
* We use the first 16 bits of GCleanupNode->phase_and_flags as the phase.
* However we want callers to be able to specify zero as default phase,
* negative as early, positive as late. So convert to a unsigned phase
* here, and combine with flags.
*/
g_assert ((flags & PHASE_MASK) == 0);
phase = CLAMP (phase, -1024, 1024) + G_MAXINT16;
node->phase_and_flags = (guint) phase | flags;
node->next = cleanup->nodes;
cleanup->nodes = node;
}
g_bit_unlock (&cleanup->lock, 1);
return node;
}
void
g_cleanup_annotate (gpointer cleanup_item,
const gchar *func_name)
{
GCleanupNode *node;
if (cleanup_item == NULL)
return;
node = cleanup_item;
node->func_name = func_name;
if (check_if_verbose ())
{
fprintf (stderr, "GLib-Cleanup-DEBUG: pushed: %s (%p) at %u\n",
func_name, node->data, node->phase_and_flags & PHASE_MASK);
}
}
/** xxxx
* g_cleanup_list_push:
* @scope: (allow-none): a #GCleanupScope
* @phase: a phase to run this cleanup item in
* @cleanup_func: the cleanup function
* @user_data: (allow-none): data for the cleanup function
* @func_name: (allow-none): static string representing name of function
*
* Adds a cleanup item to @scope.
*
* When g_cleanup_list_clean() is called on @scope, @cleanup_func will be
* called with @user_data.
*
* Most typically, you will not use this function directly. See
* G_CLEANUP(), G_CLEANUP_FUNC().
*
* This function is threadsafe. Multiple threads can add to the same
* scope at the same time.
*
* The returned pointer can be used with g_cleanup_list_remove()
* to later remove the item.
*
* Since: 2.40
*
* Returns: a tag which can be used for item removal
* xxxxx
**/
gpointer
g_cleanup_push (GCleanupScope *cleanup,
gint phase,
GCleanupFunc cleanup_func,
gpointer user_data)
{
GRealCleanup *real = (GRealCleanup *)cleanup;
if (cleanup && (real->flags & G_CLEANUP_SCOPE_FORCE || g_cleanup_enabled))
return cleanup_scope_push (real, phase, 0, cleanup_func, user_data);
return NULL;
}
gpointer
g_cleanup_push_pointer (GCleanupScope *cleanup,
gint phase,
GCleanupFunc cleanup_func,
gpointer *pointer_to_data)
{
GRealCleanup *real = (GRealCleanup *)cleanup;
if (cleanup && (real->flags & G_CLEANUP_SCOPE_FORCE || g_cleanup_enabled))
{
return cleanup_scope_push (real, phase, DEREF_CLEAR_POINTER,
cleanup_func, pointer_to_data);
}
return NULL;
}
static gboolean
dummy_callback (gpointer user_data)
{
g_assert_not_reached ();
return FALSE;
}
void
g_cleanup_push_source (GCleanupScope *cleanup,
gint phase,
GSource *source)
{
GRealCleanup *real = (GRealCleanup *)cleanup;
gpointer cleanup_item;
GSource *child;
/*
* So we want to get a callback when a source is destroyed. The only way
* I've found to do that is by registering a child source. By setting NULL
* for all the functions, they return FALSE, and don't dispatch().
*/
static GSourceFuncs funcs = { NULL, };
if (cleanup && (real->flags & G_CLEANUP_SCOPE_FORCE || g_cleanup_enabled))
{
cleanup_item = cleanup_scope_push (real, phase, 0, (GCleanupFunc)g_source_destroy, source);
if (cleanup_item)
{
child = g_source_new (&funcs, sizeof (GSource));
g_source_set_callback (child, dummy_callback, cleanup_item, g_cleanup_remove);
g_source_add_child_source (source, child);
g_source_unref (child);
}
}
}
/** xxxx
* g_cleanup_steal:
* @scope: (allow-none): a #GCleanupScope
* @item: (allow-none): the cleanup item
*
* Removes an @item in the scope.
*
* It is not typically necessary to remove cleanup items, since cleanup is
* usually done on global or otherwise persistent data.
*
* This function reverses a previous call to g_cleanup_list_push(), and takes
* the item pointer returned by g_cleanup_list_push().
*
* This function is threadsafe. You can call this function one or zero times
* for an item that was added to the @scope. However you should not call this
* function after g_cleanup_list_clean() returns.
*
* Since: 2.40
* xxxxx
**/
void
g_cleanup_remove (gpointer cleanup_item)
{
g_cleanup_steal (cleanup_item, NULL);
}
GCleanupFunc
g_cleanup_steal (gpointer cleanup_item,
gpointer *stolen_data)
{
GCleanupNode *node;
GCleanupFunc func;
gpointer data;
if (!cleanup_item)
return NULL;
/*
* We optimize the case where items are removed during cleanup, as this
* happens very often. However, see g_cleanup_list_add().
*/
node = cleanup_item;
/*
* Always access @func atomically. This allows us to not hold the lock
* while executing the callbacks in g_cleanup_list_clean().
*/
do
{
data = node->data;
func = g_atomic_pointer_get (&node->func);
if (!func)
break;
}
while (!g_atomic_pointer_compare_and_exchange (&node->func, func, NULL));
if (!func)
return NULL;
/* Help g_cleanup_push find this removed item */
g_atomic_int_add (&marked, 1);
if (check_if_verbose ())
{
fprintf (stderr, "GLib-Cleanup-DEBUG: remove: %s (%p) at %d\n",
node->func_name, data, node->phase_and_flags & PHASE_MASK);
}
if (stolen_data)
*stolen_data = data;
return func;
}
/** xxxx
* g_cleanup_list_clean:
* @scope: a #GCleanupScope
*
* Clears @scope.
*
* This results in all of the previously-added functions being called.
*
* You usually do not need to call this directly. %G_CLEANUP_DEFINE will
* emit a destructor function to call this when your library or program
* is being unloaded.
*
* This function is threadsafe. Changes can occur while adds and
* changes are occuring in other threads.
*
* Since: 2.40
* xxx
**/
void
g_cleanup_clean (GCleanupScope *scope)
{
GRealCleanup *cleanup;
GCleanupNode *later;
GCleanupNode *nodes;
GCleanupNode *node;
GCleanupNode **last;
GCleanupFunc func;
gboolean verbose;
gint phase, node_phase;
gint next;
if (!scope)
return;
verbose = check_if_verbose ();
cleanup = (GRealCleanup *)scope;
later = NULL;
nodes = NULL;
phase = 0;
next = G_MAXUINT16;
do
{
later = nodes;
g_bit_lock (&cleanup->lock, 1);
nodes = cleanup->nodes;
cleanup->nodes = NULL;
g_bit_unlock (&cleanup->lock, 1);
/* The current phase */
phase = next;
/* No next phase yet */
next = G_MAXUINT16;
/* Find a lower phase, and find last node */
last = &nodes;
for (node = nodes; node; node = node->next)
{
node_phase = node->phase_and_flags & PHASE_MASK;
if (node_phase < phase)
phase = node_phase;
last = &node->next;
}
/* Join the two lists */
*last = later;
/* Run that phase */
for (node = nodes; node; node = node->next)
{
func = g_atomic_pointer_get (&node->func);
if (!func)
continue;
node_phase = node->phase_and_flags & PHASE_MASK;
/* Part of this phase */
if (phase == node_phase)
{
if (g_atomic_pointer_compare_and_exchange (&node->func, func, NULL))
{
if (verbose)
{
fprintf (stderr, "GLib-Cleanup-DEBUG: clean: %s (%p) at %d\n",
node->func_name, node->data, phase);
}
if (node->phase_and_flags & DEREF_CLEAR_POINTER)
g_clear_pointer ((gpointer *)node->data, func);
else
(func) (node->data);
}
}
/* Find the next phase */
else if (node_phase > phase && node_phase < next)
{
next = node_phase;
}
}
}
while (next != G_MAXUINT16);
/* Free all the nodes */
while (nodes)
{
node = nodes;
nodes = node->next;
free (node);
}
if (verbose)
fprintf (stderr, "GLib-Cleanup-DEBUG: cleanup: done\n");
}
/**
* G_CLEANUP_DEFINE:
*
* Sets up the GLib memory cleanup infrastructure for a shared library
* or executable program. This macro should be used once per
* shared library or executable.
*
* The macro defines a linked scope to which cleanup functions will be
* added if memory cleanup has been enabled. It also defines a
* destructor function to free the items in this scope on the current
* module being unloaded (usually after main() returns).
*
* Since: 2.40
**/
/**
* G_CLEANUP:
* @data: the data to free
* @notify: the function used to free data
*
* Marks an item to be freed when performing memory cleanup.
*
* If memory cleanup is enabled then @function will be called on @data
* at the time that destructors are being run for the current module
* (ie: at program exit or module unload).
*
* In order for this to work, G_CLEANUP_DEFINE needs to be used exactly
* once somewhere in a source file in your module.
*
* If you want to call a function to cleanup several static variables
* then use G_CLEANUP_FUNC() instead.
*
* Since: 2.40
**/
/**
* G_CLEANUP_FUNC:
* @cleanup_func: the cleanup function to call
*
* Adds a function to be called when performing memory cleanup.
*
* If memory cleanup is enabled then @function will be called at the
* time that destructors are being run for the current module (ie: at
* program exit or module unload).
*
* In order for this to work, G_CLEANUP_DEFINE needs to be used exactly
* once somewhere in a source file in your module.
*
* Since: 2.40
**/

153
glib/gcleanup.h Normal file
View File

@@ -0,0 +1,153 @@
/*
* Copyright © 2013 Canonical Limited
* Copyright © 2013 Red Hat Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the licence, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*
* Author: Ryan Lortie <desrt@desrt.ca>
* Stef Walter <stefw@redhat.com>
*/
#ifndef __G_CLEANUP_H__
#define __G_CLEANUP_H__
#if !defined (__GLIB_H_INSIDE__) && !defined (GLIB_COMPILATION)
#error "Only <glib.h> can be included directly."
#endif
#include <glib/gconstructor.h>
#include <glib/gmain.h>
#include <glib/gquark.h>
#include <glib/gtypes.h>
G_BEGIN_DECLS
/* Don't use codes higher than 1000 or lower than -1000 */
enum {
G_CLEANUP_PHASE_EARLY = -50,
G_CLEANUP_PHASE_DEFAULT = 0,
G_CLEANUP_PHASE_LATE = 50,
G_CLEANUP_PHASE_GRAVEYARD = 100,
};
enum {
G_CLEANUP_SCOPE_FORCE = 1 << 0,
};
typedef struct
{
/*< private >*/
gint value;
gpointer priv[4];
} GCleanupScope;
typedef void (* GCleanupFunc) (gpointer user_data);
GLIB_AVAILABLE_IN_2_40
gboolean g_cleanup_is_enabled (void);
GLIB_AVAILABLE_IN_2_40
gpointer g_cleanup_push (GCleanupScope *cleanup,
gint phase,
GCleanupFunc cleanup_func,
gpointer user_data);
GLIB_AVAILABLE_IN_2_40
gpointer g_cleanup_push_pointer (GCleanupScope *cleanup,
gint phase,
GCleanupFunc cleanup_func,
gpointer *pointer_to_data);
GLIB_AVAILABLE_IN_2_40
void g_cleanup_push_source (GCleanupScope *cleanup,
gint phase,
GSource *source);
GLIB_AVAILABLE_IN_2_40 /* NOTE: annotate() very useful for debugging, but might not merge */
void g_cleanup_annotate (gpointer cleanup_item,
const gchar *func_name);
GLIB_AVAILABLE_IN_2_40
void g_cleanup_remove (gpointer cleanup_item);
GLIB_AVAILABLE_IN_2_40
GCleanupFunc g_cleanup_steal (gpointer cleanup_item,
gpointer *user_data);
GLIB_AVAILABLE_IN_2_40
void g_cleanup_clean (GCleanupScope *cleanup);
#if defined(G_CLEANUP_SCOPE) && defined(G_HAS_CONSTRUCTORS) && !defined(G_DEFINE_CONSTRUCTOR_NEEDS_PRAGMA)
/*
* G_CLEANUP_SCOPE acts as a pointer with a constant address usable in initializers.
*/
extern GCleanupScope G_CLEANUP_SCOPE[1];
#define G_CLEANUP_DEFINE_WITH_FLAGS(flags) \
GCleanupScope G_CLEANUP_SCOPE[1] = { { flags, { 0, }, } }; \
G_DEFINE_DESTRUCTOR (G_PASTE (G_CLEANUP_SCOPE, _perform)) \
static void G_PASTE (G_CLEANUP_SCOPE, _perform) (void) { \
g_cleanup_clean (G_CLEANUP_SCOPE); \
}
#define G_CLEANUP_IN(data, func, phase) \
G_STMT_START { \
gpointer _it = g_cleanup_push (G_CLEANUP_SCOPE, phase, \
(void*) (func), (data)); \
g_cleanup_annotate (_it, G_STRINGIFY (func)); \
if (0) (func) ((data)); \
} G_STMT_END
#define G_CLEANUP_FUNC_IN(func, phase) \
G_STMT_START { \
gpointer _it = g_cleanup_push (G_CLEANUP_SCOPE, phase, \
(void*) (func), NULL); \
g_cleanup_annotate (_it, G_STRINGIFY (func)); \
if (0) (func) (); \
} G_STMT_END
#define G_CLEANUP_POINTER_IN(pp, func, phase) \
G_STMT_START { \
gpointer *_pp = (gpointer *)pp; \
gpointer _it = g_cleanup_push_pointer (G_CLEANUP_SCOPE, phase, \
(void*) (func), _pp); \
g_cleanup_annotate (_it, G_STRINGIFY (func)); \
if (0) (func) (*(pp)); \
} G_STMT_END
#else
#define G_CLEANUP_SCOPE NULL
#define G_CLEANUP_DEFINE_WITH_FLAGS(flags)
#define G_CLEANUP_IN(data, func, phase) \
G_STMT_START { \
if (0) (func) (data); \
} G_STMT_END
#define G_CLEANUP_FUNC_IN(func, phase) \
G_STMT_START { \
if (0) (func) (); \
} G_STMT_END
#define G_CLEANUP_POINTER_IN(pp, func, phase) \
G_STMT_START { \
if (0) (func) (*(pp)); \
} G_STMT_END
#endif
#define G_CLEANUP_DEFINE \
G_CLEANUP_DEFINE_WITH_FLAGS(0)
#define G_CLEANUP(data, func) \
G_CLEANUP_IN (data, func, G_CLEANUP_PHASE_DEFAULT)
#define G_CLEANUP_FUNC(func) \
G_CLEANUP_FUNC_IN (func, G_CLEANUP_PHASE_DEFAULT)
#define G_CLEANUP_POINTER(pp, func) \
G_CLEANUP_POINTER_IN (func, G_CLEANUP_PHASE_DEFAULT)
G_END_DECLS
#endif /* __G_CLEANUP_H__ */

View File

@@ -43,6 +43,7 @@ gboolean g_mem_gc_friendly = TRUE;
#else
gboolean g_mem_gc_friendly = FALSE;
#endif
gboolean g_cleanup_enabled = FALSE;
GLogLevelFlags g_log_msg_prefix = G_LOG_LEVEL_ERROR | G_LOG_LEVEL_WARNING |
G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_DEBUG;
GLogLevelFlags g_log_always_fatal = G_LOG_FATAL_MASK;
@@ -203,6 +204,8 @@ g_debug_init (void)
{
const GDebugKey keys[] = {
{ "gc-friendly", 1 },
{ "cleanup", 2 },
/* warning: G_LOG_LEVEL_ERROR is 4, so you'd better not use that one next... */
{"fatal-warnings", G_LOG_LEVEL_WARNING | G_LOG_LEVEL_CRITICAL },
{"fatal-criticals", G_LOG_LEVEL_CRITICAL }
};
@@ -213,6 +216,7 @@ g_debug_init (void)
g_log_always_fatal |= flags & G_LOG_LEVEL_MASK;
g_mem_gc_friendly = flags & 1;
g_cleanup_enabled = (flags & 2) != 0;
}
static void

View File

@@ -26,6 +26,7 @@
extern GLogLevelFlags g_log_always_fatal;
extern GLogLevelFlags g_log_msg_prefix;
extern gboolean g_cleanup_enabled;
#ifdef G_OS_WIN32
#include <windows.h>

View File

@@ -40,6 +40,7 @@
#include <glib/gbytes.h>
#include <glib/gcharset.h>
#include <glib/gchecksum.h>
#include <glib/gcleanup.h>
#include <glib/gconstructor.h>
#include <glib/gconvert.h>
#include <glib/gdataset.h>

View File

@@ -11,6 +11,7 @@ bookmarkfile
bytes
cache
checksum
cleanup
collate
cond
convert

View File

@@ -30,6 +30,7 @@ test_extra_programs = \
$(NULL)
test_programs = \
cleanup \
array-test \
asyncqueue \
base64 \

197
glib/tests/cleanup.c Normal file
View File

@@ -0,0 +1,197 @@
/*
* Copyright 2011 Red Hat, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* See the included COPYING file for more information.
*/
#undef G_CLEANUP_SCOPE
#define G_CLEANUP_SCOPE my_cleanup
#include <glib.h>
#include <string.h>
GCleanupScope G_CLEANUP_SCOPE[1] = { { G_CLEANUP_SCOPE_FORCE, } };
static void
cleanup_one (gpointer data)
{
gint *value = data;
g_assert_cmpint (*value, ==, 1);
(*value)++;
}
static void
cleanup_two (gpointer data)
{
gint *value = data;
g_assert_cmpint (*value, ==, 2);
(*value)++;
}
static void
cleanup_three (gpointer data)
{
gint *value = data;
g_assert_cmpint (*value, ==, 3);
(*value)++;
}
static void
cleanup_four (gpointer data)
{
gint *value = data;
g_assert_cmpint (*value, ==, 4);
(*value)++;
}
static void
cleanup_two_and_push_three (gpointer data)
{
cleanup_two (data);
/* Push another item */
G_CLEANUP_IN (data, cleanup_three, G_CLEANUP_PHASE_DEFAULT);
}
static void
test_push_and_clean (void)
{
gint value = 1;
/* This tests ordering of pushing three, and having it run before four is executed */
G_CLEANUP_IN (&value, cleanup_one, G_CLEANUP_PHASE_EARLY);
G_CLEANUP_IN (&value, cleanup_two_and_push_three, G_CLEANUP_PHASE_DEFAULT);
G_CLEANUP_IN (&value, cleanup_four, G_CLEANUP_PHASE_LATE);
g_cleanup_clean (my_cleanup);
g_assert_cmpint (value, ==, 5);
}
static void
test_push_and_remove (void)
{
gint value = 1;
gpointer item;
gpointer item2;
gpointer func;
gpointer data;
/* This tests ordering of pushing three, and having it run before four is executed */
G_CLEANUP_IN (&value, cleanup_one, G_CLEANUP_PHASE_EARLY);
item = g_cleanup_push (my_cleanup, G_CLEANUP_PHASE_EARLY, cleanup_three, &value);
item2 = g_cleanup_push (my_cleanup, G_CLEANUP_PHASE_EARLY, cleanup_four, &value);
G_CLEANUP_IN (&value, cleanup_two, G_CLEANUP_PHASE_DEFAULT);
G_CLEANUP_IN (item2, g_cleanup_remove, G_CLEANUP_PHASE_EARLY);
G_CLEANUP_IN (&value, cleanup_three, G_CLEANUP_PHASE_DEFAULT + 1);
G_CLEANUP_IN (&value, cleanup_four, G_CLEANUP_PHASE_LATE);
/* One remove happens before clean */
func = g_cleanup_steal (item, &data);
g_assert (func == cleanup_three);
g_assert (data == &value);
g_cleanup_clean (my_cleanup);
/* The other remove happened during clean */
g_assert_cmpint (value, ==, 5);
}
static void
cleanup_pointer (gchar *value)
{
g_assert_cmpstr (value, ==, "blah");
memcpy (value, "alot", 4);
}
static void
test_pointer (void)
{
gchar buf_one[] = "blah";
gchar *pointer_one = buf_one;
gchar *pointer_two = NULL;
gchar buf_three[] = "blah";
gchar *pointer_three = buf_three;
gpointer item;
/* This tests ordering of pushing three, and having it run before four is executed */
G_CLEANUP_POINTER_IN (&pointer_one, cleanup_pointer, 0);
G_CLEANUP_POINTER_IN (&pointer_two, cleanup_pointer, 0);
item = g_cleanup_push_pointer (my_cleanup, 0, (GCleanupFunc)cleanup_pointer, (gpointer *)&pointer_three);
G_CLEANUP_IN (item, g_cleanup_remove, G_CLEANUP_PHASE_EARLY);
g_cleanup_clean (my_cleanup);
g_assert_cmpstr (buf_one, ==, "alot");
g_assert (pointer_one == NULL);
g_assert (pointer_two == NULL);
g_assert_cmpstr (buf_three, ==, "blah");
g_assert (pointer_three == buf_three);
}
static void
test_source_cleaned (void)
{
static GSourceFuncs funcs = { NULL, };
GSource *source;
guint id;
source = g_source_new (&funcs, sizeof (GSource));
id = g_source_attach (source, NULL);
g_cleanup_push_source (my_cleanup, 0, source);
g_source_unref (source);
g_assert (g_main_context_find_source_by_id (NULL, id) != NULL);
g_cleanup_clean (my_cleanup);
g_assert (g_main_context_find_source_by_id (NULL, id) == NULL);
}
static void
test_source_destroyed (void)
{
static GSourceFuncs funcs = { NULL, };
GSource *source;
guint id;
source = g_source_new (&funcs, sizeof (GSource));
id = g_source_attach (source, NULL);
g_cleanup_push_source (my_cleanup, 0, source);
g_source_unref (source);
g_assert (g_main_context_find_source_by_id (NULL, id) != NULL);
g_source_destroy (source);
g_assert (g_main_context_find_source_by_id (NULL, id) == NULL);
g_cleanup_clean (my_cleanup);
/* We're really checking for invalid memory access in this test */
}
int
main (int argc, char **argv)
{
g_test_init (&argc, &argv, NULL);
g_test_add_func ("/cleanup/push-and-clean", test_push_and_clean);
g_test_add_func ("/cleanup/push-and-remove", test_push_and_remove);
g_test_add_func ("/cleanup/pointer", test_pointer);
g_test_add_func ("/cleanup/source-cleaned", test_source_cleaned);
g_test_add_func ("/cleanup/source-destroyed", test_source_destroyed);
return g_test_run ();
}