Browse Source

Initial support for yield-return decompilation in assemblies compiled with the mono compiler.

pull/863/head
Daniel Grunwald 8 years ago
parent
commit
d550390f4d
  1. 3
      ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
  2. 1
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
  3. 5
      ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs
  4. 73
      ICSharpCode.Decompiler/IL/ControlFlow/StateRangeAnalysis.cs
  5. 2
      ICSharpCode.Decompiler/IL/ControlFlow/SymbolicExecution.cs
  6. 194
      ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs
  7. 18
      ICSharpCode.Decompiler/IL/ILReader.cs
  8. 2
      ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs
  9. 98
      ICSharpCode.Decompiler/Util/LongDict.cs

3
ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs

@ -666,6 +666,9 @@ namespace ICSharpCode.Decompiler.CSharp @@ -666,6 +666,9 @@ namespace ICSharpCode.Decompiler.CSharp
body.Add(new YieldBreakStatement());
}
RemoveAttribute(entityDecl, new TopLevelTypeName("System.Runtime.CompilerServices", "IteratorStateMachineAttribute"));
if (function.StateMachineCompiledWithMono) {
RemoveAttribute(entityDecl, new TopLevelTypeName("System.Diagnostics", "DebuggerHiddenAttribute"));
}
}
if (function.IsAsync) {
entityDecl.Modifiers |= Modifiers.Async;

1
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -282,6 +282,7 @@ @@ -282,6 +282,7 @@
<Compile Include="IL\Transforms\TransformCollectionAndObjectInitializers.cs" />
<Compile Include="Output\TextTokenWriter.cs" />
<Compile Include="Util\KeyComparer.cs" />
<Compile Include="Util\LongDict.cs" />
<Compile Include="Util\UnicodeNewline.cs" />
<Compile Include="FlowAnalysis\ControlFlowNode.cs" />
<Compile Include="FlowAnalysis\DataFlowVisitor.cs" />

5
ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs

@ -488,6 +488,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -488,6 +488,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
sra.CancellationToken = context.CancellationToken;
sra.doFinallyBodies = doFinallyBodies;
sra.AssignStateRanges(container, LongSet.Universe);
var stateToBlockMap = sra.GetBlockStateSetMapping(container);
foreach (var block in container.Blocks) {
context.CancellationToken.ThrowIfCancellationRequested();
@ -495,7 +496,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -495,7 +496,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
// This is likely an 'await' block
if (AnalyzeAwaitBlock(block, out var awaiterVar, out var awaiterField, out var state)) {
block.Instructions.Add(new Await(new LdLoca(awaiterVar)));
Block targetBlock = sra.FindBlock(container, state);
Block targetBlock = stateToBlockMap.GetOrDefault(state);
if (targetBlock != null) {
block.Instructions.Add(new Branch(targetBlock));
} else {
@ -509,7 +510,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -509,7 +510,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
}
}
// Skip the state dispatcher and directly jump to the initial state
var entryPoint = sra.FindBlock(container, initialState);
var entryPoint = stateToBlockMap.GetOrDefault(initialState);
if (entryPoint != null) {
container.Blocks.Insert(0, new Block {
Instructions = {

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

@ -59,6 +59,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -59,6 +59,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
readonly internal Dictionary<IMethod, LongSet> finallyMethodToStateRange; // used only for IteratorDispose
internal ILVariable doFinallyBodies;
internal ILVariable skipFinallyBodies;
public StateRangeAnalysis(StateRangeAnalysisMode mode, IField stateField, ILVariable cachedStateVar = null)
{
@ -72,7 +73,23 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -72,7 +73,23 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
if (cachedStateVar != null)
evalContext.AddStateVariable(cachedStateVar);
}
/// <summary>
/// Creates a new StateRangeAnalysis with the same settings, including any cached state vars
/// discovered by this analysis.
/// However, the new analysis has a fresh set of result ranges.
/// </summary>
internal StateRangeAnalysis CreateNestedAnalysis()
{
var sra = new StateRangeAnalysis(mode, stateField);
sra.doFinallyBodies = this.doFinallyBodies;
sra.skipFinallyBodies = this.skipFinallyBodies;
foreach (var v in this.evalContext.StateVariables) {
sra.evalContext.AddStateVariable(v);
}
return sra;
}
/// <summary>
/// Assign state ranges for all blocks within 'inst'.
/// </summary>
@ -110,7 +127,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -110,7 +127,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
Debug.Assert(stateRange.IsEmpty || !instInBlock.HasFlag(InstructionFlags.EndPointUnreachable));
}
return stateRange;
case TryFinally tryFinally:
case TryFinally tryFinally when mode == StateRangeAnalysisMode.IteratorDispose:
var afterTry = AssignStateRanges(tryFinally.TryBlock, stateRange);
// really finally should start with 'stateRange.UnionWith(afterTry)', but that's
// equal to 'stateRange'.
@ -151,11 +168,12 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -151,11 +168,12 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
return LongSet.Empty;
case Nop nop:
return stateRange;
case StLoc stloc when stloc.Variable == doFinallyBodies:
case StLoc stloc when stloc.Variable == doFinallyBodies || stloc.Variable == skipFinallyBodies:
// pre-roslyn async/await uses a generated 'bool doFinallyBodies';
// do not treat this as user code.
// Mono also does this for yield-return.
return stateRange;
case StLoc stloc when stloc.Variable.IsSingleDefinition:
case StLoc stloc:
val = evalContext.Eval(stloc.Value);
if (val.Type == SymbolicValueType.State && val.Constant == 0) {
evalContext.AddStateVariable(stloc.Variable);
@ -169,6 +187,18 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -169,6 +187,18 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
// the C# compiler puts the call to a finally method outside the try-finally block.
finallyMethodToStateRange.Add((IMethod)call.Method.MemberDefinition, stateRange);
return LongSet.Empty; // return Empty since we executed user code (the finally method)
case StObj stobj when mode == StateRangeAnalysisMode.IteratorMoveNext:
{
if (stobj.MatchStFld(out var target, out var field, out var value)
&& target.MatchLdThis() && field.MemberDefinition == stateField && value.MatchLdcI4(-1))
{
// Mono resets the state field during MoveNext();
// don't consider this user code.
return stateRange;
} else {
goto default;
}
}
default:
// User code - abort analysis
if (mode == StateRangeAnalysisMode.IteratorDispose && !(inst is Leave l && l.IsLeavingFunction)) {
@ -186,22 +216,31 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -186,22 +216,31 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
ranges.Add(block, stateRange);
}
public IEnumerable<(Block, LongSet)> GetBlockStateSetMapping(BlockContainer container)
/// <summary>
/// Gets a mapping from states to blocks.
///
/// Within the given container (which should be the container that was analyzed),
/// the mapping prefers the last block.
/// Blocks outside of the given container are preferred over blocks within the container.
/// </summary>
public LongDict<Block> GetBlockStateSetMapping(BlockContainer container)
{
foreach (var block in container.Blocks) {
if (ranges.TryGetValue(block, out var stateSet))
yield return (block, stateSet);
}
}
return LongDict.Create(GetMapping());
public Block FindBlock(BlockContainer container, int newState)
{
Block targetBlock = null;
foreach (var (block, stateSet) in GetBlockStateSetMapping(container)) {
if (stateSet.Contains(newState))
targetBlock = block;
IEnumerable<(LongSet, Block)> GetMapping()
{
// First, consider container exits:
foreach (var (block, states) in ranges) {
if (block.Parent != container)
yield return (states, block);
}
// Then blocks within the container:
foreach (var block in container.Blocks.Reverse()) {
if (ranges.TryGetValue(block, out var states)) {
yield return (states, block);
}
}
}
return targetBlock;
}
}
}

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

@ -109,6 +109,8 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -109,6 +109,8 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
if (!stateVariables.Contains(v))
stateVariables.Add(v);
}
public IEnumerable<ILVariable> StateVariables { get => stateVariables; }
static readonly SymbolicValue Failed = new SymbolicValue(SymbolicValueType.Unknown);

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

@ -57,12 +57,15 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -57,12 +57,15 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
/// <remarks>Set in MatchEnumeratorCreationPattern()</remarks>
MethodDefinition enumeratorCtor;
/// <remarks>Set in MatchEnumeratorCreationPattern()</remarks>
bool isCompiledWithMono;
/// <summary>The dispose method of the compiler-generated enumerator class.</summary>
/// <remarks>Set in ConstructExceptionTable()</remarks>
MethodDefinition disposeMethod;
/// <summary>The field in the compiler-generated class holding the current state of the state machine</summary>
/// <remarks>Set in AnalyzeCtor()</remarks>
/// <remarks>Set in AnalyzeCtor() for MS, MatchEnumeratorCreationPattern() or AnalyzeMoveNext() for Mono</remarks>
IField stateField;
/// <summary>The backing field of the 'Current' property in the compiler-generated class</summary>
@ -83,12 +86,17 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -83,12 +86,17 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
/// and the decompiled code of the finally method body.
/// </summary>
readonly Dictionary<IMethod, (int? outerState, ILFunction function)> decompiledFinallyMethods = new Dictionary<IMethod, (int? outerState, ILFunction body)>();
/// <summary>
/// Temporary stores for 'yield break'.
/// </summary>
readonly List<StLoc> returnStores = new List<StLoc>();
/// <summary>
/// Local bool variable in MoveNext() that signifies whether to skip finally bodies.
/// </summary>
ILVariable skipFinallyBodies;
#region Run() method
public void Run(ILFunction function, ILTransformContext context)
{
@ -104,6 +112,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -104,6 +112,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
this.finallyMethodToStateRange = null;
this.decompiledFinallyMethods.Clear();
this.returnStores.Clear();
this.skipFinallyBodies = null;
if (!MatchEnumeratorCreationPattern(function))
return;
BlockContainer newBody;
@ -119,6 +128,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -119,6 +128,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
context.Step("Replacing body with MoveNext() body", function);
function.IsIterator = true;
function.StateMachineCompiledWithMono = true;
function.Body = newBody;
// register any locals used in newBody
function.Variables.AddRange(newBody.Descendants.OfType<IInstructionWithVariableOperand>().Select(inst => inst.Variable).Distinct());
@ -136,12 +146,24 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -136,12 +146,24 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
// (though some may point to now-deleted blocks)
newBody.SortBlocks(deleteUnreachableBlocks: true);
DecompileFinallyBlocks();
ReconstructTryFinallyBlocks(function);
if (isCompiledWithMono) {
// mono has try-finally inline (like async on MS); we also need to sort nested blocks:
foreach (var nestedContainer in newBody.Blocks.SelectMany(c => c.Descendants).OfType<BlockContainer>()) {
nestedContainer.SortBlocks(deleteUnreachableBlocks: true);
}
} else {
DecompileFinallyBlocks();
ReconstructTryFinallyBlocks(function);
}
context.Step("Translate fields to local accesses", function);
TranslateFieldsToLocalAccess(function, function, fieldToParameterMap);
// On mono, we still need to remove traces of the state variable:
if (isCompiledWithMono && fieldToParameterMap.TryGetValue(stateField, out var stateVar)) {
returnStores.AddRange(stateVar.StoreInstructions.OfType<StLoc>());
}
if (returnStores.Count > 0) {
context.Step("Remove temporaries", function);
foreach (var store in returnStores) {
@ -170,26 +192,40 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -170,26 +192,40 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
if (body.Instructions.Count == 1) {
// No parameters passed to enumerator (not even 'this'):
// ret(newobj(...))
if (body.Instructions[0].MatchReturn(out newObj))
return MatchEnumeratorCreationNewObj(newObj);
else
if (!body.Instructions[0].MatchReturn(out newObj))
return false;
if (MatchEnumeratorCreationNewObj(newObj)) {
return true;
} else if (MatchMonoEnumeratorCreationNewObj(newObj)) {
isCompiledWithMono = true;
return true;
} else {
return false;
}
}
// If there's parameters passed to the helper class, the class instance is first
// stored in a variable, then the parameters are copied over, then the instance is returned.
int pos = 0;
// stloc(var_1, newobj(..))
if (!body.Instructions[0].MatchStLoc(out var var1, out newObj))
if (!body.Instructions[pos].MatchStLoc(out var var1, out newObj))
return false;
if (!MatchEnumeratorCreationNewObj(newObj))
if (MatchEnumeratorCreationNewObj(newObj)) {
pos++; // OK
isCompiledWithMono = false;
} else if (MatchMonoEnumeratorCreationNewObj(newObj)) {
pos++;
isCompiledWithMono = true;
} else {
return false;
int i;
for (i = 1; i < body.Instructions.Count; i++) {
}
for (; pos < body.Instructions.Count; pos++) {
// stfld(..., ldloc(var_1), ldloc(parameter))
// or (in structs): stfld(..., ldloc(var_1), ldobj(ldloc(this)))
if (!body.Instructions[i].MatchStFld(out var ldloc, out var storedField, out var value))
if (!body.Instructions[pos].MatchStFld(out var ldloc, out var storedField, out var value))
break;
if (!ldloc.MatchLdLoc(var1)) {
return false;
@ -205,18 +241,25 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -205,18 +241,25 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
}
// In debug builds, the compiler may copy the var1 into another variable (var2) before returning it.
if (i < body.Instructions.Count
&& body.Instructions[i].MatchStLoc(out var var2, out var ldlocForStloc2)
if (body.Instructions[pos].MatchStLoc(out var var2, out var ldlocForStloc2)
&& ldlocForStloc2.MatchLdLoc(var1)) {
// stloc(var_2, ldloc(var_1))
i++;
} else {
// in release builds, var1 is returned directly
var2 = var1;
pos++;
}
if (i < body.Instructions.Count
&& body.Instructions[i].MatchReturn(out var retVal)
&& retVal.MatchLdLoc(var2)) {
if (isCompiledWithMono) {
// Mono initializes the state field separately:
// (but not if it's left at the default value 0)
if (body.Instructions[pos].MatchStFld(out var target, out var field, out var value)
&& target.MatchLdLoc(var2 ?? var1)
&& (value.MatchLdcI4(-2) || value.MatchLdcI4(0)))
{
stateField = (IField)field.MemberDefinition;
isCompiledWithMono = true;
pos++;
}
}
if (body.Instructions[pos].MatchReturn(out var retVal)
&& retVal.MatchLdLoc(var2 ?? var1)) {
// ret(ldloc(var_2))
return true;
} else {
@ -256,6 +299,19 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -256,6 +299,19 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
&& IsCompilerGeneratorEnumerator(enumeratorType);
}
bool MatchMonoEnumeratorCreationNewObj(ILInstruction inst)
{
// mcs generates iterators that take no parameters in the ctor
if (!(inst is NewObj newObj))
return false;
if (newObj.Arguments.Count != 0)
return false;
enumeratorCtor = context.TypeSystem.GetCecil(newObj.Method) as MethodDefinition;
enumeratorType = enumeratorCtor?.DeclaringType;
return enumeratorType?.DeclaringType == currentType
&& IsCompilerGeneratorEnumerator(enumeratorType);
}
public static bool IsCompilerGeneratorEnumerator(TypeDefinition type)
{
if (!(type?.DeclaringType != null && type.IsCompilerGenerated()))
@ -286,7 +342,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -286,7 +342,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
stateField = (IField)field.MemberDefinition;
}
}
if (stateField == null)
if (stateField == null && !isCompiledWithMono)
throw new SymbolicAnalysisFailedException("Could not find stateField");
}
@ -387,6 +443,13 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -387,6 +443,13 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
void ConstructExceptionTable()
{
if (isCompiledWithMono) {
// On mono, we don't need to analyse Dispose() to reconstruct the try-finally structure.
disposeMethod = null;
finallyMethodToStateRange = null;
return;
}
disposeMethod = enumeratorType.Methods.FirstOrDefault(m => m.Name == "System.IDisposable.Dispose");
var function = CreateILAst(disposeMethod, context);
@ -398,6 +461,8 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -398,6 +461,8 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
[Conditional("DEBUG")]
void PrintFinallyMethodStateRanges(BlockContainer bc)
{
if (finallyMethodToStateRange == null)
return;
foreach (var (method, stateRange) in finallyMethodToStateRange) {
bc.Blocks[0].Instructions.Insert(0, new Nop {
Comment = method.Name + " in " + stateRange
@ -436,6 +501,43 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -436,6 +501,43 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
}
}
if (stateField == null) {
// With mono-compiled state machines, it's possible that we haven't discovered the state field
// yet because the compiler let it be implicitly initialized to 0.
// In this case, we must discover it from the first instruction in MoveNext():
if (body.EntryPoint.Instructions[0] is StLoc stloc
&& stloc.Value.MatchLdFld(out var target, out var field)
&& target.MatchLdThis() && field.Type.IsKnownType(KnownTypeCode.Int32)) {
stateField = (IField)field.MemberDefinition;
} else {
throw new SymbolicAnalysisFailedException("Could not find state field.");
}
}
skipFinallyBodies = null;
if (isCompiledWithMono) {
// Mono uses skipFinallyBodies; find out which variable that is:
foreach (var tryFinally in body.Descendants.OfType<TryFinally>()) {
if ((tryFinally.FinallyBlock as BlockContainer)?.EntryPoint.Instructions[0] is IfInstruction ifInst) {
if (ifInst.Condition.MatchLogicNot(out var arg) && arg.MatchLdLoc(out var v) && v.Type.IsKnownType(KnownTypeCode.Boolean)) {
bool isInitializedInEntryBlock = false;
for (int i = 0; i < 3; i++) {
if (body.EntryPoint.Instructions.ElementAtOrDefault(i) is StLoc stloc
&& stloc.Variable == v && stloc.Value.MatchLdcI4(0))
{
isInitializedInEntryBlock = true;
break;
}
}
if (isInitializedInEntryBlock) {
skipFinallyBodies = v;
break;
}
}
}
}
}
PropagateCopiesOfFields(body);
// Note: body may contain try-catch or try-finally statements that have nested block containers,
@ -443,10 +545,11 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -443,10 +545,11 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
// So for reconstructing the control flow, we only consider the blocks directly within body.
var rangeAnalysis = new StateRangeAnalysis(StateRangeAnalysisMode.IteratorMoveNext, stateField);
rangeAnalysis.skipFinallyBodies = skipFinallyBodies;
rangeAnalysis.CancellationToken = context.CancellationToken;
rangeAnalysis.AssignStateRanges(body, LongSet.Universe);
var newBody = ConvertBody(body, rangeAnalysis.GetBlockStateSetMapping(body));
var newBody = ConvertBody(body, rangeAnalysis);
moveNextFunction.Variables.Clear();
// release references from old moveNextFunction to instructions that were moved over to newBody
moveNextFunction.ReleaseRef();
@ -505,8 +608,9 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -505,8 +608,9 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
/// (on enter) this.state = N;
/// (on exit) this._finallyX();
/// </summary>
private BlockContainer ConvertBody(BlockContainer oldBody, IEnumerable<(Block, LongSet)> blockStateSets)
private BlockContainer ConvertBody(BlockContainer oldBody, StateRangeAnalysis rangeAnalysis)
{
var blockStateMap = rangeAnalysis.GetBlockStateSetMapping(oldBody);
BlockContainer newBody = new BlockContainer();
// create all new blocks so that they can be referenced by gotos
for (int blockIndex = 0; blockIndex < oldBody.Blocks.Count; blockIndex++) {
@ -545,6 +649,12 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -545,6 +649,12 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
// 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 is TryFinally tryFinally && isCompiledWithMono) {
// with mono, we have to recurse into try-finally blocks
var oldTryBlock = (BlockContainer)tryFinally.TryBlock;
var sra = rangeAnalysis.CreateNestedAnalysis();
sra.AssignStateRanges(oldTryBlock, LongSet.Universe);
tryFinally.TryBlock = ConvertBody(oldTryBlock, sra);
}
// copy over the instruction to the new block
newBlock.Instructions.Add(oldInst);
@ -570,8 +680,22 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -570,8 +680,22 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
newBlock.Instructions.Add(new InvalidBranch("Unable to find new state assignment for yield return"));
return;
}
if (!(oldBlock.Instructions[i + 2].MatchReturn(out var retVal)
&& retVal.MatchLdcI4(1))) {
int pos = i + 2;
if (oldBlock.Instructions[pos].MatchStLoc(skipFinallyBodies, out value)) {
if (!value.MatchLdcI4(1)) {
newBlock.Instructions.Add(new InvalidExpression {
ExpectedResultType = StackType.Void,
Message = "Unexpected assignment to skipFinallyBodies"
});
}
pos++;
}
if (oldBlock.Instructions[pos].MatchReturn(out var retVal) && retVal.MatchLdcI4(1)) {
// OK, found return directly after state assignment
} else if (oldBlock.Instructions[pos].MatchBranch(out var targetBlock)
&& targetBlock.Instructions[0].MatchReturn(out retVal) && retVal.MatchLdcI4(1)) {
// OK, jump to common return block (e.g. on Mono)
} else {
newBlock.Instructions.Add(new InvalidBranch("Unable to find 'return true' for yield return"));
return;
}
@ -592,15 +716,15 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -592,15 +716,15 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
ILInstruction MakeGoTo(int v)
{
Block targetBlock = null;
foreach (var (block, stateSet) in blockStateSets) {
if (stateSet.Contains(v))
targetBlock = block;
}
if (targetBlock != null)
return new Branch(newBody.Blocks[targetBlock.ChildIndex]);
else
Block targetBlock = blockStateMap.GetOrDefault(v);
if (targetBlock != null) {
if (targetBlock.Parent == oldBody)
return new Branch(newBody.Blocks[targetBlock.ChildIndex]);
else
return new Branch(targetBlock);
} else {
return new InvalidBranch("Could not find block for state " + v);
}
}
void UpdateBranchTargets(ILInstruction inst)

18
ICSharpCode.Decompiler/IL/ILReader.cs

@ -52,7 +52,7 @@ namespace ICSharpCode.Decompiler.IL @@ -52,7 +52,7 @@ namespace ICSharpCode.Decompiler.IL
StackType methodReturnStackType;
Cil.Instruction currentInstruction;
int nextInstructionIndex;
System.Collections.Immutable.ImmutableStack<ILVariable> currentStack;
ImmutableStack<ILVariable> currentStack;
ILVariable[] parameterVariables;
ILVariable[] localVariables;
BitArray isBranchTarget;
@ -60,7 +60,7 @@ namespace ICSharpCode.Decompiler.IL @@ -60,7 +60,7 @@ namespace ICSharpCode.Decompiler.IL
List<ILInstruction> instructionBuilder;
// Dictionary that stores stacks for each IL instruction
Dictionary<int, System.Collections.Immutable.ImmutableStack<ILVariable>> stackByOffset;
Dictionary<int, ImmutableStack<ILVariable>> stackByOffset;
Dictionary<Cil.ExceptionHandler, ILVariable> variableByExceptionHandler;
UnionFind<ILVariable> unionFind;
IEnumerable<ILVariable> stackVariables;
@ -86,7 +86,7 @@ namespace ICSharpCode.Decompiler.IL @@ -86,7 +86,7 @@ namespace ICSharpCode.Decompiler.IL
mainContainer = new BlockContainer();
this.instructionBuilder = new List<ILInstruction>();
this.isBranchTarget = new BitArray(body.CodeSize);
this.stackByOffset = new Dictionary<int, System.Collections.Immutable.ImmutableStack<ILVariable>>();
this.stackByOffset = new Dictionary<int, ImmutableStack<ILVariable>>();
this.variableByExceptionHandler = new Dictionary<Cil.ExceptionHandler, ILVariable>();
}
@ -185,7 +185,7 @@ namespace ICSharpCode.Decompiler.IL @@ -185,7 +185,7 @@ namespace ICSharpCode.Decompiler.IL
Debug.Fail(string.Format("IL_{0:x4}: {1}", currentInstruction.Offset, message));
}
void MergeStacks(System.Collections.Immutable.ImmutableStack<ILVariable> a, System.Collections.Immutable.ImmutableStack<ILVariable> b)
void MergeStacks(ImmutableStack<ILVariable> a, ImmutableStack<ILVariable> b)
{
var enum1 = a.GetEnumerator();
var enum2 = b.GetEnumerator();
@ -204,9 +204,9 @@ namespace ICSharpCode.Decompiler.IL @@ -204,9 +204,9 @@ namespace ICSharpCode.Decompiler.IL
}
}
void StoreStackForOffset(int offset, System.Collections.Immutable.ImmutableStack<ILVariable> stack)
void StoreStackForOffset(int offset, ImmutableStack<ILVariable> stack)
{
System.Collections.Immutable.ImmutableStack<ILVariable> existing;
ImmutableStack<ILVariable> existing;
if (stackByOffset.TryGetValue(offset, out existing)) {
MergeStacks(existing, stack);
} else {
@ -218,7 +218,7 @@ namespace ICSharpCode.Decompiler.IL @@ -218,7 +218,7 @@ namespace ICSharpCode.Decompiler.IL
{
// Fill isBranchTarget and branchStackDict based on exception handlers
foreach (var eh in body.ExceptionHandlers) {
System.Collections.Immutable.ImmutableStack<ILVariable> ehStack = null;
ImmutableStack<ILVariable> ehStack = null;
if (eh.HandlerType == Cil.ExceptionHandlerType.Catch || eh.HandlerType == Cil.ExceptionHandlerType.Filter) {
var v = new ILVariable(VariableKind.Exception, typeSystem.Resolve(eh.CatchType), eh.HandlerStart.Offset) {
Name = "E_" + eh.HandlerStart.Offset
@ -226,7 +226,7 @@ namespace ICSharpCode.Decompiler.IL @@ -226,7 +226,7 @@ namespace ICSharpCode.Decompiler.IL
variableByExceptionHandler.Add(eh, v);
ehStack = ImmutableStack.Create(v);
} else {
ehStack = System.Collections.Immutable.ImmutableStack<ILVariable>.Empty;
ehStack = ImmutableStack<ILVariable>.Empty;
}
if (eh.FilterStart != null) {
isBranchTarget[eh.FilterStart.Offset] = true;
@ -252,7 +252,7 @@ namespace ICSharpCode.Decompiler.IL @@ -252,7 +252,7 @@ namespace ICSharpCode.Decompiler.IL
instructionBuilder.Add(decodedInstruction);
if (decodedInstruction.HasDirectFlag(InstructionFlags.EndPointUnreachable)) {
if (!stackByOffset.TryGetValue(end, out currentStack)) {
currentStack = System.Collections.Immutable.ImmutableStack<ILVariable>.Empty;
currentStack = ImmutableStack<ILVariable>.Empty;
}
}
Debug.Assert(currentInstruction.Next == null || currentInstruction.Next.Offset == end);

2
ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs

@ -43,6 +43,8 @@ namespace ICSharpCode.Decompiler.IL @@ -43,6 +43,8 @@ namespace ICSharpCode.Decompiler.IL
/// </summary>
public bool IsIterator;
public bool StateMachineCompiledWithMono;
/// <summary>
/// Gets whether this function is async.
/// This flag gets set by the AsyncAwaitDecompiler.

98
ICSharpCode.Decompiler/Util/LongDict.cs

@ -0,0 +1,98 @@ @@ -0,0 +1,98 @@
// Copyright (c) 2017 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.Collections;
using System.Collections.Generic;
namespace ICSharpCode.Decompiler.Util
{
static class LongDict
{
public static LongDict<T> Create<T>(IEnumerable<(LongSet, T)> entries)
{
return new LongDict<T>(entries);
}
internal static readonly KeyComparer<LongInterval, long> StartComparer = KeyComparer.Create((LongInterval i) => i.Start);
}
/// <summary>
/// An immutable mapping from keys of type long to values of type T.
/// </summary>
struct LongDict<T> : IEnumerable<KeyValuePair<LongInterval, T>>
{
readonly LongInterval[] keys;
readonly T[] values;
/// <summary>
/// Creates a new LongDict from the given entries.
/// If there are multiple entries for the same long key,
/// the resulting LongDict will store the value from the first entry.
/// </summary>
public LongDict(IEnumerable<(LongSet, T)> entries)
{
LongSet available = LongSet.Universe;
var keys = new List<LongInterval>();
var values = new List<T>();
foreach (var (key, val) in entries) {
foreach (var interval in key.IntersectWith(available).Intervals) {
keys.Add(interval);
values.Add(val);
}
available = available.ExceptWith(key);
}
this.keys = keys.ToArray();
this.values = values.ToArray();
Array.Sort(this.keys, this.values, LongDict.StartComparer);
}
public bool TryGetValue(long key, out T value)
{
int pos = Array.BinarySearch(this.keys, new LongInterval(key, key), LongDict.StartComparer);
// If the element isn't found, BinarySearch returns the complement of "insertion position".
// We use this to find the previous element (if there wasn't any exact match).
if (pos < 0)
pos = ~pos - 1;
if (pos >= 0 && this.keys[pos].Contains(key)) {
value = this.values[pos];
return true;
}
value = default(T);
return false;
}
public T GetOrDefault(long key)
{
TryGetValue(key, out T val);
return val;
}
public IEnumerator<KeyValuePair<LongInterval, T>> GetEnumerator()
{
for (int i = 0; i < this.keys.Length; ++i) {
yield return new KeyValuePair<LongInterval, T>(this.keys[i], this.values[i]);
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
Loading…
Cancel
Save