- Stacktrace generation with failed asserts

This commit is contained in:
2025-07-05 14:22:59 -04:00
parent 0afaae72ac
commit a33bf5206f
22 changed files with 529 additions and 191 deletions

View File

@@ -57,7 +57,7 @@ struct array
///
/// \copydetails array::at(size_t) const
constexpr ValueT& at(size_t i) { static_assert(i < ElemV); assert(i < ElemV); return elements[i]; }
constexpr ValueT& at(size_t i) { static_assert(i < ElemV); assert(i < ElemV, "Array Out of Bounds"); return elements[i]; }
///
/// \brief access specified element, **with bounds checking**
@@ -70,7 +70,7 @@ struct array
///
/// \par Space-Complexity
/// Constant
constexpr const ValueT& at(size_t i) const { static_assert(i < ElemV); assert(i < ElemV); return elements[i]; }
constexpr const ValueT& at(size_t i) const { static_assert(i < ElemV); assert(i < ElemV, "Array Out of Bounds"); return elements[i]; }

View File

@@ -0,0 +1,38 @@
// =====================================================================================================================
// fennec, a free and open source game engine
// Copyright © 2025 Medusa Slockbower
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// =====================================================================================================================
#ifndef FENNEC_FPROC_STRINGS_DETAIL_CTYPE_H
#define FENNEC_FPROC_STRINGS_DETAIL_CTYPE_H
#if _MSC_VER
#pragma warning(push)
#pragma warning(disable:4117)
#define __PTRDIFF_TYPE__ ptrdiff_t
#endif
#pragma push_macro("__cplusplus")
#undef __cplusplus
#include <ctype.h>
#pragma pop_macro("__cplusplus")
#if _MSC_VER
#pragma warning(pop)
#endif
#endif // FENNEC_FPROC_STRINGS_DETAIL_CTYPE_H

View File

