pyyaml
pyyaml copied to clipboard
Loading nested object as key
The following setup triggers a crash on deserialize:
[+] In [23]: class A:
...: def __init__(self, a):
...: self.a = a
...:
...: def __hash__(self):
...: return hash(self.a)
...:
...: def __eq__(self, other):
...: return type(other) is type(self) and self.a == other.a
...:
...: def __getstate__(self):
...: return {'a': self.a}
...:
...: def __setstate__(self, state):
...: self.a = state['a']
[+] In [24]: a = A(1)
[+] In [25]: b = {(a, 1): 1}
[+] In [26]: with open('a.yaml', 'w') as fp:
...: yaml.dump(b, fp)
...:
[+] In [27]: with open('a.yaml', 'r') as fp:
...: b = yaml.unsafe_load(fp)
...:
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-27-672f39e43a2c> in <module>
1 with open('a.yaml', 'r') as fp:
----> 2 b = yaml.unsafe_load(fp)
3
~/proj/celeste/Magic/timer/yaml/__init__.py in unsafe_load(stream)
143 unsafe on untrusted input.
144 """
--> 145 return load(stream, UnsafeLoader)
146
147 def unsafe_load_all(stream):
~/proj/celeste/Magic/timer/yaml/__init__.py in load(stream, Loader)
79 loader = Loader(stream)
80 try:
---> 81 return loader.get_single_data()
82 finally:
83 loader.dispose()
~/proj/celeste/Magic/timer/yaml/constructor.py in get_single_data(self)
49 node = self.get_single_node()
50 if node is not None:
---> 51 return self.construct_document(node)
52 return None
53
~/proj/celeste/Magic/timer/yaml/constructor.py in construct_document(self, node)
58 self.state_generators = []
59 for generator in state_generators:
---> 60 for dummy in generator:
61 pass
62 self.constructed_objects = {}
~/proj/celeste/Magic/timer/yaml/constructor.py in construct_yaml_map(self, node)
411 data = {}
412 yield data
--> 413 value = self.construct_mapping(node)
414 data.update(value)
415
~/proj/celeste/Magic/timer/yaml/constructor.py in construct_mapping(self, node, deep)
216 if isinstance(node, MappingNode):
217 self.flatten_mapping(node)
--> 218 return super().construct_mapping(node, deep=deep)
219
220 def construct_yaml_null(self, node):
~/proj/celeste/Magic/timer/yaml/constructor.py in construct_mapping(self, node, deep)
142 "found unhashable key", key_node.start_mark)
143 value = self.construct_object(value_node, deep=deep)
--> 144 mapping[key] = value
145 return mapping
146
<ipython-input-17-8dbd1d52c935> in __hash__(self)
4
5 def __hash__(self):
----> 6 return hash(self.a)
7
8 def __eq__(self, other):
AttributeError: 'A' object has no attribute 'a'
I believe this is because mapping keys need to be deep-constructed prior to being inserted into the map. The following diff fixes the issue for me:
mapping = {}
for key_node, value_node in node.value:
- key = self.construct_object(key_node, deep=deep)
+ key = self.construct_object(key_node, deep=True)
if not isinstance(key, collections.abc.Hashable):
raise ConstructorError("while constructing a mapping", node.start_mark
Hit the same when using a frozen dataclass in a set or in a dictionary.
A more complex fix is needed to cover more scenarios. I will create a PR soon.
https://github.com/yaml/pyyaml/pull/654