make it safer

This commit is contained in:
J. Nick Koston
2026-03-06 07:39:49 -10:00
parent 79d7795c07
commit 6b53ccc85a
9 changed files with 29 additions and 22 deletions
+1 -1
View File
@@ -11,7 +11,7 @@ static const char *const TAG = "entity_base";
// Entity Name
const StringRef &EntityBase::get_name() const { return this->name_; }
void EntityBase::configure_entity(const char *name, uint32_t object_id_hash, uint32_t entity_strings_packed) {
void EntityBase::configure_entity_(const char *name, uint32_t object_id_hash, uint32_t entity_strings_packed) {
this->name_ = StringRef(name);
if (this->name_.empty()) {
#ifdef USE_DEVICES
+10 -3
View File
@@ -12,6 +12,10 @@
#include "device.h"
#endif
// Forward declarations for friend access from codegen-generated setup()
void setup(); // NOLINT(readability-redundant-declaration) - may be declared in Arduino.h
void original_setup(); // NOLINT(readability-redundant-declaration) - used by cpp unit tests
namespace esphome {
// Extern lookup functions for entity string tables.
@@ -52,9 +56,6 @@ class EntityBase {
// Get the name of this Entity
const StringRef &get_name() const;
/// Combined entity setup from codegen: set name, object_id hash, and entity string indices.
void configure_entity(const char *name, uint32_t object_id_hash, uint32_t entity_strings_packed);
// Get whether this Entity has its own name or it should use the device friendly_name.
bool has_own_name() const { return this->flags_.has_own_name; }
@@ -201,6 +202,12 @@ class EntityBase {
}
protected:
friend void ::setup();
friend void ::original_setup();
/// Combined entity setup from codegen: set name, object_id hash, and entity string indices.
void configure_entity_(const char *name, uint32_t object_id_hash, uint32_t entity_strings_packed);
/// Non-template helper for make_entity_preference() to avoid code bloat.
/// When preference hash algorithm changes, migration logic goes here.
ESPPreferenceObject make_entity_preference_(size_t size, uint32_t version);
+4 -4
View File
@@ -34,7 +34,7 @@ _KEY_ICON_IDX = "_entity_icon_idx"
_KEY_ENTITY_NAME = "_entity_name"
_KEY_OBJECT_ID_HASH = "_entity_object_id_hash"
# Bit layout for entity_strings_packed in configure_entity() — must match C++ in entity_base.h:
# Bit layout for entity_strings_packed in configure_entity_() — must match C++ in entity_base.h:
# [23..16] icon (8 bits) | [15..8] UoM (8 bits) | [7..0] device_class (8 bits)
_DC_SHIFT = 0
_UOM_SHIFT = 8
@@ -217,7 +217,7 @@ def setup_unit_of_measurement(config: ConfigType) -> None:
def finalize_entity_strings(var: MockObj, config: ConfigType) -> None:
"""Emit a single configure_entity() call with name, hash, and packed string indices.
"""Emit a single configure_entity_() call with name, hash, and packed string indices.
Call this at the end of each component's setup function, after
setup_entity() and any register_device_class/register_unit_of_measurement calls.
@@ -228,7 +228,7 @@ def finalize_entity_strings(var: MockObj, config: ConfigType) -> None:
uom_idx = config.get(_KEY_UOM_IDX, 0)
icon_idx = config.get(_KEY_ICON_IDX, 0)
packed = (dc_idx << _DC_SHIFT) | (uom_idx << _UOM_SHIFT) | (icon_idx << _ICON_SHIFT)
add(var.configure_entity(entity_name, object_id_hash, packed))
add(var.configure_entity_(entity_name, object_id_hash, packed))
def get_base_entity_object_id(
@@ -330,7 +330,7 @@ async def _setup_entity_impl(var: MockObj, config: ConfigType, platform: str) ->
device: MockObj = await get_variable(device_id_obj)
add(var.set_device(device))
# Pre-compute entity name and object_id hash for configure_entity()
# Pre-compute entity name and object_id hash for configure_entity_()
# which is emitted later by finalize_entity_strings().
# For named entities: pre-compute hash from entity name
# For empty-name entities: pass 0, C++ calculates hash at runtime from
@@ -29,7 +29,7 @@ def test_binary_sensor_sets_mandatory_fields(generate_main):
)
# Then
assert 'bs_1->configure_entity("test bs1",' in main_cpp
assert 'bs_1->configure_entity_("test bs1",' in main_cpp
assert "bs_1->set_pin(" in main_cpp
+1 -1
View File
@@ -26,7 +26,7 @@ def test_button_sets_mandatory_fields(generate_main):
main_cpp = generate_main("tests/component_tests/button/test_button.yaml")
# Then
assert 'wol_1->configure_entity("wol_test_1",' in main_cpp
assert 'wol_1->configure_entity_("wol_test_1",' in main_cpp
assert "wol_2->set_macaddr(18, 52, 86, 120, 144, 171);" in main_cpp
+1 -1
View File
@@ -11,4 +11,4 @@ def test_sensor_device_class_set(generate_main):
main_cpp = generate_main("tests/component_tests/sensor/test_sensor.yaml")
# Then
assert "s_1->configure_entity(" in main_cpp
assert "s_1->configure_entity_(" in main_cpp
+1 -1
View File
@@ -25,7 +25,7 @@ def test_text_sets_mandatory_fields(generate_main):
main_cpp = generate_main("tests/component_tests/text/test_text.yaml")
# Then
assert 'it_1->configure_entity("test 1 text",' in main_cpp
assert 'it_1->configure_entity_("test 1 text",' in main_cpp
def test_text_config_value_internal_set(generate_main):
@@ -25,9 +25,9 @@ def test_text_sensor_sets_mandatory_fields(generate_main):
main_cpp = generate_main("tests/component_tests/text_sensor/test_text_sensor.yaml")
# Then
assert 'ts_1->configure_entity("Template Text Sensor 1",' in main_cpp
assert 'ts_2->configure_entity("Template Text Sensor 2",' in main_cpp
assert 'ts_3->configure_entity("Template Text Sensor 3",' in main_cpp
assert 'ts_1->configure_entity_("Template Text Sensor 1",' in main_cpp
assert 'ts_2->configure_entity_("Template Text Sensor 2",' in main_cpp
assert 'ts_3->configure_entity_("Template Text Sensor 3",' in main_cpp
def test_text_sensor_config_value_internal_set(generate_main):
@@ -54,5 +54,5 @@ def test_text_sensor_device_class_set(generate_main):
main_cpp = generate_main("tests/component_tests/text_sensor/test_text_sensor.yaml")
# Then
assert "ts_2->configure_entity(" in main_cpp
assert "ts_3->configure_entity(" in main_cpp
assert "ts_2->configure_entity_(" in main_cpp
assert "ts_3->configure_entity_(" in main_cpp
+5 -5
View File
@@ -31,10 +31,10 @@ from esphome.helpers import sanitize, snake_case
from .common import load_config_from_fixture
# Pre-compiled regex pattern for extracting names from configure_entity/set_name calls
# Matches: .configure_entity("name", ...) or .set_name("name", ...)
# Pre-compiled regex pattern for extracting names from configure_entity_/set_name calls
# Matches: .configure_entity_("name", ...) or .set_name("name", ...)
ENTITY_NAME_PATTERN = re.compile(
r'\.(?:configure_entity|set_name)\(["\']([^"\']*)["\']'
r'\.(?:configure_entity_|set_name)\(["\']([^"\']*)["\']'
)
FIXTURES_DIR = Path(__file__).parent.parent / "fixtures" / "core" / "entity_helpers"
@@ -291,7 +291,7 @@ def extract_object_id_from_config(config: dict[str, Any]) -> str | None:
def extract_object_id_from_expressions(expressions: list[str]) -> str | None:
"""Extract the object ID from configure_entity() calls in generated expressions."""
"""Extract the object ID from configure_entity_() calls in generated expressions."""
for expr in expressions:
if match := ENTITY_NAME_PATTERN.search(expr):
name = match.group(1)
@@ -954,7 +954,7 @@ async def test_setup_entity_direct_call(setup_test_environment: list[str]) -> No
# Direct call mode: await setup_entity(var, config, "camera")
await setup_entity(var, config, "camera")
# Should have emitted configure_entity
# Should have emitted configure_entity_
object_id = extract_object_id_from_expressions(added_expressions)
assert object_id == "my_camera"