1 /* grefstring.c: Reference counted strings
2 *
3 * Copyright 2018 Emmanuele Bassi
4 *
5 * SPDX-License-Identifier: LGPL-2.1-or-later
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include "config.h"
22
23 #include "grefstring.h"
24
25 #include "ghash.h"
26 #include "gmessages.h"
27 #include "grcbox.h"
28 #include "gthread.h"
29
30 #include <string.h>
31
32 /* A global table of refcounted strings; the hash table does not own
33 * the strings, just a pointer to them. Strings are interned as long
34 * as they are alive; once their reference count drops to zero, they
35 * are removed from the table
36 */
37 G_LOCK_DEFINE_STATIC (interned_ref_strings);
38 static GHashTable *interned_ref_strings;
39
40 /**
41 * g_ref_string_new:
42 * @str: (not nullable): a NUL-terminated string
43 *
44 * Creates a new reference counted string and copies the contents of @str
45 * into it.
46 *
47 * Returns: (transfer full) (not nullable): the newly created reference counted string
48 *
49 * Since: 2.58
50 */
51 char *
52 g_ref_string_new (const char *str)
53 {
54 char *res;
55 gsize len;
56
57 g_return_val_if_fail (str != NULL, NULL);
58
59 len = strlen (str);
60
61 res = (char *) g_atomic_rc_box_dup (sizeof (char) * len + 1, str);
62
63 return res;
64 }
65
66 /**
67 * g_ref_string_new_len:
68 * @str: (not nullable): a string
69 * @len: length of @str to use, or -1 if @str is nul-terminated
70 *
71 * Creates a new reference counted string and copies the contents of @str
72 * into it, up to @len bytes.
73 *
74 * Since this function does not stop at nul bytes, it is the caller's
75 * responsibility to ensure that @str has at least @len addressable bytes.
76 *
77 * Returns: (transfer full) (not nullable): the newly created reference counted string
78 *
79 * Since: 2.58
80 */
81 char *
82 g_ref_string_new_len (const char *str, gssize len)
83 {
84 char *res;
85
86 g_return_val_if_fail (str != NULL, NULL);
87
88 if (len < 0)
89 return g_ref_string_new (str);
90
91 /* allocate then copy as str[len] may not be readable */
92 res = (char *) g_atomic_rc_box_alloc ((gsize) len + 1);
93 memcpy (res, str, len);
94 res[len] = '\0';
95
96 return res;
97 }
98
99 /* interned_str_equal: variant of g_str_equal() that compares
100 * pointers as well as contents; this avoids running strcmp()
101 * on arbitrarily long strings, as it's more likely to have
102 * g_ref_string_new_intern() being called on the same refcounted
103 * string instance, than on a different string with the same
104 * contents
105 */
106 static gboolean
107 interned_str_equal (gconstpointer v1,
108 gconstpointer v2)
109 {
110 const char *str1 = v1;
111 const char *str2 = v2;
112
113 if (v1 == v2)
114 return TRUE;
115
116 return strcmp (str1, str2) == 0;
117 }
118
119 /**
120 * g_ref_string_new_intern:
121 * @str: (not nullable): a NUL-terminated string
122 *
123 * Creates a new reference counted string and copies the content of @str
124 * into it.
125 *
126 * If you call this function multiple times with the same @str, or with
127 * the same contents of @str, it will return a new reference, instead of
128 * creating a new string.
129 *
130 * Returns: (transfer full) (not nullable): the newly created reference
131 * counted string, or a new reference to an existing string
132 *
133 * Since: 2.58
134 */
135 char *
136 g_ref_string_new_intern (const char *str)
137 {
138 char *res;
139
140 g_return_val_if_fail (str != NULL, NULL);
141
142 G_LOCK (interned_ref_strings);
143
144 if (G_UNLIKELY (interned_ref_strings == NULL))
145 interned_ref_strings = g_hash_table_new (g_str_hash, interned_str_equal);
146
147 res = g_hash_table_lookup (interned_ref_strings, str);
148 if (res != NULL)
149 {
150 /* We acquire the reference while holding the lock, to
151 * avoid a potential race between releasing the lock on
152 * the hash table and another thread releasing the reference
153 * on the same string
154 */
155 g_atomic_rc_box_acquire (res);
156 G_UNLOCK (interned_ref_strings);
157 return res;
158 }
159
160 res = g_ref_string_new (str);
161 g_hash_table_add (interned_ref_strings, res);
162 G_UNLOCK (interned_ref_strings);
163
164 return res;
165 }
166
167 /**
168 * g_ref_string_acquire:
169 * @str: a reference counted string
170 *
171 * Acquires a reference on a string.
172 *
173 * Returns: the given string, with its reference count increased
174 *
175 * Since: 2.58
176 */
177 char *
178 g_ref_string_acquire (char *str)
179 {
180 g_return_val_if_fail (str != NULL, NULL);
181
182 return g_atomic_rc_box_acquire (str);
183 }
184
185 static void
186 remove_if_interned (gpointer data)
187 {
188 char *str = data;
189
190 G_LOCK (interned_ref_strings);
191
192 if (G_LIKELY (interned_ref_strings != NULL))
193 {
194 g_hash_table_remove (interned_ref_strings, str);
195
196 if (g_hash_table_size (interned_ref_strings) == 0)
197 g_clear_pointer (&interned_ref_strings, g_hash_table_destroy);
198 }
199
200 G_UNLOCK (interned_ref_strings);
201 }
202
203 /**
204 * g_ref_string_release:
205 * @str: a reference counted string
206 *
207 * Releases a reference on a string; if it was the last reference, the
208 * resources allocated by the string are freed as well.
209 *
210 * Since: 2.58
211 */
212 void
213 g_ref_string_release (char *str)
214 {
215 g_return_if_fail (str != NULL);
216
217 g_atomic_rc_box_release_full (str, remove_if_interned);
218 }
219
220 /**
221 * g_ref_string_length:
222 * @str: a reference counted string
223 *
224 * Retrieves the length of @str.
225 *
226 * Returns: the length of the given string, in bytes
227 *
228 * Since: 2.58
229 */
230 gsize
231 g_ref_string_length (char *str)
232 {
233 g_return_val_if_fail (str != NULL, 0);
234
235 return g_atomic_rc_box_get_size (str) - 1;
236 }