diff --git a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs index 645af437a..9d33f2e98 100644 --- a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs @@ -259,6 +259,7 @@ namespace ICSharpCode.Decompiler.CSharp var oldContinueCount = continueCount; var oldBreakTarget = breakTarget; var loop = ConvertLoop(container); + loop.AddAnnotation(container); continueTarget = oldContinueTarget; continueCount = oldContinueCount; breakTarget = oldBreakTarget; diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs b/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs index b6240a4c0..7e213c1ef 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs @@ -112,8 +112,10 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms this.context = null; } } - + #region FindInsertionPoints + List<(InsertionPoint InsertionPoint, BlockContainer Loop)> loopTracking = new List<(InsertionPoint, BlockContainer)>(); + /// /// Finds insertion points for all variables used within `node` /// and adds them to the variableDict. @@ -126,24 +128,42 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms /// void FindInsertionPoints(AstNode node, int nodeLevel) { - for (AstNode child = node.FirstChild; child != null; child = child.NextSibling) { - FindInsertionPoints(child, nodeLevel + 1); + BlockContainer loop = node.Annotation(); + if (loop != null) { + loopTracking.Add((new InsertionPoint { level = nodeLevel, nextNode = node }, loop)); } - var identExpr = node as IdentifierExpression; - if (identExpr != null) { - var rr = identExpr.GetResolveResult() as ILVariableResolveResult; - if (rr != null && VariableNeedsDeclaration(rr.Variable.Kind)) { - var newPoint = new InsertionPoint { level = nodeLevel, nextNode = identExpr }; - VariableToDeclare v; - if (variableDict.TryGetValue(rr.Variable, out v)) { - v.InsertionPoint = FindCommonParent(v.InsertionPoint, newPoint); - } else { - v = new VariableToDeclare( - rr.Variable.Type, rr.Variable.Name, rr.Variable.HasInitialValue, - newPoint, sourceOrder: variableDict.Count); - variableDict.Add(rr.Variable, v); + try { + for (AstNode child = node.FirstChild; child != null; child = child.NextSibling) { + FindInsertionPoints(child, nodeLevel + 1); + } + var identExpr = node as IdentifierExpression; + if (identExpr != null) { + var rr = identExpr.GetResolveResult() as ILVariableResolveResult; + if (rr != null && VariableNeedsDeclaration(rr.Variable.Kind)) { + var variable = rr.Variable; + InsertionPoint newPoint; + int startIndex = loopTracking.Count - 1; + if (variable.CaptureScope != null && startIndex > -1 && variable.CaptureScope != loopTracking[startIndex].Loop) { + while (startIndex > -1 && loopTracking[startIndex].Loop != variable.CaptureScope) + startIndex--; + newPoint = loopTracking[startIndex + 1].InsertionPoint; + } else { + newPoint = new InsertionPoint { level = nodeLevel, nextNode = identExpr }; + } + VariableToDeclare v; + if (variableDict.TryGetValue(rr.Variable, out v)) { + v.InsertionPoint = FindCommonParent(v.InsertionPoint, newPoint); + } else { + v = new VariableToDeclare( + rr.Variable.Type, rr.Variable.Name, rr.Variable.HasInitialValue, + newPoint, sourceOrder: variableDict.Count); + variableDict.Add(rr.Variable, v); + } } } + } finally { + if (loop != null) + loopTracking.RemoveAt(loopTracking.Count - 1); } } diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs b/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs index 4029711a1..679c475a0 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs @@ -451,6 +451,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms InExpression = m.Get("collection").Single().Detach(), EmbeddedStatement = newBody }.WithAnnotation(itemVarDecl.Variables.Single().Annotation()); + foreachStatement.CopyAnnotationsFrom(loop); if (foreachStatement.InExpression is BaseReferenceExpression) { foreachStatement.InExpression = new ThisReferenceExpression().CopyAnnotationsFrom(foreachStatement.InExpression); } @@ -547,6 +548,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms }.WithAnnotation(itemVarDecl.Variables.Single().Annotation()); BlockStatement body = new BlockStatement(); foreachStatement.EmbeddedStatement = body; + foreachStatement.CopyAnnotationsFrom(loop); ((BlockStatement)node.Parent).Statements.InsertBefore(node, foreachStatement); body.Add(node.Detach()); @@ -639,6 +641,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms foreach (Statement stmt in m3.Get("statement")) newBody.Add(stmt.Detach()); forStatement = new ForStatement(); + forStatement.CopyAnnotationsFrom(loop); forStatement.Initializers.Add(node); forStatement.Condition = loop.Condition.Detach(); forStatement.Iterators.Add(m3.Get("increment").Single().Detach()); @@ -671,6 +674,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms BlockStatement block = (BlockStatement)whileLoop.EmbeddedStatement; block.Statements.Last().Remove(); // remove if statement doLoop.EmbeddedStatement = block.Detach(); + doLoop.CopyAnnotationsFrom(whileLoop); whileLoop.ReplaceWith(doLoop); // we may have to extract variable definitions out of the loop if they were used in the condition: diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index 49dace89d..906752754 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -62,6 +62,10 @@ 3.5 + + ..\packages\System.ValueTuple.4.3.0\lib\netstandard1.0\System.ValueTuple.dll + True + 3.5 diff --git a/ICSharpCode.Decompiler/IL/ILVariable.cs b/ICSharpCode.Decompiler/IL/ILVariable.cs index 22adc7571..dffcbd7b4 100644 --- a/ICSharpCode.Decompiler/IL/ILVariable.cs +++ b/ICSharpCode.Decompiler/IL/ILVariable.cs @@ -220,6 +220,9 @@ namespace ICSharpCode.Decompiler.IL if (hasInitialValue && Kind != VariableKind.Parameter) { output.Write(" init"); } + if (CaptureScope != null) { + output.Write(" captured in " + CaptureScope.EntryPoint.Label); + } } internal void WriteTo(ITextOutput output) diff --git a/ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs b/ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs index e3ef51bde..9a5f3f9cb 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs @@ -57,13 +57,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms // TODO : it is probably not a good idea to remove *all* display-classes // is there a way to minimize the false-positives? if (newObj != null && IsSimpleDisplayClass(newObj.Method)) { + targetVariable.CaptureScope = FindBlockContainer(block); targetsToReplace.Add((IInstructionWithVariableOperand)block.Instructions[i]); } } } } foreach (var target in targetsToReplace.OrderByDescending(t => ((ILInstruction)t).ILRange.Start)) { - function.AcceptVisitor(new TransformDisplayClassUsages(target, orphanedVariableInits)); + function.AcceptVisitor(new TransformDisplayClassUsages(target, target.Variable.CaptureScope, orphanedVariableInits)); } foreach (var store in orphanedVariableInits) { ILInstruction containingBlock = store.Parent as Block; @@ -72,6 +73,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms } } + private BlockContainer FindBlockContainer(Block block) + { + ILInstruction current = block; + while (current != null) { + current = current.Parent; + if (current is BlockContainer) + return (BlockContainer)current; + } + throw new NotSupportedException(); + } + bool IsSimpleDisplayClass(IMethod method) { if (!method.IsCompilerGeneratedOrIsInCompilerGeneratedClass()) @@ -181,6 +193,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms class TransformDisplayClassUsages : ILVisitor { ILFunction currentFunction; + BlockContainer captureScope; readonly IInstructionWithVariableOperand targetLoad; readonly List targetAndCopies = new List(); readonly List orphanedVariableInits; @@ -192,9 +205,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms public ILInstruction value; } - public TransformDisplayClassUsages(IInstructionWithVariableOperand targetLoad, List orphanedVariableInits) + public TransformDisplayClassUsages(IInstructionWithVariableOperand targetLoad, BlockContainer captureScope, List orphanedVariableInits) { this.targetLoad = targetLoad; + this.captureScope = captureScope; this.orphanedVariableInits = orphanedVariableInits; this.targetAndCopies.Add(targetLoad.Variable); } @@ -242,16 +256,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms return; field = (IField)field.MemberDefinition; DisplayClassVariable info; - if (initValues.TryGetValue(field, out info) && info.variable != null) { + ILInstruction value; + if (initValues.TryGetValue(field, out info)) { inst.ReplaceWith(new StLoc(info.variable, inst.Value)); } else { - ILInstruction value; - ILVariable v; - if (inst.Value.MatchLdLoc(out v) && v.Kind != VariableKind.PinnedLocal) { + if (inst.Value.MatchLdLoc(out var v) && v.Kind == VariableKind.Parameter) { + // special case for parameters: remove copies of parameter values. orphanedVariableInits.Add(inst); value = inst.Value; } else { v = currentFunction.RegisterVariable(VariableKind.Local, field.Type, field.Name); + v.CaptureScope = captureScope; inst.ReplaceWith(new StLoc(v, inst.Value)); value = new LdLoc(v); } @@ -283,11 +298,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms DisplayClassVariable info; if (!initValues.TryGetValue(field, out info)) { var v = currentFunction.RegisterVariable(VariableKind.Local, field.Type, field.Name); + v.CaptureScope = captureScope; inst.ReplaceWith(new LdLoca(v)); var value = new LdLoc(v); initValues.Add(field, new DisplayClassVariable { value = value, variable = v }); - } else if (info.value is LdLoc) { - inst.ReplaceWith(new LdLoca(((LdLoc)info.value).Variable)); + } else if (info.value is LdLoc l) { + inst.ReplaceWith(new LdLoca(l.Variable)); } else { throw new NotImplementedException(); } diff --git a/ICSharpCode.Decompiler/Tests/TestCases/Correctness/Capturing.cs b/ICSharpCode.Decompiler/Tests/TestCases/Correctness/Capturing.cs index 1dc1e15de..5bb4ff577 100644 --- a/ICSharpCode.Decompiler/Tests/TestCases/Correctness/Capturing.cs +++ b/ICSharpCode.Decompiler/Tests/TestCases/Correctness/Capturing.cs @@ -11,6 +11,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness static void Main(string[] args) { TestCase1(); + TestCase2(); + TestCase3(); + TestCase4("TestCase4"); } static void TestCase1() @@ -32,6 +35,50 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness action(); } + static void TestCase2() + { + Console.WriteLine("TestCase2"); + List actions = new List(); + int max = 5; + string line; + while (ReadLine(out line, ref max)) { + string capture = line; + actions.Add(() => Console.WriteLine(capture)); + } + // line still declared + line = null; + Console.WriteLine("----"); + foreach (var action in actions) + action(); + } + + static void TestCase3() + { + Console.WriteLine("TestCase3"); + List actions = new List(); + int max = 5; + string line, capture; + while (ReadLine(out line, ref max)) { + capture = line; + actions.Add(() => Console.WriteLine(capture)); + } + // line still declared + line = null; + Console.WriteLine("----"); + foreach (var action in actions) + action(); + } + + static void TestCase4(string capture) + { + Console.WriteLine("TestCase4"); + List actions = new List(); + actions.Add(() => Console.WriteLine(capture)); + Console.WriteLine("----"); + foreach (var action in actions) + action(); + } + private static bool ReadLine(out string line, ref int v) { line = v + " line"; diff --git a/ICSharpCode.Decompiler/packages.config b/ICSharpCode.Decompiler/packages.config index 718fb8877..d4a92e683 100644 --- a/ICSharpCode.Decompiler/packages.config +++ b/ICSharpCode.Decompiler/packages.config @@ -1,4 +1,5 @@  + \ No newline at end of file