diff --git a/canopen/objectdictionary/__init__.py b/canopen/objectdictionary/__init__.py index 40ab8fbf..de8a279f 100644 --- a/canopen/objectdictionary/__init__.py +++ b/canopen/objectdictionary/__init__.py @@ -4,6 +4,7 @@ from __future__ import annotations +import copy import logging import struct from collections.abc import Iterator, Mapping, MutableMapping @@ -286,10 +287,15 @@ def __getitem__(self, subindex: Union[int, str]) -> ODVariable: var = ODVariable(name, self.index, subindex) var.parent = self for attr in ("data_type", "unit", "factor", "min", "max", "default", - "access_type", "description", "value_descriptions", - "bit_definitions", "storage_location"): + "access_type", "description", "storage_location"): if attr in template.__dict__: var.__dict__[attr] = template.__dict__[attr] + # Mutable containers must be copied independently to avoid shared-state + # mutations across generated subindex variables and the template. + if "value_descriptions" in template.__dict__: + var.value_descriptions = template.value_descriptions.copy() + if "bit_definitions" in template.__dict__: + var.bit_definitions = copy.deepcopy(template.bit_definitions) else: raise KeyError(f"Could not find subindex {pretty_index(None, subindex)}") return var diff --git a/test/test_od.py b/test/test_od.py index d6e3e984..4f6d34a0 100644 --- a/test/test_od.py +++ b/test/test_od.py @@ -315,6 +315,37 @@ def test_subindexes(self): self.assertEqual(array[2].name, "Test Variable 2") self.assertEqual(array[3].name, "Test Variable_3") + def _make_template_array(self): + array = od.ODArray("Test Array", 0x1000) + sub0 = od.ODVariable("Last subindex", 0x1000, 0) + sub0.data_type = od.UNSIGNED8 + array.add_member(sub0) + template = od.ODVariable("Test Variable", 0x1000, 1) + template.data_type = od.UNSIGNED32 + return array, template + + def test_generated_subindex_value_descriptions_independent(self): + array, template = self._make_template_array() + template.add_value_description(1, "One") + array.add_member(template) + sub2 = array[2] + sub3 = array[3] + # Mutating one generated variable must not affect the other or the template + sub2.value_descriptions[2] = "Two" + self.assertNotIn(2, sub3.value_descriptions) + self.assertNotIn(2, template.value_descriptions) + + def test_generated_subindex_bit_definitions_independent(self): + array, template = self._make_template_array() + template.add_bit_definition("FLAG", [0]) + array.add_member(template) + sub2 = array[2] + sub3 = array[3] + # Mutating a list inside bit_definitions must not affect others + sub2.bit_definitions["FLAG"].append(1) + self.assertEqual(sub3.bit_definitions["FLAG"], [0]) + self.assertEqual(template.bit_definitions["FLAG"], [0]) + class TestEquality(unittest.TestCase):