Browse Source

Reconstruct try-finally blocks in yield return decompiler

pull/728/merge
Daniel Grunwald 9 years ago
parent
commit
5621101436
  1. 36
      ICSharpCode.Decompiler/CSharp/StatementBuilder.cs
  2. 3
      ICSharpCode.Decompiler/FlowAnalysis/DataFlowVisitor.cs
  3. 1
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
  4. 23
      ICSharpCode.Decompiler/IL/BlockBuilder.cs
  5. 6
      ICSharpCode.Decompiler/IL/ControlFlow/ControlFlowGraph.cs
  6. 11
      ICSharpCode.Decompiler/IL/ControlFlow/ControlFlowSimplification.cs
  7. 28
      ICSharpCode.Decompiler/IL/ControlFlow/ExitPoints.cs
  8. 2
      ICSharpCode.Decompiler/IL/ControlFlow/StateRangeAnalysis.cs
  9. 189
      ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs
  10. 8
      ICSharpCode.Decompiler/IL/ILReader.cs
  11. 83
      ICSharpCode.Decompiler/IL/Instructions.cs
  12. 5
      ICSharpCode.Decompiler/IL/Instructions.tt
  13. 11
      ICSharpCode.Decompiler/IL/Instructions/Leave.cs
  14. 11
      ICSharpCode.Decompiler/IL/Instructions/PatternMatching.cs
  15. 115
      ICSharpCode.Decompiler/IL/Instructions/Return.cs
  16. 151
      ICSharpCode.Decompiler/Tests/TestCases/Pretty/YieldReturn.cs
  17. 6
      ICSharpCode.Decompiler/Util/CollectionExtensions.cs

36
ICSharpCode.Decompiler/CSharp/StatementBuilder.cs

@ -148,7 +148,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -148,7 +148,7 @@ namespace ICSharpCode.Decompiler.CSharp
{
if (inst.TargetContainer == breakTarget)
return new BreakStatement();
if (inst.TargetContainer.SlotInfo == ILFunction.BodySlot) {
if (inst.IsLeavingFunction) {
if (currentFunction.IsIterator)
return new YieldBreakStatement();
else
@ -174,9 +174,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -174,9 +174,7 @@ namespace ICSharpCode.Decompiler.CSharp
protected internal override Statement VisitReturn(Return inst)
{
if (inst.ReturnValue == null)
return new ReturnStatement();
return new ReturnStatement(exprBuilder.Translate(inst.ReturnValue).ConvertTo(currentMethod.ReturnType, exprBuilder));
return new ReturnStatement(exprBuilder.Translate(inst.Value).ConvertTo(currentMethod.ReturnType, exprBuilder));
}
protected internal override Statement VisitYieldReturn(YieldReturn inst)
@ -298,7 +296,9 @@ namespace ICSharpCode.Decompiler.CSharp @@ -298,7 +296,9 @@ namespace ICSharpCode.Decompiler.CSharp
// and set the continueTarget to correctly convert 'br incrementBlock' instructions to continue;
Block incrementBlock = null;
if (container.EntryPoint.IncomingEdgeCount == 2) {
incrementBlock = container.Blocks.SingleOrDefault(b => b.Instructions.Last().MatchBranch(container.EntryPoint) && b.Instructions.All(IsSimpleStatement));
incrementBlock = container.Blocks.SingleOrDefault(
b => b.Instructions.Last().MatchBranch(container.EntryPoint)
&& b.Instructions.SkipLast(1).All(IsSimpleStatement));
if (incrementBlock != null)
continueTarget = incrementBlock;
}
@ -354,18 +354,23 @@ namespace ICSharpCode.Decompiler.CSharp @@ -354,18 +354,23 @@ namespace ICSharpCode.Decompiler.CSharp
stmt.Remove();
return new WhileStatement(conditionExpr, blockStatement);
}
/// <summary>
/// Gets whether the statement is 'simple' (usable as for loop iterator):
/// Currently we only accept calls and assignments.
/// </summary>
private static bool IsSimpleStatement(ILInstruction inst)
{
switch (inst) {
case IfInstruction i:
case SwitchInstruction s:
case TryCatch t:
case TryFault fa:
case TryFinally fi:
return false;
default:
switch (inst.OpCode) {
case OpCode.Call:
case OpCode.CallVirt:
case OpCode.NewObj:
case OpCode.StLoc:
case OpCode.StObj:
case OpCode.CompoundAssignmentInstruction:
return true;
default:
return false;
}
}
@ -431,7 +436,8 @@ namespace ICSharpCode.Decompiler.CSharp @@ -431,7 +436,8 @@ namespace ICSharpCode.Decompiler.CSharp
if (leave.ChildIndex != block.Instructions.Count - 1 || block.FinalInstruction.OpCode != OpCode.Nop)
return false;
BlockContainer container = (BlockContainer)block.Parent;
return block.ChildIndex == container.Blocks.Count - 1;
return block.ChildIndex == container.Blocks.Count - 1
&& container == leave.TargetContainer;
}
}
}

