diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
index 19be73a45..529256bda 100644
--- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
+++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
@@ -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;
diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
index 0d04041fd..bd4c8f531 100644
--- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
+++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
@@ -282,6 +282,7 @@
+
diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs b/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs
index 8a8a03f3a..051e6fe9a 100644
--- a/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs
+++ b/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs
@@ -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
// 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
}
}
// 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 = {
diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/StateRangeAnalysis.cs b/ICSharpCode.Decompiler/IL/ControlFlow/StateRangeAnalysis.cs
index 0a50c5bf8..475dcc872 100644
--- a/ICSharpCode.Decompiler/IL/ControlFlow/StateRangeAnalysis.cs
+++ b/ICSharpCode.Decompiler/IL/ControlFlow/StateRangeAnalysis.cs
@@ -59,6 +59,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
readonly internal Dictionary 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
if (cachedStateVar != null)
evalContext.AddStateVariable(cachedStateVar);
}
-
+
+ ///
+ /// 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.
+ ///
+ 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;
+ }
+
///
/// Assign state ranges for all blocks within 'inst'.
///
@@ -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
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
// 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
ranges.Add(block, stateRange);
}
- public IEnumerable<(Block, LongSet)> GetBlockStateSetMapping(BlockContainer container)
+ ///
+ /// 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.
+ ///
+ public LongDict 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;
}
}
}
diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/SymbolicExecution.cs b/ICSharpCode.Decompiler/IL/ControlFlow/SymbolicExecution.cs
index 96ca08445..dc0ac8db3 100644
--- a/ICSharpCode.Decompiler/IL/ControlFlow/SymbolicExecution.cs
+++ b/ICSharpCode.Decompiler/IL/ControlFlow/SymbolicExecution.cs
@@ -109,6 +109,8 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
if (!stateVariables.Contains(v))
stateVariables.Add(v);
}
+
+ public IEnumerable StateVariables { get => stateVariables; }
static readonly SymbolicValue Failed = new SymbolicValue(SymbolicValueType.Unknown);
diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs b/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs
index 50b4afbbc..c391af8c5 100644
--- a/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs
+++ b/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs
@@ -57,12 +57,15 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
/// Set in MatchEnumeratorCreationPattern()
MethodDefinition enumeratorCtor;
+ /// Set in MatchEnumeratorCreationPattern()
+ bool isCompiledWithMono;
+
/// The dispose method of the compiler-generated enumerator class.
/// Set in ConstructExceptionTable()
MethodDefinition disposeMethod;
/// The field in the compiler-generated class holding the current state of the state machine
- /// Set in AnalyzeCtor()
+ /// Set in AnalyzeCtor() for MS, MatchEnumeratorCreationPattern() or AnalyzeMoveNext() for Mono
IField stateField;
/// The backing field of the 'Current' property in the compiler-generated class
@@ -83,12 +86,17 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
/// and the decompiled code of the finally method body.
///
readonly Dictionary decompiledFinallyMethods = new Dictionary();
-
+
///
/// Temporary stores for 'yield break'.
///
readonly List returnStores = new List();
+ ///
+ /// Local bool variable in MoveNext() that signifies whether to skip finally bodies.
+ ///
+ ILVariable skipFinallyBodies;
+
#region Run() method
public void Run(ILFunction function, ILTransformContext context)
{
@@ -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
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().Select(inst => inst.Variable).Distinct());
@@ -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()) {
+ 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());
+ }
+
if (returnStores.Count > 0) {
context.Step("Remove temporaries", function);
foreach (var store in returnStores) {
@@ -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
}
// 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
&& 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
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
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
[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
}
}
+ 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()) {
+ 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
// 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
/// (on enter) this.state = N;
/// (on exit) this._finallyX();
///
- 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
// 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
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
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)
diff --git a/ICSharpCode.Decompiler/IL/ILReader.cs b/ICSharpCode.Decompiler/IL/ILReader.cs
index 0e3e25a8c..efde011f3 100644
--- a/ICSharpCode.Decompiler/IL/ILReader.cs
+++ b/ICSharpCode.Decompiler/IL/ILReader.cs
@@ -52,7 +52,7 @@ namespace ICSharpCode.Decompiler.IL
StackType methodReturnStackType;
Cil.Instruction currentInstruction;
int nextInstructionIndex;
- System.Collections.Immutable.ImmutableStack currentStack;
+ ImmutableStack currentStack;
ILVariable[] parameterVariables;
ILVariable[] localVariables;
BitArray isBranchTarget;
@@ -60,7 +60,7 @@ namespace ICSharpCode.Decompiler.IL
List instructionBuilder;
// Dictionary that stores stacks for each IL instruction
- Dictionary> stackByOffset;
+ Dictionary> stackByOffset;
Dictionary variableByExceptionHandler;
UnionFind unionFind;
IEnumerable stackVariables;
@@ -86,7 +86,7 @@ namespace ICSharpCode.Decompiler.IL
mainContainer = new BlockContainer();
this.instructionBuilder = new List();
this.isBranchTarget = new BitArray(body.CodeSize);
- this.stackByOffset = new Dictionary>();
+ this.stackByOffset = new Dictionary>();
this.variableByExceptionHandler = new Dictionary();
}
@@ -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 a, System.Collections.Immutable.ImmutableStack b)
+ void MergeStacks(ImmutableStack a, ImmutableStack b)
{
var enum1 = a.GetEnumerator();
var enum2 = b.GetEnumerator();
@@ -204,9 +204,9 @@ namespace ICSharpCode.Decompiler.IL
}
}
- void StoreStackForOffset(int offset, System.Collections.Immutable.ImmutableStack stack)
+ void StoreStackForOffset(int offset, ImmutableStack stack)
{
- System.Collections.Immutable.ImmutableStack existing;
+ ImmutableStack existing;
if (stackByOffset.TryGetValue(offset, out existing)) {
MergeStacks(existing, stack);
} else {
@@ -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 ehStack = null;
+ ImmutableStack 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
variableByExceptionHandler.Add(eh, v);
ehStack = ImmutableStack.Create(v);
} else {
- ehStack = System.Collections.Immutable.ImmutableStack.Empty;
+ ehStack = ImmutableStack.Empty;
}
if (eh.FilterStart != null) {
isBranchTarget[eh.FilterStart.Offset] = true;
@@ -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.Empty;
+ currentStack = ImmutableStack.Empty;
}
}
Debug.Assert(currentInstruction.Next == null || currentInstruction.Next.Offset == end);
diff --git a/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs b/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs
index f09728368..d1b73ab5a 100644
--- a/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs
+++ b/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs
@@ -43,6 +43,8 @@ namespace ICSharpCode.Decompiler.IL
///
public bool IsIterator;
+ public bool StateMachineCompiledWithMono;
+
///
/// Gets whether this function is async.
/// This flag gets set by the AsyncAwaitDecompiler.
diff --git a/ICSharpCode.Decompiler/Util/LongDict.cs b/ICSharpCode.Decompiler/Util/LongDict.cs
new file mode 100644
index 000000000..109b530e7
--- /dev/null
+++ b/ICSharpCode.Decompiler/Util/LongDict.cs
@@ -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 Create(IEnumerable<(LongSet, T)> entries)
+ {
+ return new LongDict(entries);
+ }
+
+ internal static readonly KeyComparer StartComparer = KeyComparer.Create((LongInterval i) => i.Start);
+ }
+
+ ///
+ /// An immutable mapping from keys of type long to values of type T.
+ ///
+ struct LongDict : IEnumerable>
+ {
+ readonly LongInterval[] keys;
+ readonly T[] values;
+
+ ///
+ /// 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.
+ ///
+ public LongDict(IEnumerable<(LongSet, T)> entries)
+ {
+ LongSet available = LongSet.Universe;
+ var keys = new List();
+ var values = new List();
+ 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> GetEnumerator()
+ {
+ for (int i = 0; i < this.keys.Length; ++i) {
+ yield return new KeyValuePair(this.keys[i], this.values[i]);
+ }
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+ }
+}