make leak-safe (hopefully)

This commit is contained in:
2026-03-05 15:54:00 +01:00
parent 03a2d9c70e
commit e4707d5264

109
cfg.h
View File

@@ -89,7 +89,7 @@ void string_append(string* a, string* b) {
string string_slice(string* s, size_t front, size_t back) { string string_slice(string* s, size_t front, size_t back) {
assert(front < back); assert(front < back);
assert(front < s->len); assert(front < s->len);
assert(back < s->len); assert(back <= s->len);
size_t len = back - front; size_t len = back - front;
string res = new_string_sized(len); string res = new_string_sized(len);
res.len = len; res.len = len;
@@ -387,10 +387,15 @@ string config_value_str(config_value* c, size_t indent) {
string_cappend(&res, "\n"); string_cappend(&res, "\n");
sub = config_str(c->section, indent+2); sub = config_str(c->section, indent+2);
string_append(&res, &sub); string_append(&res, &sub);
free_string(sub);
return res; return res;
case config_value_string: case config_value_string:
string_cappend(&res, "\""); string_cappend(&res, "\"");
string_append(&res, &c->s); for (i = 0; i < c->s.len; i++) {
if (c->s.str[i] == '"' || c->s.str[i] == '\\') string_cappend(&res, "\\");
char ch[2] = {c->s.str[i], '\0'};
string_cappend(&res, ch);
}
string_cappend(&res, "\""); string_cappend(&res, "\"");
break; break;
case config_value_number: case config_value_number:
@@ -467,6 +472,7 @@ string config_str(config* c, size_t indent) {
string_cappend(&res, "\n"); string_cappend(&res, "\n");
} }
free(keys.data);
return res; return res;
} }
@@ -509,9 +515,10 @@ typedef struct parsed_config {
bool config_parse_line(string* s, size_t* line, size_t* col) { bool config_parse_line(string* s, size_t* line, size_t* col) {
bool seen = false; bool seen = false;
while (*s->str == '\n') { while (s->len > 0 && *s->str == '\n') {
seen = true; seen = true;
s->str++; s->str++;
s->len--;
(*line)++; (*line)++;
*col = 1; *col = 1;
} }
@@ -520,8 +527,9 @@ bool config_parse_line(string* s, size_t* line, size_t* col) {
ssize_t config_parse_indent(string* s, size_t* col) { ssize_t config_parse_indent(string* s, size_t* col) {
ssize_t count = 0; ssize_t count = 0;
while (*s->str == ' ') { while (s->len > 0 && *s->str == ' ') {
s->str++; s->str++;
s->len--;
count++; count++;
(*col)++; (*col)++;
} }
@@ -542,43 +550,51 @@ parsed_config config_parse_internal(string*, size_t*, size_t*, size_t);
parsed_value config_parse_list(string* s, size_t* line, size_t* col, size_t indent, size_t consumed) { parsed_value config_parse_list(string* s, size_t* line, size_t* col, size_t indent, size_t consumed) {
parsed_value elem; parsed_value elem;
parsed_value err;
size_t ind; size_t ind;
list_config_value* l = malloc(sizeof(list_config_value)); list_config_value* l = malloc(sizeof(list_config_value));
*l =new_list_config_value(); *l =new_list_config_value();
while(true) { while(true) {
if (s->str[0] != ' ' && s->str[0] != '\n') return config_value_parse_error("expected space to start list element", *line, *col); if (s->len == 0) { err = config_value_parse_error("unexpected EOF in list", *line, *col); goto fail; }
if (s->str[0] == ' ') s->str++; if (s->str[0] != ' ' && s->str[0] != '\n') { err = config_value_parse_error("expected space to start list element", *line, *col); goto fail; }
if (s->str[0] == ' ') { s->str++; s->len--; }
consumed += 1; consumed += 1;
elem = config_parse_value(s, line, col, indent); elem = config_parse_value(s, line, col, indent);
if (!elem.ok) return elem; if (!elem.ok) { err = elem; goto fail; }
list_push_config_value(l, elem.v); list_push_config_value(l, elem.v);
consumed += elem.consumed; consumed += elem.consumed;
s->str += elem.consumed;
if (s->str[0] == '\0') break; if (s->len == 0 || s->str[0] == '\0') break;
if (!config_parse_line(s, line, col)) return config_value_parse_error("expected newline", *line, *col); if (!config_parse_line(s, line, col)) { err = config_value_parse_error("expected newline", *line, *col); goto fail; }
ind = config_parse_indent(s, col); ind = config_parse_indent(s, col);
if(ind < indent) { s->str -= ind; col -= ind; break; } if(ind < indent) { s->str -= ind + 1; s->len += ind + 1; *col -= ind; (*line)--; break; }
if (ind > indent) return config_value_parse_error("unexpected indent", *line, *col); if (ind > indent) { err = config_value_parse_error("unexpected indent", *line, *col); goto fail; }
consumed += ind; consumed += ind;
if (s->str[0] != '-') return config_value_parse_error("expected hyphen to start list element", *line, *col); if (s->len == 0 || s->str[0] != '-') { err = config_value_parse_error("expected hyphen to start list element", *line, *col); goto fail; }
s->str++; s->str++;
s->len--;
consumed += 1; consumed += 1;
} }
return (parsed_value){.ok=true, .consumed=consumed, .v=config_list(l)}; return (parsed_value){.ok=true, .consumed=consumed, .v=config_list(l)};
fail:
free_list_config_value(*l);
free(l);
return err;
} }
parsed_value config_parse_section(string* s, size_t* line, size_t* col, size_t indent, size_t consumed) { parsed_value config_parse_section(string* s, size_t* line, size_t* col, size_t indent, size_t consumed) {
config* res; config* res;
*col -= consumed; *col -= consumed;
s->str -= consumed; s->str -= consumed;
s->len += consumed;
parsed_config c = config_parse_internal(s, line, col, indent); parsed_config c = config_parse_internal(s, line, col, indent);
if (!c.ok) return (parsed_value){.ok=false, .consumed=c.consumed, .err=c.err}; if (!c.ok) return (parsed_value){.ok=false, .consumed=c.consumed, .err=c.err};
@@ -597,26 +613,38 @@ parsed_value config_parse_list_or_section(string* s, size_t* line, size_t* col,
if (ind < indent) return config_value_parse_error("expected greater indent", *line, *col); if (ind < indent) return config_value_parse_error("expected greater indent", *line, *col);
if (ind > indent) return config_value_parse_error("unexpected indent", *line, *col); if (ind > indent) return config_value_parse_error("unexpected indent", *line, *col);
if (s->str[0] == '-') { s->str++; return config_parse_list(s, line, col, indent, ind+1); } if (s->len == 0) return config_value_parse_error("unexpected EOF", *line, *col);
if (s->str[0] == '-') { s->str++; s->len--; return config_parse_list(s, line, col, indent, ind+1); }
return config_parse_section(s, line, col, indent, ind); return config_parse_section(s, line, col, indent, ind);
} }
parsed_value config_parse_string(string* s, size_t* line, size_t* col) { parsed_value config_parse_string(string* s, size_t* line, size_t* col) {
size_t i; size_t i;
string val = new_string();
for (i = 1; i < s->len; i++) { for (i = 1; i < s->len; i++) {
(*col)++; (*col)++;
if (s->str[i] == '\\') { if (s->str[i] == '\\') {
if (i +1 == s->len) return config_value_parse_error("Unexpected EOF, expected end of string", *line, *col); if (i +1 == s->len) { free_string(val); return config_value_parse_error("Unexpected EOF, expected end of string", *line, *col); }
i++; i++;
char ch[2] = {s->str[i], '\0'};
string_cappend(&val, ch);
} else if (s->str[i] == '"') { } else if (s->str[i] == '"') {
return (parsed_value){.ok=true, .consumed=i+1, .v=config_string(string_slice(s, 1, i))}; size_t consumed = i+1;
s->str += consumed;
s->len -= consumed;
return (parsed_value){.ok=true, .consumed=consumed, .v=config_string(val)};
} else if (s->str[i] == '\0') { } else if (s->str[i] == '\0') {
break; break;
} else if (s->str[i] == '\n') { } else if (s->str[i] == '\n') {
(*line)++; (*line)++;
*col = 0; *col = 0;
string_cappend(&val, "\n");
} else {
char ch[2] = {s->str[i], '\0'};
string_cappend(&val, ch);
} }
} }
free_string(val);
return config_value_parse_error("Unterminated string", *line, *col); return config_value_parse_error("Unterminated string", *line, *col);
} }
@@ -624,7 +652,7 @@ parsed_value config_parse_number(string* s, size_t* line, size_t* col) {
int i; int i;
ssize_t end = string_find_whitespace(s); ssize_t end = string_find_whitespace(s);
bool dot = false; bool dot = false;
string to_parse = string_slice(s, 0, end == -1 ? s->len-1 : end); string to_parse = string_slice(s, 0, end == -1 ? s->len : end);
for (i = 0; i < to_parse.len; i++) { for (i = 0; i < to_parse.len; i++) {
(*col)++; (*col)++;
@@ -635,18 +663,29 @@ parsed_value config_parse_number(string* s, size_t* line, size_t* col) {
} else if (to_parse.str[i] == '\0') { } else if (to_parse.str[i] == '\0') {
break; break;
} else { } else {
free_string(to_parse);
return config_value_parse_error("Expected number, got unparseable", *line, *col); return config_value_parse_error("Expected number, got unparseable", *line, *col);
} }
} }
char* to_parse_cstr = string_cstr(&to_parse);
double result = strtod(to_parse_cstr, NULL);
free(to_parse_cstr);
size_t to_parse_len = to_parse.len;
free_string(to_parse);
s->str += to_parse_len;
s->len -= to_parse_len;
return (parsed_value){ return (parsed_value){
.ok=true, .ok=true,
.consumed=to_parse.len, .consumed=to_parse_len,
.v=config_number(strtod(string_cstr(&to_parse), NULL)) .v=config_number(result)
}; };
} }
parsed_value config_parse_value(string* s, size_t* line, size_t* col, size_t indent) { parsed_value config_parse_value(string* s, size_t* line, size_t* col, size_t indent) {
if (s->len == 0) return config_value_parse_error("unexpected EOF, expected value", *line, *col);
if (s->str[0] == '"') return config_parse_string(s, line, col); if (s->str[0] == '"') return config_parse_string(s, line, col);
if (s->str[0] == '\n') return config_parse_list_or_section(s, line, col, indent+2); if (s->str[0] == '\n') return config_parse_list_or_section(s, line, col, indent+2);
return config_parse_number(s, line, col); return config_parse_number(s, line, col);
@@ -662,9 +701,10 @@ parsed_config config_parse_error(char* msg, size_t line, size_t col) {
} }
void config_parse_trim(string* s, size_t* line, size_t* col, size_t* consumed) { void config_parse_trim(string* s, size_t* line, size_t* col, size_t* consumed) {
while (*s->str == ' ' || *s->str == '\t') { while (s->len > 0 && (*s->str == ' ' || *s->str == '\t')) {
(*col)++; (*col)++;
s->str++; s->str++;
s->len--;
(*consumed)++; (*consumed)++;
} }
} }
@@ -678,34 +718,43 @@ parsed_config config_parse_internal(string* s, size_t* line, size_t* col, size_t
size_t consumed = 0; size_t consumed = 0;
map_string_config_value values = new_map_string_config_value(1024); map_string_config_value values = new_map_string_config_value(1024);
while(s->str[0] != '\0') { while(s->len > 0 && s->str[0] != '\0') {
if (s->str[0] == '\n') {
config_parse_line(s, line, col);
continue;
}
ind = config_parse_indent(s, col); ind = config_parse_indent(s, col);
if (ind > indent) return config_parse_error("unexpected indent", *line, *col); if (ind > indent) { res = config_parse_error("unexpected indent", *line, *col); goto fail; }
if (ind < indent) break; if (ind < indent) { s->str -= ind + 1; s->len += ind + 1; *col -= ind; (*line)--; break; }
consumed += ind; consumed += ind;
whitespace = string_find_whitespace(s); whitespace = string_find_whitespace(s);
if (whitespace == -1) return config_parse_error("expected whitespace, got EOF", *line, (*col)+s->len); if (whitespace == -1) { res = config_parse_error("expected whitespace, got EOF", *line, (*col)+s->len); goto fail; }
if (whitespace == 0) {
if (s->len == 0 || s->str[0] != '\n') { res = config_parse_error("unexpected whitespace", *line, *col); goto fail; }
config_parse_line(s, line, col); continue;
}
label = string_slice(s, 0, whitespace); label = string_slice(s, 0, whitespace);
s->str += whitespace; s->str += whitespace;
s->len -= whitespace;
*col += whitespace; *col += whitespace;
consumed += consumed; consumed += whitespace;
config_parse_trim(s, line, col, &consumed); config_parse_trim(s, line, col, &consumed);
val = config_parse_value(s, line, col, indent); val = config_parse_value(s, line, col, indent);
if (!val.ok) return (parsed_config){.ok=false, .err=val.err}; if (!val.ok) { free_string(label); res = (parsed_config){.ok=false, .err=val.err}; goto fail; }
s->str += val.consumed;
consumed += val.consumed; consumed += val.consumed;
map_put_string_config_value(&values, label, val.v); map_put_string_config_value(&values, label, val.v);
if (!config_parse_line(s, line, col) && s->str[0] != '\0') return config_parse_error("expected newline", *line, *col); if (!config_parse_line(s, line, col) && s->len > 0 && s->str[0] != '\0') { res = config_parse_error("expected newline", *line, *col); goto fail; }
consumed++; consumed++;
} }
@@ -713,6 +762,10 @@ parsed_config config_parse_internal(string* s, size_t* line, size_t* col, size_t
res.ok = true; res.ok = true;
res.consumed = consumed; res.consumed = consumed;
return res; return res;
fail:
free_map_string_config_value(values);
return res;
} }
parsed_config config_parse(string* s) { parsed_config config_parse(string* s) {