diff --git a/src/openvic-simulation/core/Assert.hpp b/src/openvic-simulation/core/Assert.hpp new file mode 100644 index 00000000..0abda13d --- /dev/null +++ b/src/openvic-simulation/core/Assert.hpp @@ -0,0 +1,113 @@ +#pragma once + +#include // IWYU pragma: keep for use of cross-library reference for standard library macro definitions + +/// Adds macros for memory hardening asserts: +// OV_HARDEN_ASSERT_VALID_RANGE_MESSAGE(BEGIN, END, MSG) +// OV_HARDEN_ASSERT_NONEMPTY_RANGE(BEGIN, END, FUNC_NAME) - requires FUNC_NAME to be string literal +// OV_HARDEN_ASSERT_ACCESS(INDEX, FUNC_NAME) - requires `this->size()` is a valid call and FUNC_NAME to be string literal +// OV_HARDEN_ASSERT_NONEMPTY(FUNC_NAME) - requires `!this->empty()` is a valid call and FUNC_NAME to be string literal +// OV_HARDEN_ASSERT_VALID_ITERATOR(IT, FUNC_NAME) - requires FUNC_NAME to be string literal +// +// Adds macros for aborting/throwing for strict container errors: +// OV_THROW_OUT_OF_RANGE(CLASS_NAME, FUNC_NAME, VAR_NAME, VAR, SIZE) +// OV_THROW_LENGTH_ERROR(CLASS_NAME, FUNC_NAME) - requires CLASS_NAME and FUNC_NAME to be string literals + +#ifdef __GLIBCXX__ +#include + +#define OV_HARDEN_ASSERT_VALID_RANGE_MESSAGE(BEGIN, END, MSG) __glibcxx_assert(BEGIN <= END) +#define OV_HARDEN_ASSERT_NONEMPTY_RANGE(BEGIN, END, FUNC_NAME) __glibcxx_requires_non_empty_range(BEGIN, END) + +#define OV_HARDEN_ASSERT_ACCESS(INDEX, FUNC_NAME) __glibcxx_requires_subscript(INDEX) +#define OV_HARDEN_ASSERT_NONEMPTY(FUNC_NAME) __glibcxx_requires_nonempty() +#define OV_HARDEN_ASSERT_VALID_ITERATOR(IT, FUNC_NAME) \ + OV_HARDEN_ASSERT_VALID_RANGE_MESSAGE(IT, end(), FUNC_NAME " called with a non-dereferenceable iterator") + +#elif defined(_LIBCPP_VERSION) +#include <__assert> + +#define OV_HARDEN_ASSERT_VALID_RANGE_MESSAGE(BEGIN, END, MSG) _LIBCPP_ASSERT_VALID_INPUT_RANGE(BEGIN <= END, MSG) +#define OV_HARDEN_ASSERT_NONEMPTY_RANGE(BEGIN, END, FUNC_NAME) \ + OV_HARDEN_ASSERT_VALID_RANGE_MESSAGE(BEGIN, END, FUNC_NAME " called with an invalid range") + +#define OV_HARDEN_ASSERT_ACCESS(INDEX, FUNC_NAME) \ + _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(INDEX < size(), FUNC_NAME " index out of bounds") +#define OV_HARDEN_ASSERT_NONEMPTY(FUNC_NAME) \ + _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(!empty(), FUNC_NAME " called on an empty container") +#define OV_HARDEN_ASSERT_VALID_ITERATOR(IT, FUNC_NAME) \ + _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(IT != end(), FUNC_NAME " called with a non-dereferenceable iterator") + +#elif defined(_MSVC_STL_VERSION) && _MSVC_STL_HARDENING == 1 +#include + +#define OV_HARDEN_ASSERT_VALID_RANGE_MESSAGE(BEGIN, END, MSG) _STL_VERIFY(BEGIN <= END, MSG) +#define OV_HARDEN_ASSERT_NONEMPTY_RANGE(BEGIN, END, FUNC_NAME) \ + OV_HARDEN_ASSERT_VALID_RANGE_MESSAGE(BEGIN, END, FUNC_NAME " called with an invalid range") + +#define OV_HARDEN_ASSERT_ACCESS(INDEX, FUNC_NAME) _STL_VERIFY(INDEX < size(), FUNC_NAME " index out of bounds") +#define OV_HARDEN_ASSERT_NONEMPTY(FUNC_NAME) _STL_VERIFY(!empty(), FUNC_NAME " called on an empty container") +#define OV_HARDEN_ASSERT_VALID_ITERATOR(IT, FUNC_NAME) \ + OV_HARDEN_ASSERT_VALID_RANGE_MESSAGE(IT, end(), FUNC_NAME " called with a non-dereferenceable iterator") + +#else +#define OV_HARDEN_ASSERT_VALID_RANGE_MESSAGE(BEGIN, END, MSG) +#define OV_HARDEN_ASSERT_NONEMPTY_RANGE(BEGIN, END, FUNC_NAME) +#define OV_HARDEN_ASSERT_ACCESS(INDEX, FUNC_NAME) +#define OV_HARDEN_ASSERT_NONEMPTY(FUNC_NAME) +#define OV_HARDEN_ASSERT_VALID_ITERATOR(IT, FUNC_NAME) + +// clang-format off +# if defined(_GLIBCXX_ASSERTIONS) || _LIBCPP_HARDENING_MODE == _LIBCPP_HARDENING_MODE_FAST || _MSVC_STL_HARDENING == 1 +#warning "Unsupported standard library for memory hardening, hardening asserts will be ignored." +# endif +// clang-format on + +#endif + +#ifdef __GLIBCXX__ +#if __has_include() +#include +#elif __has_include() +#include +#else +#include +#warning "Unknown GLIBCXX library version" +#define OV_THROW_OUT_OF_RANGE(CLASS_NAME, FUNC_NAME, VAR_NAME, VAR, SIZE) std::abort() +#define OV_THROW_LENGTH_ERROR(CLASS_NAME, FUNC_NAME) std::abort() +#endif + +#ifndef OV_THROW_OUT_OF_RANGE +#define OV_THROW_OUT_OF_RANGE(CLASS_NAME, FUNC_NAME, VAR_NAME, VAR, SIZE) \ + std::__throw_out_of_range_fmt( \ + __N("%s::%s: %s (which is %zu) >= this->size() (which is %zu)"), CLASS_NAME, FUNC_NAME, VAR_NAME, VAR, SIZE \ + ) +#endif + +#ifndef OV_THROW_LENGTH_ERROR +#define OV_THROW_LENGTH_ERROR(CLASS_NAME, FUNC_NAME) std::__throw_length_error(__N(CLASS_NAME "::" FUNC_NAME)) +#endif + +#elif defined(_LIBCPP_VERSION) && __has_include() +#include + +#define OV_THROW_OUT_OF_RANGE(CLASS_NAME, FUNC_NAME, VAR_NAME, VAR, SIZE) std::__throw_out_of_range(CLASS_NAME) +#define OV_THROW_LENGTH_ERROR(CLASS_NAME, FUNC_NAME) std::__throw_length_error(CLASS_NAME) + +#elif defined(_MSVC_STL_VERSION) && __has_include() +#include + +#define OV_THROW_OUT_OF_RANGE(CLASS_NAME, FUNC_NAME, VAR_NAME, VAR, SIZE) \ + std::_Xout_of_range("invalid " CLASS_NAME " subscript") +#define OV_THROW_LENGTH_ERROR(CLASS_NAME, FUNC_NAME) std::_Xlength_error(CLASS_NAME " too long") +#endif + +#ifndef OV_THROW_OUT_OF_RANGE +#include +#define OV_THROW_OUT_OF_RANGE(CLASS_NAME, FUNC_NAME, VAR_NAME, VAR, SIZE) std::abort() +#endif + +#ifndef OV_THROW_LENGTH_ERROR +#include +#define OV_THROW_LENGTH_ERROR(CLASS_NAME, FUNC_NAME) std::abort() +#endif diff --git a/src/openvic-simulation/types/Colour.hpp b/src/openvic-simulation/types/Colour.hpp index 587bcc27..acfb2605 100644 --- a/src/openvic-simulation/types/Colour.hpp +++ b/src/openvic-simulation/types/Colour.hpp @@ -871,10 +871,10 @@ struct fmt::formatter { size_t i = 0; for (char& c : lower) { c = std::tolower(result[i]); - if (c == '\0') { + ++i; + if (i == result.size()) { break; } - ++i; } return detail::write(out, string_view { lower.data(), i }, specs, ctx.locale()); } diff --git a/src/openvic-simulation/types/CowVector.hpp b/src/openvic-simulation/types/CowVector.hpp index 86dbf85c..22b7abb3 100644 --- a/src/openvic-simulation/types/CowVector.hpp +++ b/src/openvic-simulation/types/CowVector.hpp @@ -11,6 +11,7 @@ #include #include +#include "openvic-simulation/core/Assert.hpp" #include "openvic-simulation/types/BasicIterator.hpp" #include "openvic-simulation/utility/Allocator.hpp" #include "openvic-simulation/core/Compare.hpp" @@ -39,6 +40,10 @@ namespace OpenVic { OV_ALWAYS_INLINE cow_vector(allocate_tag_t, Allocator const& alloc, size_t reserve) : alloc(alloc), _data(_allocate_payload(reserve)) {} + [[noreturn]] void _abort_on_out_of_range(const char* func_name, const char* var_name, size_t var, size_t size) const { + OV_THROW_OUT_OF_RANGE("cow_vector", func_name, var_name, var, size); + } + public: using value_type = T; using allocator_type = Allocator; @@ -166,19 +171,24 @@ namespace OpenVic { } const_reference at(size_type pos) const { - // TODO: crash on boundary violation - return (*this)[pos]; + if (OV_unlikely(pos >= size())) { + _abort_on_out_of_range("at", "pos", pos, size()); + } + return _data->array[pos]; } const_reference operator[](size_type pos) const { + OV_HARDEN_ASSERT_ACCESS(pos, "operator[]"); return _data->array[pos]; } const_reference front() const { + OV_HARDEN_ASSERT_NONEMPTY("front"); return *_data->array; } const_reference back() const { + OV_HARDEN_ASSERT_NONEMPTY("back"); return *_data->array_end; } @@ -329,9 +339,8 @@ namespace OpenVic { template inline difference_type _validate_iterator_difference(InputIt first, InputIt last) { - difference_type result = last - first; - // TODO: crash on negative result - return result; + OV_HARDEN_ASSERT_VALID_RANGE_MESSAGE(first, last, "_validate_iterator_difference called with invalid range"); + return last - first; } struct for_overwrite_t {}; @@ -367,19 +376,24 @@ namespace OpenVic { } reference at(size_type pos) { - return (*this)[pos]; + if (OV_unlikely(pos >= size())) { + _abort_on_out_of_range("at", "pos", pos, size()); + } + return _data->array[pos]; } reference operator[](size_type pos) { - // TODO: crash on boundary violation + OV_HARDEN_ASSERT_ACCESS(pos, "operator[]"); return _data->array[pos]; } reference front() { + OV_HARDEN_ASSERT_NONEMPTY("front"); return *_data->array; } reference back() { + OV_HARDEN_ASSERT_NONEMPTY("back"); return *_data->array_end; } @@ -404,8 +418,8 @@ namespace OpenVic { } void reserve(size_type count) { - if (count > max_size()) { - // TODO: crash + if (OV_unlikely(count > max_size())) { + OV_THROW_LENGTH_ERROR("cow_vector::writer", "reserve"); } if (capacity() >= count) { return; @@ -510,6 +524,7 @@ namespace OpenVic { } iterator erase(const_iterator pos) { + OV_HARDEN_ASSERT_VALID_ITERATOR(pos, "erase(const_iterator)"); if (pos + 1 != end()) { std::move(pos + 1, end(), pos); } @@ -561,7 +576,7 @@ namespace OpenVic { } void pop_back() { - // TODO: assert if empty + OV_HARDEN_ASSERT_NONEMPTY("pop_back"); --_data->array_end; allocator_traits::destroy(alloc, _data->array_end); } @@ -1081,6 +1096,10 @@ namespace OpenVic { _range_insert(end(), first, last, std::iterator_traits::iterator_category()); } } + + [[noreturn]] void _abort_on_out_of_range(const char* func_name, const char* var_name, size_t var, size_t size) const { + OV_THROW_OUT_OF_RANGE("cow_vector::writer", func_name, var_name, var, size); + } }; template diff --git a/src/openvic-simulation/types/FixedVector.hpp b/src/openvic-simulation/types/FixedVector.hpp index 9f9a7070..ce6a97bb 100644 --- a/src/openvic-simulation/types/FixedVector.hpp +++ b/src/openvic-simulation/types/FixedVector.hpp @@ -6,6 +6,7 @@ #include #include +#include "openvic-simulation/core/Assert.hpp" #include "openvic-simulation/core/template/Concepts.hpp" #include "openvic-simulation/core/Typedefs.hpp" @@ -138,28 +139,28 @@ namespace OpenVic::_detail { const_reverse_iterator crend() const { return const_reverse_iterator(begin()); } T& operator[](const size_t index) { - assert(index < _size); + OV_HARDEN_ASSERT_ACCESS(index, "operator[]"); return _data_start_ptr[index]; } const T& operator[](const size_t index) const { - assert(index < _size); + OV_HARDEN_ASSERT_ACCESS(index, "operator[]"); return _data_start_ptr[index]; } T& front() { - assert(!empty()); + OV_HARDEN_ASSERT_NONEMPTY("front"); return *begin(); } const T& front() const { - assert(!empty()); + OV_HARDEN_ASSERT_NONEMPTY("front"); return *cbegin(); } T& back() { - assert(!empty()); + OV_HARDEN_ASSERT_NONEMPTY("back"); return *(end()-1); } const T& back() const { - assert(!empty()); + OV_HARDEN_ASSERT_NONEMPTY("back"); return *(cend()-1); } diff --git a/src/openvic-simulation/types/RingBuffer.hpp b/src/openvic-simulation/types/RingBuffer.hpp index 94b43bf1..731395e8 100644 --- a/src/openvic-simulation/types/RingBuffer.hpp +++ b/src/openvic-simulation/types/RingBuffer.hpp @@ -18,6 +18,7 @@ #include #include +#include "openvic-simulation/core/Assert.hpp" #include "openvic-simulation/core/Typedefs.hpp" namespace OpenVic { @@ -215,34 +216,49 @@ namespace OpenVic { allocator_type get_allocator() const { return _allocator; } + + private: + reference _unsafe_access(const size_type index) { + return _data[_ring_wrap(_offset + index, capacity())]; + } + const_reference _unsafe_access(const size_type index) const { + return _data[_ring_wrap(_offset + index, capacity())]; + } + + public: reference front() { - return at(0); + OV_HARDEN_ASSERT_NONEMPTY("front"); + return _unsafe_access(0); } reference back() { - return at(size() - 1); + OV_HARDEN_ASSERT_NONEMPTY("back"); + return _unsafe_access(size() - 1); } const_reference back() const { - return at(size() - 1); + OV_HARDEN_ASSERT_NONEMPTY("back"); + return _unsafe_access(size() - 1); } const_reference operator[](const size_type index) const { - return _data[_ring_wrap(_offset + index, capacity())]; + OV_HARDEN_ASSERT_ACCESS(index, "operator[]"); + return _unsafe_access(index); } reference operator[](const size_type index) { - return _data[_ring_wrap(_offset + index, capacity())]; + OV_HARDEN_ASSERT_ACCESS(index, "operator[]"); + return _unsafe_access(index); } const_reference at(const size_type index) const { if (OV_unlikely(index >= size())) { - std::abort(); + _abort_on_out_of_range("at", "index", index, size()); } - return (*this)[index]; + return _unsafe_access(index); } reference at(const size_type index) { if (OV_unlikely(index >= size())) { - std::abort(); + _abort_on_out_of_range("at", "index", index, size()); } - return (*this)[index]; + return _unsafe_access(index); } iterator begin() noexcept { @@ -693,6 +709,7 @@ namespace OpenVic { return erase(pos, last); } iterator erase(const_iterator pos) noexcept(noexcept(erase(pos, 1))) { + OV_HARDEN_ASSERT_VALID_ITERATOR(pos, "erase(const_iterator)"); return erase(pos, 1); } @@ -764,6 +781,10 @@ namespace OpenVic { return (ring_index <= ring_capacity) ? ring_index : ring_index - ring_capacity - 1; } + [[noreturn]] void _abort_on_out_of_range(const char* func_name, const char* var_name, size_t var, size_t size) const { + OV_THROW_OUT_OF_RANGE("RingBuffer", func_name, var_name, var, size); + } + // The start of the dynamically allocated backing array. pointer _data = nullptr; // The next position to write to for push_back(). diff --git a/src/openvic-simulation/types/StackString.hpp b/src/openvic-simulation/types/StackString.hpp index 3cfa4a36..d2c73aec 100644 --- a/src/openvic-simulation/types/StackString.hpp +++ b/src/openvic-simulation/types/StackString.hpp @@ -5,6 +5,7 @@ #include #include +#include "openvic-simulation/core/Assert.hpp" #include "openvic-simulation/utility/Containers.hpp" namespace OpenVic { @@ -40,6 +41,7 @@ namespace OpenVic { } constexpr decltype(_array)::const_reference operator[](size_t index) const { + OV_HARDEN_ASSERT_ACCESS(index, "operator[]"); return _array[index]; }