Browse Source
Improved variable placement.
Improved variable placement.
The variable placement step now happens much later in the decompiler pipeline, which simplifies some AST transforms.pull/100/head

8 changed files with 345 additions and 231 deletions
-
8ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs
-
91ICSharpCode.Decompiler/Ast/DeclareVariableInSmallestScope.cs
-
255ICSharpCode.Decompiler/Ast/Transforms/DeclareVariables.cs
-
27ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs
-
2ICSharpCode.Decompiler/Ast/Transforms/IntroduceUsingDeclarations.cs
-
190ICSharpCode.Decompiler/Ast/Transforms/PatternStatementTransform.cs
-
1ICSharpCode.Decompiler/Ast/Transforms/TransformationPipeline.cs
-
2ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
@ -1,91 +0,0 @@ |
|||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
|||
// This code is distributed under MIT X11 license (for details please see \doc\license.txt)
|
|||
|
|||
using System; |
|||
using System.Linq; |
|||
using ICSharpCode.NRefactory.CSharp; |
|||
using ICSharpCode.NRefactory.CSharp.PatternMatching; |
|||
|
|||
namespace ICSharpCode.Decompiler.Ast |
|||
{ |
|||
/// <summary>
|
|||
/// Helper class for declaring variables.
|
|||
/// </summary>
|
|||
public static class DeclareVariableInSmallestScope |
|||
{ |
|||
static readonly ExpressionStatement assignmentPattern = new ExpressionStatement( |
|||
new AssignmentExpression( |
|||
new NamedNode("ident", new IdentifierExpression()), |
|||
new AnyNode("init") |
|||
)); |
|||
|
|||
/// <summary>
|
|||
/// Declares a variable in the smallest required scope.
|
|||
/// </summary>
|
|||
/// <param name="node">The root of the subtree being searched for the best insertion position</param>
|
|||
/// <param name="type">The type of the new variable</param>
|
|||
/// <param name="name">The name of the new variable</param>
|
|||
/// <param name="allowPassIntoLoops">Whether the variable is allowed to be placed inside a loop</param>
|
|||
public static VariableDeclarationStatement DeclareVariable(AstNode node, AstType type, string name, bool allowPassIntoLoops = true) |
|||
{ |
|||
VariableDeclarationStatement result = null; |
|||
AstNode pos = FindInsertPos(node, name, allowPassIntoLoops); |
|||
if (pos != null) { |
|||
Match m = assignmentPattern.Match(pos); |
|||
if (m != null && m.Get<IdentifierExpression>("ident").Single().Identifier == name) { |
|||
result = new VariableDeclarationStatement(type, name, m.Get<Expression>("init").Single().Detach()); |
|||
result.Variables.Single().CopyAnnotationsFrom(((ExpressionStatement)pos).Expression); |
|||
result.CopyAnnotationsFrom(pos); |
|||
pos.ReplaceWith(result); |
|||
} else { |
|||
result = new VariableDeclarationStatement(type, name); |
|||
pos.Parent.InsertChildBefore(pos, result, BlockStatement.StatementRole); |
|||
} |
|||
} |
|||
return result; |
|||
} |
|||
|
|||
static AstNode FindInsertPos(AstNode node, string name, bool allowPassIntoLoops) |
|||
{ |
|||
AstNode pos = null; |
|||
AstNode withinPos = null; |
|||
while (node != null) { |
|||
IdentifierExpression ident = node as IdentifierExpression; |
|||
if (ident != null && ident.Identifier == name && ident.TypeArguments.Count == 0) |
|||
return node; |
|||
|
|||
FixedStatement fixedStatement = node as FixedStatement; |
|||
if (fixedStatement != null) { |
|||
foreach (VariableInitializer v in fixedStatement.Variables) { |
|||
if (v.Name == name) |
|||
return null; // no need to introduce the variable here
|
|||
} |
|||
} |
|||
|
|||
AstNode withinCurrent = FindInsertPos(node.FirstChild, name, allowPassIntoLoops); |
|||
if (withinCurrent != null) { |
|||
if (pos == null) { |
|||
pos = node; |
|||
withinPos = withinCurrent; |
|||
} else { |
|||
return pos; |
|||
} |
|||
} |
|||
node = node.NextSibling; |
|||
} |
|||
if (withinPos != null && withinPos.Role == BlockStatement.StatementRole && AllowPassInto(pos, allowPassIntoLoops)) |
|||
return withinPos; |
|||
else |
|||
return pos; |
|||
} |
|||
|
|||
static bool AllowPassInto(AstNode node, bool allowPassIntoLoops) |
|||
{ |
|||
if (node is AnonymousMethodExpression || node is LambdaExpression) |
|||
return false; |
|||
if (node is ForStatement || node is ForeachStatement || node is DoWhileStatement || node is WhileStatement) |
|||
return allowPassIntoLoops; |
|||
return true; |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,255 @@ |
|||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
|||
// This code is distributed under MIT X11 license (for details please see \doc\license.txt)
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Diagnostics; |
|||
using System.Linq; |
|||
using System.Threading; |
|||
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 DeclaredVariableAnnotation { |
|||
public readonly ExpressionStatement OriginalAssignmentStatement; |
|||
|
|||
public DeclaredVariableAnnotation(ExpressionStatement originalAssignmentStatement) |
|||
{ |
|||
this.OriginalAssignmentStatement = originalAssignmentStatement; |
|||
} |
|||
} |
|||
static readonly DeclaredVariableAnnotation declaredVariableAnnotation = new DeclaredVariableAnnotation(null); |
|||
|
|||
readonly CancellationToken cancellationToken; |
|||
|
|||
public DeclareVariables(DecompilerContext context) |
|||
{ |
|||
this.cancellationToken = context.CancellationToken; |
|||
} |
|||
|
|||
public void Run(AstNode node) |
|||
{ |
|||
Run(node, null); |
|||
} |
|||
|
|||
void Run(AstNode node, DefiniteAssignmentAnalysis daa) |
|||
{ |
|||
BlockStatement block = node as BlockStatement; |
|||
if (block != null) { |
|||
var variables = block.Statements.TakeWhile(stmt => stmt is VariableDeclarationStatement |
|||
&& stmt.Annotation<DeclaredVariableAnnotation>() == null) |
|||
.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) { |
|||
string variableName = varDecl.Variables.Single().Name; |
|||
bool allowPassIntoLoops = varDecl.Variables.Single().Annotation<DelegateConstruction.CapturedVariableAnnotation>() == null; |
|||
DeclareVariableInBlock(daa, block, varDecl.Type, variableName, allowPassIntoLoops); |
|||
} |
|||
} |
|||
} |
|||
for (AstNode child = node.FirstChild; child != null; child = child.NextSibling) { |
|||
Run(child, daa); |
|||
} |
|||
} |
|||
|
|||
void DeclareVariableInBlock(DefiniteAssignmentAnalysis daa, BlockStatement block, AstType type, string variableName, 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 ok = true; |
|||
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
|
|||
ok = false; |
|||
break; |
|||
} |
|||
// 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 assigend by the first sub-block
|
|||
Statement nextStatement = stmt.NextStatement; |
|||
// The next statement might be a variable declaration that we inserted, and thus does not exist
|
|||
// in the definite assignment graph. Thus we need to look up the corresponding instruction
|
|||
// prior to the introduction of the VariableDeclarationStatement.
|
|||
while (nextStatement is VariableDeclarationStatement) { |
|||
DeclaredVariableAnnotation annotation = nextStatement.Annotation<DeclaredVariableAnnotation>(); |
|||
if (annotation == null) |
|||
break; |
|||
if (annotation.OriginalAssignmentStatement != null) { |
|||
nextStatement = annotation.OriginalAssignmentStatement; |
|||
break; |
|||
} |
|||
nextStatement = nextStatement.NextStatement; |
|||
} |
|||
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) { |
|||
ok = false; |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
if (declarationPoint == null) { |
|||
// The variable isn't used at all
|
|||
return; |
|||
} |
|||
if (ok) { |
|||
// Declare the variable within the sub-blocks
|
|||
foreach (Statement stmt in block.Statements) { |
|||
foreach (BlockStatement subBlock in stmt.Children.OfType<BlockStatement>()) { |
|||
DeclareVariableInBlock(daa, subBlock, type, variableName, allowPassIntoLoops); |
|||
} |
|||
} |
|||
} else { |
|||
// Try converting an assignment expression into a VariableDeclarationStatement
|
|||
ExpressionStatement es = declarationPoint 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) { |
|||
// convert the declarationPoint into a VariableDeclarationStatement
|
|||
declarationPoint.ReplaceWith( |
|||
new VariableDeclarationStatement { |
|||
Type = (AstType)type.Clone(), |
|||
Variables = { |
|||
new VariableInitializer(variableName, ae.Right.Detach()).CopyAnnotationsFrom(ae) |
|||
} |
|||
}.CopyAnnotationsFrom(es).WithAnnotation(new DeclaredVariableAnnotation(es))); |
|||
return; |
|||
} |
|||
} |
|||
} |
|||
// Declare the variable in front of declarationPoint
|
|||
block.Statements.InsertBefore( |
|||
declarationPoint, |
|||
new VariableDeclarationStatement((AstType)type.Clone(), variableName) |
|||
.WithAnnotation(declaredVariableAnnotation)); |
|||
} |
|||
} |
|||
|
|||
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); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
// We can move the variable into a sub-block only if the variable is used in only that sub-block
|
|||
for (AstNode child = stmt.FirstChild; child != null; child = child.NextSibling) { |
|||
if (!(child is BlockStatement) && UsesVariable(child, variableName)) |
|||
return false; |
|||
} |
|||
return true; |
|||
} |
|||
|
|||
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
|
|||
} |
|||
|
|||
for (AstNode child = node.FirstChild; child != null; child = child.NextSibling) { |
|||
if (UsesVariable(child, variableName)) |
|||
return true; |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
#region FindInsertPos
|
|||
static AstNode FindInsertPos(AstNode node, string name, bool allowPassIntoLoops) |
|||
{ |
|||
AstNode pos = null; |
|||
AstNode withinPos = null; |
|||
while (node != null) { |
|||
IdentifierExpression ident = node as IdentifierExpression; |
|||
if (ident != null && ident.Identifier == name && ident.TypeArguments.Count == 0) |
|||
return node; |
|||
|
|||
FixedStatement fixedStatement = node as FixedStatement; |
|||
if (fixedStatement != null) { |
|||
foreach (VariableInitializer v in fixedStatement.Variables) { |
|||
if (v.Name == name) |
|||
return null; // no need to introduce the variable here
|
|||
} |
|||
} |
|||
ForeachStatement foreachStatement = node as ForeachStatement; |
|||
if (foreachStatement != null) { |
|||
if (foreachStatement.VariableName == name) |
|||
return null; // no need to introduce the variable here
|
|||
} |
|||
|
|||
AstNode withinCurrent = FindInsertPos(node.FirstChild, name, allowPassIntoLoops); |
|||
if (withinCurrent != null) { |
|||
if (pos == null) { |
|||
pos = node; |
|||
withinPos = withinCurrent; |
|||
} else { |
|||
return pos; |
|||
} |
|||
} |
|||
node = node.NextSibling; |
|||
} |
|||
if (withinPos != null && withinPos.Role == BlockStatement.StatementRole && AllowPassInto(pos, allowPassIntoLoops)) |
|||
return withinPos; |
|||
else |
|||
return pos; |
|||
} |
|||
|
|||
static bool AllowPassInto(AstNode node, bool allowPassIntoLoops) |
|||
{ |
|||
if (node is AnonymousMethodExpression || node is LambdaExpression) |
|||
return false; |
|||
|
|||
return allowPassIntoLoops; |
|||
return true; |
|||
} |
|||
#endregion
|
|||
} |
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue