Compare commits

...

11 Commits

Author SHA1 Message Date
Avril ae6e40453d
`TRANSMUTE()`: Fix input type size not being properly checked.
3 years ago
Avril 87e102ded9 Added `TRANSMUTE(value, to_type)` macro, which performs a bit-cast on `value` to `to_type`. The sizes of the types must be the same or a compilation error is raised. There is currently no alignment restriction on the types (maybe there should be?).
3 years ago
Avril a826612de5
Replace `Nx()` (temp value-assigning) macros with `Nv()` to avoid conflict with `TRACEx()` and for more clarity.
3 years ago
Avril 158b6e1fd4
Added `_str_raw_to_cstrsa()`: Convert raw buffer to nul-terminated string that is dynamically allocated and statically held. (i.e. do not `free()`)
3 years ago
Avril 267a4dae66 str_t impl: added alloc bare
3 years ago
Avril 152e0aa8e7
Start writing new string-slicing module.
3 years ago
Avril aa94ab1622
Failed string API attempt. Branching for rework.
3 years ago
Avril 0db8d882d0
Attempted rewrite of _display_get_fmt()`, it currently doesn"t work. To compile using the old functional but (probably) unsafe function, pass `-D_DEBUG_USE_UNSAFE_DISPLAY_FMT_ARGS` in `CFLAGS`.
3 years ago
Avril dca7e48d4a
`s_strncpy_n()`, `s_strncat_n()` trivial implementation.
3 years ago
Avril eee5203ba9
Start: double-bounded string mutation functions.
3 years ago
Avril e9ccf5e2f2
Basic implementation of more sensible string mutating functions for display format string creation. (`strncat_n()`, `strncpy_n()`.)
3 years ago

@ -18,11 +18,13 @@
#define _cold __attribute__((cold))
#define _hot __attribute__((hot))
#define _dead __attribute__((unused))
#ifndef __cplusplus
#define noreturn __attribute__((noreturn))
#define deprecated __attribute__((deprecated))
#define _deprecated __attribute__((deprecated))
#define deprecated_message(str) __attribute__((deprecated(str)))
#define _deprecated_msg(str) deprecated_message(str)
#endif
#define noinline __attribute__((noinline))
@ -42,6 +44,31 @@
#define restrict __restrict__
#endif
// Function conventions
#define _cconv__mixin _mixin
#define _cconv__pure _pure
#define _cconv__readonly _readonly
#define _cconv__cold _cold
#define _cconv__dead _dead
#define _cconv__unused _dead
#define _cconv__hot _hot
#define _cconv__entry __attribute__((constructor))
#define _cconv__exit __attribute__((destructor))
// Allocates
#define _cconv__alloc __attribute__((malloc))
// Infallible alloc
#define _cconv__ialloc __attribute__((malloc)) __attribute__((returns_nonnull))
#define _cconv__nonnull __attribute__((returns_nonnull))
#define _callcv(name) _cconv__ ## name
#define _callconv(name) _callcv(name)
#define _callat_entry _callcv(entry)
#define _callat_exit _callcv(exit)
#define _callat(when) _callat_ ## when
// Argument attribute
// Used like: `int *pOUT output, const int *pIN input`
@ -108,6 +135,8 @@ _mixin void _drain_val(void* x, ...) { IGNORE(x); } // This compiles to no-op on
// Allocation macros
#define box(t) aligned_alloc(_Alignof(t), sizeof(t))
#define box_value(v) ({ let _box = box(var(v)); *_box = (v); _box; })
#define unbox_value(v) ({ let _v = (v); let _res = *_v; free(_v); _res; })
#define stackalloc(t) __builtin_alloca_with_align(sizeof(t), _Alignof(t))
// Function macros
@ -177,6 +206,15 @@ static_assert_eq(bswap(128lu), 9223372036854775808lu, "bswap128 (lu) failed (1)"
static_assert_eq(128lu, bswap(9223372036854775808lu), "bswap128 (lu) failed (2)");
static_assert_eq(bswap(bswap(128lu)), 128, "bswap128 (lu) failed (3)");
// Transmute
#define TRANSMUTE(val, type) ({ union _trans { var(val) input; type output; }; \
_Static_assert(sizeof(var(val)) == sizeof(type), "Cannot transmute values of different sizes"); \
_Static_assert(sizeof(union _trans) == sizeof(type), "Cannot transmute values of different sizes"); \
/* XXX: Do we need do check this for equality? Can we re-word it or do we even need it at all? _Static_assert(_Alignof(union _trans) == _Alignof(type), "Cannot transmute values of different alignments");*/ \
union _trans _trans__value = { .input = (val), }; \
_trans__value.output; })
// Trace message output
#include "trace.h"
@ -234,4 +272,9 @@ static_assert_eq(bswap(bswap(128lu)), 128, "bswap128 (lu) failed (3)");
#define VERSION_REV(ver) _AS_u32(_AS_u32(ver) & 0xffu)
_Static_assert( (VERSION_COMP(VERSION(1,2,3,4), VER_COMP_MIN) >> VER_COMP_MIN) == 2u, "invalid native version spec");
// Misc.
#define PTR_ASSIGN(ptr, val) ( (ptr) ? (*(ptr) = (val), (ptr)) : (ptr) )
#define PTR_ASSIGNv(ptr, val) ({ let _ptr = (ptr); let _val = (val); PTR_ASSIGN(_ptr, _val); })
#endif /* _MACROS_H */

@ -0,0 +1,31 @@
//! Better string handling
#ifndef _STR_H
#define _STR_H
#include <macros.h>
#include <ints.h>
#define STRF_OWNED AS(1ul << 5, int)
#define STRF_DERIVED AS(1ul << 6, int)
enum str_ownership {
STR_NULL = 0,
STR_OWNED = STRF_OWNED,
STR_OWNED_STATIC,
STR_OWNED_MALLOC,
STR_OWNED_STACK,
STR_DERIVED = STRF_DERIVED,
STR_DERIVED_STATIC,
STR_DERIVED_MALLOC,
STR_DERIVED_STACK,
};
typedef struct string str_t;
str_t* str_alloc(usize cap) _callconv(alloc);
char* str_new(const char* pIN cstr) _callconv(alloc);
void str_free(str_t* restrict str);
#endif /* _STR_H */

@ -11,8 +11,101 @@
#define FLAGSET(f, flag) (( (f) & (flag) ) == (flag))
#define DFLAGSET(f, flagname) FLAGSET(f, DISPF_SHOW_ ## flagname)
//TODO: This probably won't work. Write wrappers for strncpy(), strncat() that return number of bytes written isntead of useless pointer that we already have.
static usize _display_get_fmt(dispflags_t flags, bool matched, usize _len, char str[pOUT _len])
#define CMP(x,cmp,y) ( (x) cmp (y) ? (x) : (y) )
#define CMPv(x,cmp,y) ({ let _x = (x); let _y = (y); CMP(_x, cmp, _y); })
#define MIN(x, y) CMP((x), <, (y))
#define MINv(x, y) CMPv((x), <, (y))
#define MAX(x, y) CMP((x), >, (y))
#define MAXv(x, y) CMPv((x), >, (y))
// Returns number of bytes written to `dest`. Otherwise same as `strncpy()`.
static usize strncpy_n(char*pOUT dest, const char* restrict src, usize n)
{
usize i;
for(i=0;i<n;i++)
if(! (*dest++ = *src++)) return i;
dest[i] = 0;
return i;
}
// Will not write past `dn` bytes of `dest`, will not read past `sn` bytes of `src`. Otherwise, same as `strncpy_n()`.
static usize s_strncpy_n(usize sn, usize dn; char dest[static pOUT dn], usize dn, const char src[static restrict sn], usize sn)
{
usize i;
usize n = MIN(sn,dn);
for(i=0;i<n;i++)
if(! (*dest++ = *src++)) return i;
if(i<dn) dest[i] = 0;
return i;
}
// Will not write past `dn` bytes of `dest`, will not read past `sn` bytes of `src`. Otherwise, same as `strncat_n()`.
static inline usize s_strncat_n(usize sn, usize dn; char dest[static pOUT dn], usize dn, const char src[static restrict sn], usize sn)
{
usize dl = MINv(strlen(dest), dn);
return s_strncpy_n(dest + dl, dn - dl, src, sn);
}
// Returns number of bytes written to (dest+strlen(dest)). Otherwise same as `strncat()`.
static inline usize strncat_n(char*pOUT dest, const char* restrict src, usize n)
{
usize dl = strlen(dest);
return strncpy_n(dest+dl, src, n);
}
//TODO: This isn't working at all...
#ifdef _DEBUG_USE_UNSAFE_DISPLAY_FMT_ARGS
_dead
#endif
static usize _display_get_fmt_n(dispflags_t flags, bool matched, usize _n, char str[pOUT _n])
{
register usize n = _n, fw=0;
const char* fmts[] = {
DFLAGSET(flags, FAILURES)
? "failure: "
: "",
"match: ",
"%lu: ",
SLICE_FORMAT,
" (len %lu)",
(const char*)NULL,
};
usize w = s_strncpy_n(str, n, fmts[!!matched], n); // SAFETY: Will never read past `fmts[x]` nul-terminator, so setting n as both dest and src length here is safe.
//TODO: Fix this... It isn't working...
#define ADDSTR(s) do { \
debug_assert((s)); \
w = MINv(s_strncat_n(str, n, (s), n), n); \
/* Remove written from `n` */ \
n -= w; \
/* Check if we have written into the limit (if so, w will be equal to n above, since w is high-bounded to n by `MINx()` above. So if n is 0, we have hit the limit. */ \
ifU(!n) { \
/*XXX: Should this be `str[w-1]?` If this assert fails then yes it should. */ \
debug_assert(w < _n); \
str[w] = 0; \
return fw + w; \
} \
/* Increase `str` pointer by written */ \
str += w; \
/* Increase return value by written. */ \
fw += w; \
} while(0)
if(DFLAGSET(flags, NUMBER)) ADDSTR(fmts[2]);
if(DFLAGSET(flags, SLICE)) ADDSTR(fmts[3]);
if(DFLAGSET(flags, LENGTH)) ADDSTR(fmts[4]);
ADDSTR("\n");
TRACE("Output string written: %lu, (rlen %lu)", fw, n);
#undef ADDSTR
return fw;
}
//XXX: This works, but seems inherantly unsafe. Rewrite to use the above `strn*_n()` functions for better length tracking.
#ifndef _DEBUG_USE_UNSAFE_DISPLAY_FMT_ARGS
_dead deprecated_message("this is unsafe, use `_display_get_fmt_n`")
#endif
static usize _display_get_fmt0(dispflags_t flags, bool matched, usize _len, char str[pOUT _len])
{
register usize len = _len;
static const char* fmts[] = { "match: ", "%lu: ", SLICE_FORMAT, " (len %lu)", "failure: ", (const char*)NULL };
@ -30,6 +123,12 @@ static usize _display_get_fmt(dispflags_t flags, bool matched, usize _len, char
#undef ADDSTR
}
#ifdef _DEBUG_USE_UNSAFE_DISPLAY_FMT_ARGS
#define _display_get_fmt(...) _display_get_fmt0(__VA_ARGS__)
#else
#define _display_get_fmt(...) _display_get_fmt_n(__VA_ARGS__)
#endif
static void _display_normal(FILE* output, dispin_t*pIN input, dispflags_t flags)
{
bool matched = input->matched;
@ -49,9 +148,14 @@ static void _display_normal(FILE* output, dispin_t*pIN input, dispflags_t flags)
#endif
INFO("Mapped region slice for %lu, pre-transform (at %p): " SLICE_FORMAT ", len: %lu", i+1, base, SLICE_FORMAT_ARGS(&cslice), cslice.len);
//Honour `flags`
// --- get format string from `flags` ---
char fmt[200]; //TODO: Find the realistic max for this.
fmt[_display_get_fmt(flags, matched, 100, fmt)] = 0;
usize fmtw = _display_get_fmt(flags, matched, sizeof(fmt)-1, fmt);
assert(fmtw+1 < sizeof(fmt));
fmt[fmtw+1] = 0;
// ---
TRACE("Format string get: `%s' (%lu)", fmt, fmtw);
fprintf(output, fmt, i+1, pos.start, pos.end, pos.end-pos.start);
}

