From de33be74da7d552b1d1a0c75c18b2c94a8addb97 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Mon, 22 Jun 2026 22:23:06 +0200 Subject: [PATCH] Don't reconstruct a partially written stackalloc as an initializer HandleSequentialLocAllocInitializer formed a stackalloc initializer whenever the explicit stores were contiguous from offset 0, even if they did not cover the whole buffer. A 'stackalloc byte[16]' reinterpreted as int and written through three of its four elements decompiled to 'stackalloc int[4] { 1, v, 3 }', whose initializer has fewer elements than the declared length and does not compile. Require every element to be written (from the constant data blob or an explicit store) before forming the initializer; otherwise the buffer stays a plain stackalloc with individual stores. Found while exploring stackalloc-initializer coverage. Assisted-by: Claude:claude-opus-4-8:Claude Code --- .../Pretty/CS73_StackAllocInitializers.cs | 27 +++++++++++++++++++ .../Transforms/TransformArrayInitializers.cs | 8 ++++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CS73_StackAllocInitializers.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CS73_StackAllocInitializers.cs index d082d149a..e5ec81ba4 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CS73_StackAllocInitializers.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CS73_StackAllocInitializers.cs @@ -257,6 +257,33 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty return UsePointer((byte*)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). + public unsafe string PartialReinterpret(int v) + { +#if OPT + byte* num = stackalloc byte[16]; + *(int*)num = 1; + ((int*)num)[1] = v; + ((int*)num)[2] = 3; + long num2 = 0L; + return UseBytePointer(num, &num2); +#else + byte* ptr = stackalloc byte[16]; + *(int*)ptr = 1; + ((int*)ptr)[1] = v; + ((int*)ptr)[2] = 3; + long num = 0L; + return UseBytePointer(ptr, &num); +#endif + } + + public unsafe static string UseBytePointer(byte* ptr, long* length) + { + return ((int*)ptr)->ToString(); + } + public unsafe string NegativeOffsets(int a, int b, int c) { #if OPT diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs index 6245920c1..9ae14ec99 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs @@ -515,8 +515,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms elementCount++; } - if (values == null || store.Kind != VariableKind.StackSlot || store.StoreCount != 1 - || store.AddressCount != 0 || store.LoadCount > values.Length + 1) + // Every element must be written, either from the constant data blob or by an explicit + // store. A gap means the buffer is only partially initialized (e.g. a 'stackalloc + // byte[16]' reinterpreted and written through a few elements), which cannot be + // represented as an initializer and has to stay a sequence of individual stores. + if (values == null || Array.IndexOf(values, null) >= 0 || store.Kind != VariableKind.StackSlot + || store.StoreCount != 1 || store.AddressCount != 0 || store.LoadCount > values.Length + 1) return false; if (store.LoadInstructions.Last().Parent is StLoc finalStore)