/*
 * gobject-list: a LD_PRELOAD library for tracking the lifetime of GObjects
 *
 * Copyright (C) 2011 Collabora Ltd.
 * Copyright (C) 2011 Morten Welinder
 *
 * 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.1 of the License, 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
 * USA
 *
 * Authors:
 *     Danielle Madeley  <danielle.madeley@collabora.co.uk>
 *     Morten Welinder <terra@gnome.org>
 */

/* Minimal Makefile:

   CFLAGS=`pkg-config --cflags glib-2.0`
   LIBS=`pkg-config --libs glib-2.0`

   libgobject-list.so: gobject-list.c
   gcc -fPIC -rdynamic -g -c -Wall ${CFLAGS} $<
   gcc -shared -Wl,-soname,$@ -o $@ gobject-list.o -lc -ldl ${LIBS}
*/

#include <glib-object.h>

#include <dlfcn.h>
#include <signal.h>
#include <string.h>
#include <stdlib.h>

static GHashTable *objects = NULL;

static GObject * (*real_g_object_constructor) (GType                  type,
					       guint                  n_construct_properties,
					       GObjectConstructParam *construct_params);
static void (*real_g_object_finalize) (GObject *object);

static gpointer (*real_gdk_window_get_user_data) (gpointer, gpointer *);
static gpointer (*real_gdk_display_manager_get) (void);
static void (*real_gdk_flush) (void);
static GSList * (*real_gdk_display_manager_list_displays) (gpointer);
static void (*real_gdk_display_close) (gpointer);
static GType _gdk_window_object_type;
static gboolean seen_gdk_window = FALSE;
static GType (*real_gdk_window_object_get_type) (void);

static const char * (*real_gtk_label_get_text) (gpointer);
static gpointer (*real_gtk_widget_get_parent) (gpointer);
static GSList * (*real_gtk_size_group_get_widgets) (gpointer);
static GType (*real_gtk_action_group_get_type) (void);
static GType (*real_gtk_action_get_type) (void);
static GType (*real_gtk_label_get_type) (void);
static GType (*real_gtk_widget_get_type) (void);
static GType (*real_gtk_window_get_type) (void);
static GType (*real_gtk_size_group_get_type) (void);

static void initialize (void);

static gboolean
object_filter (const char *obj_name)
{
  static const char *filter;
  static gboolean inited = FALSE;

  if (!inited) {
    inited = TRUE;
    filter = g_getenv ("GOBJECT_LIST_FILTER");
  }

  if (filter == NULL)
    return TRUE;
  else
    return (strncmp (filter, obj_name, strlen (filter)) == 0);
}

static void
dump_object_list (void)
{
  GHashTableIter iter;
  GObject *obj;

  g_hash_table_iter_init (&iter, objects);
  while (g_hash_table_iter_next (&iter, (gpointer) &obj, NULL))
    {
      g_printerr (" - %p, %s: %u refs",
		  obj, G_OBJECT_TYPE_NAME (obj), obj->ref_count);

      if (real_gtk_action_group_get_type &&
	  G_TYPE_CHECK_INSTANCE_TYPE (obj, real_gtk_action_group_get_type ()))
	{
	  gchar *name = NULL;

	  g_object_get (obj, "name", &name, NULL);
	  if (name)
	    g_printerr (" name=%s", name);
	  g_free (name);
	}

      if (real_gtk_action_get_type &&
	  G_TYPE_CHECK_INSTANCE_TYPE (obj, real_gtk_action_get_type ()))
	{
	  gchar *name = NULL;

	  g_object_get (obj, "name", &name, NULL);
	  if (name)
	    g_printerr (" name=%s", name);
	  g_free (name);
	}

      if (real_gtk_label_get_type &&
	  G_TYPE_CHECK_INSTANCE_TYPE (obj, real_gtk_label_get_type ()))
	{
	  g_printerr (" label=\"%s\"", real_gtk_label_get_text (obj));
	}

      if (real_gtk_widget_get_type &&
	  G_TYPE_CHECK_INSTANCE_TYPE (obj, real_gtk_widget_get_type ()))
	{
	  GObject *parent = real_gtk_widget_get_parent (obj);
	  gchar *name = NULL;

	  if (parent)
	    g_printerr (" parent=%p", parent);

	  g_object_get (obj, "name", &name, NULL);
	  if (name && *name)
	    g_printerr (" name=%s", name);
	  g_free (name);
	}

      if (real_gtk_window_get_type &&
	  G_TYPE_CHECK_INSTANCE_TYPE (obj, real_gtk_window_get_type ()))
	{
	  gchar *title = NULL;

	  g_object_get (obj, "title", &title, NULL);
	  if (title)
	    g_printerr (" title=%s", title);
	  g_free (title);
	}

      if (real_gtk_size_group_get_type &&
	  G_TYPE_CHECK_INSTANCE_TYPE (obj, real_gtk_size_group_get_type ()))
	{
	  GSList *l = real_gtk_size_group_get_widgets (obj);
	  if (l)
	    {
	      g_printerr (" widgets=[");
	      while (l) {
		g_printerr ("%p", l->data);
		l = l->next;
		if (l)
		  g_printerr (",");
	      }
	      g_printerr ("]");
	    }
	}

      if (_gdk_window_object_type &&
	  G_TYPE_CHECK_INSTANCE_TYPE (obj, _gdk_window_object_type))
	{
	  gpointer user;

	  real_gdk_window_get_user_data (obj, &user);
	  if (user)
	    g_printerr (" user=%p", user);
	}

      g_printerr ("\n");
    }
}

