From 0aae6ca92209d6e231bf45a04574f846f27b1f7e Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 13 Mar 2011 03:35:40 +0100 Subject: [PATCH 1/5] Add control flow analysis for the NRefactory C# AST. --- .../CSharp/Analysis/ControlFlow.cs | 516 ++++++++++++++++++ .../ICSharpCode.NRefactory.csproj | 4 + 2 files changed, 520 insertions(+) create mode 100644 NRefactory/ICSharpCode.NRefactory/CSharp/Analysis/ControlFlow.cs diff --git a/NRefactory/ICSharpCode.NRefactory/CSharp/Analysis/ControlFlow.cs b/NRefactory/ICSharpCode.NRefactory/CSharp/Analysis/ControlFlow.cs new file mode 100644 index 000000000..785ff4bf8 --- /dev/null +++ b/NRefactory/ICSharpCode.NRefactory/CSharp/Analysis/ControlFlow.cs @@ -0,0 +1,516 @@ +// 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 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 ControlFlowNodeType Type; + + public readonly List Predecessors = new List(); + public readonly List Successors = new List(); + + public ControlFlowNode(Statement previousStatement, Statement nextStatement) + { + if (previousStatement == null && nextStatement == null) + throw new ArgumentException("previousStatement and nextStatement must not be both null"); + this.PreviousStatement = previousStatement; + this.NextStatement = nextStatement; + } + + /// + /// Creates a control flow edge from source to this. + /// + internal ControlFlowNode ConnectWith(ControlFlowNode source) + { + source.Successors.Add(this); + this.Predecessors.Add(source); + return this; + } + } + + 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 ControlFlowGraphBuilder + { + protected virtual ControlFlowNode CreateNode(Statement previousStatement, Statement nextStatement) + { + return new ControlFlowNode(previousStatement, nextStatement); + } + + List nodes; + Dictionary labels; + List gotoStatements; + + public ControlFlowNode[] BuildControlFlowGraph(BlockStatement block) + { + NodeCreationVisitor nodeCreationVisitor = new NodeCreationVisitor(); + nodeCreationVisitor.builder = this; + try { + nodes = new List(); + labels = new Dictionary(); + gotoStatements = new List(); + ControlFlowNode entryPoint = CreateStartNode(block); + nodeCreationVisitor.VisitBlockStatement(block, entryPoint); + + // Resolve goto statements: + foreach (ControlFlowNode gotoStmt in gotoStatements) { + string label = ((GotoStatement)gotoStmt.NextStatement).Label; + ControlFlowNode labelNode; + if (labels.TryGetValue(label, out labelNode)) + labelNode.ConnectWith(gotoStmt); + } + + return nodes.ToArray(); + } finally { + nodes = null; + labels = null; + gotoStatements = null; + } + } + + ControlFlowNode CreateStartNode(Statement statement) + { + ControlFlowNode node = CreateNode(null, statement); + node.Type = ControlFlowNodeType.StartNode; + nodes.Add(node); + return node; + } + + ControlFlowNode CreateSpecialNode(Statement statement, ControlFlowNodeType type) + { + ControlFlowNode node = CreateNode(statement, null); + node.Type = type; + nodes.Add(node); + return node; + } + + ControlFlowNode CreateEndNode(Statement statement) + { + // Find the next statement in the same role: + AstNode next = statement; + do { + next = next.NextSibling; + } while (next != null && next.Role != statement.Role); + + Statement nextStatement = next as Statement; + ControlFlowNode node = CreateNode(statement, nextStatement); + node.Type = nextStatement != null ? ControlFlowNodeType.BetweenStatements : ControlFlowNodeType.EndNode; + nodes.Add(node); + return node; + } + + #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(); + + 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 builder.CreateEndNode(blockStatement).ConnectWith(childNode); + } + + ControlFlowNode HandleStatementList(AstNodeCollection statements, ControlFlowNode source) + { + ControlFlowNode childNode = null; + foreach (Statement stmt in statements) { + if (childNode == null) { + childNode = builder.CreateStartNode(stmt); + childNode.ConnectWith(source); + } + 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 builder.CreateEndNode(emptyStatement).ConnectWith(data); + } + + public override ControlFlowNode VisitLabelStatement(LabelStatement labelStatement, ControlFlowNode data) + { + ControlFlowNode end = builder.CreateEndNode(labelStatement); + builder.labels[labelStatement.Label] = end; + return end.ConnectWith(data); + } + + public override ControlFlowNode VisitVariableDeclarationStatement(VariableDeclarationStatement variableDeclarationStatement, ControlFlowNode data) + { + return builder.CreateEndNode(variableDeclarationStatement).ConnectWith(data); + } + + public override ControlFlowNode VisitExpressionStatement(ExpressionStatement expressionStatement, ControlFlowNode data) + { + return builder.CreateEndNode(expressionStatement).ConnectWith(data); + } + + public override ControlFlowNode VisitIfElseStatement(IfElseStatement ifElseStatement, ControlFlowNode data) + { + bool? cond = builder.EvaluateCondition(ifElseStatement.Condition); + ControlFlowNode trueBegin = builder.CreateStartNode(ifElseStatement.TrueStatement); + if (cond != false) + trueBegin.ConnectWith(data); + 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) + falseBegin.ConnectWith(data); + falseEnd = ifElseStatement.FalseStatement.AcceptVisitor(this, falseBegin); + } + ControlFlowNode end = builder.CreateEndNode(ifElseStatement); + end.ConnectWith(trueEnd); + if (falseEnd != null) { + end.ConnectWith(falseEnd); + } else if (cond != true) { + end.ConnectWith(data); + } + 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) { + end.ConnectWith(data); + } + + 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); + + conditionNode.ConnectWith(data); + + bool? cond = builder.EvaluateCondition(whileStatement.Condition); + ControlFlowNode bodyStart = builder.CreateStartNode(whileStatement.EmbeddedStatement); + if (cond != false) + bodyStart.ConnectWith(conditionNode); + ControlFlowNode bodyEnd = whileStatement.EmbeddedStatement.AcceptVisitor(this, bodyStart); + conditionNode.ConnectWith(bodyEnd); + if (cond != true) + end.ConnectWith(conditionNode); + + 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); + bodyStart.ConnectWith(data); + ControlFlowNode bodyEnd = doWhileStatement.EmbeddedStatement.AcceptVisitor(this, bodyStart); + conditionNode.ConnectWith(bodyEnd); + + bool? cond = builder.EvaluateCondition(doWhileStatement.Condition); + if (cond != false) + bodyStart.ConnectWith(conditionNode); + if (cond != true) + end.ConnectWith(conditionNode); + + 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); + conditionNode.ConnectWith(data); + + int iteratorStartNodeID = builder.nodes.Count; + ControlFlowNode iteratorEnd = HandleStatementList(forStatement.Iterators, null); + ControlFlowNode iteratorStart; + if (iteratorEnd != null) { + iteratorStart = builder.nodes[iteratorStartNodeID]; + iteratorEnd.ConnectWith(conditionNode); + } else { + iteratorStart = conditionNode; + } + + breakTargets.Push(end); + continueTargets.Push(iteratorStart); + + ControlFlowNode bodyStart = builder.CreateStartNode(forStatement.EmbeddedStatement); + ControlFlowNode bodyEnd = forStatement.EmbeddedStatement.AcceptVisitor(this, bodyStart); + iteratorStart.ConnectWith(bodyEnd); + + breakTargets.Pop(); + continueTargets.Pop(); + + bool? cond = forStatement.Condition.IsNull ? true : builder.EvaluateCondition(forStatement.Condition); + if (cond != false) + bodyStart.ConnectWith(conditionNode); + if (cond != true) + end.ConnectWith(conditionNode); + + return end; + } + + ControlFlowNode HandleEmbeddedStatement(Statement embeddedStatement, ControlFlowNode source) + { + if (embeddedStatement == null || embeddedStatement.IsNull) + return source; + ControlFlowNode bodyStart = builder.CreateStartNode(embeddedStatement); + if (source != null) + bodyStart.ConnectWith(source); + 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); + conditionNode.ConnectWith(data); + + breakTargets.Push(end); + continueTargets.Push(conditionNode); + + ControlFlowNode bodyEnd = HandleEmbeddedStatement(foreachStatement.EmbeddedStatement, conditionNode); + conditionNode.ConnectWith(bodyEnd); + + breakTargets.Pop(); + continueTargets.Pop(); + + return end.ConnectWith(conditionNode); + } + + public override ControlFlowNode VisitBreakStatement(BreakStatement breakStatement, ControlFlowNode data) + { + if (breakTargets.Count > 0) + breakTargets.Peek().ConnectWith(data); + return builder.CreateEndNode(breakStatement); + } + + public override ControlFlowNode VisitContinueStatement(ContinueStatement continueStatement, ControlFlowNode data) + { + if (continueTargets.Count > 0) + continueTargets.Peek().ConnectWith(data); + 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); + end.ConnectWith(HandleEmbeddedStatement(tryCatchStatement.TryBlock, data)); + foreach (CatchClause cc in tryCatchStatement.CatchClauses) { + end.ConnectWith(HandleEmbeddedStatement(cc.Body, data)); + } + 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 builder.CreateEndNode(checkedStatement).ConnectWith(bodyEnd); + } + + public override ControlFlowNode VisitUncheckedStatement(UncheckedStatement uncheckedStatement, ControlFlowNode data) + { + ControlFlowNode bodyEnd = HandleEmbeddedStatement(uncheckedStatement.Body, data); + return builder.CreateEndNode(uncheckedStatement).ConnectWith(bodyEnd); + } + + public override ControlFlowNode VisitLockStatement(LockStatement lockStatement, ControlFlowNode data) + { + ControlFlowNode bodyEnd = HandleEmbeddedStatement(lockStatement.EmbeddedStatement, data); + return builder.CreateEndNode(lockStatement).ConnectWith(bodyEnd); + } + + public override ControlFlowNode VisitUsingStatement(UsingStatement usingStatement, ControlFlowNode data) + { + data = HandleEmbeddedStatement(usingStatement.ResourceAcquisition as Statement, data); + ControlFlowNode bodyEnd = HandleEmbeddedStatement(usingStatement.EmbeddedStatement, data); + return builder.CreateEndNode(usingStatement).ConnectWith(bodyEnd); + } + + public override ControlFlowNode VisitYieldStatement(YieldStatement yieldStatement, ControlFlowNode data) + { + return builder.CreateEndNode(yieldStatement).ConnectWith(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 builder.CreateEndNode(unsafeStatement).ConnectWith(bodyEnd); + } + + public override ControlFlowNode VisitFixedStatement(FixedStatement fixedStatement, ControlFlowNode data) + { + ControlFlowNode bodyEnd = HandleEmbeddedStatement(fixedStatement.EmbeddedStatement, data); + return builder.CreateEndNode(fixedStatement).ConnectWith(bodyEnd); + } + } + } +} diff --git a/NRefactory/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj b/NRefactory/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj index b72cd1146..b0595a6e2 100644 --- a/NRefactory/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj +++ b/NRefactory/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj @@ -61,6 +61,7 @@ + @@ -354,5 +355,8 @@ Mono.Cecil + + + \ No newline at end of file From 623f006ae483c40b507a6f6413260f97506da17c Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 13 Mar 2011 15:02:23 +0100 Subject: [PATCH 2/5] Add definite assignment analysis. --- .../CSharp/Analysis/ControlFlow.cs | 283 ++++++--- .../Analysis/DefiniteAssignmentAnalysis.cs | 579 ++++++++++++++++++ .../CSharp/Ast/AstNodeCollection.cs | 7 +- .../ICSharpCode.NRefactory.csproj | 1 + 4 files changed, 786 insertions(+), 84 deletions(-) create mode 100644 NRefactory/ICSharpCode.NRefactory/CSharp/Analysis/DefiniteAssignmentAnalysis.cs diff --git a/NRefactory/ICSharpCode.NRefactory/CSharp/Analysis/ControlFlow.cs b/NRefactory/ICSharpCode.NRefactory/CSharp/Analysis/ControlFlow.cs index 785ff4bf8..3d0d25523 100644 --- a/NRefactory/ICSharpCode.NRefactory/CSharp/Analysis/ControlFlow.cs +++ b/NRefactory/ICSharpCode.NRefactory/CSharp/Analysis/ControlFlow.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using ICSharpCode.NRefactory.CSharp.Resolver; namespace ICSharpCode.NRefactory.CSharp.Analysis @@ -16,27 +17,18 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis public readonly Statement PreviousStatement; public readonly Statement NextStatement; - public ControlFlowNodeType Type; + public readonly ControlFlowNodeType Type; - public readonly List Predecessors = new List(); - public readonly List Successors = new List(); + public readonly List Outgoing = new List(); + public readonly List Incoming = new List(); - public ControlFlowNode(Statement previousStatement, Statement nextStatement) + 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; - } - - /// - /// Creates a control flow edge from source to this. - /// - internal ControlFlowNode ConnectWith(ControlFlowNode source) - { - source.Successors.Add(this); - this.Predecessors.Add(source); - return this; + this.Type = type; } } @@ -64,74 +56,179 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis 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(); + } + + /// + /// 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 { - protected virtual ControlFlowNode CreateNode(Statement previousStatement, Statement nextStatement) + // 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); + 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 ControlFlowNode[] BuildControlFlowGraph(BlockStatement block) + public IList BuildControlFlowGraph(Statement statement) { NodeCreationVisitor nodeCreationVisitor = new NodeCreationVisitor(); nodeCreationVisitor.builder = this; try { - nodes = new List(); - labels = new Dictionary(); - gotoStatements = new List(); - ControlFlowNode entryPoint = CreateStartNode(block); - nodeCreationVisitor.VisitBlockStatement(block, entryPoint); + 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)) - labelNode.ConnectWith(gotoStmt); + nodeCreationVisitor.Connect(gotoStmt, labelNode, ControlFlowEdgeType.Jump); } - return nodes.ToArray(); + AnnotateLeaveEdgesWithTryFinallyBlocks(); + + return nodes; } finally { - nodes = null; - labels = null; - gotoStatements = null; + 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); - node.Type = ControlFlowNodeType.StartNode; + ControlFlowNode node = CreateNode(null, statement, ControlFlowNodeType.StartNode); nodes.Add(node); return node; } ControlFlowNode CreateSpecialNode(Statement statement, ControlFlowNodeType type) { - ControlFlowNode node = CreateNode(statement, null); - node.Type = type; + ControlFlowNode node = CreateNode(statement, null, type); nodes.Add(node); return node; } ControlFlowNode CreateEndNode(Statement statement) { - // Find the next statement in the same role: - AstNode next = statement; - do { - next = next.NextSibling; - } while (next != null && next.Role != statement.Role); - - Statement nextStatement = next as Statement; - ControlFlowNode node = CreateNode(statement, nextStatement); - node.Type = nextStatement != null ? ControlFlowNodeType.BetweenStatements : ControlFlowNodeType.EndNode; + 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 /// @@ -172,6 +269,24 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis 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. @@ -182,7 +297,7 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis { // C# 4.0 spec: §8.2 Blocks ControlFlowNode childNode = HandleStatementList(blockStatement.Statements, data); - return builder.CreateEndNode(blockStatement).ConnectWith(childNode); + return CreateConnectedEndNode(blockStatement, childNode); } ControlFlowNode HandleStatementList(AstNodeCollection statements, ControlFlowNode source) @@ -191,7 +306,7 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis foreach (Statement stmt in statements) { if (childNode == null) { childNode = builder.CreateStartNode(stmt); - childNode.ConnectWith(source); + Connect(source, childNode); } Debug.Assert(childNode.NextStatement == stmt); childNode = stmt.AcceptVisitor(this, childNode); @@ -202,24 +317,24 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis public override ControlFlowNode VisitEmptyStatement(EmptyStatement emptyStatement, ControlFlowNode data) { - return builder.CreateEndNode(emptyStatement).ConnectWith(data); + return CreateConnectedEndNode(emptyStatement, data); } public override ControlFlowNode VisitLabelStatement(LabelStatement labelStatement, ControlFlowNode data) { - ControlFlowNode end = builder.CreateEndNode(labelStatement); + ControlFlowNode end = CreateConnectedEndNode(labelStatement, data); builder.labels[labelStatement.Label] = end; - return end.ConnectWith(data); + return end; } public override ControlFlowNode VisitVariableDeclarationStatement(VariableDeclarationStatement variableDeclarationStatement, ControlFlowNode data) { - return builder.CreateEndNode(variableDeclarationStatement).ConnectWith(data); + return CreateConnectedEndNode(variableDeclarationStatement, data); } public override ControlFlowNode VisitExpressionStatement(ExpressionStatement expressionStatement, ControlFlowNode data) { - return builder.CreateEndNode(expressionStatement).ConnectWith(data); + return CreateConnectedEndNode(expressionStatement, data); } public override ControlFlowNode VisitIfElseStatement(IfElseStatement ifElseStatement, ControlFlowNode data) @@ -227,7 +342,7 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis bool? cond = builder.EvaluateCondition(ifElseStatement.Condition); ControlFlowNode trueBegin = builder.CreateStartNode(ifElseStatement.TrueStatement); if (cond != false) - trueBegin.ConnectWith(data); + Connect(data, trueBegin, ControlFlowEdgeType.ConditionTrue); ControlFlowNode trueEnd = ifElseStatement.TrueStatement.AcceptVisitor(this, trueBegin); ControlFlowNode falseEnd; if (ifElseStatement.FalseStatement.IsNull) { @@ -235,15 +350,15 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis } else { ControlFlowNode falseBegin = builder.CreateStartNode(ifElseStatement.FalseStatement); if (cond != true) - falseBegin.ConnectWith(data); + Connect(data, falseBegin, ControlFlowEdgeType.ConditionFalse); falseEnd = ifElseStatement.FalseStatement.AcceptVisitor(this, falseBegin); } ControlFlowNode end = builder.CreateEndNode(ifElseStatement); - end.ConnectWith(trueEnd); + Connect(trueEnd, end); if (falseEnd != null) { - end.ConnectWith(falseEnd); + Connect(falseEnd, end); } else if (cond != true) { - end.ConnectWith(data); + Connect(data, end, ControlFlowEdgeType.ConditionFalse); } return end; } @@ -283,7 +398,7 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis } breakTargets.Pop(); if (defaultSection == null && sectionMatchedByConstant == null) { - end.ConnectWith(data); + Connect(data, end); } if (gotoCaseOrDefault.Count > gotoCaseOrDefaultInOuterScope) { @@ -314,16 +429,16 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis breakTargets.Push(end); continueTargets.Push(conditionNode); - conditionNode.ConnectWith(data); + Connect(data, conditionNode); bool? cond = builder.EvaluateCondition(whileStatement.Condition); ControlFlowNode bodyStart = builder.CreateStartNode(whileStatement.EmbeddedStatement); if (cond != false) - bodyStart.ConnectWith(conditionNode); + Connect(conditionNode, bodyStart, ControlFlowEdgeType.ConditionTrue); ControlFlowNode bodyEnd = whileStatement.EmbeddedStatement.AcceptVisitor(this, bodyStart); - conditionNode.ConnectWith(bodyEnd); + Connect(bodyEnd, conditionNode); if (cond != true) - end.ConnectWith(conditionNode); + Connect(conditionNode, end, ControlFlowEdgeType.ConditionFalse); breakTargets.Pop(); continueTargets.Pop(); @@ -339,15 +454,15 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis continueTargets.Push(conditionNode); ControlFlowNode bodyStart = builder.CreateStartNode(doWhileStatement.EmbeddedStatement); - bodyStart.ConnectWith(data); + Connect(data, bodyStart); ControlFlowNode bodyEnd = doWhileStatement.EmbeddedStatement.AcceptVisitor(this, bodyStart); - conditionNode.ConnectWith(bodyEnd); + Connect(bodyEnd, conditionNode); bool? cond = builder.EvaluateCondition(doWhileStatement.Condition); if (cond != false) - bodyStart.ConnectWith(conditionNode); + Connect(conditionNode, bodyStart, ControlFlowEdgeType.ConditionTrue); if (cond != true) - end.ConnectWith(conditionNode); + Connect(conditionNode, end, ControlFlowEdgeType.ConditionFalse); breakTargets.Pop(); continueTargets.Pop(); @@ -360,14 +475,14 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis // for (initializers ; cond; iterators) { embeddedStmt; } ControlFlowNode end = builder.CreateEndNode(forStatement); ControlFlowNode conditionNode = builder.CreateSpecialNode(forStatement, ControlFlowNodeType.LoopCondition); - conditionNode.ConnectWith(data); + Connect(data, conditionNode); int iteratorStartNodeID = builder.nodes.Count; ControlFlowNode iteratorEnd = HandleStatementList(forStatement.Iterators, null); ControlFlowNode iteratorStart; if (iteratorEnd != null) { iteratorStart = builder.nodes[iteratorStartNodeID]; - iteratorEnd.ConnectWith(conditionNode); + Connect(iteratorEnd, conditionNode); } else { iteratorStart = conditionNode; } @@ -377,16 +492,16 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis ControlFlowNode bodyStart = builder.CreateStartNode(forStatement.EmbeddedStatement); ControlFlowNode bodyEnd = forStatement.EmbeddedStatement.AcceptVisitor(this, bodyStart); - iteratorStart.ConnectWith(bodyEnd); + Connect(bodyEnd, iteratorStart); breakTargets.Pop(); continueTargets.Pop(); bool? cond = forStatement.Condition.IsNull ? true : builder.EvaluateCondition(forStatement.Condition); if (cond != false) - bodyStart.ConnectWith(conditionNode); + Connect(conditionNode, bodyStart, ControlFlowEdgeType.ConditionTrue); if (cond != true) - end.ConnectWith(conditionNode); + Connect(conditionNode, end, ControlFlowEdgeType.ConditionFalse); return end; } @@ -397,7 +512,7 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis return source; ControlFlowNode bodyStart = builder.CreateStartNode(embeddedStatement); if (source != null) - bodyStart.ConnectWith(source); + Connect(source, bodyStart); return embeddedStatement.AcceptVisitor(this, bodyStart); } @@ -406,31 +521,33 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis // foreach (...) { embeddedStmt } ControlFlowNode end = builder.CreateEndNode(foreachStatement); ControlFlowNode conditionNode = builder.CreateSpecialNode(foreachStatement, ControlFlowNodeType.LoopCondition); - conditionNode.ConnectWith(data); + Connect(data, conditionNode); breakTargets.Push(end); continueTargets.Push(conditionNode); ControlFlowNode bodyEnd = HandleEmbeddedStatement(foreachStatement.EmbeddedStatement, conditionNode); - conditionNode.ConnectWith(bodyEnd); + Connect(bodyEnd, conditionNode); breakTargets.Pop(); continueTargets.Pop(); - return end.ConnectWith(conditionNode); + Connect(conditionNode, end); + + return end; } public override ControlFlowNode VisitBreakStatement(BreakStatement breakStatement, ControlFlowNode data) { if (breakTargets.Count > 0) - breakTargets.Peek().ConnectWith(data); + Connect(data, breakTargets.Peek(), ControlFlowEdgeType.Jump); return builder.CreateEndNode(breakStatement); } public override ControlFlowNode VisitContinueStatement(ContinueStatement continueStatement, ControlFlowNode data) { if (continueTargets.Count > 0) - continueTargets.Peek().ConnectWith(data); + Connect(data, continueTargets.Peek(), ControlFlowEdgeType.Jump); return builder.CreateEndNode(continueStatement); } @@ -453,9 +570,13 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis public override ControlFlowNode VisitTryCatchStatement(TryCatchStatement tryCatchStatement, ControlFlowNode data) { ControlFlowNode end = builder.CreateEndNode(tryCatchStatement); - end.ConnectWith(HandleEmbeddedStatement(tryCatchStatement.TryBlock, data)); + var edge = Connect(HandleEmbeddedStatement(tryCatchStatement.TryBlock, data), end); + if (!tryCatchStatement.FinallyBlock.IsNull) + edge.AddJumpOutOfTryFinally(tryCatchStatement); foreach (CatchClause cc in tryCatchStatement.CatchClauses) { - end.ConnectWith(HandleEmbeddedStatement(cc.Body, data)); + 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. @@ -468,31 +589,31 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis public override ControlFlowNode VisitCheckedStatement(CheckedStatement checkedStatement, ControlFlowNode data) { ControlFlowNode bodyEnd = HandleEmbeddedStatement(checkedStatement.Body, data); - return builder.CreateEndNode(checkedStatement).ConnectWith(bodyEnd); + return CreateConnectedEndNode(checkedStatement, bodyEnd); } public override ControlFlowNode VisitUncheckedStatement(UncheckedStatement uncheckedStatement, ControlFlowNode data) { ControlFlowNode bodyEnd = HandleEmbeddedStatement(uncheckedStatement.Body, data); - return builder.CreateEndNode(uncheckedStatement).ConnectWith(bodyEnd); + return CreateConnectedEndNode(uncheckedStatement, bodyEnd); } public override ControlFlowNode VisitLockStatement(LockStatement lockStatement, ControlFlowNode data) { ControlFlowNode bodyEnd = HandleEmbeddedStatement(lockStatement.EmbeddedStatement, data); - return builder.CreateEndNode(lockStatement).ConnectWith(bodyEnd); + 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 builder.CreateEndNode(usingStatement).ConnectWith(bodyEnd); + return CreateConnectedEndNode(usingStatement, bodyEnd); } public override ControlFlowNode VisitYieldStatement(YieldStatement yieldStatement, ControlFlowNode data) { - return builder.CreateEndNode(yieldStatement).ConnectWith(data); + return CreateConnectedEndNode(yieldStatement, data); } public override ControlFlowNode VisitYieldBreakStatement(YieldBreakStatement yieldBreakStatement, ControlFlowNode data) @@ -503,13 +624,13 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis public override ControlFlowNode VisitUnsafeStatement(UnsafeStatement unsafeStatement, ControlFlowNode data) { ControlFlowNode bodyEnd = HandleEmbeddedStatement(unsafeStatement.Body, data); - return builder.CreateEndNode(unsafeStatement).ConnectWith(bodyEnd); + return CreateConnectedEndNode(unsafeStatement, bodyEnd); } public override ControlFlowNode VisitFixedStatement(FixedStatement fixedStatement, ControlFlowNode data) { ControlFlowNode bodyEnd = HandleEmbeddedStatement(fixedStatement.EmbeddedStatement, data); - return builder.CreateEndNode(fixedStatement).ConnectWith(bodyEnd); + 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..958e1642a --- /dev/null +++ b/NRefactory/ICSharpCode.NRefactory/CSharp/Analysis/DefiniteAssignmentAnalysis.cs @@ -0,0 +1,579 @@ +// 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 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 unassigned. + /// + DefinitelyUnassigned, + /// + /// 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); + } + } + + public void Analyze(string variable, DefiniteAssignmentStatus initialStatus = DefiniteAssignmentStatus.DefinitelyUnassigned) + { + 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], DefiniteAssignmentStatus.DefinitelyUnassigned); + // 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); + } + } + + 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)) { + DefiniteAssignmentStatus s = edgeStatus[edge]; + if (s == DefiniteAssignmentStatus.DefinitelyUnassigned || 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 -> Definitely[Un]Assigned + // Going against this direction indicates a bug and could cause infinite loops. + switch (newStatus) { + case DefiniteAssignmentStatus.PotentiallyAssigned: + if (oldStatus != DefiniteAssignmentStatus.CodeUnreachable) + throw new InvalidOperationException("Invalid state transition"); + break; + case DefiniteAssignmentStatus.DefinitelyUnassigned: + case DefiniteAssignmentStatus.DefinitelyAssigned: + if (!(oldStatus == DefiniteAssignmentStatus.CodeUnreachable || oldStatus == DefiniteAssignmentStatus.PotentiallyAssigned)) + throw new InvalidOperationException("Invalid state transition"); + break; + case DefiniteAssignmentStatus.CodeUnreachable: + 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 if (afterRight == DefiniteAssignmentStatus.DefinitelyUnassigned) + return afterRight; + 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 if (afterRight == DefiniteAssignmentStatus.DefinitelyUnassigned) + return DefiniteAssignmentStatus.DefinitelyUnassigned; + 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/ICSharpCode.NRefactory.csproj b/NRefactory/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj index b0595a6e2..bd1348ebe 100644 --- a/NRefactory/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj +++ b/NRefactory/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj @@ -62,6 +62,7 @@ + From 6e3427d10be348847e6afefd000a109093e4e17d Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 13 Mar 2011 15:16:34 +0100 Subject: [PATCH 3/5] Set NRefactory back to .NET 4.0 (we're not going to port ILSpy to .NET 3.5) --- ILSpy.sln | 10 +++++----- .../CSharp/Ast/TypeMembers/Accessor.cs | 6 +----- .../ICSharpCode.NRefactory.csproj | 8 ++++---- .../ICSharpCode.NRefactory/Utils/DotNet35Compat.cs | 4 ++-- 4 files changed, 12 insertions(+), 16 deletions(-) diff --git a/ILSpy.sln b/ILSpy.sln index ebfadb908..f3c38c7d8 100644 --- a/ILSpy.sln +++ b/ILSpy.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 11.00 # Visual Studio 2010 -# SharpDevelop 4.0.1.7096 +# SharpDevelop 4.0.1.7100 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ILSpy", "ILSpy\ILSpy.csproj", "{1E85EFF9-E370-4683-83E4-8A3D063FF791}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ICSharpCode.TreeView", "SharpTreeView\ICSharpCode.TreeView.csproj", "{DDE2A481-8271-4EAC-A330-8FA6A38D13D1}" @@ -40,13 +40,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/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 bd1348ebe..40d8f5afd 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 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); From c9ec992688aa3a4f58630185de9a686119692c47 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 13 Mar 2011 17:07:29 +0100 Subject: [PATCH 4/5] Fix issues in definite assignment analysis. --- .../Analysis/DefiniteAssignmentTests.cs | 132 ++++++++++++ .../ICSharpCode.NRefactory.Tests.csproj | 2 + .../CSharp/Analysis/ControlFlow.cs | 1 + .../Analysis/DefiniteAssignmentAnalysis.cs | 101 +++++++-- .../CSharp/Ast/DepthFirstAstVisitor.cs | 8 +- .../CSharp/Ast/Expressions/Expression.cs | 2 + .../CSharp/Ast/Statements/Statement.cs | 15 ++ .../ICSharpCode.NRefactory.csproj | 1 + .../Utils/GraphVizGraph.cs | 204 ++++++++++++++++++ 9 files changed, 442 insertions(+), 24 deletions(-) create mode 100644 NRefactory/ICSharpCode.NRefactory.Tests/CSharp/Analysis/DefiniteAssignmentTests.cs create mode 100644 NRefactory/ICSharpCode.NRefactory/Utils/GraphVizGraph.cs 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 index 3d0d25523..2f1e2b47e 100644 --- a/NRefactory/ICSharpCode.NRefactory/CSharp/Analysis/ControlFlow.cs +++ b/NRefactory/ICSharpCode.NRefactory/CSharp/Analysis/ControlFlow.cs @@ -79,6 +79,7 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis { if (jumpOutOfTryFinally == null) jumpOutOfTryFinally = new List(); + jumpOutOfTryFinally.Add(tryFinally); } /// diff --git a/NRefactory/ICSharpCode.NRefactory/CSharp/Analysis/DefiniteAssignmentAnalysis.cs b/NRefactory/ICSharpCode.NRefactory/CSharp/Analysis/DefiniteAssignmentAnalysis.cs index 958e1642a..534e42325 100644 --- a/NRefactory/ICSharpCode.NRefactory/CSharp/Analysis/DefiniteAssignmentAnalysis.cs +++ b/NRefactory/ICSharpCode.NRefactory/CSharp/Analysis/DefiniteAssignmentAnalysis.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using ICSharpCode.NRefactory.CSharp.Resolver; +using ICSharpCode.NRefactory.Utils; namespace ICSharpCode.NRefactory.CSharp.Analysis { @@ -19,10 +20,6 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis /// PotentiallyAssigned, /// - /// The variable is definitely unassigned. - /// - DefinitelyUnassigned, - /// /// The variable is definitely assigned. /// DefinitelyAssigned, @@ -82,7 +79,16 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis } } - public void Analyze(string variable, DefiniteAssignmentStatus initialStatus = DefiniteAssignmentStatus.DefinitelyUnassigned) + /// + /// 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: @@ -93,7 +99,7 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis edgeStatus[edge] = DefiniteAssignmentStatus.CodeUnreachable; } - ChangeNodeStatus(allNodes[0], DefiniteAssignmentStatus.DefinitelyUnassigned); + ChangeNodeStatus(allNodes[0], initialStatus); // Iterate as long as the input status of some nodes is changing: while (nodesWithModifiedInput.Count > 0) { ControlFlowNode node = nodesWithModifiedInput.Dequeue(); @@ -105,6 +111,64 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis } } + 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. @@ -151,9 +215,11 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis 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)) { - DefiniteAssignmentStatus s = edgeStatus[edge]; - if (s == DefiniteAssignmentStatus.DefinitelyUnassigned || s == DefiniteAssignmentStatus.PotentiallyAssigned) { - ChangeEdgeStatus(edge, outputStatus); + if (edge.IsLeavingTryFinally && edge.TryFinallyStatements.Contains(tryFinally)) { + DefiniteAssignmentStatus s = edgeStatus[edge]; + if (s == DefiniteAssignmentStatus.PotentiallyAssigned) { + ChangeEdgeStatus(edge, outputStatus); + } } } } @@ -197,19 +263,18 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis if (oldStatus == newStatus) return; // Ensure that status can change only in one direction: - // CodeUnreachable -> PotentiallyAssigned -> Definitely[Un]Assigned + // CodeUnreachable -> PotentiallyAssigned -> DefinitelyAssigned // Going against this direction indicates a bug and could cause infinite loops. - switch (newStatus) { + switch (oldStatus) { case DefiniteAssignmentStatus.PotentiallyAssigned: - if (oldStatus != DefiniteAssignmentStatus.CodeUnreachable) + if (newStatus != DefiniteAssignmentStatus.DefinitelyAssigned) throw new InvalidOperationException("Invalid state transition"); break; - case DefiniteAssignmentStatus.DefinitelyUnassigned: - case DefiniteAssignmentStatus.DefinitelyAssigned: - if (!(oldStatus == DefiniteAssignmentStatus.CodeUnreachable || oldStatus == DefiniteAssignmentStatus.PotentiallyAssigned)) + case DefiniteAssignmentStatus.CodeUnreachable: + if (!(newStatus == DefiniteAssignmentStatus.PotentiallyAssigned || newStatus == DefiniteAssignmentStatus.DefinitelyAssigned)) throw new InvalidOperationException("Invalid state transition"); break; - case DefiniteAssignmentStatus.CodeUnreachable: + case DefiniteAssignmentStatus.DefinitelyAssigned: throw new InvalidOperationException("Invalid state transition"); default: throw new InvalidOperationException("Invalid value for DefiniteAssignmentStatus"); @@ -449,8 +514,6 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis return DefiniteAssignmentStatus.AssignedAfterTrueExpression; else if (afterLeft == DefiniteAssignmentStatus.AssignedAfterFalseExpression && afterRight == DefiniteAssignmentStatus.AssignedAfterFalseExpression) return DefiniteAssignmentStatus.AssignedAfterFalseExpression; - else if (afterRight == DefiniteAssignmentStatus.DefinitelyUnassigned) - return afterRight; else return DefiniteAssignmentStatus.PotentiallyAssigned; } else if (binaryOperatorExpression.Operator == BinaryOperatorType.ConditionalOr) { @@ -477,8 +540,6 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis return DefiniteAssignmentStatus.AssignedAfterFalseExpression; else if (afterLeft == DefiniteAssignmentStatus.AssignedAfterTrueExpression && afterRight == DefiniteAssignmentStatus.AssignedAfterTrueExpression) return DefiniteAssignmentStatus.AssignedAfterTrueExpression; - else if (afterRight == DefiniteAssignmentStatus.DefinitelyUnassigned) - return DefiniteAssignmentStatus.DefinitelyUnassigned; else return DefiniteAssignmentStatus.PotentiallyAssigned; } else if (binaryOperatorExpression.Operator == BinaryOperatorType.NullCoalescing) { 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/ICSharpCode.NRefactory.csproj b/NRefactory/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj index 40d8f5afd..6a57c5315 100644 --- a/NRefactory/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj +++ b/NRefactory/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj @@ -334,6 +334,7 @@ + diff --git a/NRefactory/ICSharpCode.NRefactory/Utils/GraphVizGraph.cs b/NRefactory/ICSharpCode.NRefactory/Utils/GraphVizGraph.cs new file mode 100644 index 000000000..2bf93c353 --- /dev/null +++ b/NRefactory/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("];"); + } + } +} From 68b07e28288d79a9ddcc7ff59894dce08337ec2e Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 13 Mar 2011 17:09:07 +0100 Subject: [PATCH 5/5] Remove GraphViz from ICSharpCode.Decompiler. --- .../FlowAnalysis/ControlFlowGraph.cs | 4 +- .../FlowAnalysis/SsaForm.cs | 1 + ICSharpCode.Decompiler/GraphVizGraph.cs | 197 ------------------ .../ICSharpCode.Decompiler.csproj | 1 - ILSpy/MainWindow.xaml.cs | 2 +- 5 files changed, 4 insertions(+), 201 deletions(-) delete mode 100644 ICSharpCode.Decompiler/GraphVizGraph.cs 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/GraphVizGraph.cs b/ICSharpCode.Decompiler/GraphVizGraph.cs deleted file mode 100644 index 62b957f25..000000000 --- a/ICSharpCode.Decompiler/GraphVizGraph.cs +++ /dev/null @@ -1,197 +0,0 @@ -// 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. - -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Text.RegularExpressions; - -namespace ICSharpCode.Decompiler -{ - /// - /// GraphViz graph. - /// - public sealed class GraphVizGraph - { - List nodes = new List(); - List edges = new List(); - - public string rankdir; - - 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); - } - - 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) - { - 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("];"); - } - } -} diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index 1dee46ee2..d0e482afb 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -85,7 +85,6 @@ - diff --git a/ILSpy/MainWindow.xaml.cs b/ILSpy/MainWindow.xaml.cs index 88b28ff1a..25d4103ea 100644 --- a/ILSpy/MainWindow.xaml.cs +++ b/ILSpy/MainWindow.xaml.cs @@ -273,7 +273,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)