Browse Source

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
pull/3813/head
Siegfried Pammer 2 weeks ago committed by Siegfried Pammer
parent
commit
de33be74da
  1. 27
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/CS73_StackAllocInitializers.cs
  2. 8
      ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs

27
ICSharpCode.Decompiler.Tests/TestCases/Pretty/CS73_StackAllocInitializers.cs

@ -257,6 +257,33 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -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

8
ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs

@ -515,8 +515,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -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)

Loading…
Cancel
Save