summaryrefslogtreecommitdiff
path: root/ICSharpCode.Decompiler/Ast/Transforms/DeclareVariables.cs
diff options
context:
space:
mode:
Diffstat (limited to 'ICSharpCode.Decompiler/Ast/Transforms/DeclareVariables.cs')
-rw-r--r--ICSharpCode.Decompiler/Ast/Transforms/DeclareVariables.cs368
1 files changed, 368 insertions, 0 deletions
diff --git a/ICSharpCode.Decompiler/Ast/Transforms/DeclareVariables.cs b/ICSharpCode.Decompiler/Ast/Transforms/DeclareVariables.cs
new file mode 100644
index 00000000..a27f30b4
--- /dev/null
+++ b/ICSharpCode.Decompiler/Ast/Transforms/DeclareVariables.cs
@@ -0,0 +1,368 @@
+// 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;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading;
+using ICSharpCode.Decompiler.ILAst;
+using ICSharpCode.NRefactory.CSharp;
+using ICSharpCode.NRefactory.CSharp.Analysis;
+
+namespace ICSharpCode.Decompiler.Ast.Transforms
+{
+ /// <summary>
+ /// Moves variable declarations to improved positions.
+ /// </summary>
+ public class DeclareVariables : IAstTransform
+ {
+ sealed class VariableToDeclare
+ {
+ public AstType Type;
+ public string Name;
+ public ILVariable ILVariable;
+
+ public AssignmentExpression ReplacedAssignment;
+ public Statement InsertionPoint;
+ }
+
+ readonly CancellationToken cancellationToken;
+ List<VariableToDeclare> variablesToDeclare = new List<VariableToDeclare>();
+
+ public DeclareVariables(DecompilerContext context)
+ {
+ cancellationToken = context.CancellationToken;
+ }
+
+ public void Run(AstNode node)
+ {
+ Run(node, 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);
+ if (v.ILVariable != null)
+ decl.Variables.Single().AddAnnotation(v.ILVariable);
+ block.Statements.InsertBefore(
+ v.InsertionPoint,
+ decl);
+ }
+ }
+ // 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);
+ }
+ }
+ }
+ variablesToDeclare = null;
+ }
+
+ void Run(AstNode node, DefiniteAssignmentAnalysis daa)
+ {
+ BlockStatement block = node as BlockStatement;
+ if (block != null) {
+ var variables = block.Statements.TakeWhile(stmt => stmt is VariableDeclarationStatement)
+ .Cast<VariableDeclarationStatement>().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<ILVariable>();
+ bool allowPassIntoLoops = initializer.Annotation<DelegateConstruction.CapturedVariableAnnotation>() == null;
+ DeclareVariableInBlock(daa, block, varDecl.Type, variableName, v, allowPassIntoLoops);
+ }
+ }
+ }
+ for (AstNode child = node.FirstChild; child != null; child = child.NextSibling) {
+ Run(child, daa);
+ }
+ }
+
+ 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<BlockStatement>()) {
+ 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;
+ }
+
+ bool TryConvertAssignmentExpressionIntoVariableDeclaration(Expression expression, AstType type, string variableName)
+ {
+ 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<ILVariable>(), ReplacedAssignment = ae });
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /// <summary>
+ /// Finds the declaration point for the variable within the specified block.
+ /// </summary>
+ /// <param name="daa">
+ /// Definite assignment analysis, must be prepared for 'block' or one of its parents.
+ /// </param>
+ /// <param name="varDecl">The variable to declare</param>
+ /// <param name="block">The block in which the variable should be declared</param>
+ /// <param name="declarationPoint">
+ /// Output parameter: the first statement within 'block' where the variable needs to be declared.
+ /// </param>
+ /// <returns>
+ /// Returns whether it is possible to move the variable declaration into sub-blocks.
+ /// </returns>
+ 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<DelegateConstruction.CapturedVariableAnnotation>() == null;
+ return FindDeclarationPoint(daa, variableName, allowPassIntoLoops, block, out declarationPoint);
+ }
+
+ static bool FindDeclarationPoint(DefiniteAssignmentAnalysis daa, string variableName, bool allowPassIntoLoops, BlockStatement block, out Statement declarationPoint)
+ {
+ // 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;
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+ static bool CanMoveVariableUseIntoSubBlock(Statement stmt, string variableName, bool allowPassIntoLoops)
+ {
+ 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);
+ }
+ }
+ }
+
+ 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;
+ }
+ } else {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ static IEnumerable<AstNode> 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)
+ {
+ return node is CatchClause || node is 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
+ }
+ }
+ }
+
+ 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;
+ }
+ return false;
+ }
+ }
+}