Summary
GNOME Shell crashes with SIGSEGV (signal 11) when the changed::skin-tone / changed::gender settings handlers registered in EmojiSearchItem fire on a stale instance whose St.Entry has already been disposed. The handlers are connected in the EmojiSearchItem constructor but are never disconnected, so they outlive the entry they reference. When the setting later changes (e.g. clicking a skin-tone button), the orphaned handler calls this.searchEntry.get_text() on a destroyed St.Entry, which hits clutter_text_get_text: assertion 'CLUTTER_IS_TEXT (self)' failed and segfaults the whole shell.
This was introduced by #126 (skin-tone matching on search, shipped in v33) and is still present in v34 (latest).
Environment
|
|
| Extension version |
34 (latest; bug also in master) |
| GNOME Shell |
50.1 (Ubuntu 26.04, Wayland) |
| Mutter |
libmutter-clutter-18.so.0 |
| Signal |
11 (SIGSEGV) |
Crash traces
GJS stack trace (from the system journal):
Object St.Entry (0x...), has been already disposed — impossible to access it.
#0 emojiSearchItem.js:112 (let searchedText = this.searchEntry.get_text();)
#1 emojiSearchItem.js:36 (changed::skin-tone handler → this._onSearchTextChanged())
#3 emojiOptionsBar.js:112 (this._settings.set_int("skin-tone", 0);)
clutter_text_get_text: assertion 'CLUTTER_IS_TEXT (self)' failed
GNOME Shell crashed with signal 11
JS ERROR: TypeError: can't access property "toLowerCase", searchedText is null
_onSearchTextChanged@emojiSearchItem.js:118
EmojiSearchItem/<@emojiSearchItem.js:36
_buildToneButton/<@emojiOptionsBar.js:112
Native backtrace (from the apport core dump):
StacktraceTop:
clutter_text_get_text () from /usr/lib/x86_64-linux-gnu/mutter-18/libmutter-clutter-18.so.0
?? () from libffi.so.8
ffi_call () from libffi.so.8
?? () from libgjs.so.0
The native frame confirms the JS call searchEntry.get_text() reaches clutter_text_get_text() on a Clutter.Text that has already been freed, via the GJS → libffi → C bridge.
Root cause
In emojiSearchItem.js, the constructor connects two settings handlers but does not store the handler IDs and never disconnects them:
// emojiSearchItem.js, constructor
if (this._settings && this._settings.connect) {
this._settings.connect('changed::skin-tone', () => {
this._onSearchTextChanged(); // line 36
});
this._settings.connect('changed::gender', () => {
this._onSearchTextChanged();
});
}
EmojiSearchItem has no destroy() method, and two code paths drop a searchItem (and dispose its St.Entry) without disconnecting these handlers:
updateNbCols() (extension.js) replaces the instance and leaks the old one:
this.searchItem = new EmojiSearchItem(this, nbCols); // old instance + its skin-tone/gender handlers leaked
disable() (extension.js) destroys super_btn (which disposes the entry) and nulls searchItem/_settings, but never disconnects the skin-tone/gender handlers:
this.super_btn.destroy(); // disposes searchEntry
this.searchItem = null; // handlers on _settings still connected
this._settings = null;
(Note: disable() also forgets this._settings.disconnect(this.signaux[3]) — the changed::nbcols handler is leaked too.)
Because GNOME caches Gio.Settings backends per schema, a write through any live wrapper for org.gnome.shell.extensions.emoji-copy notifies all still-connected handlers — including the orphaned one bound to a disposed entry. The handler then runs _onSearchTextChanged(), whose very first line dereferences the dead entry:
_onSearchTextChanged() {
let searchedText = this.searchEntry.get_text(); // disposed St.Entry → CLUTTER_IS_TEXT assertion → SIGSEGV
...
searchedText = searchedText.toLowerCase().trim(); // get_text() returns null on disposed entry → TypeError
The TypeError: ... toLowerCase ... null is the JS-visible symptom; the fatal event is the native use-after-free in clutter_text_get_text.
Steps to reproduce
- Install/enable emoji-copy v33 or v34.
- Trigger at least one
disable → enable cycle without restarting GNOME Shell — e.g. lock and unlock the screen (Super+L), suspend/resume, toggle the extension off/on, or change "number of columns" in Preferences (the updateNbCols() path). This leaves an orphaned skin-tone/gender handler bound to a now-disposed St.Entry.
- Open the emoji panel and click a skin-tone (or gender) button.
- → GNOME Shell crashes with SIGSEGV; the whole session is torn down.
In practice this happens silently after any lock/unlock or suspend/resume during a normal work session, then the next skin-tone click crashes the desktop.
Proposed fix
Track the handler IDs, add a destroy() that disconnects them, call it whenever a searchItem is dropped, and make _onSearchTextChanged() defensive.
emojiSearchItem.js — store IDs in the constructor:
this._settingsHandlerIds = [];
if (this._settings && this._settings.connect) {
this._settingsHandlerIds.push(
this._settings.connect('changed::skin-tone', () => this._onSearchTextChanged()),
this._settings.connect('changed::gender', () => this._onSearchTextChanged()),
);
}
Add a destroy():
destroy() {
if (this._settings && this._settingsHandlerIds) {
for (const id of this._settingsHandlerIds)
this._settings.disconnect(id);
}
this._settingsHandlerIds = [];
this.super_item?.destroy();
}
Guard _onSearchTextChanged() (belt-and-suspenders):
_onSearchTextChanged() {
let searchedText = this.searchEntry?.get_text();
if (searchedText === null || searchedText === undefined || searchedText === "") {
this._buildRecents();
this._updateSensitivity();
return;
}
searchedText = searchedText.toLowerCase().trim();
...
}
extension.js — destroy the old instance before replacing / disabling, and fix the leaked nbcols handler:
updateNbCols() {
let nbCols = this._settings.get_int("nbcols");
this.emojiCategories.forEach((c) => c.setNbCols(nbCols));
this.searchItem?.destroy();
this.searchItem = new EmojiSearchItem(this, nbCols);
}
disable() {
this.searchItem.saveRecents();
if (this._settings.get_boolean("active-keybind"))
Main.wm.removeKeybinding("emoji-keybind");
this._settings.disconnect(this.signaux[0]);
this._settings.disconnect(this.signaux[1]);
this._settings.disconnect(this.signaux[2]);
this._settings.disconnect(this.signaux[3]); // was leaked
if (this.timeoutSourceId) {
GLib.Source.remove(this.timeoutSourceId);
this.timeoutSourceId = null;
}
this.sqlite.destroy();
this.searchItem.destroy(); // disconnect skin-tone/gender handlers
this.super_btn.destroy();
this.searchItem = null;
this._settings = null;
this.super_btn = null;
this.sqlite = null;
this.signaux = [];
}
(Minor, while in there: enable() calls searchItem._recentlyUsedInit() a second time at line 87 even though the constructor already calls it at line 46 — the duplicate rebuilds _recents and is harmless but unnecessary.)
Happy to open a PR if that helps.
Summary
GNOME Shell crashes with SIGSEGV (signal 11) when the
changed::skin-tone/changed::gendersettings handlers registered inEmojiSearchItemfire on a stale instance whoseSt.Entryhas already been disposed. The handlers are connected in theEmojiSearchItemconstructor but are never disconnected, so they outlive the entry they reference. When the setting later changes (e.g. clicking a skin-tone button), the orphaned handler callsthis.searchEntry.get_text()on a destroyedSt.Entry, which hitsclutter_text_get_text: assertion 'CLUTTER_IS_TEXT (self)' failedand segfaults the whole shell.This was introduced by #126 (skin-tone matching on search, shipped in v33) and is still present in v34 (latest).
Environment
libmutter-clutter-18.so.0Crash traces
GJS stack trace (from the system journal):
Native backtrace (from the apport core dump):
The native frame confirms the JS call
searchEntry.get_text()reachesclutter_text_get_text()on aClutter.Textthat has already been freed, via the GJS → libffi → C bridge.Root cause
In
emojiSearchItem.js, the constructor connects two settings handlers but does not store the handler IDs and never disconnects them:EmojiSearchItemhas nodestroy()method, and two code paths drop asearchItem(and dispose itsSt.Entry) without disconnecting these handlers:updateNbCols()(extension.js) replaces the instance and leaks the old one:disable()(extension.js) destroyssuper_btn(which disposes the entry) and nullssearchItem/_settings, but never disconnects the skin-tone/gender handlers:disable()also forgetsthis._settings.disconnect(this.signaux[3])— thechanged::nbcolshandler is leaked too.)Because GNOME caches
Gio.Settingsbackends per schema, a write through any live wrapper fororg.gnome.shell.extensions.emoji-copynotifies all still-connected handlers — including the orphaned one bound to a disposed entry. The handler then runs_onSearchTextChanged(), whose very first line dereferences the dead entry:The
TypeError: ... toLowerCase ... nullis the JS-visible symptom; the fatal event is the native use-after-free inclutter_text_get_text.Steps to reproduce
disable → enablecycle without restarting GNOME Shell — e.g. lock and unlock the screen (Super+L), suspend/resume, toggle the extension off/on, or change "number of columns" in Preferences (theupdateNbCols()path). This leaves an orphaned skin-tone/gender handler bound to a now-disposedSt.Entry.In practice this happens silently after any lock/unlock or suspend/resume during a normal work session, then the next skin-tone click crashes the desktop.
Proposed fix
Track the handler IDs, add a
destroy()that disconnects them, call it whenever asearchItemis dropped, and make_onSearchTextChanged()defensive.emojiSearchItem.js— store IDs in the constructor:Add a
destroy():Guard
_onSearchTextChanged()(belt-and-suspenders):extension.js— destroy the old instance before replacing / disabling, and fix the leakednbcolshandler:(Minor, while in there:
enable()callssearchItem._recentlyUsedInit()a second time at line 87 even though the constructor already calls it at line 46 — the duplicate rebuilds_recentsand is harmless but unnecessary.)Happy to open a PR if that helps.