/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- * * Copyright (C) 2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ /** * SECTION:as-ref-string * @short_description: Reference counted strings * @include: appstream-glib.h * @stability: Unstable * * These functions are used to implement refcounted C strings. */ #include "config.h" #include #include "as-ref-string.h" typedef struct { volatile gint refcnt; } AsRefStringHeader; #define AS_REFPTR_TO_HEADER(o) ((AsRefStringHeader *) ((void *) ((guint8 *) o - sizeof (AsRefStringHeader)))) #define AS_REFPTR_FROM_HEADER(o) ((gpointer) (((guint8 *) o) + sizeof (AsRefStringHeader))) /* use the top bit for static mask */ #define AS_REFPTR_STATIC_MASK 0x80000000 #define AS_REFPTR_IS_STATIC(hdr) (hdr->refcnt & AS_REFPTR_STATIC_MASK) static GHashTable *as_ref_string_hash = NULL; static GMutex as_ref_string_mutex; /** * as_ref_string_debug_start: * * Starts collection of refcounted string data. * * Since: 0.7.9 */ void as_ref_string_debug_start (void) { g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&as_ref_string_mutex); if (as_ref_string_hash == NULL) as_ref_string_hash = g_hash_table_new (g_direct_hash, g_direct_equal); } /** * as_ref_string_debug_end: * * Ends collection of refcounted string data. * * Since: 0.7.9 */ void __attribute__ ((destructor)) as_ref_string_debug_end (void) { g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&as_ref_string_mutex); g_clear_pointer (&as_ref_string_hash, g_hash_table_unref); } /** * as_ref_string_new_static: * @str: a string * * Returns a refcounted string from a static string. The static string cannot * be unloaded and freed. * * Returns: a %AsRefString * * Since: 0.6.6 */ /** * as_ref_string_new_copy_with_length: * @str: a string * @len: length of @str, not including the NUL byte * * Returns a deep copied refcounted string. The returned string can be modified * without affecting other refcounted versions. * * This function is deprecated since 0.7.9. * * Returns: a %AsRefString * * Since: 0.6.6 */ AsRefString * as_ref_string_new_copy_with_length (const gchar *str, gsize len) { return as_ref_string_new_with_length (str, len); } /** * as_ref_string_new_copy: * @str: a string * * Returns a deep copied refcounted string. The returned string can be modified * without affecting other refcounted versions. * * This function is deprecated since 0.7.9. * * Returns: a %AsRefString * * Since: 0.6.6 */ AsRefString * as_ref_string_new_copy (const gchar *str) { g_return_val_if_fail (str != NULL, NULL); return as_ref_string_new_with_length (str, strlen (str)); } /** * as_ref_string_new_with_length: * @str: a string * @len: length of @str, not including the NUL byte * * Returns a immutable refcounted string. The returned string cannot be modified * without affecting other refcounted versions. * * Returns: a %AsRefString * * Since: 0.6.6 */ AsRefString * as_ref_string_new_with_length (const gchar *str, gsize len) { g_return_val_if_fail (str != NULL, NULL); AsRefStringHeader *hdr; AsRefString *rstr_new; /* create object */ hdr = g_malloc (len + sizeof (AsRefStringHeader) + 1); hdr->refcnt = 1; rstr_new = AS_REFPTR_FROM_HEADER (hdr); memcpy (rstr_new, str, len); rstr_new[len] = '\0'; /* for dedupe stats */ if (as_ref_string_hash != NULL) { g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&as_ref_string_mutex); g_hash_table_add (as_ref_string_hash, rstr_new); } /* return to data, not the header */ return rstr_new; } /** * as_ref_string_new: * @str: a string * * Returns a immutable refcounted string. The returned string cannot be modified * without affecting other refcounted versions. * * Returns: a %AsRefString * * Since: 0.6.6 */ AsRefString * as_ref_string_new (const gchar *str) { g_return_val_if_fail (str != NULL, NULL); return as_ref_string_new_with_length (str, strlen (str)); } /** * as_ref_string_ref: * @rstr: a #AsRefString * * Adds a reference to the string. * * Returns: the same %AsRefString * * Since: 0.6.6 */ AsRefString * as_ref_string_ref (AsRefString *rstr) { AsRefStringHeader *hdr; g_return_val_if_fail (rstr != NULL, NULL); hdr = AS_REFPTR_TO_HEADER (rstr); if (AS_REFPTR_IS_STATIC (hdr)) return rstr; g_atomic_int_inc (&hdr->refcnt); return rstr; } /** * as_ref_string_unref: * @rstr: a #AsRefString * * Removes a reference to the string. * * Returns: the same %AsRefString, or %NULL if the refcount dropped to zero * * Since: 0.6.6 */ AsRefString * as_ref_string_unref (AsRefString *rstr) { AsRefStringHeader *hdr; g_return_val_if_fail (rstr != NULL, NULL); hdr = AS_REFPTR_TO_HEADER (rstr); if (AS_REFPTR_IS_STATIC (hdr)) return rstr; if (g_atomic_int_dec_and_test (&hdr->refcnt)) { /* for dedupe stats */ if (as_ref_string_hash != NULL) { g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&as_ref_string_mutex); g_hash_table_remove (as_ref_string_hash, rstr); } g_free (hdr); return NULL; } return rstr; } /** * as_ref_string_assign: * @rstr_ptr: (out): a #AsRefString * @rstr: a #AsRefString * * This function unrefs and clears @rstr_ptr if set, then sets @rstr if * non-NULL. If @rstr and @rstr_ptr are the same string the action is ignored. * * This function can ONLY be used when @str is guaranteed to be a * refcounted string and is suitable for use when getting strings from * methods without a fixed API. * * This function is slightly faster than as_ref_string_assign_safe() as no * hash table lookup is done on the @rstr pointer. * * Since: 0.6.6 */ void as_ref_string_assign (AsRefString **rstr_ptr, AsRefString *rstr) { g_return_if_fail (rstr_ptr != NULL); if (*rstr_ptr == rstr) return; if (*rstr_ptr != NULL) { as_ref_string_unref (*rstr_ptr); *rstr_ptr = NULL; } if (rstr != NULL) *rstr_ptr = as_ref_string_ref (rstr); } /** * as_ref_string_assign_safe: * @rstr_ptr: (out): a #AsRefString * @str: a string, or a #AsRefString * * This function unrefs and clears @rstr_ptr if set, then sets @rstr if * non-NULL. If @rstr and @rstr_ptr are the same string the action is ignored. * * This function should be used when @str cannot be guaranteed to be a * refcounted string and is suitable for use in existing object setters. * * Since: 0.6.6 */ void as_ref_string_assign_safe (AsRefString **rstr_ptr, const gchar *str) { g_return_if_fail (rstr_ptr != NULL); if (*rstr_ptr != NULL) { as_ref_string_unref (*rstr_ptr); *rstr_ptr = NULL; } if (str != NULL) *rstr_ptr = as_ref_string_new (str); } static gint as_ref_string_sort_by_refcnt_cb (gconstpointer a, gconstpointer b) { AsRefStringHeader *hdr1 = AS_REFPTR_TO_HEADER (a); AsRefStringHeader *hdr2 = AS_REFPTR_TO_HEADER (b); if (hdr1->refcnt > hdr2->refcnt) return -1; if (hdr1->refcnt < hdr2->refcnt) return 1; return 0; } /** * as_ref_string_debug: * @flags: some #AsRefStringDebugFlags, e.g. %AS_REF_STRING_DEBUG_DUPES * * This function outputs some debugging information to a string. * * Returns: a string describing the current state of the dedupe hash. * * Since: 0.6.6 */ gchar * as_ref_string_debug (AsRefStringDebugFlags flags) { g_autoptr(GString) tmp = g_string_new (NULL); g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&as_ref_string_mutex); /* not yet enabled */ if (as_ref_string_hash == NULL) return NULL; /* overview */ g_string_append_printf (tmp, "Size of hash table: %u\n", g_hash_table_size (as_ref_string_hash)); /* success: deduped */ if (flags & AS_REF_STRING_DEBUG_DEDUPED) { GList *l; g_autoptr(GList) keys = g_hash_table_get_keys (as_ref_string_hash); /* split up sections */ if (tmp->len > 0) g_string_append (tmp, "\n\n"); /* sort by refcount number */ keys = g_list_sort (keys, as_ref_string_sort_by_refcnt_cb); g_string_append (tmp, "Deduplicated strings:\n"); for (l = keys; l != NULL; l = l->next) { const gchar *str = l->data; AsRefStringHeader *hdr = AS_REFPTR_TO_HEADER (str); if (AS_REFPTR_IS_STATIC (hdr)) continue; g_string_append_printf (tmp, "%i\t%s\n", hdr->refcnt, str); } } /* failed: duplicate */ if (flags & AS_REF_STRING_DEBUG_DUPES) { GList *l; GList *l2; g_autoptr(GHashTable) dupes = g_hash_table_new (g_direct_hash, g_direct_equal); g_autoptr(GList) keys = g_hash_table_get_keys (as_ref_string_hash); /* split up sections */ if (tmp->len > 0) g_string_append (tmp, "\n\n"); g_string_append (tmp, "Duplicated strings:\n"); for (l = keys; l != NULL; l = l->next) { const gchar *str = l->data; AsRefStringHeader *hdr = AS_REFPTR_TO_HEADER (str); guint dupe_cnt = 0; if (AS_REFPTR_IS_STATIC (hdr)) continue; if (g_hash_table_contains (dupes, hdr)) continue; g_hash_table_add (dupes, (gpointer) hdr); for (l2 = l; l2 != NULL; l2 = l2->next) { const gchar *str2 = l2->data; AsRefStringHeader *hdr2 = AS_REFPTR_TO_HEADER (str2); if (AS_REFPTR_IS_STATIC (hdr2)) continue; if (g_hash_table_contains (dupes, hdr2)) continue; if (l == l2) continue; if (g_strcmp0 (str, str2) != 0) continue; g_hash_table_add (dupes, (gpointer) hdr2); dupe_cnt += 1; } if (dupe_cnt > 1) { g_string_append_printf (tmp, "%u\t%s\n", dupe_cnt, str); } } } return g_string_free (g_steal_pointer(&tmp), FALSE); }