Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }} \
Expand Down
8 changes: 8 additions & 0 deletions include/yaml-cpp/exceptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions src/exceptions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
20 changes: 15 additions & 5 deletions src/stream.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include <istream>

#include "stream.h"
#include "yaml-cpp/exceptions.h"

#ifndef YAML_PREFETCH_SIZE
#define YAML_PREFETCH_SIZE 2048
Expand Down Expand Up @@ -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.
Expand All @@ -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];
Expand Down Expand Up @@ -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()) {
Expand Down Expand Up @@ -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<std::size_t>(
pBuf->sgetn(ReadBuffer(m_pPrefetched), YAML_PREFETCH_SIZE));
try {
m_nPrefetchedAvailable = static_cast<std::size_t>(
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);
}
Expand Down
3 changes: 2 additions & 1 deletion src/stream.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <deque>
#include <ios>
#include <istream>
#include <memory>
#include <set>
#include <string>

Expand Down Expand Up @@ -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<char> m_readahead;
unsigned char* const m_pPrefetched;
std::unique_ptr<unsigned char[]> m_pPrefetched;
mutable size_t m_nPrefetchedAvailable;
mutable size_t m_nPrefetchedUsed;

Expand Down
56 changes: 56 additions & 0 deletions test/integration/load_node_test.cpp
Original file line number Diff line number Diff line change
@@ -1,15 +1,71 @@
#include "yaml-cpp/yaml.h" // IWYU pragma: keep

#include <sstream>
#include <string>

#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<std::string>());
Expand Down