capnproto / go-capnp

Cap'n Proto library and code generator for Go
https://capnproto.org
Other
1.22k stars 110 forks source link

Setting a field in the data section of an unmarshaled struct created with a previous version that did not have any data section fields panics #562

Open xortive opened 6 months ago

xortive commented 6 months ago
@0xbf613bdc8c0f6ae4;
struct OldStruct @0xee1b66e79b51bbbf {  # 0 bytes, 2 ptrs
  foo @0 :Data;  # ptr[0]
  bar @1 :Data;  # ptr[1]
}
struct NewStruct @0xb78a3d22d4285878 {  # 8 bytes, 2 ptrs
  foo @0 :Data;  # ptr[0]
  bar @1 :Data;  # ptr[1]
  newField @2 :Bool;  # bits[0, 1)
}

If one were to serialize OldStruct with go-capnp, deserialize it, and set newField, it will panic here: https://github.com/capnproto/go-capnp/blob/0d218d2660ffa094198d7aba689c4eb04ff6ae18/struct.go#L201

I think go-capnp should allocate a data section in this scenario to match the C++ implementation.

reproduction test:

func TestDataSectionBug(t *testing.T) {
    arena := capnp.SingleSegment(nil)
    oldMsg, seg, err := capnp.NewMessage(arena)
    assert.NoError(t, err)

    oldStruct, err := schema.NewOldStruct(seg)
    assert.NoError(t, err)

    err = oldStruct.SetFoo([]byte{255, 255})
    assert.NoError(t, err)
    err = oldStruct.SetFoo([]byte{127, 127})
    assert.NoError(t, err)

    oldSerialized, err := oldMsg.Marshal()
    assert.NoError(t, err)

    newMsg, err := capnp.Unmarshal(oldSerialized)
    assert.NoError(t, err)

    newStruct, err := schema.ReadRootNewStruct(newMsg)
    assert.NoError(t, err)

    newStruct.SetNewField(true) // panics
}