Browse Source

Don't assert decompiled local types match the signature

DebugInfoGenerator asserted that every local variable's decompiled type
is equivalent to the metadata local-signature type at its slot. That
holds at IL-read time but not afterwards: variable splitting gives one
slot several typed variables, pinned-region locals are modeled as
pointers (int* vs int& pinned), and generic-context type-parameter
identity differs. The assertion therefore aborted PDB generation (Debug
builds) for ordinary inputs such as any method with a fixed statement.

The type is never written to the PDB - only the slot index and name are -
so the mismatch cannot affect the debugging experience. The slot index,
the only emitted value, is correct by construction: it is the IL
ldloc/stloc operand, sourced from the signature slot when the variable is
created and copied verbatim by SplitVariables; it is never reassigned.
Keep only the index-bound check and document why the type is not verified.

Assisted-by: Claude:claude-opus-4-8:Claude Code
pull/3832/head
Siegfried Pammer 1 week ago committed by Siegfried Pammer
parent
commit
ca5eec3cc9
  1. 1
      ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
  2. 23
      ICSharpCode.Decompiler.Tests/PdbGenerationTestRunner.cs
  3. 34
      ICSharpCode.Decompiler.Tests/TestCases/PdbGen/PinnedLocalSlot.il
  4. 9
      ICSharpCode.Decompiler/DebugInfo/DebugInfoGenerator.cs

1
ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj

@ -164,6 +164,7 @@ @@ -164,6 +164,7 @@
<ItemGroup>
<None Include="TestCases\PdbGen\*.cs" />
<None Include="TestCases\PdbGen\PinnedRegionWarning.il" />
<None Include="TestCases\PdbGen\PinnedLocalSlot.il" />
</ItemGroup>
<ItemGroup>

23
ICSharpCode.Decompiler.Tests/PdbGenerationTestRunner.cs

@ -336,6 +336,29 @@ namespace ICSharpCode.Decompiler.Tests @@ -336,6 +336,29 @@ namespace ICSharpCode.Decompiler.Tests
Assert.DoesNotThrow(() => decompiler.CreateSequencePoints(syntaxTree));
}
[Test]
public async Task PinnedLocalSlot()
{
// The decompiler models a pinned-region local as a pointer (int*), which never matches the
// metadata local signature type (int& pinned). The slot index is still faithful to the IL,
// and the type is never written to the PDB, so generating debug info must not assert on the
// type difference.
string ilFile = Path.Combine(TestCasePath, nameof(PinnedLocalSlot) + ".il");
string peFileName = await Tester.AssembleIL(ilFile, AssemblerOptions.Library);
var module = new PEFile(peFileName);
var resolver = new UniversalAssemblyResolver(peFileName, false,
module.Metadata.DetectTargetFrameworkId(), null, PEStreamOptions.PrefetchEntireImage);
var decompiler = new CSharpDecompiler(module, resolver, new DecompilerSettings());
var syntaxTree = decompiler.DecompileType(new Decompiler.TypeSystem.FullTypeName("C"));
Assert.DoesNotThrow(() => {
var debugInfo = new Decompiler.DebugInfo.DebugInfoGenerator(decompiler.TypeSystem);
syntaxTree.AcceptVisitor(debugInfo);
});
}
private class TestProgressReporter : IProgress<DecompilationProgress>
{
private Action<DecompilationProgress> reportFunc;

34
ICSharpCode.Decompiler.Tests/TestCases/PdbGen/PinnedLocalSlot.il

@ -0,0 +1,34 @@ @@ -0,0 +1,34 @@
// A simple pinned pointer (fixed statement). The metadata local signature types the slot as
// `int32& pinned`, but the decompiler models a pinned-region local as a pointer (`int*`). The two
// never match, yet the slot index is faithful to the IL ldloc/stloc operand, so PDB generation
// must not assert on the type difference. Regression fixture for that assertion.
.assembly extern System.Runtime { }
.assembly PinnedLocalSlot { }
.module PinnedLocalSlot.dll
.class public auto ansi beforefieldinit C extends [System.Runtime]System.Object
{
.method public hidebysig instance int32 M(int32[] a) cil managed
{
.maxstack 2
.locals ( [0] int32& pinned p )
ldarg.1
ldc.i4.0
ldelema [System.Runtime]System.Int32
stloc.0
ldloc.0
ldind.i4
ldc.i4.0
conv.u
stloc.0
ret
}
.method public hidebysig specialname rtspecialname instance void .ctor() cil managed
{
.maxstack 1
ldarg.0
call instance void [System.Runtime]System.Object::.ctor()
ret
}
}

9
ICSharpCode.Decompiler/DebugInfo/DebugInfoGenerator.cs

@ -253,7 +253,14 @@ namespace ICSharpCode.Decompiler.DebugInfo @@ -253,7 +253,14 @@ namespace ICSharpCode.Decompiler.DebugInfo
if (v.Index != null && v.Kind.IsLocal())
{
#if DEBUG
Debug.Assert(v.Index < types.Length && NormalizeTypeVisitor.TypeErasure.EquivalentTypes(v.Type, types[v.Index.Value]));
// The index is the IL ldloc/stloc operand, which indexes the local signature
// directly: ILReader creates one variable per signature slot, and SplitVariables
// copies that index. It is therefore correct by construction. The variable type
// is intentionally not checked against the signature - it is never written to the
// PDB (only the slot index and name are), and it legitimately diverges after
// variable splitting (one slot, several typed variables), for pinned-region locals
// modeled as pointers, and through generic-context type-parameter identity.
Debug.Assert(v.Index < types.Length);
#endif
localVariables.Add(v);
}

Loading…
Cancel
Save