From 027cb0c35c4269091e301f10a4c461a22bbf6126 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 26 Mar 2026 14:11:15 -1000 Subject: [PATCH] Fix compat: use owned vectors instead of heap leak Replace new-and-leak deprecated setters with plain owned vector members on ClimateTraits. Copies copy the vector (same cost as before this PR). No heap leak, no dangling pointer, no smart pointer overhead. All compat paths (getters, find, supports) check the owned vector as fallback when the pointer path is not set. --- esphome/components/climate/climate_traits.cpp | 20 ++++++++-- esphome/components/climate/climate_traits.h | 38 +++++++++++++------ 2 files changed, 42 insertions(+), 16 deletions(-) diff --git a/esphome/components/climate/climate_traits.cpp b/esphome/components/climate/climate_traits.cpp index 3af0e609a8..398e25f69e 100644 --- a/esphome/components/climate/climate_traits.cpp +++ b/esphome/components/climate/climate_traits.cpp @@ -8,13 +8,25 @@ namespace esphome::climate { static const std::vector EMPTY_CUSTOM_MODES; // NOLINT const std::vector &ClimateTraits::get_supported_custom_fan_modes() const { - // Compat: return empty ref when pointer is null. Remove in 2026.11.0 (change return to const vector *). - return this->supported_custom_fan_modes_ ? *this->supported_custom_fan_modes_ : EMPTY_CUSTOM_MODES; + if (this->supported_custom_fan_modes_) { + return *this->supported_custom_fan_modes_; + } + // Compat: fall back to owned vector from deprecated setters. Remove in 2026.11.0. + if (!this->compat_custom_fan_modes_.empty()) { + return this->compat_custom_fan_modes_; + } + return EMPTY_CUSTOM_MODES; } const std::vector &ClimateTraits::get_supported_custom_presets() const { - // Compat: return empty ref when pointer is null. Remove in 2026.11.0 (change return to const vector *). - return this->supported_custom_presets_ ? *this->supported_custom_presets_ : EMPTY_CUSTOM_MODES; + if (this->supported_custom_presets_) { + return *this->supported_custom_presets_; + } + // Compat: fall back to owned vector from deprecated setters. Remove in 2026.11.0. + if (!this->compat_custom_presets_.empty()) { + return this->compat_custom_presets_; + } + return EMPTY_CUSTOM_MODES; } int8_t ClimateTraits::get_target_temperature_accuracy_decimals() const { diff --git a/esphome/components/climate/climate_traits.h b/esphome/components/climate/climate_traits.h index 86ebf33c48..939bf96ed5 100644 --- a/esphome/components/climate/climate_traits.h +++ b/esphome/components/climate/climate_traits.h @@ -148,7 +148,8 @@ class ClimateTraits { bool supports_fan_mode(ClimateFanMode fan_mode) const { return this->supported_fan_modes_.count(fan_mode); } bool get_supports_fan_modes() const { return !this->supported_fan_modes_.empty() || - (this->supported_custom_fan_modes_ && !this->supported_custom_fan_modes_->empty()); + (this->supported_custom_fan_modes_ && !this->supported_custom_fan_modes_->empty()) || + !this->compat_custom_fan_modes_.empty(); // Compat: remove in 2026.11.0 } const ClimateFanModeMask &get_supported_fan_modes() const { return this->supported_fan_modes_; } @@ -159,19 +160,21 @@ class ClimateTraits { // Remove before 2026.11.0 ESPDEPRECATED("Call set_supported_custom_fan_modes() on the Climate entity instead. Removed in 2026.11.0", "2026.5.0") void set_supported_custom_fan_modes(std::initializer_list modes) { - // NOLINT - intentional leak: pointer must survive copies of ClimateTraits - this->supported_custom_fan_modes_ = new std::vector(modes); // NOLINT + // Compat: store in owned vector. Copies copy the vector (same cost as before this PR). + this->compat_custom_fan_modes_ = modes; } // Remove before 2026.11.0 ESPDEPRECATED("Call set_supported_custom_fan_modes() on the Climate entity instead. Removed in 2026.11.0", "2026.5.0") void set_supported_custom_fan_modes(const std::vector &modes) { - this->supported_custom_fan_modes_ = new std::vector(modes); // NOLINT + this->compat_custom_fan_modes_ = modes; } // Compat: returns const ref with empty fallback. In 2026.11.0 change to return const vector *. const std::vector &get_supported_custom_fan_modes() const; bool supports_custom_fan_mode(const char *custom_fan_mode) const { - return this->supported_custom_fan_modes_ && vector_contains(*this->supported_custom_fan_modes_, custom_fan_mode); + return (this->supported_custom_fan_modes_ && + vector_contains(*this->supported_custom_fan_modes_, custom_fan_mode)) || + vector_contains(this->compat_custom_fan_modes_, custom_fan_mode); // Compat: remove in 2026.11.0 } bool supports_custom_fan_mode(const std::string &custom_fan_mode) const { return this->supports_custom_fan_mode(custom_fan_mode.c_str()); @@ -190,18 +193,19 @@ class ClimateTraits { // Remove before 2026.11.0 ESPDEPRECATED("Call set_supported_custom_presets() on the Climate entity instead. Removed in 2026.11.0", "2026.5.0") void set_supported_custom_presets(std::initializer_list presets) { - this->supported_custom_presets_ = new std::vector(presets); // NOLINT + this->compat_custom_presets_ = presets; } // Remove before 2026.11.0 ESPDEPRECATED("Call set_supported_custom_presets() on the Climate entity instead. Removed in 2026.11.0", "2026.5.0") void set_supported_custom_presets(const std::vector &presets) { - this->supported_custom_presets_ = new std::vector(presets); // NOLINT + this->compat_custom_presets_ = presets; } // Compat: returns const ref with empty fallback. In 2026.11.0 change to return const vector *. const std::vector &get_supported_custom_presets() const; bool supports_custom_preset(const char *custom_preset) const { - return this->supported_custom_presets_ && vector_contains(*this->supported_custom_presets_, custom_preset); + return (this->supported_custom_presets_ && vector_contains(*this->supported_custom_presets_, custom_preset)) || + vector_contains(this->compat_custom_presets_, custom_preset); // Compat: remove in 2026.11.0 } bool supports_custom_preset(const std::string &custom_preset) const { return this->supports_custom_preset(custom_preset.c_str()); @@ -270,8 +274,11 @@ class ClimateTraits { return this->find_custom_fan_mode_(custom_fan_mode, strlen(custom_fan_mode)); } const char *find_custom_fan_mode_(const char *custom_fan_mode, size_t len) const { - return this->supported_custom_fan_modes_ ? vector_find(*this->supported_custom_fan_modes_, custom_fan_mode, len) - : nullptr; + if (this->supported_custom_fan_modes_) { + return vector_find(*this->supported_custom_fan_modes_, custom_fan_mode, len); + } + // Compat: check owned vector from deprecated setters. Remove in 2026.11.0. + return vector_find(this->compat_custom_fan_modes_, custom_fan_mode, len); } /// Find and return the matching custom preset pointer from supported presets, or nullptr if not found @@ -280,8 +287,11 @@ class ClimateTraits { return this->find_custom_preset_(custom_preset, strlen(custom_preset)); } const char *find_custom_preset_(const char *custom_preset, size_t len) const { - return this->supported_custom_presets_ ? vector_find(*this->supported_custom_presets_, custom_preset, len) - : nullptr; + if (this->supported_custom_presets_) { + return vector_find(*this->supported_custom_presets_, custom_preset, len); + } + // Compat: check owned vector from deprecated setters. Remove in 2026.11.0. + return vector_find(this->compat_custom_presets_, custom_preset, len); } uint32_t feature_flags_{0}; @@ -304,6 +314,10 @@ class ClimateTraits { */ const std::vector *supported_custom_fan_modes_{nullptr}; const std::vector *supported_custom_presets_{nullptr}; + // Compat: owned storage for deprecated setters. Copies copy the vector (same cost as pre-PR). + // Remove in 2026.11.0. + std::vector compat_custom_fan_modes_; + std::vector compat_custom_presets_; }; } // namespace esphome::climate