mirror of https://github.com/icsharpcode/ILSpy.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
337 lines
14 KiB
337 lines
14 KiB
// 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) |
|
{ |
|
this.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; |
|
} |
|
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); |
|
} |
|
} |
|
} |
|
|
|
// 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 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; |
|
} |
|
} |
|
}
|
|
|