#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/param.h>
#include "util.h"
#include "strbuf.h"
static bool buf_realloc(struct strbuf *buf, size_t sz)
{
- void *tmp;
+ void *tmp = realloc(buf->heap ? buf->bytes : NULL, sz);
- tmp = realloc(buf->bytes, sz);
- if (sz > 0 && tmp == NULL)
- return false;
+ if (sz > 0) {
+ if (tmp == NULL)
+ return false;
+
+ if (!buf->heap)
+ memcpy(tmp, buf->bytes, MIN(buf->size, sz));
+ }
+ buf->heap = true;
buf->bytes = tmp;
buf->size = sz;
buf->bytes = NULL;
buf->size = 0;
buf->used = 0;
+ buf->heap = true;
}
void strbuf_release(struct strbuf *buf)
{
- free(buf->bytes);
+ if (buf->heap)
+ free(buf->bytes);
}
char *strbuf_steal(struct strbuf *buf)
#include <stdbool.h>
#include <stddef.h>
+#include <alloca.h>
#include "macro.h"
char *bytes;
size_t size;
size_t used;
+ bool heap;
};
+/*
+ * Declare and initialize strbuf without any initial storage
+ */
+#define DECLARE_STRBUF(name__) \
+ _cleanup_strbuf_ struct strbuf name__ = { \
+ .heap = true, \
+ }
+
+/*
+ * Declare and initialize strbuf with an initial buffer on stack. The @sz__ must be a
+ * build-time constant, as if the buffer had been declared on stack.
+ */
+#define DECLARE_STRBUF_WITH_STACK(name__, sz__) \
+ assert_cc(__builtin_constant_p(sz__)); \
+ char name__##_storage__[sz__]; \
+ _cleanup_strbuf_ struct strbuf name__ = { \
+ .bytes = name__##_storage__, \
+ .size = sz__, \
+ }
+
void strbuf_init(struct strbuf *buf);
void strbuf_release(struct strbuf *buf);
char *strbuf_steal(struct strbuf *buf);
/*
- * Return a C string owned by the buffer invalidated if the buffer is
- * changed).
+ * Return a C string owned by the buffer. It becomes an invalid
+ * pointer if strbuf is changed. It may also not survive a return
+ * from current function if it was initialized with stack space
*/
const char *strbuf_str(struct strbuf *buf);
}
DEFINE_TEST(test_strbuf_steal, .description = "test strbuf_steal with cleanup");
+static int test_strbuf_with_stack(const struct test *t)
+{
+ const char test[] = "test-something-small";
+ const char *stack_buf;
+ char *p;
+ DECLARE_STRBUF_WITH_STACK(buf, 256);
+ DECLARE_STRBUF_WITH_STACK(buf2, sizeof(test) + 1);
+ DECLARE_STRBUF_WITH_STACK(buf3, sizeof(test) + 1);
+
+ strbuf_pushchars(&buf, test);
+ assert_return(streq(test, strbuf_str(&buf)), EXIT_FAILURE);
+ p = strbuf_steal(&buf);
+ assert_return(streq(test, p), EXIT_FAILURE);
+ free(p);
+
+ strbuf_pushchars(&buf2, test);
+ assert_return(streq(test, strbuf_str(&buf2)), EXIT_FAILURE);
+ /* It fits on stack, but when we steal, we get a copy on heap */
+ p = strbuf_steal(&buf2);
+ assert_return(streq(test, p), EXIT_FAILURE);
+ free(p);
+
+ /*
+ * Check assumption about buffer being on stack vs heap is indeed valid.
+ * Not to be done in real code.
+ */
+ strbuf_clear(&buf3);
+ stack_buf = buf3.bytes;
+ strbuf_pushchars(&buf3, test);
+ assert_return(stack_buf == buf3.bytes, EXIT_FAILURE);
+
+ assert_return(streq(test, strbuf_str(&buf3)), EXIT_FAILURE);
+ assert_return(stack_buf == buf3.bytes, EXIT_FAILURE);
+
+ strbuf_pushchars(&buf3, "-overflow");
+ assert_return(stack_buf != buf3.bytes, EXIT_FAILURE);
+
+ return 0;
+}
+DEFINE_TEST(test_strbuf_with_stack, .description = "test strbuf with stack");
+
+static int test_strbuf_with_heap(const struct test *t)
+{
+ DECLARE_STRBUF(heapbuf);
+
+ assert_return(heapbuf.bytes == NULL, EXIT_FAILURE);
+ assert_return(heapbuf.size == 0, EXIT_FAILURE);
+ assert_return(heapbuf.used == 0, EXIT_FAILURE);
+ strbuf_pushchars(&heapbuf, "-overflow");
+ assert_return(heapbuf.bytes != NULL, EXIT_FAILURE);
+ assert_return(heapbuf.size != 0, EXIT_FAILURE);
+ assert_return(heapbuf.used != 0, EXIT_FAILURE);
+
+ return 0;
+}
+DEFINE_TEST(test_strbuf_with_heap, .description = "test strbuf with heap only");
+
TESTSUITE_MAIN();