Browse Source

Merge pull request #2993 from icsharpcode/reimport

pull/2994/head
Siegfried Pammer 2 years ago committed by GitHub
parent
commit
d1e9f8bd77
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs
  2. 32
      ICSharpCode.Decompiler.Tests/TestCases/Correctness/StackTypes.il
  3. 14
      ICSharpCode.Decompiler.Tests/TestCases/ILPretty/EmptyBodies.cs
  4. 45
      ICSharpCode.Decompiler.Tests/TestCases/ILPretty/EmptyBodies.il
  5. 123
      ICSharpCode.Decompiler/IL/BlockBuilder.cs
  6. 10
      ICSharpCode.Decompiler/IL/ControlFlow/ControlFlowSimplification.cs
  7. 386
      ICSharpCode.Decompiler/IL/ILReader.cs

6
ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs

@ -263,6 +263,12 @@ namespace ICSharpCode.Decompiler.Tests @@ -263,6 +263,12 @@ namespace ICSharpCode.Decompiler.Tests
await Run();
}
[Test]
public async Task EmptyBodies()
{
await Run();
}
async Task Run([CallerMemberName] string testName = null, DecompilerSettings settings = null,
AssemblerOptions assemblerOptions = AssemblerOptions.Library)
{

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

@ -79,7 +79,34 @@ pointless: @@ -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: @@ -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: @@ -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: @@ -187,7 +212,6 @@ pointless:
conv.u
br after_if
}
*/
.method public static void RunInt32OrNativeMultiUse(int32 val)
{

14
ICSharpCode.Decompiler.Tests/TestCases/ILPretty/EmptyBodies.cs

@ -0,0 +1,14 @@ @@ -0,0 +1,14 @@
internal class EmptyBodies
{
public static void RetVoid()
{
}
public static int RetInt()
{
/*Error: Method body consists only of 'ret', but nothing is being returned. Decompiled assembly might be a reference assembly.*/;
}
public static void Nop()
{
/*Error: End of method reached without returning.*/;
}
}

45
ICSharpCode.Decompiler.Tests/TestCases/ILPretty/EmptyBodies.il

@ -0,0 +1,45 @@ @@ -0,0 +1,45 @@
#define CORE_ASSEMBLY "System.Runtime"
.assembly extern CORE_ASSEMBLY
{
.publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A ) // .?_....:
.ver 4:0:0:0
}
.class private auto ansi beforefieldinit EmptyBodies
extends [CORE_ASSEMBLY]System.Object
{
// I cannot test a truly empty body because the assembler will automatically add a ret instruction.
.method public hidebysig static void RetVoid () cil managed
{
.maxstack 8
ret
}
.method public hidebysig static int32 RetInt () cil managed
{
.maxstack 8
ret
}
.method public hidebysig static void Nop () cil managed
{
.maxstack 8
nop
}
.method public hidebysig specialname rtspecialname
instance void .ctor () cil managed
{
// Method begins at RVA 0x206e
// Code size 8 (0x8)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [System.Private.CoreLib]System.Object::.ctor()
IL_0006: nop
IL_0007: ret
} // end of method Example::.ctor
} // end of class EmptyBodies

123
ICSharpCode.Decompiler/IL/BlockBuilder.cs

