From a5d6a38df519c2d7ccfe04aabc4f8722725ba2db Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 6 Sep 2026 00:41:00 +0200 Subject: [PATCH] Invent a syntax for `ldtoken`. Still not valid C# code, but at least it's now possible to see which member is referenced. --- .../Ugly/NoArrayInitializers.Expected.cs | 3 +- .../CSharp/ExpressionBuilder.cs | 30 +++++++++++++++++++ .../Implementation/MergedNamespace.cs | 5 +++- 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Ugly/NoArrayInitializers.Expected.cs b/ICSharpCode.Decompiler.Tests/TestCases/Ugly/NoArrayInitializers.Expected.cs index 0ed732c8a4..ff0f1f8d8c 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Ugly/NoArrayInitializers.Expected.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Ugly/NoArrayInitializers.Expected.cs @@ -1,4 +1,3 @@ -using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -19,7 +18,7 @@ public class NoArrayInitializers public int[] LiteralArray() { int[] array = new int[3]; - RuntimeHelpers.InitializeArray(array, (RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/); + RuntimeHelpers.InitializeArray(array, __ldtoken(_003CPrivateImplementationDetails_003E._4636993D3E1DA4E9D6B8F87B79E8F7C6D018580D52661950EABC3845C5897A4D)); return array; } diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index 600ca0b845..53934fa03a 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -5153,6 +5153,36 @@ protected internal override TranslatedExpression VisitLdVirtFtn(LdVirtFtn inst, .WithILInstruction(inst); } + protected internal override TranslatedExpression VisitLdMemberToken(LdMemberToken inst, TranslationContext context) + { + // C# does not have syntax for obtaining a member token; so we emit pseudo-syntax: + // fields: __ldtoken(DeclaringType.Member) + // methods: __ldtoken(DeclaringType.Member(parameter-types)) + var classType = astBuilder.ConvertType(inst.Member.DeclaringType); + var mre = new MemberReferenceExpression( + new TypeReferenceExpression(classType), + inst.Member.Name + ); + Expression memberExpr = mre.WithRR(new MemberResolveResult(null, inst.Member)); + if (inst.Member is IMethod method) + { + foreach (var t in method.TypeArguments) + { + mre.TypeArguments.Add(astBuilder.ConvertType(t)); + } + var inv = new InvocationExpression(memberExpr); + foreach (var param in method.Parameters) + { + inv.Arguments.Add(new TypeReferenceExpression(astBuilder.ConvertType(param.Type))); + } + memberExpr = inv; + } + var tokenType = inst.Member is IField ? KnownTypeCode.RuntimeFieldHandle : KnownTypeCode.RuntimeMethodHandle; + return new InvocationExpression(new IdentifierExpression("__ldtoken"), memberExpr) + .WithRR(new ResolveResult(compilation.FindType(tokenType))) + .WithILInstruction(inst); + } + protected internal override TranslatedExpression VisitCallIndirect(CallIndirect inst, TranslationContext context) { if (inst.IsInstance) diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/MergedNamespace.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/MergedNamespace.cs index 48037ad37f..95e06647dd 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/MergedNamespace.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/MergedNamespace.cs @@ -152,7 +152,10 @@ public ITypeDefinition GetTypeDefinition(string name, int typeParameterCount) // and using the main assembly can cause a stack overflow if there // are internal assembly attributes. } - anyTypeDef = typeDef; + // If there's multiple possible matches prefer the first one -- we most likely are + // in the context of the assembly being decompiled, which usually is the first in the list, + // so this increases the odds of picking the correct type. + anyTypeDef ??= typeDef; } } return anyTypeDef;