Skip to content

Commit 156332c

Browse files
committed
fix: segfault when moving scoped_ostream_redirect
The default move constructor left the stream (`std::cout`) pointing at the moved-from `pythonbuf`, whose internal buffer and streambuf pointers were nulled by the move. Any subsequent write through the stream dereferenced null, causing a segfault. Replace `= default` with an explicit move constructor that re-points the stream to the new buffer and disarms the moved-from destructor.
1 parent 98e50b7 commit 156332c

File tree

3 files changed

+24
-2
lines changed

3 files changed

+24
-2
lines changed

include/pybind11/iostream.h

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,10 +178,18 @@ class scoped_ostream_redirect {
178178
old = costream.rdbuf(&buffer);
179179
}
180180

181-
~scoped_ostream_redirect() { costream.rdbuf(old); }
181+
~scoped_ostream_redirect() {
182+
if (old) {
183+
costream.rdbuf(old);
184+
}
185+
}
182186

183187
scoped_ostream_redirect(const scoped_ostream_redirect &) = delete;
184-
scoped_ostream_redirect(scoped_ostream_redirect &&other) = default;
188+
scoped_ostream_redirect(scoped_ostream_redirect &&other)
189+
: old(other.old), costream(other.costream), buffer(std::move(other.buffer)) {
190+
other.old = nullptr; // Disarm moved-from destructor
191+
costream.rdbuf(&buffer); // Re-point stream to our buffer
192+
}
185193
scoped_ostream_redirect &operator=(const scoped_ostream_redirect &) = delete;
186194
scoped_ostream_redirect &operator=(scoped_ostream_redirect &&) = delete;
187195
};

tests/test_iostream.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,4 +123,11 @@ TEST_SUBMODULE(iostream, m) {
123123
.def("stop", &TestThread::stop)
124124
.def("join", &TestThread::join)
125125
.def("sleep", &TestThread::sleep);
126+
127+
m.def("move_redirect_output", [](const std::string &msg) {
128+
py::scoped_ostream_redirect redir1(std::cout, py::module_::import("sys").attr("stdout"));
129+
std::cout << "before" << std::flush;
130+
py::scoped_ostream_redirect redir2(std::move(redir1));
131+
std::cout << msg << std::flush;
132+
});
126133
}

tests/test_iostream.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,13 @@ def test_redirect_both(capfd):
284284
assert stream2.getvalue() == msg2
285285

286286

287+
def test_move_redirect(capsys):
288+
m.move_redirect_output("after")
289+
stdout, stderr = capsys.readouterr()
290+
assert stdout == "beforeafter"
291+
assert not stderr
292+
293+
287294
@pytest.mark.skipif(sys.platform.startswith("emscripten"), reason="Requires threads")
288295
def test_threading():
289296
with m.ostream_redirect(stdout=True, stderr=False):

0 commit comments

Comments
 (0)