diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs b/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs index a2b386105..bd1acf794 100644 --- a/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs +++ b/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs @@ -70,6 +70,10 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow /// Set in AnalyzeCurrentProperty() IField currentField; + /// The disposing field of the compiler-generated enumerator class./summary> + /// Set in ConstructExceptionTable() for assembly compiled with Mono + IField disposingField; + /// Maps the fields of the compiler-generated class to the original parameters. /// Set in MatchEnumeratorCreationPattern() and ResolveIEnumerableIEnumeratorFieldMapping() readonly Dictionary fieldToParameterMap = new Dictionary(); @@ -112,6 +116,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow this.enumeratorCtor = default; this.stateField = null; this.currentField = null; + this.disposingField = null; this.fieldToParameterMap.Clear(); this.finallyMethodToStateRange = null; this.decompiledFinallyMethods.Clear(); @@ -147,22 +152,26 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow } context.Step("Delete unreachable blocks", function); - // Note: because this only deletes blocks outright, the 'stateChanges' entries remain valid - // (though some may point to now-deleted blocks) - newBody.SortBlocks(deleteUnreachableBlocks: true); 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 { + // We need to clean up nested blocks before the main block, so that edges from unreachable code + // in nested containers into the main container are removed before we clean up the main container. + } + // Note: because this only deletes blocks outright, the 'stateChanges' entries remain valid + // (though some may point to now-deleted blocks) + newBody.SortBlocks(deleteUnreachableBlocks: true); + + if (!isCompiledWithMono) { DecompileFinallyBlocks(); ReconstructTryFinallyBlocks(function); } context.Step("Translate fields to local accesses", function); - TranslateFieldsToLocalAccess(function, function, fieldToParameterMap); + TranslateFieldsToLocalAccess(function, function, fieldToParameterMap, isCompiledWithMono); CleanSkipFinallyBodies(function); @@ -459,18 +468,31 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow void ConstructExceptionTable() { if (isCompiledWithMono) { + disposeMethod = metadata.GetTypeDefinition(enumeratorType).GetMethods().FirstOrDefault(m => metadata.GetString(metadata.GetMethodDefinition(m).Name) == "Dispose"); + var function = CreateILAst(disposeMethod, context); + BlockContainer body = (BlockContainer)function.Body; + + for (var i = 0; (i < body.EntryPoint.Instructions.Count) && !(body.EntryPoint.Instructions[i] is Branch); i++) { + if (body.EntryPoint.Instructions[i] is StObj stobj + && stobj.MatchStFld(out var target, out var field, out var value) + && target.MatchLdThis() + && field.Type.IsKnownType(KnownTypeCode.Boolean) + && value.MatchLdcI4(1)) { + disposingField = (IField)field.MemberDefinition; + break; + } + } + // On mono, we don't need to analyse Dispose() to reconstruct the try-finally structure. - disposeMethod = default; finallyMethodToStateRange = default; - return; + } else { + // Non-Mono: analyze try-finally structure in Dispose() + disposeMethod = metadata.GetTypeDefinition(enumeratorType).GetMethods().FirstOrDefault(m => metadata.GetString(metadata.GetMethodDefinition(m).Name) == "System.IDisposable.Dispose"); + var function = CreateILAst(disposeMethod, context); + var rangeAnalysis = new StateRangeAnalysis(StateRangeAnalysisMode.IteratorDispose, stateField); + rangeAnalysis.AssignStateRanges(function.Body, LongSet.Universe); + finallyMethodToStateRange = rangeAnalysis.finallyMethodToStateRange; } - - disposeMethod = metadata.GetTypeDefinition(enumeratorType).GetMethods().FirstOrDefault(m => metadata.GetString(metadata.GetMethodDefinition(m).Name) == "System.IDisposable.Dispose"); - var function = CreateILAst(disposeMethod, context); - - var rangeAnalysis = new StateRangeAnalysis(StateRangeAnalysisMode.IteratorDispose, stateField); - rangeAnalysis.AssignStateRanges(function.Body, LongSet.Universe); - finallyMethodToStateRange = rangeAnalysis.finallyMethodToStateRange; } [Conditional("DEBUG")] @@ -633,7 +655,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow newBody.Blocks.Add(new Block { ILRange = oldBody.Blocks[blockIndex].ILRange }); } // convert contents of blocks - + for (int i = 0; i < oldBody.Blocks.Count; i++) { var oldBlock = oldBody.Blocks[i]; var newBlock = newBody.Blocks[i]; @@ -656,7 +678,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow } else if (field.MemberDefinition.Equals(currentField)) { // create yield return newBlock.Instructions.Add(new YieldReturn(value) { ILRange = oldInst.ILRange }); - ConvertBranchAfterYieldReturn(newBlock, oldBlock, oldInst.ChildIndex); + ConvertBranchAfterYieldReturn(newBlock, oldBlock, oldInst.ChildIndex + 1); break; // we're done with this basic block } } else if (oldInst is Call call && call.Arguments.Count == 1 && call.Arguments[0].MatchLdThis() @@ -687,16 +709,51 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow }); return newBody; - void ConvertBranchAfterYieldReturn(Block newBlock, Block oldBlock, int i) + void ConvertBranchAfterYieldReturn(Block newBlock, Block oldBlock, int pos) { - if (!(oldBlock.Instructions[i + 1].MatchStFld(out var target, out var field, out var value) + Block targetBlock; + if (isCompiledWithMono && disposingField != null) { + // Mono skips over the state assignment if 'this.disposing' is set: + // ... + // stfld $current(ldloc this, yield-expr) + // if (ldfld $disposing(ldloc this)) br IL_007c + // br targetBlock + // } + // + // Block targetBlock (incoming: 1) { + // stfld $PC(ldloc this, ldc.i4 1) + // br setSkipFinallyBodies + // } + // + // Block setSkipFinallyBodies (incoming: 2) { + // stloc skipFinallyBodies(ldc.i4 1) + // br returnBlock + // } + if (oldBlock.Instructions[pos].MatchIfInstruction(out var condition, out _) + && condition.MatchLdFld(out var condTarget, out var condField) + && condTarget.MatchLdThis() && condField.MemberDefinition.Equals(disposingField) + && oldBlock.Instructions[pos + 1].MatchBranch(out targetBlock) + && targetBlock.Parent == oldBlock.Parent) { + // Keep looking at the target block: + oldBlock = targetBlock; + pos = 0; + } + } + + if (oldBlock.Instructions[pos].MatchStFld(out var target, out var field, out var value) && target.MatchLdThis() && field.MemberDefinition == stateField - && value.MatchLdcI4(out int newState))) { + && value.MatchLdcI4(out int newState)) { + pos++; + } else { newBlock.Instructions.Add(new InvalidBranch("Unable to find new state assignment for yield return")); return; } - int pos = i + 2; + // Mono may have 'br setSkipFinallyBodies' here, so follow the branch + if (oldBlock.Instructions[pos].MatchBranch(out targetBlock) && targetBlock.Parent == oldBlock.Parent) { + oldBlock = targetBlock; + pos = 0; + } if (oldBlock.Instructions[pos].MatchStLoc(skipFinallyBodies, out value)) { if (!value.MatchLdcI4(1)) { newBlock.Instructions.Add(new InvalidExpression { @@ -706,9 +763,10 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow } 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) + } else if (oldBlock.Instructions[pos].MatchBranch(out targetBlock) && targetBlock.Instructions[0].MatchReturn(out retVal) && retVal.MatchLdcI4(1)) { // OK, jump to common return block (e.g. on Mono) } else { @@ -782,7 +840,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow /// /// Translates all field accesses in `function` to local variable accesses. /// - internal static void TranslateFieldsToLocalAccess(ILFunction function, ILInstruction inst, Dictionary fieldToVariableMap) + internal static void TranslateFieldsToLocalAccess(ILFunction function, ILInstruction inst, Dictionary fieldToVariableMap, bool isCompiledWithMono = false) { if (inst is LdFlda ldflda && ldflda.Target.MatchLdThis()) { var fieldDef = (IField)ldflda.Field.MemberDefinition; @@ -804,11 +862,11 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow } else { inst.ReplaceWith(new LdLoca(v) { ILRange = inst.ILRange }); } - } else if (inst.MatchLdThis()) { + } else if (!isCompiledWithMono && inst.MatchLdThis()) { inst.ReplaceWith(new InvalidExpression("stateMachine") { ExpectedResultType = inst.ResultType, ILRange = inst.ILRange }); } else { foreach (var child in inst.Children) { - TranslateFieldsToLocalAccess(function, child, fieldToVariableMap); + TranslateFieldsToLocalAccess(function, child, fieldToVariableMap, isCompiledWithMono); } if (inst is LdObj ldobj && ldobj.Target is LdLoca ldloca && ldloca.Variable.StateMachineField != null) { LdLoc ldloc = new LdLoc(ldloca.Variable); diff --git a/ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs b/ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs index 6c6cc0366..f4015a48a 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs @@ -47,7 +47,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms ILFunction f = TransformDelegateConstruction(call, out ILInstruction target); if (f != null) { call.ReplaceWith(f); - if (target is IInstructionWithVariableOperand && !target.MatchLdThis()) + if (target is IInstructionWithVariableOperand) targetsToReplace.Add((IInstructionWithVariableOperand)target); } } @@ -269,6 +269,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms protected internal override void VisitStLoc(StLoc inst) { base.VisitStLoc(inst); + if (targetLoad is ILInstruction instruction && instruction.MatchLdThis()) + return; if (inst.Variable == targetLoad.Variable) orphanedVariableInits.Add(inst); if (MatchesTargetOrCopyLoad(inst.Value)) { @@ -285,7 +287,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms protected internal override void VisitStObj(StObj inst) { base.VisitStObj(inst); - if (!inst.Target.MatchLdFlda(out ILInstruction target, out IField field) || !MatchesTargetOrCopyLoad(target)) + if (!inst.Target.MatchLdFlda(out ILInstruction target, out IField field) || !MatchesTargetOrCopyLoad(target) || target.MatchLdThis()) return; field = (IField)field.MemberDefinition; ILInstruction value; @@ -321,6 +323,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms protected internal override void VisitLdFlda(LdFlda inst) { base.VisitLdFlda(inst); + if (inst.Target.MatchLdThis() && inst.Field.Name == "$this" + && inst.Field.MemberDefinition.ReflectionName.Contains("c__Iterator")) { + var variable = currentFunction.Variables.First((f) => f.Index == -1); + inst.ReplaceWith(new LdLoca(variable) { ILRange = inst.ILRange }); + } if (inst.Parent is LdObj || inst.Parent is StObj) return; if (!MatchesTargetOrCopyLoad(inst.Target))