From 05dfb3725bd41c1f6996488fe239674212b0d1cb Mon Sep 17 00:00:00 2001 From: Sebastiaan de Schaetzen Date: Sun, 26 Apr 2026 22:39:55 +0200 Subject: [PATCH] fix: replace unsafe fixed-size buffers with dynamic formatting helpers; add util format helpers; centralize log_on_line cleanup --- v0/location.h | 4 ++-- v0/log.c | 47 +++++++++++++++++++++++++++++------------------ v0/str.h | 17 +++++++++++++++++ v0/test.c | 33 ++++++++++++++++++++++++--------- v0/test_log.c | 17 +++++++++++------ v0/test_token.c | 23 ++++++++++++++--------- v0/token.c | 6 +++++- v0/util.c | 27 +++++++++++++++++++++++++++ v0/util.h | 27 +++++++++++++++++++++++++++ 9 files changed, 156 insertions(+), 45 deletions(-) create mode 100644 v0/str.h create mode 100644 v0/util.c create mode 100644 v0/util.h diff --git a/v0/location.h b/v0/location.h index 4aa60c2..21d93c2 100644 --- a/v0/location.h +++ b/v0/location.h @@ -4,7 +4,7 @@ #ifndef LOCATION_H #define LOCATION_H -#include "string.h" +#include "str.h" #include @@ -25,4 +25,4 @@ typedef struct { int column_end; } Location; -#endif \ No newline at end of file +#endif diff --git a/v0/log.c b/v0/log.c index c900f94..b6783e0 100644 --- a/v0/log.c +++ b/v0/log.c @@ -3,6 +3,7 @@ #include #include #include +#include "util.h" static LogError* s_logError = NULL; @@ -19,51 +20,61 @@ void log_error(const char* msg) { } void log_on_line(Location* loc, int to_column, const char* msg, ...) { - char line_prefix[32]; - int prefix_len = snprintf(line_prefix, sizeof(line_prefix), "%d| ", loc->line); - + char* line_prefix = NULL; + char* formatted_msg = NULL; + char* header = NULL; + char* buffer = NULL; + + line_prefix = format_string("%d| ", loc->line); + if (!line_prefix) goto cleanup; + int caret_len = to_column - loc->column_start + 1; if (caret_len < 1) caret_len = 1; // Format the message va_list args; va_start(args, msg); - char formatted_msg[256]; - vsnprintf(formatted_msg, sizeof(formatted_msg), msg, args); + formatted_msg = format_string_va(msg, args); va_end(args); + if (!formatted_msg) goto cleanup; // Header logic - char header[512]; if (loc->filename && loc->filename[0] != '\0') { - sprintf(header, "--- %s ---\n", loc->filename); + header = format_string("--- %s ---\n", loc->filename); } else { - sprintf(header, "--- \n"); + header = format_string("--- \n"); } + if (!header) goto cleanup; size_t total_size = strlen(header) + 20 + - prefix_len + loc->line_text.length + 2 + // line| text\n - prefix_len + loc->column_start - 1 + caret_len + 2 + // indent + ^^\n - prefix_len + 3 + strlen(formatted_msg) + 2 + // indent + msg\n - 100; + strlen(line_prefix) + loc->line_text.length + 2 + // line| text\n + strlen(line_prefix) + loc->column_start - 1 + caret_len + 2 + // indent + ^^\n + strlen(line_prefix) + 3 + strlen(formatted_msg) + 2 + // indent + msg\n + 10; - char* buffer = (char*)malloc(total_size); - if (!buffer) return; + buffer = (char*)malloc(total_size); + if (!buffer) goto cleanup; char* p = buffer; p += sprintf(p, "%s", header); p += sprintf(p, "%s%.*s\n", line_prefix, (int)loc->line_text.length, loc->line_text.data); - + // Caret line - for (int i = 0; i < prefix_len + loc->column_start - 1; i++) *p++ = ' '; + for (int i = 0; i < (int)(strlen(line_prefix) + loc->column_start - 1); i++) *p++ = ' '; for (int i = 0; i < caret_len; i++) *p++ = '^'; *p++ = '\n'; // Message line - for (int i = 0; i < prefix_len; i++) *p++ = ' '; + for (size_t i = 0; i < strlen(line_prefix); i++) *p++ = ' '; p += sprintf(p, "%s\n", formatted_msg); - + *p = '\0'; log_error(buffer); + +cleanup: + free(line_prefix); + free(formatted_msg); + free(header); free(buffer); } diff --git a/v0/str.h b/v0/str.h new file mode 100644 index 0000000..52ea019 --- /dev/null +++ b/v0/str.h @@ -0,0 +1,17 @@ +/** + * Contains the definition of the String structure, which is a simple representation of a string in C. + */ +#ifndef STR_H +#define STR_H + +#include + +/** + * A simple string structure that holds a pointer to the character data and its length. + */ +typedef struct { + char* data; + size_t length; +} String; + +#endif diff --git a/v0/test.c b/v0/test.c index 2f23397..c152e9d 100644 --- a/v0/test.c +++ b/v0/test.c @@ -3,6 +3,7 @@ #include #include #include +#include "util.h" static jmp_buf s_testJmp; static const char* s_failMsg; @@ -49,8 +50,11 @@ char* read_file_content(const char* filepath) { } void assert_log_file(const char* msg) { - char filepath[256]; - snprintf(filepath, sizeof(filepath), "v0/tests/%s.log", s_currentTestName); + char* filepath = format_string("v0/tests/%s.log", s_currentTestName); + if (!filepath) { + fail("out of memory"); + return; + } const char* generate = getenv("GENERATE_GOLDEN"); if (generate && strcmp(generate, "1") == 0) { @@ -67,18 +71,24 @@ void assert_log_file(const char* msg) { char* content = read_file_content(filepath); if (!content) { fail("could not open golden file for reading"); + free(filepath); return; } assert_str(content, s_logOutput, msg); free(content); + free(filepath); } void assert_int(int expected, int actual, const char* msg) { if (expected != actual) { - char buf[64]; - snprintf(buf, sizeof(buf), "%s (expected %d, got %d)", msg, expected, actual); - fail(buf); + char* buf = format_string("%s (expected %d, got %d)", msg, expected, actual); + if (buf) { + fail(buf); + free(buf); + } else { + fail("out of memory"); + } } } @@ -95,17 +105,22 @@ void assert_false(bool condition, const char* msg) { } TokenStream* tokenstream_get_test(void) { - char filepath[256]; - snprintf(filepath, sizeof(filepath), "v0/tests/%s.c2", s_currentTestName); + char* filepath = format_string("v0/tests/%s.c2", s_currentTestName); + if (!filepath) { + fail("out of memory"); + return NULL; + } if (s_testSource) free(s_testSource); s_testSource = read_file_content(filepath); if (!s_testSource) { fail("could not read test source file"); + free(filepath); return NULL; } - - return tokenstream_open(filepath, s_testSource); + TokenStream* ts = tokenstream_open(filepath, s_testSource); + free(filepath); + return ts; } static void log_append(const char* msg) { diff --git a/v0/test_log.c b/v0/test_log.c index 4682111..48ef572 100644 --- a/v0/test_log.c +++ b/v0/test_log.c @@ -1,23 +1,28 @@ #include "test.h" #include "log.h" #include +#include +#include "util.h" -static char s_lastLoggedError[256]; +static char* s_lastLoggedError = NULL; static void mock_log(const char* msg) { - strncpy(s_lastLoggedError, msg, sizeof(s_lastLoggedError) - 1); - s_lastLoggedError[sizeof(s_lastLoggedError) - 1] = '\0'; + free(s_lastLoggedError); + s_lastLoggedError = format_string("%s", msg ? msg : ""); } static void test_log_error(void) { log_set_output(mock_log); - - memset(s_lastLoggedError, 0, sizeof(s_lastLoggedError)); + + free(s_lastLoggedError); + s_lastLoggedError = NULL; log_error("test error message"); - + assert_str("test error message", s_lastLoggedError, "expected 'test error message'"); log_set_output(NULL); // Reset to default + free(s_lastLoggedError); + s_lastLoggedError = NULL; } static void test_log_on_line(void) { diff --git a/v0/test_token.c b/v0/test_token.c index e57a957..6790702 100644 --- a/v0/test_token.c +++ b/v0/test_token.c @@ -1,6 +1,7 @@ #include "test.h" #include "token.h" #include +#include static void test_tokenstream_open_fail(void) { TokenStream* ts = tokenstream_open(NULL, NULL); @@ -97,22 +98,26 @@ static void test_tokenstream_info(void) { Token t1 = tokenstream_next(ts); if (t1.token != TOKEN_MODULE) fail("expected TOKEN_MODULE"); - char buf1[32]; - memcpy(buf1, t1.text.data, t1.text.length); - buf1[t1.text.length] = '\0'; - assert_str("module", buf1, "info: expected 'module'"); + char* buf1 = malloc((size_t)t1.text.length + 1); + if (!buf1) fail("out of memory"); + memcpy(buf1, t1.text.data, t1.text.length); + buf1[t1.text.length] = '\0'; + assert_str("module", buf1, "info: expected 'module'"); if (t1.location.line != 1) fail("expected line 1"); if (t1.location.column_start != 1) fail("expected column 1"); Token t2 = tokenstream_next(ts); if (t2.token != TOKEN_IDENTIFIER) fail("expected TOKEN_IDENTIFIER"); - char buf2[32]; - memcpy(buf2, t2.text.data, t2.text.length); - buf2[t2.text.length] = '\0'; - assert_str("main", buf2, "info: expected 'main'"); + char* buf2 = malloc((size_t)t2.text.length + 1); + if (!buf2) { free(buf1); fail("out of memory"); } + memcpy(buf2, t2.text.data, t2.text.length); + buf2[t2.text.length] = '\0'; + assert_str("main", buf2, "info: expected 'main'"); if (t2.location.line != 1) fail("expected line 1"); if (t2.location.column_start != 8) fail("expected column 8"); - tokenstream_close(ts); + free(buf1); + free(buf2); + tokenstream_close(ts); } diff --git a/v0/token.c b/v0/token.c index aa4ac78..69f706a 100644 --- a/v0/token.c +++ b/v0/token.c @@ -124,7 +124,11 @@ TokenStream* tokenstream_open(const char* filename, const char* code) { return NULL; } - ts->filename = strdup(filename ? filename : "unknown"); + const char* name_src = filename ? filename : "unknown"; + ts->filename = malloc(strlen(name_src) + 1); + if (ts->filename) { + memcpy(ts->filename, name_src, strlen(name_src) + 1); + } ts->code = code; ts->pos = 0; ts->line = 1; diff --git a/v0/util.c b/v0/util.c new file mode 100644 index 0000000..0939a07 --- /dev/null +++ b/v0/util.c @@ -0,0 +1,27 @@ +#include "util.h" +#include +#include +#include + +char* format_string_va(const char* fmt, va_list args) { + if (!fmt) return NULL; + va_list args_copy; + va_copy(args_copy, args); + int needed = vsnprintf(NULL, 0, fmt, args_copy); + va_end(args_copy); + if (needed < 0) return NULL; + + char* buf = (char*)malloc((size_t)needed + 1); + if (!buf) return NULL; + vsnprintf(buf, (size_t)needed + 1, fmt, args); + return buf; +} + +char* format_string(const char* fmt, ...) { + if (!fmt) return NULL; + va_list args; + va_start(args, fmt); + char* s = format_string_va(fmt, args); + va_end(args); + return s; +} diff --git a/v0/util.h b/v0/util.h new file mode 100644 index 0000000..d998a77 --- /dev/null +++ b/v0/util.h @@ -0,0 +1,27 @@ +#ifndef UTIL_H +#define UTIL_H + +#include +#include + +/** + * Formats a string using printf-style formatting and returns a newly allocated string. + * The caller is responsible for freeing the returned string. + * + * @param fmt The format string. + * @param ... The values to format. + * @return A newly allocated string containing the formatted output. + */ +char* format_string(const char* fmt, ...); + +/** + * Formats a string using printf-style formatting with a va_list and returns a newly allocated string. + * The caller is responsible for freeing the returned string. + * + * @param fmt The format string. + * @param args The va_list of values to format. + * @return A newly allocated string containing the formatted output. + */ +char* format_string_va(const char* fmt, va_list args); + +#endif