diff --git a/ICSharpCode.Decompiler/CSharp/Annotations.cs b/ICSharpCode.Decompiler/CSharp/Annotations.cs index 8c7cd3281..b9c4443c0 100644 --- a/ICSharpCode.Decompiler/CSharp/Annotations.cs +++ b/ICSharpCode.Decompiler/CSharp/Annotations.cs @@ -111,4 +111,14 @@ namespace ICSharpCode.Decompiler.CSharp return node.Annotation() ?? ErrorResolveResult.UnknownError; } } + + public class ILVariableResolveResult : ResolveResult + { + public readonly ILVariable Variable; + + public ILVariableResolveResult(ILVariable v, IType type) : base(type) + { + this.Variable = v; + } + } } diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index 0a26a0586..8aaef900a 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -39,6 +39,7 @@ namespace ICSharpCode.Decompiler.CSharp /// /// /// Instances of this class are not thread-safe. Use separate instances to decompile multiple members in parallel. + /// (in particular, the transform instances are not thread-safe) /// public class CSharpDecompiler { @@ -71,7 +72,7 @@ namespace ICSharpCode.Decompiler.CSharp new ReplaceMethodCallsWithOperators(), new IntroduceUnsafeModifier(), new AddCheckedBlocks(), - //new DeclareVariables(), // should run after most transforms that modify statements + new DeclareVariables(), // should run after most transforms that modify statements new ConvertConstructorCallIntoInitializer(), // must run after DeclareVariables new DecimalConstantTransform(), new IntroduceUsingDeclarations(), diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index eee54c1d5..64c56f200 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -109,7 +109,6 @@ namespace ICSharpCode.Decompiler.CSharp expr = new ThisReferenceExpression(); else expr = new IdentifierExpression(variable.Name); - // TODO: use LocalResolveResult instead if (variable.Type.Kind == TypeKind.ByReference) { // When loading a by-ref parameter, use 'ref paramName'. // We'll strip away the 'ref' when dereferencing. @@ -117,11 +116,13 @@ namespace ICSharpCode.Decompiler.CSharp // Ensure that the IdentifierExpression itself also gets a resolve result, as that might // get used after the 'ref' is stripped away: var elementType = ((ByReferenceType)variable.Type).ElementType; - expr.WithRR(new ResolveResult(elementType)); + expr.WithRR(new ILVariableResolveResult(variable, elementType)); expr = new DirectionExpression(FieldDirection.Ref, expr); + return expr.WithRR(new ResolveResult(variable.Type)); + } else { + return expr.WithRR(new ILVariableResolveResult(variable, variable.Type)); } - return expr.WithRR(new ResolveResult(variable.Type)); } ExpressionWithResolveResult ConvertField(IField field, ILInstruction target = null) diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs b/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs index a5f0b47cb..f1b1797d6 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs @@ -24,343 +24,216 @@ using System.Threading; using ICSharpCode.Decompiler.IL; using ICSharpCode.NRefactory.CSharp; using ICSharpCode.NRefactory.CSharp.Analysis; +using ICSharpCode.NRefactory.PatternMatching; +using ICSharpCode.NRefactory.Semantics; +using ICSharpCode.NRefactory.TypeSystem; +using ICSharpCode.NRefactory.Utils; namespace ICSharpCode.Decompiler.CSharp.Transforms { /// - /// Moves variable declarations to improved positions. + /// Insert variable declarations. /// public class DeclareVariables : IAstTransform { - sealed class VariableToDeclare + struct InsertionPoint { - public AstType Type; - public string Name; - public ILVariable ILVariable; + internal int level; + internal AstNode nextNode; - public AssignmentExpression ReplacedAssignment; - public Statement InsertionPoint; - } - - CancellationToken cancellationToken; - List variablesToDeclare = new List(); - - public void Run(AstNode rootNode, TransformContext context) - { - this.cancellationToken = context.CancellationToken; - RunInternal(rootNode, null); - // Declare all the variables at the end, after all the logic has run. - // This is done so that definite assignment analysis can work on a single representation and doesn't have to be updated - // when we change the AST. - foreach (var v in variablesToDeclare) { - if (v.ReplacedAssignment == null) { - BlockStatement block = (BlockStatement)v.InsertionPoint.Parent; - var decl = new VariableDeclarationStatement((AstType)v.Type.Clone(), v.Name); - var ilVar = v.ILVariable; - if (ilVar != null) { - decl.Variables.Single().AddAnnotation(ilVar); - if (ilVar.HasInitialValue && ilVar.Kind == VariableKind.Local) - decl.Variables.Single().Initializer = new DefaultValueExpression((AstType)v.Type.Clone()); - } - block.Statements.InsertBefore(v.InsertionPoint, decl); - } + /// Go up one level + internal InsertionPoint Up() + { + return new InsertionPoint { + level = level - 1, + nextNode = nextNode.Parent + }; } - // First do all the insertions, then do all the replacements. This is necessary because a replacement might remove our reference point from the AST. - foreach (var v in variablesToDeclare) { - if (v.ReplacedAssignment != null) { - // We clone the right expression so that it doesn't get removed from the old ExpressionStatement, - // which might be still in use by the definite assignment graph. - VariableInitializer initializer = new VariableInitializer(v.Name, v.ReplacedAssignment.Right.Detach()).CopyAnnotationsFrom(v.ReplacedAssignment).WithAnnotation(v.ILVariable); - VariableDeclarationStatement varDecl = new VariableDeclarationStatement { - Type = (AstType)v.Type.Clone(), - Variables = { initializer } - }; - ExpressionStatement es = v.ReplacedAssignment.Parent as ExpressionStatement; - if (es != null) { - // Note: if this crashes with 'Cannot replace the root node', check whether two variables were assigned the same name - es.ReplaceWith(varDecl.CopyAnnotationsFrom(es)); - } else { - v.ReplacedAssignment.ReplaceWith(varDecl); - } + + internal InsertionPoint UpTo(int targetLevel) + { + InsertionPoint result = this; + while (result.level > targetLevel) { + result.nextNode = result.nextNode.Parent; + result.level -= 1; } + return result; } - variablesToDeclare = null; } - void RunInternal(AstNode node, DefiniteAssignmentAnalysis daa) + class VariableToDeclare { - BlockStatement block = node as BlockStatement; - if (block != null) { - var variables = block.Statements.TakeWhile(stmt => stmt is VariableDeclarationStatement) - .Cast().ToList(); - if (variables.Count > 0) { - // remove old variable declarations: - foreach (VariableDeclarationStatement varDecl in variables) { - Debug.Assert(varDecl.Variables.Single().Initializer.IsNull); - varDecl.Remove(); - } - if (daa == null) { - // If possible, reuse the DefiniteAssignmentAnalysis that was created for the parent block - daa = new DefiniteAssignmentAnalysis(block, cancellationToken); - } - foreach (VariableDeclarationStatement varDecl in variables) { - VariableInitializer initializer = varDecl.Variables.Single(); - string variableName = initializer.Name; - ILVariable v = initializer.Annotation(); - bool allowPassIntoLoops = initializer.Annotation() == null; - DeclareVariableInBlock(daa, block, varDecl.Type, variableName, v, allowPassIntoLoops); - } - } - } - for (AstNode child = node.FirstChild; child != null; child = child.NextSibling) { - RunInternal(child, daa); + public readonly IType Type; + public readonly string Name; + /// + /// Whether the variable needs to be default-initialized. + /// + public readonly 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 bool RemovedDueToCollision; + + 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; } } - void DeclareVariableInBlock(DefiniteAssignmentAnalysis daa, BlockStatement block, AstType type, string variableName, ILVariable v, bool allowPassIntoLoops) - { - // declarationPoint: The point where the variable would be declared, if we decide to declare it in this block - Statement declarationPoint = null; - // Check whether we can move down the variable into the sub-blocks - bool canMoveVariableIntoSubBlocks = FindDeclarationPoint(daa, variableName, allowPassIntoLoops, block, out declarationPoint); - if (declarationPoint == null) { - // The variable isn't used at all - return; - } - if (canMoveVariableIntoSubBlocks) { - // Declare the variable within the sub-blocks - foreach (Statement stmt in block.Statements) { - ForStatement forStmt = stmt as ForStatement; - if (forStmt != null && forStmt.Initializers.Count == 1) { - // handle the special case of moving a variable into the for initializer - if (TryConvertAssignmentExpressionIntoVariableDeclaration(forStmt.Initializers.Single(), type, variableName)) - continue; - } - UsingStatement usingStmt = stmt as UsingStatement; - if (usingStmt != null && usingStmt.ResourceAcquisition is AssignmentExpression) { - // handle the special case of moving a variable into a using statement - if (TryConvertAssignmentExpressionIntoVariableDeclaration((Expression)usingStmt.ResourceAcquisition, type, variableName)) - continue; - } - IfElseStatement ies = stmt as IfElseStatement; - if (ies != null) { - foreach (var child in IfElseChainChildren(ies)) { - BlockStatement subBlock = child as BlockStatement; - if (subBlock != null) - DeclareVariableInBlock(daa, subBlock, type, variableName, v, allowPassIntoLoops); - } - continue; - } - foreach (AstNode child in stmt.Children) { - BlockStatement subBlock = child as BlockStatement; - if (subBlock != null) { - DeclareVariableInBlock(daa, subBlock, type, variableName, v, allowPassIntoLoops); - } else if (HasNestedBlocks(child)) { - foreach (BlockStatement nestedSubBlock in child.Children.OfType()) { - DeclareVariableInBlock(daa, nestedSubBlock, type, variableName, v, allowPassIntoLoops); - } - } - } - } - } else { - // Try converting an assignment expression into a VariableDeclarationStatement - if (!TryConvertAssignmentExpressionIntoVariableDeclaration(declarationPoint, type, variableName)) { - // Declare the variable in front of declarationPoint - variablesToDeclare.Add(new VariableToDeclare { Type = type, Name = variableName, ILVariable = v, InsertionPoint = declarationPoint }); - } - } - } - - bool TryConvertAssignmentExpressionIntoVariableDeclaration(Statement declarationPoint, AstType type, string variableName) - { - // convert the declarationPoint into a VariableDeclarationStatement - ExpressionStatement es = declarationPoint as ExpressionStatement; - if (es != null) { - return TryConvertAssignmentExpressionIntoVariableDeclaration(es.Expression, type, variableName); - } - return false; - } + Dictionary variableDict = new Dictionary(); - bool TryConvertAssignmentExpressionIntoVariableDeclaration(Expression expression, AstType type, string variableName) + TransformContext context; + + public void Run(AstNode rootNode, TransformContext context) { - AssignmentExpression ae = expression as AssignmentExpression; - if (ae != null && ae.Operator == AssignmentOperatorType.Assign) { - IdentifierExpression ident = ae.Left as IdentifierExpression; - if (ident != null && ident.Identifier == variableName) { - variablesToDeclare.Add(new VariableToDeclare { Type = type, Name = variableName, ILVariable = ident.Annotation(), ReplacedAssignment = ae }); - return true; - } + try { + this.context = context; + FindInsertionPoints(rootNode, 0); + ResolveOverlap(); + InsertVariableDeclarations(); + } finally { + variableDict.Clear(); + this.context = null; } - return false; } + #region FindInsertionPoints /// - /// Finds the declaration point for the variable within the specified block. + /// Finds insertion points for all variables used within `node` + /// and adds them to the variableDict. + /// + /// `level` == nesting depth of `node` within root node. /// - /// - /// Definite assignment analysis, must be prepared for 'block' or one of its parents. - /// - /// The variable to declare - /// The block in which the variable should be declared - /// - /// Output parameter: the first statement within 'block' where the variable needs to be declared. - /// - /// - /// Returns whether it is possible to move the variable declaration into sub-blocks. - /// - public static bool FindDeclarationPoint(DefiniteAssignmentAnalysis daa, VariableDeclarationStatement varDecl, BlockStatement block, out Statement declarationPoint) - { - string variableName = varDecl.Variables.Single().Name; - bool allowPassIntoLoops = varDecl.Variables.Single().Annotation() == null; - return FindDeclarationPoint(daa, variableName, allowPassIntoLoops, block, out declarationPoint); - } - - static bool FindDeclarationPoint(DefiniteAssignmentAnalysis daa, string variableName, bool allowPassIntoLoops, BlockStatement block, out Statement declarationPoint) + void FindInsertionPoints(AstNode node, int nodeLevel) { - // declarationPoint: The point where the variable would be declared, if we decide to declare it in this block - declarationPoint = null; - foreach (Statement stmt in block.Statements) { - if (UsesVariable(stmt, variableName)) { - if (declarationPoint == null) - declarationPoint = stmt; - if (!CanMoveVariableUseIntoSubBlock(stmt, variableName, allowPassIntoLoops)) { - // If it's not possible to move the variable use into a nested block, - // we need to declare the variable in this block - return false; - } - // If we can move the variable into the sub-block, we need to ensure that the remaining code - // does not use the value that was assigned by the first sub-block - Statement nextStatement = stmt.GetNextStatement(); - if (nextStatement != null) { - // Analyze the range from the next statement to the end of the block - daa.SetAnalyzedRange(nextStatement, block); - daa.Analyze(variableName); - if (daa.UnassignedVariableUses.Count > 0) { - return false; - } + 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 && rr.Variable.Kind != VariableKind.Parameter && rr.Variable.Kind != VariableKind.Exception) { + 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); } } } - 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 - static bool CanMoveVariableUseIntoSubBlock(Statement stmt, string variableName, bool allowPassIntoLoops) + void ResolveOverlap() { - if (!allowPassIntoLoops && (stmt is ForStatement || stmt is ForeachStatement || stmt is DoWhileStatement || stmt is WhileStatement)) - return false; - - ForStatement forStatement = stmt as ForStatement; - if (forStatement != null && forStatement.Initializers.Count == 1) { - // for-statement is special case: we can move variable declarations into the initializer - ExpressionStatement es = forStatement.Initializers.Single() as ExpressionStatement; - if (es != null) { - AssignmentExpression ae = es.Expression as AssignmentExpression; - if (ae != null && ae.Operator == AssignmentOperatorType.Assign) { - IdentifierExpression ident = ae.Left as IdentifierExpression; - if (ident != null && ident.Identifier == variableName) { - return !UsesVariable(ae.Right, variableName); - } - } - } - } - - UsingStatement usingStatement = stmt as UsingStatement; - if (usingStatement != null) { - // using-statement is special case: we can move variable declarations into the initializer - AssignmentExpression ae = usingStatement.ResourceAcquisition as AssignmentExpression; - if (ae != null && ae.Operator == AssignmentOperatorType.Assign) { - IdentifierExpression ident = ae.Left as IdentifierExpression; - if (ident != null && ident.Identifier == variableName) { - return !UsesVariable(ae.Right, variableName); - } + var multiDict = new MultiDictionary(); + foreach (var v in variableDict.Values) { + // Go up to the next BlockStatement (we can't add variable declarations anywhere else in the AST) + while (!(v.InsertionPoint.nextNode.Parent is BlockStatement)) { + v.InsertionPoint = v.InsertionPoint.Up(); } - } - - IfElseStatement ies = stmt as IfElseStatement; - if (ies != null) { - foreach (var child in IfElseChainChildren(ies)) { - if (!(child is BlockStatement) && UsesVariable(child, variableName)) - return false; - } - return true; - } - - // We can move the variable into a sub-block only if the variable is used in only that sub-block (and not in expressions such as the loop condition) - for (AstNode child = stmt.FirstChild; child != null; child = child.NextSibling) { - if (!(child is BlockStatement) && UsesVariable(child, variableName)) { - if (HasNestedBlocks(child)) { - // catch clauses/switch sections can contain nested blocks - for (AstNode grandchild = child.FirstChild; grandchild != null; grandchild = grandchild.NextSibling) { - if (!(grandchild is BlockStatement) && UsesVariable(grandchild, variableName)) - return false; + + // 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); + if (point1.nextNode.Parent == point2.nextNode.Parent) { + // We found a collision! + prev.RemovedDueToCollision = true; + // 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; } - } else { - return false; + // 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); } - return true; } - static IEnumerable IfElseChainChildren(IfElseStatement ies) - { - IfElseStatement prev; - do { - yield return ies.Condition; - yield return ies.TrueStatement; - prev = ies; - ies = ies.FalseStatement as IfElseStatement; - } while (ies != null); - if (!prev.FalseStatement.IsNull) - yield return prev.FalseStatement; - } - - static bool HasNestedBlocks(AstNode node) + void InsertVariableDeclarations() { - return node is CatchClause || node is NRefactory.CSharp.SwitchSection; - } - - static bool UsesVariable(AstNode node, string variableName) - { - IdentifierExpression ie = node as IdentifierExpression; - if (ie != null && ie.Identifier == variableName) - return true; - - FixedStatement fixedStatement = node as FixedStatement; - if (fixedStatement != null) { - foreach (VariableInitializer v in fixedStatement.Variables) { - if (v.Name == variableName) - return false; // no need to introduce the variable here - } - } - - ForeachStatement foreachStatement = node as ForeachStatement; - if (foreachStatement != null) { - if (foreachStatement.VariableName == variableName) - return false; // no need to introduce the variable here - } - - UsingStatement usingStatement = node as UsingStatement; - if (usingStatement != null) { - VariableDeclarationStatement varDecl = usingStatement.ResourceAcquisition as VariableDeclarationStatement; - if (varDecl != null) { - foreach (VariableInitializer v in varDecl.Variables) { - if (v.Name == variableName) - return false; // no need to introduce the variable here + var replacements = new List>(); + foreach (var v in variableDict.Values) { + if (v.RemovedDueToCollision) + continue; + + AstType type = context.TypeSystemAstBuilder.ConvertType(v.Type); + + var boe = v.InsertionPoint.nextNode as BinaryOperatorExpression; + if (boe != null && boe.Left.IsMatch(new IdentifierExpression(v.Name))) { + + var vds = new VariableDeclarationStatement(type, v.Name, boe.Right.Detach()); + var init = vds.Variables.Single(); + init.AddAnnotation(boe.Left.GetResolveResult()); + foreach (object annotation in boe.Left.Annotations.Concat(boe.Annotations)) { + if (!(annotation is ResolveResult)) { + init.AddAnnotation(annotation); + } } + replacements.Add(new KeyValuePair(v.InsertionPoint.nextNode, vds)); + } else { + Expression initializer = null; + if (v.DefaultInitialization) { + initializer = new DefaultValueExpression(type.Clone()); + } + v.InsertionPoint.nextNode.Parent.InsertChildBefore( + v.InsertionPoint.nextNode, + new VariableDeclarationStatement(type, v.Name, initializer), + BlockStatement.StatementRole); } } - - CatchClause catchClause = node as CatchClause; - if (catchClause != null && catchClause.VariableName == variableName) { - return false; // no need to introduce the variable here - } - - for (AstNode child = node.FirstChild; child != null; child = child.NextSibling) { - if (UsesVariable(child, variableName)) - return true; + // 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); } - return false; } } }