From 9c90be07d6000aea8696caa0cf6e23ed967da425 Mon Sep 17 00:00:00 2001 From: frapank Date: Fri, 3 Jul 2026 19:17:43 +0200 Subject: [PATCH 1/2] fix: dangling s2 pointer in concat when s2 aliases s1's buffer If s2 points inside s1 (e.g. dstrappend(s, s + i)), dstrreserve() can realloc s1's buffer and invalidate s2 before the copy happens. Detect the aliasing before reserving and re-derive s2 from the new buffer. --- dstr.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/dstr.c b/dstr.c index b83f7e1..48e5c46 100644 --- a/dstr.c +++ b/dstr.c @@ -351,11 +351,21 @@ static dstr _dstr_concat_impl(dstr s1, size_t new_len = s1_len + s2_len; if (cap < new_len + 1) cap = new_len + 1; + + uintptr_t s1_addr = (uintptr_t)s1; + uintptr_t s2_addr = (uintptr_t)s2; + size_t s1_cap = dstrcap(s1); + bool s2_aliases_s1 = s2_addr >= s1_addr && s2_addr - s1_addr < s1_cap; + size_t s2_offset = s2_aliases_s1 ? (size_t)(s2_addr - s1_addr) : 0; + dstr tmp = dstrreserve(s1, cap); if (!tmp) return NULL; s1 = tmp; + if (s2_aliases_s1) + s2 = s1 + s2_offset; + uint8_t hdr_type = DSTRGETTYPE(s1); void* hd = NULL; switch (hdr_type) { From 829bb4656a7c7b628f1fbd47fff6fbcb932c0558 Mon Sep 17 00:00:00 2001 From: frapank Date: Fri, 3 Jul 2026 19:17:57 +0200 Subject: [PATCH 2/2] add: amortized capacity growth and memchr-based dstrfind dstrcat_base/dstrpush_base/dstrappend_base now grow capacity by 1.5x via _dstr_grow_cap() instead of reserving exactly what's needed, avoiding O(n^2) reallocations under repeated growth. dstrfind() now scans with memchr() for the first byte instead of memcmp()-ing at every position. --- dstr.c | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/dstr.c b/dstr.c index 48e5c46..a33926d 100644 --- a/dstr.c +++ b/dstr.c @@ -152,6 +152,14 @@ static inline void* _dstr_get_hdr_and_type(dstr s, enum dstrhd_type* t_out) } } +static inline size_t _dstr_grow_cap(size_t old_cap, size_t needed) +{ + size_t grown = old_cap < 16 ? 16 : old_cap; + if (grown <= SIZE_MAX - grown / 2) + grown += grown / 2; + return grown > needed ? grown : needed; +} + // strfind ssize_t dstrfind(dstr s, const char* needle) { @@ -164,10 +172,17 @@ ssize_t dstrfind(dstr s, const char* needle) if (nlen > len) return -1; - for (size_t i = 0; i <= len - nlen; i++) { - if (memcmp(&s[i], needle, nlen) == 0) { - return i; - } + size_t remaining = len - nlen + 1; + const char* cursor = s; + + while (remaining > 0) { + const char* hit = memchr(cursor, needle[0], remaining); + if (!hit) + return -1; + if (memcmp(hit, needle, nlen) == 0) + return hit - s; + remaining -= (size_t)(hit - cursor) + 1; + cursor = hit + 1; } return -1; @@ -397,7 +412,8 @@ dstr dstrcat_base(dstr s1, const char* s2) return s1; size_t s1_len = dstrlen(s1); size_t s2_len = strlen(s2); - return _dstr_concat_impl(s1, s1_len, s2, s2_len, s1_len + s2_len + 1); + size_t cap = _dstr_grow_cap(dstrcap(s1), s1_len + s2_len + 1); + return _dstr_concat_impl(s1, s1_len, s2, s2_len, cap); } dstr dstrcat_custom(dstr s1, const char* s2, size_t cap) @@ -437,7 +453,8 @@ dstr dstrpush_custom(dstr s1, const char c, size_t cap) dstr dstrpush_base(dstr s1, const char c) { - return dstrpush_custom(s1, c, dstrcap(s1) + 1); + size_t cap = dstrcap(s1); + return dstrpush_custom(s1, c, _dstr_grow_cap(cap, cap + 1)); } // strappend @@ -447,7 +464,8 @@ dstr dstrappend_base(dstr s1, const dstr s2) return s1; size_t s1_len = dstrlen(s1); size_t s2_len = dstrlen(s2); - return _dstr_concat_impl(s1, s1_len, s2, s2_len, s1_len + s2_len + 1); + size_t cap = _dstr_grow_cap(dstrcap(s1), s1_len + s2_len + 1); + return _dstr_concat_impl(s1, s1_len, s2, s2_len, cap); } dstr dstrappend_custom(dstr s1, const dstr s2, size_t cap)