[glib/wip/gcleanup: 10/71] gcleanup: Implementation of GCleanupScope and associated macros



commit 81a2133a72f16e568083d3ecacfac1803e1374a8
Author: Stef Walter <stefw gnome org>
Date:   Wed Nov 13 08:54:52 2013 +0100

    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

 docs/reference/glib/glib-sections.txt |   15 +
 glib/Makefile.am                      |    2 +
 glib/gcleanup.c                       |  620 +++++++++++++++++++++++++++++++++
 glib/gcleanup.h                       |  153 ++++++++
 glib/glib-init.c                      |    4 +
 glib/glib-init.h                      |    1 +
 glib/glib.h                           |    1 +
 glib/tests/.gitignore                 |    1 +
 glib/tests/Makefile.am                |    1 +
 glib/tests/cleanup.c                  |  197 +++++++++++
 10 files changed, 995 insertions(+), 0 deletions(-)
---
diff --git a/docs/reference/glib/glib-sections.txt b/docs/reference/glib/glib-sections.txt
index 9613a5f..0198855 100644
--- a/docs/reference/glib/glib-sections.txt
+++ b/docs/reference/glib/glib-sections.txt
@@ -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>
diff --git a/glib/Makefile.am b/glib/Makefile.am
index 7c737d8..9c3c19e 100644
--- a/glib/Makefile.am
+++ b/glib/Makefile.am
@@ -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      \
diff --git a/glib/gcleanup.c b/glib/gcleanup.c
new file mode 100644
index 0000000..797d08f
--- /dev/null
+++ b/glib/gcleanup.c
@@ -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
+ **/
diff --git a/glib/gcleanup.h b/glib/gcleanup.h
new file mode 100644
index 0000000..f52ef9b
--- /dev/null
+++ b/glib/gcleanup.h
@@ -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__ */
diff --git a/glib/glib-init.c b/glib/glib-init.c
index ceb9395..70dfc54 100644
--- a/glib/glib-init.c
+++ b/glib/glib-init.c
@@ -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
diff --git a/glib/glib-init.h b/glib/glib-init.h
index 5b150a4..c279215 100644
--- a/glib/glib-init.h
+++ b/glib/glib-init.h
@@ -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>
diff --git a/glib/glib.h b/glib/glib.h
index c117761..e72034a 100644
--- a/glib/glib.h
+++ b/glib/glib.h
@@ -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>
diff --git a/glib/tests/.gitignore b/glib/tests/.gitignore
index 22bce40..8204f6f 100644
--- a/glib/tests/.gitignore
+++ b/glib/tests/.gitignore
@@ -11,6 +11,7 @@ bookmarkfile
 bytes
 cache
 checksum
+cleanup
 collate
 cond
 convert
diff --git a/glib/tests/Makefile.am b/glib/tests/Makefile.am
index 65598b4..93b27d2 100644
--- a/glib/tests/Makefile.am
+++ b/glib/tests/Makefile.am
@@ -30,6 +30,7 @@ test_extra_programs = \
        $(NULL)
 
 test_programs = \
+       cleanup                         \
        array-test                      \
        asyncqueue                      \
        base64                          \
diff --git a/glib/tests/cleanup.c b/glib/tests/cleanup.c
new file mode 100644
index 0000000..47e8e59
--- /dev/null
+++ b/glib/tests/cleanup.c
@@ -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 ();
+}


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]