Skip to content

py_to_monty() does not detect cyclic Python inputs and can segfault #351

@alexmojaki

Description

@alexmojaki

Bug

pydantic_monty input conversion does not appear to do any cycle detection on the Python -> MontyObject path.

As a result:

  • a self-referential dataclass input raises RecursionError
  • a self-referential list or dict input can segfault the Python process
  • mixed cyclic graphs can fail in different ways depending on which Python object types the recursion passes through

This is on commit a8645d8be07eac3a8b53ea6cb04d512d38024c41.

Repros

1. Self-referential dataclass: RecursionError

import dataclasses
import pydantic_monty

@dataclasses.dataclass
class A:
    a: object

x = A(1)
x.a = x

print(pydantic_monty.Monty('1', inputs=['x']).run(inputs={'x': x}))

This raises:

RecursionError: Stack overflow (used 16353 kB) in comparison

2. Self-referential list: segfault

import pydantic_monty

x = []
x.append(x)

print(pydantic_monty.Monty('1', inputs=['x']).run(inputs={'x': x}))

Running with python3 -X faulthandler shows the crash recursing in _monty's convert::py_to_monty.

The same happens with a self-referential dict:

import pydantic_monty

d = {}
d['self'] = d

print(pydantic_monty.Monty('1', inputs=['x']).run(inputs={'x': d}))

3. Mixed cyclic graphs change the failure mode

Some larger cyclic graphs do not fail the same way as the simple repros.

For example, this dataclass -> dict -> dataclass cycle aborts harder than the simpler dataclass self-cycle:

import dataclasses
import pydantic_monty

@dataclasses.dataclass
class Box:
    value: object

obj = {}
box = Box(obj)
obj['box'] = box

print(pydantic_monty.Monty('1', inputs=['x']).run(inputs={'x': box}))

This produced:

Fatal Python error: _Py_CheckRecursiveCall: Unrecoverable stack overflow (used 16376 kB) in comparison

By contrast, these still produced the softer RecursionError path for me:

  • dataclass -> list -> dataclass
  • dataclass <-> dataclass
  • a shared-object graph that later closes a cycle through a dataclass

So the bug is broader than just “dataclasses recurse” or “lists segfault”: the exact crash mode depends on the mix of Python container/object types traversed by py_to_monty().

Why this looks like the root cause

py_to_monty() recursively walks Python containers and dataclass fields, but I don't see any visited-set / identity memo on this path:

  • crates/monty-python/src/convert.rs
    • list conversion recurses via py_to_monty()
    • dict conversion recurses via py_to_monty()
    • dataclass conversion delegates to dataclass_to_monty()
  • crates/monty-python/src/dataclass.rs
    • dataclass_to_monty() walks fields and recursively calls py_to_monty() on field values

By contrast, Monty does have cycle handling on the output side when converting Value -> MontyObject via MontyObject::Cycle in crates/monty/src/object.rs.

So this looks like an asymmetry: output conversion is cycle-aware, input conversion is not.

Expected

At minimum, cyclic Python inputs should fail safely with a normal Python/Monty exception.

Even better would be one of:

  • reject cyclic inputs explicitly with a clear error
  • or add cycle-aware input conversion if that is supposed to be supported

But a process crash from a Python input value seems like a real bug either way.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions