From 75b2beb262d5b154120394e0a7278a3f40ad8c82 Mon Sep 17 00:00:00 2001 From: Julian Wingert Date: Thu, 3 Sep 2026 20:45:57 +0000 Subject: [PATCH] Test a stream read that fails on the first attempt FailingStreamBuf serves 32 bytes before it throws, so Stream's constructor completes and the failure is raised later, while scanning. Stream's constructor ends in ReadAheadTo(0), so a buffer that throws immediately raises it from inside the constructor instead - a path nothing covered. It is also the path that leaked before m_pPrefetched became a unique_ptr: a destructor does not run for an object whose constructor threw. Restoring the raw pointer keeps every existing test passing and green, while this one reports 2048 bytes leaked under AddressSanitizer. --- test/integration/load_node_test.cpp | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/test/integration/load_node_test.cpp b/test/integration/load_node_test.cpp index bc57c0a39..e5f52bea6 100644 --- a/test/integration/load_node_test.cpp +++ b/test/integration/load_node_test.cpp @@ -25,6 +25,22 @@ class FailingStreamBuf : public std::stringbuf { bool first_read_; }; +// Fails on the very first read, which the buffer above cannot do: it serves 32 +// bytes before throwing, so Stream's constructor completes and the failure is +// raised later, during scanning. Stream's constructor ends in ReadAheadTo(0), +// so a buffer that throws immediately makes the failure escape the constructor +// itself - a different path, and the one that used to leak the prefetch buffer +// because ~Stream cannot run for an object that was never constructed. +class ImmediatelyFailingStreamBuf : public std::stringbuf { + public: + ImmediatelyFailingStreamBuf() : std::stringbuf(std::string()) {} + + protected: + std::streamsize xsgetn(char*, std::streamsize) override { + throw std::ios_base::failure("simulated read failure"); + } +}; + TEST(LoadNodeTest, Reassign) { Node node = Load("foo"); node = Node(); @@ -55,6 +71,12 @@ TEST(LoadNodeTest, RejectsInputStreamFailureWhileReading) { EXPECT_THROW(Load(stream), BadStream); } +TEST(LoadNodeTest, RejectsInputStreamFailureOnFirstRead) { + ImmediatelyFailingStreamBuf buffer; + std::istream stream(&buffer); + EXPECT_THROW(Load(stream), BadStream); +} + TEST(LoadNodeTest, EmptyInputStreamRemainsNull) { std::istringstream stream; EXPECT_TRUE(Load(stream).IsNull());