Browse Source

Fix #901: Refactor ILReader: only read reachable code + support reimports

This makes our logic more similar to that used by the dotnet runtime. This lets us infer correct stack types in edge cases such as #2401. It also improves support for obfuscated control flow such as #2878.
pull/2993/head
Daniel Grunwald 2 years ago
parent
commit
b93e65cdad
  1. 32
      ICSharpCode.Decompiler.Tests/TestCases/Correctness/StackTypes.il
  2. 134
      ICSharpCode.Decompiler/IL/BlockBuilder.cs
  3. 409
      ICSharpCode.Decompiler/IL/ILReader.cs

32
ICSharpCode.Decompiler.Tests/TestCases/Correctness/StackTypes.il

@ -79,7 +79,34 @@ pointless:
box native int box native int
call void [mscorlib]System.Console::WriteLine(string, object) 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):" ldstr "Int32OrNativeLoopStyle(0x7fffffff):"
call void [mscorlib]System.Console::WriteLine(string) call void [mscorlib]System.Console::WriteLine(string)
ldc.i4 0x7fffffff ldc.i4 0x7fffffff
@ -101,7 +128,6 @@ pointless:
call native int Program::Int32OrNativeDeadCode(int32) call native int Program::Int32OrNativeDeadCode(int32)
box native int box native int
call void [mscorlib]System.Console::WriteLine(string, object) call void [mscorlib]System.Console::WriteLine(string, object)
*/
ldc.i4 0x7fffffff ldc.i4 0x7fffffff
call void Program::RunInt32OrNativeMultiUse(int32) call void Program::RunInt32OrNativeMultiUse(int32)
@ -127,7 +153,6 @@ pointless:
ret ret
} }
/*
.method public static native int Int32OrNativeReordered(int32 val, bool use_native) .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. // The spec is ambiguous whether the addition will be in 32-bits or native size.
@ -187,7 +212,6 @@ pointless:
conv.u conv.u
br after_if br after_if
} }
*/
.method public static void RunInt32OrNativeMultiUse(int32 val) .method public static void RunInt32OrNativeMultiUse(int32 val)
{ {

134
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 // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
#nullable enable
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
@ -29,6 +29,11 @@ using ICSharpCode.Decompiler.Util;
namespace ICSharpCode.Decompiler.IL namespace ICSharpCode.Decompiler.IL
{ {
/// <summary>
/// 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.
/// </summary>
class BlockBuilder class BlockBuilder
{ {
readonly MethodBodyBlock body; readonly MethodBodyBlock body;
@ -64,7 +69,6 @@ namespace ICSharpCode.Decompiler.IL
var tryRange = new Interval(eh.TryOffset, eh.TryOffset + eh.TryLength); var tryRange = new Interval(eh.TryOffset, eh.TryOffset + eh.TryLength);
var handlerBlock = new BlockContainer(); var handlerBlock = new BlockContainer();
handlerBlock.AddILRange(new Interval(eh.HandlerOffset, eh.HandlerOffset + eh.HandlerLength)); handlerBlock.AddILRange(new Interval(eh.HandlerOffset, eh.HandlerOffset + eh.HandlerLength));
handlerBlock.Blocks.Add(new Block());
handlerContainers.Add(handlerBlock.StartILOffset, handlerBlock); handlerContainers.Add(handlerBlock.StartILOffset, handlerBlock);
if (eh.Kind == ExceptionRegionKind.Fault || eh.Kind == ExceptionRegionKind.Finally) if (eh.Kind == ExceptionRegionKind.Fault || eh.Kind == ExceptionRegionKind.Finally)
@ -94,7 +98,6 @@ namespace ICSharpCode.Decompiler.IL
{ {
var filterBlock = new BlockContainer(expectedResultType: StackType.I4); var filterBlock = new BlockContainer(expectedResultType: StackType.I4);
filterBlock.AddILRange(new Interval(eh.FilterOffset, eh.HandlerOffset)); filterBlock.AddILRange(new Interval(eh.FilterOffset, eh.HandlerOffset));
filterBlock.Blocks.Add(new Block());
handlerContainers.Add(filterBlock.StartILOffset, filterBlock); handlerContainers.Add(filterBlock.StartILOffset, filterBlock);
filter = filterBlock; filter = filterBlock;
} }
@ -117,20 +120,18 @@ namespace ICSharpCode.Decompiler.IL
} }
int currentTryIndex; int currentTryIndex;
TryInstruction nextTry; TryInstruction? nextTry;
BlockContainer currentContainer; BlockContainer? currentContainer;
Block currentBlock;
readonly Stack<BlockContainer> containerStack = new Stack<BlockContainer>(); readonly Stack<BlockContainer> containerStack = new Stack<BlockContainer>();
public void CreateBlocks(BlockContainer mainContainer, List<ILInstruction> instructions, BitSet incomingBranches, CancellationToken cancellationToken) public void CreateBlocks(BlockContainer mainContainer, IEnumerable<Block> basicBlocks, CancellationToken cancellationToken)
{ {
CreateContainerStructure(); CreateContainerStructure();
mainContainer.SetILRange(new Interval(0, body.GetCodeSize())); mainContainer.SetILRange(new Interval(0, body.GetCodeSize()));
currentContainer = mainContainer; if (!basicBlocks.Any())
if (instructions.Count == 0)
{ {
currentContainer.Blocks.Add(new Block { mainContainer.Blocks.Add(new Block {
Instructions = { Instructions = {
new InvalidBranch("Empty body found. Decompiled assembly might be a reference assembly.") new InvalidBranch("Empty body found. Decompiled assembly might be a reference assembly.")
} }
@ -138,99 +139,42 @@ namespace ICSharpCode.Decompiler.IL
return; return;
} }
foreach (var inst in instructions) currentContainer = mainContainer;
foreach (var block in basicBlocks.OrderBy(b => b.StartILOffset))
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
int start = inst.StartILOffset; int start = block.StartILOffset;
if (currentBlock == null || (incomingBranches[start] && !IsStackAdjustment(inst))) // Leave nested containers if necessary
while (start >= currentContainer.EndILOffset)
{ {
// Finish up the previous block currentContainer = containerStack.Pop();
FinalizeCurrentBlock(start, fallthrough: true); }
// Leave nested containers if necessary // Enter a handler if necessary
while (start >= currentContainer.EndILOffset) if (handlerContainers.TryGetValue(start, out BlockContainer? handlerContainer))
{ {
currentContainer = containerStack.Pop(); containerStack.Push(currentContainer);
currentBlock = currentContainer.Blocks.Last(); currentContainer = handlerContainer;
// 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));
} }
// Enter a try block if necessary
while (nextTry != null && start == nextTry.TryBlock.StartILOffset) 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); containerStack.Push(currentContainer);
currentContainer = (BlockContainer)nextTry.TryBlock; currentContainer = (BlockContainer)nextTry.TryBlock;
currentBlock = new Block();
currentContainer.Blocks.Add(currentBlock);
currentBlock.SetILRange(new Interval(start, start));
nextTry = tryInstructionList.ElementAtOrDefault(++currentTryIndex); nextTry = tryInstructionList.ElementAtOrDefault(++currentTryIndex);
} }
currentBlock.Instructions.Add(inst); currentContainer.Blocks.Add(block);
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);
} }
Debug.Assert(currentTryIndex == tryInstructionList.Count && nextTry == null);
ConnectBranches(mainContainer, cancellationToken); ConnectBranches(mainContainer, cancellationToken);
CreateOnErrorDispatchers(); 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) void ConnectBranches(ILInstruction inst, CancellationToken cancellationToken)
{ {
switch (inst) switch (inst)
@ -238,12 +182,16 @@ namespace ICSharpCode.Decompiler.IL
case Branch branch: case Branch branch:
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
Debug.Assert(branch.TargetBlock == null); Debug.Assert(branch.TargetBlock == null);
branch.TargetBlock = FindBranchTarget(branch.TargetILOffset); var targetBlock = FindBranchTarget(branch.TargetILOffset);
if (branch.TargetBlock == null) if (targetBlock == null)
{ {
branch.ReplaceWith(new InvalidBranch("Could not find block for branch target " branch.ReplaceWith(new InvalidBranch("Could not find block for branch target "
+ Disassembler.DisassemblerHelpers.OffsetToString(branch.TargetILOffset)).WithILRange(branch)); + Disassembler.DisassemblerHelpers.OffsetToString(branch.TargetILOffset)).WithILRange(branch));
} }
else
{
branch.TargetBlock = targetBlock;
}
break; break;
case Leave leave: case Leave leave:
// ret (in void method) = leave(mainContainer) // 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) foreach (var container in containerStack)
{ {
@ -291,7 +239,7 @@ namespace ICSharpCode.Decompiler.IL
if (container.SlotInfo == TryCatchHandler.BodySlot) if (container.SlotInfo == TryCatchHandler.BodySlot)
{ {
// catch handler is allowed to branch back into try block (VB On Error) // 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) if (tryCatch.TryBlock.StartILOffset < targetILOffset && targetILOffset < tryCatch.TryBlock.EndILOffset)
{ {
return CreateBranchTargetForOnErrorJump(tryCatch, targetILOffset); return CreateBranchTargetForOnErrorJump(tryCatch, targetILOffset);
@ -345,7 +293,7 @@ namespace ICSharpCode.Decompiler.IL
{ {
foreach (var (tryCatch, dispatch) in onErrorDispatchers) 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 // 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))); 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 // 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)); newBlock.Instructions.AddRange(block.Instructions.Skip(splitAt));
block.Instructions.RemoveRange(splitAt, block.Instructions.Count - splitAt); block.Instructions.RemoveRange(splitAt, block.Instructions.Count - splitAt);
block.Instructions.Add(new Branch(newBlock)); 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 // Update the branches that jump directly into the try block
foreach (var b in dispatch.Branches) foreach (var b in dispatch.Branches)
{ {

409
ICSharpCode.Decompiler/IL/ILReader.cs

@ -46,6 +46,77 @@ namespace ICSharpCode.Decompiler.IL
/// </remarks> /// </remarks>
public class ILReader public class ILReader
{ {
/// <summary>
/// Represents a block of IL instructions.
/// </summary>
private sealed class ImportedBlock
{
// These members are immediately available after construction:
public readonly Block Block = new Block(BlockKind.ControlFlow);
public ImmutableStack<ILVariable> 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<ILVariable>)> OutgoingEdges = new();
public ImportedBlock(int offset, ImmutableStack<ILVariable> inputStack)
{
this.InputStack = inputStack;
this.Block.AddILRange(new Interval(offset, offset));
}
/// <summary>
/// Compares stack types and update InputStack if necessary.
/// Returns true if InputStack was updated, making a reimport necessary.
/// </summary>
public bool MergeStackTypes(ImmutableStack<ILVariable> 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<ILVariable>();
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 ICompilation compilation;
readonly MetadataModule module; readonly MetadataModule module;
readonly MetadataReader metadata; readonly MetadataReader metadata;
@ -80,19 +151,17 @@ namespace ICSharpCode.Decompiler.IL
StackType methodReturnStackType; StackType methodReturnStackType;
BlobReader reader; BlobReader reader;
ImmutableStack<ILVariable> currentStack = ImmutableStack<ILVariable>.Empty; ImmutableStack<ILVariable> currentStack = ImmutableStack<ILVariable>.Empty;
ImportedBlock? currentBlock;
List<ILInstruction> expressionStack = new List<ILInstruction>(); List<ILInstruction> expressionStack = new List<ILInstruction>();
ILVariable[] parameterVariables = null!; ILVariable[] parameterVariables = null!;
ILVariable[] localVariables = null!; ILVariable[] localVariables = null!;
BitSet isBranchTarget = null!; BitSet isBranchTarget = null!;
BlockContainer mainContainer = null!; BlockContainer mainContainer = null!;
List<ILInstruction> instructionBuilder = new List<ILInstruction>();
int currentInstructionStart; int currentInstructionStart;
// Dictionary that stores stacks for each IL instruction Dictionary<int, ImportedBlock> blocksByOffset = new Dictionary<int, ImportedBlock>();
Dictionary<int, ImmutableStack<ILVariable>> stackByOffset = new Dictionary<int, ImmutableStack<ILVariable>>(); Queue<ImportedBlock> importQueue = new Queue<ImportedBlock>();
Dictionary<ExceptionRegion, ILVariable> variableByExceptionHandler = new Dictionary<ExceptionRegion, ILVariable>(); Dictionary<ExceptionRegion, ILVariable> variableByExceptionHandler = new Dictionary<ExceptionRegion, ILVariable>();
UnionFind<ILVariable> unionFind = null!;
List<(ILVariable, ILVariable)> stackMismatchPairs = new List<(ILVariable, ILVariable)>();
IEnumerable<ILVariable>? stackVariables; IEnumerable<ILVariable>? stackVariables;
void Init(MethodDefinitionHandle methodDefinitionHandle, MethodBodyBlock body, GenericContext genericContext) void Init(MethodDefinitionHandle methodDefinitionHandle, MethodBodyBlock body, GenericContext genericContext)
@ -117,8 +186,6 @@ namespace ICSharpCode.Decompiler.IL
this.reader = body.GetILReader(); this.reader = body.GetILReader();
this.currentStack = ImmutableStack<ILVariable>.Empty; this.currentStack = ImmutableStack<ILVariable>.Empty;
this.expressionStack.Clear(); this.expressionStack.Clear();
this.unionFind = new UnionFind<ILVariable>();
this.stackMismatchPairs.Clear();
this.methodReturnStackType = method.ReturnType.GetStackType(); this.methodReturnStackType = method.ReturnType.GetStackType();
InitParameterVariables(); InitParameterVariables();
localVariables = InitLocalVariables(); localVariables = InitLocalVariables();
@ -128,9 +195,9 @@ namespace ICSharpCode.Decompiler.IL
v.UsesInitialValue = true; v.UsesInitialValue = true;
} }
this.mainContainer = new BlockContainer(expectedResultType: methodReturnStackType); this.mainContainer = new BlockContainer(expectedResultType: methodReturnStackType);
this.instructionBuilder.Clear(); this.blocksByOffset.Clear();
this.importQueue.Clear();
this.isBranchTarget = new BitSet(reader.Length); this.isBranchTarget = new BitSet(reader.Length);
this.stackByOffset.Clear();
this.variableByExceptionHandler.Clear(); this.variableByExceptionHandler.Clear();
} }
@ -284,82 +351,71 @@ namespace ICSharpCode.Decompiler.IL
Warnings.Add(string.Format("IL_{0:x4}: {1}", currentInstructionStart, message)); Warnings.Add(string.Format("IL_{0:x4}: {1}", currentInstructionStart, message));
} }
ImmutableStack<ILVariable> MergeStacks(ImmutableStack<ILVariable> a, ImmutableStack<ILVariable> b) /// <summary>
/// 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.
/// </summary>
UnionFind<ILVariable> CheckOutgoingEdges()
{ {
if (CheckStackCompatibleWithoutAdjustments(a, b)) var unionFind = new UnionFind<ILVariable>();
{ foreach (var block in blocksByOffset.Values)
// We only need to union the input variables, but can
// otherwise re-use the existing stack.
ImmutableStack<ILVariable> 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
{ {
// The more complex case where the stacks don't match exactly. foreach (var (outgoing, stack) in block.OutgoingEdges)
var output = new List<ILVariable>();
while (!a.IsEmpty && !b.IsEmpty)
{ {
var varA = a.Peek(); var a = stack;
var varB = b.Peek(); var b = outgoing.InputStack;
if (varA.StackType == varB.StackType) if (a.Count() != b.Count())
{ {
unionFind.Merge(varA, varB); // Let's not try to merge mismatched stacks.
output.Add(varA); 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)) var varA = a.Peek();
{ var varB = b.Peek();
Warn("Incompatible stack types: " + varA.StackType + " vs " + varB.StackType); if (varA.StackType == varB.StackType)
}
if (varA.StackType > varB.StackType)
{ {
output.Add(varA); // The stack types match, so we can merge the variables.
// every store to varB should also store to varA unionFind.Merge(varA, varB);
stackMismatchPairs.Add((varB, varA));
} }
else else
{ {
output.Add(varB); Debug.Assert(varA.StackType < varB.StackType);
// every store to varA should also store to varB if (!IsValidTypeStackTypeMerge(varA.StackType, varB.StackType))
stackMismatchPairs.Add((varA, varB)); {
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<ILVariable> a, ImmutableStack<ILVariable> b) /// <summary>
/// Inserts a copy from varA to varB (with conversion) at the end of <paramref name="block"/>.
/// If the block ends with a branching instruction, the copy is inserted before the branching instruction.
/// </summary>
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) // Branch instruction mustn't be initializing varA.
return false; Debug.Assert(!block.Instructions[insertionPosition - 1].HasFlag(InstructionFlags.MayWriteLocals));
a = a.Pop(); insertionPosition--;
b = b.Pop();
} }
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) if (stackType1 == StackType.I && stackType2 == StackType.I4)
return true; return true;
@ -375,42 +431,66 @@ namespace ICSharpCode.Decompiler.IL
/// <summary> /// <summary>
/// Stores the given stack for a branch to `offset`. /// 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)
/// </summary> /// </summary>
void StoreStackForOffset(int offset, ref ImmutableStack<ILVariable> stack) ImportedBlock StoreStackForOffset(int offset, ImmutableStack<ILVariable> stack)
{ {
if (stackByOffset.TryGetValue(offset, out var existing)) if (blocksByOffset.TryGetValue(offset, out var existing))
{ {
stack = MergeStacks(existing, stack); bool wasImported = existing.ImportStarted;
if (stack != existing) if (existing.MergeStackTypes(stack) && wasImported)
stackByOffset[offset] = stack; {
// If the stack changed, we need to re-import the block.
importQueue.Enqueue(existing);
}
return existing;
} }
else else
{ {
stackByOffset.Add(offset, stack); ImportedBlock newBlock = new ImportedBlock(offset, stack);
blocksByOffset.Add(offset, newBlock);
importQueue.Enqueue(newBlock);
return newBlock;
} }
} }
void ReadInstructions(CancellationToken cancellationToken) void ReadInstructions(CancellationToken cancellationToken)
{ {
reader.Reset(); reader.Reset();
StoreStackForOffset(0, ImmutableStack<ILVariable>.Empty);
ILParser.SetBranchTargets(ref reader, isBranchTarget); ILParser.SetBranchTargets(ref reader, isBranchTarget);
reader.Reset();
PrepareBranchTargetsAndStacksForExceptionHandlers(); 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) while (reader.RemainingBytes > 0)
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
int start = reader.Offset; int start = reader.Offset;
if (isBranchTarget[start])
{
FlushExpressionStack();
StoreStackForOffset(start, ref currentStack);
}
currentInstructionStart = start; currentInstructionStart = start;
bool startedWithEmptyStack = CurrentStackIsEmpty(); bool startedWithEmptyStack = CurrentStackIsEmpty();
DecodedInstruction decodedInstruction; DecodedInstruction decodedInstruction;
@ -432,9 +512,9 @@ namespace ICSharpCode.Decompiler.IL
{ {
// Flush to avoid re-ordering of side-effects // Flush to avoid re-ordering of side-effects
FlushExpressionStack(); 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 // If this instruction is the first in a new block, avoid it being inlined
// into the next instruction. // into the next instruction.
@ -442,39 +522,39 @@ namespace ICSharpCode.Decompiler.IL
// detect block starts, and doesn't search nested instructions. // detect block starts, and doesn't search nested instructions.
FlushExpressionStack(); FlushExpressionStack();
} }
if (inst.HasDirectFlag(InstructionFlags.EndPointUnreachable))
if ((!decodedInstruction.PushedOnExpressionStack && IsSequencePointInstruction(inst)) || startedWithEmptyStack)
{ {
FlushExpressionStack(); this.SequencePointCandidates.Add(inst.StartILOffset);
if (stackByOffset.TryGetValue(end, out var stackAfterEnd))
{
currentStack = stackAfterEnd;
}
else
{
currentStack = ImmutableStack<ILVariable>.Empty;
}
nextInstructionBeginsNewBlock = true;
} }
else
if (inst.HasDirectFlag(InstructionFlags.EndPointUnreachable))
{ {
nextInstructionBeginsNewBlock = inst.HasFlag(InstructionFlags.MayBranch); break; // end of block, don't parse following instructions if they are unreachable
} }
else if (isBranchTarget[end] || inst.HasFlag(InstructionFlags.MayBranch))
if ((!decodedInstruction.PushedOnExpressionStack && IsSequencePointInstruction(inst)) || startedWithEmptyStack)
{ {
this.SequencePointCandidates.Add(inst.StartILOffset); break; // end of block (we'll create fall through)
} }
} }
// Finalize block
FlushExpressionStack(); FlushExpressionStack();
block.Block.AddILRange(new Interval(block.StartILOffset, reader.Offset));
var visitor = new CollectStackVariablesVisitor(unionFind); if (!block.Block.HasFlag(InstructionFlags.EndPointUnreachable))
for (int i = 0; i < instructionBuilder.Count; i++)
{ {
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() private bool CurrentStackIsEmpty()
@ -488,9 +568,7 @@ namespace ICSharpCode.Decompiler.IL
foreach (var eh in body.ExceptionRegions) foreach (var eh in body.ExceptionRegions)
{ {
// Always mark the start of the try block as a "branch target". // Always mark the start of the try block as a "branch target".
// We don't actually need to store the stack state here, // We need to ensure that we put a block boundary there.
// but we need to ensure that no ILInstructions are inlined
// into the try-block.
isBranchTarget[eh.TryOffset] = true; isBranchTarget[eh.TryOffset] = true;
ImmutableStack<ILVariable> ehStack; ImmutableStack<ILVariable> ehStack;
@ -520,25 +598,23 @@ namespace ICSharpCode.Decompiler.IL
if (eh.FilterOffset != -1) if (eh.FilterOffset != -1)
{ {
isBranchTarget[eh.FilterOffset] = true; isBranchTarget[eh.FilterOffset] = true;
StoreStackForOffset(eh.FilterOffset, ref ehStack); StoreStackForOffset(eh.FilterOffset, ehStack);
} }
if (eh.HandlerOffset != -1) if (eh.HandlerOffset != -1)
{ {
isBranchTarget[eh.HandlerOffset] = true; 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 || if (instruction.OpCode is OpCode.Nop
(instructionBuilder.Count > 0 or OpCode.Call
&& instructionBuilder.Last().OpCode is OpCode.Call or OpCode.CallIndirect
or OpCode.CallIndirect or OpCode.CallVirt)
or OpCode.CallVirt))
{ {
return true; return true;
} }
else else
@ -547,39 +623,6 @@ namespace ICSharpCode.Decompiler.IL
} }
} }
void InsertStackAdjustments()
{
if (stackMismatchPairs.Count == 0)
return;
var dict = new MultiDictionary<ILVariable, ILVariable>();
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<ILInstruction>();
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;
}
/// <summary> /// <summary>
/// Debugging helper: writes the decoded instruction stream interleaved with the inferred evaluation stack layout. /// Debugging helper: writes the decoded instruction stream interleaved with the inferred evaluation stack layout.
/// </summary> /// </summary>
@ -588,39 +631,24 @@ namespace ICSharpCode.Decompiler.IL
{ {
Init(method, body, genericContext); Init(method, body, genericContext);
ReadInstructions(cancellationToken); 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(" [");
{ bool isFirstElement = true;
output.Write(" "); foreach (var element in importBlock.InputStack)
inst.WriteTo(output, new ILAstWritingOptions());
output.WriteLine();
continue;
}
if (stackByOffset.TryGetValue(inst.StartILOffset, out var stack))
{ {
output.Write(" ["); if (isFirstElement)
bool isFirstElement = true; isFirstElement = false;
foreach (var element in stack) else
{ output.Write(", ");
if (isFirstElement) output.WriteLocalReference(element.Name, element);
isFirstElement = false; output.Write(":");
else output.Write(element.StackType);
output.Write(", ");
output.WriteLocalReference(element.Name, element);
output.Write(":");
output.Write(element.StackType);
}
output.Write(']');
output.WriteLine();
} }
if (isBranchTarget[inst.StartILOffset]) output.Write(']');
output.Write('*'); output.WriteLine();
else
output.Write(' '); importBlock.Block.WriteTo(output, new ILAstWritingOptions());
output.WriteLocalReference("IL_" + inst.StartILOffset.ToString("x4"), inst.StartILOffset, isDefinition: true);
output.Write(": ");
inst.WriteTo(output, new ILAstWritingOptions());
output.WriteLine(); output.WriteLine();
} }
new Disassembler.MethodBodyDisassembler(output, cancellationToken) { DetectControlStructure = false } new Disassembler.MethodBodyDisassembler(output, cancellationToken) { DetectControlStructure = false }
@ -636,7 +664,7 @@ namespace ICSharpCode.Decompiler.IL
Init(method, body, genericContext); Init(method, body, genericContext);
ReadInstructions(cancellationToken); ReadInstructions(cancellationToken);
var blockBuilder = new BlockBuilder(body, variableByExceptionHandler, compilation); 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); var function = new ILFunction(this.method, body.GetCodeSize(), this.genericContext, mainContainer, kind);
function.Variables.AddRange(parameterVariables); function.Variables.AddRange(parameterVariables);
function.Variables.AddRange(localVariables); function.Variables.AddRange(localVariables);
@ -1187,7 +1215,7 @@ namespace ICSharpCode.Decompiler.IL
return currentStack.Peek().StackType; return currentStack.Peek().StackType;
} }
class CollectStackVariablesVisitor : ILVisitor<ILInstruction> sealed class CollectStackVariablesVisitor : ILVisitor<ILInstruction>
{ {
readonly UnionFind<ILVariable> unionFind; readonly UnionFind<ILVariable> unionFind;
internal readonly HashSet<ILVariable> variables = new HashSet<ILVariable>(); internal readonly HashSet<ILVariable> variables = new HashSet<ILVariable>();
@ -1216,7 +1244,7 @@ namespace ICSharpCode.Decompiler.IL
{ {
var variable = unionFind.Find(inst.Variable); var variable = unionFind.Find(inst.Variable);
if (variables.Add(variable)) if (variables.Add(variable))
variable.Name = "S_" + (variables.Count - 1); variable.Name = $"S_{variables.Count - 1}";
return new LdLoc(variable).WithILRange(inst); return new LdLoc(variable).WithILRange(inst);
} }
return inst; return inst;
@ -1229,7 +1257,7 @@ namespace ICSharpCode.Decompiler.IL
{ {
var variable = unionFind.Find(inst.Variable); var variable = unionFind.Find(inst.Variable);
if (variables.Add(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 new StLoc(variable, inst.Value).WithILRange(inst);
} }
return inst; return inst;
@ -1296,7 +1324,7 @@ namespace ICSharpCode.Decompiler.IL
return Cast(inst, expectedType, Warnings, reader.Offset); return Cast(inst, expectedType, Warnings, reader.Offset);
} }
internal static ILInstruction Cast(ILInstruction inst, StackType expectedType, List<string> warnings, int ilOffset) internal static ILInstruction Cast(ILInstruction inst, StackType expectedType, List<string>? warnings, int ilOffset)
{ {
if (expectedType != inst.ResultType) if (expectedType != inst.ResultType)
{ {
@ -1913,11 +1941,13 @@ namespace ICSharpCode.Decompiler.IL
} }
} }
void MarkBranchTarget(int targetILOffset) void MarkBranchTarget(int targetILOffset, bool isFallThrough = false)
{ {
FlushExpressionStack(); FlushExpressionStack();
Debug.Assert(isBranchTarget[targetILOffset]); Debug.Assert(isFallThrough || isBranchTarget[targetILOffset]);
StoreStackForOffset(targetILOffset, ref currentStack); var targetBlock = StoreStackForOffset(targetILOffset, currentStack);
Debug.Assert(currentBlock != null);
currentBlock.OutgoingEdges.Add((targetBlock, currentStack));
} }
/// <summary> /// <summary>
@ -1928,6 +1958,7 @@ namespace ICSharpCode.Decompiler.IL
/// </summary> /// </summary>
private void FlushExpressionStack() private void FlushExpressionStack()
{ {
Debug.Assert(currentBlock != null);
foreach (var inst in expressionStack) foreach (var inst in expressionStack)
{ {
Debug.Assert(inst.ResultType != StackType.Void); Debug.Assert(inst.ResultType != StackType.Void);
@ -1935,7 +1966,7 @@ namespace ICSharpCode.Decompiler.IL
var v = new ILVariable(VariableKind.StackSlot, type, inst.ResultType); var v = new ILVariable(VariableKind.StackSlot, type, inst.ResultType);
v.HasGeneratedName = true; v.HasGeneratedName = true;
currentStack = currentStack.Push(v); currentStack = currentStack.Push(v);
instructionBuilder.Add(new StLoc(v, inst).WithILRange(inst)); currentBlock.Block.Instructions.Add(new StLoc(v, inst).WithILRange(inst));
} }
expressionStack.Clear(); expressionStack.Clear();
} }

Loading…
Cancel
Save