Fix #3799 and three related stackalloc initializer decompilation defects#3802
Merged
Conversation
siegfriedpammer
force-pushed
the
fix-3799-stackalloc-elementtype
branch
from
June 22, 2026 19:32
a1684dd to
15b74c2
Compare
Member
Author
|
Reviewed the full diff. The four fixes are each narrowly scoped and individually test-covered:
No correctness concerns. (GitHub does not allow approving one's own PR, so this is a comment rather than a formal approval.) (This review was written by an AI agent on behalf of Siegfried Pammer.) |
…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
A constant-size stackalloc initializer stores its constant elements through a data blob (cpblk from a <PrivateImplementationDetails> field). ReadElement decoded every element by width, so a 4-byte float was read as Int32 and an 8-byte double as Int64. The resulting constant carried the raw bit pattern (1f decoded as 1.0653532E+09f) and its stack type no longer matched the store, tripping StObj.CheckInvariant. Dispatch on the element's type code so Single and Double are read as floating-point, matching the heap-array decoder. Found while exploring stackalloc-initializer coverage around the element-type hint fix; floating-point element types had no test case. Assisted-by: Claude:claude-opus-4-8:Claude Code
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
A stackalloc whose result is a pointer is only valid C# as the initializer of a
pointer-typed local. The inliner moved a single-use pointer stackalloc into its
use, producing e.g. 'K.V(stackalloc int[3] { 1, 2, v })' or '*stackalloc ...';
in an expression position the stackalloc is typed as Span<T>, which does not
convert to a pointer, so the output did not compile. Keep such a stackalloc as a
separate local. Moving it into a local store (its declaration) and into the
Span<T>/ReadOnlySpan<T> constructor stay allowed, since those are the positions
where the pointer or span form is exactly what is wanted.
Found while exploring stackalloc-initializer coverage.
Assisted-by: Claude:claude-opus-4-8:Claude Code
siegfriedpammer
force-pushed
the
fix-3799-stackalloc-elementtype
branch
2 times, most recently
from
June 23, 2026 06:02
f579b5e to
94fd452
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #3799, and bundles three independent
stackallocinitializer decompilation defects found alongside it.Only the first change below is the reported crash and the actual #3799 fix. The other three were uncovered by an exploratory testing sweep over the surrounding code (varying element types, element positions, allocation shapes, and use contexts, validating each result by recompiling it); they are unrelated improvements and do not reference the issue.
1. Element type lost when the type hint is not a pointer (the reported crash, fixes #3799)
A constant-size
stackallocinitializer whose buffer is passed to a static call without being dereferenced threwgiven Block is invalid!(reproduced on the reporter'sJetBrains.HabitatDetector.dll). The element type is recovered from the surrounding type hint, but for this shape the folded constant size erases thesizeofand optimized codegen keeps the buffer as a native int, so the hint is not a pointer and the element type fell back tobyte. Derive it from the type actually being stored.2. float/double constants decoded as raw integer bits
A mixed float/double initializer stores its constant prefix through a data blob;
ReadElementdecoded each element by width, so1fwas read asInt32and emitted as1.0653532E+09f(and the wrong stack type trippedStObj.CheckInvariant). Dispatch on the type code soSingle/Doubleare read as floating-point, matching the heap-array decoder.3. Partially written reinterpreted buffer reconstructed as an initializer
byte* p = stackalloc byte[16]written through three of its fourintslots decompiled tostackalloc int[4] { 1, v, 3 }— an initializer with fewer elements than the length, which does not compile. Only form the initializer when every element is written; otherwise keep individual stores.4. Pointer-typed stackalloc inlined into an expression
A single-use pointer stackalloc was inlined into its use, producing
K.V(stackalloc int[3] { 1, 2, v })or*stackalloc ...; in expression position the stackalloc is typedSpan<T>, which does not convert to a pointer, so the output did not compile. Keep it a local; inlining into a local store (its declaration) and into theSpan<T>/ReadOnlySpan<T>constructor stay allowed.Tests
Each fix adds a regression case to
CS73_StackAllocInitializers(red without the fix in the relevant configurations, green with it). No regressions across the stackalloc/span/pointer-touching suites:CS73_StackAllocInitializers,InitializerTests,UnsafeCode,InlineArrayTests,RefFields,IndexRangeTest,ParamsCollections,PointerArithmetic,CompoundAssignmentTest,ExpressionTrees(Pretty), andUnsafeCode/InitializerTests(Correctness). The minimal repro and the realJetBrains.HabitatDetectormethod decompile correctly.This PR description was written by an AI agent (Claude, claude-opus-4-8, via Claude Code).