From 5d2d4f658e62eee7471c8429215aec839b3d83a6 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 8 Nov 2026 13:34:29 +0100 Subject: [PATCH] Bound cyclic base type traversal Assisted-by: OpenCode:gpt-5.6-luna:OpenCode --- .../Rewrite/MarkupExtensionRewritePass.cs | 16 +- .../Xaml/XamlProperty.cs | 9 +- .../ICSharpCode.Decompiler.Tests.csproj | 9 +- .../ILPrettyTestRunner.cs | 6 + .../TestCases/ILPretty/CyclicBaseClass.cs | 45 +++++ .../TestCases/ILPretty/CyclicBaseClass.il | 163 ++++++++++++++++++ .../IL/Transforms/DeconstructionTransform.cs | 8 +- 7 files changed, 232 insertions(+), 24 deletions(-) create mode 100644 ICSharpCode.Decompiler.Tests/TestCases/ILPretty/CyclicBaseClass.cs create mode 100644 ICSharpCode.Decompiler.Tests/TestCases/ILPretty/CyclicBaseClass.il diff --git a/ICSharpCode.BamlDecompiler/Rewrite/MarkupExtensionRewritePass.cs b/ICSharpCode.BamlDecompiler/Rewrite/MarkupExtensionRewritePass.cs index a1d1fd1d75a..c9a97a9d97b 100644 --- a/ICSharpCode.BamlDecompiler/Rewrite/MarkupExtensionRewritePass.cs +++ b/ICSharpCode.BamlDecompiler/Rewrite/MarkupExtensionRewritePass.cs @@ -116,17 +116,9 @@ bool CanInlineExt(XamlContext ctx, XElement ctxElement) var type = ctxElement.Annotation(); if (type != null && type.ResolvedType != null) { - var typeDef = type.ResolvedType.GetDefinition()?.DirectBaseTypes.FirstOrDefault(); - bool isExt = false; - while (typeDef != null) - { - if (typeDef.FullName == "System.Windows.Markup.MarkupExtension") - { - isExt = true; - break; - } - typeDef = typeDef.DirectBaseTypes.FirstOrDefault(); - } + var typeDef = type.ResolvedType.GetDefinition(); + bool isExt = typeDef != null && typeDef.GetNonInterfaceBaseTypes() + .Any(t => t != typeDef && t.FullName == "System.Windows.Markup.MarkupExtension"); if (!isExt) return false; } @@ -209,4 +201,4 @@ XamlExtension InlineExtension(XamlContext ctx, XElement ctxElement) return ext; } } -} \ No newline at end of file +} diff --git a/ICSharpCode.BamlDecompiler/Xaml/XamlProperty.cs b/ICSharpCode.BamlDecompiler/Xaml/XamlProperty.cs index cb422b0d81a..8c41c337998 100644 --- a/ICSharpCode.BamlDecompiler/Xaml/XamlProperty.cs +++ b/ICSharpCode.BamlDecompiler/Xaml/XamlProperty.cs @@ -81,14 +81,11 @@ public bool IsAttachedTo(XamlType type) } var declType = ResolvedMember.DeclaringType; - var t = type.ResolvedType; - - do + foreach (var t in type.ResolvedType.GetNonInterfaceBaseTypes()) { if (t.FullName == declType.FullName && t.TypeParameterCount == declType.TypeParameterCount) return false; - t = t.DirectBaseTypes.FirstOrDefault(); - } while (t != null); + } return true; } @@ -123,4 +120,4 @@ public XName ToXName(XamlContext ctx, XElement parent, bool isFullName = true) public override string ToString() => PropertyName; } -} \ No newline at end of file +} diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj index ccfa8e6a748..93db0b3e3f5 100644 --- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj @@ -213,9 +213,12 @@ - - - + + + + + + diff --git a/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs index 134587ea295..d91e172ffc8 100644 --- a/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs @@ -407,6 +407,12 @@ public async Task EmptyBodies() await Run(); } + [Test] + public async Task CyclicBaseClass() + { + await Run(); + } + [Test] public async Task SealedRecordProtectedCopyCtor() { diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/CyclicBaseClass.cs b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/CyclicBaseClass.cs new file mode 100644 index 00000000000..af3e71507fe --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/CyclicBaseClass.cs @@ -0,0 +1,45 @@ +namespace CyclicBaseClass +{ + public abstract class Expanding : Expanding> + { + } + + public static class Extensions + { + public static void Deconstruct(this SelfDerived value, out int a, out int b) + { + a = 1; + b = 2; + } + + public static void Deconstruct(this Expanding value, out int a, out int b) + { + a = 1; + b = 2; + } + } + + public static class NestedDeconstruction + { + public static int Sum(Pair pair) + { + var (num4, (num5, num6)) = pair; + return num4 + num5 + num6; + } + + public static int Sum(Pair> pair) + { + var (num4, (num5, num6)) = pair; + return num4 + num5 + num6; + } + } + + public abstract class Pair + { + public abstract void Deconstruct(out int first, out T second); + } + + public abstract class SelfDerived : SelfDerived + { + } +} diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/CyclicBaseClass.il b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/CyclicBaseClass.il new file mode 100644 index 00000000000..1759403c128 --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/CyclicBaseClass.il @@ -0,0 +1,163 @@ +.assembly extern mscorlib +{ + .publickeytoken = (B7 7A 5C 56 19 34 E0 89) + .ver 4:0:0:0 +} + +.assembly CyclicBaseClass +{ + .hash algorithm 0x00008004 + .ver 1:0:0:0 +} + +.module CyclicBaseClass.dll + +.class public auto ansi abstract beforefieldinit CyclicBaseClass.SelfDerived + extends CyclicBaseClass.SelfDerived +{ +} + +.class public auto ansi abstract beforefieldinit CyclicBaseClass.Expanding`1 + extends class CyclicBaseClass.Expanding`1> +{ +} + +.class public auto ansi abstract sealed beforefieldinit CyclicBaseClass.Extensions + extends [mscorlib]System.Object +{ + .custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( + 01 00 00 00 + ) + .method public hidebysig static + void Deconstruct ( + class CyclicBaseClass.SelfDerived 'value', + [out] int32& a, + [out] int32& b + ) cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( + 01 00 00 00 + ) + .maxstack 8 + ldarg.1 + ldc.i4.1 + stind.i4 + ldarg.2 + ldc.i4.2 + stind.i4 + ret + } + + .method public hidebysig static + void Deconstruct ( + class CyclicBaseClass.Expanding`1 'value', + [out] int32& a, + [out] int32& b + ) cil managed + { + .custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( + 01 00 00 00 + ) + .maxstack 8 + ldarg.1 + ldc.i4.1 + stind.i4 + ldarg.2 + ldc.i4.2 + stind.i4 + ret + } +} + +.class public auto ansi abstract beforefieldinit CyclicBaseClass.Pair`1 + extends [mscorlib]System.Object +{ + .method public hidebysig newslot abstract virtual + instance void Deconstruct ( + [out] int32& first, + [out] !T& second + ) cil managed + { + } + + .method family hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + .maxstack 8 + ldarg.0 + call instance void [mscorlib]System.Object::.ctor() + ret + } +} + +.class public auto ansi abstract sealed beforefieldinit CyclicBaseClass.NestedDeconstruction + extends [mscorlib]System.Object +{ + .method public hidebysig static + int32 Sum ( + class CyclicBaseClass.Pair`1 pair + ) cil managed + { + .maxstack 3 + .locals init ( + [0] int32, + [1] int32, + [2] int32, + [3] class CyclicBaseClass.SelfDerived, + [4] int32, + [5] int32 + ) + ldarg.0 + ldloca.s 2 + ldloca.s 3 + callvirt instance void class CyclicBaseClass.Pair`1::Deconstruct(int32&, !0&) + ldloc.3 + ldloca.s 4 + ldloca.s 5 + call void CyclicBaseClass.Extensions::Deconstruct(class CyclicBaseClass.SelfDerived, int32&, int32&) + ldloc.2 + ldloc.s 4 + stloc.0 + ldloc.s 5 + stloc.1 + ldloc.0 + add + ldloc.1 + add + ret + } + + .method public hidebysig static + int32 Sum ( + class CyclicBaseClass.Pair`1> pair + ) cil managed + { + .maxstack 3 + .locals init ( + [0] int32, + [1] int32, + [2] int32, + [3] class CyclicBaseClass.Expanding`1, + [4] int32, + [5] int32 + ) + ldarg.0 + ldloca.s 2 + ldloca.s 3 + callvirt instance void class CyclicBaseClass.Pair`1>::Deconstruct(int32&, !0&) + ldloc.3 + ldloca.s 4 + ldloca.s 5 + call void CyclicBaseClass.Extensions::Deconstruct(class CyclicBaseClass.Expanding`1, int32&, int32&) + ldloc.2 + ldloc.s 4 + stloc.0 + ldloc.s 5 + stloc.1 + ldloc.0 + add + ldloc.1 + add + ret + } +} diff --git a/ICSharpCode.Decompiler/IL/Transforms/DeconstructionTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/DeconstructionTransform.cs index d17c240f478..12c56165e60 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/DeconstructionTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/DeconstructionTransform.cs @@ -630,9 +630,12 @@ static bool IsReceiverReference(ILInstruction target, ILVariable receiver) static bool BindsOnElementType(IMethod method, IType elementType) { int outParamCount = method.Parameters.Count - (method.IsStatic ? 1 : 0); - IType type = elementType; - while (type != null) + // The element type followed by its base classes, most derived first. The base class is + // metadata and may be cyclic, which GetNonInterfaceBaseTypes() tolerates. + foreach (IType type in elementType.GetNonInterfaceBaseTypes().Reverse()) { + if (type != elementType && type.Kind != TypeKind.Class) + continue; if (!method.IsStatic && NormalizeTypeVisitor.TypeErasure.EquivalentTypes(type, method.DeclaringType)) return true; if (type.GetMethods(m => m.Name == "Deconstruct", GetMemberOptions.IgnoreInheritedMembers) @@ -643,7 +646,6 @@ static bool BindsOnElementType(IMethod method, IType elementType) // method (and wins over a called extension method). return false; } - type = type.DirectBaseTypes.FirstOrDefault(t => t.Kind == TypeKind.Class)!; } // The chain ended without seeing the declaring type, so an instance method's // binding cannot be verified. An extension method is reached by its receiver