gtk-rs / gtk4-rs

Rust bindings of GTK 4
https://gtk-rs.org/gtk4-rs/
MIT License
1.89k stars 174 forks source link

Segmentation fault with list filtering/deleting/selecting #1673

Closed keirlawson closed 7 months ago

keirlawson commented 7 months ago

Bug description

This code causes a segmentation fault. I've minimised this as best I can, apologies as the snippet is still quite long.

use gtk::glib::clone;
use gtk::{glib, prelude::*, Box, Button, Entry, FilterListModel, ListItem, SingleSelection, StringFilter};
use gtk::{
    Application, ApplicationWindow, Label, ListView,
    ScrolledWindow, SignalListItemFactory, StringList, StringObject, Widget,
};

const APP_ID: &str = "org.gtk_rs.ListWidgets6";

fn main() -> glib::ExitCode {
    let app = Application::builder().application_id(APP_ID).build();

    app.connect_activate(build_ui);

    app.run()
}

fn build_ui(app: &Application) {
    let model: StringList = (0..=100_000).map(|number| number.to_string()).collect();

    let factory = SignalListItemFactory::new();
    factory.connect_setup(move |_, list_item| {
        let label = Label::new(None);
        let list_item = list_item
            .downcast_ref::<ListItem>()
            .expect("Needs to be ListItem");
        list_item.set_child(Some(&label));

        list_item
            .property_expression("item")
            .chain_property::<StringObject>("string")
            .bind(&label, "label", Widget::NONE);
    });

    let delete_button = Button::builder().label("delete top of underlying").build();
    delete_button.connect_clicked(clone!(@strong model, => move |_|{
        model.remove(0);
    }));

    let filter_entry = Entry::builder().build();
    let filter = StringFilter::builder()
        .match_mode(gtk::StringFilterMatchMode::Substring)
        .expression(StringObject::this_expression("string"))
        .build();
    filter_entry.bind_property("text", &filter, "search").build();

    let filter_model = FilterListModel::new(Some(model), Some(filter));
    let selection_model = SingleSelection::new(Some(filter_model));
    selection_model.connect_items_changed(|selection, _, _, _| {
        selection.set_selected(0)
    });

    let list_view = ListView::new(Some(selection_model), Some(factory));

    let scrolled_window = ScrolledWindow::builder()
        .child(&list_view)
        .vexpand(true)
        .build();

    let layout = Box::builder()
        .orientation(gtk::Orientation::Vertical)
        .build();

    layout.append(&filter_entry);
    layout.append(&scrolled_window);
    layout.append(&delete_button);

    let window = ApplicationWindow::builder()
        .application(app)
        .title("My GTK App")
        .default_width(600)
        .default_height(300)
        .child(&layout)
        .build();

    window.present();

    filter_entry.set_text("999");
    delete_button.emit_clicked();
    filter_entry.set_text("");
}
sdroege commented 7 months ago

Backtrace of the crash

#0  gtk_list_item_manager_model_selection_changed_cb (model=<optimized out>, position=280, n_items=719, self=0x555555f3c5e0) at ../gtk/gtklistitemmanager.c:1677
#1  0x00007ffff722a52a in g_closure_invoke (closure=0x555555f3da90, return_value=0x0, n_param_values=3, param_values=0x7fffffff8a50, invocation_hint=0x7fffffff89a0)
    at ../gobject/gclosure.c:832
#2  0x00007ffff7258fec in signal_emit_unlocked_R.isra.0 (node=node@entry=0x7fffffff8b50, detail=detail@entry=0, instance=instance@entry=0x555555f29240, 
    emission_return=emission_return@entry=0x0, instance_and_params=instance_and_params@entry=0x7fffffff8a50) at ../gobject/gsignal.c:3980
#3  0x00007ffff7249d59 in signal_emit_valist_unlocked (instance=instance@entry=0x555555f29240, signal_id=signal_id@entry=139, detail=detail@entry=0, 
    var_args=var_args@entry=0x7fffffff8cc0) at ../gobject/gsignal.c:3612
