diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs b/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs index 9de322e0d..2ac6782a5 100644 --- a/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs +++ b/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs @@ -82,7 +82,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow /// For each finally method, stores the target state when entering the finally block, /// and the decompiled code of the finally method body. /// - readonly Dictionary decompiledFinallyMethods = new Dictionary(); + readonly Dictionary decompiledFinallyMethods = new Dictionary(); /// /// Temporary stores for 'yield break'. @@ -137,7 +137,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow newBody.SortBlocks(deleteUnreachableBlocks: true); DecompileFinallyBlocks(); - ReconstructTryFinallyBlocks(newBody); + ReconstructTryFinallyBlocks(function); context.Step("Translate fields to local accesses", function); TranslateFieldsToLocalAccess(function, function, fieldToParameterMap); @@ -188,12 +188,17 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow int i; for (i = 1; i < body.Instructions.Count; i++) { // stfld(..., ldloc(var_1), ldloc(parameter)) - if (!body.Instructions[i].MatchStFld(out var ldloc, out var storedField, out var loadParameter)) + // or (in structs): stfld(..., ldloc(var_1), ldobj(ldloc(this))) + if (!body.Instructions[i].MatchStFld(out var ldloc, out var storedField, out var value)) break; - if (ldloc.MatchLdLoc(var1) - && loadParameter.MatchLdLoc(out var parameter) - && parameter.Kind == VariableKind.Parameter) { + if (!ldloc.MatchLdLoc(var1)) { + return false; + } + if (value.MatchLdLoc(out var parameter) && parameter.Kind == VariableKind.Parameter) { fieldToParameterMap[(IField)storedField.MemberDefinition] = parameter; + } else if (value is LdObj ldobj && ldobj.Target.MatchLdThis()) { + // copy of 'this' in struct + fieldToParameterMap[(IField)storedField.MemberDefinition] = ((LdLoc)ldobj.Target).Variable; } else { return false; } @@ -618,7 +623,12 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow v.StateMachineField = ldflda.Field; fieldToVariableMap.Add(fieldDef, v); } - inst.ReplaceWith(new LdLoca(v) { ILRange = inst.ILRange }); + if (v.StackType == StackType.Ref) { + Debug.Assert(v.Kind == VariableKind.Parameter && v.Index < 0); // this pointer + inst.ReplaceWith(new LdLoc(v) { ILRange = inst.ILRange }); + } else { + inst.ReplaceWith(new LdLoca(v) { ILRange = inst.ILRange }); + } } else if (inst.MatchLdThis()) { inst.ReplaceWith(new InvalidExpression("stateMachine") { ExpectedResultType = inst.ResultType, ILRange = inst.ILRange }); } else { @@ -651,7 +661,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow body.EntryPoint.Instructions.RemoveAt(0); } function.ReleaseRef(); // make body reusable outside of function - decompiledFinallyMethods.Add(method, (newState, body)); + decompiledFinallyMethods.Add(method, (newState, function)); } } #endregion @@ -670,8 +680,9 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow /// /// Precondition: the blocks in newBody are topologically sorted. /// - void ReconstructTryFinallyBlocks(BlockContainer newBody) + void ReconstructTryFinallyBlocks(ILFunction iteratorFunction) { + BlockContainer newBody = (BlockContainer)iteratorFunction.Body; context.Stepper.Step("Reconstuct try-finally blocks"); var blockState = new int[newBody.Blocks.Count]; blockState[0] = -1; @@ -736,7 +747,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow { var finallyMethod = FindFinallyMethod(state); Debug.Assert(finallyMethod != null); - // remove the method so that it doesn't get cause ambiguity when processing nested try-finally blocks + // remove the method so that it doesn't cause ambiguity when processing nested try-finally blocks finallyMethodToStateRange.Remove(finallyMethod); var tryBlock = new Block(); @@ -748,7 +759,10 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow ILInstruction finallyBlock; if (decompiledFinallyMethods.TryGetValue(finallyMethod, out var decompiledMethod)) { - finallyBlock = decompiledMethod.body; + finallyBlock = decompiledMethod.function.Body; + var vars = decompiledMethod.function.Variables.ToArray(); + decompiledMethod.function.Variables.Clear(); + iteratorFunction.Variables.AddRange(vars); } else { finallyBlock = new InvalidBranch("Missing decompiledFinallyMethod"); } diff --git a/ICSharpCode.Decompiler/Tests/TestCases/Correctness/YieldReturn.cs b/ICSharpCode.Decompiler/Tests/TestCases/Correctness/YieldReturn.cs index 4b6bc6037..18483b8cd 100644 --- a/ICSharpCode.Decompiler/Tests/TestCases/Correctness/YieldReturn.cs +++ b/ICSharpCode.Decompiler/Tests/TestCases/Correctness/YieldReturn.cs @@ -63,9 +63,10 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness Print("TryFinallyWithTwoExitPointsInNestedCatch(true)", TryFinallyWithTwoExitPointsInNestedCatch(true).GetEnumerator()); #endif Print("GenericYield()", GenericYield().GetEnumerator()); + StructWithYieldReturn.Run(); } - static void Print(string name, IEnumerator enumerator) + internal static void Print(string name, IEnumerator enumerator) { Console.WriteLine(name + ": Test start"); while (enumerator.MoveNext()) { @@ -427,6 +428,19 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness } #endif + public static IEnumerable LocalInFinally(T a) where T : IDisposable + { + yield return 1; + try { + yield return 2; + } finally { + T b = a; + b.Dispose(); + b.Dispose(); + } + yield return 3; + } + public static IEnumerable GenericYield() where T : new() { T val = new T(); @@ -435,4 +449,23 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness } } } + + struct StructWithYieldReturn + { + public static void Run() + { + var s = new StructWithYieldReturn { val = 2 }; + var count = s.Count(); + YieldReturnTest.Print("StructWithYieldReturn", count.GetEnumerator()); + YieldReturnTest.Print("StructWithYieldReturn (again)", count.GetEnumerator()); + } + + int val; + + public IEnumerable Count() + { + yield return val++; + yield return val++; + } + } } \ No newline at end of file