From 79cd9896ef1a1b611e7d391c2d36cca0122edfdc Mon Sep 17 00:00:00 2001 From: Luca Bartoli Date: Sun, 19 Nov 2023 21:37:10 +0100 Subject: [PATCH] iox-#1431: Add unsafe_raw_access to iox::string Implement a method that provides direct access to the string's underlying pointer. This allows data to be written directly to the string, eliminating the need for copies. The string's length is checked on each call to ensure the null terminator is in the correct position. Signed-off-by: Luca Bartoli --- .../release-notes/iceoryx-unreleased.md | 1 + .../buffer/include/iox/buffer_info.hpp | 32 ++++++++ .../moduletests/test_vocabulary_string.cpp | 76 +++++++++++++++++++ .../vocabulary/include/iox/detail/string.inl | 15 ++++ .../vocabulary/include/iox/string.hpp | 17 +++++ 5 files changed, 141 insertions(+) create mode 100644 iceoryx_hoofs/buffer/include/iox/buffer_info.hpp diff --git a/doc/website/release-notes/iceoryx-unreleased.md b/doc/website/release-notes/iceoryx-unreleased.md index e295e1bae5..8d48e9d46d 100644 --- a/doc/website/release-notes/iceoryx-unreleased.md +++ b/doc/website/release-notes/iceoryx-unreleased.md @@ -52,6 +52,7 @@ - Create iceoryx version header for the C-binding [#1014](https://github.com/eclipse-iceoryx/iceoryx/issues/1014) - Create macros to deprecate header and code constructs [#2057](https://github.com/eclipse-iceoryx/iceoryx/issues/2057) - Switch to C++17 on all platforms [#2066](https://github.com/eclipse-iceoryx/iceoryx/issues/2066) +- Implement `unsafe_raw_access` in `iox::string` and add `BufferInfo` struct [#1431](https://github.com/eclipse-iceoryx/iceoryx/issues/1431) **Bugfixes:** diff --git a/iceoryx_hoofs/buffer/include/iox/buffer_info.hpp b/iceoryx_hoofs/buffer/include/iox/buffer_info.hpp new file mode 100644 index 0000000000..49c448d910 --- /dev/null +++ b/iceoryx_hoofs/buffer/include/iox/buffer_info.hpp @@ -0,0 +1,32 @@ +// Copyright 2023, Eclipse Foundation and the iceoryx contributors. All rights reserved. +// +// 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 +// +// http://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. +// +// SPDX-License-Identifier: Apache-2.0 +#ifndef IOX_HOOFS_BUFFER_BUFFER_INFO_HPP +#define IOX_HOOFS_BUFFER_BUFFER_INFO_HPP + +#include + +namespace iox +{ +/// @brief struct used to define the used size and total size of a buffer +struct BufferInfo +{ + uint64_t used_size{0}; + uint64_t total_size{0}; +}; + +} // namespace iox + +#endif \ No newline at end of file diff --git a/iceoryx_hoofs/test/moduletests/test_vocabulary_string.cpp b/iceoryx_hoofs/test/moduletests/test_vocabulary_string.cpp index a6b6b6bd27..4d5e98fb70 100644 --- a/iceoryx_hoofs/test/moduletests/test_vocabulary_string.cpp +++ b/iceoryx_hoofs/test/moduletests/test_vocabulary_string.cpp @@ -19,6 +19,7 @@ #include "iceoryx_hoofs/testing/fatal_failure.hpp" #include "iox/string.hpp" #include "test.hpp" +#include namespace { @@ -643,6 +644,81 @@ TYPED_TEST(stringTyped_test, UnsafeAssignOfNullptrFails) EXPECT_THAT(this->testSubject.unsafe_assign(nullptr), Eq(false)); } +/// @note void unsafe_raw_access(const std::function& func) noexcept +TYPED_TEST(stringTyped_test, UnsafeRawAccessOfCStringOfSize0ResultsInSize0) +{ + ::testing::Test::RecordProperty("TEST_ID", "43e10399-445d-42af-80b1-25071590de0a"); + this->testSubject.unsafe_raw_access([this](char* str, const auto info) -> uint64_t { + //NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.strcpy,-warnings-as-errors) + strcpy(str, ""); + EXPECT_THAT(info.used_size, this->testSubject.size()); + using MyString = typename TestFixture::stringType; + EXPECT_THAT(info.total_size, MyString::capacity() + 1); // real buffer size + return 0U; + }); + EXPECT_THAT(this->testSubject.size(), Eq(0U)); + EXPECT_THAT(this->testSubject.c_str(), StrEq("")); +} + +TYPED_TEST(stringTyped_test, UnsafeRawAccessOfCStringOfSize1ResultsInSize1) +{ + ::testing::Test::RecordProperty("TEST_ID", "a3a3395e-2b69-400c-876a-1fdf70cf2d4a"); + this->testSubject.unsafe_raw_access([this](char* str, const auto info) -> uint64_t { + //NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.strcpy,-warnings-as-errors) + strcpy(str, "M"); + EXPECT_THAT(info.used_size, this->testSubject.size()); + using MyString = typename TestFixture::stringType; + EXPECT_THAT(info.total_size, MyString::capacity() + 1); // real buffer size + return 1U; + }); + EXPECT_THAT(this->testSubject.size(), Eq(1U)); + EXPECT_THAT(this->testSubject.c_str(), StrEq("M")); +} + +TYPED_TEST(stringTyped_test, UnsafeRawAccessCStringOfSizeCapaResultsInSizeCapa) +{ + ::testing::Test::RecordProperty("TEST_ID", "49faad68-52fa-4024-993c-49b05e7cb971"); + using MyString = typename TestFixture::stringType; + constexpr auto STRINGCAP = MyString::capacity(); + std::vector testCharstring(STRINGCAP, 'M'); + testCharstring.emplace_back('\0'); + this->testSubject.unsafe_raw_access([&](char* str, const auto) -> uint64_t { + //NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.strcpy,-warnings-as-errors) + strcpy(str, testCharstring.data()); + return STRINGCAP; + }); + EXPECT_THAT(this->testSubject.unsafe_assign(testCharstring.data()), Eq(true)); + EXPECT_THAT(this->testSubject.size(), Eq(STRINGCAP)); +} + +TYPED_TEST(stringTyped_test, UnsafeRawAccessCStringOutOfBoundFail) +{ + ::testing::Test::RecordProperty("TEST_ID", "b25c35db-1c0d-4f0e-b4bc-b9430a6696f1"); + IOX_EXPECT_FATAL_FAILURE( + [this] { + this->testSubject.unsafe_raw_access([](char* str, const auto info) -> uint64_t { + //NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.strcpy,-warnings-as-errors) + strcpy(str, "M"); + return info.total_size + 1U; + }); + }, + iox::HoofsError::EXPECTS_ENSURES_FAILED); +} + +TYPED_TEST(stringTyped_test, UnsafeRawAccessCStringWrongLenghtFail) +{ + ::testing::Test::RecordProperty("TEST_ID", "411f5db1-18b8-45c3-9ad6-3c886fb12a26"); + IOX_EXPECT_FATAL_FAILURE( + [this] { + this->testSubject.unsafe_raw_access([](char* str, const auto) -> uint64_t { + //NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.strcpy,-warnings-as-errors) + strcpy(str, "M"); + return 0U; + }); + }, + iox::HoofsError::EXPECTS_ENSURES_FAILED); +} + /// @note template /// int64_t compare(const string& other) const noexcept TYPED_TEST(stringTyped_test, CompareEqStringsResultsInZero) diff --git a/iceoryx_hoofs/vocabulary/include/iox/detail/string.inl b/iceoryx_hoofs/vocabulary/include/iox/detail/string.inl index 5726ca4c98..aa473123f6 100644 --- a/iceoryx_hoofs/vocabulary/include/iox/detail/string.inl +++ b/iceoryx_hoofs/vocabulary/include/iox/detail/string.inl @@ -238,6 +238,21 @@ inline bool string::unsafe_assign(const char* const str) noexcept return true; } +template +inline void +string::unsafe_raw_access(const iox::function_ref& func) noexcept +{ + iox::BufferInfo info{m_rawstringSize, Capacity + 1}; + uint64_t len = func(m_rawstring, info); + + IOX_EXPECTS_WITH_MSG(Capacity >= len, + "unsafe_auto_raw_access failed. Data wrote outside the maximun string capacity of " + << Capacity); + IOX_EXPECTS_WITH_MSG(m_rawstring[len] == '\0', "String does not have the terminator at the returned size"); + m_rawstringSize = len; +} + + template template inline IsStringOrCharArray string::compare(const T& other) const noexcept diff --git a/iceoryx_hoofs/vocabulary/include/iox/string.hpp b/iceoryx_hoofs/vocabulary/include/iox/string.hpp index 16f2327cae..3142bffbcf 100644 --- a/iceoryx_hoofs/vocabulary/include/iox/string.hpp +++ b/iceoryx_hoofs/vocabulary/include/iox/string.hpp @@ -17,8 +17,10 @@ #ifndef IOX_HOOFS_VOCABULARY_STRING_HPP #define IOX_HOOFS_VOCABULARY_STRING_HPP +#include "iox/buffer_info.hpp" #include "iox/detail/string_internal.hpp" #include "iox/detail/string_type_traits.hpp" +#include "iox/function.hpp" #include "iox/log/logstream.hpp" #include "iox/optional.hpp" #include "iox/type_traits.hpp" @@ -423,6 +425,21 @@ class string final template IsStringOrCharArrayOrChar unsafe_append(const T& str) noexcept; + /// @brief direct access to the string's raw pointer. The access resizes the data with the value returned by the + /// passed function. If the data written has not the terminator at the returned size, a FATAL error occurs. + /// + /// @param [in] function func is a function composed by the raw data pointer and the BufferInfo with current size + /// and total size, including the space for the zero termination. The return value is the string length. + /// + /// @code + /// iox::string<100> s; + /// s.unsafe_raw_access([] (auto* str, const auto info) { + /// strncpy(str, "Hello World", info.total_size); + /// return strlen("Hello World"); + /// }); + /// @endcode + void unsafe_raw_access(const iox::function_ref& func) noexcept; + /// @brief inserts a iox::string or char array in the range [str[0], str[count]) at position pos. The insertion /// fails if the string capacity would be exceeded or pos is greater than the string size or count is greater than /// the string to be inserted.