diff --git a/ICSharpCode.Decompiler/FlowAnalysis/ControlFlowGraph.cs b/ICSharpCode.Decompiler/FlowAnalysis/ControlFlowGraph.cs index 89cd6fcf2..64b866c54 100644 --- a/ICSharpCode.Decompiler/FlowAnalysis/ControlFlowGraph.cs +++ b/ICSharpCode.Decompiler/FlowAnalysis/ControlFlowGraph.cs @@ -23,6 +23,8 @@ using System.Diagnostics; using System.Linq; using System.Threading; +using ICSharpCode.NRefactory.Utils; + namespace ICSharpCode.Decompiler.FlowAnalysis { /// @@ -57,7 +59,6 @@ namespace ICSharpCode.Decompiler.FlowAnalysis Debug.Assert(ExceptionalExit.NodeType == ControlFlowNodeType.ExceptionalExit); } - #if DEBUG public GraphVizGraph ExportGraph() { GraphVizGraph graph = new GraphVizGraph(); @@ -87,7 +88,6 @@ namespace ICSharpCode.Decompiler.FlowAnalysis } return graph; } - #endif /// /// Resets "Visited" to false for all nodes in this graph. diff --git a/ICSharpCode.Decompiler/FlowAnalysis/SsaForm.cs b/ICSharpCode.Decompiler/FlowAnalysis/SsaForm.cs index ed4e2892c..1a85e76a7 100644 --- a/ICSharpCode.Decompiler/FlowAnalysis/SsaForm.cs +++ b/ICSharpCode.Decompiler/FlowAnalysis/SsaForm.cs @@ -22,6 +22,7 @@ using System.Collections.ObjectModel; using System.Diagnostics; using System.Linq; +using ICSharpCode.NRefactory.Utils; using Mono.Cecil; using Mono.Cecil.Cil; diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index 28282e6eb..8484e0c49 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -88,7 +88,6 @@ - diff --git a/ILSpy.sln b/ILSpy.sln index 74641ca8e..3ab32061b 100644 --- a/ILSpy.sln +++ b/ILSpy.sln @@ -48,13 +48,13 @@ Global {DDE2A481-8271-4EAC-A330-8FA6A38D13D1}.Release|Any CPU.Build.0 = Release|Any CPU {DDE2A481-8271-4EAC-A330-8FA6A38D13D1}.Release|x86.ActiveCfg = Release|Any CPU {DDE2A481-8271-4EAC-A330-8FA6A38D13D1}.Release|x86.Build.0 = Release|Any CPU - {D68133BD-1E63-496E-9EDE-4FBDBF77B486}.Debug|Any CPU.ActiveCfg = net_3_5_Debug|Any CPU + {D68133BD-1E63-496E-9EDE-4FBDBF77B486}.Debug|Any CPU.ActiveCfg = net_4_0_Debug|Any CPU {D68133BD-1E63-496E-9EDE-4FBDBF77B486}.Debug|Any CPU.Build.0 = net_4_0_Debug|Any CPU - {D68133BD-1E63-496E-9EDE-4FBDBF77B486}.Debug|x86.ActiveCfg = net_3_5_Debug|Any CPU + {D68133BD-1E63-496E-9EDE-4FBDBF77B486}.Debug|x86.ActiveCfg = net_4_0_Debug|Any CPU {D68133BD-1E63-496E-9EDE-4FBDBF77B486}.Debug|x86.Build.0 = net_2_0_Debug|Any CPU - {D68133BD-1E63-496E-9EDE-4FBDBF77B486}.Release|Any CPU.ActiveCfg = net_3_5_Release|Any CPU + {D68133BD-1E63-496E-9EDE-4FBDBF77B486}.Release|Any CPU.ActiveCfg = net_4_0_Release|Any CPU {D68133BD-1E63-496E-9EDE-4FBDBF77B486}.Release|Any CPU.Build.0 = net_4_0_Release|Any CPU - {D68133BD-1E63-496E-9EDE-4FBDBF77B486}.Release|x86.ActiveCfg = net_3_5_Release|Any CPU + {D68133BD-1E63-496E-9EDE-4FBDBF77B486}.Release|x86.ActiveCfg = net_4_0_Release|Any CPU {D68133BD-1E63-496E-9EDE-4FBDBF77B486}.Release|x86.Build.0 = net_2_0_Debug|Any CPU {6C55B776-26D4-4DB3-A6AB-87E783B2F3D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6C55B776-26D4-4DB3-A6AB-87E783B2F3D1}.Debug|Any CPU.Build.0 = Debug|Any CPU diff --git a/ILSpy/MainWindow.xaml.cs b/ILSpy/MainWindow.xaml.cs index 74c174ef1..8a659fe44 100644 --- a/ILSpy/MainWindow.xaml.cs +++ b/ILSpy/MainWindow.xaml.cs @@ -278,7 +278,7 @@ namespace ICSharpCode.ILSpy typeof(ICSharpCode.TreeView.SharpTreeView).Assembly, typeof(Mono.Cecil.AssemblyDefinition).Assembly, typeof(ICSharpCode.AvalonEdit.TextEditor).Assembly, - typeof(ICSharpCode.Decompiler.GraphVizGraph).Assembly, + typeof(ICSharpCode.Decompiler.Ast.AstBuilder).Assembly, typeof(MainWindow).Assembly }; foreach (System.Reflection.Assembly asm in initialAssemblies) diff --git a/NRefactory/ICSharpCode.NRefactory.Tests/CSharp/Analysis/DefiniteAssignmentTests.cs b/NRefactory/ICSharpCode.NRefactory.Tests/CSharp/Analysis/DefiniteAssignmentTests.cs new file mode 100644 index 000000000..247c1fb39 --- /dev/null +++ b/NRefactory/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/NRefactory/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj b/NRefactory/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj index 7bc0981b5..6d351a08a 100644 --- a/NRefactory/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj +++ b/NRefactory/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj @@ -59,6 +59,7 @@ + @@ -162,6 +163,7 @@ + diff --git a/NRefactory/ICSharpCode.NRefactory/CSharp/Analysis/ControlFlow.cs b/NRefactory/ICSharpCode.NRefactory/CSharp/Analysis/ControlFlow.cs new file mode 100644 index 000000000..2f1e2b47e --- /dev/null +++ b/NRefactory/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/NRefactory/ICSharpCode.NRefactory/CSharp/Analysis/DefiniteAssignmentAnalysis.cs b/NRefactory/ICSharpCode.NRefactory/CSharp/Analysis/DefiniteAssignmentAnalysis.cs new file mode 100644 index 000000000..534e42325 --- /dev/null +++ b/NRefactory/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/NRefactory/ICSharpCode.NRefactory/CSharp/Ast/AstNodeCollection.cs b/NRefactory/ICSharpCode.NRefactory/CSharp/Ast/AstNodeCollection.cs index c7f266d97..07e062672 100644 --- a/NRefactory/ICSharpCode.NRefactory/CSharp/Ast/AstNodeCollection.cs +++ b/NRefactory/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/NRefactory/ICSharpCode.NRefactory/CSharp/Ast/DepthFirstAstVisitor.cs b/NRefactory/ICSharpCode.NRefactory/CSharp/Ast/DepthFirstAstVisitor.cs index e0e9615f3..dff52990d 100644 --- a/NRefactory/ICSharpCode.NRefactory/CSharp/Ast/DepthFirstAstVisitor.cs +++ b/NRefactory/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/NRefactory/ICSharpCode.NRefactory/CSharp/Ast/Expressions/Expression.cs b/NRefactory/ICSharpCode.NRefactory/CSharp/Ast/Expressions/Expression.cs index 3055ae7fb..ea5e22f64 100644 --- a/NRefactory/ICSharpCode.NRefactory/CSharp/Ast/Expressions/Expression.cs +++ b/NRefactory/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/NRefactory/ICSharpCode.NRefactory/CSharp/Ast/Statements/Statement.cs b/NRefactory/ICSharpCode.NRefactory/CSharp/Ast/Statements/Statement.cs index 0d678ff59..ef01d54b3 100644 --- a/NRefactory/ICSharpCode.NRefactory/CSharp/Ast/Statements/Statement.cs +++ b/NRefactory/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/NRefactory/ICSharpCode.NRefactory/CSharp/Ast/TypeMembers/Accessor.cs b/NRefactory/ICSharpCode.NRefactory/CSharp/Ast/TypeMembers/Accessor.cs index cc5651d87..748948f6a 100644 --- a/NRefactory/ICSharpCode.NRefactory/CSharp/Ast/TypeMembers/Accessor.cs +++ b/NRefactory/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/NRefactory/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj b/NRefactory/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj index c317932d9..89db6429e 100644 --- a/NRefactory/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj +++ b/NRefactory/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/NRefactory/ICSharpCode.NRefactory/Utils/DotNet35Compat.cs b/NRefactory/ICSharpCode.NRefactory/Utils/DotNet35Compat.cs index 85c599947..df2a58dea 100644 --- a/NRefactory/ICSharpCode.NRefactory/Utils/DotNet35Compat.cs +++ b/NRefactory/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.Decompiler/GraphVizGraph.cs b/NRefactory/ICSharpCode.NRefactory/Utils/GraphVizGraph.cs similarity index 79% rename from ICSharpCode.Decompiler/GraphVizGraph.cs rename to NRefactory/ICSharpCode.NRefactory/Utils/GraphVizGraph.cs index 62b957f25..2bf93c353 100644 --- a/ICSharpCode.Decompiler/GraphVizGraph.cs +++ b/NRefactory/ICSharpCode.NRefactory/Utils/GraphVizGraph.cs @@ -1,28 +1,14 @@ -// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of this -// software and associated documentation files (the "Software"), to deal in the Software -// without restriction, including without limitation the rights to use, copy, modify, merge, -// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons -// to whom the Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all copies or -// substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR -// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE -// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. +// 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.Decompiler +namespace ICSharpCode.NRefactory.Utils { /// /// GraphViz graph. @@ -33,6 +19,7 @@ namespace ICSharpCode.Decompiler List edges = new List(); public string rankdir; + public string Title; public void AddEdge(GraphVizEdge edge) { @@ -50,6 +37,24 @@ namespace ICSharpCode.Decompiler 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]+$")) { @@ -92,6 +97,8 @@ namespace ICSharpCode.Decompiler 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);