Fix use-after-free by moving config-reload into the manage cycle

This commit is contained in:
Ben Buhse 2026-01-31 14:08:46 -06:00
commit 4157dd67f6
No known key found for this signature in database
GPG key ID: 7916ACFCD38FD0B4
3 changed files with 33 additions and 14 deletions

View file

@ -21,6 +21,13 @@ xkb_bindings: *XkbBindings,
// WM Configuration // WM Configuration
config: *Config, config: *Config,
/// State consumed in manage() phase, reset at end of manage().
pending_manage: PendingManage = .{},
pub const PendingManage = struct {
config: ?*Config = null,
};
pub fn create( pub fn create(
wl_display: *wl.Display, wl_display: *wl.Display,
wl_registry: *wl.Registry, wl_registry: *wl.Registry,
@ -52,6 +59,22 @@ pub fn destroy(context: *Context) void {
utils.allocator.destroy(context); utils.allocator.destroy(context);
} }
pub fn manage(context: *Context) void {
defer context.pending_manage = .{};
if (context.pending_manage.config) |new_config| {
// Destroy all existing bindings
var it = context.xkb_bindings.bindings.safeIterator(.forward);
while (it.next()) |binding| {
binding.link.remove();
binding.destroy();
}
context.config.destroy();
context.config = new_config;
context.initialized = false;
}
}
const std = @import("std"); const std = @import("std");
const wayland = @import("wayland"); const wayland = @import("wayland");

View file

@ -186,9 +186,12 @@ fn manage_start(wm: *WindowManager) void {
const river_window_manager_v1 = wm.river_window_manager_v1; const river_window_manager_v1 = wm.river_window_manager_v1;
const context = wm.context; const context = wm.context;
// This gets used shortly, so it goes at the beginning
context.manage();
if (!context.initialized) { if (!context.initialized) {
// This code (should) only be run once while initializing the WM, so let's // This code runs during initial startup and after config reloads.
// mark it as cold.
@branchHint(.cold); @branchHint(.cold);
context.initialized = true; context.initialized = true;
@ -220,7 +223,7 @@ fn manage_start(wm: *WindowManager) void {
} }
} }
// Manage the WM itself first // Manage the WM itself before outputs/windows/seats
if (wm.pending_manage.primary_ratio) |primary_ratio| { if (wm.pending_manage.primary_ratio) |primary_ratio| {
// Ratios outside of this range can cause crashes anyways (when doing the layout calulcation) // Ratios outside of this range can cause crashes anyways (when doing the layout calulcation)
std.debug.assert(primary_ratio >= 0.10 and primary_ratio <= 0.90); std.debug.assert(primary_ratio >= 0.10 and primary_ratio <= 0.90);

View file

@ -45,7 +45,7 @@ const XkbBinding = struct {
return xkb_binding; return xkb_binding;
} }
fn destroy(xkb_binding: *XkbBinding) void { pub fn destroy(xkb_binding: *XkbBinding) void {
xkb_binding.xkb_binding_v1.destroy(); xkb_binding.xkb_binding_v1.destroy();
utils.allocator.destroy(xkb_binding); utils.allocator.destroy(xkb_binding);
} }
@ -137,16 +137,9 @@ const XkbBinding = struct {
log.err("Failed to reload Config. Not deleting old one", .{}); log.err("Failed to reload Config. Not deleting old one", .{});
return; return;
}; };
// Clean up old one // Send the config to the WM to handle during next manage
var it = context.xkb_bindings.bindings.safeIterator(.forward); context.pending_manage.config = new_config;
while (it.next()) |binding| { context.wm.river_window_manager_v1.manageDirty();
binding.link.remove();
binding.destroy();
}
context.config.destroy();
// Replace the old one
context.config = new_config;
context.initialized = false;
}, },
.toggle_fullscreen => { .toggle_fullscreen => {
const seat = context.wm.seats.first() orelse return; const seat = context.wm.seats.first() orelse return;