@ -0,0 +1,197 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <str.h>
struct string {
usize len, cap;
struct str_meta {
enum str_ownership owned;
union _str_meta_inner {
// `STRF_OWNED`
struct str_meta_owned {
_Atomic usize refs; // Number of derrivations from this
void (*m_free)(void*); // How to free (when `_MALLOC`).
_Atomic bool orphan; // If a `derive` references this parent somehow, but this string has been `str_free()`d. Tell the derived string that sets the `refs` to 0 to free it.
} owned;
// `STRF_DERIVED`
struct str_meta_derived {
// String this is derived from, can be either owned or derived.
struct string* _shared parent;
// `str`'s offset from `parent->str`.
usize offset;
} derived;
} _inner;
} meta;
// The start of this string slice. Regardless of slicing
char* str;
};
//TODO: We can use offsetof() to reverse the result of `str_new()` from char* (str field) to str_t*.
inline static isize _str_wrap_index(usize _max, isize wr)
{
isize max = (isize)_max;
ifU(max<0) FATAL("max too large, must be <= isize::max (%lu)", SIZE_MAX>>1);
else if(!max) return 0;
while(wr<0) wr = max + wr;
wr = wr >=max ? wr % max : wr;
debug_assert(wr>0);
return wr;
}
// Create a cstring (nul-terminated) from `from`. This string is statically allocated and should not be modified.
// `sl` - Max size of `from` buffer. If 0, then just free the allocated buffer and return empty string;
// `from` - Non-nul-terminated string buffer to copy from.
// `len` - A pointer to a wrappable index for `from`. If `len` is negative, then offset from `sl`, if it's positive, offset from 0; if it's NULL, use `sl` itself. It is wrapped around `sl` in the case that the computed offset is >= sl.
//
// # Notes
// Don't `free()` this returned pointer, this function handles that on call and on program exit.
static const char* _str_raw_to_cstrsa(usize sl, const char from[restrict sl], isize*pINOUT len)
{
static _Thread_local char* _ma_buf = NULL;
if(!sl) {
free(_ma_buf); //free(NULL) is allowed.
_ma_buf=NULL;
return "";
}
_callcv(exit) void _cleanup() { free(_ma_buf); _ma_buf = NULL; }
isize fp = (isize) (sl & (SIZE_MAX >> 1));
if(!len) len = &fp;
TRACE("copying wrapped %ld bytes from buffer %p[%lu]", *len, from, sl);
usize rl = (usize) (*len = _str_wrap_index(sl, *len));
if(_ma_buf) _ma_buf = realloc(_ma_buf, rl+1);
else _ma_buf = malloc(rl+1);
memmove(_ma_buf, from, rl);
_ma_buf[rl] = 0;
return (const char*)_ma_buf;
}
inline static bool _str_meta_is_owned(enum str_ownership owned)
{
return AS(owned, u32) & AS(STRF_OWNED, u32);
}
// Returns true for owned, false for derived
inline static bool _str_meta_parts_from(enum str_ownership o, union _str_meta_inner *pIN inner, struct str_meta_owned* *pOUT own, struct str_meta_derived* *pOUT derive)
{
if(_str_meta_is_owned(o))
return (PTR_ASSIGN(own, &inner->owned), true);
else return (PTR_ASSIGN(derive, &inner->derived), false);
}
inline static bool _str_is_owned(const str_t*pIN str)
{
return _str_meta_is_owned(str->meta.owned);
}
inline static bool _str_is_owned_parts(str_t*pIN str, struct str_meta_owned* *pOUT own, struct str_meta_derived* *pOUT derive)
{
return _str_meta_parts_from(str->meta.owned, &str->meta._inner, own, derive);
}
static union _str_meta_inner _str_default_meta(enum str_ownership oship, bool *pOUT owned)
{
union _str_meta_inner output;
struct str_meta_owned* o_own = NULL;
struct str_meta_derived* o_der = NULL;
// PTR_ASSIGNv always evaluates both args, so this is fine
PTR_ASSIGNv(owned,
_str_meta_parts_from(oship, &output,
&o_own,
&o_der)
);
switch(oship)
{
case STR_OWNED_STACK:
case STR_OWNED_STATIC:
debug_assert(o_own);
*o_own = (struct str_meta_owned){
.refs = 0,
.m_free = NULL,
.orphan = false,
};
break;
case STR_OWNED_MALLOC:
debug_assert(o_own);
*o_own = (struct str_meta_owned){
.refs = 0,
.m_free = &free,
.orphan = false,
};
break;
case STR_DERIVED_STATIC:
case STR_DERIVED_STACK:
case STR_DERIVED_MALLOC:
debug_assert(o_der);
*o_der = (struct str_meta_derived){
.parent = NULL,
.offset = 0,
};
break;
default:
ERROR("Cannot assign metadata from ownership type 0x%x, returning 0'd metadata", AS(oship, u32));
memset(&output, 0, sizeof(output));
}
return output;
}
/// Allocates a bare new str_t. The metadata will be incomplete for `derived` string slices, and `str` will be `NULL`. For `owned` strings, `str` will be `calloc()`ed to `cap+1`.
static str_t _str_alloc_bare(usize cap, enum str_ownership owned)
{
bool is_owned;
let meta_inner = _str_default_meta(owned, &is_owned);
struct str_meta meta = {
.owned = owned,
._inner = meta_inner,
};
str_t str = {
.len = 0,
.cap = cap,
.meta = meta,
};
str.str = is_owned ? calloc(cap+1, 1) : NULL;
return str;
}
static void _str_force_free(struct str_meta_owned*pIN o_own, void* ptr)
{
(o_own->m_free ?: &free)(ptr);
}
void str_free(str_t* restrict strp)
{
str_t str = unbox_value(strp);
struct str_meta_owned* o_own = NULL;
struct str_meta_derived* o_der = NULL;
if(_str_is_owned_parts(&str, &o_own, &o_der))
{
debug_assert(o_own);
if(o_own->refs > 0) o_own->orphan = true;
else if(str.meta.owned == STR_OWNED_MALLOC && str.str)
_str_force_free(o_own, str.str);
}
else {
debug_assert(o_der);
TODO(" \
//TODO: Find top-level parent (traverse list of potentially derived `o_der->parent`s. \
//TODO: While doing so, compute the full offset that this slice has from the top owned parent. \
//TODO: Then, decrement its `refs` \
//TODO: Then, if it is marked as `orphan`, and we have decremented its `refs` to 0, free its `ptr`. \
");
}
}

@ -0,0 +1,23 @@
#include <string.h>
#include <ints.h>
#include <macros.h>
#include <tests.h>
DEFTEST(transmute_copy)
{
static const u64 EXPECTED = 18446743474098365025lu;
struct { char i[4]; i32 u; } input = { .i = {'a','b','c','d'}, .u = -140 };
let output = TRANSMUTE(input, u64);
TRACE("Transmute test, expected 0x%lx, got 0x%lx", EXPECTED, output);
//XXX: TODO: This test will fail on Big Endian machines.
TEST_ASSERT(output == EXPECTED);
TRACE("Alignment of output: %lu, alignment of input: %lu", _Alignof(output), _Alignof(input));
let input2 = TRANSMUTE(output, var(input));
TRACE("Transmute output passed, trying to transmute back");
TEST_ASSERT( memcmp(&input2, &input, sizeof(input)) == 0 );
return TEST_OK;
}
RUNTEST_DEBUG(transmute_copy);
Loading…
Cancel
Save