Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
70220cb
Fix tracing monitor teardown leak and threadpool work_item wait race
Jun 12, 2026
8e88c1a
threadpool: add work_queue close() teardown + synchronous destructor …
Jun 12, 2026
aafec20
math: fix unsigned->signed negate boundary; add signed*signed->unsign…
Jun 12, 2026
15e45fb
math: fix most-negative-result boundary in mixed-sign divide; add edg…
Jun 12, 2026
32fb163
arefc_ptr: remove unsound compare_exchange_strong (atomic-cell CAS)
Jun 12, 2026
afb9f52
arefc_ptr: fix exception safety in mmake_arefc_ex and const-ref copy-…
Jun 13, 2026
849ba79
arefc_ptr: use RAII (deleter) instead of try/catch for mmake_arefc_ex…
Jun 13, 2026
feef471
arefc_ptr: fix converting operator= and hide raw-pointer reset
Jun 13, 2026
a00ccaf
sstring: IWYU <atomic>, constrain greedy templates, pass-by-ref compare
Jun 13, 2026
a7b59ef
const_string: simplify operator<=> and lock buffer-layout size invariant
Jun 13, 2026
9e79480
sstring: use m::math checked arithmetic for offset/size computations
Jun 13, 2026
7fc98d0
sstring: route remaining integer arithmetic through m::math (safe-by-…
Jun 13, 2026
0c29b21
inplace_vector: route integer arithmetic through m::math (safe-by-def…
Jun 13, 2026
57c98ad
inplace_vector: fix operator<=> to do lexicographic comparison
Jun 13, 2026
e7aed6f
inplace_vector: test append_range overflow/over-capacity guards
Jun 13, 2026
d03b782
string_inserter: guard operator* resize against size overflow
Jun 13, 2026
2a878c6
sstring/stringish: accept string literals and avoid hard error in tot…
Jun 13, 2026
068992f
utf: reject UTF-16 surrogate code points on encode and decode
Jun 13, 2026
a467c40
tracing: fix lost-wakeup deadlock in message_queue teardown
Jun 13, 2026
b5918be
utf: route encode/decode invalid-encoding throws through the utf exce…
Jun 13, 2026
afa6e89
Address Copilot PR review feedback
Jun 13, 2026
4435c20
Fix alignment UB in do_get_multi_string_value (Copilot review)
Jun 13, 2026
45b4276
tracing: use std::destroy_at for placement-new message destruction
Jun 13, 2026
aad644d
threadpool: cancel not-yet-started work items on teardown; fix test a…
Jun 14, 2026
d34afbe
Fix UB reading past string_view and dangling moved-from envelope source
Jun 14, 2026
e356926
tracing: make monitor message construction exception-safe via RAII ro…
Jun 14, 2026
281f596
tracing: fix message_queue lost-wakeup with a wake-generation counter
Jun 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,6 @@ struct std::formatter<fmtFILETIME, CharT>
st.wDay,
st.wHour,
st.wMinute,
st.wHour,
st.wMinute,
st.wSecond,
st.wMilliseconds);
else
Expand All @@ -116,8 +114,6 @@ struct std::formatter<fmtFILETIME, CharT>
st.wDay,
st.wHour,
st.wMinute,
st.wHour,
st.wMinute,
st.wSecond,
st.wMilliseconds);
}
Expand Down
4 changes: 2 additions & 2 deletions src/Windows/libraries/formatters/test/test_FILETIME.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ constexpr FILETIME negative_ft{0x0, 0x81e00000};
TEST(FILETIME, lbasic)
{
auto s = std::format(L"{}", fmtFILETIME(ft1));
EXPECT_EQ(s, L"{ Tu 2029-02-20 23:41:23.041 }"s);
EXPECT_EQ(s, L"{ Tu 2029-02-20 23:41:22.111 }"s);
}

TEST(FILETIME, lnegative)
Expand All @@ -34,7 +34,7 @@ TEST(FILETIME, lnegative)
TEST(FILETIME, basic)
{
auto s = std::format("{}", fmtFILETIME(ft1));
EXPECT_EQ(s, "{ Tu 2029-02-20 23:41:23.041 }"s);
EXPECT_EQ(s, "{ Tu 2029-02-20 23:41:22.111 }"s);
}

