diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 578ffb84c..67b5e0c98 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -79,6 +79,9 @@ jobs: - name: Run Tests shell: bash run: | + if [[ "${{ matrix.os }}" == "windows-latest" && "${{ matrix.generator }}" == "MinGW Makefiles" ]]; then + export PATH="/c/mingw64/bin:$PATH" + fi ctest \ --test-dir build \ --build-config ${{ env.CMAKE_BUILD_TYPE }} \ diff --git a/include/yaml-cpp/exceptions.h b/include/yaml-cpp/exceptions.h index 40bfcbf5f..e11f4137a 100644 --- a/include/yaml-cpp/exceptions.h +++ b/include/yaml-cpp/exceptions.h @@ -98,6 +98,7 @@ const char* const INVALID_ANCHOR = "invalid anchor"; const char* const INVALID_ALIAS = "invalid alias"; const char* const INVALID_TAG = "invalid tag"; const char* const BAD_FILE = "bad file"; +const char* const BAD_STREAM = "bad stream"; const char* const UNEXPECTED_TOKEN_AFTER_DOC = "unexpected token after end of document"; const char* const NON_UNIQUE_MAP_KEY = "map keys must be unique"; @@ -305,6 +306,13 @@ class YAML_CPP_API EmitterException : public Exception { ~EmitterException() YAML_CPP_NOEXCEPT override; }; +class YAML_CPP_API BadStream : public Exception { + public: + BadStream() : Exception(Mark::null_mark(), ErrorMsg::BAD_STREAM) {} + BadStream(const BadStream&) = default; + ~BadStream() YAML_CPP_NOEXCEPT override; +}; + class YAML_CPP_API BadFile : public Exception { public: explicit BadFile(const std::string& filename) diff --git a/src/exceptions.cpp b/src/exceptions.cpp index af99fd6b7..9ef5da24b 100644 --- a/src/exceptions.cpp +++ b/src/exceptions.cpp @@ -16,6 +16,7 @@ BadSubscript::~BadSubscript() YAML_CPP_NOEXCEPT = default; BadPushback::~BadPushback() YAML_CPP_NOEXCEPT = default; BadInsert::~BadInsert() YAML_CPP_NOEXCEPT = default; EmitterException::~EmitterException() YAML_CPP_NOEXCEPT = default; +BadStream::~BadStream() YAML_CPP_NOEXCEPT = default; BadFile::~BadFile() YAML_CPP_NOEXCEPT = default; NonUniqueMapKey::~NonUniqueMapKey() YAML_CPP_NOEXCEPT = default; } // namespace YAML diff --git a/src/stream.cpp b/src/stream.cpp index 794ae873a..1959afa30 100644 --- a/src/stream.cpp +++ b/src/stream.cpp @@ -1,6 +1,7 @@ #include #include "stream.h" +#include "yaml-cpp/exceptions.h" #ifndef YAML_PREFETCH_SIZE #define YAML_PREFETCH_SIZE 2048 @@ -192,8 +193,8 @@ Stream::Stream(std::istream& input) m_nPrefetchedUsed(0) { using char_traits = std::istream::traits_type; - if (!input) - return; + if (input.fail()) + throw BadStream(); // Determine (or guess) the character-set by reading the BOM, if any. See // the YAML specification for the determination algorithm. @@ -202,6 +203,8 @@ Stream::Stream(std::istream& input) UtfIntroState state = uis_start; for (; !s_introFinalState[state];) { std::istream::int_type ch = input.get(); + if (input.bad() || (input.fail() && !input.eof())) + throw BadStream(); intro[nIntroUsed++] = ch; UtfIntroCharType charType = IntroCharTypeOf(ch); UtfIntroState newState = s_introTransitions[state][charType]; @@ -240,7 +243,7 @@ Stream::Stream(std::istream& input) ReadAheadTo(0); } -Stream::~Stream() { delete[] m_pPrefetched; } +Stream::~Stream() = default; char Stream::peek() const { if (m_readahead.empty()) { @@ -423,9 +426,16 @@ inline char* ReadBuffer(unsigned char* pBuffer) { unsigned char Stream::GetNextByte() const { if (m_nPrefetchedUsed >= m_nPrefetchedAvailable) { std::streambuf* pBuf = m_input.rdbuf(); - m_nPrefetchedAvailable = static_cast( - pBuf->sgetn(ReadBuffer(m_pPrefetched), YAML_PREFETCH_SIZE)); + try { + m_nPrefetchedAvailable = static_cast( + pBuf->sgetn(ReadBuffer(m_pPrefetched.get()), YAML_PREFETCH_SIZE)); + } catch (const std::ios_base::failure&) { + throw BadStream(); + } m_nPrefetchedUsed = 0; + if (m_input.fail()) { + throw BadStream(); + } if (!m_nPrefetchedAvailable) { m_input.setstate(std::ios_base::eofbit); } diff --git a/src/stream.h b/src/stream.h index c5e3798c8..4689957a6 100644 --- a/src/stream.h +++ b/src/stream.h @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -55,7 +56,7 @@ class Stream { CharacterSet m_charSet; char m_lineEndingSymbol{}; // 0 means it is not determined yet, must be '\n' or '\r' mutable std::deque m_readahead; - unsigned char* const m_pPrefetched; + std::unique_ptr m_pPrefetched; mutable size_t m_nPrefetchedAvailable; mutable size_t m_nPrefetchedUsed; diff --git a/test/integration/load_node_test.cpp b/test/integration/load_node_test.cpp index e81c1dd77..bc57c0a39 100644 --- a/test/integration/load_node_test.cpp +++ b/test/integration/load_node_test.cpp @@ -1,15 +1,71 @@ #include "yaml-cpp/yaml.h" // IWYU pragma: keep +#include +#include + #include "gtest/gtest.h" namespace YAML { namespace { +class FailingStreamBuf : public std::stringbuf { + public: + explicit FailingStreamBuf(const std::string& input) + : std::stringbuf(input), first_read_(true) {} + + protected: + std::streamsize xsgetn(char* output, std::streamsize count) override { + if (first_read_) { + first_read_ = false; + return std::stringbuf::xsgetn(output, count > 32 ? 32 : count); + } + throw std::ios_base::failure("simulated read failure"); + } + + private: + bool first_read_; +}; + TEST(LoadNodeTest, Reassign) { Node node = Load("foo"); node = Node(); EXPECT_TRUE(node.IsNull()); } +TEST(LoadNodeTest, RejectsFailedInputStream) { + std::istringstream stream("key: value"); + stream.setstate(std::ios_base::failbit); + EXPECT_THROW(Load(stream), BadStream); +} + +TEST(LoadNodeTest, RejectsBadInputStream) { + std::istringstream stream("key: value"); + stream.setstate(std::ios_base::badbit); + EXPECT_THROW(Load(stream), BadStream); +} + +TEST(LoadNodeTest, LoadAllRejectsFailedInputStream) { + std::istringstream stream("---\nfirst\n---\nsecond\n"); + stream.setstate(std::ios_base::failbit); + EXPECT_THROW(LoadAll(stream), BadStream); +} + +TEST(LoadNodeTest, RejectsInputStreamFailureWhileReading) { + FailingStreamBuf buffer("value: " + std::string(128, 'a')); + std::istream stream(&buffer); + EXPECT_THROW(Load(stream), BadStream); +} + +TEST(LoadNodeTest, EmptyInputStreamRemainsNull) { + std::istringstream stream; + EXPECT_TRUE(Load(stream).IsNull()); +} + +TEST(LoadNodeTest, EofInputStreamRemainsNull) { + std::istringstream stream; + stream.setstate(std::ios_base::eofbit); + EXPECT_TRUE(Load(stream).IsNull()); +} + TEST(LoadNodeTest, FallbackValues) { Node node = Load("foo: bar\nx: 2"); EXPECT_EQ("bar", node["foo"].as());