diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CS73_StackAllocInitializers.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CS73_StackAllocInitializers.cs index 1b12436a1..264be7e2a 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CS73_StackAllocInitializers.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CS73_StackAllocInitializers.cs @@ -219,6 +219,17 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty return UsePointer((byte*)ptr); } + // The buffer is only passed to a static call, never dereferenced as a typed pointer, so + // some compilers keep it on the stack as a native int rather than in an int* local. + // Combined with the constant allocation size being folded to a byte count, that previously + // made the decompiler lose the element type and throw "given Block is invalid!". + public unsafe string Issue3799(int pid) + { + int* ptr = stackalloc int[4] { 1, 14, 1, pid }; + long num = 0L; + return UseTwoPointers(ptr, &num); + } + public unsafe string NotAnInitializer(int a, int b, int c) { int* ptr = stackalloc int[6]; @@ -255,6 +266,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty return ptr->ToString(); } + public unsafe static string UseTwoPointers(int* ptr, long* length) + { + return ((byte*)ptr)->ToString(); + } + public string GetSpan() { Span span = stackalloc int[GetSize()]; diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index 0f7bdbd92..0b6b87504 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -3713,7 +3713,13 @@ namespace ICSharpCode.Decompiler.CSharp IType elementType; if (block.Instructions.Count < 2 || !block.Instructions[1].MatchStObj(out _, out _, out var t)) throw new ArgumentException("given Block is invalid!"); - if (typeHint is PointerType pt && !TypeUtils.IsCompatibleTypeForMemoryAccess(t, pt.ElementType)) + // Derive the element type from the type actually being stored when the type hint does + // not pin it down. The hint is unreliable here: when the allocation size is a folded + // constant (e.g. 'localloc 16' for 'stackalloc int[4]') the element type can no longer + // be read off a 'count * sizeof(T)' expression, and the surrounding context may type the + // buffer as a plain native int rather than a T*. In both cases falling back to 't' keeps + // the element type consistent with the stores in the block. + if (!(typeHint is PointerType pt) || !TypeUtils.IsCompatibleTypeForMemoryAccess(t, pt.ElementType)) { typeHint = new PointerType(t); }