// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team // // 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.Collections.Generic; using System.Diagnostics; using System.Linq; using ICSharpCode.Decompiler.CSharp.Syntax; using ICSharpCode.Decompiler.CSharp.Syntax.PatternMatching; using ICSharpCode.Decompiler.IL; using ICSharpCode.Decompiler.Semantics; using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.Util; namespace ICSharpCode.Decompiler.CSharp.Transforms { /// /// Insert variable declarations. /// public class DeclareVariables : IAstTransform { /// /// 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 { /// /// The nesting level of `nextNode` within the AST. /// Used to speed up FindCommonParent(). /// internal int level; internal AstNode nextNode; /// Go up one level internal InsertionPoint Up() { return new InsertionPoint { level = level - 1, nextNode = nextNode.Parent }; } internal InsertionPoint UpTo(int targetLevel) { InsertionPoint result = this; while (result.level > targetLevel) { result.nextNode = result.nextNode.Parent; result.level -= 1; } return result; } } [DebuggerDisplay("VariableToDeclare(Name={Name})")] class VariableToDeclare { public readonly IType Type; public readonly string Name; /// /// Whether the variable needs to be default-initialized. /// public bool DefaultInitialization; /// /// Integer value that can be used to compare to VariableToDeclare instances /// to determine which variable was used first in the source code. /// /// Assuming both insertion points are on the same level, the variable /// with the lower SourceOrder value has the insertion point that comes /// first in the source code. /// public int SourceOrder; public InsertionPoint InsertionPoint; public VariableToDeclare ReplacementDueToCollision; public bool RemovedDueToCollision => ReplacementDueToCollision != null; public VariableToDeclare(IType type, string name, bool defaultInitialization, InsertionPoint insertionPoint, int sourceOrder) { this.Type = type; this.Name = name; this.DefaultInitialization = defaultInitialization; this.InsertionPoint = insertionPoint; this.SourceOrder = sourceOrder; } } readonly Dictionary variableDict = new Dictionary(); public void Run(AstNode rootNode, TransformContext context) { try { variableDict.Clear(); EnsureExpressionStatementsAreValid(rootNode); FindInsertionPoints(rootNode, 0); ResolveCollisions(); InsertVariableDeclarations(context); } finally { variableDict.Clear(); } } /// /// 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) { foreach (var stmt in rootNode.DescendantsAndSelf.OfType()) { if (!IsValidInStatementExpression(stmt.Expression)) { // assign result to dummy variable var v = new ILVariable(VariableKind.StackSlot, stmt.Expression.GetResolveResult().Type, 0); v.Name = "_"; stmt.Expression = new AssignmentExpression( new IdentifierExpression(v.Name).WithRR(new ILVariableResolveResult(v, v.Type)), stmt.Expression.Detach()); } } } private static bool IsValidInStatementExpression(Expression expr) { switch (expr) { case InvocationExpression ie: case ObjectCreateExpression oce: case AssignmentExpression ae: case ErrorExpression ee: return true; case UnaryOperatorExpression uoe: switch (uoe.Operator) { case UnaryOperatorType.PostIncrement: case UnaryOperatorType.PostDecrement: case UnaryOperatorType.Increment: case UnaryOperatorType.Decrement: case UnaryOperatorType.Await: return true; default: return false; } default: return false; } } #endregion #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. /// /// `level` == nesting depth of `node` within root node. /// /// /// Insertion point for a variable = common parent of all uses of that variable /// = smallest possible scope that contains all the uses of the variable /// void FindInsertionPoints(AstNode node, int nodeLevel) { BlockContainer loop = node.Annotation(); if (loop != null) { loopTracking.Add((new InsertionPoint { level = nodeLevel, nextNode = node }, loop)); } 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); } } bool VariableNeedsDeclaration(VariableKind kind) { switch (kind) { case VariableKind.PinnedLocal: case VariableKind.Parameter: case VariableKind.Exception: case VariableKind.UsingLocal: case VariableKind.ForeachLocal: return false; default: return true; } } /// /// Finds an insertion point in a common parent instruction. /// InsertionPoint FindCommonParent(InsertionPoint oldPoint, InsertionPoint newPoint) { // First ensure we're looking at nodes on the same level: oldPoint = oldPoint.UpTo(newPoint.level); newPoint = newPoint.UpTo(oldPoint.level); Debug.Assert(newPoint.level == oldPoint.level); // Then go up the tree until both points share the same parent: while (oldPoint.nextNode.Parent != newPoint.nextNode.Parent) { oldPoint = oldPoint.Up(); newPoint = newPoint.Up(); } // return oldPoint as that one comes first in the source code return oldPoint; } #endregion /// /// Some variable declarations in C# are illegal (colliding), /// even though the variable live ranges are not overlapping. /// /// Multiple declarations in same block: /// /// int i = 1; use(1); /// int i = 2; use(2); /// /// /// "Hiding" declaration in nested block: /// /// int i = 1; use(1); /// if (...) { /// int i = 2; use(2); /// } /// /// /// Nested blocks are illegal even if the parent block /// declares the variable later: /// /// if (...) { /// int i = 1; use(i); /// } /// int i = 2; use(i); /// /// /// ResolveCollisions() detects all these cases, and combines the variable declarations /// to a single declaration that is usable for the combined scopes. /// void ResolveCollisions() { var multiDict = new MultiDictionary(); foreach (var v in variableDict.Values) { // We can only insert variable declarations in blocks, but FindInsertionPoints() didn't // guarantee that it finds only blocks. // Fix that up now. while (!(v.InsertionPoint.nextNode.Parent is BlockStatement)) { if (v.InsertionPoint.nextNode.Parent is ForStatement f && v.InsertionPoint.nextNode == f.Initializers.FirstOrDefault() && IsMatchingAssignment(v, out _)) break; if (v.InsertionPoint.nextNode.Parent is UsingStatement u && v.InsertionPoint.nextNode == u.ResourceAcquisition && IsMatchingAssignment(v, out _)) break; v.InsertionPoint = v.InsertionPoint.Up(); } // Go through all potentially colliding variables: foreach (var prev in multiDict[v.Name]) { if (prev.RemovedDueToCollision) continue; // Go up until both nodes are on the same level: InsertionPoint point1 = prev.InsertionPoint.UpTo(v.InsertionPoint.level); InsertionPoint point2 = v.InsertionPoint.UpTo(prev.InsertionPoint.level); Debug.Assert(point1.level == point2.level); if (point1.nextNode.Parent == point2.nextNode.Parent) { // We found a collision! 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, // we also need to copy prev's SourceOrder value. v.InsertionPoint = point1; v.SourceOrder = prev.SourceOrder; } else { v.InsertionPoint = point2; } v.DefaultInitialization |= prev.DefaultInitialization; // I think we don't need to re-check the dict entries that we already checked earlier, // because the new v.InsertionPoint only collides with another point x if either // the old v.InsertionPoint or the old prev.InsertionPoint already collided with x. } } multiDict.Add(v.Name, v); } } bool IsMatchingAssignment(VariableToDeclare v, out AssignmentExpression assignment) { assignment = v.InsertionPoint.nextNode as AssignmentExpression ?? (v.InsertionPoint.nextNode as ExpressionStatement)?.Expression as AssignmentExpression; Expression expectedExpr = new IdentifierExpression(v.Name); if (v.Type.Kind == TypeKind.ByReference) { expectedExpr = new DirectionExpression(FieldDirection.Ref, expectedExpr); } if (assignment != null && assignment.Operator == AssignmentOperatorType.Assign && assignment.Left.IsMatch(expectedExpr)) return true; return false; } void InsertVariableDeclarations(TransformContext context) { var replacements = new List>(); foreach (var p in variableDict) { var v = p.Value; if (v.RemovedDueToCollision) continue; if (IsMatchingAssignment(v, out AssignmentExpression assignment)) { AstType type; if (context.Settings.AnonymousTypes && v.Type.ContainsAnonymousType()) { type = new SimpleType("var"); } else { type = context.TypeSystemAstBuilder.ConvertType(v.Type); } var vds = new VariableDeclarationStatement(type, v.Name, assignment.Right.Detach()); var init = vds.Variables.Single(); init.AddAnnotation(assignment.Left.GetResolveResult()); foreach (object annotation in assignment.Left.Annotations.Concat(assignment.Annotations)) { if (!(annotation is ResolveResult)) { init.AddAnnotation(annotation); } } replacements.Add(new KeyValuePair(v.InsertionPoint.nextNode, vds)); } else { Expression initializer = null; AstType type = context.TypeSystemAstBuilder.ConvertType(v.Type); if (v.DefaultInitialization) { initializer = new DefaultValueExpression(type.Clone()); } var vds = new VariableDeclarationStatement(type, v.Name, initializer); vds.Variables.Single().AddAnnotation(new ILVariableResolveResult(p.Key, p.Key.Type)); Debug.Assert(v.InsertionPoint.nextNode.Role == BlockStatement.StatementRole); v.InsertionPoint.nextNode.Parent.InsertChildBefore( v.InsertionPoint.nextNode, vds, BlockStatement.StatementRole); } } // perform replacements at end, so that we don't replace a node while it is still referenced by a VariableToDeclare foreach (var pair in replacements) { pair.Key.ReplaceWith(pair.Value); } } } }