Error when using an allocated slice inside a function (defer allocator free within function)

I got an error about Segmentation fault at address [...] when I tried to loop over a string with an allocated slice inside a custom function but I got NO error if I do the same thing in the main function.

Working code without function

The following program just iterate through a string, copy each character to an allocated slice and return the slice (which is expected to be equal to the input string).

Working code
const std = @import("std");
const testing = std.testing;

test "Same output as input" {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    const allocator = gpa.allocator();
    defer {
        _= gpa.deinit();
    }

    const input = "abc";
    std.debug.print("input: {s}\n",.{input});

        //<Begin code to embed in function
        const output = try allocator.alloc(u8, input.len);
        defer allocator.free(output);
        for (input,0..) |t,i| {
            output[i] = t;
        }
        //>End code to embed in function

    const actual = output;
    std.debug.print("output: {s}\n",.{actual});
    const expected = input;
    try testing.expectEqualStrings(expected, actual);

}

It pass the test without problem by running zig test main.zig.

Non-Working code

If I try to make a function doing exactly what it is written between the two comments //<Begin and //>End, in the snippet above, I got an error.

Error message:

zig test main2.zig
input: abc
result: { Segmentation fault at address 0x1003f9000
/Users/[...]/lib/std/fmt.zig:654:29: 0x1001bcf20 in formatType__anon_6182 (test)
                for (value, 0..) |elem, i| {
                            ^
Unwind error at address `test:0x1001bcf20` (error.InvalidUnwindInfo), trace may be incomplete

error: the following test command crashed:
/Users/[...]/.zig-cache/o/adf5dce631c4aa715277eb83a0172a35/test --seed=0x542c8295

Non working code with a function:

const std = @import("std");
const mem = std.mem;
const testing = std.testing;


test "Same output as input" {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    const allocator = gpa.allocator();
    defer {
        _= gpa.deinit();
    }

    const input = "abc";
    std.debug.print("input: {s}\n",.{input});
    const output = try returnInput(allocator,input);
    std.debug.print("result: {any}\n",.{output});  

    const expected = input;
    const actual = output;
    try testing.expectEqualStrings(expected, actual);
}

pub fn returnInput(allocator: mem.Allocator, input: []const u8) mem.Allocator.Error![]u8 {
        const output = try allocator.alloc(u8, input.len);
        defer allocator.free(output);
        for (input,0..) |t,i| {
            output[i] = t;
        }
    return output;
}

Questions

  1. Why I got an error when using a function?
  2. How to avoid this error and pass the test?
$ zig version
0.14.0-dev.2370+5c6b25d9b

Your returnInput function frees output before returning.

pub fn returnInput(allocator: mem.Allocator, input: []const u8) mem.Allocator.Error![]u8 {
    const output = try allocator.alloc(u8, input.len);
    defer allocator.free(output); // <- run this line when we return from the function
    for (input,0..) |t,i| {
        output[i] = t;
    }
    return output; // <- output is freed here
}

defer runs when control leaves the enclosing block (which in this case is the function itself). To correct the test, you want to move this defer statement to the test itself.

const output = try returnInput(allocator, input);
defer allocator.free(output);
6 Likes

Thanks a lot (spend half a day on this :sweat_smile:)!

Do you know by chance if it is planed to improve the error message in this case? Or about any related open issue on github?

I don’t know what the plans are, but I don’t think there’s a lot of room for better error messages in this situation. If you’re new to low-ish level programming and having to explicitly manage memory, you might be unfamiliar with the term “segmentation fault”, which means that your program tried to access memory it no longer owns (or never owned in the first place). So if you get an error like that, it’s usually a good indicator that your code is trying to read from or write to a chunk of memory that you have already freed.

2 Likes

If you use the tesing allocator (std.testing.allocator) instead of the general purpose allocator in tests the error message is usually pretty good. Don’t remember how it looks for segfaults but for leaked memory it will point to the exact allocation that was not freed.

1 Like

I will definitely use testing allocator in the future thanks!
However, maybe I did something wrong, but I obtain the exact same error message with testing.allocator (see code below).

Error message
$ zig test main.zig
input: abc
result: { Segmentation fault at address 0x102e89000
/Users/[..]/lib/std/fmt.zig:654:29: 0x102c4e6b0 in formatType__anon_6204 (test)
                for (value, 0..) |elem, i| {
                            ^
Unwind error at address `test:0x102c4e6b0` (error.InvalidUnwindInfo), trace may be incomplete

error: the following test command crashed:
/Users/[..]/.zig-cache/o/adf5dce631c4aa715277eb83a0172a35/test --seed=0x2ef96b76
Code
const std = @import("std");
const mem = std.mem;
const testing = std.testing;


test "Same output as input" {
    const input = "abc";
    std.debug.print("input: {s}\n",.{input});
    const output = try returnInput(testing.allocator,input);
    std.debug.print("result: {any}\n",.{output});  

    const expected = input;
    const actual = output;
    try testing.expectEqualStrings(expected, actual);
}

pub fn returnInput(allocator: mem.Allocator, input: []const u8) mem.Allocator.Error![]u8 {
    const output = try allocator.alloc(u8, input.len);
    defer allocator.free(output);
    for (input,0..) |t,i| {
        output[i] = t;
    }
    return output;
}

Also, if I omit completely the defer allocator.free(output), then I get another error message about memory leak but it seems to be the same using a gpa or a testing.alloc…

Error without:

defer allocator.free(output)

$ zig test main.zig
input: abc
result: { 97, 98, 99 }
[gpa] (err): memory address 0x1007e3000 leaked:
/Users/[..]/main.zig:24:43: 0x100586ede in returnInput (test)
        const output = try allocator.alloc(u8, input.len);
                                          ^
/Users/[..]/main.zig:15:35: 0x100586b6e in test.Same output as input (test)
    const output = try returnInput(testing.allocator,input);
                                  ^
/Users/laetitiarigal/Documents/tyty/zigbin/lib/compiler/test_runner.zig:208:25: 0x100634b40 in mainTerminal (test)
        if (test_fn.func()) |_| {
                        ^
/Users/[..]/lib/compiler/test_runner.zig:57:28: 0x10062ed0a in main (test)
        return mainTerminal();
                           ^
/Users/[..]/lib/std/start.zig:608:22: 0x10062e74b in main (test)
            root.main();
                     ^
???:?:?: 0x10f39752d in ??? (???)

All 1 tests passed.
1 errors were logged.
1 tests leaked memory.
error: the following test command failed with exit code 1:
/Users/laetitiarigal/Documents/tyty/testzig/.zig-cache/o/adf5dce631c4aa715277eb83a0172a35/test --seed=0x517db9a3

Unrelated: If I try to print the output as string {s} instead of {any}, I get a completely different error about unreachable code:

Error message with output print as any
$ zig test main2.zig
input: abc
result: thread 4115686 panic: reached unreachable code
/Users/[..]/lib/std/posix.zig:1260:23: 0x10c431146 in write (test)
            .FAULT => unreachable,
                      ^
/Users/[..]/lib/std/fs/File.zig:1327:23: 0x10c418c48 in write (test)
    return posix.write(self.handle, bytes);
                      ^
/Users/[..]/lib/std/io.zig:360:27: 0x10c413f58 in typeErasedWriteFn (test)
            return writeFn(ptr.*, bytes);
                          ^
/Users/[..]/lib/std/io/Writer.zig:13:24: 0x10c43faa9 in write (test)
    return self.writeFn(self.context, bytes);
                       ^
/Users/[..]/lib/std/io/Writer.zig:19:32: 0x10c4206c7 in writeAll (test)
        index += try self.write(bytes[index..]);
                               ^
/Users/[..]/lib/std/fmt.zig:1058:28: 0x10c454662 in formatBuf__anon_7844 (test)
        try writer.writeAll(buf);
                           ^
/Users/[..]/lib/std/fmt.zig:651:37: 0x10c431d9f in formatType__anon_6086 (test)
                    return formatBuf(value, options, writer);
                                    ^
/Users/[..]/lib/std/fmt.zig:188:23: 0x10c419e9b in format__anon_3146 (test)
        try formatType(
                      ^
/Users/[..]/lib/std/io/Writer.zig:24:26: 0x10c414810 in print__anon_2246 (test)
    return std.fmt.format(self, format, args);
                         ^
/Users/[..]/lib/std/io.zig:324:47: 0x10c41135e in print__anon_1826 (test)
            return @errorCast(self.any().print(format, args));
                                              ^
/Users/[..]/main.zig:16:20: 0x10c410c3f in test.Same output as input (test)
    std.debug.print("result: {s}\n",.{output});
                   ^
/Users/[..]/lib/compiler/test_runner.zig:208:25: 0x10c4bfe00 in mainTerminal (test)
        if (test_fn.func()) |_| {
                        ^
/Users/[..]/lib/compiler/test_runner.zig:57:28: 0x10c4ba08a in main (test)
        return mainTerminal();
                           ^
/Users/[..]/lib/std/start.zig:608:22: 0x10c4b9acb in main (test)
            root.main();
                     ^
???:?:?: 0x115eb252d in ??? (???)
Unwind information for `???:0x115eb252d` was not available, trace may be incomplete

???:?:?: 0x0 in ??? (???)
error: the following test command crashed:
/Users/[..]/.zig-cache/o/adf5dce631c4aa715277eb83a0172a35/test --seed=0x76f71fb5

That part is what I was talking about, it shows exactly what wasn’t freed. I didn’t know both allocators give the same error messages, but it makes sense considering the description of the gpa.

2 Likes