diff --git a/App/lib/core/Gen/StructGenMixin.vp b/App/lib/core/Gen/StructGenMixin.vp index 8842409..f4f396c 100644 --- a/App/lib/core/Gen/StructGenMixin.vp +++ b/App/lib/core/Gen/StructGenMixin.vp @@ -1,4 +1,3 @@ -import t, c from stdint import * import memhub import string @@ -2498,6 +2497,20 @@ novirtual class StructGenMixin: struct_entry.FieldCount = idx + 1 return idx + def _copy_ast_vtable(self, src: ast.AST*, dst: ast.AST*) -> None: + """Copy the vtable word (field 0) from one AST node to another. + + AST classes are @t.CVTable: virtual calls such as kind() dispatch through the first + field. Both nodes are viewed as a single uint64 slot, which reads better than assigning + a pointer to a struct-typed lvalue (TPC rejects that implicit pointer -> struct value + conversion; it also once emitted no load/store at all, leaving vptr=0 and crashing + kind() with `load i8*, i8** [0 + 0]`). + """ + if src is None or dst is None: return + src_vt: uint64* = (uint64*)(src) + dst_vt: uint64* = (uint64*)(dst) + dst_vt[0] = src_vt[0] + def _deep_copy_default_ast(self, pool: memhub.MemBuddy*, default_val: ast.AST*) -> ast.AST*: """Deep-copy a field default AST (constants and lists) into the perm pool.""" if default_val is None: return None @@ -2507,10 +2520,8 @@ novirtual class StructGenMixin: dst_c: ast.Constant* = ast.Constant*(pool.alloc_tagged(ast.Constant.__sizeof__(), memhub.DETSITE_STRUCTGENMIXIN_641)) if dst_c is None: return None string.memset(dst_c, 0, ast.Constant.__sizeof__()) - # Copy the vtable pointer (AST is @t.CVTable; kind() virtual calls depend on the first-field vptr); must match _deep_copy_list_ast (c.Deref read + c.DerefAs write). - # The old form silently failed under TPC: no load-vptr instruction and no store emitted, leaving vptr=0, so a later node.kind() reads `load i8*, i8** [0 + 0]` -> ASan READ at 0x0. Evidence: ViperC\output\b5883dc9c114a604.ll:10382-10384. - vt_ptr_c: t.CPtr = t.CPtr(*((uint64*)(default_val))) - c.DerefAs(dst_c, vt_ptr_c) + # Copy the vtable pointer (AST is @t.CVTable; virtual kind() dispatches through field 0). + self._copy_ast_vtable(default_val, dst_c) # Copy position fields (for error location). dst_c.lineno = src_c.lineno dst_c.col_offset = src_c.col_offset @@ -2534,9 +2545,8 @@ novirtual class StructGenMixin: dst_l: ast.List* = ast.List*(pool.alloc(ast.List.__sizeof__())) if dst_l is None: return None string.memset(dst_l, 0, ast.List.__sizeof__()) - # Copy the vtable pointer (AST is @t.CVTable; kind() virtual calls depend on the first-field vptr). - vt_l: t.CPtr = t.CPtr(*((uint64*)(default_val))) - c.DerefAs(dst_l, vt_l) + # Copy the vtable pointer (same requirement as the Constant copy above). + self._copy_ast_vtable(default_val, dst_l) # Copy position fields and ctx. dst_l.lineno = src_l.lineno dst_l.col_offset = src_l.col_offset @@ -2547,7 +2557,7 @@ novirtual class StructGenMixin: src_elts_l: list[ast.AST*]* = src_l.elts if src_elts_l is not None: cnt_l: sizet = len(src_elts_l) - lst_copy_l: list[ast.AST*]* = (list[ast.AST*] | t.CPtr)(pool.alloc_tagged(list[ast.AST*].__sizeof__(), memhub.DETSITE_STRUCTGENMIXIN_693)) + lst_copy_l: list[ast.AST*]* = list[ast.AST*]*(pool.alloc_tagged(list[ast.AST*].__sizeof__(), memhub.DETSITE_STRUCTGENMIXIN_693)) if lst_copy_l is not None: string.memset(lst_copy_l, 0, list[ast.AST*].__sizeof__()) # The count must never be fixed from the source count up front; it must be settled by the actual written slot count (written_l below). @@ -2557,7 +2567,7 @@ novirtual class StructGenMixin: elem_sz_l: sizet = 8 lst_copy_l.__elem_size__ = elem_sz_l lst_copy_l.__pool__ = None - data_l: t.CPtr = pool.alloc_tagged(cnt_l * elem_sz_l, memhub.DETSITE_STRUCTGENMIXIN_698) + data_l: rawptr = pool.alloc_tagged(cnt_l * elem_sz_l, memhub.DETSITE_STRUCTGENMIXIN_698) if data_l is not None: string.memset(data_l, 0, cnt_l * elem_sz_l) lst_copy_l.__data__ = data_l