gstrfuncs: Improve inline version of g_strdup() to avoid breaking C++ code

Wrap the logic into a G_ALWAYS_INLINE function, instead of using a
complex statement-expression which is not allowed in braced initializer
lists and expanded into some bad thing when it's used as
`::g_strdup(...)`.

We cannot use `__builtin_constant_p (str)` because GCC documentation
clearly states that it always produces 0 when str is a const char *
argument of an inline function.  But `__builtin_constant_p (!str)`,
`__builtin_constant_p (!!str)`, and
`__builtin_constant_p (strlen (str))` functions properly with `-O1` or
above enabled.

Fixes #2936.
This commit is contained in:
Xi Ruoyao 2023-03-13 16:23:37 +08:00
parent ed256d6447
commit cc7f2f81cc
No known key found for this signature in database
GPG Key ID: ACAAD20E19E710E3
2 changed files with 58 additions and 17 deletions

View File

@ -204,23 +204,6 @@ gboolean (g_str_has_prefix) (const gchar *str,
(g_str_has_suffix) (STR, SUFFIX) \
)
#define g_strdup(STR) \
(__builtin_constant_p ((STR)) ? \
(G_LIKELY ((STR) != NULL) ? \
G_GNUC_EXTENSION ({ \
const char *const ___str = ((STR)); \
const char *const __str = _G_STR_NONNULL (___str); \
const size_t __str_len = strlen (__str) + 1; \
char *__dup_str = (char *) g_malloc (__str_len); \
(char *) memcpy (__dup_str, __str, __str_len); \
}) \
: \
(char *) (NULL) \
) \
: \
(g_strdup) ((STR)) \
)
#endif /* !defined (__GI_SCANNER__) */
#endif /* !defined (__GTK_DOC_IGNORE__) */
#endif /* G_GNUC_CHECK_VERSION (2, 0) */
@ -318,6 +301,32 @@ GLIB_AVAILABLE_IN_ALL
gchar* g_strjoin (const gchar *separator,
...) G_GNUC_MALLOC G_GNUC_NULL_TERMINATED;
#if G_GNUC_CHECK_VERSION(2, 0)
#ifndef __GTK_DOC_IGNORE__
#ifndef __GI_SCANNER__
G_ALWAYS_INLINE static inline char *
g_strdup_inline (const char *str)
{
if (__builtin_constant_p (!str) && !str)
return NULL;
if (__builtin_constant_p (!!str) && !!str && __builtin_constant_p (strlen (str)))
{
const size_t len = strlen (str) + 1;
char *dup_str = (char *) g_malloc (len);
return (char *) memcpy (dup_str, str, len);
}
return g_strdup (str);
}
#define g_strdup(x) g_strdup_inline (x)
#endif /* !defined (__GI_SCANNER__) */
#endif /* !defined (__GTK_DOC_IGNORE__) */
#endif /* G_GNUC_CHECK_VERSION (2, 0) */
/* Make a copy of a string interpreting C string -style escape
* sequences. Inverse of g_strescape. The recognized sequences are \b
* \f \n \r \t \\ \" and the octal format.

View File

@ -349,6 +349,36 @@ test_strdup_macro (void)
g_free (str);
}
static void
test_strdup_macro_qualified (void)
{
gchar *str;
g_assert_null (::g_strdup (NULL));
str = ::g_strdup ("C++ is cool too!");
g_assert_nonnull (str);
g_assert_cmpstr (str, ==, "C++ is cool too!");
g_free (str);
}
static void
test_strdup_macro_nested_initializer (void)
{
struct
{
char *p, *q;
} strings = {
g_strdup (NULL),
g_strdup ("C++ is cool too!"),
};
g_assert_null (strings.p);
g_assert_nonnull (strings.q);
g_assert_cmpstr (strings.q, ==, "C++ is cool too!");
g_free (strings.q);
}
static void
test_str_has_prefix (void)
{
@ -527,6 +557,8 @@ main (int argc, char *argv[])
g_test_add_func ("/C++/str-equal", test_str_equal);
g_test_add_func ("/C++/strdup", test_strdup);
g_test_add_func ("/C++/strdup/macro", test_strdup_macro);
g_test_add_func ("/C++/strdup/macro/qualified", test_strdup_macro_qualified);
g_test_add_func ("/C++/strdup/macro/nested-initializer", test_strdup_macro_nested_initializer);
g_test_add_func ("/C++/str-has-prefix", test_str_has_prefix);
g_test_add_func ("/C++/str-has-prefix/macro", test_str_has_prefix_macro);
g_test_add_func ("/C++/str-has-suffix", test_str_has_suffix);