RAM allocation failures lead to abort instead of graceful recovery
- Dominant language
- No language data
- Stars
- 0
- Forks
- 0
- PR merge metrics
- No merged PRs in 30d
Description
`new (std::nothrow)` never returns nullptr on ESP-IDF, the device aborts instead; every site that relies on it for out of memory handling needs to move to `RAMAllocator`, and a ci-custom lint will reject new uses.
On-device result from the AtomU running the PR esphome/esphome#19093 build with ESP-IDF 5.5.5, GCC 14.2, exceptions disabled. Each button asks for 8 MB with about 199 KB free.
- RAMAllocator allocate logged a return of 0x0 and the device stayed up.
- nothrow new[] never logged a result. The API connection reset, and the debug reset reason after reboot read "exception/panic".
- Serial capture during the press shows the abort and the symbolized backtrace:
```
abort() was called at PC 0x4013e61e on core 1
0x4013e61e: __wrap___cxa_allocate_exception cxx_exception_stubs.cpp:184
0x4013dfe1: operator new(unsigned int) libsupc++/new_op.cc:54
0x4013e035: operator new[](unsigned int) libsupc++/new_opv.cc:32
0x4013e045: operator new[](unsigned int, std::nothrow_t const&) libsupc++/new_opvnt.cc:38
0x400dbbbf: the button lambda
```
Why it happens. The nothrow operators in libsupc++ are implemented as a try/catch around the throwing operator new. On failure the throwing one calls the exception allocator. ESP-IDF's cxx component wraps both exception entry points with `--wrap` and points them at abort when `CONFIG_COMPILER_CXX_EXCEPTIONS` is off, which is our sdkconfig. The disassembly of the previously built firmware.elf shows exactly this call chain. Espressif closed https://github.com/espressif/esp-idf/issues/18226 as expected behaviour, and their suggested workaround is to define the two nothrow operators globally to call malloc.
Migration pattern, from `APIBuffer` in esphome/esphome#19093. Owned storage lives in a `RAMUniquePtr` (esphome/esphome#19245), whose deleter frees through the allocator, growth goes through `reallocate` and reports failure to the caller instead of aborting:
```cpp
RAMUniquePtr data_;
bool APIBuffer::grow_(size_t n) {
if (n > MAX_SIZE)
return false;
uint8_t *grown = RAMAllocator().reallocate(this->data_.get(), n);
if (grown == nullptr)
return false;
(void) this->data_.release(); // realloc already freed or reused the old block
this->data_.reset(grown);
this->capacity_ = n;
return true;
}
```
Callers check the `[[nodiscard]]` bool from `reserve` and `resize` and drop the connection on false. For an object with a constructor, allocate raw storage with `RAMAllocator` and construct it with placement new.
| Site | Fix |
| --- | --- |
| `esphome/components/api/api_buffer.cpp` grow path | esphome/esphome#19093 |
| `esphome/components/api/api_overflow_buffer.cpp` data and Entry, two sites | esphome/esphome#19093 |
| `esphome/components/esphome/ota/ota_esphome_noise.cpp` NoiseSession | esphome/esphome#19249, merged |
| `esphome/components/ota/ota_signature_esp_idf.cpp` signature block | esphome/esphome#19251, merged |
| `esphome/components/ethernet/w5500_custom_spi.cpp` SPI context | esphome/esphome#19248, merged |
| `esphome/components/nextion/nextion.cpp` NextionComponentBase, two sites | esphome/esphome#19246, merged |
| `esphome/components/esphome/ota/ota_esphome.cpp` InflateSession, added by esphome/esphome#19037 | esphome/esphome#19037 |
| `esphome/components/esp8266/__init__.py` comment recommending nothrow, next to a `NEW_OOM_ABORT` flag Arduino core 3 no longer reads | esphome/esphome#19247 |
| `esphome/components/esphome/ota/ota_esphome.cpp` auth buffer, throwing `std::make_unique` aborts on OOM the same way | esphome/esphome#19249, merged |
| `esphome/components/wifi/wifi_component_esp_idf.cpp` scan results, `std::vector::reserve` aborts on OOM through the same stubs (esphome/esphome#19243) | esphome/esphome#19253 for FixedVector, esphome/esphome#19254 for the scan results, both merged |
| ci-custom lint rejecting `std::nothrow`, message points at RAMAllocator | esphome/esphome#19244 |
| `RAMAllocator::make_unique` and `RAMUniquePtr` core helper the sites above use | esphome/esphome#19245, merged |
| swap the hand written `FreeDeleter` in `api_buffer.h` for `RAMUniquePtr` | esphome/esphome#19093, on top of the merged esphome/esphome#19245 |
Contributor guide
No contributing guide indexed for this repository
Research direction
Start with the listed allocation sites, especially esphome/components/api/api_overflow_buffer.cpp, esphome/components/esphome/ota/ota_esphome.cpp, and esphome/components/wifi/wifi_component_esp_idf.cpp, then inspect RAMAllocator and RAMUniquePtr from the referenced core helper. Run the ci-custom lint and trace each remaining allocation path; done means all unsafe nothrow or throwing allocation sites are migrated and allocation failure is reported without aborting.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- embedded-iot
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Clearly specified
- Newbie friendliness
- 35/100