diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index 1d3990401..55f20d3dc 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -122,7 +122,8 @@ namespace ICSharpCode.Decompiler.CSharp } }, new DelegateConstruction(), - new AssignVariableNames() + new AssignVariableNames(), + new ProxyCallReplacer(), }; } diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index 912fa636d..e68291245 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -273,6 +273,7 @@ + diff --git a/ICSharpCode.Decompiler/IL/Transforms/ProxyCallReplacer.cs b/ICSharpCode.Decompiler/IL/Transforms/ProxyCallReplacer.cs new file mode 100644 index 000000000..fac68c61e --- /dev/null +++ b/ICSharpCode.Decompiler/IL/Transforms/ProxyCallReplacer.cs @@ -0,0 +1,126 @@ +using System.Diagnostics; +using ICSharpCode.Decompiler.TypeSystem; +using Mono.Cecil; + +namespace ICSharpCode.Decompiler.IL.Transforms +{ + class ProxyCallReplacer : ILVisitor, IILTransform + { + ILTransformContext context; + + public void Run(ILFunction function, ILTransformContext context) + { + this.context = context; + function.AcceptVisitor(this); + } + + protected override void Default(ILInstruction inst) + { + foreach (var child in inst.Children) { + child.AcceptVisitor(this); + } + } + + protected internal override void VisitCallVirt(CallVirt inst) + { + VisitCally(inst); + } + + protected internal override void VisitCall(Call inst) + { + VisitCally(inst); + } + + protected internal void VisitCally(CallInstruction inst) + { + if (inst.Method.DeclaringTypeDefinition == null) // TODO: investigate why + return; + foreach (IMethod method in inst.Method.DeclaringTypeDefinition.Methods) { + if (method.FullName.Equals(inst.Method.FullName)) { + MethodDefinition methodDef = context.TypeSystem.GetCecil(method) as MethodDefinition; + if (methodDef != null && methodDef.Body != null) { + if (method.IsCompilerGeneratedOrIsInCompilerGeneratedClass()) { + // partially copied from CSharpDecompiler + var specializingTypeSystem = this.context.TypeSystem.GetSpecializingTypeSystem(this.context.TypeSystem.Compilation.TypeResolveContext); + var ilReader = new ILReader(specializingTypeSystem); + System.Threading.CancellationToken cancellationToken = new System.Threading.CancellationToken(); + var function = ilReader.ReadIL(methodDef.Body, cancellationToken); + var context = new ILTransformContext(function, specializingTypeSystem, this.context.Settings) { + CancellationToken = cancellationToken + }; + foreach (var transform in CSharp.CSharpDecompiler.GetILTransforms()) { + if (transform.GetType() != typeof(ProxyCallReplacer)) { // don't call itself on itself + cancellationToken.ThrowIfCancellationRequested(); + transform.Run(function, context); + } + } + Call currentCall = new ProxyMethodVisitor().GetCalledMethod(function, context); + if (currentCall != null) { + Call newInst = (Call)currentCall.Clone(); + newInst.Arguments.Clear(); + + ILInstruction thisArg = inst.Arguments.ElementAtOrDefault(0).Clone(); + + // special handling for first argument (this) - the underlying issue may be somewhere else + // normally + // leave IL_0000(await(callvirt<> n__0(ldobj xxHandler(ldloca this), ldobj System.Net.Http.HttpRequestMessage(ldloca request), ldobj System.Threading.CancellationToken(ldloca cancellationToken)))) + // would be decompiled to + // return await((DelegatingHandler)this).SendAsync(request, cancellationToken); + // this changes it to + // return await base.SendAsync(request, cancellationToken); + if (thisArg.OpCode == OpCode.LdObj && thisArg.Children.Count > 0 && thisArg.Children[0].OpCode == OpCode.LdLoca) { + thisArg = new LdLoc(((LdLoca)thisArg.Children[0]).Variable); + } + + newInst.Arguments.Add(thisArg); + + // add everything except first argument + for (int i = 1; i < inst.Arguments.Count; i++) { + newInst.Arguments.Add(inst.Arguments.ElementAtOrDefault(i).Clone()); + } + inst.ReplaceWith(newInst); + } + } + } + } + } + } + + // Checks if the method is a proxy method by checking if it only contains a call instruction and if it has special compiler attributes. + private class ProxyMethodVisitor : ILVisitor + { + ILTransformContext context; + int invalidInstructions = 0; + Call currentCall; + + public Call GetCalledMethod(ILFunction function, ILTransformContext context) + { + this.context = context; + function.AcceptVisitor(this); + if (invalidInstructions == 0) { + return currentCall; + } + return null; + } + + protected override void Default(ILInstruction inst) + { + if (inst.OpCode != OpCode.ILFunction && + inst.OpCode != OpCode.BlockContainer && + inst.OpCode != OpCode.Block && + inst.OpCode != OpCode.Leave && + inst.OpCode != OpCode.Nop) { + invalidInstructions++; + } + foreach (var child in inst.Children) { + child.AcceptVisitor(this); + } + } + + protected internal override void VisitCall(Call inst) + { + currentCall = inst; + } + } + } +}