Skip to content

Memory leak in AbiType.toString() method #36

Description

@ch4r10t33r

Bug Description

Memory leak detected in examples/04_smart_contracts when using ABI encoding functions. The leak occurs in the AbiType.toString() method which allocates memory for type strings but doesn't properly clean them up.

Error Output

error(gpa): memory address 0x105140000 leaked: 
/Users/partha/.local/share/zigup/0.14.1/files/lib/std/mem/Allocator.zig:423:40: 0x104f756fb in dupe__anon_8621 (04_smart_contracts)
    const new_buf = try allocator.alloc(T, m.len);
                                       ^
/Users/partha/zig/zigeth/src/abi/types.zig:110:43: 0x104ffa6ff in toString (04_smart_contracts)
            .address => try allocator.dupe(u8, "address"),
                                          ^

Stack Trace

/Users/partha/zig/zigeth/src/abi/types.zig:110:43: in toString
            .address => try allocator.dupe(u8, "address"),
                                          ^
/Users/partha/zig/zigeth/src/abi/types.zig:189:56: in getSignature
            try sig.appendSlice(try param.type.toString(allocator));
                                                       ^
/Users/partha/zig/zigeth/src/abi/types.zig:170:48: in getSelector
        const signature = try self.getSignature(allocator);
                                               ^
/Users/partha/zig/zigeth/src/abi/encode.zig:115:46: in encodeFunctionCall
    const selector = try function.getSelector(allocator);
                                             ^
/Users/partha/zig/zigeth/examples/04_smart_contracts.zig:73:58: in main
        const encoded = try zigeth.abi.encodeFunctionCall(

Steps to Reproduce

  1. Build examples with Zig's debug mode (includes GPA leak detection):

    zig build -Dexamples examples
  2. Run example 04:

    ./zig-out/examples/04_smart_contracts
  3. Example runs successfully but reports memory leak at exit

Expected Behavior

Example should run without any memory leaks. All allocated memory should be properly freed.

Environment

  • Zig Version: 0.14.1
  • OS: macOS (darwin 23.6.0)
  • Architecture: ARM64 (Apple Silicon)

Code Context

The leak is in src/abi/types.zig in the toString() method:

pub fn toString(self: AbiType, allocator: std.mem.Allocator) ![]u8 {
    return switch (self) {
        .uint256 => try allocator.dupe(u8, "uint256"),
        .address => try allocator.dupe(u8, "address"),  // <-- LEAK HERE
        .bool_type => try allocator.dupe(u8, "bool"),
        // ... more cases
    };
}

This is called from getSignature():

pub fn getSignature(self: Function, allocator: std.mem.Allocator) ![]u8 {
    // ...
    for (self.inputs) |param| {
        if (first) { first = false; } else { try sig.append(','); }
        try sig.appendSlice(try param.type.toString(allocator));  // <-- Never freed!
    }
    // ...
}

Root Cause

The toString() method allocates memory for the type string, but the caller (getSignature()) appends it to an ArrayList and never frees the original allocation. The string is duplicated into the ArrayList, creating a leak.

Suggested Fix

Option 1: Use stack allocation for known strings

pub fn toString(self: AbiType, allocator: std.mem.Allocator) ![]u8 {
    _ = allocator;  // Don't allocate for static strings
    return switch (self) {
        .uint256 => "uint256",
        .address => "address",
        .bool_type => "bool",
        // ... return const string literals
    };
}

Then update callers to handle const strings (no need to free).

Option 2: Fix the caller to free

pub fn getSignature(self: Function, allocator: std.mem.Allocator) ![]u8 {
    // ...
    for (self.inputs) |param| {
        if (first) { first = false; } else { try sig.append(','); }
        const type_str = try param.type.toString(allocator);
        defer allocator.free(type_str);  // <-- Free after use
        try sig.appendSlice(type_str);
    }
    // ...
}

Recommended: Option 1 is cleaner since these are constant string literals.

Impact

  • Severity: Low - Example runs successfully, just leaks small amounts of memory
  • Scope: Affects all code using encodeFunctionCall() and ABI type signatures
  • Performance: Minimal - only leaks a few bytes per function call
  • User Impact: Only visible with leak detection enabled (debug builds)

Related Files

  • src/abi/types.zig - Contains the leaking toString() method
  • src/abi/encode.zig - Uses getSelector() which triggers the leak
  • examples/04_smart_contracts.zig - Example that demonstrates the leak

Testing

After fix, verify with:

zig build -Dexamples examples
./zig-out/examples/04_smart_contracts
# Should show no memory leaks

Additional Context

  • Detected by Zig's GeneralPurposeAllocator leak detection
  • Examples complete successfully despite the leak
  • This is a pre-existing issue, not caused by the u256 migration

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions