From 49ae9b490d12f79d51d04a27daed9a8e01c65758 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Thu, 29 Sep 2022 11:57:53 -0700 Subject: [PATCH] strfuncs: add g_set_str() This is like our other suite of g_set_*() based APIs to simplify and improve correctness of setters for fields, properties, and more. This implementation specifically handles setting string values that may point to an offset within the current string by copying before free. strcmp() is used directly (as opposed to g_strcmp0() due to it being in gtestutils.h as well as to increase the chance that the compiler will hoist the implementation. Fixes #2747 --- docs/reference/glib/glib-sections.txt.in | 1 + glib/gstrfuncs.h | 47 ++++++++++++++++++++++++ glib/tests/strfuncs.c | 28 ++++++++++++++ 3 files changed, 76 insertions(+) diff --git a/docs/reference/glib/glib-sections.txt.in b/docs/reference/glib/glib-sections.txt.in index 2b500257c..10d5029ff 100644 --- a/docs/reference/glib/glib-sections.txt.in +++ b/docs/reference/glib/glib-sections.txt.in @@ -1464,6 +1464,7 @@ utimbuf String Utility Functions string_utils glib.h,glib/gprintf.h +g_set_str g_strdup g_strndup g_strdupv diff --git a/glib/gstrfuncs.h b/glib/gstrfuncs.h index 37d2728d8..3c1dc45ec 100644 --- a/glib/gstrfuncs.h +++ b/glib/gstrfuncs.h @@ -32,9 +32,11 @@ #endif #include +#include #include #include #include +#include G_BEGIN_DECLS @@ -363,6 +365,51 @@ gboolean g_ascii_string_to_unsigned (const gchar *str, guint64 *out_num, GError **error); +/** + * g_set_str: (skip) + * @str_pointer: (inout) (not optional) (nullable): a pointer to either a string or %NULL + * @new_str: (nullable): a string to assign to @str_pointer, or %NULL + * + * Updates a pointer to a string to a copy of @new_str. The previous string + * pointed to by @str_pointer will be freed with g_free(). + * + * @str_pointer must not be %NULL, but can point to a %NULL value. + * + * One convenient usage of this function is in implementing property settings: + * |[ + * void + * foo_set_bar (Foo *foo, + * const char *new_bar) + * { + * g_return_if_fail (IS_FOO (foo)); + * + * if (g_set_str (&foo->bar, new_bar)) + * g_object_notify (foo, "bar"); + * } + * ]| + * + * Returns: %TRUE if the value of @str_pointer changed, %FALSE otherwise + * + * Since: 2.76 + */ +GLIB_AVAILABLE_STATIC_INLINE_IN_2_76 +static inline gboolean +g_set_str (char **str_pointer, + const char *new_str) +{ + char *copy; + + if (*str_pointer == new_str || + (*str_pointer && new_str && strcmp (*str_pointer, new_str) == 0)) + return FALSE; + + copy = g_strdup (new_str); + g_free (*str_pointer); + *str_pointer = copy; + + return TRUE; +} + G_END_DECLS #endif /* __G_STRFUNCS_H__ */ diff --git a/glib/tests/strfuncs.c b/glib/tests/strfuncs.c index 082eec074..0ae3f89c1 100644 --- a/glib/tests/strfuncs.c +++ b/glib/tests/strfuncs.c @@ -2546,6 +2546,33 @@ test_ascii_string_to_number_pathological (void) g_assert_cmpint (svalue, ==, G_MININT64); } +static void +test_set_str (void) +{ + char *str = NULL; + + g_assert_false (g_set_str (&str, NULL)); + g_assert_null (str); + + g_assert_true (g_set_str (&str, "")); + g_assert_false (g_set_str (&str, "")); + g_assert_nonnull (str); + g_assert_true ((gpointer)str != (gpointer)""); + g_assert_cmpstr (str, ==, ""); + + g_assert_true (g_set_str (&str, NULL)); + g_assert_null (str); + + g_assert_true (g_set_str (&str, "")); + g_assert_true (g_set_str (&str, "test")); + g_assert_cmpstr (str, ==, "test"); + + g_assert_true (g_set_str (&str, &str[2])); + g_assert_cmpstr (str, ==, "st"); + + g_free (str); +} + int main (int argc, char *argv[]) @@ -2563,6 +2590,7 @@ main (int argc, g_test_add_func ("/strfuncs/has-suffix", test_has_suffix); g_test_add_func ("/strfuncs/memdup", test_memdup); g_test_add_func ("/strfuncs/memdup2", test_memdup2); + g_test_add_func ("/strfuncs/set_str", test_set_str); g_test_add_func ("/strfuncs/stpcpy", test_stpcpy); g_test_add_func ("/strfuncs/str_match_string", test_str_match_string); g_test_add_func ("/strfuncs/str_tokenize_and_fold", test_str_tokenize_and_fold);