diff --git a/AUTHORS.rst b/AUTHORS.rst index 6b44851..d8044c4 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -98,3 +98,4 @@ Suggestions and bug reporting: - d00m514y3r - Sébastien Weber (seb5g) - Ward Loos (wrdls) +- Vitaliy Voloshin (vitalivo) diff --git a/CHANGES.rst b/CHANGES.rst index c4a347d..3ac66a8 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,11 @@ Changelog ========= +Unreleased +---------- + +* Prevent frozen BoxList mutation through clear, in-place addition, and in-place multiplication. + Version 7.4.1 ------------- diff --git a/box/box_list.py b/box/box_list.py index 72609ff..2988d90 100644 --- a/box/box_list.py +++ b/box/box_list.py @@ -62,7 +62,7 @@ def __init__(self, iterable: Iterable | None = None, box_class: type[box.Box] = def frozen(*args, **kwargs): raise BoxError("BoxList is frozen") - for method in ["append", "extend", "insert", "pop", "remove", "reverse", "sort"]: + for method in ["append", "clear", "extend", "insert", "pop", "remove", "reverse", "sort"]: self.__setattr__(method, frozen) def __getitem__(self, item): @@ -113,6 +113,16 @@ def __setitem__(self, key, value): return super().__getitem__(pos).__setitem__(children, value) super().__setitem__(key, value) + def __iadd__(self, value): + if self.box_options.get("frozen_box"): + raise BoxError("BoxList is frozen") + return super().__iadd__(value) + + def __imul__(self, value): + if self.box_options.get("frozen_box"): + raise BoxError("BoxList is frozen") + return super().__imul__(value) + def _is_intact_type(self, obj): if self.box_options.get("box_intact_types") and isinstance(obj, self.box_options["box_intact_types"]): return True diff --git a/test/test_box_list.py b/test/test_box_list.py index 9cd90e0..7f6a1b5 100644 --- a/test/test_box_list.py +++ b/test/test_box_list.py @@ -3,6 +3,7 @@ # Test files gathered from json.org and yaml.org import json +import operator import os import shutil import sys @@ -274,3 +275,27 @@ def test_circular_references(self): circular_list.append(circular_list) circular_box = BoxList(circular_list) assert circular_box[0] == circular_box + + +@pytest.mark.parametrize( + "mutate, expected", + [ + (lambda value: value.clear(), []), + (lambda value: operator.iadd(value, [3]), [1, 2, 3]), + (lambda value: operator.imul(value, 2), [1, 2, 1, 2]), + (lambda value: operator.imul(value, 0), []), + ], +) +def test_frozen_list_inplace_mutations(mutate, expected): + frozen = BoxList([1, 2], frozen_box=True) + original_hash = hash(frozen) + with pytest.raises(BoxError, match="BoxList is frozen"): + mutate(frozen) + assert frozen == [1, 2] + assert hash(frozen) == original_hash + + mutable = BoxList([1, 2]) + alias = mutable + mutate(mutable) + assert mutable is alias + assert mutable == expected