From 5f1244a22ac4fc3399b03a7df4d925fe64088b5d Mon Sep 17 00:00:00 2001 From: Antony Peacock Date: Sat, 11 May 2024 10:29:00 +0100 Subject: [PATCH 1/4] Initial sanitizer find modules --- cmake/modules/FindASan.cmake | 37 +++++ cmake/modules/FindMSan.cmake | 29 ++++ cmake/modules/FindSanitizers.cmake | 0 cmake/modules/FindTSan.cmake | 29 ++++ cmake/modules/FindUBSan.cmake | 29 ++++ cmake/morpheus_check_compiler_flags.cmake | 148 ++++++++++++++++++ cmake/sanitisers.cmake | 11 +- .../src/morpheus/core/base/sanitizers.hpp | 2 +- 8 files changed, 281 insertions(+), 4 deletions(-) create mode 100644 cmake/modules/FindASan.cmake create mode 100644 cmake/modules/FindMSan.cmake create mode 100644 cmake/modules/FindSanitizers.cmake create mode 100644 cmake/modules/FindTSan.cmake create mode 100644 cmake/modules/FindUBSan.cmake create mode 100644 cmake/morpheus_check_compiler_flags.cmake diff --git a/cmake/modules/FindASan.cmake b/cmake/modules/FindASan.cmake new file mode 100644 index 00000000..3444f491 --- /dev/null +++ b/cmake/modules/FindASan.cmake @@ -0,0 +1,37 @@ +include_guard(GLOBAL) + +option(MORPHEUS_ADDRESS_SANITIZER "Enable AddressSanitizer for sanitized targets." OFF) + +if (NOT MORPHEUS_ADDRESS_SANITIZER) + return() +endif() + +if (MORPHEUS_ADDRESS_SANITIZER AND (MORPHEUS_MEMORY_SANITIZER OR MORPHEUS_THREAD_SANITIZER)) + message(FATAL_ERROR "morpheus: AddressSanitizer is not compatible with ThreadSanitizer or MemorySanitizer.") +endif () + +# For gcc and clang first attempt to sanitize address with full frame pointer information. +list(APPEND asanCandidateFlag "-g -fsanitize=address -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer") +list(APPEND asanCandidateFlag "-g -fsanitize=address -fno-omit-frame-pointer") +list(APPEND asanCandidateFlag "-g -fsanitize=address") +list(APPEND asanCandidateFlag "/fsanitize=address") # MSVC syntax + +include(morpheus_check_compiler_flags) +morpheus_check_compiler_flags( + NAME "Address Sanitizer" + PREFIX ASan + FLAGS ${asanCandidateFlag} + RESULT asanSupported +) + +find_package_handle_standard_args(ASan REQUIRED_VARS asanSupported) + +if (ASan_FOUND AND NOT TARGET ASan::ASan) + add_library(ASan INTERFACE) + add_library(morpheus::ASan ALIAS ASan) + target_compile_options(ASan + INTERFACE + + ) +endif() + diff --git a/cmake/modules/FindMSan.cmake b/cmake/modules/FindMSan.cmake new file mode 100644 index 00000000..ffe1ffb9 --- /dev/null +++ b/cmake/modules/FindMSan.cmake @@ -0,0 +1,29 @@ +include_guard(GLOBAL) + +option(MORPHEUS_MEMORY_SANITIZER "Enable MemorySanitizer for sanitized targets." OFF) + +if (NOT MORPHEUS_MEMORY_SANITIZER) + return() +endif() + +list(APPEND msanCandidateFlag "-g -fsanitize=memory") # Gcc & Clang syntax +list(APPEND msanCandidateFlag "/fsanitize=memory") # MSVC syntax + +include(morpheus_check_compiler_flags) +morpheus_check_compiler_flags( + NAME "Thread Sanitizer" + PREFIX MSan + FLAGS ${msanCandidateFlag} + RESULT msanSupported +) + +find_package_handle_standard_args(TSan REQUIRED_VARS msanSupported) + +if (MSan_FOUND AND NOT TARGET morpheus::MSan) + add_library(MSan INTERFACE) + add_library(morpheus::MSan ALIAS MSan) + target_compile_options(MSan + INTERFACE + #msanCandidateFlag + ) +endif() \ No newline at end of file diff --git a/cmake/modules/FindSanitizers.cmake b/cmake/modules/FindSanitizers.cmake new file mode 100644 index 00000000..e69de29b diff --git a/cmake/modules/FindTSan.cmake b/cmake/modules/FindTSan.cmake new file mode 100644 index 00000000..0e060707 --- /dev/null +++ b/cmake/modules/FindTSan.cmake @@ -0,0 +1,29 @@ +include_guard(GLOBAL) + +option(MORPHEUS_THREAD_SANITIZER "Enable ThreadSanitizer for sanitized targets." OFF) + +if (NOT MORPHEUS_THREAD_SANITIZER) + return() +endif() + +list(APPEND tsanCandidateFlag "-g -fsanitize=thread") # Gcc & Clang syntax +list(APPEND tsanCandidateFlag "/fsanitize=thread") # MSVC syntax + +include(morpheus_check_compiler_flags) +morpheus_check_compiler_flags( + NAME "Thread Sanitizer" + PREFIX TSan + FLAGS ${tsanCandidateFlag} + RESULT tsanSupported +) + +find_package_handle_standard_args(TSan REQUIRED_VARS tsanSupported) + +if (TSan_FOUND AND NOT TARGET morpheus::TSan) + add_library(TSan INTERFACE) + add_library(morpheus::TSan ALIAS TSan) + target_compile_options(TSan + INTERFACE + #tsanCandidateFlag + ) +endif() diff --git a/cmake/modules/FindUBSan.cmake b/cmake/modules/FindUBSan.cmake new file mode 100644 index 00000000..e919cae2 --- /dev/null +++ b/cmake/modules/FindUBSan.cmake @@ -0,0 +1,29 @@ +include_guard(GLOBAL) + +option(MORPHEUS_UNDEFINED_BEHAVIOUR_SANITIZER "Enable UndefineBehaviourSanitizer for sanitized targets." OFF) + +if (NOT MORPHEUS_UNDEFINED_BEHAVIOUR_SANITIZER) + return() +endif() + +list(APPEND ubsanCandidateFlag "-g -fsanitize=undefined") # Gcc & Clang syntax +list(APPEND ubsanCandidateFlag "/fsanitize=undefined") # MSVC syntax + +include(morpheus_check_compiler_flags) +morpheus_check_compiler_flags( + NAME "Undefined behavior Sanitizer" + PREFIX UBSan + FLAGS ${ubsanCandidateFlag} + RESULT ubsanSupported +) + +find_package_handle_standard_args(UBSan REQUIRED_VARS ubsanSupported) + +if (UBSan_FOUND AND NOT TARGET morpheus::UBSan) + add_library(UBSan INTERFACE) + add_library(morpheus::UBSan ALIAS UBSan) + target_compile_options(UBSan + INTERFACE + #ubsanCandidateFlag + ) +endif() diff --git a/cmake/morpheus_check_compiler_flags.cmake b/cmake/morpheus_check_compiler_flags.cmake new file mode 100644 index 00000000..4bf619cc --- /dev/null +++ b/cmake/morpheus_check_compiler_flags.cmake @@ -0,0 +1,148 @@ +include_guard(GLOBAL) + +cmake_minimum_required(VERSION 3.25) + +#[=======================================================================[.rst: +morpheus_check_compiler_flag +------------------ + +Overview +^^^^^^^^ + +Checks for the given compiler if the given flag is supported for the specified languages +if CMake provides a compiler check module for the underlying language. + +.. code-block:: cmake + + morpheus_check_compiler_flag( + [FLAG ] + [LANGUAGE ] + [RESULT ] + ) + -- Confirms support for the compile flag across enabled languages. + + ``FLAG`` + The ``FLAG`` compiler flag to test if is supported for the specified language. + + ``RESULT`` + The ``RESULT`` option is required to store the result of the function. TRUE if + there is compiler support for the feature. + +#]=======================================================================] +function(morpheus_check_compiler_flag) + set(options) + set(oneValueArgs FLAG LANGUAGE RESULT) + set(multiValueArgs) + cmake_parse_arguments(MORPHEUS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if (NOT MORPHEUS_FLAG) + message(FATAL_ERROR "FLAG parameter must be supplied") + endif() + if (NOT MORPHEUS_LANGUAGE) + message(FATAL_ERROR "LANGUAGE parameter must be supplied") + endif() + if (NOT MORPHEUS_RESULT) + message(FATAL_ERROR "RESULT parameter must be supplied") + endif() + + if (${MORPHEUS_LANGUAGE} STREQUAL "C") + include(CheckCCompilerFlag) + check_c_compiler_flag("${MORPHEUS_FLAG}" ${MORPHEUS_RESULT}) + elseif (${MORPHEUS_LANGUAGE} STREQUAL "CXX") + include(CheckCXXCompilerFlag) + check_cxx_compiler_flag("${MORPHEUS_FLAG}" ${MORPHEUS_RESULT}) + elseif (${MORPHEUS_LANGUAGE} STREQUAL "Fortran") + include(CheckFortranCompilerFlag) + check_fortran_compiler_flag("${MORPHEUS_FLAG}" ${MORPHEUS_RESULT}) + elseif (${MORPHEUS_LANGUAGE} STREQUAL "OBJC") + include(CheckOBJCCompilerFlag) + check_objc_compiler_flag("${MORPHEUS_FLAG}" ${MORPHEUS_RESULT}) + elseif (${MORPHEUS_LANGUAGE} STREQUAL "OBJCXX") + include(CheckOBJCXXCompilerFlag) + check_objcxx_compiler_flag("${MORPHEUS_FLAG}" ${MORPHEUS_RESULT}) + else() + set(${MORPHEUS_RESULT} FALSE) + return(PROPAGATE ${MORPHEUS_RESULT}) + endif() +endfunction() + + +#[=======================================================================[.rst: +morpheus_check_compiler_flags +------------------ + +Overview +^^^^^^^^ + +Checks for the given compile supported of the given flags across all languages in +use which have compiler check modules provides in CMake. + +.. code-block:: cmake + + morpheus_check_compiler_flags( + [QUIET] + [FLAGS ] + [RESULT ] + ) + -- Confirms support for compiler flags across enabled languages. + + ``RESULT`` + The ``RESULT`` option is required to store the results of the function. TRUE if + there is compiler support for the feature. + + ``FLAGS`` + The ``FLAGS`` compiler flags to test if are supported for the enabled languages. + +#]=======================================================================] +function(morpheus_check_compiler_flags) + set(options QUIET) + set(oneValueArgs NAME PREFIX RESULT) + set(multiValueArgs FLAGS) + cmake_parse_arguments(MORPHEUS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + if (NOT MORPHEUS_NAME) + message(FATAL_ERROR "NAME parameter must be supplied") + endif() + if (NOT MORPHEUS_PREFIX) + message(FATAL_ERROR "PREFIX parameter must be supplied") + endif() + if (NOT MORPHEUS_RESULT) + message(FATAL_ERROR "RESULT parameter must be supplied") + endif() + if (NOT MORPHEUS_FLAGS) + message(FATAL_ERROR "FLAGS parameter must be supplied") + endif() + + get_property(languages GLOBAL PROPERTY ENABLED_LANGUAGES) + + foreach(language IN LISTS languages) + + set(COMPILER ${CMAKE_${language}_COMPILER_ID}) + + foreach(flags IN LISTS MORPHEUS_FLAGS) + if (NOT MORPHEUS_QUIET) + message(STATUS "Try ${COMPILER} ${MORPHEUS_NAME} flag = [${flags}]") + endif() + + set(CMAKE_REQUIRED_FLAGS "${flags}") + unset(flagsSupported CACHE) + morpheus_check_compiler_flag(FLAG "${flags}" LANGUAGE ${language} RESULT flagsSupported) + + if (flagsSupported) + set(${MORPHEUS_PREFIX}_${COMPILER}_FLAGS "${flags}" CACHE STRING "${MORPHEUS_NAME} flags for ${COMPILER} compiler.") + mark_as_advanced(${MORPHEUS_PREFIX}_${COMPILER}_FLAGS) + break() + endif() + endforeach() + + if (NOT ${flagsSupported}) + set(${MORPHEUS_PREFIX}_${COMPILER}_FLAGS "" CACHE STRING "${MORPHEUS_NAME} flags for ${COMPILER} compiler.") + mark_as_advanced(${MORPHEUS_PREFIX}_${COMPILER}_FLAGS) + message(WARNING "${MORPHEUS_NAME} is not available for ${COMPILER} compiler. Targets using " + "this compiler will be compiled without ${MORPHEUS_NAME}.") + endif() + endforeach() + + set(${MORPHEUS_RESULT} FALSE) + return(PROPAGATE ${MORPHEUS_RESULT}) + +endfunction() diff --git a/cmake/sanitisers.cmake b/cmake/sanitisers.cmake index 7d3e0696..35e3f896 100644 --- a/cmake/sanitisers.cmake +++ b/cmake/sanitisers.cmake @@ -19,6 +19,7 @@ include_guard(GLOBAL) include(targets) +#[[ option(ENABLE_ADDRESS_SANITIZER "Enable AddressSanitizer for sanitized targets." OFF) option(ENABLE_MEMORY_SANITIZER "Enable MemorySanitizer for sanitized targets." OFF) option(ENABLE_THREAD_SANITIZER "Enable ThreadSanitizer for sanitized targets." OFF) @@ -27,7 +28,8 @@ option(ENABLE_UNDEFINED_BEHAVIOUR_SANITIZER "Enable UndefineBehaviourSanitizer f if(NOT (ENABLE_ADDRESS_SANITIZER OR ENABLE_MEMORY_SANITIZER OR ENABLE_THREAD_SANITIZER OR ENABLE_UNDEFINED_BEHAVIOUR_SANITIZER)) return() endif() - +]] +#[[ FetchContent_Declare( sanitizers GIT_REPOSITORY https://github.com/arsenm/sanitizers-cmake @@ -37,13 +39,16 @@ if(NOT sanitizers_POPULATED) FetchContent_Populate(sanitizers) list(APPEND CMAKE_MODULE_PATH ${sanitizers_SOURCE_DIR}/cmake) endif() +]] set(SANITIZE_ADDRESS ${ENABLE_ADDRESS_SANITIZER} CACHE BOOL "Enable AddressSanitizer for sanitized targets." FORCE) set(SANITIZE_MEMORY ${ENABLE_MEMORY_SANITIZER} CACHE BOOL "Enable MemorySanitizer for sanitized targets." FORCE) set(SANITIZE_THREAD ${ENABLE_THREAD_SANITIZER} CACHE BOOL "Enable ThreadSanitizer for sanitized targets." FORCE) set(SANITIZE_UNDEFINED ${ENABLE_UNDEFINED_BEHAVIOUR_SANITIZER} CACHE BOOL "Enable UndefinedBehaviorSanitizer for sanitized targets." FORCE) -find_package(Sanitizers) +#find_package(Sanitizers) + +find_package(ASan) targets_get_all(RESULT allTargets DIRECTORY ${PROJECT_SOURCE_DIR}) targets_filter_for_sources(RESULT targetsWithSource TARGETS ${allTargets}) -add_sanitizers("${targetsWithSource}") +#add_sanitizers("${targetsWithSource}") diff --git a/libraries/core/src/morpheus/core/base/sanitizers.hpp b/libraries/core/src/morpheus/core/base/sanitizers.hpp index 3a76558b..539a11dd 100644 --- a/libraries/core/src/morpheus/core/base/sanitizers.hpp +++ b/libraries/core/src/morpheus/core/base/sanitizers.hpp @@ -47,4 +47,4 @@ namespace mopheus #define MORPHEUS_NO_SANITIZE_ADDRESS #endif -} // namespace mopheus +} // namespace morpheus From eea64ae92a3313ec779347ab04cf6c044f88dfdf Mon Sep 17 00:00:00 2001 From: Antony Peacock Date: Sat, 11 May 2024 10:45:48 +0100 Subject: [PATCH 2/4] Add a simple ASAN test case --- cmake/modules/FindSanitizers.cmake | 1 + cmake/morpheus_check_compiler_flags.cmake | 3 +++ libraries/core/tests/cmake/CMakeLists.txt | 10 ++++++++++ libraries/core/tests/cmake/asan.tests.cpp | 18 ++++++++++++++++++ 4 files changed, 32 insertions(+) create mode 100644 libraries/core/tests/cmake/CMakeLists.txt create mode 100644 libraries/core/tests/cmake/asan.tests.cpp diff --git a/cmake/modules/FindSanitizers.cmake b/cmake/modules/FindSanitizers.cmake index e69de29b..8b137891 100644 --- a/cmake/modules/FindSanitizers.cmake +++ b/cmake/modules/FindSanitizers.cmake @@ -0,0 +1 @@ + diff --git a/cmake/morpheus_check_compiler_flags.cmake b/cmake/morpheus_check_compiler_flags.cmake index 4bf619cc..1962f369 100644 --- a/cmake/morpheus_check_compiler_flags.cmake +++ b/cmake/morpheus_check_compiler_flags.cmake @@ -117,6 +117,9 @@ function(morpheus_check_compiler_flags) foreach(language IN LISTS languages) set(COMPILER ${CMAKE_${language}_COMPILER_ID}) + if (${MORPHEUS_PREFIX}_${COMPILER}_FLAGS) + continue() + endif() foreach(flags IN LISTS MORPHEUS_FLAGS) if (NOT MORPHEUS_QUIET) diff --git a/libraries/core/tests/cmake/CMakeLists.txt b/libraries/core/tests/cmake/CMakeLists.txt new file mode 100644 index 00000000..c8ab07d5 --- /dev/null +++ b/libraries/core/tests/cmake/CMakeLists.txt @@ -0,0 +1,10 @@ +morpheus_add_tests( + NAME MorpheusAsanTests + FOLDER "Libraries/Core" +) + +find_package(ASan REQUIRED) +target_sources(MorpheusAsanTests + PUBLIC + asan.tests.cpp +) diff --git a/libraries/core/tests/cmake/asan.tests.cpp b/libraries/core/tests/cmake/asan.tests.cpp new file mode 100644 index 00000000..0b838cde --- /dev/null +++ b/libraries/core/tests/cmake/asan.tests.cpp @@ -0,0 +1,18 @@ +#include + +#include +#include + +namespace quantbox +{ + +TEST_CASE("Ensure the address sanitizer detects unsafe use after free memory usage", "[quantbox.cmake.asan]") +{ + auto storage = std::make_unique(1); + std::reference_wrapper dangling(*storage); + storage.release(); + REQUIRE(!storage); + dangling.get() = 0; // If asan works this is an error! +} + +} // quantbox From 88f97b37c3ccdae9cd5dda65a552303726a8127a Mon Sep 17 00:00:00 2001 From: Antony Peacock Date: Wed, 26 Jun 2024 14:13:56 +0100 Subject: [PATCH 3/4] Add concept for standard container requirements (#314) * Add concept for standard container requirements * Archtypes for container and allocator aware types, with tests. * std::vector sepcialisation of get_allocator is not noexcept * Support sequence containers * Remove duplication * Include ranges * Correct namespace * Update archtype for from_range * Range methods fix * Patch up the sequence concept as best we can * Contiguous concept with tests * Concept for reversible container requirements and partial concepts for associativ and unordered * Complete associative concept * Try compound requirement for visual studio * Complete unorder container concepts * Check for all basic string instantiations --- .../core/src/morpheus/core/CMakeLists.txt | 3 +- .../morpheus/core/containers/CMakeLists.txt | 1 + .../core/containers/concepts/CMakeLists.txt | 12 ++ .../containers/concepts/allocator_aware.hpp | 29 +++ .../core/containers/concepts/associative.hpp | 66 +++++++ .../core/containers/concepts/container.hpp | 34 ++++ .../core/containers/concepts/contiguous.hpp | 21 +++ .../containers/concepts/detail/CMakeLists.txt | 4 + .../concepts/detail/return_types.hpp | 33 ++++ .../core/containers/concepts/reversible.hpp | 28 +++ .../core/containers/concepts/sequence.hpp | 67 +++++++ .../core/containers/concepts/unordered.hpp | 85 +++++++++ .../morpheus/core/functional/CMakeLists.txt | 2 + .../core/functional/concepts/CMakeLists.txt | 5 + .../core/functional/concepts/function.hpp | 19 ++ .../core/functional/concepts/hash.hpp | 21 +++ .../core/memory/concepts/allocator.hpp | 22 ++- .../core/memory/concepts/nullable_pointer.hpp | 7 +- .../src/morpheus/core/meta/CMakeLists.txt | 1 + .../core/src/morpheus/core/meta/is_array.hpp | 15 ++ .../core/testing/morpheus/CMakeLists.txt | 1 + .../morpheus/containers/CMakeLists.txt | 1 + .../containers/concepts/CMakeLists.txt | 1 + .../concepts/archtypes/CMakeLists.txt | 9 + .../concepts/archtypes/allocator_aware.hpp | 32 ++++ .../concepts/archtypes/associative.hpp | 148 +++++++++++++++ .../concepts/archtypes/container.hpp | 48 +++++ .../concepts/archtypes/reversible.hpp | 29 +++ .../concepts/archtypes/sequence.hpp | 55 ++++++ .../concepts/archtypes/unordered.hpp | 171 ++++++++++++++++++ libraries/core/tests/CMakeLists.txt | 1 + .../core/tests/containers/CMakeLists.txt | 1 + .../tests/containers/concepts/CMakeLists.txt | 10 + .../concepts/allocator_aware.tests.cpp | 38 ++++ .../containers/concepts/associative.tests.cpp | 45 +++++ .../containers/concepts/container.tests.cpp | 44 +++++ .../containers/concepts/contiguous.tests.cpp | 51 ++++++ .../containers/concepts/reversible.tests.cpp | 43 +++++ .../containers/concepts/sequence.tests.cpp | 42 +++++ .../containers/concepts/unordered.tests.cpp | 45 +++++ libraries/core/tests/meta/CMakeLists.txt | 1 + libraries/core/tests/meta/is_array.tests.cpp | 39 ++++ 42 files changed, 1318 insertions(+), 12 deletions(-) create mode 100644 libraries/core/src/morpheus/core/containers/CMakeLists.txt create mode 100644 libraries/core/src/morpheus/core/containers/concepts/CMakeLists.txt create mode 100644 libraries/core/src/morpheus/core/containers/concepts/allocator_aware.hpp create mode 100644 libraries/core/src/morpheus/core/containers/concepts/associative.hpp create mode 100644 libraries/core/src/morpheus/core/containers/concepts/container.hpp create mode 100644 libraries/core/src/morpheus/core/containers/concepts/contiguous.hpp create mode 100644 libraries/core/src/morpheus/core/containers/concepts/detail/CMakeLists.txt create mode 100644 libraries/core/src/morpheus/core/containers/concepts/detail/return_types.hpp create mode 100644 libraries/core/src/morpheus/core/containers/concepts/reversible.hpp create mode 100644 libraries/core/src/morpheus/core/containers/concepts/sequence.hpp create mode 100644 libraries/core/src/morpheus/core/containers/concepts/unordered.hpp create mode 100644 libraries/core/src/morpheus/core/functional/concepts/CMakeLists.txt create mode 100644 libraries/core/src/morpheus/core/functional/concepts/function.hpp create mode 100644 libraries/core/src/morpheus/core/functional/concepts/hash.hpp create mode 100644 libraries/core/src/morpheus/core/meta/is_array.hpp create mode 100644 libraries/core/testing/morpheus/containers/CMakeLists.txt create mode 100644 libraries/core/testing/morpheus/containers/concepts/CMakeLists.txt create mode 100644 libraries/core/testing/morpheus/containers/concepts/archtypes/CMakeLists.txt create mode 100644 libraries/core/testing/morpheus/containers/concepts/archtypes/allocator_aware.hpp create mode 100644 libraries/core/testing/morpheus/containers/concepts/archtypes/associative.hpp create mode 100644 libraries/core/testing/morpheus/containers/concepts/archtypes/container.hpp create mode 100644 libraries/core/testing/morpheus/containers/concepts/archtypes/reversible.hpp create mode 100644 libraries/core/testing/morpheus/containers/concepts/archtypes/sequence.hpp create mode 100644 libraries/core/testing/morpheus/containers/concepts/archtypes/unordered.hpp create mode 100644 libraries/core/tests/containers/CMakeLists.txt create mode 100644 libraries/core/tests/containers/concepts/CMakeLists.txt create mode 100644 libraries/core/tests/containers/concepts/allocator_aware.tests.cpp create mode 100644 libraries/core/tests/containers/concepts/associative.tests.cpp create mode 100644 libraries/core/tests/containers/concepts/container.tests.cpp create mode 100644 libraries/core/tests/containers/concepts/contiguous.tests.cpp create mode 100644 libraries/core/tests/containers/concepts/reversible.tests.cpp create mode 100644 libraries/core/tests/containers/concepts/sequence.tests.cpp create mode 100644 libraries/core/tests/containers/concepts/unordered.tests.cpp create mode 100644 libraries/core/tests/meta/is_array.tests.cpp diff --git a/libraries/core/src/morpheus/core/CMakeLists.txt b/libraries/core/src/morpheus/core/CMakeLists.txt index 5ad041c1..b231c883 100644 --- a/libraries/core/src/morpheus/core/CMakeLists.txt +++ b/libraries/core/src/morpheus/core/CMakeLists.txt @@ -10,8 +10,9 @@ target_sources(MorpheusCore ) add_subdirectory(base) -add_subdirectory(conformance) add_subdirectory(concurrency) +add_subdirectory(conformance) +add_subdirectory(containers) add_subdirectory(functional) add_subdirectory(memory) add_subdirectory(meta) diff --git a/libraries/core/src/morpheus/core/containers/CMakeLists.txt b/libraries/core/src/morpheus/core/containers/CMakeLists.txt new file mode 100644 index 00000000..e632a43f --- /dev/null +++ b/libraries/core/src/morpheus/core/containers/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(concepts) \ No newline at end of file diff --git a/libraries/core/src/morpheus/core/containers/concepts/CMakeLists.txt b/libraries/core/src/morpheus/core/containers/concepts/CMakeLists.txt new file mode 100644 index 00000000..06e8a108 --- /dev/null +++ b/libraries/core/src/morpheus/core/containers/concepts/CMakeLists.txt @@ -0,0 +1,12 @@ +add_subdirectory(detail) + +target_sources(MorpheusCore + PUBLIC + allocator_aware.hpp + associative.hpp + container.hpp + contiguous.hpp + reversible.hpp + sequence.hpp + unordered.hpp +) diff --git a/libraries/core/src/morpheus/core/containers/concepts/allocator_aware.hpp b/libraries/core/src/morpheus/core/containers/concepts/allocator_aware.hpp new file mode 100644 index 00000000..6b1c6adc --- /dev/null +++ b/libraries/core/src/morpheus/core/containers/concepts/allocator_aware.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include "morpheus/core/containers/concepts/container.hpp" + +#include +#include + +namespace morpheus::containers::concepts +{ + +/// \concept AllocatorAware +/// Concept capturing the requirements for an allocator aware container as outline in the standard at +/// [container.alloc.reqmts], details at +/// AllocatorAwareContainer. +template +concept AllocatorAware = Container && requires(X x, X&& rx, typename X::allocator_type a){ + requires std::same_as; + requires std::default_initializable; + { X(a) }; + { X(std::as_const(x), a) }; + { X(std::move(rx)) }; + { X(std::move(rx), a) }; + { std::as_const(x).get_allocator() } -> std::same_as; + { x = x } -> std::same_as; + { x = rx } -> std::same_as; + { x.swap(x) } -> std::same_as; +}; + +} // namespace morpheus::containers::concepts diff --git a/libraries/core/src/morpheus/core/containers/concepts/associative.hpp b/libraries/core/src/morpheus/core/containers/concepts/associative.hpp new file mode 100644 index 00000000..58df3dbb --- /dev/null +++ b/libraries/core/src/morpheus/core/containers/concepts/associative.hpp @@ -0,0 +1,66 @@ +#pragma once + +#include "morpheus/core/containers/concepts/detail/return_types.hpp" +#include "morpheus/core/containers/concepts/allocator_aware.hpp" + +#include +#include + +namespace morpheus::containers::concepts +{ + +/// \concept Associative +/// Concept capturing the requirements for an associative container as outline in the standard at +/// [associative.reqmts], details at +/// AssociativeContainer. +template +concept Associative = AllocatorAware && requires(T t, typename T::value_type v, typename T::key_type k, typename T::size_type s, typename T::iterator i, + typename T::const_iterator ci, typename T::key_compare c, typename T::node_type n, + std::initializer_list il) +{ + requires (std::default_initializable) and (std::copy_constructible); + { T(c) }; + { T(i, i, c) }; + { T(i, i) }; +#if (__cpp_lib_containers_ranges >= 202202L) + { T(std::from_range, ranges::subrange{}, c) }; + { T(std::from_range, ranges::subrange{}) }; +#endif // (__cpp_lib_containers_ranges >= 202202L) + { T(il, c) }; + { T(il) }; + { t = il }; + { std::as_const(t).key_comp() } -> std::same_as; + { std::as_const(t).value_comp() } -> std::same_as; + { t.emplace() } -> detail::InsertReturnType; + { t.emplace_hint(i) } -> std::same_as; + { t.insert(v) } -> detail::InsertReturnType; + { t.insert(ci, v) } -> std::same_as; + { t.insert(i, i) } -> std::same_as; +#if (__cpp_lib_containers_ranges >= 202202L) + { t.insert_range(ranges::subrange{}) } -> std::same_as; +#endif // (__cpp_lib_containers_ranges >= 202202L) + { t.insert(il) } -> std::same_as; + { t.insert(std::move(n)) } -> detail::InsertNodeHandleReturnType; + { t.insert(ci, std::move(n)) } -> std::same_as; + { t.extract(k) } -> std::same_as; + { t.extract(ci) } -> std::same_as; + { t.merge(t) } -> std::same_as; + { t.erase(i) } -> std::same_as; + { t.erase(ci) } -> std::same_as; + { t.erase(i, i) } -> std::same_as; + { t.erase(ci, ci) } -> std::same_as; + { t.erase(k) } -> std::same_as; + { t.clear() } -> std::same_as; + { t.find(k) } -> std::same_as; + { std::as_const(t).find(k) } -> std::same_as; + { std::as_const(t).count(k) } -> std::same_as; + { std::as_const(t).contains(k) } -> std::same_as; + { t.lower_bound(k) } -> detail::BoundReturnType; + { std::as_const(t).lower_bound(k) } -> detail::BoundConstReturnType; + { t.equal_range(k) } -> detail::BoundReturnType; + { std::as_const(t).equal_range(k) } -> detail::BoundConstReturnType; + { t.upper_bound(k) } -> detail::BoundReturnType; + { std::as_const(t).upper_bound(k) } -> detail::BoundConstReturnType; +}; + +} // namespace morpheus::containers::concepts diff --git a/libraries/core/src/morpheus/core/containers/concepts/container.hpp b/libraries/core/src/morpheus/core/containers/concepts/container.hpp new file mode 100644 index 00000000..f8b1c062 --- /dev/null +++ b/libraries/core/src/morpheus/core/containers/concepts/container.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include +#include +#include + +namespace morpheus::containers::concepts +{ + +/// \concept Container +/// Concept capturing the requirements for a container as outline in the standard at +/// [container.requirements], details at +/// Container. +template +concept Container = requires(C c) { + typename C::value_type; + typename C::reference; + typename C::const_reference; + requires std::forward_iterator; + requires std::forward_iterator; + requires std::signed_integral; + requires std::unsigned_integral; + requires std::convertible_to; + requires std::regular; + { begin(c) } -> std::same_as; + { end(c) } -> std::same_as; + { cbegin(std::as_const(c)) } -> std::same_as; + { cend(std::as_const(c)) } -> std::same_as; + requires (not std::bidirectional_iterator) or requires {{ size(c) } -> std::same_as; }; + { c.max_size() } -> std::same_as; + { empty(c) } -> std::same_as; +}; + +} // namespace morpheus::containers::concepts \ No newline at end of file diff --git a/libraries/core/src/morpheus/core/containers/concepts/contiguous.hpp b/libraries/core/src/morpheus/core/containers/concepts/contiguous.hpp new file mode 100644 index 00000000..8689d46c --- /dev/null +++ b/libraries/core/src/morpheus/core/containers/concepts/contiguous.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include "morpheus/core/containers/concepts/sequence.hpp" + +#include +#include + +namespace morpheus::containers::concepts +{ + +/// \concept Contiguous +/// Concept capturing the requirements for a contiguous container, a named requirement outlined in the standard at +/// [container.requirements.general], details at +/// ContiguousContainer. +template +concept Contiguous = StrictSequence && requires(T t){ + requires std::contiguous_iterator; + requires std::contiguous_iterator; +}; + +} // namespace morpheus::containers::concepts diff --git a/libraries/core/src/morpheus/core/containers/concepts/detail/CMakeLists.txt b/libraries/core/src/morpheus/core/containers/concepts/detail/CMakeLists.txt new file mode 100644 index 00000000..0a2cb302 --- /dev/null +++ b/libraries/core/src/morpheus/core/containers/concepts/detail/CMakeLists.txt @@ -0,0 +1,4 @@ +target_sources(MorpheusCore + PUBLIC + return_types.hpp +) diff --git a/libraries/core/src/morpheus/core/containers/concepts/detail/return_types.hpp b/libraries/core/src/morpheus/core/containers/concepts/detail/return_types.hpp new file mode 100644 index 00000000..e522553d --- /dev/null +++ b/libraries/core/src/morpheus/core/containers/concepts/detail/return_types.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include +#include + +namespace morpheus::containers::concepts::detail +{ + +template +concept InsertReturnType = requires +{ + requires std::same_as or std::same_as>; +}; + +template +concept InsertNodeHandleReturnType = requires +{ + requires requires { std::same_as; } or requires { std::same_as; }; +}; + +template +concept BoundReturnType = requires +{ + requires std::same_as or std::same_as>; +}; + +template +concept BoundConstReturnType = requires +{ + requires std::same_as or std::same_as>; +}; + +} // namespace morpheus::containers::concepts \ No newline at end of file diff --git a/libraries/core/src/morpheus/core/containers/concepts/reversible.hpp b/libraries/core/src/morpheus/core/containers/concepts/reversible.hpp new file mode 100644 index 00000000..9759644d --- /dev/null +++ b/libraries/core/src/morpheus/core/containers/concepts/reversible.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include "morpheus/core/containers/concepts/container.hpp" + +#include +#include +#include + +namespace morpheus::containers::concepts +{ + +/// \concept Reversible +/// Concept capturing the requirements for a reversible container, a named requirement outlined in the standard at +/// [container.rev.reqmts], details at +/// ReversibleContainer. +template +concept Reversible = Container && requires(T t){ + typename T::reverse_iterator; + typename T::const_reverse_iterator; + requires std::same_as>; + requires std::same_as>; + { rbegin(t) } -> std::same_as; + { rend(t) } -> std::same_as; + { crbegin(std::as_const(t)) } -> std::same_as; + { crend(std::as_const(t)) } -> std::same_as; +}; + +} // namespace morpheus::containers::concepts diff --git a/libraries/core/src/morpheus/core/containers/concepts/sequence.hpp b/libraries/core/src/morpheus/core/containers/concepts/sequence.hpp new file mode 100644 index 00000000..329cf5ae --- /dev/null +++ b/libraries/core/src/morpheus/core/containers/concepts/sequence.hpp @@ -0,0 +1,67 @@ +#pragma once + +#include "morpheus/core/containers/concepts/container.hpp" +#include "morpheus/core/conformance/ranges.hpp" +#include "morpheus/core/meta/is_array.hpp" +#include "morpheus/core/meta/is_string.hpp" + +#include +#include +#include +#include +#include + +namespace morpheus::containers::concepts +{ + +/// \concept Sequence +/// Concept capturing the requirements for a sequence container as outline in the standard at +/// [sequence.requirements], details at +/// SequenceContainer. +template +concept Sequence = Container && requires(T t, typename T::value_type v, typename T::size_type s, typename T::iterator i, std::initializer_list il){ + { T(s, v) }; + { T(i, i) }; +#if (__cpp_lib_containers_ranges >= 202202L) + { T(std::from_range, ranges::subrange{}) }; +#endif // (__cpp_lib_containers_ranges >= 202202L) + { T(il) }; + { t = il }; + { t.insert(i, v) } -> std::same_as; + { t.insert(i, std::move(v)) } -> std::same_as; + { t.insert(i, s, v) } -> std::same_as; + { t.insert(i, i, i) } -> std::same_as; +#if (__cpp_lib_containers_ranges >= 202202L) + { t.insert_range(i, ranges::subrange{}) } -> std::same_as; +#endif // (__cpp_lib_containers_ranges >= 202202L) + { t.insert(i, il) } -> std::same_as; + { t.erase(i) } -> std::same_as; + { t.erase(i, i) } -> std::same_as; + { t.clear() } -> std::same_as; + { t.assign(i, i) } -> std::same_as; +#if (__cpp_lib_containers_ranges >= 202202L) + { t.assign_range(ranges::subrange{}) } -> std::same_as; +#endif // (__cpp_lib_containers_ranges >= 202202L) + { t.assign(il) } -> std::same_as; + { t.assign(s, v) } -> std::same_as; +}; + +/// \concept StrictSequence +/// Concept strictly capturing the requirements for a sequence container as outline in the standard at +/// [sequence.requirements], details at +/// SequenceContainer. +/// The requirements [sequence.requirements] don't +/// actually match numerous types listed as sequence containers. This includes: +/// - std::string +/// - std::forward_list +/// - std::array +/// Hoever they are listed as as Sequence types while having interfaces that differ from the Sequence +/// container requirements so this extension concept exactly mirrors that requirement. +template +concept StrictSequence = meta::is_array_v || + meta::is_string_v || + std::same_as> || + Sequence; + + +} // namespace morpheus::containers::concepts diff --git a/libraries/core/src/morpheus/core/containers/concepts/unordered.hpp b/libraries/core/src/morpheus/core/containers/concepts/unordered.hpp new file mode 100644 index 00000000..10e790f8 --- /dev/null +++ b/libraries/core/src/morpheus/core/containers/concepts/unordered.hpp @@ -0,0 +1,85 @@ +#pragma once + +#include "morpheus/core/containers/concepts/detail/return_types.hpp" +#include "morpheus/core/containers/concepts/allocator_aware.hpp" +#include "morpheus/core/functional/concepts/hash.hpp" + +#include +#include + +namespace morpheus::containers::concepts +{ + +/// \concept Unordered +/// Concept capturing the requirements for an unordered associative container as outline in the standard at +/// [unord.req], details at +/// UnorderedAssociativeContainerr. +template +concept Unordered = AllocatorAware && requires(T t, typename T::value_type v, typename T::key_type k, typename T::size_type s, typename T::iterator i, + typename T::const_iterator ci, typename T::hasher h, typename T::key_equal e, typename T::local_iterator l, + typename T::node_type n, std::initializer_list il) +{ + requires functional::concepts::Hash; + requires (std::default_initializable) && (std::copy_constructible); + { T(s, h, e) }; + { T(s, h) }; + { T(s) }; + { T(i, i, s, h, e) }; + { T(i, i, s, h) }; + { T(i, i, s) }; + { T(i, i) }; +#if (__cpp_lib_containers_ranges >= 202202L) + { T(std::from_range, ranges::subrange{}, s, h, e) }; + { T(std::from_range, ranges::subrange{}, s, h) }; + { T(std::from_range, ranges::subrange{}, s) }; + { T(std::from_range, ranges::subrange{}) }; +#endif // (__cpp_lib_containers_ranges >= 202202L) + { T(il, s, h, e) }; + { T(il, s, h) }; + { T(il, s) }; + { T(il) }; + { t = il }; + { std::as_const(t).hash_function() } -> std::same_as; + { std::as_const(t).key_eq() } -> std::same_as; + { t.emplace() } -> detail::InsertReturnType; + { t.emplace_hint(i) } -> std::same_as; + { t.insert(v) } -> detail::InsertReturnType; + { t.insert(ci, v) } -> std::same_as; + { t.insert(i, i) } -> std::same_as; +#if (__cpp_lib_containers_ranges >= 202202L) + { t.insert_range(ranges::subrange{}) } -> std::same_as; +#endif // (__cpp_lib_containers_ranges >= 202202L) + { t.insert(il) } -> std::same_as; + { t.insert(std::move(n)) } -> detail::InsertNodeHandleReturnType; + { t.insert(ci, std::move(n)) } -> std::same_as; + { t.extract(k) } -> std::same_as; + { t.extract(ci) } -> std::same_as; + { t.merge(t) } -> std::same_as; + { t.erase(i) } -> std::same_as; + { t.erase(ci) } -> std::same_as; + { t.erase(i, i) } -> std::same_as; + { t.erase(ci, ci) } -> std::same_as; + { t.erase(k) } -> std::same_as; + { t.clear() } -> std::same_as; + { t.find(k) } -> std::same_as; + { std::as_const(t).find(k) } -> std::same_as; + { std::as_const(t).count(k) } -> std::same_as; + { std::as_const(t).contains(k) } -> std::same_as; + { t.equal_range(k) } -> detail::BoundReturnType; + { std::as_const(t).equal_range(k) } -> detail::BoundConstReturnType; + { std::as_const(t).bucket_count() } -> std::same_as; + { std::as_const(t).max_bucket_count() } -> std::same_as; + { std::as_const(t).bucket(s) } -> std::same_as; + { std::as_const(t).bucket_size(s) } -> std::same_as; + { t.begin(s) } -> std::same_as; + { std::as_const(t).begin(s) } -> std::same_as; + { t.end(s) } -> std::same_as; + { std::as_const(t).end(s) } -> std::same_as; + { std::as_const(t).load_factor() } -> std::same_as; + { std::as_const(t).max_load_factor() } -> std::same_as; + { t.max_load_factor(float{}) } -> std::same_as; + { t.rehash(s) } -> std::same_as; + { t.reserve(s) } -> std::same_as; +}; + +} // namespace morpheus::containers::concepts diff --git a/libraries/core/src/morpheus/core/functional/CMakeLists.txt b/libraries/core/src/morpheus/core/functional/CMakeLists.txt index 60a3a78e..e97113ec 100644 --- a/libraries/core/src/morpheus/core/functional/CMakeLists.txt +++ b/libraries/core/src/morpheus/core/functional/CMakeLists.txt @@ -1,3 +1,5 @@ +add_subdirectory(concepts) + target_sources(MorpheusCore PUBLIC function_ref.hpp diff --git a/libraries/core/src/morpheus/core/functional/concepts/CMakeLists.txt b/libraries/core/src/morpheus/core/functional/concepts/CMakeLists.txt new file mode 100644 index 00000000..b80bb2ab --- /dev/null +++ b/libraries/core/src/morpheus/core/functional/concepts/CMakeLists.txt @@ -0,0 +1,5 @@ +target_sources(MorpheusCore + PUBLIC + function.hpp + hash.hpp +) diff --git a/libraries/core/src/morpheus/core/functional/concepts/function.hpp b/libraries/core/src/morpheus/core/functional/concepts/function.hpp new file mode 100644 index 00000000..3acd29f3 --- /dev/null +++ b/libraries/core/src/morpheus/core/functional/concepts/function.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include +#include + +namespace morpheus::functional::concepts +{ + +/// \concept Function +/// Concept capturing the requirements for a function object, a named requirement outlined in the standard at +/// [func.require], details at +/// FunctionObject. +template +concept Function = requires(T t){ + requires std::is_object_v; + requires std::invocable; +}; + +} // namespace morpheus::functional::concepts diff --git a/libraries/core/src/morpheus/core/functional/concepts/hash.hpp b/libraries/core/src/morpheus/core/functional/concepts/hash.hpp new file mode 100644 index 00000000..ac43f2e4 --- /dev/null +++ b/libraries/core/src/morpheus/core/functional/concepts/hash.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include "morpheus/core/functional/concepts/function.hpp" + +#include + +namespace morpheus::functional::concepts +{ + +/// \concept Hash +/// Concept capturing the requirements for a hash, a named requirement outlined in the standard at +/// [hash.requirements], details at +/// Hash. +template +concept Hash = requires(T t){ + requires std::copy_constructible; + requires std::destructible; + requires Function; +}; + +} // namespace morpheus::functional::concepts diff --git a/libraries/core/src/morpheus/core/memory/concepts/allocator.hpp b/libraries/core/src/morpheus/core/memory/concepts/allocator.hpp index 67e22548..a744bb17 100644 --- a/libraries/core/src/morpheus/core/memory/concepts/allocator.hpp +++ b/libraries/core/src/morpheus/core/memory/concepts/allocator.hpp @@ -16,22 +16,30 @@ namespace morpheus::memory::concepts /// \concept Allocator /// Concept capturing the requirements for an allocator as outline in the standard at /// [allocator.requirements]. - +/// \par +/// Allocators provide a number of services: +/// - Perform allocation, construction, destruction and deallocation. +/// - Seperate allocation from construction, +/// - Seperate deallocation from destruction. +/// - Encapsulate information about the allocation strategy. +/// - Encapsulate information about the addressing model. +/// - Hide memory management and addressing model details from users. +/// - Support the reuse of an allocation strategy across allocation users. /// \note /// https://rawgit.com/google/cxx-std-draft/allocator-paper/allocator_user_guide.html#reqs template