From 971a1a3e00eeb3f657cbf5a22bd59c4c20ad4e89 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 19 Jan 2026 08:49:31 -1000 Subject: [PATCH] [ci] Block new std::to_string() usage, suggest snprintf alternatives --- .../components/api/homeassistant_service.h | 4 +- esphome/components/esp32_ble/ble_uuid.h | 2 +- esphome/components/shtcx/shtcx.cpp | 2 +- .../voice_assistant/voice_assistant.h | 2 +- script/ci-custom.py | 41 +++++++++++++++++++ 5 files changed, 47 insertions(+), 4 deletions(-) diff --git a/esphome/components/api/homeassistant_service.h b/esphome/components/api/homeassistant_service.h index 9bffe18764..aa09d1abcc 100644 --- a/esphome/components/api/homeassistant_service.h +++ b/esphome/components/api/homeassistant_service.h @@ -25,7 +25,9 @@ template class TemplatableStringValue : public TemplatableValue static std::string value_to_string(T &&val) { return to_string(std::forward(val)); } + template static std::string value_to_string(T &&val) { + return to_string(std::forward(val)); + } // NOLINT // Overloads for string types - needed because std::to_string doesn't support them static std::string value_to_string(char *val) { diff --git a/esphome/components/esp32_ble/ble_uuid.h b/esphome/components/esp32_ble/ble_uuid.h index ae593955a4..ac7ad42240 100644 --- a/esphome/components/esp32_ble/ble_uuid.h +++ b/esphome/components/esp32_ble/ble_uuid.h @@ -46,7 +46,7 @@ class ESPBTUUID { esp_bt_uuid_t get_uuid() const; - std::string to_string() const; + std::string to_string() const; // NOLINT const char *to_str(std::span output) const; protected: diff --git a/esphome/components/shtcx/shtcx.cpp b/esphome/components/shtcx/shtcx.cpp index 933dd9bde9..5d5fbf1740 100644 --- a/esphome/components/shtcx/shtcx.cpp +++ b/esphome/components/shtcx/shtcx.cpp @@ -52,7 +52,7 @@ void SHTCXComponent::dump_config() { ESP_LOGCONFIG(TAG, "SHTCx:\n" " Model: %s (%04x)", - to_string(this->type_), this->sensor_id_); + to_string(this->type_), this->sensor_id_); // NOLINT LOG_I2C_DEVICE(this); if (this->is_failed()) { ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL); diff --git a/esphome/components/voice_assistant/voice_assistant.h b/esphome/components/voice_assistant/voice_assistant.h index b1b3df7bbd..7daf84454a 100644 --- a/esphome/components/voice_assistant/voice_assistant.h +++ b/esphome/components/voice_assistant/voice_assistant.h @@ -81,7 +81,7 @@ struct Timer { this->id.c_str(), this->name.c_str(), this->total_seconds, this->seconds_left, YESNO(this->is_active)); return buffer.data(); } - std::string to_string() const { + std::string to_string() const { // NOLINT char buffer[TO_STR_BUFFER_SIZE]; return this->to_str(buffer); } diff --git a/script/ci-custom.py b/script/ci-custom.py index ee2e73872c..8e08ba4095 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -732,6 +732,47 @@ def lint_no_heap_allocating_helpers(fname, match): ) +@lint_re_check( + # Match std::to_string() or unqualified to_string() calls + # The esphome namespace has "using std::to_string;" so unqualified calls resolve to std::to_string + # Use negative lookbehind to avoid matching: + # - Function definitions: "const char *to_string(" or "std::string to_string(" + # - Method definitions: "Class::to_string(" + # - Method calls: ".to_string(" or "->to_string(" + # - Other identifiers: "_to_string(" + r"(?:])to_string\s*\(" + CPP_RE_EOL, + include=cpp_include, + exclude=[ + # Vendored library + "esphome/components/http_request/httplib.h", + # Deprecated helpers that return std::string + "esphome/core/helpers.cpp", + # The using declaration itself + "esphome/core/helpers.h", + # Test fixtures - not production embedded code + "tests/integration/fixtures/*", + ], +) +def lint_no_std_to_string(fname, match): + return ( + f"{highlight('std::to_string()')} allocates heap memory. On long-running embedded " + f"devices, repeated heap allocations fragment memory over time.\n" + f"Please use {highlight('snprintf()')} with a stack buffer instead.\n" + f"\n" + f"Buffer sizes and format specifiers:\n" + f" uint8_t/int8_t: 4 chars - %u / %d (or PRIu8/PRId8)\n" + f" uint16_t/int16_t: 6 chars - %u / %d (or PRIu16/PRId16)\n" + f" uint32_t/int32_t: 11 chars - %" + "PRIu32 / %" + "PRId32\n" + " uint64_t/int64_t: 21 chars - %" + "PRIu64 / %" + "PRId64\n" + f" float/double: 24 chars - %.8g (15 digits + sign + decimal + e+XXX)\n" + f" 317 chars - %f (for DBL_MAX: 309 int digits + decimal + 6 frac + sign)\n" + f"\n" + f"For sensor values, use value_accuracy_to_buf() from helpers.h.\n" + f'Example: char buf[11]; snprintf(buf, sizeof(buf), "%" PRIu32, value);\n' + f"(If strictly necessary, add `{highlight('// NOLINT')}` to the end of the line)" + ) + + @lint_content_find_check( "ESP_LOG", include=["*.h", "*.tcc"],