private void DetermineCaptureAndDeclarationScopes(Dictionary <MethodDefinitionHandle, LocalFunctionInfo> .ValueCollection localFunctions) { foreach (var info in localFunctions) { context.CancellationToken.ThrowIfCancellationRequested(); if (info.Definition == null) { context.Function.Warnings.Add($"Could not decode local function '{info.Method}'"); continue; } context.StepStartGroup($"Determine and move to declaration scope of " + info.Definition.Name, info.Definition); try { var localFunction = info.Definition; foreach (var useSite in info.UseSites) { DetermineCaptureAndDeclarationScope(info, useSite); if (context.Function.Method.IsConstructor && localFunction.DeclarationScope == null) { localFunction.DeclarationScope = BlockContainer.FindClosestContainer(useSite); } } if (localFunction.DeclarationScope == null) { localFunction.DeclarationScope = (BlockContainer)context.Function.Body; } ILFunction declaringFunction = GetDeclaringFunction(localFunction); if (declaringFunction != context.Function) { context.Step($"Move {localFunction.Name} from {context.Function.Name} to {declaringFunction.Name}", localFunction); context.Function.LocalFunctions.Remove(localFunction); declaringFunction.LocalFunctions.Add(localFunction); } if (TryValidateSkipCount(info, out int skipCount) && skipCount != localFunction.ReducedMethod.NumberOfCompilerGeneratedTypeParameters) { Debug.Assert(false); context.Function.Warnings.Add($"Could not decode local function '{info.Method}'"); if (declaringFunction != context.Function) { declaringFunction.LocalFunctions.Remove(localFunction); } } } finally { context.StepEndGroup(keepIfEmpty: true); } } }
public void Run(Block block, int pos, StatementTransformContext context) { this.context = context; context.StepStartGroup($"ExpressionTransforms ({block.Label}:{pos})", block.Instructions[pos]); block.Instructions[pos].AcceptVisitor(this); context.StepEndGroup(keepIfEmpty: true); }
void IILTransform.Run(ILFunction function, ILTransformContext context) { if (!context.Settings.AnonymousMethods) { return; } this.context = context; this.decompilationContext = new SimpleTypeResolveContext(function.Method); var orphanedVariableInits = new List <ILInstruction>(); var targetsToReplace = new List <IInstructionWithVariableOperand>(); var translatedDisplayClasses = new HashSet <ITypeDefinition>(); var cancellationToken = context.CancellationToken; foreach (var inst in function.Descendants) { cancellationToken.ThrowIfCancellationRequested(); if (inst is NewObj call) { context.StepStartGroup($"TransformDelegateConstruction {call.StartILOffset}", call); ILFunction f = TransformDelegateConstruction(call, out ILInstruction target); if (f != null && target is IInstructionWithVariableOperand instWithVar) { if (instWithVar.Variable.Kind == VariableKind.Local) { instWithVar.Variable.Kind = VariableKind.DisplayClassLocal; } targetsToReplace.Add(instWithVar); } context.StepEndGroup(); } if (inst.MatchStLoc(out ILVariable targetVariable, out ILInstruction value)) { var newObj = value as NewObj; // TODO : it is probably not a good idea to remove *all* display-classes // is there a way to minimize the false-positives? if (newObj != null && IsInSimpleDisplayClass(newObj.Method)) { targetVariable.CaptureScope = BlockContainer.FindClosestContainer(inst); targetsToReplace.Add((IInstructionWithVariableOperand)inst); translatedDisplayClasses.Add(newObj.Method.DeclaringTypeDefinition); } } } foreach (var target in targetsToReplace.OrderByDescending(t => ((ILInstruction)t).StartILOffset)) { context.Step($"TransformDisplayClassUsages {target.Variable}", (ILInstruction)target); function.AcceptVisitor(new TransformDisplayClassUsages(function, target, target.Variable.CaptureScope, orphanedVariableInits, translatedDisplayClasses)); } context.Step($"Remove orphanedVariableInits", function); foreach (var store in orphanedVariableInits) { if (store.Parent is Block containingBlock) { containingBlock.Instructions.Remove(store); } } }
/// <summary> /// The transform works like this: /// /// <para> /// local functions can either be used in method calls, i.e., call and callvirt instructions, /// or can be used as part of the "delegate construction" pattern, i.e., <c>newobj Delegate(<target-expression>, ldftn <method>)</c>. /// </para> /// As local functions can be declared practically anywhere, we have to take a look at all use-sites and infer the declaration location from that. Use-sites can be call, callvirt and ldftn instructions. /// After all use-sites are collected we construct the ILAst of the local function and add it to the parent function. /// Then all use-sites of the local-function are transformed to a call to the <c>LocalFunctionMethod</c> or a ldftn of the <c>LocalFunctionMethod</c>. /// In a next step we handle all nested local functions. /// After all local functions are transformed, we move all local functions that capture any variables to their respective declaration scope. /// </summary> public void Run(ILFunction function, ILTransformContext context) { if (!context.Settings.LocalFunctions) { return; } this.context = context; this.resolveContext = new SimpleTypeResolveContext(function.Method); var localFunctions = new Dictionary <MethodDefinitionHandle, LocalFunctionInfo>(); var cancellationToken = context.CancellationToken; // Find all local functions declared inside this method, including nested local functions or local functions declared in lambdas. FindUseSites(function, context, localFunctions); foreach (var(method, info) in localFunctions) { cancellationToken.ThrowIfCancellationRequested(); var firstUseSite = info.UseSites[0]; context.StepStartGroup($"Transform " + info.Definition.Name, info.Definition); try { var localFunction = info.Definition; if (!localFunction.Method.IsStatic) { var target = firstUseSite.Arguments[0]; context.Step($"Replace 'this' with {target}", localFunction); var thisVar = localFunction.Variables.SingleOrDefault(VariableKindExtensions.IsThis); localFunction.AcceptVisitor(new DelegateConstruction.ReplaceDelegateTargetVisitor(target, thisVar)); } foreach (var useSite in info.UseSites) { context.Step("Transform use site at " + useSite.StartILOffset, useSite); if (useSite.OpCode == OpCode.NewObj) { TransformToLocalFunctionReference(localFunction, useSite); } else { DetermineCaptureAndDeclarationScope(localFunction, useSite); TransformToLocalFunctionInvocation(localFunction.ReducedMethod, useSite); } } if (localFunction.DeclarationScope == null) { localFunction.DeclarationScope = (BlockContainer)function.Body; } else if (localFunction.DeclarationScope != function.Body && localFunction.DeclarationScope.Parent is ILFunction declaringFunction) { function.LocalFunctions.Remove(localFunction); declaringFunction.LocalFunctions.Add(localFunction); } } finally { context.StepEndGroup(); } } }
protected internal override void VisitILFunction(ILFunction function) { context.StepStartGroup("Visit " + function.Name); try { this.currentFunctions.Push(function); base.VisitILFunction(function); } finally { this.currentFunctions.Pop(); context.StepEndGroup(keepIfEmpty: true); } }
void IILTransform.Run(ILFunction function, ILTransformContext context) { if (!context.Settings.AnonymousMethods) { return; } var prevContext = this.context; var prevDecompilationContext = this.decompilationContext; try { activeMethods.Push((MethodDefinitionHandle)function.Method.MetadataToken); this.context = context; this.decompilationContext = new SimpleTypeResolveContext(function.Method); var cancellationToken = context.CancellationToken; foreach (var inst in function.Descendants) { cancellationToken.ThrowIfCancellationRequested(); if (!MatchDelegateConstruction(inst, out var targetMethod, out var target, out var delegateType, allowTransformed: false)) { continue; } context.StepStartGroup($"TransformDelegateConstruction {inst.StartILOffset}", inst); ILFunction f = TransformDelegateConstruction(inst, targetMethod, target, delegateType); if (f != null && target is IInstructionWithVariableOperand instWithVar) { var v = instWithVar.Variable; if (v.Kind == VariableKind.Local) { v.Kind = VariableKind.DisplayClassLocal; } if (v.IsSingleDefinition && v.StoreInstructions.SingleOrDefault() is StLoc store && store.Value is NewObj) { v.CaptureScope = BlockContainer.FindClosestContainer(store); } } context.StepEndGroup(); } } finally { this.context = prevContext; this.decompilationContext = prevDecompilationContext; activeMethods.Pop(); } }
/// <summary> /// catch E_189 : 0200007C System.Exception when (BlockContainer { /// Block IL_0079 (incoming: 1) { /// stloc S_30(ldloc E_189) /// br IL_0085 /// } /// /// Block IL_0085 (incoming: 1) { /// stloc I_1(ldloc S_30) /// where S_30 and I_1 are single definition /// => /// copy-propagate E_189 to replace all uses of S_30 and I_1 /// </summary> static void PropagateExceptionVariable(ILTransformContext context, TryCatchHandler handler) { var exceptionVariable = handler.Variable; if (!exceptionVariable.IsSingleDefinition) { return; } context.StepStartGroup(nameof(PropagateExceptionVariable)); int i = 0; while (i < exceptionVariable.LoadInstructions.Count) { var load = exceptionVariable.LoadInstructions[i]; if (!load.IsDescendantOf(handler)) { i++; continue; } // We are only interested in store "statements" copying the exception variable // without modifying it. var statement = LocalFunctionDecompiler.GetStatement(load); if (!(statement is StLoc stloc)) { i++; continue; } // simple copy case: // stloc b(ldloc a) if (stloc.Value == load) { PropagateExceptionInstance(stloc); } // if the type of the cast-class instruction matches the exceptionType, // this cast can be removed without losing any side-effects. // Note: this would also hold true iff exceptionType were an exception derived // from cc.Type, however, we are more restrictive to match the pattern exactly. // stloc b(castclass exceptionType(ldloc a)) else if (stloc.Value is CastClass cc && cc.Type.Equals(exceptionVariable.Type) && cc.Argument == load) { stloc.Value = load; PropagateExceptionInstance(stloc); }
void IILTransform.Run(ILFunction function, ILTransformContext context) { if (!context.Settings.AnonymousMethods) { return; } this.context = context; this.decompilationContext = new SimpleTypeResolveContext(function.Method); var cancellationToken = context.CancellationToken; foreach (var inst in function.Descendants) { cancellationToken.ThrowIfCancellationRequested(); if (inst is NewObj call) { context.StepStartGroup($"TransformDelegateConstruction {call.StartILOffset}", call); ILFunction f = TransformDelegateConstruction(call, out ILInstruction target); if (f != null && target is IInstructionWithVariableOperand instWithVar) { if (instWithVar.Variable.Kind == VariableKind.Local) { instWithVar.Variable.Kind = VariableKind.DisplayClassLocal; } var displayClassTypeDef = instWithVar.Variable.Type.GetDefinition(); if (instWithVar.Variable.IsSingleDefinition && instWithVar.Variable.StoreInstructions.SingleOrDefault() is StLoc store) { if (store.Value is NewObj newObj) { instWithVar.Variable.CaptureScope = BlockContainer.FindClosestContainer(store); } } } context.StepEndGroup(); } } }
/// <summary> /// The transform works like this: /// /// <para> /// local functions can either be used in method calls, i.e., call and callvirt instructions, /// or can be used as part of the "delegate construction" pattern, i.e., /// <c>newobj Delegate(<target-expression>, ldftn <method>)</c>. /// </para> /// As local functions can be declared practically anywhere, we have to take a look at /// all use-sites and infer the declaration location from that. Use-sites can be call, /// callvirt and ldftn instructions. /// After all use-sites are collected we construct the ILAst of the local function /// and add it to the parent function. /// Then all use-sites of the local-function are transformed to a call to the /// <c>LocalFunctionMethod</c> or a ldftn of the <c>LocalFunctionMethod</c>. /// In a next step we handle all nested local functions. /// After all local functions are transformed, we move all local functions that capture /// any variables to their respective declaration scope. /// </summary> public void Run(ILFunction function, ILTransformContext context) { if (!context.Settings.LocalFunctions) { return; } // Disable the transform if we are decompiling a display-class or local function method: // This happens if a local function or display class is selected in the ILSpy tree view. if (IsLocalFunctionMethod(function.Method, context) || IsLocalFunctionDisplayClass(function.Method.ParentModule.PEFile, (TypeDefinitionHandle)function.Method.DeclaringTypeDefinition.MetadataToken, context)) { return; } this.context = context; this.resolveContext = new SimpleTypeResolveContext(function.Method); var localFunctions = new Dictionary <MethodDefinitionHandle, LocalFunctionInfo>(); var cancellationToken = context.CancellationToken; // Find all local functions declared inside this method, including nested local functions or local functions declared in lambdas. FindUseSites(function, context, localFunctions); foreach (var(_, info) in localFunctions) { cancellationToken.ThrowIfCancellationRequested(); if (info.Definition == null) { function.Warnings.Add($"Could not decode local function '{info.Method}'"); continue; } context.StepStartGroup($"Transform " + info.Definition.Name, info.Definition); try { var localFunction = info.Definition; if (!localFunction.Method.IsStatic) { var thisVar = localFunction.Variables.SingleOrDefault(VariableKindExtensions.IsThis); var target = info.UseSites.Where(us => us.Arguments[0].MatchLdLoc(out _)).FirstOrDefault()?.Arguments[0]; if (target == null) { target = info.UseSites[0].Arguments[0]; if (target.MatchLdFld(out var target1, out var field) && thisVar.Type.Equals(field.Type) && field.Type.Kind == TypeKind.Class && TransformDisplayClassUsage.IsPotentialClosure(context, field.Type.GetDefinition())) { var variable = function.Descendants.OfType <ILFunction>().SelectMany(f => f.Variables).Where(v => !v.IsThis() && TransformDisplayClassUsage.IsClosure(context, v, out var varType, out _) && varType.Equals(field.Type)).OnlyOrDefault(); if (variable != null) { target = new LdLoc(variable); HandleArgument(localFunction, 1, 0, target); } } } context.Step($"Replace 'this' with {target}", localFunction); localFunction.AcceptVisitor(new DelegateConstruction.ReplaceDelegateTargetVisitor(target, thisVar)); } foreach (var useSite in info.UseSites) { DetermineCaptureAndDeclarationScope(localFunction, useSite); if (function.Method.IsConstructor && localFunction.DeclarationScope == null) { localFunction.DeclarationScope = BlockContainer.FindClosestContainer(useSite); } } if (localFunction.DeclarationScope == null) { localFunction.DeclarationScope = (BlockContainer)function.Body; } ILFunction declaringFunction = GetDeclaringFunction(localFunction); if (declaringFunction != function) { function.LocalFunctions.Remove(localFunction); declaringFunction.LocalFunctions.Add(localFunction); } if (TryValidateSkipCount(info, out int skipCount) && skipCount != localFunction.ReducedMethod.NumberOfCompilerGeneratedTypeParameters) { Debug.Assert(false); function.Warnings.Add($"Could not decode local function '{info.Method}'"); if (declaringFunction != function) { declaringFunction.LocalFunctions.Remove(localFunction); } continue; } foreach (var useSite in info.UseSites) { context.Step($"Transform use site at IL_{useSite.StartILOffset:x4}", useSite); if (useSite.OpCode == OpCode.NewObj) { TransformToLocalFunctionReference(localFunction, useSite); } else { TransformToLocalFunctionInvocation(localFunction.ReducedMethod, useSite); } } } finally { context.StepEndGroup(); } } }
ILFunction TransformDelegateConstruction(NewObj value, out ILInstruction target) { target = null; if (!IsDelegateConstruction(value)) { return(null); } var targetMethod = ((IInstructionWithMethodOperand)value.Arguments[1]).Method; if (!IsAnonymousMethod(decompilationContext.CurrentTypeDefinition, targetMethod)) { return(null); } if (LocalFunctionDecompiler.IsLocalFunctionMethod(targetMethod.ParentModule.PEFile, (MethodDefinitionHandle)targetMethod.MetadataToken)) { return(null); } target = value.Arguments[0]; if (targetMethod.MetadataToken.IsNil) { return(null); } var methodDefinition = context.PEFile.Metadata.GetMethodDefinition((MethodDefinitionHandle)targetMethod.MetadataToken); if (!methodDefinition.HasBody()) { return(null); } var genericContext = GenericContextFromTypeArguments(targetMethod.Substitution); if (genericContext == null) { return(null); } var ilReader = context.CreateILReader(); var body = context.PEFile.Reader.GetMethodBody(methodDefinition.RelativeVirtualAddress); var function = ilReader.ReadIL((MethodDefinitionHandle)targetMethod.MetadataToken, body, genericContext.Value, context.CancellationToken); function.DelegateType = value.Method.DeclaringType; function.CheckInvariant(ILPhase.Normal); // Embed the lambda into the parent function's ILAst, so that "Show steps" can show // how the lambda body is being transformed. value.ReplaceWith(function); var contextPrefix = targetMethod.Name; foreach (ILVariable v in function.Variables.Where(v => v.Kind != VariableKind.Parameter)) { v.Name = contextPrefix + v.Name; } var nestedContext = new ILTransformContext(context, function); function.RunTransforms(CSharpDecompiler.GetILTransforms().TakeWhile(t => !(t is DelegateConstruction)).Concat(GetTransforms()), nestedContext); nestedContext.Step("DelegateConstruction (ReplaceDelegateTargetVisitor)", function); function.AcceptVisitor(new ReplaceDelegateTargetVisitor(target, function.Variables.SingleOrDefault(v => v.Index == -1 && v.Kind == VariableKind.Parameter))); // handle nested lambdas nestedContext.StepStartGroup("DelegateConstruction (nested lambdas)", function); ((IILTransform) new DelegateConstruction()).Run(function, nestedContext); nestedContext.StepEndGroup(); function.AddILRange(target); function.AddILRange(value); function.AddILRange(value.Arguments[1]); return(function); }
ILFunction TransformDelegateConstruction( ILInstruction value, IMethod targetMethod, ILInstruction target, IType delegateType) { if (!IsAnonymousMethod(decompilationContext.CurrentTypeDefinition, targetMethod)) { return(null); } if (targetMethod.MetadataToken.IsNil) { return(null); } if (LocalFunctionDecompiler.IsLocalFunctionMethod(targetMethod, context)) { return(null); } if (!ValidateDelegateTarget(target)) { return(null); } var handle = (MethodDefinitionHandle)targetMethod.MetadataToken; if (activeMethods.Contains(handle)) { this.context.Function.Warnings.Add(" Found self-referencing delegate construction. Abort transformation to avoid stack overflow."); return(null); } var methodDefinition = context.PEFile.Metadata.GetMethodDefinition((MethodDefinitionHandle)targetMethod.MetadataToken); if (!methodDefinition.HasBody()) { return(null); } var genericContext = GenericContextFromTypeArguments(targetMethod.Substitution); if (genericContext == null) { return(null); } var ilReader = context.CreateILReader(); var body = context.PEFile.Reader.GetMethodBody(methodDefinition.RelativeVirtualAddress); var function = ilReader.ReadIL((MethodDefinitionHandle)targetMethod.MetadataToken, body, genericContext.Value, ILFunctionKind.Delegate, context.CancellationToken); function.DelegateType = delegateType; // Embed the lambda into the parent function's ILAst, so that "Show steps" can show // how the lambda body is being transformed. value.ReplaceWith(function); function.CheckInvariant(ILPhase.Normal); var contextPrefix = targetMethod.Name; foreach (ILVariable v in function.Variables.Where(v => v.Kind != VariableKind.Parameter)) { v.Name = contextPrefix + v.Name; } var nestedContext = new ILTransformContext(context, function); function.RunTransforms(CSharpDecompiler.GetILTransforms().TakeWhile(t => !(t is DelegateConstruction)).Concat(GetTransforms()), nestedContext); nestedContext.Step("DelegateConstruction (ReplaceDelegateTargetVisitor)", function); function.AcceptVisitor(new ReplaceDelegateTargetVisitor(target, function.Variables.SingleOrDefault(VariableKindExtensions.IsThis))); // handle nested lambdas nestedContext.StepStartGroup("DelegateConstruction (nested lambdas)", function); ((IILTransform)this).Run(function, nestedContext); nestedContext.StepEndGroup(); function.AddILRange(target); function.AddILRange(value); if (value is Call call) { function.AddILRange(call.Arguments[1]); } return(function); }