[gcc/cabi_cortex] pin noreturn contract on _exit/abort (fixes #1298)#1355
[gcc/cabi_cortex] pin noreturn contract on _exit/abort (fixes #1298)#1355adityachilka1 wants to merge 1 commit into
Conversation
…#1298) GCC warns `'noreturn' function does return` on the modm-supplied definitions of `_exit()` and `abort()`. Newlib's `<stdlib.h>` declares these symbols `__noreturn__`, and the modm definitions are weak overrides of those declarations -- so the compiler enforces the noreturn contract. The implementations call `modm_assert(false, ...)`. Under the default assertion handler that's effectively a halt and `__builtin_unreachable` is invoked inside the macro (behaviour `0x04`). But the assertion handler is a public override point: a user can install one that returns normally (logging-only, fault-injection tests, etc.). In that case the functions fall through their closing brace and the compiler is right to complain. Add a `for(;;){}` after the assert in both functions. It's unreachable when the assertion handler halts as designed, and it's the safety net when the handler doesn't, so the noreturn contract holds either way. Closes the open question from modm-io#1298 (salkinium asked what happens if we remove the return statements -- there aren't any to remove; the fix is to add the noreturn-satisfying loop instead).
|
Shouldn't we fix the modm_assert handler instead? |
|
Good question — I weighed both directions before opening this PR. Sharing my reasoning so you can pick. Why I landed on the
A user-supplied handler is allowed to return — that's the whole point of soft asserts, fault injection, post-mortem logging, hostile-test rigs, etc. So a noreturn attribute on the declaration would either silently mislead the optimiser (UB if a user override returns) or attribute-clash with handlers that already build successfully against the current declaration. Could we fix it inside the assert macro? We could push a That's the asymmetry — the boundary is the right place to pin the contract. Happy to flip the approach if you'd rather: If you'd prefer the fix inside the assert plumbing, I can rework it three ways:
Your call — it's your handler design and I don't want to push a change you'd rather make differently. If (1) sounds right I'll close this and open a follow-up against the macro. |
|
This LLM doesn't understand the problem nor the fix sadly :-( |
Closes #1298.
The warning
_exit()andabort()are declared__noreturn__in newlib's<stdlib.h>. The modm definitions are weak overrides of those declarations, so the compiler enforces the noreturn contract.Why GCC is right
Inside the modm assertion macro:
For
modm_assert(false, ...)the behaviour is0x04, so__builtin_unreachable()does fire — but only whenmodm_assert_report()actually returns.modm_assert_reportis a public override point (modm_extern_c void modm_assert_report(_modm_assertion_info *info);), so a user can install a handler that returns normally (debug logging, fault injection, soft assertions, etc.). When they do, the function falls through past__builtin_unreachable()and returns — violating the noreturn contract that newlib promised.That's what's happening on the reporter's build.
_exit()andabort()need to be unconditionally non-returning.Fix
Add a
for(;;){}after the assert call in each function. It's unreachable when the assertion handler halts as designed (zero overhead, dead code), and it's the safety net that pins the noreturn contract when the handler chooses to continue. Either way the function never returns.modm_weak void _exit(int status) { modm_assert(false, "libc.exit", "The libc exit(status) function was called!", status); + for (;;) { } } modm_weak void abort(void) { modm_assert(false, "libc.abort", "The libc abort() function was called!"); + for (;;) { } }Also added a comment block above the functions documenting why the loop is there (so it doesn't get optimised out by a well-meaning future cleanup).
Why not
[[noreturn]]/modm_noreturn?Adding a noreturn attribute to the definitions doesn't fix the underlying analysis problem — GCC would still warn that a noreturn-marked function falls through. And it risks attribute-mismatch errors against newlib's declaration. The infinite loop is the canonical fix and is what the language-lawyer interpretation expects.
Answer to @salkinium's question on the issue thread
There aren't any return statements to remove — the functions just end after the assert. The right move is to add a noreturn-satisfying construct, which is what this PR does.
No behavioural change
When the assertion handler halts as intended (default modm behaviour), the loop is unreachable and the warning goes away. When a user-supplied handler returns, the loop now correctly pins noreturn instead of falling through into undefined behaviour.
Credit
Thanks to @Tecnologic for filing the report with the exact warning output and to @salkinium for pointing the investigation in the right direction.