After an evaluation, GNOME has moved from Bugzilla to GitLab. Learn more about GitLab.
No new issues can be reported in GNOME Bugzilla anymore.
To report an issue in a GNOME project, go to GNOME GitLab.
Do not go to GNOME Gitlab for: Bluefish, Doxygen, GnuCash, GStreamer, java-gnome, LDTP, NetworkManager, Tomboy.
Bug 661554 - GIO's use of GError is wrong
GIO's use of GError is wrong
Status: RESOLVED FIXED
Product: glib
Classification: Platform
Component: gio
unspecified
Other All
: Normal normal
: ---
Assigned To: gtkdev
gtkdev
: 702333 (view as bug list)
Depends on:
Blocks:
 
 
Reported: 2011-10-12 14:19 UTC by Colin Walters
Modified: 2015-02-20 19:04 UTC
See Also:
GNOME target: ---
GNOME version: ---


Attachments
GFileEnumerator: Add g_file_enumerator_get_next() (5.12 KB, patch)
2011-10-12 14:19 UTC, Colin Walters
none Details | Review
GFileEnumerator: Add API that's more friendly to propgating GError (8.98 KB, patch)
2012-07-07 23:11 UTC, Colin Walters
none Details | Review
sample diff between users (1.41 KB, text/plain)
2012-07-07 23:13 UTC, Colin Walters
  Details
Add a better enumeration API (9.39 KB, patch)
2015-02-12 23:25 UTC, Colin Walters
none Details | Review
Also check the value of "count" (9.43 KB, patch)
2015-02-12 23:28 UTC, Colin Walters
none Details | Review

Description Colin Walters 2011-10-12 14:19:03 UTC
g_file_enumerator_next_file() does not correctly use #GError.
Comment 1 Colin Walters 2011-10-12 14:19:04 UTC
Created attachment 198850 [details] [review]
GFileEnumerator: Add g_file_enumerator_get_next()
Comment 2 Matthias Clasen 2011-10-16 15:31:59 UTC
Review of attachment 198850 [details] [review]:

I think it is a bit of a stretch to call this an incorrect use of GError. 
Nothing wrong with adding an alternative that is more convenient for bindings, though.
Comment 3 Colin Walters 2011-10-16 17:18:04 UTC
This doesn't just impact bindings - these kinds of GError misuses are more painful to use from *C* too.

