From 904072ce79363d419d2120b9851c42ae085c0b93 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 29 Jan 2026 18:17:34 -0600 Subject: [PATCH] make sure NRVO works --- esphome/components/json/json_util.cpp | 31 ++++++++++++++++----------- esphome/components/json/json_util.h | 10 +++++++++ 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/esphome/components/json/json_util.cpp b/esphome/components/json/json_util.cpp index 79375b861b..2db76a6b9f 100644 --- a/esphome/components/json/json_util.cpp +++ b/esphome/components/json/json_util.cpp @@ -62,15 +62,6 @@ JsonDocument parse_json(const uint8_t *data, size_t len) { } SerializationBuffer<> JsonBuilder::serialize() { - if (doc_.overflowed()) { - ESP_LOGE(TAG, "JSON document overflow"); - SerializationBuffer<> result(2); - auto *buf = result.data_writable(); - buf[0] = '{'; - buf[1] = '}'; - buf[2] = '\0'; - return result; - } // Intentionally avoid measureJson() - it instantiates DummyWriter templates that add ~1KB of flash. // Instead, try serializing to stack buffer first. 768 bytes covers 99.9% of JSON payloads // (sensors ~200B, lights ~170B, climate ~700B). Only entities with 40+ options exceed this. @@ -78,18 +69,32 @@ SerializationBuffer<> JsonBuilder::serialize() { // For the rare large case: serialize twice (once truncated, once to heap) - less efficient but // saves ~1KB flash that would otherwise be wasted on every build. // serializeJson() returns actual size needed even if truncated, so we can retry with exact size. + // + // Single return variable enables NRVO (Named Return Value Optimization), avoiding memcpy on return. constexpr size_t buf_size = SerializationBuffer<>::BUFFER_SIZE; SerializationBuffer<> result(buf_size - 1); // Max content size (reserve 1 for null) + + if (doc_.overflowed()) { + ESP_LOGE(TAG, "JSON document overflow"); + auto *buf = result.data_writable(); + buf[0] = '{'; + buf[1] = '}'; + buf[2] = '\0'; + result.set_size(2); + return result; + } + size_t size = serializeJson(doc_, result.data_writable(), buf_size); if (size < buf_size) { // Fits in stack buffer - update size to actual length result.set_size(size); return result; } - // Needs heap allocation - serialize again with exact size - SerializationBuffer<> heap_result(size); - serializeJson(doc_, heap_result.data_writable(), size + 1); - return heap_result; + + // Needs heap allocation - reallocate and serialize again with exact size + result.reallocate_heap(size); + serializeJson(doc_, result.data_writable(), size + 1); + return result; } } // namespace json diff --git a/esphome/components/json/json_util.h b/esphome/components/json/json_util.h index 73847c2bb8..4c51a61ddf 100644 --- a/esphome/components/json/json_util.h +++ b/esphome/components/json/json_util.h @@ -89,6 +89,16 @@ template class SerializationBuffer { /// Set actual size after serialization (must not exceed allocated size) void set_size(size_t size) { size_ = size; } + /// Reallocate to heap buffer with new size (for when stack buffer is too small) + /// This invalidates any previous buffer content + void reallocate_heap(size_t size) { + delete[] heap_buffer_; + heap_buffer_ = new char[size + 1]; + buffer_ = heap_buffer_; + size_ = size; + buffer_[0] = '\0'; + } + /// Implicit conversion to std::string for backward compatibility operator std::string() const { return std::string(buffer_, size_); } // NOLINT(google-explicit-constructor)