TEST(FILETIME, negative)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,5 +108,5 @@ TEST(FILE_NOTIFY_EXTENDED_INFORMATION, first)
auto s = std::format(L"{}", *p);
EXPECT_EQ(
s,
L"{ NextEntryOffset: 0, Action: FILE_ACTION_ADDED, CreationTime: { Su 2043-05-31 11:40:11.040 }, LastModificationTime: { Su 2043-05-31 11:40:11.040 }, LastChangeTime: { Su 2043-05-31 11:40:11.040 }, LastAccessTime: { Mo 1601-01-01 00:00:00.000 }, AllocatedLength: 0, FileSize: 0, FileAttributes: 0, ReparsePointTag: 0, FileId: 0, ParentFileId: 0, FileName: \"README.TXT\" }"s);
L"{ NextEntryOffset: 0, Action: FILE_ACTION_ADDED, CreationTime: { Su 2043-05-31 11:40:44.848 }, LastModificationTime: { Su 2043-05-31 11:40:44.848 }, LastChangeTime: { Su 2043-05-31 11:40:44.848 }, LastAccessTime: { Mo 1601-01-01 00:00:00.000 }, AllocatedLength: 0, FileSize: 0, FileAttributes: 0, ReparsePointTag: 0, FileId: 0, ParentFileId: 0, FileName: \"README.TXT\" }"s);
}
6 changes: 5 additions & 1 deletion src/include/m/utility/string_inserter.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <string>
#include <type_traits>

#include <m/math/math.h>
#include <m/utility/concepts.h>

namespace m
Expand Down Expand Up @@ -72,8 +73,11 @@ namespace m
[[nodiscard]] constexpr value_type&
operator*() noexcept
{
// m::math::add fail-stops on overflow rather than wrapping m_index + 1 to a
// small size: a wrap would shrink the string and leave the subsequent index
// out of bounds (a wild write).
if (m_string->size() <= m_index)
m_string->resize(m_index + 1);
m_string->resize(m::math::add(m_index, size_type{1}, size_type{}));

return (*m_string)[m_index];
}
Expand Down
110 changes: 58 additions & 52 deletions src/include/m/utility/stringish.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,98 +18,104 @@ namespace m
requires(character<T>)
class basic_sstring;

// These predicates use std::decay_t (not remove_cvref_t) on the queried type so that a
// string-literal argument, whose deduced type is an array (e.g. char const[N]) when bound
// to a forwarding reference, decays to its pointer form (char const*) before being matched.
// For every other accepted type decay_t is equivalent to remove_cvref_t, so this only adds
// the array forms (mapped to their already-accepted pointer types); it never accepts
// anything new beyond that.
template <typename T, typename TChar>
concept stringish =
std::same_as<remove_cvref_t<T>, std::basic_string<TChar>> ||
std::same_as<remove_cvref_t<T>, std::basic_string_view<TChar>> ||
std::same_as<remove_cvref_t<T>, m::basic_sstring<TChar>> ||
std::same_as<remove_cvref_t<T>, TChar*> || std::same_as<remove_cvref_t<T>, TChar const*>;
std::same_as<std::decay_t<T>, std::basic_string<TChar>> ||
std::same_as<std::decay_t<T>, std::basic_string_view<TChar>> ||
std::same_as<std::decay_t<T>, m::basic_sstring<TChar>> ||
std::same_as<std::decay_t<T>, TChar*> || std::same_as<std::decay_t<T>, TChar const*>;

