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 701296 - gtkfixed accepts toplevel windows into its children list after gtk_widget_set_parent fails
gtkfixed accepts toplevel windows into its children list after gtk_widget_set...
Status: RESOLVED FIXED
Product: gtk+
Classification: Platform
Component: Widget: Other
3.22.x
Other All
: Normal major
: ---
Assigned To: Daniel Boles
gtk-bugs
Depends on:
Blocks:
 
 
Reported: 2013-05-30 16:17 UTC by Damien Ruscoe
Modified: 2017-10-13 12:37 UTC
See Also:
GNOME target: ---
GNOME version: ---


Attachments
Reproducable case (890 bytes, text/x-csrc)
2013-05-30 16:18 UTC, Damien Ruscoe
  Details
patch file (2.35 KB, patch)
2013-05-30 16:19 UTC, Damien Ruscoe
rejected Details | Review
Fixed: If can’t add child, don’t add to child info (1.19 KB, patch)
2017-10-12 12:49 UTC, Daniel Boles
none Details | Review
Fixed: If can’t add child, don’t add to child info (1.19 KB, patch)
2017-10-12 13:06 UTC, Daniel Boles
committed Details | Review
Fixed: Handle set_parent failing for other reasons (1.29 KB, patch)
2017-10-13 12:17 UTC, Daniel Boles
none Details | Review

Description Damien Ruscoe 2013-05-30 16:17:52 UTC
gtk_fixed_put [gtkfixed.c:150] is a re-parenting function.

It accepts a widget and attempts to set that widgets parent to the GtkFixed widget (gtk_widget_set_parent).

After that it adds the widget to the children list of the GtkFixed widget.


If gtk_widget_set_parent() fails [for reasons: 1) the widget already has a parent 2) the widget is a toplevel window] then it silently exits.

If the parent has not been set for the widget, the widget is *still* added to the children list of the GtkFixed widget.

Later on...

Events may cause traversals over the GtkFixed children list (such as a EVENT_EXPOSE event] and act on the children. 
The expose event in particular checks if the GtkFixed children have the parent pointer set to the GtkFixed widget (This stage has failed earlier).
This causes a GPF.


I have produced a Gtk app to demonstrate this bug.
Running it will produce the GPF.
Patch included.

Thanks,
Damien
Comment 1 Damien Ruscoe 2013-05-30 16:18:50 UTC
Created attachment 245660 [details]
Reproducable case
Comment 2 Damien Ruscoe 2013-05-30 16:19:13 UTC
Created attachment 245661 [details] [review]
patch file
Comment 3 Damien Ruscoe 2013-05-30 16:21:22 UTC
Additional info:

The actual GPF...

Gtk:ERROR:/build/buildd/gtk+2.0-2.24.10/gtk/gtkcontainer.c:2763:IA__gtk_container_propagate_expose: assertion failed: (child->parent == GTK_WIDGET (container))
Comment 4 Daniel Boles 2017-10-12 12:44:00 UTC
Review of attachment 245661 [details] [review]:

::: gtk+-2.24.10_orig/gtk/gtkfixed.c
@@ +163,3 @@
   child_info->y = y;
 
+  if (gtk_widget_set_parent (widget, GTK_WIDGET (fixed)))

I don't know why you thought that fixing a local thinko in GtkFixed required an API break for all GtkWidgets, everywhere...

The correct thing to do is far simpler: just g_return_if_fail (gtk_widget_get_parent (widget) == NULL), so that the errant non-child does not get added to the internal list of supposed children.

@@ +164,3 @@
 
