diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/ContextTrackingVisitor.cs b/ICSharpCode.Decompiler/CSharp/Transforms/ContextTrackingVisitor.cs index 477074c8e..41ee16cf0 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/ContextTrackingVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/ContextTrackingVisitor.cs @@ -36,6 +36,12 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms currentMethod = context.DecompiledMember as IMethod; } + protected void Uninitialize() + { + currentTypeDefinition = null; + currentMethod = null; + } + public override TResult VisitTypeDeclaration(TypeDeclaration typeDeclaration) { ITypeDefinition oldType = currentTypeDefinition; diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs b/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs index b73ab839b..ae4775303 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs @@ -37,6 +37,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms /// Represents a position immediately before nextNode. /// nextNode is either an ExpressionStatement in a BlockStatement, or an initializer in a for-loop. /// + [DebuggerDisplay("level = {level}, nextNode = {nextNode}")] struct InsertionPoint { /// @@ -66,6 +67,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms } } + [DebuggerDisplay("VariableToDeclare(Name={Name})")] class VariableToDeclare { public readonly IType Type; @@ -87,9 +89,10 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms public int SourceOrder; public InsertionPoint InsertionPoint; - - public bool RemovedDueToCollision; - + + public VariableToDeclare ReplacementDueToCollision; + public bool RemovedDueToCollision => ReplacementDueToCollision != null; + public VariableToDeclare(IType type, string name, bool defaultInitialization, InsertionPoint insertionPoint, int sourceOrder) { this.Type = type; @@ -102,22 +105,48 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms readonly Dictionary variableDict = new Dictionary(); - TransformContext context; - public void Run(AstNode rootNode, TransformContext context) { try { - this.context = context; + variableDict.Clear(); EnsureExpressionStatementsAreValid(rootNode); FindInsertionPoints(rootNode, 0); ResolveCollisions(); - InsertVariableDeclarations(); + InsertVariableDeclarations(context); } finally { variableDict.Clear(); - this.context = null; } } + /// + /// Analyze the input AST (containing undeclared variables) + /// for where those variables would be declared by this transform. + /// Analysis does not modify the AST. + /// + public void Analyze(AstNode rootNode) + { + variableDict.Clear(); + FindInsertionPoints(rootNode, 0); + ResolveCollisions(); + } + + /// + /// Get the position where the declaration for the variable will be inserted. + /// + public AstNode GetDeclarationPoint(ILVariable variable) + { + VariableToDeclare v = variableDict[variable]; + while (v.ReplacementDueToCollision != null) { + v = v.ReplacementDueToCollision; + } + return v.InsertionPoint.nextNode; + } + + public void ClearAnalysisResults() + { + variableDict.Clear(); + } + #region EnsureExpressionStatementsAreValid void EnsureExpressionStatementsAreValid(AstNode rootNode) { @@ -297,7 +326,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms Debug.Assert(point1.level == point2.level); if (point1.nextNode.Parent == point2.nextNode.Parent) { // We found a collision! - prev.RemovedDueToCollision = true; + prev.ReplacementDueToCollision = v; // Continue checking other entries in multiDict against the new position of `v`. if (prev.SourceOrder < v.SourceOrder) { // If we switch v's insertion point to prev's insertion point, @@ -318,7 +347,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms } } - void InsertVariableDeclarations() + void InsertVariableDeclarations(TransformContext context) { var replacements = new List>(); foreach (var p in variableDict) { diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs b/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs index e55335c21..f30226665 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs @@ -35,13 +35,23 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms /// public sealed class PatternStatementTransform : ContextTrackingVisitor, IAstTransform { + readonly DeclareVariables declareVariables = new DeclareVariables(); TransformContext context; public void Run(AstNode rootNode, TransformContext context) { - this.context = context; - base.Initialize(context); - rootNode.AcceptVisitor(this); + if (this.context != null) + throw new InvalidOperationException("Reentrancy in PatternStatementTransform.Run?"); + try { + this.context = context; + base.Initialize(context); + declareVariables.Analyze(rootNode); + rootNode.AcceptVisitor(this); + } finally { + this.context = null; + base.Uninitialize(); + declareVariables.ClearAnalysisResults(); + } } #region Visitor Overrides @@ -434,7 +444,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms return foreachStatement; } - static bool VariableCanBeDeclaredInLoop(IL.ILVariable itemVar, WhileStatement loop) + bool VariableCanBeDeclaredInLoop(IL.ILVariable itemVar, WhileStatement loop) { if (itemVar == null || !(itemVar.Kind == IL.VariableKind.Local || itemVar.Kind == IL.VariableKind.StackSlot)) { // only locals/temporaries can be converted into foreach loop variable @@ -457,7 +467,9 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms // captured variables cannot be declared in the loop unless the loop is their capture scope return false; } - return true; + + AstNode declPoint = declareVariables.GetDeclarationPoint(itemVar); + return declPoint.Ancestors.Contains(loop); } static bool AddressUsedForSingleCall(IL.ILVariable v, IL.BlockContainer loop) diff --git a/ICSharpCode.Decompiler/Tests/TestCases/Pretty/Loops.cs b/ICSharpCode.Decompiler/Tests/TestCases/Pretty/Loops.cs index 9c5c73b60..31f562821 100644 --- a/ICSharpCode.Decompiler/Tests/TestCases/Pretty/Loops.cs +++ b/ICSharpCode.Decompiler/Tests/TestCases/Pretty/Loops.cs @@ -197,5 +197,42 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty } Console.WriteLine("End of method"); } + + public void DoubleForEachWithSameVariable(IEnumerable enumerable) + { + foreach (string current in enumerable) { + current.ToLower(); + } + foreach (string current in enumerable) { + current.ToLower(); + } + } + + static void ForeachExceptForNameCollision(IEnumerable inputs) + { + Console.WriteLine("ForeachWithNameCollision"); + int input; + using (var enumerator = inputs.GetEnumerator()) { + while (enumerator.MoveNext()) { + input = enumerator.Current; + Console.WriteLine(input); + } + } + input = 1; + Console.WriteLine(input); + } + + static void ForeachExceptForContinuedUse(IEnumerable inputs) + { + Console.WriteLine("ForeachExceptForContinuedUse"); + int input = 0; + using (var enumerator = inputs.GetEnumerator()) { + while (enumerator.MoveNext()) { + input = enumerator.Current; + Console.WriteLine(input); + } + } + Console.WriteLine("Last: " + input); + } } }