From 8351bec1dfdbe0d3da6479b590b2b079954e825e Mon Sep 17 00:00:00 2001 From: Victor Nicollet Date: Fri, 18 Jul 2025 15:17:17 +0200 Subject: [PATCH 01/38] 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/38] 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/38] 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/38] 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/38] 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/38] 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/38] 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/38] 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/38] 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/38] 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/38] 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/38] 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 c1c06a6fa242bba9aff0b01c6ced31bd3afb7e97 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Thu, 14 May 2026 14:39:43 -0600 Subject: [PATCH 13/38] Fix WriteRawX methods to advance by written length Before this change, the length of the source span would dictate how large the destination span became and how far we advanced the writer, without any regard to how many bytes would actually be copied into that buffer. --- src/MessagePack/Internal/UnsafeMemory.Low.cs | 56 +++-- src/MessagePack/Internal/UnsafeMemory.cs | 208 +++++++++---------- src/MessagePack/Internal/UnsafeMemory.tt | 8 +- tests/MessagePack.Tests/UnsafeMemoryTest.cs | 35 +++- 4 files changed, 173 insertions(+), 134 deletions(-) diff --git a/src/MessagePack/Internal/UnsafeMemory.Low.cs b/src/MessagePack/Internal/UnsafeMemory.Low.cs index 00683ea89..08224a7a5 100644 --- a/src/MessagePack/Internal/UnsafeMemory.Low.cs +++ b/src/MessagePack/Internal/UnsafeMemory.Low.cs @@ -15,12 +15,20 @@ public static class UnsafeMemory public static readonly bool Is32Bit = IntPtr.Size == 4; } + /// + /// Highly tuned method for writing raw bytes to a . + /// + /// + /// The methods on this class are not safe, in that they use pointer arithmetic + /// and assume that the caller has provided a with a length + /// of at least the number of bytes being written. The caller must ensure that this is the case. + /// public static partial class UnsafeMemory32 { [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw1(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(1); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -28,13 +36,13 @@ public static unsafe void WriteRaw1(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(2); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -42,13 +50,13 @@ public static unsafe void WriteRaw2(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(3); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -57,16 +65,24 @@ public static unsafe void WriteRaw3(ref MessagePackWriter writer, ReadOnlySpan + /// Highly tuned method for writing raw bytes to a . + /// + /// + /// The methods on this class are not safe, in that they use pointer arithmetic + /// and assume that the caller has provided a with a length + /// of at least the number of bytes being written. The caller must ensure that this is the case. + /// public static partial class UnsafeMemory64 { [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw1(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(1); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -74,13 +90,13 @@ public static unsafe void WriteRaw1(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(2); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -88,13 +104,13 @@ public static unsafe void WriteRaw2(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(3); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -103,13 +119,13 @@ public static unsafe void WriteRaw3(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(4); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -117,13 +133,13 @@ public static unsafe void WriteRaw4(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(5); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -132,13 +148,13 @@ public static unsafe void WriteRaw5(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(6); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -147,13 +163,13 @@ public static unsafe void WriteRaw6(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(7); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -162,7 +178,7 @@ public static unsafe void WriteRaw7(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(4); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -26,13 +26,13 @@ public static unsafe void WriteRaw4(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(5); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -41,13 +41,13 @@ public static unsafe void WriteRaw5(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(6); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -56,13 +56,13 @@ public static unsafe void WriteRaw6(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(7); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -71,13 +71,13 @@ public static unsafe void WriteRaw7(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(8); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -86,13 +86,13 @@ public static unsafe void WriteRaw8(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(9); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -102,13 +102,13 @@ public static unsafe void WriteRaw9(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(10); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -118,13 +118,13 @@ public static unsafe void WriteRaw10(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 6) = *(int*)(pSrc + 6); } - writer.Advance(src.Length); + writer.Advance(10); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw11(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(11); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -134,13 +134,13 @@ public static unsafe void WriteRaw11(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 7) = *(int*)(pSrc + 7); } - writer.Advance(src.Length); + writer.Advance(11); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw12(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(12); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -150,13 +150,13 @@ public static unsafe void WriteRaw12(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 8) = *(int*)(pSrc + 8); } - writer.Advance(src.Length); + writer.Advance(12); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw13(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(13); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -167,13 +167,13 @@ public static unsafe void WriteRaw13(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 9) = *(int*)(pSrc + 9); } - writer.Advance(src.Length); + writer.Advance(13); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw14(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(14); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -184,13 +184,13 @@ public static unsafe void WriteRaw14(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 10) = *(int*)(pSrc + 10); } - writer.Advance(src.Length); + writer.Advance(14); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw15(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(15); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -201,13 +201,13 @@ public static unsafe void WriteRaw15(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 11) = *(int*)(pSrc + 11); } - writer.Advance(src.Length); + writer.Advance(15); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw16(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(16); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -218,13 +218,13 @@ public static unsafe void WriteRaw16(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 12) = *(int*)(pSrc + 12); } - writer.Advance(src.Length); + writer.Advance(16); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw17(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(17); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -236,13 +236,13 @@ public static unsafe void WriteRaw17(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 13) = *(int*)(pSrc + 13); } - writer.Advance(src.Length); + writer.Advance(17); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw18(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(18); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -254,13 +254,13 @@ public static unsafe void WriteRaw18(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 14) = *(int*)(pSrc + 14); } - writer.Advance(src.Length); + writer.Advance(18); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw19(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(19); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -272,13 +272,13 @@ public static unsafe void WriteRaw19(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 15) = *(int*)(pSrc + 15); } - writer.Advance(src.Length); + writer.Advance(19); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw20(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(20); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -290,13 +290,13 @@ public static unsafe void WriteRaw20(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 16) = *(int*)(pSrc + 16); } - writer.Advance(src.Length); + writer.Advance(20); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw21(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(21); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -309,13 +309,13 @@ public static unsafe void WriteRaw21(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 17) = *(int*)(pSrc + 17); } - writer.Advance(src.Length); + writer.Advance(21); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw22(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(22); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -328,13 +328,13 @@ public static unsafe void WriteRaw22(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 18) = *(int*)(pSrc + 18); } - writer.Advance(src.Length); + writer.Advance(22); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw23(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(23); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -347,13 +347,13 @@ public static unsafe void WriteRaw23(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 19) = *(int*)(pSrc + 19); } - writer.Advance(src.Length); + writer.Advance(23); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw24(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(24); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -366,13 +366,13 @@ public static unsafe void WriteRaw24(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 20) = *(int*)(pSrc + 20); } - writer.Advance(src.Length); + writer.Advance(24); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw25(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(25); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -386,13 +386,13 @@ public static unsafe void WriteRaw25(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 21) = *(int*)(pSrc + 21); } - writer.Advance(src.Length); + writer.Advance(25); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw26(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(26); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -406,13 +406,13 @@ public static unsafe void WriteRaw26(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 22) = *(int*)(pSrc + 22); } - writer.Advance(src.Length); + writer.Advance(26); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw27(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(27); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -426,13 +426,13 @@ public static unsafe void WriteRaw27(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 23) = *(int*)(pSrc + 23); } - writer.Advance(src.Length); + writer.Advance(27); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw28(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(28); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -446,13 +446,13 @@ public static unsafe void WriteRaw28(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 24) = *(int*)(pSrc + 24); } - writer.Advance(src.Length); + writer.Advance(28); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw29(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(29); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -467,13 +467,13 @@ public static unsafe void WriteRaw29(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 25) = *(int*)(pSrc + 25); } - writer.Advance(src.Length); + writer.Advance(29); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw30(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(30); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -488,13 +488,13 @@ public static unsafe void WriteRaw30(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 26) = *(int*)(pSrc + 26); } - writer.Advance(src.Length); + writer.Advance(30); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw31(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(31); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -509,7 +509,7 @@ public static unsafe void WriteRaw31(ref MessagePackWriter writer, ReadOnlySpan< *(int*)(pDst + 27) = *(int*)(pSrc + 27); } - writer.Advance(src.Length); + writer.Advance(31); } } @@ -518,7 +518,7 @@ public static partial class UnsafeMemory64 [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw8(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(8); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -526,13 +526,13 @@ public static unsafe void WriteRaw8(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(9); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -541,13 +541,13 @@ public static unsafe void WriteRaw9(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(10); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -556,13 +556,13 @@ public static unsafe void WriteRaw10(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 2) = *(long*)(pSrc + 2); } - writer.Advance(src.Length); + writer.Advance(10); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw11(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(11); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -571,13 +571,13 @@ public static unsafe void WriteRaw11(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 3) = *(long*)(pSrc + 3); } - writer.Advance(src.Length); + writer.Advance(11); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw12(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(12); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -586,13 +586,13 @@ public static unsafe void WriteRaw12(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 4) = *(long*)(pSrc + 4); } - writer.Advance(src.Length); + writer.Advance(12); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw13(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(13); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -601,13 +601,13 @@ public static unsafe void WriteRaw13(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 5) = *(long*)(pSrc + 5); } - writer.Advance(src.Length); + writer.Advance(13); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw14(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(14); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -616,13 +616,13 @@ public static unsafe void WriteRaw14(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 6) = *(long*)(pSrc + 6); } - writer.Advance(src.Length); + writer.Advance(14); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw15(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(15); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -631,13 +631,13 @@ public static unsafe void WriteRaw15(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 7) = *(long*)(pSrc + 7); } - writer.Advance(src.Length); + writer.Advance(15); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw16(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(16); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -646,13 +646,13 @@ public static unsafe void WriteRaw16(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 8) = *(long*)(pSrc + 8); } - writer.Advance(src.Length); + writer.Advance(16); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw17(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(17); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -662,13 +662,13 @@ public static unsafe void WriteRaw17(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 9) = *(long*)(pSrc + 9); } - writer.Advance(src.Length); + writer.Advance(17); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw18(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(18); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -678,13 +678,13 @@ public static unsafe void WriteRaw18(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 10) = *(long*)(pSrc + 10); } - writer.Advance(src.Length); + writer.Advance(18); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw19(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(19); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -694,13 +694,13 @@ public static unsafe void WriteRaw19(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 11) = *(long*)(pSrc + 11); } - writer.Advance(src.Length); + writer.Advance(19); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw20(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(20); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -710,13 +710,13 @@ public static unsafe void WriteRaw20(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 12) = *(long*)(pSrc + 12); } - writer.Advance(src.Length); + writer.Advance(20); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw21(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(21); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -726,13 +726,13 @@ public static unsafe void WriteRaw21(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 13) = *(long*)(pSrc + 13); } - writer.Advance(src.Length); + writer.Advance(21); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw22(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(22); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -742,13 +742,13 @@ public static unsafe void WriteRaw22(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 14) = *(long*)(pSrc + 14); } - writer.Advance(src.Length); + writer.Advance(22); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw23(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(23); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -758,13 +758,13 @@ public static unsafe void WriteRaw23(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 15) = *(long*)(pSrc + 15); } - writer.Advance(src.Length); + writer.Advance(23); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw24(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(24); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -774,13 +774,13 @@ public static unsafe void WriteRaw24(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 16) = *(long*)(pSrc + 16); } - writer.Advance(src.Length); + writer.Advance(24); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw25(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(25); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -791,13 +791,13 @@ public static unsafe void WriteRaw25(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 17) = *(long*)(pSrc + 17); } - writer.Advance(src.Length); + writer.Advance(25); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw26(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(26); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -808,13 +808,13 @@ public static unsafe void WriteRaw26(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 18) = *(long*)(pSrc + 18); } - writer.Advance(src.Length); + writer.Advance(26); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw27(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(27); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -825,13 +825,13 @@ public static unsafe void WriteRaw27(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 19) = *(long*)(pSrc + 19); } - writer.Advance(src.Length); + writer.Advance(27); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw28(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(28); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -842,13 +842,13 @@ public static unsafe void WriteRaw28(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 20) = *(long*)(pSrc + 20); } - writer.Advance(src.Length); + writer.Advance(28); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw29(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(29); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -859,13 +859,13 @@ public static unsafe void WriteRaw29(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 21) = *(long*)(pSrc + 21); } - writer.Advance(src.Length); + writer.Advance(29); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw30(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(30); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -876,13 +876,13 @@ public static unsafe void WriteRaw30(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 22) = *(long*)(pSrc + 22); } - writer.Advance(src.Length); + writer.Advance(30); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw31(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(31); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -893,7 +893,7 @@ public static unsafe void WriteRaw31(ref MessagePackWriter writer, ReadOnlySpan< *(long*)(pDst + 23) = *(long*)(pSrc + 23); } - writer.Advance(src.Length); + writer.Advance(31); } } } diff --git a/src/MessagePack/Internal/UnsafeMemory.tt b/src/MessagePack/Internal/UnsafeMemory.tt index 3410e207b..3610b7c9e 100644 --- a/src/MessagePack/Internal/UnsafeMemory.tt +++ b/src/MessagePack/Internal/UnsafeMemory.tt @@ -28,7 +28,7 @@ namespace MessagePack.Internal [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw<#= i #>(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(<#= i #>); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -41,7 +41,7 @@ namespace MessagePack.Internal <# } #> } - writer.Advance(src.Length); + writer.Advance(<#= i #>); } <# } #> } @@ -52,7 +52,7 @@ namespace MessagePack.Internal [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void WriteRaw<#= i #>(ref MessagePackWriter writer, ReadOnlySpan src) { - Span dst = writer.GetSpan(src.Length); + Span dst = writer.GetSpan(<#= i #>); fixed (byte* pSrc = &src[0]) fixed (byte* pDst = &dst[0]) @@ -65,7 +65,7 @@ namespace MessagePack.Internal <# } #> } - writer.Advance(src.Length); + writer.Advance(<#= i #>); } <# } #> } diff --git a/tests/MessagePack.Tests/UnsafeMemoryTest.cs b/tests/MessagePack.Tests/UnsafeMemoryTest.cs index c9b5f023e..8c3af1431 100644 --- a/tests/MessagePack.Tests/UnsafeMemoryTest.cs +++ b/tests/MessagePack.Tests/UnsafeMemoryTest.cs @@ -3,15 +3,9 @@ using System; using System.Buffers; -using System.Collections.Generic; using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; -using MessagePack.Formatters; using MessagePack.Internal; using Nerdbank.Streams; -using Xunit; namespace MessagePack.Tests { @@ -70,6 +64,35 @@ public void WriteRaw() } } + [Fact] + public void WriteRaw_LargerSpan() + { + ReadOnlySpan src = new byte[MessagePackRange.MaxFixStringLength + 15]; + Sequence dst = new(); + + // x86 + for (int i = 1; i <= MessagePackRange.MaxFixStringLength; i++) + { + dst.Reset(); + MessagePackWriter dstWriter = new(dst); + (typeof(UnsafeMemory32).GetMethod("WriteRaw" + i).CreateDelegate(typeof(WriteDelegate)) as WriteDelegate).Invoke(ref dstWriter, src); + dstWriter.Flush(); + dst.Length.Is(i); + src[0..i].SequenceEqual(CodeGenHelpers.GetSpanFromSequence(dst.AsReadOnlySequence)).IsTrue(); + } + + // x64 + for (int i = 1; i <= MessagePackRange.MaxFixStringLength; i++) + { + dst.Reset(); + var dstWriter = new MessagePackWriter(dst); + (typeof(UnsafeMemory64).GetMethod("WriteRaw" + i).CreateDelegate(typeof(WriteDelegate)) as WriteDelegate).Invoke(ref dstWriter, src); + dstWriter.Flush(); + dst.Length.Is(i); + src[0..i].SequenceEqual(CodeGenHelpers.GetSpanFromSequence(dst.AsReadOnlySequence)).IsTrue(); + } + } + #endif } } From 719e690abae8d4dba6b0c43088c8ded29cdc1ba2 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Wed, 29 Apr 2026 13:59:17 -0600 Subject: [PATCH 14/38] Bound LZ4 input reads for CWE-125 The LZ4 block decoder accepted only the destination length and advanced through the compressed input without knowing where the source buffer ended. Malformed compressed payloads could therefore drive unchecked native reads before the existing post-decode length check ran. Pass the compressed input length into the 32-bit and 64-bit decoders and reject malformed blocks before token, literal, offset, and match-length reads would move past the source buffer. Add a regression test that verifies malformed LZ4 data fails as a normal serialization exception. --- src/MessagePack/LZ4/LZ4Codec.Unsafe.cs | 4 +- .../LZ4/LZ4Codec.Unsafe32.Dirty.cs | 49 ++++++++++++++++--- .../LZ4/LZ4Codec.Unsafe64.Dirty.cs | 49 ++++++++++++++++--- tests/MessagePack.Tests/LZ4Test.cs | 41 ++++++++++++++++ 4 files changed, 125 insertions(+), 18 deletions(-) diff --git a/src/MessagePack/LZ4/LZ4Codec.Unsafe.cs b/src/MessagePack/LZ4/LZ4Codec.Unsafe.cs index ec9a28bc9..1efabf184 100644 --- a/src/MessagePack/LZ4/LZ4Codec.Unsafe.cs +++ b/src/MessagePack/LZ4/LZ4Codec.Unsafe.cs @@ -99,11 +99,11 @@ public static unsafe int Decode(ReadOnlySpan input, Span output) int length; if (IntPtr.Size == 4) { - length = LZ4_uncompress_32(inputPtr, outputPtr, output.Length); + length = LZ4_uncompress_32(inputPtr, input.Length, outputPtr, output.Length); } else { - length = LZ4_uncompress_64(inputPtr, outputPtr, output.Length); + length = LZ4_uncompress_64(inputPtr, input.Length, outputPtr, output.Length); } if (length != input.Length) diff --git a/src/MessagePack/LZ4/LZ4Codec.Unsafe32.Dirty.cs b/src/MessagePack/LZ4/LZ4Codec.Unsafe32.Dirty.cs index 22d39b25d..23a40ddca 100644 --- a/src/MessagePack/LZ4/LZ4Codec.Unsafe32.Dirty.cs +++ b/src/MessagePack/LZ4/LZ4Codec.Unsafe32.Dirty.cs @@ -600,6 +600,7 @@ private static unsafe int LZ4_compress64kCtx_32( private static unsafe int LZ4_uncompress_32( byte* src, + int src_len, byte* dst, int dst_len) { @@ -609,6 +610,7 @@ private static unsafe int LZ4_uncompress_32( { // r93 var src_p = src; + var src_end = src + src_len; byte* xxx_ref; var dst_p = dst; @@ -627,16 +629,26 @@ private static unsafe int LZ4_uncompress_32( int length; // get runlength + if (src_p >= src_end) + { + goto _output_error; + } + xxx_token = *src_p++; if ((length = (int)(xxx_token >> ML_BITS)) == RUN_MASK) { int len; - for (; (len = *src_p++) == 255; length += 255) + do { - /* do nothing */ - } + if (src_p >= src_end) + { + goto _output_error; + } - length += len; + len = *src_p++; + length += len; + } + while (len == 255); } // copy literals @@ -649,11 +661,21 @@ private static unsafe int LZ4_uncompress_32( goto _output_error; // Error : not enough place for another match (min 4) + 5 literals } + if (length > src_end - src_p) + { + goto _output_error; + } + BlockCopy32(src_p, dst_p, length); src_p += length; break; // EOF } + if (length > src_end - src_p) + { + goto _output_error; + } + do { *(uint*)dst_p = *(uint*)src_p; @@ -668,6 +690,11 @@ private static unsafe int LZ4_uncompress_32( dst_p = dst_cpy; // get offset + if (src_end - src_p < 2) + { + goto _output_error; + } + xxx_ref = dst_cpy - (*(ushort*)src_p); src_p += 2; if (xxx_ref < dst) @@ -678,12 +705,18 @@ private static unsafe int LZ4_uncompress_32( // get matchlength if ((length = (int)(xxx_token & ML_MASK)) == ML_MASK) { - for (; *src_p == 255; length += 255) + int len; + do { - src_p++; - } + if (src_p >= src_end) + { + goto _output_error; + } - length += *src_p++; + len = *src_p++; + length += len; + } + while (len == 255); } // copy repeated sequence diff --git a/src/MessagePack/LZ4/LZ4Codec.Unsafe64.Dirty.cs b/src/MessagePack/LZ4/LZ4Codec.Unsafe64.Dirty.cs index ba10e68d2..f807ef17e 100644 --- a/src/MessagePack/LZ4/LZ4Codec.Unsafe64.Dirty.cs +++ b/src/MessagePack/LZ4/LZ4Codec.Unsafe64.Dirty.cs @@ -612,6 +612,7 @@ private static unsafe int LZ4_compress64kCtx_64( private static unsafe int LZ4_uncompress_64( byte* src, + int src_len, byte* dst, int dst_len) { @@ -622,6 +623,7 @@ private static unsafe int LZ4_uncompress_64( { // r93 var src_p = src; + var src_end = src + src_len; byte* dst_ref; var dst_p = dst; @@ -640,16 +642,26 @@ private static unsafe int LZ4_uncompress_64( int length; // get runlength + if (src_p >= src_end) + { + goto _output_error; + } + token = *src_p++; if ((length = token >> ML_BITS) == RUN_MASK) { int len; - for (; (len = *src_p++) == 255; length += 255) + do { - /* do nothing */ - } + if (src_p >= src_end) + { + goto _output_error; + } - length += len; + len = *src_p++; + length += len; + } + while (len == 255); } // copy literals @@ -662,11 +674,21 @@ private static unsafe int LZ4_uncompress_64( goto _output_error; // Error : not enough place for another match (min 4) + 5 literals } + if (length > src_end - src_p) + { + goto _output_error; + } + BlockCopy64(src_p, dst_p, length); src_p += length; break; // EOF } + if (length > src_end - src_p) + { + goto _output_error; + } + do { *(ulong*)dst_p = *(ulong*)src_p; @@ -678,6 +700,11 @@ private static unsafe int LZ4_uncompress_64( dst_p = dst_cpy; // get offset + if (src_end - src_p < 2) + { + goto _output_error; + } + dst_ref = dst_cpy - (*(ushort*)src_p); src_p += 2; if (dst_ref < dst) @@ -688,12 +715,18 @@ private static unsafe int LZ4_uncompress_64( // get matchlength if ((length = token & ML_MASK) == ML_MASK) { - for (; *src_p == 255; length += 255) + int len; + do { - src_p++; - } + if (src_p >= src_end) + { + goto _output_error; + } - length += *src_p++; + len = *src_p++; + length += len; + } + while (len == 255); } // copy repeated sequence diff --git a/tests/MessagePack.Tests/LZ4Test.cs b/tests/MessagePack.Tests/LZ4Test.cs index f314b572a..062576fcd 100644 --- a/tests/MessagePack.Tests/LZ4Test.cs +++ b/tests/MessagePack.Tests/LZ4Test.cs @@ -32,6 +32,47 @@ public void Lz4Compress() Execute(10000); } + [Fact] + [Trait("CWE", "125")] + public void Lz4BlockRejectsTruncatedLiteralRun() + { + const int extensionByteCount = 1024; + int uncompressedLength = 15 + (255 * extensionByteCount) + 16; + + byte[] sizeHeader = + { + 0xCE, + (byte)(uncompressedLength >> 24), + (byte)(uncompressedLength >> 16), + (byte)(uncompressedLength >> 8), + (byte)uncompressedLength, + }; + + byte[] lz4 = new byte[1 + extensionByteCount]; + lz4[0] = 0xF0; + for (int i = 1; i < lz4.Length; i++) + { + lz4[i] = 0xFF; + } + + int bodyLength = sizeHeader.Length + lz4.Length; + byte[] payload = new byte[6 + bodyLength]; + int offset = 0; + payload[offset++] = 0xC9; + payload[offset++] = (byte)(bodyLength >> 24); + payload[offset++] = (byte)(bodyLength >> 16); + payload[offset++] = (byte)(bodyLength >> 8); + payload[offset++] = (byte)bodyLength; + payload[offset++] = 99; + System.Array.Copy(sizeHeader, 0, payload, offset, sizeHeader.Length); + offset += sizeHeader.Length; + System.Array.Copy(lz4, 0, payload, offset, lz4.Length); + + var options = MessagePackSerializerOptions.Standard.WithCompression(MessagePackCompression.Lz4Block); + + Assert.Throws(() => MessagePackSerializer.Deserialize(payload, options)); + } + private void Execute(int count) { // Large From f093bdc1207f6576088d39801fa43e92cec1e5c1 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Wed, 29 Apr 2026 14:06:32 -0600 Subject: [PATCH 15/38] Reject nested typeless blocklist bypass for CWE-502 Typeless deserialization previously validated only the outer wire-supplied type before resolving a formatter. Container types could therefore hide a disallowed element or generic argument from the mitigation and from custom option overrides. Validate element and constructed generic argument types before formatter resolution, and add regression coverage for nested typeless disallowed types. --- .../MessagePackSerializerOptions.cs | 47 +++++++++++++++++-- .../MessagePackSerializerTypelessTests.cs | 24 ++++++++-- 2 files changed, 64 insertions(+), 7 deletions(-) diff --git a/src/MessagePack/MessagePackSerializerOptions.cs b/src/MessagePack/MessagePackSerializerOptions.cs index dc4598239..18c197f78 100644 --- a/src/MessagePack/MessagePackSerializerOptions.cs +++ b/src/MessagePack/MessagePackSerializerOptions.cs @@ -175,16 +175,32 @@ protected MessagePackSerializerOptions(MessagePackSerializerOptions copyFrom) /// The type to be instantiated. /// Thrown if the is not allowed to be deserialized. /// + /// /// This method provides a means for an important security mitigation when using the Typeless formatter to prevent untrusted messagepack from /// deserializing objects that may be harmful if instantiated, disposed or finalized. - /// The default implementation throws for only a few known dangerous types. + /// The default implementation throws for only a few known dangerous types, or types that nest those dangerous types as generic type arguments or array element types. /// Applications that deserialize from untrusted sources should override this method and throw if the type is not among the expected set. + /// + /// + /// This method is for backward compatibility reasons. + /// For better security, the preferred method to override is . + /// /// public virtual void ThrowIfDeserializingTypeIsDisallowed(Type type) { - if (type.FullName is string fullName && DisallowedTypes.Contains(fullName)) + this.ThrowIfDeserializingTypeIsDisallowedCore(type); + + if (type.HasElementType && type.GetElementType() is Type elementType) { - throw new MessagePackSerializationException($"Deserialization attempted to create the type {fullName} which is not allowed."); + this.ThrowIfDeserializingTypeIsDisallowed(elementType); + } + + if (type.IsConstructedGenericType) + { + foreach (Type genericTypeArgument in type.GenericTypeArguments) + { + this.ThrowIfDeserializingTypeIsDisallowed(genericTypeArgument); + } } } @@ -361,6 +377,31 @@ public MessagePackSerializerOptions WithPool(SequencePool pool) return result; } + /// + /// Checks whether a specific given type may be deserialized, disregarding generic type arguments or array element types. + /// + /// The type to be instantiated. + /// Thrown if the is not allowed to be deserialized. + /// + /// + /// This method provides a means for an important security mitigation when using the Typeless formatter to prevent untrusted messagepack from + /// deserializing objects that may be harmful if instantiated, disposed or finalized. + /// The default implementation throws for only a few known dangerous types. + /// Applications that deserialize from untrusted sources should override this method and throw if the type is not among the expected set. + /// + /// + /// This method is called from the default implementation of + /// for the top-level type and again for each generic type argument or array element type. + /// + /// + protected virtual void ThrowIfDeserializingTypeIsDisallowedCore(Type type) + { + if (type.FullName is string fullName && DisallowedTypes.Contains(fullName)) + { + throw new MessagePackSerializationException($"Deserialization attempted to create the type {fullName} which is not allowed."); + } + } + /// /// Creates a clone of this instance with the same properties set. /// diff --git a/tests/MessagePack.Tests/MessagePackSerializerTypelessTests.cs b/tests/MessagePack.Tests/MessagePackSerializerTypelessTests.cs index c92c8c994..da7246a43 100644 --- a/tests/MessagePack.Tests/MessagePackSerializerTypelessTests.cs +++ b/tests/MessagePack.Tests/MessagePackSerializerTypelessTests.cs @@ -4,12 +4,10 @@ #if !UNITY_2018_3_OR_NEWER using System; +using System.Collections.Generic; using System.Runtime.Serialization; -using MessagePack; using MessagePack.Formatters; using MessagePack.Resolvers; -using Xunit; -using Xunit.Abstractions; public class MessagePackSerializerTypelessTests { @@ -47,6 +45,24 @@ public void SerializationOfDisallowedType() Assert.IsType(ex.InnerException); } + [Theory] + [MemberData(nameof(DisallowedNestedTypeData))] + [Trait("CWE", "502")] + public void SerializationOfDisallowedNestedType(object value) + { + var myOptions = new MyTypelessOptions(); + byte[] msgpack = MessagePackSerializer.Typeless.Serialize(value, myOptions); + this.logger.WriteLine(MessagePackSerializer.ConvertToJson(msgpack, myOptions)); + var ex = Assert.Throws(() => MessagePackSerializer.Typeless.Deserialize(msgpack, myOptions)); + Assert.IsType(ex.InnerException); + } + + public static IEnumerable DisallowedNestedTypeData() + { + yield return new object[] { new MyObject[] { new() { SomeValue = 5 } } }; + yield return new object[] { new List { new() { SomeValue = 5 } } }; + } + [Fact(Skip = "Known bug https://github.com/neuecc/MessagePack-CSharp/issues/651")] public void DecimalShouldBeDeserializedAsDecimal() { @@ -137,7 +153,7 @@ internal MyTypelessOptions(MyTypelessOptions copyFrom) { } - public override void ThrowIfDeserializingTypeIsDisallowed(Type type) + protected override void ThrowIfDeserializingTypeIsDisallowedCore(Type type) { if (type == typeof(MyObject)) { From b9cb6050908f75ad813a662fbe7cc06f84fabd23 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Wed, 13 May 2026 16:30:16 -0600 Subject: [PATCH 16/38] Use iteration for skipping msgpack structures for CWE-674 The `MessagePackReader` has no concept of a depth limit, and it implemented its `Skip()` method recursively which can easily blow the stack for deeply nested msgpack structures. Rather than introduce a new API with an adjustable depth limit, set to a 'secure' but otherwise arbitrary default, we can make `Skip()` iterate instead of recurse in order to avoid ever crashing. --- src/MessagePack/MessagePackReader.cs | 185 +++++++++++------- .../MessagePackReaderTests.cs | 43 ++++ 2 files changed, 157 insertions(+), 71 deletions(-) diff --git a/src/MessagePack/MessagePackReader.cs b/src/MessagePack/MessagePackReader.cs index b7eebae74..3a858cc8c 100644 --- a/src/MessagePack/MessagePackReader.cs +++ b/src/MessagePack/MessagePackReader.cs @@ -153,64 +153,123 @@ public byte NextCode /// internal bool TrySkip() { - if (this.reader.Remaining == 0) + long remainingStructures = 1; + while (remainingStructures > 0) { - return false; - } + if (this.reader.Remaining == 0) + { + return false; + } - byte code = this.NextCode; - switch (code) - { - case byte x when MessagePackCode.IsPositiveFixInt(x) || MessagePackCode.IsNegativeFixInt(x): - case MessagePackCode.Nil: - case MessagePackCode.True: - case MessagePackCode.False: - return this.reader.TryAdvance(1); - case MessagePackCode.Int8: - case MessagePackCode.UInt8: - return this.reader.TryAdvance(2); - case MessagePackCode.Int16: - case MessagePackCode.UInt16: - return this.reader.TryAdvance(3); - case MessagePackCode.Int32: - case MessagePackCode.UInt32: - case MessagePackCode.Float32: - return this.reader.TryAdvance(5); - case MessagePackCode.Int64: - case MessagePackCode.UInt64: - case MessagePackCode.Float64: - return this.reader.TryAdvance(9); - case byte x when MessagePackCode.IsFixMap(x): - case MessagePackCode.Map16: - case MessagePackCode.Map32: - return this.TrySkipNextMap(); - case byte x when MessagePackCode.IsFixArray(x): - case MessagePackCode.Array16: - case MessagePackCode.Array32: - return this.TrySkipNextArray(); - case byte x when MessagePackCode.IsFixStr(x): - case MessagePackCode.Str8: - case MessagePackCode.Str16: - case MessagePackCode.Str32: - return this.TryGetStringLengthInBytes(out uint length) && this.reader.TryAdvance(length); - case MessagePackCode.Bin8: - case MessagePackCode.Bin16: - case MessagePackCode.Bin32: - return this.TryGetBytesLength(out length) && this.reader.TryAdvance(length); - case MessagePackCode.FixExt1: - case MessagePackCode.FixExt2: - case MessagePackCode.FixExt4: - case MessagePackCode.FixExt8: - case MessagePackCode.FixExt16: - case MessagePackCode.Ext8: - case MessagePackCode.Ext16: - case MessagePackCode.Ext32: - return this.TryReadExtensionFormatHeader(out ExtensionHeader header) && this.reader.TryAdvance(header.Length); - default: - // We don't actually expect to ever hit this point, since every code is supported. - Debug.Fail("Missing handler for code: " + code); - throw ThrowInvalidCode(code); + remainingStructures--; + byte code = this.NextCode; + switch (code) + { + case byte x when MessagePackCode.IsPositiveFixInt(x) || MessagePackCode.IsNegativeFixInt(x): + case MessagePackCode.Nil: + case MessagePackCode.True: + case MessagePackCode.False: + if (!this.reader.TryAdvance(1)) + { + return false; + } + + break; + case MessagePackCode.Int8: + case MessagePackCode.UInt8: + if (!this.reader.TryAdvance(2)) + { + return false; + } + + break; + case MessagePackCode.Int16: + case MessagePackCode.UInt16: + if (!this.reader.TryAdvance(3)) + { + return false; + } + + break; + case MessagePackCode.Int32: + case MessagePackCode.UInt32: + case MessagePackCode.Float32: + if (!this.reader.TryAdvance(5)) + { + return false; + } + + break; + case MessagePackCode.Int64: + case MessagePackCode.UInt64: + case MessagePackCode.Float64: + if (!this.reader.TryAdvance(9)) + { + return false; + } + + break; + case byte x when MessagePackCode.IsFixMap(x): + case MessagePackCode.Map16: + case MessagePackCode.Map32: + if (!this.TryReadMapHeader(out int count)) + { + return false; + } + + remainingStructures = checked(remainingStructures + ((long)count * 2)); + break; + case byte x when MessagePackCode.IsFixArray(x): + case MessagePackCode.Array16: + case MessagePackCode.Array32: + if (!this.TryReadArrayHeader(out count)) + { + return false; + } + + remainingStructures = checked(remainingStructures + count); + break; + case byte x when MessagePackCode.IsFixStr(x): + case MessagePackCode.Str8: + case MessagePackCode.Str16: + case MessagePackCode.Str32: + if (!this.TryGetStringLengthInBytes(out uint length) || !this.reader.TryAdvance(length)) + { + return false; + } + + break; + case MessagePackCode.Bin8: + case MessagePackCode.Bin16: + case MessagePackCode.Bin32: + if (!this.TryGetBytesLength(out length) || !this.reader.TryAdvance(length)) + { + return false; + } + + break; + case MessagePackCode.FixExt1: + case MessagePackCode.FixExt2: + case MessagePackCode.FixExt4: + case MessagePackCode.FixExt8: + case MessagePackCode.FixExt16: + case MessagePackCode.Ext8: + case MessagePackCode.Ext16: + case MessagePackCode.Ext32: + if (!this.TryReadExtensionFormatHeader(out ExtensionHeader header) || !this.reader.TryAdvance(header.Length)) + { + return false; + } + + break; + default: + // We don't actually expect to ever hit this point, since every code is supported. + Debug.Fail("Missing handler for code: " + code); + throw ThrowInvalidCode(code); + } } + + return true; } /// @@ -1099,21 +1158,5 @@ private string ReadStringSlow(uint byteLength) return value; } - private bool TrySkipNextArray() => this.TryReadArrayHeader(out int count) && this.TrySkip(count); - - private bool TrySkipNextMap() => this.TryReadMapHeader(out int count) && this.TrySkip(count * 2); - - private bool TrySkip(int count) - { - for (int i = 0; i < count; i++) - { - if (!this.TrySkip()) - { - return false; - } - } - - return true; - } } } diff --git a/tests/MessagePack.Tests/MessagePackReaderTests.cs b/tests/MessagePack.Tests/MessagePackReaderTests.cs index 7ac748023..126e518fc 100644 --- a/tests/MessagePack.Tests/MessagePackReaderTests.cs +++ b/tests/MessagePack.Tests/MessagePackReaderTests.cs @@ -330,6 +330,49 @@ public void ReadRaw() Assert.True(reader.End); } + [Fact] + [Trait("CWE", "674")] + public void Skip_DeeplyNestedArrays_DoesNotOverflowStack() + { + const int depth = 100_000; + byte[] msgpack = new byte[depth + 1]; + + for (int i = 0; i < depth; i++) + { + msgpack[i] = MessagePackCode.MinFixArray + 1; + } + + msgpack[^1] = MessagePackCode.Nil; + + MessagePackReader reader = new(msgpack); + + reader.Skip(); + + Assert.True(reader.End); + } + + [Fact] + [Trait("CWE", "674")] + public void Skip_DeeplyNestedMaps_DoesNotOverflowStack() + { + const int depth = 100_000; + byte[] msgpack = new byte[(depth * 2) + 1]; + + for (int i = 0; i < depth; i++) + { + msgpack[i * 2] = MessagePackCode.MinFixMap + 1; + msgpack[(i * 2) + 1] = MessagePackCode.Nil; + } + + msgpack[^1] = MessagePackCode.Nil; + + MessagePackReader reader = new(msgpack); + + reader.Skip(); + + Assert.True(reader.End); + } + [Fact] public void Depth() { From 26d4e743ca2a88a5626e62cced3762820e392325 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Wed, 29 Apr 2026 14:02:55 -0600 Subject: [PATCH 17/38] Reject invalid DateTime ext lengths for CWE-789 DateTime extension decoding previously treated any declared timestamp length as a partial token before validating whether that length was legal. Malformed input could therefore force the reader slow path to prepare a stack buffer sized from the untrusted extension header. Validate timestamp extension lengths before insufficient-buffer handling so only the supported timestamp encodings can proceed to buffering. Add a regression test that verifies oversized malformed DateTime extension headers fail as a normal serialization exception. --- src/MessagePack/MessagePackPrimitives.Readers.cs | 6 ++++++ tests/MessagePack.Tests/MessagePackReaderTests.cs | 9 +++++++++ 2 files changed, 15 insertions(+) diff --git a/src/MessagePack/MessagePackPrimitives.Readers.cs b/src/MessagePack/MessagePackPrimitives.Readers.cs index e7da34e48..1de3a33d5 100644 --- a/src/MessagePack/MessagePackPrimitives.Readers.cs +++ b/src/MessagePack/MessagePackPrimitives.Readers.cs @@ -342,6 +342,12 @@ public static DecodeResult TryReadDateTime(ReadOnlySpan source, ExtensionH return DecodeResult.TokenMismatch; } + if (header.Length is not (4 or 8 or 12)) + { + value = default; + return DecodeResult.TokenMismatch; + } + if (source.Length < tokenSize) { value = default; diff --git a/tests/MessagePack.Tests/MessagePackReaderTests.cs b/tests/MessagePack.Tests/MessagePackReaderTests.cs index 7ac748023..30f96ffc2 100644 --- a/tests/MessagePack.Tests/MessagePackReaderTests.cs +++ b/tests/MessagePack.Tests/MessagePackReaderTests.cs @@ -400,6 +400,15 @@ void AssertIncomplete(WriterEncoder encoder, ReadOperation decoder, bool v AssertIncomplete((ref MessagePackWriter writer) => writer.Write(0xff), (ref MessagePackReader reader) => reader.ReadUInt64()); } + [Fact] + [Trait("CWE", "789")] + public void ReadDateTime_RejectsInvalidExtensionLengthBeforeBuffering() + { + byte[] payload = [MessagePackCode.Ext32, 0x00, 0x10, 0x00, 0x00, 0xff]; + + Assert.Throws(() => new MessagePackReader(new ReadOnlySequence(payload)).ReadDateTime()); + } + [Fact] public void CreatePeekReader() { From 9b5783a7e40b1281ffce32d8631b9cf4b76b2404 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Wed, 29 Apr 2026 14:15:31 -0600 Subject: [PATCH 18/38] Fix CWE-789 multidimensional array allocation validation Multidimensional array deserialization trusted dimension values and allocated arrays before confirming that the flattened element count matched the serialized element array header. This could let malformed data request disproportionate allocations before validation. Validate non-negative dimensions and checked flattened lengths for 2D, 3D, and 4D array formatters before allocation, and add regression coverage for mismatched element counts under untrusted data options. --- .../MultiDimensionalArrayFormatter.cs | 38 +++++++++++- .../MultiDimensionalArrayTest.cs | 58 ++++++++++++++++++- 2 files changed, 92 insertions(+), 4 deletions(-) diff --git a/src/MessagePack/Formatters/MultiDimensionalArrayFormatter.cs b/src/MessagePack/Formatters/MultiDimensionalArrayFormatter.cs index 30b2f462b..4455b31e6 100644 --- a/src/MessagePack/Formatters/MultiDimensionalArrayFormatter.cs +++ b/src/MessagePack/Formatters/MultiDimensionalArrayFormatter.cs @@ -2,9 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; -using System.Buffers; -using System.Collections.Generic; -using System.Text; +using System.Diagnostics.CodeAnalysis; using MessagePack.Internal; #pragma warning disable SA1402 // File may only contain a single type @@ -64,6 +62,7 @@ public void Serialize(ref MessagePackWriter writer, T[,]? value, MessagePackSeri var iLength = reader.ReadInt32(); var jLength = reader.ReadInt32(); var maxLen = reader.ReadArrayHeader(); + MultiDimensionalArrayFormatterHelper.ThrowIfLengthsDontMatch("T[,]", maxLen, iLength, jLength); var array = new T[iLength, jLength]; @@ -151,6 +150,7 @@ public void Serialize(ref MessagePackWriter writer, T[,,]? value, MessagePackSer var jLength = reader.ReadInt32(); var kLength = reader.ReadInt32(); var maxLen = reader.ReadArrayHeader(); + MultiDimensionalArrayFormatterHelper.ThrowIfLengthsDontMatch("T[,,]", maxLen, iLength, jLength, kLength); var array = new T[iLength, jLength, kLength]; @@ -248,6 +248,8 @@ public void Serialize(ref MessagePackWriter writer, T[,,,]? value, MessagePackSe var kLength = reader.ReadInt32(); var lLength = reader.ReadInt32(); var maxLen = reader.ReadArrayHeader(); + MultiDimensionalArrayFormatterHelper.ThrowIfLengthsDontMatch("T[,,,]", maxLen, iLength, jLength, kLength, lLength); + var array = new T[iLength, jLength, kLength, lLength]; var i = 0; @@ -295,4 +297,34 @@ public void Serialize(ref MessagePackWriter writer, T[,,,]? value, MessagePackSe } } } + + internal static class MultiDimensionalArrayFormatterHelper + { + internal static void ThrowIfLengthsDontMatch(string format, int actualLength, int firstLength, int secondLength, int thirdLength = 1, int fourthLength = 1) + { + if (firstLength < 0 || secondLength < 0 || thirdLength < 0 || fourthLength < 0) + { + ThrowInvalidFormat(format); + } + + int expectedLength; + try + { + expectedLength = checked(firstLength * secondLength * thirdLength * fourthLength); + } + catch (OverflowException) + { + ThrowInvalidFormat(format); + return; + } + + if (expectedLength != actualLength) + { + ThrowInvalidFormat(format); + } + } + + [DoesNotReturn] + private static void ThrowInvalidFormat(string format) => throw new MessagePackSerializationException($"Invalid {format} format"); + } } diff --git a/tests/MessagePack.Tests/MultiDimensionalArrayTest.cs b/tests/MessagePack.Tests/MultiDimensionalArrayTest.cs index f4a0a2619..591e5472f 100644 --- a/tests/MessagePack.Tests/MultiDimensionalArrayTest.cs +++ b/tests/MessagePack.Tests/MultiDimensionalArrayTest.cs @@ -10,7 +10,7 @@ namespace MessagePack.Tests { - public class MultiDimensionalArrayTest + public class MultiDimensionalArrayTest(ITestOutputHelper logger) { private T Convert(T value) { @@ -67,5 +67,61 @@ public void MultiDimensional(int dataI, int dataJ, int dataK, int dataL) } } } + + [Fact] + [Trait("CWE", "789")] + public void RejectsTwoDimensionalArrayWithMismatchedElementCount() + { + byte[] payload = + { + 0x93, + 0xCE, 0x00, 0x00, 0x07, 0xD0, + 0xCE, 0x00, 0x00, 0x07, 0xD0, + 0x90, + }; + + AssertRejects(payload); + } + + [Fact] + [Trait("CWE", "789")] + public void RejectsThreeDimensionalArrayWithMismatchedElementCount() + { + byte[] payload = + { + 0x94, + 0xCC, 0x80, + 0xCC, 0x80, + 0xCC, 0x80, + 0x90, + }; + + AssertRejects(payload); + } + + [Fact] + [Trait("CWE", "789")] + public void RejectsFourDimensionalArrayWithMismatchedElementCount() + { + byte[] payload = + { + 0x95, + 0x20, + 0x20, + 0x20, + 0x20, + 0x90, + }; + + AssertRejects(payload); + } + + private void AssertRejects(byte[] payload) + { + var options = MessagePackSerializerOptions.Standard.WithSecurity(MessagePackSecurity.UntrustedData); + + var ex = Assert.Throws(() => MessagePackSerializer.Deserialize(payload, options)); + logger.WriteLine(ex.ToString()); + } } } From b414e6dffc7bd115003e7dd669417ee198cc4da8 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Wed, 29 Apr 2026 14:22:23 -0600 Subject: [PATCH 19/38] Guard dynamic union depth for CWE-674 DynamicUnionResolver emitted deserializers did not count the union formatter frame against MessagePackSecurity's object graph depth budget. That left recursive union values and skipped unknown union payloads less constrained than source-generated unions and dynamic object formatters. Emit DepthStep after nil handling and decrement reader.Depth before returning, matching the existing dynamic object formatter pattern. Add a regression test proving unknown union payloads respect the depth limit without including exploit payload details. --- .../Resolvers/DynamicUnionResolver.cs | 21 +++++++++- .../MessagePackSerializerTest.cs | 38 +++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/src/MessagePack/Resolvers/DynamicUnionResolver.cs b/src/MessagePack/Resolvers/DynamicUnionResolver.cs index 9281c6667..01fe027e7 100644 --- a/src/MessagePack/Resolvers/DynamicUnionResolver.cs +++ b/src/MessagePack/Resolvers/DynamicUnionResolver.cs @@ -321,6 +321,14 @@ private static void BuildDeserialize(Type type, UnionAttribute[] infos, MethodBu il.MarkLabel(falseLabel); + var reader = new ArgumentField(il, 1); + + // options.Security.DepthStep(ref reader); + il.EmitLdarg(2); + il.EmitCall(getSecurityFromOptions); + reader.EmitLdarg(); + il.EmitCall(securityDepthStep); + // IFormatterResolver resolver = options.Resolver; LocalBuilder localResolver = il.DeclareLocal(typeof(IFormatterResolver)); il.EmitLdarg(2); @@ -329,7 +337,6 @@ private static void BuildDeserialize(Type type, UnionAttribute[] infos, MethodBu // read-array header and validate, reader.ReadArrayHeader() != 2) throw; Label rightLabel = il.DefineLabel(); - var reader = new ArgumentField(il, 1); reader.EmitLdarg(); il.EmitCall(MessagePackReaderTypeInfo.ReadArrayHeader); il.EmitLdc_I4(2); @@ -396,6 +403,14 @@ private static void BuildDeserialize(Type type, UnionAttribute[] infos, MethodBu il.MarkLabel(loopEnd); + // reader.Depth--; + reader.EmitLdarg(); + il.Emit(OpCodes.Dup); + il.EmitCall(readerDepthGet); + il.Emit(OpCodes.Ldc_I4_1); + il.Emit(OpCodes.Sub_Ovf); + il.EmitCall(readerDepthSet); + il.Emit(OpCodes.Ldloc, result); il.Emit(OpCodes.Ret); } @@ -420,6 +435,10 @@ private static bool IsZeroStartSequential(UnionAttribute[] infos) private static readonly Type refKvp = typeof(KeyValuePair).MakeByRefType(); private static readonly MethodInfo getFormatterWithVerify = typeof(FormatterResolverExtensions).GetRuntimeMethods().First(x => x.Name == "GetFormatterWithVerify"); private static readonly MethodInfo getResolverFromOptions = typeof(MessagePackSerializerOptions).GetRuntimeProperty(nameof(MessagePackSerializerOptions.Resolver))!.GetMethod!; + private static readonly MethodInfo getSecurityFromOptions = typeof(MessagePackSerializerOptions).GetRuntimeProperty(nameof(MessagePackSerializerOptions.Security))!.GetMethod!; + private static readonly MethodInfo securityDepthStep = typeof(MessagePackSecurity).GetRuntimeMethod(nameof(MessagePackSecurity.DepthStep), new[] { typeof(MessagePackReader).MakeByRefType() })!; + private static readonly MethodInfo readerDepthGet = typeof(MessagePackReader).GetRuntimeProperty(nameof(MessagePackReader.Depth))!.GetMethod!; + private static readonly MethodInfo readerDepthSet = typeof(MessagePackReader).GetRuntimeProperty(nameof(MessagePackReader.Depth))!.SetMethod!; private static readonly Func getSerialize = t => typeof(IMessagePackFormatter<>).MakeGenericType(t).GetRuntimeMethod("Serialize", new[] { typeof(MessagePackWriter).MakeByRefType(), t, typeof(MessagePackSerializerOptions) })!; private static readonly Func getDeserialize = t => typeof(IMessagePackFormatter<>).MakeGenericType(t).GetRuntimeMethod("Deserialize", new[] { typeof(MessagePackReader).MakeByRefType(), typeof(MessagePackSerializerOptions) })!; diff --git a/tests/MessagePack.Tests/MessagePackSerializerTest.cs b/tests/MessagePack.Tests/MessagePackSerializerTest.cs index 211a35143..31f1a95e8 100644 --- a/tests/MessagePack.Tests/MessagePackSerializerTest.cs +++ b/tests/MessagePack.Tests/MessagePackSerializerTest.cs @@ -275,6 +275,18 @@ public void StackDepthCheck_DynamicObjectResolver() Assert.IsType(ex.InnerException); } + [Fact] + [Trait("CWE", "674")] + public void StackDepthCheck_DynamicUnionResolver() + { + byte[] msgpack = MessagePackSerializer.Serialize(new DepthCheckedUnionBranch()); + var options = MessagePackSerializerOptions.Standard + .WithSecurity(MessagePackSecurity.UntrustedData.WithMaximumObjectGraphDepth(1)); + + var ex = Assert.Throws(() => MessagePackSerializer.Deserialize(msgpack, options)); + Assert.IsType(ex.InnerException); + } + #endif private delegate void WriterHelper(ref MessagePackWriter writer); @@ -414,4 +426,30 @@ protected override void Dispose(bool disposing) base.Dispose(disposing); } } + + [MessagePackObject(keyAsPropertyName: true)] + public class SkipUnknownMemberTarget + { + public int Known { get; set; } + } + + [Union(0, typeof(DepthCheckedUnionLeaf))] + [Union(999, typeof(DepthCheckedUnionBranch))] + public interface IDepthCheckedUnionNode + { + } + + [MessagePackObject] + public class DepthCheckedUnionLeaf : IDepthCheckedUnionNode + { + [Key(0)] + public int Value { get; set; } + } + + [MessagePackObject] + public class DepthCheckedUnionBranch : IDepthCheckedUnionNode + { + [Key(0)] + public IDepthCheckedUnionNode Child { get; set; } + } } From f96fcf053fd2b22f4b13b0a68d8981fecc2fee1a Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Wed, 29 Apr 2026 14:18:40 -0600 Subject: [PATCH 20/38] Use secure lookup comparer for CWE-407 InterfaceLookupFormatter created its intermediate Dictionary with the default comparer, so ILookup deserialization did not honor MessagePackSecurity.UntrustedData hash-collision resistance. Pass the security-provided equality comparer into the intermediate dictionary and add regression coverage that verifies colliding long keys use the hardened comparer. --- src/MessagePack/Formatters/CollectionFormatter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MessagePack/Formatters/CollectionFormatter.cs b/src/MessagePack/Formatters/CollectionFormatter.cs index 2ed9bf563..cf1086b5b 100644 --- a/src/MessagePack/Formatters/CollectionFormatter.cs +++ b/src/MessagePack/Formatters/CollectionFormatter.cs @@ -928,7 +928,7 @@ protected override ILookup Complete(Dictionary> Create(int count, MessagePackSerializerOptions options) { - return new Dictionary>(count); + return new Dictionary>(count, options.Security.GetEqualityComparer()); } } From 0555f07cbf217773070a150c97cbbd2b3a37ac8d Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Wed, 29 Apr 2026 14:26:16 -0600 Subject: [PATCH 21/38] Validate Unity blit lengths for CWE-789 UnsafeBlitFormatter trusted the nested byte count from extension payloads when allocating arrays, even though the surrounding extension length had already bounded the body. Malformed inputs could request allocations that were not supported by the declared extension data. Parse the extension body through a bounded reader and reject negative, unaligned, or mismatched byte counts before allocating. Add a regression test for the malformed length case. --- .../MessagePack/Extension/UnsafeBlitFormatter.cs | 16 +++++++++++----- .../ExtensionTests/UnityShimTest.cs | 12 ++++++++++++ 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Extension/UnsafeBlitFormatter.cs b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Extension/UnsafeBlitFormatter.cs index 63f137a39..fdbe234fb 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Extension/UnsafeBlitFormatter.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Extension/UnsafeBlitFormatter.cs @@ -55,19 +55,25 @@ public unsafe void Serialize(ref MessagePackWriter writer, T[]? value, MessagePa return null; } - ExtensionHeader header = reader.ReadExtensionFormatHeader(); - if (header.TypeCode != this.TypeCode) + ExtensionResult extension = reader.ReadExtensionFormat(); + if (extension.TypeCode != this.TypeCode) { throw new InvalidOperationException("Invalid typeCode."); } - var byteLength = reader.ReadInt32(); - var isLittleEndian = reader.ReadBoolean(); + MessagePackReader extensionReader = reader.Clone(extension.Data); + var byteLength = extensionReader.ReadInt32(); + var isLittleEndian = extensionReader.ReadBoolean(); + long remainingBytes = extensionReader.Sequence.Length - extensionReader.Consumed; + if (byteLength < 0 || byteLength % sizeof(T) != 0 || byteLength != remainingBytes) + { + throw new MessagePackSerializationException("Invalid Unity blit extension length."); + } // Allocate a T[] that we will return. We'll then cast the T[] as byte[] so we can copy the byte sequence directly into it. var result = new T[byteLength / sizeof(T)]; Span resultAsBytes = MemoryMarshal.Cast(result); - reader.ReadRaw(byteLength).CopyTo(resultAsBytes); + extensionReader.ReadRaw(byteLength).CopyTo(resultAsBytes); // Reverse the byte order if necessary. if (isLittleEndian != BitConverter.IsLittleEndian && result.Length > 0) diff --git a/tests/MessagePack.Tests/ExtensionTests/UnityShimTest.cs b/tests/MessagePack.Tests/ExtensionTests/UnityShimTest.cs index 7e35b2448..899f839a7 100644 --- a/tests/MessagePack.Tests/ExtensionTests/UnityShimTest.cs +++ b/tests/MessagePack.Tests/ExtensionTests/UnityShimTest.cs @@ -94,6 +94,18 @@ public void EnsureSpecCompatibilityTest(BlitContainer data) EnsureSpecCompatibility(data.Array); } + [Fact] + [Trait("CWE", "789")] + public void BlitRejectsByteLengthThatExceedsExtensionBody() + { + MessagePackSerializerOptions options = MessagePackSerializerOptions.Standard.WithResolver(new WithUnityBlitResolver()); + byte[] payload = { 0xC7, 0x06, unchecked((byte)ReservedExtensionTypeCodes.UnityInt), 0xCE, 0x00, 0x00, 0x00, 0x08, 0xC3 }; + + var ex = Assert.Throws(() => MessagePackSerializer.Deserialize(payload, options)); + var inner = Assert.IsType(ex.InnerException); + Assert.Contains("Invalid Unity blit extension length", inner.Message); + } + public class WithUnityBlitResolver : IFormatterResolver { public IMessagePackFormatter GetFormatter() From b3af7cf7041e1bd33212c37d555cae4364407763 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Wed, 29 Apr 2026 14:29:15 -0600 Subject: [PATCH 22/38] Guard JSON conversion depth for CWE-674 ConvertFromJson now applies the configured MessagePackSecurity maximum object graph depth while translating nested JSON objects and arrays, preventing deeply nested input from recursing until stack exhaustion. Added a bounded regression test that verifies over-depth JSON is rejected for both compressed and uncompressed conversion paths. --- src/MessagePack/MessagePackSerializer.Json.cs | 21 +++++++++++++++++-- tests/MessagePack.Tests/ToJsonTest.cs | 13 ++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/MessagePack/MessagePackSerializer.Json.cs b/src/MessagePack/MessagePackSerializer.Json.cs index ebcddd555..5e5c2177c 100644 --- a/src/MessagePack/MessagePackSerializer.Json.cs +++ b/src/MessagePack/MessagePackSerializer.Json.cs @@ -187,6 +187,11 @@ public static void ConvertFromJson(TextReader reader, ref MessagePackWriter writ } private static uint FromJsonCore(TinyJsonReader jr, ref MessagePackWriter writer, MessagePackSerializerOptions options) + { + return FromJsonCore(jr, ref writer, options, 0); + } + + private static uint FromJsonCore(TinyJsonReader jr, ref MessagePackWriter writer, MessagePackSerializerOptions options, int depth) { uint count = 0; while (jr.Read()) @@ -196,11 +201,13 @@ private static uint FromJsonCore(TinyJsonReader jr, ref MessagePackWriter writer case TinyJsonToken.None: break; case TinyJsonToken.StartObject: + VerifyJsonObjectGraphDepth(options, depth); + // Set up a scratch area to serialize the collection since we don't know its length yet, which must be written first. using (var scratchRental = options.SequencePool.Rent()) { MessagePackWriter scratchWriter = writer.Clone(scratchRental.Value); - var mapCount = FromJsonCore(jr, ref scratchWriter, options); + var mapCount = FromJsonCore(jr, ref scratchWriter, options, depth + 1); scratchWriter.Flush(); mapCount = mapCount / 2; // remove propertyname string count. @@ -213,11 +220,13 @@ private static uint FromJsonCore(TinyJsonReader jr, ref MessagePackWriter writer case TinyJsonToken.EndObject: return count; // break case TinyJsonToken.StartArray: + VerifyJsonObjectGraphDepth(options, depth); + // Set up a scratch area to serialize the collection since we don't know its length yet, which must be written first. using (var scratchRental = options.SequencePool.Rent()) { MessagePackWriter scratchWriter = writer.Clone(scratchRental.Value); - var arrayCount = FromJsonCore(jr, ref scratchWriter, options); + var arrayCount = FromJsonCore(jr, ref scratchWriter, options, depth + 1); scratchWriter.Flush(); writer.WriteArrayHeader(arrayCount); @@ -273,6 +282,14 @@ private static uint FromJsonCore(TinyJsonReader jr, ref MessagePackWriter writer return count; } + private static void VerifyJsonObjectGraphDepth(MessagePackSerializerOptions options, int depth) + { + if (depth >= options.Security.MaximumObjectGraphDepth) + { + throw new InsufficientExecutionStackException($"This JSON sequence has an object graph that exceeds the maximum depth allowed of {options.Security.MaximumObjectGraphDepth}."); + } + } + private static void ToJsonCore(ref MessagePackReader reader, TextWriter writer, MessagePackSerializerOptions options) { MessagePackType type = reader.NextMessagePackType; diff --git a/tests/MessagePack.Tests/ToJsonTest.cs b/tests/MessagePack.Tests/ToJsonTest.cs index 8069ea266..c2e3be2bb 100644 --- a/tests/MessagePack.Tests/ToJsonTest.cs +++ b/tests/MessagePack.Tests/ToJsonTest.cs @@ -47,6 +47,19 @@ public void ComplexToJson() this.JsonConvert(json, LZ4Standard).Is(json); } + [Theory] + [InlineData(false)] + [InlineData(true)] + [Trait("CWE", "674")] + public void ConvertFromJsonRejectsExcessiveNesting(bool compression) + { + var options = MessagePackSerializerOptions.Standard + .WithCompression(compression ? MessagePackCompression.Lz4Block : MessagePackCompression.None) + .WithSecurity(MessagePackSecurity.UntrustedData.WithMaximumObjectGraphDepth(3)); + + Assert.Throws(() => MessagePackSerializer.ConvertFromJson("[[[[1]]]]", options)); + } + [Fact] public void FloatJson() { From 66ad0894bcbddc696e9dd77410e5ea4c6291f696 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Wed, 29 Apr 2026 14:32:00 -0600 Subject: [PATCH 23/38] Avoid JSON separator recursion for CWE-674 TinyJsonReader previously skipped JSON separators with a recursive ReadNextToken call, so a long separator sequence supplied to ConvertFromJson could consume one stack frame per separator and terminate the process. Change separator skipping to stay within the tokenizer loop instead, preserving the existing tokenization behavior without stack growth. Add regression coverage that converts a long separator-prefixed JSON value successfully. --- src/MessagePack/Internal/TinyJsonReader.cs | 108 +++++++++++---------- tests/MessagePack.Tests/ToJsonTest.cs | 10 ++ 2 files changed, 65 insertions(+), 53 deletions(-) diff --git a/src/MessagePack/Internal/TinyJsonReader.cs b/src/MessagePack/Internal/TinyJsonReader.cs index dd386177c..67dbf01e8 100644 --- a/src/MessagePack/Internal/TinyJsonReader.cs +++ b/src/MessagePack/Internal/TinyJsonReader.cs @@ -137,62 +137,64 @@ private static bool IsWordBreak(char c) private void ReadNextToken() { - this.SkipWhiteSpace(); - - var intChar = this.reader.Peek(); - if (intChar == -1) + while (true) { - this.TokenType = TinyJsonToken.None; - return; - } + this.SkipWhiteSpace(); - var c = (char)intChar; - switch (c) - { - case '{': - this.TokenType = TinyJsonToken.StartObject; - return; - case '}': - this.TokenType = TinyJsonToken.EndObject; - return; - case '[': - this.TokenType = TinyJsonToken.StartArray; - return; - case ']': - this.TokenType = TinyJsonToken.EndArray; - return; - case '"': - this.TokenType = TinyJsonToken.String; - return; - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - case '-': - this.TokenType = TinyJsonToken.Number; - return; - case 't': - this.TokenType = TinyJsonToken.True; - return; - case 'f': - this.TokenType = TinyJsonToken.False; - return; - case 'n': - this.TokenType = TinyJsonToken.Null; - return; - case ',': - case ':': - this.reader.Read(); - this.ReadNextToken(); + var intChar = this.reader.Peek(); + if (intChar == -1) + { + this.TokenType = TinyJsonToken.None; return; - default: - throw new TinyJsonException("Invalid String:" + c); + } + + var c = (char)intChar; + switch (c) + { + case '{': + this.TokenType = TinyJsonToken.StartObject; + return; + case '}': + this.TokenType = TinyJsonToken.EndObject; + return; + case '[': + this.TokenType = TinyJsonToken.StartArray; + return; + case ']': + this.TokenType = TinyJsonToken.EndArray; + return; + case '"': + this.TokenType = TinyJsonToken.String; + return; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + this.TokenType = TinyJsonToken.Number; + return; + case 't': + this.TokenType = TinyJsonToken.True; + return; + case 'f': + this.TokenType = TinyJsonToken.False; + return; + case 'n': + this.TokenType = TinyJsonToken.Null; + return; + case ',': + case ':': + this.reader.Read(); + continue; + default: + throw new TinyJsonException("Invalid String:" + c); + } } } diff --git a/tests/MessagePack.Tests/ToJsonTest.cs b/tests/MessagePack.Tests/ToJsonTest.cs index 8069ea266..d65acb5ca 100644 --- a/tests/MessagePack.Tests/ToJsonTest.cs +++ b/tests/MessagePack.Tests/ToJsonTest.cs @@ -47,6 +47,16 @@ public void ComplexToJson() this.JsonConvert(json, LZ4Standard).Is(json); } + [Fact] + [Trait("CWE", "674")] + public void ConvertFromJsonSkipsLongSeparatorRunIteratively() + { + var json = new string(',', 200_000) + "null"; + var msgpack = MessagePackSerializer.ConvertFromJson(json); + + MessagePackSerializer.ConvertToJson(msgpack).Is("null"); + } + [Fact] public void FloatJson() { From 082ba7daba9b6a1acda945d0840abf9903d53cc2 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Wed, 29 Apr 2026 14:35:35 -0600 Subject: [PATCH 24/38] Guard typeless JSON depth for CWE-674 ConvertToJson recursively expands typeless extension values while composing JSON, but that path was not counted against MessagePackSecurity.MaximumObjectGraphDepth. Deeply nested typeless values could therefore bypass UntrustedData depth checks and exhaust the call stack. Wrap typeless extension JSON processing in DepthStep/decrement accounting and add regression coverage that expects the configured depth limit to be enforced. --- src/MessagePack/MessagePackSerializer.Json.cs | 68 +++++++++++-------- .../MessagePackSerializerTest.cs | 34 ++++++++++ 2 files changed, 72 insertions(+), 30 deletions(-) diff --git a/src/MessagePack/MessagePackSerializer.Json.cs b/src/MessagePack/MessagePackSerializer.Json.cs index ebcddd555..9edda47fd 100644 --- a/src/MessagePack/MessagePackSerializer.Json.cs +++ b/src/MessagePack/MessagePackSerializer.Json.cs @@ -395,46 +395,54 @@ private static void ToJsonCore(ref MessagePackReader reader, TextWriter writer, } else if (extHeader.TypeCode == ReservedExtensionTypeCodes.TypelessFormatter) { - // prepare type name token - var privateBuilder = new StringBuilder(); - var typeNameTokenBuilder = new StringBuilder(); - SequencePosition positionBeforeTypeNameRead = reader.Position; - ToJsonCore(ref reader, new StringWriter(typeNameTokenBuilder), options); - int typeNameReadSize = (int)reader.Sequence.Slice(positionBeforeTypeNameRead, reader.Position).Length; - if (extHeader.Length > typeNameReadSize) + options.Security.DepthStep(ref reader); + try { - // object map or array - MessagePackType typeInside = reader.NextMessagePackType; - if (typeInside != MessagePackType.Array && typeInside != MessagePackType.Map) + // prepare type name token + var privateBuilder = new StringBuilder(); + var typeNameTokenBuilder = new StringBuilder(); + SequencePosition positionBeforeTypeNameRead = reader.Position; + ToJsonCore(ref reader, new StringWriter(typeNameTokenBuilder), options); + int typeNameReadSize = (int)reader.Sequence.Slice(positionBeforeTypeNameRead, reader.Position).Length; + if (extHeader.Length > typeNameReadSize) { - privateBuilder.Append("{"); - } + // object map or array + MessagePackType typeInside = reader.NextMessagePackType; + if (typeInside != MessagePackType.Array && typeInside != MessagePackType.Map) + { + privateBuilder.Append("{"); + } - ToJsonCore(ref reader, new StringWriter(privateBuilder), options); + ToJsonCore(ref reader, new StringWriter(privateBuilder), options); - // insert type name token to start of object map or array - if (typeInside != MessagePackType.Array) - { - typeNameTokenBuilder.Insert(0, "\"$type\":"); - } + // insert type name token to start of object map or array + if (typeInside != MessagePackType.Array) + { + typeNameTokenBuilder.Insert(0, "\"$type\":"); + } - if (typeInside != MessagePackType.Array && typeInside != MessagePackType.Map) - { - privateBuilder.Append("}"); - } + if (typeInside != MessagePackType.Array && typeInside != MessagePackType.Map) + { + privateBuilder.Append("}"); + } - if (privateBuilder.Length > 2) - { - typeNameTokenBuilder.Append(","); - } + if (privateBuilder.Length > 2) + { + typeNameTokenBuilder.Append(","); + } - privateBuilder.Insert(1, typeNameTokenBuilder.ToString()); + privateBuilder.Insert(1, typeNameTokenBuilder.ToString()); - writer.Write(privateBuilder.ToString()); + writer.Write(privateBuilder.ToString()); + } + else + { + writer.Write("{\"$type\":" + typeNameTokenBuilder.ToString() + "}"); + } } - else + finally { - writer.Write("{\"$type\":" + typeNameTokenBuilder.ToString() + "}"); + reader.Depth--; } } else diff --git a/tests/MessagePack.Tests/MessagePackSerializerTest.cs b/tests/MessagePack.Tests/MessagePackSerializerTest.cs index 211a35143..81b2b6cba 100644 --- a/tests/MessagePack.Tests/MessagePackSerializerTest.cs +++ b/tests/MessagePack.Tests/MessagePackSerializerTest.cs @@ -209,6 +209,18 @@ public async Task SerializeAndDeserializeAsync_MultipleValues_SeekableStream(boo Assert.Equal(3, await MessagePackSerializer.DeserializeAsync(stream)); } + [Fact] + [Trait("CWE", "674")] + public void StackDepthCheck_ConvertToJsonTypelessExtension() + { + const int maxDepth = 3; + byte[] msgpack = BuildNestedTypelessExtension(maxDepth + 1); + var options = MessagePackSerializerOptions.Standard + .WithSecurity(MessagePackSecurity.UntrustedData.WithMaximumObjectGraphDepth(maxDepth)); + + AssertConvertToJsonRecursionCheckThrows(new ReadOnlySequence(msgpack), options); + } + [Theory] [InlineData(true)] [InlineData(false)] @@ -318,6 +330,28 @@ private static void AssertConvertToJsonRecursionCheckThrows(ReadOnlySequence(ex.InnerException); } + private static byte[] BuildNestedTypelessExtension(int levels) + { + byte[] msgpack = new byte[(levels * 6) + 2]; + int offset = msgpack.Length; + msgpack[--offset] = (byte)'x'; + msgpack[--offset] = 0xa1; + int innerLength = 2; + + for (int level = 0; level < levels; level++) + { + msgpack[--offset] = unchecked((byte)ReservedExtensionTypeCodes.TypelessFormatter); + msgpack[--offset] = (byte)innerLength; + msgpack[--offset] = (byte)(innerLength >> 8); + msgpack[--offset] = (byte)(innerLength >> 16); + msgpack[--offset] = (byte)(innerLength >> 24); + msgpack[--offset] = 0xc9; + innerLength += 6; + } + + return msgpack; + } + [DataContract] public class RecursiveObjectGraph { From 46c6a0fe2f2403a282d2e5de2771f242f963044b Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Wed, 29 Apr 2026 14:43:06 -0600 Subject: [PATCH 25/38] Fix CWE-190 map header length overflow Promote map entry count multiplication to long when validating the minimum encoded payload length and when skipping map contents. This keeps oversized malformed map headers on the insufficient-buffer path instead of relying on Int32 arithmetic behavior. Add regression coverage for oversized map headers in ReadMapHeader and Skip. --- src/MessagePack/MessagePackReader.cs | 2 +- .../MessagePackReaderTests.cs | 26 +++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/MessagePack/MessagePackReader.cs b/src/MessagePack/MessagePackReader.cs index b7eebae74..6383ff667 100644 --- a/src/MessagePack/MessagePackReader.cs +++ b/src/MessagePack/MessagePackReader.cs @@ -371,7 +371,7 @@ public int ReadMapHeader() // Protect against corrupted or mischievous data that may lead to allocating way too much memory. // We allow for each primitive to be the minimal 1 byte in size, and we have a key=value map, so that's 2 bytes. // Formatters that know each element is larger can optionally add a stronger check. - ThrowInsufficientBufferUnless(this.reader.Remaining >= count * 2); + ThrowInsufficientBufferUnless(this.reader.Remaining >= (long)count * 2); return count; } diff --git a/tests/MessagePack.Tests/MessagePackReaderTests.cs b/tests/MessagePack.Tests/MessagePackReaderTests.cs index 7ac748023..0957d8f76 100644 --- a/tests/MessagePack.Tests/MessagePackReaderTests.cs +++ b/tests/MessagePack.Tests/MessagePackReaderTests.cs @@ -101,6 +101,32 @@ public void ReadMapHeader_MitigatesLargeAllocations() }); } + [Fact] + [Trait("CWE", "190")] + public void ReadMapHeader_MitigatesLargeAllocations_WhenMinimumPayloadLengthOverflowsInt32() + { + byte[] msgpack = { MessagePackCode.Map32, 0x40, 0, 0, 0 }; + + Assert.Throws(() => + { + var reader = new MessagePackReader(msgpack); + reader.ReadMapHeader(); + }); + } + + [Fact] + [Trait("CWE", "190")] + public void SkipMap_MitigatesLargeAllocations_WhenMinimumPayloadLengthOverflowsInt32() + { + byte[] msgpack = { MessagePackCode.Map32, 0x40, 0, 0, 0 }; + + Assert.Throws(() => + { + var reader = new MessagePackReader(msgpack); + reader.Skip(); + }); + } + [Fact] public void TryReadMapHeader() { From 25a3493e6c531203221e56b2e862ce628cd9acaf Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Wed, 29 Apr 2026 14:39:01 -0600 Subject: [PATCH 26/38] Limit untrusted ExpandoObject maps for CWE-407 ExpandoObject map materialization inserts each member through ExpandoObject, whose member table grows with linear scans and array copies. Under the untrusted-data resolver preset, very large maps could consume disproportionate CPU before application code sees the result. Reject oversized ExpandoObject maps when hash-collision hardening is active, covering both direct ExpandoObject deserialization and nested maps produced by ExpandoObjectResolver. Add focused regression coverage for both paths. --- .../Formatters/ExpandoObjectFormatter.cs | 11 +++++++ .../Resolvers/ExpandoObjectResolver.cs | 1 + tests/MessagePack.Tests/ExpandoObjectTests.cs | 30 +++++++++++++++++++ 3 files changed, 42 insertions(+) diff --git a/src/MessagePack/Formatters/ExpandoObjectFormatter.cs b/src/MessagePack/Formatters/ExpandoObjectFormatter.cs index 2d9328880..98932401f 100644 --- a/src/MessagePack/Formatters/ExpandoObjectFormatter.cs +++ b/src/MessagePack/Formatters/ExpandoObjectFormatter.cs @@ -8,6 +8,8 @@ namespace MessagePack.Formatters { public class ExpandoObjectFormatter : IMessagePackFormatter { + internal const int MaximumUntrustedDataMemberCount = 1024; + public static readonly IMessagePackFormatter Instance = new ExpandoObjectFormatter(); private ExpandoObjectFormatter() @@ -23,6 +25,7 @@ private ExpandoObjectFormatter() var result = new ExpandoObject(); int count = reader.ReadMapHeader(); + ThrowIfMapTooLargeForUntrustedData(count, options); if (count > 0) { IFormatterResolver resolver = options.Resolver; @@ -49,6 +52,14 @@ private ExpandoObjectFormatter() return result; } + internal static void ThrowIfMapTooLargeForUntrustedData(int count, MessagePackSerializerOptions options) + { + if (options.Security.HashCollisionResistant && count > MaximumUntrustedDataMemberCount) + { + throw new MessagePackSerializationException($"ExpandoObject map size exceeds the limit of {MaximumUntrustedDataMemberCount} entries allowed under untrusted data security mode."); + } + } + public void Serialize(ref MessagePackWriter writer, ExpandoObject? value, MessagePackSerializerOptions options) { if (value is null) diff --git a/src/MessagePack/Resolvers/ExpandoObjectResolver.cs b/src/MessagePack/Resolvers/ExpandoObjectResolver.cs index ffc7b149c..11107efac 100644 --- a/src/MessagePack/Resolvers/ExpandoObjectResolver.cs +++ b/src/MessagePack/Resolvers/ExpandoObjectResolver.cs @@ -40,6 +40,7 @@ private class PrimitiveObjectWithExpandoMaps : PrimitiveObjectFormatter { protected override object DeserializeMap(ref MessagePackReader reader, int length, MessagePackSerializerOptions options) { + ExpandoObjectFormatter.ThrowIfMapTooLargeForUntrustedData(length, options); IMessagePackFormatter keyFormatter = options.Resolver.GetFormatterWithVerify(); IMessagePackFormatter? objectFormatter = options.Resolver.GetFormatterWithVerify(); IDictionary dictionary = new ExpandoObject(); diff --git a/tests/MessagePack.Tests/ExpandoObjectTests.cs b/tests/MessagePack.Tests/ExpandoObjectTests.cs index 61ae7b0bf..40546e58e 100644 --- a/tests/MessagePack.Tests/ExpandoObjectTests.cs +++ b/tests/MessagePack.Tests/ExpandoObjectTests.cs @@ -3,6 +3,7 @@ #if !UNITY_2018_3_OR_NEWER +using System.Collections.Generic; using System.Dynamic; using System.Runtime.Serialization; using MessagePack.Resolvers; @@ -76,6 +77,24 @@ public void ExpandoObject_DeepGraphContainsCustomTypes() Assert.Equal(expando.Other.OtherProperty, expando2.Other.OtherProperty); } + [Fact] + [Trait("CWE", "407")] + public void ExpandoObject_UntrustedDataRejectsLargeMaps() + { + byte[] msgpack = CreateMapWithNilValues(1025); + + Assert.Throws(() => MessagePackSerializer.Deserialize(msgpack, ExpandoObjectResolver.Options)); + } + + [Fact] + [Trait("CWE", "407")] + public void ExpandoObjectNestedMap_UntrustedDataRejectsLargeMaps() + { + byte[] msgpack = CreateMapWithNilValues(1025); + + Assert.Throws(() => MessagePackSerializer.Deserialize(msgpack, ExpandoObjectResolver.Options)); + } + #if !UNITY_2018_3_OR_NEWER [Fact] @@ -106,6 +125,17 @@ public class CustomObject [DataMember] public string OtherProperty { get; set; } } + + private static byte[] CreateMapWithNilValues(int count) + { + var dictionary = new Dictionary(); + for (int index = 0; index < count; index++) + { + dictionary.Add("k" + index.ToString(System.Globalization.CultureInfo.InvariantCulture), null); + } + + return MessagePackSerializer.Serialize(dictionary, MessagePackSerializerOptions.Standard); + } } } From f077798ea3998ddc937056dd53afa2d0fc679d81 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Wed, 29 Apr 2026 14:50:01 -0600 Subject: [PATCH 27/38] Default MVC input formatter to UntrustedData for CWE-1188 MessagePackInputFormatter handles HTTP request bodies, but its default/null options path fell back to MessagePackSerializerOptions.Standard and therefore TrustedData. That left hash-based model binding without the untrusted-data collision-resistance defaults expected at this trust boundary. Default null input-formatter options to Standard.WithSecurity(UntrustedData), while preserving caller-supplied options. Add a regression test that verifies the parameterless formatter deserializes dictionary request bodies with the collision-resistant comparer. --- .../MessagePackInputFormatter.cs | 7 +++-- .../AspNetCoreMvcFormatterTest.cs | 29 +++++++++++++++++++ 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/MessagePack.AspNetCoreMvcFormatter/MessagePackInputFormatter.cs b/src/MessagePack.AspNetCoreMvcFormatter/MessagePackInputFormatter.cs index c2038f5a0..f84b4bf7a 100644 --- a/src/MessagePack.AspNetCoreMvcFormatter/MessagePackInputFormatter.cs +++ b/src/MessagePack.AspNetCoreMvcFormatter/MessagePackInputFormatter.cs @@ -9,16 +9,17 @@ namespace MessagePack.AspNetCoreMvcFormatter public class MessagePackInputFormatter : InputFormatter { private const string ContentType = "application/x-msgpack"; - private readonly MessagePackSerializerOptions? options; + private static readonly MessagePackSerializerOptions DefaultOptions = MessagePackSerializerOptions.Standard.WithSecurity(MessagePackSecurity.UntrustedData); + private readonly MessagePackSerializerOptions options; public MessagePackInputFormatter() - : this(null) + : this(DefaultOptions) { } public MessagePackInputFormatter(MessagePackSerializerOptions? options) { - this.options = options; + this.options = options ?? DefaultOptions; SupportedMediaTypes.Add(ContentType); } diff --git a/tests/MessagePack.AspNetCoreMvcFormatter.Tests/AspNetCoreMvcFormatterTest.cs b/tests/MessagePack.AspNetCoreMvcFormatter.Tests/AspNetCoreMvcFormatterTest.cs index 96b54b315..ed2219b1e 100644 --- a/tests/MessagePack.AspNetCoreMvcFormatter.Tests/AspNetCoreMvcFormatterTest.cs +++ b/tests/MessagePack.AspNetCoreMvcFormatter.Tests/AspNetCoreMvcFormatterTest.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; +using System.Collections.Generic; using System.IO; using System.Text; using System.Threading; @@ -195,6 +196,34 @@ public void MessagePackInputFormatterSupportsXMsgPack() inputFormatter.SupportedMediaTypes.Is(MsgPackContentType); } + [Fact] + public async Task MessagePackInputFormatterDefaultsToUntrustedData() + { + const long value1 = 0x100000001; + const long value2 = 0x200000002; + + var messagePackBinary = MessagePackSerializer.Serialize(new Dictionary + { + [value1] = 1, + [value2] = 2, + }); + + var httpContext = new DefaultHttpContext(); + httpContext.Features.Set(new TestResponseFeature()); + httpContext.Request.Body = new NonSeekableReadStream(messagePackBinary); + httpContext.Request.ContentType = MsgPackContentType; + + InputFormatterContext inputFormatterContext = this.CreateInputFormatterContext(typeof(Dictionary), httpContext); + var inputFormatter = new MessagePackInputFormatter(); + + InputFormatterResult result = await inputFormatter.ReadAsync(inputFormatterContext); + + Assert.False(result.HasError); + var dictionary = Assert.IsType>(result.Model); + Assert.Equal(EqualityComparer.Default.GetHashCode(value1), EqualityComparer.Default.GetHashCode(value2)); + Assert.NotEqual(dictionary.Comparer.GetHashCode(value1), dictionary.Comparer.GetHashCode(value2)); + } + /// /// JsonOutputFormatterTests.cs#L453. /// From 2b5a500ac56c2ae59183337e93cbe7dd1a5a6164 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Wed, 29 Apr 2026 14:46:53 -0600 Subject: [PATCH 28/38] Guard LZ4 decompression length for CWE-409 Validate declared LZ4 decompressed block sizes against the compressed block size before requesting an output buffer. This rejects unreasonable Lz4Block and Lz4BlockArray declarations before allocation while preserving normal compressed payload handling. --- src/MessagePack/MessagePackSecurity.cs | 33 +++++++ src/MessagePack/MessagePackSerializer.Json.cs | 2 +- src/MessagePack/MessagePackSerializer.cs | 16 +++- tests/MessagePack.Tests/LZ4Test.cs | 91 +++++++++++++++++++ .../MessagePackSecurityTests.cs | 11 +++ 5 files changed, 150 insertions(+), 3 deletions(-) diff --git a/src/MessagePack/MessagePackSecurity.cs b/src/MessagePack/MessagePackSecurity.cs index 7b3fe62b0..515d8a024 100644 --- a/src/MessagePack/MessagePackSecurity.cs +++ b/src/MessagePack/MessagePackSecurity.cs @@ -19,6 +19,8 @@ namespace MessagePack /// public class MessagePackSecurity { + private const int DefaultUntrustedDataMaximumDecompressedSize = 64 * 1024 * 1024; + /// /// Gets an instance preconfigured with settings that omit hash collision resistance protections. /// Useful for deserializing fully-trusted and valid msgpack sequences. @@ -27,6 +29,7 @@ public class MessagePackSecurity { HashCollisionResistant = false, MaximumObjectGraphDepth = 500, + MaximumDecompressedSize = int.MaxValue, }; /// @@ -36,6 +39,7 @@ public class MessagePackSecurity { HashCollisionResistant = true, MaximumObjectGraphDepth = 500, + MaximumDecompressedSize = DefaultUntrustedDataMaximumDecompressedSize, }; private static readonly SipHash Hash = new(); @@ -62,6 +66,7 @@ protected MessagePackSecurity(MessagePackSecurity copyFrom) this.HashCollisionResistant = copyFrom.HashCollisionResistant; this.MaximumObjectGraphDepth = copyFrom.MaximumObjectGraphDepth; + this.MaximumDecompressedSize = copyFrom.MaximumDecompressedSize; } /// @@ -86,6 +91,12 @@ protected MessagePackSecurity(MessagePackSecurity copyFrom) /// public int MaximumObjectGraphDepth { get; private set; } = 500; + /// + /// Gets the maximum decompressed size in bytes allowed when deserializing compressed payloads. + /// + /// The default value is for and 64MB for . + public int MaximumDecompressedSize { get; private set; } = int.MaxValue; + /// /// Gets a copy of these options with the property set to a new value. /// @@ -103,6 +114,28 @@ public MessagePackSecurity WithMaximumObjectGraphDepth(int maximumObjectGraphDep return clone; } + /// + /// Gets a copy of these options with the property set to a new value. + /// + /// The new value for the property. Must not be negative. + /// The new instance; or the original if the value is unchanged. + public MessagePackSecurity WithMaximumDecompressedSize(int maximumDecompressedSize) + { + if (this.MaximumDecompressedSize == maximumDecompressedSize) + { + return this; + } + + if (maximumDecompressedSize < 0) + { + throw new ArgumentOutOfRangeException(nameof(maximumDecompressedSize)); + } + + var clone = this.Clone(); + clone.MaximumDecompressedSize = maximumDecompressedSize; + return clone; + } + /// /// Gets a copy of these options with the property set to a new value. /// diff --git a/src/MessagePack/MessagePackSerializer.Json.cs b/src/MessagePack/MessagePackSerializer.Json.cs index ebcddd555..a012aab44 100644 --- a/src/MessagePack/MessagePackSerializer.Json.cs +++ b/src/MessagePack/MessagePackSerializer.Json.cs @@ -92,7 +92,7 @@ public static void ConvertToJson(ref MessagePackReader reader, TextWriter jsonWr { using (var scratchRental = options.SequencePool.Rent()) { - if (TryDecompress(ref reader, scratchRental.Value)) + if (TryDecompress(ref reader, scratchRental.Value, options)) { var scratchReader = new MessagePackReader(scratchRental.Value) { diff --git a/src/MessagePack/MessagePackSerializer.cs b/src/MessagePack/MessagePackSerializer.cs index 3cbb44cec..98764cde6 100644 --- a/src/MessagePack/MessagePackSerializer.cs +++ b/src/MessagePack/MessagePackSerializer.cs @@ -233,7 +233,7 @@ public static T Deserialize(ref MessagePackReader reader, MessagePackSerializ using (var msgPackUncompressedRental = options.SequencePool.Rent()) { var msgPackUncompressed = msgPackUncompressedRental.Value; - if (TryDecompress(ref reader, msgPackUncompressed)) + if (TryDecompress(ref reader, msgPackUncompressed, options)) { MessagePackReader uncompressedReader = reader.Clone(msgPackUncompressed.AsReadOnlySequence); return options.Resolver.GetFormatterWithVerify().Deserialize(ref uncompressedReader, options); @@ -481,7 +481,7 @@ private static int LZ4Operation(in ReadOnlySequence input, Span outp } } - private static bool TryDecompress(ref MessagePackReader reader, IBufferWriter writer) + private static bool TryDecompress(ref MessagePackReader reader, IBufferWriter writer, MessagePackSerializerOptions options) { if (!reader.End) { @@ -503,6 +503,7 @@ private static bool TryDecompress(ref MessagePackReader reader, IBufferWriter compressedData = extReader.Sequence.Slice(extReader.Position); + ThrowIfInvalidLz4BlockLength(uncompressedLength, options.Security.MaximumDecompressedSize); Span uncompressedSpan = writer.GetSpan(uncompressedLength).Slice(0, uncompressedLength); int actualUncompressedLength = LZ4Operation(compressedData, uncompressedSpan, LZ4CodecDecode); Debug.Assert(actualUncompressedLength == uncompressedLength, "Unexpected length of uncompressed data."); @@ -529,6 +530,7 @@ private static bool TryDecompress(ref MessagePackReader reader, IBufferWriter.Shared.Rent(sequenceCount); try { + long remainingMaxDecompressedSize = options.Security.MaximumDecompressedSize; for (int i = 0; i < sequenceCount; i++) { uncompressedLengths[i] = reader.ReadInt32(); @@ -538,6 +540,8 @@ private static bool TryDecompress(ref MessagePackReader reader, IBufferWriter lz4Block = reader.ReadBytes() ?? throw MessagePackSerializationException.ThrowUnexpectedNilWhileDeserializing>(); + ThrowIfInvalidLz4BlockLength(uncompressedLength, remainingMaxDecompressedSize); + remainingMaxDecompressedSize -= uncompressedLength; Span uncompressedSpan = writer.GetSpan(uncompressedLength).Slice(0, uncompressedLength); var actualUncompressedLength = LZ4Operation(lz4Block, uncompressedSpan, LZ4CodecDecode); Debug.Assert(actualUncompressedLength == uncompressedLength, "Unexpected length of uncompressed data."); @@ -558,6 +562,14 @@ private static bool TryDecompress(ref MessagePackReader reader, IBufferWriter remainingMaxDecompressedSize) + { + throw new MessagePackSerializationException("LZ4 block declares a decompressed length that exceeds the configured maximum."); + } + } + private static void ToLZ4BinaryCore(in ReadOnlySequence msgpackUncompressedData, ref MessagePackWriter writer, MessagePackCompression compression, int minCompressionSize) { if (msgpackUncompressedData.Length < minCompressionSize) diff --git a/tests/MessagePack.Tests/LZ4Test.cs b/tests/MessagePack.Tests/LZ4Test.cs index f314b572a..eb80bb2a2 100644 --- a/tests/MessagePack.Tests/LZ4Test.cs +++ b/tests/MessagePack.Tests/LZ4Test.cs @@ -1,6 +1,7 @@ // 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.Buffers; using System.Linq; using Xunit; using Xunit.Abstractions; @@ -22,6 +23,64 @@ public LZ4Test(ITestOutputHelper logger) #endif + [Theory] + [InlineData(MessagePackCompression.Lz4Block)] + [InlineData(MessagePackCompression.Lz4BlockArray)] + [Trait("CWE", "409")] + public void Lz4RejectsDeclaredOutputOverMaximumBeforeAllocating(MessagePackCompression compression) + { + byte[] payload = compression == MessagePackCompression.Lz4Block + ? [0xC7, 0x06, 0x63, 0xD2, 0x7F, 0xFF, 0xFF, 0xFF, 0x00] + : [0x92, 0xC7, 0x05, 0x62, 0xD2, 0x7F, 0xFF, 0xFF, 0xFF, 0xC4, 0x01, 0x00]; + var arrayPool = new ThrowingArrayPool(1024); + var options = MessagePackSerializerOptions.Standard + .WithCompression(compression) + .WithSecurity(MessagePackSecurity.UntrustedData) + .WithPool(new SequencePool(1, arrayPool)); + + MessagePackSerializationException ex = Assert.Throws(() => MessagePackSerializer.Deserialize(payload, options)); + + Assert.Contains("exceeds the configured maximum", FlattenMessages(ex)); + Assert.Null(arrayPool.LargestRequestedLength); + } + + [Fact] + [Trait("CWE", "409")] + public void Lz4BlockArrayRejectsTotalDeclaredOutputOverMaximum() + { + byte[] payload = + [ + 0x93, + 0xC7, 0x02, 0x62, 0x04, 0x04, + 0xC4, 0x05, 0x40, 0x20, 0x20, 0x20, 0x20, + 0xC4, 0x05, 0x40, 0x20, 0x20, 0x20, 0x20, + ]; + var options = MessagePackSerializerOptions.Standard + .WithCompression(MessagePackCompression.Lz4BlockArray) + .WithSecurity(MessagePackSecurity.UntrustedData.WithMaximumDecompressedSize(7)); + + MessagePackSerializationException ex = Assert.Throws(() => MessagePackSerializer.Deserialize(payload, options)); + + Assert.Contains("exceeds the configured maximum", FlattenMessages(ex)); + } + + [Theory] + [InlineData(MessagePackCompression.Lz4Block)] + [InlineData(MessagePackCompression.Lz4BlockArray)] + [Trait("CWE", "409")] + public void Lz4AllowsHighlyCompressiblePayloadWithinMaximum(MessagePackCompression compression) + { + string data = new string(' ', 100_000); + var options = MessagePackSerializerOptions.Standard + .WithCompression(compression) + .WithSecurity(MessagePackSecurity.UntrustedData.WithMaximumDecompressedSize(200_000)); + + byte[] payload = MessagePackSerializer.Serialize(data, options); + string actual = MessagePackSerializer.Deserialize(payload, options); + + Assert.Equal(data, actual); + } + [Fact] public void Lz4Compress() { @@ -82,5 +141,37 @@ private static void SequenceStructuralEqual(SharedData.SimpleStringKeyData[] act actual[i].Prop3.Is(expected[i].Prop3); } } + + private static string FlattenMessages(System.Exception ex) + { + return ex.InnerException is null ? ex.Message : ex.Message + " " + FlattenMessages(ex.InnerException); + } + + private class ThrowingArrayPool : ArrayPool + { + private readonly int maximumLength; + + internal ThrowingArrayPool(int maximumLength) + { + this.maximumLength = maximumLength; + } + + internal int? LargestRequestedLength { get; private set; } + + public override byte[] Rent(int minimumLength) + { + this.LargestRequestedLength = this.LargestRequestedLength.HasValue ? System.Math.Max(this.LargestRequestedLength.Value, minimumLength) : minimumLength; + if (minimumLength > this.maximumLength) + { + throw new System.InvalidOperationException("Unexpected decompression allocation request: " + minimumLength); + } + + return new byte[minimumLength]; + } + + public override void Return(byte[] array, bool clearArray = false) + { + } + } } } diff --git a/tests/MessagePack.Tests/MessagePackSecurityTests.cs b/tests/MessagePack.Tests/MessagePackSecurityTests.cs index 499425777..b517810b8 100644 --- a/tests/MessagePack.Tests/MessagePackSecurityTests.cs +++ b/tests/MessagePack.Tests/MessagePackSecurityTests.cs @@ -22,12 +22,14 @@ public MessagePackSecurityTests(ITestOutputHelper logger) public void Untrusted() { Assert.True(MessagePackSecurity.UntrustedData.HashCollisionResistant); + Assert.Equal(64 * 1024 * 1024, MessagePackSecurity.UntrustedData.MaximumDecompressedSize); } [Fact] public void Trusted() { Assert.False(MessagePackSecurity.TrustedData.HashCollisionResistant); + Assert.Equal(int.MaxValue, MessagePackSecurity.TrustedData.MaximumDecompressedSize); } [Fact] @@ -37,6 +39,15 @@ public void WithHashCollisionResistant() Assert.True(MessagePackSecurity.TrustedData.WithHashCollisionResistant(true).HashCollisionResistant); } + [Fact] + [Trait("CWE", "409")] + public void WithMaximumDecompressedSize() + { + Assert.Same(MessagePackSecurity.UntrustedData, MessagePackSecurity.UntrustedData.WithMaximumDecompressedSize(64 * 1024 * 1024)); + Assert.Throws(() => MessagePackSecurity.UntrustedData.WithMaximumDecompressedSize(-1)); + Assert.Equal(1024, MessagePackSecurity.UntrustedData.WithMaximumDecompressedSize(1024).MaximumDecompressedSize); + } + [Fact] public void EqualityComparer_CollisionResistance_Int64() { From fb0fe9f0095ebadf4b62c563852ce68ba2655ae4 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Wed, 29 Apr 2026 15:35:24 -0600 Subject: [PATCH 29/38] Honor TypeFormatter options hooks for CWE-470 --- .../StandardClassLibraryFormatter.cs | 11 ++- .../StandardClassLibraryFormatterTests.cs | 84 +++++++++++++++++++ 2 files changed, 92 insertions(+), 3 deletions(-) diff --git a/src/MessagePack/Formatters/StandardClassLibraryFormatter.cs b/src/MessagePack/Formatters/StandardClassLibraryFormatter.cs index 71a64908f..0558394d8 100644 --- a/src/MessagePack/Formatters/StandardClassLibraryFormatter.cs +++ b/src/MessagePack/Formatters/StandardClassLibraryFormatter.cs @@ -837,9 +837,14 @@ public void Serialize(ref MessagePackWriter writer, T? value, MessagePackSeriali public T? Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) { - return reader.ReadString() is string value - ? (T?)Type.GetType(value, throwOnError: true) - : null; + if (reader.ReadString() is not string value) + { + return null; + } + + Type type = options.LoadType(value) ?? throw new TypeLoadException(value); + options.ThrowIfDeserializingTypeIsDisallowed(type); + return (T?)type; } } diff --git a/tests/MessagePack.Tests/StandardClassLibraryFormatterTests.cs b/tests/MessagePack.Tests/StandardClassLibraryFormatterTests.cs index 2a86f2d49..991956eba 100644 --- a/tests/MessagePack.Tests/StandardClassLibraryFormatterTests.cs +++ b/tests/MessagePack.Tests/StandardClassLibraryFormatterTests.cs @@ -36,6 +36,27 @@ public void SystemType_Serializable_Null() Assert.Equal(type, type2); } + [Fact] + public void SystemType_DeserializeUsesLoadType() + { + byte[] msgpack = MessagePackSerializer.Serialize(new TypeHolder { Type = typeof(Uri) }, MessagePackSerializerOptions.Standard); + var options = new RejectingTypeLoadOptions(typeof(Uri)); + + var ex = Assert.Throws(() => MessagePackSerializer.Deserialize(msgpack, options)); + Assert.IsType(ex.InnerException); + Assert.Equal(1, options.LoadTypeCalls); + } + + [Fact] + public void SystemType_DeserializeRejectsDisallowedType() + { + byte[] msgpack = MessagePackSerializer.Serialize(new TypeHolder { Type = typeof(Uri) }, MessagePackSerializerOptions.Standard); + var options = new DisallowingTypeOptions(typeof(Uri)); + + var ex = Assert.Throws(() => MessagePackSerializer.Deserialize(msgpack, options)); + Assert.IsType(ex.InnerException); + } + [Fact] public void DeserializeByteArrayFromFixArray() { @@ -145,5 +166,68 @@ private T Roundtrip(T value, bool breakupBuffer = false) return MessagePackSerializer.Deserialize(msgpack, MessagePackSerializerOptions.Standard); } } + + [MessagePackObject] + public class TypeHolder + { + [Key(0)] + public Type Type { get; set; } + } + + private class RejectingTypeLoadOptions : MessagePackSerializerOptions + { + private readonly Type rejectedType; + + internal RejectingTypeLoadOptions(Type rejectedType) + : base(MessagePackSerializerOptions.Standard) + { + this.rejectedType = rejectedType; + } + + private RejectingTypeLoadOptions(RejectingTypeLoadOptions copyFrom) + : base(copyFrom) + { + this.rejectedType = copyFrom.rejectedType; + this.LoadTypeCalls = copyFrom.LoadTypeCalls; + } + + public int LoadTypeCalls { get; private set; } + + public override Type LoadType(string typeName) + { + Type type = base.LoadType(typeName); + this.LoadTypeCalls++; + return type == this.rejectedType ? null : type; + } + + protected override MessagePackSerializerOptions Clone() => new RejectingTypeLoadOptions(this); + } + + private class DisallowingTypeOptions : MessagePackSerializerOptions + { + private readonly Type rejectedType; + + internal DisallowingTypeOptions(Type rejectedType) + : base(MessagePackSerializerOptions.Standard) + { + this.rejectedType = rejectedType; + } + + private DisallowingTypeOptions(DisallowingTypeOptions copyFrom) + : base(copyFrom) + { + this.rejectedType = copyFrom.rejectedType; + } + + public override void ThrowIfDeserializingTypeIsDisallowed(Type type) + { + if (type == this.rejectedType) + { + throw new TypeAccessException(); + } + } + + protected override MessagePackSerializerOptions Clone() => new DisallowingTypeOptions(this); + } } } From 50a24731211b365ef8a7204db8b7e190eed2fa04 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Wed, 29 Apr 2026 15:30:08 -0600 Subject: [PATCH 30/38] 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 31/38] 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 32/38] 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 33/38] 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 34/38] 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 From cea945a48fe9f272c10fa5980e2702d5b2124c14 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Thu, 21 May 2026 14:25:38 -0600 Subject: [PATCH 35/38] Add several known unsafe 'gadgets' to the disallow list Thanks to @svenclaesson for calling these types out as valuable to block by default. --- src/MessagePack/MessagePackSerializerOptions.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/MessagePack/MessagePackSerializerOptions.cs b/src/MessagePack/MessagePackSerializerOptions.cs index dc4598239..8249dbcdf 100644 --- a/src/MessagePack/MessagePackSerializerOptions.cs +++ b/src/MessagePack/MessagePackSerializerOptions.cs @@ -24,14 +24,25 @@ public class MessagePackSerializerOptions /// private static readonly HashSet DisallowedTypes = new HashSet { + "Microsoft.VisualStudio.Text.Formatting.TextFormattingRunProperties", + "System.CodeDom.Compiler.CompilerResults", "System.CodeDom.Compiler.TempFileCollection", + "System.Configuration.SettingsPropertyValue", + "System.Data.DataSet", + "System.Data.DataTable", + "System.Diagnostics.Process", + "System.Diagnostics.ProcessStartInfo", + "System.Drawing.Design.ToolboxItemContainer", "System.IdentityModel.Tokens.SessionSecurityToken", "System.Management.IWbemClassObjectFreeThreaded", "System.Security.Claims.ClaimsIdentity", + "System.Security.Claims.ClaimsPrincipal", "System.Security.Principal.WindowsIdentity", + "System.Security.Principal.WindowsPrincipal", "System.Web.Security.RolePrincipal", "System.Windows.Data.ObjectDataProvider", "System.Windows.ResourceDictionary", + "System.Workflow.ComponentModel.Serialization.ActivitySurrogateSelector", }; #if !DYNAMICCODEDUMPER From 3a020c238ad98c9d737c7dee8b8c091097ebcec8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 21 May 2026 21:06:21 +0000 Subject: [PATCH 36/38] feat: Update package.json to 3.1.6 Commit by [GitHub Actions](https://github.com/MessagePack-CSharp/MessagePack-CSharp/actions/runs/26253102878) --- .../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 23ec4fe5f..d1e46f1a1 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.5", + "version": "3.1.6", "unity": "2021.3", "description": "Extremely Fast MessagePack Serializer for C#.", "keywords": [ From a77d92b7ce9c10c8c19c7ddfc02d9c1483eb6684 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Mon, 1 Jun 2026 11:53:38 -0600 Subject: [PATCH 37/38] Add `scoped` to `MessagePackWriter.Write(ReadOnlySpan)` methods --- src/MessagePack/MessagePackWriter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/MessagePack/MessagePackWriter.cs b/src/MessagePack/MessagePackWriter.cs index e8ef3f068..b3b78f5aa 100644 --- a/src/MessagePack/MessagePackWriter.cs +++ b/src/MessagePack/MessagePackWriter.cs @@ -456,7 +456,7 @@ public void Write(byte[]? src) /// /// When is , the msgpack code used is , or instead. /// - public void Write(ReadOnlySpan src) + public void Write(scoped ReadOnlySpan src) { int length = (int)src.Length; this.WriteBinHeader(length); @@ -607,7 +607,7 @@ public unsafe void Write(string? value) /// . /// /// The value to write. - public unsafe void Write(ReadOnlySpan value) + public unsafe void Write(scoped ReadOnlySpan value) { ref byte buffer = ref this.WriteString_PrepareSpan(value.Length, out int bufferSize, out int useOffset); fixed (char* pValue = value) From 1b53ae8b7681340199faf337bcbb0eb5aed702de Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 9 Jun 2026 17:04:55 +0000 Subject: [PATCH 38/38] feat: Update package.json to 3.1.7 Commit by [GitHub Actions](https://github.com/MessagePack-CSharp/MessagePack-CSharp/actions/runs/27222604788) --- .../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 d1e46f1a1..211ed594d 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.6", + "version": "3.1.7", "unity": "2021.3", "description": "Extremely Fast MessagePack Serializer for C#.", "keywords": [