static gboolean
myfunc (..., GError **error)
{
  GError *temp_error = NULL;

  while ((filename = g_data_input_stream_read_upto (datain, &separator, 1,
                                                    &filename_len, NULL, &temp_error)) != NULL)
  {
    ..
}
  if (filename == NULL && temp_error != NULL)
    {
      g_propagate_prefixed_error (error, temp_error, "%s", "While reading filelist: ");
      goto out;
    }

Is the only right way to use this function.  You have to allocate a temporary GError.
Comment 4 Colin Walters 2011-10-16 17:40:23 UTC
Actually the tradeoff made here is they are slightly easier to use if your function itself doesn't take a GError.

But if you're wrapping any of these GIO calls in a library which itself throws GError, following the rules you have to allow the caller to pass NULL for your GError, so you have to allocate a temporary.

In the end really all functions that throw a GError should just always return gboolean - otherwise it's a mess.
Comment 5 Colin Walters 2011-10-16 17:43:42 UTC
Hmm...so it turns out GIO has this problem rather pervasively, and I'm less sure it makes sense to add a lot of new functions.

It may be better to just stick with the status quo, add documentation for each function that has this issue, and have all callers allocate temporary errors.
Comment 6 Colin Walters 2011-10-16 17:48:11 UTC
Partial list of broken functions:

g_file_enumerator_next_file
g_file_enumerator_next_files_finish
g_data_input_stream_read_byte
g_data_input_stream_read_int16
g_data_input_stream_read_uint16
g_data_input_stream_read_int32
g_data_input_stream_read_uint32
g_data_input_stream_read_int64
g_data_input_stream_read_uint64
g_data_input_stream_read_line
g_data_input_stream_read_line_utf8
g_data_input_stream_read_line_finish

For all of these functions, the return value is also a valid result, so to distinguish errors from non-errors, you have to pass a non-NULL GError.
Comment 7 Dan Winship 2011-10-17 00:59:39 UTC
(In reply to comment #4)
> But if you're wrapping any of these GIO calls in a library which itself throws
> GError, following the rules you have to allow the caller to pass NULL for your
> GError, so you have to allocate a temporary.
> 
> In the end really all functions that throw a GError should just always return
> gboolean - otherwise it's a mess.

FWIW, even then you need to allocate a temporary GError sometimes, if you need to "catch" some errors and "re-throw" the rest. (This pops up all over the place in the tls code with G_IO_ERROR_WOULD_BLOCK.)
Comment 8 Allison Karlitskaya (desrt) 2012-01-19 19:22:14 UTC
g_file_enumerator_next_file is pretty bad.  Even when it fails (and returns a GError), you don't know why: it could have failed to get the attributes of a single file (so you should try again to get the next file) or it could be completely dead (so you should quit).

I think this API would be better:


gboolean    g_file_enumerator_next     (GFileEnumerator  *enumerator);
GFileInfo * g_file_enumerator_get_info (GFileEnumerator  *enumerator,
                                        GError          **error);

then you'd write this:

  while (enumerator.next ()) {
      try {
          info = enumerator.get_info();
      } catch {
          ... handle failure of single file ...
      }
  }

  try {
      enumerator.close ();
  } catch {
      ... report higher-level failure to iterate ...
  }

we could also add

const gchar * g_file_enumerator_get_name (GFileEnumerator *enumerator);

that you could call to find out the name of the file that we failed to get the info for.
Comment 9 Colin Walters 2012-01-19 19:24:24 UTC
As long as we're poking GFileEnumerator, I pretty much always want to get a GFile* for the child too...so an API that gave me both would be cool.
Comment 10 Colin Walters 2012-07-07 22:12:25 UTC
(In reply to comment #8)
 
> I think this API would be better:
> 
> 
> gboolean    g_file_enumerator_next     (GFileEnumerator  *enumerator);
> GFileInfo * g_file_enumerator_get_info (GFileEnumerator  *enumerator,
>                                         GError          **error);

Yeah...the main problem with this is that we need to implement it on top of the old API; adding vfuncs isn't really an option since it would break "third party" GFileEnumerators (and I actually have one in ostree).

The problem with your proposal could be best phrased as "where does the GCancellable go"?  Under the constraint of building on top of the old API, it needs to go in _next(), but the GError would stay on _get_info(), which is weird.

I'm not sure if we have any other precedent for iterators that take GError?
Comment 11 Colin Walters 2012-07-07 23:11:06 UTC
Created attachment 218240 [details] [review]
GFileEnumerator: Add API that's more friendly to propgating GError

The current synchronous g_file_enumerator_next_file() API makes life
painful for callers which want to re-throw a GError; one has to
allocate a temporary GError to correctly distinguish between a normal
end-of-stream versus an error condition.

Doing this "right" requires separating the two cases, and that really
requires two different API calls.
Comment 12 Colin Walters 2012-07-07 23:13:18 UTC
Created attachment 218241 [details]
sample diff between users

This diff shows that the new API is nicer - in particular we can drop this ugly bit:

   if (temp_error)
     {
       g_propagate_error (error, temp_error);
       goto out;
     }
Comment 13 Colin Walters 2012-07-07 23:21:57 UTC
The (conceptually simpler) alternative to the above patch is my original patch; would require callers to distinguish between end-of-stream versus error by testing whether or not the return info is NULL, but that's a lot less annoying than having to allocate a temporary GError.

I kind of prefer my original patch actually, so I'm going to un-obsolete it and we can discuss between.
Comment 14 Dan Winship 2012-07-09 16:23:14 UTC
(In reply to comment #13)
> The (conceptually simpler) alternative to the above patch is my original patch;
> would require callers to distinguish between end-of-stream versus error by
> testing whether or not the return info is NULL

you mean whether the error is NULL, right? the return info would be NULL in both cases

I think I like your version better too, given the constraints around the GCancellable.

Also, g_socket_address_enumerator_next() has exactly the same problem.
Comment 15 Colin Walters 2012-07-12 14:44:35 UTC
Comment on attachment 198850 [details] [review]
GFileEnumerator: Add g_file_enumerator_get_next()

Actually no, forget this first one.  When I tried to use it in practice it didn't really work.  The second one does work.
Comment 16 Colin Walters 2015-02-12 23:25:14 UTC
Created attachment 296725 [details] [review]
Add a better enumeration API

3.5 years later, here's a better patch.
Comment 17 Colin Walters 2015-02-12 23:28:12 UTC
Created attachment 296726 [details] [review]
Also check the value of "count"

In quick self review, I noticed I wasn't testing the value of "count".  Fix that. 

Also I should note this function has been in use for a few years in libgsystem:
https://git.gnome.org/browse/libgsystem/tree/src/gsystem-file-utils.c#n926
Comment 18 Colin Walters 2015-02-20 15:10:51 UTC
*** Bug 702333 has been marked as a duplicate of this bug. ***
Comment 19 Alexander Larsson 2015-02-20 15:35:21 UTC
Review of attachment 296726 [details] [review]:

::: gio/gfileenumerator.c
@@ +577,3 @@
+ * g_file_enumerator_iterate:
+ * @direnum: an open #GFileEnumerator
+ * @out_info: (out) (transfer none) (allow-none): Output location for the next #GFileInfo

This is not allow-none. We unconditionally assign to it.
I think it should be though. You can as well check for if child != NULL for end-of-stream.

@@ +589,3 @@
+ * In contrast, with this function, a %FALSE return from
+ * gs_file_enumerator_iterate() <emphasis>always</emphasis> means
+ * "error".  End of iteration is signaled by @out_info being %NULL.

@out_info or @out_child being %NULL

@@ +641,3 @@
+  if (g_once_init_enter (&quarks_initialized))
+    {
+      cached_info_quark = g_quark_from_static_string ("gsystem-cached-info");

g-cached-info?

@@ +646,3 @@
+    }
+
+  *out_info = g_file_enumerator_next_file (direnum, cancellable, &temp_error);

I think we should have a temporary info, and allow out_info == NULL.

@@ +652,3 @@
+    {
+      g_propagate_error (error, temp_error);
+      goto out;

I don't really see why we have to use goto here. There is no cleanup, so just return FALSE.

@@ +656,3 @@
+  else if (*out_info != NULL)
+    {
+      g_object_set_qdata_full ((GObject*)direnum, cached_info_quark, *out_info, (GDestroyNotify)g_object_unref);

We should only set this if out_info is != NULL, otherwise we can unref it after we created the child.

::: gio/tests/file.c
@@ +701,3 @@
+    {
+      (void)g_file_enumerator_iterate (fenum, &info, NULL, NULL, &error);
+      g_assert_no_error (error);

Also assert it returns TRUE?
Comment 20 Alexander Larsson 2015-02-20 15:44:56 UTC
Review of attachment 296726 [details] [review]:

(other than that it looks good to me)
Comment 21 Colin Walters 2015-02-20 19:04:10 UTC
Ok, pushed with all of those changes, though I kept the "goto out"
since I just find it cleaner personally.

One thing though - I had to add a warning if the enumerator was
created without "standard::name" being specified, and we weren't
requesting a GFileInfo return.  In that case we have no way to return
the child.

Maybe a future version of this could silently upgrade the iterator to
force querying the name - I can't think of a use case for not getting
the name.