template <typename T>
concept any_stringish =
std::same_as<remove_cvref_t<T>, std::basic_string<char>> ||
std::same_as<remove_cvref_t<T>, std::basic_string<wchar_t>> ||
std::same_as<remove_cvref_t<T>, std::basic_string<char8_t>> ||
std::same_as<remove_cvref_t<T>, std::basic_string<char16_t>> ||
std::same_as<remove_cvref_t<T>, std::basic_string<char32_t>> ||
std::same_as<remove_cvref_t<T>, std::basic_string_view<char>> ||
std::same_as<remove_cvref_t<T>, std::basic_string_view<wchar_t>> ||
std::same_as<remove_cvref_t<T>, std::basic_string_view<char8_t>> ||
std::same_as<remove_cvref_t<T>, std::basic_string_view<char16_t>> ||
std::same_as<remove_cvref_t<T>, std::basic_string_view<char32_t>> ||
std::same_as<remove_cvref_t<T>, m::basic_sstring<char>> ||
std::same_as<remove_cvref_t<T>, m::basic_sstring<wchar_t>> ||
std::same_as<remove_cvref_t<T>, m::basic_sstring<char8_t>> ||
std::same_as<remove_cvref_t<T>, m::basic_sstring<char16_t>> ||
std::same_as<remove_cvref_t<T>, m::basic_sstring<char32_t>> ||
std::same_as<remove_cvref_t<T>, char*> || std::same_as<remove_cvref_t<T>, wchar_t*> ||
std::same_as<remove_cvref_t<T>, char8_t*> || std::same_as<remove_cvref_t<T>, char16_t*> ||
std::same_as<remove_cvref_t<T>, char32_t*> ||
std::same_as<remove_cvref_t<T>, char const*> ||
std::same_as<remove_cvref_t<T>, wchar_t const*> ||
std::same_as<remove_cvref_t<T>, char8_t const*> ||
std::same_as<remove_cvref_t<T>, char16_t const*> ||
std::same_as<remove_cvref_t<T>, char32_t const*>;
std::same_as<std::decay_t<T>, std::basic_string<char>> ||
std::same_as<std::decay_t<T>, std::basic_string<wchar_t>> ||
std::same_as<std::decay_t<T>, std::basic_string<char8_t>> ||
std::same_as<std::decay_t<T>, std::basic_string<char16_t>> ||
std::same_as<std::decay_t<T>, std::basic_string<char32_t>> ||
std::same_as<std::decay_t<T>, std::basic_string_view<char>> ||
std::same_as<std::decay_t<T>, std::basic_string_view<wchar_t>> ||
std::same_as<std::decay_t<T>, std::basic_string_view<char8_t>> ||
std::same_as<std::decay_t<T>, std::basic_string_view<char16_t>> ||
std::same_as<std::decay_t<T>, std::basic_string_view<char32_t>> ||
std::same_as<std::decay_t<T>, m::basic_sstring<char>> ||
std::same_as<std::decay_t<T>, m::basic_sstring<wchar_t>> ||
std::same_as<std::decay_t<T>, m::basic_sstring<char8_t>> ||
std::same_as<std::decay_t<T>, m::basic_sstring<char16_t>> ||
std::same_as<std::decay_t<T>, m::basic_sstring<char32_t>> ||
std::same_as<std::decay_t<T>, char*> || std::same_as<std::decay_t<T>, wchar_t*> ||
std::same_as<std::decay_t<T>, char8_t*> || std::same_as<std::decay_t<T>, char16_t*> ||
std::same_as<std::decay_t<T>, char32_t*> ||
std::same_as<std::decay_t<T>, char const*> ||
std::same_as<std::decay_t<T>, wchar_t const*> ||
std::same_as<std::decay_t<T>, char8_t const*> ||
std::same_as<std::decay_t<T>, char16_t const*> ||
std::same_as<std::decay_t<T>, char32_t const*>;

template <typename T, typename Enable = void>
struct stringish_char_type;

template <typename T>
struct stringish_char_type<
T,
std::enable_if_t<std::same_as<remove_cvref_t<T>, std::basic_string<char>> ||
std::same_as<remove_cvref_t<T>, std::basic_string_view<char>> ||
std::same_as<remove_cvref_t<T>, m::basic_sstring<char>> ||
std::same_as<remove_cvref_t<T>, char*> ||
std::same_as<remove_cvref_t<T>, char const*>>>
std::enable_if_t<std::same_as<std::decay_t<T>, std::basic_string<char>> ||
std::same_as<std::decay_t<T>, std::basic_string_view<char>> ||
std::same_as<std::decay_t<T>, m::basic_sstring<char>> ||
std::same_as<std::decay_t<T>, char*> ||
std::same_as<std::decay_t<T>, char const*>>>
{
using type = char;
};

