diff --git a/esphome/components/esp32/operator_new.cpp b/esphome/components/esp32/operator_new.cpp index dbe5e576630..0291afdb445 100644 --- a/esphome/components/esp32/operator_new.cpp +++ b/esphome/components/esp32/operator_new.cpp @@ -1,7 +1,7 @@ /* * Replaces operator new for ESP-IDF builds without C++ exceptions. libstdc++'s nothrow forms call the * throwing form and catch std::bad_alloc, but the throw aborts here, so they abort instead of returning - * nullptr. These forms return nullptr or abort directly, which also drops the exception class code. + * nullptr. All forms stay in this file: --undefined=_Znwj pulls it in ahead of libstdc++. */ #include "esphome/core/defines.h" @@ -15,14 +15,17 @@ #include #include "esp_system.h" -namespace esphome::esp32 {} +namespace esphome::esp32 {} // namespace esphome::esp32 -void *operator new(std::size_t size, const std::nothrow_t & /*tag*/) noexcept { +namespace { + +// Standard new_handler retry loop; returns nullptr once no handler is installed +template void *alloc_or_null(std::size_t size, Alloc alloc) { // malloc(0) may return NULL, but operator new must return a unique pointer if (size == 0) size = 1; for (;;) { - void *ptr = std::malloc(size); // NOLINT(cppcoreguidelines-no-malloc) + void *ptr = alloc(size); if (ptr != nullptr) return ptr; std::new_handler handler = std::get_new_handler(); @@ -32,41 +35,39 @@ void *operator new(std::size_t size, const std::nothrow_t & /*tag*/) noexcept { } } -void *operator new(std::size_t size) { - void *ptr = ::operator new(size, std::nothrow); // NOLINT: this is the nothrow form being fixed +void *abort_if_null(void *ptr) { if (ptr == nullptr) esp_system_abort("std::bad_alloc"); return ptr; } -void *operator new[](std::size_t size) { return ::operator new(size); } -void *operator new[](std::size_t size, const std::nothrow_t &tag) noexcept { return ::operator new(size, tag); } - -// Aligned forms; libstdc++'s aligned delete frees with free(), which matches memalign() -void *operator new(std::size_t size, std::align_val_t align, const std::nothrow_t & /*tag*/) noexcept { - if (size == 0) - size = 1; - for (;;) { - void *ptr = memalign(static_cast(align), size); - if (ptr != nullptr) - return ptr; - std::new_handler handler = std::get_new_handler(); - if (handler == nullptr) - return nullptr; - handler(); - } +void *plain_alloc(std::size_t size) { + return alloc_or_null(size, [](std::size_t n) { return std::malloc(n); }); // NOLINT(cppcoreguidelines-no-malloc) } +// libstdc++'s aligned delete frees with free(), which matches memalign() +void *aligned_alloc_or_null(std::size_t size, std::align_val_t align) { + return alloc_or_null(size, [align](std::size_t n) { return memalign(static_cast(align), n); }); +} + +} // namespace + +void *operator new(std::size_t size) { return abort_if_null(plain_alloc(size)); } +void *operator new[](std::size_t size) { return abort_if_null(plain_alloc(size)); } +void *operator new(std::size_t size, const std::nothrow_t & /*tag*/) noexcept { return plain_alloc(size); } +void *operator new[](std::size_t size, const std::nothrow_t & /*tag*/) noexcept { return plain_alloc(size); } + void *operator new(std::size_t size, std::align_val_t align) { - void *ptr = ::operator new(size, align, std::nothrow); // NOLINT: this is the nothrow form being fixed - if (ptr == nullptr) - esp_system_abort("std::bad_alloc"); - return ptr; + return abort_if_null(aligned_alloc_or_null(size, align)); } - -void *operator new[](std::size_t size, std::align_val_t align) { return ::operator new(size, align); } -void *operator new[](std::size_t size, std::align_val_t align, const std::nothrow_t &tag) noexcept { - return ::operator new(size, align, tag); +void *operator new[](std::size_t size, std::align_val_t align) { + return abort_if_null(aligned_alloc_or_null(size, align)); +} +void *operator new(std::size_t size, std::align_val_t align, const std::nothrow_t & /*tag*/) noexcept { + return aligned_alloc_or_null(size, align); +} +void *operator new[](std::size_t size, std::align_val_t align, const std::nothrow_t & /*tag*/) noexcept { + return aligned_alloc_or_null(size, align); } #endif // CONFIG_COMPILER_CXX_EXCEPTIONS diff --git a/script/ci-custom.py b/script/ci-custom.py index af862e54889..d28993054ab 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -1257,9 +1257,8 @@ def lint_no_std_bind(fname, match): ) def lint_no_std_nothrow(fname, match): return ( - f"Please use {highlight('RAMAllocator')} from esphome/core/helpers.h instead of " - f"{highlight('new (std::nothrow)')}: it returns nullptr on failure on every platform and lets you choose PSRAM " - f"or internal RAM.\n" + f"Use {highlight('RAMAllocator')} from esphome/core/helpers.h instead of {highlight('new (std::nothrow)')}; " + f"it returns nullptr on failure on every platform and can choose PSRAM or internal RAM.\n" f" Before: {highlight('auto *buf = new (std::nothrow) uint8_t[n];')}\n" f" After: {highlight('auto buf = RAMAllocator().make_unique_array_for_overwrite(n);')}\n" f"For one object use {highlight('RAMAllocator().make_unique(args...)')}; both return empty on failure.\n"