diff options
| author | Colomban Wendling <ban@herbesfolles.org> | 2016-10-09 15:05:28 +0200 |
|---|---|---|
| committer | Colomban Wendling <ban@herbesfolles.org> | 2016-10-09 15:05:28 +0200 |
| commit | cd2c087ed511241a2a3dda164d2f235288cc8343 (patch) | |
| tree | a3e518702f115028fbb0f1f31c664eb0cbd14df3 | |
| parent | 7ebaee87efd56d61db6470de12a720006d64d863 (diff) | |
| download | scintilla-mirror-cd2c087ed511241a2a3dda164d2f235288cc8343.tar.gz | |
GTK: Avoid theoretical access to a destroyed object on async paste
GTK clipboard is asynchronous, which means that the answer to a request
can theoretically arrive at any moment in the future after the request.
This poses a problem as the receiving code has to make sure the object
on which the paste was requested still actually exists by the time the
response arrives, as it could have been destroyed in the meantime.
A possible solution is to add a reference to the object during the
query so that it is kept alive as needed. However, this means that if
the paste request really takes a long time to get answered, it can
prevent the application from destroying the object explicitly,
possibly at the user's request.
So instead, use a helper object adding a weak reference to the object,
and only process the paste request response if the object is still
alive then.
All this is fairly theoretical though, as in practice paste is
generally not effectively asynchronous (GTK tries and calls the
response callback directly in the request call when possible), and when
it is effectively asynchronous, it generally is very fast.
| -rw-r--r-- | gtk/ScintillaGTK.cxx | 65 |
1 files changed, 58 insertions, 7 deletions
diff --git a/gtk/ScintillaGTK.cxx b/gtk/ScintillaGTK.cxx index 08d2714e7..33aec818b 100644 --- a/gtk/ScintillaGTK.cxx +++ b/gtk/ScintillaGTK.cxx @@ -307,8 +307,6 @@ private: static void Destroy(GObject *object); static void SelectionReceived(GtkWidget *widget, GtkSelectionData *selection_data, guint time); - static void ClipboardReceived(GtkClipboard *clipboard, GtkSelectionData *selection_data, - gpointer data); static void SelectionGet(GtkWidget *widget, GtkSelectionData *selection_data, guint info, guint time); static gint SelectionClear(GtkWidget *widget, GdkEventSelection *selection_event); @@ -1452,10 +1450,39 @@ void ScintillaGTK::Copy() { } } -void ScintillaGTK::ClipboardReceived(GtkClipboard *clipboard, GtkSelectionData *selection_data, gpointer data) { - ScintillaGTK *sciThis = static_cast<ScintillaGTK *>(data); - sciThis->ReceivedSelection(selection_data); -} +// helper class to watch a GObject lifetime and get notified when it dies +class GObjectWatcher { + GObject *weakRef; + + void WeakNotifyThis(GObject *obj) { + PLATFORM_ASSERT(obj == weakRef); + + Destroyed(); + weakRef = 0; + } + + static void WeakNotify(gpointer data, GObject *obj) { + static_cast<GObjectWatcher*>(data)->WeakNotifyThis(obj); + } + +public: + GObjectWatcher(GObject *obj) : + weakRef(obj) { + g_object_weak_ref(weakRef, WeakNotify, this); + } + + virtual ~GObjectWatcher() { + if (weakRef) { + g_object_weak_unref(weakRef, WeakNotify, this); + } + } + + virtual void Destroyed() {} + + bool IsDestroyed() { + return weakRef != 0; + } +}; void ScintillaGTK::Paste() { atomSought = atomUTF8; @@ -1463,7 +1490,31 @@ void ScintillaGTK::Paste() { gtk_widget_get_clipboard(GTK_WIDGET(PWidget(wMain)), atomClipboard); if (clipBoard == NULL) return; - gtk_clipboard_request_contents(clipBoard, atomSought, ClipboardReceived, this); + + // helper class for the asynchronous paste not to risk calling in a destroyed ScintillaGTK + class Helper : GObjectWatcher { + ScintillaGTK *sci; + + virtual void Destroyed() { + sci = 0; + } + + public: + Helper(ScintillaGTK *sci_) : + GObjectWatcher(G_OBJECT(PWidget(sci_->wMain))), + sci(sci_) { + } + + static void ClipboardReceived(GtkClipboard *, GtkSelectionData *selection_data, gpointer data) { + Helper *self = static_cast<Helper*>(data); + if (self->sci != 0) { + self->sci->ReceivedSelection(selection_data); + } + delete self; + } + }; + + gtk_clipboard_request_contents(clipBoard, atomSought, Helper::ClipboardReceived, new Helper(this)); } void ScintillaGTK::CreateCallTipWindow(PRectangle rc) { |
