I am using Mocha to parse a config. On deinit, the GBA detects a memory leak. This is because I didn't run the .deinit() function on the config variable, however when used as shown in the following snippet, it causes a dangling pointer I think and segfaults. How can I free config without this happening? In this snippet: https://zigbin.io/99e49e I tried to allocate the value, but no luck.
#Help with memory leak/segmentation fault
1 messages · Page 1 of 1 (latest)
that alloc.create looks unnecessary
and you're also not freeing it
you can probably delete lines 10 and 11 and just return deserialised
you're also not deferring the config.deinit(alloc)
Sorry, was going to specify why. That is what I was originally doing, but that didn't work either.
all it's doing is copying a pointer for no reason
pub fn setConfigFromPath(alloc: std.mem.Allocator, path: []const u8) !Self {
const file = try std.fs.cwd().readFileAlloc(alloc, path, 512);
defer alloc.free(file);
var config = try mocha.Parser.parse(alloc, file);
defer config.deinit(alloc);
return try config.deserialize(Self, alloc);
}
This segfaults as well when I access it
what does config.deserialize do
if it is returning a pointer into the config you will be getting undefined behaviour
It creates whatever the first argument is
https://github.com/hqnna/mocha/blob/main/src/lang/types.zig#L85-L113
and what are the fields of Self
Currently just channels: []const List.Item
pub const Item = struct {
selected: bool = false,
title: []const u8,
description: []const u8,
};
well your problem is probably that config.deserialize is returning pointers to inside of the config, which you are freeing
I concur.
Yeah, how could I avoid that? Do I have to modify Mocha?
I already have a slight fork, so maybe something I did broke it too...
you could duplicate all the fields out or don't free config until you are completely done
Not sure if config needs file to stick around, but assuming so, the simplest is probably to just not free the file contents, and return that as well.
It doesn't need file to stick around
Seems odd for it to duplicate it. 🤔
I'd double check that.
Either way though, keep whichever is needed around (file or config), or copy out just the bits you actually need to keep alive.
i.e: If Self has some string fields that you need to keep alive, then you can use something like this to keep it alive:
self.that_field = try some_other_allocator.dupe(u8, self.that_field);
my favourite parsing libraries are the ones that provide tokeniser APIs, then you can control exactly what is allocated
I don't think file is duplicated, it only segfaults when config is deinitialized. Still confused on why it does that though... I think it is something with my modification of Mocha, so I'll look at that. Thanks!
Is there something like dupe for single items?
for single items you can probably just not allocate them and return them directly
if you're doing the copy approach, you'll need to allocate a slice of List.Item, and allocate duplicates of title and description
there shouldn't be any single item allocations there
but if you really needed it, you would probably .create and set it manually
I think the issue has to do with string fields being stored in config
.string => |string| Value{ .string = try allocator.dupeZ(u8, string) },
Okay, so if inside Mocha I duplicate the string it will work, but I get the memory leak detected on exit. So now I have to figure out how to free this...
I tried string.*, but that gives error: index syntax required for slice type '[:0]const u8'
you need to pass the slice into the allocator
I think this is the sane approach, but not here; I would just do this:
fn clone(item: Item, ally: Allocator) !Item {
const buf = try ally.alloc(u8, item.title.len + item.description.len);
const title = buf[0..item.title.len];
@memcpy(title, item.title);
const desc = buf[item.title.len..];
@memcpy(desc, item.description);
return .{
.selected = item.selected,
.title = title,
.description = desc,
};
}
Then you can just do:
const self = try config.deserialize(Self, alloc);
return self.clone();
I think I would prefer to fix it in the library itself?
instead of hacking something on top
which is what I was trying to do with the snippet above, but I am unsure how to do it without an allocator
Depends what you mean by 'fix' I suppose.
This arrangement is a pretty common one in parsers, since most of the time you'd use an arena, and destroy the whole lot at once - save for the one piece that you actually want to keep around.
I'm also not sure how practical it is to parse without an allocator, unless you're doing a streaming parser that hands you each piece as you ask for more or something.
Even in that case, you'd have to duplicate the parts you wanted to keep around, so I'm not sure that would really help you here.
I considered using an arena allocator, might as well just do that