diff --git a/ICSharpCode.NRefactory.Tests/CSharp/Analysis/DefiniteAssignmentTests.cs b/ICSharpCode.NRefactory.Tests/CSharp/Analysis/DefiniteAssignmentTests.cs
new file mode 100644
index 0000000000..247c1fb394
--- /dev/null
+++ b/ICSharpCode.NRefactory.Tests/CSharp/Analysis/DefiniteAssignmentTests.cs
@@ -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));
+ }
+ }
+}
diff --git a/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj b/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj
index 7bc0981b52..6d351a08af 100644
--- a/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj
+++ b/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj
@@ -59,6 +59,7 @@
+
@@ -162,6 +163,7 @@
+
diff --git a/ICSharpCode.NRefactory/CSharp/Analysis/ControlFlow.cs b/ICSharpCode.NRefactory/CSharp/Analysis/ControlFlow.cs
new file mode 100644
index 0000000000..2f1e2b47e3
--- /dev/null
+++ b/ICSharpCode.NRefactory/CSharp/Analysis/ControlFlow.cs
@@ -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
+{
+ ///
+ /// Represents a node in the control flow graph of a C# method.
+ ///
+ public class ControlFlowNode
+ {
+ public readonly Statement PreviousStatement;
+ public readonly Statement NextStatement;
+
+ public readonly ControlFlowNodeType Type;
+
+ public readonly List Outgoing = new List();
+ public readonly List Incoming = new List();
+
+ 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
+ {
+ ///
+ /// Unknown node type
+ ///
+ None,
+ ///
+ /// Node in front of a statement
+ ///
+ StartNode,
+ ///
+ /// Node between two statements
+ ///
+ BetweenStatements,
+ ///
+ /// Node at the end of a statement list
+ ///
+ EndNode,
+ ///
+ /// Node representing the position before evaluating the condition of a loop.
+ ///
+ LoopCondition
+ }
+
+ public class ControlFlowEdge
+ {
+ public readonly ControlFlowNode From;
+ public readonly ControlFlowNode To;
+ public readonly ControlFlowEdgeType Type;
+
+ List 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();
+ jumpOutOfTryFinally.Add(tryFinally);
+ }
+
+ ///
+ /// Gets whether this control flow edge is leaving any try-finally statements.
+ ///
+ public bool IsLeavingTryFinally {
+ get { return jumpOutOfTryFinally != null; }
+ }
+
+ ///
+ /// Gets the try-finally statements that this control flow edge is leaving.
+ ///
+ public IEnumerable TryFinallyStatements {
+ get { return jumpOutOfTryFinally ?? Enumerable.Empty(); }
+ }
+ }
+
+ public enum ControlFlowEdgeType
+ {
+ ///
+ /// Regular control flow.
+ ///
+ Normal,
+ ///
+ /// Conditional control flow (edge taken if condition is true)
+ ///
+ ConditionTrue,
+ ///
+ /// Conditional control flow (edge taken if condition is false)
+ ///
+ ConditionFalse,
+ ///
+ /// A jump statement (goto, goto case, break or continue)
+ ///
+ Jump
+ }
+
+ ///
+ /// Constructs the control flow graph for C# statements.
+ ///
+ 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 nodes;
+ Dictionary labels;
+ List gotoStatements;
+
+ public IList BuildControlFlowGraph(Statement statement)
+ {
+ NodeCreationVisitor nodeCreationVisitor = new NodeCreationVisitor();
+ nodeCreationVisitor.builder = this;
+ try {
+ this.nodes = new List();
+ this.labels = new Dictionary();
+ this.gotoStatements = new List();
+ 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 targetParentTryCatch = new HashSet(targetStatement.Ancestors.OfType());
+ 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
+ ///
+ /// Evaluates an expression.
+ ///
+ /// The constant value of the expression; or null if the expression is not a constant.
+ ConstantResolveResult EvaluateConstant(Expression expr)
+ {
+ return null; // TODO: implement this using the C# resolver
+ }
+
+ ///
+ /// Evaluates an expression.
+ ///
+ /// The value of the constant boolean expression; or null if the value is not a constant boolean expression.
+ 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
+ {
+ // '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 breakTargets = new Stack();
+ Stack continueTargets = new Stack();
+ List gotoCaseOrDefault = new List();
+
+ 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;
+ }
+
+ ///
+ /// Creates an end node for stmt and connects from with the new node.
+ ///
+ 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 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)
+ {
+ // while (cond) { embeddedStmt; }
+ 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)
+ {
+ // do { embeddedStmt; } while(cond);
+ 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 ; cond; iterators) { embeddedStmt; }
+ 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)
+ {
+ // foreach (...) { embeddedStmt }
+ 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);
+ }
+ }
+ }
+}
diff --git a/ICSharpCode.NRefactory/CSharp/Analysis/DefiniteAssignmentAnalysis.cs b/ICSharpCode.NRefactory/CSharp/Analysis/DefiniteAssignmentAnalysis.cs
new file mode 100644
index 0000000000..534e423258
--- /dev/null
+++ b/ICSharpCode.NRefactory/CSharp/Analysis/DefiniteAssignmentAnalysis.cs
@@ -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
+{
+ ///
+ /// Represents the definite assignment status of a variable at a specific location.
+ ///
+ public enum DefiniteAssignmentStatus
+ {
+ ///
+ /// The variable might be assigned or unassigned.
+ ///
+ PotentiallyAssigned,
+ ///
+ /// The variable is definitely assigned.
+ ///
+ DefinitelyAssigned,
+ ///
+ /// The variable is definitely assigned iff the expression results in the value 'true'.
+ ///
+ AssignedAfterTrueExpression,
+ ///
+ /// The variable is definitely assigned iff the expression results in the value 'false'.
+ ///
+ AssignedAfterFalseExpression,
+ ///
+ /// The code is unreachable.
+ ///
+ CodeUnreachable
+ }
+
+ ///
+ /// Implements the C# definite assignment analysis (C# 4.0 Spec: §5.3 Definite assignment)
+ ///
+ public class DefiniteAssignmentAnalysis
+ {
+ readonly DefiniteAssignmentVisitor visitor = new DefiniteAssignmentVisitor();
+ readonly List allNodes = new List();
+ readonly Dictionary beginNodeDict = new Dictionary();
+ readonly Dictionary endNodeDict = new Dictionary();
+ Dictionary nodeStatus = new Dictionary();
+ Dictionary edgeStatus = new Dictionary();
+
+ string variableName;
+ List unassignedVariableUses = new List();
+
+ Queue nodesWithModifiedInput = new Queue();
+
+ 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().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);
+ }
+ }
+
+ ///
+ /// Gets the unassigned usages of the previously analyzed variable.
+ ///
+ public IList 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]];
+ }
+
+ ///
+ /// Exports the CFG. This method is intended to help debugging issues related to definite assignment.
+ ///
+ 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);
+ }
+
+ ///
+ /// Evaluates an expression.
+ ///
+ /// The constant value of the expression; or null if the expression is not a constant.
+ ConstantResolveResult EvaluateConstant(Expression expr)
+ {
+ return null; // TODO: implement this using the C# resolver
+ }
+
+ ///
+ /// Evaluates an expression.
+ ///
+ /// The value of the constant boolean expression; or null if the value is not a constant boolean expression.
+ 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
+ {
+ 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;
+ }
+ }
+ }
+}
diff --git a/ICSharpCode.NRefactory/CSharp/Ast/AstNodeCollection.cs b/ICSharpCode.NRefactory/CSharp/Ast/AstNodeCollection.cs
index c7f266d97b..07e0626721 100644
--- a/ICSharpCode.NRefactory/CSharp/Ast/AstNodeCollection.cs
+++ b/ICSharpCode.NRefactory/CSharp/Ast/AstNodeCollection.cs
@@ -30,10 +30,11 @@ namespace ICSharpCode.NRefactory.CSharp
public int Count {
get {
- var e = GetEnumerator();
int count = 0;
- while (e.MoveNext())
- count++;
+ for (AstNode cur = node.FirstChild; cur != null; cur = cur.NextSibling) {
+ if (cur.Role == role)
+ count++;
+ }
return count;
}
}
diff --git a/ICSharpCode.NRefactory/CSharp/Ast/DepthFirstAstVisitor.cs b/ICSharpCode.NRefactory/CSharp/Ast/DepthFirstAstVisitor.cs
index e0e9615f32..dff52990de 100644
--- a/ICSharpCode.NRefactory/CSharp/Ast/DepthFirstAstVisitor.cs
+++ b/ICSharpCode.NRefactory/CSharp/Ast/DepthFirstAstVisitor.cs
@@ -1,4 +1,4 @@
-//
+//
// IAstVisitor.cs
//
// Author:
@@ -52,17 +52,17 @@ namespace ICSharpCode.NRefactory.CSharp
public virtual S VisitComment (Comment comment, T data)
{
- return default (S);
+ return VisitChildren (comment, data);
}
public virtual S VisitIdentifier (Identifier identifier, T data)
{
- return default (S);
+ return VisitChildren (identifier, data);
}
public virtual S VisitCSharpTokenNode (CSharpTokenNode token, T data)
{
- return default (S);
+ return VisitChildren (token, data);
}
public virtual S VisitPrimitiveType (PrimitiveType primitiveType, T data)
diff --git a/ICSharpCode.NRefactory/CSharp/Ast/Expressions/Expression.cs b/ICSharpCode.NRefactory/CSharp/Ast/Expressions/Expression.cs
index 3055ae7fbe..ea5e22f64c 100644
--- a/ICSharpCode.NRefactory/CSharp/Ast/Expressions/Expression.cs
+++ b/ICSharpCode.NRefactory/CSharp/Ast/Expressions/Expression.cs
@@ -54,6 +54,8 @@ namespace ICSharpCode.NRefactory.CSharp
// Make debugging easier by giving Expressions a ToString() implementation
public override string ToString()
{
+ if (IsNull)
+ return "Null";
StringWriter w = new StringWriter();
AcceptVisitor(new OutputVisitor(w, new CSharpFormattingPolicy()), null);
return w.ToString();
diff --git a/ICSharpCode.NRefactory/CSharp/Ast/Statements/Statement.cs b/ICSharpCode.NRefactory/CSharp/Ast/Statements/Statement.cs
index 0d678ff59f..ef01d54b3a 100644
--- a/ICSharpCode.NRefactory/CSharp/Ast/Statements/Statement.cs
+++ b/ICSharpCode.NRefactory/CSharp/Ast/Statements/Statement.cs
@@ -2,6 +2,7 @@
// This code is distributed under MIT X11 license (for details please see \doc\license.txt)
using System;
+using System.IO;
namespace ICSharpCode.NRefactory.CSharp
{
@@ -52,5 +53,19 @@ namespace ICSharpCode.NRefactory.CSharp
public override NodeType NodeType {
get { return NodeType.Statement; }
}
+
+ // Make debugging easier by giving Statements a ToString() implementation
+ public override string ToString()
+ {
+ if (IsNull)
+ return "Null";
+ StringWriter w = new StringWriter();
+ AcceptVisitor(new OutputVisitor(w, new CSharpFormattingPolicy()), null);
+ string text = w.ToString().TrimEnd().Replace("\t", "").Replace(w.NewLine, " ");
+ if (text.Length > 100)
+ return text.Substring(0, 97) + "...";
+ else
+ return text;
+ }
}
}
diff --git a/ICSharpCode.NRefactory/CSharp/Ast/TypeMembers/Accessor.cs b/ICSharpCode.NRefactory/CSharp/Ast/TypeMembers/Accessor.cs
index cc5651d871..748948f6a7 100644
--- a/ICSharpCode.NRefactory/CSharp/Ast/TypeMembers/Accessor.cs
+++ b/ICSharpCode.NRefactory/CSharp/Ast/TypeMembers/Accessor.cs
@@ -56,11 +56,7 @@ namespace ICSharpCode.NRefactory.CSharp
get { return GetChildByRole (Roles.Body); }
set { SetChildByRole (Roles.Body, value); }
}
-
- public AstNodeCollection Parameters {
- get { return GetChildrenByRole(Roles.Parameter); }
- }
-
+
public override S AcceptVisitor(IAstVisitor visitor, T data)
{
return visitor.VisitAccessor (this, data);
diff --git a/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj b/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj
index c317932d90..89db6429e5 100644
--- a/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj
+++ b/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj
@@ -7,7 +7,7 @@
Library
ICSharpCode.NRefactory
ICSharpCode.NRefactory
- v3.5
+ v4.0
Properties
10.0.0
2.0
@@ -15,7 +15,7 @@
False
false
1591,0618
-
+ Client
False
-Microsoft.Design#CA1026;-Microsoft.Security#CA2104
@@ -30,14 +30,14 @@
bin\Debug\
Full
False
- DEBUG;TRACE;FULL_AST;DOTNET35
+ DEBUG;TRACE;FULL_AST
False
bin\Release\
None
True
- TRACE;FULL_AST;DOTNET35
+ TRACE;FULL_AST
False
@@ -61,6 +61,8 @@
+
+
@@ -334,6 +336,7 @@
+
@@ -356,5 +359,8 @@
Mono.Cecil
+
+
+
\ No newline at end of file
diff --git a/ICSharpCode.NRefactory/Utils/DotNet35Compat.cs b/ICSharpCode.NRefactory/Utils/DotNet35Compat.cs
index 85c599947e..df2a58dea0 100644
--- a/ICSharpCode.NRefactory/Utils/DotNet35Compat.cs
+++ b/ICSharpCode.NRefactory/Utils/DotNet35Compat.cs
@@ -16,7 +16,7 @@ internal static class DotNet35Compat
#endif
}
- public static IEnumerable SafeCast(this IEnumerable elements) where T : U
+ public static IEnumerable SafeCast(this IEnumerable elements) where T : class, U where U : class
{
#if DOTNET35
foreach (T item in elements)
@@ -26,7 +26,7 @@ internal static class DotNet35Compat
#endif
}
- public static Predicate SafeCast(this Predicate predicate) where U : T
+ public static Predicate SafeCast(this Predicate predicate) where U : class, T where T : class
{
#if DOTNET35
return e => predicate(e);
diff --git a/ICSharpCode.NRefactory/Utils/GraphVizGraph.cs b/ICSharpCode.NRefactory/Utils/GraphVizGraph.cs
new file mode 100644
index 0000000000..2bf93c353f
--- /dev/null
+++ b/ICSharpCode.NRefactory/Utils/GraphVizGraph.cs
@@ -0,0 +1,204 @@
+// Copyright (c) 2010 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.Globalization;
+using System.IO;
+using System.Text.RegularExpressions;
+
+namespace ICSharpCode.NRefactory.Utils
+{
+ ///
+ /// GraphViz graph.
+ ///
+ public sealed class GraphVizGraph
+ {
+ List nodes = new List();
+ List edges = new List();
+
+ public string rankdir;
+ public string Title;
+
+ public void AddEdge(GraphVizEdge edge)
+ {
+ edges.Add(edge);
+ }
+
+ public void AddNode(GraphVizNode node)
+ {
+ nodes.Add(node);
+ }
+
+ public void Save(string fileName)
+ {
+ using (StreamWriter writer = new StreamWriter(fileName))
+ Save(writer);
+ }
+
+ public void Show()
+ {
+ Show(null);
+ }
+
+ public void Show(string name)
+ {
+ if (name == null)
+ name = Title;
+ if (name != null)
+ foreach (char c in Path.GetInvalidFileNameChars())
+ name = name.Replace(c, '-');
+ string fileName = name != null ? Path.Combine(Path.GetTempPath(), name) : Path.GetTempFileName();
+ Save(fileName + ".gv");
+ Process.Start("dot", "\"" + fileName + ".gv\" -Tpng -o \"" + fileName + ".png\"").WaitForExit();
+ Process.Start(fileName + ".png");
+ }
+
+ static string Escape(string text)
+ {
+ if (Regex.IsMatch(text, @"^[\w\d]+$")) {
+ return text;
+ } else {
+ return "\"" + text.Replace("\\", "\\\\").Replace("\r", "").Replace("\n", "\\n").Replace("\"", "\\\"") + "\"";
+ }
+ }
+
+ static void WriteGraphAttribute(TextWriter writer, string name, string value)
+ {
+ if (value != null)
+ writer.WriteLine("{0}={1};", name, Escape(value));
+ }
+
+ internal static void WriteAttribute(TextWriter writer, string name, double? value, ref bool isFirst)
+ {
+ if (value != null) {
+ WriteAttribute(writer, name, value.Value.ToString(CultureInfo.InvariantCulture), ref isFirst);
+ }
+ }
+
+ internal static void WriteAttribute(TextWriter writer, string name, bool? value, ref bool isFirst)
+ {
+ if (value != null) {
+ WriteAttribute(writer, name, value.Value ? "true" : "false", ref isFirst);
+ }
+ }
+
+ internal static void WriteAttribute(TextWriter writer, string name, string value, ref bool isFirst)
+ {
+ if (value != null) {
+ if (isFirst)
+ isFirst = false;
+ else
+ writer.Write(',');
+ writer.Write("{0}={1}", name, Escape(value));
+ }
+ }
+
+ public void Save(TextWriter writer)
+ {
+ if (writer == null)
+ throw new ArgumentNullException("writer");
+ writer.WriteLine("digraph G {");
+ writer.WriteLine("node [fontsize = 16];");
+ WriteGraphAttribute(writer, "rankdir", rankdir);
+ foreach (GraphVizNode node in nodes) {
+ node.Save(writer);
+ }
+ foreach (GraphVizEdge edge in edges) {
+ edge.Save(writer);
+ }
+ writer.WriteLine("}");
+ }
+ }
+
+ public sealed class GraphVizEdge
+ {
+ public readonly string Source, Target;
+
+ /// edge stroke color
+ public string color;
+ /// use edge to affect node ranking
+ public bool? constraint;
+
+ public string label;
+
+ public string style;
+
+ /// point size of label
+ public int? fontsize;
+
+ public GraphVizEdge(string source, string target)
+ {
+ if (source == null)
+ throw new ArgumentNullException("source");
+ if (target == null)
+ throw new ArgumentNullException("target");
+ this.Source = source;
+ this.Target = target;
+ }
+
+ public GraphVizEdge(int source, int target)
+ {
+ this.Source = source.ToString(CultureInfo.InvariantCulture);
+ this.Target = target.ToString(CultureInfo.InvariantCulture);
+ }
+
+ public void Save(TextWriter writer)
+ {
+ writer.Write("{0} -> {1} [", Source, Target);
+ bool isFirst = true;
+ GraphVizGraph.WriteAttribute(writer, "label", label, ref isFirst);
+ GraphVizGraph.WriteAttribute(writer, "style", style, ref isFirst);
+ GraphVizGraph.WriteAttribute(writer, "fontsize", fontsize, ref isFirst);
+ GraphVizGraph.WriteAttribute(writer, "color", color, ref isFirst);
+ GraphVizGraph.WriteAttribute(writer, "constraint", constraint, ref isFirst);
+ writer.WriteLine("];");
+ }
+ }
+
+ public sealed class GraphVizNode
+ {
+ public readonly string ID;
+ public string label;
+
+ public string labelloc;
+
+ /// point size of label
+ public int? fontsize;
+
+ /// minimum height in inches
+ public double? height;
+
+ /// space around label
+ public string margin;
+
+ /// node shape
+ public string shape;
+
+ public GraphVizNode(string id)
+ {
+ if (id == null)
+ throw new ArgumentNullException("id");
+ this.ID = id;
+ }
+
+ public GraphVizNode(int id)
+ {
+ this.ID = id.ToString(CultureInfo.InvariantCulture);
+ }
+
+ public void Save(TextWriter writer)
+ {
+ writer.Write(ID);
+ writer.Write(" [");
+ bool isFirst = true;
+ GraphVizGraph.WriteAttribute(writer, "label", label, ref isFirst);
+ GraphVizGraph.WriteAttribute(writer, "labelloc", labelloc, ref isFirst);
+ GraphVizGraph.WriteAttribute(writer, "fontsize", fontsize, ref isFirst);
+ GraphVizGraph.WriteAttribute(writer, "margin", margin, ref isFirst);
+ GraphVizGraph.WriteAttribute(writer, "shape", shape, ref isFirst);
+ writer.WriteLine("];");
+ }
+ }
+}