diff --git a/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/ArrayRecord.cs b/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/ArrayRecord.cs
index 4fe6fb1e0942b4..cda3ca97dee6b2 100644
--- a/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/ArrayRecord.cs
+++ b/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/ArrayRecord.cs
@@ -114,6 +114,22 @@ private void HandleNext(object value, NextInfo info, int size)
internal abstract (AllowedRecordTypes allowed, PrimitiveType primitiveType) GetAllowedRecordType();
+ ///
+ /// Throws if nulls are not allowed and the records are known to contain at least one Multiple Null Record.
+ ///
+ ///
+ /// 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.
+ ///
+ private protected void ThrowIfNullsAreNotAllowedButPresent(List records, bool allowNulls)
+ {
+ if (!allowNulls && ArrayInfo.FlattenedLength != records.Count)
+ {
+ ThrowHelper.ThrowArrayContainedNulls();
+ }
+ }
+
internal static void Populate(List source, Array destination, int[] lengths, AllowedRecordTypes allowedRecordTypes, bool allowNulls)
{
int[] indices = new int[lengths.Length];
diff --git a/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/ArraySingleObjectRecord.cs b/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/ArraySingleObjectRecord.cs
index 3010b2930494ae..564b2e7924a41c 100644
--- a/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/ArraySingleObjectRecord.cs
+++ b/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/ArraySingleObjectRecord.cs
@@ -32,6 +32,8 @@ public override TypeName TypeName
private SerializationRecord?[] ToArray(bool allowNulls)
{
+ ThrowIfNullsAreNotAllowedButPresent(Records, allowNulls);
+
SerializationRecord?[] values = new SerializationRecord?[Length];
int valueIndex = 0;
diff --git a/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/ArraySingleStringRecord.cs b/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/ArraySingleStringRecord.cs
index b276fc8955c5f7..aa104ee0cad646 100644
--- a/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/ArraySingleStringRecord.cs
+++ b/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/ArraySingleStringRecord.cs
@@ -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;
diff --git a/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/JaggedArrayRecord.cs b/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/JaggedArrayRecord.cs
index 6ac97ef40675d6..9d596e673d2995 100644
--- a/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/JaggedArrayRecord.cs
+++ b/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/JaggedArrayRecord.cs
@@ -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
{
diff --git a/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/RectangularArrayRecord.cs b/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/RectangularArrayRecord.cs
index 64cd104eea44e1..ea3d8b10ad86e2 100644
--- a/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/RectangularArrayRecord.cs
+++ b/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/RectangularArrayRecord.cs
@@ -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 =
diff --git a/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/SZArrayOfRecords.cs b/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/SZArrayOfRecords.cs
index b77a4a57a2a348..c71f352af38a67 100644
--- a/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/SZArrayOfRecords.cs
+++ b/src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/SZArrayOfRecords.cs
@@ -38,6 +38,8 @@ public override TypeName TypeName
private SerializationRecord?[] ToArray(bool allowNulls)
{
+ ThrowIfNullsAreNotAllowedButPresent(Records, allowNulls);
+
SerializationRecord?[] result = new SerializationRecord?[Length];
int resultIndex = 0;
diff --git a/src/libraries/System.Formats.Nrbf/tests/ArrayOfSerializationRecordsTests.cs b/src/libraries/System.Formats.Nrbf/tests/ArrayOfSerializationRecordsTests.cs
index 18e39a5fd68e1f..4f60c30bac2f11 100644
--- a/src/libraries/System.Formats.Nrbf/tests/ArrayOfSerializationRecordsTests.cs
+++ b/src/libraries/System.Formats.Nrbf/tests/ArrayOfSerializationRecordsTests.cs
@@ -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);
+
+ 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(() =>
+ {
+ if (serializationRecord is SZArrayRecord arrayOfStrings)
+ {
+ arrayOfStrings.GetArray(allowNulls: false);
+ }
+ else
+ {
+ ((SZArrayRecord)serializationRecord).GetArray(allowNulls: false);
+ }
+ });
+
+ long after = GC.GetAllocatedBytesForCurrentThread();
+
+ Assert.InRange(after, before, before + AllocationThreshold);
+ }
+#endif
}
}