Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
85 changes: 85 additions & 0 deletions 0xtest/unit/test_str_builder.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*!
* @copyright
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT license.
*/

#include "0xc/std/str_builder.h"
#include "0xc/sys/check.h"
#include "0xc/sys/unit.h"

#include <string.h>

static void
UNIT_TEST_FUNC(str_builder_init)(
__unused const unit_t *un)
{
char buff[10] = "";
xc_str_builder_t sb;
bool result = false;

result = xc_str_builder_init(&sb, buff, sizeof(buff));
checkint("valid initialization succeeds", result, true);
checkint("length is initialized to 0", sb.length, 0);
checkint("capacity is correctly set", sb.capacity, 10);

result = xc_str_builder_init(NULL, buff, 10);
checkint("null builder fails safely", result, false);

result = xc_str_builder_init(&sb, NULL, 10);
checkint("null buffer fails safely", result, false);

result = xc_str_builder_init(&sb, buff, 0);
checkint("zero capacity fails safely", result, false);
}

static void
UNIT_TEST_FUNC(str_builder_append_overflow)(
__unused const unit_t *un)
{
char buff[12] = "";
xc_str_builder_t sb;
bool result = false;

xc_str_builder_init(&sb, buff, sizeof(buff));

result = xc_str_builder_append(&sb, "Hello");
checkint("normal append succeeds", result, true);
checkint("length updated correctly", sb.length, 5);
checkcstr("buffer contains Hello", sb.buffer, "Hello");

result = xc_str_builder_append(&sb, " World");
checkint("second append succeeds", result, true);
checkint("length updated to 11", sb.length, 11);
checkcstr("buffer contains Hello World", sb.buffer, "Hello World");

/* OVERFLOW TEST: Capacity is 12. Length is 11. */
result = xc_str_builder_append(&sb, "!");
checkint("overflow append fails safely", result, false);
checkint("length remains 11", sb.length, 11);
checkcstr("buffer untouched by overflow", sb.buffer, "Hello World");
}

static void
UNIT_TEST_FUNC(str_builder_format_truncation)(
__unused const unit_t *un)
{
char buff[16] = "";
xc_str_builder_t sb;
bool result = false;

xc_str_builder_init(&sb, buff, sizeof(buff));

result = xc_str_builder_appendf(&sb, "Age: %d", 25);
checkint("formatted append succeeds", result, true);
checkcstr("buffer formatted correctly", sb.buffer, "Age: 25");

/* TRUNCATION TEST: Try to format a string that exceeds remaining space */
result = xc_str_builder_appendf(&sb, " is a very long string");
checkint("truncating format fails safely", result, false);
checkcstr("buffer untouched by truncation", sb.buffer, "Age: 25");
}

UNIT_TEST(str_builder_init);
UNIT_TEST(str_builder_append_overflow);
UNIT_TEST(str_builder_format_truncation);
130 changes: 130 additions & 0 deletions src/0xc/std/str_builder.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/*!
* @copyright
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT license.
*/

#include "str_builder.h"
#include <stdio.h>
#include <string.h>

bool xc_str_builder_init(
xc_str_builder_t *sb,
char *buffer,
size_t capacity )
{
if (!sb || !buffer || !capacity) {
return false;
}

sb->buffer = buffer;
sb->capacity = capacity;
sb->length = 0;
sb->buffer[0] = '\0';

return true;
}

bool xc_str_builder_append(
xc_str_builder_t *sb,
const char *suffix )
{
if (!sb || !suffix) {
return false;
}

size_t suffix_len = strlen(suffix);
if (sb->length + suffix_len >= sb->capacity) {
return false;
}

memcpy(sb->buffer + sb->length, suffix, suffix_len);
sb->length += suffix_len;
sb->buffer[sb->length] = '\0';

return true;
}

bool xc_str_builder_appendf(
xc_str_builder_t *sb,
const char *format,
... )
{
if (!sb || !format) {
return false;
}

size_t remaining = sb->capacity - sb->length;
if (!remaining) {
return false;
}

va_list args;
va_start(args, format);

int written = vsnprintf(sb->buffer + sb->length, remaining, format, args);

va_end(args);

if (written < 0) {
return false;
}

// If it truncated, UNDO the partial write!
if ((size_t)written >= remaining) {
sb->buffer[sb->length] = '\0';
return false;
}

sb->length += (size_t)written;

return true;
}

