diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CS73_StackAllocInitializers.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CS73_StackAllocInitializers.cs index e5ec81ba4..9485527df 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CS73_StackAllocInitializers.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CS73_StackAllocInitializers.cs @@ -257,6 +257,18 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty return UsePointer((byte*)ptr); } + // A pointer-typed stackalloc passed to a call must stay a local: inlining it into the + // argument would retype it as Span, which does not convert to the pointer parameter. + public unsafe void StackAllocPassedToPointerCall(int v) + { + int* ptr = stackalloc int[3] { 1, 2, v }; + UseIntPointer(ptr); + } + + public unsafe static void UseIntPointer(int* ptr) + { + } + // A buffer that is only partially written through a reinterpreting cast is not an // initializer: the fourth int is never assigned, so it must stay a sequence of stores // rather than be reconstructed as 'stackalloc int[4] { 1, v, 3 }' (too few elements). diff --git a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs index 0bc5c2eb6..2c60cfc43 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs @@ -249,6 +249,19 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (r.Type == FindResultType.Found || r.Type == FindResultType.NamedArgument) { var loadInst = r.LoadInst; + // A stackalloc whose result is a pointer (rather than a Span) is only valid C# as + // the initializer of a pointer-typed local. Inlining it into any expression position + // retypes it as Span, which breaks every pointer use (dereference, a pointer-typed + // argument, a reinterpreting cast). Moving it into a local store keeps it a local + // initializer, and the Span/ReadOnlySpan constructor is the one position where + // reinterpreting it as a span is exactly the point; everything else must not inline. + if (inlinedExpression.ResultType == StackType.I + && (inlinedExpression is LocAlloc || inlinedExpression is Block { Kind: BlockKind.StackAllocInitializer }) + && loadInst.Parent is not StLoc + && !IsStackAllocSpanConstructorArgument(loadInst)) + { + return false; + } if (loadInst.OpCode == OpCode.LdLoca) { if (!IsGeneratedTemporaryForAddressOf((LdLoca)loadInst, v, inlinedExpression, options)) @@ -291,6 +304,21 @@ namespace ICSharpCode.Decompiler.IL.Transforms return false; } + /// + /// Returns true if is the pointer argument of a + /// new Span<T>(pointer, length) / new ReadOnlySpan<T>(pointer, length) + /// constructor, i.e. the one position where a pointer-typed stackalloc may be inlined + /// (TransformSpanTCtorContainingStackAlloc turns it into a span stackalloc afterwards). + /// + static bool IsStackAllocSpanConstructorArgument(ILInstruction loadInst) + { + return loadInst.Parent is NewObj newObj + && newObj.Arguments.Count == 2 + && newObj.Arguments[0] == loadInst + && (newObj.Method.DeclaringType.IsKnownType(KnownTypeCode.SpanOfT) + || newObj.Method.DeclaringType.IsKnownType(KnownTypeCode.ReadOnlySpanOfT)); + } + /// /// Is this a temporary variable generated by the C# compiler for instance method calls on value type values ///