3
ICSharpCode.Decompiler/FlowAnalysis/DataFlowVisitor.cs

@ -430,8 +430,7 @@ namespace ICSharpCode.Decompiler.FlowAnalysis @@ -430,8 +430,7 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
protected internal override void VisitReturn(Return inst)
{
if (inst.ReturnValue != null)
inst.ReturnValue.AcceptVisitor(this);
inst.Value.AcceptVisitor(this);
MarkUnreachable();
}

1
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -320,7 +320,6 @@ @@ -320,7 +320,6 @@
<Compile Include="IL\Instructions\Leave.cs" />
<Compile Include="IL\Instructions\MemoryInstructions.cs" />
<Compile Include="IL\Instructions\PatternMatching.cs" />
<Compile Include="IL\Instructions\Return.cs" />
<Compile Include="IL\Instructions\SimpleInstruction.cs" />
<Compile Include="IL\Instructions\SwitchInstruction.cs" />
<Compile Include="IL\Instructions\TryInstruction.cs" />

23
ICSharpCode.Decompiler/IL/BlockBuilder.cs

@ -107,10 +107,9 @@ namespace ICSharpCode.Decompiler.IL @@ -107,10 +107,9 @@ namespace ICSharpCode.Decompiler.IL
Block currentBlock;
Stack<BlockContainer> containerStack = new Stack<BlockContainer>();
public BlockContainer CreateBlocks(List<ILInstruction> instructions, BitArray incomingBranches)
public void CreateBlocks(BlockContainer mainContainer, List<ILInstruction> instructions, BitArray incomingBranches)
{
CreateContainerStructure();
var mainContainer = new BlockContainer();
mainContainer.ILRange = new Interval(0, body.CodeSize);
currentContainer = mainContainer;
@ -155,7 +154,6 @@ namespace ICSharpCode.Decompiler.IL @@ -155,7 +154,6 @@ namespace ICSharpCode.Decompiler.IL
FinalizeCurrentBlock(body.CodeSize, fallthrough: false);
containerStack.Clear();
ConnectBranches(mainContainer);
return mainContainer;
}
private void FinalizeCurrentBlock(int currentILOffset, bool fallthrough)
@ -170,19 +168,20 @@ namespace ICSharpCode.Decompiler.IL @@ -170,19 +168,20 @@ namespace ICSharpCode.Decompiler.IL
void ConnectBranches(ILInstruction inst)
{
switch (inst.OpCode) {
case OpCode.Branch:
var branch = (Branch)inst;
switch (inst) {
case Branch branch:
Debug.Assert(branch.TargetBlock == null);
branch.TargetBlock = FindBranchTarget(branch.TargetILOffset);
break;
case OpCode.Leave:
var leave = (Leave)inst;
Debug.Assert(leave.TargetContainer == null);
leave.TargetContainer = containerStack.Peek();
case Leave leave:
// ret (in void method) = leave(mainContainer)
// endfinally = leave(null)
if (leave.TargetContainer == null) {
// assign the finally/filter container
leave.TargetContainer = containerStack.Peek();
}
break;
case OpCode.BlockContainer:
var container = (BlockContainer)inst;
case BlockContainer container:
containerStack.Push(container);
foreach (var block in container.Blocks) {
ConnectBranches(block);

6
ICSharpCode.Decompiler/IL/ControlFlow/ControlFlowGraph.cs

@ -100,7 +100,11 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -100,7 +100,11 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
// Leave instructions (like other exits out of the container)
// are ignored for the CFG and dominance,
// but is relevant for HasReachableExit().
nodeHasDirectExitOutOfContainer.Set(i);
// However, a 'leave' that exits the whole function represents a void return,
// and is not considered a reachable exit (just like non-void returns).
if (!(leave.TargetContainer.Parent is ILFunction)) {
nodeHasDirectExitOutOfContainer.Set(i);
}
}
}
}

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

@ -64,12 +64,12 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -64,12 +64,12 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
Return ret = (Return)block.Instructions[1];
ILVariable v;
ILInstruction inst;
if (ret.ReturnValue != null && ret.ReturnValue.MatchLdLoc(out v)
if (ret.Value.MatchLdLoc(out v)
&& v.IsSingleDefinition && v.LoadCount == 1 && block.Instructions[0].MatchStLoc(v, out inst))
{
inst.AddILRange(ret.ReturnValue.ILRange);
inst.AddILRange(ret.Value.ILRange);
inst.AddILRange(block.Instructions[0].ILRange);
ret.ReturnValue = inst;
ret.Value = inst;
block.Instructions.RemoveAt(0);
}
}
@ -127,8 +127,9 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -127,8 +127,9 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
{
if (targetBlock.Instructions.Count != 1 || targetBlock.FinalInstruction.OpCode != OpCode.Nop)
return false;
var ret = targetBlock.Instructions[0] as Return;
return ret != null && (ret.ReturnValue == null || ret.ReturnValue.OpCode == OpCode.LdLoc);
var inst = targetBlock.Instructions[0];
return (inst is Return ret && ret.Value is LdLoc
|| inst is Leave leave && leave.IsLeavingFunction);
}
static bool CombineBlockWithNextBlock(BlockContainer container, Block block)

28
ICSharpCode.Decompiler/IL/ControlFlow/ExitPoints.cs

@ -26,7 +26,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -26,7 +26,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
/// Detect suitable exit points for BlockContainers.
///
/// An "exit point" is an instruction that causes control flow
/// to leave the container (a branch, leave or value-less return instruction).
/// to leave the container (a branch or leave instruction).
///
/// If an "exit point" instruction is placed immediately following a
/// block container, each equivalent exit point within the container
@ -49,10 +49,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -49,10 +49,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
{
static readonly Nop ExitNotYetDetermined = new Nop();
static readonly Nop NoExit = new Nop();
// The special ReturnExit value (indicating fall-through out of a void method)
// is considered a compatible exit point with all normal return instructions.
static readonly Return ReturnExit = new Return();
/// <summary>
/// Gets the next instruction after <paramref name="inst"/> is executed.
/// Returns NoExit when the next instruction cannot be identified;
@ -70,15 +67,13 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -70,15 +67,13 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
|| slot == PinnedRegion.BodySlot)
{
return GetExit(inst.Parent);
} else if (slot == ILFunction.BodySlot) {
return ReturnExit;
}
return NoExit;
}
/// <summary>
/// Returns true iff exit1 and exit2 are both exit instructions
/// (branch, leave, or return) and both represent the same exit.
/// (branch or leave) and both represent the same exit.
/// </summary>
internal static bool CompatibleExitInstruction(ILInstruction exit1, ILInstruction exit2)
{
@ -93,10 +88,6 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -93,10 +88,6 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
Leave leave1 = (Leave)exit1;
Leave leave2 = (Leave)exit2;
return leave1.TargetContainer == leave2.TargetContainer;
case OpCode.Return:
Return ret1 = (Return)exit1;
Return ret2 = (Return)exit2;
return ret1.ReturnValue == null && ret2.ReturnValue == null;
default:
return false;
}
@ -159,7 +150,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -159,7 +150,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
void HandleExit(ILInstruction inst)
{
if (currentExit == ExitNotYetDetermined && !(inst is Return)) {
if (currentExit == ExitNotYetDetermined && !(inst is Leave l && l.IsLeavingFunction)) {
currentExit = inst;
inst.ReplaceWith(new Leave(currentContainer) { ILRange = inst.ILRange });
} else if (CompatibleExitInstruction(inst, currentExit)) {
@ -178,16 +169,6 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -178,16 +169,6 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
{
HandleExit(inst);
}
protected internal override void VisitReturn(Return inst)
{
if (inst.ReturnValue == null) {
// only void returns are considered exit points
HandleExit(inst);
} else {
base.VisitReturn(inst);
}
}
}
/*
@ -212,7 +193,6 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -212,7 +193,6 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
void Visit(ILInstruction inst)
{
switch (inst.OpCode) {
case OpCode.Return:
case OpCode.Leave:
case OpCode.Branch:
if (DetectExitPoints.CompatibleExitInstruction(inst, exitPoint)) {

2
ICSharpCode.Decompiler/IL/ControlFlow/StateRangeAnalysis.cs

@ -169,7 +169,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -169,7 +169,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
return LongSet.Empty; // return Empty since we executed user code (the finally method)
default:
// User code - abort analysis
if (mode == StateRangeAnalysisMode.IteratorDispose && inst.OpCode != OpCode.Return) {
if (mode == StateRangeAnalysisMode.IteratorDispose && !(inst is Leave l && l.IsLeavingFunction)) {
throw new SymbolicAnalysisFailedException("Unexpected instruction in Iterator.Dispose()");
}
return LongSet.Empty;

189
ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs

@ -17,6 +17,7 @@ @@ -17,6 +17,7 @@
// DEALINGS IN THE SOFTWARE.
using ICSharpCode.Decompiler.CSharp;
using ICSharpCode.Decompiler.FlowAnalysis;
using ICSharpCode.Decompiler.IL.Transforms;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.Util;
@ -77,10 +78,18 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -77,10 +78,18 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
/// <remarks>Set in ConstructExceptionTable()</remarks>
Dictionary<IMethod, LongSet> finallyMethodToStateRange;
/// <summary>
/// For each finally method, stores the target state when entering the finally block,
/// and the decompiled code of the finally method body.
/// </summary>
readonly Dictionary<IMethod, (int? outerState, BlockContainer body)> decompiledFinallyMethods = new Dictionary<IMethod, (int? outerState, BlockContainer body)>();
/*
/// <summary>
/// List of blocks that change the iterator state on block entry.
/// </summary>
readonly List<(int state, Block block)> stateChanges = new List<(int state, Block block)>();
*/
#region Run() method
public void Run(ILFunction function, ILTransformContext context)
@ -95,7 +104,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -95,7 +104,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
this.currentField = null;
this.fieldToParameterMap.Clear();
this.finallyMethodToStateRange = null;
this.stateChanges.Clear();
this.decompiledFinallyMethods.Clear();
if (!MatchEnumeratorCreationPattern(function))
return;
BlockContainer newBody;
@ -123,7 +132,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -123,7 +132,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
// (though some may point to now-deleted blocks)
newBody.SortBlocks(deleteUnreachableBlocks: true);
context.Step("Reconstruct try-finally blocks", function);
DecompileFinallyBlocks();
ReconstructTryFinallyBlocks(newBody);
context.Step("Translate fields to local accesses", function);
@ -440,35 +449,34 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -440,35 +449,34 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
if (oldInst.MatchStFld(out var target, out var field, out var value) && target.MatchLdThis()) {
if (field.MemberDefinition.Equals(stateField)) {
if (value.MatchLdcI4(out int newState)) {
// On state change, break up the block (if necessary):
if (newBlock.Instructions.Count > 0) {
var newBlock2 = new Block();
newBlock2.ILRange = new Interval(oldInst.ILRange.Start, oldInst.ILRange.Start);
newBody.Blocks.Add(newBlock2);
newBlock.Instructions.Add(new Branch(newBlock2));
newBlock = newBlock2;
}
#if DEBUG
newBlock.Instructions.Add(new Nop { Comment = "iterator._state = " + newState });
#endif
stateChanges.Add((newState, newBlock));
// On state change, break up the block:
// (this allows us to consider each block individually for try-finally reconstruction)
newBlock = SplitBlock(newBlock, oldInst);
// We keep the state-changing instruction around (as first instruction of the new block)
// for reconstructing the try-finallys.
} else {
newBlock.Instructions.Add(new InvalidExpression("Assigned non-constant to iterator.state field") {
ILRange = oldInst.ILRange
});
continue; // don't copy over this instruction, but continue with the basic block
}
continue; // don't copy over this instruction, but continue with the basic block
} else if (field.MemberDefinition.Equals(currentField)) {
// create yield return
newBlock.Instructions.Add(new YieldReturn(value) { ILRange = oldInst.ILRange });
ConvertBranchAfterYieldReturn(newBlock, oldBlock, oldInst.ChildIndex);
break; // we're done with this basic block
}
} else if (oldInst is Call call && call.Arguments.Count == 1 && call.Arguments[0].MatchLdThis()
&& finallyMethodToStateRange.ContainsKey((IMethod)call.Method.MemberDefinition))
{
// Break up the basic block on a call to a finally method
// (this allows us to consider each block individually for try-finally reconstruction)
newBlock = SplitBlock(newBlock, oldInst);
} else if (oldInst.MatchReturn(out value)) {
if (value.MatchLdLoc(out var v)) {
ssaDefs.TryGetValue(v, out value);
}
if (value.MatchLdcI4(0)) {
if (value != null && value.MatchLdcI4(0)) {
// yield break
newBlock.Instructions.Add(new Leave(newBody) { ILRange = oldInst.ILRange });
} else {
@ -510,6 +518,18 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -510,6 +518,18 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
newBlock.Instructions.Add(MakeGoTo(newState));
}
Block SplitBlock(Block newBlock, ILInstruction oldInst)
{
if (newBlock.Instructions.Count > 0) {
var newBlock2 = new Block();
newBlock2.ILRange = new Interval(oldInst.ILRange.Start, oldInst.ILRange.Start);
newBody.Blocks.Add(newBlock2);
newBlock.Instructions.Add(new Branch(newBlock2));
newBlock = newBlock2;
}
return newBlock;
}
ILInstruction MakeGoTo(int v)
{
Block targetBlock = null;
@ -573,13 +593,146 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -573,13 +593,146 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
}
#endregion
#region DecompileFinallyBlocks
void DecompileFinallyBlocks()
{
foreach (var method in finallyMethodToStateRange.Keys) {
var function = CreateILAst((MethodDefinition)context.TypeSystem.GetCecil(method));
var body = (BlockContainer)function.Body;
var newState = GetNewState(body.EntryPoint);
if (newState != null) {
body.EntryPoint.Instructions.RemoveAt(0);
}
function.ReleaseRef(); // make body reusable outside of function
decompiledFinallyMethods.Add(method, (newState, body));
}
}
#endregion
#region Reconstruct try-finally blocks
private void ReconstructTryFinallyBlocks(BlockContainer newBody)
/// <summary>
/// Reconstruct try-finally blocks.
/// * The stateChanges (iterator._state = N;) tell us when to open a try-finally block
/// * The calls to the finally method tell us when to leave the try block.
///
/// There might be multiple stateChanges for a given try-finally block, e.g.
/// both the original entry point, and the target when leaving a nested block.
/// In proper C# code, the entry point of the try-finally will dominate all other code
/// in the try-block, so we can use dominance to find the proper entry point.
///
/// Precondition: the blocks in newBody are topologically sorted.
/// </summary>
void ReconstructTryFinallyBlocks(BlockContainer newBody)
{
// TODO
context.Stepper.Step("Reconstuct try-finally blocks");
var blockState = new int[newBody.Blocks.Count];
blockState[0] = -1;
var stateToContainer = new Dictionary<int, BlockContainer>();
stateToContainer.Add(-1, newBody);
// First, analyse the newBody: for each block, determine the active state number.
foreach (var block in newBody.Blocks) {
int oldState = blockState[block.ChildIndex];
BlockContainer container; // new container for the block
if (GetNewState(block) is int newState) {
// OK, state change
// Remove the state-changing instruction
block.Instructions.RemoveAt(0);
if (!stateToContainer.TryGetValue(newState, out container)) {
// First time we see this state.
// This means we just found the entry point of a try block.
CreateTryBlock(block, newState);
// CreateTryBlock() wraps the contents of 'block' with a TryFinally.
// We thus need to put the block (which now contains the whole TryFinally)
// into the parent container.
// Assuming a state transition never enters more than one state at once,
// we can use stateToContainer[oldState] as parent.
container = stateToContainer[oldState];
}
} else {
// Because newBody is topologically sorted we because we just removed unreachable code,
// we can assume that blockState[] was already set for this block.
newState = oldState;
container = stateToContainer[oldState];
}
if (container != newBody) {
// Move the block into the container.
container.Blocks.Add(block);
// Keep the stale reference in newBody.Blocks for now, to avoid
// changing the ChildIndex of the other blocks while we use it
// to index the blockState array.
}
#if DEBUG
block.Instructions.Insert(0, new Nop { Comment = "state == " + newState });
#endif
// Propagate newState to successor blocks
foreach (var branch in block.Descendants.OfType<Branch>()) {
if (branch.TargetBlock.Parent == newBody) {
Debug.Assert(blockState[branch.TargetBlock.ChildIndex] == newState || blockState[branch.TargetBlock.ChildIndex] == 0);
blockState[branch.TargetBlock.ChildIndex] = newState;
}
}
}
newBody.Blocks.RemoveAll(b => b.Parent != newBody);
void CreateTryBlock(Block block, int state)
{
var finallyMethod = FindFinallyMethod(state);
Debug.Assert(finallyMethod != null);
// remove the method so that it doesn't get cause ambiguity when processing nested try-finally blocks
finallyMethodToStateRange.Remove(finallyMethod);
var tryBlock = new Block();
tryBlock.ILRange = block.ILRange;
tryBlock.Instructions.AddRange(block.Instructions);
var tryBlockContainer = new BlockContainer();
tryBlockContainer.Blocks.Add(tryBlock);
stateToContainer.Add(state, tryBlockContainer);
ILInstruction finallyBlock;
if (decompiledFinallyMethods.TryGetValue(finallyMethod, out var decompiledMethod)) {
finallyBlock = decompiledMethod.body;
} else {
finallyBlock = new InvalidBranch("Missing decompiledFinallyMethod");
}
block.Instructions.Clear();
block.Instructions.Add(new TryFinally(tryBlockContainer, finallyBlock));
}
IMethod FindFinallyMethod(int state)
{
IMethod foundMethod = null;
foreach (var (method, stateRange) in finallyMethodToStateRange) {
if (stateRange.Contains(state)) {
if (foundMethod == null)
foundMethod = method;
else
Debug.Fail("Ambiguous finally method for state " + state);
}
}
return foundMethod;
}
}
// Gets the state that is transitioned to at the start of the block
int? GetNewState(Block block)
{
if (block.Instructions[0].MatchStFld(out var target, out var field, out var value)
&& target.MatchLdThis()
&& field.MemberDefinition.Equals(stateField)
&& value.MatchLdcI4(out int newState))
{
return newState;
} else if (block.Instructions[0] is Call call
&& call.Arguments.Count == 1 && call.Arguments[0].MatchLdThis()
&& decompiledFinallyMethods.TryGetValue((IMethod)call.Method.MemberDefinition, out var finallyMethod))
{
return finallyMethod.outerState;
}
return null;
}
#endregion
}
}

8
ICSharpCode.Decompiler/IL/ILReader.cs

@ -53,6 +53,7 @@ namespace ICSharpCode.Decompiler.IL @@ -53,6 +53,7 @@ namespace ICSharpCode.Decompiler.IL
ILVariable[] parameterVariables;
ILVariable[] localVariables;
BitArray isBranchTarget;
BlockContainer mainContainer;
List<ILInstruction> instructionBuilder;
// Dictionary that stores stacks for each IL instruction
@ -78,6 +79,7 @@ namespace ICSharpCode.Decompiler.IL @@ -78,6 +79,7 @@ namespace ICSharpCode.Decompiler.IL
v.HasInitialValue = true;
}
}
mainContainer = new BlockContainer();
this.instructionBuilder = new List<ILInstruction>();
this.isBranchTarget = new BitArray(body.CodeSize);
this.stackByOffset = new Dictionary<int, System.Collections.Immutable.ImmutableStack<ILVariable>>();
@ -292,8 +294,8 @@ namespace ICSharpCode.Decompiler.IL @@ -292,8 +294,8 @@ namespace ICSharpCode.Decompiler.IL
Init(body);
ReadInstructions(cancellationToken);
var blockBuilder = new BlockBuilder(body, typeSystem, variableByExceptionHandler);
var container = blockBuilder.CreateBlocks(instructionBuilder, isBranchTarget);
var function = new ILFunction(body.Method, container);
blockBuilder.CreateBlocks(mainContainer, instructionBuilder, isBranchTarget);
var function = new ILFunction(body.Method, mainContainer);
CollectionExtensions.AddRange(function.Variables, parameterVariables);
CollectionExtensions.AddRange(function.Variables, localVariables);
CollectionExtensions.AddRange(function.Variables, stackVariables);
@ -920,7 +922,7 @@ namespace ICSharpCode.Decompiler.IL @@ -920,7 +922,7 @@ namespace ICSharpCode.Decompiler.IL
private ILInstruction Return()
{
if (methodReturnStackType == StackType.Void)
return new IL.Return();
return new IL.Leave(mainContainer);
else
return new IL.Return(Pop(methodReturnStackType));
}