@ -16,8 +16,8 @@ @@ -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; @@ -29,6 +29,11 @@ using ICSharpCode.Decompiler.Util;
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
{
readonly MethodBodyBlock body;
@ -64,7 +69,6 @@ namespace ICSharpCode.Decompiler.IL @@ -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 @@ -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,120 +120,52 @@ namespace ICSharpCode.Decompiler.IL @@ -117,120 +120,52 @@ namespace ICSharpCode.Decompiler.IL
}
int currentTryIndex;
TryInstruction nextTry;
TryInstruction? nextTry;
BlockContainer currentContainer;
Block currentBlock;
BlockContainer? currentContainer;
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();
mainContainer.SetILRange(new Interval(0, body.GetCodeSize()));
currentContainer = mainContainer;
if (instructions.Count == 0)
{
currentContainer.Blocks.Add(new Block {
Instructions = {
new InvalidBranch("Empty body found. Decompiled assembly might be a reference assembly.")
}
});
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)))
{
// Finish up the previous block
FinalizeCurrentBlock(start, fallthrough: true);
int start = block.StartILOffset;
// 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))
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)
{
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 +173,16 @@ namespace ICSharpCode.Decompiler.IL @@ -238,12 +173,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 +218,7 @@ namespace ICSharpCode.Decompiler.IL @@ -279,7 +218,7 @@ namespace ICSharpCode.Decompiler.IL
}
}
Block FindBranchTarget(int targetILOffset)
Block? FindBranchTarget(int targetILOffset)
{
foreach (var container in containerStack)
{
@ -291,7 +230,7 @@ namespace ICSharpCode.Decompiler.IL @@ -291,7 +230,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 +284,7 @@ namespace ICSharpCode.Decompiler.IL @@ -345,7 +284,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 +294,7 @@ namespace ICSharpCode.Decompiler.IL @@ -355,7 +294,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)
{

10
ICSharpCode.Decompiler/IL/ControlFlow/ControlFlowSimplification.cs

@ -258,7 +258,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -258,7 +258,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
if (targetBlock.StartILOffset < block.StartILOffset && IsDeadTrueStore(block))
{
// The C# compiler generates a dead store for the condition of while (true) loops.
block.Instructions.RemoveRange(block.Instructions.Count - 3, 2);
block.Instructions.RemoveAt(block.Instructions.Count - 2);
}
if (block.ILRangeIsEmpty)
@ -275,15 +275,13 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -275,15 +275,13 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
/// </summary>
private static bool IsDeadTrueStore(Block block)
{
if (block.Instructions.Count < 3)
if (block.Instructions.Count < 2)
return false;
if (!(block.Instructions.SecondToLastOrDefault() is StLoc deadStore && block.Instructions[block.Instructions.Count - 3] is StLoc tempStore))
if (!(block.Instructions.SecondToLastOrDefault() is StLoc deadStore))
return false;
if (!(deadStore.Variable.LoadCount == 0 && deadStore.Variable.AddressCount == 0))
return false;
if (!(deadStore.Value.MatchLdLoc(tempStore.Variable) && tempStore.Variable.IsSingleDefinition && tempStore.Variable.LoadCount == 1))
return false;
return tempStore.Value.MatchLdcI4(1) && deadStore.Variable.Type.IsKnownType(KnownTypeCode.Boolean);
return deadStore.Value.MatchLdcI4(1) && deadStore.Variable.Type.IsKnownType(KnownTypeCode.Boolean);
}
}
}

386
ICSharpCode.Decompiler/IL/ILReader.cs

