From 8351bec1dfdbe0d3da6479b590b2b079954e825e Mon Sep 17 00:00:00 2001 From: Victor Nicollet Date: Fri, 18 Jul 2025 15:17:17 +0200 Subject: [PATCH 01/17] Use 'Write' instead of 'WriteInt32' for union type keys Union type keys set with are usually small positive integers, likely to fit in a single byte. As late as version 2.5.198, the library would encode [Union(0, typeof(Foo))] as two bytes 0x92 0x00 (one for "two-element array", and one for the 0 integer key), followed by the serialization of type Foo. In version 3.1.4, [Union(0, typeof(Foo))] is instead encoded as six bytes 0x92 0xD2 0x00 0x00 0x00 0x00 (one for "two-element array", one for "32-bit integer", and the four bytes of the 0 integer key). Although the two representations are compatible, for code bases that use many small polymorphic objects, the migration from 2.x to 3.x would lead to a significant increase in the size of the serialized data. This behavior is caused by the generated code for the interface using `WriteInt32` (which always encodes the integer as five bytes), and is fixed by instead using `Write` (which uses the smallest possible number of bytes for its argument). --- src/MessagePack.SourceGenerator/Transforms/UnionTemplate.cs | 2 +- src/MessagePack.SourceGenerator/Transforms/UnionTemplate.tt | 2 +- ...tedMessagePackResolver.MyTestNamespace.IMyTypeFormatter.g.cs | 2 +- ...tedMessagePackResolver.ContainingClass.IMyTypeFormatter.g.cs | 2 +- ...ssagePack.GeneratedMessagePackResolver.IMyTypeFormatter.g.cs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/MessagePack.SourceGenerator/Transforms/UnionTemplate.cs b/src/MessagePack.SourceGenerator/Transforms/UnionTemplate.cs index 12ce6a290..08e2ed04a 100644 --- a/src/MessagePack.SourceGenerator/Transforms/UnionTemplate.cs +++ b/src/MessagePack.SourceGenerator/Transforms/UnionTemplate.cs @@ -71,7 +71,7 @@ public virtual string TransformText() if (value != null && this.typeToKeyAndJumpMap.TryGetValue(value.GetType().TypeHandle, out keyValuePair)) { writer.WriteArrayHeader(2); - writer.WriteInt32(keyValuePair.Key); + writer.Write(keyValuePair.Key); switch (keyValuePair.Value) { "); diff --git a/src/MessagePack.SourceGenerator/Transforms/UnionTemplate.tt b/src/MessagePack.SourceGenerator/Transforms/UnionTemplate.tt index 67b78a537..359e165e7 100644 --- a/src/MessagePack.SourceGenerator/Transforms/UnionTemplate.tt +++ b/src/MessagePack.SourceGenerator/Transforms/UnionTemplate.tt @@ -34,7 +34,7 @@ using MsgPack = global::MessagePack; if (value != null && this.typeToKeyAndJumpMap.TryGetValue(value.GetType().TypeHandle, out keyValuePair)) { writer.WriteArrayHeader(2); - writer.WriteInt32(keyValuePair.Key); + writer.Write(keyValuePair.Key); switch (keyValuePair.Value) { <# for(var i = 0; i < Info.SubTypes.Length; i++) { var item = Info.SubTypes[i]; #> diff --git a/tests/MessagePack.SourceGenerator.Tests/Resources/UnionFormatter(Namespace)/Formatters.MessagePack.GeneratedMessagePackResolver.MyTestNamespace.IMyTypeFormatter.g.cs b/tests/MessagePack.SourceGenerator.Tests/Resources/UnionFormatter(Namespace)/Formatters.MessagePack.GeneratedMessagePackResolver.MyTestNamespace.IMyTypeFormatter.g.cs index be91b6294..f6e75173c 100644 --- a/tests/MessagePack.SourceGenerator.Tests/Resources/UnionFormatter(Namespace)/Formatters.MessagePack.GeneratedMessagePackResolver.MyTestNamespace.IMyTypeFormatter.g.cs +++ b/tests/MessagePack.SourceGenerator.Tests/Resources/UnionFormatter(Namespace)/Formatters.MessagePack.GeneratedMessagePackResolver.MyTestNamespace.IMyTypeFormatter.g.cs @@ -32,7 +32,7 @@ public void Serialize(ref MsgPack::MessagePackWriter writer, global::MyTestNames if (value != null && this.typeToKeyAndJumpMap.TryGetValue(value.GetType().TypeHandle, out keyValuePair)) { writer.WriteArrayHeader(2); - writer.WriteInt32(keyValuePair.Key); + writer.Write(keyValuePair.Key); switch (keyValuePair.Value) { case 0: diff --git a/tests/MessagePack.SourceGenerator.Tests/Resources/UnionFormatter(NestingClass)/Formatters.MessagePack.GeneratedMessagePackResolver.ContainingClass.IMyTypeFormatter.g.cs b/tests/MessagePack.SourceGenerator.Tests/Resources/UnionFormatter(NestingClass)/Formatters.MessagePack.GeneratedMessagePackResolver.ContainingClass.IMyTypeFormatter.g.cs index 63f0c831e..5fd854188 100644 --- a/tests/MessagePack.SourceGenerator.Tests/Resources/UnionFormatter(NestingClass)/Formatters.MessagePack.GeneratedMessagePackResolver.ContainingClass.IMyTypeFormatter.g.cs +++ b/tests/MessagePack.SourceGenerator.Tests/Resources/UnionFormatter(NestingClass)/Formatters.MessagePack.GeneratedMessagePackResolver.ContainingClass.IMyTypeFormatter.g.cs @@ -32,7 +32,7 @@ public void Serialize(ref MsgPack::MessagePackWriter writer, global::ContainingC if (value != null && this.typeToKeyAndJumpMap.TryGetValue(value.GetType().TypeHandle, out keyValuePair)) { writer.WriteArrayHeader(2); - writer.WriteInt32(keyValuePair.Key); + writer.Write(keyValuePair.Key); switch (keyValuePair.Value) { case 0: diff --git a/tests/MessagePack.SourceGenerator.Tests/Resources/UnionFormatter(None)/Formatters.MessagePack.GeneratedMessagePackResolver.IMyTypeFormatter.g.cs b/tests/MessagePack.SourceGenerator.Tests/Resources/UnionFormatter(None)/Formatters.MessagePack.GeneratedMessagePackResolver.IMyTypeFormatter.g.cs index 7d39b6866..b849b5ca4 100644 --- a/tests/MessagePack.SourceGenerator.Tests/Resources/UnionFormatter(None)/Formatters.MessagePack.GeneratedMessagePackResolver.IMyTypeFormatter.g.cs +++ b/tests/MessagePack.SourceGenerator.Tests/Resources/UnionFormatter(None)/Formatters.MessagePack.GeneratedMessagePackResolver.IMyTypeFormatter.g.cs @@ -31,7 +31,7 @@ public void Serialize(ref MsgPack::MessagePackWriter writer, global::IMyType val if (value != null && this.typeToKeyAndJumpMap.TryGetValue(value.GetType().TypeHandle, out keyValuePair)) { writer.WriteArrayHeader(2); - writer.WriteInt32(keyValuePair.Key); + writer.Write(keyValuePair.Key); switch (keyValuePair.Value) { case 0: From 55b4d1a12c51c698dd6a082e07b31c5d797d1398 Mon Sep 17 00:00:00 2001 From: Andrey Bykiev Date: Sun, 17 Aug 2025 21:45:09 +0300 Subject: [PATCH 02/17] Remove unneeded GetTypeInfo() calls (#2206) * [WIP} Remove unneeded GetTypeInfo() calls Improve performcance by removing unneeded GetTypeInfo() calls * typeof(T) reuse * Remove AsType() calls * Revert IsSupportedType with TypeInfo * Fix code review remarks --- .../DynamicObjectTypeFallbackFormatter.cs | 5 +- .../Formatters/EnumAsStringFormatter`1.cs | 6 +- .../Formatters/PrimitiveObjectFormatter.cs | 10 ++- .../Formatters/TypelessFormatter.cs | 11 +-- .../Internal/DynamicAssemblyFactory.cs | 2 +- .../Internal/ILGeneratorExtensions.cs | 10 +-- .../Internal/ReflectionExtensions.cs | 32 +------- src/MessagePack/Internal/Sequence`1.cs | 2 +- src/MessagePack/MessagePackSecurity.cs | 36 ++++----- .../MessagePackSerializer.NonGeneric.cs | 9 +-- .../Resolvers/AttributeFormatterResolver.cs | 2 +- .../DynamicEnumAsStringIgnoreCaseResolver.cs | 14 ++-- .../Resolvers/DynamicEnumAsStringResolver.cs | 14 ++-- .../Resolvers/DynamicEnumResolver.cs | 18 ++--- .../Resolvers/DynamicGenericResolver.cs | 73 ++++++++++--------- .../Resolvers/DynamicObjectResolver.cs | 72 +++++++++--------- .../Resolvers/DynamicUnionResolver.cs | 24 +++--- .../Resolvers/ImmutableCollectionResolver.cs | 18 +++-- .../Resolvers/SkipClrVisibilityChecks.cs | 24 +++--- 19 files changed, 179 insertions(+), 203 deletions(-) diff --git a/src/MessagePack/Formatters/DynamicObjectTypeFallbackFormatter.cs b/src/MessagePack/Formatters/DynamicObjectTypeFallbackFormatter.cs index 2c43fa7d2..884d391dd 100644 --- a/src/MessagePack/Formatters/DynamicObjectTypeFallbackFormatter.cs +++ b/src/MessagePack/Formatters/DynamicObjectTypeFallbackFormatter.cs @@ -33,7 +33,6 @@ public void Serialize(ref MessagePackWriter writer, object? value, MessagePackSe } Type type = value.GetType(); - TypeInfo ti = type.GetTypeInfo(); if (type == typeof(object)) { @@ -42,7 +41,7 @@ public void Serialize(ref MessagePackWriter writer, object? value, MessagePackSe return; } - if (PrimitiveObjectFormatter.IsSupportedType(type, ti, value)) + if (PrimitiveObjectFormatter.IsSupportedType(type, value)) { if (!(value is System.Collections.IDictionary || value is System.Collections.ICollection)) { @@ -70,7 +69,7 @@ public void Serialize(ref MessagePackWriter writer, object? value, MessagePackSe Expression.Convert(param0, formatterType), serializeMethodInfo, param1, - ti.IsValueType ? Expression.Unbox(param2, type) : Expression.Convert(param2, type), + type.IsValueType ? Expression.Unbox(param2, type) : Expression.Convert(param2, type), param3); serializerDelegate = Expression.Lambda(body, param0, param1, param2, param3).Compile(); diff --git a/src/MessagePack/Formatters/EnumAsStringFormatter`1.cs b/src/MessagePack/Formatters/EnumAsStringFormatter`1.cs index e3b81fd54..5458dbda4 100644 --- a/src/MessagePack/Formatters/EnumAsStringFormatter`1.cs +++ b/src/MessagePack/Formatters/EnumAsStringFormatter`1.cs @@ -39,8 +39,10 @@ public EnumAsStringFormatter(bool ignoreCase) this.ignoreCase = ignoreCase; StringComparer stringComparer = ignoreCase ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal; - this.isFlags = typeof(T).GetCustomAttribute() is object; - var fields = typeof(T).GetFields(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Static); + var type = typeof(T); + + this.isFlags = type.GetCustomAttribute() is object; + var fields = type.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Static); var nameValueMapping = new Dictionary(fields.Length, ignoreCase ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal); var valueNameMapping = new Dictionary(); Dictionary? clrToSerializationName = null; diff --git a/src/MessagePack/Formatters/PrimitiveObjectFormatter.cs b/src/MessagePack/Formatters/PrimitiveObjectFormatter.cs index 0e3230e0d..ced58432c 100644 --- a/src/MessagePack/Formatters/PrimitiveObjectFormatter.cs +++ b/src/MessagePack/Formatters/PrimitiveObjectFormatter.cs @@ -37,7 +37,13 @@ protected PrimitiveObjectFormatter() { } + [Obsolete("Please, use the method overload without TypeInfo")] public static bool IsSupportedType(Type type, TypeInfo typeInfo, object value) + { + return IsSupportedType(type, value); + } + + public static bool IsSupportedType(Type type, object value) { if (value == null) { @@ -49,7 +55,7 @@ public static bool IsSupportedType(Type type, TypeInfo typeInfo, object value) return true; } - if (typeInfo.IsEnum) + if (type.IsEnum) { return true; } @@ -133,7 +139,7 @@ public void Serialize(ref MessagePackWriter writer, object? value, MessagePackSe } else { - if (t.GetTypeInfo().IsEnum) + if (t.IsEnum) { Type underlyingType = Enum.GetUnderlyingType(t); var code2 = TypeToJumpCode[underlyingType]; diff --git a/src/MessagePack/Formatters/TypelessFormatter.cs b/src/MessagePack/Formatters/TypelessFormatter.cs index 9e63ca447..a051be19d 100644 --- a/src/MessagePack/Formatters/TypelessFormatter.cs +++ b/src/MessagePack/Formatters/TypelessFormatter.cs @@ -147,8 +147,7 @@ public void Serialize(ref MessagePackWriter writer, object? value, MessagePackSe var typeNameCache = options.OmitAssemblyVersion ? ShortenedTypeNameCache : FullTypeNameCache; if (!typeNameCache.TryGetValue(type, out byte[]? typeName)) { - TypeInfo ti = type.GetTypeInfo(); - if (ti.IsAnonymous() || UseBuiltinTypes.Contains(type)) + if (type.IsAnonymous() || UseBuiltinTypes.Contains(type)) { typeName = null; } @@ -176,8 +175,6 @@ public void Serialize(ref MessagePackWriter writer, object? value, MessagePackSe { if (!Serializers.TryGetValue(type, out serializeMethod)) { - TypeInfo ti = type.GetTypeInfo(); - Type formatterType = typeof(IMessagePackFormatter<>).MakeGenericType(type); ParameterExpression param0 = Expression.Parameter(typeof(object), "formatter"); ParameterExpression param1 = Expression.Parameter(typeof(MessagePackWriter).MakeByRefType(), "writer"); @@ -190,7 +187,7 @@ public void Serialize(ref MessagePackWriter writer, object? value, MessagePackSe Expression.Convert(param0, formatterType), serializeMethodInfo, param1, - ti.IsValueType ? Expression.Unbox(param2, type) : Expression.Convert(param2, type), + type.IsValueType ? Expression.Unbox(param2, type) : Expression.Convert(param2, type), param3); serializeMethod = Expression.Lambda(body, param0, param1, param2, param3).Compile(); @@ -305,8 +302,6 @@ private object DeserializeByTypeName(ArraySegment typeName, ref MessagePac { if (!Deserializers.TryGetValue(type, out deserializeMethod)) { - TypeInfo ti = type.GetTypeInfo(); - Type formatterType = typeof(IMessagePackFormatter<>).MakeGenericType(type); ParameterExpression param0 = Expression.Parameter(typeof(object), "formatter"); ParameterExpression param1 = Expression.Parameter(typeof(MessagePackReader).MakeByRefType(), "reader"); @@ -321,7 +316,7 @@ private object DeserializeByTypeName(ArraySegment typeName, ref MessagePac param2); Expression body = deserialize; - if (ti.IsValueType) + if (type.IsValueType) { body = Expression.Convert(deserialize, typeof(object)); } diff --git a/src/MessagePack/Internal/DynamicAssemblyFactory.cs b/src/MessagePack/Internal/DynamicAssemblyFactory.cs index e0804c69f..ea5c9d578 100644 --- a/src/MessagePack/Internal/DynamicAssemblyFactory.cs +++ b/src/MessagePack/Internal/DynamicAssemblyFactory.cs @@ -54,7 +54,7 @@ public DynamicAssemblyFactory(string moduleName) { ImmutableHashSet.Builder skipVisibilityAssemblies = this.lastCreatedDynamicAssemblySkipVisibilityChecks.ToBuilder(); int originalCount = skipVisibilityAssemblies.Count; - SkipClrVisibilityChecks.GetSkipVisibilityChecksRequirements(type.GetTypeInfo(), skipVisibilityAssemblies); + SkipClrVisibilityChecks.GetSkipVisibilityChecksRequirements(type, skipVisibilityAssemblies); lock (this) { diff --git a/src/MessagePack/Internal/ILGeneratorExtensions.cs b/src/MessagePack/Internal/ILGeneratorExtensions.cs index 43a969da9..ff088b895 100644 --- a/src/MessagePack/Internal/ILGeneratorExtensions.cs +++ b/src/MessagePack/Internal/ILGeneratorExtensions.cs @@ -27,8 +27,7 @@ public ArgumentField(ILGenerator il, int i, Type type) { this.il = il; this.i = i; - TypeInfo ti = type.GetTypeInfo(); - this.@ref = (ti.IsClass || ti.IsInterface || ti.IsAbstract) ? false : true; + this.@ref = (type.IsClass || type.IsInterface || type.IsAbstract) ? false : true; } public void EmitLoad() @@ -228,7 +227,7 @@ public static void EmitLdc_I4(this ILGenerator il, int value) public static void EmitUnboxOrCast(this ILGenerator il, Type type) { - if (type.GetTypeInfo().IsValueType) + if (type.IsValueType) { il.Emit(OpCodes.Unbox_Any, type); } @@ -240,7 +239,7 @@ public static void EmitUnboxOrCast(this ILGenerator il, Type type) public static void EmitBoxOrDoNothing(this ILGenerator il, Type type) { - if (type.GetTypeInfo().IsValueType) + if (type.IsValueType) { il.Emit(OpCodes.Box, type); } @@ -362,7 +361,8 @@ public static void EmitULong(this ILGenerator il, ulong value) public static void EmitThrowNotimplemented(this ILGenerator il) { - il.Emit(OpCodes.Newobj, typeof(System.NotImplementedException).GetTypeInfo().DeclaredConstructors.First(x => x.GetParameters().Length == 0)); + il.Emit(OpCodes.Newobj, typeof(System.NotImplementedException).GetConstructors() + .First(x => x.GetParameters().Length == 0)); il.Emit(OpCodes.Throw); } diff --git a/src/MessagePack/Internal/ReflectionExtensions.cs b/src/MessagePack/Internal/ReflectionExtensions.cs index 52eea0735..001fc9158 100644 --- a/src/MessagePack/Internal/ReflectionExtensions.cs +++ b/src/MessagePack/Internal/ReflectionExtensions.cs @@ -2,25 +2,18 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; -using System.Linq; -using System.Reflection; using System.Runtime.CompilerServices; namespace MessagePack.Internal { internal static class ReflectionExtensions { - public static bool IsNullable(this System.Reflection.TypeInfo type) + public static bool IsNullable(this Type type) { return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(System.Nullable<>); } - public static bool IsPublic(this System.Reflection.TypeInfo type) - { - return type.IsPublic; - } - - public static bool IsAnonymous(this System.Reflection.TypeInfo type) + public static bool IsAnonymous(this Type type) { return type.Namespace == null && type.IsSealed @@ -34,26 +27,5 @@ public static bool IsIndexer(this System.Reflection.PropertyInfo propertyInfo) { return propertyInfo.GetIndexParameters().Length > 0; } - - public static bool IsConstructedGenericType(this System.Reflection.TypeInfo type) - { - return type.AsType().IsConstructedGenericType; - } - - public static MethodInfo? GetGetMethod(this PropertyInfo propInfo) - { - return propInfo.GetMethod; - } - - public static MethodInfo? GetSetMethod(this PropertyInfo propInfo) - { - return propInfo.SetMethod; - } - - public static bool HasPrivateCtorForSerialization(this TypeInfo type) - { - var markedCtor = type.DeclaredConstructors.SingleOrDefault(x => x.GetCustomAttribute(false) != null); - return markedCtor?.Attributes.HasFlag(MethodAttributes.Private) ?? false; - } } } diff --git a/src/MessagePack/Internal/Sequence`1.cs b/src/MessagePack/Internal/Sequence`1.cs index 9d4ff862c..3d9ec35ac 100644 --- a/src/MessagePack/Internal/Sequence`1.cs +++ b/src/MessagePack/Internal/Sequence`1.cs @@ -361,7 +361,7 @@ private class SequenceSegment : ReadOnlySequenceSegment /// /// A value indicating whether the element may contain references (and thus must be cleared). /// - private static readonly bool MayContainReferences = !typeof(T).GetTypeInfo().IsPrimitive; + private static readonly bool MayContainReferences = !typeof(T).IsPrimitive; #pragma warning disable SA1011 // Closing square brackets should be spaced correctly /// diff --git a/src/MessagePack/MessagePackSecurity.cs b/src/MessagePack/MessagePackSecurity.cs index 2e59d957f..7b3fe62b0 100644 --- a/src/MessagePack/MessagePackSecurity.cs +++ b/src/MessagePack/MessagePackSecurity.cs @@ -153,30 +153,32 @@ private class HashResistantCache static HashResistantCache() { + var type = typeof(T); + // We have to specially handle some 32-bit types (e.g. float) where multiple in-memory representations should hash to the same value. // Any type supported by the PrimitiveObjectFormatter should be added here if supporting it as a key in a collection makes sense. EqualityComparer = - typeof(T) == typeof(bool) ? (IEqualityComparer)CollisionResistantHasherUnmanaged.Instance : - typeof(T) == typeof(char) ? (IEqualityComparer)CollisionResistantHasherUnmanaged.Instance : - typeof(T) == typeof(sbyte) ? (IEqualityComparer)CollisionResistantHasherUnmanaged.Instance : - typeof(T) == typeof(byte) ? (IEqualityComparer)CollisionResistantHasherUnmanaged.Instance : - typeof(T) == typeof(short) ? (IEqualityComparer)CollisionResistantHasherUnmanaged.Instance : - typeof(T) == typeof(ushort) ? (IEqualityComparer)CollisionResistantHasherUnmanaged.Instance : - typeof(T) == typeof(int) ? (IEqualityComparer)CollisionResistantHasherUnmanaged.Instance : - typeof(T) == typeof(uint) ? (IEqualityComparer)CollisionResistantHasherUnmanaged.Instance : - typeof(T) == typeof(long) ? (IEqualityComparer)CollisionResistantHasherUnmanaged.Instance : - typeof(T) == typeof(ulong) ? (IEqualityComparer)CollisionResistantHasherUnmanaged.Instance : - typeof(T) == typeof(Guid) ? (IEqualityComparer)CollisionResistantHasherUnmanaged.Instance : + type == typeof(bool) ? (IEqualityComparer)CollisionResistantHasherUnmanaged.Instance : + type == typeof(char) ? (IEqualityComparer)CollisionResistantHasherUnmanaged.Instance : + type == typeof(sbyte) ? (IEqualityComparer)CollisionResistantHasherUnmanaged.Instance : + type == typeof(byte) ? (IEqualityComparer)CollisionResistantHasherUnmanaged.Instance : + type == typeof(short) ? (IEqualityComparer)CollisionResistantHasherUnmanaged.Instance : + type == typeof(ushort) ? (IEqualityComparer)CollisionResistantHasherUnmanaged.Instance : + type == typeof(int) ? (IEqualityComparer)CollisionResistantHasherUnmanaged.Instance : + type == typeof(uint) ? (IEqualityComparer)CollisionResistantHasherUnmanaged.Instance : + type == typeof(long) ? (IEqualityComparer)CollisionResistantHasherUnmanaged.Instance : + type == typeof(ulong) ? (IEqualityComparer)CollisionResistantHasherUnmanaged.Instance : + type == typeof(Guid) ? (IEqualityComparer)CollisionResistantHasherUnmanaged.Instance : // Data types that are managed or have multiple in-memory representations for equivalent values: - typeof(T) == typeof(float) ? (IEqualityComparer)SingleEqualityComparer.Instance : - typeof(T) == typeof(double) ? (IEqualityComparer)DoubleEqualityComparer.Instance : - typeof(T) == typeof(string) ? (IEqualityComparer)StringEqualityComparer.Instance : - typeof(T) == typeof(DateTime) ? (IEqualityComparer)DateTimeEqualityComparer.Instance : - typeof(T) == typeof(DateTimeOffset) ? (IEqualityComparer)DateTimeOffsetEqualityComparer.Instance : + type == typeof(float) ? (IEqualityComparer)SingleEqualityComparer.Instance : + type == typeof(double) ? (IEqualityComparer)DoubleEqualityComparer.Instance : + type == typeof(string) ? (IEqualityComparer)StringEqualityComparer.Instance : + type == typeof(DateTime) ? (IEqualityComparer)DateTimeEqualityComparer.Instance : + type == typeof(DateTimeOffset) ? (IEqualityComparer)DateTimeOffsetEqualityComparer.Instance : // Call out each primitive behind an enum explicitly to avoid dynamically generating code. - typeof(T).GetTypeInfo().IsEnum && typeof(T).GetTypeInfo().GetEnumUnderlyingType() is Type underlying ? ( + type.IsEnum && type.GetEnumUnderlyingType() is Type underlying ? ( underlying == typeof(byte) ? CollisionResistantEnumHasher.Instance : underlying == typeof(sbyte) ? CollisionResistantEnumHasher.Instance : underlying == typeof(ushort) ? CollisionResistantEnumHasher.Instance : diff --git a/src/MessagePack/MessagePackSerializer.NonGeneric.cs b/src/MessagePack/MessagePackSerializer.NonGeneric.cs index 1495e4e65..b86c4c862 100644 --- a/src/MessagePack/MessagePackSerializer.NonGeneric.cs +++ b/src/MessagePack/MessagePackSerializer.NonGeneric.cs @@ -134,7 +134,6 @@ private class CompiledMethods internal CompiledMethods(Type type) { - TypeInfo ti = type.GetTypeInfo(); { // public static byte[] Serialize(T obj, MessagePackSerializerOptions options, CancellationToken cancellationToken) MethodInfo serialize = GetMethod(nameof(Serialize), type, new Type?[] { null, typeof(MessagePackSerializerOptions), typeof(CancellationToken) }); @@ -151,7 +150,7 @@ internal CompiledMethods(Type type) MethodCallExpression body = Expression.Call( null, serialize, - ti.IsValueType ? Expression.Unbox(param1, type) : Expression.Convert(param1, type), + type.IsValueType ? Expression.Unbox(param1, type) : Expression.Convert(param1, type), param2, param3); Func lambda = Expression.Lambda>(body, param1, param2, param3).Compile(PreferInterpretation); @@ -178,7 +177,7 @@ internal CompiledMethods(Type type) null, serialize, param1, - ti.IsValueType ? Expression.Unbox(param2, type) : Expression.Convert(param2, type), + type.IsValueType ? Expression.Unbox(param2, type) : Expression.Convert(param2, type), param3, param4); Action lambda = Expression.Lambda>(body, param1, param2, param3, param4).Compile(PreferInterpretation); @@ -205,7 +204,7 @@ internal CompiledMethods(Type type) null, serialize, param1, - ti.IsValueType ? Expression.Unbox(param2, type) : Expression.Convert(param2, type), + type.IsValueType ? Expression.Unbox(param2, type) : Expression.Convert(param2, type), param3, param4); Func lambda = Expression.Lambda>(body, param1, param2, param3, param4).Compile(PreferInterpretation); @@ -232,7 +231,7 @@ internal CompiledMethods(Type type) null, serialize, param1, - ti.IsValueType ? Expression.Unbox(param2, type) : Expression.Convert(param2, type), + type.IsValueType ? Expression.Unbox(param2, type) : Expression.Convert(param2, type), param3, param4); Action, object?, MessagePackSerializerOptions?, CancellationToken> lambda = Expression.Lambda, object?, MessagePackSerializerOptions?, CancellationToken>>(body, param1, param2, param3, param4).Compile(PreferInterpretation); diff --git a/src/MessagePack/Resolvers/AttributeFormatterResolver.cs b/src/MessagePack/Resolvers/AttributeFormatterResolver.cs index eec41fc92..e420661ac 100644 --- a/src/MessagePack/Resolvers/AttributeFormatterResolver.cs +++ b/src/MessagePack/Resolvers/AttributeFormatterResolver.cs @@ -33,7 +33,7 @@ private static class FormatterCache static FormatterCache() { - MessagePackFormatterAttribute? attr = typeof(T).GetTypeInfo().GetCustomAttribute(); + MessagePackFormatterAttribute? attr = typeof(T).GetCustomAttribute(); if (attr == null) { return; diff --git a/src/MessagePack/Resolvers/DynamicEnumAsStringIgnoreCaseResolver.cs b/src/MessagePack/Resolvers/DynamicEnumAsStringIgnoreCaseResolver.cs index afa565d02..2a7f723c2 100644 --- a/src/MessagePack/Resolvers/DynamicEnumAsStringIgnoreCaseResolver.cs +++ b/src/MessagePack/Resolvers/DynamicEnumAsStringIgnoreCaseResolver.cs @@ -32,27 +32,27 @@ private static class FormatterCache static FormatterCache() { - TypeInfo ti = typeof(T).GetTypeInfo(); + Type type = typeof(T); - if (ti.IsNullable()) + if (type.IsNullable()) { // build underlying type and use wrapped formatter. - ti = ti.GenericTypeArguments[0].GetTypeInfo(); - if (!ti.IsEnum) + type = type.GenericTypeArguments[0]; + if (!type.IsEnum) { return; } - var innerFormatter = Instance.GetFormatterDynamic(ti.AsType()); + var innerFormatter = Instance.GetFormatterDynamic(type); if (innerFormatter == null) { return; } - Formatter = (IMessagePackFormatter?)Activator.CreateInstance(typeof(StaticNullableFormatter<>).MakeGenericType(ti.AsType()), new object[] { innerFormatter }); + Formatter = (IMessagePackFormatter?)Activator.CreateInstance(typeof(StaticNullableFormatter<>).MakeGenericType(type), new object[] { innerFormatter }); return; } - else if (!ti.IsEnum) + else if (!type.IsEnum) { return; } diff --git a/src/MessagePack/Resolvers/DynamicEnumAsStringResolver.cs b/src/MessagePack/Resolvers/DynamicEnumAsStringResolver.cs index b8610fa3c..d2de589e2 100644 --- a/src/MessagePack/Resolvers/DynamicEnumAsStringResolver.cs +++ b/src/MessagePack/Resolvers/DynamicEnumAsStringResolver.cs @@ -41,27 +41,27 @@ private static class FormatterCache static FormatterCache() { - TypeInfo ti = typeof(T).GetTypeInfo(); + Type type = typeof(T); - if (ti.IsNullable()) + if (type.IsNullable()) { // build underlying type and use wrapped formatter. - ti = ti.GenericTypeArguments[0].GetTypeInfo(); - if (!ti.IsEnum) + type = type.GenericTypeArguments[0]; + if (!type.IsEnum) { return; } - var innerFormatter = DynamicEnumAsStringResolver.Instance.GetFormatterDynamic(ti.AsType()); + var innerFormatter = DynamicEnumAsStringResolver.Instance.GetFormatterDynamic(type); if (innerFormatter == null) { return; } - Formatter = (IMessagePackFormatter?)Activator.CreateInstance(typeof(StaticNullableFormatter<>).MakeGenericType(ti.AsType()), new object[] { innerFormatter }); + Formatter = (IMessagePackFormatter?)Activator.CreateInstance(typeof(StaticNullableFormatter<>).MakeGenericType(type), new object[] { innerFormatter }); return; } - else if (!ti.IsEnum) + else if (!type.IsEnum) { return; } diff --git a/src/MessagePack/Resolvers/DynamicEnumResolver.cs b/src/MessagePack/Resolvers/DynamicEnumResolver.cs index c05769f1b..e993f4af0 100644 --- a/src/MessagePack/Resolvers/DynamicEnumResolver.cs +++ b/src/MessagePack/Resolvers/DynamicEnumResolver.cs @@ -54,32 +54,32 @@ private static class FormatterCache static FormatterCache() { - TypeInfo ti = typeof(T).GetTypeInfo(); - if (ti.IsNullable()) + Type type = typeof(T); + if (type.IsNullable()) { // build underlying type and use wrapped formatter. - ti = ti.GenericTypeArguments[0].GetTypeInfo(); - if (!ti.IsEnum) + type = type.GenericTypeArguments[0]; + if (!type.IsEnum) { return; } - var innerFormatter = DynamicEnumResolver.Instance.GetFormatterDynamic(ti.AsType()); + var innerFormatter = DynamicEnumResolver.Instance.GetFormatterDynamic(type); if (innerFormatter == null) { return; } - Formatter = (IMessagePackFormatter?)Activator.CreateInstance(typeof(StaticNullableFormatter<>).MakeGenericType(ti.AsType()), new object[] { innerFormatter }); + Formatter = (IMessagePackFormatter?)Activator.CreateInstance(typeof(StaticNullableFormatter<>).MakeGenericType(type), new object[] { innerFormatter }); return; } - else if (!ti.IsEnum) + else if (!type.IsEnum) { return; } - TypeInfo formatterTypeInfo = BuildType(typeof(T), allowPrivate: false); - Formatter = (IMessagePackFormatter?)Activator.CreateInstance(formatterTypeInfo.AsType()); + TypeInfo formatterTypeInfo = BuildType(type, allowPrivate: false); + Formatter = (IMessagePackFormatter?)Activator.CreateInstance(formatterTypeInfo); } } diff --git a/src/MessagePack/Resolvers/DynamicGenericResolver.cs b/src/MessagePack/Resolvers/DynamicGenericResolver.cs index cb1a2248a..31a5a73fe 100644 --- a/src/MessagePack/Resolvers/DynamicGenericResolver.cs +++ b/src/MessagePack/Resolvers/DynamicGenericResolver.cs @@ -93,8 +93,6 @@ internal static class DynamicGenericResolverGetFormatterHelper // Reduce IL2CPP code generate size(don't write long code in ) internal static object? GetFormatter(Type t) { - TypeInfo ti = t.GetTypeInfo(); - if (t.IsArray) { var rank = t.GetArrayRank(); @@ -125,23 +123,22 @@ internal static class DynamicGenericResolverGetFormatterHelper return null; // not supported built-in } } - else if (ti.IsGenericType) + else if (t.IsGenericType) { - Type genericType = ti.GetGenericTypeDefinition(); - TypeInfo genericTypeInfo = genericType.GetTypeInfo(); - var isNullable = genericTypeInfo.IsNullable(); - Type? nullableElementType = isNullable ? ti.GenericTypeArguments[0] : null; + Type genericType = t.GetGenericTypeDefinition(); + var isNullable = genericType.IsNullable(); + Type? nullableElementType = isNullable ? t.GenericTypeArguments[0] : null; if (genericType == typeof(KeyValuePair<,>)) { - return CreateInstance(typeof(KeyValuePairFormatter<,>), ti.GenericTypeArguments); + return CreateInstance(typeof(KeyValuePairFormatter<,>), t.GenericTypeArguments); } // Tuple - else if (ti.FullName?.StartsWith("System.Tuple") is true) + else if (t.FullName?.StartsWith("System.Tuple") is true) { Type? tupleFormatterType = null; - switch (ti.GenericTypeArguments.Length) + switch (t.GenericTypeArguments.Length) { case 1: tupleFormatterType = typeof(TupleFormatter<>); @@ -168,17 +165,17 @@ internal static class DynamicGenericResolverGetFormatterHelper tupleFormatterType = typeof(TupleFormatter<,,,,,,,>); break; default: - throw new MessagePackSerializationException("Unsupported arity for Tuple generic type: " + ti.Name); + throw new MessagePackSerializationException("Unsupported arity for Tuple generic type: " + t.Name); } - return CreateInstance(tupleFormatterType, ti.GenericTypeArguments); + return CreateInstance(tupleFormatterType, t.GenericTypeArguments); } // ValueTuple - else if (ti.FullName?.StartsWith("System.ValueTuple") is true) + else if (t.FullName?.StartsWith("System.ValueTuple") is true) { Type? tupleFormatterType = null; - switch (ti.GenericTypeArguments.Length) + switch (t.GenericTypeArguments.Length) { case 1: tupleFormatterType = typeof(ValueTupleFormatter<>); @@ -205,61 +202,61 @@ internal static class DynamicGenericResolverGetFormatterHelper tupleFormatterType = typeof(ValueTupleFormatter<,,,,,,,>); break; default: - throw new MessagePackSerializationException("Unsupported arity for ValueTuple generic type: " + ti.Name); + throw new MessagePackSerializationException("Unsupported arity for ValueTuple generic type: " + t.Name); } - return CreateInstance(tupleFormatterType, ti.GenericTypeArguments); + return CreateInstance(tupleFormatterType, t.GenericTypeArguments); } // ArraySegment else if (genericType == typeof(ArraySegment<>)) { - if (ti.GenericTypeArguments[0] == typeof(byte)) + if (t.GenericTypeArguments[0] == typeof(byte)) { return ByteArraySegmentFormatter.Instance; } else { - return CreateInstance(typeof(ArraySegmentFormatter<>), ti.GenericTypeArguments); + return CreateInstance(typeof(ArraySegmentFormatter<>), t.GenericTypeArguments); } } // Memory else if (genericType == typeof(Memory<>)) { - if (ti.GenericTypeArguments[0] == typeof(byte)) + if (t.GenericTypeArguments[0] == typeof(byte)) { return ByteMemoryFormatter.Instance; } else { - return CreateInstance(typeof(MemoryFormatter<>), ti.GenericTypeArguments); + return CreateInstance(typeof(MemoryFormatter<>), t.GenericTypeArguments); } } // ReadOnlyMemory else if (genericType == typeof(ReadOnlyMemory<>)) { - if (ti.GenericTypeArguments[0] == typeof(byte)) + if (t.GenericTypeArguments[0] == typeof(byte)) { return ByteReadOnlyMemoryFormatter.Instance; } else { - return CreateInstance(typeof(ReadOnlyMemoryFormatter<>), ti.GenericTypeArguments); + return CreateInstance(typeof(ReadOnlyMemoryFormatter<>), t.GenericTypeArguments); } } // ReadOnlySequence else if (genericType == typeof(ReadOnlySequence<>)) { - if (ti.GenericTypeArguments[0] == typeof(byte)) + if (t.GenericTypeArguments[0] == typeof(byte)) { return ByteReadOnlySequenceFormatter.Instance; } else { - return CreateInstance(typeof(ReadOnlySequenceFormatter<>), ti.GenericTypeArguments); + return CreateInstance(typeof(ReadOnlySequenceFormatter<>), t.GenericTypeArguments); } } @@ -274,11 +271,11 @@ internal static class DynamicGenericResolverGetFormatterHelper { if (FormatterMap.TryGetValue(genericType, out Type? formatterType)) { - return CreateInstance(formatterType, ti.GenericTypeArguments); + return CreateInstance(formatterType, t.GenericTypeArguments); } } } - else if (ti.IsEnum) + else if (t.IsEnum) { return CreateInstance(typeof(GenericEnumFormatter<>), new[] { t }); } @@ -302,11 +299,13 @@ internal static class DynamicGenericResolverGetFormatterHelper return NonGenericInterfaceDictionaryFormatter.Instance; } - if (typeof(IList).GetTypeInfo().IsAssignableFrom(ti) && ti.DeclaredConstructors.Any(x => x.GetParameters().Length == 0)) + if (typeof(IList).IsAssignableFrom(t) && t.GetConstructors(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.NonPublic) + .Any(x => x.GetParameters().Length == 0)) { return Activator.CreateInstance(typeof(NonGenericListFormatter<>).MakeGenericType(t)); } - else if (typeof(IDictionary).GetTypeInfo().IsAssignableFrom(ti) && ti.DeclaredConstructors.Any(x => x.GetParameters().Length == 0)) + else if (typeof(IDictionary).IsAssignableFrom(t) && t.GetConstructors(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.NonPublic) + .Any(x => x.GetParameters().Length == 0)) { return Activator.CreateInstance(typeof(NonGenericDictionaryFormatter<>).MakeGenericType(t)); } @@ -315,8 +314,9 @@ internal static class DynamicGenericResolverGetFormatterHelper // check inherited types(e.g. Foo : ICollection<>, Bar : ICollection) { // generic dictionary - var dictionaryDef = ti.ImplementedInterfaces.FirstOrDefault(x => x.GetTypeInfo().IsConstructedGenericType() && x.GetGenericTypeDefinition() == typeof(IDictionary<,>)); - if (dictionaryDef != null && ti.DeclaredConstructors.Any(x => !x.IsStatic && x.GetParameters().Length == 0)) + var dictionaryDef = t.GetInterfaces().FirstOrDefault(x => x.IsConstructedGenericType && x.GetGenericTypeDefinition() == typeof(IDictionary<,>)); + if (dictionaryDef != null && t.GetConstructors(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.NonPublic) + .Any(x => x.GetParameters().Length == 0)) { Type keyType = dictionaryDef.GenericTypeArguments[0]; Type valueType = dictionaryDef.GenericTypeArguments[1]; @@ -324,7 +324,7 @@ internal static class DynamicGenericResolverGetFormatterHelper } // generic dictionary with collection ctor - var dictionaryInterfaceDef = ti.ImplementedInterfaces.FirstOrDefault(x => x.GetTypeInfo().IsConstructedGenericType() && + var dictionaryInterfaceDef = t.GetInterfaces().FirstOrDefault(x => x.IsConstructedGenericType && (x.GetGenericTypeDefinition() == typeof(IReadOnlyDictionary<,>))); if (dictionaryInterfaceDef != null) { @@ -336,7 +336,7 @@ internal static class DynamicGenericResolverGetFormatterHelper typeof(IReadOnlyDictionary<,>).MakeGenericType(keyType, valueType), typeof(IEnumerable<>).MakeGenericType(typeof(KeyValuePair<,>).MakeGenericType(keyType, valueType)), }; - foreach (var constructor in ti.DeclaredConstructors) + foreach (var constructor in t.GetConstructors(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.NonPublic)) { ParameterInfo[] parameters = constructor.GetParameters(); if (parameters.Length == 1 && @@ -348,8 +348,9 @@ internal static class DynamicGenericResolverGetFormatterHelper } // generic collection - var collectionDef = ti.ImplementedInterfaces.FirstOrDefault(x => x.GetTypeInfo().IsConstructedGenericType() && x.GetGenericTypeDefinition() == typeof(ICollection<>)); - if (collectionDef != null && ti.DeclaredConstructors.Any(x => !x.IsStatic && x.GetParameters().Length == 0)) + var collectionDef = t.GetInterfaces().FirstOrDefault(x => x.IsConstructedGenericType && x.GetGenericTypeDefinition() == typeof(ICollection<>)); + if (collectionDef != null && t.GetConstructors(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.NonPublic) + .Any(x => x.GetParameters().Length == 0)) { Type elemType = collectionDef.GenericTypeArguments[0]; return CreateInstance(typeof(GenericCollectionFormatter<,>), new[] { elemType, t }); @@ -358,11 +359,11 @@ internal static class DynamicGenericResolverGetFormatterHelper // generic IEnumerable collection // looking for combination of IEnumerable and constructor that takes // enumeration of the same type - foreach (var enumerableCollectionDef in ti.ImplementedInterfaces.Where(x => x.GetTypeInfo().IsConstructedGenericType() && x.GetGenericTypeDefinition() == typeof(IEnumerable<>))) + foreach (var enumerableCollectionDef in t.GetInterfaces().Where(x => x.IsConstructedGenericType && x.GetGenericTypeDefinition() == typeof(IEnumerable<>))) { Type elemType = enumerableCollectionDef.GenericTypeArguments[0]; Type paramInterface = typeof(IEnumerable<>).MakeGenericType(elemType); - foreach (var constructor in ti.DeclaredConstructors) + foreach (var constructor in t.GetConstructors(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.NonPublic)) { var parameters = constructor.GetParameters(); if (parameters.Length == 1 && parameters[0].ParameterType.IsAssignableFrom(paramInterface)) diff --git a/src/MessagePack/Resolvers/DynamicObjectResolver.cs b/src/MessagePack/Resolvers/DynamicObjectResolver.cs index c3d281337..6eb62e235 100644 --- a/src/MessagePack/Resolvers/DynamicObjectResolver.cs +++ b/src/MessagePack/Resolvers/DynamicObjectResolver.cs @@ -61,15 +61,15 @@ private DynamicObjectResolver() internal static IMessagePackFormatter? BuildFormatterHelper(IFormatterResolver self, DynamicAssemblyFactory dynamicAssemblyFactory, bool forceStringKey, bool contractless, bool allowPrivate) { - TypeInfo ti = typeof(T).GetTypeInfo(); + Type type = typeof(T); - if (ti.IsInterface || ti.IsAbstract) + if (type.IsInterface || type.IsAbstract) { return null; } DynamicAssembly? dynamicAssembly = null; - if (ti.IsAnonymous()) + if (type.IsAnonymous()) { forceStringKey = true; contractless = true; @@ -78,25 +78,25 @@ private DynamicObjectResolver() // but *not* look at non-public members to avoid double-serialization of the properties // as well as their backing fields. allowPrivate = false; - dynamicAssembly = DynamicAssemblyFactory.GetDynamicAssembly(typeof(T), true); + dynamicAssembly = DynamicAssemblyFactory.GetDynamicAssembly(type, true); } - else if (ti.IsNullable()) + else if (type.IsNullable()) { - ti = ti.GenericTypeArguments[0].GetTypeInfo(); + type = type.GenericTypeArguments[0]; - var innerFormatter = self.GetFormatterDynamic(ti.AsType()); + var innerFormatter = self.GetFormatterDynamic(type); if (innerFormatter == null) { return null; } - return (IMessagePackFormatter?)Activator.CreateInstance(typeof(StaticNullableFormatter<>).MakeGenericType(ti.AsType()), [innerFormatter]); + return (IMessagePackFormatter?)Activator.CreateInstance(typeof(StaticNullableFormatter<>).MakeGenericType(type), [innerFormatter]); } - allowPrivate |= !contractless && typeof(T).GetCustomAttributes().Any(a => a.AllowPrivate); - dynamicAssembly ??= DynamicAssemblyFactory.GetDynamicAssembly(typeof(T), allowPrivate); - TypeInfo? formatterTypeInfo = DynamicObjectTypeBuilder.BuildType(dynamicAssembly, typeof(T), forceStringKey, contractless, allowPrivate); - return formatterTypeInfo is null ? null : (IMessagePackFormatter)ResolverUtilities.ActivateFormatter(formatterTypeInfo.AsType()); + allowPrivate |= !contractless && type.GetCustomAttributes().Any(a => a.AllowPrivate); + dynamicAssembly ??= DynamicAssemblyFactory.GetDynamicAssembly(type, allowPrivate); + TypeInfo? formatterTypeInfo = DynamicObjectTypeBuilder.BuildType(dynamicAssembly, type, forceStringKey, contractless, allowPrivate); + return formatterTypeInfo is null ? null : (IMessagePackFormatter)ResolverUtilities.ActivateFormatter(formatterTypeInfo); } private static class FormatterCache @@ -239,7 +239,7 @@ internal static class DynamicObjectTypeBuilder return null; } - if (!allowPrivate && !(type.IsPublic || type.IsNestedPublic) && !type.GetTypeInfo().IsAnonymous()) + if (!allowPrivate && !(type.IsPublic || type.IsNestedPublic) && !type.IsAnonymous()) { throw new MessagePackSerializationException("Building dynamic formatter only allows public type. Type: " + type.FullName); } @@ -462,7 +462,7 @@ private static void BuildSerialize(Type type, ObjectSerializationInfo info, ILGe var argOptions = new ArgumentField(il, firstArgIndex + 2); // if(value == null) return WriteNil - if (type.GetTypeInfo().IsClass) + if (type.IsClass) { Label elseBody = il.DefineLabel(); @@ -476,7 +476,7 @@ private static void BuildSerialize(Type type, ObjectSerializationInfo info, ILGe } // IMessagePackSerializationCallbackReceiver.OnBeforeSerialize() - if (type.GetTypeInfo().ImplementedInterfaces.Any(x => x == typeof(IMessagePackSerializationCallbackReceiver))) + if (type.GetInterfaces().Any(x => x == typeof(IMessagePackSerializationCallbackReceiver))) { // call directly MethodInfo[] runtimeMethods = type.GetRuntimeMethods().Where(x => x.Name == "OnBeforeSerialize").ToArray(); @@ -515,7 +515,7 @@ private static void BuildSerialize(Type type, ObjectSerializationInfo info, ILGe { if (intKeyMap.TryGetValue(i, out ObjectSerializationInfo.EmittableMember? member)) { - EmitSerializeValue(il, type.GetTypeInfo(), member, index++, tryEmitLoadCustomFormatter, argWriter, argValue, argOptions, localResolver); + EmitSerializeValue(il, member, index++, tryEmitLoadCustomFormatter, argWriter, argValue, argOptions, localResolver); } else { @@ -568,7 +568,7 @@ private static void BuildSerialize(Type type, ObjectSerializationInfo info, ILGe il.EmitCall(MessagePackWriterTypeInfo.WriteRaw); } - EmitSerializeValue(il, type.GetTypeInfo(), item, index, tryEmitLoadCustomFormatter, argWriter, argValue, argOptions, localResolver); + EmitSerializeValue(il, item, index, tryEmitLoadCustomFormatter, argWriter, argValue, argOptions, localResolver); index++; } } @@ -576,7 +576,7 @@ private static void BuildSerialize(Type type, ObjectSerializationInfo info, ILGe il.Emit(OpCodes.Ret); } - private static void EmitSerializeValue(ILGenerator il, TypeInfo type, ObjectSerializationInfo.EmittableMember member, int index, Func tryEmitLoadCustomFormatter, ArgumentField argWriter, ArgumentField argValue, ArgumentField argOptions, LocalBuilder localResolver) + private static void EmitSerializeValue(ILGenerator il, ObjectSerializationInfo.EmittableMember member, int index, Func tryEmitLoadCustomFormatter, ArgumentField argWriter, ArgumentField argValue, ArgumentField argOptions, LocalBuilder localResolver) { Label endLabel = il.DefineLabel(); Type t = member.Type; @@ -592,7 +592,7 @@ private static void EmitSerializeValue(ILGenerator il, TypeInfo type, ObjectSeri } else if (ObjectSerializationInfo.IsOptimizeTargetType(t)) { - if (!t.GetTypeInfo().IsValueType) + if (!t.IsValueType) { // As a nullable type (e.g. byte[] and string) we need to call WriteNil for null values. Label writeNonNilValueLabel = il.DefineLabel(); @@ -1060,7 +1060,7 @@ private static void BuildDeserializeInternalTryReadNil(Type type, ILGenerator il argReader.EmitLdarg(); il.EmitCall(MessagePackReaderTypeInfo.TryReadNil); il.Emit(OpCodes.Brfalse_S, falseLabel); - if (type.GetTypeInfo().IsClass) + if (type.IsClass) { il.Emit(OpCodes.Ldnull); il.Emit(OpCodes.Ret); @@ -1087,7 +1087,7 @@ private static void BuildDeserializeInternalDepthUnStep(ILGenerator il, ref Argu private static void BuildDeserializeInternalOnAfterDeserialize(Type type, ObjectSerializationInfo info, ILGenerator il, LocalBuilder localResult) { - if (type.GetTypeInfo().ImplementedInterfaces.All(x => x != typeof(IMessagePackSerializationCallbackReceiver))) + if (type.GetInterfaces().All(x => x != typeof(IMessagePackSerializationCallbackReceiver))) { return; } @@ -1197,7 +1197,7 @@ private static void BuildDeserializeInternalDeserializeValueAssignDirectly(TypeB } else if (ObjectSerializationInfo.IsOptimizeTargetType(t)) { - if (!t.GetTypeInfo().IsValueType) + if (!t.IsValueType) { // As a nullable type (e.g. byte[] and string) we need to first call TryReadNil // if (reader.TryReadNil()) @@ -1267,7 +1267,7 @@ private static void BuildDeserializeInternalDeserializeValueAssignLocalVariable( } else if (ObjectSerializationInfo.IsOptimizeTargetType(t)) { - if (!t.GetTypeInfo().IsValueType) + if (!t.IsValueType) { // As a nullable type (e.g. byte[] and string) we need to first call TryReadNil // if (reader.TryReadNil()) @@ -1383,8 +1383,8 @@ internal static class CodeGenHelpersTypeInfo internal static class EmitInfo { internal static readonly MethodInfo GetTypeFromHandle = ExpressionUtility.GetMethodInfo(() => Type.GetTypeFromHandle(default(RuntimeTypeHandle))); - internal static readonly MethodInfo TypeGetProperty = ExpressionUtility.GetMethodInfo((Type t) => t.GetTypeInfo().GetProperty(default(string)!, default(BindingFlags))); - internal static readonly MethodInfo TypeGetField = ExpressionUtility.GetMethodInfo((Type t) => t.GetTypeInfo().GetField(default(string)!, default(BindingFlags))); + internal static readonly MethodInfo TypeGetProperty = ExpressionUtility.GetMethodInfo((Type t) => t.GetProperty(default(string)!, default(BindingFlags))); + internal static readonly MethodInfo TypeGetField = ExpressionUtility.GetMethodInfo((Type t) => t.GetField(default(string)!, default(BindingFlags))); internal static readonly MethodInfo GetCustomAttributeMessagePackFormatterAttribute = ExpressionUtility.GetMethodInfo(() => CustomAttributeExtensions.GetCustomAttribute(default(MemberInfo)!, default(bool))); internal static readonly MethodInfo ActivatorCreateInstance = ExpressionUtility.GetMethodInfo(() => Activator.CreateInstance(default(Type)!, default(object[]))); @@ -1451,13 +1451,12 @@ private ObjectSerializationInfo(Type type, EmittableMemberAndConstructorParamete internal static ObjectSerializationInfo? CreateOrNull(Type type, bool forceStringKey, bool contractless, bool allowPrivate) { - TypeInfo ti = type.GetTypeInfo(); - var isClass = ti.IsClass || ti.IsInterface || ti.IsAbstract; - var isClassRecord = isClass && IsClassRecord(ti); - var isStruct = ti.IsValueType; + var isClass = type.IsClass || type.IsInterface || type.IsAbstract; + var isClassRecord = isClass && IsClassRecord(type); + var isStruct = type.IsValueType; - MessagePackObjectAttribute? contractAttr = ti.GetCustomAttributes().FirstOrDefault(); - DataContractAttribute? dataContractAttr = ti.GetCustomAttribute(); + MessagePackObjectAttribute? contractAttr = type.GetCustomAttributes().FirstOrDefault(); + DataContractAttribute? dataContractAttr = type.GetCustomAttribute(); if (contractAttr == null && dataContractAttr == null && !forceStringKey && !contractless) { return null; @@ -1672,11 +1671,12 @@ bool AddEmittableMemberOrIgnore(bool isIntKeyMode, EmittableMember member, bool // GetConstructor IEnumerator? ctorEnumerator = null; - ConstructorInfo? ctor = ti.DeclaredConstructors.SingleOrDefault(x => x.GetCustomAttribute(false) is not null); + ConstructorInfo? ctor = type.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly) + .SingleOrDefault(x => x.GetCustomAttribute(false) is not null); if (ctor == null) { - ctorEnumerator = - ti.DeclaredConstructors.Where(x => !x.IsStatic && (allowPrivate || x.IsPublic)).OrderByDescending(x => x.GetParameters().Length) + ctorEnumerator = (allowPrivate ? type.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly) : + type.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly)).OrderByDescending(x => x.GetParameters().Length) .GetEnumerator(); if (ctorEnumerator.MoveNext()) @@ -1709,7 +1709,7 @@ bool AddEmittableMemberOrIgnore(bool isIntKeyMode, EmittableMember member, bool if (ctorParamIndexIntMembersDictionary.TryGetValue(ctorParamIndex, out paramMember)) { if ((item.ParameterType == paramMember.Type || - item.ParameterType.GetTypeInfo().IsAssignableFrom(paramMember.Type)) + item.ParameterType.IsAssignableFrom(paramMember.Type)) && paramMember.IsReadable) { constructorParameters.Add(new EmittableMemberAndConstructorParameter(paramMember, item)); @@ -1915,7 +1915,7 @@ private static IEnumerable GetAllProperties(Type type) } } - private static bool IsClassRecord(TypeInfo type) + private static bool IsClassRecord(Type type) { // The only truly unique thing about a C# 9 record class is the presence of a $ method, // which cannot be declared in C# because of the reserved characters in its name. diff --git a/src/MessagePack/Resolvers/DynamicUnionResolver.cs b/src/MessagePack/Resolvers/DynamicUnionResolver.cs index b65ca772d..9281c6667 100644 --- a/src/MessagePack/Resolvers/DynamicUnionResolver.cs +++ b/src/MessagePack/Resolvers/DynamicUnionResolver.cs @@ -68,44 +68,42 @@ private static class FormatterCache static FormatterCache() { - TypeInfo ti = typeof(T).GetTypeInfo(); - if (ti.IsNullable()) + Type type = typeof(T); + if (type.IsNullable()) { - ti = ti.GenericTypeArguments[0].GetTypeInfo(); + type = type.GenericTypeArguments[0]; - var innerFormatter = DynamicUnionResolver.Instance.GetFormatterDynamic(ti.AsType()); + var innerFormatter = DynamicUnionResolver.Instance.GetFormatterDynamic(type); if (innerFormatter == null) { return; } - Formatter = (IMessagePackFormatter?)Activator.CreateInstance(typeof(StaticNullableFormatter<>).MakeGenericType(ti.AsType()), new object[] { innerFormatter }); + Formatter = (IMessagePackFormatter?)Activator.CreateInstance(typeof(StaticNullableFormatter<>).MakeGenericType(type), new object[] { innerFormatter }); return; } - TypeInfo? formatterTypeInfo = BuildType(typeof(T)); + TypeInfo? formatterTypeInfo = BuildType(type); if (formatterTypeInfo == null) { return; } - Formatter = (IMessagePackFormatter?)Activator.CreateInstance(formatterTypeInfo.AsType()); + Formatter = (IMessagePackFormatter?)Activator.CreateInstance(formatterTypeInfo); } } private static TypeInfo? BuildType(Type type) { - TypeInfo ti = type.GetTypeInfo(); - // order by key(important for use jump-table of switch) - UnionAttribute[] unionAttrs = ti.GetCustomAttributes().OrderBy(x => x.Key).ToArray(); + UnionAttribute[] unionAttrs = type.GetCustomAttributes().OrderBy(x => x.Key).ToArray(); if (unionAttrs.Length == 0) { return null; } - if (!ti.IsInterface && !ti.IsAbstract) + if (!type.IsInterface && !type.IsAbstract) { throw new MessagePackDynamicUnionResolverException("Union can only be interface or abstract class. Type:" + type.Name); } @@ -283,7 +281,7 @@ private static void BuildSerialize(Type type, UnionAttribute[] infos, MethodBuil il.EmitLdarg(1); il.EmitLdarg(2); - if (item.Attr.SubType.GetTypeInfo().IsValueType) + if (item.Attr.SubType.IsValueType) { il.Emit(OpCodes.Unbox_Any, item.Attr.SubType); } @@ -387,7 +385,7 @@ private static void BuildDeserialize(Type type, UnionAttribute[] infos, MethodBu il.EmitLdarg(1); il.EmitLdarg(2); il.EmitCall(getDeserialize(item.Attr.SubType)); - if (item.Attr.SubType.GetTypeInfo().IsValueType) + if (item.Attr.SubType.IsValueType) { il.Emit(OpCodes.Box, item.Attr.SubType); } diff --git a/src/MessagePack/Resolvers/ImmutableCollectionResolver.cs b/src/MessagePack/Resolvers/ImmutableCollectionResolver.cs index de645990e..b7eb81dc5 100644 --- a/src/MessagePack/Resolvers/ImmutableCollectionResolver.cs +++ b/src/MessagePack/Resolvers/ImmutableCollectionResolver.cs @@ -63,18 +63,15 @@ internal static class ImmutableCollectionGetFormatterHelper internal static object? GetFormatter(Type t) { - TypeInfo ti = t.GetTypeInfo(); - - if (ti.IsGenericType) + if (t.IsGenericType) { - Type genericType = ti.GetGenericTypeDefinition(); - TypeInfo genericTypeInfo = genericType.GetTypeInfo(); - var isNullable = genericTypeInfo.IsNullable(); - Type? nullableElementType = isNullable ? ti.GenericTypeArguments[0] : null; + Type genericType = t.GetGenericTypeDefinition(); + var isNullable = genericType.IsNullable(); + Type? nullableElementType = isNullable ? t.GenericTypeArguments[0] : null; if (FormatterMap.TryGetValue(genericType, out Type? formatterType)) { - return CreateInstance(formatterType, ti.GenericTypeArguments); + return CreateInstance(formatterType, t.GenericTypeArguments); } else if (isNullable && nullableElementType?.IsConstructedGenericType is true && nullableElementType.GetGenericTypeDefinition() == typeof(ImmutableArray<>)) { @@ -97,5 +94,10 @@ public static bool IsNullable(this System.Reflection.TypeInfo type) { return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(System.Nullable<>); } + + public static bool IsNullable(this Type type) + { + return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(System.Nullable<>); + } } } diff --git a/src/MessagePack/Resolvers/SkipClrVisibilityChecks.cs b/src/MessagePack/Resolvers/SkipClrVisibilityChecks.cs index c6fdfccfc..17d463fef 100644 --- a/src/MessagePack/Resolvers/SkipClrVisibilityChecks.cs +++ b/src/MessagePack/Resolvers/SkipClrVisibilityChecks.cs @@ -76,39 +76,39 @@ internal SkipClrVisibilityChecks(AssemblyBuilder assemblyBuilder, ModuleBuilder /// Scans a given type for references to non-public types and adds any assemblies that declare those types /// to a given set. /// - /// The type which may be internal. + /// The type which may be internal. /// The set of assemblies to add to where non-public types are found. - internal static void GetSkipVisibilityChecksRequirements(TypeInfo typeInfo, ImmutableHashSet.Builder referencedAssemblies) + internal static void GetSkipVisibilityChecksRequirements(Type type, ImmutableHashSet.Builder referencedAssemblies) { - if (typeInfo.IsArray) + if (type.IsArray) { - GetSkipVisibilityChecksRequirements(typeInfo.GetElementType()!.GetTypeInfo(), referencedAssemblies); + GetSkipVisibilityChecksRequirements(type.GetElementType()!, referencedAssemblies); } - AddTypeIfNonPublic(typeInfo); + AddTypeIfNonPublic(type); - foreach (Type arg in typeInfo.GenericTypeArguments) + foreach (Type arg in type.GenericTypeArguments) { AddTypeIfNonPublic(arg); } // We must walk each base type individually to ensure we don't miss any private members, // since even with BindingFlags.NonPublic, GetMembers will not return from base types. - for (TypeInfo? target = typeInfo; target is not null; target = target.BaseType?.GetTypeInfo()) + for (Type? target = type; target is not null; target = target.BaseType) { ScanDirectType(target); } - void ScanDirectType(TypeInfo typeInfo) + void ScanDirectType(Type type) { - foreach (MemberInfo member in typeInfo.GetMembers(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly)) + foreach (MemberInfo member in type.GetMembers(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly)) { switch (member) { case FieldInfo field: if (!field.IsPublic) { - referencedAssemblies.Add(typeInfo.Assembly.GetName()); + referencedAssemblies.Add(type.Assembly.GetName()); } AddTypeIfNonPublic(field.FieldType); @@ -116,7 +116,7 @@ void ScanDirectType(TypeInfo typeInfo) case PropertyInfo property: if (property.SetMethod?.IsPublic is false || property.GetMethod?.IsPublic is false) { - referencedAssemblies.Add(typeInfo.Assembly.GetName()); + referencedAssemblies.Add(type.Assembly.GetName()); } AddTypeIfNonPublic(property.PropertyType); @@ -124,7 +124,7 @@ void ScanDirectType(TypeInfo typeInfo) case ConstructorInfo constructorInfo: if (!constructorInfo.IsPublic) { - referencedAssemblies.Add(typeInfo.Assembly.GetName()); + referencedAssemblies.Add(type.Assembly.GetName()); } foreach (ParameterInfo parameter in constructorInfo.GetParameters()) From f82f9eb81de3f64529f3d6599d49d554c553745d Mon Sep 17 00:00:00 2001 From: ABykiev Date: Mon, 18 Aug 2025 21:44:07 +0300 Subject: [PATCH 03/17] Fix various disposable issues This PR fixes a memory leak in Json Serializer and other small disposable issues --- .../Formatters/DictionaryFormatter.cs | 7 +----- src/MessagePack/Internal/Sequence`1.cs | 2 +- src/MessagePack/Internal/TinyJsonReader.cs | 2 +- src/MessagePack/MessagePackSerializer.Json.cs | 22 +++++++++++-------- 4 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/MessagePack/Formatters/DictionaryFormatter.cs b/src/MessagePack/Formatters/DictionaryFormatter.cs index af4d367c5..32ebc2c6b 100644 --- a/src/MessagePack/Formatters/DictionaryFormatter.cs +++ b/src/MessagePack/Formatters/DictionaryFormatter.cs @@ -53,8 +53,7 @@ public void Serialize(ref MessagePackWriter writer, TDictionary? value, MessageP writer.WriteMapHeader(count); - TEnumerator e = this.GetSourceEnumerator(value); - try + using (TEnumerator e = this.GetSourceEnumerator(value)) { while (e.MoveNext()) { @@ -64,10 +63,6 @@ public void Serialize(ref MessagePackWriter writer, TDictionary? value, MessageP valueFormatter.Serialize(ref writer, item.Value, options); } } - finally - { - e.Dispose(); - } } } diff --git a/src/MessagePack/Internal/Sequence`1.cs b/src/MessagePack/Internal/Sequence`1.cs index 3d9ec35ac..72a445541 100644 --- a/src/MessagePack/Internal/Sequence`1.cs +++ b/src/MessagePack/Internal/Sequence`1.cs @@ -28,7 +28,7 @@ namespace Nerdbank.Streams // NOTE: invalid namespace, should modify /// Instance members are not thread-safe. /// [DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")] - internal class Sequence : IBufferWriter, IDisposable + internal sealed class Sequence : IBufferWriter, IDisposable { private const int MaximumAutoGrowSize = 32 * 1024; diff --git a/src/MessagePack/Internal/TinyJsonReader.cs b/src/MessagePack/Internal/TinyJsonReader.cs index e4ceaabfe..dd386177c 100644 --- a/src/MessagePack/Internal/TinyJsonReader.cs +++ b/src/MessagePack/Internal/TinyJsonReader.cs @@ -58,7 +58,7 @@ protected TinyJsonException(SerializationInfo info, StreamingContext context) } } - internal class TinyJsonReader : IDisposable + internal sealed class TinyJsonReader : IDisposable { private readonly TextReader reader; private readonly bool disposeInnerReader; diff --git a/src/MessagePack/MessagePackSerializer.Json.cs b/src/MessagePack/MessagePackSerializer.Json.cs index 1dfdf49db..ebcddd555 100644 --- a/src/MessagePack/MessagePackSerializer.Json.cs +++ b/src/MessagePack/MessagePackSerializer.Json.cs @@ -44,9 +44,11 @@ public static void SerializeToJson(TextWriter textWriter, T obj, MessagePackS /// Thrown if an error occurs during serialization. public static string SerializeToJson(T obj, MessagePackSerializerOptions? options = null, CancellationToken cancellationToken = default) { - var writer = new StringWriter(); - SerializeToJson(writer, obj, options, cancellationToken); - return writer.ToString(); + using (var writer = new StringWriter()) + { + SerializeToJson(writer, obj, options, cancellationToken); + return writer.ToString(); + } } /// @@ -61,13 +63,15 @@ public static string SerializeToJson(T obj, MessagePackSerializerOptions? opt /// Thrown if an error occurs while reading the messagepack data or writing out the JSON. public static string ConvertToJson(in ReadOnlySequence bytes, MessagePackSerializerOptions? options = null, CancellationToken cancellationToken = default) { - var jsonWriter = new StringWriter(); - var reader = new MessagePackReader(bytes) + using (var jsonWriter = new StringWriter()) { - CancellationToken = cancellationToken, - }; - ConvertToJson(ref reader, jsonWriter, options); - return jsonWriter.ToString(); + var reader = new MessagePackReader(bytes) + { + CancellationToken = cancellationToken, + }; + ConvertToJson(ref reader, jsonWriter, options); + return jsonWriter.ToString(); + } } /// From 503949862b5c237ddd3413e0f4877c71505656a4 Mon Sep 17 00:00:00 2001 From: T0PP1ng <110217581+T0PP1ng@users.noreply.github.com> Date: Thu, 21 Aug 2025 11:14:24 +0700 Subject: [PATCH 04/17] Use UtcTicks instead of Ticks --- src/MessagePack/Formatters/StandardClassLibraryFormatter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MessagePack/Formatters/StandardClassLibraryFormatter.cs b/src/MessagePack/Formatters/StandardClassLibraryFormatter.cs index 51ce1f8c1..ef1479e59 100644 --- a/src/MessagePack/Formatters/StandardClassLibraryFormatter.cs +++ b/src/MessagePack/Formatters/StandardClassLibraryFormatter.cs @@ -257,7 +257,7 @@ private DateTimeOffsetFormatter() public void Serialize(ref MessagePackWriter writer, DateTimeOffset value, MessagePackSerializerOptions options) { writer.WriteArrayHeader(2); - writer.Write(new DateTime(value.Ticks, DateTimeKind.Utc)); // current ticks as is + writer.Write(new DateTime(value.UtcTicks, DateTimeKind.Utc)); // current ticks as is writer.Write((short)value.Offset.TotalMinutes); // offset is normalized in minutes return; } From 39e4a792100d19e7da6ed42ff12b3bc21aac28e8 Mon Sep 17 00:00:00 2001 From: Khuong Nguyen Date: Mon, 1 Sep 2025 02:39:19 +0700 Subject: [PATCH 05/17] Merge pull request #2226 from khuongntrd/fix/recursive-generic-source-generation fix: prevent StackOverflow in Equals with recursive generic constraints --- .../CodeAnalysis/GenericTypeParameterInfo.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/MessagePack.SourceGenerator/CodeAnalysis/GenericTypeParameterInfo.cs b/src/MessagePack.SourceGenerator/CodeAnalysis/GenericTypeParameterInfo.cs index c35fe171a..959df2db8 100644 --- a/src/MessagePack.SourceGenerator/CodeAnalysis/GenericTypeParameterInfo.cs +++ b/src/MessagePack.SourceGenerator/CodeAnalysis/GenericTypeParameterInfo.cs @@ -1,4 +1,4 @@ -// Copyright (c) All contributors. All rights reserved. +// Copyright (c) All contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Collections.Immutable; @@ -88,4 +88,8 @@ void AddIf(bool condition, string constraint) return builder.ToString(); } + + public virtual bool Equals(GenericTypeParameterInfo? other) => other is not null && this.Name == other.Name; + + public override int GetHashCode() => StringComparer.Ordinal.GetHashCode(this.Name); } From 068f70e2abd319292bf832db8ec8cbf9e5220150 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Wed, 22 Oct 2025 21:00:05 -0600 Subject: [PATCH 06/17] Fix LICENSE notices for BufferWriter.cs --- LICENSE | 18 ++++++++++++++++++ src/MessagePack/BufferWriter.cs | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index eebeb87e0..c4bc51dae 100644 --- a/LICENSE +++ b/LICENSE @@ -39,3 +39,21 @@ Redistributions of source code must retain the above copyright notice, this list Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +--- + +BufferWriter.cs + +Copyright 2019 .NET Foundation + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/src/MessagePack/BufferWriter.cs b/src/MessagePack/BufferWriter.cs index 90ee06e3e..b2ca60937 100644 --- a/src/MessagePack/BufferWriter.cs +++ b/src/MessagePack/BufferWriter.cs @@ -2,7 +2,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Copyright (c) Andrew Arnott. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. using System; using System.Buffers; From d853db62e6bcc40a426b28f9674ffebe5f8b7854 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Mon, 30 Mar 2026 16:13:47 -0600 Subject: [PATCH 07/17] Add more types to the default disallow list of named types to be deserialized --- src/MessagePack/MessagePackSerializerOptions.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/MessagePack/MessagePackSerializerOptions.cs b/src/MessagePack/MessagePackSerializerOptions.cs index f750f93df..dc4598239 100644 --- a/src/MessagePack/MessagePackSerializerOptions.cs +++ b/src/MessagePack/MessagePackSerializerOptions.cs @@ -25,7 +25,13 @@ public class MessagePackSerializerOptions private static readonly HashSet DisallowedTypes = new HashSet { "System.CodeDom.Compiler.TempFileCollection", + "System.IdentityModel.Tokens.SessionSecurityToken", "System.Management.IWbemClassObjectFreeThreaded", + "System.Security.Claims.ClaimsIdentity", + "System.Security.Principal.WindowsIdentity", + "System.Web.Security.RolePrincipal", + "System.Windows.Data.ObjectDataProvider", + "System.Windows.ResourceDictionary", }; #if !DYNAMICCODEDUMPER From 5509faa527d336f4596925c66470d3cf16ff4f0a Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Wed, 13 May 2026 16:06:35 -0600 Subject: [PATCH 08/17] Fix typo --- src/MessagePack/Formatters/UnsafeBinaryFormatters.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MessagePack/Formatters/UnsafeBinaryFormatters.cs b/src/MessagePack/Formatters/UnsafeBinaryFormatters.cs index 19147191c..df4a7c136 100644 --- a/src/MessagePack/Formatters/UnsafeBinaryFormatters.cs +++ b/src/MessagePack/Formatters/UnsafeBinaryFormatters.cs @@ -66,7 +66,7 @@ private NativeDecimalFormatter() { } - /* decimal underlying "flags, hi, lo, mid" fields are sequential and same layuout with .NET Framework and Mono(Unity) + /* decimal underlying "flags, hi, lo, mid" fields are sequential and same layout with .NET Framework and Mono(Unity) * But target machines must be same endian so restrict only for little endian. */ public unsafe void Serialize(ref MessagePackWriter writer, Decimal value, MessagePackSerializerOptions options) From c7fea751b0155d957d628d237fb5a509626c44c6 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Wed, 13 May 2026 16:07:14 -0600 Subject: [PATCH 09/17] Ignore *.lscache files These files are produced by the new C# Dev Kit extension. --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 6ef3a7ca5..7209c6cbc 100644 --- a/.gitignore +++ b/.gitignore @@ -371,3 +371,5 @@ src/MessagePack.UnityClient/Assets/Packages/ BenchmarkDotNet.Artifacts/ src/MessagePack.UnityClient/.vsconfig + +*.lscache From 2e191c0b1c42161aac8c6f5f019d82d3563d7bcc Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Wed, 13 May 2026 16:38:40 -0600 Subject: [PATCH 10/17] Build with .NET SDK 10.0.300 --- global.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/global.json b/global.json index 088f23e11..723fc16cb 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "9.0.100", + "version": "10.0.300", "rollForward": "patch", "allowPrerelease": false } From ecba7969d234c317bd1f0ee9d6327ead46e61ec2 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Thu, 14 May 2026 12:28:28 -0600 Subject: [PATCH 11/17] Revert #2225 which broke our tests --- .../StandardClassLibraryFormatter.cs | 5 +++- tests/MessagePack.Tests/FormatterTest.cs | 24 +++++++++++++++++-- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/MessagePack/Formatters/StandardClassLibraryFormatter.cs b/src/MessagePack/Formatters/StandardClassLibraryFormatter.cs index ef1479e59..71a64908f 100644 --- a/src/MessagePack/Formatters/StandardClassLibraryFormatter.cs +++ b/src/MessagePack/Formatters/StandardClassLibraryFormatter.cs @@ -257,7 +257,10 @@ private DateTimeOffsetFormatter() public void Serialize(ref MessagePackWriter writer, DateTimeOffset value, MessagePackSerializerOptions options) { writer.WriteArrayHeader(2); - writer.Write(new DateTime(value.UtcTicks, DateTimeKind.Utc)); // current ticks as is + + // We're writing a *local* DateTime value in msgpack encoding as if it were UTC time. + // That's incorrect msgpack encoding, but fixing it now would compromise backward compatibility. + writer.Write(new DateTime(value.Ticks, DateTimeKind.Utc)); // current ticks as is writer.Write((short)value.Offset.TotalMinutes); // offset is normalized in minutes return; } diff --git a/tests/MessagePack.Tests/FormatterTest.cs b/tests/MessagePack.Tests/FormatterTest.cs index d74e4ba5e..fdb496b06 100644 --- a/tests/MessagePack.Tests/FormatterTest.cs +++ b/tests/MessagePack.Tests/FormatterTest.cs @@ -227,8 +227,12 @@ public void DateTimeOffsetTest() { string id = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "Tokyo Standard Time" : "Cuba"; DateTimeOffset now = new DateTime(DateTime.UtcNow.Ticks + TimeZoneInfo.FindSystemTimeZoneById(id).BaseUtcOffset.Ticks, DateTimeKind.Local); - var binary = MessagePackSerializer.Serialize(now); - MessagePackSerializer.Deserialize(binary).Is(now); + AssertRoundtrip(now); + + AssertRoundtrip(DateTimeOffset.Now); + + // Try specific offset values because CI/PR builds run on agents that run on the UTC time zone. + AssertRoundtrip(DateTimeOffset.Now.ToOffset(TimeSpan.FromHours(4))); } [Fact] @@ -323,5 +327,21 @@ public void HalfTest() } #endif + + private static DateTimeOffset AssertRoundtrip(DateTimeOffset value) + { + var result = MessagePackSerializer.Deserialize(MessagePackSerializer.Serialize(value)); + result.Is(value, DateTimeOffsetEqualityComparer.Instance); + return result; + } + + private class DateTimeOffsetEqualityComparer : IEqualityComparer + { + internal static readonly DateTimeOffsetEqualityComparer Instance = new(); + + public bool Equals(DateTimeOffset x, DateTimeOffset y) => x.EqualsExact(y); + + public int GetHashCode(DateTimeOffset obj) => obj.UtcDateTime.GetHashCode(); + } } } From 06ea24954efa0fe6f1cac5d87046f9b54dac2dcc Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Fri, 15 May 2026 10:06:03 -0600 Subject: [PATCH 12/17] Fix build warnings --- sandbox/Sandbox/Program.cs | 3 ++- .../MessagePack.SourceGenerator.csproj | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/sandbox/Sandbox/Program.cs b/sandbox/Sandbox/Program.cs index d787a3247..f8f3ad819 100644 --- a/sandbox/Sandbox/Program.cs +++ b/sandbox/Sandbox/Program.cs @@ -26,7 +26,8 @@ //} -public class ClassA where T : ClassA.ClassB +public class ClassA + where T : ClassA.ClassB { public class ClassB { diff --git a/src/MessagePack.SourceGenerator/MessagePack.SourceGenerator.csproj b/src/MessagePack.SourceGenerator/MessagePack.SourceGenerator.csproj index e3c4c5e90..5c73a4db2 100644 --- a/src/MessagePack.SourceGenerator/MessagePack.SourceGenerator.csproj +++ b/src/MessagePack.SourceGenerator/MessagePack.SourceGenerator.csproj @@ -9,6 +9,7 @@ embedded false true + $(NoWarn);RS2007 From 50a24731211b365ef8a7204db8b7e190eed2fa04 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Wed, 29 Apr 2026 15:30:08 -0600 Subject: [PATCH 13/17] Document strong-name key intent for CWE-798 --- SECURITY.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/SECURITY.md b/SECURITY.md index aedee6a15..e2612cff1 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -12,6 +12,12 @@ Each supported major version is only serviced for security issues at its tip. For example 2.5 will receive updates but 2.4 will not. 3.0 will receive updates until 3.1 is stable, at which point 3.0 will no longer received security updates. +## Strong-name key + +This repository intentionally includes `opensource.snk`, the strong-name key used to provide stable assembly identity for open-source builds. This key is public by design and is not a credential or package-authenticity secret. + +Do not rely on strong names as a security boundary or as proof that a package was published by the project maintainers. Consume packages from trusted package sources and use the normal NuGet and release provenance checks for package authenticity. + ## Reporting a Vulnerability Please use [the Security tab](https://github.com/MessagePack-CSharp/MessagePack-CSharp/security) to responsibly report security vulnerabilities. From 49db75fef8aeb6cfa48366f452a1bba0ae1aedc8 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Wed, 29 Apr 2026 15:32:09 -0600 Subject: [PATCH 14/17] Harden release dry-run logging for CWE-532 --- .github/workflows/_create-release.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/_create-release.yaml b/.github/workflows/_create-release.yaml index ec5728191..d3133a142 100644 --- a/.github/workflows/_create-release.yaml +++ b/.github/workflows/_create-release.yaml @@ -153,7 +153,7 @@ jobs: fi if [[ "${{ inputs.dry-run }}" == "true" ]]; then - echo "(dry run) dotnet nuget push \"${nuget_path}\" --skip-duplicate -s https://api.nuget.org/v3/index.json -k \"${{ env.NUGET_KEY }}\"" + echo "(dry run) dotnet nuget push \"${nuget_path}\" --skip-duplicate -s https://api.nuget.org/v3/index.json -k \"***\"" else dotnet nuget push "${nuget_path}" --skip-duplicate -s https://api.nuget.org/v3/index.json -k "${{ env.NUGET_KEY }}" fi From 9ee41c497cd5733386f5a3db86f3bcc740cc96a0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 20 May 2026 23:36:36 +0000 Subject: [PATCH 15/17] feat: Update package.json to 3.1.5 Commit by [GitHub Actions](https://github.com/MessagePack-CSharp/MessagePack-CSharp/actions/runs/26196209731) --- .../Assets/Scripts/MessagePack/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/package.json b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/package.json index 097e1d562..23ec4fe5f 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/package.json +++ b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/package.json @@ -1,7 +1,7 @@ { "name": "com.github.messagepack-csharp", "displayName": "MessagePack", - "version": "3.1.4", + "version": "3.1.5", "unity": "2021.3", "description": "Extremely Fast MessagePack Serializer for C#.", "keywords": [ From a48b40bdb62471e82913fe0b501862af99d9e603 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Wed, 20 May 2026 17:42:52 -0600 Subject: [PATCH 16/17] Use newer github actions --- .github/actions/setup-dotnet/action.yaml | 2 +- .github/workflows/_create-release.yaml | 9 ++++++--- .github/workflows/_update-packagejson.yaml | 2 +- .github/workflows/build-release.yml | 4 ++-- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/.github/actions/setup-dotnet/action.yaml b/.github/actions/setup-dotnet/action.yaml index e44880788..839f27ae0 100644 --- a/.github/actions/setup-dotnet/action.yaml +++ b/.github/actions/setup-dotnet/action.yaml @@ -14,7 +14,7 @@ runs: using: "composite" steps: # see: https://github.com/actions/setup-dotnet - - uses: actions/setup-dotnet@v4 + - uses: actions/setup-dotnet@v5 with: global-json-file: ${{ inputs.global-json-file }} diff --git a/.github/workflows/_create-release.yaml b/.github/workflows/_create-release.yaml index d3133a142..3fda09b25 100644 --- a/.github/workflows/_create-release.yaml +++ b/.github/workflows/_create-release.yaml @@ -56,14 +56,17 @@ jobs: echo "Validation error! 'inputs.release-asset-path' cannot be blank when 'inputs.release-upload' is true." exit 1 - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: ref: ${{ inputs.commit-id }} - uses: ./.github/actions/setup-dotnet # Download(All) Artifacts to $GITHUB_WORKSPACE - - name: donload artifacts - uses: actions/download-artifact@v4 # must sync with actions/upload-artifact@v4 in build-release + - name: download artifacts + uses: actions/download-artifact@v8 # must sync with actions/upload-artifact@v7 in build-release + with: + name: nuget + path: ./nuget - name: Show download aritifacts run: ls -lR - name: Validate package exists in artifact - release assets diff --git a/.github/workflows/_update-packagejson.yaml b/.github/workflows/_update-packagejson.yaml index c461c9bed..6b64f8e1b 100644 --- a/.github/workflows/_update-packagejson.yaml +++ b/.github/workflows/_update-packagejson.yaml @@ -71,7 +71,7 @@ jobs: run: | echo "branch-name=test-release/${{ inputs.tag }}" | tee -a "$GITHUB_OUTPUT" - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 # package.json # "version": 1.2.3 -> "version": 2.0.0 diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 444bdae1a..d15486134 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -34,7 +34,7 @@ jobs: timeout-minutes: 10 steps: - run: echo ${{ needs.update-packagejson.outputs.sha }} - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: ref: ${{ needs.update-packagejson.outputs.sha }} fetch-depth: 0 @@ -44,7 +44,7 @@ jobs: - run: dotnet test -c Release --no-build - run: dotnet pack -c Release -p:Version=${{ needs.update-packagejson.outputs.normalized_tag }} -o ./publish - name: upload artifacts - uses: actions/upload-artifact@v4 # must sync with actions/download-artifact@v4 in create-release + uses: actions/upload-artifact@v7 # must sync with actions/download-artifact@v8 in create-release with: name: nuget path: ./publish/ From 56aee1ecf45ed0549649cda019c3143de3fb0f47 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Wed, 20 May 2026 18:01:43 -0600 Subject: [PATCH 17/17] Switch to NuGet Trusted Publishing --- .github/workflows/_create-release.yaml | 13 +++++++++++-- .github/workflows/build-release.yml | 1 + 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/workflows/_create-release.yaml b/.github/workflows/_create-release.yaml index 3fda09b25..1c88829c7 100644 --- a/.github/workflows/_create-release.yaml +++ b/.github/workflows/_create-release.yaml @@ -42,10 +42,12 @@ on: jobs: create-release: name: Create Release + permissions: + id-token: write + contents: write env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} # auto generated token GH_REPO: ${{ github.repository }} - NUGET_KEY: ${{ secrets.NUGET_KEY }} runs-on: ubuntu-latest timeout-minutes: 10 steps: @@ -143,6 +145,13 @@ jobs: done <<< "${{ inputs.release-asset-path }}" if: ${{ inputs.release-upload }} + - name: NuGet login (OIDC) + id: nuget-login + if: ${{ inputs.nuget-push }} + uses: NuGet/login@v1 + with: + user: ${{ secrets.NUGET_USER }} + # Upload to NuGet - name: Upload to NuGet (DryRun=${{ inputs.dry-run }}) if: ${{ inputs.nuget-push }} @@ -158,7 +167,7 @@ jobs: if [[ "${{ inputs.dry-run }}" == "true" ]]; then echo "(dry run) dotnet nuget push \"${nuget_path}\" --skip-duplicate -s https://api.nuget.org/v3/index.json -k \"***\"" else - dotnet nuget push "${nuget_path}" --skip-duplicate -s https://api.nuget.org/v3/index.json -k "${{ env.NUGET_KEY }}" + dotnet nuget push "${nuget_path}" --skip-duplicate -s https://api.nuget.org/v3/index.json -k "${{ steps.nuget-login.outputs.NUGET_API_KEY }}" fi done <<< "${{ inputs.nuget-path }}" diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index d15486134..b73370cf9 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -15,6 +15,7 @@ on: permissions: actions: write contents: write + id-token: write jobs: # for unity. need update package.json from tag