bool xc_str_builder_append_char(
xc_str_builder_t *sb,
char c )
{
if (!sb) {
return false;
}

if (sb->length + 1 >= sb->capacity) {
return false;
}

sb->buffer[sb->length] = c;
sb->length++;
sb->buffer[sb->length] = '\0';

return true;
}

void xc_str_builder_clear(
xc_str_builder_t *sb )
{
if (!sb) {
return;
}

sb->length = 0;
sb->buffer[0] = '\0';
}

bool xc_str_builder_truncate(
xc_str_builder_t *sb,
size_t new_length )
{
if (!sb) {
return false;
}

if (new_length > sb->length) {
return false;
}

sb->length = new_length;
sb->buffer[sb->length] = '\0';

return true;
}
145 changes: 145 additions & 0 deletions src/0xc/std/str_builder.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/*!
* @copyright
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT license.
*
* @header
* Bounds-tracked string builder functions.
*/
#ifndef __ZX_STD_STR_BUILDER_H
#define __ZX_STD_STR_BUILDER_H

#include <0xc/std/api.h>

// IWYU pragma: begin_exports
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
// IWYU pragma: end_exports

__API_HEADER_BEGIN(c, nonnull, single)

// MARK: Public

/*!
* @typedef xc_str_builder_t
* Wraps a statically-allocated character array to provide bounds-tracked
* string building and concatenation.
*/
typedef struct {
char *buffer;
size_t capacity;
size_t length;
} xc_str_builder_t;

/*!
* @function xc_str_builder_init
* Initializes the builder, wrapping an existing buffer safely.
*
* @param SB
* The string builder instance to initialize.
*
* @param BUFFER
* The statically-allocated destination buffer.
*
* @param CAPACITY
* The total capacity of the buffer (including the null terminator).
*
* @result
* Returns true if successfully initialized, false if arguments are invalid.
*/
bool xc_str_builder_init(
xc_str_builder_t *sb,
char *buffer,
size_t capacity
);

/*!
* @function xc_str_builder_append
* Appends a null-terminated string to the builder.
*
* @param SB
* The string builder instance.
*
* @param SUFFIX
* The null-terminated string to append.
*
* @result
* Returns true if the string was successfully appended. Returns false if the
* operation would exceed the buffer's capacity.
*/
bool xc_str_builder_append(
xc_str_builder_t *sb,
const char *suffix
);

/*!
* @function xc_str_builder_appendf
* Safely formats and appends data without silent truncation.
*
* @param SB
* The string builder instance.
*
* @param FORMAT
* The format string, followed by format arguments.
*
* @result
* Returns true if the formatted string was completely appended. Returns false
* if truncation occurred or arguments were invalid, leaving the buffer intact.
*/
bool xc_str_builder_appendf(
xc_str_builder_t *sb,
const char *format,
...
);

/*!
* @function xc_str_builder_append_char
* Appends a single character safely using O(1) performance.
*
* @param SB
* The string builder instance.
*
* @param C
* The character to append.
*
* @result
* Returns true if the character was appended, false if capacity was exceeded.
*/
bool xc_str_builder_append_char(
xc_str_builder_t *sb,
char c
);

/*!
* @function xc_str_builder_clear
* Resets the string builder length to 0, allowing reuse of the buffer.
*
* @param SB
* The string builder instance.
*/
void xc_str_builder_clear(
xc_str_builder_t *sb
);

/*!
* @function xc_str_builder_truncate
* Safely shortens the string to the specified length.
*
* @param SB
* The string builder instance.
*
* @param NEW_LENGTH
* The length to shorten the string to.
*
* @result
* Returns true on success, false if the new length exceeds current length.
*/
bool xc_str_builder_truncate(
xc_str_builder_t *sb,
size_t new_length
);

__API_HEADER_END(c, nonnull, single)

#endif // __ZX_STD_STR_BUILDER_H