From 29b652941cace1f94b561b0c27b2c71200b57aa4 Mon Sep 17 00:00:00 2001 From: Havoc Pennington Date: Wed, 6 Sep 2000 23:30:21 +0000 Subject: [PATCH] docs 2000-09-06 Havoc Pennington * gerror.c: docs * docs/reference/glib/tmpl/error_reporting.sgml: docs --- ChangeLog | 6 + ChangeLog.pre-2-0 | 6 + ChangeLog.pre-2-10 | 6 + ChangeLog.pre-2-12 | 6 + ChangeLog.pre-2-2 | 6 + ChangeLog.pre-2-4 | 6 + ChangeLog.pre-2-6 | 6 + ChangeLog.pre-2-8 | 6 + docs/reference/glib/tmpl/error_reporting.sgml | 362 +++++++++++++++++- gerror.c | 77 ++++ glib/gerror.c | 77 ++++ 11 files changed, 563 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 58c3a6335..bcd73e9a5 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +2000-09-06 Havoc Pennington + + * gerror.c: docs + + * docs/reference/glib/tmpl/error_reporting.sgml: docs + Wed Sep 6 10:28:34 2000 Owen Taylor * guniprop.c gunicode.h gutf8.c: Some inline docs fixes. diff --git a/ChangeLog.pre-2-0 b/ChangeLog.pre-2-0 index 58c3a6335..bcd73e9a5 100644 --- a/ChangeLog.pre-2-0 +++ b/ChangeLog.pre-2-0 @@ -1,3 +1,9 @@ +2000-09-06 Havoc Pennington + + * gerror.c: docs + + * docs/reference/glib/tmpl/error_reporting.sgml: docs + Wed Sep 6 10:28:34 2000 Owen Taylor * guniprop.c gunicode.h gutf8.c: Some inline docs fixes. diff --git a/ChangeLog.pre-2-10 b/ChangeLog.pre-2-10 index 58c3a6335..bcd73e9a5 100644 --- a/ChangeLog.pre-2-10 +++ b/ChangeLog.pre-2-10 @@ -1,3 +1,9 @@ +2000-09-06 Havoc Pennington + + * gerror.c: docs + + * docs/reference/glib/tmpl/error_reporting.sgml: docs + Wed Sep 6 10:28:34 2000 Owen Taylor * guniprop.c gunicode.h gutf8.c: Some inline docs fixes. diff --git a/ChangeLog.pre-2-12 b/ChangeLog.pre-2-12 index 58c3a6335..bcd73e9a5 100644 --- a/ChangeLog.pre-2-12 +++ b/ChangeLog.pre-2-12 @@ -1,3 +1,9 @@ +2000-09-06 Havoc Pennington + + * gerror.c: docs + + * docs/reference/glib/tmpl/error_reporting.sgml: docs + Wed Sep 6 10:28:34 2000 Owen Taylor * guniprop.c gunicode.h gutf8.c: Some inline docs fixes. diff --git a/ChangeLog.pre-2-2 b/ChangeLog.pre-2-2 index 58c3a6335..bcd73e9a5 100644 --- a/ChangeLog.pre-2-2 +++ b/ChangeLog.pre-2-2 @@ -1,3 +1,9 @@ +2000-09-06 Havoc Pennington + + * gerror.c: docs + + * docs/reference/glib/tmpl/error_reporting.sgml: docs + Wed Sep 6 10:28:34 2000 Owen Taylor * guniprop.c gunicode.h gutf8.c: Some inline docs fixes. diff --git a/ChangeLog.pre-2-4 b/ChangeLog.pre-2-4 index 58c3a6335..bcd73e9a5 100644 --- a/ChangeLog.pre-2-4 +++ b/ChangeLog.pre-2-4 @@ -1,3 +1,9 @@ +2000-09-06 Havoc Pennington + + * gerror.c: docs + + * docs/reference/glib/tmpl/error_reporting.sgml: docs + Wed Sep 6 10:28:34 2000 Owen Taylor * guniprop.c gunicode.h gutf8.c: Some inline docs fixes. diff --git a/ChangeLog.pre-2-6 b/ChangeLog.pre-2-6 index 58c3a6335..bcd73e9a5 100644 --- a/ChangeLog.pre-2-6 +++ b/ChangeLog.pre-2-6 @@ -1,3 +1,9 @@ +2000-09-06 Havoc Pennington + + * gerror.c: docs + + * docs/reference/glib/tmpl/error_reporting.sgml: docs + Wed Sep 6 10:28:34 2000 Owen Taylor * guniprop.c gunicode.h gutf8.c: Some inline docs fixes. diff --git a/ChangeLog.pre-2-8 b/ChangeLog.pre-2-8 index 58c3a6335..bcd73e9a5 100644 --- a/ChangeLog.pre-2-8 +++ b/ChangeLog.pre-2-8 @@ -1,3 +1,9 @@ +2000-09-06 Havoc Pennington + + * gerror.c: docs + + * docs/reference/glib/tmpl/error_reporting.sgml: docs + Wed Sep 6 10:28:34 2000 Owen Taylor * guniprop.c gunicode.h gutf8.c: Some inline docs fixes. diff --git a/docs/reference/glib/tmpl/error_reporting.sgml b/docs/reference/glib/tmpl/error_reporting.sgml index 132d20077..df4ac510e 100644 --- a/docs/reference/glib/tmpl/error_reporting.sgml +++ b/docs/reference/glib/tmpl/error_reporting.sgml @@ -3,10 +3,364 @@ Error Reporting +System for reporting errors - + +GLib provides a standard method of reporting errors from a called function to +the calling code. (This is the same problem solved by exceptions in other +languages.) It's important to understand that this method is both a +data type (the #GError object) and a set of +rules. If you use #GError incorrectly, then your code will not +properly interoperate with other code that uses #GError, and users of your API +will probably get confused. + + + +First and foremost: #GError should only be used to report +recoverable runtime errors, never to report programming errors. If +the programmer has screwed up, then you should use g_warning(), +g_return_if_fail(), g_assert(), g_error(), or some similar facility. +(Incidentally, remember that the g_error() function should +only be used for programming errors, it should not be used +to print any error reportable via #GError.) + + + +Examples of recoverable runtime errors are "file not found" or "failed to parse +input." Examples of programming errors are "NULL passed to strcmp()" or +"attempted to free the same pointer twice." These two kinds of errors are +fundamentally different: runtime errors should be handled or reported to the +user, programming errors should be eliminated by fixing the bug in the program. +This is why most functions in GLib and GTK+ do not use the #GError facility. + + + +Functions that can fail take a return location for a #GError as their last argument. +For example: + +gchar* g_file_get_contents (const gchar *filename, GError **error); + +If you pass a non-NULL value for the error argument, it should +point to a location where an error can be placed. For example: + +gchar *contents; +GError *err = NULL; +contents = g_file_get_contents ("foo.txt", &err); +g_assert ((contents == NULL && err != NULL) || (contents != NULL && err == NULL)); +if (err != NULL) + { + /* Report error to user, and free error */ + g_assert (contents == NULL); + fprintf (stderr, "Unable to read file foo.txt: %s\n", err->message); + g_error_free (err); + } +else + { + /* Use file contents */ + g_assert (contents != NULL); + } + +Note that err != NULL in this example is a +reliable indicator of whether g_file_get_contents() +failed. Also, g_file_get_contents() uses the convention that a NULL return value +means an error occurred (but not all functions use this convention). + + + +Because g_file_get_contents() returns NULL on failure, if you are only +interested in whether it failed and don't need to display an error message, you +can pass NULL for the error argument: + +contents = g_file_get_contents ("foo.txt", NULL); /* ignore errors */ +if (contents != NULL) + /* no error occurred */ ; +else + /* error */ ; + + + + +The #GError object contains three fields: domain indicates +the module the error-reporting function is located in, code +indicates the specific error that occurred, and message is a +user-readable error message with as many details as possible. Several functions +are provided to deal with an error received from a called function: +g_error_matches() returns TRUE if the error matches a given domain and code, +g_propagate_error() copies an error into an error location (so the calling +function will receive it), and g_clear_error() clears an error location by +freeing the error and resetting the location to NULL. To display an error to the +user, simply display error->message, perhaps along with +additional context known only to the calling function (the file being opened, or +whatever). + + + +When implementing a function that can report errors, the basic tool is +g_set_error(). Typically, if a fatal error occurs you want to g_set_error(), +then return immediately. g_set_error() does nothing if the error location passed +to it is NULL. Here's an example: + +gint +foo_open_file (GError **error) +{ + gint fd; + + fd = open ("file.txt", O_RDONLY); + + if (fd < 0) + { + g_set_error (error, + FOO_ERROR, /* error domain */ + FOO_ERROR_BLAH, /* error code */ + "Failed to open file: %s", /* error message format string */ + g_strerror (errno)); + return -1; + } + else + return fd; +} + + + + +Things are somewhat more complicated if you yourself call another function that +can report a #GError. If the sub-function indicates fatal errors in some way +other than reporting a #GError, such as by returning TRUE on success, you can +simply do the following: + +gboolean +my_function_that_can_fail (GError **err) +{ + g_return_val_if_fail (err == NULL || *err == NULL, FALSE); + + if (!sub_function_that_can_fail (err)) + { + /* assert that error was set by the sub-function */ + g_assert (err == NULL || *err != NULL); + return FALSE; + } + + /* otherwise continue, no error occurred */ + g_assert (err == NULL || *err == NULL); +} + + + + +If the sub-function does not indicate errors other than by reporting a #GError, +you need to create a temporary #GError since the passed-in one may be NULL. +g_propagate_error() is intended for use in this case. + +gboolean +my_function_that_can_fail (GError **err) +{ + GError *tmp_error; + + g_return_val_if_fail (err == NULL || *err == NULL, FALSE); + + tmp_error = NULL; + sub_function_that_can_fail (&tmp_error); + + if (tmp_error != NULL) + { + /* store tmp_error in err, if err != NULL, + * otherwise call g_error_free() on tmp_error + */ + g_propagate_error (err, tmp_error); + return FALSE; + } + + /* otherwise continue, no error occurred */ +} + + + + +Error pileups are always a bug. For example, this code is incorrect: + +gboolean +my_function_that_can_fail (GError **err) +{ + GError *tmp_error; + + g_return_val_if_fail (err == NULL || *err == NULL, FALSE); + + tmp_error = NULL; + sub_function_that_can_fail (&tmp_error); + other_function_that_can_fail (&tmp_error); + + if (tmp_error != NULL) + { + g_propagate_error (err, tmp_error); + return FALSE; + } +} + +tmp_error should be checked immediately after +sub_function_that_can_fail(), and either cleared or propagated upward. The rule +is: after each error, you must either handle the error, or return it to the +calling function. Note that passing NULL for the error location is the +equivalent of handling an error by always doing nothing about it. So the +following code is fine, assuming errors in sub_function_that_can_fail() are not +fatal to my_function_that_can_fail(): + +gboolean +my_function_that_can_fail (GError **err) +{ + GError *tmp_error; + + g_return_val_if_fail (err == NULL || *err == NULL, FALSE); + + sub_function_that_can_fail (NULL); /* ignore errors */ + + tmp_error = NULL; + other_function_that_can_fail (&tmp_error); + + if (tmp_error != NULL) + { + g_propagate_error (err, tmp_error); + return FALSE; + } +} + + + + +Note that passing NULL for the error location ignores +errors; it's equivalent to try { sub_function_that_can_fail (); } catch +(...) {} in C++. It does not mean to leave errors +unhandled; it means to handle them by doing nothing. + + + +Error domains and codes are conventionally named as follows: + + + +The error domain is called +<NAMESPACE>_<MODULE>_ERROR, for example +%G_EXEC_ERROR or %G_THREAD_ERROR. + + + + +The error codes are in an enumeration called +<Namespace>_<Module>_Error; for example, +#GThreadError or #GExecError. + + + + +Members of the error code enumeration are called <NAMESPACE>_<MODULE>_ERROR_<CODE>, for example %G_EXEC_ERROR_FORK or %G_THREAD_ERROR_AGAIN. + + + + +If there's a "generic" or "unknown" error code for unrecoverable errors it +doesn't make sense to distinguish with specific codes, it should be called +<NAMESPACE>_<MODULE>_ERROR_FAILED, for +example %G_EXEC_ERROR_FAILED or %G_THREAD_ERROR_FAILED. + + + + + + +Summary of rules for use of #GError: + + + + Do not report programming errors via #GError. + + + + + + The last argument of a function that returns an error should be a + location where a #GError can be placed (i.e. "#GError** error"). If + #GError is used with varargs, the #GError** should be the last + argument before the "...". + + + + + + The caller may pass NULL for the #GError** if they are not interested + in details of the exact error that occurred. + + + + + + If NULL is passed for the #GError** argument, then errors should + not be returned to the caller, but your function should still + abort and return if an error occurs. That is, control flow should + not be affected by whether the caller wants to get a #GError. + + + + + + If a #GError is reported, then your function by definition + had a fatal failure and did not complete whatever it was supposed + to do. If the failure was not fatal, then you handled it + and you should not report it. If it was fatal, then you must report it + and discontinue whatever you were doing immediately. + + + + + + A #GError* must be initialized to NULL before passing its address to + a function that can report errors. + + + + + + "Piling up" errors is always a bug. That is, if you assign a new + #GError to a #GError* that is non-NULL, thus overwriting the previous + error, it indicates that you should have aborted the operation instead + of continuing. If you were able to continue, you should have cleared + the previous error with g_clear_error(). g_set_error() will complain + if you pile up errors. + + + + + + + By convention, if you return a boolean value indicating success + then TRUE means success and FALSE means failure. If FALSE is returned, + the error must be set to a non-NULL value. + + + + + + + A NULL return value is also frequently used to mean that an error + occurred. You should make clear in your documentation whether NULL is + a valid return value in non-error cases; if NULL is a valid value, + then users must check whether an error was returned to see if the + function succeeded. + + + + + + When implementing a function that can report errors, you may want 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);). + + + + + @@ -101,5 +455,11 @@ Error Reporting @err: + diff --git a/gerror.c b/gerror.c index 17baf19eb..1a032e73e 100644 --- a/gerror.c +++ b/gerror.c @@ -43,6 +43,18 @@ g_error_new_valist(GQuark domain, return error; } +/** + * g_error_new: + * @domain: error domain + * @code: error code + * @format: printf()-style format for error message + * @Varargs: parameters for message format + * + * Creates a new #GError with the given @domain and @code, + * and a message formatted with @format. + * + * Return value: a new #GError + **/ GError* g_error_new (GQuark domain, gint code, @@ -62,6 +74,19 @@ g_error_new (GQuark domain, return error; } +/** + * g_error_new_literal: + * @domain: error domain + * @code: error code + * @message: error message + * + * Creates a new #GError; unlike g_error_new(), @message is not + * a printf()-style format string. Use this function if @message + * contains text you don't have control over, that could include + * printf() escape sequences. + * + * Return value: a new #GError + **/ GError* g_error_new_literal (GQuark domain, gint code, @@ -81,6 +106,13 @@ g_error_new_literal (GQuark domain, return err; } +/** + * g_error_free: + * @error: a #GError + * + * Frees a #GError and associated resources. + * + **/ void g_error_free (GError *error) { @@ -91,6 +123,14 @@ g_error_free (GError *error) g_free (error); } +/** + * g_error_copy: + * @error: a #GError + * + * Makes a copy of @error. + * + * Return value: a new #GError + **/ GError* g_error_copy (const GError *error) { @@ -107,6 +147,17 @@ g_error_copy (const GError *error) return copy; } +/** + * g_error_matches: + * @error: a #GError + * @domain: an error domain + * @code: an error code + * + * Returns TRUE if @error matches @domain and @code, FALSE + * otherwise. + * + * Return value: whether @error has @domain and @code + **/ gboolean g_error_matches (const GError *error, GQuark domain, @@ -120,6 +171,17 @@ g_error_matches (const GError *error, #define ERROR_OVERWRITTEN_WARNING "GError set over the top of a previous GError or uninitialized memory.\n" \ "This indicates a bug in someone's code. You must ensure an error is NULL before it's set." +/** + * g_set_error: + * @err: a return location for a #GError, or NULL + * @domain: error domain + * @code: error code + * @format: printf()-style format + * @Varargs: args for @format + * + * Does nothing if @err is NULL; if @err is non-NULL, then *@err must + * be NULL. A new #GError is created and assigned to *@err. + **/ void g_set_error (GError **err, GQuark domain, @@ -140,6 +202,14 @@ g_set_error (GError **err, va_end (args); } +/** + * g_propagate_error: + * @dest: error return location + * @src: error to move into the return location + * + * Does nothing if @dest is NULL; otherwise, + * moves @src into *@dest. *@dest must be NULL. + **/ void g_propagate_error (GError **dest, GError *src) @@ -155,6 +225,13 @@ g_propagate_error (GError **dest, *dest = src; } +/** + * g_clear_error: + * @err: a #GError return location + * + * If @err is NULL, does nothing. If @err is non-NULL, + * calls g_error_free() on *@err and sets *@err to NULL. + **/ void g_clear_error (GError **err) { diff --git a/glib/gerror.c b/glib/gerror.c index 17baf19eb..1a032e73e 100644 --- a/glib/gerror.c +++ b/glib/gerror.c @@ -43,6 +43,18 @@ g_error_new_valist(GQuark domain, return error; } +/** + * g_error_new: + * @domain: error domain + * @code: error code + * @format: printf()-style format for error message + * @Varargs: parameters for message format + * + * Creates a new #GError with the given @domain and @code, + * and a message formatted with @format. + * + * Return value: a new #GError + **/ GError* g_error_new (GQuark domain, gint code, @@ -62,6 +74,19 @@ g_error_new (GQuark domain, return error; } +/** + * g_error_new_literal: + * @domain: error domain + * @code: error code + * @message: error message + * + * Creates a new #GError; unlike g_error_new(), @message is not + * a printf()-style format string. Use this function if @message + * contains text you don't have control over, that could include + * printf() escape sequences. + * + * Return value: a new #GError + **/ GError* g_error_new_literal (GQuark domain, gint code, @@ -81,6 +106,13 @@ g_error_new_literal (GQuark domain, return err; } +/** + * g_error_free: + * @error: a #GError + * + * Frees a #GError and associated resources. + * + **/ void g_error_free (GError *error) { @@ -91,6 +123,14 @@ g_error_free (GError *error) g_free (error); } +/** + * g_error_copy: + * @error: a #GError + * + * Makes a copy of @error. + * + * Return value: a new #GError + **/ GError* g_error_copy (const GError *error) { @@ -107,6 +147,17 @@ g_error_copy (const GError *error) return copy; } +/** + * g_error_matches: + * @error: a #GError + * @domain: an error domain + * @code: an error code + * + * Returns TRUE if @error matches @domain and @code, FALSE + * otherwise. + * + * Return value: whether @error has @domain and @code + **/ gboolean g_error_matches (const GError *error, GQuark domain, @@ -120,6 +171,17 @@ g_error_matches (const GError *error, #define ERROR_OVERWRITTEN_WARNING "GError set over the top of a previous GError or uninitialized memory.\n" \ "This indicates a bug in someone's code. You must ensure an error is NULL before it's set." +/** + * g_set_error: + * @err: a return location for a #GError, or NULL + * @domain: error domain + * @code: error code + * @format: printf()-style format + * @Varargs: args for @format + * + * Does nothing if @err is NULL; if @err is non-NULL, then *@err must + * be NULL. A new #GError is created and assigned to *@err. + **/ void g_set_error (GError **err, GQuark domain, @@ -140,6 +202,14 @@ g_set_error (GError **err, va_end (args); } +/** + * g_propagate_error: + * @dest: error return location + * @src: error to move into the return location + * + * Does nothing if @dest is NULL; otherwise, + * moves @src into *@dest. *@dest must be NULL. + **/ void g_propagate_error (GError **dest, GError *src) @@ -155,6 +225,13 @@ g_propagate_error (GError **dest, *dest = src; } +/** + * g_clear_error: + * @err: a #GError return location + * + * If @err is NULL, does nothing. If @err is non-NULL, + * calls g_error_free() on *@err and sets *@err to NULL. + **/ void g_clear_error (GError **err) {