Skip to content

Commit 9e3f253

Browse files
committed
od: Fix shared-state mutations in ODArray.__getitem__ generated subindices
When ODArray.__getitem__ auto-generates a variable from the array template for an undefined subindex, mutable attributes were copied by reference, so mutations to value_descriptions or bit_definitions on one generated variable would silently affect all others and the template itself. Fix by copying the two affected attributes independently: - value_descriptions: shallow copy (.copy()) — values are immutable str - bit_definitions: deep copy (copy.deepcopy()) — values are list[int] Adds regression tests for both cases.
1 parent 7f8347f commit 9e3f253

2 files changed

Lines changed: 39 additions & 2 deletions

File tree

canopen/objectdictionary/__init__.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from __future__ import annotations
66

7+
import copy
78
import logging
89
import struct
910
from collections.abc import Iterator, Mapping, MutableMapping
@@ -286,10 +287,15 @@ def __getitem__(self, subindex: Union[int, str]) -> ODVariable:
286287
var = ODVariable(name, self.index, subindex)
287288
var.parent = self
288289
for attr in ("data_type", "unit", "factor", "min", "max", "default",
289-
"access_type", "description", "value_descriptions",
290-
"bit_definitions", "storage_location"):
290+
"access_type", "description", "storage_location"):
291291
if attr in template.__dict__:
292292
var.__dict__[attr] = template.__dict__[attr]
293+
# Mutable containers must be copied independently to avoid shared-state
294+
# mutations across generated subindex variables and the template.
295+
if "value_descriptions" in template.__dict__:
296+
var.value_descriptions = template.value_descriptions.copy()
297+
if "bit_definitions" in template.__dict__:
298+
var.bit_definitions = copy.deepcopy(template.bit_definitions)
293299
else:
294300
raise KeyError(f"Could not find subindex {pretty_index(None, subindex)}")
295301
return var

test/test_od.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,37 @@ def test_subindexes(self):
315315
self.assertEqual(array[2].name, "Test Variable 2")
316316
self.assertEqual(array[3].name, "Test Variable_3")
317317

318+
def _make_template_array(self):
319+
array = od.ODArray("Test Array", 0x1000)
320+
sub0 = od.ODVariable("Last subindex", 0x1000, 0)
321+
sub0.data_type = od.UNSIGNED8
322+
array.add_member(sub0)
323+
template = od.ODVariable("Test Variable", 0x1000, 1)
324+
template.data_type = od.UNSIGNED32
325+
return array, template
326+
327+
def test_generated_subindex_value_descriptions_independent(self):
328+
array, template = self._make_template_array()
329+
template.add_value_description(1, "One")
330+
array.add_member(template)
331+
sub2 = array[2]
332+
sub3 = array[3]
333+
# Mutating one generated variable must not affect the other or the template
334+
sub2.value_descriptions[2] = "Two"
335+
self.assertNotIn(2, sub3.value_descriptions)
336+
self.assertNotIn(2, template.value_descriptions)
337+
338+
def test_generated_subindex_bit_definitions_independent(self):
339+
array, template = self._make_template_array()
340+
template.add_bit_definition("FLAG", [0])
341+
array.add_member(template)
342+
sub2 = array[2]
343+
sub3 = array[3]
344+
# Mutating a list inside bit_definitions must not affect others
345+
sub2.bit_definitions["FLAG"].append(1)
346+
self.assertEqual(sub3.bit_definitions["FLAG"], [0])
347+
self.assertEqual(template.bit_definitions["FLAG"], [0])
348+
318349

319350
class TestEquality(unittest.TestCase):
320351

0 commit comments

Comments
 (0)