#4  0x00007ffff7249f91 in g_signal_emit_valist (instance=0x555555f29240, signal_id=139, detail=0, var_args=var_args@entry=0x7fffffff8cc0) at ../gobject/gsignal.c:3355
#5  0x00007ffff724a053 in g_signal_emit (instance=<optimized out>, signal_id=<optimized out>, detail=<optimized out>) at ../gobject/gsignal.c:3675
#6  0x00007ffff7801bba in gtk_single_selection_set_selected (position=0, self=0x555555f29240) at ../gtk/gtksingleselection.c:678
#7  gtk_single_selection_set_selected (self=0x555555f29240, position=<optimized out>) at ../gtk/gtksingleselection.c:648
#8  0x00005555555747f3 in gtk4::auto::single_selection::SingleSelection::set_selected (self=0x7fffffff8e70, position=0) at gtk4/src/auto/single_selection.rs:128
#9  0x0000555555570c8d in foo::build_ui::{closure#2} (selection=0x7fffffff8e70) at foo/src/main.rs:53
#10 0x000055555556c5a9 in gio::auto::list_model::ListModelExt::connect_items_changed::items_changed_trampoline<gtk4::auto::single_selection::SingleSelection, foo::build_ui::{closure_env#2}> (this=0x555555f29240, position=0, removed=280, added=100000, f=0x1)
    at /home/slomo/.cargo/git/checkouts/gtk-rs-core-7be42ca38bd6361c/92dc873/gio/src/auto/list_model.rs:86
#11 0x00007ffff722a52a in g_closure_invoke (closure=0x555555f294b0, return_value=0x0, n_param_values=4, param_values=0x7fffffff9090, invocation_hint=0x7fffffff8fe0)
    at ../gobject/gclosure.c:832
#12 0x00007ffff7258fec in signal_emit_unlocked_R.isra.0 (node=node@entry=0x7fffffff91b0, detail=detail@entry=0, instance=instance@entry=0x555555f29240, 
    emission_return=emission_return@entry=0x0, instance_and_params=instance_and_params@entry=0x7fffffff9090) at ../gobject/gsignal.c:3980
#13 0x00007ffff7249d59 in signal_emit_valist_unlocked (instance=instance@entry=0x555555f29240, signal_id=signal_id@entry=16, detail=detail@entry=0, 
    var_args=var_args@entry=0x7fffffff9320) at ../gobject/gsignal.c:3612
#14 0x00007ffff7249f91 in g_signal_emit_valist (instance=0x555555f29240, signal_id=16, detail=0, var_args=var_args@entry=0x7fffffff9320) at ../gobject/gsignal.c:3355
#15 0x00007ffff724a053 in g_signal_emit (instance=instance@entry=0x555555f29240, signal_id=<optimized out>, detail=detail@entry=0) at ../gobject/gsignal.c:3675
#16 0x00007ffff74f67d4 in g_list_model_items_changed (list=list@entry=0x555555f29240, position=position@entry=0, removed=removed@entry=280, added=added@entry=100000)
    at ../gio/glistmodel.c:323
#17 0x00007ffff77faed5 in gtk_single_selection_items_changed_cb (model=<optimized out>, position=0, removed=280, added=100000, self=0x555555f29240)
    at ../gtk/gtksingleselection.c:296
#18 0x00007ffff722a52a in g_closure_invoke (closure=0x555555f29310, return_value=0x0, n_param_values=4, param_values=0x7fffffff9630, invocation_hint=0x7fffffff9580)
    at ../gobject/gclosure.c:832
#19 0x00007ffff7258fec in signal_emit_unlocked_R.isra.0 (node=node@entry=0x7fffffff9750, detail=detail@entry=0, instance=instance@entry=0x555555f28490, 
    emission_return=emission_return@entry=0x0, instance_and_params=instance_and_params@entry=0x7fffffff9630) at ../gobject/gsignal.c:3980
#20 0x00007ffff7249d59 in signal_emit_valist_unlocked (instance=instance@entry=0x555555f28490, signal_id=signal_id@entry=16, detail=detail@entry=0, 
    var_args=var_args@entry=0x7fffffff98c0) at ../gobject/gsignal.c:3612
--Type <RET> for more, q to quit, c to continue without paging--
#21 0x00007ffff7249f91 in g_signal_emit_valist (instance=0x555555f28490, signal_id=16, detail=0, var_args=var_args@entry=0x7fffffff98c0) at ../gobject/gsignal.c:3355
#22 0x00007ffff724a053 in g_signal_emit (instance=instance@entry=0x555555f28490, signal_id=<optimized out>, detail=detail@entry=0) at ../gobject/gsignal.c:3675
#23 0x00007ffff74f67d4 in g_list_model_items_changed (list=list@entry=0x555555f28490, position=<optimized out>, removed=<optimized out>, added=<optimized out>)
    at ../gio/glistmodel.c:323
#24 0x00007ffff773d1a2 in gtk_filter_list_model_refilter (self=0x555555f28490, change=<optimized out>) at ../gtk/gtkfilterlistmodel.c:547
#25 0x00007ffff7228b38 in g_cclosure_marshal_VOID__ENUMv (closure=<optimized out>, return_value=<optimized out>, instance=<optimized out>, args=<optimized out>, 
    marshal_data=<optimized out>, n_params=<optimized out>, param_types=0x555555f1dfe0) at ../gobject/gmarshal.c:1028
