From 14a6924d2fe27083556e323ec7efdee7567d3a4f Mon Sep 17 00:00:00 2001 From: Kimplul Date: Wed, 26 Aug 2026 21:02:53 +0300 Subject: add simple string buffer + Has short-string optimization, which seems to trigger an erroneous warning about out-of-bounds writes to unions. Should probably report upstream. --- include/conts/sp.h | 251 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 251 insertions(+) create mode 100644 include/conts/sp.h (limited to 'include') diff --git a/include/conts/sp.h b/include/conts/sp.h new file mode 100644 index 0000000..73fcdda --- /dev/null +++ b/include/conts/sp.h @@ -0,0 +1,251 @@ +#ifndef SP_H +#define SP_H + +#define SP_SHORT_STR_LEN sizeof(char *) +#include +#include +#include +#include +#include +#include "conts.h" + +#endif /* SP_H */ + +#ifndef SP_MALLOC +#define SP_MALLOC malloc +#endif + +#ifndef SP_REALLOC +#define SP_REALLOC realloc +#endif + +#ifndef SP_FREE +#define SP_FREE free +#endif + +#if defined(SP_NAME) || !defined(SP_DEFAULTED) + +#if !defined(SP_NAME) +#define SP_NAME sp +#define SP_DEFAULTED +#endif + +#define SP(a) CONTS_JOIN(SP_NAME, a) +#define SP_STRUCT SP_NAME + +struct SP_STRUCT { + size_t l; + union { + char *p; + char b[SP_SHORT_STR_LEN]; + }; +}; + +static inline struct SP_STRUCT SP(create)() +{ + struct SP_STRUCT s; + s.l = 0; + s.b[0] = '\0'; + return s; +} + +static inline void SP(destroy)(const struct SP_STRUCT *s) +{ + if (s->l >= SP_SHORT_STR_LEN) + SP_FREE(s->p); +} + +static inline size_t SP(len)(const struct SP_STRUCT *s) +{ + return s->l; +} + +static inline size_t SP(cap)(size_t n) +{ + size_t c = SP_SHORT_STR_LEN; + while (c <= n) + c *= 2; + + return c; +} + +static inline char *SP(str)(struct SP_STRUCT *s) +{ + if (s->l < SP_SHORT_STR_LEN) + return &s->b[0]; + + return s->p; +} + +static inline char *SP(at)(struct SP_STRUCT *s, size_t n) +{ + /* n == s->l is allowed, but it's just the trailing \0 */ + assert(n <= s->l); + if (s->l < SP_SHORT_STR_LEN) + return &s->b[n]; + + return &s->p[n]; +} + +static inline void SP(trunc)(struct SP_STRUCT *s) +{ + if (s->l < SP_SHORT_STR_LEN) + s->b[s->l] = '\0'; + else + s->p[s->l] = '\0'; +} + +static inline int SP(append_strn)(struct SP_STRUCT *s, const char *buf, size_t n) +{ + size_t nl = s->l + n; + + /* we can fit into the static buffer */ + if (nl < SP_SHORT_STR_LEN) { + memcpy(s->b + s->l, buf, n); + s->b[nl] = '\0'; + s->l = nl; + return 0; + } + + /* we need a dynamic buffer, but the current string is in the static + * buffer */ + if (s->l < SP_SHORT_STR_LEN) { + size_t c = SP(cap)(nl); + char *p = SP_MALLOC(c * sizeof(char)); + if (!p) + return -1; + + memcpy(p, s->b, s->l); + memcpy(p + s->l, buf, n); + p[nl] = '\0'; + + s->p = p; + s->l = nl; + return 0; + } + + /* we already have a buffer, extend it if need be */ + size_t c = SP(cap)(s->l); + if (c <= nl) { + char *p = SP_REALLOC(s->p, SP(cap)(nl)); + if (!p) + return -1; + + s->p = p; + } + + memcpy(s->p + s->l, buf, n); + s->p[nl] = '\0'; + return 0; +} + +static inline int SP(append_str)(struct SP_STRUCT *s, const char *buf) +{ + return SP(append_strn)(s, buf, strlen(buf)); +} + +static inline int SP(append_vfmt)(struct SP_STRUCT *s, const char *fmt, va_list args) +{ + /* opportunistically copy into our existing buffer */ + size_t c = SP(cap)(s->l); + + va_list cp; + va_copy(cp, args); + int l = vsnprintf(SP(str)(s) + s->l, c - s->l, fmt, cp); + va_end(cp); + + /* something went wrong, place back \0 to keep string in tact */ + if (l < 0) { + SP(trunc)(s); + va_end(args); + return l; + } + + /* everything written fit into our buffer */ + if (s->l + l < c) { + s->l += l; + va_end(args); + return 0; + } + + /* since the static buffer is always available, we end up here when we + * either run out of space in it or a dynamic buffer. */ + size_t cn = SP(cap)(s->l + l); + + char *p; + if (s->l < SP_SHORT_STR_LEN) { + /* If static, allocate a dynamic buffer. */ + p = SP_MALLOC(cn * sizeof(char)); + if (!p) { + SP(trunc)(s); + va_end(args); + return -1; + } + + memcpy(p, s->b, s->l); + } else { + /* dynamic buffer, resize */ + p = SP_REALLOC(s->p, cn * sizeof(char)); + if (!p) { + SP(trunc)(s); + va_end(args); + return -1; + } + } + + int ln = vsnprintf(p + s->l, cn - s->l, fmt, args); + va_end(args); + assert(ln == l); + s->p = p; + s->l += l; + return 0; +} + +static inline int SP(append_fmt)(struct SP_STRUCT *s, const char *fmt, ...) +{ + int r = 0; + va_list args; + va_start(args, fmt); + r = SP(append_vfmt)(s, fmt, args); + va_end(args); + return r; +} + +typedef struct { + size_t i; + char *p; +} SP(iter); + +static inline SP(iter) SP(begin)(struct SP_STRUCT *s) +{ + size_t i = 0; + return (SP(iter)){ + .i = i, + .p = SP(at)(s, i) + }; +} + +static inline int SP(end)(struct SP_STRUCT *s, SP(iter) iter) +{ + return iter.i >= s->l; +} + +static inline SP(iter) SP(next)(struct SP_STRUCT *s, SP(iter) iter) +{ + size_t n = iter.i + 1; + return (SP(iter)){ + .i = n, + .p = SP(at)(s, n) + }; +} + +#endif /* !defined(SP_DEFAULT_NAME) && SP_NAME != sp */ + +#undef SP_STRUCT +#undef SP_REALLOC +#undef SP_MALLOC +#undef SP_FREE +#undef SP_NAME +#undef SP + +/* purposefully let SP_DEFAULTED by */ -- cgit v1.3