Files

307 lines
7.7 KiB
C#
Raw Permalink Normal View History

2026-08-27 10:56:38 -06:00
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using ProtoBuf.Compiler;
using ProtoBuf.Meta;
namespace ProtoBuf.Serializers;
internal sealed class ArrayDecorator : ProtoDecoratorBase
{
private readonly int fieldNumber;
private const byte OPTIONS_WritePacked = 1;
private const byte OPTIONS_OverwriteList = 2;
private const byte OPTIONS_SupportNull = 4;
private readonly byte options;
private readonly WireType packedWireType;
private readonly Type arrayType;
private readonly Type itemType;
public override Type ExpectedType => arrayType;
public override bool RequiresOldValue => AppendToCollection;
public override bool ReturnsValue => true;
private bool AppendToCollection => (options & 2) == 0;
private bool SupportNull => (options & 4) != 0;
public ArrayDecorator(TypeModel model, IProtoSerializer tail, int fieldNumber, bool writePacked, WireType packedWireType, Type arrayType, bool overwriteList, bool supportNull)
: base(tail)
{
itemType = arrayType.GetElementType();
Type type = (supportNull ? itemType : (Helpers.GetUnderlyingType(itemType) ?? itemType));
if ((writePacked || packedWireType != WireType.None) && fieldNumber <= 0)
{
throw new ArgumentOutOfRangeException("fieldNumber");
}
if (!ListDecorator.CanPack(packedWireType))
{
if (writePacked)
{
throw new InvalidOperationException("Only simple data-types can use packed encoding");
}
packedWireType = WireType.None;
}
this.fieldNumber = fieldNumber;
this.packedWireType = packedWireType;
if (writePacked)
{
options |= 1;
}
if (overwriteList)
{
options |= 2;
}
if (supportNull)
{
options |= 4;
}
this.arrayType = arrayType;
}
private bool CanUsePackedPrefix()
{
return CanUsePackedPrefix(packedWireType, itemType);
}
internal static bool CanUsePackedPrefix(WireType packedWireType, Type itemType)
{
if (packedWireType != WireType.Fixed64 && packedWireType != WireType.Fixed32)
{
return false;
}
if (!Helpers.IsValueType(itemType))
{
return false;
}
return (object)Helpers.GetUnderlyingType(itemType) == null;
}
protected override void EmitWrite(CompilerContext ctx, Local valueFrom)
{
using Local local = ctx.GetLocalWithValue(arrayType, valueFrom);
using Local i = new Local(ctx, ctx.MapType(typeof(int)));
bool flag = (options & 1) != 0;
bool flag2 = flag && CanUsePackedPrefix();
using Local local2 = ((flag && !flag2) ? new Local(ctx, ctx.MapType(typeof(SubItemToken))) : null);
Type type = ctx.MapType(typeof(ProtoWriter));
if (flag)
{
ctx.LoadValue(fieldNumber);
ctx.LoadValue(2);
ctx.LoadReaderWriter();
ctx.EmitCall(type.GetMethod("WriteFieldHeader"));
if (flag2)
{
ctx.LoadLength(local, zeroIfNull: false);
ctx.LoadValue((int)packedWireType);
ctx.LoadReaderWriter();
ctx.EmitCall(type.GetMethod("WritePackedPrefix"));
}
else
{
ctx.LoadValue(local);
ctx.LoadReaderWriter();
ctx.EmitCall(type.GetMethod("StartSubItem"));
ctx.StoreValue(local2);
}
ctx.LoadValue(fieldNumber);
ctx.LoadReaderWriter();
ctx.EmitCall(type.GetMethod("SetPackedField"));
}
EmitWriteArrayLoop(ctx, i, local);
if (flag)
{
if (flag2)
{
ctx.LoadValue(fieldNumber);
ctx.LoadReaderWriter();
ctx.EmitCall(type.GetMethod("ClearPackedField"));
}
else
{
ctx.LoadValue(local2);
ctx.LoadReaderWriter();
ctx.EmitCall(type.GetMethod("EndSubItem"));
}
}
}
private void EmitWriteArrayLoop(CompilerContext ctx, Local i, Local arr)
{
ctx.LoadValue(0);
ctx.StoreValue(i);
CodeLabel label = ctx.DefineLabel();
CodeLabel label2 = ctx.DefineLabel();
ctx.Branch(label, @short: false);
ctx.MarkLabel(label2);
ctx.LoadArrayValue(arr, i);
if (SupportNull)
{
Tail.EmitWrite(ctx, null);
}
else
{
ctx.WriteNullCheckedTail(itemType, Tail, null);
}
ctx.LoadValue(i);
ctx.LoadValue(1);
ctx.Add();
ctx.StoreValue(i);
ctx.MarkLabel(label);
ctx.LoadValue(i);
ctx.LoadLength(arr, zeroIfNull: false);
ctx.BranchIfLess(label2, @short: false);
}
public override void Write(object value, ProtoWriter dest)
{
IList list = (IList)value;
int count = list.Count;
bool flag = (options & 1) != 0;
bool flag2 = flag && CanUsePackedPrefix();
SubItemToken token;
if (flag)
{
ProtoWriter.WriteFieldHeader(fieldNumber, WireType.String, dest);
if (flag2)
{
ProtoWriter.WritePackedPrefix(list.Count, packedWireType, dest);
token = default(SubItemToken);
}
else
{
token = ProtoWriter.StartSubItem(value, dest);
}
ProtoWriter.SetPackedField(fieldNumber, dest);
}
else
{
token = default(SubItemToken);
}
bool flag3 = !SupportNull;
for (int i = 0; i < count; i++)
{
object obj = list[i];
if (flag3 && obj == null)
{
throw new NullReferenceException();
}
Tail.Write(obj, dest);
}
if (flag)
{
if (flag2)
{
ProtoWriter.ClearPackedField(fieldNumber, dest);
}
else
{
ProtoWriter.EndSubItem(token, dest);
}
}
}
public override object Read(object value, ProtoReader source)
{
int field = source.FieldNumber;
BasicList basicList = new BasicList();
if (packedWireType != WireType.None && source.WireType == WireType.String)
{
SubItemToken token = ProtoReader.StartSubItem(source);
while (ProtoReader.HasSubValue(packedWireType, source))
{
basicList.Add(Tail.Read(null, source));
}
ProtoReader.EndSubItem(token, source);
}
else
{
do
{
basicList.Add(Tail.Read(null, source));
}
while (source.TryReadFieldHeader(field));
}
int num = (AppendToCollection ? ((value != null) ? ((Array)value).Length : 0) : 0);
Array array = Array.CreateInstance(itemType, num + basicList.Count);
if (num != 0)
{
((Array)value).CopyTo(array, 0);
}
basicList.CopyTo(array, num);
return array;
}
protected override void EmitRead(CompilerContext ctx, Local valueFrom)
{
Type type = ctx.MapType(typeof(List<>)).MakeGenericType(itemType);
Type expectedType = ExpectedType;
using Local local = (AppendToCollection ? ctx.GetLocalWithValue(expectedType, valueFrom) : null);
using Local local2 = new Local(ctx, expectedType);
using Local local3 = new Local(ctx, type);
ctx.EmitCtor(type);
ctx.StoreValue(local3);
ListDecorator.EmitReadList(ctx, local3, Tail, type.GetMethod("Add"), packedWireType, castListForAdd: false);
using (Local local4 = (AppendToCollection ? new Local(ctx, ctx.MapType(typeof(int))) : null))
{
Type[] array = new Type[2]
{
ctx.MapType(typeof(Array)),
ctx.MapType(typeof(int))
};
if (AppendToCollection)
{
ctx.LoadLength(local, zeroIfNull: true);
ctx.CopyValue();
ctx.StoreValue(local4);
ctx.LoadAddress(local3, type);
ctx.LoadValue(type.GetProperty("Count"));
ctx.Add();
ctx.CreateArray(itemType, null);
ctx.StoreValue(local2);
ctx.LoadValue(local4);
CodeLabel label = ctx.DefineLabel();
ctx.BranchIfFalse(label, @short: true);
ctx.LoadValue(local);
ctx.LoadValue(local2);
ctx.LoadValue(0);
ctx.EmitCall(expectedType.GetMethod("CopyTo", array));
ctx.MarkLabel(label);
ctx.LoadValue(local3);
ctx.LoadValue(local2);
ctx.LoadValue(local4);
}
else
{
ctx.LoadAddress(local3, type);
ctx.LoadValue(type.GetProperty("Count"));
ctx.CreateArray(itemType, null);
ctx.StoreValue(local2);
ctx.LoadAddress(local3, type);
ctx.LoadValue(local2);
ctx.LoadValue(0);
}
array[0] = expectedType;
MethodInfo method = type.GetMethod("CopyTo", array);
if ((object)method == null)
{
array[1] = ctx.MapType(typeof(Array));
method = type.GetMethod("CopyTo", array);
}
ctx.EmitCall(method);
}
ctx.LoadValue(local2);
}
}