From 57f5f11e8f5a0d5a5b3c12bb471b76154d8c1008 Mon Sep 17 00:00:00 2001 From: Alex Gerveshi Date: Mon, 10 Aug 2026 07:47:18 +0100 Subject: [PATCH 1/3] Add alignment padding to the end of struct types --- src/FlatSharp/TypeModel/StructTypeModel.cs | 3 +++ src/FlatSharp/TypeModel/ValueStructTypeModel.cs | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/FlatSharp/TypeModel/StructTypeModel.cs b/src/FlatSharp/TypeModel/StructTypeModel.cs index 029f3bb3..4e7327f9 100644 --- a/src/FlatSharp/TypeModel/StructTypeModel.cs +++ b/src/FlatSharp/TypeModel/StructTypeModel.cs @@ -256,6 +256,9 @@ public override void Validate() this.inlineSize += length; } + // FlatBuffers spec requires the size to be a multiple of the alignment (equivalent to flatc PadLastField). + this.inlineSize += SerializationHelpers.GetAlignmentError(this.inlineSize, this.maxAlignment); + foreach (StructMemberModel member in this.memberTypes) { ITypeModel memberModel = member.ItemTypeModel; diff --git a/src/FlatSharp/TypeModel/ValueStructTypeModel.cs b/src/FlatSharp/TypeModel/ValueStructTypeModel.cs index f95123a6..ac82b66a 100644 --- a/src/FlatSharp/TypeModel/ValueStructTypeModel.cs +++ b/src/FlatSharp/TypeModel/ValueStructTypeModel.cs @@ -182,7 +182,7 @@ public override CodeGeneratedMethod CreateSerializeMethodBody(SerializationCodeG { body = $@" {slice} - + {StrykerSuppressor.SuppressNextLine("boolean")} if ({StrykerSuppressor.BitConverterTypeName}.IsLittleEndian) {{ @@ -308,6 +308,9 @@ public override void Initialize() this.inlineSize += propertyModel.PhysicalLayout[0].InlineSize; } + // FlatBuffers spec requires the size to be a multiple of the alignment (equivalent to flatc PadLastField). + this.inlineSize += SerializationHelpers.GetAlignmentError(this.inlineSize, this.maxAlignment); + this.isExternal = this.ClrType.GetCustomAttribute() is not null; this.CanMarshalOnSerialize = false; this.CanMarshalOnParse = false; From 78477479f9a251afef215f4d4711565c8015a236 Mon Sep 17 00:00:00 2001 From: Alex Gerveshi Date: Mon, 10 Aug 2026 07:48:23 +0100 Subject: [PATCH 2/3] Update unit tests --- .../ValueStructs/ValueStructTests.cs | 34 ++++++++++++------- src/Tests/Stryker/CodeGen/Schema.fbs | 6 ++-- src/Tests/Stryker/Tests/FullTreeTests.cs | 2 +- src/Tests/Stryker/Tests/StructFieldTests.cs | 11 +++--- src/Tests/Stryker/Tests/UnionFieldTests.cs | 4 ++- src/Tests/Stryker/Tests/UnionVectorTests.cs | 3 +- 6 files changed, 38 insertions(+), 22 deletions(-) diff --git a/src/Tests/FlatSharpEndToEndTests/ValueStructs/ValueStructTests.cs b/src/Tests/FlatSharpEndToEndTests/ValueStructs/ValueStructTests.cs index 77064414..5d4567a0 100644 --- a/src/Tests/FlatSharpEndToEndTests/ValueStructs/ValueStructTests.cs +++ b/src/Tests/FlatSharpEndToEndTests/ValueStructs/ValueStructTests.cs @@ -76,7 +76,7 @@ public void WriteThrough_ValueStruct_InTable() [TestMethod] public void Basics() { - Assert.AreEqual(36, Unsafe.SizeOf()); + Assert.AreEqual(40, Unsafe.SizeOf()); } [TestMethod] @@ -189,8 +189,13 @@ public void ValueStructs_UnityNative_WellAligned_Serialize(FlatBufferDeserializa } } + /// + /// PoorlyAligned's fields end at 5, but its size is padded up to 8 to match its alignment, so + /// elements can be laid out sequentially and the unsafe span path applies. + /// [TestMethod] - public void ValueStructs_UnityNative_PoorlyAligned_Serialize() + [DynamicData(nameof(DynamicDataHelper.DeserializationModes), typeof(DynamicDataHelper))] + public void ValueStructs_UnityNative_PoorlyAligned_Serialize(FlatBufferDeserializationOption option) { int count = 10; @@ -200,8 +205,15 @@ public void ValueStructs_UnityNative_PoorlyAligned_Serialize() PoorlyAligned = new(Enumerable.Range(0, count).Select(x => new PoorlyAligned { X = x, Y = 1, }).ToArray(), default), }; - var ex = Assert.ThrowsException(() => source.AllocateAndSerialize()); - Assert.AreEqual("Type 'FlatSharpEndToEndTests.ValueStructs.PoorlyAligned' does not support Unsafe Span operations because the size (5) is not a multiple of the alignment (4).", ex.Message); + byte[] data = source.AllocateAndSerialize(); + + var parsed = UnityVectors_List.Serializer.Parse(data, option); + + for (int i = 0; i < count; ++i) + { + Assert.AreEqual(source.PoorlyAligned.Value[i].X, parsed.PoorlyAligned[i].X); + Assert.AreEqual(source.PoorlyAligned.Value[i].Y, parsed.PoorlyAligned[i].Y); + } } [TestMethod] @@ -251,15 +263,13 @@ public void ValueStructs_UnityNative_PoorlyAligned_Parse(FlatBufferDeserializati GCHandle handle = GCHandle.Alloc(data); try { - var ex = Assert.ThrowsException(() => - { - var parsed = UnityVectors_Native.Serializer.Parse(new MemoryInputBuffer(data, true), option); - float f = parsed.PoorlyAligned.Value[0].X; - }); + var parsed = UnityVectors_Native.Serializer.Parse(new MemoryInputBuffer(data, true), option); - Assert.AreEqual( - "Type 'FlatSharpEndToEndTests.ValueStructs.PoorlyAligned' does not support Unsafe Span operations because the size (5) is not a multiple of the alignment (4).", - ex.Message); + for (int i = 0; i < count; ++i) + { + Assert.AreEqual(source.PoorlyAligned[i].X, parsed.PoorlyAligned.Value[i].X); + Assert.AreEqual(source.PoorlyAligned[i].Y, parsed.PoorlyAligned.Value[i].Y); + } } finally { diff --git a/src/Tests/Stryker/CodeGen/Schema.fbs b/src/Tests/Stryker/CodeGen/Schema.fbs index 51196a4b..50bda0c9 100644 --- a/src/Tests/Stryker/CodeGen/Schema.fbs +++ b/src/Tests/Stryker/CodeGen/Schema.fbs @@ -16,7 +16,7 @@ enum Fruit : int Pear } -/// Poorly Aligned. Total Size = 10, Alignment = 4 +/// Poorly Aligned. Fields end at 10, Total Size = 12, Alignment = 4 struct ValueStruct (fs_valueStruct) { /// Position 0, Length 4, Alignment 4 @@ -29,7 +29,7 @@ struct ValueStruct (fs_valueStruct) c : [ short : 2 ] (fs_unsafeStructVector); } -/// Poorly aligned. Total Size == 26, Alignemnt = 8 +/// Poorly aligned. Fields end at 28, Total Size == 32, Alignemnt = 8 struct RefStruct { /// Position 0, Length 8, Alignment 8, WriteThrough = true @@ -44,7 +44,7 @@ struct RefStruct /// Position 14, length 2, Alignment 1, WriteThrough = false d : [ byte : 2 ]; - /// Position 16, Length 10, Alignment 4, WriteThrough = false + /// Position 16, Length 12, Alignment 4, WriteThrough = false e : ValueStruct; } diff --git a/src/Tests/Stryker/Tests/FullTreeTests.cs b/src/Tests/Stryker/Tests/FullTreeTests.cs index d9eb0494..458ebe13 100644 --- a/src/Tests/Stryker/Tests/FullTreeTests.cs +++ b/src/Tests/Stryker/Tests/FullTreeTests.cs @@ -61,7 +61,7 @@ public void GetMaxSize() Root root = this.CreateRoot(); int maxSize = Root.Serializer.GetMaxSize(root); - Assert.AreEqual(898, maxSize); + Assert.AreEqual(914, maxSize); } [TestMethod] diff --git a/src/Tests/Stryker/Tests/StructFieldTests.cs b/src/Tests/Stryker/Tests/StructFieldTests.cs index fe28f656..05611ea2 100644 --- a/src/Tests/Stryker/Tests/StructFieldTests.cs +++ b/src/Tests/Stryker/Tests/StructFieldTests.cs @@ -61,14 +61,15 @@ public void ValueStructTableField(FlatBufferDeserializationOption option) 4, 0, // offset of field 0 0, 0, // padding - 242, 255, 255, 255, // soffset to vtable + 240, 255, 255, 255, // soffset to vtable 4, 0, 0, 0, // valuestruct.a 3, 0, // valuestruct.b, padding 1, 0, // valuestruct.c[0] 2, 0, // valuestruct.c[1] + 0, 0, // valuestruct trailing padding 8, 0, // vtable length - 14, 0, // table length + 16, 0, // table length 0, 0, // field 0 (not present) 4, 0, // field 1 }; @@ -280,7 +281,7 @@ private static Root CreateRootReferenceStruct(out RefStruct rs, out ValueStruct 4, 0, // offset of field 0 0, 0, // padding - 226, 255, 255, 255, // soffset to vtable + 220, 255, 255, 255, // soffset to vtable 12, 0, 0, 0, // refStruct.A (ulong) 0, 0, 0, 0, @@ -290,9 +291,11 @@ private static Root CreateRootReferenceStruct(out RefStruct rs, out ValueStruct 3, 0, // valuestruct.b, padding 1, 0, // valuestruct.c[0] 2, 0, // valuestruct.c[1] + 0, 0, // valuestruct trailing padding + 0, 0, 0, 0, // refStruct trailing padding 6, 0, // vtable length - 30, 0, // table length + 36, 0, // table length 4, 0, // field 0 }; diff --git a/src/Tests/Stryker/Tests/UnionFieldTests.cs b/src/Tests/Stryker/Tests/UnionFieldTests.cs index de52224d..846fe235 100644 --- a/src/Tests/Stryker/Tests/UnionFieldTests.cs +++ b/src/Tests/Stryker/Tests/UnionFieldTests.cs @@ -343,7 +343,8 @@ private Root CreateRoot_RefStructMember(out byte[] expectedData) 0, 0, // D[0,1] 0, 0, 0, 0, // ValueStruct (default) 0, 0, 0, 0, - 0, 0 + 0, 0, 0, 0, // ValueStruct trailing padding + 0, 0, 0, 0 // RefStruct trailing padding }; return root; @@ -396,6 +397,7 @@ private Root CreateRoot_ValueStructMember(out byte[] expectedData) 4, 0, 1, 0, 2, 0, + 0, 0, // ValueStruct trailing padding }; return root; diff --git a/src/Tests/Stryker/Tests/UnionVectorTests.cs b/src/Tests/Stryker/Tests/UnionVectorTests.cs index 010c6b13..765e7099 100644 --- a/src/Tests/Stryker/Tests/UnionVectorTests.cs +++ b/src/Tests/Stryker/Tests/UnionVectorTests.cs @@ -281,7 +281,7 @@ private Root CreateRoot(out byte[] expectedData) 16, 0, 0, 0, 40, 0, 0, 0, 52, 0, 0, 0, - 76, 0, 0, 0, + 80, 0, 0, 0, 244, 255, 255, 255, // key soffset to vtable 0, 0, 0, 0, // apple @@ -306,6 +306,7 @@ private Root CreateRoot(out byte[] expectedData) 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, // string B('c'), 0 From 20e924bf9f4b23d18231b703d3cfd7d7f5d448fa Mon Sep 17 00:00:00 2001 From: Alex Gerveshi Date: Mon, 10 Aug 2026 07:49:12 +0100 Subject: [PATCH 3/3] Update test csproj files for Unity compatibility --- src/Tests/FlatSharpEndToEndTests/FlatSharpEndToEndTests.csproj | 2 +- src/Tests/Stryker/CodeGen/CodeGen.csproj | 2 +- src/Tests/Stryker/Tests/StrykerTests.csproj | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Tests/FlatSharpEndToEndTests/FlatSharpEndToEndTests.csproj b/src/Tests/FlatSharpEndToEndTests/FlatSharpEndToEndTests.csproj index a7e1e2d8..fc91eefc 100644 --- a/src/Tests/FlatSharpEndToEndTests/FlatSharpEndToEndTests.csproj +++ b/src/Tests/FlatSharpEndToEndTests/FlatSharpEndToEndTests.csproj @@ -10,7 +10,7 @@ true annotations $(OutputPath)$(AssemblyName).xml - CS1591 + $(NoWarn);CS1591 False True latest diff --git a/src/Tests/Stryker/CodeGen/CodeGen.csproj b/src/Tests/Stryker/CodeGen/CodeGen.csproj index ce8957d3..18c1d3ce 100644 --- a/src/Tests/Stryker/CodeGen/CodeGen.csproj +++ b/src/Tests/Stryker/CodeGen/CodeGen.csproj @@ -8,7 +8,7 @@ FlatSharpStrykerTests true annotations - CS1591 + $(NoWarn);CS1591 diff --git a/src/Tests/Stryker/Tests/StrykerTests.csproj b/src/Tests/Stryker/Tests/StrykerTests.csproj index 63658b8c..4ce42c5c 100644 --- a/src/Tests/Stryker/Tests/StrykerTests.csproj +++ b/src/Tests/Stryker/Tests/StrykerTests.csproj @@ -6,7 +6,7 @@ FlatSharpStrykerTests FlatSharpStrykerTests annotations - CS1591 + $(NoWarn);CS1591