fix: prevent memory leaks in module parser

This commit is contained in:
Fernando Araoz 2024-12-16 07:10:06 -05:00
parent 81e38ab8a8
commit 5337ba48a4
2 changed files with 19 additions and 12 deletions

View File

@ -17,36 +17,32 @@ pub const Module = struct {
pub fn init(target: *@This(), tokens: *const TokenStream, pos: usize, allocator: std.mem.Allocator) ParseError!void { pub fn init(target: *@This(), tokens: *const TokenStream, pos: usize, allocator: std.mem.Allocator) ParseError!void {
var arrl = std.ArrayList(*statement.Statement).init(allocator); var arrl = std.ArrayList(*statement.Statement).init(allocator);
errdefer arrl.deinit(); errdefer arrl.deinit();
errdefer for (arrl.items) |i| {
i.deinit();
allocator.destroy(i);
};
const input_len = tokens.items.len; const input_len = tokens.items.len;
var current_pos = pos; var current_pos = pos;
// parse many statements // parse many statements
while (current_pos < input_len) { while (current_pos < input_len) {
// FIXME: if a statement was added to the array list,
// and then one of these fails,
// will all previous statements leak memory?
var stmt = try allocator.create(statement.Statement); var stmt = try allocator.create(statement.Statement);
errdefer allocator.destroy(stmt); errdefer allocator.destroy(stmt);
const next_pos = try stmt.init(tokens, current_pos, allocator); const next_pos = try stmt.init(tokens, current_pos, allocator);
current_pos = next_pos; current_pos = next_pos;
arrl.append(stmt) catch { try arrl.append(stmt);
// TODO: free stmt if this fails
return ParseError.Error;
};
} }
target.* = .{ target.* = .{
// FIXME: is this copying the whole arraylist? should use a pointer?
.statements = arrl, .statements = arrl,
.alloc = allocator, .alloc = allocator,
}; };
} }
pub fn deinit(self: @This()) void { pub fn deinit(self: @This()) void {
// FIXME: should deinit all elements inside the arraylist no? otherwise
// they will leak no?
for (self.statements.items) |stmt| { for (self.statements.items) |stmt| {
stmt.deinit(); stmt.deinit();
self.alloc.destroy(stmt); self.alloc.destroy(stmt);
@ -69,3 +65,15 @@ test "should parse a single statement" {
defer module.deinit(); defer module.deinit();
} }
test "should clean memory if a statement parsing fails after one item has been inserted" {
const input = "var my_variable = 322 unrelated()";
const tokens = try lexic.tokenize(input, std.testing.allocator);
defer tokens.deinit();
var module: Module = undefined;
_ = module.init(&tokens, 0, std.testing.allocator) catch {
return;
};
defer module.deinit();
}

View File

@ -43,7 +43,6 @@ pub const VariableBinding = struct {
// parse expression // parse expression
if (pos + 3 >= tokens.items.len) return ParseError.Error; if (pos + 3 >= tokens.items.len) return ParseError.Error;
// TODO: allocate on the stack
const exp = allocator.create(expression.Expression) catch { const exp = allocator.create(expression.Expression) catch {
return ParseError.OutOfMemory; return ParseError.OutOfMemory;
}; };
@ -103,7 +102,7 @@ test "should fail is it doesnt start with var" {
try std.testing.expect(false); try std.testing.expect(false);
} }
test "should fail if the idenfier is missing" { test "should fail if the identifier is missing" {
const input = "var "; const input = "var ";
const tokens = try lexic.tokenize(input, std.testing.allocator); const tokens = try lexic.tokenize(input, std.testing.allocator);
defer tokens.deinit(); defer tokens.deinit();