Browse Source

Fixed issues with detection of using statements.

pull/194/merge
Daniel Grunwald 14 years ago
parent
commit
3833643aaf
  1. 58
      ICSharpCode.Decompiler/Ast/Transforms/PatternStatementTransform.cs
  2. 14
      NRefactory/ICSharpCode.NRefactory/CSharp/Analysis/DefiniteAssignmentAnalysis.cs

58
ICSharpCode.Decompiler/Ast/Transforms/PatternStatementTransform.cs

@ -154,7 +154,7 @@ namespace ICSharpCode.Decompiler.Ast.Transforms @@ -154,7 +154,7 @@ namespace ICSharpCode.Decompiler.Ast.Transforms
}
static readonly AstNode usingTryCatchPattern = new TryCatchStatement {
TryBlock = new AnyNode("body"),
TryBlock = new AnyNode(),
FinallyBlock = new BlockStatement {
new Choice {
{ "valueType",
@ -180,7 +180,7 @@ namespace ICSharpCode.Decompiler.Ast.Transforms @@ -180,7 +180,7 @@ namespace ICSharpCode.Decompiler.Ast.Transforms
{
Match m1 = variableAssignPattern.Match(node);
if (!m1.Success) return null;
AstNode tryCatch = node.NextSibling;
TryCatchStatement tryCatch = node.NextSibling as TryCatchStatement;
Match m2 = usingTryCatchPattern.Match(tryCatch);
if (!m2.Success) return null;
string variableName = m1.Get<IdentifierExpression>("variable").Single().Identifier;
@ -207,28 +207,26 @@ namespace ICSharpCode.Decompiler.Ast.Transforms @@ -207,28 +207,26 @@ namespace ICSharpCode.Decompiler.Ast.Transforms
if (varDecl == null || !(varDecl.Parent is BlockStatement))
return null;
// Create the using statement so that we can use definite assignment analysis to check
// whether the variable is declared correctly there.
// Validate that the variable is not used after the using statement:
if (!IsVariableValueUnused(varDecl, tryCatch))
return null;
node.Remove();
BlockStatement body = m2.Get<BlockStatement>("body").Single();
UsingStatement usingStatement = new UsingStatement();
usingStatement.ResourceAcquisition = node.Expression.Detach();
usingStatement.EmbeddedStatement = body.Detach();
usingStatement.EmbeddedStatement = tryCatch.TryBlock.Detach();
tryCatch.ReplaceWith(usingStatement);
// Check whether moving the variable declaration into the resource acquisition is possible
Statement declarationPoint;
if (CanMoveVariableDeclarationIntoStatement(varDecl, usingStatement, out declarationPoint)) {
// Moving the variable into the UsingStatement is allowed.
// But if possible, we'll eliminate the variable completely:
if (body.Descendants.OfType<IdentifierExpression>().Any(ident => ident.Identifier == variableName)) {
// If possible, we'll eliminate the variable completely:
if (usingStatement.EmbeddedStatement.Descendants.OfType<IdentifierExpression>().Any(ident => ident.Identifier == variableName)) {
// variable is used, so we'll create a variable declaration
usingStatement.ResourceAcquisition = new VariableDeclarationStatement {
Type = (AstType)varDecl.Type.Clone(),
Variables = {
new VariableInitializer {
Name = variableName,
Initializer = m1.Get<Expression>("initializer").Single().Detach()
}.CopyAnnotationsFrom(usingStatement.ResourceAcquisition)
}.CopyAnnotationsFrom(node.Expression)
}
}.CopyAnnotationsFrom(node);
} else {
@ -236,15 +234,6 @@ namespace ICSharpCode.Decompiler.Ast.Transforms @@ -236,15 +234,6 @@ namespace ICSharpCode.Decompiler.Ast.Transforms
usingStatement.ResourceAcquisition = m1.Get<Expression>("initializer").Single().Detach();
}
return usingStatement;
} else {
// oops, we can't use a using statement after all; so revert back to try-finally
usingStatement.ReplaceWith(tryCatch);
((TryCatchStatement)tryCatch).TryBlock = body.Detach();
node.Expression = (Expression)usingStatement.ResourceAcquisition.Detach();
tryCatch.Parent.InsertChildBefore(tryCatch, node, BlockStatement.StatementRole);
return null;
}
}
internal static VariableDeclarationStatement FindVariableDeclaration(AstNode node, string identifier)
@ -262,6 +251,29 @@ namespace ICSharpCode.Decompiler.Ast.Transforms @@ -262,6 +251,29 @@ namespace ICSharpCode.Decompiler.Ast.Transforms
return null;
}
/// <summary>
/// Gets whether the old variable value (assigned inside 'targetStatement' or earlier)
/// is read anywhere in the remaining scope of the variable declaration.
/// </summary>
bool IsVariableValueUnused(VariableDeclarationStatement varDecl, Statement targetStatement)
{
Debug.Assert(targetStatement.Ancestors.Contains(varDecl.Parent));
BlockStatement block = (BlockStatement)varDecl.Parent;
DefiniteAssignmentAnalysis daa = new DefiniteAssignmentAnalysis(block, context.CancellationToken);
daa.SetAnalyzedRange(targetStatement, block, startInclusive: false);
daa.Analyze(varDecl.Variables.Single().Name);
return daa.UnassignedVariableUses.Count == 0;
}
// I used this in the first implementation of the using-statement transform, but now no longer
// because there were problems when multiple using statements were using the same variable
// - no single using statement could be transformed without making the C# code invalid,
// but transforming both would work.
// We now use 'IsVariableValueUnused' which will perform the transform
// even if it results in two variables with the same name and overlapping scopes.
// (this issue could be fixed later by renaming one of the variables)
// I'm not sure whether the other consumers of 'CanMoveVariableDeclarationIntoStatement' should be changed the same way.
bool CanMoveVariableDeclarationIntoStatement(VariableDeclarationStatement varDecl, Statement targetStatement, out Statement declarationPoint)
{
Debug.Assert(targetStatement.Ancestors.Contains(varDecl.Parent));

14
NRefactory/ICSharpCode.NRefactory/CSharp/Analysis/DefiniteAssignmentAnalysis.cs

@ -167,12 +167,14 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis @@ -167,12 +167,14 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis
/// This method can be used to restrict the analysis to only a part of the method.
/// Only the control flow paths that are fully contained within the selected part will be analyzed.
/// </summary>
/// <remarks>Both 'start' and 'end' are inclusive.</remarks>
public void SetAnalyzedRange(Statement start, Statement end)
{
Debug.Assert(beginNodeDict.ContainsKey(start) && endNodeDict.ContainsKey(end));
int startIndex = beginNodeDict[start].Index;
int endIndex = endNodeDict[end].Index;
/// <remarks>By default, both 'start' and 'end' are inclusive.</remarks>
public void SetAnalyzedRange(Statement start, Statement end, bool startInclusive = true, bool endInclusive = true)
{
var dictForStart = startInclusive ? beginNodeDict : endNodeDict;
var dictForEnd = endInclusive ? endNodeDict : beginNodeDict;
Debug.Assert(dictForStart.ContainsKey(start) && dictForEnd.ContainsKey(end));
int startIndex = dictForStart[start].Index;
int endIndex = dictForEnd[end].Index;
if (startIndex > endIndex)
throw new ArgumentException("The start statement must be lexically preceding the end statement");
this.analyzedRangeStart = startIndex;

Loading…
Cancel
Save