Crash due to postponed focus loss
Original Reporter info from Mantis: maaartinus
-
Reporter name: Maaartin
Original Reporter info from Mantis: maaartinus
- Reporter name: Maaartin
Description:
The postponed focus loss implemented in gtk2callback.inc can lead to accessing a freed widget (and to a crash).
Steps to reproduce:
Compile and start the attached project, make sure that the TEdit is focused and use the TLabel to make the TGroup invisible. Valgrind reports
Invalid read of size 4
at 0x471A782: g_type_check_instance_is_a (gtype.c:3964)
...
Address 0x5a4dbb0 is 88 bytes inside a block of size 244 free'd
This is the address of the freed widget associated with the TEdit. The check at gtype.c:3967
node = lookup_type_node_I (type_instance->g_class->g_type);
accesses a location pointed to by the freed memory. Using a debugging version of glib2, all freed memory gets set to 0xAA and you'll get
Invalid read of size 4
at 0x471A78C: g_type_check_instance_is_a (gtype.c:3967)
...
Address 0xaaaaaaaa is not stack'd, malloc'd or (recently) free'd
and an access violation. Without the debugging version, the outcome is essentially random.
Additional information:
The crash comes from the TEdit losing focus in a postponed manner (on idle). That's why the TButton does not lead to a crash, as the focus is transferred immediately (and the button gets no special postponed handling). Using the TLabel (or another non-focusable control) leads to
function GTKKillFocusCBAfter(widget: PGtkWidget; event:PGdkEventFocus; data: gPointer) : GBoolean; cdecl;
at https://github.com/alrieckert/lazarus/blob/26cc81599e6af68cb0a0a3a8c4cf2f6ef155ef78/lcl/interfaces/gtk2/gtk2callback.inc#L1159
adding an event to be executed when idle. This event contains the address of the widget which gets freed.
Now, inside of
function GtkEntryDelayClearCursorPos(AGtkWidget: Pointer): GBoolean; cdecl;
at https://github.com/alrieckert/lazarus/blob/26cc81599e6af68cb0a0a3a8c4cf2f6ef155ef78/lcl/interfaces/gtk2/gtk2callback.inc#L1084
the check "type_instance->g_class->g_type" called from
Result := (AGtkWidget <> nil) and (GTK_IS_WIDGET(AGtkWidget));
follows a pointer from the the freed memory to nirvana and crashes randomly.
The overcomplicated make-invisible + reparent + make-visible sequence seem to be necessary for this to happen here, but I'd bet there are other ways, too. I guess, the reference counting or whatever gets used to prevent crashes gets fooled by by the reparenting.
I guess, cancelling the event is impossible, so either the widget must be kept until the event gets delivered or some hacky widget-independent tracking of invalid addresses is needed.
Mantis conversion info:
- Mantis ID: 28840
- Version: 1.4.2