Skip to content

Commit

Permalink
iox-#1431: Add unsafe_raw_access to iox::string
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
lucabart97 committed Nov 19, 2023
1 parent 51a018a commit 15d633b
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 0 deletions.
1 change: 1 addition & 0 deletions doc/website/release-notes/iceoryx-unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:**

Expand Down
32 changes: 32 additions & 0 deletions iceoryx_hoofs/buffer/include/iox/buffer_info.hpp
Original file line number Diff line number Diff line change
@@ -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 <cstdint>

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
76 changes: 76 additions & 0 deletions iceoryx_hoofs/test/moduletests/test_vocabulary_string.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "iceoryx_hoofs/testing/fatal_failure.hpp"
#include "iox/string.hpp"
#include "test.hpp"
#include <cstring>

namespace
{
Expand Down Expand Up @@ -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<void(char*, const uint64_t, const uint64_t)>& 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<char> 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<iox::HoofsError>(
[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<iox::HoofsError>(
[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 <uint64_t N>
/// int64_t compare(const string<N>& other) const noexcept
TYPED_TEST(stringTyped_test, CompareEqStringsResultsInZero)
Expand Down
15 changes: 15 additions & 0 deletions iceoryx_hoofs/vocabulary/include/iox/detail/string.inl
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,21 @@ inline bool string<Capacity>::unsafe_assign(const char* const str) noexcept
return true;
}

template <uint64_t Capacity>
inline void
string<Capacity>::unsafe_raw_access(const iox::function_ref<uint64_t(char*, const iox::BufferInfo info)>& 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 <uint64_t Capacity>
template <typename T>
inline IsStringOrCharArray<T, int64_t> string<Capacity>::compare(const T& other) const noexcept
Expand Down
17 changes: 17 additions & 0 deletions iceoryx_hoofs/vocabulary/include/iox/string.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -423,6 +425,21 @@ class string final
template <typename T>
IsStringOrCharArrayOrChar<T, bool> 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<uint64_t(char*, const iox::BufferInfo info)>& 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.
Expand Down

0 comments on commit 15d633b

Please sign in to comment.