From c42cfac50fc3f162ee0ea1df9fd795a64c9d4f86 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 26 Mar 2026 14:28:55 -1000 Subject: [PATCH] Fix dangling reference in save_state_, restore deleted string overloads 1. save_state_() bound a reference to supported_preset_modes() from a temporary FanTraits returned by get_traits(). When the compat path fired, the temporary was destroyed and the reference dangled. Fix: keep the FanTraits alive for the loop duration. 2. Restore deleted std::vector / initializer_list overloads to preserve clear compile-time diagnostics. --- esphome/components/fan/fan.cpp | 6 ++++-- esphome/components/fan/fan_traits.h | 4 ++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/esphome/components/fan/fan.cpp b/esphome/components/fan/fan.cpp index 124c2105be..fb9654da5b 100644 --- a/esphome/components/fan/fan.cpp +++ b/esphome/components/fan/fan.cpp @@ -297,9 +297,11 @@ void Fan::save_state_() { state.preset_mode = FanRestoreState::NO_PRESET; if (this->has_preset_mode()) { - // Use Fan-owned vector, or fall back to traits for deprecated path + // Use Fan-owned vector, or fall back to traits for deprecated path. + // Keep traits alive so the reference to compat_preset_modes_ doesn't dangle. + auto traits = this->supported_preset_modes_ ? FanTraits() : this->get_traits(); const auto &preset_modes = - this->supported_preset_modes_ ? *this->supported_preset_modes_ : this->get_traits().supported_preset_modes(); + this->supported_preset_modes_ ? *this->supported_preset_modes_ : traits.supported_preset_modes(); for (size_t i = 0; i < preset_modes.size(); i++) { if (preset_modes[i] == this->preset_mode_) { state.preset_mode = i; diff --git a/esphome/components/fan/fan_traits.h b/esphome/components/fan/fan_traits.h index 6a003f535f..046ae50929 100644 --- a/esphome/components/fan/fan_traits.h +++ b/esphome/components/fan/fan_traits.h @@ -48,6 +48,10 @@ class FanTraits { this->compat_preset_modes_ = preset_modes; } + // Deleted overloads to catch incorrect std::string usage at compile time with clear error messages + void set_supported_preset_modes(const std::vector &preset_modes) = delete; + void set_supported_preset_modes(std::initializer_list preset_modes) = delete; + /// Return if preset modes are supported bool supports_preset_modes() const { return (this->preset_modes_ && !this->preset_modes_->empty()) || !this->compat_preset_modes_.empty();