Skip to content

Tolk: Self-assignment of lazy struct loses preserved tail, corrupts unloaded fields #1970

@Gusarich

Description

@Gusarich

When a lazily-loaded struct is modified and then self-assigned, the compiler loses track of the preserved tail slice, causing unloaded fields to serialize as null.

struct S { b: int32; c: int32?; }

fun onInternalMessage() {
    val initial = S{ b: 2, c: 3 }.toCell();
    var st = lazy S.fromCell(initial);
    st.b += 1;
    val good = st.toCell();
    val bad  = (st = st).toCell();
    val shouldBe   = S{ b: 3, c: 3 }.toCell();
    val becomesBad = S{ b: 3, c: null }.toCell();
    return (good.hash() == shouldBe.hash()) && (bad.hash() == becomesBad.hash());
}

Returns -1 (true) — confirming good has c=3 but bad has c=null.

When st.b is modified, the compiler preserves the unparsed tail containing c. On st.toCell() it appends this tail directly with STSLICE:

// val good = st.toCell()
DUP
NEWC
32 STI
s1 s2 XCHG
STSLICE                 // appends preserved tail containing c=3
ENDC

But st = st breaks tail tracking. The compiler switches to field-by-field serialization, reading st.c from the stack where it's still null:

// val bad = (st = st).toCell()
SWAP
NEWC
32 STI
s2 PUSH                 // st.c from stack — never loaded, still null
ISNULL
IF:<{
  1 2 BLKDROP2
  b{0} STSLICECONST     // serializes c as absent
}>ELSE<{
  ...
}>
ENDC

Self-assignment should be a semantic no-op but silently corrupts unloaded fields.

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