Skip to content

$new(field.typ.pointee_type) inside $for field in T.fields allocates the wrong type when generic is instantiated for multiple structs #26997

@enghitalo

Description

@enghitalo

Describe the bug

When a generic function using $new(field.typ.pointee_type) is instantiated for multiple struct types whose pointer fields have different pointee types, every instantiation generates C code that allocates the same (last-seen) pointee type. Statically the return type of $new is correct, but the underlying heap object is of the wrong type — producing silently wrong reads, type confusion, and runtime crashes when the wrong-typed object is dereferenced.

Introduced by 8b5f74e.

Related: #26995 (same family of bug for T.pointee_type directly), #26996 ($zero(field.typ.payload_type)).

Reproduction Steps

module main

struct Inner {
    value int
}

struct WrapperA {
mut:
    a &int = unsafe { nil }
}

struct WrapperB {
mut:
    b &Inner = unsafe { nil }
}

fn fill[T](mut x T) {
    $for field in T.fields {
        $if field.indirections == 1 {
            mut p := $new(field.typ.pointee_type)
            x.$(field.name) = p
        }
    }
}

fn main() {
    mut a := WrapperA{}
    mut b := WrapperB{}
    fill(mut a)
    fill(mut b)
    dump(*a.a)
    dump(b.b.value)
}

Expected Behavior

fill[WrapperA] allocates an int for a.a; fill[WrapperB] allocates an Inner for b.b.

Current Behavior

The program runs and prints 0 / 0, but inspecting v -keepc shows that both fill[WrapperA] and fill[WrapperB] emit main__Inner* p = HEAP(main__Inner, ...):

VV_LOC void main__fill_T_main__WrapperA(main__WrapperA* x) {
    /* field 0 : a — field.typ = 8 (&int) */
    {
        main__Inner* p = HEAP(main__Inner, ((main__Inner){.value = 0,}));
        x->a = p;   // assigning Inner* to int* (silent in C)
    }
}

x->a is declared int* but receives an Inner* from the heap. In this minimal repro it appears to "work" because zeroed memory reads as 0, but in real code (e.g. vlib/x/json2/decode.v decoding multiple struct types each containing pointer fields) it leads to runtime panics during downstream decode, since the heap object is the wrong type and shape.

Possible Solution

Resolve field.typ.pointee_type per-field-per-instantiation instead of sharing one resolved type across all $for expansions.

V version

V 0.5.1 6dd9033.eb1d47b

Environment details

linux, Ubuntu 24.04 LTS, gcc 14.2.0, tcc 0.9.28rc

Note

You can use the 👍 reaction to increase the issue's priority for developers.

Please note that only the 👍 reaction to the issue itself counts as a vote.
Other reactions and those to comments will not be taken into account.

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