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