From 150e0775b9fe6ffb042e5796d35e03d8d7283b15 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 25 Mar 2026 15:36:08 -1000 Subject: [PATCH] Add static_asserts for forwarder size, widen type hint - static_assert that forwarders are pointer-sized and trivially copyable so future field additions can't silently cause heap allocation in Callback::create(). - Widen forwarder parameter type hint to MockObj | MockObjClass. --- esphome/automation.py | 2 +- esphome/core/automation.h | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/esphome/automation.py b/esphome/automation.py index 996181f50b..7b1d6ceca1 100644 --- a/esphome/automation.py +++ b/esphome/automation.py @@ -671,7 +671,7 @@ async def build_callback_automation( callback_method: str, args: TemplateArgsType, config: ConfigType, - forwarder: MockObjClass | None = None, + forwarder: MockObj | MockObjClass | None = None, ) -> None: """Build an Automation and register it as a callback on the parent. diff --git a/esphome/core/automation.h b/esphome/core/automation.h index 35f9e9bf38..fc2cad99be 100644 --- a/esphome/core/automation.h +++ b/esphome/core/automation.h @@ -494,12 +494,14 @@ template class Automation { /// Callback forwarder that triggers an Automation directly. /// One operator() instantiation per Automation signature, shared across all call sites. +/// Must stay pointer-sized to fit inline in Callback::ctx_ without heap allocation. template struct TriggerForwarder { Automation *automation; void operator()(const Ts &...args) const { this->automation->trigger(args...); } }; /// Callback forwarder that triggers an Automation<> only when the bool arg is true. +/// Must stay pointer-sized to fit inline in Callback::ctx_ without heap allocation. struct TriggerOnTrueForwarder { Automation<> *automation; void operator()(bool state) const { @@ -509,6 +511,7 @@ struct TriggerOnTrueForwarder { }; /// Callback forwarder that triggers an Automation<> only when the bool arg is false. +/// Must stay pointer-sized to fit inline in Callback::ctx_ without heap allocation. struct TriggerOnFalseForwarder { Automation<> *automation; void operator()(bool state) const { @@ -517,4 +520,13 @@ struct TriggerOnFalseForwarder { } }; +// Ensure forwarders fit in Callback::ctx_ (pointer-sized inline storage). +// If these fail, the forwarder would heap-allocate in Callback::create(). +static_assert(sizeof(TriggerForwarder<>) <= sizeof(void *)); +static_assert(sizeof(TriggerOnTrueForwarder) <= sizeof(void *)); +static_assert(sizeof(TriggerOnFalseForwarder) <= sizeof(void *)); +static_assert(std::is_trivially_copyable_v>); +static_assert(std::is_trivially_copyable_v); +static_assert(std::is_trivially_copyable_v); + } // namespace esphome