diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Correctness/StackTypes.il b/ICSharpCode.Decompiler.Tests/TestCases/Correctness/StackTypes.il index 9c178a2ce..47ebea33a 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Correctness/StackTypes.il +++ b/ICSharpCode.Decompiler.Tests/TestCases/Correctness/StackTypes.il @@ -79,7 +79,34 @@ pointless: box native int call void [mscorlib]System.Console::WriteLine(string, object) - /* + ldstr "Int32OrNativeReordered(0x7fffffff, false) = {0}" + ldc.i4 0x7fffffff + ldc.i4 0 + call native int Program::Int32OrNativeReordered(int32, bool) + box native int + call void [mscorlib]System.Console::WriteLine(string, object) + + ldstr "Int32OrNativeReordered(0x7fffffff, true) = {0}" + ldc.i4 0x7fffffff + ldc.i4 1 + call native int Program::Int32OrNativeReordered(int32, bool) + box native int + call void [mscorlib]System.Console::WriteLine(string, object) + + ldstr "Int32OrNativeReordered(-1, false) = {0}" + ldc.i4.m1 + ldc.i4 0 + call native int Program::Int32OrNativeReordered(int32, bool) + box native int + call void [mscorlib]System.Console::WriteLine(string, object) + + ldstr "Int32OrNativeReordered(-1, true) = {0}" + ldc.i4.m1 + ldc.i4 1 + call native int Program::Int32OrNativeReordered(int32, bool) + box native int + call void [mscorlib]System.Console::WriteLine(string, object) + ldstr "Int32OrNativeLoopStyle(0x7fffffff):" call void [mscorlib]System.Console::WriteLine(string) ldc.i4 0x7fffffff @@ -101,7 +128,6 @@ pointless: call native int Program::Int32OrNativeDeadCode(int32) box native int call void [mscorlib]System.Console::WriteLine(string, object) - */ ldc.i4 0x7fffffff call void Program::RunInt32OrNativeMultiUse(int32) @@ -127,7 +153,6 @@ pointless: ret } - /* .method public static native int Int32OrNativeReordered(int32 val, bool use_native) { // The spec is ambiguous whether the addition will be in 32-bits or native size. @@ -187,7 +212,6 @@ pointless: conv.u br after_if } - */ .method public static void RunInt32OrNativeMultiUse(int32 val) { diff --git a/ICSharpCode.Decompiler/IL/BlockBuilder.cs b/ICSharpCode.Decompiler/IL/BlockBuilder.cs index 75a7613bf..be06a5034 100644 --- a/ICSharpCode.Decompiler/IL/BlockBuilder.cs +++ b/ICSharpCode.Decompiler/IL/BlockBuilder.cs @@ -16,8 +16,8 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +#nullable enable using System; -using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -29,6 +29,11 @@ using ICSharpCode.Decompiler.Util; namespace ICSharpCode.Decompiler.IL { + /// + /// Converts the list of basic blocks from ILReader into a BlockContainer structure. + /// This involves creating nested block containers for exception handlers, and creating + /// branches between the blocks. + /// class BlockBuilder { readonly MethodBodyBlock body; @@ -64,7 +69,6 @@ namespace ICSharpCode.Decompiler.IL var tryRange = new Interval(eh.TryOffset, eh.TryOffset + eh.TryLength); var handlerBlock = new BlockContainer(); handlerBlock.AddILRange(new Interval(eh.HandlerOffset, eh.HandlerOffset + eh.HandlerLength)); - handlerBlock.Blocks.Add(new Block()); handlerContainers.Add(handlerBlock.StartILOffset, handlerBlock); if (eh.Kind == ExceptionRegionKind.Fault || eh.Kind == ExceptionRegionKind.Finally) @@ -94,7 +98,6 @@ namespace ICSharpCode.Decompiler.IL { var filterBlock = new BlockContainer(expectedResultType: StackType.I4); filterBlock.AddILRange(new Interval(eh.FilterOffset, eh.HandlerOffset)); - filterBlock.Blocks.Add(new Block()); handlerContainers.Add(filterBlock.StartILOffset, filterBlock); filter = filterBlock; } @@ -117,20 +120,18 @@ namespace ICSharpCode.Decompiler.IL } int currentTryIndex; - TryInstruction nextTry; + TryInstruction? nextTry; - BlockContainer currentContainer; - Block currentBlock; + BlockContainer? currentContainer; readonly Stack containerStack = new Stack(); - public void CreateBlocks(BlockContainer mainContainer, List instructions, BitSet incomingBranches, CancellationToken cancellationToken) + public void CreateBlocks(BlockContainer mainContainer, IEnumerable basicBlocks, CancellationToken cancellationToken) { CreateContainerStructure(); mainContainer.SetILRange(new Interval(0, body.GetCodeSize())); - currentContainer = mainContainer; - if (instructions.Count == 0) + if (!basicBlocks.Any()) { - currentContainer.Blocks.Add(new Block { + mainContainer.Blocks.Add(new Block { Instructions = { new InvalidBranch("Empty body found. Decompiled assembly might be a reference assembly.") } @@ -138,99 +139,42 @@ namespace ICSharpCode.Decompiler.IL return; } - foreach (var inst in instructions) + currentContainer = mainContainer; + foreach (var block in basicBlocks.OrderBy(b => b.StartILOffset)) { cancellationToken.ThrowIfCancellationRequested(); - int start = inst.StartILOffset; - if (currentBlock == null || (incomingBranches[start] && !IsStackAdjustment(inst))) + int start = block.StartILOffset; + // Leave nested containers if necessary + while (start >= currentContainer.EndILOffset) { - // Finish up the previous block - FinalizeCurrentBlock(start, fallthrough: true); - // Leave nested containers if necessary - while (start >= currentContainer.EndILOffset) - { - currentContainer = containerStack.Pop(); - currentBlock = currentContainer.Blocks.Last(); - // this container is skipped (i.e. the loop will execute again) - // set ILRange to the last instruction offset inside the block. - if (start >= currentContainer.EndILOffset) - { - Debug.Assert(currentBlock.ILRangeIsEmpty); - currentBlock.AddILRange(new Interval(currentBlock.StartILOffset, start)); - } - } - // Enter a handler if necessary - if (handlerContainers.TryGetValue(start, out BlockContainer handlerContainer)) - { - containerStack.Push(currentContainer); - currentContainer = handlerContainer; - currentBlock = handlerContainer.EntryPoint; - } - else - { - FinalizeCurrentBlock(start, fallthrough: false); - // Create the new block - currentBlock = new Block(); - currentContainer.Blocks.Add(currentBlock); - } - currentBlock.SetILRange(new Interval(start, start)); + currentContainer = containerStack.Pop(); + } + // Enter a handler if necessary + if (handlerContainers.TryGetValue(start, out BlockContainer? handlerContainer)) + { + containerStack.Push(currentContainer); + currentContainer = handlerContainer; } + // Enter a try block if necessary while (nextTry != null && start == nextTry.TryBlock.StartILOffset) { - currentBlock.Instructions.Add(nextTry); + var blockForTry = new Block(); + blockForTry.SetILRange(nextTry); + blockForTry.Instructions.Add(nextTry); + currentContainer.Blocks.Add(blockForTry); + containerStack.Push(currentContainer); currentContainer = (BlockContainer)nextTry.TryBlock; - currentBlock = new Block(); - currentContainer.Blocks.Add(currentBlock); - currentBlock.SetILRange(new Interval(start, start)); nextTry = tryInstructionList.ElementAtOrDefault(++currentTryIndex); } - currentBlock.Instructions.Add(inst); - if (inst.HasFlag(InstructionFlags.EndPointUnreachable)) - FinalizeCurrentBlock(inst.EndILOffset, fallthrough: false); - else if (!CreateExtendedBlocks && inst.HasFlag(InstructionFlags.MayBranch)) - FinalizeCurrentBlock(inst.EndILOffset, fallthrough: true); - } - FinalizeCurrentBlock(mainContainer.EndILOffset, fallthrough: false); - // Finish up all containers - while (containerStack.Count > 0) - { - currentContainer = containerStack.Pop(); - currentBlock = currentContainer.Blocks.Last(); - FinalizeCurrentBlock(mainContainer.EndILOffset, fallthrough: false); + currentContainer.Blocks.Add(block); } + Debug.Assert(currentTryIndex == tryInstructionList.Count && nextTry == null); ConnectBranches(mainContainer, cancellationToken); CreateOnErrorDispatchers(); } - static bool IsStackAdjustment(ILInstruction inst) - { - return inst is StLoc stloc && stloc.IsStackAdjustment; - } - - private void FinalizeCurrentBlock(int currentILOffset, bool fallthrough) - { - if (currentBlock == null) - return; - Debug.Assert(currentBlock.ILRangeIsEmpty); - currentBlock.SetILRange(new Interval(currentBlock.StartILOffset, currentILOffset)); - if (fallthrough) - { - if (currentBlock.Instructions.LastOrDefault() is SwitchInstruction switchInst && switchInst.Sections.Last().Body.MatchNop()) - { - // Instead of putting the default branch after the switch instruction - switchInst.Sections.Last().Body = new Branch(currentILOffset); - Debug.Assert(switchInst.HasFlag(InstructionFlags.EndPointUnreachable)); - } - else - { - currentBlock.Instructions.Add(new Branch(currentILOffset)); - } - } - currentBlock = null; - } - void ConnectBranches(ILInstruction inst, CancellationToken cancellationToken) { switch (inst) @@ -238,12 +182,16 @@ namespace ICSharpCode.Decompiler.IL case Branch branch: cancellationToken.ThrowIfCancellationRequested(); Debug.Assert(branch.TargetBlock == null); - branch.TargetBlock = FindBranchTarget(branch.TargetILOffset); - if (branch.TargetBlock == null) + var targetBlock = FindBranchTarget(branch.TargetILOffset); + if (targetBlock == null) { branch.ReplaceWith(new InvalidBranch("Could not find block for branch target " + Disassembler.DisassemblerHelpers.OffsetToString(branch.TargetILOffset)).WithILRange(branch)); } + else + { + branch.TargetBlock = targetBlock; + } break; case Leave leave: // ret (in void method) = leave(mainContainer) @@ -279,7 +227,7 @@ namespace ICSharpCode.Decompiler.IL } } - Block FindBranchTarget(int targetILOffset) + Block? FindBranchTarget(int targetILOffset) { foreach (var container in containerStack) { @@ -291,7 +239,7 @@ namespace ICSharpCode.Decompiler.IL if (container.SlotInfo == TryCatchHandler.BodySlot) { // catch handler is allowed to branch back into try block (VB On Error) - TryCatch tryCatch = (TryCatch)container.Parent.Parent; + TryCatch tryCatch = (TryCatch)container.Parent!.Parent!; if (tryCatch.TryBlock.StartILOffset < targetILOffset && targetILOffset < tryCatch.TryBlock.EndILOffset) { return CreateBranchTargetForOnErrorJump(tryCatch, targetILOffset); @@ -345,7 +293,7 @@ namespace ICSharpCode.Decompiler.IL { foreach (var (tryCatch, dispatch) in onErrorDispatchers) { - Block block = (Block)tryCatch.Parent; + Block block = (Block)tryCatch.Parent!; // Before the regular entry point of the try-catch, insert an. instruction that resets the dispatcher variable block.Instructions.Insert(tryCatch.ChildIndex, new StLoc(dispatch.Variable, new LdcI4(-1))); // Split the block, so that we can introduce branches that jump directly into the try block @@ -355,7 +303,7 @@ namespace ICSharpCode.Decompiler.IL newBlock.Instructions.AddRange(block.Instructions.Skip(splitAt)); block.Instructions.RemoveRange(splitAt, block.Instructions.Count - splitAt); block.Instructions.Add(new Branch(newBlock)); - ((BlockContainer)block.Parent).Blocks.Add(newBlock); + ((BlockContainer)block.Parent!).Blocks.Add(newBlock); // Update the branches that jump directly into the try block foreach (var b in dispatch.Branches) { diff --git a/ICSharpCode.Decompiler/IL/ILReader.cs b/ICSharpCode.Decompiler/IL/ILReader.cs index 154e65c5c..f53a56acc 100644 --- a/ICSharpCode.Decompiler/IL/ILReader.cs +++ b/ICSharpCode.Decompiler/IL/ILReader.cs @@ -46,6 +46,77 @@ namespace ICSharpCode.Decompiler.IL /// public class ILReader { + /// + /// Represents a block of IL instructions. + /// + private sealed class ImportedBlock + { + // These members are immediately available after construction: + public readonly Block Block = new Block(BlockKind.ControlFlow); + public ImmutableStack InputStack; + + public int StartILOffset => Block.StartILOffset; + /// True if the import is in progress or completed. + public bool ImportStarted = false; + + // When the block is imported, Block.Instructions is filled with the imported instructions + // and the following members are initialized: + public List<(ImportedBlock, ImmutableStack)> OutgoingEdges = new(); + + public ImportedBlock(int offset, ImmutableStack inputStack) + { + this.InputStack = inputStack; + this.Block.AddILRange(new Interval(offset, offset)); + } + + /// + /// Compares stack types and update InputStack if necessary. + /// Returns true if InputStack was updated, making a reimport necessary. + /// + public bool MergeStackTypes(ImmutableStack newEdge) + { + var a = this.InputStack; + var b = newEdge; + bool changed = false; + while (!a.IsEmpty && !b.IsEmpty) + { + if (a.Peek().StackType < b.Peek().StackType) + { + changed = true; + } + a = a.Pop(); + b = b.Pop(); + } + if (!changed || !(a.IsEmpty && b.IsEmpty)) + return false; + a = this.InputStack; + b = newEdge; + var output = new List(); + while (!a.IsEmpty && !b.IsEmpty) + { + if (a.Peek().StackType < b.Peek().StackType) + { + output.Add(b.Peek()); + } + else + { + output.Add(a.Peek()); + } + a = a.Pop(); + b = b.Pop(); + } + this.InputStack = ImmutableStack.CreateRange(output); + this.ImportStarted = false; + return true; + } + + internal void ResetForReimport() + { + this.Block.Instructions.Clear(); + this.OutgoingEdges.Clear(); + } + } + readonly ICompilation compilation; readonly MetadataModule module; readonly MetadataReader metadata; @@ -80,19 +151,17 @@ namespace ICSharpCode.Decompiler.IL StackType methodReturnStackType; BlobReader reader; ImmutableStack currentStack = ImmutableStack.Empty; + ImportedBlock? currentBlock; List expressionStack = new List(); ILVariable[] parameterVariables = null!; ILVariable[] localVariables = null!; BitSet isBranchTarget = null!; BlockContainer mainContainer = null!; - List instructionBuilder = new List(); int currentInstructionStart; - // Dictionary that stores stacks for each IL instruction - Dictionary> stackByOffset = new Dictionary>(); + Dictionary blocksByOffset = new Dictionary(); + Queue importQueue = new Queue(); Dictionary variableByExceptionHandler = new Dictionary(); - UnionFind unionFind = null!; - List<(ILVariable, ILVariable)> stackMismatchPairs = new List<(ILVariable, ILVariable)>(); IEnumerable? stackVariables; void Init(MethodDefinitionHandle methodDefinitionHandle, MethodBodyBlock body, GenericContext genericContext) @@ -117,8 +186,6 @@ namespace ICSharpCode.Decompiler.IL this.reader = body.GetILReader(); this.currentStack = ImmutableStack.Empty; this.expressionStack.Clear(); - this.unionFind = new UnionFind(); - this.stackMismatchPairs.Clear(); this.methodReturnStackType = method.ReturnType.GetStackType(); InitParameterVariables(); localVariables = InitLocalVariables(); @@ -128,9 +195,9 @@ namespace ICSharpCode.Decompiler.IL v.UsesInitialValue = true; } this.mainContainer = new BlockContainer(expectedResultType: methodReturnStackType); - this.instructionBuilder.Clear(); + this.blocksByOffset.Clear(); + this.importQueue.Clear(); this.isBranchTarget = new BitSet(reader.Length); - this.stackByOffset.Clear(); this.variableByExceptionHandler.Clear(); } @@ -284,82 +351,71 @@ namespace ICSharpCode.Decompiler.IL Warnings.Add(string.Format("IL_{0:x4}: {1}", currentInstructionStart, message)); } - ImmutableStack MergeStacks(ImmutableStack a, ImmutableStack b) + /// + /// Check control flow edges for compatible stacks. + /// Returns union find data structure for unifying the different variables for the same stack slot. + /// Also inserts stack adjustments where necessary. + /// + UnionFind CheckOutgoingEdges() { - if (CheckStackCompatibleWithoutAdjustments(a, b)) - { - // We only need to union the input variables, but can - // otherwise re-use the existing stack. - ImmutableStack output = a; - while (!a.IsEmpty && !b.IsEmpty) - { - Debug.Assert(a.Peek().StackType == b.Peek().StackType); - unionFind.Merge(a.Peek(), b.Peek()); - a = a.Pop(); - b = b.Pop(); - } - return output; - } - else if (a.Count() != b.Count()) - { - // Let's not try to merge mismatched stacks. - Warn("Incompatible stack heights: " + a.Count() + " vs " + b.Count()); - return a; - } - else + var unionFind = new UnionFind(); + foreach (var block in blocksByOffset.Values) { - // The more complex case where the stacks don't match exactly. - var output = new List(); - while (!a.IsEmpty && !b.IsEmpty) + foreach (var (outgoing, stack) in block.OutgoingEdges) { - var varA = a.Peek(); - var varB = b.Peek(); - if (varA.StackType == varB.StackType) + var a = stack; + var b = outgoing.InputStack; + if (a.Count() != b.Count()) { - unionFind.Merge(varA, varB); - output.Add(varA); + // Let's not try to merge mismatched stacks. + Warnings.Add($"IL_{block.Block.EndILOffset:x4}->IL{outgoing.StartILOffset:x4}: Incompatible stack heights: {a.Count()} vs {b.Count()}"); + continue; } - else + while (!a.IsEmpty && !b.IsEmpty) { - if (!IsValidTypeStackTypeMerge(varA.StackType, varB.StackType)) - { - Warn("Incompatible stack types: " + varA.StackType + " vs " + varB.StackType); - } - if (varA.StackType > varB.StackType) + var varA = a.Peek(); + var varB = b.Peek(); + if (varA.StackType == varB.StackType) { - output.Add(varA); - // every store to varB should also store to varA - stackMismatchPairs.Add((varB, varA)); + // The stack types match, so we can merge the variables. + unionFind.Merge(varA, varB); } else { - output.Add(varB); - // every store to varA should also store to varB - stackMismatchPairs.Add((varA, varB)); + Debug.Assert(varA.StackType < varB.StackType); + if (!IsValidTypeStackTypeMerge(varA.StackType, varB.StackType)) + { + Warnings.Add($"IL_{block.Block.EndILOffset:x4}->IL{outgoing.StartILOffset:x4}: Incompatible stack types: {varA.StackType} vs {varB.StackType}"); + } + InsertStackAdjustment(block.Block, varA, varB); } + a = a.Pop(); + b = b.Pop(); } - a = a.Pop(); - b = b.Pop(); } - // because we built up output by popping from the input stacks, we need to reverse it to get back the original order - output.Reverse(); - return ImmutableStack.CreateRange(output); } + return unionFind; } - static bool CheckStackCompatibleWithoutAdjustments(ImmutableStack a, ImmutableStack b) + /// + /// Inserts a copy from varA to varB (with conversion) at the end of . + /// If the block ends with a branching instruction, the copy is inserted before the branching instruction. + /// + private void InsertStackAdjustment(Block block, ILVariable varA, ILVariable varB) { - while (!a.IsEmpty && !b.IsEmpty) + int insertionPosition = block.Instructions.Count; + while (insertionPosition > 0 && block.Instructions[insertionPosition - 1].HasFlag(InstructionFlags.MayBranch)) { - if (a.Peek().StackType != b.Peek().StackType) - return false; - a = a.Pop(); - b = b.Pop(); + // Branch instruction mustn't be initializing varA. + Debug.Assert(!block.Instructions[insertionPosition - 1].HasFlag(InstructionFlags.MayWriteLocals)); + insertionPosition--; } - return a.IsEmpty && b.IsEmpty; + ILInstruction value = new LdLoc(varA); + value = new Conv(value, varB.StackType.ToPrimitiveType(), false, Sign.Signed); + block.Instructions.Insert(insertionPosition, new StLoc(varB, value) { IsStackAdjustment = true }); } - private bool IsValidTypeStackTypeMerge(StackType stackType1, StackType stackType2) + private static bool IsValidTypeStackTypeMerge(StackType stackType1, StackType stackType2) { if (stackType1 == StackType.I && stackType2 == StackType.I4) return true; @@ -375,42 +431,66 @@ namespace ICSharpCode.Decompiler.IL /// /// Stores the given stack for a branch to `offset`. - /// - /// The stack may be modified if stack adjustments are necessary. (e.g. implicit I4->I conversion) /// - void StoreStackForOffset(int offset, ref ImmutableStack stack) + ImportedBlock StoreStackForOffset(int offset, ImmutableStack stack) { - if (stackByOffset.TryGetValue(offset, out var existing)) + if (blocksByOffset.TryGetValue(offset, out var existing)) { - stack = MergeStacks(existing, stack); - if (stack != existing) - stackByOffset[offset] = stack; + bool wasImported = existing.ImportStarted; + if (existing.MergeStackTypes(stack) && wasImported) + { + // If the stack changed, we need to re-import the block. + importQueue.Enqueue(existing); + } + return existing; } else { - stackByOffset.Add(offset, stack); + ImportedBlock newBlock = new ImportedBlock(offset, stack); + blocksByOffset.Add(offset, newBlock); + importQueue.Enqueue(newBlock); + return newBlock; } } void ReadInstructions(CancellationToken cancellationToken) { reader.Reset(); + StoreStackForOffset(0, ImmutableStack.Empty); ILParser.SetBranchTargets(ref reader, isBranchTarget); - reader.Reset(); PrepareBranchTargetsAndStacksForExceptionHandlers(); - bool nextInstructionBeginsNewBlock = false; + // Import of IL byte codes: + while (importQueue.Count > 0) + { + cancellationToken.ThrowIfCancellationRequested(); + ImportedBlock block = importQueue.Dequeue(); + ReadBlock(block, cancellationToken); + } - reader.Reset(); + // Merge different variables for same stack slot: + var unionFind = CheckOutgoingEdges(); + var visitor = new CollectStackVariablesVisitor(unionFind); + foreach (var block in blocksByOffset.Values) + { + block.Block.AcceptVisitor(visitor); + } + stackVariables = visitor.variables; + } + + void ReadBlock(ImportedBlock block, CancellationToken cancellationToken) + { + Debug.Assert(!block.ImportStarted); + block.ResetForReimport(); + block.ImportStarted = true; + reader.Offset = block.StartILOffset; + currentBlock = block; + currentStack = block.InputStack; + // Read instructions until we reach the end of the block. while (reader.RemainingBytes > 0) { cancellationToken.ThrowIfCancellationRequested(); int start = reader.Offset; - if (isBranchTarget[start]) - { - FlushExpressionStack(); - StoreStackForOffset(start, ref currentStack); - } currentInstructionStart = start; bool startedWithEmptyStack = CurrentStackIsEmpty(); DecodedInstruction decodedInstruction; @@ -432,9 +512,9 @@ namespace ICSharpCode.Decompiler.IL { // Flush to avoid re-ordering of side-effects FlushExpressionStack(); - instructionBuilder.Add(inst); + block.Block.Instructions.Add(inst); } - else if (isBranchTarget[start] || nextInstructionBeginsNewBlock) + else if (start == block.StartILOffset) { // If this instruction is the first in a new block, avoid it being inlined // into the next instruction. @@ -442,39 +522,39 @@ namespace ICSharpCode.Decompiler.IL // detect block starts, and doesn't search nested instructions. FlushExpressionStack(); } - if (inst.HasDirectFlag(InstructionFlags.EndPointUnreachable)) + + if ((!decodedInstruction.PushedOnExpressionStack && IsSequencePointInstruction(inst)) || startedWithEmptyStack) { - FlushExpressionStack(); - if (stackByOffset.TryGetValue(end, out var stackAfterEnd)) - { - currentStack = stackAfterEnd; - } - else - { - currentStack = ImmutableStack.Empty; - } - nextInstructionBeginsNewBlock = true; + this.SequencePointCandidates.Add(inst.StartILOffset); } - else + + if (inst.HasDirectFlag(InstructionFlags.EndPointUnreachable)) { - nextInstructionBeginsNewBlock = inst.HasFlag(InstructionFlags.MayBranch); + break; // end of block, don't parse following instructions if they are unreachable } - - if ((!decodedInstruction.PushedOnExpressionStack && IsSequencePointInstruction(inst)) || startedWithEmptyStack) + else if (isBranchTarget[end] || inst.HasFlag(InstructionFlags.MayBranch)) { - this.SequencePointCandidates.Add(inst.StartILOffset); + break; // end of block (we'll create fall through) } } - + // Finalize block FlushExpressionStack(); - - var visitor = new CollectStackVariablesVisitor(unionFind); - for (int i = 0; i < instructionBuilder.Count; i++) + block.Block.AddILRange(new Interval(block.StartILOffset, reader.Offset)); + if (!block.Block.HasFlag(InstructionFlags.EndPointUnreachable)) { - instructionBuilder[i] = instructionBuilder[i].AcceptVisitor(visitor); + // create fall through branch + MarkBranchTarget(reader.Offset, isFallThrough: true); + if (block.Block.Instructions.LastOrDefault() is SwitchInstruction switchInst && switchInst.Sections.Last().Body.MatchNop()) + { + // Instead of putting the default branch after the switch instruction + switchInst.Sections.Last().Body = new Branch(reader.Offset); + Debug.Assert(switchInst.HasFlag(InstructionFlags.EndPointUnreachable)); + } + else + { + block.Block.Instructions.Add(new Branch(reader.Offset)); + } } - stackVariables = visitor.variables; - InsertStackAdjustments(); } private bool CurrentStackIsEmpty() @@ -488,9 +568,7 @@ namespace ICSharpCode.Decompiler.IL foreach (var eh in body.ExceptionRegions) { // Always mark the start of the try block as a "branch target". - // We don't actually need to store the stack state here, - // but we need to ensure that no ILInstructions are inlined - // into the try-block. + // We need to ensure that we put a block boundary there. isBranchTarget[eh.TryOffset] = true; ImmutableStack ehStack; @@ -520,25 +598,23 @@ namespace ICSharpCode.Decompiler.IL if (eh.FilterOffset != -1) { isBranchTarget[eh.FilterOffset] = true; - StoreStackForOffset(eh.FilterOffset, ref ehStack); + StoreStackForOffset(eh.FilterOffset, ehStack); } if (eh.HandlerOffset != -1) { isBranchTarget[eh.HandlerOffset] = true; - StoreStackForOffset(eh.HandlerOffset, ref ehStack); + StoreStackForOffset(eh.HandlerOffset, ehStack); } } } - private bool IsSequencePointInstruction(ILInstruction instruction) + private static bool IsSequencePointInstruction(ILInstruction instruction) { - if (instruction.OpCode == OpCode.Nop || - (instructionBuilder.Count > 0 - && instructionBuilder.Last().OpCode is OpCode.Call - or OpCode.CallIndirect - or OpCode.CallVirt)) + if (instruction.OpCode is OpCode.Nop + or OpCode.Call + or OpCode.CallIndirect + or OpCode.CallVirt) { - return true; } else @@ -547,39 +623,6 @@ namespace ICSharpCode.Decompiler.IL } } - void InsertStackAdjustments() - { - if (stackMismatchPairs.Count == 0) - return; - var dict = new MultiDictionary(); - foreach (var (origA, origB) in stackMismatchPairs) - { - var a = unionFind.Find(origA); - var b = unionFind.Find(origB); - Debug.Assert(a.StackType < b.StackType); - // For every store to a, insert a converting store to b. - if (!dict[a].Contains(b)) - dict.Add(a, b); - } - var newInstructions = new List(); - foreach (var inst in instructionBuilder) - { - newInstructions.Add(inst); - if (inst is StLoc store) - { - foreach (var additionalVar in dict[store.Variable]) - { - ILInstruction value = new LdLoc(store.Variable); - value = new Conv(value, additionalVar.StackType.ToPrimitiveType(), false, Sign.Signed); - newInstructions.Add(new StLoc(additionalVar, value) { - IsStackAdjustment = true, - }.WithILRange(inst)); - } - } - } - instructionBuilder = newInstructions; - } - /// /// Debugging helper: writes the decoded instruction stream interleaved with the inferred evaluation stack layout. /// @@ -588,39 +631,24 @@ namespace ICSharpCode.Decompiler.IL { Init(method, body, genericContext); ReadInstructions(cancellationToken); - foreach (var inst in instructionBuilder) + foreach (var importBlock in blocksByOffset.Values.OrderBy(b => b.StartILOffset)) { - if (inst is StLoc stloc && stloc.IsStackAdjustment) - { - output.Write(" "); - inst.WriteTo(output, new ILAstWritingOptions()); - output.WriteLine(); - continue; - } - if (stackByOffset.TryGetValue(inst.StartILOffset, out var stack)) + output.Write(" ["); + bool isFirstElement = true; + foreach (var element in importBlock.InputStack) { - output.Write(" ["); - bool isFirstElement = true; - foreach (var element in stack) - { - if (isFirstElement) - isFirstElement = false; - else - output.Write(", "); - output.WriteLocalReference(element.Name, element); - output.Write(":"); - output.Write(element.StackType); - } - output.Write(']'); - output.WriteLine(); + if (isFirstElement) + isFirstElement = false; + else + output.Write(", "); + output.WriteLocalReference(element.Name, element); + output.Write(":"); + output.Write(element.StackType); } - if (isBranchTarget[inst.StartILOffset]) - output.Write('*'); - else - output.Write(' '); - output.WriteLocalReference("IL_" + inst.StartILOffset.ToString("x4"), inst.StartILOffset, isDefinition: true); - output.Write(": "); - inst.WriteTo(output, new ILAstWritingOptions()); + output.Write(']'); + output.WriteLine(); + + importBlock.Block.WriteTo(output, new ILAstWritingOptions()); output.WriteLine(); } new Disassembler.MethodBodyDisassembler(output, cancellationToken) { DetectControlStructure = false } @@ -636,7 +664,7 @@ namespace ICSharpCode.Decompiler.IL Init(method, body, genericContext); ReadInstructions(cancellationToken); var blockBuilder = new BlockBuilder(body, variableByExceptionHandler, compilation); - blockBuilder.CreateBlocks(mainContainer, instructionBuilder, isBranchTarget, cancellationToken); + blockBuilder.CreateBlocks(mainContainer, blocksByOffset.Values.Select(ib => ib.Block), cancellationToken); var function = new ILFunction(this.method, body.GetCodeSize(), this.genericContext, mainContainer, kind); function.Variables.AddRange(parameterVariables); function.Variables.AddRange(localVariables); @@ -1187,7 +1215,7 @@ namespace ICSharpCode.Decompiler.IL return currentStack.Peek().StackType; } - class CollectStackVariablesVisitor : ILVisitor + sealed class CollectStackVariablesVisitor : ILVisitor { readonly UnionFind unionFind; internal readonly HashSet variables = new HashSet(); @@ -1216,7 +1244,7 @@ namespace ICSharpCode.Decompiler.IL { var variable = unionFind.Find(inst.Variable); if (variables.Add(variable)) - variable.Name = "S_" + (variables.Count - 1); + variable.Name = $"S_{variables.Count - 1}"; return new LdLoc(variable).WithILRange(inst); } return inst; @@ -1229,7 +1257,7 @@ namespace ICSharpCode.Decompiler.IL { var variable = unionFind.Find(inst.Variable); if (variables.Add(variable)) - variable.Name = "S_" + (variables.Count - 1); + variable.Name = $"S_{variables.Count - 1}"; return new StLoc(variable, inst.Value).WithILRange(inst); } return inst; @@ -1296,7 +1324,7 @@ namespace ICSharpCode.Decompiler.IL return Cast(inst, expectedType, Warnings, reader.Offset); } - internal static ILInstruction Cast(ILInstruction inst, StackType expectedType, List warnings, int ilOffset) + internal static ILInstruction Cast(ILInstruction inst, StackType expectedType, List? warnings, int ilOffset) { if (expectedType != inst.ResultType) { @@ -1913,11 +1941,13 @@ namespace ICSharpCode.Decompiler.IL } } - void MarkBranchTarget(int targetILOffset) + void MarkBranchTarget(int targetILOffset, bool isFallThrough = false) { FlushExpressionStack(); - Debug.Assert(isBranchTarget[targetILOffset]); - StoreStackForOffset(targetILOffset, ref currentStack); + Debug.Assert(isFallThrough || isBranchTarget[targetILOffset]); + var targetBlock = StoreStackForOffset(targetILOffset, currentStack); + Debug.Assert(currentBlock != null); + currentBlock.OutgoingEdges.Add((targetBlock, currentStack)); } /// @@ -1928,6 +1958,7 @@ namespace ICSharpCode.Decompiler.IL /// private void FlushExpressionStack() { + Debug.Assert(currentBlock != null); foreach (var inst in expressionStack) { Debug.Assert(inst.ResultType != StackType.Void); @@ -1935,7 +1966,7 @@ namespace ICSharpCode.Decompiler.IL var v = new ILVariable(VariableKind.StackSlot, type, inst.ResultType); v.HasGeneratedName = true; currentStack = currentStack.Push(v); - instructionBuilder.Add(new StLoc(v, inst).WithILRange(inst)); + currentBlock.Block.Instructions.Add(new StLoc(v, inst).WithILRange(inst)); } expressionStack.Clear(); }