nuttx/libs/libc/stdlib
Junbo Zheng 7797b12244
Some checks are pending
Build Documentation / build-html (push) Waiting to run
MemBrowse Memory Report / changes-filter (push) Waiting to run
MemBrowse Memory Report / load-targets (push) Waiting to run
MemBrowse Memory Report / identical (push) Blocked by required conditions
MemBrowse Memory Report / analyze (push) Blocked by required conditions
libc/atexit: honor registrations made during exit processing
atexit_call_exitfuncs() cached its loop bound on entry
(for (idx = aehead->nfuncs - 1; idx >= 0; idx--)), while
atexit_register() appends new entries at funcs[nfuncs] and bumps nfuncs.
Any function registered by an exit handler via atexit() / on_exit() /
__cxa_atexit() lands above the cached bound and is never invoked, even
though the registration returns OK.

This contradicts the exit(3) documentation that NuttX mirrors verbatim
in its own exit() docstring (libs/libc/stdlib/lib_exit.c):

  It is possible for one of these functions to use atexit(3) or
  on_exit(3) to register an additional function to be executed
  during exit processing; the new registration is added to the
  front of the list of functions that remain to be called.

The same restructure closes a second defect: atexit_call_exitfuncs()
read and cleared the task-group-shared ta_exit list without holding
ta_lock, while atexit_register() takes it ("The following must be
atomic").  Entries are now claimed under the lock and the handler is
invoked with the lock released, so a handler re-entering
atexit_register() cannot deadlock (also safe with the non-recursive
nxmutex used here).

Evidence: exit(3) man page, DESCRIPTION -
https://man7.org/linux/man-pages/man3/exit.3.html
NuttX mirrors this passage verbatim in its own exit() docstring --
5a209a853e/libs/libc/stdlib/lib_exit.c (L65-L70)

Before:
```
A handler that registers another function during exit processing
gets a success return from atexit(), but the new function is never
invoked - it lands above the loop bound cached on entry.
```

After:
```
A registration made during exit processing runs before the older
remaining handlers (order A -> B -> C below), matching the exit(3)
guarantee, and the list is consumed under ta_lock.
```

Testing:

Simulated (sim:nsh, CONFIG_LIBC_MAX_EXITFUNS=8).

Build and run:
```
cmake -B build -DBOARD_CONFIG=sim:nsh -GNinja
cmake -S . -B build   # after setting CONFIG_LIBC_MAX_EXITFUNS=8
                       # in build/.config (sim:nsh default is 1)
cmake --build build -j$(nproc)
(echo hello; echo poweroff) | ./build/nuttx
```
"hello" runs the test at the NSH prompt; poweroff terminates the sim.

The test was carried by apps/examples/hello/hello_main.c (scratch only,
not part of this commit); its diff:

```
--- a/examples/hello/hello_main.c
+++ b/examples/hello/hello_main.c
@@ -24,6 +24,7 @@

 #include <nuttx/config.h>
 #include <stdio.h>
+#include <stdlib.h>

 /****************************************************************************
  * Public Functions
@@ -33,8 +34,29 @@
  * hello_main
  ****************************************************************************/

+static void handler_b(void)
+{
+  printf("ATEXIT-TEST: handler B called (registered during exit)\n");
+}
+
+static void handler_a(void)
+{
+  int ret;
+
+  printf("ATEXIT-TEST: handler A called\n");
+  ret = atexit(handler_b);
+  printf("ATEXIT-TEST: atexit(handler_b) inside A returned %d\n", ret);
+}
+
+static void handler_c(void)
+{
+  printf("ATEXIT-TEST: handler C called\n");
+}
+
 int main(int argc, FAR char *argv[])
 {
   printf("Hello, World!!\n");
+  atexit(handler_c);   /* older entry, must run LAST */
+  atexit(handler_a);   /* registers handler_b during exit */
   return 0;
 }
```

Before the fix:
```
Hello, World!!
ATEXIT-TEST: handler A called
ATEXIT-TEST: atexit(handler_b) inside A returned 0
ATEXIT-TEST: handler C called
```
(handler B is never invoked although its registration returned 0)

After the fix:
```
Hello, World!!
ATEXIT-TEST: handler A called
ATEXIT-TEST: atexit(handler_b) inside A returned 0
ATEXIT-TEST: handler B called (registered during exit)
ATEXIT-TEST: handler C called
```

Assisted-by: Claude Code (GLM-5.3) <claude@anthropic.com>
Signed-off-by: Junbo Zheng <zhengjunbo1@xiaomi.com>
2026-09-14 18:52:46 -03:00
..
CMakeLists.txt
Kconfig
lib_abort.c
lib_abs.c
lib_aligned_alloc.c
lib_arc4random.c
lib_atexit.c libc/atexit: honor registrations made during exit processing 2026-09-14 18:52:46 -03:00
lib_atof.c
lib_atoi.c
lib_atol.c
lib_atoll.c
lib_bsearch.c
lib_checkbase.c
lib_div.c
lib_exit.c
lib_getprogname.c
lib_itoa.c
lib_labs.c
lib_ldiv.c
lib_llabs.c
lib_lldiv.c
lib_mblen.c
lib_mbstowcs.c
lib_mbtowc.c
lib_mkdtemp.c
lib_mkstemp.c
lib_mktemp.c
lib_openpty.c
lib_posix_memalign.c
lib_ptsname.c
lib_ptsnamer.c
lib_qsort.c
lib_rand.c
lib_rand48.c
lib_reallocarray.c
lib_realpath.c fs: rename PSEUDOFS_SOFTLINKS to FS_LINKS 2026-08-27 01:12:33 +08:00
lib_srand.c
lib_strtol.c
lib_strtold.c
lib_strtoll.c
lib_strtoul.c
lib_strtoull.c
lib_unlockpt.c
lib_valloc.c
lib_wcstombs.c
lib_wctomb.c
Make.defs