blob: 167c24a197e720201e79020f5a0cb5f91e6327f1 [file] [log] [blame]
/* grefstring.c: Reference counted strings
*
* Copyright 2018 Emmanuele Bassi
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "grefstring.h"
#include "ghash.h"
#include "gmessages.h"
#include "grcbox.h"
#include "gthread.h"
#include <string.h>
/* A global table of refcounted strings; the hash table does not own
* the strings, just a pointer to them. Strings are interned as long
* as they are alive; once their reference count drops to zero, they
* are removed from the table
*/
G_LOCK_DEFINE_STATIC (interned_ref_strings);
static GHashTable *interned_ref_strings;
/**
* g_ref_string_new:
* @str: (not nullable): a NUL-terminated string
*
* Creates a new reference counted string and copies the contents of @str
* into it.
*
* Returns: (transfer full) (not nullable): the newly created reference counted string
*
* Since: 2.58
*/
char *
g_ref_string_new (const char *str)
{
char *res;
gsize len;
g_return_val_if_fail (str != NULL, NULL);
len = strlen (str);
res = (char *) g_atomic_rc_box_dup (sizeof (char) * len + 1, str);
return res;
}
/**
* g_ref_string_new_len:
* @str: (not nullable): a string
* @len: length of @str to use, or -1 if @str is nul-terminated
*
* Creates a new reference counted string and copies the contents of @str
* into it, up to @len bytes.
*
* Since this function does not stop at nul bytes, it is the caller's
* responsibility to ensure that @str has at least @len addressable bytes.
*
* Returns: (transfer full) (not nullable): the newly created reference counted string
*
* Since: 2.58
*/
char *
g_ref_string_new_len (const char *str, gssize len)
{
char *res;
g_return_val_if_fail (str != NULL, NULL);
if (len < 0)
return g_ref_string_new (str);
/* allocate then copy as str[len] may not be readable */
res = (char *) g_atomic_rc_box_alloc ((gsize) len + 1);
memcpy (res, str, len);
res[len] = '\0';
return res;
}
/* interned_str_equal: variant of g_str_equal() that compares
* pointers as well as contents; this avoids running strcmp()
* on arbitrarily long strings, as it's more likely to have
* g_ref_string_new_intern() being called on the same refcounted
* string instance, than on a different string with the same
* contents
*/
static gboolean
interned_str_equal (gconstpointer v1,
gconstpointer v2)
{
const char *str1 = v1;
const char *str2 = v2;
if (v1 == v2)
return TRUE;
return strcmp (str1, str2) == 0;
}
/**
* g_ref_string_new_intern:
* @str: (not nullable): a NUL-terminated string
*
* Creates a new reference counted string and copies the content of @str
* into it.
*
* If you call this function multiple times with the same @str, or with
* the same contents of @str, it will return a new reference, instead of
* creating a new string.
*
* Returns: (transfer full) (not nullable): the newly created reference
* counted string, or a new reference to an existing string
*
* Since: 2.58
*/
char *
g_ref_string_new_intern (const char *str)
{
char *res;
g_return_val_if_fail (str != NULL, NULL);
G_LOCK (interned_ref_strings);
if (G_UNLIKELY (interned_ref_strings == NULL))
interned_ref_strings = g_hash_table_new (g_str_hash, interned_str_equal);
res = g_hash_table_lookup (interned_ref_strings, str);
if (res != NULL)
{
/* We acquire the reference while holding the lock, to
* avoid a potential race between releasing the lock on
* the hash table and another thread releasing the reference
* on the same string
*/
g_atomic_rc_box_acquire (res);
G_UNLOCK (interned_ref_strings);
return res;
}
res = g_ref_string_new (str);
g_hash_table_add (interned_ref_strings, res);
G_UNLOCK (interned_ref_strings);
return res;
}
/**
* g_ref_string_acquire:
* @str: a reference counted string
*
* Acquires a reference on a string.
*
* Returns: the given string, with its reference count increased
*
* Since: 2.58
*/
char *
g_ref_string_acquire (char *str)
{
g_return_val_if_fail (str != NULL, NULL);
return g_atomic_rc_box_acquire (str);
}
static void
remove_if_interned (gpointer data)
{
char *str = data;
G_LOCK (interned_ref_strings);
if (G_LIKELY (interned_ref_strings != NULL))
{
g_hash_table_remove (interned_ref_strings, str);
if (g_hash_table_size (interned_ref_strings) == 0)
g_clear_pointer (&interned_ref_strings, g_hash_table_destroy);
}
G_UNLOCK (interned_ref_strings);
}
/**
* g_ref_string_release:
* @str: a reference counted string
*
* Releases a reference on a string; if it was the last reference, the
* resources allocated by the string are freed as well.
*
* Since: 2.58
*/
void
g_ref_string_release (char *str)
{
g_return_if_fail (str != NULL);
g_atomic_rc_box_release_full (str, remove_if_interned);
}
/**
* g_ref_string_length:
* @str: a reference counted string
*
* Retrieves the length of @str.
*
* Returns: the length of the given string, in bytes
*
* Since: 2.58
*/
gsize
g_ref_string_length (char *str)
{
g_return_val_if_fail (str != NULL, 0);
return g_atomic_rc_box_get_size (str) - 1;
}