@samitouri / QOSamiQemu / commits / 0457762e9e

irq: add per-IRQ observer to fix qemu_irq_intercept_in leak

qemu_irq_intercept_in() saves original IRQ handlers by allocating new QOM objects, which are never freed. On a PC machine, this leaks IRQ objects (one per IOAPIC pin) on every qtest run. Rather than tracking allocations to free later, avoid them: add an "observer" field to IRQState, called by qemu_set_irq() after the real handler. Interception sets the observer instead of rewriting handlers, so there's nothing to save and nothing to leak. Fix qemu_notirq() to route through qemu_set_irq() so inverted IRQs trigger observers too. Drop the LSan suppression. Reviewed-by: Fabiano Rosas <farosas@suse.de> Reviewed-by: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com> Reviewed-by: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com> Message-ID: <20260623-b4-ui-v4-3-4656aec3398d@redhat.com>

Marc-André Lureau committed Jun 23, 2026 at 11:44 UTC 0457762e9e08be216567c33ca89ce7523a1213ba
4 files changed +10 -21
hw/core/irq.c
+6 -6
@@ -32,6 +32,9 @@ void qemu_set_irq(qemu_irq irq, int level)
32 return;
33
34 irq->handler(irq->opaque, irq->n, level);
35 + if (unlikely(irq->observer)) {
36 + irq->observer(irq->opaque, irq->n, level);
37 + }
38 }
39
40 static void init_irq_fields(IRQState *irq, qemu_irq_handler handler,
@@ -111,7 +114,7 @@ static void qemu_notirq(void *opaque, int line, int level)
114 {
115 IRQState *irq = opaque;
116
114 - irq->handler(irq->opaque, irq->n, !level);
117 + qemu_set_irq(irq, !level);
118 }
119
120 qemu_irq qemu_irq_invert(qemu_irq irq)
@@ -121,14 +124,11 @@ qemu_irq qemu_irq_invert(qemu_irq irq)
124 return qemu_allocate_irq(qemu_notirq, irq, 0);
125 }
126
124 -void qemu_irq_intercept_in(qemu_irq *gpio_in, qemu_irq_handler handler, int n)
127 +void qemu_irq_set_observer(qemu_irq *gpio_in, qemu_irq_handler handler, int n)
128 {
129 int i;
127 - qemu_irq *old_irqs = qemu_allocate_irqs(NULL, NULL, n);
130 for (i = 0; i < n; i++) {
129 - *old_irqs[i] = *gpio_in[i];
130 - gpio_in[i]->handler = handler;
131 - gpio_in[i]->opaque = &old_irqs[i];
131 + gpio_in[i]->observer = handler;
132 }
133 }
134
include/hw/core/irq.h
+3 -3
@@ -14,6 +14,7 @@ struct IRQState {
14 qemu_irq_handler handler;
15 void *opaque;
16 int n;
17 + qemu_irq_handler observer;
18 };
19
20 void qemu_set_irq(qemu_irq irq, int level);
@@ -96,9 +97,8 @@ void qemu_free_irq(qemu_irq irq);
97 /* Returns a new IRQ with opposite polarity. */
98 qemu_irq qemu_irq_invert(qemu_irq irq);
99
99 -/* For internal use in qtest. Similar to qemu_irq_split, but operating
100 - on an existing vector of qemu_irq. */
101 -void qemu_irq_intercept_in(qemu_irq *gpio_in, qemu_irq_handler handler, int n);
100 +/* For internal use in qtest. */
101 +void qemu_irq_set_observer(qemu_irq *gpio_in, qemu_irq_handler handler, int n);
102
103 /**
104 * qemu_irq_is_connected: Return true if IRQ line is wired up
scripts/lsan_suppressions.txt
-8
@@ -16,11 +16,3 @@ leak:/lib64/libxkbcommon.so.0
16 # https://github.com/GNOME/glib/blob/main/tools/glib.supp
17 # This avoids false positive leak reports for the qga-ssh-test.
18 leak:g_set_user_dirs
19 -
20 -# qemu_irq_intercept_in is only used by the qtest harness, and
21 -# its API inherently involves a leak.
22 -# While we could keep track of the old IRQ data structure
23 -# in order to free it, it doesn't seem very important to fix
24 -# since it is only used by the qtest test harness.
25 -# Just ignore the leak, at least for the moment.
26 -leak:qemu_irq_intercept_in
system/qtest.c
+1 -4
@@ -326,9 +326,6 @@ void qtest_sendf(CharFrontend *chr, const char *fmt, ...)
326
327 static void qtest_irq_handler(void *opaque, int n, int level)
328 {
329 - qemu_irq old_irq = *(qemu_irq *)opaque;
330 - qemu_set_irq(old_irq, level);
331 -
329 if (irq_levels[n] != level) {
330 CharFrontend *chr = &qtest->qtest_chr;
331 irq_levels[n] = level;
@@ -421,7 +418,7 @@ static void qtest_process_command(CharFrontend *chr, gchar **words)
418 interception_succeeded = true;
419 }
420 } else {
424 - qemu_irq_intercept_in(ngl->in, qtest_irq_handler,
421 + qemu_irq_set_observer(ngl->in, qtest_irq_handler,
422 ngl->num_in);
423 interception_succeeded = true;
424 }