Nilorea Library
C utilities for networking, threading, graphics
Loading...
Searching...
No Matches
ex_gui_reentrant.c

n_gui_process_event re-entrancy regression test (headless).

n_gui_process_event re-entrancy regression test (headless). A button on_click that opens/raises another window reorders ctx->windows (n_gui_raise_window removes + re-pushes + re-sorts the window list). The mouse-release handler in n_gui_process_event iterates ctx->windows while firing that callback; firing it inline freed the LIST_NODE the iterator had already cached as "next", a use-after-free that crashed on the following step. This test reproduces the exact click (press then release over the button) headlessly and asserts the callback fired and the process survives, which under ASan fails loudly if the deferred-dispatch fix regresses.

Author
Castagnier Mickael
Version
1.0
Date
28/06/2026
/*
* Nilorea Library
* Copyright (C) 2005-2026 Castagnier Mickael
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
#define ALLEGRO_UNSTABLE 1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "nilorea/n_log.h"
#include "nilorea/n_gui.h"
static int log_level = LOG_ERR;
static int failures = 0;
/* the button callback raises the popup window, reordering ctx->windows */
static N_GUI_CTX* g_gui = NULL;
static int g_popup_win = -1;
static int g_click_count = 0;
static void on_open_popup(int widget_id, void* user_data) {
(void)widget_id;
(void)user_data;
if (g_gui && g_popup_win >= 0) {
n_gui_raise_window(g_gui, g_popup_win); /* removes + re-pushes + re-sorts */
}
}
static void expect_true(const char* label, int cond) {
if (!cond) {
n_log(LOG_ERR, "%s: condition false", label);
} else {
n_log(LOG_INFO, "%s: OK", label);
}
}
static int process_args(int argc, char** argv) {
int opt;
while ((opt = getopt(argc, argv, "V:")) != -1) {
switch (opt) {
case 'V':
if (!strcmp("LOG_NULL", optarg))
else if (!strcmp("LOG_NOTICE", optarg))
else if (!strcmp("LOG_INFO", optarg))
else if (!strcmp("LOG_ERR", optarg))
else if (!strcmp("LOG_DEBUG", optarg))
else
return -1;
break;
default:
return -1;
}
}
return 0;
}
/* Build a left-button mouse event at (x, y). */
static ALLEGRO_EVENT mouse_event(int type, int x, int y) {
ALLEGRO_EVENT ev;
memset(&ev, 0, sizeof(ev));
ev.type = (unsigned int)type;
ev.mouse.x = x;
ev.mouse.y = y;
ev.mouse.button = 1;
return ev;
}
int main(int argc, char** argv) {
if (process_args(argc, argv) != 0) {
fprintf(stderr, "Usage: %s [-V LOG_LEVEL]\n", argv[0]);
return 2;
}
if (!al_init()) {
n_log(LOG_ERR, "al_init failed");
return 1;
}
if (!al_init_font_addon()) {
n_log(LOG_ERR, "al_init_font_addon failed");
return 1;
}
/* Builtin font does not need a display. */
ALLEGRO_FONT* font = al_create_builtin_font();
if (!font) {
n_log(LOG_ERR, "al_create_builtin_font failed");
return 1;
}
if (!gui) {
n_log(LOG_ERR, "n_gui_new_ctx failed");
al_destroy_font(font);
return 1;
}
n_gui_set_display_size(gui, 800.0f, 600.0f);
/* Window A (frameless so the button's screen coords are trivial) holds the
button; the popup P sits clear of the button and after A in z-order, so
its node is the iterator's cached "next" when the click fires. */
int win_a = n_gui_add_window(gui, "A", 0.0f, 0.0f, 200.0f, 150.0f);
int btn = n_gui_add_button(gui, win_a, "Open", 10.0f, 10.0f, 80.0f, 30.0f, N_GUI_SHAPE_ROUNDED, on_open_popup, NULL);
g_popup_win = n_gui_add_window(gui, "P", 300.0f, 0.0f, 200.0f, 150.0f);
expect_true("windows + button created", win_a >= 0 && g_popup_win >= 0 && btn >= 0);
/* Press then release over the button centre (50, 25). The release fires the
callback that raises P; before the fix this freed the cached "next" node
and the loop dereferenced it. */
{
ALLEGRO_EVENT down = mouse_event(ALLEGRO_EVENT_MOUSE_BUTTON_DOWN, 50, 25);
ALLEGRO_EVENT up = mouse_event(ALLEGRO_EVENT_MOUSE_BUTTON_UP, 50, 25);
n_gui_process_event(gui, up); /* must not crash */
}
expect_true("button callback fired exactly once", g_click_count == 1);
expect_true("popup is open after the click", n_gui_window_is_open(gui, g_popup_win));
/* A second identical click must also be safe and fire again. */
{
ALLEGRO_EVENT down = mouse_event(ALLEGRO_EVENT_MOUSE_BUTTON_DOWN, 50, 25);
ALLEGRO_EVENT up = mouse_event(ALLEGRO_EVENT_MOUSE_BUTTON_UP, 50, 25);
}
expect_true("button callback fired again", g_click_count == 2);
al_destroy_font(font);
if (failures > 0) {
n_log(LOG_ERR, "ex_gui_reentrant: %d check(s) failed", failures);
return 1;
}
n_log(LOG_NOTICE, "ex_gui_reentrant: all checks passed");
return 0;
}
void process_args(int argc, char **argv)
static int failures
Definition ex_atlas.c:51
int main(void)
Definition ex_atlas.c:117
static void expect_true(const char *label, int cond)
Definition ex_atlas.c:53
static int log_level
static ALLEGRO_EVENT mouse_event(int type, int x, int y, int dz)
Build a left-button mouse event at (x, y).
static N_GUI_CTX * gui
static N_GUI_CTX * g_gui
static int g_click_count
static void on_open_popup(int widget_id, void *user_data)
static int g_popup_win
static void type(N_GUI_CTX *gui, const char *s)
Type a whole string into whatever has focus.
FONT * font
the default 8x8 font, valid after allegro_init()
void n_gui_set_display_size(N_GUI_CTX *ctx, float w, float h)
set the display (viewport) size so global scrollbars can be computed.
Definition n_gui.c:12348
void n_gui_raise_window(N_GUI_CTX *ctx, int window_id)
Bring a window to the front (top of draw order).
Definition n_gui.c:2058
int n_gui_process_event(N_GUI_CTX *ctx, ALLEGRO_EVENT event)
Process an allegro event through the GUI system.
Definition n_gui.c:13611
void n_gui_window_set_zorder(N_GUI_CTX *ctx, int window_id, int z_mode, int z_value)
Set window z-order mode and value.
Definition n_gui.c:2403
#define N_GUI_WIN_FRAMELESS
frameless window: no title bar drawn, drag via window body unless N_GUI_WIN_FIXED_POSITION is also se...
Definition n_gui.h:276
int n_gui_window_is_open(N_GUI_CTX *ctx, int window_id)
Check if a window is currently visible.
Definition n_gui.c:2506
int n_gui_add_window(N_GUI_CTX *ctx, const char *title, float x, float y, float w, float h)
Add a new pseudo-window to the context.
Definition n_gui.c:1277
N_GUI_CTX * n_gui_new_ctx(ALLEGRO_FONT *default_font)
Create a new GUI context.
Definition n_gui.c:1103
void n_gui_destroy_ctx(N_GUI_CTX **ctx)
Destroy a GUI context and all its windows/widgets.
Definition n_gui.c:1209
void n_gui_open_window(N_GUI_CTX *ctx, int window_id)
Open (show) a window.
Definition n_gui.c:1436
#define N_GUI_ZORDER_POPUP
popup/modal dialogs: drawn above EVERYTHING including ALWAYS_ON_TOP windows.
Definition n_gui.h:369
#define N_GUI_SHAPE_ROUNDED
rounded rectangle shape
Definition n_gui.h:172
void n_gui_window_set_flags(N_GUI_CTX *ctx, int window_id, int flags)
Set feature flags on a window.
Definition n_gui.c:2515
int n_gui_add_button(N_GUI_CTX *ctx, int window_id, const char *label, float x, float y, float w, float h, int shape, void(*on_click)(int, void *), void *user_data)
Add a button widget to a window.
Definition n_gui.c:2720
The top-level GUI context that holds all windows.
Definition n_gui.h:1669
#define n_log(__LEVEL__,...)
Logging function wrapper to get line and func.
Definition n_log.h:113
#define LOG_DEBUG
debug-level messages
Definition n_log.h:108
#define LOG_ERR
error conditions
Definition n_log.h:100
void set_log_level(const int log_level)
Set the global log level value ( static int LOG_LEVEL ).
Definition n_log.c:123
#define LOG_NOTICE
normal but significant condition
Definition n_log.h:104
#define LOG_NULL
no log output
Definition n_log.h:70
#define LOG_INFO
informational
Definition n_log.h:106
Common headers and low-level functions & define.
GUI system: buttons, sliders, text areas, checkboxes, scrollbars, dropdown menus, windows.
Generic log system.