Skip to content

Commit 84b26c4

Browse files
authored
[NRBF] Allow the users to decode System.Nullable<UserStruct> (#118276)
* add a failing test that was super hard to extract from the repro * fix: allow any SystemClass to be represented as ClassWithMembersAndTypes in the payload
1 parent 3876cec commit 84b26c4

File tree

2 files changed

+48
-2
lines changed

2 files changed

+48
-2
lines changed

src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/MemberTypeInfo.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,10 @@ internal static MemberTypeInfo Decode(BinaryReader reader, int count, PayloadOpt
9191
const AllowedRecordTypes SystemClass = Classes | AllowedRecordTypes.SystemClassWithMembersAndTypes
9292
// All primitive types can be stored by using one of the interfaces they implement.
9393
// Example: `new IEnumerable[1] { "hello" }` or `new IComparable[1] { int.MaxValue }`.
94-
| AllowedRecordTypes.BinaryObjectString | AllowedRecordTypes.MemberPrimitiveTyped;
95-
const AllowedRecordTypes NonSystemClass = Classes | AllowedRecordTypes.ClassWithMembersAndTypes;
94+
| AllowedRecordTypes.BinaryObjectString | AllowedRecordTypes.MemberPrimitiveTyped
95+
// System.Nullable<UserStruct> is a special case of SystemClassWithMembersAndTypes
96+
| AllowedRecordTypes.ClassWithMembersAndTypes;
97+
const AllowedRecordTypes NonSystemClass = Classes | AllowedRecordTypes.ClassWithMembersAndTypes;
9698

9799
return binaryType switch
98100
{

src/libraries/System.Formats.Nrbf/tests/EdgeCaseTests.cs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,4 +144,48 @@ public void CanReadAllKindsOfDateTimes_DateTimeIsMemberOfTheRootRecord(DateTime
144144
Assert.Equal(input.Ticks, classRecord.GetDateTime(nameof(ClassWithDateTime.Value)).Ticks);
145145
Assert.Equal(input.Kind, classRecord.GetDateTime(nameof(ClassWithDateTime.Value)).Kind);
146146
}
147+
148+
[Fact]
149+
public void CanReadUserClassStoredAsSystemClass()
150+
{
151+
// For the following data, BinaryFormatter serializes the ClassWithNullableStructField class
152+
// as a record with a single field called "NullableField" with BinaryType.SystemClass (!!!)
153+
// and TypeName being System.Nullable`1[[SampleStruct, $AssemblyName]].
154+
// It most likely does so, because it's System.Nullable<$NonSystemStruct>.
155+
// But later it serializes the SampleStruct as a ClassWithMembersAndTypes record,
156+
// not SystemClassWithMembersAndTypes.
157+
// It does so, only when the payload contains at least one class with the nullable field being null.
158+
159+
using MemoryStream stream = Serialize(
160+
new ClassWithNullableStructField[]
161+
{
162+
new ClassWithNullableStructField() { NullableField = null }, // having a null here is crucial for the test
163+
new ClassWithNullableStructField() { NullableField = new ClassWithNullableStructField.SampleStruct() { Value = 42 } }
164+
}
165+
);
166+
167+
SZArrayRecord<SerializationRecord> arrayRecord = (SZArrayRecord<SerializationRecord>)NrbfDecoder.Decode(stream);
168+
SerializationRecord[] records = arrayRecord.GetArray();
169+
Assert.Equal(2, arrayRecord.Length);
170+
Assert.All(records, record => Assert.True(record.TypeNameMatches(typeof(ClassWithNullableStructField))));
171+
Assert.Null(((ClassRecord)records[0]).GetClassRecord(nameof(ClassWithNullableStructField.NullableField)));
172+
173+
ClassRecord? notNullRecord = ((ClassRecord)records[1]).GetClassRecord(nameof(ClassWithNullableStructField.NullableField));
174+
Assert.NotNull(notNullRecord);
175+
Assert.Equal(42, notNullRecord.GetInt32(nameof(ClassWithNullableStructField.SampleStruct.Value)));
176+
}
177+
178+
[Serializable]
179+
public class ClassWithNullableStructField
180+
{
181+
#pragma warning disable IDE0001 // Simplify names
182+
public System.Nullable<SampleStruct> NullableField;
183+
#pragma warning restore IDE0001
184+
185+
[Serializable]
186+
public struct SampleStruct
187+
{
188+
public int Value;
189+
}
190+
}
147191
}

0 commit comments

Comments
 (0)