diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs index e02f23ff91..bad7cbccd1 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs @@ -447,6 +447,7 @@ private static TypeC TestC() #endif #if CS110 && !NET40 public static ReadOnlySpan UTF8Literal => "Hello, world!"u8; + public static ReadOnlySpan UTF8LiteralWithNullTerminator => "Hello, world!\0"u8; #endif #endregion @@ -805,6 +806,19 @@ private static void OutOfMemory() array[0] = 1; Console.WriteLine(array.Length); } + +#if !NET40 && CS70 + public static ReadOnlySpan ReadOnlySpanInitializer_ByteArray() + { + return new byte[3] { 1, 2, 3 }; + } + + public static ReadOnlySpan ReadOnlySpanInitializer_Int32Array() + { + return new int[3] { 1, 2, 3 }; + } +#endif + #endregion #region Object initializers diff --git a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs index ed086d68f2..1b8d092646 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs @@ -289,6 +289,12 @@ protected internal override void VisitCall(Call inst) replacement.AcceptVisitor(this); return; } + if (TransformArrayInitializers.TransformRuntimeHelpersCreateSpanInitialization(inst, context, out var replacement2)) + { + context.Step("TransformRuntimeHelpersCreateSpanInitialization: single-dim", inst); + inst.ReplaceWith(replacement2); + return; + } base.VisitCall(inst); TransformAssignment.HandleCompoundAssign(inst, context); } diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs index 7ddec2e564..45110c12d7 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs @@ -119,28 +119,57 @@ internal static bool TransformSpanTArrayInitialization(NewObj inst, StatementTra replacement = null; if (!context.Settings.ArrayInitializers) return false; - if (MatchSpanTCtorWithPointerAndSize(inst, context, out var elementType, out var field, out var size)) + if (!MatchSpanTCtorWithPointerAndSize(inst, context, out var elementType, out var field, out var size)) + return false; + if (!field.HasFlag(System.Reflection.FieldAttributes.HasFieldRVA)) + return false; + var initialValue = field.GetInitialValue(context.PEFile, context.TypeSystem); + replacement = DecodeArrayInitializerOrUTF8StringLiteral(context, elementType, initialValue, size); + return replacement != null; + } + + internal static bool TransformRuntimeHelpersCreateSpanInitialization(Call inst, StatementTransformContext context, out ILInstruction replacement) + { + replacement = null; + if (!context.Settings.ArrayInitializers) + return false; + if (!MatchRuntimeHelpersCreateSpan(inst, context, out var elementType, out var field)) + return false; + if (!field.HasFlag(System.Reflection.FieldAttributes.HasFieldRVA)) + return false; + if (IsSubPatternOfCpblkInitializer(inst)) + return false; + var initialValue = field.GetInitialValue(context.PEFile, context.TypeSystem); + var elementTypeSize = elementType.GetSize(); + if (elementTypeSize <= 0 || initialValue.Length % elementTypeSize != 0) + return false; + var size = initialValue.Length / elementTypeSize; + replacement = DecodeArrayInitializerOrUTF8StringLiteral(context, elementType, initialValue, size); + return replacement != null; + } + + private static bool IsSubPatternOfCpblkInitializer(Call inst) + { + if (inst.Parent is not AddressOf { Parent: Call { Parent: Cpblk cpblk } get_Item }) + return false; + return MatchGetStaticFieldAddress(get_Item, out _); + } + + private static ILInstruction DecodeArrayInitializerOrUTF8StringLiteral(StatementTransformContext context, IType elementType, BlobReader initialValue, int size) + { + if (context.Settings.Utf8StringLiterals && elementType.IsKnownType(KnownTypeCode.Byte) + && DecodeUTF8String(initialValue, size, out string text)) { - if (field.HasFlag(System.Reflection.FieldAttributes.HasFieldRVA)) - { - var valuesList = new List(); - var initialValue = field.GetInitialValue(context.PEFile, context.TypeSystem); - if (context.Settings.Utf8StringLiterals && - elementType.IsKnownType(KnownTypeCode.Byte) && - DecodeUTF8String(initialValue, size, out string text)) - { - replacement = new LdStrUtf8(text); - return true; - } - if (DecodeArrayInitializer(elementType, initialValue, new[] { size }, valuesList)) - { - var tempStore = context.Function.RegisterVariable(VariableKind.InitializerTarget, new ArrayType(context.TypeSystem, elementType)); - replacement = BlockFromInitializer(tempStore, elementType, new[] { size }, valuesList.ToArray()); - return true; - } - } + return new LdStrUtf8(text); } - return false; + var valuesList = new List(); + if (DecodeArrayInitializer(elementType, initialValue, new[] { size }, valuesList)) + { + var tempStore = context.Function.RegisterVariable(VariableKind.InitializerTarget, new ArrayType(context.TypeSystem, elementType)); + return BlockFromInitializer(tempStore, elementType, new[] { size }, valuesList.ToArray()); + } + + return null; } private static unsafe bool DecodeUTF8String(BlobReader blob, int size, out string text) @@ -153,9 +182,13 @@ private static unsafe bool DecodeUTF8String(BlobReader blob, int size, out strin for (int i = 0; i < size; i++) { byte val = blob.CurrentPointer[i]; - // If the string has control characters, it's probably binary data and not a string. - if (val < 0x20 && val is not ((byte)'\r' or (byte)'\n' or (byte)'\t')) + if (val == 0 && i == size - 1 && size > 1) + { + // Allow explicit null-termination character. + } + else if (val < 0x20 && val is not ((byte)'\r' or (byte)'\n' or (byte)'\t')) { + // If the string has control characters, it's probably binary data and not a string. text = null; return false; } @@ -187,6 +220,25 @@ static bool MatchSpanTCtorWithPointerAndSize(NewObj newObj, StatementTransformCo return true; } + static bool MatchRuntimeHelpersCreateSpan(Call inst, StatementTransformContext context, out IType elementType, out FieldDefinition field) + { + field = default; + elementType = null; + if (!IsRuntimeHelpers(inst.Method.DeclaringType)) + return false; + if (inst.Arguments.Count != 1) + return false; + if (inst.Method is not { Name: "CreateSpan", TypeArguments: [var type] }) + return false; + elementType = type; + if (!inst.Arguments[0].UnwrapConv(ConversionKind.StopGCTracking).MatchLdMemberToken(out var member)) + return false; + if (member.MetadataToken.IsNil) + return false; + field = context.PEFile.Metadata.GetFieldDefinition((FieldDefinitionHandle)member.MetadataToken); + return true; + } + bool DoTransformMultiDim(ILFunction function, Block body, int pos) { if (pos >= body.Instructions.Count - 2) @@ -334,7 +386,7 @@ bool HandleCpblkInitializer(Block block, int pos, ILVariable v, long length, out return true; } - bool MatchGetStaticFieldAddress(ILInstruction input, out IField field) + static bool MatchGetStaticFieldAddress(ILInstruction input, out IField field) { if (input.MatchLdsFlda(out field)) return true; @@ -357,7 +409,7 @@ bool MatchGetStaticFieldAddress(ILInstruction input, out IField field) return field != null; } - static bool IsRuntimeHelpers(IType type) => type is { Name: "RuntimeHelpers", Namespace: "System.Runtime.CompilerServices" }; + static bool IsRuntimeHelpers(IType type) => type is { Name: "RuntimeHelpers", Namespace: "System.Runtime.CompilerServices", TypeParameterCount: 0 }; unsafe bool HandleSequentialLocAllocInitializer(Block block, int pos, ILVariable store, ILInstruction locAllocInstruction, out IType elementType, out StObj[] values, out int instructionsToRemove) {