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(); + } + } +}