Browse Source

New DeclareVariables implementation.

pull/728/head
Daniel Grunwald 9 years ago
parent
commit
7846e37724
  1. 10
      ICSharpCode.Decompiler/CSharp/Annotations.cs
  2. 3
      ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
  3. 7
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  4. 459
      ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs

10
ICSharpCode.Decompiler/CSharp/Annotations.cs

@ -111,4 +111,14 @@ namespace ICSharpCode.Decompiler.CSharp @@ -111,4 +111,14 @@ namespace ICSharpCode.Decompiler.CSharp
return node.Annotation<ResolveResult>() ?? ErrorResolveResult.UnknownError;
}
}
public class ILVariableResolveResult : ResolveResult
{
public readonly ILVariable Variable;
public ILVariableResolveResult(ILVariable v, IType type) : base(type)
{
this.Variable = v;
}
}
}

3
ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs

@ -39,6 +39,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -39,6 +39,7 @@ namespace ICSharpCode.Decompiler.CSharp
/// </summary>
/// <remarks>
/// 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)
/// </remarks>
public class CSharpDecompiler
{
@ -71,7 +72,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -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(),

7
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -109,7 +109,6 @@ namespace ICSharpCode.Decompiler.CSharp @@ -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 @@ -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)

459
ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs

@ -24,343 +24,216 @@ using System.Threading; @@ -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
{
/// <summary>
/// Moves variable declarations to improved positions.
/// Insert variable declarations.
/// </summary>
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<VariableToDeclare> variablesToDeclare = new List<VariableToDeclare>();
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);
}
/// <summary>Go up one level</summary>
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<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<CapturedVariableAnnotation>() == 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;
/// <summary>
/// Whether the variable needs to be default-initialized.
/// </summary>
public readonly bool DefaultInitialization;
/// <summary>
/// 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.
/// </summary>
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<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;
}
Dictionary<ILVariable, VariableToDeclare> variableDict = new Dictionary<ILVariable, VariableToDeclare>();
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<ILVariable>(), ReplacedAssignment = ae });
return true;
}
try {
this.context = context;
FindInsertionPoints(rootNode, 0);
ResolveOverlap();
InsertVariableDeclarations();
} finally {
variableDict.Clear();
this.context = null;
}
return false;
}
#region FindInsertionPoints
/// <summary>
/// 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.
/// </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<CapturedVariableAnnotation>() == 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;
}
/// <summary>
/// Finds an insertion point in a common parent instruction.
/// </summary>
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<string, VariableToDeclare>();
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<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)
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<KeyValuePair<AstNode, AstNode>>();
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<AstNode, AstNode>(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;
}
}
}

Loading…
Cancel
Save