gerror: Add support for extended errors

This commit adds a G_DEFINE_EXTENDED_ERROR macro and
g_error_domain_register() functions to register extended error
domains.
This commit is contained in:
Krzesimir Nowak 2019-12-25 13:07:32 +01:00 committed by Philip Withnall
parent b715e4c9d0
commit ae72f9de35
6 changed files with 595 additions and 10 deletions

View File

@ -754,6 +754,13 @@ g_propagate_error
g_clear_error
g_prefix_error
g_propagate_prefixed_error
<SUBSECTION>
GErrorInitFunc
GErrorCopyFunc
GErrorClearFunc
G_DEFINE_EXTENDED_ERROR
g_error_domain_register_static
g_error_domain_register
</SECTION>
<SECTION>

View File

@ -372,27 +372,338 @@
* to add a check at the top of your function that the error return
* location is either %NULL or contains a %NULL error (e.g.
* `g_return_if_fail (error == NULL || *error == NULL);`).
*
* Since GLib 2.68 it is possible to extend the #GError type. This is
* done with the G_DEFINE_EXTENDED_ERROR() macro. To create an
* extended #GError type do something like this in the header file:
* |[<!-- language="C" -->
* typedef enum
* {
* MY_ERROR_BAD_REQUEST,
* } MyError;
* #define MY_ERROR (my_error_quark ())
* GQuark my_error_quark (void);
* int
* my_error_get_parse_error_id (GError *error);
* const char *
* my_error_get_bad_request_details (GError *error);
* ]|
* and in implementation:
* |[<!-- language="C" -->
* typedef struct
* {
* int parse_error_id;
* char *bad_request_details;
* } MyErrorPrivate;
*
* static void
* my_error_private_init (MyErrorPrivate *priv)
* {
* priv->parse_error_id = -1;
* // No need to set priv->bad_request_details to NULL,
* // the struct is initialized with zeros.
* }
*
* static void
* my_error_private_copy (const MyErrorPrivate *src_priv, MyErrorPrivate *dest_priv)
* {
* dest_priv->parse_error_id = src_priv->parse_error_id;
* dest_priv->bad_request_details = g_strdup (src_priv->bad_request_details);
* }
*
* static void
* my_error_private_clear (MyErrorPrivate *priv)
* {
* g_free (priv->bad_request_details);
* }
*
* // This defines the my_error_get_private and my_error_quark functions.
* G_DEFINE_EXTENDED_ERROR (MyError, my_error)
*
* int
* my_error_get_parse_error_id (GError *error)
* {
* MyErrorPrivate *priv = my_error_get_private (error);
* g_return_val_if_fail (priv != NULL, -1);
* return priv->parse_error_id;
* }
*
* const char *
* my_error_get_bad_request_details (GError *error)
* {
* MyErrorPrivate *priv = my_error_get_private (error);
* g_return_val_if_fail (priv != NULL, NULL);
* g_return_val_if_fail (error->code != MY_ERROR_BAD_REQUEST, NULL);
* return priv->bad_request_details;
* }
*
* static void
* my_error_set_bad_request (GError **error,
* const char *reason,
* int error_id,
* const char *details)
* {
* MyErrorPrivate *priv;
* g_set_error (error, MY_ERROR, MY_ERROR_BAD_REQUEST, "Invalid request: %s", reason);
* if (error != NULL && *error != NULL)
* {
* priv = my_error_get_private (error);
* g_return_val_if_fail (priv != NULL, NULL);
* priv->parse_error_id = error_id;
* priv->bad_request_details = g_strdup (details);
* }
* }
* ]|
* An example of use of the error could be:
* |[<!-- language="C" -->
* gboolean
* send_request (GBytes *request, GError **error)
* {
* ParseFailedStatus *failure = validate_request (request);
* if (failure != NULL)
* {
* my_error_set_bad_request (error, failure->reason, failure->error_id, failure->details);
* parse_failed_status_free (failure);
* return FALSE;
* }
*
* return send_one (request, error);
* }
* ]|
*
* Please note that if you are a library author and your library
* exposes an existing error domain, then you can't make this error
* domain an extended one without breaking ABI. This is because
* earlier it was possible to create an error with this error domain
* on the stack and then copy it with g_error_copy(). If the new
* version of your library makes the error domain an extended one,
* then g_error_copy() called by code that allocated the error on the
* stack will try to copy more data than it used to, which will lead
* to undefined behavior. You must not stack-allocate errors with an
* extended error domain, and it is bad practice to stack-allocate any
* other #GErrors.
*
* Extended error domains in unloadable plugins/modules are not
* supported.
*/
#include "config.h"
#include <string.h>
#include "gerror.h"
#include "ghash.h"
#include "glib-init.h"
#include "gslice.h"
#include "gstrfuncs.h"
#include "gtestutils.h"
#include "gthread.h"
static GRWLock error_domain_global;
/* error_domain_ht must be accessed with error_domain_global
* locked.
*/
static GHashTable *error_domain_ht = NULL;
void
g_error_init (void)
{
error_domain_ht = g_hash_table_new (NULL, NULL);
}
typedef struct
{
/* private_size is already aligned. */
gsize private_size;
GErrorInitFunc init;
GErrorCopyFunc copy;
GErrorClearFunc clear;
} ErrorDomainInfo;
/* Must be called with error_domain_global locked.
*/
static inline ErrorDomainInfo *
error_domain_lookup (GQuark domain)
{
return g_hash_table_lookup (error_domain_ht,
GUINT_TO_POINTER (domain));
}
/* Copied from gtype.c. */
#define STRUCT_ALIGNMENT (2 * sizeof (gsize))
#define ALIGN_STRUCT(offset) \
((offset + (STRUCT_ALIGNMENT - 1)) & -STRUCT_ALIGNMENT)
static void
error_domain_register (GQuark error_quark,
gsize error_type_private_size,
GErrorInitFunc error_type_init,
GErrorCopyFunc error_type_copy,
GErrorClearFunc error_type_clear)
{
g_rw_lock_writer_lock (&error_domain_global);
if (error_domain_lookup (error_quark) == NULL)
{
ErrorDomainInfo *info = g_new (ErrorDomainInfo, 1);
info->private_size = ALIGN_STRUCT (error_type_private_size);
info->init = error_type_init;
info->copy = error_type_copy;
info->clear = error_type_clear;
g_hash_table_insert (error_domain_ht,
GUINT_TO_POINTER (error_quark),
info);
}
else
{
const char *name = g_quark_to_string (error_quark);
g_critical ("Attempted to register an extended error domain for %s more than once", name);
}
g_rw_lock_writer_unlock (&error_domain_global);
}
/**
* g_error_domain_register_static:
* @error_type_name: static string to create a #GQuark from
* @error_type_private_size: size of the private error data in bytes
* @error_type_init: function initializing fields of the private error data
* @error_type_copy: function copying fields of the private error data
* @error_type_clear: function freeing fields of the private error data
*
* This function registers an extended #GError domain.
*
* @error_type_name should not be freed. @error_type_private_size must
* be greater than 0.
*
* @error_type_init receives an initialized #GError and should then initialize
* the private data.
*
* @error_type_copy is a function that receives both original and a copy
* #GError and should copy the fields of the private error data. The standard
* #GError fields are already handled.
*
* @error_type_clear receives the pointer to the error, and it should free the
* fields of the private error data. It should not free the struct itself though.
*
* Normally, it is better to use G_DEFINE_EXTENDED_ERROR(), as it
* already takes care of passing valid information to this function.
*
* Returns: #GQuark representing the error domain
* Since: 2.68
*/
GQuark
g_error_domain_register_static (const char *error_type_name,
gsize error_type_private_size,
GErrorInitFunc error_type_init,
GErrorCopyFunc error_type_copy,
GErrorClearFunc error_type_clear)
{
GQuark error_quark;
g_return_val_if_fail (error_type_name != NULL, 0);
g_return_val_if_fail (error_type_private_size > 0, 0);
g_return_val_if_fail (error_type_init != NULL, 0);
g_return_val_if_fail (error_type_copy != NULL, 0);
g_return_val_if_fail (error_type_clear != NULL, 0);
error_quark = g_quark_from_static_string (error_type_name);
error_domain_register (error_quark,
error_type_private_size,
error_type_init,
error_type_copy,
error_type_clear);
return error_quark;
}
/**
* g_error_domain_register:
* @error_type_name: string to create a #GQuark from
* @error_type_private_size: size of the private error data in bytes
* @error_type_init: function initializing fields of the private error data
* @error_type_copy: function copying fields of the private error data
* @error_type_clear: function freeing fields of the private error data
*
* This function registers an extended #GError domain.
* @error_type_name will be duplicated. Otherwise does the same as
* g_error_domain_register_static().
*
* Returns: #GQuark representing the error domain
* Since: 2.68
*/
GQuark
g_error_domain_register (const char *error_type_name,
gsize error_type_private_size,
GErrorInitFunc error_type_init,
GErrorCopyFunc error_type_copy,
GErrorClearFunc error_type_clear)
{
GQuark error_quark;
g_return_val_if_fail (error_type_name != NULL, 0);
g_return_val_if_fail (error_type_private_size > 0, 0);
g_return_val_if_fail (error_type_init != NULL, 0);
g_return_val_if_fail (error_type_copy != NULL, 0);
g_return_val_if_fail (error_type_clear != NULL, 0);
error_quark = g_quark_from_string (error_type_name);
error_domain_register (error_quark,
error_type_private_size,
error_type_init,
error_type_copy,
error_type_clear);
return error_quark;
}
static GError *
g_error_new_steal (GQuark domain,
gint code,
gchar *message)
g_error_allocate (GQuark domain, ErrorDomainInfo *out_info)
{
GError *error = g_slice_new (GError);
guint8 *allocated;
GError *error;
ErrorDomainInfo *info;
gsize private_size;
g_rw_lock_reader_lock (&error_domain_global);
info = error_domain_lookup (domain);
if (info != NULL)
{
if (out_info != NULL)
*out_info = *info;
private_size = info->private_size;
g_rw_lock_reader_unlock (&error_domain_global);
}
else
{
g_rw_lock_reader_unlock (&error_domain_global);
if (out_info != NULL)
memset (out_info, 0, sizeof (*out_info));
private_size = 0;
}
allocated = g_slice_alloc0 (private_size + sizeof (GError));
error = (GError *) (allocated + private_size);
return error;
}
/* This function takes ownership of @message. */
static GError *
g_error_new_steal (GQuark domain,
gint code,
gchar *message,
ErrorDomainInfo *out_info)
{
ErrorDomainInfo info;
GError *error = g_error_allocate (domain, &info);
error->domain = domain;
error->code = code;
error->message = message;
if (info.init != NULL)
info.init (error);
if (out_info != NULL)
*out_info = info;
return error;
}
@ -424,7 +735,7 @@ g_error_new_valist (GQuark domain,
g_warn_if_fail (domain != 0);
g_warn_if_fail (format != NULL);
return g_error_new_steal (domain, code, g_strdup_vprintf (format, args));
return g_error_new_steal (domain, code, g_strdup_vprintf (format, args), NULL);
}
/**
@ -479,7 +790,7 @@ g_error_new_literal (GQuark domain,
g_return_val_if_fail (message != NULL, NULL);
g_return_val_if_fail (domain != 0, NULL);
return g_error_new_steal (domain, code, g_strdup (message));
return g_error_new_steal (domain, code, g_strdup (message), NULL);
}
/**
@ -491,11 +802,31 @@ g_error_new_literal (GQuark domain,
void
g_error_free (GError *error)
{
gsize private_size;
ErrorDomainInfo *info;
guint8 *allocated;
g_return_if_fail (error != NULL);
g_free (error->message);
g_rw_lock_reader_lock (&error_domain_global);
info = error_domain_lookup (error->domain);
if (info != NULL)
{
GErrorClearFunc clear = info->clear;
g_slice_free (GError, error);
private_size = info->private_size;
g_rw_lock_reader_unlock (&error_domain_global);
clear (error);
}
else
{
g_rw_lock_reader_unlock (&error_domain_global);
private_size = 0;
}
g_free (error->message);
allocated = ((guint8 *) error) - private_size;
g_slice_free1 (private_size + sizeof (GError), allocated);
}
/**
@ -509,14 +840,22 @@ g_error_free (GError *error)
GError*
g_error_copy (const GError *error)
{
GError *copy;
ErrorDomainInfo info;
g_return_val_if_fail (error != NULL, NULL);
/* See g_error_new_valist for why these don't return */
g_warn_if_fail (error->domain != 0);
g_warn_if_fail (error->message != NULL);
return g_error_new_steal (error->domain,
copy = g_error_new_steal (error->domain,
error->code,
g_strdup (error->message));
g_strdup (error->message),
&info);
if (info.copy != NULL)
info.copy (error, copy);
return copy;
}
/**

View File

@ -47,6 +47,143 @@ struct _GError
gchar *message;
};
/**
* G_DEFINE_EXTENDED_ERROR:
* @ErrorType: name to return a #GQuark for
* @error_type: prefix for the function name
*
* A convenience macro which defines two functions. First, returning
* the #GQuark for the extended error type @ErrorType; it is called
* `@error_type_quark()`. Second, returning the private data from a
* passed #GError; it is called `@error_type_get_private()`.
*
* For this macro to work, a type named `@ErrorTypePrivate` should be
* defined, `@error_type_private_init()`, `@error_type_private_copy()`
* and `@error_type_private_clear()` functions need to be either
* declared or defined. The functions should be similar to
* #GErrorInitFunc, #GErrorCopyFunc and #GErrorClearFunc,
* respectively, but they should receive the private data type instead
* of #GError.
*
* Since: 2.68
*/
#define G_DEFINE_EXTENDED_ERROR(ErrorType, error_type) \
static inline ErrorType ## Private * \
error_type ## _get_private (const GError *error) \
{ \
/* Copied from gtype.c (STRUCT_ALIGNMENT and ALIGN_STRUCT macros). */ \
const gsize sa = 2 * sizeof (gsize); \
const gsize as = (sizeof (ErrorType ## Private) + (sa - 1)) & -sa; \
g_return_val_if_fail (error != NULL, NULL); \
g_return_val_if_fail (error->domain == error_type ## _quark (), NULL); \
return (ErrorType ## Private *) (((guint8 *)error) - as); \
} \
\
static void \
g_error_with_ ## error_type ## _private_init (GError *error) \
{ \
ErrorType ## Private *priv = error_type ## _get_private (error); \
error_type ## _private_init (priv); \
} \
\
static void \
g_error_with_ ## error_type ## _private_copy (const GError *src_error, \
GError *dest_error) \
{ \
const ErrorType ## Private *src_priv = error_type ## _get_private (src_error); \
ErrorType ## Private *dest_priv = error_type ## _get_private (dest_error); \
error_type ## _private_copy (src_priv, dest_priv); \
} \
\
static void \
g_error_with_ ## error_type ## _private_clear (GError *error) \
{ \
ErrorType ## Private *priv = error_type ## _get_private (error); \
error_type ## _private_clear (priv); \
} \
\
GQuark \
error_type ## _quark (void) \
{ \
static GQuark q; \
static gsize initialized = 0; \
\
if (g_once_init_enter (&initialized)) \
{ \
q = g_error_domain_register_static (#ErrorType, \
sizeof (ErrorType ## Private), \
g_error_with_ ## error_type ## _private_init, \
g_error_with_ ## error_type ## _private_copy, \
g_error_with_ ## error_type ## _private_clear); \
g_once_init_leave (&initialized, 1); \
} \
\
return q; \
}
/**
* GErrorInitFunc:
* @error: extended error
*
* Specifies the type of function which is called just after an
* extended error instance is created and its fields filled. It should
* only initialize the fields in the private data, which can be
* received with the generated `*_get_private()` function.
*
* Normally, it is better to use G_DEFINE_EXTENDED_ERROR(), as it
* already takes care of getting the private data from @error.
*
* Since: 2.68
*/
typedef void (*GErrorInitFunc) (GError *error);
/**
* GErrorCopyFunc:
* @src_error: source extended error
* @dest_error: destination extended error
*
* Specifies the type of function which is called when an extended
* error instance is copied. It is passed the pointer to the
* destination error and source error, and should copy only the fields
* of the private data from @src_error to @dest_error.
*
* Normally, it is better to use G_DEFINE_EXTENDED_ERROR(), as it
* already takes care of getting the private data from @src_error and
* @dest_error.
*
* Since: 2.68
*/
typedef void (*GErrorCopyFunc) (const GError *src_error, GError *dest_error);
/**
* GErrorClearFunc:
* @error: extended error to clear
*
* Specifies the type of function which is called when an extended
* error instance is freed. It is passed the error pointer about to be
* freed, and should free the error's private data fields.
*
* Normally, it is better to use G_DEFINE_EXTENDED_ERROR(), as it
* already takes care of getting the private data from @error.
*
* Since: 2.68
*/
typedef void (*GErrorClearFunc) (GError *error);
GLIB_AVAILABLE_IN_2_68
GQuark g_error_domain_register_static (const char *error_type_name,
gsize error_type_private_size,
GErrorInitFunc error_type_init,
GErrorCopyFunc error_type_copy,
GErrorClearFunc error_type_clear);
GLIB_AVAILABLE_IN_2_68
GQuark g_error_domain_register (const char *error_type_name,
gsize error_type_private_size,
GErrorInitFunc error_type_init,
GErrorCopyFunc error_type_copy,
GErrorClearFunc error_type_clear);
GLIB_AVAILABLE_IN_ALL
GError* g_error_new (GQuark domain,
gint code,

View File

@ -337,6 +337,7 @@ glib_init (void)
g_messages_prefixed_init ();
g_debug_init ();
g_quark_init ();
g_error_init ();
}
#if defined (G_OS_WIN32)

View File

@ -27,6 +27,7 @@ extern GLogLevelFlags g_log_msg_prefix;
void glib_init (void);
void g_quark_init (void);
void g_error_init (void);
#ifdef G_OS_WIN32
#include <windows.h>

View File

@ -82,6 +82,105 @@ test_copy (void)
g_error_free (copy);
}
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);
}
int
main (int argc, char *argv[])
{
@ -91,6 +190,7 @@ main (int argc, char *argv[])
g_test_add_func ("/error/prefix", test_prefix);
g_test_add_func ("/error/literal", test_literal);
g_test_add_func ("/error/copy", test_copy);
g_test_add_func ("/error/extended", test_extended);
return g_test_run ();
}