fix: replace unsafe fixed-size buffers with dynamic formatting helpers; add util format helpers; centralize log_on_line cleanup

This commit is contained in:
2026-04-26 22:39:55 +02:00
parent 70998643fb
commit 05dfb3725b
9 changed files with 156 additions and 45 deletions
+2 -2
View File
@@ -4,7 +4,7 @@
#ifndef LOCATION_H #ifndef LOCATION_H
#define LOCATION_H #define LOCATION_H
#include "string.h" #include "str.h"
#include <stddef.h> #include <stddef.h>
@@ -25,4 +25,4 @@ typedef struct {
int column_end; int column_end;
} Location; } Location;
#endif #endif
+29 -18
View File
@@ -3,6 +3,7 @@
#include <string.h> #include <string.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdarg.h> #include <stdarg.h>
#include "util.h"
static LogError* s_logError = NULL; 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, ...) { void log_on_line(Location* loc, int to_column, const char* msg, ...) {
char line_prefix[32]; char* line_prefix = NULL;
int prefix_len = snprintf(line_prefix, sizeof(line_prefix), "%d| ", loc->line); 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; int caret_len = to_column - loc->column_start + 1;
if (caret_len < 1) caret_len = 1; if (caret_len < 1) caret_len = 1;
// Format the message // Format the message
va_list args; va_list args;
va_start(args, msg); va_start(args, msg);
char formatted_msg[256]; formatted_msg = format_string_va(msg, args);
vsnprintf(formatted_msg, sizeof(formatted_msg), msg, args);
va_end(args); va_end(args);
if (!formatted_msg) goto cleanup;
// Header logic // Header logic
char header[512];
if (loc->filename && loc->filename[0] != '\0') { if (loc->filename && loc->filename[0] != '\0') {
sprintf(header, "--- %s ---\n", loc->filename); header = format_string("--- %s ---\n", loc->filename);
} else { } else {
sprintf(header, "--- \n"); header = format_string("--- \n");
} }
if (!header) goto cleanup;
size_t total_size = strlen(header) + 20 + size_t total_size = strlen(header) + 20 +
prefix_len + loc->line_text.length + 2 + // line| text\n strlen(line_prefix) + loc->line_text.length + 2 + // line| text\n
prefix_len + loc->column_start - 1 + caret_len + 2 + // indent + ^^\n strlen(line_prefix) + loc->column_start - 1 + caret_len + 2 + // indent + ^^\n
prefix_len + 3 + strlen(formatted_msg) + 2 + // indent + msg\n strlen(line_prefix) + 3 + strlen(formatted_msg) + 2 + // indent + msg\n
100; 10;
char* buffer = (char*)malloc(total_size); buffer = (char*)malloc(total_size);
if (!buffer) return; if (!buffer) goto cleanup;
char* p = buffer; char* p = buffer;
p += sprintf(p, "%s", header); p += sprintf(p, "%s", header);
p += sprintf(p, "%s%.*s\n", line_prefix, (int)loc->line_text.length, loc->line_text.data); p += sprintf(p, "%s%.*s\n", line_prefix, (int)loc->line_text.length, loc->line_text.data);
// Caret line // 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++ = '^'; for (int i = 0; i < caret_len; i++) *p++ = '^';
*p++ = '\n'; *p++ = '\n';
// Message line // 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 += sprintf(p, "%s\n", formatted_msg);
*p = '\0'; *p = '\0';
log_error(buffer); log_error(buffer);
cleanup:
free(line_prefix);
free(formatted_msg);
free(header);
free(buffer); free(buffer);
} }
+17
View File
@@ -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 <stddef.h>
/**
* A simple string structure that holds a pointer to the character data and its length.
*/
typedef struct {
char* data;
size_t length;
} String;
#endif
+24 -9
View File
@@ -3,6 +3,7 @@
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <stdlib.h> #include <stdlib.h>
#include "util.h"
static jmp_buf s_testJmp; static jmp_buf s_testJmp;
static const char* s_failMsg; static const char* s_failMsg;
@@ -49,8 +50,11 @@ char* read_file_content(const char* filepath) {
} }
void assert_log_file(const char* msg) { void assert_log_file(const char* msg) {
char filepath[256]; char* filepath = format_string("v0/tests/%s.log", s_currentTestName);
snprintf(filepath, sizeof(filepath), "v0/tests/%s.log", s_currentTestName); if (!filepath) {
fail("out of memory");
return;
}
const char* generate = getenv("GENERATE_GOLDEN"); const char* generate = getenv("GENERATE_GOLDEN");
if (generate && strcmp(generate, "1") == 0) { if (generate && strcmp(generate, "1") == 0) {
@@ -67,18 +71,24 @@ void assert_log_file(const char* msg) {
char* content = read_file_content(filepath); char* content = read_file_content(filepath);
if (!content) { if (!content) {
fail("could not open golden file for reading"); fail("could not open golden file for reading");
free(filepath);
return; return;
} }
assert_str(content, s_logOutput, msg); assert_str(content, s_logOutput, msg);
free(content); free(content);
free(filepath);
} }
void assert_int(int expected, int actual, const char* msg) { void assert_int(int expected, int actual, const char* msg) {
if (expected != actual) { if (expected != actual) {
char buf[64]; char* buf = format_string("%s (expected %d, got %d)", msg, expected, actual);
snprintf(buf, sizeof(buf), "%s (expected %d, got %d)", msg, expected, actual); if (buf) {
fail(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) { TokenStream* tokenstream_get_test(void) {
char filepath[256]; char* filepath = format_string("v0/tests/%s.c2", s_currentTestName);
snprintf(filepath, sizeof(filepath), "v0/tests/%s.c2", s_currentTestName); if (!filepath) {
fail("out of memory");
return NULL;
}
if (s_testSource) free(s_testSource); if (s_testSource) free(s_testSource);
s_testSource = read_file_content(filepath); s_testSource = read_file_content(filepath);
if (!s_testSource) { if (!s_testSource) {
fail("could not read test source file"); fail("could not read test source file");
free(filepath);
return NULL; return NULL;
} }
TokenStream* ts = tokenstream_open(filepath, s_testSource);
return tokenstream_open(filepath, s_testSource); free(filepath);
return ts;
} }
static void log_append(const char* msg) { static void log_append(const char* msg) {
+11 -6
View File
@@ -1,23 +1,28 @@
#include "test.h" #include "test.h"
#include "log.h" #include "log.h"
#include <string.h> #include <string.h>
#include <stdlib.h>
#include "util.h"
static char s_lastLoggedError[256]; static char* s_lastLoggedError = NULL;
static void mock_log(const char* msg) { static void mock_log(const char* msg) {
strncpy(s_lastLoggedError, msg, sizeof(s_lastLoggedError) - 1); free(s_lastLoggedError);
s_lastLoggedError[sizeof(s_lastLoggedError) - 1] = '\0'; s_lastLoggedError = format_string("%s", msg ? msg : "");
} }
static void test_log_error(void) { static void test_log_error(void) {
log_set_output(mock_log); log_set_output(mock_log);
memset(s_lastLoggedError, 0, sizeof(s_lastLoggedError)); free(s_lastLoggedError);
s_lastLoggedError = NULL;
log_error("test error message"); log_error("test error message");
assert_str("test error message", s_lastLoggedError, "expected 'test error message'"); assert_str("test error message", s_lastLoggedError, "expected 'test error message'");
log_set_output(NULL); // Reset to default log_set_output(NULL); // Reset to default
free(s_lastLoggedError);
s_lastLoggedError = NULL;
} }
static void test_log_on_line(void) { static void test_log_on_line(void) {
+14 -9
View File
@@ -1,6 +1,7 @@
#include "test.h" #include "test.h"
#include "token.h" #include "token.h"
#include <string.h> #include <string.h>
#include <stdlib.h>
static void test_tokenstream_open_fail(void) { static void test_tokenstream_open_fail(void) {
TokenStream* ts = tokenstream_open(NULL, NULL); TokenStream* ts = tokenstream_open(NULL, NULL);
@@ -97,22 +98,26 @@ static void test_tokenstream_info(void) {
Token t1 = tokenstream_next(ts); Token t1 = tokenstream_next(ts);
if (t1.token != TOKEN_MODULE) fail("expected TOKEN_MODULE"); if (t1.token != TOKEN_MODULE) fail("expected TOKEN_MODULE");
char buf1[32]; char* buf1 = malloc((size_t)t1.text.length + 1);
memcpy(buf1, t1.text.data, t1.text.length); if (!buf1) fail("out of memory");
buf1[t1.text.length] = '\0'; memcpy(buf1, t1.text.data, t1.text.length);
assert_str("module", buf1, "info: expected 'module'"); buf1[t1.text.length] = '\0';
assert_str("module", buf1, "info: expected 'module'");
if (t1.location.line != 1) fail("expected line 1"); if (t1.location.line != 1) fail("expected line 1");
if (t1.location.column_start != 1) fail("expected column 1"); if (t1.location.column_start != 1) fail("expected column 1");
Token t2 = tokenstream_next(ts); Token t2 = tokenstream_next(ts);
if (t2.token != TOKEN_IDENTIFIER) fail("expected TOKEN_IDENTIFIER"); if (t2.token != TOKEN_IDENTIFIER) fail("expected TOKEN_IDENTIFIER");
char buf2[32]; char* buf2 = malloc((size_t)t2.text.length + 1);
memcpy(buf2, t2.text.data, t2.text.length); if (!buf2) { free(buf1); fail("out of memory"); }
buf2[t2.text.length] = '\0'; memcpy(buf2, t2.text.data, t2.text.length);
assert_str("main", buf2, "info: expected 'main'"); buf2[t2.text.length] = '\0';
assert_str("main", buf2, "info: expected 'main'");
if (t2.location.line != 1) fail("expected line 1"); if (t2.location.line != 1) fail("expected line 1");
if (t2.location.column_start != 8) fail("expected column 8"); if (t2.location.column_start != 8) fail("expected column 8");
tokenstream_close(ts); free(buf1);
free(buf2);
tokenstream_close(ts);
} }
+5 -1
View File
@@ -124,7 +124,11 @@ TokenStream* tokenstream_open(const char* filename, const char* code) {
return NULL; 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->code = code;
ts->pos = 0; ts->pos = 0;
ts->line = 1; ts->line = 1;
+27
View File
@@ -0,0 +1,27 @@
#include "util.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
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;
}
+27
View File
@@ -0,0 +1,27 @@
#ifndef UTIL_H
#define UTIL_H
#include <stdarg.h>
#include <stddef.h>
/**
* 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