Why compiler did not detect leak

pub fn main() !void {
    const allocator = std.heap.page_allocator;
    const is_special  = try isSpecial(allocator, "admin");
    std.debug.print("{}", .{is_special});
}


fn isSpecial(allocator: Allocator, name: [] const u8) !bool {
	const lower = try allocLower(allocator, name);
    // defer allocator.free(lower);     // this should be detected by zig 
	return std.mem.eql(u8, lower, "admin");
}

fn allocLower(allocator: Allocator, str: []const u8) ![]const u8 {
	var dest = try allocator.alloc(u8, str.len);

	for (str, 0..) |c, i| {
		dest[i] = switch (c) {
			'A'...'Z' => c + 32,
			else => c,
		};
	}

	return dest;
}

this compiles without any leaked errors

>> zig build run 
true 

What kind of allocator are you using? If you’re using the general purpose allocator, are you checking for leaks on deinit?

EDIT: I see you’re using a page_allocator - this won’t check for leaks.

Not all allocators check for leaks - you need to use the proper allocator to gain that functionality and then use its API properly.

4 Likes

For more context, please refer to these threads:

Also, you may want to consider looking at functions in the standard library:

std.ascii.toLower
/// Lowercases the character and returns it as-is if already lowercase or not a letter.
pub fn toLower(c: u8) u8 {
    const mask = @as(u8, @intFromBool(isUpper(c))) << 5;
    return c | mask;
}
3 Likes

Using GenealPurposeAllocator detects the leak and logs out the code where it happens.

test "GPA" {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    const allocator = gpa.allocator();
    defer {
        const deinit_status = gpa.deinit();
        //fail test; can't try in defer as defer is executed after we return
        if (deinit_status == .leak) std.testing.expect(false) catch @panic("TEST FAIL");
    }

	const is = try isSpecial(allocator, "admin");
	std.debug.print("{}", .{is});
}

Good stuff - I’m going to mark your answer as the solution with a few comments.

if (deinit_status == .leak) std.testing.expect(false) catch @panic("TEST FAIL");

If you’re going to panic, then you can go straight for it:

if (deinit_status == .leak) @panic("TEST FAIL");

In general, I don’t recommend panic during testing. Unless you are testing something that is deeply memory intensive and you’re afraid that you’ll run out of system memory during the course of the test (or there’s some other danger in continuing), then you should just return the error:

try std.testing.expect(deinit_status != .leak);

Alternatively, you can use a more direct function here with std.testing.expectEqual.

Keep in mind that the memory your application is using is automatically freed after the application closes so the panic is unnecessary in most cases.

I’m also going to point out that during tests, you probably want to use the std.testing.allocator instead - it’s built exactly for what you’re trying to do here.

7 Likes