static void
sig_usr1_handler (int signal)
{
  g_printerr ("\nLiving Objects:\n");
  dump_object_list ();
}

static void
exiting (void)
{
  if (seen_gdk_window)
    {
      GSList *displays;

      real_gdk_flush ();
      while (g_main_context_iteration (NULL, FALSE))
	;/* nothing */

      displays = real_gdk_display_manager_list_displays
	(real_gdk_display_manager_get ());
      g_slist_foreach (displays, (GFunc)real_gdk_display_close, NULL);
      g_slist_free (displays);
    }
  g_printerr ("\nStill Alive:\n");
  dump_object_list ();
}

static void *
get_func (void *handle, const char *func_name)
{
  void *func;
  char *error;

  func = dlsym (handle, func_name);

  if ((error = dlerror ()) != NULL)
    g_error ("Failed to find symbol: %s", error);

  return func;
}

static GObject*
hook_g_object_constructor (GType                  type,
			   guint                  n_construct_properties,
			   GObjectConstructParam *construct_params)
{
  GObject *obj = real_g_object_constructor (type,
					    n_construct_properties,
					    construct_params);
  const char *obj_name;

  if (!obj)
    return NULL;

  obj_name = G_OBJECT_TYPE_NAME (obj);
  if (object_filter (obj_name))
    {
      g_printerr (" ++ Created object %p, %s\n", obj, obj_name);
      g_hash_table_insert (objects, obj, GUINT_TO_POINTER (TRUE));
    }

  return obj;
}

static void
hook_g_object_finalize (GObject *obj)
{
  char *typ = g_strdup (G_OBJECT_TYPE_NAME (obj));
  g_hash_table_remove (objects, obj);
  real_g_object_finalize (obj);
  g_printerr (" -- Finalized object %p, %s\n", obj, typ);
  g_free (typ);
}

GType
gdk_window_object_get_type (void)
{
  seen_gdk_window = TRUE;
  return real_gdk_window_object_get_type ();
}

void
g_type_init (void)
{
  initialize ();
}


static void
initialize (void)
{
  void (*real_g_type_init) (void);
  void *gdk_handle;
  void *gtk_handle;
  void *glib_handle;

  if (G_LIKELY (objects != NULL))
    return;

  /* Note: we are not in a sane state until we call the real g_type_init().  */

  glib_handle = dlopen("libgobject-2.0.so.0", RTLD_LAZY);
  if (glib_handle == NULL)
    g_error ("Failed to open libgobject-2.0.so.0: %s", dlerror ());

  real_g_type_init = get_func (glib_handle, "g_type_init");
  real_g_type_init ();

  /* set up signal handler */
  signal (SIGUSR1, sig_usr1_handler);

  /* set up objects map */
  objects = g_hash_table_new (NULL, NULL);

  /* Set up exit handler */
  atexit (exiting);

  {
    GObjectClass *klass = g_type_class_ref (G_TYPE_OBJECT);

    real_g_object_constructor = klass->constructor;
    klass->constructor = hook_g_object_constructor;

    real_g_object_finalize = klass->finalize;
    klass->finalize = hook_g_object_finalize;

    g_type_class_unref (klass);
  }

  gdk_handle = dlopen ("libgdk-x11-2.0.so.0", RTLD_LAZY);
  if (gdk_handle)
    {
      real_gdk_window_object_get_type = get_func (gdk_handle, "gdk_window_object_get_type");

      real_gdk_window_get_user_data = get_func (gdk_handle, "gdk_window_get_user_data");
      real_gdk_display_manager_get = get_func (gdk_handle, "gdk_display_manager_get");
      real_gdk_display_manager_list_displays = get_func (gdk_handle, "gdk_display_manager_list_displays");
      real_gdk_display_close = get_func (gdk_handle, "gdk_display_close");
      real_gdk_flush = get_func (gdk_handle, "gdk_flush");
    }

  gtk_handle = dlopen ("libgtk-x11-2.0.so.0", RTLD_LAZY);
  if (gtk_handle)
    {
      real_gtk_action_get_type = get_func (gtk_handle, "gtk_action_get_type");
      real_gtk_action_group_get_type = get_func (gtk_handle, "gtk_action_group_get_type");
      real_gtk_label_get_type = get_func (gtk_handle, "gtk_label_get_type");
      real_gtk_widget_get_type = get_func (gtk_handle, "gtk_widget_get_type");
      real_gtk_window_get_type = get_func (gtk_handle, "gtk_window_get_type");
      real_gtk_size_group_get_type = get_func (gtk_handle, "gtk_size_group_get_type");

      real_gtk_label_get_text = get_func (gtk_handle, "gtk_label_get_text");
      real_gtk_widget_get_parent = get_func (gtk_handle, "gtk_widget_get_parent");
      real_gtk_size_group_get_widgets = get_func (gtk_handle, "gtk_size_group_get_widgets");
    }
}
