Skip to content

Commit 3c82d70

Browse files
gdadunashvili4og
authored andcommitted
safe_atomics: Add utility for overflow protected atomic fetch_add
GIT_ORIGIN_SPP_REV_ID: 42bdb0e51ce1b0e97d5bff11166d1c91f587afcb
1 parent b873e7f commit 3c82d70

File tree

9 files changed

+446
-1
lines changed

9 files changed

+446
-1
lines changed
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# *******************************************************************************
2+
# Copyright (c) 2025 Contributors to the Eclipse Foundation
3+
#
4+
# See the NOTICE file(s) distributed with this work for additional
5+
# information regarding copyright ownership.
6+
#
7+
# This program and the accompanying materials are made available under the
8+
# terms of the Apache License Version 2.0 which is available at
9+
# https://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# SPDX-License-Identifier: Apache-2.0
12+
# *******************************************************************************
13+
14+
load("@score_baselibs//:bazel/unit_tests.bzl", "cc_gtest_unit_test", "cc_unit_test_suites_for_host_and_qnx")
15+
load("@score_baselibs//score/language/safecpp:toolchain_features.bzl", "COMPILER_WARNING_FEATURES")
16+
17+
cc_library(
18+
name = "try_atomic_add",
19+
srcs = ["try_atomic_add.cpp"],
20+
hdrs = ["try_atomic_add.h"],
21+
features = COMPILER_WARNING_FEATURES,
22+
tags = ["FFI"],
23+
visibility = [
24+
"@score_baselibs//score/language/safecpp/safe_math:__pkg__",
25+
],
26+
deps = [
27+
":error",
28+
"@score_baselibs//score/language/safecpp/safe_math/details/addition_subtraction",
29+
"@score_baselibs//score/memory/shared:atomic_indirector",
30+
"@score_baselibs//score/result",
31+
],
32+
)
33+
34+
cc_library(
35+
name = "error",
36+
srcs = ["error.cpp"],
37+
hdrs = ["error.h"],
38+
features = COMPILER_WARNING_FEATURES,
39+
tags = ["FFI"],
40+
visibility = ["@score_baselibs//score/language/safecpp/safe_atomics:__subpackages__"],
41+
deps = ["@score_baselibs//score/result"],
42+
)
43+
44+
cc_gtest_unit_test(
45+
name = "try_atomic_add_test",
46+
srcs = ["try_atomic_add_test.cpp"],
47+
features = COMPILER_WARNING_FEATURES,
48+
deps = [
49+
":try_atomic_add",
50+
"@score_baselibs//score/language/safecpp/safe_math/details:test_type_collection",
51+
"@score_baselibs//score/memory/shared:atomic_indirector_mock_binding",
52+
],
53+
)
54+
55+
cc_gtest_unit_test(
56+
name = "error_test",
57+
srcs = ["error_test.cpp"],
58+
features = COMPILER_WARNING_FEATURES,
59+
deps = [":error"],
60+
)
61+
62+
cc_unit_test_suites_for_host_and_qnx(
63+
name = "unit_tests",
64+
cc_unit_tests = [
65+
":try_atomic_add_test",
66+
":error_test",
67+
],
68+
visibility = ["@score_baselibs//score/language/safecpp:__pkg__"],
69+
)
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/********************************************************************************
2+
* Copyright (c) 2025 Contributors to the Eclipse Foundation
3+
*
4+
* See the NOTICE file(s) distributed with this work for additional
5+
* information regarding copyright ownership.
6+
*
7+
* This program and the accompanying materials are made available under the
8+
* terms of the Apache License Version 2.0 which is available at
9+
* https://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* SPDX-License-Identifier: Apache-2.0
12+
********************************************************************************/
13+
#include "score/language/safecpp/safe_atomics/error.h"
14+
15+
std::string_view score::safe_atomics::ErrorDomain::MessageFor(const score::result::ErrorCode& code) const noexcept
16+
{
17+
using SafeMathErrorCode = score::safe_atomics::ErrorCode;
18+
using ResultErrorCode = std::decay_t<decltype(code)>;
19+
static_assert(std::is_same_v<ResultErrorCode, std::underlying_type_t<SafeMathErrorCode>>);
20+
21+
// Suppress "AUTOSAR C++14 A7-2-1" as false positive
22+
// // Suppress "AUTOSAR C++14 M6-4-5": The `return` statement in this case clause unconditionally exits the
23+
// function, making an additional `break` statement redundant.
24+
// coverity[autosar_cpp14_m6_4_3_violation]
25+
// coverity[autosar_cpp14_a7_2_1_violation: FALSE]
26+
switch (static_cast<SafeMathErrorCode>(code))
27+
{
28+
case ErrorCode::kUnexpectedError:
29+
return "Unexpected Error was propagated up by a dependent library";
30+
// coverity[autosar_cpp14_m6_4_5_violation]
31+
case ErrorCode::kExceedsNumericLimits:
32+
return "Operation exceeds numeric limits";
33+
// coverity[autosar_cpp14_m6_4_5_violation]
34+
case ErrorCode::kMaxRetriesReached:
35+
return "Max retries reached";
36+
// coverity[autosar_cpp14_m6_4_5_violation]
37+
case ErrorCode::kUnknown:
38+
[[fallthrough]];
39+
default:
40+
return "Unknown error";
41+
}
42+
}
43+
44+
namespace
45+
{
46+
constexpr score::safe_atomics::ErrorDomain my_safe_math_error_domain;
47+
}
48+
49+
score::result::Error score::safe_atomics::MakeError(const ErrorCode code, const std::string_view user_message) noexcept
50+
{
51+
return {static_cast<score::result::ErrorCode>(code), my_safe_math_error_domain, user_message};
52+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/********************************************************************************
2+
* Copyright (c) 2025 Contributors to the Eclipse Foundation
3+
*
4+
* See the NOTICE file(s) distributed with this work for additional
5+
* information regarding copyright ownership.
6+
*
7+
* This program and the accompanying materials are made available under the
8+
* terms of the Apache License Version 2.0 which is available at
9+
* https://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* SPDX-License-Identifier: Apache-2.0
12+
********************************************************************************/
13+
#ifndef SCORE_LANGUAGE_SAFECPP_SAFE_ATOMICS_ERROR_H
14+
#define SCORE_LANGUAGE_SAFECPP_SAFE_ATOMICS_ERROR_H
15+
16+
#include "score/result/error.h"
17+
#include "score/result/error_code.h"
18+
#include "score/result/error_domain.h"
19+
20+
namespace score::safe_atomics
21+
{
22+
enum class ErrorCode : score::result::ErrorCode
23+
{
24+
kUnknown = 0, // Value for default initialization - never returned on purpose
25+
kUnexpectedError,
26+
kExceedsNumericLimits,
27+
kMaxRetriesReached,
28+
};
29+
30+
class ErrorDomain final : public score::result::ErrorDomain
31+
{
32+
public:
33+
std::string_view MessageFor(const score::result::ErrorCode& code) const noexcept override;
34+
};
35+
36+
score::result::Error MakeError(const ErrorCode code, const std::string_view user_message = "") noexcept;
37+
38+
} // namespace score::safe_atomics
39+
40+
#endif // SCORE_LANGUAGE_SAFECPP_SAFE_ATOMICS_ERROR_H
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/********************************************************************************
2+
* Copyright (c) 2025 Contributors to the Eclipse Foundation
3+
*
4+
* See the NOTICE file(s) distributed with this work for additional
5+
* information regarding copyright ownership.
6+
*
7+
* This program and the accompanying materials are made available under the
8+
* terms of the Apache License Version 2.0 which is available at
9+
* https://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* SPDX-License-Identifier: Apache-2.0
12+
********************************************************************************/
13+
#include "score/language/safecpp/safe_atomics/error.h"
14+
15+
#include "gtest/gtest.h"
16+
17+
namespace score::safe_atomics
18+
{
19+
namespace
20+
{
21+
class ErrorDomainTest : public ::testing::Test
22+
{
23+
protected:
24+
ErrorDomain message_obj;
25+
26+
auto verifyErrorMessage(ErrorCode errorCode, std::string_view errorOutput)
27+
{
28+
const auto errorCodeTest = message_obj.MessageFor(static_cast<score::result::ErrorCode>(errorCode));
29+
ASSERT_EQ(errorCodeTest, errorOutput);
30+
}
31+
};
32+
33+
TEST_F(ErrorDomainTest, kUnexpectedError)
34+
{
35+
verifyErrorMessage(ErrorCode::kUnexpectedError, "Unexpected Error was propagated up by a dependent library");
36+
}
37+
38+
TEST_F(ErrorDomainTest, kExceedsNumericLimits)
39+
{
40+
verifyErrorMessage(ErrorCode::kExceedsNumericLimits, "Operation exceeds numeric limits");
41+
}
42+
43+
TEST_F(ErrorDomainTest, kMaxRetriesReached)
44+
{
45+
verifyErrorMessage(ErrorCode::kMaxRetriesReached, "Max retries reached");
46+
}
47+
48+
TEST_F(ErrorDomainTest, kUnknown)
49+
{
50+
verifyErrorMessage(ErrorCode::kUnknown, "Unknown error");
51+
}
52+
53+
TEST_F(ErrorDomainTest, defaultValue)
54+
{
55+
// Errorcode has enum until value from 0, to use default case use -1.
56+
int ValueOutOfRange = -1;
57+
verifyErrorMessage(static_cast<ErrorCode>(ValueOutOfRange), "Unknown error");
58+
}
59+
} // namespace
60+
} // namespace score::safe_atomics
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/********************************************************************************
2+
* Copyright (c) 2025 Contributors to the Eclipse Foundation
3+
*
4+
* See the NOTICE file(s) distributed with this work for additional
5+
* information regarding copyright ownership.
6+
*
7+
* This program and the accompanying materials are made available under the
8+
* terms of the Apache License Version 2.0 which is available at
9+
* https://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* SPDX-License-Identifier: Apache-2.0
12+
********************************************************************************/
13+
#include "score/language/safecpp/safe_atomics/try_atomic_add.h"
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/********************************************************************************
2+
* Copyright (c) 2025 Contributors to the Eclipse Foundation
3+
*
4+
* See the NOTICE file(s) distributed with this work for additional
5+
* information regarding copyright ownership.
6+
*
7+
* This program and the accompanying materials are made available under the
8+
* terms of the Apache License Version 2.0 which is available at
9+
* https://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* SPDX-License-Identifier: Apache-2.0
12+
********************************************************************************/
13+
#ifndef SCORE_LIB_SAFE_MATH_SAFE_ATOMICS_H
14+
#define SCORE_LIB_SAFE_MATH_SAFE_ATOMICS_H
15+
16+
#include "score/language/safecpp/safe_atomics/error.h"
17+
18+
#include "score/language/safecpp/safe_math/details/addition_subtraction/addition_subtraction.h"
19+
#include "score/memory/shared/atomic_indirector.h"
20+
#include "score/result/result.h"
21+
22+
#include <atomic>
23+
#include <type_traits>
24+
25+
namespace score::safe_atomics
26+
{
27+
namespace details
28+
{
29+
30+
/// \brief Similar to std::atomic fetch_add except protects against integer overflow.
31+
///
32+
/// \tparam T Type of underlying atomic
33+
/// \tparam AtomicIndirectorType when set to AtomicIndirectorReal, will dispatch to regular atomic operations. When
34+
/// set to AtomicIndirectorMock, allows mocking of atomic behaviour for testing.
35+
///
36+
/// \Return Previous value of atomic before addition if addition would not lead to integer overflow. Otherwise, returns
37+
/// an error.
38+
template <class T,
39+
template <class> class AtomicIndirectorType = memory::shared::AtomicIndirectorReal,
40+
typename std::enable_if_t<std::is_integral<T>::value, bool> = true>
41+
score::Result<T> TryAtomicAddImpl(std::atomic<T>& atomic, const T addition_value, const std::size_t max_retries)
42+
{
43+
for (std::size_t i = 0; i < max_retries; ++i)
44+
{
45+
auto current_value = atomic.load();
46+
const auto addition_result = safe_math::Add(current_value, addition_value);
47+
if (!(addition_result.has_value()))
48+
{
49+
50+
if (addition_result.error() == safe_math::ErrorCode::kExceedsNumericLimits)
51+
{
52+
return MakeUnexpected(ErrorCode::kExceedsNumericLimits);
53+
}
54+
55+
// Defensive Programming. This path can only be triggered if the implementation of this function or the
56+
// safe_math::Add function changes
57+
// LCOV_EXCL_LINE (Defensive programming.)
58+
return MakeUnexpected(ErrorCode::kUnexpectedError, addition_result.error().Message());
59+
}
60+
61+
const auto exchange_result = AtomicIndirectorType<T>::compare_exchange_strong(
62+
atomic, current_value, addition_result.value(), std::memory_order_seq_cst);
63+
if (exchange_result)
64+
{
65+
return current_value;
66+
}
67+
}
68+
69+
return MakeUnexpected(ErrorCode::kMaxRetriesReached);
70+
}
71+
72+
} // namespace details
73+
74+
template <class T>
75+
score::Result<T> TryAtomicAdd(std::atomic<T>& atomic, const T addition_value, const std::size_t max_retries = 10U)
76+
{
77+
return details::TryAtomicAddImpl<T>(atomic, addition_value, max_retries);
78+
}
79+
80+
} // namespace score::safe_atomics
81+
82+
#endif // SCORE_LIB_SAFE_MATH_SAFE_ATOMICS_H

0 commit comments

Comments
 (0)