diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj index 2d40851ac..d38245d46 100644 --- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj @@ -164,6 +164,7 @@ + diff --git a/ICSharpCode.Decompiler.Tests/PdbGenerationTestRunner.cs b/ICSharpCode.Decompiler.Tests/PdbGenerationTestRunner.cs index 904c769d8..0f2b5cf1f 100644 --- a/ICSharpCode.Decompiler.Tests/PdbGenerationTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/PdbGenerationTestRunner.cs @@ -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 { private Action reportFunc; diff --git a/ICSharpCode.Decompiler.Tests/TestCases/PdbGen/PinnedLocalSlot.il b/ICSharpCode.Decompiler.Tests/TestCases/PdbGen/PinnedLocalSlot.il new file mode 100644 index 000000000..88e3f4f22 --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/PdbGen/PinnedLocalSlot.il @@ -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 + } +} diff --git a/ICSharpCode.Decompiler/DebugInfo/DebugInfoGenerator.cs b/ICSharpCode.Decompiler/DebugInfo/DebugInfoGenerator.cs index 5ae963094..a5c8f5782 100644 --- a/ICSharpCode.Decompiler/DebugInfo/DebugInfoGenerator.cs +++ b/ICSharpCode.Decompiler/DebugInfo/DebugInfoGenerator.cs @@ -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); }