internal static bool IsCatchWhenBlock(Block block) { var container = BlockContainer.FindClosestContainer(block); return(container?.Parent is TryCatchHandler handler && handler.Filter == container); }
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); } } }
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); } } }
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(); } }
bool ShouldSwapIfTargets(ILInstruction inst1, ILInstruction inst2) { Block block1 = null, block2 = null; if (inst1.MatchBranch(out block1) && inst2.MatchBranch(out block2)) { // prefer arranging stuff in IL order return(block1.ILRange.Start > block2.ILRange.Start); } BlockContainer container1, container2; if (inst1.MatchLeave(out container1) && container1.Parent is TryInstruction) { // 'leave tryBlock' is considered to have a later target than // any branch within the container, and also a later target // than a return instruction. // This is necessary to avoid "goto" statements in the // ExceptionHandling.ConditionalReturnInThrow test. if (!inst2.MatchLeave(out container2)) { container2 = block2?.Parent as BlockContainer; } return(container2 == null || container2.IsDescendantOf(container1)); } if (inst1.MatchBranch(out block1) && inst2.MatchLeave(out container2) && block1.IncomingEdgeCount > 1) { // if (..) goto x; leave c; // Unless x can be inlined, it's better to swap the order if the 'leave' // has a chance to turn into a 'break;' or 'return;' if (container2.Parent is ILFunction) { return(true); // return } if (container2.EntryPoint.IncomingEdgeCount > 1) { // break return(BlockContainer.FindClosestContainer(inst2) == container2); } } return(false); }
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(); } } }
/// <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 IL_{useSite.StartILOffset:x4}", useSite); if (useSite.OpCode == OpCode.NewObj) { TransformToLocalFunctionReference(localFunction, useSite); } else { DetermineCaptureAndDeclarationScope(localFunction, useSite); TransformToLocalFunctionInvocation(localFunction.ReducedMethod, useSite); } if (function.Method.IsConstructor && localFunction.DeclarationScope == null) { localFunction.DeclarationScope = BlockContainer.FindClosestContainer(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(); } } }
private BlockContainer FindBlockContainer(Block block) { return(BlockContainer.FindClosestContainer(block) ?? throw new NotSupportedException()); }
public static void Run(ILFunction function, ILTransformContext context) { if (!context.Settings.AwaitInCatchFinally) { return; } HashSet <BlockContainer> changedContainers = new HashSet <BlockContainer>(); // analyze all try-catch statements in the function foreach (var tryCatch in function.Descendants.OfType <TryCatch>().ToArray()) { if (!(tryCatch.Parent?.Parent is BlockContainer container)) { continue; } // await in finally uses a single catch block with catch-type object if (tryCatch.Handlers.Count != 1 || !(tryCatch.Handlers[0].Body is BlockContainer catchBlockContainer) || !tryCatch.Handlers[0].Variable.Type.IsKnownType(KnownTypeCode.Object)) { continue; } // and consists of an assignment to a temporary that is used outside the catch block // and a jump to the finally block var block = catchBlockContainer.EntryPoint; if (block.Instructions.Count < 2 || !block.Instructions[0].MatchStLoc(out var globalCopyVar, out var value) || !value.MatchLdLoc(tryCatch.Handlers[0].Variable)) { continue; } if (block.Instructions.Count == 3) { if (!block.Instructions[1].MatchStLoc(out var globalCopyVarTemp, out value) || !value.MatchLdLoc(globalCopyVar)) { continue; } globalCopyVar = globalCopyVarTemp; } if (!block.Instructions[block.Instructions.Count - 1].MatchBranch(out var entryPointOfFinally)) { continue; } // globalCopyVar should only be used once, at the end of the finally-block if (globalCopyVar.LoadCount != 1 || globalCopyVar.StoreCount > 2) { continue; } var tempStore = globalCopyVar.LoadInstructions[0].Parent as StLoc; if (tempStore == null || !MatchExceptionCaptureBlock(tempStore, out var exitOfFinally, out var afterFinally, out var blocksToRemove)) { continue; } if (!MatchAfterFinallyBlock(ref afterFinally, blocksToRemove, out bool removeFirstInstructionInAfterFinally)) { continue; } var cfg = new ControlFlowGraph(container, context.CancellationToken); var exitOfFinallyNode = cfg.GetNode(exitOfFinally); var entryPointOfFinallyNode = cfg.GetNode(entryPointOfFinally); var additionalBlocksInFinally = new HashSet <Block>(); var invalidExits = new List <ControlFlowNode>(); TraverseDominatorTree(entryPointOfFinallyNode); void TraverseDominatorTree(ControlFlowNode node) { if (entryPointOfFinallyNode != node) { if (entryPointOfFinallyNode.Dominates(node)) { additionalBlocksInFinally.Add((Block)node.UserData); } else { invalidExits.Add(node); } } if (node == exitOfFinallyNode) { return; } foreach (var child in node.DominatorTreeChildren) { TraverseDominatorTree(child); } } if (invalidExits.Any()) { continue; } context.Step("Inline finally block with await", tryCatch.Handlers[0]); foreach (var blockToRemove in blocksToRemove) { blockToRemove.Remove(); } var finallyContainer = new BlockContainer(); entryPointOfFinally.Remove(); if (removeFirstInstructionInAfterFinally) { afterFinally.Instructions.RemoveAt(0); } changedContainers.Add(container); var outer = BlockContainer.FindClosestContainer(container.Parent); if (outer != null) { changedContainers.Add(outer); } finallyContainer.Blocks.Add(entryPointOfFinally); finallyContainer.AddILRange(entryPointOfFinally); exitOfFinally.Instructions.RemoveRange(tempStore.ChildIndex, 3); exitOfFinally.Instructions.Add(new Leave(finallyContainer)); foreach (var branchToFinally in container.Descendants.OfType <Branch>()) { if (branchToFinally.TargetBlock == entryPointOfFinally) { branchToFinally.ReplaceWith(new Branch(afterFinally)); } } foreach (var newBlock in additionalBlocksInFinally) { newBlock.Remove(); finallyContainer.Blocks.Add(newBlock); finallyContainer.AddILRange(newBlock); } tryCatch.ReplaceWith(new TryFinally(tryCatch.TryBlock, finallyContainer).WithILRange(tryCatch.TryBlock)); } // clean up all modified containers foreach (var container in changedContainers) { container.SortBlocks(deleteUnreachableBlocks: true); } }