diff --git a/src/odr/internal/common/element_registry.hpp b/src/odr/internal/common/element_registry.hpp index 4436c1367..11f8b8c36 100644 --- a/src/odr/internal/common/element_registry.hpp +++ b/src/odr/internal/common/element_registry.hpp @@ -58,6 +58,9 @@ template class SortedSideTable final { public: T &emplace(const ElementIdentifier id, T value) { + if (id > std::numeric_limits::max()) { + throw std::out_of_range("SortedSideTable::emplace: identifier too wide"); + } if (!m_entries.empty() && m_entries.back().first >= id) { throw std::invalid_argument( "SortedSideTable::emplace: identifier out of order"); diff --git a/src/odr/internal/common/text_cursor.hpp b/src/odr/internal/common/text_cursor.hpp index a1e13422e..3fd0a863e 100644 --- a/src/odr/internal/common/text_cursor.hpp +++ b/src/odr/internal/common/text_cursor.hpp @@ -3,6 +3,7 @@ #include #include +#include #include namespace odr::internal { @@ -32,7 +33,12 @@ class TextCursor { return c; } - void advance(const std::size_t count) { m_rest.remove_prefix(count); } + void advance(const std::size_t count) { + if (count > m_rest.size()) { + throw std::out_of_range("TextCursor::advance: past end of input"); + } + m_rest.remove_prefix(count); + } /// Back to a view @ref rest answered earlier, undoing what was read since. void seek(const std::string_view at) { m_rest = at; } @@ -46,7 +52,7 @@ class TextCursor { /// Whitespace ahead of it is filler; the text itself is the token. [[nodiscard]] bool consume(const char c) { skip_whitespace(); - if (peek() != c) { + if (empty() || peek() != c) { return false; } advance(1); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index e230dca1c..0f5566c00 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -54,12 +54,14 @@ add_executable(odr_test "src/internal/cfb/cfb_archive_test.cpp" + "src/internal/common/element_registry_test.cpp" "src/internal/common/filesystem_test.cpp" "src/internal/common/list_numbering_test.cpp" "src/internal/common/path_test.cpp" "src/internal/common/table_cursor_test.cpp" "src/internal/common/sheet_cell_source_test.cpp" "src/internal/common/table_range_test.cpp" + "src/internal/common/text_cursor_test.cpp" "src/internal/crypto/crypto_util_test.cpp" diff --git a/test/src/internal/common/element_registry_test.cpp b/test/src/internal/common/element_registry_test.cpp new file mode 100644 index 000000000..7785ebe4f --- /dev/null +++ b/test/src/internal/common/element_registry_test.cpp @@ -0,0 +1,23 @@ +#include + +#include + +#include +#include +#include + +using namespace odr::internal; + +TEST(SortedSideTable, rejects_wide_ids_without_corrupting_order) { + SortedSideTable table; + table.emplace(1, 10); + table.emplace(255, 20); + EXPECT_THROW(table.emplace(256, 30), std::out_of_range); + EXPECT_THROW( + table.emplace(std::numeric_limits::max(), 40), + std::out_of_range); + EXPECT_EQ(table.at(1), 10); + EXPECT_EQ(table.at(255), 20); + EXPECT_EQ(table.find(256), nullptr); + EXPECT_EQ(table.find(0), nullptr); +} diff --git a/test/src/internal/common/text_cursor_test.cpp b/test/src/internal/common/text_cursor_test.cpp new file mode 100644 index 000000000..dcd3b004f --- /dev/null +++ b/test/src/internal/common/text_cursor_test.cpp @@ -0,0 +1,20 @@ +#include + +#include + +#include +#include + +using namespace odr::internal; + +TEST(TextCursor, rejects_overread_and_distinguishes_nul_from_end) { + TextCursor cursor(std::string_view("\0x", 2)); + EXPECT_THROW(cursor.advance(3), std::out_of_range); + EXPECT_EQ(cursor.rest().size(), 2); + EXPECT_TRUE(cursor.consume('\0')); + EXPECT_EQ(cursor.take(), 'x'); + EXPECT_FALSE(cursor.consume('\0')); + EXPECT_TRUE(cursor.empty()); + EXPECT_NO_THROW(cursor.advance(0)); + EXPECT_THROW(cursor.advance(1), std::out_of_range); +}