mirror of https://github.com/icsharpcode/ILSpy.git
17 changed files with 1483 additions and 44 deletions
@ -0,0 +1,132 @@ |
|||||||
|
// 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 NUnit.Framework; |
||||||
|
|
||||||
|
namespace ICSharpCode.NRefactory.CSharp.Analysis |
||||||
|
{ |
||||||
|
[TestFixture] |
||||||
|
public class DefiniteAssignmentTests |
||||||
|
{ |
||||||
|
[Test] |
||||||
|
public void TryFinally() |
||||||
|
{ |
||||||
|
BlockStatement block = new BlockStatement { |
||||||
|
new TryCatchStatement { |
||||||
|
TryBlock = new BlockStatement { |
||||||
|
new GotoStatement("LABEL"), |
||||||
|
new AssignmentExpression(new IdentifierExpression("i"), new PrimitiveExpression(1)) |
||||||
|
}, |
||||||
|
CatchClauses = { |
||||||
|
new CatchClause { |
||||||
|
Body = new BlockStatement { |
||||||
|
new AssignmentExpression(new IdentifierExpression("i"), new PrimitiveExpression(3)) |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
FinallyBlock = new BlockStatement { |
||||||
|
new AssignmentExpression(new IdentifierExpression("j"), new PrimitiveExpression(5)) |
||||||
|
} |
||||||
|
}, |
||||||
|
new LabelStatement { Label = "LABEL" }, |
||||||
|
new EmptyStatement() |
||||||
|
}; |
||||||
|
TryCatchStatement tryCatchStatement = (TryCatchStatement)block.Statements.First(); |
||||||
|
Statement stmt1 = tryCatchStatement.TryBlock.Statements.ElementAt(1); |
||||||
|
Statement stmt3 = tryCatchStatement.CatchClauses.Single().Body.Statements.Single(); |
||||||
|
Statement stmt5 = tryCatchStatement.FinallyBlock.Statements.Single(); |
||||||
|
LabelStatement label = (LabelStatement)block.Statements.ElementAt(1); |
||||||
|
|
||||||
|
DefiniteAssignmentAnalysis da = new DefiniteAssignmentAnalysis(block); |
||||||
|
da.Analyze("i"); |
||||||
|
Assert.AreEqual(0, da.UnassignedVariableUses.Count); |
||||||
|
Assert.AreEqual(DefiniteAssignmentStatus.PotentiallyAssigned, da.GetStatusBefore(tryCatchStatement)); |
||||||
|
Assert.AreEqual(DefiniteAssignmentStatus.CodeUnreachable, da.GetStatusBefore(stmt1)); |
||||||
|
Assert.AreEqual(DefiniteAssignmentStatus.CodeUnreachable, da.GetStatusAfter(stmt1)); |
||||||
|
Assert.AreEqual(DefiniteAssignmentStatus.PotentiallyAssigned, da.GetStatusBefore(stmt3)); |
||||||
|
Assert.AreEqual(DefiniteAssignmentStatus.DefinitelyAssigned, da.GetStatusAfter(stmt3)); |
||||||
|
Assert.AreEqual(DefiniteAssignmentStatus.PotentiallyAssigned, da.GetStatusBefore(stmt5)); |
||||||
|
Assert.AreEqual(DefiniteAssignmentStatus.PotentiallyAssigned, da.GetStatusAfter(stmt5)); |
||||||
|
Assert.AreEqual(DefiniteAssignmentStatus.DefinitelyAssigned, da.GetStatusAfter(tryCatchStatement)); |
||||||
|
Assert.AreEqual(DefiniteAssignmentStatus.DefinitelyAssigned, da.GetStatusBefore(label)); |
||||||
|
Assert.AreEqual(DefiniteAssignmentStatus.PotentiallyAssigned, da.GetStatusAfter(label)); |
||||||
|
|
||||||
|
da.Analyze("j"); |
||||||
|
Assert.AreEqual(0, da.UnassignedVariableUses.Count); |
||||||
|
Assert.AreEqual(DefiniteAssignmentStatus.PotentiallyAssigned, da.GetStatusBefore(tryCatchStatement)); |
||||||
|
Assert.AreEqual(DefiniteAssignmentStatus.CodeUnreachable, da.GetStatusBefore(stmt1)); |
||||||
|
Assert.AreEqual(DefiniteAssignmentStatus.CodeUnreachable, da.GetStatusAfter(stmt1)); |
||||||
|
Assert.AreEqual(DefiniteAssignmentStatus.PotentiallyAssigned, da.GetStatusBefore(stmt3)); |
||||||
|
Assert.AreEqual(DefiniteAssignmentStatus.PotentiallyAssigned, da.GetStatusAfter(stmt3)); |
||||||
|
Assert.AreEqual(DefiniteAssignmentStatus.PotentiallyAssigned, da.GetStatusBefore(stmt5)); |
||||||
|
Assert.AreEqual(DefiniteAssignmentStatus.DefinitelyAssigned, da.GetStatusAfter(stmt5)); |
||||||
|
Assert.AreEqual(DefiniteAssignmentStatus.DefinitelyAssigned, da.GetStatusAfter(tryCatchStatement)); |
||||||
|
Assert.AreEqual(DefiniteAssignmentStatus.DefinitelyAssigned, da.GetStatusBefore(label)); |
||||||
|
Assert.AreEqual(DefiniteAssignmentStatus.DefinitelyAssigned, da.GetStatusAfter(label)); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
public void ConditionalAnd() |
||||||
|
{ |
||||||
|
IfElseStatement ifStmt = new IfElseStatement { |
||||||
|
Condition = new BinaryOperatorExpression { |
||||||
|
Left = new BinaryOperatorExpression(new IdentifierExpression("x"), BinaryOperatorType.GreaterThan, new PrimitiveExpression(0)), |
||||||
|
Operator = BinaryOperatorType.ConditionalAnd, |
||||||
|
Right = new BinaryOperatorExpression { |
||||||
|
Left = new ParenthesizedExpression { |
||||||
|
Expression = new AssignmentExpression { |
||||||
|
Left = new IdentifierExpression("i"), |
||||||
|
Operator = AssignmentOperatorType.Assign, |
||||||
|
Right = new IdentifierExpression("y") |
||||||
|
} |
||||||
|
}, |
||||||
|
Operator = BinaryOperatorType.GreaterThanOrEqual, |
||||||
|
Right = new PrimitiveExpression(0) |
||||||
|
} |
||||||
|
}, |
||||||
|
TrueStatement = new BlockStatement(), |
||||||
|
FalseStatement = new BlockStatement() |
||||||
|
}; |
||||||
|
DefiniteAssignmentAnalysis da = new DefiniteAssignmentAnalysis(ifStmt); |
||||||
|
da.Analyze("i"); |
||||||
|
Assert.AreEqual(0, da.UnassignedVariableUses.Count); |
||||||
|
Assert.AreEqual(DefiniteAssignmentStatus.PotentiallyAssigned, da.GetStatusBefore(ifStmt)); |
||||||
|
Assert.AreEqual(DefiniteAssignmentStatus.DefinitelyAssigned, da.GetStatusBefore(ifStmt.TrueStatement)); |
||||||
|
Assert.AreEqual(DefiniteAssignmentStatus.PotentiallyAssigned, da.GetStatusBefore(ifStmt.FalseStatement)); |
||||||
|
Assert.AreEqual(DefiniteAssignmentStatus.PotentiallyAssigned, da.GetStatusAfter(ifStmt)); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
public void ConditionalOr() |
||||||
|
{ |
||||||
|
IfElseStatement ifStmt = new IfElseStatement { |
||||||
|
Condition = new BinaryOperatorExpression { |
||||||
|
Left = new BinaryOperatorExpression(new IdentifierExpression("x"), BinaryOperatorType.GreaterThan, new PrimitiveExpression(0)), |
||||||
|
Operator = BinaryOperatorType.ConditionalOr, |
||||||
|
Right = new BinaryOperatorExpression { |
||||||
|
Left = new ParenthesizedExpression { |
||||||
|
Expression = new AssignmentExpression { |
||||||
|
Left = new IdentifierExpression("i"), |
||||||
|
Operator = AssignmentOperatorType.Assign, |
||||||
|
Right = new IdentifierExpression("y") |
||||||
|
} |
||||||
|
}, |
||||||
|
Operator = BinaryOperatorType.GreaterThanOrEqual, |
||||||
|
Right = new PrimitiveExpression(0) |
||||||
|
} |
||||||
|
}, |
||||||
|
TrueStatement = new BlockStatement(), |
||||||
|
FalseStatement = new BlockStatement() |
||||||
|
}; |
||||||
|
DefiniteAssignmentAnalysis da = new DefiniteAssignmentAnalysis(ifStmt); |
||||||
|
da.Analyze("i"); |
||||||
|
Assert.AreEqual(0, da.UnassignedVariableUses.Count); |
||||||
|
Assert.AreEqual(DefiniteAssignmentStatus.PotentiallyAssigned, da.GetStatusBefore(ifStmt)); |
||||||
|
Assert.AreEqual(DefiniteAssignmentStatus.PotentiallyAssigned, da.GetStatusBefore(ifStmt.TrueStatement)); |
||||||
|
Assert.AreEqual(DefiniteAssignmentStatus.DefinitelyAssigned, da.GetStatusBefore(ifStmt.FalseStatement)); |
||||||
|
Assert.AreEqual(DefiniteAssignmentStatus.PotentiallyAssigned, da.GetStatusAfter(ifStmt)); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,638 @@ |
|||||||
|
// 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 ICSharpCode.NRefactory.CSharp.Resolver; |
||||||
|
|
||||||
|
namespace ICSharpCode.NRefactory.CSharp.Analysis |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Represents a node in the control flow graph of a C# method.
|
||||||
|
/// </summary>
|
||||||
|
public class ControlFlowNode |
||||||
|
{ |
||||||
|
public readonly Statement PreviousStatement; |
||||||
|
public readonly Statement NextStatement; |
||||||
|
|
||||||
|
public readonly ControlFlowNodeType Type; |
||||||
|
|
||||||
|
public readonly List<ControlFlowEdge> Outgoing = new List<ControlFlowEdge>(); |
||||||
|
public readonly List<ControlFlowEdge> Incoming = new List<ControlFlowEdge>(); |
||||||
|
|
||||||
|
public ControlFlowNode(Statement previousStatement, Statement nextStatement, ControlFlowNodeType type) |
||||||
|
{ |
||||||
|
if (previousStatement == null && nextStatement == null) |
||||||
|
throw new ArgumentException("previousStatement and nextStatement must not be both null"); |
||||||
|
this.PreviousStatement = previousStatement; |
||||||
|
this.NextStatement = nextStatement; |
||||||
|
this.Type = type; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public enum ControlFlowNodeType |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Unknown node type
|
||||||
|
/// </summary>
|
||||||
|
None, |
||||||
|
/// <summary>
|
||||||
|
/// Node in front of a statement
|
||||||
|
/// </summary>
|
||||||
|
StartNode, |
||||||
|
/// <summary>
|
||||||
|
/// Node between two statements
|
||||||
|
/// </summary>
|
||||||
|
BetweenStatements, |
||||||
|
/// <summary>
|
||||||
|
/// Node at the end of a statement list
|
||||||
|
/// </summary>
|
||||||
|
EndNode, |
||||||
|
/// <summary>
|
||||||
|
/// Node representing the position before evaluating the condition of a loop.
|
||||||
|
/// </summary>
|
||||||
|
LoopCondition |
||||||
|
} |
||||||
|
|
||||||
|
public class ControlFlowEdge |
||||||
|
{ |
||||||
|
public readonly ControlFlowNode From; |
||||||
|
public readonly ControlFlowNode To; |
||||||
|
public readonly ControlFlowEdgeType Type; |
||||||
|
|
||||||
|
List<TryCatchStatement> jumpOutOfTryFinally; |
||||||
|
|
||||||
|
public ControlFlowEdge(ControlFlowNode from, ControlFlowNode to, ControlFlowEdgeType type) |
||||||
|
{ |
||||||
|
if (from == null) |
||||||
|
throw new ArgumentNullException("from"); |
||||||
|
if (to == null) |
||||||
|
throw new ArgumentNullException("to"); |
||||||
|
this.From = from; |
||||||
|
this.To = to; |
||||||
|
this.Type = type; |
||||||
|
} |
||||||
|
|
||||||
|
internal void AddJumpOutOfTryFinally(TryCatchStatement tryFinally) |
||||||
|
{ |
||||||
|
if (jumpOutOfTryFinally == null) |
||||||
|
jumpOutOfTryFinally = new List<TryCatchStatement>(); |
||||||
|
jumpOutOfTryFinally.Add(tryFinally); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets whether this control flow edge is leaving any try-finally statements.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsLeavingTryFinally { |
||||||
|
get { return jumpOutOfTryFinally != null; } |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the try-finally statements that this control flow edge is leaving.
|
||||||
|
/// </summary>
|
||||||
|
public IEnumerable<TryCatchStatement> TryFinallyStatements { |
||||||
|
get { return jumpOutOfTryFinally ?? Enumerable.Empty<TryCatchStatement>(); } |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public enum ControlFlowEdgeType |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Regular control flow.
|
||||||
|
/// </summary>
|
||||||
|
Normal, |
||||||
|
/// <summary>
|
||||||
|
/// Conditional control flow (edge taken if condition is true)
|
||||||
|
/// </summary>
|
||||||
|
ConditionTrue, |
||||||
|
/// <summary>
|
||||||
|
/// Conditional control flow (edge taken if condition is false)
|
||||||
|
/// </summary>
|
||||||
|
ConditionFalse, |
||||||
|
/// <summary>
|
||||||
|
/// A jump statement (goto, goto case, break or continue)
|
||||||
|
/// </summary>
|
||||||
|
Jump |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructs the control flow graph for C# statements.
|
||||||
|
/// </summary>
|
||||||
|
public class ControlFlowGraphBuilder |
||||||
|
{ |
||||||
|
// Written according to the reachability rules in the C# spec (§8.1 End points and reachability)
|
||||||
|
|
||||||
|
protected virtual ControlFlowNode CreateNode(Statement previousStatement, Statement nextStatement, ControlFlowNodeType type) |
||||||
|
{ |
||||||
|
return new ControlFlowNode(previousStatement, nextStatement, type); |
||||||
|
} |
||||||
|
|
||||||
|
protected virtual ControlFlowEdge CreateEdge(ControlFlowNode from, ControlFlowNode to, ControlFlowEdgeType type) |
||||||
|
{ |
||||||
|
return new ControlFlowEdge(from, to, type); |
||||||
|
} |
||||||
|
|
||||||
|
Statement rootStatement; |
||||||
|
List<ControlFlowNode> nodes; |
||||||
|
Dictionary<string, ControlFlowNode> labels; |
||||||
|
List<ControlFlowNode> gotoStatements; |
||||||
|
|
||||||
|
public IList<ControlFlowNode> BuildControlFlowGraph(Statement statement) |
||||||
|
{ |
||||||
|
NodeCreationVisitor nodeCreationVisitor = new NodeCreationVisitor(); |
||||||
|
nodeCreationVisitor.builder = this; |
||||||
|
try { |
||||||
|
this.nodes = new List<ControlFlowNode>(); |
||||||
|
this.labels = new Dictionary<string, ControlFlowNode>(); |
||||||
|
this.gotoStatements = new List<ControlFlowNode>(); |
||||||
|
this.rootStatement = statement; |
||||||
|
ControlFlowNode entryPoint = CreateStartNode(statement); |
||||||
|
statement.AcceptVisitor(nodeCreationVisitor, entryPoint); |
||||||
|
|
||||||
|
// Resolve goto statements:
|
||||||
|
foreach (ControlFlowNode gotoStmt in gotoStatements) { |
||||||
|
string label = ((GotoStatement)gotoStmt.NextStatement).Label; |
||||||
|
ControlFlowNode labelNode; |
||||||
|
if (labels.TryGetValue(label, out labelNode)) |
||||||
|
nodeCreationVisitor.Connect(gotoStmt, labelNode, ControlFlowEdgeType.Jump); |
||||||
|
} |
||||||
|
|
||||||
|
AnnotateLeaveEdgesWithTryFinallyBlocks(); |
||||||
|
|
||||||
|
return nodes; |
||||||
|
} finally { |
||||||
|
this.nodes = null; |
||||||
|
this.labels = null; |
||||||
|
this.gotoStatements = null; |
||||||
|
this.rootStatement = null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void AnnotateLeaveEdgesWithTryFinallyBlocks() |
||||||
|
{ |
||||||
|
foreach (ControlFlowEdge edge in nodes.SelectMany(n => n.Outgoing)) { |
||||||
|
if (edge.Type != ControlFlowEdgeType.Jump) { |
||||||
|
// Only jumps are potential candidates for leaving try-finally blocks.
|
||||||
|
// Note that the regular edges leaving try or catch blocks are already annotated by the visitor.
|
||||||
|
continue; |
||||||
|
} |
||||||
|
Statement gotoStatement = edge.From.NextStatement; |
||||||
|
Debug.Assert(gotoStatement is GotoStatement || gotoStatement is GotoDefaultStatement || gotoStatement is GotoCaseStatement || gotoStatement is BreakStatement || gotoStatement is ContinueStatement); |
||||||
|
Statement targetStatement = edge.To.PreviousStatement ?? edge.To.NextStatement; |
||||||
|
if (gotoStatement.Parent == targetStatement.Parent) |
||||||
|
continue; |
||||||
|
HashSet<TryCatchStatement> targetParentTryCatch = new HashSet<TryCatchStatement>(targetStatement.Ancestors.OfType<TryCatchStatement>()); |
||||||
|
for (AstNode node = gotoStatement.Parent; node != null; node = node.Parent) { |
||||||
|
TryCatchStatement leftTryCatch = node as TryCatchStatement; |
||||||
|
if (leftTryCatch != null) { |
||||||
|
if (targetParentTryCatch.Contains(leftTryCatch)) |
||||||
|
break; |
||||||
|
if (!leftTryCatch.FinallyBlock.IsNull) |
||||||
|
edge.AddJumpOutOfTryFinally(leftTryCatch); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#region Create*Node
|
||||||
|
ControlFlowNode CreateStartNode(Statement statement) |
||||||
|
{ |
||||||
|
ControlFlowNode node = CreateNode(null, statement, ControlFlowNodeType.StartNode); |
||||||
|
nodes.Add(node); |
||||||
|
return node; |
||||||
|
} |
||||||
|
|
||||||
|
ControlFlowNode CreateSpecialNode(Statement statement, ControlFlowNodeType type) |
||||||
|
{ |
||||||
|
ControlFlowNode node = CreateNode(statement, null, type); |
||||||
|
nodes.Add(node); |
||||||
|
return node; |
||||||
|
} |
||||||
|
|
||||||
|
ControlFlowNode CreateEndNode(Statement statement) |
||||||
|
{ |
||||||
|
Statement nextStatement; |
||||||
|
if (statement == rootStatement) { |
||||||
|
nextStatement = null; |
||||||
|
} else { |
||||||
|
// Find the next statement in the same role:
|
||||||
|
AstNode next = statement; |
||||||
|
do { |
||||||
|
next = next.NextSibling; |
||||||
|
} while (next != null && next.Role != statement.Role); |
||||||
|
nextStatement = next as Statement; |
||||||
|
} |
||||||
|
ControlFlowNodeType type = nextStatement != null ? ControlFlowNodeType.BetweenStatements : ControlFlowNodeType.EndNode; |
||||||
|
ControlFlowNode node = CreateNode(statement, nextStatement, type); |
||||||
|
nodes.Add(node); |
||||||
|
return node; |
||||||
|
} |
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Constant evaluation
|
||||||
|
/// <summary>
|
||||||
|
/// Evaluates an expression.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The constant value of the expression; or null if the expression is not a constant.</returns>
|
||||||
|
ConstantResolveResult EvaluateConstant(Expression expr) |
||||||
|
{ |
||||||
|
return null; // TODO: implement this using the C# resolver
|
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Evaluates an expression.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The value of the constant boolean expression; or null if the value is not a constant boolean expression.</returns>
|
||||||
|
bool? EvaluateCondition(Expression expr) |
||||||
|
{ |
||||||
|
ConstantResolveResult crr = EvaluateConstant(expr); |
||||||
|
if (crr != null) |
||||||
|
return crr.ConstantValue as bool?; |
||||||
|
else |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
bool AreEqualConstants(ConstantResolveResult c1, ConstantResolveResult c2) |
||||||
|
{ |
||||||
|
return false; // TODO: implement this using the resolver's operator==
|
||||||
|
} |
||||||
|
#endregion
|
||||||
|
|
||||||
|
sealed class NodeCreationVisitor : DepthFirstAstVisitor<ControlFlowNode, ControlFlowNode> |
||||||
|
{ |
||||||
|
// 'data' parameter: input control flow node (start of statement being visited)
|
||||||
|
// Return value: result control flow node (end of statement being visited)
|
||||||
|
|
||||||
|
internal ControlFlowGraphBuilder builder; |
||||||
|
Stack<ControlFlowNode> breakTargets = new Stack<ControlFlowNode>(); |
||||||
|
Stack<ControlFlowNode> continueTargets = new Stack<ControlFlowNode>(); |
||||||
|
List<ControlFlowNode> gotoCaseOrDefault = new List<ControlFlowNode>(); |
||||||
|
|
||||||
|
internal ControlFlowEdge Connect(ControlFlowNode from, ControlFlowNode to, ControlFlowEdgeType type = ControlFlowEdgeType.Normal) |
||||||
|
{ |
||||||
|
ControlFlowEdge edge = builder.CreateEdge(from, to, type); |
||||||
|
from.Outgoing.Add(edge); |
||||||
|
to.Incoming.Add(edge); |
||||||
|
return edge; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an end node for <c>stmt</c> and connects <c>from</c> with the new node.
|
||||||
|
/// </summary>
|
||||||
|
ControlFlowNode CreateConnectedEndNode(Statement stmt, ControlFlowNode from) |
||||||
|
{ |
||||||
|
ControlFlowNode newNode = builder.CreateEndNode(stmt); |
||||||
|
Connect(from, newNode); |
||||||
|
return newNode; |
||||||
|
} |
||||||
|
|
||||||
|
protected override ControlFlowNode VisitChildren(AstNode node, ControlFlowNode data) |
||||||
|
{ |
||||||
|
// We have overrides for all possible expressions and should visit expressions only.
|
||||||
|
throw new NotImplementedException(); |
||||||
|
} |
||||||
|
|
||||||
|
public override ControlFlowNode VisitBlockStatement(BlockStatement blockStatement, ControlFlowNode data) |
||||||
|
{ |
||||||
|
// C# 4.0 spec: §8.2 Blocks
|
||||||
|
ControlFlowNode childNode = HandleStatementList(blockStatement.Statements, data); |
||||||
|
return CreateConnectedEndNode(blockStatement, childNode); |
||||||
|
} |
||||||
|
|
||||||
|
ControlFlowNode HandleStatementList(AstNodeCollection<Statement> statements, ControlFlowNode source) |
||||||
|
{ |
||||||
|
ControlFlowNode childNode = null; |
||||||
|
foreach (Statement stmt in statements) { |
||||||
|
if (childNode == null) { |
||||||
|
childNode = builder.CreateStartNode(stmt); |
||||||
|
Connect(source, childNode); |
||||||
|
} |
||||||
|
Debug.Assert(childNode.NextStatement == stmt); |
||||||
|
childNode = stmt.AcceptVisitor(this, childNode); |
||||||
|
Debug.Assert(childNode.PreviousStatement == stmt); |
||||||
|
} |
||||||
|
return childNode ?? source; |
||||||
|
} |
||||||
|
|
||||||
|
public override ControlFlowNode VisitEmptyStatement(EmptyStatement emptyStatement, ControlFlowNode data) |
||||||
|
{ |
||||||
|
return CreateConnectedEndNode(emptyStatement, data); |
||||||
|
} |
||||||
|
|
||||||
|
public override ControlFlowNode VisitLabelStatement(LabelStatement labelStatement, ControlFlowNode data) |
||||||
|
{ |
||||||
|
ControlFlowNode end = CreateConnectedEndNode(labelStatement, data); |
||||||
|
builder.labels[labelStatement.Label] = end; |
||||||
|
return end; |
||||||
|
} |
||||||
|
|
||||||
|
public override ControlFlowNode VisitVariableDeclarationStatement(VariableDeclarationStatement variableDeclarationStatement, ControlFlowNode data) |
||||||
|
{ |
||||||
|
return CreateConnectedEndNode(variableDeclarationStatement, data); |
||||||
|
} |
||||||
|
|
||||||
|
public override ControlFlowNode VisitExpressionStatement(ExpressionStatement expressionStatement, ControlFlowNode data) |
||||||
|
{ |
||||||
|
return CreateConnectedEndNode(expressionStatement, data); |
||||||
|
} |
||||||
|
|
||||||
|
public override ControlFlowNode VisitIfElseStatement(IfElseStatement ifElseStatement, ControlFlowNode data) |
||||||
|
{ |
||||||
|
bool? cond = builder.EvaluateCondition(ifElseStatement.Condition); |
||||||
|
ControlFlowNode trueBegin = builder.CreateStartNode(ifElseStatement.TrueStatement); |
||||||
|
if (cond != false) |
||||||
|
Connect(data, trueBegin, ControlFlowEdgeType.ConditionTrue); |
||||||
|
ControlFlowNode trueEnd = ifElseStatement.TrueStatement.AcceptVisitor(this, trueBegin); |
||||||
|
ControlFlowNode falseEnd; |
||||||
|
if (ifElseStatement.FalseStatement.IsNull) { |
||||||
|
falseEnd = null; |
||||||
|
} else { |
||||||
|
ControlFlowNode falseBegin = builder.CreateStartNode(ifElseStatement.FalseStatement); |
||||||
|
if (cond != true) |
||||||
|
Connect(data, falseBegin, ControlFlowEdgeType.ConditionFalse); |
||||||
|
falseEnd = ifElseStatement.FalseStatement.AcceptVisitor(this, falseBegin); |
||||||
|
} |
||||||
|
ControlFlowNode end = builder.CreateEndNode(ifElseStatement); |
||||||
|
Connect(trueEnd, end); |
||||||
|
if (falseEnd != null) { |
||||||
|
Connect(falseEnd, end); |
||||||
|
} else if (cond != true) { |
||||||
|
Connect(data, end, ControlFlowEdgeType.ConditionFalse); |
||||||
|
} |
||||||
|
return end; |
||||||
|
} |
||||||
|
|
||||||
|
public override ControlFlowNode VisitSwitchStatement(SwitchStatement switchStatement, ControlFlowNode data) |
||||||
|
{ |
||||||
|
// First, figure out which switch section will get called (if the expression is constant):
|
||||||
|
ConstantResolveResult constant = builder.EvaluateConstant(switchStatement.Expression); |
||||||
|
SwitchSection defaultSection = null; |
||||||
|
SwitchSection sectionMatchedByConstant = null; |
||||||
|
foreach (SwitchSection section in switchStatement.SwitchSections) { |
||||||
|
foreach (CaseLabel label in section.CaseLabels) { |
||||||
|
if (label.Expression.IsNull) { |
||||||
|
defaultSection = section; |
||||||
|
} else if (constant != null) { |
||||||
|
ConstantResolveResult labelConstant = builder.EvaluateConstant(label.Expression); |
||||||
|
if (builder.AreEqualConstants(constant, labelConstant)) |
||||||
|
sectionMatchedByConstant = section; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
if (constant != null && sectionMatchedByConstant == null) |
||||||
|
sectionMatchedByConstant = defaultSection; |
||||||
|
|
||||||
|
int gotoCaseOrDefaultInOuterScope = gotoCaseOrDefault.Count; |
||||||
|
|
||||||
|
ControlFlowNode end = builder.CreateEndNode(switchStatement); |
||||||
|
breakTargets.Push(end); |
||||||
|
foreach (SwitchSection section in switchStatement.SwitchSections) { |
||||||
|
if (constant == null || section == sectionMatchedByConstant) { |
||||||
|
HandleStatementList(section.Statements, data); |
||||||
|
} else { |
||||||
|
// This section is unreachable: pass null to HandleStatementList.
|
||||||
|
HandleStatementList(section.Statements, null); |
||||||
|
} |
||||||
|
// Don't bother connecting the ends of the sections: the 'break' statement takes care of that.
|
||||||
|
} |
||||||
|
breakTargets.Pop(); |
||||||
|
if (defaultSection == null && sectionMatchedByConstant == null) { |
||||||
|
Connect(data, end); |
||||||
|
} |
||||||
|
|
||||||
|
if (gotoCaseOrDefault.Count > gotoCaseOrDefaultInOuterScope) { |
||||||
|
// Resolve 'goto case' statements:
|
||||||
|
throw new NotImplementedException(); |
||||||
|
} |
||||||
|
|
||||||
|
return end; |
||||||
|
} |
||||||
|
|
||||||
|
public override ControlFlowNode VisitGotoCaseStatement(GotoCaseStatement gotoCaseStatement, ControlFlowNode data) |
||||||
|
{ |
||||||
|
gotoCaseOrDefault.Add(data); |
||||||
|
return builder.CreateEndNode(gotoCaseStatement); |
||||||
|
} |
||||||
|
|
||||||
|
public override ControlFlowNode VisitGotoDefaultStatement(GotoDefaultStatement gotoDefaultStatement, ControlFlowNode data) |
||||||
|
{ |
||||||
|
gotoCaseOrDefault.Add(data); |
||||||
|
return builder.CreateEndNode(gotoDefaultStatement); |
||||||
|
} |
||||||
|
|
||||||
|
public override ControlFlowNode VisitWhileStatement(WhileStatement whileStatement, ControlFlowNode data) |
||||||
|
{ |
||||||
|
// <data> <condition> while (cond) { <bodyStart> embeddedStmt; <bodyEnd> } <end>
|
||||||
|
ControlFlowNode end = builder.CreateEndNode(whileStatement); |
||||||
|
ControlFlowNode conditionNode = builder.CreateSpecialNode(whileStatement, ControlFlowNodeType.LoopCondition); |
||||||
|
breakTargets.Push(end); |
||||||
|
continueTargets.Push(conditionNode); |
||||||
|
|
||||||
|
Connect(data, conditionNode); |
||||||
|
|
||||||
|
bool? cond = builder.EvaluateCondition(whileStatement.Condition); |
||||||
|
ControlFlowNode bodyStart = builder.CreateStartNode(whileStatement.EmbeddedStatement); |
||||||
|
if (cond != false) |
||||||
|
Connect(conditionNode, bodyStart, ControlFlowEdgeType.ConditionTrue); |
||||||
|
ControlFlowNode bodyEnd = whileStatement.EmbeddedStatement.AcceptVisitor(this, bodyStart); |
||||||
|
Connect(bodyEnd, conditionNode); |
||||||
|
if (cond != true) |
||||||
|
Connect(conditionNode, end, ControlFlowEdgeType.ConditionFalse); |
||||||
|
|
||||||
|
breakTargets.Pop(); |
||||||
|
continueTargets.Pop(); |
||||||
|
return end; |
||||||
|
} |
||||||
|
|
||||||
|
public override ControlFlowNode VisitDoWhileStatement(DoWhileStatement doWhileStatement, ControlFlowNode data) |
||||||
|
{ |
||||||
|
// <data> do { <bodyStart> embeddedStmt; <bodyEnd>} <condition> while(cond); <end>
|
||||||
|
ControlFlowNode end = builder.CreateEndNode(doWhileStatement); |
||||||
|
ControlFlowNode conditionNode = builder.CreateSpecialNode(doWhileStatement, ControlFlowNodeType.LoopCondition); |
||||||
|
breakTargets.Push(end); |
||||||
|
continueTargets.Push(conditionNode); |
||||||
|
|
||||||
|
ControlFlowNode bodyStart = builder.CreateStartNode(doWhileStatement.EmbeddedStatement); |
||||||
|
Connect(data, bodyStart); |
||||||
|
ControlFlowNode bodyEnd = doWhileStatement.EmbeddedStatement.AcceptVisitor(this, bodyStart); |
||||||
|
Connect(bodyEnd, conditionNode); |
||||||
|
|
||||||
|
bool? cond = builder.EvaluateCondition(doWhileStatement.Condition); |
||||||
|
if (cond != false) |
||||||
|
Connect(conditionNode, bodyStart, ControlFlowEdgeType.ConditionTrue); |
||||||
|
if (cond != true) |
||||||
|
Connect(conditionNode, end, ControlFlowEdgeType.ConditionFalse); |
||||||
|
|
||||||
|
breakTargets.Pop(); |
||||||
|
continueTargets.Pop(); |
||||||
|
return end; |
||||||
|
} |
||||||
|
|
||||||
|
public override ControlFlowNode VisitForStatement(ForStatement forStatement, ControlFlowNode data) |
||||||
|
{ |
||||||
|
data = HandleStatementList(forStatement.Initializers, data); |
||||||
|
// for (initializers <data>; <condition>cond; <iteratorStart>iterators<iteratorEnd>) { <bodyStart> embeddedStmt; <bodyEnd> } <end>
|
||||||
|
ControlFlowNode end = builder.CreateEndNode(forStatement); |
||||||
|
ControlFlowNode conditionNode = builder.CreateSpecialNode(forStatement, ControlFlowNodeType.LoopCondition); |
||||||
|
Connect(data, conditionNode); |
||||||
|
|
||||||
|
int iteratorStartNodeID = builder.nodes.Count; |
||||||
|
ControlFlowNode iteratorEnd = HandleStatementList(forStatement.Iterators, null); |
||||||
|
ControlFlowNode iteratorStart; |
||||||
|
if (iteratorEnd != null) { |
||||||
|
iteratorStart = builder.nodes[iteratorStartNodeID]; |
||||||
|
Connect(iteratorEnd, conditionNode); |
||||||
|
} else { |
||||||
|
iteratorStart = conditionNode; |
||||||
|
} |
||||||
|
|
||||||
|
breakTargets.Push(end); |
||||||
|
continueTargets.Push(iteratorStart); |
||||||
|
|
||||||
|
ControlFlowNode bodyStart = builder.CreateStartNode(forStatement.EmbeddedStatement); |
||||||
|
ControlFlowNode bodyEnd = forStatement.EmbeddedStatement.AcceptVisitor(this, bodyStart); |
||||||
|
Connect(bodyEnd, iteratorStart); |
||||||
|
|
||||||
|
breakTargets.Pop(); |
||||||
|
continueTargets.Pop(); |
||||||
|
|
||||||
|
bool? cond = forStatement.Condition.IsNull ? true : builder.EvaluateCondition(forStatement.Condition); |
||||||
|
if (cond != false) |
||||||
|
Connect(conditionNode, bodyStart, ControlFlowEdgeType.ConditionTrue); |
||||||
|
if (cond != true) |
||||||
|
Connect(conditionNode, end, ControlFlowEdgeType.ConditionFalse); |
||||||
|
|
||||||
|
return end; |
||||||
|
} |
||||||
|
|
||||||
|
ControlFlowNode HandleEmbeddedStatement(Statement embeddedStatement, ControlFlowNode source) |
||||||
|
{ |
||||||
|
if (embeddedStatement == null || embeddedStatement.IsNull) |
||||||
|
return source; |
||||||
|
ControlFlowNode bodyStart = builder.CreateStartNode(embeddedStatement); |
||||||
|
if (source != null) |
||||||
|
Connect(source, bodyStart); |
||||||
|
return embeddedStatement.AcceptVisitor(this, bodyStart); |
||||||
|
} |
||||||
|
|
||||||
|
public override ControlFlowNode VisitForeachStatement(ForeachStatement foreachStatement, ControlFlowNode data) |
||||||
|
{ |
||||||
|
// <data> foreach (<condition>...) { <bodyStart>embeddedStmt<bodyEnd> } <end>
|
||||||
|
ControlFlowNode end = builder.CreateEndNode(foreachStatement); |
||||||
|
ControlFlowNode conditionNode = builder.CreateSpecialNode(foreachStatement, ControlFlowNodeType.LoopCondition); |
||||||
|
Connect(data, conditionNode); |
||||||
|
|
||||||
|
breakTargets.Push(end); |
||||||
|
continueTargets.Push(conditionNode); |
||||||
|
|
||||||
|
ControlFlowNode bodyEnd = HandleEmbeddedStatement(foreachStatement.EmbeddedStatement, conditionNode); |
||||||
|
Connect(bodyEnd, conditionNode); |
||||||
|
|
||||||
|
breakTargets.Pop(); |
||||||
|
continueTargets.Pop(); |
||||||
|
|
||||||
|
Connect(conditionNode, end); |
||||||
|
|
||||||
|
return end; |
||||||
|
} |
||||||
|
|
||||||
|
public override ControlFlowNode VisitBreakStatement(BreakStatement breakStatement, ControlFlowNode data) |
||||||
|
{ |
||||||
|
if (breakTargets.Count > 0) |
||||||
|
Connect(data, breakTargets.Peek(), ControlFlowEdgeType.Jump); |
||||||
|
return builder.CreateEndNode(breakStatement); |
||||||
|
} |
||||||
|
|
||||||
|
public override ControlFlowNode VisitContinueStatement(ContinueStatement continueStatement, ControlFlowNode data) |
||||||
|
{ |
||||||
|
if (continueTargets.Count > 0) |
||||||
|
Connect(data, continueTargets.Peek(), ControlFlowEdgeType.Jump); |
||||||
|
return builder.CreateEndNode(continueStatement); |
||||||
|
} |
||||||
|
|
||||||
|
public override ControlFlowNode VisitGotoStatement(GotoStatement gotoStatement, ControlFlowNode data) |
||||||
|
{ |
||||||
|
builder.gotoStatements.Add(data); |
||||||
|
return builder.CreateEndNode(gotoStatement); |
||||||
|
} |
||||||
|
|
||||||
|
public override ControlFlowNode VisitReturnStatement(ReturnStatement returnStatement, ControlFlowNode data) |
||||||
|
{ |
||||||
|
return builder.CreateEndNode(returnStatement); // end not connected with data
|
||||||
|
} |
||||||
|
|
||||||
|
public override ControlFlowNode VisitThrowStatement(ThrowStatement throwStatement, ControlFlowNode data) |
||||||
|
{ |
||||||
|
return builder.CreateEndNode(throwStatement); // end not connected with data
|
||||||
|
} |
||||||
|
|
||||||
|
public override ControlFlowNode VisitTryCatchStatement(TryCatchStatement tryCatchStatement, ControlFlowNode data) |
||||||
|
{ |
||||||
|
ControlFlowNode end = builder.CreateEndNode(tryCatchStatement); |
||||||
|
var edge = Connect(HandleEmbeddedStatement(tryCatchStatement.TryBlock, data), end); |
||||||
|
if (!tryCatchStatement.FinallyBlock.IsNull) |
||||||
|
edge.AddJumpOutOfTryFinally(tryCatchStatement); |
||||||
|
foreach (CatchClause cc in tryCatchStatement.CatchClauses) { |
||||||
|
edge = Connect(HandleEmbeddedStatement(cc.Body, data), end); |
||||||
|
if (!tryCatchStatement.FinallyBlock.IsNull) |
||||||
|
edge.AddJumpOutOfTryFinally(tryCatchStatement); |
||||||
|
} |
||||||
|
if (!tryCatchStatement.FinallyBlock.IsNull) { |
||||||
|
// Don't connect the end of the try-finally block to anything.
|
||||||
|
// Consumers of the CFG will have to special-case try-finally.
|
||||||
|
HandleEmbeddedStatement(tryCatchStatement.FinallyBlock, data); |
||||||
|
} |
||||||
|
return end; |
||||||
|
} |
||||||
|
|
||||||
|
public override ControlFlowNode VisitCheckedStatement(CheckedStatement checkedStatement, ControlFlowNode data) |
||||||
|
{ |
||||||
|
ControlFlowNode bodyEnd = HandleEmbeddedStatement(checkedStatement.Body, data); |
||||||
|
return CreateConnectedEndNode(checkedStatement, bodyEnd); |
||||||
|
} |
||||||
|
|
||||||
|
public override ControlFlowNode VisitUncheckedStatement(UncheckedStatement uncheckedStatement, ControlFlowNode data) |
||||||
|
{ |
||||||
|
ControlFlowNode bodyEnd = HandleEmbeddedStatement(uncheckedStatement.Body, data); |
||||||
|
return CreateConnectedEndNode(uncheckedStatement, bodyEnd); |
||||||
|
} |
||||||
|
|
||||||
|
public override ControlFlowNode VisitLockStatement(LockStatement lockStatement, ControlFlowNode data) |
||||||
|
{ |
||||||
|
ControlFlowNode bodyEnd = HandleEmbeddedStatement(lockStatement.EmbeddedStatement, data); |
||||||
|
return CreateConnectedEndNode(lockStatement, bodyEnd); |
||||||
|
} |
||||||
|
|
||||||
|
public override ControlFlowNode VisitUsingStatement(UsingStatement usingStatement, ControlFlowNode data) |
||||||
|
{ |
||||||
|
data = HandleEmbeddedStatement(usingStatement.ResourceAcquisition as Statement, data); |
||||||
|
ControlFlowNode bodyEnd = HandleEmbeddedStatement(usingStatement.EmbeddedStatement, data); |
||||||
|
return CreateConnectedEndNode(usingStatement, bodyEnd); |
||||||
|
} |
||||||
|
|
||||||
|
public override ControlFlowNode VisitYieldStatement(YieldStatement yieldStatement, ControlFlowNode data) |
||||||
|
{ |
||||||
|
return CreateConnectedEndNode(yieldStatement, data); |
||||||
|
} |
||||||
|
|
||||||
|
public override ControlFlowNode VisitYieldBreakStatement(YieldBreakStatement yieldBreakStatement, ControlFlowNode data) |
||||||
|
{ |
||||||
|
return builder.CreateEndNode(yieldBreakStatement); // end not connected with data
|
||||||
|
} |
||||||
|
|
||||||
|
public override ControlFlowNode VisitUnsafeStatement(UnsafeStatement unsafeStatement, ControlFlowNode data) |
||||||
|
{ |
||||||
|
ControlFlowNode bodyEnd = HandleEmbeddedStatement(unsafeStatement.Body, data); |
||||||
|
return CreateConnectedEndNode(unsafeStatement, bodyEnd); |
||||||
|
} |
||||||
|
|
||||||
|
public override ControlFlowNode VisitFixedStatement(FixedStatement fixedStatement, ControlFlowNode data) |
||||||
|
{ |
||||||
|
ControlFlowNode bodyEnd = HandleEmbeddedStatement(fixedStatement.EmbeddedStatement, data); |
||||||
|
return CreateConnectedEndNode(fixedStatement, bodyEnd); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,640 @@ |
|||||||
|
// 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 ICSharpCode.NRefactory.CSharp.Resolver; |
||||||
|
using ICSharpCode.NRefactory.Utils; |
||||||
|
|
||||||
|
namespace ICSharpCode.NRefactory.CSharp.Analysis |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Represents the definite assignment status of a variable at a specific location.
|
||||||
|
/// </summary>
|
||||||
|
public enum DefiniteAssignmentStatus |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// The variable might be assigned or unassigned.
|
||||||
|
/// </summary>
|
||||||
|
PotentiallyAssigned, |
||||||
|
/// <summary>
|
||||||
|
/// The variable is definitely assigned.
|
||||||
|
/// </summary>
|
||||||
|
DefinitelyAssigned, |
||||||
|
/// <summary>
|
||||||
|
/// The variable is definitely assigned iff the expression results in the value 'true'.
|
||||||
|
/// </summary>
|
||||||
|
AssignedAfterTrueExpression, |
||||||
|
/// <summary>
|
||||||
|
/// The variable is definitely assigned iff the expression results in the value 'false'.
|
||||||
|
/// </summary>
|
||||||
|
AssignedAfterFalseExpression, |
||||||
|
/// <summary>
|
||||||
|
/// The code is unreachable.
|
||||||
|
/// </summary>
|
||||||
|
CodeUnreachable |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Implements the C# definite assignment analysis (C# 4.0 Spec: §5.3 Definite assignment)
|
||||||
|
/// </summary>
|
||||||
|
public class DefiniteAssignmentAnalysis |
||||||
|
{ |
||||||
|
readonly DefiniteAssignmentVisitor visitor = new DefiniteAssignmentVisitor(); |
||||||
|
readonly List<ControlFlowNode> allNodes = new List<ControlFlowNode>(); |
||||||
|
readonly Dictionary<Statement, ControlFlowNode> beginNodeDict = new Dictionary<Statement, ControlFlowNode>(); |
||||||
|
readonly Dictionary<Statement, ControlFlowNode> endNodeDict = new Dictionary<Statement, ControlFlowNode>(); |
||||||
|
Dictionary<ControlFlowNode, DefiniteAssignmentStatus> nodeStatus = new Dictionary<ControlFlowNode, DefiniteAssignmentStatus>(); |
||||||
|
Dictionary<ControlFlowEdge, DefiniteAssignmentStatus> edgeStatus = new Dictionary<ControlFlowEdge, DefiniteAssignmentStatus>(); |
||||||
|
|
||||||
|
string variableName; |
||||||
|
List<IdentifierExpression> unassignedVariableUses = new List<IdentifierExpression>(); |
||||||
|
|
||||||
|
Queue<ControlFlowNode> nodesWithModifiedInput = new Queue<ControlFlowNode>(); |
||||||
|
|
||||||
|
public DefiniteAssignmentAnalysis(Statement rootStatement) |
||||||
|
{ |
||||||
|
visitor.analysis = this; |
||||||
|
ControlFlowGraphBuilder b = new ControlFlowGraphBuilder(); |
||||||
|
allNodes.AddRange(b.BuildControlFlowGraph(rootStatement)); |
||||||
|
foreach (AstNode descendant in rootStatement.Descendants) { |
||||||
|
// Anonymous methods have separate control flow graphs, but we also need to analyze those.
|
||||||
|
AnonymousMethodExpression ame = descendant as AnonymousMethodExpression; |
||||||
|
if (ame != null) |
||||||
|
allNodes.AddRange(b.BuildControlFlowGraph(ame.Body)); |
||||||
|
LambdaExpression lambda = descendant as LambdaExpression; |
||||||
|
if (lambda != null && lambda.Body is Statement) |
||||||
|
allNodes.AddRange(b.BuildControlFlowGraph((Statement)lambda.Body)); |
||||||
|
} |
||||||
|
// Verify that we created nodes for all statements:
|
||||||
|
Debug.Assert(!rootStatement.DescendantsAndSelf.OfType<Statement>().Except(allNodes.Select(n => n.NextStatement)).Any()); |
||||||
|
// Now register the nodes in the dictionaries:
|
||||||
|
foreach (ControlFlowNode node in allNodes) { |
||||||
|
if (node.Type == ControlFlowNodeType.StartNode || node.Type == ControlFlowNodeType.BetweenStatements) |
||||||
|
beginNodeDict.Add(node.NextStatement, node); |
||||||
|
if (node.Type == ControlFlowNodeType.BetweenStatements || node.Type == ControlFlowNodeType.EndNode) |
||||||
|
endNodeDict.Add(node.PreviousStatement, node); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the unassigned usages of the previously analyzed variable.
|
||||||
|
/// </summary>
|
||||||
|
public IList<IdentifierExpression> UnassignedVariableUses { |
||||||
|
get { |
||||||
|
return unassignedVariableUses.AsReadOnly(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void Analyze(string variable, DefiniteAssignmentStatus initialStatus = DefiniteAssignmentStatus.PotentiallyAssigned) |
||||||
|
{ |
||||||
|
this.variableName = variable; |
||||||
|
// Reset the status:
|
||||||
|
unassignedVariableUses.Clear(); |
||||||
|
foreach (ControlFlowNode node in allNodes) { |
||||||
|
nodeStatus[node] = DefiniteAssignmentStatus.CodeUnreachable; |
||||||
|
foreach (ControlFlowEdge edge in node.Outgoing) |
||||||
|
edgeStatus[edge] = DefiniteAssignmentStatus.CodeUnreachable; |
||||||
|
} |
||||||
|
|
||||||
|
ChangeNodeStatus(allNodes[0], initialStatus); |
||||||
|
// Iterate as long as the input status of some nodes is changing:
|
||||||
|
while (nodesWithModifiedInput.Count > 0) { |
||||||
|
ControlFlowNode node = nodesWithModifiedInput.Dequeue(); |
||||||
|
DefiniteAssignmentStatus inputStatus = DefiniteAssignmentStatus.CodeUnreachable; |
||||||
|
foreach (ControlFlowEdge edge in node.Incoming) { |
||||||
|
inputStatus = MergeStatus(inputStatus, edgeStatus[edge]); |
||||||
|
} |
||||||
|
ChangeNodeStatus(node, inputStatus); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public DefiniteAssignmentStatus GetStatusBefore(Statement statement) |
||||||
|
{ |
||||||
|
return nodeStatus[beginNodeDict[statement]]; |
||||||
|
} |
||||||
|
|
||||||
|
public DefiniteAssignmentStatus GetStatusAfter(Statement statement) |
||||||
|
{ |
||||||
|
return nodeStatus[endNodeDict[statement]]; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Exports the CFG. This method is intended to help debugging issues related to definite assignment.
|
||||||
|
/// </summary>
|
||||||
|
public GraphVizGraph ExportGraph() |
||||||
|
{ |
||||||
|
GraphVizGraph g = new GraphVizGraph(); |
||||||
|
g.Title = "DefiniteAssignment - " + variableName; |
||||||
|
for (int i = 0; i < allNodes.Count; i++) { |
||||||
|
string name = nodeStatus[allNodes[i]].ToString() + Environment.NewLine; |
||||||
|
switch (allNodes[i].Type) { |
||||||
|
case ControlFlowNodeType.StartNode: |
||||||
|
case ControlFlowNodeType.BetweenStatements: |
||||||
|
name += allNodes[i].NextStatement.ToString(); |
||||||
|
break; |
||||||
|
case ControlFlowNodeType.EndNode: |
||||||
|
name += "End of " + allNodes[i].PreviousStatement.ToString(); |
||||||
|
break; |
||||||
|
case ControlFlowNodeType.LoopCondition: |
||||||
|
name += "Condition in " + allNodes[i].NextStatement.ToString(); |
||||||
|
break; |
||||||
|
default: |
||||||
|
name += allNodes[i].Type.ToString(); |
||||||
|
break; |
||||||
|
} |
||||||
|
g.AddNode(new GraphVizNode(i) { label = name }); |
||||||
|
foreach (ControlFlowEdge edge in allNodes[i].Outgoing) { |
||||||
|
GraphVizEdge ge = new GraphVizEdge(i, allNodes.IndexOf(edge.To)); |
||||||
|
if (edgeStatus.Count > 0) |
||||||
|
ge.label = edgeStatus[edge].ToString(); |
||||||
|
if (edge.IsLeavingTryFinally) |
||||||
|
ge.style = "dashed"; |
||||||
|
switch (edge.Type) { |
||||||
|
case ControlFlowEdgeType.ConditionTrue: |
||||||
|
ge.color = "green"; |
||||||
|
break; |
||||||
|
case ControlFlowEdgeType.ConditionFalse: |
||||||
|
ge.color = "red"; |
||||||
|
break; |
||||||
|
case ControlFlowEdgeType.Jump: |
||||||
|
ge.color = "blue"; |
||||||
|
break; |
||||||
|
} |
||||||
|
g.AddEdge(ge); |
||||||
|
} |
||||||
|
} |
||||||
|
return g; |
||||||
|
} |
||||||
|
|
||||||
|
static DefiniteAssignmentStatus MergeStatus(DefiniteAssignmentStatus a, DefiniteAssignmentStatus b) |
||||||
|
{ |
||||||
|
// The result will be DefinitelyAssigned if at least one incoming edge is DefinitelyAssigned and all others are unreachable.
|
||||||
|
// The result will be DefinitelyUnassigned if at least one incoming edge is DefinitelyUnassigned and all others are unreachable.
|
||||||
|
// The result will be Unreachable if all incoming edges are unreachable.
|
||||||
|
// Otherwise, the result will be PotentiallyAssigned.
|
||||||
|
|
||||||
|
if (a == b) |
||||||
|
return a; |
||||||
|
else if (a == DefiniteAssignmentStatus.CodeUnreachable) |
||||||
|
return b; |
||||||
|
else if (b == DefiniteAssignmentStatus.CodeUnreachable) |
||||||
|
return a; |
||||||
|
else |
||||||
|
return DefiniteAssignmentStatus.PotentiallyAssigned; |
||||||
|
} |
||||||
|
|
||||||
|
void ChangeNodeStatus(ControlFlowNode node, DefiniteAssignmentStatus inputStatus) |
||||||
|
{ |
||||||
|
if (nodeStatus[node] == inputStatus) |
||||||
|
return; |
||||||
|
nodeStatus[node] = inputStatus; |
||||||
|
DefiniteAssignmentStatus outputStatus; |
||||||
|
switch (node.Type) { |
||||||
|
case ControlFlowNodeType.StartNode: |
||||||
|
case ControlFlowNodeType.BetweenStatements: |
||||||
|
if (node.NextStatement is IfElseStatement) { |
||||||
|
// Handle if-else as a condition node
|
||||||
|
goto case ControlFlowNodeType.LoopCondition; |
||||||
|
} |
||||||
|
if (inputStatus == DefiniteAssignmentStatus.DefinitelyAssigned) { |
||||||
|
// There isn't any way to un-assign variables, so we don't have to check the expression
|
||||||
|
// if the status already is definitely assigned.
|
||||||
|
outputStatus = DefiniteAssignmentStatus.DefinitelyAssigned; |
||||||
|
} else { |
||||||
|
outputStatus = CleanSpecialValues(node.NextStatement.AcceptVisitor(visitor, inputStatus)); |
||||||
|
} |
||||||
|
break; |
||||||
|
case ControlFlowNodeType.EndNode: |
||||||
|
outputStatus = inputStatus; |
||||||
|
if (node.PreviousStatement.Role == TryCatchStatement.FinallyBlockRole |
||||||
|
&& (outputStatus == DefiniteAssignmentStatus.DefinitelyAssigned || outputStatus == DefiniteAssignmentStatus.PotentiallyAssigned)) |
||||||
|
{ |
||||||
|
TryCatchStatement tryFinally = (TryCatchStatement)node.PreviousStatement.Parent; |
||||||
|
// Changing the status on a finally block potentially changes the status of all edges leaving that finally block:
|
||||||
|
foreach (ControlFlowEdge edge in allNodes.SelectMany(n => n.Outgoing)) { |
||||||
|
if (edge.IsLeavingTryFinally && edge.TryFinallyStatements.Contains(tryFinally)) { |
||||||
|
DefiniteAssignmentStatus s = edgeStatus[edge]; |
||||||
|
if (s == DefiniteAssignmentStatus.PotentiallyAssigned) { |
||||||
|
ChangeEdgeStatus(edge, outputStatus); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
break; |
||||||
|
case ControlFlowNodeType.LoopCondition: |
||||||
|
ForeachStatement foreachStmt = node.NextStatement as ForeachStatement; |
||||||
|
if (foreachStmt != null) { |
||||||
|
outputStatus = CleanSpecialValues(foreachStmt.InExpression.AcceptVisitor(visitor, inputStatus)); |
||||||
|
if (foreachStmt.VariableName == this.variableName) |
||||||
|
outputStatus = DefiniteAssignmentStatus.DefinitelyAssigned; |
||||||
|
break; |
||||||
|
} else { |
||||||
|
Debug.Assert(node.NextStatement is IfElseStatement || node.NextStatement is WhileStatement || node.NextStatement is ForStatement || node.NextStatement is DoWhileStatement); |
||||||
|
Expression condition = node.NextStatement.GetChildByRole(AstNode.Roles.Condition); |
||||||
|
if (condition.IsNull) |
||||||
|
outputStatus = inputStatus; |
||||||
|
else |
||||||
|
outputStatus = condition.AcceptVisitor(visitor, inputStatus); |
||||||
|
foreach (ControlFlowEdge edge in node.Outgoing) { |
||||||
|
if (edge.Type == ControlFlowEdgeType.ConditionTrue && outputStatus == DefiniteAssignmentStatus.AssignedAfterTrueExpression) { |
||||||
|
ChangeEdgeStatus(edge, DefiniteAssignmentStatus.DefinitelyAssigned); |
||||||
|
} else if (edge.Type == ControlFlowEdgeType.ConditionFalse && outputStatus == DefiniteAssignmentStatus.AssignedAfterFalseExpression) { |
||||||
|
ChangeEdgeStatus(edge, DefiniteAssignmentStatus.DefinitelyAssigned); |
||||||
|
} else { |
||||||
|
ChangeEdgeStatus(edge, CleanSpecialValues(outputStatus)); |
||||||
|
} |
||||||
|
} |
||||||
|
return; |
||||||
|
} |
||||||
|
default: |
||||||
|
throw new InvalidOperationException(); |
||||||
|
} |
||||||
|
foreach (ControlFlowEdge edge in node.Outgoing) { |
||||||
|
ChangeEdgeStatus(edge, outputStatus); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void ChangeEdgeStatus(ControlFlowEdge edge, DefiniteAssignmentStatus newStatus) |
||||||
|
{ |
||||||
|
DefiniteAssignmentStatus oldStatus = edgeStatus[edge]; |
||||||
|
if (oldStatus == newStatus) |
||||||
|
return; |
||||||
|
// Ensure that status can change only in one direction:
|
||||||
|
// CodeUnreachable -> PotentiallyAssigned -> DefinitelyAssigned
|
||||||
|
// Going against this direction indicates a bug and could cause infinite loops.
|
||||||
|
switch (oldStatus) { |
||||||
|
case DefiniteAssignmentStatus.PotentiallyAssigned: |
||||||
|
if (newStatus != DefiniteAssignmentStatus.DefinitelyAssigned) |
||||||
|
throw new InvalidOperationException("Invalid state transition"); |
||||||
|
break; |
||||||
|
case DefiniteAssignmentStatus.CodeUnreachable: |
||||||
|
if (!(newStatus == DefiniteAssignmentStatus.PotentiallyAssigned || newStatus == DefiniteAssignmentStatus.DefinitelyAssigned)) |
||||||
|
throw new InvalidOperationException("Invalid state transition"); |
||||||
|
break; |
||||||
|
case DefiniteAssignmentStatus.DefinitelyAssigned: |
||||||
|
throw new InvalidOperationException("Invalid state transition"); |
||||||
|
default: |
||||||
|
throw new InvalidOperationException("Invalid value for DefiniteAssignmentStatus"); |
||||||
|
} |
||||||
|
edgeStatus[edge] = newStatus; |
||||||
|
nodesWithModifiedInput.Enqueue(edge.To); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Evaluates an expression.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The constant value of the expression; or null if the expression is not a constant.</returns>
|
||||||
|
ConstantResolveResult EvaluateConstant(Expression expr) |
||||||
|
{ |
||||||
|
return null; // TODO: implement this using the C# resolver
|
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Evaluates an expression.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The value of the constant boolean expression; or null if the value is not a constant boolean expression.</returns>
|
||||||
|
bool? EvaluateCondition(Expression expr) |
||||||
|
{ |
||||||
|
ConstantResolveResult crr = EvaluateConstant(expr); |
||||||
|
if (crr != null) |
||||||
|
return crr.ConstantValue as bool?; |
||||||
|
else |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
static DefiniteAssignmentStatus CleanSpecialValues(DefiniteAssignmentStatus status) |
||||||
|
{ |
||||||
|
if (status == DefiniteAssignmentStatus.AssignedAfterTrueExpression) |
||||||
|
return DefiniteAssignmentStatus.PotentiallyAssigned; |
||||||
|
else if (status == DefiniteAssignmentStatus.AssignedAfterFalseExpression) |
||||||
|
return DefiniteAssignmentStatus.PotentiallyAssigned; |
||||||
|
else |
||||||
|
return status; |
||||||
|
} |
||||||
|
|
||||||
|
sealed class DefiniteAssignmentVisitor : DepthFirstAstVisitor<DefiniteAssignmentStatus, DefiniteAssignmentStatus> |
||||||
|
{ |
||||||
|
internal DefiniteAssignmentAnalysis analysis; |
||||||
|
|
||||||
|
// The general approach for unknown nodes is to pass the status through all child nodes in order
|
||||||
|
protected override DefiniteAssignmentStatus VisitChildren(AstNode node, DefiniteAssignmentStatus data) |
||||||
|
{ |
||||||
|
// the special values are valid as output only, not as input
|
||||||
|
Debug.Assert(data == CleanSpecialValues(data)); |
||||||
|
DefiniteAssignmentStatus status = data; |
||||||
|
foreach (AstNode child in node.Children) { |
||||||
|
Debug.Assert(!(child is Statement)); // statements are visited with the CFG, not with the visitor pattern
|
||||||
|
status = child.AcceptVisitor(this, status); |
||||||
|
status = CleanSpecialValues(status); |
||||||
|
} |
||||||
|
return status; |
||||||
|
} |
||||||
|
|
||||||
|
#region Statements
|
||||||
|
// For statements, the visitor only describes the effect of the statement itself;
|
||||||
|
// we do not consider the effect of any nested statements.
|
||||||
|
// This is done because the nested statements will be reached using the control flow graph.
|
||||||
|
|
||||||
|
// In fact, these methods are present so that the default logic in VisitChildren does not try to visit the nested statements.
|
||||||
|
|
||||||
|
public override DefiniteAssignmentStatus VisitBlockStatement(BlockStatement blockStatement, DefiniteAssignmentStatus data) |
||||||
|
{ |
||||||
|
return data; |
||||||
|
} |
||||||
|
|
||||||
|
public override DefiniteAssignmentStatus VisitCheckedStatement(CheckedStatement checkedStatement, DefiniteAssignmentStatus data) |
||||||
|
{ |
||||||
|
return data; |
||||||
|
} |
||||||
|
|
||||||
|
public override DefiniteAssignmentStatus VisitUncheckedStatement(UncheckedStatement uncheckedStatement, DefiniteAssignmentStatus data) |
||||||
|
{ |
||||||
|
return data; |
||||||
|
} |
||||||
|
|
||||||
|
// ExpressionStatement handled by default logic
|
||||||
|
// VariableDeclarationStatement handled by default logic
|
||||||
|
|
||||||
|
public override DefiniteAssignmentStatus VisitVariableInitializer(VariableInitializer variableInitializer, DefiniteAssignmentStatus data) |
||||||
|
{ |
||||||
|
if (variableInitializer.Initializer.IsNull) { |
||||||
|
return data; |
||||||
|
} else { |
||||||
|
DefiniteAssignmentStatus status = variableInitializer.Initializer.AcceptVisitor(this, data); |
||||||
|
if (variableInitializer.Name == analysis.variableName) |
||||||
|
return DefiniteAssignmentStatus.DefinitelyAssigned; |
||||||
|
else |
||||||
|
return status; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// IfStatement not handled by visitor, but special-cased in the code consuming the control flow graph
|
||||||
|
|
||||||
|
public override DefiniteAssignmentStatus VisitSwitchStatement(SwitchStatement switchStatement, DefiniteAssignmentStatus data) |
||||||
|
{ |
||||||
|
return switchStatement.Expression.AcceptVisitor(this, data); |
||||||
|
} |
||||||
|
|
||||||
|
public override DefiniteAssignmentStatus VisitWhileStatement(WhileStatement whileStatement, DefiniteAssignmentStatus data) |
||||||
|
{ |
||||||
|
return data; // condition is handled by special condition CFG node
|
||||||
|
} |
||||||
|
|
||||||
|
public override DefiniteAssignmentStatus VisitDoWhileStatement(DoWhileStatement doWhileStatement, DefiniteAssignmentStatus data) |
||||||
|
{ |
||||||
|
return data; // condition is handled by special condition CFG node
|
||||||
|
} |
||||||
|
|
||||||
|
public override DefiniteAssignmentStatus VisitForStatement(ForStatement forStatement, DefiniteAssignmentStatus data) |
||||||
|
{ |
||||||
|
return data; // condition is handled by special condition CFG node; initializer and iterator statements are handled by CFG
|
||||||
|
} |
||||||
|
|
||||||
|
// Break/Continue/Goto: handled by default logic
|
||||||
|
|
||||||
|
// ThrowStatement: handled by default logic (just visit the expression)
|
||||||
|
// ReturnStatement: handled by default logic (just visit the expression)
|
||||||
|
|
||||||
|
public override DefiniteAssignmentStatus VisitTryCatchStatement(TryCatchStatement tryCatchStatement, DefiniteAssignmentStatus data) |
||||||
|
{ |
||||||
|
return data; // no special logic when entering the try-catch-finally statement
|
||||||
|
// TODO: where to put the special logic when exiting the try-finally statement?
|
||||||
|
} |
||||||
|
|
||||||
|
public override DefiniteAssignmentStatus VisitForeachStatement(ForeachStatement foreachStatement, DefiniteAssignmentStatus data) |
||||||
|
{ |
||||||
|
return data; // assignment of the foreach loop variable is done when handling the condition node
|
||||||
|
} |
||||||
|
|
||||||
|
public override DefiniteAssignmentStatus VisitUsingStatement(UsingStatement usingStatement, DefiniteAssignmentStatus data) |
||||||
|
{ |
||||||
|
if (usingStatement.ResourceAcquisition is Expression) |
||||||
|
return usingStatement.ResourceAcquisition.AcceptVisitor(this, data); |
||||||
|
else |
||||||
|
return data; // don't handle resource acquisition statements, as those are connected in the control flow graph
|
||||||
|
} |
||||||
|
|
||||||
|
public override DefiniteAssignmentStatus VisitLockStatement(LockStatement lockStatement, DefiniteAssignmentStatus data) |
||||||
|
{ |
||||||
|
return lockStatement.Expression.AcceptVisitor(this, data); |
||||||
|
} |
||||||
|
|
||||||
|
// Yield statements use the default logic
|
||||||
|
|
||||||
|
public override DefiniteAssignmentStatus VisitUnsafeStatement(UnsafeStatement unsafeStatement, DefiniteAssignmentStatus data) |
||||||
|
{ |
||||||
|
return data; |
||||||
|
} |
||||||
|
|
||||||
|
public override DefiniteAssignmentStatus VisitFixedStatement(FixedStatement fixedStatement, DefiniteAssignmentStatus data) |
||||||
|
{ |
||||||
|
DefiniteAssignmentStatus status = data; |
||||||
|
foreach (var variable in fixedStatement.Variables) |
||||||
|
status = variable.AcceptVisitor(this, status); |
||||||
|
return status; |
||||||
|
} |
||||||
|
#endregion
|
||||||
|
|
||||||
|
public override DefiniteAssignmentStatus VisitDirectionExpression(DirectionExpression directionExpression, DefiniteAssignmentStatus data) |
||||||
|
{ |
||||||
|
if (directionExpression.FieldDirection == FieldDirection.Out) { |
||||||
|
return HandleAssignment(directionExpression.Expression, null, data); |
||||||
|
} else { |
||||||
|
// use default logic for 'ref'
|
||||||
|
return VisitChildren(directionExpression, data); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public override DefiniteAssignmentStatus VisitAssignmentExpression(AssignmentExpression assignmentExpression, DefiniteAssignmentStatus data) |
||||||
|
{ |
||||||
|
if (assignmentExpression.Operator == AssignmentOperatorType.Assign) { |
||||||
|
return HandleAssignment(assignmentExpression.Left, assignmentExpression.Right, data); |
||||||
|
} else { |
||||||
|
// use default logic for compound assignment operators
|
||||||
|
return VisitChildren(assignmentExpression, data); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
DefiniteAssignmentStatus HandleAssignment(Expression left, Expression right, DefiniteAssignmentStatus initialStatus) |
||||||
|
{ |
||||||
|
IdentifierExpression ident = left as IdentifierExpression; |
||||||
|
if (ident != null && ident.Identifier == analysis.variableName) { |
||||||
|
right.AcceptVisitor(this, initialStatus); |
||||||
|
return DefiniteAssignmentStatus.DefinitelyAssigned; |
||||||
|
} else { |
||||||
|
DefiniteAssignmentStatus status = left.AcceptVisitor(this, initialStatus); |
||||||
|
status = right.AcceptVisitor(this, CleanSpecialValues(status)); |
||||||
|
return CleanSpecialValues(status); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public override DefiniteAssignmentStatus VisitParenthesizedExpression(ParenthesizedExpression parenthesizedExpression, DefiniteAssignmentStatus data) |
||||||
|
{ |
||||||
|
// Don't use the default logic here because we don't want to clean up the special values.
|
||||||
|
return parenthesizedExpression.Expression.AcceptVisitor(this, data); |
||||||
|
} |
||||||
|
|
||||||
|
public override DefiniteAssignmentStatus VisitCheckedExpression(CheckedExpression checkedExpression, DefiniteAssignmentStatus data) |
||||||
|
{ |
||||||
|
return checkedExpression.Expression.AcceptVisitor(this, data); |
||||||
|
} |
||||||
|
|
||||||
|
public override DefiniteAssignmentStatus VisitUncheckedExpression(UncheckedExpression uncheckedExpression, DefiniteAssignmentStatus data) |
||||||
|
{ |
||||||
|
return uncheckedExpression.Expression.AcceptVisitor(this, data); |
||||||
|
} |
||||||
|
|
||||||
|
public override DefiniteAssignmentStatus VisitBinaryOperatorExpression(BinaryOperatorExpression binaryOperatorExpression, DefiniteAssignmentStatus data) |
||||||
|
{ |
||||||
|
if (binaryOperatorExpression.Operator == BinaryOperatorType.ConditionalAnd) { |
||||||
|
// Handle constant left side of && expressions (not in the C# spec, but done by the MS compiler)
|
||||||
|
bool? cond = analysis.EvaluateCondition(binaryOperatorExpression.Left); |
||||||
|
if (cond == true) |
||||||
|
return binaryOperatorExpression.Right.AcceptVisitor(this, data); // right operand gets evaluated unconditionally
|
||||||
|
else if (cond == false) |
||||||
|
return data; // right operand never gets evaluated
|
||||||
|
// C# 4.0 spec: §5.3.3.24 Definite Assignment for && expressions
|
||||||
|
DefiniteAssignmentStatus afterLeft = binaryOperatorExpression.Left.AcceptVisitor(this, data); |
||||||
|
DefiniteAssignmentStatus beforeRight; |
||||||
|
if (afterLeft == DefiniteAssignmentStatus.AssignedAfterTrueExpression) |
||||||
|
beforeRight = DefiniteAssignmentStatus.DefinitelyAssigned; |
||||||
|
else if (afterLeft == DefiniteAssignmentStatus.AssignedAfterFalseExpression) |
||||||
|
beforeRight = DefiniteAssignmentStatus.PotentiallyAssigned; |
||||||
|
else |
||||||
|
beforeRight = afterLeft; |
||||||
|
DefiniteAssignmentStatus afterRight = binaryOperatorExpression.Right.AcceptVisitor(this, beforeRight); |
||||||
|
if (afterLeft == DefiniteAssignmentStatus.DefinitelyAssigned) |
||||||
|
return DefiniteAssignmentStatus.DefinitelyAssigned; |
||||||
|
else if (afterRight == DefiniteAssignmentStatus.DefinitelyAssigned && afterLeft == DefiniteAssignmentStatus.AssignedAfterFalseExpression) |
||||||
|
return DefiniteAssignmentStatus.DefinitelyAssigned; |
||||||
|
else if (afterRight == DefiniteAssignmentStatus.DefinitelyAssigned || afterRight == DefiniteAssignmentStatus.AssignedAfterTrueExpression) |
||||||
|
return DefiniteAssignmentStatus.AssignedAfterTrueExpression; |
||||||
|
else if (afterLeft == DefiniteAssignmentStatus.AssignedAfterFalseExpression && afterRight == DefiniteAssignmentStatus.AssignedAfterFalseExpression) |
||||||
|
return DefiniteAssignmentStatus.AssignedAfterFalseExpression; |
||||||
|
else |
||||||
|
return DefiniteAssignmentStatus.PotentiallyAssigned; |
||||||
|
} else if (binaryOperatorExpression.Operator == BinaryOperatorType.ConditionalOr) { |
||||||
|
// C# 4.0 spec: §5.3.3.25 Definite Assignment for || expressions
|
||||||
|
bool? cond = analysis.EvaluateCondition(binaryOperatorExpression.Left); |
||||||
|
if (cond == false) |
||||||
|
return binaryOperatorExpression.Right.AcceptVisitor(this, data); // right operand gets evaluated unconditionally
|
||||||
|
else if (cond == true) |
||||||
|
return data; // right operand never gets evaluated
|
||||||
|
DefiniteAssignmentStatus afterLeft = binaryOperatorExpression.Left.AcceptVisitor(this, data); |
||||||
|
DefiniteAssignmentStatus beforeRight; |
||||||
|
if (afterLeft == DefiniteAssignmentStatus.AssignedAfterTrueExpression) |
||||||
|
beforeRight = DefiniteAssignmentStatus.PotentiallyAssigned; |
||||||
|
else if (afterLeft == DefiniteAssignmentStatus.AssignedAfterFalseExpression) |
||||||
|
beforeRight = DefiniteAssignmentStatus.DefinitelyAssigned; |
||||||
|
else |
||||||
|
beforeRight = afterLeft; |
||||||
|
DefiniteAssignmentStatus afterRight = binaryOperatorExpression.Right.AcceptVisitor(this, beforeRight); |
||||||
|
if (afterLeft == DefiniteAssignmentStatus.DefinitelyAssigned) |
||||||
|
return DefiniteAssignmentStatus.DefinitelyAssigned; |
||||||
|
else if (afterRight == DefiniteAssignmentStatus.DefinitelyAssigned && afterLeft == DefiniteAssignmentStatus.AssignedAfterTrueExpression) |
||||||
|
return DefiniteAssignmentStatus.DefinitelyAssigned; |
||||||
|
else if (afterRight == DefiniteAssignmentStatus.DefinitelyAssigned || afterRight == DefiniteAssignmentStatus.AssignedAfterFalseExpression) |
||||||
|
return DefiniteAssignmentStatus.AssignedAfterFalseExpression; |
||||||
|
else if (afterLeft == DefiniteAssignmentStatus.AssignedAfterTrueExpression && afterRight == DefiniteAssignmentStatus.AssignedAfterTrueExpression) |
||||||
|
return DefiniteAssignmentStatus.AssignedAfterTrueExpression; |
||||||
|
else |
||||||
|
return DefiniteAssignmentStatus.PotentiallyAssigned; |
||||||
|
} else if (binaryOperatorExpression.Operator == BinaryOperatorType.NullCoalescing) { |
||||||
|
// C# 4.0 spec: §5.3.3.27 Definite assignment for ?? expressions
|
||||||
|
ConstantResolveResult crr = analysis.EvaluateConstant(binaryOperatorExpression.Left); |
||||||
|
if (crr != null && crr.ConstantValue == null) |
||||||
|
return binaryOperatorExpression.Right.AcceptVisitor(this, data); |
||||||
|
DefiniteAssignmentStatus status = CleanSpecialValues(binaryOperatorExpression.Left.AcceptVisitor(this, data)); |
||||||
|
binaryOperatorExpression.Right.AcceptVisitor(this, status); |
||||||
|
return status; |
||||||
|
} else { |
||||||
|
// use default logic for other operators
|
||||||
|
return VisitChildren(binaryOperatorExpression, data); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public override DefiniteAssignmentStatus VisitUnaryOperatorExpression(UnaryOperatorExpression unaryOperatorExpression, DefiniteAssignmentStatus data) |
||||||
|
{ |
||||||
|
if (unaryOperatorExpression.Operator == UnaryOperatorType.Not) { |
||||||
|
// C# 4.0 spec: §5.3.3.26 Definite assignment for ! expressions
|
||||||
|
DefiniteAssignmentStatus status = unaryOperatorExpression.Expression.AcceptVisitor(this, data); |
||||||
|
if (status == DefiniteAssignmentStatus.AssignedAfterFalseExpression) |
||||||
|
return DefiniteAssignmentStatus.AssignedAfterTrueExpression; |
||||||
|
else if (status == DefiniteAssignmentStatus.AssignedAfterTrueExpression) |
||||||
|
return DefiniteAssignmentStatus.AssignedAfterFalseExpression; |
||||||
|
else |
||||||
|
return status; |
||||||
|
} else { |
||||||
|
// use default logic for other operators
|
||||||
|
return VisitChildren(unaryOperatorExpression, data); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public override DefiniteAssignmentStatus VisitConditionalExpression(ConditionalExpression conditionalExpression, DefiniteAssignmentStatus data) |
||||||
|
{ |
||||||
|
// C# 4.0 spec: §5.3.3.28 Definite assignment for ?: expressions
|
||||||
|
bool? cond = analysis.EvaluateCondition(conditionalExpression.Condition); |
||||||
|
if (cond == true) { |
||||||
|
return conditionalExpression.TrueExpression.AcceptVisitor(this, data); |
||||||
|
} else if (cond == false) { |
||||||
|
return conditionalExpression.FalseExpression.AcceptVisitor(this, data); |
||||||
|
} else { |
||||||
|
DefiniteAssignmentStatus afterCondition = conditionalExpression.Condition.AcceptVisitor(this, data); |
||||||
|
|
||||||
|
DefiniteAssignmentStatus beforeTrue, beforeFalse; |
||||||
|
if (afterCondition == DefiniteAssignmentStatus.AssignedAfterTrueExpression) { |
||||||
|
beforeTrue = DefiniteAssignmentStatus.DefinitelyAssigned; |
||||||
|
beforeFalse = DefiniteAssignmentStatus.PotentiallyAssigned; |
||||||
|
} else if (afterCondition == DefiniteAssignmentStatus.AssignedAfterFalseExpression) { |
||||||
|
beforeTrue = DefiniteAssignmentStatus.PotentiallyAssigned; |
||||||
|
beforeFalse = DefiniteAssignmentStatus.DefinitelyAssigned; |
||||||
|
} else { |
||||||
|
beforeTrue = afterCondition; |
||||||
|
beforeFalse = afterCondition; |
||||||
|
} |
||||||
|
|
||||||
|
DefiniteAssignmentStatus afterTrue = conditionalExpression.TrueExpression.AcceptVisitor(this, beforeTrue); |
||||||
|
DefiniteAssignmentStatus afterFalse = conditionalExpression.TrueExpression.AcceptVisitor(this, beforeFalse); |
||||||
|
return MergeStatus(CleanSpecialValues(afterTrue), CleanSpecialValues(afterFalse)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public override DefiniteAssignmentStatus VisitAnonymousMethodExpression(AnonymousMethodExpression anonymousMethodExpression, DefiniteAssignmentStatus data) |
||||||
|
{ |
||||||
|
BlockStatement body = anonymousMethodExpression.Body; |
||||||
|
foreach (ControlFlowNode node in analysis.allNodes) { |
||||||
|
if (node.NextStatement == body) |
||||||
|
analysis.ChangeNodeStatus(node, data); |
||||||
|
} |
||||||
|
return data; |
||||||
|
} |
||||||
|
|
||||||
|
public override DefiniteAssignmentStatus VisitLambdaExpression(LambdaExpression lambdaExpression, DefiniteAssignmentStatus data) |
||||||
|
{ |
||||||
|
Statement body = lambdaExpression.Body as Statement; |
||||||
|
if (body != null) { |
||||||
|
foreach (ControlFlowNode node in analysis.allNodes) { |
||||||
|
if (node.NextStatement == body) |
||||||
|
analysis.ChangeNodeStatus(node, data); |
||||||
|
} |
||||||
|
} else { |
||||||
|
lambdaExpression.Body.AcceptVisitor(this, data); |
||||||
|
} |
||||||
|
return data; |
||||||
|
} |
||||||
|
|
||||||
|
public override DefiniteAssignmentStatus VisitIdentifierExpression(IdentifierExpression identifierExpression, DefiniteAssignmentStatus data) |
||||||
|
{ |
||||||
|
if (data != DefiniteAssignmentStatus.DefinitelyAssigned |
||||||
|
&& identifierExpression.Identifier == analysis.variableName && identifierExpression.TypeArguments.Count == 0) |
||||||
|
{ |
||||||
|
analysis.unassignedVariableUses.Add(identifierExpression); |
||||||
|
} |
||||||
|
return data; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue