diff --git a/doc/website/release-notes/iceoryx-unreleased.md b/doc/website/release-notes/iceoryx-unreleased.md index e295e1bae58..8d48e9d46d3 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 00000000000..49c448d9108 --- /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 a6b6b6bd27e..b820fa25a45 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([&testCharstring, &STRINGCAP](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 5726ca4c983..aa473123f6c 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 16f2327cae9..3142bffbcf5 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.