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);
}