83
ICSharpCode.Decompiler/IL/Instructions.cs

@ -113,7 +113,7 @@ namespace ICSharpCode.Decompiler.IL @@ -113,7 +113,7 @@ namespace ICSharpCode.Decompiler.IL
LdMemberToken,
/// <summary>Allocates space in the stack frame</summary>
LocAlloc,
/// <summary>Returns from the current method or lambda.</summary>
/// <summary>Returns from the current method or lambda. Only used when returning a value; void returns are represented using a 'leave' instruction.</summary>
Return,
/// <summary>Load address of instance field</summary>
LdFlda,
@ -2443,10 +2443,77 @@ namespace ICSharpCode.Decompiler.IL @@ -2443,10 +2443,77 @@ namespace ICSharpCode.Decompiler.IL
}
namespace ICSharpCode.Decompiler.IL
{
/// <summary>Returns from the current method or lambda.</summary>
/// <summary>Returns from the current method or lambda. Only used when returning a value; void returns are represented using a 'leave' instruction.</summary>
public sealed partial class Return : ILInstruction
{
public Return(ILInstruction value) : base(OpCode.Return)
{
this.Value = value;
}
public static readonly SlotInfo ValueSlot = new SlotInfo("Value", canInlineInto: true);
ILInstruction value;
public ILInstruction Value {
get { return this.value; }
set {
ValidateChild(value);
SetChildInstruction(ref this.value, value, 0);
}
}
protected sealed override int GetChildCount()
{
return 1;
}
protected sealed override ILInstruction GetChild(int index)
{
switch (index) {
case 0:
return this.value;
default:
throw new IndexOutOfRangeException();
}
}
protected sealed override void SetChild(int index, ILInstruction value)
{
switch (index) {
case 0:
this.Value = value;
break;
default:
throw new IndexOutOfRangeException();
}
}
protected sealed override SlotInfo GetChildSlot(int index)
{
switch (index) {
case 0:
return ValueSlot;
default:
throw new IndexOutOfRangeException();
}
}
public sealed override ILInstruction Clone()
{
var clone = (Return)ShallowClone();
clone.Value = this.value.Clone();
return clone;
}
public override StackType ResultType { get { return StackType.Void; } }
protected override InstructionFlags ComputeFlags()
{
return value.Flags | InstructionFlags.MayBranch | InstructionFlags.EndPointUnreachable;
}
public override InstructionFlags DirectFlags {
get {
return InstructionFlags.MayBranch | InstructionFlags.EndPointUnreachable;
}
}
public override void WriteTo(ITextOutput output)
{
output.Write(OpCode);
output.Write('(');
this.value.WriteTo(output);
output.Write(')');
}
public override void AcceptVisitor(ILVisitor visitor)
{
visitor.VisitReturn(this);
@ -2462,7 +2529,7 @@ namespace ICSharpCode.Decompiler.IL @@ -2462,7 +2529,7 @@ namespace ICSharpCode.Decompiler.IL
protected internal override bool PerformMatch(ILInstruction other, ref Patterns.Match match)
{
var o = other as Return;
return o != null && this.hasArgument == o.hasArgument && (!hasArgument || this.ReturnValue.PerformMatch(o.ReturnValue, ref match));
return o != null && this.value.PerformMatch(o.value, ref match);
}
}
}
@ -5018,6 +5085,16 @@ namespace ICSharpCode.Decompiler.IL @@ -5018,6 +5085,16 @@ namespace ICSharpCode.Decompiler.IL
argument = default(ILInstruction);
return false;
}
public bool MatchReturn(out ILInstruction value)
{
var inst = this as Return;
if (inst != null) {
value = inst.Value;
return true;
}
value = default(ILInstruction);
return false;
}
public bool MatchLdFlda(out ILInstruction target, out IField field)
{
var inst = this as LdFlda;

5
ICSharpCode.Decompiler/IL/Instructions.tt

@ -158,9 +158,8 @@ @@ -158,9 +158,8 @@
CustomClassName("LdMemberToken"), NoArguments, HasMemberOperand, ResultType("O")),
new OpCode("localloc", "Allocates space in the stack frame",
CustomClassName("LocAlloc"), Unary, ResultType("I"), MayThrow),
new OpCode("ret", "Returns from the current method or lambda.",
CustomClassName("Return"), CustomConstructor, CustomComputeFlags, MayBranch, UnconditionalBranch,
MatchCondition("this.hasArgument == o.hasArgument && (!hasArgument || this.ReturnValue.PerformMatch(o.ReturnValue, ref match))")),
new OpCode("ret", "Returns from the current method or lambda. Only used when returning a value; void returns are represented using a 'leave' instruction.",
CustomClassName("Return"), CustomArguments("value"), MayBranch, UnconditionalBranch),
new OpCode("ldflda", "Load address of instance field",
CustomClassName("LdFlda"), CustomArguments("target"), MayThrowIfNotDelayed, HasFieldOperand, ResultType("Ref")),

