fix(gen): keep new T() construction in the storage it allocated

For a plain struct with no field defaults and no constructor arguments,
the `A()` call path returned `zeroinitializer` directly instead of
constructing into the storage provided by `new`. Consequences for
`p: A* = new A()`:

- the malloc block was leaked,
- the struct value was materialized into an entry alloca by coercion,
  so `p` pointed at the frame,
- the slife scope-exit free then freed a stack address (heap corruption / 0xC0000374).

Reuse the constructor path whenever a `new` storage descriptor is
pending, so `_translate_struct_ctor` zero-initializes the heap block and
returns its pointer.

Also guard the compiler-owned `new ... slife` binding: the target slot
must be a pointer variable, otherwise a value-typed `T` target would make
the scope-exit free load the struct value and bitcast it to a pointer.

Probe: `new heap A()`, `new A()`, `new stack A()` and `new heap hlife A()`
on a field-only class all print the expected values and exit 0; the
generated IR frees the malloc pointer instead of a stack slot.
Self-host 110/110; ListTest, GenericTest3, PtrOpProbe and HashLibTest
pass; NewTest, NewLifetimeTest, StructTest, EscapeOkTest and
OopInheritTest pass. NewAstTest and GenericOopTest fail the same way on
the previous compiler (pre-existing).
This commit is contained in:
TermiNexus committed 2026-11-02 14:45:44 +08:00
1 parent 77e879ef18
commit d9511a54ea
2 files changed
+12

No files matched your search

+6
View File
@@ -1711,6 +1711,12 @@ novirtual class AssignGenMixin:
# 生命期已是 FRAME(propagate_life 同一谓词),这里补上**出口释放**这一半。
owned = self.Gen.CallGenCtor._new_is_nll_demotable(trans, rhs_node, target_name)
if owned != 0:
# `new` yields T*: a compiler-owned slife binding must be a pointer
# variable. A value-typed `T` target would make the scope-exit free
# load the struct value and bitcast it to a pointer (invalid IR).
if target_slot.Ty is None or target_slot.Ty.Pointee == 0 or self.Gen.ExprGenBase.is_ptr_type(target_slot.Ty.Pointee) == 0:
self.Gen.TypeGenErr.fatal_error(rhs_node, "`new ...` returns T* and must be stored in a pointer variable (T*); a value-typed T target cannot own the heap object", "TypeError")
return 0
sh: mlirlite.Value* = self.Gen.VarGen._SlifeEnsureOwned(pool, builder, target_slot)
if sh is None: return 0
self._SlifeDropOwned(pool, builder, mod, trans, target_slot)
+6
View File
@@ -601,6 +601,12 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase):
if gen_has_defaults == 1:
# Has defaults: use the constructor path to apply them.
return self.Gen.CallGenCtor._translate_struct_ctor(pool, builder, mod, struct_ty, func_name, cargs, sizet(0), trans, cl.IsVolatile)
# `new ... T()` already allocated storage: construct there and return its
# pointer. Returning ConstZero would discard that storage and make the
# caller materialize a stack temporary instead (leaking the heap block and
# freeing a frame address at scope exit).
if self.Gen.CallGenCtor.CurCtorStorage() is not None:
return self.Gen.CallGenCtor._translate_struct_ctor(pool, builder, mod, struct_ty, func_name, cargs, sizet(0), trans, cl.IsVolatile)
# No defaults: return zeroinitializer.
ret: mlirlite.Value* = mlirlite.ConstZero(pool, struct_ty)
return ret