template <typename T>
struct stringish_char_type<
T,
std::enable_if_t<std::same_as<remove_cvref_t<T>, std::basic_string<wchar_t>> ||
std::same_as<remove_cvref_t<T>, std::basic_string_view<wchar_t>> ||
std::same_as<remove_cvref_t<T>, m::basic_sstring<wchar_t>> ||
std::same_as<remove_cvref_t<T>, wchar_t*> ||
std::same_as<remove_cvref_t<T>, wchar_t const*>>>
std::enable_if_t<std::same_as<std::decay_t<T>, std::basic_string<wchar_t>> ||
std::same_as<std::decay_t<T>, std::basic_string_view<wchar_t>> ||
std::same_as<std::decay_t<T>, m::basic_sstring<wchar_t>> ||
std::same_as<std::decay_t<T>, wchar_t*> ||
std::same_as<std::decay_t<T>, wchar_t const*>>>
{
using type = wchar_t;
};

template <typename T>
struct stringish_char_type<
T,
std::enable_if_t<std::same_as<remove_cvref_t<T>, std::basic_string<char8_t>> ||
std::same_as<remove_cvref_t<T>, std::basic_string_view<char8_t>> ||
std::same_as<remove_cvref_t<T>, m::basic_sstring<char8_t>> ||
std::same_as<remove_cvref_t<T>, char8_t*> ||
std::same_as<remove_cvref_t<T>, char8_t const*>>>
std::enable_if_t<std::same_as<std::decay_t<T>, std::basic_string<char8_t>> ||
std::same_as<std::decay_t<T>, std::basic_string_view<char8_t>> ||
std::same_as<std::decay_t<T>, m::basic_sstring<char8_t>> ||
std::same_as<std::decay_t<T>, char8_t*> ||
std::same_as<std::decay_t<T>, char8_t const*>>>
{
using type = char8_t;
};

template <typename T>
struct stringish_char_type<
T,
std::enable_if_t<std::same_as<remove_cvref_t<T>, std::basic_string<char16_t>> ||
std::same_as<remove_cvref_t<T>, std::basic_string_view<char16_t>> ||
std::same_as<remove_cvref_t<T>, m::basic_sstring<char16_t>> ||
std::same_as<remove_cvref_t<T>, char16_t*> ||
std::same_as<remove_cvref_t<T>, char16_t const*>>>
std::enable_if_t<std::same_as<std::decay_t<T>, std::basic_string<char16_t>> ||
std::same_as<std::decay_t<T>, std::basic_string_view<char16_t>> ||
std::same_as<std::decay_t<T>, m::basic_sstring<char16_t>> ||
std::same_as<std::decay_t<T>, char16_t*> ||
std::same_as<std::decay_t<T>, char16_t const*>>>
{
using type = char16_t;
};

template <typename T>
struct stringish_char_type<
T,
std::enable_if_t<std::same_as<remove_cvref_t<T>, std::basic_string<char32_t>> ||
std::same_as<remove_cvref_t<T>, std::basic_string_view<char32_t>> ||
std::same_as<remove_cvref_t<T>, m::basic_sstring<char32_t>> ||
std::same_as<remove_cvref_t<T>, char32_t*> ||
std::same_as<remove_cvref_t<T>, char32_t const*>>>
std::enable_if_t<std::same_as<std::decay_t<T>, std::basic_string<char32_t>> ||
std::same_as<std::decay_t<T>, std::basic_string_view<char32_t>> ||
std::same_as<std::decay_t<T>, m::basic_sstring<char32_t>> ||
std::same_as<std::decay_t<T>, char32_t*> ||
std::same_as<std::decay_t<T>, char32_t const*>>>
{
using type = char32_t;
};
Expand Down
78 changes: 30 additions & 48 deletions src/libraries/arefc_ptr/include/m/arefc_ptr/arefc_ptr.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,13 @@ namespace m
constexpr void
operator()(aggregate<T>* aggptr) const noexcept
{
aggregate<T>::deallocate(aggptr);
// The unique_ptr returned by aggregate::allocate() owns raw storage
// whose object has NOT been constructed yet (construction happens later
// in mmake_arefc_ex via the caller's callback). So on cleanup we must
// only deallocate, never run the object's destructor on unconstructed
// storage. Once the object is constructed, ownership is released to the
// arefc_ptr and destruction becomes the refcount's responsibility.
aggregate<T>::deallocate(aggptr, /* do_destroy */ false);
}
};