@@ -0,0 +1,297 @@
// =====================================================================================================================
// fennec, a free and open source game engine
// Copyright © 2025 Medusa Slockbower
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// =====================================================================================================================
#ifndef FENNEC_FPROC_STRINGS_STRING_H
#define FENNEC_FPROC_STRINGS_STRING_H
#include <fennec/fproc/strings/detail/__ctype.h>
#include <fennec/lang/assert.h>
#include <fennec/memory/allocator.h>
#include <fennec/memory/memory.h>
namespace fennec
{
// TODO: Document
using ::isalnum;
using ::isalpha;
using ::islower;
using ::isupper;
using ::isdigit;
using ::isxdigit;
using ::iscntrl;
using ::isgraph;
using ::isspace;
using ::isblank;
using ::isprint;
using ::ispunct;
using ::tolower;
using ::toupper;
///
/// \brief Struct for wrapping c-style strings
///
/// \details fennec will use this class over `const char*` for memory safety.
/// behaviour guarantees that the underlying string is null-terminated
template<typename AllocT = allocator<char>>
struct string
{
public:
using alloc_t = allocation<char, AllocT>;
///
/// \brief Default Constructor, initializes empty string
constexpr string()
: _str()
{ }
///
/// \brief Sized Constructor, initializes a null-terminated string of size `n` with null characters
/// \param n the number of characters
///
/// \details adds additional character for null termination.
constexpr string(size_t n)
: string('\0', n)
{ }
///
/// \brief Sized Constructor, initializes a null-terminated string of size `n` filled with the character `c`
/// \param c the character to fill with
/// \param n the number of characters
///
/// \details adds additional character for null termination.
constexpr string(char c, size_t n)
: _str(n + 1)
{ fennec::memset(_str, c, n); _str[n] = '\0'; }
///
/// \brief Buffer Copy Constructor
/// \param str the buffer to copy
/// \param len number of characters in the buffer
///
/// \details adds additional character for null termination. Ignores whether str is null-terminated.
/// This constructor makes the assumption that `len` is the intended number of characters.
constexpr string(const char* str, size_t len)
: _str(str, len + 1)
{ _str[len] = '\0'; }
///
/// \brief Array Copy Constructor
/// \tparam N the number of characters
/// \param str the array to copy
///
/// \details if str is not null-terminated, adds additional character for null-termination.
template<size_t N>
constexpr string(const char str[N])
{
if (str[N - 1] == '\0') string(str, N - 1);
else string(str, N);
}
///
/// \brief String Copy Constructor
/// \param str the string to copy
constexpr string(const string& str)
: string(str, str.size() - 1)
{ }
constexpr string(string&& str) noexcept
: _str(fennec::move(str._str))
{ }
///
/// \brief String Destructor, cleans up the underlying allocation
constexpr ~string() = default; // allocation cleans up itself
// Properties ==========================================================================================================
///
/// \returns The size of the string excluding null terminator
constexpr size_t size() const { return _str.capacity() - 1; }
// Access ==============================================================================================================
///
/// \brief Array Access Operator
/// \param i the index to access
/// \returns a reference to the character
constexpr char& operator[](int i) {
assert(i >= 0 && i < size(), "Array Out of Bounds");
return _str[i];
}
///
/// \brief Const-Array Access Operator
/// \param i the index to access
/// \returns a copy of the character
constexpr char operator[](int i) const {
assert(i >= 0 && i < size(), "Array Out of Bounds");
return _str[i];
}
///
/// \brief Dereference Operator
/// \returns A const qualified pointer to the underlying allocation
constexpr const char* operator*() const { return _str; }
///
/// \brief Implicit Dereference Cast
constexpr operator const char*() const { return _str; }
// Examination =========================================================================================================
///
/// \returns The length of the string to the first null-terminator
constexpr size_t length() const
{ return find('\0'); }
///
/// \brief String Comparison
/// \param ostr the string to compare against
/// \returns Zero if both strings are equal, otherwise a negative value if lhs appears before rhs according to the
/// current locale, otherwise a positive value.
constexpr int compare(const string& ostr) const
{ return ::strcoll(_str, ostr); }
///
/// \brief Finds the index of the first occurrence of `x` in the string
/// \param x the character to find
/// \returns The index of `x` if it occurs in the string, otherwise returns `size()`
constexpr size_t find(char x) const
{
const char* loc = ::strchr(_str, x);
return loc ? loc - _str : size();
}
///
/// \brief Finds the index of the first occurrence of `str` in the string.
/// \param str the string to find
/// \returns The index of `str` if it occurs in the string, otherwise returns `size()`
constexpr size_t find(const string& str) const
{
const char* loc = ::strstr(_str, str);
return loc ? loc - _str : size();
}
///
/// \brief Finds the index of the last occurrence of `x` in the string.
/// \param x the string to find
/// \returns The index of `x` if it occurs in the string, otherwise returns `size()`
constexpr size_t rfind(char x) const
{
const char* loc = ::strrchr(_str, x);
return loc ? loc - _str : size();
}
// TODO: constexpr size_t rfind(const string& str) const;
// Manipulation ========================================================================================================
///
/// \brief Resize the string, filling additional bytes with `'\0'`
/// \param n the new size of the string
constexpr void resize(size_t n)
{
size_t i = size();
_str.reallocate(n + 1);
if (n > i) fennec::memset(*_str + i, '\0', n - i);
}
///
/// \brief Copy Assignment Operator
/// \param str the string to copy
/// \returns a reference to `this`
constexpr string& operator=(const string& str)
{
if (str.size() > size()) string::resize(str.size());
fennec::memcpy(_str, str, str.size());
_str[str.size()] = '\0';
return *this;
}
///
/// \brief Move Assignment Operator
/// \param str the string to move
/// \returns a reference to `this`
constexpr string& operator=(string&& str) noexcept
{
_str = fennec::move(str._str);
return *this;
}
///
/// \brief Retrieve a substring of a string
/// \param i the start index
/// \param n the number of characters
/// \return
constexpr string substring(size_t i, size_t n = size())
{
assert(i < size(), "Array Out of Bounds");
n = min(n, size() - i);
return string(&_str[i], n);
}
///
/// \brief Returns a string with `c` appended to it
/// \param c
/// \returns
constexpr string operator+(char c)
{
// Copy contents with one additional byte.
string res(_str, _str.size());
res[size()] = c; // Set the last character to c
return res;
}
constexpr string operator+(const string& str)
{
string res(size() + str.size()); // Make a new string with the size of this + str
fennec::memcpy(res, _str, size()); // Copy the contents of this
fennec::memcpy(&res[size()], str, str.size()); // Append the contents of str
return res;
}
constexpr string& operator+=(char c)
{
size_t x = size();
resize(x + 1);
_str[x] = c;
return *this;
}
constexpr string& operator+=(const string& str)
{
size_t x = size();
string::resize(x + str.size());
fennec::memcpy(&_str[x], str, str.size());
return *this;
}
private:
alloc_t _str;
};
}
#endif // FENNEC_FPROC_STRINGS_STRING_H

