mirror of https://github.com/icsharpcode/ILSpy.git
17 changed files with 1483 additions and 44 deletions
@ -0,0 +1,132 @@
@@ -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 @@
@@ -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 @@
@@ -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