11
ICSharpCode.Decompiler/IL/Instructions/Leave.cs

@ -81,6 +81,17 @@ namespace ICSharpCode.Decompiler.IL @@ -81,6 +81,17 @@ namespace ICSharpCode.Decompiler.IL
get { return targetContainer != null ? targetContainer.EntryPoint.Label : string.Empty; }
}
/// <summary>
/// Gets whether the leave instruction is leaving the whole ILFunction.
/// (TargetContainer == main container of the function).
///
/// This is only valid for functions returning void (representing value-less "return;"),
/// and for iterators (representing "yield break;").
/// </summary>
public bool IsLeavingFunction {
get { return targetContainer?.Parent is ILFunction; }
}
internal override void CheckInvariant(ILPhase phase)
{
base.CheckInvariant(phase);

11
ICSharpCode.Decompiler/IL/Instructions/PatternMatching.cs

@ -111,17 +111,6 @@ namespace ICSharpCode.Decompiler.IL @@ -111,17 +111,6 @@ namespace ICSharpCode.Decompiler.IL
var inst = this as Leave;
return inst != null && inst.TargetContainer == targetContainer;
}
public bool MatchReturn(out ILInstruction returnValue)
{
if (this is Return ret) {
returnValue = ret.ReturnValue;
return returnValue != null;
} else {
returnValue = null;
return false;
}
}
public bool MatchIfInstruction(out ILInstruction condition, out ILInstruction trueInst, out ILInstruction falseInst)
{

115
ICSharpCode.Decompiler/IL/Instructions/Return.cs

@ -1,115 +0,0 @@ @@ -1,115 +0,0 @@
// Copyright (c) 2014 Daniel Grunwald
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Diagnostics;
namespace ICSharpCode.Decompiler.IL
{
partial class Return
{
public static readonly SlotInfo ReturnValueSlot = new SlotInfo("ReturnValue", canInlineInto: true);
readonly bool hasArgument;
ILInstruction returnValue;
/// <summary>
/// The value to return. Null if this return statement is within a void method.
/// </summary>
public ILInstruction ReturnValue {
get { return returnValue; }
set {
if (hasArgument) {
ValidateChild(value);
SetChildInstruction(ref returnValue, value, 0);
} else {
Debug.Assert(value == null);
}
}
}
public Return() : base(OpCode.Return)
{
}
public Return(ILInstruction argument) : base(OpCode.Return)
{
this.hasArgument = true;
this.ReturnValue = argument;
}
public override ILInstruction Clone()
{
Return clone = (Return)ShallowClone();
if (hasArgument)
clone.ReturnValue = returnValue.Clone();
return clone;
}
protected override InstructionFlags ComputeFlags()
{
InstructionFlags flags = InstructionFlags.MayBranch | InstructionFlags.EndPointUnreachable;
if (hasArgument) {
flags |= returnValue.Flags;
}
return flags;
}
public override InstructionFlags DirectFlags {
get {
return InstructionFlags.MayBranch | InstructionFlags.EndPointUnreachable;
}
}
public override void WriteTo(ITextOutput output)
{
output.Write(OpCode);
if (returnValue != null) {
output.Write('(');
returnValue.WriteTo(output);
output.Write(')');
}
}
protected override int GetChildCount()
{
return hasArgument ? 1 : 0;
}
protected override ILInstruction GetChild(int index)
{
if (index == 0 && hasArgument)
return returnValue;
else
throw new IndexOutOfRangeException();
}
protected override void SetChild(int index, ILInstruction value)
{
if (index == 0 && hasArgument)
ReturnValue = value;
else
throw new IndexOutOfRangeException();
}
protected override SlotInfo GetChildSlot(int index)
{
return ReturnValueSlot;
}
}
}

151
ICSharpCode.Decompiler/Tests/TestCases/Pretty/YieldReturn.cs

@ -190,5 +190,156 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -190,5 +190,156 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
Console.WriteLine("outer finally");
}
}
public static IEnumerable<int> YieldBreakInCatch()
{
yield return 0;
try {
Console.WriteLine("In Try");
} catch {
// yield return is not allowed in catch, but yield break is
yield break;
}
yield return 1;
}
public static IEnumerable<int> YieldBreakInCatchInTryFinally()
{
try {
yield return 0;
try {
Console.WriteLine("In Try");
} catch {
// yield return is not allowed in catch, but yield break is
yield break;
}
} finally {
Console.WriteLine("Finally");
}
}
public static IEnumerable<int> YieldBreakInTryCatchInTryFinally()
{
try {
yield return 0;
try {
Console.WriteLine("In Try");
yield break;
} catch {
Console.WriteLine("Catch");
}
} finally {
Console.WriteLine("Finally");
}
}
public static IEnumerable<int> YieldBreakInTryFinallyInTryFinally()
{
try {
yield return 0;
try {
Console.WriteLine("In Try");
yield break;
} finally {
Console.WriteLine("Inner Finally");
}
} finally {
Console.WriteLine("Finally");
}
}
public static IEnumerable<int> UnconditionalThrowInTryFinally()
{
// Here, MoveNext() doesn't call the finally methods at all
// (only indirectly via Dispose())
try {
yield return 0;
throw new NotImplementedException();
} finally {
Console.WriteLine("Finally");
}
}
public static IEnumerable<int> NestedTryFinallyStartingOnSamePosition()
{
// The first user IL instruction is already in 2 nested try blocks.
try {
try {
yield return 0;
} finally {
Console.WriteLine("Inner Finally");
}
} finally {
Console.WriteLine("Outer Finally");
}
}
public static IEnumerable<int> TryFinallyWithTwoExitPoints(bool b)
{
// The first user IL instruction is already in 2 nested try blocks.
try {
if (b) {
yield return 1;
goto exit1;
} else {
yield return 2;
goto exit2;
}
} finally {
Console.WriteLine("Finally");
}
exit1:
Console.WriteLine("Exit1");
yield break;
exit2:
Console.WriteLine("Exit2");
}
public static IEnumerable<int> TryFinallyWithTwoExitPointsInNestedTry(bool b)
{
// The first user IL instruction is already in 2 nested try blocks.
try {
yield return 1;
try {
if (b)
goto exit1;
else
goto exit2;
} catch {
Console.WriteLine("Catch");
}
} finally {
Console.WriteLine("Finally");
}
exit1:
Console.WriteLine("Exit1");
yield break;
exit2:
Console.WriteLine("Exit2");
}
public static IEnumerable<int> TryFinallyWithTwoExitPointsInNestedCatch(bool b)
{
// The first user IL instruction is already in 2 nested try blocks.
try {
yield return 1;
try {
Console.WriteLine("Nested Try");
} catch {
if (b)
goto exit1;
else
goto exit2;
}
} finally {
Console.WriteLine("Finally");
}
exit1:
Console.WriteLine("Exit1");
yield break;
exit2:
Console.WriteLine("Exit2");
}
}
}

6
ICSharpCode.Decompiler/Util/CollectionExtensions.cs

@ -1,5 +1,6 @@ @@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace ICSharpCode.Decompiler.Util
{
@ -15,6 +16,11 @@ namespace ICSharpCode.Decompiler.Util @@ -15,6 +16,11 @@ namespace ICSharpCode.Decompiler.Util
{
return new HashSet<T>(input);
}
public static IEnumerable<T> SkipLast<T>(this IReadOnlyCollection<T> input, int count)
{
return input.Skip(input.Count - count);
}
public static T PopOrDefault<T>(this Stack<T> stack)
{

Loading…
Cancel
Save