fix: replace unsafe fixed-size buffers with dynamic formatting helpers; add util format helpers; centralize log_on_line cleanup
This commit is contained in:
+1
-1
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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,8 +20,13 @@ 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;
|
||||||
@@ -28,42 +34,47 @@ void log_on_line(Location* loc, int to_column, const char* msg, ...) {
|
|||||||
// 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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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) {
|
||||||
|
|||||||
+9
-4
@@ -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
@@ -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
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
Reference in New Issue
Block a user