1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2 *
3 * Copyright (C) 2016 Richard Hughes <richard@hughsie.com>
4 *
5 * SPDX-License-Identifier: LGPL-2.1+
6 */
7
8 /**
9 * SECTION:as-ref-string
10 * @short_description: Reference counted strings
11 * @include: appstream-glib.h
12 * @stability: Unstable
13 *
14 * These functions are used to implement refcounted C strings.
15 */
16
17 #include "config.h"
18
19 #include <string.h>
20
21 #include "as-ref-string.h"
22
23 typedef struct {
24 volatile gint refcnt;
25 } AsRefStringHeader;
26
27 #define AS_REFPTR_TO_HEADER(o) ((AsRefStringHeader *) ((void *) ((guint8 *) o - sizeof (AsRefStringHeader))))
28 #define AS_REFPTR_FROM_HEADER(o) ((gpointer) (((guint8 *) o) + sizeof (AsRefStringHeader)))
29
30 /* use the top bit for static mask */
31 #define AS_REFPTR_STATIC_MASK 0x80000000
32 #define AS_REFPTR_IS_STATIC(hdr) (hdr->refcnt & AS_REFPTR_STATIC_MASK)
33
34 static GHashTable *as_ref_string_hash = NULL;
35 static GMutex as_ref_string_mutex;
36
37 /**
38 * as_ref_string_debug_start:
39 *
40 * Starts collection of refcounted string data.
41 *
42 * Since: 0.7.9
43 */
44 void
as_ref_string_debug_start(void)45 as_ref_string_debug_start (void)
46 {
47 g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&as_ref_string_mutex);
48 if (as_ref_string_hash == NULL)
49 as_ref_string_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
50 }
51
52 /**
53 * as_ref_string_debug_end:
54 *
55 * Ends collection of refcounted string data.
56 *
57 * Since: 0.7.9
58 */
59 void __attribute__ ((destructor))
as_ref_string_debug_end(void)60 as_ref_string_debug_end (void)
61 {
62 g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&as_ref_string_mutex);
63 g_clear_pointer (&as_ref_string_hash, g_hash_table_unref);
64 }
65
66 /**
67 * as_ref_string_new_static:
68 * @str: a string
69 *
70 * Returns a refcounted string from a static string. The static string cannot
71 * be unloaded and freed.
72 *
73 * Returns: a %AsRefString
74 *
75 * Since: 0.6.6
76 */
77
78 /**
79 * as_ref_string_new_copy_with_length:
80 * @str: a string
81 * @len: length of @str, not including the NUL byte
82 *
83 * Returns a deep copied refcounted string. The returned string can be modified
84 * without affecting other refcounted versions.
85 *
86 * This function is deprecated since 0.7.9.
87 *
88 * Returns: a %AsRefString
89 *
90 * Since: 0.6.6
91 */
92 AsRefString *
as_ref_string_new_copy_with_length(const gchar * str,gsize len)93 as_ref_string_new_copy_with_length (const gchar *str, gsize len)
94 {
95 return as_ref_string_new_with_length (str, len);
96 }
97
98 /**
99 * as_ref_string_new_copy:
100 * @str: a string
101 *
102 * Returns a deep copied refcounted string. The returned string can be modified
103 * without affecting other refcounted versions.
104 *
105 * This function is deprecated since 0.7.9.
106 *
107 * Returns: a %AsRefString
108 *
109 * Since: 0.6.6
110 */
111 AsRefString *
as_ref_string_new_copy(const gchar * str)112 as_ref_string_new_copy (const gchar *str)
113 {
114 g_return_val_if_fail (str != NULL, NULL);
115 return as_ref_string_new_with_length (str, strlen (str));
116 }
117
118 /**
119 * as_ref_string_new_with_length:
120 * @str: a string
121 * @len: length of @str, not including the NUL byte
122 *
123 * Returns a immutable refcounted string. The returned string cannot be modified
124 * without affecting other refcounted versions.
125 *
126 * Returns: a %AsRefString
127 *
128 * Since: 0.6.6
129 */
130 AsRefString *
as_ref_string_new_with_length(const gchar * str,gsize len)131 as_ref_string_new_with_length (const gchar *str, gsize len)
132 {
133 g_return_val_if_fail (str != NULL, NULL);
134 AsRefStringHeader *hdr;
135 AsRefString *rstr_new;
136
137 /* create object */
138 hdr = g_malloc (len + sizeof (AsRefStringHeader) + 1);
139 hdr->refcnt = 1;
140 rstr_new = AS_REFPTR_FROM_HEADER (hdr);
141 memcpy (rstr_new, str, len);
142 rstr_new[len] = '\0';
143
144 /* for dedupe stats */
145 if (as_ref_string_hash != NULL) {
146 g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&as_ref_string_mutex);
147 g_hash_table_add (as_ref_string_hash, rstr_new);
148 }
149
150 /* return to data, not the header */
151 return rstr_new;
152 }
153
154 /**
155 * as_ref_string_new:
156 * @str: a string
157 *
158 * Returns a immutable refcounted string. The returned string cannot be modified
159 * without affecting other refcounted versions.
160 *
161 * Returns: a %AsRefString
162 *
163 * Since: 0.6.6
164 */
165 AsRefString *
as_ref_string_new(const gchar * str)166 as_ref_string_new (const gchar *str)
167 {
168 g_return_val_if_fail (str != NULL, NULL);
169 return as_ref_string_new_with_length (str, strlen (str));
170 }
171
172 /**
173 * as_ref_string_ref:
174 * @rstr: a #AsRefString
175 *
176 * Adds a reference to the string.
177 *
178 * Returns: the same %AsRefString
179 *
180 * Since: 0.6.6
181 */
182 AsRefString *
as_ref_string_ref(AsRefString * rstr)183 as_ref_string_ref (AsRefString *rstr)
184 {
185 AsRefStringHeader *hdr;
186 g_return_val_if_fail (rstr != NULL, NULL);
187 hdr = AS_REFPTR_TO_HEADER (rstr);
188 if (AS_REFPTR_IS_STATIC (hdr))
189 return rstr;
190 g_atomic_int_inc (&hdr->refcnt);
191 return rstr;
192 }
193
194 /**
195 * as_ref_string_unref:
196 * @rstr: a #AsRefString
197 *
198 * Removes a reference to the string.
199 *
200 * Returns: the same %AsRefString, or %NULL if the refcount dropped to zero
201 *
202 * Since: 0.6.6
203 */
204 AsRefString *
as_ref_string_unref(AsRefString * rstr)205 as_ref_string_unref (AsRefString *rstr)
206 {
207 AsRefStringHeader *hdr;
208
209 g_return_val_if_fail (rstr != NULL, NULL);
210
211 hdr = AS_REFPTR_TO_HEADER (rstr);
212 if (AS_REFPTR_IS_STATIC (hdr))
213 return rstr;
214 if (g_atomic_int_dec_and_test (&hdr->refcnt)) {
215
216 /* for dedupe stats */
217 if (as_ref_string_hash != NULL) {
218 g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&as_ref_string_mutex);
219 g_hash_table_remove (as_ref_string_hash, rstr);
220 }
221
222 g_free (hdr);
223 return NULL;
224 }
225 return rstr;
226 }
227
228 /**
229 * as_ref_string_assign:
230 * @rstr_ptr: (out): a #AsRefString
231 * @rstr: a #AsRefString
232 *
233 * This function unrefs and clears @rstr_ptr if set, then sets @rstr if
234 * non-NULL. If @rstr and @rstr_ptr are the same string the action is ignored.
235 *
236 * This function can ONLY be used when @str is guaranteed to be a
237 * refcounted string and is suitable for use when getting strings from
238 * methods without a fixed API.
239 *
240 * This function is slightly faster than as_ref_string_assign_safe() as no
241 * hash table lookup is done on the @rstr pointer.
242 *
243 * Since: 0.6.6
244 */
245 void
as_ref_string_assign(AsRefString ** rstr_ptr,AsRefString * rstr)246 as_ref_string_assign (AsRefString **rstr_ptr, AsRefString *rstr)
247 {
248 g_return_if_fail (rstr_ptr != NULL);
249 if (*rstr_ptr == rstr)
250 return;
251 if (*rstr_ptr != NULL) {
252 as_ref_string_unref (*rstr_ptr);
253 *rstr_ptr = NULL;
254 }
255 if (rstr != NULL)
256 *rstr_ptr = as_ref_string_ref (rstr);
257 }
258
259 /**
260 * as_ref_string_assign_safe:
261 * @rstr_ptr: (out): a #AsRefString
262 * @str: a string, or a #AsRefString
263 *
264 * This function unrefs and clears @rstr_ptr if set, then sets @rstr if
265 * non-NULL. If @rstr and @rstr_ptr are the same string the action is ignored.
266 *
267 * This function should be used when @str cannot be guaranteed to be a
268 * refcounted string and is suitable for use in existing object setters.
269 *
270 * Since: 0.6.6
271 */
272 void
as_ref_string_assign_safe(AsRefString ** rstr_ptr,const gchar * str)273 as_ref_string_assign_safe (AsRefString **rstr_ptr, const gchar *str)
274 {
275 g_return_if_fail (rstr_ptr != NULL);
276 if (*rstr_ptr != NULL) {
277 as_ref_string_unref (*rstr_ptr);
278 *rstr_ptr = NULL;
279 }
280 if (str != NULL)
281 *rstr_ptr = as_ref_string_new (str);
282 }
283
284 static gint
as_ref_string_sort_by_refcnt_cb(gconstpointer a,gconstpointer b)285 as_ref_string_sort_by_refcnt_cb (gconstpointer a, gconstpointer b)
286 {
287 AsRefStringHeader *hdr1 = AS_REFPTR_TO_HEADER (a);
288 AsRefStringHeader *hdr2 = AS_REFPTR_TO_HEADER (b);
289 if (hdr1->refcnt > hdr2->refcnt)
290 return -1;
291 if (hdr1->refcnt < hdr2->refcnt)
292 return 1;
293 return 0;
294 }
295
296 /**
297 * as_ref_string_debug:
298 * @flags: some #AsRefStringDebugFlags, e.g. %AS_REF_STRING_DEBUG_DUPES
299 *
300 * This function outputs some debugging information to a string.
301 *
302 * Returns: a string describing the current state of the dedupe hash.
303 *
304 * Since: 0.6.6
305 */
306 gchar *
as_ref_string_debug(AsRefStringDebugFlags flags)307 as_ref_string_debug (AsRefStringDebugFlags flags)
308 {
309 GString *tmp = g_string_new (NULL);
310 g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&as_ref_string_mutex);
311
312 /* not yet enabled */
313 if (as_ref_string_hash == NULL)
314 return NULL;
315
316 /* overview */
317 g_string_append_printf (tmp, "Size of hash table: %u\n",
318 g_hash_table_size (as_ref_string_hash));
319
320 /* success: deduped */
321 if (flags & AS_REF_STRING_DEBUG_DEDUPED) {
322 GList *l;
323 g_autoptr(GList) keys = g_hash_table_get_keys (as_ref_string_hash);
324
325 /* split up sections */
326 if (tmp->len > 0)
327 g_string_append (tmp, "\n\n");
328
329 /* sort by refcount number */
330 keys = g_list_sort (keys, as_ref_string_sort_by_refcnt_cb);
331 g_string_append (tmp, "Deduplicated strings:\n");
332 for (l = keys; l != NULL; l = l->next) {
333 const gchar *str = l->data;
334 AsRefStringHeader *hdr = AS_REFPTR_TO_HEADER (str);
335 if (AS_REFPTR_IS_STATIC (hdr))
336 continue;
337 g_string_append_printf (tmp, "%i\t%s\n", hdr->refcnt, str);
338 }
339 }
340
341 /* failed: duplicate */
342 if (flags & AS_REF_STRING_DEBUG_DUPES) {
343 GList *l;
344 GList *l2;
345 g_autoptr(GHashTable) dupes = g_hash_table_new (g_direct_hash, g_direct_equal);
346 g_autoptr(GList) keys = g_hash_table_get_keys (as_ref_string_hash);
347
348 /* split up sections */
349 if (tmp->len > 0)
350 g_string_append (tmp, "\n\n");
351
352 g_string_append (tmp, "Duplicated strings:\n");
353 for (l = keys; l != NULL; l = l->next) {
354 const gchar *str = l->data;
355 AsRefStringHeader *hdr = AS_REFPTR_TO_HEADER (str);
356 guint dupe_cnt = 0;
357
358 if (AS_REFPTR_IS_STATIC (hdr))
359 continue;
360
361 if (g_hash_table_contains (dupes, hdr))
362 continue;
363 g_hash_table_add (dupes, (gpointer) hdr);
364
365 for (l2 = l; l2 != NULL; l2 = l2->next) {
366 const gchar *str2 = l2->data;
367 AsRefStringHeader *hdr2 = AS_REFPTR_TO_HEADER (str2);
368 if (AS_REFPTR_IS_STATIC (hdr2))
369 continue;
370 if (g_hash_table_contains (dupes, hdr2))
371 continue;
372 if (l == l2)
373 continue;
374 if (g_strcmp0 (str, str2) != 0)
375 continue;
376 g_hash_table_add (dupes, (gpointer) hdr2);
377 dupe_cnt += 1;
378 }
379 if (dupe_cnt > 1) {
380 g_string_append_printf (tmp, "%u\t%s\n",
381 dupe_cnt, str);
382 }
383 }
384 }
385 return g_string_free (tmp, FALSE);
386 }
387