glib/glib/tests/error.c
Philip Withnall c4203f740c gerror: Emit a critical warning if the message format is NULL
The code has warned about this since commit 6d9f874330 in 2011.

glibc 2.37 has just started asserting if a `NULL` format is passed to
`sprintf()`, which caused the existing GLib workaround to start
asserting too.

Bite the bullet and upgrade the warning for `format != NULL` to a
critical warning. Projects have had 12 years to fix their code.

The original bug reports
(https://bugzilla.gnome.org/show_bug.cgi?id=660371,
https://bugzilla.gnome.org/show_bug.cgi?id=560482) are actually both
about a domain which is `0`, rather than a format which is `NULL`, which
gives some confidence that this change will actually impact very little
third party code.

Since it doesn’t currently need changing, I have not touched the warning
about `domain != 0`.

Signed-off-by: Philip Withnall <pwithnall@endlessos.org>

Fixes: #2913
2023-02-20 16:41:23 +00:00

418 lines
12 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include <glib.h>
#include "glib-private.h"
static void
test_overwrite (void)
{
GError *error, *dest, *src;
if (!g_test_undefined ())
return;
error = g_error_new_literal (G_MARKUP_ERROR, G_MARKUP_ERROR_EMPTY, "bla");
g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING,
"*set over the top*");
g_set_error_literal (&error, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, "bla");
g_test_assert_expected_messages ();
g_assert_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_EMPTY);
g_error_free (error);
error = g_error_new_literal (G_MARKUP_ERROR, G_MARKUP_ERROR_EMPTY, "bla");
g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING,
"*set over the top*");
g_set_error (&error, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, "bla");
g_test_assert_expected_messages ();
g_assert_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_EMPTY);
g_error_free (error);
dest = g_error_new_literal (G_MARKUP_ERROR, G_MARKUP_ERROR_EMPTY, "bla");
src = g_error_new_literal (G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, "bla");
g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING,
"*set over the top*");
g_propagate_error (&dest, src);
g_test_assert_expected_messages ();
g_assert_error (dest, G_MARKUP_ERROR, G_MARKUP_ERROR_EMPTY);
g_error_free (dest);
}
static void
test_prefix (void)
{
GError *error;
GError *dest, *src;
error = NULL;
g_prefix_error (&error, "foo %d %s: ", 1, "two");
g_assert (error == NULL);
error = g_error_new_literal (G_MARKUP_ERROR, G_MARKUP_ERROR_EMPTY, "bla");
g_prefix_error (&error, "foo %d %s: ", 1, "two");
g_assert_cmpstr (error->message, ==, "foo 1 two: bla");
g_error_free (error);
dest = NULL;
src = g_error_new_literal (G_MARKUP_ERROR, G_MARKUP_ERROR_EMPTY, "bla");
g_propagate_prefixed_error (&dest, src, "foo %d %s: ", 1, "two");
g_assert_cmpstr (dest->message, ==, "foo 1 two: bla");
g_error_free (dest);
src = g_error_new_literal (G_MARKUP_ERROR, G_MARKUP_ERROR_EMPTY, "bla");
g_propagate_prefixed_error (NULL, src, "foo %d %s: ", 1, "two");
}
static void
test_prefix_literal (void)
{
GError *error = NULL;
g_prefix_error_literal (NULL, "foo: ");
g_prefix_error_literal (&error, "foo: ");
g_assert_null (error);
error = NULL;
g_prefix_error_literal (&error, "foo: ");
g_assert_null (error);
error = g_error_new_literal (G_MARKUP_ERROR, G_MARKUP_ERROR_EMPTY, "bla");
g_assert_nonnull (error);
g_prefix_error_literal (&error, "foo: ");
g_assert_cmpstr (error->message, ==, "foo: bla");
g_error_free (error);
}
static void
test_literal (void)
{
GError *error;
error = NULL;
g_set_error_literal (&error, G_MARKUP_ERROR, G_MARKUP_ERROR_EMPTY, "%s %d %x");
g_assert_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_EMPTY);
g_assert_cmpstr (error->message, ==, "%s %d %x");
g_error_free (error);
}
static void
test_copy (void)
{
GError *error;
GError *copy;
error = NULL;
g_set_error_literal (&error, G_MARKUP_ERROR, G_MARKUP_ERROR_EMPTY, "%s %d %x");
copy = g_error_copy (error);
g_assert_error (copy, G_MARKUP_ERROR, G_MARKUP_ERROR_EMPTY);
g_assert_cmpstr (copy->message, ==, "%s %d %x");
g_error_free (error);
g_error_free (copy);
}
static void
test_new_valist_invalid_va (gpointer dummy,
...)
{
#if defined(__linux__) && defined(__GLIBC__)
/* Only worth testing this on Linux with glibc; if other platforms regress on
* this legacy behaviour, we dont care. In particular, calling
* g_error_new_valist() with a %NULL format will crash on FreeBSD as its
* implementation of vasprintf() is less forgiving than Linuxs. Thats
* fine: its a programmer error in either case. */
g_test_summary ("Test that g_error_new_valist() rejects invalid input");
if (!g_test_undefined ())
{
g_test_skip ("Not testing response to programmer error");
return;
}
{
GError *error = NULL;
va_list ap;
va_start (ap, dummy);
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
#pragma GCC diagnostic ignored "-Wformat-extra-args"
g_test_expect_message (G_LOG_DOMAIN,
G_LOG_LEVEL_CRITICAL,
"*g_error_new_valist: assertion 'format != NULL' failed*");
error = g_error_new_valist (G_MARKUP_ERROR, G_MARKUP_ERROR_EMPTY, NULL, ap);
g_test_assert_expected_messages ();
g_assert_null (error);
#pragma GCC diagnostic pop
va_end (ap);
}
{
GError *error = NULL, *error_copy = NULL;
va_list ap;
va_start (ap, dummy);
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
g_test_expect_message (G_LOG_DOMAIN,
G_LOG_LEVEL_WARNING,
"*g_error_new_valist: runtime check failed*");
error = g_error_new_valist (0, G_MARKUP_ERROR_EMPTY, "Message", ap);
g_test_assert_expected_messages ();
g_assert_nonnull (error);
#pragma GCC diagnostic pop
g_test_expect_message (G_LOG_DOMAIN,
G_LOG_LEVEL_WARNING,
"*g_error_copy: runtime check failed*");
error_copy = g_error_copy (error);
g_test_assert_expected_messages ();
g_assert_nonnull (error_copy);
g_clear_error (&error);
g_clear_error (&error_copy);
va_end (ap);
}
#else /* if !__linux__ || !__GLIBC__ */
g_test_skip ("g_error_new_valist() programmer error handling is only relevant on Linux with glibc");
#endif /* !__linux__ || ! __GLIBC__ */
}
static void
test_new_valist_invalid (void)
{
/* We need a wrapper function so we can build a va_list */
test_new_valist_invalid_va (NULL);
}
static void
test_matches (void)
{
GError *error = NULL;
g_test_summary ("Test g_error_matches()");
error = g_error_new (G_MARKUP_ERROR, G_MARKUP_ERROR_EMPTY, "Oh no!");
g_assert_true (g_error_matches (error, G_MARKUP_ERROR, G_MARKUP_ERROR_EMPTY));
g_assert_false (g_error_matches (NULL, G_MARKUP_ERROR, G_MARKUP_ERROR_EMPTY));
g_assert_false (g_error_matches (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE)); /* same numeric value as G_MARKUP_ERROR_EMPTY */
g_assert_false (g_error_matches (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED)); /* different numeric value from G_MARKUP_ERROR_EMPTY */
g_assert_false (g_error_matches (error, G_MARKUP_ERROR, G_MARKUP_ERROR_BAD_UTF8));
g_error_free (error);
}
static void
test_clear (void)
{
GError *error = NULL;
g_test_summary ("Test g_error_clear()");
g_clear_error (&error);
g_assert_null (error);
g_clear_error (NULL);
error = g_error_new (G_MARKUP_ERROR, G_MARKUP_ERROR_EMPTY, "Oh no!");
g_clear_error (&error);
g_assert_null (error);
}
typedef struct
{
int init_called;
int copy_called;
int free_called;
} TestErrorCheck;
static TestErrorCheck *init_check;
GQuark test_error_quark (void);
#define TEST_ERROR (test_error_quark ())
typedef struct
{
int foo;
TestErrorCheck *check;
} TestErrorPrivate;
static void
test_error_private_init (TestErrorPrivate *priv)
{
priv->foo = 13;
/* If that triggers, it's test bug.
*/
g_assert_nonnull (init_check);
/* Using global init_check, because error->check is still nil at
* this point.
*/
init_check->init_called++;
}
static void
test_error_private_copy (const TestErrorPrivate *src_priv,
TestErrorPrivate *dest_priv)
{
dest_priv->foo = src_priv->foo;
dest_priv->check = src_priv->check;
dest_priv->check->copy_called++;
}
static void
test_error_private_clear (TestErrorPrivate *priv)
{
priv->check->free_called++;
}
G_DEFINE_EXTENDED_ERROR (TestError, test_error)
static TestErrorPrivate *
fill_test_error (GError *error, TestErrorCheck *check)
{
TestErrorPrivate *test_error = test_error_get_private (error);
test_error->check = check;
return test_error;
}
static void
test_extended (void)
{
TestErrorCheck check = { 0, 0, 0 };
GError *error;
TestErrorPrivate *test_priv;
GError *copy_error;
TestErrorPrivate *copy_test_priv;
init_check = &check;
error = g_error_new_literal (TEST_ERROR, 0, "foo");
test_priv = fill_test_error (error, &check);
g_assert_cmpint (check.init_called, ==, 1);
g_assert_cmpint (check.copy_called, ==, 0);
g_assert_cmpint (check.free_called, ==, 0);
g_assert_cmpuint (error->domain, ==, TEST_ERROR);
g_assert_cmpint (test_priv->foo, ==, 13);
copy_error = g_error_copy (error);
g_assert_cmpint (check.init_called, ==, 2);
g_assert_cmpint (check.copy_called, ==, 1);
g_assert_cmpint (check.free_called, ==, 0);
g_assert_cmpuint (error->domain, ==, copy_error->domain);
g_assert_cmpint (error->code, ==, copy_error->code);
g_assert_cmpstr (error->message, ==, copy_error->message);
copy_test_priv = test_error_get_private (copy_error);
g_assert_cmpint (test_priv->foo, ==, copy_test_priv->foo);
g_error_free (error);
g_error_free (copy_error);
g_assert_cmpint (check.init_called, ==, 2);
g_assert_cmpint (check.copy_called, ==, 1);
g_assert_cmpint (check.free_called, ==, 2);
}
static void
test_extended_duplicate (void)
{
g_test_summary ("Test that registering a duplicate extended error domain doesnt work");
if (!g_test_subprocess ())
{
/* Spawn a subprocess and expect it to fail. */
g_test_trap_subprocess (NULL, 0, G_TEST_SUBPROCESS_DEFAULT);
g_test_trap_assert_failed ();
g_test_trap_assert_stderr ("*CRITICAL*Attempted to register an extended error domain for TestError more than once*");
}
else
{
GQuark q;
guint i;
for (i = 0; i < 2; i++)
{
q = g_error_domain_register_static ("TestError",
sizeof (TestErrorPrivate),
g_error_with_test_error_private_init,
g_error_with_test_error_private_copy,
g_error_with_test_error_private_clear);
g_assert_cmpstr (g_quark_to_string (q), ==, "TestError");
}
}
}
typedef struct
{
int dummy;
} TestErrorNonStaticPrivate;
static void test_error_non_static_private_init (GError *error) {}
static void test_error_non_static_private_copy (const GError *src_error, GError *dest_error) {}
static void test_error_non_static_private_clear (GError *error) {}
static void
test_extended_non_static (void)
{
gchar *domain_name = g_strdup ("TestErrorNonStatic");
GQuark q;
GError *error = NULL;
g_test_summary ("Test registering an extended error domain with a non-static name");
q = g_error_domain_register (domain_name,
sizeof (TestErrorNonStaticPrivate),
test_error_non_static_private_init,
test_error_non_static_private_copy,
test_error_non_static_private_clear);
g_free (domain_name);
error = g_error_new (q, 0, "Test error: %s", "hello");
g_assert_true (g_error_matches (error, q, 0));
g_assert_cmpstr (g_quark_to_string (q), ==, "TestErrorNonStatic");
g_error_free (error);
}
int
main (int argc, char *argv[])
{
g_test_init (&argc, &argv, NULL);
g_test_add_func ("/error/overwrite", test_overwrite);
g_test_add_func ("/error/prefix", test_prefix);
g_test_add_func ("/error/prefix-literal", test_prefix_literal);
g_test_add_func ("/error/literal", test_literal);
g_test_add_func ("/error/copy", test_copy);
g_test_add_func ("/error/matches", test_matches);
g_test_add_func ("/error/clear", test_clear);
g_test_add_func ("/error/new-valist/invalid", test_new_valist_invalid);
g_test_add_func ("/error/extended", test_extended);
g_test_add_func ("/error/extended/duplicate", test_extended_duplicate);
g_test_add_func ("/error/extended/non-static", test_extended_non_static);
return g_test_run ();
}