diff --git a/ICSharpCode.NRefactory/CSharp/Analysis/ControlFlow.cs b/ICSharpCode.NRefactory/CSharp/Analysis/ControlFlow.cs index 92ffa63fa7..1660e99b14 100644 --- a/ICSharpCode.NRefactory/CSharp/Analysis/ControlFlow.cs +++ b/ICSharpCode.NRefactory/CSharp/Analysis/ControlFlow.cs @@ -222,14 +222,15 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis return node; } - ControlFlowNode CreateSpecialNode(Statement statement, ControlFlowNodeType type) + ControlFlowNode CreateSpecialNode(Statement statement, ControlFlowNodeType type, bool addToNodeList = true) { ControlFlowNode node = CreateNode(null, statement, type); - nodes.Add(node); + if (addToNodeList) + nodes.Add(node); return node; } - ControlFlowNode CreateEndNode(Statement statement) + ControlFlowNode CreateEndNode(Statement statement, bool addToNodeList = true) { Statement nextStatement; if (statement == rootStatement) { @@ -244,7 +245,8 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis } ControlFlowNodeType type = nextStatement != null ? ControlFlowNodeType.BetweenStatements : ControlFlowNodeType.EndNode; ControlFlowNode node = CreateNode(statement, nextStatement, type); - nodes.Add(node); + if (addToNodeList) + nodes.Add(node); return node; } #endregion @@ -259,7 +261,7 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis /// Evaluates an expression. /// /// The constant value of the expression; or null if the expression is not a constant. - internal ConstantResolveResult EvaluateConstant(Expression expr) + ConstantResolveResult EvaluateConstant(Expression expr) { if (EvaluateOnlyPrimitiveConstants) { if (!(expr is PrimitiveExpression || expr is NullReferenceExpression)) @@ -272,7 +274,7 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis /// Evaluates an expression. /// /// The value of the constant boolean expression; or null if the value is not a constant boolean expression. - internal bool? EvaluateCondition(Expression expr) + bool? EvaluateCondition(Expression expr) { ConstantResolveResult rr = EvaluateConstant(expr); if (rr != null) @@ -418,7 +420,7 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis int gotoCaseOrDefaultInOuterScope = gotoCaseOrDefault.Count; - ControlFlowNode end = builder.CreateEndNode(switchStatement); + ControlFlowNode end = builder.CreateEndNode(switchStatement, addToNodeList: false); breakTargets.Push(end); foreach (SwitchSection section in switchStatement.SwitchSections) { if (constant == null || section == sectionMatchedByConstant) { @@ -439,6 +441,7 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis throw new NotImplementedException(); } + builder.nodes.Add(end); return end; } @@ -457,7 +460,7 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis public override ControlFlowNode VisitWhileStatement(WhileStatement whileStatement, ControlFlowNode data) { // while (cond) { embeddedStmt; } - ControlFlowNode end = builder.CreateEndNode(whileStatement); + ControlFlowNode end = builder.CreateEndNode(whileStatement, addToNodeList: false); ControlFlowNode conditionNode = builder.CreateSpecialNode(whileStatement, ControlFlowNodeType.LoopCondition); breakTargets.Push(end); continueTargets.Push(conditionNode); @@ -475,14 +478,15 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis breakTargets.Pop(); continueTargets.Pop(); + builder.nodes.Add(end); 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); + ControlFlowNode end = builder.CreateEndNode(doWhileStatement, addToNodeList: false); + ControlFlowNode conditionNode = builder.CreateSpecialNode(doWhileStatement, ControlFlowNodeType.LoopCondition, addToNodeList: false); breakTargets.Push(end); continueTargets.Push(conditionNode); @@ -499,6 +503,8 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis breakTargets.Pop(); continueTargets.Pop(); + builder.nodes.Add(conditionNode); + builder.nodes.Add(end); return end; } @@ -506,7 +512,7 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis { data = HandleStatementList(forStatement.Initializers, data); // for (initializers ; cond; iterators) { embeddedStmt; } - ControlFlowNode end = builder.CreateEndNode(forStatement); + ControlFlowNode end = builder.CreateEndNode(forStatement, addToNodeList: false); ControlFlowNode conditionNode = builder.CreateSpecialNode(forStatement, ControlFlowNodeType.LoopCondition); Connect(data, conditionNode); @@ -536,6 +542,7 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis if (cond != true) Connect(conditionNode, end, ControlFlowEdgeType.ConditionFalse); + builder.nodes.Add(end); return end; } @@ -552,7 +559,7 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis public override ControlFlowNode VisitForeachStatement(ForeachStatement foreachStatement, ControlFlowNode data) { // foreach (...) { embeddedStmt } - ControlFlowNode end = builder.CreateEndNode(foreachStatement); + ControlFlowNode end = builder.CreateEndNode(foreachStatement, addToNodeList: false); ControlFlowNode conditionNode = builder.CreateSpecialNode(foreachStatement, ControlFlowNodeType.LoopCondition); Connect(data, conditionNode); @@ -566,7 +573,7 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis continueTargets.Pop(); Connect(conditionNode, end); - + builder.nodes.Add(end); return end; } @@ -602,7 +609,7 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis public override ControlFlowNode VisitTryCatchStatement(TryCatchStatement tryCatchStatement, ControlFlowNode data) { - ControlFlowNode end = builder.CreateEndNode(tryCatchStatement); + ControlFlowNode end = builder.CreateEndNode(tryCatchStatement, addToNodeList: false); var edge = Connect(HandleEmbeddedStatement(tryCatchStatement.TryBlock, data), end); if (!tryCatchStatement.FinallyBlock.IsNull) edge.AddJumpOutOfTryFinally(tryCatchStatement); @@ -616,6 +623,7 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis // Consumers of the CFG will have to special-case try-finally. HandleEmbeddedStatement(tryCatchStatement.FinallyBlock, data); } + builder.nodes.Add(end); return end; } diff --git a/ICSharpCode.NRefactory/CSharp/Analysis/DefiniteAssignmentAnalysis.cs b/ICSharpCode.NRefactory/CSharp/Analysis/DefiniteAssignmentAnalysis.cs index c02b5ea30f..fb723aedce 100644 --- a/ICSharpCode.NRefactory/CSharp/Analysis/DefiniteAssignmentAnalysis.cs +++ b/ICSharpCode.NRefactory/CSharp/Analysis/DefiniteAssignmentAnalysis.cs @@ -44,21 +44,40 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis /// public class DefiniteAssignmentAnalysis { - readonly ControlFlowGraphBuilder cfgBuilder = new ControlFlowGraphBuilder(); + sealed class DefiniteAssignmentNode : ControlFlowNode + { + public int Index; + public DefiniteAssignmentStatus NodeStatus; + + public DefiniteAssignmentNode(Statement previousStatement, Statement nextStatement, ControlFlowNodeType type) + : base(previousStatement, nextStatement, type) + { + } + } + + sealed class DerivedControlFlowGraphBuilder : ControlFlowGraphBuilder + { + protected override ControlFlowNode CreateNode(Statement previousStatement, Statement nextStatement, ControlFlowNodeType type) + { + return new DefiniteAssignmentNode(previousStatement, nextStatement, type); + } + } + + readonly DerivedControlFlowGraphBuilder cfgBuilder = new DerivedControlFlowGraphBuilder(); readonly DefiniteAssignmentVisitor visitor = new DefiniteAssignmentVisitor(); - readonly List allNodes = new List(); - readonly Dictionary beginNodeDict = new Dictionary(); - readonly Dictionary endNodeDict = new Dictionary(); - readonly Dictionary conditionNodeDict = new Dictionary(); + readonly List allNodes = new List(); + readonly Dictionary beginNodeDict = new Dictionary(); + readonly Dictionary endNodeDict = new Dictionary(); + readonly Dictionary conditionNodeDict = new Dictionary(); readonly ResolveVisitor resolveVisitor; readonly CancellationToken cancellationToken; - Dictionary nodeStatus = new Dictionary(); Dictionary edgeStatus = new Dictionary(); string variableName; List unassignedVariableUses = new List(); + int analyzedRangeStart, analyzedRangeEnd; - Queue nodesWithModifiedInput = new Queue(); + Queue nodesWithModifiedInput = new Queue(); public DefiniteAssignmentAnalysis(Statement rootStatement, CancellationToken cancellationToken = default(CancellationToken)) : this(rootStatement, null, cancellationToken) @@ -83,20 +102,18 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis if (resolveVisitor.TypeResolveContext is MinimalResolveContext) { cfgBuilder.EvaluateOnlyPrimitiveConstants = true; } - allNodes.AddRange(cfgBuilder.BuildControlFlowGraph(rootStatement, resolveVisitor)); - 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(cfgBuilder.BuildControlFlowGraph(ame.Body, resolveVisitor)); - LambdaExpression lambda = descendant as LambdaExpression; - if (lambda != null && lambda.Body is Statement) - allNodes.AddRange(cfgBuilder.BuildControlFlowGraph((Statement)lambda.Body, resolveVisitor)); - } - // 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) { + allNodes.AddRange(cfgBuilder.BuildControlFlowGraph(rootStatement, resolveVisitor).Cast()); + for (int i = 0; i < allNodes.Count; i++) { + DefiniteAssignmentNode node = allNodes[i]; + node.Index = i; // assign numbers to the nodes + if (node.Type == ControlFlowNodeType.StartNode || node.Type == ControlFlowNodeType.BetweenStatements) { + // Anonymous methods have separate control flow graphs, but we also need to analyze those. + // Iterate backwards so that anonymous methods are inserted in the correct order + for (AstNode child = node.NextStatement.LastChild; child != null; child = child.PrevSibling) { + InsertAnonymousMethods(i + 1, child); + } + } + // Now register the node in the dictionaries: if (node.Type == ControlFlowNodeType.StartNode || node.Type == ControlFlowNodeType.BetweenStatements) beginNodeDict.Add(node.NextStatement, node); if (node.Type == ControlFlowNodeType.BetweenStatements || node.Type == ControlFlowNodeType.EndNode) @@ -104,6 +121,32 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis if (node.Type == ControlFlowNodeType.LoopCondition) conditionNodeDict.Add(node.NextStatement, node); } + // Verify that we created nodes for all statements: + Debug.Assert(!rootStatement.DescendantsAndSelf.OfType().Except(allNodes.Select(n => n.NextStatement)).Any()); + this.analyzedRangeStart = 0; + this.analyzedRangeEnd = allNodes.Count - 1; + } + + void InsertAnonymousMethods(int insertPos, AstNode node) + { + // Ignore any statements, as those have their own ControlFlowNode and get handled separately + if (node is Statement) + return; + AnonymousMethodExpression ame = node as AnonymousMethodExpression; + if (ame != null) { + allNodes.InsertRange(insertPos, cfgBuilder.BuildControlFlowGraph(ame.Body, resolveVisitor).Cast()); + return; + } + LambdaExpression lambda = node as LambdaExpression; + if (lambda != null && lambda.Body is Statement) { + allNodes.InsertRange(insertPos, cfgBuilder.BuildControlFlowGraph((Statement)lambda.Body, resolveVisitor).Cast()); + return; + } + // Descend into child expressions + // Iterate backwards so that anonymous methods are inserted in the correct order + for (AstNode child = node.LastChild; child != null; child = child.PrevSibling) { + InsertAnonymousMethods(insertPos, child); + } } /// @@ -115,21 +158,37 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis } } + /// + /// Sets the range of statements to be analyzed. + /// This method can be used to restrict the analysis to only a part of the method. + /// Only the control flow paths that are fully contained within the selected part will be analyzed. + /// + /// Both 'start' and 'end' are inclusive. + public void SetAnalyzedRange(Statement start, Statement end) + { + int startIndex = beginNodeDict[start].Index; + int endIndex = endNodeDict[end].Index; + if (startIndex > endIndex) + throw new ArgumentException("The start statement must be lexically preceding the end statement"); + this.analyzedRangeStart = startIndex; + this.analyzedRangeEnd = endIndex; + } + public void Analyze(string variable, DefiniteAssignmentStatus initialStatus = DefiniteAssignmentStatus.PotentiallyAssigned) { this.variableName = variable; // Reset the status: unassignedVariableUses.Clear(); - foreach (ControlFlowNode node in allNodes) { - nodeStatus[node] = DefiniteAssignmentStatus.CodeUnreachable; + foreach (DefiniteAssignmentNode node in allNodes) { + node.NodeStatus = DefiniteAssignmentStatus.CodeUnreachable; foreach (ControlFlowEdge edge in node.Outgoing) edgeStatus[edge] = DefiniteAssignmentStatus.CodeUnreachable; } - ChangeNodeStatus(allNodes[0], initialStatus); + ChangeNodeStatus(allNodes[analyzedRangeStart], initialStatus); // Iterate as long as the input status of some nodes is changing: while (nodesWithModifiedInput.Count > 0) { - ControlFlowNode node = nodesWithModifiedInput.Dequeue(); + DefiniteAssignmentNode node = nodesWithModifiedInput.Dequeue(); DefiniteAssignmentStatus inputStatus = DefiniteAssignmentStatus.CodeUnreachable; foreach (ControlFlowEdge edge in node.Incoming) { inputStatus = MergeStatus(inputStatus, edgeStatus[edge]); @@ -140,17 +199,17 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis public DefiniteAssignmentStatus GetStatusBefore(Statement statement) { - return nodeStatus[beginNodeDict[statement]]; + return beginNodeDict[statement].NodeStatus; } public DefiniteAssignmentStatus GetStatusAfter(Statement statement) { - return nodeStatus[endNodeDict[statement]]; + return endNodeDict[statement].NodeStatus; } public DefiniteAssignmentStatus GetStatusBeforeLoopCondition(Statement statement) { - return nodeStatus[conditionNodeDict[statement]]; + return conditionNodeDict[statement].NodeStatus; } /// @@ -161,7 +220,7 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis GraphVizGraph g = new GraphVizGraph(); g.Title = "DefiniteAssignment - " + variableName; for (int i = 0; i < allNodes.Count; i++) { - string name = nodeStatus[allNodes[i]].ToString() + Environment.NewLine; + string name = "#" + i + " = " + allNodes[i].NodeStatus.ToString() + Environment.NewLine; switch (allNodes[i].Type) { case ControlFlowNodeType.StartNode: case ControlFlowNodeType.BetweenStatements: @@ -179,7 +238,7 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis } g.AddNode(new GraphVizNode(i) { label = name }); foreach (ControlFlowEdge edge in allNodes[i].Outgoing) { - GraphVizEdge ge = new GraphVizEdge(i, allNodes.IndexOf(edge.To)); + GraphVizEdge ge = new GraphVizEdge(i, ((DefiniteAssignmentNode)edge.To).Index); if (edgeStatus.Count > 0) ge.label = edgeStatus[edge].ToString(); if (edge.IsLeavingTryFinally) @@ -218,11 +277,11 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis return DefiniteAssignmentStatus.PotentiallyAssigned; } - void ChangeNodeStatus(ControlFlowNode node, DefiniteAssignmentStatus inputStatus) + void ChangeNodeStatus(DefiniteAssignmentNode node, DefiniteAssignmentStatus inputStatus) { - if (nodeStatus[node] == inputStatus) + if (node.NodeStatus == inputStatus) return; - nodeStatus[node] = inputStatus; + node.NodeStatus = inputStatus; DefiniteAssignmentStatus outputStatus; switch (node.Type) { case ControlFlowNodeType.StartNode: @@ -312,7 +371,9 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis throw new InvalidOperationException("Invalid value for DefiniteAssignmentStatus"); } edgeStatus[edge] = newStatus; - nodesWithModifiedInput.Enqueue(edge.To); + DefiniteAssignmentNode targetNode = (DefiniteAssignmentNode)edge.To; + if (analyzedRangeStart <= targetNode.Index && targetNode.Index <= analyzedRangeEnd) + nodesWithModifiedInput.Enqueue(targetNode); } /// @@ -321,7 +382,11 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis /// The constant value of the expression; or null if the expression is not a constant. ConstantResolveResult EvaluateConstant(Expression expr) { - return cfgBuilder.EvaluateConstant(expr); + if (resolveVisitor.TypeResolveContext is MinimalResolveContext) { + if (!(expr is PrimitiveExpression || expr is NullReferenceExpression)) + return null; + } + return resolveVisitor.Resolve(expr) as ConstantResolveResult; } /// @@ -330,7 +395,11 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis /// The value of the constant boolean expression; or null if the value is not a constant boolean expression. bool? EvaluateCondition(Expression expr) { - return cfgBuilder.EvaluateCondition(expr); + ConstantResolveResult rr = EvaluateConstant(expr); + if (rr != null) + return rr.ConstantValue as bool?; + else + return null; } static DefiniteAssignmentStatus CleanSpecialValues(DefiniteAssignmentStatus status) @@ -633,10 +702,7 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis 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); - } + analysis.ChangeNodeStatus(analysis.beginNodeDict[body], data); return data; } @@ -644,10 +710,7 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis { Statement body = lambdaExpression.Body as Statement; if (body != null) { - foreach (ControlFlowNode node in analysis.allNodes) { - if (node.NextStatement == body) - analysis.ChangeNodeStatus(node, data); - } + analysis.ChangeNodeStatus(analysis.beginNodeDict[body], data); } else { lambdaExpression.Body.AcceptVisitor(this, data); }