void SimplifyBranchChains(ILFunction function, ILTransformContext context) { List <(BlockContainer, Block)> blocksToAdd = new List <(BlockContainer, Block)>(); HashSet <Block> visitedBlocks = new HashSet <Block>(); foreach (var branch in function.Descendants.OfType <Branch>()) { // Resolve chained branches to the final target: var targetBlock = branch.TargetBlock; visitedBlocks.Clear(); while (targetBlock.Instructions.Count == 1 && targetBlock.Instructions[0].OpCode == OpCode.Branch) { if (!visitedBlocks.Add(targetBlock)) { // prevent infinite loop when branch chain is cyclic break; } context.Step("Simplify branch to branch", branch); var nextBranch = (Branch)targetBlock.Instructions[0]; branch.TargetBlock = nextBranch.TargetBlock; branch.AddILRange(nextBranch.ILRange); if (targetBlock.IncomingEdgeCount == 0) { targetBlock.Instructions.Clear(); // mark the block for deletion } targetBlock = branch.TargetBlock; } if (IsBranchToReturnBlock(branch)) { if (aggressivelyDuplicateReturnBlocks) { // Replace branches to 'return blocks' with the return instruction context.Step("Replace branch to return with return", branch); branch.ReplaceWith(targetBlock.Instructions[0].Clone()); } else if (branch.TargetContainer != branch.Ancestors.OfType <BlockContainer>().First()) { // We don't want to always inline the return directly, because this // might force us to place the return within a loop, when it's better // placed outside. // But we do want to move the return block into the correct try-finally scope, // so that loop detection at least has the option to put it inside // the loop body. context.Step("Copy return block into try block", branch); Block blockCopy = (Block)branch.TargetBlock.Clone(); BlockContainer localContainer = branch.Ancestors.OfType <BlockContainer>().First(); blocksToAdd.Add((localContainer, blockCopy)); branch.TargetBlock = blockCopy; } } else if (targetBlock.Instructions.Count == 1 && targetBlock.Instructions[0] is Leave leave && leave.Value.MatchNop()) { context.Step("Replace branch to leave with leave", branch); // Replace branches to 'leave' instruction with the leave instruction branch.ReplaceWith(leave.Clone()); } if (targetBlock.IncomingEdgeCount == 0) { targetBlock.Instructions.Clear(); // mark the block for deletion } } foreach (var(container, block) in blocksToAdd) { container.Blocks.Add(block); } }
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; } // Detect all handlers that contain an await expression AnalyzeHandlers(tryCatch.Handlers, out var catchHandlerIdentifier, out var transformableCatchBlocks); var cfg = new ControlFlowGraph(container, context.CancellationToken); if (transformableCatchBlocks.Count > 0) { changedContainers.Add(container); } foreach (var result in transformableCatchBlocks) { var node = cfg.GetNode(result.RealCatchBlockEntryPoint); context.Step("Inline catch block with await", result.Handler); // Remove the IfInstruction from the jump table and eliminate all branches to the block. var jumpTableBlock = (Block)result.JumpTableEntry.Parent; jumpTableBlock.Instructions.RemoveAt(result.JumpTableEntry.ChildIndex); foreach (var branch in tryCatch.Descendants.OfType <Branch>()) { if (branch.TargetBlock == jumpTableBlock) { if (result.NextBlockOrExitContainer is BlockContainer exitContainer) { branch.ReplaceWith(new Leave(exitContainer)); } else { branch.ReplaceWith(new Branch((Block)result.NextBlockOrExitContainer)); } } } // Add the real catch block entry-point to the block container var catchBlockHead = ((BlockContainer)result.Handler.Body).Blocks.Last(); result.RealCatchBlockEntryPoint.Remove(); ((BlockContainer)result.Handler.Body).Blocks.Insert(0, result.RealCatchBlockEntryPoint); // Remove the generated catch block catchBlockHead.Remove(); // Inline all blocks that are dominated by the entrypoint of the real catch block foreach (var n in cfg.cfg) { if (((Block)n.UserData).Parent == result.Handler.Body) { continue; } if (node.Dominates(n)) { MoveBlock((Block)n.UserData, (BlockContainer)result.Handler.Body); } } // Remove all assignments to the common object variable that stores the exception object. if (result.ObjectVariableStore != null) { foreach (var load in result.ObjectVariableStore.Variable.LoadInstructions.ToArray()) { if (load.Parent is CastClass cc && cc.Type == result.Handler.Variable.Type) { cc.ReplaceWith(new LdLoc(result.Handler.Variable)); } else { load.ReplaceWith(new LdLoc(result.Handler.Variable)); } } } }
public void Run(ILFunction function, ILTransformContext context) { var insts = LivenessHelper.GetInstructions(function); if (insts.Count == 1) { // empty body, skip method return; } // skip marked methods //if (function.CecilMethod.CustomAttributes.Any(x => x.AttributeType.Name == "ManualRefCounting")) if (function.Method.GetAttributes().Any(x => x.AttributeType.Name == "ManualRefCounting")) { return; } IEnumerable <ILVariable> vars = function.Variables; var usedVars = new List <ILVariable>(); foreach (var v in vars) { //var aliasSource = TypeInference.GetAliasSource(v); //// don't ref count parameters like this. Todo check for out and ref parameters (does O work already?) //if (aliasSource?.Kind == VariableKind.Parameter && aliasSource.StackType == StackType.O) // continue; // no typeof intermediate variables if (v.StoreInstructions.Count == 1 && (v.StoreInstructions.First() as StLoc)?.Value is LdTypeToken) { continue; } // skip variables that only contain NULL, etc if (TypeInference.GetVariableType(v) == null) { continue; } // only reference types or valuetypes that require ref counting if (TypeInference.GetVariableType(v).Kind == TypeKind.Pointer) { continue; var pType = TypeInference.GetVariableType(v) as ICSharpCode.Decompiler.TypeSystem.PointerType; var elemType = pType.ElementType; //if (!elemType.IsReferenceType.Value) // continue; if (!elemType.IsReferenceType.Value && !elemType.GetMethods().Any(x => x.Name.EndsWith("_AddRef"))) { continue; } } else { if (!TypeInference.GetVariableType(v).IsReferenceType.Value&& !TypeInference.GetVariableType(v).GetMethods().Any(x => x.Name.EndsWith("_AddRef"))) { continue; } } // references are not supported in C Debug.Assert(TypeInference.GetVariableType(v).Kind != TypeKind.ByReference); //todo: are the following two needed? //if (TypeInference.GetVariableType(v).Kind == TypeKind.Pointer) // continue; //if (TypeInference.GetVariableType(v).Kind == TypeKind.ByReference) // continue; usedVars.Add(v); } vars = usedVars.ToArray(); var liveDebug = DebugLiveness.DebugLocalLiveness(function, vars); AddRefCountingLocalLiveness(insts, vars); ILAstDebugPrinter.DebugIlAst(function, "after_ref", liveDebug); // Add casts to make compiler happy // Todo: I would prefer to access base field insead. No easy representation at this stage though. // Todo: move to own optimization foreach (var c in function.Body.Descendants.OfType <CallInstruction>()) { for (int idx = 0; idx < c.Method.Parameters.Count; idx++) { var p = c.Method.Parameters[idx]; var value = c.Arguments[idx]; var argType = TypeInference.GetInstType(value); var pType = p.Type; if (argType != null && argType != pType) { value.ReplaceWith(new CastClass(value.Clone(), pType)); } } } }
public Dictionary <long, EventRegistration[]> DecompileEventMappings(string fullTypeName, CancellationToken cancellationToken) { var result = new Dictionary <long, EventRegistration[]>(); TypeDefinition type = this.assembly.MainModule.GetType(fullTypeName); if (type == null) { return(result); } MethodDefinition method = null; foreach (var m in type.Methods) { if (m.Name == "System.Windows.Markup.IComponentConnector.Connect") { method = m; break; } } if (method == null) { return(result); } // decompile method and optimize the switch var typeSystem = new DecompilerTypeSystem(method.Module); var ilReader = new ILReader(typeSystem); var function = ilReader.ReadIL(method.Body, cancellationToken); var context = new ILTransformContext(function, typeSystem) { CancellationToken = cancellationToken }; function.RunTransforms(CSharpDecompiler.GetILTransforms(), context); var block = function.Body.Children.OfType <Block>().First(); var ilSwitch = block.Children.OfType <SwitchInstruction>().FirstOrDefault(); if (ilSwitch != null) { foreach (var section in ilSwitch.Sections) { var events = FindEvents(section.Body); foreach (long id in section.Labels.Values) { result.Add(id, events); } } } else { foreach (var ifInst in function.Descendants.OfType <IfInstruction>()) { var comp = ifInst.Condition as Comp; if (comp.Kind != ComparisonKind.Inequality && comp.Kind != ComparisonKind.Equality) { continue; } int id; if (!comp.Right.MatchLdcI4(out id)) { continue; } var events = FindEvents(comp.Kind == ComparisonKind.Inequality ? ifInst.FalseInst : ifInst.TrueInst); result.Add(id, events); } } return(result); }
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; } // } catch exceptionVariable : 02000078 System.Object when (ldc.i4 1) BlockContainer { // Block IL_004a (incoming: 1) { // stloc objectVariable(ldloc exceptionVariable) // br finallyBlock // } // // } // } // // Block finallyBlock (incoming: 2) { // if (comp.o(ldloc b == ldnull)) br afterFinallyBlock // br finallyBlockContinuation // } // // Block finallyBlockContinuation (incoming: 1) { // await(addressof System.Threading.Tasks.ValueTask(callvirt DisposeAsync(ldloc b))) // br afterFinallyBlock // } // // Block afterFinallyBlock (incoming: 2) { // stloc V_1(ldloc objectVariable) // if (comp.o(ldloc V_1 == ldnull)) br IL_00ea // br IL_00cf // } // await in finally uses a single catch block with catch-type object if (tryCatch.Handlers.Count != 1) { continue; } var handler = tryCatch.Handlers[0]; var exceptionVariable = handler.Variable; if (handler.Body is not BlockContainer catchBlockContainer) { continue; } if (!exceptionVariable.Type.IsKnownType(KnownTypeCode.Object)) { continue; } // Matches the await finally pattern: // [stloc V_3(ldloc E_100) - copy exception variable to a temporary] // stloc V_6(ldloc V_3) - store exception in 'global' object variable // br IL_0075 - jump out of catch block to the head of the finallyBlock var catchBlockEntry = catchBlockContainer.EntryPoint; ILVariable objectVariable; switch (catchBlockEntry.Instructions.Count) { case 2: if (!catchBlockEntry.Instructions[0].MatchStLoc(out objectVariable, out var value))
public void Run(ILFunction function, ILTransformContext context) { cancellationToken = context.CancellationToken; currentExit = NoExit; function.AcceptVisitor(this); }
public static void Run(ILFunction function, ILTransformContext context) { if (!context.Settings.AwaitInCatchFinally) { return; } HashSet <BlockContainer> changedContainers = new HashSet <BlockContainer>(); HashSet <Block> removedBlocks = new HashSet <Block>(); // 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; } // Detect all handlers that contain an await expression AnalyzeHandlers(tryCatch.Handlers, out var catchHandlerIdentifier, out var transformableCatchBlocks); var cfg = new ControlFlowGraph(container, context.CancellationToken); if (transformableCatchBlocks.Count > 0) { changedContainers.Add(container); } SwitchInstruction switchInstructionOpt = null; foreach (var result in transformableCatchBlocks) { removedBlocks.Clear(); var node = cfg.GetNode(result.RealCatchBlockEntryPoint); context.StepStartGroup($"Inline catch block with await (at {result.Handler.Variable.Name})", result.Handler); // Remove the IfInstruction from the jump table and eliminate all branches to the block. switch (result.JumpTableEntry) { case IfInstruction jumpTableEntry: var jumpTableBlock = (Block)jumpTableEntry.Parent; context.Step("Remove jump-table entry", result.JumpTableEntry); jumpTableBlock.Instructions.RemoveAt(result.JumpTableEntry.ChildIndex); foreach (var branch in tryCatch.Descendants.OfType <Branch>()) { if (branch.TargetBlock == jumpTableBlock) { if (result.NextBlockOrExitContainer is BlockContainer exitContainer) { context.Step("branch jumpTableBlock => leave exitContainer", branch); branch.ReplaceWith(new Leave(exitContainer)); } else { context.Step("branch jumpTableBlock => branch nextBlock", branch); branch.ReplaceWith(new Branch((Block)result.NextBlockOrExitContainer)); } } } break; case SwitchSection jumpTableEntry: Debug.Assert(switchInstructionOpt == null || jumpTableEntry.Parent == switchInstructionOpt); switchInstructionOpt = (SwitchInstruction)jumpTableEntry.Parent; break; } // Add the real catch block entry-point to the block container var catchBlockHead = ((BlockContainer)result.Handler.Body).Blocks.Last(); result.RealCatchBlockEntryPoint.Remove(); ((BlockContainer)result.Handler.Body).Blocks.Insert(0, result.RealCatchBlockEntryPoint); // Remove the generated catch block catchBlockHead.Remove(); TransformAsyncThrowToThrow(context, removedBlocks, result.RealCatchBlockEntryPoint); // Inline all blocks that are dominated by the entrypoint of the real catch block foreach (var n in cfg.cfg) { Block block = (Block)n.UserData; if (node.Dominates(n)) { TransformAsyncThrowToThrow(context, removedBlocks, block); if (block.Parent == result.Handler.Body) { continue; } if (!removedBlocks.Contains(block)) { context.Step("Move block", result.Handler.Body); MoveBlock(block, (BlockContainer)result.Handler.Body); } } } // Remove unreachable pattern blocks // TODO : sanity check if (result.NextBlockOrExitContainer is Block nextBlock && nextBlock.IncomingEdgeCount == 0) { List <Block> dependentBlocks = new List <Block>(); Block current = nextBlock; do { foreach (var branch in current.Descendants.OfType <Branch>()) { dependentBlocks.Add(branch.TargetBlock); } current.Remove(); dependentBlocks.Remove(current); current = dependentBlocks.FirstOrDefault(b => b.IncomingEdgeCount == 0); } while (current != null); } // Remove all assignments to the common object variable that stores the exception object. if (result.ObjectVariable != result.Handler.Variable) { foreach (var load in result.ObjectVariable.LoadInstructions.ToArray()) { if (!load.IsDescendantOf(result.Handler)) { continue; } if (load.Parent is CastClass cc && cc.Type.Equals(result.Handler.Variable.Type)) { cc.ReplaceWith(new LdLoc(result.Handler.Variable).WithILRange(cc).WithILRange(load)); } else { load.ReplaceWith(new LdLoc(result.Handler.Variable).WithILRange(load)); } } } context.StepEndGroup(keepIfEmpty: true); }
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); } }
public void Run(ILFunction function, ILTransformContext context) { this.context = context; Visit((BlockContainer)function.Body, null); }