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:
1 parent
77e879ef18
commit
d9511a54ea
2 files changed
+12
No files matched your search
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user