Skip to content

Commit 0b1864c

Browse files
committed
Avoid crash when converting dict with circular reference
Fixes #73
1 parent 482c9a2 commit 0b1864c

File tree

2 files changed

+29
-3
lines changed

2 files changed

+29
-3
lines changed

include/pybind11_json/pybind11_json.hpp

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#ifndef PYBIND11_JSON_HPP
1010
#define PYBIND11_JSON_HPP
1111

12+
#include <set>
1213
#include <string>
1314
#include <vector>
1415

@@ -67,8 +68,12 @@ namespace pyjson
6768
}
6869
}
6970

70-
inline nl::json to_json(const py::handle& obj)
71+
inline nl::json to_json(const py::handle& obj, std::set<const PyObject*>& refs)
7172
{
73+
if (auto [_, unique] = refs.insert(obj.ptr()); !unique) {
74+
throw std::runtime_error("Circular reference detected");
75+
}
76+
7277
if (obj.ptr() == nullptr || obj.is_none())
7378
{
7479
return nullptr;
@@ -121,7 +126,7 @@ namespace pyjson
121126
auto out = nl::json::array();
122127
for (const py::handle value : obj)
123128
{
124-
out.push_back(to_json(value));
129+
out.push_back(to_json(value, refs));
125130
}
126131
return out;
127132
}
@@ -130,12 +135,19 @@ namespace pyjson
130135
auto out = nl::json::object();
131136
for (const py::handle key : obj)
132137
{
133-
out[py::str(key).cast<std::string>()] = to_json(obj[key]);
138+
out[py::str(key).cast<std::string>()] = to_json(obj[key], refs);
134139
}
135140
return out;
136141
}
137142
throw std::runtime_error("to_json not implemented for this type of object: " + py::repr(obj).cast<std::string>());
138143
}
144+
145+
inline nl::json to_json(const py::handle& obj)
146+
{
147+
std::set<const PyObject*> refs;
148+
return to_json(obj, refs);
149+
}
150+
139151
}
140152

141153
// nlohmann_json serializers

test/test_pybind11_json.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,3 +481,17 @@ TEST(pybind11_caster_fromjson, dict)
481481
ASSERT_EQ(j["number"].cast<int>(), 1234);
482482
ASSERT_EQ(j["hello"].cast<std::string>(), "world");
483483
}
484+
485+
TEST(pybind11_caster_tojson, recursive_dict)
486+
{
487+
py::scoped_interpreter guard;
488+
py::module m = create_module("test");
489+
490+
m.def("to_json", &test_fromtojson);
491+
492+
// Simulate calling this binding from Python with a dictionary as argument
493+
py::dict obj("number"_a=1234, "hello"_a="world");
494+
obj["recur"] = obj;
495+
496+
ASSERT_ANY_THROW(m.attr("to_json")(obj));
497+
}

0 commit comments

Comments
 (0)