You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.
Related: #26995 (same family of bug for T.pointee_type directly), #26996 ($zero(field.typ.payload_type)).
Reproduction Steps
modulemainstructInner {
value int
}
structWrapperA {
mut:
a &int=unsafe { nil }
}
structWrapperB {
mut:
b &Inner=unsafe { nil }
}
fnfill[T](mut x T) {
$for field in T.fields {
$if field.indirections==1 {
mutp:= $new(field.typ.pointee_type)
x.$(field.name) = p
}
}
}
fnmain() {
muta:= WrapperA{}
mutb:= 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 bothfill[WrapperA] and fill[WrapperB] emit main__Inner* p = HEAP(main__Inner, ...):
VV_LOCvoidmain__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.
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$newis 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_typedirectly), #26996 ($zero(field.typ.payload_type)).Reproduction Steps
Expected Behavior
fill[WrapperA]allocates anintfora.a;fill[WrapperB]allocates anInnerforb.b.Current Behavior
The program runs and prints
0/0, but inspectingv -keepcshows that bothfill[WrapperA]andfill[WrapperB]emitmain__Inner* p = HEAP(main__Inner, ...):x->ais declaredint*but receives anInner*from the heap. In this minimal repro it appears to "work" because zeroed memory reads as0, but in real code (e.g.vlib/x/json2/decode.vdecoding 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_typeper-field-per-instantiation instead of sharing one resolved type across all$forexpansions.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.