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