fix(lifecycle): Lua state not freed if free_all_mem() is re-entered #41366

Problem:
Leak reported by ASAN CI, always at Lua state init.

      ERROR: LeakSanitizer: detected memory leaks
          strbuf_init src/cjson/strbuf.c:62:22
          json_create_config src/cjson/lua_cjson.c:635:5
          lua_cjson_new src/cjson/lua_cjson.c:2060:5
          nlua_state_add_stdlib src/nvim/lua/stdlib.c:840:3
          nlua_state_init src/nvim/lua/executor.c:973:3
      ...
      SUMMARY: AddressSanitizer: 1055 byte(s) leaked in 2 allocation(s).

Analysis:
`getout()` may run during `free_all_mem()` (that's why it checks
`!entered_free_all_mem`). If that happens, the second `os_exit()`
returns early from `free_all_mem()` then `exit()` is called and Lua
state is never closed.

Solution:
Always call `nlua_free_all_mem()` in the early-return case.
This commit is contained in:
Justin M. Keyes
2026-08-18 08:30:42 -04:00
committed by GitHub
parent f6bf814378
commit 1eae512285
2 changed files with 5 additions and 2 deletions

View File

@@ -1061,12 +1061,14 @@ static lua_State *nlua_init_state(bool thread)
return lstate; return lstate;
} }
/// Disposes global Lua state. Idempotent, harmless if called multiple times.
void nlua_free_all_mem(void) void nlua_free_all_mem(void)
{ {
if (!global_lstate) { if (!global_lstate) {
return; return;
} }
lua_State *lstate = global_lstate; lua_State *lstate = global_lstate;
global_lstate = NULL;
nlua_unref_global(lstate, require_ref); nlua_unref_global(lstate, require_ref);
nlua_common_free_all_mem(lstate); nlua_common_free_all_mem(lstate);
nlua_treesitter_free(); nlua_treesitter_free();

View File

@@ -861,9 +861,10 @@ void free_all_mem(void)
{ {
buf_T *buf, *nextbuf; buf_T *buf, *nextbuf;
// When we cause a crash here it is caught and Vim tries to exit cleanly. // If a routine below recurses into free_all_mem, don't try freeing everything again.
// Don't try freeing everything again.
if (entered_free_all_mem) { if (entered_free_all_mem) {
// Except the Lua state. #39675
nlua_free_all_mem();
return; return;
} }
entered_free_all_mem = true; entered_free_all_mem = true;