#26 0x00007ffff7249e85 in _g_closure_invoke_va (param_types=0x555555f1dfe0, n_params=<optimized out>, args=0x7fffffff9c50, instance=0x555555f26fa0, return_value=0x0, 
    closure=0x555555f285d0) at ../gobject/gclosure.c:895
#27 signal_emit_valist_unlocked (instance=instance@entry=0x555555f26fa0, signal_id=signal_id@entry=137, detail=detail@entry=0, var_args=var_args@entry=0x7fffffff9c50)
    at ../gobject/gsignal.c:3516
#28 0x00007ffff7249f91 in g_signal_emit_valist (instance=0x555555f26fa0, signal_id=137, detail=0, var_args=var_args@entry=0x7fffffff9c50) at ../gobject/gsignal.c:3355
#29 0x00007ffff724a053 in g_signal_emit (instance=<optimized out>, signal_id=<optimized out>, detail=<optimized out>) at ../gobject/gsignal.c:3675
#30 0x00007ffff7817467 in gtk_string_filter_set_search (search=<optimized out>, self=0x555555f26fa0) at ../gtk/gtkstringfilter.c:377
#31 gtk_string_filter_set_search (self=0x555555f26fa0, search=<optimized out>) at ../gtk/gtkstringfilter.c:350
#32 0x00007ffff723a75a in object_set_property (object=object@entry=0x555555f26fa0, pspec=0x555555f26ed0, value=value@entry=0x7fffffff9e50, nqueue=nqueue@entry=0x0, 
    user_specified=user_specified@entry=1) at ../gobject/gobject.c:1811
#33 0x00007ffff723d697 in g_object_setv (values=<optimized out>, names=<optimized out>, n_properties=<optimized out>, object=0x555555f26fa0) at ../gobject/gobject.c:2722
#34 g_object_setv (object=0x555555f26fa0, n_properties=<optimized out>, names=<optimized out>, values=<optimized out>) at ../gobject/gobject.c:2693
#35 0x00007ffff723d8d1 in g_object_set_property (object=<optimized out>, property_name=<optimized out>, value=<optimized out>) at ../gobject/gobject.c:3022
#36 0x00007ffff722bab7 in on_source_notify (source=<optimized out>, pspec=<optimized out>, context=<optimized out>) at ../gobject/gbinding.c:556
#37 0x00007ffff722a52a in g_closure_invoke (closure=0x555555f27aa0, return_value=0x0, n_param_values=2, param_values=0x7fffffffa0b0, invocation_hint=0x7fffffffa000)
    at ../gobject/gclosure.c:832
#38 0x00007ffff7258fec in signal_emit_unlocked_R.isra.0 (node=node@entry=0x7fffffffa1a0, detail=detail@entry=600, instance=instance@entry=0x555555f11130, 
    emission_return=emission_return@entry=0x0, instance_and_params=instance_and_params@entry=0x7fffffffa0b0) at ../gobject/gsignal.c:3980
#39 0x00007ffff7249d59 in signal_emit_valist_unlocked (instance=instance@entry=0x555555f11130, signal_id=signal_id@entry=1, detail=detail@entry=600, 
    var_args=var_args@entry=0x7fffffffa310) at ../gobject/gsignal.c:3612
#40 0x00007ffff7249f91 in g_signal_emit_valist (instance=0x555555f11130, signal_id=1, detail=600, var_args=var_args@entry=0x7fffffffa310) at ../gobject/gsignal.c:3355
#41 0x00007ffff724a053 in g_signal_emit (instance=<optimized out>, signal_id=<optimized out>, detail=<optimized out>) at ../gobject/gsignal.c:3675
#42 0x00007ffff72356f4 in g_object_dispatch_properties_changed (object=0x555555f11130, n_pspecs=<optimized out>, pspecs=<optimized out>) at ../gobject/gobject.c:1427
#43 0x00007ffff722aaf0 in g_object_notify_queue_thaw (object=0x555555f11130, nqueue=<optimized out>) at ../gobject/gobject.c:358
#44 0x00007ffff7238dfe in g_object_thaw_notify (object=0x555555f11130) at ../gobject/gobject.c:1700
#45 g_object_thaw_notify (object=0x555555f11130) at ../gobject/gobject.c:1675
#46 0x00007ffff7714d04 in gtk_editable_set_text (text=0x5555555e1e97 "", editable=0x555555f11130) at ../gtk/gtkeditable.c:602
--Type <RET> for more, q to quit, c to continue without paging--
#47 gtk_editable_set_text (editable=0x555555f11130, text=0x5555555e1e97 "") at ../gtk/gtkeditable.c:591
#48 0x000055555556e923 in gtk4::auto::editable::EditableExt::set_text<gtk4::auto::entry::Entry> (self=0x7fffffffab90, text=...) at gtk4/src/auto/editable.rs:228
#49 0x000055555556d112 in foo::build_ui (app=0x7fffffffd340) at foo/src/main.rs:82
#50 0x000055555556ee29 in core::ops::function::Fn::call<fn(&gtk4::auto::application::Application), (&gtk4::auto::application::Application)> ()
    at /rustc/aedd173a2c086e558c2b66d3743b344f977621a7/library/core/src/ops/function.rs:79
