pyyaml icon indicating copy to clipboard operation
pyyaml copied to clipboard

Loading nested object as key

Open rhelmot opened this issue 3 years ago • 3 comments

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

rhelmot avatar Mar 06 '22 23:03 rhelmot

Hit the same when using a frozen dataclass in a set or in a dictionary.

ivannp avatar Jul 06 '22 21:07 ivannp

A more complex fix is needed to cover more scenarios. I will create a PR soon.

ivannp avatar Jul 16 '22 16:07 ivannp

https://github.com/yaml/pyyaml/pull/654

ivannp avatar Jul 16 '22 17:07 ivannp