Expand Down Expand Up @@ -500,7 +506,7 @@ namespace m
~arefc_ptr() { reset(); }

arefc_ptr&
operator=(arefc_ptr& other) noexcept
operator=(arefc_ptr const& other) noexcept
{
if (this != &other)
{
Expand All @@ -516,11 +522,11 @@ namespace m
arefc_ptr&
operator=(arefc_ptr<U> const& other) noexcept
{
if (this != &other)
{
reset();
put(other.addref());
}
// No self-assignment guard: a different specialization arefc_ptr<U> can never
// be the same object as *this, and comparing the unrelated pointer types would
// be ill-formed. (When U == T the non-template copy-assignment is selected.)
reset();
put(other.addref());

return *this;
}
Expand Down Expand Up @@ -548,10 +554,9 @@ namespace m
}

void
reset(T* ptr_in = nullptr) noexcept
reset() noexcept
{
auto const ptr = m_ptr.exchange(increment_ref(ptr_in), std::memory_order_acq_rel);
decrement_ref(ptr);
reset(nullptr);
}

T&
Expand Down Expand Up @@ -588,47 +593,20 @@ namespace m
return v;
}

bool
compare_exchange_strong(arefc_ptr& expected, arefc_ptr const& desired) noexcept
{
// The trick here is to not mess up the reference counting!
//
// it would seem trivial to just "pass through" the m_ptr values but that's
// only part of the story.
//

T* e = expected.get();
T* d = desired.get();

T* old_e = e; // save a copy so we don't have to re-load

// Pre-increment d's refcount so that if the CAS succeeds, m_ptr holds
// a valid reference to d without a window where the refcount is zero.
increment_ref(d);

if (m_ptr.compare_exchange_strong(e, d, std::memory_order_acq_rel))
{
M_INTERNAL_ERROR_CHECK(e == old_e);

// Account for the fact that m_ptr no longer refers to `e`
decrement_ref(e);
return true;
}

// The CAS failed: m_ptr still holds its current value (now captured in `e`).
// `d` was pre-incremented above but was never stored in m_ptr, so we must
// undo that increment to avoid a permanent ref-count leak on `desired`.
decrement_ref(d);

// Update `expected` to reflect the actual current value of m_ptr.
expected.reset(e);

return false;
}

private:
constexpr arefc_ptr(T* ptr) noexcept: m_ptr(ptr) {}

// Raw-pointer reset: `ptr_in` must already point just past a control area
// (i.e. be a pointer obtained from an arefc_ptr-managed object) or be null.
// Private because passing an arbitrary raw pointer is unsafe; external callers
// use the no-arg reset().
void
reset(T* ptr_in) noexcept
{
auto const ptr = m_ptr.exchange(increment_ref(ptr_in), std::memory_order_acq_rel);
decrement_ref(ptr);
}

arefc_ptr_impl::control_area_t<T>*
get_control_area() const
{
Expand Down Expand Up @@ -726,9 +704,13 @@ namespace m
auto a = aggregate_type::allocate(extra_bytes_required);

auto const object_span = a->get_object_byte_span();

// If `fn` throws, `a`'s deleter deallocates the raw storage without running
// a destructor on the never-constructed object (RAII cleanup).
auto const ptr =
std::invoke(std::forward<Fn>(fn), object_span, std::forward<Args>(args)...);

// Construction succeeded: hand ownership to the arefc_ptr.
a.release();
arefc_ptr<T> retval(ptr);
return retval;
Expand Down
Loading
Loading