#51 0x000055555556b4ec in gio::auto::application::ApplicationExt::connect_activate::activate_trampoline<gtk4::auto::application::Application, fn(&gtk4::auto::application::Application)> (this=0x5555556321e0, f=0x1) at /home/slomo/.cargo/git/checkouts/gtk-rs-core-7be42ca38bd6361c/92dc873/gio/src/auto/application.rs:458
#52 0x00007ffff722a52a in g_closure_invoke (closure=0x5555556340b0, return_value=0x0, n_param_values=1, param_values=0x7fffffffd550, invocation_hint=0x7fffffffd4a0)
    at ../gobject/gclosure.c:832
#53 0x00007ffff7258fec in signal_emit_unlocked_R.isra.0 (node=node@entry=0x7fffffffd620, detail=detail@entry=0, instance=instance@entry=0x5555556321e0, 
    emission_return=emission_return@entry=0x0, instance_and_params=instance_and_params@entry=0x7fffffffd550) at ../gobject/gsignal.c:3980
#54 0x00007ffff7249d59 in signal_emit_valist_unlocked (instance=instance@entry=0x5555556321e0, signal_id=signal_id@entry=8, detail=detail@entry=0, 
    var_args=var_args@entry=0x7fffffffd790) at ../gobject/gsignal.c:3612
#55 0x00007ffff7249f91 in g_signal_emit_valist (instance=0x5555556321e0, signal_id=8, detail=0, var_args=var_args@entry=0x7fffffffd790) at ../gobject/gsignal.c:3355
#56 0x00007ffff724a053 in g_signal_emit (instance=instance@entry=0x5555556321e0, signal_id=<optimized out>, detail=detail@entry=0) at ../gobject/gsignal.c:3675
#57 0x00007ffff750d8c7 in g_application_activate (application=0x5555556321e0) at ../gio/gapplication.c:2316
#58 0x00007ffff7512718 in g_application_real_local_command_line (application=0x5555556321e0, arguments=0x7fffffffd8e8, exit_status=0x7fffffffd8e4) at ../gio/gapplication.c:1152
#59 0x00007ffff75128c3 in g_application_run (application=0x5555556321e0, argc=<optimized out>, argv=0x555555634170) at ../gio/gapplication.c:2546
#60 0x0000555555567a6a in gio::application::ApplicationExtManual::run_with_args<gtk4::auto::application::Application, alloc::string::String> (self=0x7fffffffdad8, args=...)
    at /home/slomo/.cargo/git/checkouts/gtk-rs-core-7be42ca38bd6361c/92dc873/gio/src/application.rs:29
#61 0x0000555555567b6a in gio::application::ApplicationExtManual::run<gtk4::auto::application::Application> (self=0x7fffffffdad8)
    at /home/slomo/.cargo/git/checkouts/gtk-rs-core-7be42ca38bd6361c/92dc873/gio/src/application.rs:22
#62 0x000055555556c9b9 in foo::main () at foo/src/main.rs:17
sdroege commented 7 months ago

See https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/7095

benjamin-otte commented 7 months ago
    selection_model.connect_items_changed(|selection, _, _, _| {
        selection.set_selected(0)
    });

This is going to cause reentrancy and therefor isn't allowed.

The code is triggering a selection change while the previous item change has not finished propagating to all other change handlers.

keirlawson commented 7 months ago

Ok, what is the correct approach to trigger a selection change on item change?

benjamin-otte commented 7 months ago

The absolutely best and most correct way is to write a SelectionModel that does what you want. GtkSingleSelection with does trigger a selection change on item change, just not the one you want I guess.

The less involved but somewhat hacky way would be to add a new main loop source via g_idle_add or so that runs after the items-changed has finished.

And the very-hacky-but-maybe-good-enough-i-hope-it-doesnt-break method would be to connect_after() to items-changed.

Also, I have no idea why you want to trigger a selection-changed - do you just want set_autoselect()?

keirlawson commented 7 months ago

The use case is I am writing a notes app where I want to keep the current note "selected" so long as the note list is not filtered such that it is not visible, and restore that selection when the filter is cleared such that it is visible again

benjamin-otte commented 7 months ago

I wrote some code for a FixedSelection for that use case and people have asked about it a few times, but nobody ended up using it so we didn't merge it. https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/4140 has the code.