View File

@@ -25,8 +25,9 @@
using assert_handler = void (*)(const char *, const char *, int , const char *);
extern void __assert_impl(const char* expression, const char* file, int line, const char* function);
extern void __assert_impl(const char* expression, const char* file, int line, const char* function, const char* desc);
#define assert(expression) if(not(expression)) { __assert_impl(#expression, __FILE__, __LINE__, __PRETTY_FUNCTION__); }
#define assert(expression, description) \
if(not(expression)) { __assert_impl(#expression, __FILE__, __LINE__, __PRETTY_FUNCTION__, description); }
#endif // FENNEC_LANG_ASSERT_H

View File

@@ -26,9 +26,9 @@
#pragma push_macro("__cplusplus")
#undef __cplusplus
extern "C" {
extern "C" {
#include <stdlib.h>
}
}
#pragma pop_macro("__cplusplus")
#if _MSC_VER

View File

@@ -153,17 +153,6 @@
/// \copybrief fennec::int64_t
///
///
///// <tr><td width="50%" style="vertical-align: top"> <br>
///// <tt>\ref fennec::float32_t "float32_t"</tt>
///// <td width="50%" style="vertical-align: top">
///// \copybrief fennec::float32_t
/////
///// <tr><td width="50%" style="vertical-align: top"> <br>
///// <tt>\ref fennec::float64_t "float64_t"</tt>
///// <td width="50%" style="vertical-align: top">
///// \copybrief fennec::float64_t
///
///
/// <tr><th colspan=2 style="text-align: center;">Special Types
/// <tr><td width="50%" style="vertical-align: top"> <br>
/// <tt>\ref fennec::nullptr_t "nullptr_t"</tt>

View File

@@ -324,7 +324,7 @@ struct matrix
///
/// \copydetails matrix::operator[](size_t) const
constexpr column_t& operator[](size_t i)
{ assert(i < columns); return data[i]; }
{ assert(i < columns, "Array Out of Bounds"); return data[i]; }
///
/// \brief returns the column at index \f$i\f$
@@ -333,20 +333,20 @@ struct matrix
/// \param i the index
/// \returns the column at index \f$i\f$
constexpr const column_t& operator[](size_t i) const
{ assert(i < columns); return data[i]; }
{ assert(i < columns, "Array Out of Bounds"); return data[i]; }
///
/// \copydetails matrix::operator()(size_t, size_t) const
constexpr scalar_t& operator()(size_t i, size_t j)
{ assert(i < columns && j < rows); return data[i][j]; }
constexpr scalar_t& operator[](size_t i, size_t j)
{ assert(i < columns && j < rows, "Array Out of Bounds"); return data[i][j]; }
///
/// \brief returns the cell in row \p j column \p i
/// \param i the column
/// \param j the row
/// \returns the cell at the specified index.
constexpr scalar_t operator()(size_t i, size_t j) const
{ assert(i < columns && j < rows); return data[i][j]; }
constexpr scalar_t operator[](size_t i, size_t j) const
{ assert(i < columns && j < rows, "Array Out of Bounds"); return data[i][j]; }
/// @}

View File

@@ -710,7 +710,7 @@ struct vector : detail::vector_base_type<ScalarT, sizeof...(IndicesV)>
/// \brief unary boolean not operator
///
/// \details
/// \param v the vector
/// \param x the vector
/// \returns A \ref fennec_math_vector "vector" \a v such that, \f$v_i=!x_i\f$
constexpr friend vector_t operator!(const vector_t& x)
{ return vector_t(!x[IndicesV] ...); }

View File

@@ -22,6 +22,7 @@
#include <fennec/memory/ptr_traits.h>
#include <fennec/lang/conditional_types.h>
#include <fennec/lang/numeric_transforms.h>
#include <fennec/lang/types.h>
#include <fennec/lang/type_traits.h>
#include <fennec/math/common.h>
@@ -64,7 +65,7 @@ private:
struct __diff<AllocT, PtrT, void_t<typename AllocT::diff_t>> { using type = typename AllocT::diff_t; };
template<typename AllocT, typename DiffT, typename = void>
struct __size : std::make_unsigned<DiffT> {};
struct __size : make_unsigned<DiffT> {};
template<typename AllocT, typename DiffT>
struct __size<AllocT, DiffT, void_t<typename AllocT::size_t>> { using type = typename AllocT::size_t; };
@@ -188,7 +189,17 @@ public:
/// \brief Sized Constructor, initializes the allocation with a block of size `n * sizeof(T)` bytes
/// \param n The number of elements of type `T` to allocate for
constexpr allocation(size_t n) noexcept
: _data(_alloc.al), _capacity(n) {}
: _data(nullptr), _capacity(0)
{ allocate(n); }
///
/// \brief Buffer Copy Constructor, initializes the allocation with a block of size `n * sizeof(T)` bytes.
/// Then, the contents of data are copied into the allocation.
/// \param data the buffer to copy
/// \param n the number of elements
constexpr allocation(const T* data, size_t n)
: allocation(n)
{ fennec::memcpy(_data, data, n); }
///
/// \brief Allocator Constructor
@@ -205,7 +216,20 @@ public:
///
/// \details This constructor should be used when the type `AllocT` needs internal data.
constexpr allocation(size_t n, const alloc_t& alloc) noexcept
: _alloc(alloc), _data(nullptr), _capacity(0) {}
: _alloc(alloc), _data(nullptr), _capacity(0)
{ allocate(n); }
///
/// \brief Buffer Copy Allocator Constructor, initializes the allocation with a block of size `n * sizeof(T)` bytes.
/// Then, the contents of data are copied into the allocation.
/// \param data the buffer to copy
/// \param n the number of elements
/// \param alloc The allocation object to copy.
///
/// \details This constructor should be used when the type `AllocT` needs internal data.
constexpr allocation(const T* data, size_t n, const alloc_t& alloc)
: allocation(n, alloc)
{ fennec::memcpy(_data, data, n); }
///
/// \brief Copy Constructor, creates an allocation of equal size and performs a byte-wise copy
@@ -227,6 +251,35 @@ public:
/// \brief Default Destructor, releases the memory block if still present
constexpr ~allocation() noexcept { if (_data) _alloc.deallocate(_data); }
///
/// \brief Copy Assignment Operator
/// \param alloc the allocation to copy
/// \returns a reference to `this`
constexpr allocation& operator=(const allocation& alloc)
{
allocation::allocate(alloc.capacity());
fennec::memcpy(_data, alloc, size());
return *this;
}
///
/// \brief Move Assignment Operator
/// \param alloc the allocation to copy
/// \returns a reference to `this`
constexpr allocation& operator=(allocation&& alloc) noexcept
{
// Copy contents
_alloc = alloc._alloc;
_data = alloc._data;
_capacity = alloc._capacity;
// Cleanup alloc
alloc._data = nullptr;
alloc._capacity = 0;
return *this;
}
///
/// \brief Allocate a block of memory for the allocation.
/// If there is already an allocated block of memory, the previous allocation is released.
@@ -236,7 +289,7 @@ public:
if (_data)
_alloc.deallocate(_data);
_data = alloc_t::allocate(_capacity = n);
_data = _alloc.allocate(_capacity = n);
}
///
@@ -245,6 +298,7 @@ public:
{
if (_data)
_alloc.deallocate(_data);
_data = nullptr;
_capacity = 0;
}
@@ -258,7 +312,7 @@ public:
return _alloc.allocate(_capacity = n);
value_t* old = _data;
_data = alloc_t::allocate(n);
_data = _alloc.allocate(n);
fennec::memcpy(_data, old, min(_capacity, n) * sizeof(T));
_alloc.deallocate(old);
_capacity = n;

View File

@@ -21,46 +21,32 @@
#include <fennec/lang/types.h>
namespace fennec
{
namespace detail
{
constexpr size_t __memcpy_8(void* dst, const void* src)
{ *static_cast<uint8_t*>(dst) = *static_cast<const uint8_t*>(src); return 1; }
// deprecated, switched to ISO C implementation
// see https://git.mslockbo.org/mslockbo/fennec/src/commit/0eeb7ae3cff9d78e98dc5d9fc09bcb98b10986b9 for previous
// implementation
// helper for copying 2 bytes at once
constexpr size_t __memcpy_16(void* dst, const void* src)
{ *static_cast<uint16_t*>(dst) = *static_cast<const uint16_t*>(src); return 2; }
#if _MSC_VER
#pragma warning(push)
#pragma warning(disable:4117)
#endif
// helper for copying 4 bytes at once
constexpr size_t __memcpy_32(void* dst, const void* src)
{ *static_cast<uint32_t*>(dst) = *static_cast<const uint32_t*>(src); return 4; }
#if __GNUC__
#define __OPTIMIZE__
#endif
// helper for copying 8 bytes at once
constexpr size_t __memcpy_64(void* dst, const void* src)
{ *static_cast<uint64_t*>(dst) = *static_cast<const uint64_t*>(src); return 8; }
#pragma push_macro("__cplusplus")
#undef __cplusplus
#include <string.h>
#pragma pop_macro("__cplusplus")
constexpr size_t __memcpy(void* dst, const void* src, size_t n)
{
switch (n)
{
case 0:
return 0;
case 1:
return __memcpy_8(dst, src);
case 2: case 3:
return __memcpy_16(dst, src);
case 4: case 5: case 6: case 7:
return __memcpy_32(dst, src);
default:
return __memcpy_64(dst, src);
}
}
#if __GNUC__
#undef __OPTIMIZE__
#endif
}
}
#if _MSC_VER
#pragma warning(pop)
#endif
#endif // FENNEC_MEMORY_DETAIL_MEMORY_H

View File

@@ -21,7 +21,7 @@
#define FENNEC_MEMORY_H
#include <fennec/lang/type_traits.h>
#include <fennec/memory/detail/__memory.h>
#include <fennec/memory/detail/__string.h>
namespace fennec
{
@@ -34,27 +34,14 @@ namespace fennec
template<typename TypeT>
constexpr TypeT* addressof(TypeT& obj) { return FENNEC_BUILTIN_ADDRESSOF(obj); }
///
/// \copydetails fennec::memchr(const void*, int, size_t)
constexpr void* memchr(void* arr, int ch, size_t n)
{
uint8_t* a = static_cast<uint8_t*>(arr);
while (n-- && *a++ != ch) {}
return n ? a : nullptr;
}
///
/// \brief Finds the first occurence of ```static_cast<uint8_t>(ch)``` in the first \f$n\f$ bytes
/// \param arr Pointer to the object, interpreted as an array of bytes
/// \param ch The byte to search for
/// \param n The number of bytes to search
/// \returns A pointer to the location of \f$ch\f$, otherwise \f$nullptr\f$ if \f$ch\f$ is not found.
constexpr const void* memchr(const void* arr, int ch, size_t n)
{
const uint8_t* a = static_cast<const uint8_t*>(arr);
while (n-- && *a++ != ch) {}
return n ? a : nullptr;
}
using ::memchr;
using ::wmemchr;
///
/// \brief Compares the bytes of \f$lhs\f$ with \f$rhs\f$.
@@ -63,13 +50,8 @@ constexpr const void* memchr(const void* arr, int ch, size_t n)
/// \param n The number of bytes to parse
/// \returns \f$0\f$ if the first \f$n\f$ bytes of \f$lhs\f$ and \f$rhs\f$ are equivalent. Otherwise, returns \f$1\f$
/// for the first byte \f$b\f$ where \f$lhs[b] > \f$ rhs[b]\f$, and \f$-1\f$ for \f$
constexpr int memcmp(const void* lhs, const void* rhs, size_t n)
{
const uint8_t* lhs_ = static_cast<const uint8_t*>(lhs);
const uint8_t* rhs_ = static_cast<const uint8_t*>(rhs);
while (n-- && *lhs_++ == *rhs_++) {}
return n ? *lhs_ - *rhs_ : 0;
}
using ::memcmp;
using ::wmemcmp;
///
/// \brief Safe version of memcmp
@@ -91,19 +73,8 @@ constexpr int memcmp_s(const void* lhs, size_t n0, const void* rhs, size_t n1)
/// \f$src\f$ will be repeated in \f$dst\f$ with a period of \f$k\f$.
///
/// A full mathematical proof of this function is possible in Set Theory.
constexpr void* memcpy(void* dst, const void* src, size_t n)
{
if (dst == src) return dst;
uint8_t* d = static_cast<uint8_t*>(dst);
const uint8_t* s = static_cast<const uint8_t*>(src);
while (n > 0) {
size_t step = detail::__memcpy(d, s, n);
d += step; s += step; n -= step;
}
return dst;
}
using ::memcpy;
using ::wmemcpy;
///
/// \brief Safe version of memcpy
@@ -119,27 +90,8 @@ constexpr void* memcpy_s(void* dst, size_t n0, const void* src, size_t n1)
/// \param src The source object, interpreted as an array of bytes
/// \param n The number of bytes to copy
/// \returns \f$dst\f$
constexpr void* memmove(void* dst, const void* src, size_t n)
{
if (dst == src) return dst;
uint8_t* d = static_cast<uint8_t*>(dst);
const uint8_t* s = static_cast<const uint8_t*>(src);
if (d < s) {
while (n > 0) {
size_t step = detail::__memcpy(d, s, n);
d += step; s += step; n -= step;
}
}
else {
d += n - 1; s += n - 1;
while (n > 0) {
size_t step = detail::__memcpy(d, s, n);
d -= step; s -= step; n -= step;
}
}
return dst;
}
using ::memmove;
using ::wmemmove;
///
/// \brief Safe version of memmove
@@ -155,18 +107,8 @@ constexpr void* memmove_s(void* dst, size_t n0, const void* src, size_t n1)
/// \param ch The value, interpreted as an \f$uint8_t\f$
/// \param n The number of bytes to set
/// \returns \f$dst\f$
constexpr void* memset(void* dst, int ch, size_t n)
{
uint8_t* d = static_cast<uint8_t*>(dst);
uint8_t val[8] = { static_cast<uint8_t>(ch) };
while (n > 0) {
size_t step = detail::__memcpy(d, val, n);
d += step; n -= step;
}
return dst;
}
using ::memset;
using ::wmemset;
}

View File

@@ -20,8 +20,6 @@
#ifndef FENNEC_MEMORY_NEW_H
#define FENNEC_MEMORY_NEW_H
#include <new>
#include <fennec/lang/types.h>
#include <fennec/lang/utility.h>