From 77e9ff53cb5fd297376a4e0afba02b48e69cbcce Mon Sep 17 00:00:00 2001 From: copybara Date: Fri, 25 Apr 2025 09:50:47 +0000 Subject: [PATCH 001/158] Project import generated by Copybara. GIT_ORIGIN_SPP_REV_ID: eea3ca95164219105a71b5aec99e5e7ff475cbeb --- score/memory/.clang-tidy-extra | 22 + score/memory/BUILD | 243 ++++ score/memory/README.md | 6 + score/memory/any_string_view.cpp | 13 + score/memory/any_string_view.h | 67 ++ score/memory/any_string_view_test.cpp | 108 ++ score/memory/design/README.md | 5 + .../design/shared_memory/OffsetPtrDesign.md | 165 +++ score/memory/design/shared_memory/README.md | 364 ++++++ .../anonymous_memory_allocation.uxf | 1022 ++++++++++++++++ .../design/shared_memory/bounds_checking.uxf | 257 ++++ .../shared_memory/get_owner_uid_seq.puml | 34 + .../shared_memory/memory_allocation.uxf | 410 +++++++ .../memory_allocation_workflow.uxf | 659 +++++++++++ .../shared_memory/named_memory_allocation.uxf | 1007 ++++++++++++++++ .../shared_memory/offset_ptr_problems.md | 55 + score/memory/endianness.cpp | 65 + score/memory/endianness.h | 88 ++ score/memory/endianness_test.cpp | 64 + score/memory/pmr_ring_buffer.h | 195 +++ score/memory/pmr_ring_buffer_test.cpp | 102 ++ score/memory/shared/BUILD | 738 ++++++++++++ score/memory/shared/atomic_indirector.cpp | 13 + score/memory/shared/atomic_indirector.h | 178 +++ .../memory/shared/atomic_indirector_test.cpp | 339 ++++++ score/memory/shared/atomic_mock.cpp | 13 + score/memory/shared/atomic_mock.h | 41 + .../memory/shared/double_inverse_stored_int.h | 115 ++ .../shared/double_inverse_stored_int_test.cpp | 166 +++ .../fake/my_bounded_memory_resource.cpp | 103 ++ .../shared/fake/my_bounded_memory_resource.h | 108 ++ .../memory/shared/fake/my_memory_resource.cpp | 20 + score/memory/shared/fake/my_memory_resource.h | 136 +++ score/memory/shared/flags/BUILD | 28 + score/memory/shared/flock/BUILD | 101 ++ .../shared/flock/exclusive_flock_mutex.cpp | 34 + .../shared/flock/exclusive_flock_mutex.h | 46 + score/memory/shared/flock/flock_mutex.cpp | 68 ++ score/memory/shared/flock/flock_mutex.h | 42 + .../shared/flock/flock_mutex_and_lock.cpp | 13 + .../shared/flock/flock_mutex_and_lock.h | 52 + .../memory/shared/flock/flock_mutex_test.cpp | 193 +++ .../shared/flock/shared_flock_mutex.cpp | 33 + .../memory/shared/flock/shared_flock_mutex.h | 46 + score/memory/shared/i_atomic.h | 58 + .../memory/shared/i_shared_memory_factory.cpp | 13 + score/memory/shared/i_shared_memory_factory.h | 80 ++ .../shared/i_shared_memory_resource.cpp | 13 + .../memory/shared/i_shared_memory_resource.h | 79 ++ score/memory/shared/lock_file.cpp | 148 +++ score/memory/shared/lock_file.h | 74 ++ score/memory/shared/lock_file_test.cpp | 569 +++++++++ .../memory/shared/managed_memory_resource.cpp | 13 + score/memory/shared/managed_memory_resource.h | 163 +++ .../shared/managed_memory_resource_test.cpp | 62 + score/memory/shared/map.cpp | 13 + score/memory/shared/map.h | 58 + score/memory/shared/map_test.cpp | 97 ++ score/memory/shared/memory_region_map.cpp | 387 ++++++ score/memory/shared/memory_region_map.h | 180 +++ .../memory/shared/memory_region_map_test.cpp | 604 ++++++++++ score/memory/shared/memory_resource_proxy.cpp | 111 ++ score/memory/shared/memory_resource_proxy.h | 89 ++ .../shared/memory_resource_proxy_test.cpp | 200 ++++ .../shared/memory_resource_registry.cpp | 134 +++ .../memory/shared/memory_resource_registry.h | 116 ++ .../shared/memory_resource_registry_test.cpp | 461 ++++++++ .../shared/new_delete_delegate_resource.cpp | 193 +++ .../shared/new_delete_delegate_resource.h | 94 ++ .../new_delete_delegate_resource_test.cpp | 190 +++ score/memory/shared/offset_ptr.cpp | 45 + score/memory/shared/offset_ptr.h | 1049 +++++++++++++++++ .../memory/shared/offset_ptr_bounds_check.cpp | 147 +++ score/memory/shared/offset_ptr_bounds_check.h | 39 + .../memory/shared/pointer_arithmetic_util.cpp | 142 +++ score/memory/shared/pointer_arithmetic_util.h | 254 ++++ .../shared/pointer_arithmetic_util_test.cpp | 721 +++++++++++ .../polymorphic_offset_ptr_allocator.cpp | 13 + .../shared/polymorphic_offset_ptr_allocator.h | 174 +++ .../polymorphic_offset_ptr_allocator_test.cpp | 103 ++ .../shared/sealedshm/sealedshm_wrapper/BUILD | 80 ++ .../sealedshm_wrapper/i_sealed_shm.h | 53 + .../sealedshm_wrapper/sealed_shm.cpp | 112 ++ .../sealedshm/sealedshm_wrapper/sealed_shm.h | 89 ++ .../sealedshm_wrapper/sealed_shm_mock.cpp | 13 + .../sealedshm_wrapper/sealed_shm_mock.h | 39 + .../sealedshm_wrapper/sealed_shm_test.cpp | 154 +++ score/memory/shared/shared_memory_error.cpp | 30 + score/memory/shared/shared_memory_error.h | 66 ++ .../shared/shared_memory_error_test.cpp | 46 + score/memory/shared/shared_memory_factory.cpp | 102 ++ score/memory/shared/shared_memory_factory.h | 184 +++ .../shared/shared_memory_factory_impl.cpp | 282 +++++ .../shared/shared_memory_factory_impl.h | 80 ++ .../shared/shared_memory_factory_mock.cpp | 13 + .../shared/shared_memory_factory_mock.h | 66 ++ .../shared/shared_memory_factory_test.cpp | 844 +++++++++++++ .../memory/shared/shared_memory_resource.cpp | 1014 ++++++++++++++++ score/memory/shared/shared_memory_resource.h | 425 +++++++ .../shared_memory_resource_allocate_test.cpp | 286 +++++ ..._memory_resource_create_anonymous_test.cpp | 224 ++++ ...ed_memory_resource_create_or_open_test.cpp | 159 +++ .../shared_memory_resource_create_test.cpp | 648 ++++++++++ ...ed_memory_resource_heap_allocator_mock.cpp | 13 + ...ared_memory_resource_heap_allocator_mock.h | 78 ++ .../shared_memory_resource_misc_test.cpp | 227 ++++ .../shared/shared_memory_resource_mock.cpp | 13 + .../shared/shared_memory_resource_mock.h | 57 + .../shared_memory_resource_open_test.cpp | 567 +++++++++ .../shared/shared_memory_test_resources.cpp | 455 +++++++ .../shared/shared_memory_test_resources.h | 241 ++++ score/memory/shared/string.cpp | 13 + score/memory/shared/string.h | 70 ++ score/memory/shared/string_test.cpp | 98 ++ score/memory/shared/test/performance/BUILD | 23 + .../test/performance/offset_ptr_benchmark.cpp | 211 ++++ .../memory/shared/test/permission_check/BUILD | 32 + .../permission_check/permission_check.cpp | 177 +++ .../test_offset_ptr/arithmetic_test.cpp | 301 +++++ .../test_offset_ptr/assignment_test.cpp | 234 ++++ .../shared/test_offset_ptr/bool_ops_test.cpp | 46 + .../bounds_check_memory_pool.cpp | 28 + .../bounds_check_memory_pool.h | 196 +++ .../test_offset_ptr/bounds_check_test.cpp | 410 +++++++ .../shared/test_offset_ptr/compare_test.cpp | 111 ++ .../test_offset_ptr/construction_test.cpp | 218 ++++ .../copying_offset_ptr_bounds_check_test.cpp | 392 ++++++ .../index_dereference_test.cpp | 153 +++ .../shared/test_offset_ptr/misc_test.cpp | 221 ++++ .../offset_ptr_test_resources.cpp | 13 + .../offset_ptr_test_resources.h | 319 +++++ .../shared/typedshm/typedshm_wrapper/BUILD | 82 ++ .../typedshm/typedshm_wrapper/test/BUILD | 23 + .../test/typed_memory_mock.cpp | 13 + .../typedshm_wrapper/test/typed_memory_mock.h | 43 + .../typedshm_wrapper/typed_memory.cpp | 123 ++ .../typedshm/typedshm_wrapper/typed_memory.h | 104 ++ .../typedshm_wrapper/typed_memory_test.cpp | 130 ++ score/memory/shared/user_permission.h | 38 + score/memory/shared/vector.cpp | 13 + score/memory/shared/vector.h | 53 + score/memory/shared/vector_test.cpp | 117 ++ score/memory/split_string_view.cpp | 122 ++ score/memory/split_string_view.h | 89 ++ score/memory/split_string_view_test.cpp | 114 ++ score/memory/string_comparison_adaptor.cpp | 105 ++ score/memory/string_comparison_adaptor.h | 119 ++ .../memory/string_comparison_adaptor_test.cpp | 300 +++++ score/memory/string_literal.h | 26 + 149 files changed, 26003 insertions(+) create mode 100644 score/memory/.clang-tidy-extra create mode 100644 score/memory/BUILD create mode 100644 score/memory/README.md create mode 100644 score/memory/any_string_view.cpp create mode 100644 score/memory/any_string_view.h create mode 100644 score/memory/any_string_view_test.cpp create mode 100644 score/memory/design/README.md create mode 100644 score/memory/design/shared_memory/OffsetPtrDesign.md create mode 100644 score/memory/design/shared_memory/README.md create mode 100644 score/memory/design/shared_memory/anonymous_memory_allocation.uxf create mode 100644 score/memory/design/shared_memory/bounds_checking.uxf create mode 100644 score/memory/design/shared_memory/get_owner_uid_seq.puml create mode 100644 score/memory/design/shared_memory/memory_allocation.uxf create mode 100644 score/memory/design/shared_memory/memory_allocation_workflow.uxf create mode 100644 score/memory/design/shared_memory/named_memory_allocation.uxf create mode 100644 score/memory/design/shared_memory/offset_ptr_problems.md create mode 100644 score/memory/endianness.cpp create mode 100644 score/memory/endianness.h create mode 100644 score/memory/endianness_test.cpp create mode 100644 score/memory/pmr_ring_buffer.h create mode 100644 score/memory/pmr_ring_buffer_test.cpp create mode 100644 score/memory/shared/BUILD create mode 100644 score/memory/shared/atomic_indirector.cpp create mode 100644 score/memory/shared/atomic_indirector.h create mode 100644 score/memory/shared/atomic_indirector_test.cpp create mode 100644 score/memory/shared/atomic_mock.cpp create mode 100644 score/memory/shared/atomic_mock.h create mode 100644 score/memory/shared/double_inverse_stored_int.h create mode 100644 score/memory/shared/double_inverse_stored_int_test.cpp create mode 100644 score/memory/shared/fake/my_bounded_memory_resource.cpp create mode 100644 score/memory/shared/fake/my_bounded_memory_resource.h create mode 100644 score/memory/shared/fake/my_memory_resource.cpp create mode 100644 score/memory/shared/fake/my_memory_resource.h create mode 100644 score/memory/shared/flags/BUILD create mode 100644 score/memory/shared/flock/BUILD create mode 100644 score/memory/shared/flock/exclusive_flock_mutex.cpp create mode 100644 score/memory/shared/flock/exclusive_flock_mutex.h create mode 100644 score/memory/shared/flock/flock_mutex.cpp create mode 100644 score/memory/shared/flock/flock_mutex.h create mode 100644 score/memory/shared/flock/flock_mutex_and_lock.cpp create mode 100644 score/memory/shared/flock/flock_mutex_and_lock.h create mode 100644 score/memory/shared/flock/flock_mutex_test.cpp create mode 100644 score/memory/shared/flock/shared_flock_mutex.cpp create mode 100644 score/memory/shared/flock/shared_flock_mutex.h create mode 100644 score/memory/shared/i_atomic.h create mode 100644 score/memory/shared/i_shared_memory_factory.cpp create mode 100644 score/memory/shared/i_shared_memory_factory.h create mode 100644 score/memory/shared/i_shared_memory_resource.cpp create mode 100644 score/memory/shared/i_shared_memory_resource.h create mode 100644 score/memory/shared/lock_file.cpp create mode 100644 score/memory/shared/lock_file.h create mode 100644 score/memory/shared/lock_file_test.cpp create mode 100644 score/memory/shared/managed_memory_resource.cpp create mode 100644 score/memory/shared/managed_memory_resource.h create mode 100644 score/memory/shared/managed_memory_resource_test.cpp create mode 100644 score/memory/shared/map.cpp create mode 100644 score/memory/shared/map.h create mode 100644 score/memory/shared/map_test.cpp create mode 100644 score/memory/shared/memory_region_map.cpp create mode 100644 score/memory/shared/memory_region_map.h create mode 100644 score/memory/shared/memory_region_map_test.cpp create mode 100644 score/memory/shared/memory_resource_proxy.cpp create mode 100644 score/memory/shared/memory_resource_proxy.h create mode 100644 score/memory/shared/memory_resource_proxy_test.cpp create mode 100644 score/memory/shared/memory_resource_registry.cpp create mode 100644 score/memory/shared/memory_resource_registry.h create mode 100644 score/memory/shared/memory_resource_registry_test.cpp create mode 100644 score/memory/shared/new_delete_delegate_resource.cpp create mode 100644 score/memory/shared/new_delete_delegate_resource.h create mode 100644 score/memory/shared/new_delete_delegate_resource_test.cpp create mode 100644 score/memory/shared/offset_ptr.cpp create mode 100644 score/memory/shared/offset_ptr.h create mode 100644 score/memory/shared/offset_ptr_bounds_check.cpp create mode 100644 score/memory/shared/offset_ptr_bounds_check.h create mode 100644 score/memory/shared/pointer_arithmetic_util.cpp create mode 100644 score/memory/shared/pointer_arithmetic_util.h create mode 100644 score/memory/shared/pointer_arithmetic_util_test.cpp create mode 100644 score/memory/shared/polymorphic_offset_ptr_allocator.cpp create mode 100644 score/memory/shared/polymorphic_offset_ptr_allocator.h create mode 100644 score/memory/shared/polymorphic_offset_ptr_allocator_test.cpp create mode 100644 score/memory/shared/sealedshm/sealedshm_wrapper/BUILD create mode 100644 score/memory/shared/sealedshm/sealedshm_wrapper/i_sealed_shm.h create mode 100644 score/memory/shared/sealedshm/sealedshm_wrapper/sealed_shm.cpp create mode 100644 score/memory/shared/sealedshm/sealedshm_wrapper/sealed_shm.h create mode 100644 score/memory/shared/sealedshm/sealedshm_wrapper/sealed_shm_mock.cpp create mode 100644 score/memory/shared/sealedshm/sealedshm_wrapper/sealed_shm_mock.h create mode 100644 score/memory/shared/sealedshm/sealedshm_wrapper/sealed_shm_test.cpp create mode 100644 score/memory/shared/shared_memory_error.cpp create mode 100644 score/memory/shared/shared_memory_error.h create mode 100644 score/memory/shared/shared_memory_error_test.cpp create mode 100644 score/memory/shared/shared_memory_factory.cpp create mode 100644 score/memory/shared/shared_memory_factory.h create mode 100644 score/memory/shared/shared_memory_factory_impl.cpp create mode 100644 score/memory/shared/shared_memory_factory_impl.h create mode 100644 score/memory/shared/shared_memory_factory_mock.cpp create mode 100644 score/memory/shared/shared_memory_factory_mock.h create mode 100644 score/memory/shared/shared_memory_factory_test.cpp create mode 100644 score/memory/shared/shared_memory_resource.cpp create mode 100644 score/memory/shared/shared_memory_resource.h create mode 100644 score/memory/shared/shared_memory_resource_allocate_test.cpp create mode 100644 score/memory/shared/shared_memory_resource_create_anonymous_test.cpp create mode 100644 score/memory/shared/shared_memory_resource_create_or_open_test.cpp create mode 100644 score/memory/shared/shared_memory_resource_create_test.cpp create mode 100644 score/memory/shared/shared_memory_resource_heap_allocator_mock.cpp create mode 100644 score/memory/shared/shared_memory_resource_heap_allocator_mock.h create mode 100644 score/memory/shared/shared_memory_resource_misc_test.cpp create mode 100644 score/memory/shared/shared_memory_resource_mock.cpp create mode 100644 score/memory/shared/shared_memory_resource_mock.h create mode 100644 score/memory/shared/shared_memory_resource_open_test.cpp create mode 100644 score/memory/shared/shared_memory_test_resources.cpp create mode 100644 score/memory/shared/shared_memory_test_resources.h create mode 100644 score/memory/shared/string.cpp create mode 100644 score/memory/shared/string.h create mode 100644 score/memory/shared/string_test.cpp create mode 100644 score/memory/shared/test/performance/BUILD create mode 100644 score/memory/shared/test/performance/offset_ptr_benchmark.cpp create mode 100644 score/memory/shared/test/permission_check/BUILD create mode 100644 score/memory/shared/test/permission_check/permission_check.cpp create mode 100644 score/memory/shared/test_offset_ptr/arithmetic_test.cpp create mode 100644 score/memory/shared/test_offset_ptr/assignment_test.cpp create mode 100644 score/memory/shared/test_offset_ptr/bool_ops_test.cpp create mode 100644 score/memory/shared/test_offset_ptr/bounds_check_memory_pool.cpp create mode 100644 score/memory/shared/test_offset_ptr/bounds_check_memory_pool.h create mode 100644 score/memory/shared/test_offset_ptr/bounds_check_test.cpp create mode 100644 score/memory/shared/test_offset_ptr/compare_test.cpp create mode 100644 score/memory/shared/test_offset_ptr/construction_test.cpp create mode 100644 score/memory/shared/test_offset_ptr/copying_offset_ptr_bounds_check_test.cpp create mode 100644 score/memory/shared/test_offset_ptr/index_dereference_test.cpp create mode 100644 score/memory/shared/test_offset_ptr/misc_test.cpp create mode 100644 score/memory/shared/test_offset_ptr/offset_ptr_test_resources.cpp create mode 100644 score/memory/shared/test_offset_ptr/offset_ptr_test_resources.h create mode 100644 score/memory/shared/typedshm/typedshm_wrapper/BUILD create mode 100644 score/memory/shared/typedshm/typedshm_wrapper/test/BUILD create mode 100644 score/memory/shared/typedshm/typedshm_wrapper/test/typed_memory_mock.cpp create mode 100644 score/memory/shared/typedshm/typedshm_wrapper/test/typed_memory_mock.h create mode 100644 score/memory/shared/typedshm/typedshm_wrapper/typed_memory.cpp create mode 100644 score/memory/shared/typedshm/typedshm_wrapper/typed_memory.h create mode 100644 score/memory/shared/typedshm/typedshm_wrapper/typed_memory_test.cpp create mode 100644 score/memory/shared/user_permission.h create mode 100644 score/memory/shared/vector.cpp create mode 100644 score/memory/shared/vector.h create mode 100644 score/memory/shared/vector_test.cpp create mode 100644 score/memory/split_string_view.cpp create mode 100644 score/memory/split_string_view.h create mode 100644 score/memory/split_string_view_test.cpp create mode 100644 score/memory/string_comparison_adaptor.cpp create mode 100644 score/memory/string_comparison_adaptor.h create mode 100644 score/memory/string_comparison_adaptor_test.cpp create mode 100644 score/memory/string_literal.h diff --git a/score/memory/.clang-tidy-extra b/score/memory/.clang-tidy-extra new file mode 100644 index 000000000..967b10c77 --- /dev/null +++ b/score/memory/.clang-tidy-extra @@ -0,0 +1,22 @@ +--- +# +# This configuration file contains extra clang-tidy +# checks which shall get performed for all source files +# residing in this directory as well as its subdirectories. +# + +# NOTE: Please *NEVER* specify a wildcard pattern for enabling checks, +# such as `bugprone-*` or `performance-*`! Only disabling checks +# would be acceptable, e.g. `-bugprone-*` or `-performance-*` etc. +# Reason for such guideline is that, in case wildcards get used for +# enabling checks, upgrading the clang-tidy binary to a newer version +# would then implicitly enable the new checks available in the upgraded +# clang-tidy binary. And then our (voting) CI jobs which are performing +# the extra clang-tidy checks are highly subject to fail and code would +# have to be made compliant again first. And that would block and also +# prolong the version upgrade unnecessarily. Furthermore it is easier +# to immediately spot which checks exactly will get performed in +# case each one gets listed explicitly, as done below. +Checks: > + -*, + modernize-concat-nested-namespaces, diff --git a/score/memory/BUILD b/score/memory/BUILD new file mode 100644 index 000000000..0d5cff8e1 --- /dev/null +++ b/score/memory/BUILD @@ -0,0 +1,243 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* +package(default_visibility = [ + # "@ddad//score/memory:__subpackages__", +]) + +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* +cc_library( + name = "string_literal", + hdrs = ["string_literal.h"], + tags = ["FFI"], + visibility = [ + "//visibility:public", + # "@ddad//visibility:public", + ], +) + +cc_library( + name = "any_string_view", + srcs = ["any_string_view.cpp"], + hdrs = ["any_string_view.h"], + tags = ["FFI"], + visibility = [ + "//visibility:public", + # "@ddad//score/memory:__subpackages__", + ], + deps = ["@score-baselibs//score/language/futurecpp"], +) + +cc_library( + name = "endianness", + srcs = ["endianness.cpp"], + hdrs = ["endianness.h"], + features = [ + "treat_warnings_as_errors", + "strict_warnings", + "additional_warnings", + ], + tags = ["FFI"], + visibility = [ + "//platform/aas/lib/serialization:__subpackages__", + "//platform/aas/pas/sysmon/library:__subpackages__", + # "@ddad//score/memory:__subpackages__", + ], + deps = ["@score-baselibs//score/language/futurecpp"], +) + +cc_library( + name = "string_comparison_adaptor", + srcs = [ + "string_comparison_adaptor.cpp", + ], + hdrs = [ + "string_comparison_adaptor.h", + ], + features = [ + "treat_warnings_as_errors", + "strict_warnings", + "additional_warnings", + ], + tags = ["FFI"], + visibility = [ + "//visibility:public", + ], + deps = [ + ":string_literal", + "@score-baselibs//score/language/futurecpp", + ], +) + +cc_test( + name = "string_comparison_adaptor_unit_test", + srcs = [ + "string_comparison_adaptor_test.cpp", + ], + features = [ + "aborts_upon_exception", + "treat_warnings_as_errors", + "strict_warnings", + "additional_warnings", + ], + tags = ["unit"], + deps = [ + ":string_comparison_adaptor", + "@googletest//:gtest_main", + "@score-baselibs//score/language/futurecpp", + ], +) + +cc_library( + name = "split_string_view", + srcs = [ + "split_string_view.cpp", + ], + hdrs = [ + "split_string_view.h", + ], + features = [ + "treat_warnings_as_errors", + "strict_warnings", + "additional_warnings", + ], + tags = ["FFI"], + visibility = [ + "//score/mw/log:__subpackages__", + # "@ddad//score/memory:__subpackages__", + ], + deps = ["@score-baselibs//score/language/futurecpp"], +) + +cc_library( + name = "pmr_ring_buffer", + hdrs = [ + "pmr_ring_buffer.h", + ], + features = [ + "treat_warnings_as_errors", + "strict_warnings", + "additional_warnings", + ], + tags = ["FFI"], + visibility = [ + "//visibility:public", + # "@ddad//score/memory:__subpackages__", + ], + deps = [ + "@score-baselibs//score/language/futurecpp", + ], +) + +cc_test( + name = "split_string_view_unit_test", + srcs = [ + "split_string_view_test.cpp", + ], + features = [ + "aborts_upon_exception", + "treat_warnings_as_errors", + "strict_warnings", + "additional_warnings", + ], + tags = ["unit"], + deps = [ + ":split_string_view", + "@googletest//:gtest_main", + ], +) + +cc_test( + name = "any_string_view_test", + srcs = [ + "any_string_view_test.cpp", + ], + features = [ + "aborts_upon_exception", + "treat_warnings_as_errors", + "strict_warnings", + "additional_warnings", + ], + tags = ["unit"], + deps = [ + ":any_string_view", + "@googletest//:gtest_main", + ], +) + +cc_test( + name = "endianness_unit_test", + srcs = [ + "endianness_test.cpp", + ], + features = [ + "aborts_upon_exception", + "treat_warnings_as_errors", + "strict_warnings", + "additional_warnings", + ], + tags = ["unit"], + deps = [ + ":endianness", + "@googletest//:gtest_main", + ], +) + +cc_test( + name = "pmr_ring_buffer_unit_test", + srcs = [ + "pmr_ring_buffer_test.cpp", + ], + features = [ + "aborts_upon_exception", + "treat_warnings_as_errors", + "strict_warnings", + "additional_warnings", + ], + tags = ["unit"], + deps = [ + ":pmr_ring_buffer", + "@googletest//:gtest_main", + ], +) + +test_cases_common = [ + ":any_string_view_test", + ":endianness_unit_test", + ":pmr_ring_buffer_unit_test", + ":split_string_view_unit_test", + ":string_comparison_adaptor_unit_test", + "//score/memory/shared/flock:unit_test", + "//score/memory/shared:unit_test", + "//score/memory/shared:shared_memory_factory_test", + "//score/memory/shared:pointer_arithmetic_util_precondition_violation_test", + "//score/memory/shared:offset_ptr_precondition_violation_test", + "//score/memory/shared:offset_ptr_test", + "//score/memory/shared/sealedshm/sealedshm_wrapper:unit_test", +] + +test_suite( + name = "unit_test_suite_host", + tests = test_cases_common, + visibility = ["//platform/aas/lib:__pkg__"], +) diff --git a/score/memory/README.md b/score/memory/README.md new file mode 100644 index 000000000..a6f198b8a --- /dev/null +++ b/score/memory/README.md @@ -0,0 +1,6 @@ +# Memory + +We need different utility libraries to handle memory. One of them being custom allocators but also handling shared +memory is a common problem. + +This library shall be a single place to abstract common memory related use-cases. diff --git a/score/memory/any_string_view.cpp b/score/memory/any_string_view.cpp new file mode 100644 index 000000000..80576505e --- /dev/null +++ b/score/memory/any_string_view.cpp @@ -0,0 +1,13 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/memory/any_string_view.h" diff --git a/score/memory/any_string_view.h b/score/memory/any_string_view.h new file mode 100644 index 000000000..cb132dc0d --- /dev/null +++ b/score/memory/any_string_view.h @@ -0,0 +1,67 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef BASELIBS_SCORE_MEMORY_ANY_STRING_VIEW_H +#define BASELIBS_SCORE_MEMORY_ANY_STRING_VIEW_H + +#include "score/string_view.hpp" + +#include + +namespace score::memory +{ + +// Goal of this class is to ease the transition between score::cpp::string_view and std::string_view +// Thus the mare goal of this class is to allow implicit conversions between these two types. +class AnyStringView +{ + public: + // NOLINTNEXTLINE(google-explicit-constructor) See argumentation above + constexpr AnyStringView(const char* literal) : string_view_{literal} {}; + + // NOLINTNEXTLINE(google-explicit-constructor) See argumentation above + constexpr AnyStringView(const std::string_view& string_view) : string_view_{string_view} {}; + + // NOLINTNEXTLINE(google-explicit-constructor) See argumentation above + constexpr AnyStringView(const score::cpp::string_view& string_view) + : string_view_{string_view.data(), string_view.size()} {}; + + template + // NOLINTNEXTLINE(google-explicit-constructor) See argumentation above + constexpr AnyStringView(const std::basic_string, Allocator>& s) noexcept + : string_view_{s.data(), s.size()} + { + } + + // NOLINTBEGIN(google-explicit-constructor) See argumentation above + // coverity[autosar_cpp14_a13_5_2_violation] + constexpr operator score::cpp::string_view() const + { + return {string_view_.data(), string_view_.size()}; + } + // NOLINTEND(google-explicit-constructor) see above + + // NOLINTBEGIN(google-explicit-constructor) See argumentation above + // coverity[autosar_cpp14_a13_5_2_violation] + constexpr operator std::string_view() const + { + return string_view_; + } + // NOLINTEND(google-explicit-constructor) see above + + private: + std::string_view string_view_; +}; + +} // namespace score::memory + +#endif // BASELIBS_SCORE_MEMORY_ANY_STRING_VIEW_H diff --git a/score/memory/any_string_view_test.cpp b/score/memory/any_string_view_test.cpp new file mode 100644 index 000000000..669019ad9 --- /dev/null +++ b/score/memory/any_string_view_test.cpp @@ -0,0 +1,108 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/memory/any_string_view.h" + +#include + +namespace score::memory +{ +namespace +{ + +class AnyStringViewFixture : public ::testing::Test +{ + public: + std::string_view std_string_view_{"foo"}; + score::cpp::string_view score_string_view_{"foo"}; + std::string string_{"foo"}; +}; + +TEST_F(AnyStringViewFixture, ConvertStdToStd) +{ + std::string_view unit = AnyStringView{std_string_view_}; + EXPECT_EQ(unit, std_string_view_); +} + +TEST_F(AnyStringViewFixture, ConvertStdToAmp) +{ + score::cpp::string_view unit = AnyStringView{std_string_view_}; + EXPECT_EQ(unit, score_string_view_); +} + +TEST_F(AnyStringViewFixture, ConvertAmpToAmp) +{ + score::cpp::string_view unit = AnyStringView{score_string_view_}; + EXPECT_EQ(unit, score_string_view_); +} + +TEST_F(AnyStringViewFixture, ConvertAmpToStd) +{ + std::string_view unit = AnyStringView{score_string_view_}; + EXPECT_EQ(unit, std_string_view_); +} + +TEST_F(AnyStringViewFixture, ConvertLiteralToStd) +{ + std::string_view unit = AnyStringView{"foo"}; + EXPECT_EQ(unit, std_string_view_); +} + +TEST_F(AnyStringViewFixture, ConvertLiteralToAmp) +{ + score::cpp::string_view unit = AnyStringView{"foo"}; + EXPECT_EQ(unit, score_string_view_); +} + +TEST_F(AnyStringViewFixture, ConvertStringToAmp) +{ + score::cpp::string_view unit = AnyStringView{string_}; + EXPECT_EQ(unit, score_string_view_); +} + +TEST_F(AnyStringViewFixture, ConvertStringToStd) +{ + std::string_view unit = AnyStringView{string_}; + EXPECT_EQ(unit, std_string_view_); +} + +std::string_view constexpr SomeFunction(AnyStringView unit) +{ + return unit; +} + +TEST_F(AnyStringViewFixture, ConvertImplicitStringToStd) +{ + std::string_view unit = SomeFunction(string_); + EXPECT_EQ(unit, std_string_view_); +} + +TEST_F(AnyStringViewFixture, ConvertImplicitStdToStd) +{ + std::string_view unit = SomeFunction(std_string_view_); + EXPECT_EQ(unit, std_string_view_); +} + +TEST_F(AnyStringViewFixture, ConvertImplicitAmpToStd) +{ + std::string_view unit = SomeFunction(score_string_view_); + EXPECT_EQ(unit, std_string_view_); +} + +TEST_F(AnyStringViewFixture, ConvertImplicitLiteralToStd) +{ + std::string_view unit = SomeFunction("foo"); + EXPECT_EQ(unit, std_string_view_); +} + +} // namespace +} // namespace score::memory diff --git a/score/memory/design/README.md b/score/memory/design/README.md new file mode 100644 index 000000000..25d03ba1f --- /dev/null +++ b/score/memory/design/README.md @@ -0,0 +1,5 @@ +# Software Design Description + +This library holds an implementation to work with shared memory (see shared_memory folder). + +Besides that it holds multiple utility functions to work with different parts of memory. diff --git a/score/memory/design/shared_memory/OffsetPtrDesign.md b/score/memory/design/shared_memory/OffsetPtrDesign.md new file mode 100644 index 000000000..ba30ff79e --- /dev/null +++ b/score/memory/design/shared_memory/OffsetPtrDesign.md @@ -0,0 +1,165 @@ +# Offset Pointer + +## Overview + +When mapping shared memory into different processes, the shared memory block will be mapped to a different +virtual address space in each process. While it is theoretically possible to enforce that the shared memory block is +always mapped at the same base address in each process, this is rather impractical, since it cannot be ensured that any +given memory address is still free for use in each process. + +This means that using pointers or using complex data structures that rely on pointers in shared memory is non-trivial. +A pointer created in shared memory in one process pointing to an address in the same shared memory region will +not be valid in another process. `boost::interprocess` solves this problem with introducing a so-called +[OffsetPtr](https://www.boost.org/doc/libs/1_64_0/doc/html/boost/interprocess/offset_ptr.html). The C++-Standard +names such pointers also `fancy pointer`. + +The idea of an `OffsetPtr` is that instead of storing an address of the pointed-to object (like a normal pointer), it +stores the offset between the address of the pointed-to object and the address of the `OffsetPtr` itself. This offset +is the same in all processes, thus, a valid pointer can be calculated as the sum of the base address of the `OffsetPtr` and +the offset that it stores. + +The available public member methods are taken over from the `boost::interprocess::offset_ptr` implementation. In order to +reuse this pointer also with stl-based containers it shall implement the requirements stated by +[std::pointer_traits](https://en.cppreference.com/w/cpp/memory/pointer_traits). + +### Bounds Checking OffsetPtr +For safety reasons, it is important that when accessing the memory pointed to by an `OffsetPtr` (either by dereferencing the `OffsetPtr` or +getting a raw pointer from the `OffsetPtr` and dereferencing that), the *entire* pointed-to object must lie inside the original memory region in which +the `OffsetPtr` was created (See [this](../../../../docs/features/ipc/lola/ipnext_README.md#shared-memory-handling) for an explanation of why +bounds checking must be done). + +From a safety perspective, the point of bounds checking is to prevent a lower safety rated process from interfering with the memory of +a higher safety rated process. Currently, this is only an issue when dealing with shared memory. However, in the future, we may have +other memory resources which also require bounds checking. e.g. memory pools in which we want to make sure that the `OffsetPtr` is +not pointing to an address outside that pool. Therefore, we have a generic interface for bounds checking that doesn’t depend on the +type of memory. + +Since we may have multiple memory resources which should be bounds checked, the `MemoryResourceRegistry` provides the public interface +for these checks. An `OffsetPtr` does not know in which region / type of memory it has been allocated, so it is up to the `MemoryResourceRegistry` +to determine the relevant memory resource and memory bounds, if there are any, associated with a given `OffsetPtr`. This also means that bounds +checking has to be attempted every time an `OffsetPtr` is dereferenced, even if the `OffsetPtr` is in a type of memory that doesn’t need to be +bounds checked. Each class deriving from `ManagedMemoryResource` (e.g. `SharedMemoryResource`) can decide whether the memory that it is managing +should be bounds checked by an `OffsetPtr`. It does this by implementing the function `ManagedMemoryResource::IsOffsetPtrBoundsCheckBypassingEnabled()`. + +[Bounds checking](broken_link_k/swh/ddad_platform/aas/lib/memory/design/shared_memory/bounds_checking.uxf) contains a +minimalistic UML diagram of the bounds checking. + +#### Bounds Checking Performance - Memory Bounds Lookup +Our simple integration tests and feedback from customers revealed, that the bounds checking functionality will be hit +very frequently! In our 1st straight forward implementation approach, the MemoryResourceRegistry::GetBoundsFromAddress() +function acquired a reader-lock as we had to care for consistency between readers asking for bounds and writers, which +update the current bounds by inserting/removing resources. But this solution based on a reader-writer lock turned out to +be a big performance penalty. + +Our current solution to access the bounds within the `MemoryResourceRegistry` concurrently between readers and +writers is the following: +* there can be only one writer active at a time. So writers get "serialized" by a "normal" mutex. I.e. all writer activity + to the bounds are routed through one of the following APIs, which already care for writer serialization: + `insert_resource()`, `remove_resource()`, `clear()` +* there can be an arbitrary number of readers active, which do a bounds-lookup (this happens during OffsetPtr deref) +* the algo to synchronize the access between the single writer and the multiple readers of the bounds is a lock-free + algo based on versioning and is detailed in the next subchapter. + +So as we have lock-free access to the bounds for our readers, the footprint/runtime during the (high frequency bounds- +checking is very low, which also some benchmarks revealed (see [here](../../shared/test/performance). + +##### Lock-Free bounds-check algorithm +The known bounds (aka known regions) are stored in a map (`std::map`) containing the start +address of the region as key and its end address as value. For our lock-free algo, we are maintaining N versions of +this known regions/map and an indicator, which of the N versions is the current/most recent one. + +## OffsetPtr Implementation + +Points to be considered in implementation can be seen in [Problems to solve](./offset_ptr_problems.md#problems-to-solve). + +### Bounds checking - OffsetPtr in shared memory + +When an `OffsetPtr` is in a shared memory region, we can perform bounds checks by getting the memory bounds of that region from the +MemoryResourceRegistry using the address of the OffsetPtr (via `MemoryResourceRegistry::GetBoundsFromAddress`). We then check that +the start address and end address of the pointed-to object lie within the retrieved memory bounds. We also check that the entire +`OffsetPtr` fits within the shared memory region. + +### Bounds checking - OffsetPtr on stack + +If the `OffsetPtr` is copied out of the memory region in which it was originally created, we still need to perform bounds checks before dereferencing / +getting a raw pointrer from the `OffsetPtr`. Therefore, when copying an `OffsetPtr` from shared memory to the stack, we get the `MemoryResourceIdentifier` +of the memory resource from the `MemoryResourceRegistry` and store it within the `OffsetPtr`. When dereferencing / getting a raw pointer from an `OffsetPtr` +on the stack, we can get the memory bounds of the `OffsetPtr`'s memory region with `MemoryResourceRegistry::GetBoundsFromIdentifier`. We can use +these bounds to check that the pointed-to object is still within that memory region. + +When the `OffsetPtr` is copied back into shared memory, the `MemoryResourceIdentifier` is no longer used, since it can be corrupted by another process, +so we have to again use `MemoryResourceRegistry::GetBoundsFromAddress` to look up memory bounds for bounds checking. If the `OffsetPtr` is copied back +to the stack, then the `MemoryResourceIdentifier` will be looked up again. + +### Dereferencing / Getting OffsetPtr\ + +An `OffsetPtr` can be templated with `void`. This can be useful for applications in which type-erasure of the pointed-to type is required. However, this +means that the `OffsetPtr` does not know the size of the pointed-to object, which is required for checking that the start **and** end address of the pointed-to +object lies within the correct memory region. Therefore, we provide two additional `get()` overloads when the pointed-to type is void to allow the user to +provide the size information used to check that the end address of the pointed-to object also lies within the correct memory region: + +* `get`: This allows the caller to provide the actual PointedType as a template argument. +* `get(explicit_pointed_type_size)`: This allows the caller to provide the size of the PointedType as a function argument. This is useful if the size of the +pointed-to object is not known at compile time (and hence cannot be derived from a type), e.g. if we have an OffsetPtr pointing to a type erased array of +dynamic size. + +### Copying OffsetPtr + +As outlined in [One-past-the-end-iterators](./offset_ptr_problems.md#definitions--background), doing a bounds check on a one-past-the-end iterator may +fail if the container lies at the end of the memory region. However, we want to support the ability to copy a one-past-the-end iterator. Therefore, we +have to make sure that copying an `Offsetptr` does not perform bounds checking (even when copying out of shared memory). Since bounds checking only +needs to be done before getting a raw pointer from the `OffsetPtr` or dereferencing it (which can also be done +[if the `OffsetPtr` has been copied to the stack](#dereferencing--getting-offsetptr-on-the-stack)), we can simply avoid doing any bounds checks when +copying without violating any safety goals. + +### Bounds check "race conditions" + +Since an OffsetPtr residing in shared memory could be corrupted *during* bounds checking, we must ensure that the offset value (or any other value which resides in +shared memory such as a `MemoryResourceIdentifier`) is first copied to the stack where it cannot be corrupted by another process. This copy should be used for bounds +checking and once checked, it should be used for dereferencing, getting a raw pointer etc. + +### Pointer Arithmetic Considerations + +In the implementation of an `OffsetPtr` as described above, we need to perform pointer arithmetic in two places: + +1. When constructing or copying an `OffsetPtr`, we need to subtract the address of the `OffsetPtr` itself from the address +of the pointed-to object. +2. When dereferencing an `OffsetPtr`, we need to add the calculated offset to the address of the `OffsetPtr`. + +In (1.), subtracting two pointers which do not point to elements of the same array is undefined behaviour according +to the [standard](https://timsong-cpp.github.io/cppwp/n4659/expr.add#5). In (2.), if adding an integral type to a pointer results in an +address which does not point to an element of the same array, then this is also undefined behaviour according to the +[standard](https://timsong-cpp.github.io/cppwp/n4659/expr.add#4). To deal with these issues, we first cast the address to an integral type, +and then do the addition / subtraction on the integral types instead of pointers. We can then cast the integral type back to a pointer, if required. +The conversion of a pointer to an integral type and an integral type to a pointer are implementation defined: https://timsong-cpp.github.io/cppwp/n4659/expr.reinterpret.cast#4 +and https://timsong-cpp.github.io/cppwp/n4659/expr.reinterpret.cast#5, respectively. In this way, all "pointer arithmetic" is +now actually integer arithmetic which is implementation defined. We rely on having sufficient tests to ensure that the implementation +behaves as we expect. + +## DynamicArray Considerations + +### Bounds checking iterators / element access + +LoLa uses [DynamicArrays](../../../containers/dynamic_array.h) for its [ServiceDataStorage](../../../../mw/com/impl/bindings/lola/service_data_storage.h) +and [ServiceDataControl](../../../../mw/com/impl/bindings/lola/service_data_control.h). A `DynamicArray` is a fixed-size array data structure whose size can be +dynamically set at construction. Since these both reside in shared memory, the underlying pointer type used by `DynamicArray` must be an `OffsetPtr`. The +`DynamicArray` is therefore susceptible to similar issues of memory corruption as an `OffsetPtr`. + +For example, if the `OffsetPtr` to the underlying array is corrupted, then it may point to an address outside the correct memory region or to an address +that begins within the memory region, but the end address of the array (i.e. the start address + the array size) would reside outside the memory region. + +When accessing any elements via `at()` or `operator[]`, we must check that the element lies in the correct memory region. This is automatically +done since we use an `OffsetPtr` to point to the array, so dereferencing an element will already perform bounds checking. However, when getting any iterators or +pointers from the `DynamicArray`, we must first check that the entire underlying array lies in the correct memory region. We can do this by performing an +`OffsetPtr` bounds check on the first and last elements of the array. Since the array is contiguous, if the first and last elements are within the region, +then all elements are. We do the check on the first **and** last elements since the iterators return raw pointers which can be incremented / decremented to +dereference any element of the array. + +### One-past-the-end-iterator + +As outlined in [One-past-the-end-iterators](./offset_ptr_problems.md#definitions--background), doing a bounds check on a one-past-the-end iterator may fail +if the container lies at the end of the memory region. Since the `DynamicArray` uses raw pointers as iterators, it needs to get a raw pointer from +the one-past-the-end `OffsetPtr` (e.g. in `end()`) which does bounds checking which may fail. Therefore, we provide an additional `get()` overload called +`GetWithoutBoundsCheck()` which the `DynamicArray` can use **only** for getting the raw pointer from the one-past-the-end `OffsetPtr`. To prevent the user +from decrementing this iterator and dereferencing it without any bounds checks, the `DynamicArray` manually does bounds checking on the start +and end elements as described [above](#bounds-checking-iterators--element-access). diff --git a/score/memory/design/shared_memory/README.md b/score/memory/design/shared_memory/README.md new file mode 100644 index 000000000..d92567177 --- /dev/null +++ b/score/memory/design/shared_memory/README.md @@ -0,0 +1,364 @@ +# Shared Memory +In order to use shared memory more easily and also in combination with different dynamic containers, +an abstraction layer is introduced. + +## Use Cases / Customer Functions +There are no direct Customer Functions associated with this part of `ara::core`. +This is caused by the fact that the shared memory abstraction represents an implementation detail, +which is necessary to fulfill the [Basic Architectural thoughts](../../../../mw/com/design/README.md) of `ara::com`. + +In fact, the usage of shared memory or its allocators shall be fully transparent for a user of the `ara`-API. + +## Shared Memory based allocation +The following section gives a textual reasoning and explanation of the class diagram that can be seen [here](memory_allocation.uxf). + +![Memory Allocation](broken_link_k/swh/safe-posix-platform/platform/aas/lib/memory/design/shared_memory/memory_allocation.uxf?ref=c8a52e508408b2f3905b833f32563264ccf4069c) + +Further also some [guidance](#guidance-for-data-types-in-shared-memory) is given, which data types can be stored +in shared memory. + +### Offset Pointer +See [OffsetPtr Design](./OffsetPtrDesign.md). + +### Polymorphic OffsetPtr Allocator +A user of the `ara::com`-API is able to acquire a so called `AllocateePtr` [SWS_CM_00308]. This way he shifts the +responsibility of allocating memory for a specific data type towards the middleware. Depending on the used network binding, +it can be necessary to allocate the memory either directly in shared memory (to enable truly zero-copy mechanisms) or +on the heap (to serialize the data and send over network sockets). It shall be highlighted that the `AllocateePtr` in +both cases will point to the same data type, since this is a runtime decision (based on the results of the service discovery). + +In order to support this behaviour, polymorphic allocation needs to be introduced. The +`std::pmr::polymorphic_allocator` (or its respective implementations of AMP) cannot be reused, because it will +allocate raw pointers. As explained in [Offset Pointer](#offset-pointer) this is not suitable for the shared memory use case +and thus a respective allocator needs to be introduced. It shall be pointed out that a classical usage of polymorphic +allocation is not working out. The classical way would pass a memory resource into the allocator as a raw pointer. Even if +we would swap the raw pointer with an `OffsetPtr` it would mean that the memory resource would need to be stored inside +the shared memory. This again is not applicable for some reasons: +* some memory resource classes may contain state/data, which only has a meaning in a specific process (e.g. a top-level +shared memory resource might hold a file descriptor (fd)) to control the (POSIX) shared memory object, but this fd is +different per process. +* our memory resources are designed in a certain inheritance hierarchy (abstract super class being +[ManagedMemoryResource](#managed-memory-resources)), +because we want to have runtime polymorphism to decide, which kind of memory resource implementation is providing storage. +Storing instances of polymorphic classes into shared memory is tricky, because they contain v-tables! But v-tables contain +raw pointers, which are again process specific! + +These issues can only be solved by introducing a custom implemented indirection in the form of `MemoryResourceProxy`, +which is explained in more detail in the following chapter. Our polymorphic allocator class is `PolymorphicOffsetPtrAllocator` +and it gets such a `MemoryResourceProxy` as its "memory resource" on which it operates. + +### Managed Memory Resources + +The first part is the `ManagedMemoryResource`. Inspired by [`std::memory_resource`](https://en.cppreference.com/w/cpp/memory/memory_resource) +`ManagedMemoryResource` represents the interface which is used by any container to allocate memory. Its respective +implementations then either allocate the memory on shared memory (`SharedMemoryResource`) or heap (`HeapMemoryResource`). +It shall be noted that the important difference between an `std::memory_resource` and the newly mentioned +`ManagedMemoryResource` is, that it offers the possibility to get an `MemoryResourceProxy` for a specific resource +instance. +This `MemoryResourceProxy`, which each `ManagedMemoryResource` subclass provides, is the needed indirection mechanism, +we talked about in the previous chapter! +The idea behind the `MemoryResourceProxy` is, that it builds up a non-virtual class that can be stored +in shared memory and identifies a specific `ManagedMemoryResource` using a process-specific global instance of `MemoryResourceRegistry`. +One can think of it as a custom shared memory safe implementation of a v-table. In order for this to work, on construction +of a memory resource, it needs to register itself at the `MemoryResourceRegistry`. Then, when returning the `MemoryResourceProxy` +it needs to be constructed with the same identifier. This workflow is further illustrated in [Memory Allocation Workflow](memory_allocation_workflow.uxf). +On a second process, that did not create the shared memory, the workflow would look the same, with the only difference, +that the `MemoryResourceProxy` is not created, but rather reinterpreted from the shared memory region. + +![Memory Allocation](broken_link_k/swh/safe-posix-platform/platform/aas/lib/memory/design/shared_memory/memory_allocation_workflow.uxf?ref=8bc136c4746944bee94af634a9e2b4919c0803ab) + +The key idea of this `MemoryResourceRegistry` concept is, that the keys used for registering a `ManagedMemoryResource` into +the registry (and being the essential part of the proxy) is globally (across all processes) unique and deterministic. +But this is _easily_ achievable. E.g.: +* for a `SharedMemoryResource` the key will be generated from its file system path +* for a `HeapMemoryResource` the key will be fixed to 0. +* for a [SubResource](#subresource) the key will be generated from a combination of the key of its parent resource and a +running number + +While the `HeapMemoryResource` can be a trivial implementation using the standard global allocator, `SharedMemoryResource` +has more specifics that need to be explained. The most important one being, that it cannot be publicly constructed nor copied. +This is necessary to avoid that the same shared memory is mapped multiple times in one process. A factory +(`SharedMemoryFactory`) will take care of a safe creation and ownership transfer. Another specific is, that +`SharedMemoryResource` will interact with some operating system abstraction library (`osabstraction`) in order to +manage the shared memory physically (opening, truncating, closing). + +#### SubResource + +Besides the direct subclasses `SharedMemoryResource` and `HeapMemoryResource` of `ManagedMemoryResource` we also do foresee +another subclass `SubResource`, which allows multi-level/hierarchic stacking of memory resources. +Sample use case: We want to subdivide the memory of a shared memory object (represented by a `SharedMemoryResource`). +For instance in case of events of a specific data type DT1, where we know its max size requirements ex ante and eventually +also the max number of clients/subscribers the memory allocation strategy could be massively optimized! In such a scenario +we would create a specific/suitable `SubResource` within a `SharedMemoryResource` and for events of type DT1, we would attach an +`PolymorphicOffsetPtrAllocator` instance, which gets an `MemoryResourceProxy`, which references this `SubResource`. + +**_Note_**: Usage of such `SubResource`s is an optimization feature, which we might not yet use in our first POC. + +**Writer** + +The writer, which wants to update the known regions, 1st needs to find a version among the N versions of known regions, +which is currently **not** used/accessed by any reader. For this each known regions version has an atomic `std::uint32_t` +as ref-counter, which reflects how many readers are currently doing a bounds-check lookup on this known regions version. +Therefore, the writer checks each version, starting with the oldest, and if the ref-count is 0, then tries to atomically +change it to some specific marker value `INVALID_REF_COUNT_VAL_START` via atomic `compare_exchange` operation. +If the writer succeeds with the change, he has now unique ownership of this version, copies the map/known regions from +the current most recent version to this acquired version, does the bounds-update there, atomically sets the ref-count to +0 and finally with an atomic store operation declares this acquired version as the new current most recent version. + +`INVALID_REF_COUNT_VAL_START` is chosen as being `std::numeric_limits::max() / 2U`, i.e. it +is halfway in the range of the ref-counter type. This means, that any ref-count value between +0..`INVALID_REF_COUNT_VAL_START` are valid ref-counter values being used by readers (see below) to signal their current +usage. So we allow up to approx. 2*10⁷ threads concurrently accessing a known regions version. + +**Reader** + +A reader, which wants to access for bounds-checking the most recent known regions version does the following steps: +1. atomically load/read the indicator, which version is the most recent. +2. atomically increment the usage/reference counter of the known regions version read in step 1. and check the result + (previous ref counter) of the atomic increment. + +There are three outcomes in 2., i.e. the rec count **before** the atomic increment: +1. "Good" case: *old_ref_count < `INVALID_REF_COUNT_VAL_START` - 1.* + + This is the "good" case where the reader successfully acquired this version of known regions for read. Once a + version has been created by a writer, its ref_count will be 0. The ref_count will then be incremented every time a + reader is currently accessing that version, and then decremented again when it's finished. Therefore, if the old + old_ref_count is less than "`INVALID_REF_COUNT_VAL_START` - 1", it is in this safe-for-reading state. + + *Result*: Reader acquires the version. Other readers can also acquire it but a writer cannot write to it. + +2. "Retry" case: *`INVALID_REF_COUNT_VAL_START` <= old_ref_count < `INVALID_REF_COUNT_VAL_END`* + + A reader gets the latest version index which currently has no other readers. At this point, this thread blocks or + runs slowly. A writer updates another version, changing the latest version index to that newly modified version, so + the reader thread has loaded a version index which no longer corresponds to the true latest version. The writer or a + series of writers do this enough times that the next time that a writer acquires a version, it acquires the same + version index that the reader is accessing. But since the reader has not yet incremented the ref count of that + version, the writer acquires it. The writer will update the ref count to `INVALID_REF_COUNT_VAL_START` and begin + modifying the version. The reader thread finally unblocks and increments the ref count, but will see that the + old_ref_count is now `INVALID_REF_COUNT_VAL_START`. Therefore, it knows that a writer has acquired this version and + it should check for the new latest version index and try to acquire that version. + + *Result*: In this case, the reader will retry a specified number of times until it can acquire a version for + reading. If it cannot acquire a version for reading after these retries, it returns an empty value and the caller + can handle this case. + +3. "Failure" cases: + + * (A) *old_ref_count == `INVALID_REF_COUNT_VAL_START` - 1.* + + If the old ref-counter was equal to `INVALID_REF_COUNT_VAL_START` - 1 before incrementing, the new ref_count + after incrementing would now be `INVALID_REF_COUNT_VAL_START`. This is the value used by the writer to indicate + that it is currently writing to this version, which will prevent other readers from accessing this version. It + is also the initial value for an unused version (i.e. that have no readers), so a writer will assume that it is + free to write to this version which could lead to the version being updated *while* the reader is still reading + it. + + This case will occur if we have almost 2x10⁷ readers concurrently accessing the same version. + Alternatively, if the decrement-logic of the ref_count (when a reader is finished with the version) is broken. + + * (B) *old_ref_count == `INVALID_REF_COUNT_VAL_END`* + + If the case described in the "Retry" case occurs, then the increment operation by the reader will cause the + ref_count to be (`INVALID_REF_COUNT_VAL_START` + 1). If this occurs, enough times, then eventually the ref_count + will reach `INVALID_REF_COUNT_VAL_END`. If another reader tries to increment the ref_count of this version, then + it will overflow to 0, despite the fact that the writer is still updating the region. + + This case will occur if this rety case is encountered by almost 2x10⁷ on the same version. + + *Result*: In both these cases, we terminate. + +**_Note_**: The reason, that we use a vast range of "invalid refcounts" from `INVALID_REF_COUNT_VAL_START` to +`INVALID_REF_COUNT_VAL_END` instead of just using one marker/sentinel value `INVALID_REF_COUNT_VAL`, is that we want +to use solely `atomic_increment` in our algo instead of a pair of +`atomic_load`/`atomic_compare_exchange(loaded_val, loaded_val + 1)`! With just one marker/sentinel, we would have to +use semantically/from algo perspective such a pair of operations, which has the following downside/problem we saw in +load-tests!: If we have a concurrency/contention in reader threads between the `atomic_load` and the upcoming +`atomic_compare_exchange`, the `atomic_compare_exchange` fails, forcing us/the reader into a retry! Under simulated +heavy load, the number of needed re-tries for a reader to finally succeed in updating the ref-counter was **huge**! + +### Usage + +Since `ara::core` needs to implement different container types like `Vector` or `String` it shall be possible to reuse +standard library container. This is possible by overloading the standard library container with a custom allocator, +that follows the requirements specified in [`std::allocator_traits`](https://en.cppreference.com/w/cpp/memory/allocator_traits). +This custom allocator is called `PolymorphicOffsetPtrAllocator` and depends on the previously defined memory resource proxy +`MemoryResourceProxy`, which will then resolve the correct memory resource to use. In order to support multi-level +allocations (e.g. vector in vector) the custom allocator needs to be wrapped in`std::scoped_allocator_adaptor`. + +All in all an example usage and implementation of `ara::core::Vector` could look like this: +```c++ +template +using ara::core::Vector = std::vector > +auto memory_resource = score::memory::shared::SharedMemoryFactory.getInstance.open("/my_shm_name"); +ara::core::Vector myVector(memory_resource.getProxy()); + +myVector.push_back(42u); // Will land on shared memory + +ara::core::Vector onHeap(); +onHeap.push_back(42u); // Will land on heap +``` + +### Guidance for data types in shared memory +Due to the fact that shared memory is interpreted by two processes, there are some limitations on data types +that can be stored meaningful in the shared memory. One point already mentioned in [Offset Pointer](#offset-pointer) is +that no raw pointer can be stored in shared memory, since the pointer will be invalid in other processes. + +In order to store any data type in shared memory without serialization, it needs to be ensured that alignment and overall +memory layout will not differ between the processes that access it. Strictly speaking both processes need to be build +with the same compiler / linker and also need to use the same options for them. This includes that they use the same +standard library implementation. + +Further it is not possible to store objects with virtual functions within the shared memory. This can be explained again +by Offset Pointer problem. The pointers within the v-table will just be invalid in the other process. + +When storing templated types in shared memory, it needs to be ensured that their respective symbol names are mangled in +the same manner in both process and no conflicts arise. + +Last but not least, when instantiating data types in shared memory, placement new shall be used. Copy-Construction shall +be avoided and move construction does not seem to be possible. This leaves us with a problem on the processes that read +the data but not create them. They have to use a `reinterpret_cast` to get the respective data types from the raw memory +they opened. This causes undefined behaviour since the C++-Standard states that such casts are only defined if the +object started life already in this process. The notion of shared memory is not considered to the C++-Standard. In +practice the cast will work, but for ASIL-B software this behaviour needs to be assured by the compiler vendor. + +At the end the interaction with shared memory can look like listed: +```c++ +// 1. Process: +void* ptr = shm_open(...); +int* value = new(ptr) int; // using placement new to store data type +*value = 5; + +// 2. Process +void* ptr = shm_open(...); +int* value = reinterpret_cast(ptr); +std::cout << value << std::endl // will print 5 +*value = 42; // undefined behaviour, because reinterpret_cast is only valid if the object would life there already +``` +### SharedMemoryResource allocation +`SharedMemoryResource` objects can be requested in two different ways: +1. As named `SharedMemoryResource` objects with an entry in the path namespace (`/dev/shmem`) +2. As anonymous `SharedMemoryResource` objects that are accessed using a `shm_handle_t` + +Named `SharedMemoryResource` can be created by calling either of the following `Create APIs` +- `SharedMemoryFactory::Create(...)` +- `SharedMemoryFactory::CreateOrOpen(...)` +- `SharedMemoryFactory::CreateAnonymous(...)` + +Shared memory is allocated in either typed memory or OS-system memory based on the `prefer_typed_memory` parameter of the above APIs; when set to true, the shared memory will be allocated in a typed memory region. The [score::memory::shared::TypedMemory](../../shared/typedshm/typedshm_wrapper/typed_memory.h) class acts as a wrapper that uses the [`typed_memory_daemon`](../../../../intc/typedmemd/README.md) client interface[`score::tmd::TypedSharedMemory`](../../../../intc/typedmemd/code/clientlib/typedsharedmemory.h) APIs to allocate shared memory in typed memory. +If allocation in typed memory fails, the allocation of shared memory will fall back to the OS-system memory ([DMA Accessible Memory Fallback](broken_link_c/issue/31034619)). + +### User permissions of Shared Memory +The `permissions` parameter in the `Create APIs`, allows the user to specify the access rights for the `SharedMemoryResource`. + +For the Named Shared Memory it can be set to one of the following: +- `WorldReadable`: allows read/write access for the user and read-only access for others. +- `WorldWritable`: allows read/write access for both the user and all other users. +- `UserPermissionsMap`: allows read/write access for the user and for named shared memory, sets specific permissions for additional users listed in the `UserPermissionsMap` using Access Control Lists ([ACLs](https://www.qnx.com/developers/docs/7.1/#com.qnx.doc.security.system/topic/manual/access_control.html)). + +For the Anonymous Shared Memory check the details below [Anonymous Shared Memory](#anonymous-shared-memory) + +### Named Shared Memory +Named shared memory allocated in typed memory will inherit the effective `UID/GID` of the `typed_memory_daemon`. Whereas, named shared memory allocated in OS-system memory will have the effective `UID/GID` of the user. + +For named shared memory allocated in OS-system memory with `world-writable` mode, permissions will be enforced using `fchmod` to ensure `world-writable` access. + +The underlying `shm_open()` call uses the `O_EXCL` and `O_CREAT` flags, ensuring that duplicate names result in an error. The file permissions are determined by the `permissions` parameter[User permissions of Shared Memory](#user-permissions-of-shared-memory) passed to the `Create APIs`, and are applied via the `mode` argument in the `shm_open()` call. + +From a safety standpoint, `ASIL-B` applications should avoid creating shared memory objects with `world-readable` or `world-writable` permissions in order to reduce security risks. For more details check [Access Control concepts](broken_link_a/ui/api/v1/download/contentBrowsing/ipnext-platform-documentation/master/html/features/dac/README.html) + +![Named memory allocation](broken_link_k/swh/safe-posix-platform/platform/aas/lib/memory/design/shared_memory/named_memory_allocation.uxf?ref=8bc136c4746944bee94af634a9e2b4919c0803ab) + +### Anonymous Shared Memory +Anonymous shared memory created with `SharedMemoryFactory::CreateAnonymous(...)` does not have a representation in the file system. In fact, there is no way for a random process to identify an anonymous shared memory object. Thus, there are no corresponding implementations of `SharedMemoryFactory::Open(...)` and `SharedMemoryFactory::CreateOrOpen(...)`. To share an anonymous shared memory object with another process it must be actively shared at runtime by: +- Creating a handle using `shm_create_handle(...)` +- Sharing this handle with the other process +- Opening the handle in the other process using `shm_open_handle(...)` + +It is important to note that anonymous shared memory is currently only implemented for QNX environment. In the current design/implementation, there is no API available to share the `shm_handle_t` with other processes. Instead, the `SharedMemoryResource` holds the file descriptor of the anonymous shared memory object. + +Anonymous shared memory allocated in typed memory are created with read/write access for the user. Whereas, for anonymous shared memory allocated in OS-system memory, +underlying `shm_open()` call is made with `mode` argument based on the`permissions` parameter[User permissions of Shared Memory](#user-permissions-of-shared-memory) passed to the `SharedMemoryFactory::CreateAnonymous(...)`. + +Anonymous Shared Memory objects are created without `SHM_CREATE_HANDLE_OPT_NOFD`, such that the user is able to create further `shm_handle_t` for other processes. +These objects are then sealed by calling `shm_ctl()` with `SHMCTL_SEAL` flag to prevents the object's layout (e.g., its size and backing memory) and attributes from being modified. So that no process (including the object's creator) can modify the layout or change any attributes. + +![Anonymous memory allocation](broken_link_k/swh/safe-posix-platform/platform/aas/lib/memory/design/shared_memory/anonymous_memory_allocation.uxf?ref=8bc136c4746944bee94af634a9e2b4919c0803ab) + +## Memory Management Algorithm +The allocated shared memory needs to be managed in some way. Meaning, freed memory needs to be reused before +new memory is allocated and the shared memory segment is enlarged. + +For the proof of concept we only need a monotonic allocator. Meaning, it will only increase and not free any memory. + +## Lifetime of SharedMemoryResource and MemoryResourceRegistry + +### Background - Static construction / destruction sequence +All static objects will be destroyed at program end (after all other non-static objects have been destroyed). The order in which static objects are destroyed will be the inverse of the order in which they're created. E.g. if static object A is created and the static object B is created, then B will be destroyed before A. + +### Lifetime of SharedMemoryResource in application code +The MemoryResourceRegistry is a singleton which is created when MemoryResourceRegistry::getInstance() is called for the first time. This will be called during the construction of a SharedMemoryResource, so it is guaranteed to be created if a SharedMemoryResource is created. The destructor of SharedMemoryResource also calls MemoryResourceRegistry::getInstance(). This means that the MemoryResourceRegistry should be destroyed only after the last SharedMemoryResource has been destroyed. + +In application code, if the lifetime of a SharedMemoryResource is linked to the lifetime of a static object (e.g. it's destroyed in the destructor of the static object), then the SharedMemoryResource will be destroyed at some point during the static destruction sequence at the earliest. In this case, the user must ensure that MemoryResourceRegistry::getInstance() is called before the static object owning the shared_ptr is created. This can be solved via a singleton approach e.g. + +``` +// Assuming that UserSharedMemoryResourceOwner is created as a static variable +class UserSharedMemoryResourceOwner +{ + public: + UserSharedMemoryResourceOwner() + { + // The MemoryResourceRegistry will be created before the UserSharedMemoryResourceOwner. + // Therefore, it will be destroyed after the UserSharedMemoryResourceOwner + MemoryResourceRegistry::getInstance(); + + // Create the SharedMemoryResource and assign to memory_resource_... + } + + ~UserSharedMemoryResourceOwner() { + // If the ref count of the memory_resource_ is greater than 1, the SharedMemoryResource will not + // be destroyed here! + assert(memory_resource_.ref_count() == 1); + + // The MemoryResourceRegistry is still alive here + } + + private: + // Created in the constructor and destroyed in the destructor of UserSharedMemoryResourceOwner. + std::shared_ptr memory_resource_; +} +``` + +Obviously, since the SharedMemoryResource is contained within a shared_ptr, in the example above, the user must ensure that the ref count of the shared_ptr goes to 0 when the static object is destroyed so that the SharedMemoryResource itself is destroyed. + +## Ownership of the SharedMemoryResource + +Getting the `uid` of the creator of the underlying memory managed by a `SharedMemoryResource` is essential to ensure that only memory with the expected allowed providers can be opened by a user of the library as this feature is used by some ASIL B components (for example see this [requirement](broken_link_c/issue/33047276) or this [one](broken_link_c/issue/8742625)). +This library only supports opening a shared memory area based on the path therefore this concept is only valid for the named memory use case, annonymous memory is not considered. + +There are 2 possible use-cases : + +1. Memory is allocated in System RAM: + +The owner `uid` of the allocated memory will be the `euid` (effective user ID) of the allocating process. +Therefore, when another process opens the memory it only needs to perform a simple check by using `fstat` to get the `uid` for the memory and compare that `uid` with the passed expected provider. + +2. Memory is allocated in [Typed Memory](../../../../intc/typedmemd/README.md): + +Due to safety considerations, typed memory cannot be directly allocated by a process using the POSIX primitives, so the allocation is delegated to an ASIL B application called [`typed_memory_daemon`](broken_link_g/swh/ddad_platform/blob/master/aas/intc/typedmemd/README.md). +The `typed_memory_daemon` will allocate the memory with its `euid` as the owner `uid`. +For safety and security reasons the `typed_memory_daemon` cannot transfer ownership of the memory using `chown` to the application's `euid` from which it got the delegation, so it will use Acess Control Lists ([ACLs](https://www.qnx.com/developers/docs/7.1/#com.qnx.doc.security.system/topic/manual/access_control.html)) to give read/write permissions to the requestor's `euid` as well as any other needed `uid`. \ +In this case, to be able to identify the requestor process, the `typed_memory_daemon` will add on top of the read/write permissions also the execution permissions to the requestor's `euid` **only** as per its [ASIL B requirement](broken_link_c/issue/42033981). +When another process opens the `SharedMemoryResource` it will then check if the `uid` of the memory is identical to the `typed_memory_daemon`'s `euid`. +The `typed_memory_daemon`'s `euid` is currently a hardcoded constant in the code. +If the check is succesfull it means that the memory is in typed memory and the corresponding internal flag (`is_shm_in_typed_memory_`) will also be updated. +Then it will inspect the ACLs and compare the single `uid` with execution permissions with the passed expected provider. +If there are none or multiple `uids` with execution permissions the application will be terminated. + +This solution of using the execution bit to mark the owner/creator of the memory was chosen as a workaround to enable the usage of typed memory by components relying on the allowed providers check (e.g. users of `mw::com`). +Using the execution is a lightweight solution as it only needs one system call to get the necessary information on the ACLs. +It also does not have any impact on security as [PathTrust](https://www.qnx.com/developers/docs/7.1/#com.qnx.doc.security.system/topic/manual/pathtrust.html) is enabled which makes it impossible to execute anything allocated in `/dev/shmem` and even prevents other side effects like mapped and executed by the default QNX loader. + +The complete sequence to find the owner is: + +![GetOwnerUid sequence](broken_link_k/swh/safe-posix-platform/platform/aas/lib/memory/design/shared_memory/get_owner_uid_seq.puml?ref=7f42212f92084db1c548d7361c2cf4336cafc6dd) diff --git a/score/memory/design/shared_memory/anonymous_memory_allocation.uxf b/score/memory/design/shared_memory/anonymous_memory_allocation.uxf new file mode 100644 index 000000000..46092a890 --- /dev/null +++ b/score/memory/design/shared_memory/anonymous_memory_allocation.uxf @@ -0,0 +1,1022 @@ + + + 5 + + UMLGeneric + + 225 + 0 + 95 + 15 + + _:SharedMemoryFactoryP1_ + + + + UMLGeneric + + 60 + 25 + 10 + 860 + + + + + + UMLGeneric + + 270 + 30 + 10 + 855 + + + + + + Relation + + 65 + 95 + 215 + 20 + + lt=<<<- +CreateAnonymous(prefer_typed_memory, permissions, size) + 410.0;20.0;10.0;20.0 + + + UMLGeneric + + 435 + 95 + 105 + 15 + + _:SharedMemoryResourceP1_ + + + + Relation + + 65 + 865 + 215 + 20 + + lt=<. +instance + 10.0;20.0;410.0;20.0 + + + UMLGeneric + + 40 + 0 + 50 + 15 + + _:InstanceP1_ + + + + UMLGeneric + + 850 + 5 + 65 + 15 + + :TypedMemoryP1 + + + + Relation + + 485 + 200 + 400 + 20 + + lt=<<<- +AllocateAndOpenAnonymousTypedMemory(size) + 780.0;20.0;10.0;20.0 + + + Relation + + 485 + 275 + 400 + 20 + + lt=<. +file_descriptor + 10.0;20.0;780.0;20.0 + + + UMLGeneric + + 1285 + 135 + 65 + 15 + + _:score::os::Mman + + + + Relation + + 275 + 855 + 215 + 20 + + lt=<. +instance + 10.0;20.0;410.0;20.0 + + + Relation + + 1310 + 145 + 15 + 755 + + lt=. + 10.0;10.0;10.0;1490.0 + + + Relation + + 875 + 15 + 15 + 290 + + lt=. + 10.0;10.0;10.0;560.0 + + + Relation + + 485 + 400 + 835 + 25 + + lt=<. +file_descriptor + + 10.0;20.0;1650.0;20.0 + + + Relation + + 485 + 385 + 835 + 20 + + lt=<<<- +shm_open(SHM_ANON, O_RDWR | O_CREAT | O_ANON, mode) + 1650.0;20.0;10.0;20.0 + + + Relation + + 485 + 695 + 165 + 25 + + lt=<<<- +GetOwnerUidAndSizeOf(file_descriptor) + 10.0;30.0;60.0;30.0;60.0;10.0;10.0;10.0 + + + Relation + + 485 + 835 + 615 + 20 + + lt=<<<- +initializeInternalsInSharedMemory() - MemoryResourceProxyP1, alreadyAllocatedBytes, mutex + 1210.0;20.0;10.0;20.0 + + + UMLGeneric + + 1035 + 690 + 115 + 15 + + _:AnonymousSharedMemoryObject_ + + + + Relation + + 485 + 850 + 615 + 15 + + lt=<. + 10.0;10.0;1210.0;10.0 + + + UMLGeneric + + 1155 + 690 + 100 + 15 + + _:MemoryResourceRegistryP1_ + + + + Relation + + 485 + 795 + 720 + 20 + + lt=<<<- +insert_resource(unqiueID, this) + 1420.0;20.0;10.0;20.0 + + + Relation + + 1195 + 700 + 15 + 205 + + lt=. + 10.0;10.0;10.0;390.0 + + + Relation + + 485 + 810 + 720 + 15 + + lt=<. + 10.0;10.0;1420.0;10.0 + + + Relation + + 1090 + 700 + 15 + 200 + + lt=. + 10.0;10.0;10.0;380.0 + + + Relation + + 485 + 720 + 835 + 20 + + lt=<<<- +mmap(map memory into process) + 1650.0;20.0;10.0;20.0 + + + UMLGeneric + + 1310 + 390 + 10 + 30 + + + + + + UMLGeneric + + 1310 + 720 + 10 + 30 + + + + + + UMLGeneric + + 875 + 35 + 10 + 265 + + + + + + UMLGeneric + + 1195 + 760 + 10 + 115 + + + + + + UMLGeneric + + 1090 + 830 + 10 + 35 + + + + + + Relation + + 480 + 105 + 15 + 800 + + lt=. + 10.0;10.0;10.0;1580.0 + + + UMLGeneric + + 480 + 185 + 10 + 115 + + + + + + UMLGeneric + + 480 + 680 + 10 + 210 + + + + + + Relation + + 485 + 735 + 835 + 15 + + lt=<. + 10.0;10.0;1650.0;10.0 + + + UMLGeneric + + 1140 + 135 + 115 + 15 + + :score::tmd::ITypedSharedMemory + + + + Relation + + 1190 + 145 + 15 + 110 + + lt=. + 10.0;10.0;10.0;200.0 + + + Relation + + 880 + 210 + 320 + 20 + + lt=<<<- +AllocateHandleTypedMemory(size) + 620.0;20.0;10.0;20.0 + + + Relation + + 880 + 265 + 440 + 25 + + lt=<. +return file descriptor + + 10.0;20.0;860.0;20.0 + + + Relation + + 60 + 10 + 15 + 895 + + lt=. + 10.0;10.0;10.0;1770.0 + + + UMLGeneric + + 1190 + 215 + 10 + 30 + + + + + + UMLSpecialState + + 1195 + 895 + 10 + 10 + + type=termination + + + + UMLSpecialState + + 1090 + 890 + 10 + 10 + + type=termination + + + + UMLSpecialState + + 480 + 895 + 10 + 10 + + type=termination + + + + UMLGeneric + + 1025 + 425 + 60 + 15 + + score::os::Stat + + + + UMLGeneric + + 1095 + 425 + 95 + 15 + + os::utils::AccessControlList + + + + Relation + + 1050 + 435 + 15 + 60 + + lt=. + 10.0;10.0;10.0;100.0 + + + Relation + + 1140 + 435 + 15 + 120 + + lt=. + 10.0;10.0;10.0;220.0 + + + UMLGeneric + + 480 + 450 + 10 + 105 + + + + + + Relation + + 485 + 460 + 575 + 20 + + lt=<<<- +fchmod(file_descriptor, mode) + 1130.0;20.0;10.0;20.0 + + + UMLGeneric + + 1050 + 450 + 10 + 40 + + + + + + UMLGeneric + + 1140 + 510 + 10 + 35 + + + + + + Relation + + 485 + 515 + 665 + 20 + + lt=<<<- +AllowUser(file_descriptor, os::Acl::Permission) + 1310.0;20.0;10.0;20.0 + + + Relation + + 485 + 475 + 575 + 20 + + lt=<. + + 10.0;20.0;1130.0;20.0 + + + Relation + + 485 + 535 + 665 + 15 + + lt=<. + 10.0;10.0;1310.0;10.0 + + + UMLGeneric + + 1195 + 425 + 60 + 15 + + score::os::Unistd + + + + Relation + + 1220 + 435 + 15 + 235 + + lt=. + 10.0;10.0;10.0;450.0 + + + Relation + + 485 + 625 + 745 + 20 + + lt=<<<- +ftruncate(file_descriptor, size) + 1470.0;20.0;10.0;20.0 + + + UMLGeneric + + 1220 + 630 + 10 + 30 + + + + + + Relation + + 485 + 640 + 745 + 20 + + lt=<. + + 10.0;20.0;1470.0;20.0 + + + Relation + + 275 + 40 + 610 + 20 + + lt=<<<- +TypedMemory::Default() + 1200.0;20.0;10.0;20.0 + + + Relation + + 275 + 55 + 610 + 20 + + lt=<. +return TypedMemoryP1 + 10.0;20.0;1200.0;20.0 + + + Relation + + 270 + 10 + 15 + 895 + + lt=. + 10.0;10.0;10.0;1770.0 + + + Relation + + 275 + 125 + 215 + 20 + + lt=<<<- +CreateAnonymous(TypedMemoryP1, permissions, size) + 410.0;20.0;10.0;20.0 + + + UMLSpecialState + + 1310 + 890 + 10 + 10 + + type=termination + + + + UMLSpecialState + + 270 + 890 + 10 + 10 + + type=termination + + + + UMLSpecialState + + 60 + 890 + 10 + 10 + + type=termination + + + + UMLFrame + + 420 + 175 + 945 + 130 + + alt +-- +(typed_memory_ptr_ != nullptr) +AllocateInTypedmemory + + + + + + UMLFrame + + 420 + 320 + 945 + 350 + + alt +-- +AllocateInTypedMemory failed or AllocateInSysram(typed_memory_ptr_ == nullptr) + + + + UMLGeneric + + 480 + 580 + 10 + 30 + + + + + + Relation + + 485 + 580 + 835 + 20 + + lt=<<<- +shm_ctl(fd, SHMCTL_ANON | SHMCTL_SEAL, 0UL, size) + 1650.0;20.0;10.0;20.0 + + + Relation + + 485 + 595 + 835 + 20 + + lt=<. + + 10.0;20.0;1650.0;20.0 + + + UMLGeneric + + 1310 + 580 + 10 + 35 + + + + + + UMLGeneric + + 480 + 630 + 10 + 30 + + + + + + UMLFrame + + 445 + 435 + 515 + 125 + + alt +-- +mode == world-readable + + + + + +-- +mode == UserPermissionsMap + + + + + + + UMLGeneric + + 925 + 690 + 105 + 15 + + _:MemoryResourceProxyP1 + + + + Relation + + 975 + 700 + 15 + 205 + + lt=. + 10.0;10.0;10.0;390.0 + + + UMLSpecialState + + 975 + 890 + 10 + 10 + + type=termination + + + + UMLGeneric + + 975 + 755 + 10 + 30 + + + + + + Relation + + 485 + 760 + 500 + 20 + + lt=<<<- +Instantiate(Unique ID) + 980.0;20.0;10.0;20.0 + + + Relation + + 485 + 770 + 500 + 20 + + lt=<. + + 10.0;20.0;980.0;20.0 + + + UMLGeneric + + 480 + 345 + 10 + 30 + + + + + + Relation + + 485 + 345 + 200 + 25 + + lt=<<<- +mode = calcStatModeForPermissions(permissions) + 10.0;30.0;60.0;30.0;60.0;10.0;10.0;10.0 + + + UMLGeneric + + 480 + 390 + 10 + 30 + + + + + + Relation + + 880 + 250 + 440 + 20 + + lt=<<<- +shm_open_handle(shm_handle, O_RDWR) + 860.0;20.0;10.0;20.0 + + + Relation + + 880 + 225 + 320 + 25 + + lt=<. +return shm_handle + + 10.0;20.0;620.0;20.0 + + + UMLGeneric + + 1310 + 255 + 10 + 30 + + + + + + UMLGeneric + + 480 + 125 + 10 + 30 + + + + + diff --git a/score/memory/design/shared_memory/bounds_checking.uxf b/score/memory/design/shared_memory/bounds_checking.uxf new file mode 100644 index 000000000..17895738c --- /dev/null +++ b/score/memory/design/shared_memory/bounds_checking.uxf @@ -0,0 +1,257 @@ + + + 15 + + UMLClass + + 300 + 1470 + 1545 + 180 + + score::memory::shared::SharedMemoryResource +-- +- Create(StringLiteral, InitializeCallback, std::size_t, UserPermissions permissions = {}) noexcept: std::shared_ptr<ManagedMemoryResource> +-- +Notes: +Calls MemoryResourceRegistry::insert_resource() in Create() after mapping the shared memory into the process. + + + + + UMLClass + + 1950 + 735 + 840 + 420 + + template=T: class +/score::memory::shared::PolymorphicOffsetPtrAllocator/ +-- +Notes: +Unchanged + + + + + UMLClass + + 2790 + 1230 + 765 + 360 + + template=PointedType +score::memory::shared::OffsetPtr +-- ++ get() const: PointedType* ++ get(size_t) const: PointedType* ++ get<ExplicitPointedType>(): ExplicitPointedType* ++ GetWithoutBoundsCheck(): PointedType* +-- +Notes: +If EnableOffsetPtrBoundsChecking() has not been called +with enable=false, when get() is called, gets the memory +bounds of the region in which the offset pointer is allocated +with MemoryResourceRegistry::get_bounds_for(this). If non-null +bounds are returned, terminates if pointed-to-object is not +inside memory bounds. Otherwise, returns as normal. + + + + + Relation + + 1170 + 1365 + 45 + 135 + + lt=<<. + 10.0;10.0;10.0;70.0 + + + UMLClass + + 1905 + 1470 + 615 + 150 + + score::memory::shared::HeapMemoryResource +-- +Notes: +Since we don't want to bounds check, don't +call MemoryResourceRegistry::insert_memory_range(). + + + + + UMLClass + + 1965 + 360 + 645 + 165 + + score::memory::shared::MemoryResourceProxy +-- +Notes: +Unchanged + + + + + UMLClass + + 765 + 585 + 870 + 435 + + score::memory::shared::MemoryResourceRegistry +-- +known_regions: MemoryRegionMap +-- +using MemoryBounds = pair<uintptr_t, uintptr_t> ++ insert_resource(std::pair<std::unit64_t, ManagedMemoryResource*>): void ++ remove_resource(std::uint64_t identifier): void ++ GetBoundsFromAddress(uintptr_t): optional<pair<MemoryBounds, MemoryResourceIdentifier>> ++ GetBoundsFromIdentifier(uint64 identifier): Result<MemoryBounds> +-- +Notes: +* known_regions is a map for memory regions (i.e. storing the start and end +addresses for each memory region) which provides lock-free access to one +writer and multiple concurrent readers. +* GetBoundsFromAddress() checks if the passed pointer is within a known +memory region. If so, returns the bounds of that memory region. Else, +returns pair of null ptrs. We return memory bounds rather than a bool +value indicating whether the OffsetPtr is within the memory bounds to +avoid explicitly coupling it with an OffsetPtr. + + + + + Relation + + 1620 + 420 + 375 + 405 + + lt=<- +lookup dispatch +target + 10.0;250.0;90.0;250.0;90.0;10.0;230.0;10.0 + + + Relation + + 2355 + 510 + 225 + 270 + + lt=<<<<- +allocates memory +using + 10.0;160.0;10.0;10.0 + + + Relation + + 1695 + 510 + 390 + 720 + + lt=<- +dispatch +allocate +deallocate + 10.0;460.0;80.0;460.0;80.0;100.0;240.0;100.0;240.0;10.0 + + + Relation + + 1035 + 1005 + 90 + 135 + + lt=<<<<- +holds + 10.0;10.0;10.0;70.0 + + + UMLClass + + 705 + 1110 + 1005 + 270 + + score::memory::shared::ManagedMemoryResource +-- +Notes: +Unchanged + + + + + Relation + + 2325 + 1140 + 495 + 225 + + lt=<- +constructs and +returns + 310.0;120.0;10.0;120.0;10.0;10.0 + + + Relation + + 1170 + 1410 + 1020 + 90 + + lt=<<. + 10.0;10.0;660.0;10.0;660.0;40.0 + + + Relation + + 1620 + 885 + 1200 + 525 + + lt=<- +m2=get memory bounds + 10.0;10.0;110.0;10.0;110.0;320.0;780.0;320.0 + + + UMLNote + + 2895 + 825 + 495 + 225 + + +-- +Global Functions +-- +EnableOffsetPtrBoundsChecking(): +- Sets or clears flag which to allow bounds checking. + +bg=white + + + + diff --git a/score/memory/design/shared_memory/get_owner_uid_seq.puml b/score/memory/design/shared_memory/get_owner_uid_seq.puml new file mode 100644 index 000000000..50e35014e --- /dev/null +++ b/score/memory/design/shared_memory/get_owner_uid_seq.puml @@ -0,0 +1,34 @@ +@startuml GetOwnerUid + +participant SharedMemoryResource +participant "os::Stat" as os +participant "os::Acl" as acl + +[-> SharedMemoryResource: GetOwnerUid(fd) +SharedMemoryResource -> os : fstat(fd) +os -> SharedMemoryResource: return StatBuffer + +SharedMemoryResource -> SharedMemoryResource: set owner_uid = StatBuffer.uid + +alt StatBuffer.uid == typed memory manager uid && memory is named + SharedMemoryResource -> acl: get_acl(fd) + acl -> SharedMemoryResource: return acl + + SharedMemoryResource -> SharedMemoryResource: get uids with execution \n permissions from acl + + alt number of uids with exec permission != 1 + note over SharedMemoryResource + It is impossible to determine the owner + as mutiple uids have execution permission + Process is terminated + endnote + SharedMemoryResource -> SharedMemoryResource: std::terminate() + destroy SharedMemoryResource + end + + SharedMemoryResource -> SharedMemoryResource: owner_uid = uid found in the acls +end + +SharedMemoryResource ->[: return owner_uid + +@enduml diff --git a/score/memory/design/shared_memory/memory_allocation.uxf b/score/memory/design/shared_memory/memory_allocation.uxf new file mode 100644 index 000000000..080721ea2 --- /dev/null +++ b/score/memory/design/shared_memory/memory_allocation.uxf @@ -0,0 +1,410 @@ + + + 7 + + UMLClass + + 119 + 798 + 378 + 224 + + score::memory::shared::SharedMemoryResource +-- +fd: int +base: void* +-- +# SharedMemoryResource(StringLiteral, ...) ++ getMemoryResourceProxy(): MemoryResourceProxy* ++ getBaseAddress(): void* ++ getUsableBaseAddress(): void* ++ getEndAddress(): void* ++ getPath(): std::string* + +- do_allocate(std::size_t bytes, std::size_t alignment): void* +- do_deallocate(void* p, std::size_t bytes, std::size_t alignment): void +- do_is_equal(const offset_memory_resource& other) const noexcept: bool +-- +Notes: +Not copyable or movable and not public constructable +only Factory can create + + + + + + UMLClass + + 700 + 511 + 392 + 196 + + template=T: class +/score::memory::shared::PolymorphicOffsetPtrAllocator/ +-- +- proxy: offset_ptr<MemoryResourceProxy> +-- ++ PolymorphicOffsetPtrAllocator(MemoryResourceProxy* proxy) ++ PolymorphicOffsetPtrAllocator() ++ allocate( std::size_t n ): offset_ptr<T> ++ deallocate(offset_ptr<T> p, std::size_t n ): void ++ getMemoryResourceProxy(): offset_ptr<MemoryResourceProxy> +-- +Notes: +This data type shall apply requirements +stated by +https://en.cppreference.com/w/cpp/memory/allocator_traits +If default constructed, it will not use any indirection via a +MemoryResourceProxy to the MemoryResourceRegistry. Instead, it will +simply allocate memory on the heap. + + + + + UMLClass + + 1092 + 714 + 574 + 133 + + template=PointedType +score::memory::shared::OffsetPtr +-- +- offset: std::ptrdiff_t +-- ++ offset_ptr() noexcept; ++ offset_ptr(pointer) noexcept; +-- +Notes: +In addition to the specified members, +this data type shall apply requirements stated by +https://en.cppreference.com/w/cpp/memory/pointer_traits + + + + UMLClass + + 119 + 1057 + 882 + 147 + + score::memory::shared::SharedMemoryFactory +-- +# alreadyOpenFiles +-- ++ static getInstance() + ++ static Open(...) noexcept: std::shared_ptr<ManagedMemoryResource> ++ static Create(...) noexcept: std::shared_ptr<ManagedMemoryResource> ++ static CreateAnonymous(...) noexcept: std::shared_ptr<ManagedMemoryResource> ++ static CreateOrOpen(...) noexcept: std::shared_ptr<ManagedMemoryResource> ++ static Remove(std::string path): void +-- +Notes: +SharedMemoryFactory is not movable or copyable. +Factory Singleton, ensures that SharedMemoryResource is created only once + + + + UMLClass + + 119 + 280 + 469 + 189 + + score::cpp::pmr::memory_resource +-- +-- +memory_resource() +memory_resource(const memory_resource&) +/~memory_resource()/ + ++ allocate(std::size_t bytes, std::size_t alignment = alignof(std::max_align_t)): void* ++ deallocate(void* p, std::size_t bytes, std::size_t alignment = alignof(std::max_align_t)): void ++ is_equal(const offset_memory_resource& other) const noexcept: bool + +/- do_allocate(std::size_t bytes, std::size_t alignment): void*/ +/- do_deallocate(void* p, std::size_t bytes, std::size_t alignment): void/ +/- do_is_equal(const offset_memory_resource& other) const noexcept: bool/ +-- +Notes: +This data type shall apply requirements stated by +https://en.cppreference.com/w/cpp/memory/memory_resource + + + + + + Relation + + 336 + 763 + 21 + 49 + + lt=<<. + 10.0;10.0;10.0;50.0 + + + Relation + + 280 + 1015 + 56 + 56 + + lt=<- +creates + 10.0;10.0;10.0;60.0 + + + UMLClass + + 679 + 805 + 238 + 21 + + score::memory::shared::HeapMemoryResource + + + + + UMLNote + + 119 + 119 + 644 + 154 + + Usage: +using ara::core::Vector<T> = std::vector<T, std::scoped_allocator_adaptor<score::memory::shared::PolymorphicOffsetAllocator<T> > +auto top_level_memory_ressource = score::memory::shared::SharedMemoryFactory("/my_shm_name"); +auto sub_memory_resource_event1 = top_level_memory_ressource.createSubResource(10000, 20000); + +ara::core::Vector<std::uint8_t> myVector(sub_memory_resource_event1.getProxy()()); + +myVector.push_back(42u); // Will land on shared memory + +ara::core::Vector<std::uint8_t> onHeap(); +onHeap.push_back(42u); // Will land on heap +bg=yellow +transparency=80 + + + + UMLUseCase + + 658 + 1008 + 84 + 28 + + lt=. +osabstraction + + + + + Relation + + 490 + 952 + 182 + 84 + + lt=<- +manage +shared memory + 240.0;100.0;10.0;10.0 + + + UMLClass + + 714 + 280 + 525 + 175 + + score::memory::shared::MemoryResourceProxy +-- +- memory_identifier_: std::unit64_t +-- ++ MemoryResourceProxy(const std::uint64_t& memoryId) ++ allocate(std::size_t n ): void* ++ deallocate(void* p, std::size_t n ): void +-- +Notes: +MemoryResourceProxy is not movable or copyable. +Only does the dispatching to right MemoryResource via MemoryResourceRegistry. +Dispatching is needed as we can't put instances of real memory resources into shared memory as +they have vtables. + + + + + UMLClass + + 140 + 476 + 476 + 147 + + score::memory::shared::MemoryResourceRegistry +-- +- MemoryResourceRegistry() ++ getInstance(): MemoryResourceRegistry& ++ at(std::unit64_t): ManagedMemoryResource* ++ insert_resource(std::pair<std::unit64_t, ManagedMemoryResource*>): void ++ remove_resource(std::uint64_t identifier): void ++ get_bounds_for(void*): std::pair<void*, void*> ++ get_bounds_for(uint64 identifier): std::pair<void*, void*> +-- +Notes: +Not copyable, not movable + + + + + Relation + + 609 + 336 + 119 + 168 + + lt=<- +lookup dispatch +target + 10.0;220.0;30.0;220.0;30.0;10.0;150.0;10.0 + + + Relation + + 889 + 448 + 105 + 84 + + lt=<<<<- +allocates memory +using + 10.0;100.0;10.0;10.0 + + + Relation + + 581 + 448 + 189 + 259 + + lt=<- +dispatch +allocate +deallocate + 10.0;350.0;80.0;350.0;80.0;50.0;250.0;50.0;250.0;10.0 + + + Relation + + 273 + 616 + 49 + 49 + + lt=<<<<- +holds + 10.0;10.0;10.0;50.0 + + + UMLClass + + 119 + 651 + 469 + 119 + + score::memory::shared::ManagedMemoryResource +-- +- subresources: std::map<std::uint64_t, SubResource> +-- +/+ getMemoryResourceProxy(): MemoryResourceProxy*/ +/+ construct<T*>(args....): T*/ +/+ destruct<T>(T*): void/ +/+ getBaseAddress() = 0: void*/ +/+ getUsableBaseAddress() = 0: void*/ +/+ getEndAddress() = 0: void*/ + + + + Relation + + 126 + 462 + 21 + 203 + + lt=<<. + 10.0;10.0;10.0;270.0 + + + Relation + + 875 + 700 + 231 + 77 + + lt=<- +constructs and +returns + 310.0;80.0;10.0;80.0;10.0;10.0 + + + UMLClass + + 511 + 833 + 574 + 105 + + score::memory::shared::SubResource +-- +- parent: ManagedMemoryResource& +-- ++ SubResource(ManagedMemoryResource& parent, std::size_t initialSize, void* memory_start, std::size_t maxSize) +-- +Notes: +Not copyable and not public constructable +only parent resource can create. +This entity/class is used to create multi levels of memory resources. + + + + Relation + + 336 + 763 + 476 + 56 + + lt=<<. + 10.0;10.0;10.0;30.0;660.0;30.0;660.0;60.0 + + + Relation + + 336 + 777 + 287 + 70 + + lt=<<. + 10.0;10.0;390.0;10.0;390.0;80.0 + + diff --git a/score/memory/design/shared_memory/memory_allocation_workflow.uxf b/score/memory/design/shared_memory/memory_allocation_workflow.uxf new file mode 100644 index 000000000..6d81a9b66 --- /dev/null +++ b/score/memory/design/shared_memory/memory_allocation_workflow.uxf @@ -0,0 +1,659 @@ + + + 8 + + UMLGeneric + + 664 + 424 + 168 + 24 + + _:MemoryResourceProxy_ + + + + UMLGeneric + + 328 + 80 + 152 + 24 + + _:SharedMemoryFactoryP1_ + + + + UMLGeneric + + 240 + 112 + 16 + 712 + + + + + + UMLGeneric + + 400 + 112 + 16 + 512 + + + + + + Relation + + 248 + 112 + 168 + 32 + + lt=<<<- +open("/foo") + 190.0;20.0;10.0;20.0 + + + UMLGeneric + + 912 + 248 + 16 + 368 + + + + + + Relation + + 1104 + 256 + 24 + 128 + + lt=. + 10.0;10.0;10.0;140.0 + + + UMLGeneric + + 840 + 208 + 168 + 24 + + _:SharedMemoryResourceP1_ + + + + Relation + + 408 + 128 + 160 + 40 + + lt=<<<- +Check if instances +was already opened + 10.0;30.0;60.0;30.0;60.0;10.0;10.0;10.0 + + + Relation + + 248 + 600 + 168 + 32 + + lt=<. +instance + 10.0;20.0;190.0;20.0 + + + Relation + + 408 + 216 + 448 + 32 + + lt=<<<- +instantiate + 540.0;20.0;10.0;20.0 + + + UMLGeneric + + 1056 + 240 + 152 + 24 + + _:FooSharedMemoryObject_ + + + + UMLGeneric + + 1136 + 80 + 160 + 24 + + _:MemoryResourceRegistryP1_ + + + + Relation + + 736 + 440 + 24 + 304 + + lt=. + 10.0;10.0;10.0;360.0 + + + Relation + + 920 + 248 + 152 + 32 + + lt=<<<- +shm_open(open) + 170.0;20.0;10.0;20.0 + + + Relation + + 808 + 408 + 136 + 32 + + lt=<<<- +reinterprete(shared state) + 30.0;20.0;130.0;20.0 + + + UMLGeneric + + 456 + 680 + 160 + 24 + + _:PolymorphicOffsetAllocator_ + + + + UMLGeneric + + 1216 + 304 + 16 + 280 + + + + + + Relation + + 1216 + 96 + 24 + 224 + + lt=. + 10.0;10.0;10.0;260.0 + + + Relation + + 920 + 312 + 312 + 32 + + lt=<<<- +insert_resource(unqiueID, this) + 370.0;20.0;10.0;20.0 + + + Relation + + 920 + 336 + 312 + 24 + + lt=<. + 10.0;10.0;370.0;10.0 + + + Relation + + 920 + 360 + 200 + 32 + + lt=<<<- +read(shared state) + 230.0;20.0;10.0;20.0 + + + UMLGeneric + + 1104 + 360 + 16 + 48 + + + + + + Relation + + 1104 + 400 + 24 + 392 + + lt=. + 10.0;10.0;10.0;470.0 + + + Relation + + 1216 + 576 + 24 + 184 + + lt=. + 10.0;10.0;10.0;210.0 + + + Relation + + 248 + 632 + 680 + 32 + + lt=<<<- +getMemoryResourceProxy() + 830.0;20.0;10.0;20.0 + + + UMLGeneric + + 912 + 640 + 16 + 32 + + + + + + Relation + + 248 + 648 + 680 + 32 + + lt=<. +MemoryResourceProxy* + 10.0;20.0;830.0;20.0 + + + UMLGeneric + + 208 + 80 + 80 + 24 + + _:InstanceP1_ + + + + Relation + + 248 + 688 + 224 + 32 + + lt=<<<- +instantiate(MemoryResourceProxy*) + 260.0;20.0;10.0;20.0 + + + Relation + + 528 + 696 + 24 + 48 + + lt=. + 10.0;10.0;10.0;40.0 + + + UMLGeneric + + 528 + 728 + 16 + 96 + + + + + + Relation + + 248 + 720 + 296 + 32 + + lt=<<<- +allocate(Bytes) + 350.0;20.0;10.0;20.0 + + + Relation + + 536 + 728 + 216 + 32 + + lt=<<- +allocate(Bytes) + 250.0;20.0;10.0;20.0 + + + UMLGeneric + + 736 + 728 + 16 + 88 + + + + + + Relation + + 744 + 736 + 488 + 32 + + lt=<<<- +at(unique) + 590.0;20.0;10.0;20.0 + + + UMLGeneric + + 1216 + 744 + 16 + 32 + + + + + + Relation + + 744 + 752 + 488 + 32 + + lt=<. +ManagedMemoryResource* + 10.0;20.0;590.0;20.0 + + + Relation + + 912 + 664 + 24 + 128 + + lt=. + 10.0;10.0;10.0;140.0 + + + UMLGeneric + + 912 + 776 + 16 + 32 + + + + + + Relation + + 744 + 768 + 184 + 32 + + lt=<<<- +allocate(Bytes) + 210.0;20.0;10.0;20.0 + + + Relation + + 744 + 784 + 184 + 32 + + lt=<. +void* + 10.0;20.0;210.0;20.0 + + + Relation + + 536 + 792 + 216 + 32 + + lt=<. +void* + 10.0;20.0;250.0;20.0 + + + Relation + + 248 + 800 + 296 + 32 + + lt=<. +OffsetPtr<void> + 10.0;20.0;350.0;20.0 + + + UMLGeneric + + 1104 + 776 + 16 + 24 + + + + + + Relation + + 920 + 776 + 200 + 32 + + lt=<<<- +ftruncate(Bytes) + 230.0;20.0;10.0;20.0 + + + Relation + + 920 + 280 + 136 + 40 + + lt=<<<- +GetOwnerUid() + 10.0;30.0;60.0;30.0;60.0;10.0;10.0;10.0 + + + Relation + + 408 + 552 + 520 + 32 + + lt=<<<- +getOwnerUid() + 630.0;20.0;10.0;20.0 + + + Relation + + 408 + 576 + 520 + 24 + + lt=<. + 10.0;10.0;630.0;10.0 + + + UMLFrame + + 368 + 168 + 904 + 336 + + alt +-- +Open new instance + + + + UMLFrame + + 368 + 512 + 608 + 88 + + alt +-- +AllowedProviderCheck + + + + Relation + + 240 + 96 + 24 + 744 + + lt=. + 10.0;10.0;10.0;910.0 + + + Relation + + 400 + 96 + 24 + 544 + + lt=. + 10.0;10.0;10.0;660.0 + + + Relation + + 920 + 384 + 200 + 24 + + lt=<. + 10.0;10.0;230.0;10.0 + + + Relation + + 408 + 464 + 176 + 40 + + lt=<<<- +InsertResourceIntoMap + 10.0;30.0;60.0;30.0;60.0;10.0;10.0;10.0 + + + Relation + + 912 + 224 + 24 + 432 + + lt=. + 10.0;10.0;10.0;520.0 + + + Relation + + 408 + 456 + 520 + 24 + + lt=<. + 10.0;10.0;630.0;10.0 + + diff --git a/score/memory/design/shared_memory/named_memory_allocation.uxf b/score/memory/design/shared_memory/named_memory_allocation.uxf new file mode 100644 index 000000000..db669dda5 --- /dev/null +++ b/score/memory/design/shared_memory/named_memory_allocation.uxf @@ -0,0 +1,1007 @@ +10 + + 4 + + UMLGeneric + + 492 + 4 + 76 + 12 + + _:SharedMemoryFactoryP1_ + + + + UMLGeneric + + 368 + 28 + 8 + 736 + + + + + + UMLGeneric + + 528 + 24 + 8 + 736 + + + + + + Relation + + 372 + 60 + 164 + 16 + + lt=<<<- +Create("/foo", permissions, size, prefer_typed_memory) + 390.0;20.0;10.0;20.0 + + + UMLGeneric + + 632 + 116 + 84 + 12 + + _:SharedMemoryResourceP1_ + + + + Relation + + 372 + 748 + 164 + 12 + + lt=<. + 10.0;10.0;390.0;10.0 + + + UMLGeneric + + 352 + 4 + 40 + 12 + + _:InstanceP1_ + + + + UMLGeneric + + 964 + 28 + 52 + 12 + + :TypedMemoryP1 + + + + Relation + + 532 + 88 + 116 + 24 + + lt=<<<- +Check if instances +was already created + 10.0;40.0;150.0;40.0;150.0;10.0;10.0;10.0 + + + Relation + + 672 + 248 + 320 + 16 + + lt=<<<- +AllocateNamedTypedMemory("/foo", permissions, size) + 780.0;20.0;10.0;20.0 + + + Relation + + 672 + 276 + 320 + 16 + + lt=<. +success + 10.0;20.0;780.0;20.0 + + + UMLGeneric + + 1284 + 188 + 52 + 12 + + _:score::os::Mman + + + + Relation + + 532 + 732 + 144 + 16 + + lt=<. +instance + 10.0;20.0;340.0;20.0 + + + Relation + + 1312 + 196 + 12 + 584 + + lt=. + 10.0;10.0;10.0;1440.0 + + + Relation + + 984 + 36 + 12 + 264 + + lt=. + 10.0;10.0;10.0;640.0 + + + Relation + + 672 + 404 + 648 + 16 + + lt=<. +file_descriptor + 10.0;20.0;1600.0;20.0 + + + Relation + + 672 + 392 + 648 + 16 + + lt=<<<- +shm_open(O_RDWR|O_CREAT|O_EXCL, mode) + 1600.0;20.0;10.0;20.0 + + + Relation + + 672 + 588 + 132 + 20 + + lt=<<<- +GetOwnerUidAndSizeOf(file_descriptor) + 10.0;30.0;60.0;30.0;60.0;10.0;10.0;10.0 + + + Relation + + 672 + 700 + 480 + 16 + + lt=<<<- +initializeInternalsInSharedMemory() - MemoryResourceProxyP1, alreadyAllocatedBytes, mutex + 1180.0;20.0;10.0;20.0 + + + UMLGeneric + + 1116 + 584 + 76 + 12 + + _:FooSharedMemoryObject_ + + + + Relation + + 672 + 712 + 480 + 12 + + lt=<. + 10.0;10.0;1180.0;10.0 + + + UMLGeneric + + 1208 + 584 + 80 + 12 + + _:MemoryResourceRegistryP1_ + + + + Relation + + 672 + 668 + 576 + 16 + + lt=<<<- +insert_resource(unqiueID, this) + 1420.0;20.0;10.0;20.0 + + + Relation + + 1240 + 592 + 12 + 188 + + lt=. + 10.0;10.0;10.0;450.0 + + + Relation + + 672 + 676 + 576 + 16 + + lt=<. +return result + 10.0;20.0;1420.0;20.0 + + + Relation + + 1144 + 592 + 12 + 184 + + lt=. + 10.0;10.0;10.0;440.0 + + + Relation + + 672 + 608 + 648 + 16 + + lt=<<<- +mmap(map memory into process) + 1600.0;20.0;10.0;20.0 + + + UMLGeneric + + 1312 + 392 + 8 + 24 + + + + + + UMLGeneric + + 1312 + 608 + 8 + 24 + + + + + + UMLGeneric + + 984 + 44 + 8 + 248 + + + + + + UMLGeneric + + 1240 + 640 + 8 + 92 + + + + + + UMLGeneric + + 1144 + 696 + 8 + 28 + + + + + + Relation + + 668 + 124 + 12 + 656 + + lt=. + 10.0;10.0;10.0;1620.0 + + + UMLGeneric + + 668 + 236 + 8 + 100 + + + + + + UMLGeneric + + 668 + 576 + 8 + 192 + + + + + + Relation + + 672 + 620 + 648 + 12 + + lt=<. + 10.0;10.0;1600.0;10.0 + + + UMLGeneric + + 1124 + 184 + 92 + 12 + + :score::tmd::ITypedSharedMemory + + + + Relation + + 1164 + 192 + 12 + 100 + + lt=. + 10.0;10.0;10.0;230.0 + + + Relation + + 988 + 256 + 184 + 16 + + lt=<<<- +AllocateNamedTypedMemory(size, "/foo", permissions) + 440.0;20.0;10.0;20.0 + + + Relation + + 988 + 268 + 184 + 16 + + lt=<. + + 10.0;20.0;440.0;20.0 + + + Relation + + 368 + 12 + 12 + 764 + + lt=. + 10.0;10.0;10.0;1890.0 + + + UMLGeneric + + 1164 + 260 + 8 + 24 + + + + + + UMLSpecialState + + 1240 + 772 + 8 + 8 + + type=termination + + + + UMLSpecialState + + 1144 + 768 + 8 + 8 + + type=termination + + + + UMLSpecialState + + 668 + 772 + 8 + 8 + + type=termination + + + + UMLGeneric + + 1100 + 424 + 48 + 12 + + score::os::Stat + + + + UMLGeneric + + 1156 + 424 + 76 + 12 + + os::utils::AccessControlList + + + + Relation + + 1120 + 432 + 12 + 44 + + lt=. + 10.0;10.0;10.0;90.0 + + + Relation + + 1192 + 432 + 12 + 96 + + lt=. + 10.0;10.0;10.0;220.0 + + + Relation + + 672 + 448 + 456 + 16 + + lt=<<<- +fchmod(file_descriptor, mode) + 1120.0;20.0;10.0;20.0 + + + UMLGeneric + + 1120 + 440 + 8 + 32 + + + + + + UMLGeneric + + 1192 + 496 + 8 + 24 + + + + + + Relation + + 672 + 496 + 528 + 16 + + lt=<<<- +AllowUser(file_descriptor, os::Acl::Permission) + 1300.0;20.0;10.0;20.0 + + + Relation + + 672 + 464 + 456 + 12 + + lt=<. + 10.0;10.0;1120.0;10.0 + + + Relation + + 672 + 508 + 528 + 12 + + lt=<. + 10.0;10.0;1300.0;10.0 + + + UMLGeneric + + 1236 + 424 + 48 + 12 + + score::os::Unistd + + + + Relation + + 1256 + 432 + 12 + 132 + + lt=. + 10.0;10.0;10.0;310.0 + + + Relation + + 672 + 532 + 592 + 16 + + lt=<<<- +ftruncate(file_descriptor, size) + 1460.0;20.0;10.0;20.0 + + + UMLGeneric + + 1256 + 532 + 8 + 24 + + + + + + Relation + + 672 + 540 + 592 + 16 + + lt=<. + + 10.0;20.0;1460.0;20.0 + + + Relation + + 532 + 28 + 440 + 16 + + lt=<<<- +TypedMemory::Default() + 1080.0;20.0;10.0;20.0 + + + Relation + + 532 + 40 + 460 + 16 + + lt=<. +return TypedMemoryP1 + 10.0;20.0;1130.0;20.0 + + + Relation + + 528 + 12 + 12 + 764 + + lt=. + 10.0;10.0;10.0;1890.0 + + + Relation + + 532 + 160 + 144 + 16 + + lt=<<<- +Create("/foo", permissions, size, TypedMemoryP1) + 340.0;20.0;10.0;20.0 + + + UMLFrame + + 336 + 116 + 220 + 32 + + alt +-- + +return exisiting instance + + + + Relation + + 372 + 124 + 164 + 12 + + lt=<. + 10.0;10.0;390.0;10.0 + + + UMLSpecialState + + 1312 + 768 + 8 + 8 + + type=termination + + + + UMLGeneric + + 668 + 140 + 8 + 80 + + + + + + UMLSpecialState + + 528 + 768 + 8 + 8 + + type=termination + + + + UMLSpecialState + + 368 + 768 + 8 + 8 + + type=termination + + + + UMLFrame + + 620 + 224 + 736 + 128 + + alt +-- +AllocateInTypedmemory(typed_memory_ptr_ != nullptr) + + + + + + UMLFrame + + 620 + 364 + 736 + 200 + + alt +-- +AllocateInTypedMemory failed or AllocateInSysram(typed_memory_ptr_ == nullptr) + + + + UMLGeneric + + 668 + 532 + 8 + 24 + + + + + + UMLFrame + + 644 + 424 + 408 + 100 + + alt +-- +mode == world-readable + + + + + + +-- +mode == UserPermissionsMap + + + + UMLGeneric + + 1024 + 584 + 84 + 12 + + _:MemoryResourceProxy_ + + + + Relation + + 1064 + 592 + 12 + 188 + + lt=. + 10.0;10.0;10.0;450.0 + + + UMLSpecialState + + 1064 + 768 + 8 + 8 + + type=termination + + + + UMLGeneric + + 1064 + 636 + 8 + 24 + + + + + + Relation + + 672 + 640 + 400 + 16 + + lt=<<<- +Instantiate(Unique ID) + 980.0;20.0;10.0;20.0 + + + Relation + + 672 + 648 + 400 + 16 + + lt=<. + + 10.0;20.0;980.0;20.0 + + + Relation + + 672 + 304 + 648 + 16 + + lt=<<<- +shm_open(O_RDW|O_EXCL, mode) + 1600.0;20.0;10.0;20.0 + + + Relation + + 672 + 316 + 648 + 16 + + lt=<. +file_descriptor + 10.0;20.0;1600.0;20.0 + + + UMLGeneric + + 1312 + 304 + 8 + 24 + + + + + + Relation + + 672 + 188 + 160 + 20 + + lt=<<<- +mode = calcStatModeForPermissions(permissions) + 10.0;30.0;60.0;30.0;60.0;10.0;10.0;10.0 + + + UMLGeneric + + 668 + 392 + 8 + 24 + + + + + + UMLGeneric + + 668 + 448 + 8 + 24 + + + + + + UMLGeneric + + 668 + 496 + 8 + 24 + + + + + diff --git a/score/memory/design/shared_memory/offset_ptr_problems.md b/score/memory/design/shared_memory/offset_ptr_problems.md new file mode 100644 index 000000000..fb8afc6ae --- /dev/null +++ b/score/memory/design/shared_memory/offset_ptr_problems.md @@ -0,0 +1,55 @@ +# OffsetPtr problems to solve + +## Definitions / background: +- Start check: Checking that the start address of a pointed-to object lies the same shared memory region in which the OffsetPtr was created. +- End checks: Checking that the end address of a pointed-to object lies the same shared memory region in which the OffsetPtr was created. +- Bounds check: Start and end check. + +One-past-the-end-iterators: +- The size of memory allocated for a container usually doesn't include the size of a pointed-to object starting at a one-past-the-end iterator address. Therefore, doing an end check on a one-past-the-end iterator may fail. + - It is legal to create and copy such a pointer. + - It's illegal to dereference such a pointer. + - It's legal to decrement such a pointer and then dereference it. +- The standard library containers that we're using (i.e. std::unordered_map) are creating and copying one-past-the-end pointers. + +## Required checks: + +OffsetPtr +- Dereferencing a pointer - operator* + - We need to do a Bounds check. +- Getting raw pointer - get(), operator pointer(), operator-> + - Although we're not dereferencing the pointer, so we are not violating any safety goals by creating the raw pointer, we cannot control the bounds checking after this point (i.e. making sure that bounds checking is done before the raw pointer is dereferenced). Therefore, we need to do the same checks as if we were dereferencing the pointer. +- Copying OffsetPtr + - We don't _need_ to do any checks here. The copied-to OffsetPtr will do any required bounds checking when it's dereferenced. + +OffsetPtr +- Dereferencing a pointer - operator* + - We cannot dereference a void*. +- Getting raw pointer - get(), operator pointer(), operator-> + - We do not know the size of the pointed-to object and therefore can only perform a start check. + - It must be ensured that a full bounds check is done before the user dereferences the pointer (after they've cast it to a type). i.e. if we do a start check when getting the pointer, the user just has to do the end check. +- Copying OffsetPtr + - We don't _need_ to do any checks here. The copied-to OffsetPtr / subsequent user checks before dereferencing will do any required bounds checking. + + +## Problems to solve: + +1. UB in pointer arithmetic when calculating offset and getting absolute pointer from offset. +2. Copying offset pointer should not do end check (will fail for one-past-the-end iterator). +3. Getting a void pointer cannot do end check. Dereferencing retrieved pointer will therefore bypass end check. +4. Copying an OffsetPtr to the stack will no longer perform bounds checking. Therefore, we must be able to do bounds checking when getting / dereferencing a stack OffsetPtr. + +## Proposed solution: + +1. Cast addresses to integral types (implementation defined: `https://timsong-cpp.github.io/cppwp/n4659/expr.reinterpret.cast#4`, `https://timsong-cpp.github.io/cppwp/n4659/expr.reinterpret.cast#5`) and then do all arithmetic on integral types to avoid UB. + - When calculating the offset, cast the OffsetPtr and pointed-to addresses to integral type and subtract integers. + - When getting raw pointer from offset, cast the OffsetPtr address to integral type and add the offset to it. Cast the resulting address to a pointer. + - When copying an OffsetPtr, cast the copied-from and copied-to OffsetPtr addresses to integral types. Add the difference between the two integers to the currently stored offset and store in the copied-to OffsetPtr. +2. Copying offset pointer can now calculate the offset in an implementation defined manner (even when copying out of shared memory) so doesn't have to do any bounds checking. Therefore, copying one-past-the-end iterator will do no end check and never fail. +3. OffsetPtr could contain a templated get() (which would be the only one enabled for PointedType=void) which is templated with the real size of the type (which is currently pointed to via a void pointer). It will then do the bounds check using the size of the and return a void* or event the type itself. + - Limitation of this is that we use OffsetPtr instead of void*. We would have to change ManagedMemoryResource getUsableBaseAddress, getEndAddress etc. to return OffsetPtr instead of void*. We wanted to anyway make these private, so it might not be a big issue. + - Other option would be to have getUnsafe() and require the caller to manually do the bounds checking. +4. When copying an OffsetPtr to the stack, we must store the MemoryResource identifier to do the bounds checking on dereferencing (since we can't use the address of the OffsetPtr itself to identiy the memory resource, like when the OffsetPtr is in shared memory). + +## Misc: +- MemoryResourceRegistry can store uintptr_t instead of void* for memory bounds. diff --git a/score/memory/endianness.cpp b/score/memory/endianness.cpp new file mode 100644 index 000000000..64957c219 --- /dev/null +++ b/score/memory/endianness.cpp @@ -0,0 +1,65 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/memory/endianness.h" + +#include + +#include + +namespace score::memory +{ + +Endianness DetermineSystemEndianness() noexcept +{ + constexpr std::uint16_t value{0x0A0BU}; + std::uint8_t part_of_value{0xFFU}; + + // We do not do any reinterpret cast or anything, since this would be undefined behavior + // No risk of using banned memcpy function here that would cause UB, because passed objects are trivial types and + // minimal bytes specified to copy. + // NOLINTNEXTLINE(score-banned-function): Tolerated unless no alternative + score::cpp::ignore = std::memcpy(&part_of_value, &value, 1U); + + /* Negative test: Depends on the host system, cannot be covered in current setups. Which is no problem, + since this is only the other half of a condition. */ + if (part_of_value == 0x0BU) // LCOV_EXCL_BR_LINE + { + return Endianness::kLittleEndian; + } + else + { + return Endianness::kBigEndian; // LCOV_EXCL_LINE + } +} + +// @brief Determine on runtime if the current system is running as little endian +bool IsSystemLittleEndian() noexcept +{ + return DetermineSystemEndianness() == Endianness::kLittleEndian; +} + +// @brief Determine on runtime if the current system is running as big endian +bool IsSystemBigEndian() noexcept +{ + return DetermineSystemEndianness() == Endianness::kBigEndian; +} + +/// @brief Helper function to check if bytes need to be switched in order +/// @param required_endianness The endianness required (e.g. by requirements on the network) +/// @return bool if switching bytes is necessary in order to achieve required endianness, false otherwise +bool SwitchingBytesNecessary(const Endianness required_endianness) noexcept +{ + return required_endianness != DetermineSystemEndianness(); +} + +} // namespace score::memory diff --git a/score/memory/endianness.h b/score/memory/endianness.h new file mode 100644 index 000000000..245313634 --- /dev/null +++ b/score/memory/endianness.h @@ -0,0 +1,88 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef BASELIBS_SCORE_MEMORY_ENDIANNESS_H +#define BASELIBS_SCORE_MEMORY_ENDIANNESS_H + +#include + +namespace score::memory +{ + +/// @brief Represents the possible memory layouts a computer system can have +enum class Endianness : std::uint8_t +{ + kLittleEndian, + kBigEndian +}; + +/// @brief By interpreting the memory of an integer, determine which endianness the current system uses (on runtime) +Endianness DetermineSystemEndianness() noexcept; + +// @brief Determine on runtime if the current system is running as little endian +bool IsSystemLittleEndian() noexcept; + +// @brief Determine on runtime if the current system is running as big endian +bool IsSystemBigEndian() noexcept; + +/// @brief Helper function to check if bytes need to be switched in order +/// @param required_endianness The endianness required (e.g. by requirements on the network) +/// @return bool if switching bytes is necessary in order to achieve required endianness, false otherwise +bool SwitchingBytesNecessary(const Endianness required_endianness) noexcept; + +/// @brief Reverses the bytes in the given integer value +/// @note Within C++23 this can be replaced with https://en.cppreference.com/w/cpp/numeric/byteswap +/// @param value The value to convert +/// @return The converted value +constexpr std::uint32_t ByteSwap(const std::uint32_t value) noexcept +{ + return ((value >> 24U) & 0x000000FFU) | /*!< byte 3 to byte 0 */ + ((value >> 8U) & 0x0000FF00U) | /*!< byte 2 to byte 1 */ + ((value << 8U) & 0x00FF0000U) | /*!< byte 1 to byte 2 */ + ((value << 24U) & 0xFF000000U); /*!< byte 0 to byte 3 */ +} + +/// @brief Reverses the bytes in the given integer value +/// @note Within C++23 this can be replaced with https://en.cppreference.com/w/cpp/numeric/byteswap +/// @param value The value to convert +/// @return The converted value +constexpr std::uint64_t ByteSwap(const std::uint64_t value) noexcept +{ + return static_cast((value >> 56U) & 0x00000000000000FFULL) | /*!< byte 7 to byte 0 */ + static_cast((value >> 40U) & 0x000000000000FF00ULL) | /*!< byte 6 to byte 1 */ + static_cast((value >> 24U) & 0x0000000000FF0000ULL) | /*!< byte 5 to byte 2 */ + static_cast((value >> 8U) & 0x00000000FF000000ULL) | /*!< byte 4 to byte 3 */ + static_cast((value << 8U) & 0x000000FF00000000ULL) | /*!< byte 3 to byte 4 */ + static_cast((value << 24U) & 0x0000FF0000000000ULL) | /*!< byte 2 to byte 5 */ + static_cast((value << 40U) & 0x00FF000000000000ULL) | /*!< byte 1 to byte 6 */ + static_cast((value << 56U) & 0xFF00000000000000ULL); /*!< byte 0 to byte 7 */ +} + +/// @brief Converts the byte encoding of integer values from the big-endian byte order to the current CPU (the "host") +/// byte order uses +/// @param value The value to convert +/// @return The converted value +template +auto BigEndianToHostEndianness(const ValueType value) noexcept -> ValueType +{ + /* Negative test: Depends on the host system, cannot be covered in current setups. Which is no problem, + since this is only the other half of a condition. */ + if (IsSystemLittleEndian()) // LCOV_EXCL_BR_LINE + { + return ByteSwap(value); + } + return value; // LCOV_EXCL_LINE +} + +} // namespace score::memory + +#endif // BASELIBS_SCORE_MEMORY_ENDIANNESS_H diff --git a/score/memory/endianness_test.cpp b/score/memory/endianness_test.cpp new file mode 100644 index 000000000..ce05653b0 --- /dev/null +++ b/score/memory/endianness_test.cpp @@ -0,0 +1,64 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/memory/endianness.h" + +#include + +namespace score::memory +{ +namespace +{ + +TEST(Endianness, WeAreRunningOnALittleEndianSystem) +{ + // Listen, if this test fails, then this means that this test is no longer running on a little endian machine, _NOT_ + // that the test is wrong or the code under test! Yes, this is not a unit test, but we assume in a lot of places in + // our code that we are running on little endian (we try to clean this up, which is why we introduce this library) + // but still, better we have a test that makes this clear. + EXPECT_TRUE(IsSystemLittleEndian()); +} + +TEST(Endianness, CannotBeLittleAndBigAtTheSameTime) +{ + EXPECT_TRUE(IsSystemLittleEndian() != IsSystemBigEndian()); +} + +TEST(Endianness, SwitchingBytesNecessary) +{ + EXPECT_TRUE(SwitchingBytesNecessary(Endianness::kBigEndian)); + EXPECT_FALSE(SwitchingBytesNecessary(Endianness::kLittleEndian)); +} + +TEST(Endianness, ByteSwap) +{ + EXPECT_EQ(ByteSwap(0x10203040U), 0x40302010U); + EXPECT_EQ(ByteSwap(static_cast(0x1020304050607080ULL)), 0x8070605040302010ULL); +} + +TEST(Endianness, BigEndianToHostEndianness) +{ + if (IsSystemLittleEndian()) + { + EXPECT_EQ(BigEndianToHostEndianness(0x10203040U), ByteSwap(0x10203040U)); + EXPECT_EQ(BigEndianToHostEndianness(static_cast(0x1020304050607080ULL)), + ByteSwap(static_cast(0x1020304050607080ULL))); + } + else if (IsSystemBigEndian()) + { + EXPECT_EQ(BigEndianToHostEndianness(0x10203040U), 0x10203040U); + EXPECT_EQ(BigEndianToHostEndianness(static_cast(0x1020304050607080ULL)), 0x1020304050607080ULL); + } +} + +} // namespace +} // namespace score::memory diff --git a/score/memory/pmr_ring_buffer.h b/score/memory/pmr_ring_buffer.h new file mode 100644 index 000000000..aa275a5ff --- /dev/null +++ b/score/memory/pmr_ring_buffer.h @@ -0,0 +1,195 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef BASELIBS_SCORE_MEMORY_PMR_RING_BUFFER_H +#define BASELIBS_SCORE_MEMORY_PMR_RING_BUFFER_H + +#include "score/vector.hpp" + +#include + +namespace score::memory +{ + +/// \brief PMR-aware initialization-time-sized ring buffer queue. +/// \details This class is based on the ideas from score::cpp::circular_buffer implementation (parts of the source code +/// were copied from there), but is supposed to provide initialization-time buffer capacity. +/// Only the minimum queue-like functionality is currently implemented. +/// +/// \tparam T Specifies the value type of a single element. +/// The element type itself can be PMR-aware; use of monotonic_buffer_resource in such a case is not advised +/// due to inherent buffer space leak. +template +class PmrRingBuffer +{ + public: + using allocator_type = score::cpp::pmr::polymorphic_allocator; + using value_type = T; + using size_type = std::size_t; + + /// \brief Constructs an empty ring buffer with a given capacity, using a given PMR allocator + /// \param capacity A maximum number of values in the queue (greater than zero) + /// \param allocator The allocator bound to the memory resource used + explicit PmrRingBuffer(size_type capacity, const allocator_type& allocator) + : array_(capacity, allocator), tail_{0U}, head_{0U}, size_{0U} + { + } + + /// \brief Clears all elements from the container. + ~PmrRingBuffer() + { + clear(); + } + + /// \brief Removes all elements from the container. + void clear() + { + const size_type number_of_elements_to_remove{size()}; + for (size_type i{0U}; i < number_of_elements_to_remove; ++i) + { + pop_front(); + } + } + + /// \brief Removes the first element in the container. + void pop_front() + { + if (!empty()) + { + allocator_type allocator{array_.get_allocator()}; + std::allocator_traits::destroy(allocator, &front()); + ++head_; + head_ %= capacity(); + --size_; + } + } + + /// \brief Returns the first element in the (const) container. + /// + /// \return Reference to the first element in the container. + const T& front() const + { + SCORE_LANGUAGE_FUTURECPP_ASSERT_DBG(!empty()); + return *p_at(head_); + } + + /// \brief Returns the first element in the (non-const) container. + /// + /// \return Reference to the first element in the container. + T& front() + { + SCORE_LANGUAGE_FUTURECPP_ASSERT_DBG(!empty()); + return *p_at(head_); + } + + /// \brief constructs a new element in place at the end of the buffer + /// + /// It will remove the element at front() if the buffer is full + /// + /// \param arguments The arguments from which the element is constructed + template + void emplace_back(Ts&&... arguments) + { + if (full()) + { + pop_front(); + } + allocator_type allocator{array_.get_allocator()}; + std::allocator_traits::construct(allocator, p_at(tail_), std::forward(arguments)...); + ++tail_; + tail_ %= capacity(); + ++size_; + } + + /// \brief Checks if container contains elements. + /// + /// \return True, if the container does not contain elements - false otherwise. + bool empty() const + { + return (0U == size()); + } + + /// \brief Returns true if container is full. + /// + /// \return True if container is full - otherwise false. + bool full() const + { + return (capacity() == size()); + } + + /// \brief Returns the number elements in the container. + /// + /// \return The number of elements in the container. + size_type size() const + { + return size_; + } + + /// \brief Returns the maximum size of the circular buffer. + /// + /// \return Maximum size of the circular buffer. + size_type capacity() const + { + return array_.size(); + } + + private: + PmrRingBuffer(const PmrRingBuffer& other) = delete; + PmrRingBuffer(PmrRingBuffer&& other) = delete; + PmrRingBuffer& operator=(const PmrRingBuffer& rhs) = delete; + PmrRingBuffer& operator=(PmrRingBuffer&& rhs) = delete; + + /// \brief Returns const pointer to the element of the internal array. + /// + /// \param pos The (zero-based) position in the array + // Suppress "AUTOSAR C++14 A5-2-4" rule finding: "reinterpret_cast shall not be used.". + // Cast from std::aligned_storage (opposed to void*) needs + // reinterpret_cast. reinterpret_cast is fine here as we obey case/rule 5 under + // https://en.cppreference.com/w/cpp/language/reinterpret_cast! Alignment needs of the cast to type T are + // fulfilled as the aligned storage represented by array_ has ben exactly aligned to T's needs. + // NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast): Tolerated since performed cast std::aligned_storage + // coverity[autosar_cpp14_a5_2_4_violation] + const T* p_at(size_type pos) const + { + return reinterpret_cast(&array_[pos]); + } + // NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast): see above + + /// \brief Returns pointer to the element of the internal array. + /// + /// \param pos The (zero-based) position in the array + /* Cast from std::aligned_storage (opposed to void*) needs + * reinterpret_cast. Further explanation see above. */ + // NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast): Tolerated since performed cast std::aligned_storage + // coverity[autosar_cpp14_a5_2_4_violation] + T* p_at(size_type pos) + { + return reinterpret_cast(&array_[pos]); + } + // NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast): see above + + /// \brief Internal data array + score::cpp::pmr::vector::value>::type> array_; + + /// \brief Index of the last valid data in the circular buffer. + size_type tail_; + + /// \brief Index of the first valid data in the circular buffer. + size_type head_; + + /// \brief Amount of valid elements in the circular buffer. + size_type size_; +}; + +} // namespace score::memory + +#endif // BASELIBS_SCORE_MEMORY_PMR_RING_BUFFER_H diff --git a/score/memory/pmr_ring_buffer_test.cpp b/score/memory/pmr_ring_buffer_test.cpp new file mode 100644 index 000000000..91732f5b2 --- /dev/null +++ b/score/memory/pmr_ring_buffer_test.cpp @@ -0,0 +1,102 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/memory/pmr_ring_buffer.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace score::memory +{ + +namespace +{ + +TEST(PmrRingBufferTest, EmptyBufferIsEmpty) +{ + PmrRingBuffer buffer1(1U, score::cpp::pmr::polymorphic_allocator<>()); + PmrRingBuffer buffer2(2U, score::cpp::pmr::polymorphic_allocator<>()); + EXPECT_TRUE(buffer1.empty()); + EXPECT_TRUE(buffer2.empty()); + EXPECT_FALSE(buffer1.full()); + EXPECT_FALSE(buffer2.full()); + EXPECT_EQ(buffer1.size(), 0U); + EXPECT_EQ(buffer2.size(), 0U); +} + +TEST(PmrRingBufferTest, BufferWithOneElement) +{ + PmrRingBuffer buffer1(1U, score::cpp::pmr::polymorphic_allocator<>()); + PmrRingBuffer buffer2(2U, score::cpp::pmr::polymorphic_allocator<>()); + buffer1.emplace_back(1); + buffer2.emplace_back(2); + EXPECT_FALSE(buffer1.empty()); + EXPECT_FALSE(buffer2.empty()); + EXPECT_TRUE(buffer1.full()); + EXPECT_FALSE(buffer2.full()); + EXPECT_EQ(buffer1.size(), 1U); + EXPECT_EQ(buffer2.size(), 1U); +} + +TEST(PmrRingBufferTest, BufferWithTwoElements) +{ + PmrRingBuffer buffer1(1U, score::cpp::pmr::polymorphic_allocator<>()); + PmrRingBuffer buffer2(2U, score::cpp::pmr::polymorphic_allocator<>()); + buffer1.emplace_back(1); + buffer1.emplace_back(1); + buffer2.emplace_back(2); + buffer2.emplace_back(2); + EXPECT_FALSE(buffer1.empty()); + EXPECT_FALSE(buffer2.empty()); + EXPECT_TRUE(buffer1.full()); + EXPECT_TRUE(buffer2.full()); + EXPECT_EQ(buffer1.size(), 1U); + EXPECT_EQ(buffer2.size(), 2U); +} + +TEST(PmrRingBufferTest, BufferInBufferOut) +{ + PmrRingBuffer buffer2(2U, score::cpp::pmr::polymorphic_allocator<>()); + buffer2.emplace_back(1); + buffer2.emplace_back(2); + buffer2.emplace_back(3); + EXPECT_TRUE(buffer2.full()); + const auto& const_buffer2 = buffer2; + EXPECT_EQ(buffer2.front(), 2); + buffer2.pop_front(); + EXPECT_EQ(const_buffer2.front(), 3); + buffer2.pop_front(); + EXPECT_TRUE(buffer2.empty()); + + // popping empty buffer (without accessing its front element) is safe + buffer2.pop_front(); + EXPECT_TRUE(buffer2.empty()); + EXPECT_FALSE(buffer2.full()); + EXPECT_EQ(buffer2.size(), 0U); +} + +TEST(PmrRingBufferTest, PmrAwareElement) +{ + PmrRingBuffer> buffer2(2U, score::cpp::pmr::polymorphic_allocator<>()); + buffer2.emplace_back(std::initializer_list{1}); + buffer2.emplace_back(std::initializer_list{2, 2}); + buffer2.emplace_back(std::initializer_list{3, 3, 3}); + EXPECT_EQ(buffer2.front().size(), 2); + buffer2.pop_front(); + EXPECT_EQ(buffer2.front().size(), 3); + buffer2.pop_front(); + EXPECT_TRUE(buffer2.empty()); +} + +} // namespace + +} // namespace score::memory diff --git a/score/memory/shared/BUILD b/score/memory/shared/BUILD new file mode 100644 index 000000000..a7f691425 --- /dev/null +++ b/score/memory/shared/BUILD @@ -0,0 +1,738 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* +load("//bazel:unit_tests.bzl", "cc_gtest_unit_test", "cc_unit_test") + +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* +COMPILER_WARNING_FEATURES = [ + "treat_warnings_as_errors", + "additional_warnings", + "strict_warnings", +] + +cc_library( + name = "atomic_interface", + hdrs = ["i_atomic.h"], + features = COMPILER_WARNING_FEATURES, + tags = ["FFI"], + visibility = [ + "//score/memory/shared:__pkg__", + # "@ddad//score/memory/shared:__pkg__", + ], + deps = [], +) + +cc_library( + name = "atomic_indirector", + srcs = ["atomic_indirector.cpp"], + hdrs = ["atomic_indirector.h"], + features = COMPILER_WARNING_FEATURES, + tags = ["FFI"], + visibility = [ + "//visibility:public", + # "@ddad//visibility:public", + ], + deps = [":atomic_interface"], +) + +cc_library( + name = "atomic_indirector_mock_binding", + testonly = True, + srcs = ["atomic_mock.cpp"], + hdrs = ["atomic_mock.h"], + features = COMPILER_WARNING_FEATURES, + visibility = ["//visibility:public"], + deps = [ + ":atomic_indirector", + "@googletest//:gtest", + ], +) + +cc_library( + name = "pointer_arithmetic_util", + srcs = ["pointer_arithmetic_util.cpp"], + hdrs = ["pointer_arithmetic_util.h"], + features = COMPILER_WARNING_FEATURES, + tags = ["FFI"], + visibility = [ + "//visibility:public", + # "@ddad//score/memory:__subpackages__", + ], + deps = [ + "//score/mw/log:frontend", + ], +) + +cc_library( + name = "i_shared_memory_resource", + srcs = ["i_shared_memory_resource.cpp"], + hdrs = ["i_shared_memory_resource.h"], + features = COMPILER_WARNING_FEATURES, + tags = ["FFI"], + visibility = [ + "//visibility:public", + # "@ddad//score/memory:__subpackages__", + ], + deps = [ + ":types", + ":user_permission", + "@score-baselibs//score/language/futurecpp", + ], +) + +cc_library( + name = "shared_memory_resource_heap_allocator_mock", + testonly = True, + srcs = ["shared_memory_resource_heap_allocator_mock.cpp"], + hdrs = ["shared_memory_resource_heap_allocator_mock.h"], + features = COMPILER_WARNING_FEATURES, + tags = ["FFI"], + visibility = [ + "//visibility:public", + # "@ddad//score/memory:__subpackages__", + ], + deps = [ + ":i_shared_memory_resource", + ":new_delete_delegate_resource", + "@score-baselibs//score/language/futurecpp", + "@googletest//:gtest", + ], +) + +cc_library( + name = "shared_memory_resource_mock", + testonly = True, + srcs = ["shared_memory_resource_mock.cpp"], + hdrs = ["shared_memory_resource_mock.h"], + features = COMPILER_WARNING_FEATURES, + visibility = [ + "//visibility:public", + # "@ddad//score/memory:__subpackages__", + ], + deps = [ + ":i_shared_memory_resource", + "@score-baselibs//score/language/futurecpp", + "@googletest//:gtest", + ], +) + +cc_library( + name = "shared_memory_resource", + srcs = ["shared_memory_resource.cpp"], + hdrs = ["shared_memory_resource.h"], + features = COMPILER_WARNING_FEATURES, + tags = ["FFI"], + deps = [ + ":i_shared_memory_resource", + ":lock_file", + ":pointer_arithmetic_util", + ":types", + "//score/language/safecpp/safe_math", + "//score/memory/shared/sealedshm/sealedshm_wrapper:sealedshm", + "//score/memory/shared/typedshm/typedshm_wrapper:typedmemory", + "//score/os:errno_logging", + "//score/os:fcntl", + "//score/os:mman", + "//score/os:stat", + "//score/os:unistd", + "//score/os/utils/acl", + "//score/os/utils/interprocess:interprocess_mutex", + "//score/mw/log:frontend", + "@score-baselibs//score/language/futurecpp", + ], +) + +cc_library( + name = "i_shared_memory_factory", + srcs = ["i_shared_memory_factory.cpp"], + hdrs = ["i_shared_memory_factory.h"], + features = COMPILER_WARNING_FEATURES, + tags = ["FFI"], + deps = [ + ":i_shared_memory_resource", + ":types", + "//score/memory/shared/typedshm/typedshm_wrapper:typedmemory", + "@score-baselibs//score/language/futurecpp", + ], +) + +cc_library( + name = "shared_memory_factory_mock", + testonly = True, + srcs = ["shared_memory_factory_mock.cpp"], + hdrs = ["shared_memory_factory_mock.h"], + features = COMPILER_WARNING_FEATURES, + visibility = [ + "//visibility:public", + # "@ddad//score/memory:__subpackages__", + ], + deps = [ + ":i_shared_memory_factory", + ":types", + "//score/memory/shared/typedshm/typedshm_wrapper:typedmemory", + "@googletest//:gtest", + ], +) + +cc_library( + name = "shared_memory_factory_impl", + srcs = ["shared_memory_factory_impl.cpp"], + hdrs = ["shared_memory_factory_impl.h"], + features = COMPILER_WARNING_FEATURES, + tags = ["FFI"], + deps = [ + ":i_shared_memory_factory", + ":shared_memory_resource", + ":types", + "//score/memory/shared/typedshm/typedshm_wrapper:typedmemory", + "//score/os:mman", + "//score/os:unistd", + "//score/os/utils/acl", + "//score/mw/log:frontend", + "@score-baselibs//score/language/futurecpp", + ], +) + +cc_library( + name = "shared_memory_factory", + srcs = ["shared_memory_factory.cpp"], + hdrs = ["shared_memory_factory.h"], + features = COMPILER_WARNING_FEATURES, + implementation_deps = [ + "shared_memory_factory_impl", + "//score/os:errno_logging", + "//score/os:unistd", + "//score/mw/log:frontend", + ], + tags = ["FFI"], + deps = [ + ":i_shared_memory_factory", + ":i_shared_memory_resource", + ":types", + "@score-baselibs//score/language/futurecpp", + ], +) + +cc_library( + name = "shared", + features = COMPILER_WARNING_FEATURES, + tags = ["FFI"], + visibility = [ + "//visibility:public", + ], + deps = [ + ":shared_memory_factory", + ":shared_memory_resource", + ], +) + +cc_library( + name = "lock_file", + srcs = ["lock_file.cpp"], + hdrs = ["lock_file.h"], + features = COMPILER_WARNING_FEATURES, + implementation_deps = [ + "//score/os:fcntl", + "//score/os:stat", + "//score/os:unistd", + ], + tags = ["FFI"], + visibility = [ + "//visibility:public", + # "@ddad//score/memory:__subpackages__", + ], + deps = [ + "@score-baselibs//score/language/futurecpp", + ], +) + +cc_library( + name = "managed_memory_resource", + srcs = ["managed_memory_resource.cpp"], + hdrs = ["managed_memory_resource.h"], + features = COMPILER_WARNING_FEATURES, + tags = ["FFI"], + visibility = [ + "//visibility:public", + # "@ddad//score/memory:__subpackages__", + ], + deps = ["@score-baselibs//score/language/futurecpp"], +) + +cc_library( + name = "memory_region_map", + srcs = ["memory_region_map.cpp"], + hdrs = ["memory_region_map.h"], + features = COMPILER_WARNING_FEATURES, + implementation_deps = [ + "//score/mw/log:frontend", + ], + tags = ["FFI"], + visibility = [ + "//visibility:public", + # "@ddad//score/memory:__subpackages__", + ], + deps = [ + ":atomic_indirector", + "@score-baselibs//score/language/futurecpp", + ], +) + +cc_library( + name = "memory_resource_registry", + srcs = ["memory_resource_registry.cpp"], + hdrs = ["memory_resource_registry.h"], + features = COMPILER_WARNING_FEATURES, + implementation_deps = [ + ":pointer_arithmetic_util", + ":shared_memory_error", + "//score/mw/log:frontend", + ], + tags = ["FFI"], + visibility = [ + "//visibility:public", + # "@ddad//score/memory:__subpackages__", + ], + deps = [ + ":managed_memory_resource", + ":memory_region_map", + "//score/result", + "@score-baselibs//score/language/futurecpp", + ], +) + +cc_library( + name = "memory_resource_proxy", + srcs = ["memory_resource_proxy.cpp"], + hdrs = ["memory_resource_proxy.h"], + features = COMPILER_WARNING_FEATURES, + implementation_deps = [ + ":memory_resource_registry", + ":pointer_arithmetic_util", + "//score/mw/log:frontend", + "@score-baselibs//score/language/futurecpp", + ], + tags = ["FFI"], + visibility = [ + "//visibility:public", + # "@ddad//score/memory:__subpackages__", + ], +) + +cc_library( + name = "offset_ptr", + srcs = [ + "offset_ptr.cpp", + ], + hdrs = [ + "offset_ptr.h", + ], + features = COMPILER_WARNING_FEATURES, + tags = ["FFI"], + visibility = [ + "//visibility:public", + # "@ddad//score/memory:__subpackages__", + ], + deps = [ + ":memory_resource_registry", + ":offset_ptr_bounds_check", + ":pointer_arithmetic_util", + "//score/language/safecpp/safe_math", + "@score-baselibs//score/language/futurecpp", + ], +) + +cc_library( + name = "offset_ptr_bounds_check", + srcs = [ + "offset_ptr_bounds_check.cpp", + ], + hdrs = [ + "offset_ptr_bounds_check.h", + ], + features = COMPILER_WARNING_FEATURES, + implementation_deps = [ + ":pointer_arithmetic_util", + "//score/mw/log:frontend", + ], + tags = ["FFI"], + visibility = [ + "//visibility:public", + # "@ddad//score/memory:__subpackages__", + ], + deps = [ + ":memory_resource_registry", + "@score-baselibs//score/language/futurecpp", + ], +) + +cc_library( + name = "polymorphic_offset_ptr_allocator", + srcs = ["polymorphic_offset_ptr_allocator.cpp"], + hdrs = ["polymorphic_offset_ptr_allocator.h"], + features = COMPILER_WARNING_FEATURES, + tags = ["FFI"], + visibility = [ + "//visibility:public", + # "@ddad//score/memory:__subpackages__", + ], + deps = [ + ":memory_resource_proxy", + ":offset_ptr", + "//score/language/safecpp/safe_math", + ], +) + +cc_library( + name = "map", + srcs = ["map.cpp"], + hdrs = ["map.h"], + features = COMPILER_WARNING_FEATURES, + tags = ["FFI"], + visibility = [ + "//visibility:public", + # "@ddad//score/memory:__subpackages__", + ], + deps = [ + ":polymorphic_offset_ptr_allocator", + ] + select({ + "@platforms//os:linux": [ + "//third_party/boost:container", + "//third_party/boost:interprocess", + ], + "@platforms//os:qnx": [], + }), +) + +cc_library( + name = "string", + srcs = ["string.cpp"], + hdrs = ["string.h"], + features = COMPILER_WARNING_FEATURES, + tags = ["FFI"], + visibility = [ + "//visibility:public", + # "@ddad//score/memory:__subpackages__", + ], + deps = [ + ":polymorphic_offset_ptr_allocator", + ], +) + +cc_library( + name = "vector", + srcs = ["vector.cpp"], + hdrs = ["vector.h"], + features = COMPILER_WARNING_FEATURES, + tags = ["FFI"], + visibility = [ + "//visibility:public", + # "@ddad//score/memory:__subpackages__", + ], + deps = [ + ":polymorphic_offset_ptr_allocator", + ], +) + +cc_library( + name = "types", + srcs = [ + ], + hdrs = [ + ], + features = COMPILER_WARNING_FEATURES, + tags = ["FFI"], + visibility = [ + "//visibility:public", + ], + deps = [ + ":managed_memory_resource", + ":map", + ":memory_region_map", + ":memory_resource_proxy", + ":memory_resource_registry", + ":offset_ptr", + ":polymorphic_offset_ptr_allocator", + ":shared_memory_error", + ":string", + ":vector", + ], +) + +cc_library( + name = "shared_memory_test_resources", + testonly = True, + srcs = [ + "shared_memory_test_resources.cpp", + ], + hdrs = [ + "shared_memory_test_resources.h", + ], + features = COMPILER_WARNING_FEATURES, + tags = ["FFI"], + visibility = ["//score/memory/shared:__subpackages__"], + deps = [ + ":shared", + "//score/memory/shared/sealedshm/sealedshm_wrapper:sealedshm_mock", + "//score/memory/shared/typedshm/typedshm_wrapper/test:typedmemory_mock", + "//score/os/mocklib:acl_mock", + "//score/os/mocklib:fcntl_mock", + "//score/os/mocklib:mman_mock", + "//score/os/mocklib:stat_mock", + "//score/os/mocklib:unistd_mock", + "@score-baselibs//score/language/futurecpp", + ], +) + +cc_library( + name = "user_permission", + hdrs = ["user_permission.h"], + features = COMPILER_WARNING_FEATURES, + tags = ["FFI"], + visibility = ["//score/memory/shared:__subpackages__"], + deps = ["//score/os/utils/acl"], +) + +cc_library( + name = "new_delete_delegate_resource", + srcs = ["new_delete_delegate_resource.cpp"], + hdrs = ["new_delete_delegate_resource.h"], + features = COMPILER_WARNING_FEATURES, + implementation_deps = [ + "//score/language/safecpp/safe_math", + "//score/mw/log", + ], + tags = ["FFI"], + visibility = [ + "//visibility:public", + ], + deps = [ + ":pointer_arithmetic_util", + ":shared", + ":types", + "@score-baselibs//score/language/futurecpp", + ], +) + +cc_library( + name = "shared_memory_error", + srcs = ["shared_memory_error.cpp"], + hdrs = ["shared_memory_error.h"], + features = COMPILER_WARNING_FEATURES, + tags = ["FFI"], + visibility = ["//score/memory/shared:__pkg__"], + deps = [ + "//score/result", + ], +) + +cc_library( + name = "fake_memory_resources", + testonly = True, + srcs = [ + "fake/my_bounded_memory_resource.cpp", + "fake/my_memory_resource.cpp", + ], + hdrs = [ + "fake/my_bounded_memory_resource.h", + "fake/my_memory_resource.h", + ], + features = COMPILER_WARNING_FEATURES, + visibility = [ + "//platform/aas/lib/containers:__pkg__", + "//score/memory/shared:__subpackages__", + "//platform/aas/mw/com/impl:__subpackages__", + # "@ddad//score/memory:__subpackages__", + ], + deps = [ + ":types", + "@score-baselibs//score/language/futurecpp", + ], +) + +cc_library( + name = "offset_ptr_test_resources", + testonly = True, + srcs = ["test_offset_ptr/offset_ptr_test_resources.cpp"], + hdrs = ["test_offset_ptr/offset_ptr_test_resources.h"], + features = COMPILER_WARNING_FEATURES, + visibility = ["//score/memory/shared:__pkg__"], + deps = [ + "//score/memory/shared", + "//score/memory/shared:fake_memory_resources", + "@googletest//:gtest_main", + "@score-baselibs//score/language/futurecpp", + ], +) + +cc_library( + name = "bounds_check_memory_pool", + testonly = True, + srcs = ["test_offset_ptr/bounds_check_memory_pool.cpp"], + hdrs = ["test_offset_ptr/bounds_check_memory_pool.h"], + features = COMPILER_WARNING_FEATURES, + deps = [ + ":offset_ptr_test_resources", + ":shared", + "@score-baselibs//score/language/futurecpp", + ], +) + +cc_unit_test( + name = "pointer_arithmetic_util_precondition_violation_test", + srcs = [ + "pointer_arithmetic_util_test.cpp", + ], + features = COMPILER_WARNING_FEATURES + [ + # These tests catch exceptions instead of using gtest EXPECT_DEATH so we disable aborts_upon_exception. + "-aborts_upon_exception", + ], + visibility = [ + "//score/memory:__pkg__", + # "@ddad//score/memory:__pkg__", + ], + deps = [ + ":pointer_arithmetic_util", + "//score/language/safecpp/coverage_termination_handler", # TODO: Remove when SubtractPointersBytes is no longer noexcept and we remove the last death test. + "//score/mw/log", + "@googletest//:gtest", + "@googletest//:gtest_main", + "@score-baselibs//score/language/futurecpp:futurecpp_test_support", + ], +) + +cc_gtest_unit_test( + name = "shared_memory_factory_test", + srcs = [ + "shared_memory_factory_test.cpp", + ], + features = COMPILER_WARNING_FEATURES, + visibility = [ + "//score/memory:__pkg__", + # "@ddad//score/memory:__pkg__", + ], + deps = [ + ":shared_memory_factory", + ":shared_memory_test_resources", + "//score/mw/log", + ], +) + +cc_gtest_unit_test( + name = "offset_ptr_test", + srcs = [ + "test_offset_ptr/assignment_test.cpp", + "test_offset_ptr/bool_ops_test.cpp", + "test_offset_ptr/compare_test.cpp", + "test_offset_ptr/construction_test.cpp", + "test_offset_ptr/index_dereference_test.cpp", + "test_offset_ptr/misc_test.cpp", + ], + features = COMPILER_WARNING_FEATURES, + visibility = [ + "//score/memory:__pkg__", + # "@ddad//score/memory:__pkg__", + ], + deps = [ + ":offset_ptr", + ":offset_ptr_test_resources", + "//score/mw/log", + ], +) + +cc_unit_test( + name = "offset_ptr_precondition_violation_test", + srcs = [ + "test_offset_ptr/arithmetic_test.cpp", + "test_offset_ptr/bounds_check_test.cpp", + "test_offset_ptr/copying_offset_ptr_bounds_check_test.cpp", + ], + features = COMPILER_WARNING_FEATURES + [ + # These tests catch exceptions instead of using gtest EXPECT_DEATH so we disable aborts_upon_exception. + "-aborts_upon_exception", + ], + visibility = [ + "//score/memory:__pkg__", + # "@ddad//score/memory:__pkg__", + ], + deps = [ + ":bounds_check_memory_pool", + ":offset_ptr", + "//score/language/safecpp/coverage_termination_handler", + "//score/mw/log", + "@googletest//:gtest", + "@googletest//:gtest_main", + "@score-baselibs//score/language/futurecpp:futurecpp_test_support", + ], +) + +cc_test( + name = "unit_test", + srcs = [ + "atomic_indirector_test.cpp", + "lock_file_test.cpp", + "managed_memory_resource_test.cpp", + "map_test.cpp", + "memory_region_map_test.cpp", + "memory_resource_proxy_test.cpp", + "memory_resource_registry_test.cpp", + "new_delete_delegate_resource_test.cpp", + "polymorphic_offset_ptr_allocator_test.cpp", + "shared_memory_error_test.cpp", + "shared_memory_resource_allocate_test.cpp", + "shared_memory_resource_create_anonymous_test.cpp", + "shared_memory_resource_create_or_open_test.cpp", + "shared_memory_resource_create_test.cpp", + "shared_memory_resource_misc_test.cpp", + "shared_memory_resource_open_test.cpp", + "vector_test.cpp", + ] + select({ + "@platforms//os:linux": ["string_test.cpp"], + "@platforms//os:qnx": [], + }), + features = COMPILER_WARNING_FEATURES + [ + "aborts_upon_exception", + ], + flaky = True, # TODO: Remove flakiness (Ticket-157184) + tags = ["unit"], + visibility = [ + "//score/memory:__pkg__", + # "@ddad//score/memory:__pkg__", + ], + deps = [ + ":atomic_indirector_mock_binding", + ":fake_memory_resources", + ":lock_file", + ":new_delete_delegate_resource", + ":offset_ptr_test_resources", + ":pointer_arithmetic_util", + ":shared", + ":shared_memory_test_resources", + "//score/language/safecpp/coverage_termination_handler", + "//score/os/utils/acl:mock", + "//score/mw/log", + "@googletest//:gtest", + "@googletest//:gtest_main", + "@score-baselibs//score/language/futurecpp", + ], +) diff --git a/score/memory/shared/atomic_indirector.cpp b/score/memory/shared/atomic_indirector.cpp new file mode 100644 index 000000000..b53d335ff --- /dev/null +++ b/score/memory/shared/atomic_indirector.cpp @@ -0,0 +1,13 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/memory/shared/atomic_indirector.h" diff --git a/score/memory/shared/atomic_indirector.h b/score/memory/shared/atomic_indirector.h new file mode 100644 index 000000000..4abdb733c --- /dev/null +++ b/score/memory/shared/atomic_indirector.h @@ -0,0 +1,178 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef BASELIBS_SCORE_MEMORY_SHARED_ATOMICINDIRECTOR_H +#define BASELIBS_SCORE_MEMORY_SHARED_ATOMICINDIRECTOR_H + +#include "score/memory/shared/i_atomic.h" + +#include +#include + +namespace score::memory::shared +{ + +/** + * \brief Helper class to allow mocking of atomic operations via static dispatch. + * + * We use atomic operations such as fetch_add, compare_exchange_weak/strong etc. in various points in our code + * base which produce different code paths based on the result of these operations. It is very difficult to test these + * code paths as they are conditional on whether the atomic value has updated at a very specific time which is difficult + * to achieve reliably in tests. Therefore, we want to be able to mock these calls in our tests. + * + * Since the atomic operations are often used in time-critical parts of the system, we don't want to use the common + * approach of calling operations on a virtual base class and choosing the real or mock implementation at runtime as the + * dynamic dispatch can add additional runtime overhead. Instead, we use a static dispatch approach so that the correct + * implementation (real or mock) is chosen at compile time. When the real implementation is chosen, there is no + * additional overhead as the abstraction is optimized away by the compiler. + * + * The downside of this approach is that classes which use this mechanism must be templated with the real or mock + * implementation class (i.e. AtomicIndirectorReal or AtomicIndirectorMock). To use this class, an existing class should + * be modified as follows (see platform/aas/mw/com/impl/bindings/lola/event_data_control.h for an example): + * + * template