From 8b4e2e93d9b545f5888e2add50ed06c6df0a2ec9 Mon Sep 17 00:00:00 2001 From: njzjz-bot Date: Mon, 24 Aug 2026 03:10:37 +0800 Subject: [PATCH] fix: copy mutable defaults during normalization Coding-Agent: Codex Codex-Version: codex-cli 0.149.0 Model: gpt-5.6-sol Reasoning-Effort: xhigh --- dargs/dargs.py | 6 +++++- tests/test_normalizer.py | 20 ++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/dargs/dargs.py b/dargs/dargs.py index f067b01..0e5485e 100644 --- a/dargs/dargs.py +++ b/dargs/dargs.py @@ -665,7 +665,11 @@ def _assign_default(self, argdict: dict, path: list[str] | None = None) -> None: and self.optional and self.default is not _Flags.NONE ): - default = self.default if self.default != {} else _Flags.EMPTY_DICT + # Defaults belong to the schema and must not be shared with caller-owned + # normalized data. Deep-copying also protects nested mutable objects. + default = ( + deepcopy(self.default) if self.default != {} else _Flags.EMPTY_DICT + ) argdict[self.name] = default def _handle_empty_dict(self, argdict: dict, path: list[str] | None = None) -> None: diff --git a/tests/test_normalizer.py b/tests/test_normalizer.py index 9f9d995..64607b9 100644 --- a/tests/test_normalizer.py +++ b/tests/test_normalizer.py @@ -30,6 +30,26 @@ def make_arguments() -> list[Argument]: data = base.normalize_value({}) self.assertDictEqual(data, {"bar": {}}) + def test_mutable_defaults_are_copied(self) -> None: + """Mutating normalized defaults must not change later results or schemas.""" + items = Argument("items", list, optional=True, default=[]) + settings = Argument( + "settings", + dict, + optional=True, + default={"labels": []}, + ) + base = Argument("base", dict, [items, settings]) + + first = base.normalize_value({}) + first["items"].append("changed") + first["settings"]["labels"].append("changed") + second = base.normalize_value({}) + + self.assertEqual(second, {"items": [], "settings": {"labels": []}}) + self.assertEqual(items.default, []) + self.assertEqual(settings.default, {"labels": []}) + def test_alias(self) -> None: ca = Argument("Key1", int, alias=["Old1", "Old2"]) beg = {"Old1": 1}