diff --git a/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/cpp/CppGenerator.java b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/cpp/CppGenerator.java index 9b622e3989..55831e2dd4 100755 --- a/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/cpp/CppGenerator.java +++ b/sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/cpp/CppGenerator.java @@ -1207,6 +1207,10 @@ private void generateVarData( indent + " if (length != %3$s(0))\n" + indent + " {\n" + indent + " std::uint64_t pos = sbePosition();\n" + + indent + " if (SBE_BOUNDS_CHECK_EXPECT((length > (m_bufferLength - pos)), false))\n" + + indent + " {\n" + + indent + " throw std::runtime_error(\"buffer too short [E100]\");\n" + + indent + " }\n" + indent + " sbePosition(pos + length);\n" + indent + " std::memcpy(m_buffer + pos, src, length);\n" + indent + " }\n" + diff --git a/sbe-tool/src/test/cpp/CMakeLists.txt b/sbe-tool/src/test/cpp/CMakeLists.txt index ef756b03c8..9827cc26af 100644 --- a/sbe-tool/src/test/cpp/CMakeLists.txt +++ b/sbe-tool/src/test/cpp/CMakeLists.txt @@ -114,6 +114,7 @@ sbe_test(FieldAccessOrderCheckTest codecs) target_compile_definitions(FieldAccessOrderCheckTest PRIVATE SBE_ENABLE_PRECEDENCE_CHECKS_IN_TESTS) sbe_test(VersionedMessageTest codecs) sbe_test(Uint64LengthTest codecs) +sbe_test(VarDataPutUint64LengthTest codecs) # OtfMessageEncoderTest lives in the otf/ subdirectory and provides its own main() that takes the # IR file path as argv[1]; it is handed the generated code-generation-schema IR. diff --git a/sbe-tool/src/test/cpp/VarDataPutUint64LengthTest.cpp b/sbe-tool/src/test/cpp/VarDataPutUint64LengthTest.cpp new file mode 100644 index 0000000000..522c9e3cff --- /dev/null +++ b/sbe-tool/src/test/cpp/VarDataPutUint64LengthTest.cpp @@ -0,0 +1,74 @@ +/* + * Copyright 2026 Real Logic Limited. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include +#include +#include +#include + +#include + +#include "uint64_length/Uint64Lengths.h" + +using namespace uint64_length; + +/* + * Uint64Lengths with an empty group puts its text length prefix at offset 14 and the text at offset 22. + */ +static const std::uint64_t TEXT_OFFSET = 22; + +class VarDataPutUint64LengthTest : public testing::Test +{ +public: + Uint64Lengths &encode(std::vector &buffer) + { + m_encoder.wrapForEncode(buffer.data(), 0, buffer.size()); + m_encoder.id(7); + m_encoder.entriesCount(0); + return m_encoder; + } + +private: + Uint64Lengths m_encoder; +}; + +TEST_F(VarDataPutUint64LengthTest, shouldRejectLengthThatWrapsPosition) +{ + std::vector buffer(TEXT_OFFSET + 8); + const char src[4] = { 'a', 'b', 'c', 'd' }; + // not a constant, so the compiler does not see a huge memcpy at build time + volatile std::uint64_t length = std::numeric_limits::max() - 3; + + EXPECT_THROW(encode(buffer).putText(src, length), std::runtime_error); +} + +TEST_F(VarDataPutUint64LengthTest, shouldRejectLengthOneByteOverBufferEnd) +{ + std::vector buffer(TEXT_OFFSET + 3); + + EXPECT_THROW(encode(buffer).putText("abcd", 4), std::runtime_error); +} + +TEST_F(VarDataPutUint64LengthTest, shouldPutVarDataEndingAtBufferEnd) +{ + std::vector buffer(TEXT_OFFSET + 3); + + Uint64Lengths &encoder = encode(buffer); + encoder.putText(std::string("abc")); + + EXPECT_EQ(encoder.encodedLength(), buffer.size()); + EXPECT_EQ(std::string(buffer.data() + TEXT_OFFSET, 3), "abc"); +}