@ -46,6 +46,77 @@ namespace ICSharpCode.Decompiler.IL @@ -46,6 +46,77 @@ namespace ICSharpCode.Decompiler.IL
/// </remarks>
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 MetadataModule module;
readonly MetadataReader metadata;
@ -80,19 +151,17 @@ namespace ICSharpCode.Decompiler.IL @@ -80,19 +151,17 @@ namespace ICSharpCode.Decompiler.IL
StackType methodReturnStackType;
BlobReader reader;
ImmutableStack<ILVariable> currentStack = ImmutableStack<ILVariable>.Empty;
ImportedBlock? currentBlock;
List<ILInstruction> expressionStack = new List<ILInstruction>();
ILVariable[] parameterVariables = null!;
ILVariable[] localVariables = null!;
BitSet isBranchTarget = null!;
BlockContainer mainContainer = null!;
List<ILInstruction> instructionBuilder = new List<ILInstruction>();
int currentInstructionStart;
// Dictionary that stores stacks for each IL instruction
Dictionary<int, ImmutableStack<ILVariable>> stackByOffset = new Dictionary<int, ImmutableStack<ILVariable>>();
Dictionary<int, ImportedBlock> blocksByOffset = new Dictionary<int, ImportedBlock>();
Queue<ImportedBlock> importQueue = new Queue<ImportedBlock>();
Dictionary<ExceptionRegion, ILVariable> variableByExceptionHandler = new Dictionary<ExceptionRegion, ILVariable>();
UnionFind<ILVariable> unionFind = null!;
List<(ILVariable, ILVariable)> stackMismatchPairs = new List<(ILVariable, ILVariable)>();
IEnumerable<ILVariable>? stackVariables;
void Init(MethodDefinitionHandle methodDefinitionHandle, MethodBodyBlock body, GenericContext genericContext)
@ -117,8 +186,6 @@ namespace ICSharpCode.Decompiler.IL @@ -117,8 +186,6 @@ namespace ICSharpCode.Decompiler.IL
this.reader = body.GetILReader();
this.currentStack = ImmutableStack<ILVariable>.Empty;
this.expressionStack.Clear();
this.unionFind = new UnionFind<ILVariable>();
this.stackMismatchPairs.Clear();
this.methodReturnStackType = method.ReturnType.GetStackType();
InitParameterVariables();
localVariables = InitLocalVariables();
@ -128,9 +195,9 @@ namespace ICSharpCode.Decompiler.IL @@ -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 @@ -284,82 +351,71 @@ namespace ICSharpCode.Decompiler.IL
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)
foreach (var (outgoing, stack) in block.OutgoingEdges)
{
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())
var a = stack;
var b = outgoing.InputStack;
if (a.Count() != b.Count())
{
// Let's not try to merge mismatched stacks.
Warn("Incompatible stack heights: " + a.Count() + " vs " + b.Count());
return a;
Warnings.Add($"IL_{block.Block.EndILOffset:x4}->IL{outgoing.StartILOffset:x4}: Incompatible stack heights: {a.Count()} vs {b.Count()}");
continue;
}
else
{
// The more complex case where the stacks don't match exactly.
var output = new List<ILVariable>();
while (!a.IsEmpty && !b.IsEmpty)
{
var varA = a.Peek();
var varB = b.Peek();
if (varA.StackType == varB.StackType)
{
// The stack types match, so we can merge the variables.
unionFind.Merge(varA, varB);
output.Add(varA);
}
else
{
Debug.Assert(varA.StackType < varB.StackType);
if (!IsValidTypeStackTypeMerge(varA.StackType, varB.StackType))
{
Warn("Incompatible stack types: " + varA.StackType + " vs " + varB.StackType);
}
if (varA.StackType > varB.StackType)
{
output.Add(varA);
// every store to varB should also store to varA
stackMismatchPairs.Add((varB, varA));
}
else
{
output.Add(varB);
// every store to varA should also store to varB
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();
}
// 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)
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,73 @@ namespace ICSharpCode.Decompiler.IL @@ -375,42 +431,73 @@ namespace ICSharpCode.Decompiler.IL
/// <summary>
/// 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>
void StoreStackForOffset(int offset, ref ImmutableStack<ILVariable> stack)
ImportedBlock StoreStackForOffset(int offset, ImmutableStack<ILVariable> stack)
{
if (blocksByOffset.TryGetValue(offset, out var existing))
{
if (stackByOffset.TryGetValue(offset, out var existing))
bool wasImported = existing.ImportStarted;
if (existing.MergeStackTypes(stack) && wasImported)
{
stack = MergeStacks(existing, stack);
if (stack != existing)
stackByOffset[offset] = stack;
// 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<ILVariable>.Empty);
if (reader.Length == 0)
{
blocksByOffset[0].Block.Instructions.Add(
new InvalidBranch("Empty body found. Decompiled assembly might be a reference assembly.")
);
return;
}
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,49 +519,50 @@ namespace ICSharpCode.Decompiler.IL @@ -432,49 +519,50 @@ 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)
if ((!decodedInstruction.PushedOnExpressionStack && IsSequencePointInstruction(inst)) || startedWithEmptyStack)
{
// If this instruction is the first in a new block, avoid it being inlined
// into the next instruction.
// This is necessary because the BlockBuilder uses inst.StartILOffset to
// detect block starts, and doesn't search nested instructions.
FlushExpressionStack();
this.SequencePointCandidates.Add(inst.StartILOffset);
}
if (inst.HasDirectFlag(InstructionFlags.EndPointUnreachable))
{
FlushExpressionStack();
if (stackByOffset.TryGetValue(end, out var stackAfterEnd))
{
currentStack = stackAfterEnd;
break; // end of block, don't parse following instructions if they are unreachable
}
else
else if (isBranchTarget[end] || inst.HasFlag(InstructionFlags.MayBranch))
{
currentStack = ImmutableStack<ILVariable>.Empty;
break; // end of block (we'll create fall through)
}
nextInstructionBeginsNewBlock = true;
}
else
// Finalize block
FlushExpressionStack();
block.Block.AddILRange(new Interval(block.StartILOffset, reader.Offset));
if (!block.Block.HasFlag(InstructionFlags.EndPointUnreachable))
{
nextInstructionBeginsNewBlock = inst.HasFlag(InstructionFlags.MayBranch);
// create fall through branch
ILInstruction branch;
if (reader.RemainingBytes > 0)
{
MarkBranchTarget(reader.Offset, isFallThrough: true);
branch = new Branch(reader.Offset);
}
if ((!decodedInstruction.PushedOnExpressionStack && IsSequencePointInstruction(inst)) || startedWithEmptyStack)
else
{
this.SequencePointCandidates.Add(inst.StartILOffset);
branch = new InvalidBranch("End of method reached without returning.");
}
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 = branch;
Debug.Assert(switchInst.HasFlag(InstructionFlags.EndPointUnreachable));
}
FlushExpressionStack();
var visitor = new CollectStackVariablesVisitor(unionFind);
for (int i = 0; i < instructionBuilder.Count; i++)
else
{
instructionBuilder[i] = instructionBuilder[i].AcceptVisitor(visitor);
block.Block.Instructions.Add(branch);
}
}
stackVariables = visitor.variables;
InsertStackAdjustments();
}
private bool CurrentStackIsEmpty()
@ -488,9 +576,7 @@ namespace ICSharpCode.Decompiler.IL @@ -488,9 +576,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<ILVariable> ehStack;
@ -520,25 +606,23 @@ namespace ICSharpCode.Decompiler.IL @@ -520,25 +606,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
if (instruction.OpCode is OpCode.Nop
or OpCode.Call
or OpCode.CallIndirect
or OpCode.CallVirt))
or OpCode.CallVirt)
{
return true;
}
else
@ -547,39 +631,6 @@ namespace ICSharpCode.Decompiler.IL @@ -547,39 +631,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>
/// Debugging helper: writes the decoded instruction stream interleaved with the inferred evaluation stack layout.
/// </summary>
@ -588,20 +639,11 @@ namespace ICSharpCode.Decompiler.IL @@ -588,20 +639,11 @@ namespace ICSharpCode.Decompiler.IL
{
Init(method, body, genericContext);
ReadInstructions(cancellationToken);
foreach (var inst in instructionBuilder)
{
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))
foreach (var importBlock in blocksByOffset.Values.OrderBy(b => b.StartILOffset))
{
output.Write(" [");
bool isFirstElement = true;
foreach (var element in stack)
foreach (var element in importBlock.InputStack)
{
if (isFirstElement)
isFirstElement = false;
@ -613,14 +655,8 @@ namespace ICSharpCode.Decompiler.IL @@ -613,14 +655,8 @@ namespace ICSharpCode.Decompiler.IL
}
output.Write(']');
output.WriteLine();
}
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());
importBlock.Block.WriteTo(output, new ILAstWritingOptions());
output.WriteLine();
}
new Disassembler.MethodBodyDisassembler(output, cancellationToken) { DetectControlStructure = false }
@ -636,7 +672,7 @@ namespace ICSharpCode.Decompiler.IL @@ -636,7 +672,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 +1223,7 @@ namespace ICSharpCode.Decompiler.IL @@ -1187,7 +1223,7 @@ namespace ICSharpCode.Decompiler.IL
return currentStack.Peek().StackType;
}
class CollectStackVariablesVisitor : ILVisitor<ILInstruction>
sealed class CollectStackVariablesVisitor : ILVisitor<ILInstruction>
{
readonly UnionFind<ILVariable> unionFind;
internal readonly HashSet<ILVariable> variables = new HashSet<ILVariable>();
@ -1216,7 +1252,7 @@ namespace ICSharpCode.Decompiler.IL @@ -1216,7 +1252,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 +1265,7 @@ namespace ICSharpCode.Decompiler.IL @@ -1229,7 +1265,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 +1332,7 @@ namespace ICSharpCode.Decompiler.IL @@ -1296,7 +1332,7 @@ namespace ICSharpCode.Decompiler.IL
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)
{
@ -1451,10 +1487,19 @@ namespace ICSharpCode.Decompiler.IL @@ -1451,10 +1487,19 @@ namespace ICSharpCode.Decompiler.IL
private ILInstruction Return()
{
if (methodReturnStackType == StackType.Void)
{
return new IL.Leave(mainContainer);
}
else if (currentInstructionStart == 0)
{
Debug.Assert(expressionStack.Count == 0 && currentStack.IsEmpty);
return new InvalidBranch("Method body consists only of 'ret', but nothing is being returned. Decompiled assembly might be a reference assembly.");
}
else
{
return new IL.Leave(mainContainer, Pop(methodReturnStackType));
}
}
private ILInstruction DecodeLdstr()
{
@ -1913,11 +1958,13 @@ namespace ICSharpCode.Decompiler.IL @@ -1913,11 +1958,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));
}
/// <summary>
@ -1928,6 +1975,7 @@ namespace ICSharpCode.Decompiler.IL @@ -1928,6 +1975,7 @@ namespace ICSharpCode.Decompiler.IL
/// </summary>
private void FlushExpressionStack()
{
Debug.Assert(currentBlock != null);
foreach (var inst in expressionStack)
{
Debug.Assert(inst.ResultType != StackType.Void);
@ -1935,7 +1983,7 @@ namespace ICSharpCode.Decompiler.IL @@ -1935,7 +1983,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();
}

Loading…
Cancel
Save