From 0c23009b3efa847c99f9c1fdeda205499327a2eb Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Mon, 22 Jun 2026 21:29:20 +0200 Subject: [PATCH] Fix #3799: keep stackalloc initializer element type when the hint is not a pointer A constant-size stackalloc initializer whose buffer is only passed to a static call (never dereferenced as a typed pointer) threw "given Block is invalid!". TranslateStackAllocInitializer recovers the element type from the surrounding type hint, but that hint is unreliable for this shape: the constant allocation size is folded to a byte count, so it can no longer be read off a 'count * sizeof(T)' expression, and the buffer is kept on the stack as a native int instead of a T* local, leaving the hint a non-pointer. The guard only repaired an incompatible pointer hint, so a non-pointer hint fell through to a 'byte' element type that is incompatible with the actual stores, and the per-element check threw. Derive the element type from the type being stored whenever the hint is not already a compatible pointer. The regression is driven by the code shape, not the compiler version: in optimized builds the buffer is kept on the stack as a native int whenever it is passed to a static call without being dereferenced. This reproduces across Roslyn versions, including the pinned test compiler, so the added fixture is red without this fix in the optimized configurations. Assisted-by: Claude:claude-opus-4-8:Claude Code --- .../Pretty/CS73_StackAllocInitializers.cs | 16 ++++++++++++++++ .../CSharp/ExpressionBuilder.cs | 8 +++++++- 2 files changed, 23 insertions(+), 1 deletion(-) 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); }