Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,22 @@ private void HandleNext(object value, NextInfo info, int size)

internal abstract (AllowedRecordTypes allowed, PrimitiveType primitiveType) GetAllowedRecordType();

/// <summary>
/// Throws if nulls are not allowed and the records are known to contain at least one Multiple Null Record.
/// </summary>
/// <remarks>
/// Every record represents a single value, except for the Multiple Null Records which represent more than one null.
/// Because of that, a record count different than the total element count means that nulls are present.
/// It's important to perform this check before allocating the array, as the array can be very large.
/// </remarks>
private protected void ThrowIfNullsAreNotAllowedButPresent(List<SerializationRecord> records, bool allowNulls)
{
if (!allowNulls && ArrayInfo.FlattenedLength != records.Count)
{
ThrowHelper.ThrowArrayContainedNulls();
}
}

internal static void Populate(List<SerializationRecord> source, Array destination, int[] lengths, AllowedRecordTypes allowedRecordTypes, bool allowNulls)
{
int[] indices = new int[lengths.Length];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ public override TypeName TypeName

private SerializationRecord?[] ToArray(bool allowNulls)
{
ThrowIfNullsAreNotAllowedButPresent(Records, allowNulls);

SerializationRecord?[] values = new SerializationRecord?[Length];

int valueIndex = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ internal override (AllowedRecordTypes allowed, PrimitiveType primitiveType) GetA

private string?[] ToArray(bool allowNulls)
{
ThrowIfNullsAreNotAllowedButPresent(Records, allowNulls);

string?[] values = new string?[Length];

int valueIndex = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ internal JaggedArrayRecord(ArrayInfo arrayInfo, MemberTypeInfo memberTypeInfo, i
[RequiresDynamicCode("May call Array.CreateInstance().")]
private protected override Array Deserialize(Type arrayType, bool allowNulls)
{
ThrowIfNullsAreNotAllowedButPresent(_records, allowNulls);

// This method returns arrays of ArrayRecords.
Array array = _lengths.Length switch
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ private protected override Array Deserialize(Type arrayType, bool allowNulls)
{
bool storeStrings = _elementType == typeof(string);

ThrowIfNullsAreNotAllowedButPresent(_records, allowNulls);

// We can not deserialize non-string types.
// This method returns arrays of SerializationRecord for arrays of complex types.
Array result =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ public override TypeName TypeName

private SerializationRecord?[] ToArray(bool allowNulls)
{
ThrowIfNullsAreNotAllowedButPresent(Records, allowNulls);

SerializationRecord?[] result = new SerializationRecord?[Length];

int resultIndex = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -512,5 +512,67 @@ private static void Verify(Array input, ArrayRecord arrayRecord, Array output,
}
}
}

#if !NETFRAMEWORK
// GC.GetAllocatedBytesForCurrentThread() is not available on Full Framework.
[Theory]
[InlineData(SerializationRecordType.ArraySingleObject)]
[InlineData(SerializationRecordType.ArraySingleString)]
[InlineData(SerializationRecordType.BinaryArray)]
public void GetArray_AllowNullsFalse_DoesNotAllocateLargeArray_WhenMultipleNullRecordIsPresent(SerializationRecordType recordType)
{
// The test needs to ensure that 2GB+ array does not get allocated.
// 200k is enough to get the job done and avoid getting false positives.
const long AllocationThreshold = 200_000;

using MemoryStream stream = new();
BinaryWriter writer = new(stream, Text.Encoding.UTF8);

Comment on lines +528 to +530
WriteSerializedStreamHeader(writer);

writer.Write((byte)recordType);
writer.Write(1); // object ID

if (recordType is SerializationRecordType.BinaryArray)
{
writer.Write((byte)BinaryArrayType.Single);
writer.Write(1); // rank
writer.Write(Array.MaxLength); // length
writer.Write((byte)3); // BinaryType.SystemClass
writer.Write("System.Exception"); // element type name
}
else
{
writer.Write(Array.MaxLength); // length
}

writer.Write((byte)SerializationRecordType.ObjectNullMultiple);
writer.Write(Array.MaxLength); // null count
writer.Write((byte)SerializationRecordType.MessageEnd);

stream.Position = 0;

SerializationRecord serializationRecord = NrbfDecoder.Decode(stream);
Assert.Equal(recordType, serializationRecord.RecordType);

long before = GC.GetAllocatedBytesForCurrentThread();

Assert.Throws<SerializationException>(() =>
{
if (serializationRecord is SZArrayRecord<string> arrayOfStrings)
{
arrayOfStrings.GetArray(allowNulls: false);
}
else
{
((SZArrayRecord<SerializationRecord>)serializationRecord).GetArray(allowNulls: false);
}
});

long after = GC.GetAllocatedBytesForCurrentThread();

Assert.InRange(after, before, before + AllocationThreshold);
}
#endif
}
}
Loading