Skip to content

slice len unchanged after Allocator.resize is a footgun #19596

@Pyrolistical

Description

@Pyrolistical

Zig Version

0.12.0-dev.3609+ac21ade66

Steps to Reproduce and Observed Behavior

Given repo.zig:

const std = @import("std");

test {
    var buffer = try std.testing.allocator.alloc(u8, 3);
    defer std.testing.allocator.free(buffer);

    // realloc works as expected
    buffer = try std.testing.allocator.realloc(buffer, 2);
    try std.testing.expectEqual(2, buffer.len);

    std.debug.assert(std.testing.allocator.resize(buffer, 1));
    try std.testing.expectEqual(1, buffer.len);
}

Run zig test repo.zig:

Test [1/1] test_0... expected 1, found 2
[gpa] (err): Allocation size 1 bytes does not match free size 2. Allocation:
...\repo.zig:4:49: 0xdd118c in test_0 (test.exe.obj)
    var buffer = try std.testing.allocator.alloc(u8, 3);
                                                ^
[truncated...]

...\repo.zig:5:37: 0xbb12eb in test_0 (test.exe.obj)
    defer std.testing.allocator.free(buffer);
                                    ^
[truncated...]

...\repo.zig:12:5: 0x5a12e4 in test_0 (test.exe.obj)
    try std.testing.expectEqual(1, buffer.len);
    ^

I expected resize to have updated buffer.len, but it does not. This makes resize a footgun as buffer[1] will now segfault.

Also note how the free failed. Even the allocator expected a slice of len 1.

The following use case is how I encountered this issue:

extern fn foo(array_ptr: [*c]u8, array_capacity: u32, bytes_written: *u32) callconv(.C) void;

fn allocFoo(allocator: std.mem.Allocator, capacity: u32) ![]u8 {
  var buffer = try allocator.alloc(u8, capacity);
  errdefer allocator.free(buffer);

  var bytes_written: u32 = 0;
  foo(buffer.ptr, capacity, &bytes_written);
  if (!allocator.resize(buffer, bytes_written)) {
    buffer = try allocator.realloc(buffer, bytes_written);
  }
  return buffer;
}

test {
   const bytes = try allocFoo(std.testing.allocator, 10);
   defer std.testing.allocator.free(bytes);
   for (bytes) |_| {
     // segfaults if bytes_written is less than 10 and resize returned true
   }
}

The workaround is to use realloc instead.

Expected Behavior

To not shoot myself in the foot.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions