Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
It is possible to have binary array records have an element type of a…
…rray without being marked as jagged
  • Loading branch information
adamsitnik committed Aug 19, 2024
commit 297aa4bbea5cbf4e47ea792b200b0bf6a56d13f5
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,12 @@ private protected ArrayRecord(ArrayInfo arrayInfo)

internal long ValuesToRead { get; private protected set; }

internal ArrayInfo ArrayInfo { get; }
private protected ArrayInfo ArrayInfo { get; }

internal bool IsJagged
=> ArrayInfo.ArrayType == BinaryArrayType.Jagged
// It is possible to have binary array records have an element type of array without being marked as jagged.
|| TypeName.GetElementType().IsArray;
Comment on lines +57 to +58
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JeremyKuhne this handles the scenario you have mentioned offline (there is also a test for that). Thank you again for pointing this out!


/// <summary>
/// Allocates an array and fills it with the data provided in the serialized records (in case of primitive types like <see cref="string"/> or <see cref="int"/>) or the serialized records themselves.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,8 @@ private BinaryArrayRecord(ArrayInfo arrayInfo, MemberTypeInfo memberTypeInfo)
{
MemberTypeInfo = memberTypeInfo;
Values = [];

// We need to parse all elements of the jagged array to obtain total elements count;
_totalElementsCount = arrayInfo.ArrayType != BinaryArrayType.Jagged ? arrayInfo.TotalElementsCount : -1;
// We need to parse all elements of the jagged array to obtain total elements count.
_totalElementsCount = -1;
}

public override SerializationRecordType RecordType => SerializationRecordType.BinaryArray;
Expand All @@ -52,72 +51,15 @@ public override long TotalElementsCount
{
if (_totalElementsCount < 0)
{
_totalElementsCount = GetJaggedArrayTotalElementsCount(this);
_totalElementsCount = IsJagged
? GetJaggedArrayTotalElementsCount(this)
: ArrayInfo.TotalElementsCount;
}

return _totalElementsCount;
}
}

private static long GetJaggedArrayTotalElementsCount(BinaryArrayRecord jaggedArrayRecord)
{
long result = 0;
Queue<BinaryArrayRecord>? jaggedArrayRecords = null;

do
{
if (jaggedArrayRecords is not null)
{
jaggedArrayRecord = jaggedArrayRecords.Dequeue();
}

Debug.Assert(jaggedArrayRecord.ArrayInfo.ArrayType == BinaryArrayType.Jagged);

foreach (object value in jaggedArrayRecord.Values)
{
object item = value is MemberReferenceRecord referenceRecord
? referenceRecord.GetReferencedRecord()
: value;

if (item is not SerializationRecord record)
{
result++;
continue;
}

switch (record.RecordType)
{
case SerializationRecordType.BinaryArray:
case SerializationRecordType.ArraySinglePrimitive:
case SerializationRecordType.ArraySingleObject:
case SerializationRecordType.ArraySingleString:
ArrayRecord nestedArrayRecord = (ArrayRecord)record;
if (nestedArrayRecord.ArrayInfo.ArrayType == BinaryArrayType.Jagged)
{
(jaggedArrayRecords ??= new()).Enqueue((BinaryArrayRecord)nestedArrayRecord);
}
else
{
result += nestedArrayRecord.TotalElementsCount;
}
break;
case SerializationRecordType.ObjectNull:
case SerializationRecordType.ObjectNullMultiple256:
case SerializationRecordType.ObjectNullMultiple:
// Null Records nested inside jagged array do not increase total elements count.
// Example: "int[][] input = [[1, 2, 3], null]" is just 3 elements in total.
break;
default:
result++;
break;
}
}
}
while (jaggedArrayRecords is not null && jaggedArrayRecords.Count > 0);

return result;
}

public override TypeName TypeName
=> _typeName ??= MemberTypeInfo.GetArrayTypeName(ArrayInfo);

Expand Down Expand Up @@ -235,6 +177,65 @@ internal static ArrayRecord Decode(BinaryReader reader, RecordMap recordMap, Pay
: new BinaryArrayRecord(arrayInfo, memberTypeInfo);
}

private static long GetJaggedArrayTotalElementsCount(BinaryArrayRecord jaggedArrayRecord)
{
long result = 0;
Queue<BinaryArrayRecord>? jaggedArrayRecords = null;

do
{
if (jaggedArrayRecords is not null)
{
jaggedArrayRecord = jaggedArrayRecords.Dequeue();
}

Debug.Assert(jaggedArrayRecord.IsJagged);

foreach (object value in jaggedArrayRecord.Values)
{
object item = value is MemberReferenceRecord referenceRecord
? referenceRecord.GetReferencedRecord()
: value;

if (item is not SerializationRecord record)
{
result++;
continue;
}

switch (record.RecordType)
{
case SerializationRecordType.BinaryArray:
case SerializationRecordType.ArraySinglePrimitive:
case SerializationRecordType.ArraySingleObject:
case SerializationRecordType.ArraySingleString:
ArrayRecord nestedArrayRecord = (ArrayRecord)record;
if (nestedArrayRecord.IsJagged)
{
(jaggedArrayRecords ??= new()).Enqueue((BinaryArrayRecord)nestedArrayRecord);
}
else
{
result += nestedArrayRecord.TotalElementsCount;
}
break;
case SerializationRecordType.ObjectNull:
case SerializationRecordType.ObjectNullMultiple256:
case SerializationRecordType.ObjectNullMultiple:
// Null Records nested inside jagged array do not increase total elements count.
// Example: "int[][] input = [[1, 2, 3], null]" is just 3 elements in total.
break;
default:
result++;
break;
}
}
}
while (jaggedArrayRecords is not null && jaggedArrayRecords.Count > 0);

return result;
}

private protected override void AddValue(object value) => Values.Add(value);

internal override (AllowedRecordTypes allowed, PrimitiveType primitiveType) GetAllowedRecordType()
Expand Down
33 changes: 33 additions & 0 deletions src/libraries/System.Formats.Nrbf/tests/JaggedArraysTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Formats.Nrbf.Utils;
using System.IO;
using System.Linq;
using Xunit;

Expand Down Expand Up @@ -34,6 +35,38 @@ public void TotalElementsCountDoesNotIncludeNullArrays()
Assert.Equal(3, arrayRecord.TotalElementsCount);
}

[Fact]
public void ItIsPossibleToHaveBinaryArrayRecordsHaveAnElementTypeOfArrayWithoutBeingMarkedAsJagged()
{
int[][][] input = new int[3][][];
for (int i = 0; i < input.Length; i++)
{
input[i] = new int[4][];

for (int j = 0; j < input[i].Length; j++)
{
input[i][j] = [i, j, 0, 1, 2];
}
}

byte[] serialized = Serialize(input).ToArray();
const int ArrayTypeByteIndex =
sizeof(byte) + sizeof(int) * 4 + // stream header
sizeof(byte) + // SerializationRecordType.BinaryArray
sizeof(int); // SerializationRecordId

Assert.Equal((byte)BinaryArrayType.Jagged, serialized[ArrayTypeByteIndex]);

// change the reported array type
serialized[ArrayTypeByteIndex] = (byte)BinaryArrayType.Single;

var arrayRecord = (ArrayRecord)NrbfDecoder.Decode(new MemoryStream(serialized));

Verify(input, arrayRecord);
Assert.Equal(input, arrayRecord.GetArray(input.GetType()));
Assert.Equal(3 * 4 * 5, arrayRecord.TotalElementsCount);
}

[Fact]
public void CanReadJaggedArraysOfPrimitiveTypes_3D()
{
Expand Down