Nice job! On Jun 15, 2013 9:44 AM, "Stephan Schreiber" <i...@fs-driver.org> wrote:
> tags 711107 - help > tags 711107 + patch > thanks > > > Emilio Pozuelo Monfort<po...@debian.org> wrote: > > What happens if you do `make check' or `fakeroot make check' again? make >> check >> will set Xvfb differently than xvfb-run. >> > > I didn't check that out. I assume the test will always fail with 'make > check'. > It doesn't matter what X server you are using, or how xvfb is set. > > The test fails when gtester is invoked: > xvfb-run gtester -k --verbose children > fails, but > xvfb-run ./children > passes the test. > > This is very interesting, but a misleading and useless information ;-). > The real problem is in the tests/a11y/children.c source code file within > the test_add_remove() function; it reads local variables without > initialization: > > static void > test_add_remove (GtkWidget *widget) > { > AtkObject *accessible; > AtkObject *child_accessible; > SignalData add_data; > SignalData remove_data; > SignalData parent_data[3]; > STATE state; > gint i, j; > gint step_children; > > state.widget = widget; > accessible = gtk_widget_get_accessible (widget); > > add_data.count = 0; > remove_data.count = 0; > g_signal_connect (accessible, "children_changed::add", > G_CALLBACK (children_changed), &add_data); > g_signal_connect (accessible, "children_changed::remove", > G_CALLBACK (children_changed), &remove_data); > > step_children = atk_object_get_n_accessible_**children (accessible); > > for (i = 0; i < 3; i++) > { > if (!do_create_child (&state, i)) > break; > if (!GTK_IS_ENTRY (widget)) > { > parent_data[i].count = 0; > child_accessible = gtk_widget_get_accessible (state.child[i]); > g_signal_connect (child_accessible, "notify::accessible-parent", > G_CALLBACK (parent_notify), &(parent_data[i])); > gtk_container_add (GTK_CONTAINER (widget), state.child[i]); > } > else > child_accessible = atk_object_ref_accessible_**child (accessible, > i); > > g_assert_cmpint (add_data.count, ==, i + 1); > g_assert_cmpint (add_data.n_children, ==, step_children + i + 1); > g_assert_cmpint (remove_data.count, ==, 0); > if (!GTK_IS_ENTRY (widget)) > g_assert_cmpint (parent_data[i].count, ==, 1); > if (GTK_IS_SCROLLED_WINDOW (widget) || > GTK_IS_NOTEBOOK (widget)) > g_assert (atk_object_get_parent (ATK_OBJECT > (parent_data[i].parent)) == accessible); > else if (GTK_IS_ENTRY (widget)) > g_assert (atk_object_get_parent (child_accessible) == accessible); > else > g_assert (parent_data[i].parent == accessible); > > if (GTK_IS_ENTRY (widget)) > g_object_unref (child_accessible); > } > for (j = 0 ; j < i; j++) > { > remove_child (&state, j); > g_assert_cmpint (add_data.count, ==, i); > g_assert_cmpint (remove_data.count, ==, j + 1); > g_assert_cmpint (remove_data.n_children, ==, step_children + i - j - > 1); > if (parent_data[j].count == 2) > g_assert (parent_data[j].parent == NULL); > else if (!GTK_IS_ENTRY (widget)) > { > AtkStateSet *set; > set = atk_object_ref_state_set (ATK_OBJECT > (parent_data[j].parent)); > g_assert (atk_state_set_contains_state (set, ATK_STATE_DEFUNCT)); > g_object_unref (set); > } > } > > g_signal_handlers_disconnect_**by_func (accessible, G_CALLBACK > (children_changed), &add_data); > g_signal_handlers_disconnect_**by_func (accessible, G_CALLBACK > (children_changed), &remove_data); > } > > > When you take a look at the main() function in children.c, you realize > that test_add_remove() is called multiple times; it gets via its widget > paramter object instances of different types. > Almost all object types are derived from GtkWidget or GtkContainer; > GTK_IS_ENTRY() evaluates to false for them. > The last type is GtkEntry (created by gtk_entry_new()); GTK_IS_ENTRY() > evaluates to true for it. This is the test we have the trouble with. > > > Please take a look at the fifth line within the function: > SignalData parent_data[3]; > > Let's assume that we are running the buggy test; widget is a GtkEntry > instance; GTK_IS_ENTRY (widget) is true. > The first time you see some code for parent_data[].count is in the first > for loop. But if GTK_IS_ENTRY (widget) is true, parent_data isnt'touched at > all. No pointer to parent_data is provided to somewhere else: > > if (!GTK_IS_ENTRY (widget)) > { > parent_data[i].count = 0; > child_accessible = gtk_widget_get_accessible (state.child[i]); > g_signal_connect (child_accessible, "notify::accessible-parent", > G_CALLBACK (parent_notify), &(parent_data[i])); > ... > } > else > child_accessible = atk_object_ref_accessible_**child (accessible, > i); > > > In the second for loop: > > if (parent_data[j].count == 2) > g_assert (parent_data[j].parent == NULL); > else if (!GTK_IS_ENTRY (widget)) > > parent_data is read here without any preceding initialization. Thus, we > are running into trouble. > parent_data is not used at all on the GtkEntry case. It must not be in any > assertion in that case. > > Second problem: > If we take a closer look at the entire if-else statement, we realize that > the block after the else-if statement is never executed. Dead code. > It isn't executed on the considered last test with the GtkEntry instance > since GTK_IS_ENTRY (widget) is true. > It isn't executed on the other tests with a GtkWidget or GtkContainer > instance, because on that tests the term parent_data[j].count is *always* 2 > (see below). > > if (parent_data[j].count == 2) > g_assert (parent_data[j].parent == NULL); > else if (!GTK_IS_ENTRY (widget)) > { > AtkStateSet *set; > set = atk_object_ref_state_set (ATK_OBJECT > (parent_data[j].parent)); > g_assert (atk_state_set_contains_state (set, ATK_STATE_DEFUNCT)); > g_object_unref (set); > } > > The following code lines are executed for each used element of > parent_data[] on the tests with GtkWidget/GtkContainer (GTK_IS_ENTRY > (widget) is false): > > parent_data[i].count = 0; > g_signal_connect (child_accessible, "notify::accessible-parent", > G_CALLBACK (parent_notify), &(parent_data[i])); > > The last code line causes two subsequent calls of children_changed() - > first time when the container is added; second time when it is removed: > > static void > children_changed (AtkObject *accessible, > guint index, > gpointer child, > SignalData *data) > { > data->count++; > data->index = index; > data->n_children = atk_object_get_n_accessible_**children (accessible); > } > > Since the data parameter points to the appropriate parent_data element, > its count member is always 2 after that. > To prove that this is correct, we add an assertion that checks whether > that count is always 2. > After deleting the dead code, the code reads: > > static void > test_add_remove (GtkWidget *widget) > { > AtkObject *accessible; > AtkObject *child_accessible; > SignalData add_data; > SignalData remove_data; > SignalData parent_data[3]; > STATE state; > gint i, j; > gint step_children; > > state.widget = widget; > accessible = gtk_widget_get_accessible (widget); > > add_data.count = 0; > remove_data.count = 0; > g_signal_connect (accessible, "children_changed::add", > G_CALLBACK (children_changed), &add_data); > g_signal_connect (accessible, "children_changed::remove", > G_CALLBACK (children_changed), &remove_data); > > step_children = atk_object_get_n_accessible_**children (accessible); > > for (i = 0; i < 3; i++) > { > if (!do_create_child (&state, i)) > break; > if (!GTK_IS_ENTRY (widget)) > { > parent_data[i].count = 0; > child_accessible = gtk_widget_get_accessible (state.child[i]); > g_signal_connect (child_accessible, "notify::accessible-parent", > G_CALLBACK (parent_notify), &(parent_data[i])); > gtk_container_add (GTK_CONTAINER (widget), state.child[i]); > } > else > child_accessible = atk_object_ref_accessible_**child (accessible, > i); > > g_assert_cmpint (add_data.count, ==, i + 1); > g_assert_cmpint (add_data.n_children, ==, step_children + i + 1); > g_assert_cmpint (remove_data.count, ==, 0); > if (!GTK_IS_ENTRY (widget)) > g_assert_cmpint (parent_data[i].count, ==, 1); > if (GTK_IS_SCROLLED_WINDOW (widget) || > GTK_IS_NOTEBOOK (widget)) > g_assert (atk_object_get_parent (ATK_OBJECT > (parent_data[i].parent)) == accessible); > else if (GTK_IS_ENTRY (widget)) > g_assert (atk_object_get_parent (child_accessible) == accessible); > else > g_assert (parent_data[i].parent == accessible); > > if (GTK_IS_ENTRY (widget)) > g_object_unref (child_accessible); > } > for (j = 0 ; j < i; j++) > { > remove_child (&state, j); > g_assert_cmpint (add_data.count, ==, i); > g_assert_cmpint (remove_data.count, ==, j + 1); > g_assert_cmpint (remove_data.n_children, ==, step_children + i - j - > 1); > if (!GTK_IS_ENTRY (widget)) > { > g_assert_cmpint (parent_data[j].count, ==, 2); > g_assert (parent_data[j].parent == NULL); > } > } > > g_signal_handlers_disconnect_**by_func (accessible, G_CALLBACK > (children_changed), &add_data); > g_signal_handlers_disconnect_**by_func (accessible, G_CALLBACK > (children_changed), &remove_data); > } > > > This is the commit which introduced the bugs. > https://git.gnome.org/browse/**gtk+/commit/tests/a11y/** > children.c?h=gtk-3-8&id=**b7743430aa1471c9ec054606d22347**00e2da3eda<https://git.gnome.org/browse/gtk+/commit/tests/a11y/children.c?h=gtk-3-8&id=b7743430aa1471c9ec054606d2234700e2da3eda> > > Although it was seen on ia64 only, all archs are affected. The other archs > just had more luck. > > > The patch corrects the problem which we have on ia64. The problem on mips > is likely a completely different one. Please could you file a new separate > bug report for that? > > Stephan > >