+  if (gtk_widget_set_parent (widget, GTK_WIDGET (fixed)))
+  {

GTK+, GLib, et al. use an extra level of indentation for braces.

::: gtk+-2.24.10_orig/gtk/gtkwidget.c
@@ +8914,3 @@
   if (gtk_widget_get_has_window (widget))
     {
+      gtk_grab_remove ( widget );

What relation does this have to the current bug? Also, the spacing is wrong.

::: gtk+-2.24.10_orig/gtk/gtkwidget.h
@@ +995,3 @@
 							 gboolean      redraw_on_allocate);
 
+gboolean              gtk_widget_set_parent             (GtkWidget    *widget,

Again, no.
Comment 5 Daniel Boles 2017-10-12 12:49:41 UTC
Created attachment 361418 [details] [review]
Fixed: If can’t add child, don’t add to child info

    Fixed: If can’t add child, don’t add to child info

    If the call to set_parent() failed, we were still adding the child to
    the internal list of children, despite that it was not really added.
    That meant we could later try to do invalid stuff with that non-child.

    Fix that by asserting and giving up if the child that the user is
    attempting to add is already parented.
Comment 6 Daniel Boles 2017-10-12 12:51:32 UTC
Having said that, thanks for spotting this, of course!
Comment 7 Emmanuele Bassi (:ebassi) 2017-10-12 12:59:15 UTC
(In reply to Daniel Boles from comment #4)
> Review of attachment 245661 [details] [review] [review]:
> 
> ::: gtk+-2.24.10_orig/gtk/gtkfixed.c
> @@ +163,3 @@
>    child_info->y = y;
>  
> +  if (gtk_widget_set_parent (widget, GTK_WIDGET (fixed)))
> 
> I don't know why you thought that fixing a local thinko in GtkFixed required
> an API break for all GtkWidgets, everywhere...

Technically speaking, changing a function returning void to return something is neither an API nor an ABI break.

We're still not going to change gtk_widget_set_parent(), though.
Comment 8 Emmanuele Bassi (:ebassi) 2017-10-12 13:03:14 UTC
Review of attachment 361418 [details] [review]:

::: gtk/gtkfixed.c
@@ +236,3 @@
   g_return_if_fail (GTK_IS_FIXED (fixed));
   g_return_if_fail (GTK_IS_WIDGET (widget));
+  g_return_if_fail (gtk_widget_get_parent (widget) == NULL);

This should be:

  g_return_if_fail (_gtk_widget_get_parent (widget) == NULL);

to use the fast internal check for GtkWidget fields.
Comment 9 Daniel Boles 2017-10-12 13:06:12 UTC
Created attachment 361420 [details] [review]
Fixed: If can’t add child, don’t add to child info

Good point, thanks for the quick review!
Comment 10 Daniel Boles 2017-10-13 08:52:19 UTC
Comment on attachment 361420 [details] [review]
Fixed: If can’t add child, don’t add to child info

Come to think of it, this will explain why GtkFixed let me accidentally double up child widgets before...

I also pushed a fix for the previous dereference of priv before the type check.
Comment 11 Daniel Boles 2017-10-13 12:06:03 UTC
I guess this needs to also assert that the passed widget is not a toplevel.
Comment 12 Emmanuele Bassi (:ebassi) 2017-10-13 12:12:00 UTC
(In reply to Daniel Boles from comment #11)
> I guess this needs to also assert that the passed widget is not a toplevel.

No widget does this check, and it's kind of possible anyway, if you know what you're doing — that's how Glade implements its own UI design canvas.
Comment 13 Daniel Boles 2017-10-13 12:17:43 UTC
Created attachment 361508 [details] [review]
Fixed: Handle set_parent failing for other reasons

(In reply to Emmanuele Bassi (:ebassi) from comment #12)
> No widget does this check

However, as Damien noted, gtk_widget_set_parent() does. So, as of now, that would still assert and bail out if receiving an unparent toplevel. However, GtkFixed now only checks that the child is non-null, so it would continue to add the toplevel to its GList, even though the call to set_parent() failed.

I think the attached should handle both cases, letting set_parent() report the right errors, and ensuring GtkFixed doesn't push the non-added child to its list in either scenario.
Comment 14 Emmanuele Bassi (:ebassi) 2017-10-13 12:33:28 UTC
(In reply to Daniel Boles from comment #13)
> Created attachment 361508 [details] [review] [review]
> Fixed: Handle set_parent failing for other reasons
> 
> (In reply to Emmanuele Bassi (:ebassi) from comment #12)
> > No widget does this check
> 
> However, as Damien noted, gtk_widget_set_parent() does. So, as of now, that
> would still assert and bail out if receiving an unparent toplevel. However,
> GtkFixed now only checks that the child is non-null, so it would continue to
> add the toplevel to its GList, even though the call to set_parent() failed.

You can add a `g_assert (child->parent == fixed)` if you want to be extra paranoid, but I'll say this again: no widget does this.

We don't put a precondition check for every fallible case in every entry point; we put precondition checks in the related functions.

The `parent == NULL` check is a safe addition; `gtk_container_add()` does it, but methods that do not call `gtk_container_add()`, like `gtk_box_pack_*()` and `gtk_fixed_put()`, should do the same, at least for consistency.

In either case, you'll get a critical warning.

> I think the attached should handle both cases, letting set_parent() report
> the right errors, and ensuring GtkFixed doesn't push the non-added child to
> its list in either scenario.

No other widget does this check.

Always remember GIGO: Garbage In, Garbage Out.
Comment 15 Daniel Boles 2017-10-13 12:37:17 UTC
Makes sense, thanks for explaining. And for fixing the missing include before!