From 8a93b0c1c0f781e08832ecca014834dc6831004e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 27 Mar 2026 15:48:04 -1000 Subject: [PATCH 1/3] [sensor] Replace truncation guard with ESPHOME_DEBUG_ASSERT --- esphome/components/sensor/filter.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/esphome/components/sensor/filter.h b/esphome/components/sensor/filter.h index 21dfdd3ddc..7124c9f85d 100644 --- a/esphome/components/sensor/filter.h +++ b/esphome/components/sensor/filter.h @@ -477,10 +477,9 @@ template class OrFilter : public Filter { explicit OrFilter(std::initializer_list filters) { size_t i = 0; for (auto *f : filters) { - if (i >= N) - break; this->filters_[i++] = f; } + ESPHOME_DEBUG_ASSERT(i == N); } void initialize(Sensor *parent, Filter *next) override { From 3d68920de0c7f516ecc4725cb24192b9ebbbb566 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 27 Mar 2026 15:51:05 -1000 Subject: [PATCH 2/3] [sensor] Assert size before writing, not after --- esphome/components/sensor/filter.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/sensor/filter.h b/esphome/components/sensor/filter.h index 7124c9f85d..9c45443cdc 100644 --- a/esphome/components/sensor/filter.h +++ b/esphome/components/sensor/filter.h @@ -475,11 +475,11 @@ optional or_filter_new_value(Filter **filters, size_t count, float value, template class OrFilter : public Filter { public: explicit OrFilter(std::initializer_list filters) { + ESPHOME_DEBUG_ASSERT(filters.size() == N); size_t i = 0; for (auto *f : filters) { this->filters_[i++] = f; } - ESPHOME_DEBUG_ASSERT(i == N); } void initialize(Sensor *parent, Filter *next) override { From 1d2f11b990c42a5ff5eaf54abbf9780bdd31c78e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 27 Mar 2026 16:14:47 -1000 Subject: [PATCH 3/3] Use init_array_from helper for optimal codegen Uses memcpy for trivially copyable types, element-wise copy otherwise. ESPHOME_DEBUG_ASSERT catches size mismatches in integration tests. --- esphome/components/sensor/filter.h | 8 +------- esphome/core/helpers.h | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/esphome/components/sensor/filter.h b/esphome/components/sensor/filter.h index 9c45443cdc..3c251982e7 100644 --- a/esphome/components/sensor/filter.h +++ b/esphome/components/sensor/filter.h @@ -474,13 +474,7 @@ optional or_filter_new_value(Filter **filters, size_t count, float value, /// N is set by code generation to match the exact number of filters configured in YAML. template class OrFilter : public Filter { public: - explicit OrFilter(std::initializer_list filters) { - ESPHOME_DEBUG_ASSERT(filters.size() == N); - size_t i = 0; - for (auto *f : filters) { - this->filters_[i++] = f; - } - } + explicit OrFilter(std::initializer_list filters) { init_array_from(this->filters_, filters); } void initialize(Sensor *parent, Filter *next) override { Filter::initialize(parent, next); diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 82c6b3833c..51feaa57c5 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -497,6 +497,21 @@ template::max()> index_type capacity_{0}; }; +/// Initialize a std::array from an initializer_list. Uses memcpy for trivially copyable types (optimal codegen), +/// falls back to element-wise copy for non-trivially copyable types (e.g. TemplatableValue). +/// N is set by code generation; ESPHOME_DEBUG_ASSERT catches mismatches in debug/integration tests. +template inline void init_array_from(std::array &dest, std::initializer_list src) { + ESPHOME_DEBUG_ASSERT(src.size() == N); + if constexpr (std::is_trivially_copyable_v) { + __builtin_memcpy(dest.data(), src.begin(), N * sizeof(T)); + } else { + size_t i = 0; + for (const auto &v : src) { + dest[i++] = v; + } + } +} + /// Fixed-capacity vector - allocates once at runtime, never reallocates /// This avoids std::vector template overhead (_M_realloc_insert, _M_default_append) /// when size is known at initialization but not at compile time