diff --git a/ICSharpCode.NRefactory.Tests/CSharp/Analysis/DefiniteAssignmentTests.cs b/ICSharpCode.NRefactory.Tests/CSharp/Analysis/DefiniteAssignmentTests.cs index 2e94a1c589..10373b84ca 100644 --- a/ICSharpCode.NRefactory.Tests/CSharp/Analysis/DefiniteAssignmentTests.cs +++ b/ICSharpCode.NRefactory.Tests/CSharp/Analysis/DefiniteAssignmentTests.cs @@ -148,5 +148,54 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis Assert.AreEqual(DefiniteAssignmentStatus.CodeUnreachable, da.GetStatusAfter(loop.EmbeddedStatement)); Assert.AreEqual(DefiniteAssignmentStatus.DefinitelyAssigned, da.GetStatusAfter(loop)); } + + [Test] + public void ForLoop() + { + ForStatement loop = new ForStatement { + Initializers = { + new ExpressionStatement( + new AssignmentExpression(new IdentifierExpression("i"), new PrimitiveExpression(0)) + ) + }, + Condition = new BinaryOperatorExpression(new IdentifierExpression("i"), BinaryOperatorType.LessThan, new PrimitiveExpression(1000)), + Iterators = { + new ExpressionStatement( + new AssignmentExpression { + Left = new IdentifierExpression("i"), + Operator = AssignmentOperatorType.Add, + Right = new IdentifierExpression("j") + } + ) + }, + EmbeddedStatement = new ExpressionStatement( + new AssignmentExpression(new IdentifierExpression("j"), new IdentifierExpression("i")) + )}; + + DefiniteAssignmentAnalysis da = new DefiniteAssignmentAnalysis(loop, CecilLoaderTests.Mscorlib); + da.Analyze("i"); + Assert.AreEqual(0, da.UnassignedVariableUses.Count); + Assert.AreEqual(DefiniteAssignmentStatus.PotentiallyAssigned, da.GetStatusBefore(loop)); + Assert.AreEqual(DefiniteAssignmentStatus.PotentiallyAssigned, da.GetStatusBefore(loop.Initializers.Single())); + Assert.AreEqual(DefiniteAssignmentStatus.DefinitelyAssigned, da.GetStatusAfter(loop.Initializers.Single())); + Assert.AreEqual(DefiniteAssignmentStatus.DefinitelyAssigned, da.GetStatusBeforeLoopCondition(loop)); + Assert.AreEqual(DefiniteAssignmentStatus.DefinitelyAssigned, da.GetStatusBefore(loop.EmbeddedStatement)); + Assert.AreEqual(DefiniteAssignmentStatus.DefinitelyAssigned, da.GetStatusAfter(loop.EmbeddedStatement)); + Assert.AreEqual(DefiniteAssignmentStatus.DefinitelyAssigned, da.GetStatusBefore(loop.Iterators.Single())); + Assert.AreEqual(DefiniteAssignmentStatus.DefinitelyAssigned, da.GetStatusAfter(loop.Iterators.Single())); + Assert.AreEqual(DefiniteAssignmentStatus.DefinitelyAssigned, da.GetStatusAfter(loop)); + + da.Analyze("j"); + Assert.AreEqual(0, da.UnassignedVariableUses.Count); + Assert.AreEqual(DefiniteAssignmentStatus.PotentiallyAssigned, da.GetStatusBefore(loop)); + Assert.AreEqual(DefiniteAssignmentStatus.PotentiallyAssigned, da.GetStatusBefore(loop.Initializers.Single())); + Assert.AreEqual(DefiniteAssignmentStatus.PotentiallyAssigned, da.GetStatusAfter(loop.Initializers.Single())); + Assert.AreEqual(DefiniteAssignmentStatus.PotentiallyAssigned, da.GetStatusBeforeLoopCondition(loop)); + Assert.AreEqual(DefiniteAssignmentStatus.PotentiallyAssigned, da.GetStatusBefore(loop.EmbeddedStatement)); + Assert.AreEqual(DefiniteAssignmentStatus.DefinitelyAssigned, da.GetStatusAfter(loop.EmbeddedStatement)); + Assert.AreEqual(DefiniteAssignmentStatus.DefinitelyAssigned, da.GetStatusBefore(loop.Iterators.Single())); + Assert.AreEqual(DefiniteAssignmentStatus.DefinitelyAssigned, da.GetStatusAfter(loop.Iterators.Single())); + Assert.AreEqual(DefiniteAssignmentStatus.PotentiallyAssigned, da.GetStatusAfter(loop)); + } } } diff --git a/ICSharpCode.NRefactory/CSharp/Analysis/ControlFlow.cs b/ICSharpCode.NRefactory/CSharp/Analysis/ControlFlow.cs index 9a2fa141d5..1660e99b14 100644 --- a/ICSharpCode.NRefactory/CSharp/Analysis/ControlFlow.cs +++ b/ICSharpCode.NRefactory/CSharp/Analysis/ControlFlow.cs @@ -146,7 +146,8 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis public IList BuildControlFlowGraph(Statement statement, ITypeResolveContext context, CancellationToken cancellationToken = default(CancellationToken)) { return BuildControlFlowGraph(statement, new ResolveVisitor( - new CSharpResolver(context, cancellationToken), null, ConstantModeResolveVisitorNavigator.Skip)); + new CSharpResolver(context, cancellationToken), + null, ConstantModeResolveVisitorNavigator.Skip)); } public IList BuildControlFlowGraph(Statement statement, ResolveVisitor resolveVisitor) @@ -221,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) { @@ -243,18 +245,28 @@ 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 #region Constant evaluation + /// + /// Gets/Sets whether to handle only primitive expressions as constants (no complex expressions like "a + b"). + /// + public bool EvaluateOnlyPrimitiveConstants { get; set; } + /// /// Evaluates an expression. /// /// The constant value of the expression; or null if the expression is not a constant. ConstantResolveResult EvaluateConstant(Expression expr) { + if (EvaluateOnlyPrimitiveConstants) { + if (!(expr is PrimitiveExpression || expr is NullReferenceExpression)) + return null; + } return resolveVisitor.Resolve(expr) as ConstantResolveResult; } @@ -264,9 +276,9 @@ 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) { - ConstantResolveResult crr = EvaluateConstant(expr); - if (crr != null) - return crr.ConstantValue as bool?; + ConstantResolveResult rr = EvaluateConstant(expr); + if (rr != null) + return rr.ConstantValue as bool?; else return null; } @@ -328,7 +340,8 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis foreach (Statement stmt in statements) { if (childNode == null) { childNode = builder.CreateStartNode(stmt); - Connect(source, childNode); + if (source != null) + Connect(source, childNode); } Debug.Assert(childNode.NextStatement == stmt); childNode = stmt.AcceptVisitor(this, childNode); @@ -407,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) { @@ -428,6 +441,7 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis throw new NotImplementedException(); } + builder.nodes.Add(end); return end; } @@ -446,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); @@ -464,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); @@ -488,6 +503,8 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis breakTargets.Pop(); continueTargets.Pop(); + builder.nodes.Add(conditionNode); + builder.nodes.Add(end); return end; } @@ -495,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); @@ -525,6 +542,7 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis if (cond != true) Connect(conditionNode, end, ControlFlowEdgeType.ConditionFalse); + builder.nodes.Add(end); return end; } @@ -541,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); @@ -555,7 +573,7 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis continueTargets.Pop(); Connect(conditionNode, end); - + builder.nodes.Add(end); return end; } @@ -591,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); @@ -605,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 e377022398..d4cc6e1c53 100644 --- a/ICSharpCode.NRefactory/CSharp/Analysis/DefiniteAssignmentAnalysis.cs +++ b/ICSharpCode.NRefactory/CSharp/Analysis/DefiniteAssignmentAnalysis.cs @@ -44,22 +44,49 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis /// public class DefiniteAssignmentAnalysis { + 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 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) + { + } public DefiniteAssignmentAnalysis(Statement rootStatement, ITypeResolveContext context, CancellationToken cancellationToken = default(CancellationToken)) - : this(rootStatement, new ResolveVisitor(new CSharpResolver(context, cancellationToken), null, ConstantModeResolveVisitorNavigator.Skip)) + : this(rootStatement, new ResolveVisitor(new CSharpResolver(context ?? MinimalResolveContext.Instance, cancellationToken), + null, ConstantModeResolveVisitorNavigator.Skip)) { } @@ -72,25 +99,57 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis this.resolveVisitor = resolveVisitor; this.cancellationToken = resolveVisitor.CancellationToken; visitor.analysis = this; - ControlFlowGraphBuilder b = new ControlFlowGraphBuilder(); - allNodes.AddRange(b.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(b.BuildControlFlowGraph(ame.Body, resolveVisitor)); - LambdaExpression lambda = descendant as LambdaExpression; - if (lambda != null && lambda.Body is Statement) - allNodes.AddRange(b.BuildControlFlowGraph((Statement)lambda.Body, resolveVisitor)); + if (resolveVisitor.TypeResolveContext is MinimalResolveContext) { + cfgBuilder.EvaluateOnlyPrimitiveConstants = true; } - // 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) endNodeDict.Add(node.PreviousStatement, node); + 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()); + // Verify that we put all nodes into the dictionaries: + Debug.Assert(rootStatement.DescendantsAndSelf.OfType().All(stmt => beginNodeDict.ContainsKey(stmt))); + Debug.Assert(rootStatement.DescendantsAndSelf.OfType().All(stmt => endNodeDict.ContainsKey(stmt))); + + 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); } } @@ -103,21 +162,38 @@ 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) + { + Debug.Assert(beginNodeDict.ContainsKey(start) && endNodeDict.ContainsKey(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]); @@ -128,12 +204,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 conditionNodeDict[statement].NodeStatus; } /// @@ -144,7 +225,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: @@ -162,7 +243,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) @@ -201,11 +282,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: @@ -277,25 +358,26 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis DefiniteAssignmentStatus oldStatus = edgeStatus[edge]; if (oldStatus == newStatus) return; - // Ensure that status can change only in one direction: - // CodeUnreachable -> PotentiallyAssigned -> DefinitelyAssigned - // Going against this direction indicates a bug and could cause infinite loops. - switch (oldStatus) { - case DefiniteAssignmentStatus.PotentiallyAssigned: - if (newStatus != DefiniteAssignmentStatus.DefinitelyAssigned) - throw new InvalidOperationException("Invalid state transition"); - break; - case DefiniteAssignmentStatus.CodeUnreachable: - if (!(newStatus == DefiniteAssignmentStatus.PotentiallyAssigned || newStatus == DefiniteAssignmentStatus.DefinitelyAssigned)) - throw new InvalidOperationException("Invalid state transition"); - break; - case DefiniteAssignmentStatus.DefinitelyAssigned: - throw new InvalidOperationException("Invalid state transition"); - default: - throw new InvalidOperationException("Invalid value for DefiniteAssignmentStatus"); + // Ensure that status can cannot change back to CodeUnreachable after it once was reachable. + // Also, don't ever use AssignedAfter... for statements. + if (newStatus == DefiniteAssignmentStatus.CodeUnreachable + || newStatus == DefiniteAssignmentStatus.AssignedAfterFalseExpression + || newStatus == DefiniteAssignmentStatus.AssignedAfterTrueExpression) + { + throw new InvalidOperationException(); } + // Note that the status can change from DefinitelyAssigned + // back to PotentiallyAssigned as unreachable input edges are + // discovered to be reachable. + edgeStatus[edge] = newStatus; - nodesWithModifiedInput.Enqueue(edge.To); + DefiniteAssignmentNode targetNode = (DefiniteAssignmentNode)edge.To; + if (analyzedRangeStart <= targetNode.Index && targetNode.Index <= analyzedRangeEnd) { + // TODO: potential optimization: visit previously unreachable nodes with higher priority + // (e.g. use Deque and enqueue previously unreachable nodes at the front, but + // other nodes at the end) + nodesWithModifiedInput.Enqueue(targetNode); + } } /// @@ -304,6 +386,10 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis /// The constant value of the expression; or null if the expression is not a constant. ConstantResolveResult EvaluateConstant(Expression expr) { + if (resolveVisitor.TypeResolveContext is MinimalResolveContext) { + if (!(expr is PrimitiveExpression || expr is NullReferenceExpression)) + return null; + } return resolveVisitor.Resolve(expr) as ConstantResolveResult; } @@ -313,9 +399,9 @@ 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) { - ConstantResolveResult crr = EvaluateConstant(expr); - if (crr != null) - return crr.ConstantValue as bool?; + ConstantResolveResult rr = EvaluateConstant(expr); + if (rr != null) + return rr.ConstantValue as bool?; else return null; } @@ -477,11 +563,14 @@ namespace ICSharpCode.NRefactory.CSharp.Analysis { IdentifierExpression ident = left as IdentifierExpression; if (ident != null && ident.Identifier == analysis.variableName) { - right.AcceptVisitor(this, initialStatus); + // right==null is special case when handling 'out' expressions + if (right != null) + right.AcceptVisitor(this, initialStatus); return DefiniteAssignmentStatus.DefinitelyAssigned; } else { DefiniteAssignmentStatus status = left.AcceptVisitor(this, initialStatus); - status = right.AcceptVisitor(this, CleanSpecialValues(status)); + if (right != null) + status = right.AcceptVisitor(this, CleanSpecialValues(status)); return CleanSpecialValues(status); } } @@ -620,10 +709,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; } @@ -631,10 +717,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); } diff --git a/ICSharpCode.NRefactory/CSharp/Analysis/MinimalResolveContext.cs b/ICSharpCode.NRefactory/CSharp/Analysis/MinimalResolveContext.cs new file mode 100644 index 0000000000..d9909d0d4d --- /dev/null +++ b/ICSharpCode.NRefactory/CSharp/Analysis/MinimalResolveContext.cs @@ -0,0 +1,116 @@ +// 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.Collections.ObjectModel; +using System.Linq; + +using ICSharpCode.NRefactory.TypeSystem; +using ICSharpCode.NRefactory.TypeSystem.Implementation; + +namespace ICSharpCode.NRefactory.CSharp.Analysis +{ + /// + /// Resolve context represents the minimal mscorlib required for evaluating constants. + /// + sealed class MinimalResolveContext : IProjectContent, ISynchronizedTypeResolveContext + { + static readonly Lazy instance = new Lazy(() => new MinimalResolveContext()); + + public static MinimalResolveContext Instance { + get { return instance.Value; } + } + + readonly ReadOnlyCollection namespaces = Array.AsReadOnly(new string[] { "System" }); + readonly IAttribute[] assemblyAttributes = new IAttribute[0]; + readonly ITypeDefinition systemObject, systemValueType; + readonly ReadOnlyCollection types; + + private MinimalResolveContext() + { + List types = new List(); + types.Add(systemObject = new DefaultTypeDefinition(this, "System", "Object")); + types.Add(systemValueType = new DefaultTypeDefinition(this, "System", "ValueType") { BaseTypes = { systemObject } }); + types.Add(CreateStruct("System", "Boolean")); + types.Add(CreateStruct("System", "SByte")); + types.Add(CreateStruct("System", "Byte")); + types.Add(CreateStruct("System", "Int16")); + types.Add(CreateStruct("System", "UInt16")); + types.Add(CreateStruct("System", "Int32")); + types.Add(CreateStruct("System", "UInt32")); + types.Add(CreateStruct("System", "Int64")); + types.Add(CreateStruct("System", "UInt64")); + types.Add(CreateStruct("System", "Single")); + types.Add(CreateStruct("System", "Double")); + types.Add(CreateStruct("System", "Decimal")); + types.Add(new DefaultTypeDefinition(this, "System", "String") { BaseTypes = { systemObject } }); + foreach (ITypeDefinition type in types) + type.Freeze(); + this.types = types.AsReadOnly(); + } + + ITypeDefinition CreateStruct(string nameSpace, string name) + { + return new DefaultTypeDefinition(this, nameSpace, name) { + ClassType = ClassType.Struct, + BaseTypes = { systemValueType } + }; + } + + public ITypeDefinition GetClass(string nameSpace, string name, int typeParameterCount, StringComparer nameComparer) + { + foreach (ITypeDefinition type in types) { + if (nameComparer.Equals(type.Name, name) && nameComparer.Equals(type.Namespace, nameSpace) && type.TypeParameterCount == typeParameterCount) + return type; + } + return null; + } + + public IEnumerable GetClasses() + { + return types; + } + + public IEnumerable GetClasses(string nameSpace, StringComparer nameComparer) + { + return types.Where(t => nameComparer.Equals(t.Namespace, nameSpace)); + } + + public IEnumerable GetNamespaces() + { + return namespaces; + } + + public string GetNamespace(string nameSpace, StringComparer nameComparer) + { + foreach (string ns in namespaces) { + if (nameComparer.Equals(ns, nameSpace)) + return ns; + } + return null; + } + + public IList AssemblyAttributes { + get { return assemblyAttributes; } + } + + ICSharpCode.NRefactory.Utils.CacheManager ITypeResolveContext.CacheManager { + get { + // We don't support caching + return null; + } + } + + ISynchronizedTypeResolveContext ITypeResolveContext.Synchronize() + { + // This class is immutable + return this; + } + + void IDisposable.Dispose() + { + // exit from Synchronize() block + } + } +} diff --git a/ICSharpCode.NRefactory/CSharp/Ast/AstNodeCollection.cs b/ICSharpCode.NRefactory/CSharp/Ast/AstNodeCollection.cs index a8c11cb993..98beaeaefb 100644 --- a/ICSharpCode.NRefactory/CSharp/Ast/AstNodeCollection.cs +++ b/ICSharpCode.NRefactory/CSharp/Ast/AstNodeCollection.cs @@ -193,5 +193,15 @@ namespace ICSharpCode.NRefactory.CSharp } return false; } + + public void InsertAfter(T existingItem, T newItem) + { + node.InsertChildAfter(existingItem, newItem, role); + } + + public void InsertBefore(T existingItem, T newItem) + { + node.InsertChildBefore(existingItem, newItem, role); + } } } diff --git a/ICSharpCode.NRefactory/CSharp/Ast/Statements/Statement.cs b/ICSharpCode.NRefactory/CSharp/Ast/Statements/Statement.cs index ef01d54b3a..41cbe67f52 100644 --- a/ICSharpCode.NRefactory/CSharp/Ast/Statements/Statement.cs +++ b/ICSharpCode.NRefactory/CSharp/Ast/Statements/Statement.cs @@ -38,6 +38,38 @@ namespace ICSharpCode.NRefactory.CSharp } #endregion + /// + /// Gets the previous statement within the current block. + /// This is usually equivalent to , but will skip any non-statements (e.g. comments) + /// + public Statement PreviousStatement { + get { + AstNode node = this; + while ((node = node.PrevSibling) != null) { + Statement stmt = node as Statement; + if (stmt != null) + return stmt; + } + return null; + } + } + + /// + /// Gets the next statement within the current block. + /// This is usually equivalent to , but will skip any non-statements (e.g. comments) + /// + public Statement NextStatement { + get { + AstNode node = this; + while ((node = node.NextSibling) != null) { + Statement stmt = node as Statement; + if (stmt != null) + return stmt; + } + return null; + } + } + public new Statement Clone() { return (Statement)base.Clone(); diff --git a/ICSharpCode.NRefactory/CSharp/OutputVisitor/OutputVisitor.cs b/ICSharpCode.NRefactory/CSharp/OutputVisitor/OutputVisitor.cs index da843f39cc..9470286999 100644 --- a/ICSharpCode.NRefactory/CSharp/OutputVisitor/OutputVisitor.cs +++ b/ICSharpCode.NRefactory/CSharp/OutputVisitor/OutputVisitor.cs @@ -428,7 +428,7 @@ namespace ICSharpCode.NRefactory.CSharp if (block != null) VisitBlockStatement(block, null); else - throw new NotImplementedException(); + embeddedStatement.AcceptVisitor(this, null); } void WriteMethodBody(BlockStatement body) diff --git a/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj b/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj index 89db6429e5..a4f8243764 100644 --- a/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj +++ b/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj @@ -63,6 +63,7 @@ + diff --git a/ICSharpCode.NRefactory/TypeSystem/CecilLoader.cs b/ICSharpCode.NRefactory/TypeSystem/CecilLoader.cs index 74cbdb91ce..26d79e4a9b 100644 --- a/ICSharpCode.NRefactory/TypeSystem/CecilLoader.cs +++ b/ICSharpCode.NRefactory/TypeSystem/CecilLoader.cs @@ -35,7 +35,7 @@ namespace ICSharpCode.NRefactory.TypeSystem public bool IncludeInternalMembers { get; set; } /// - /// Gets/Sets the documentation provider that is used to retrive the XML documentation for all members. + /// Gets/Sets the documentation provider that is used to retrieve the XML documentation for all members. /// public IDocumentationProvider DocumentationProvider { get; set; } diff --git a/ICSharpCode.NRefactory/TypeSystem/ITypeResolveContext.cs b/ICSharpCode.NRefactory/TypeSystem/ITypeResolveContext.cs index 3c89b13ade..9cdfb4133d 100644 --- a/ICSharpCode.NRefactory/TypeSystem/ITypeResolveContext.cs +++ b/ICSharpCode.NRefactory/TypeSystem/ITypeResolveContext.cs @@ -44,7 +44,7 @@ namespace ICSharpCode.NRefactory.TypeSystem /// Language-specific rules for how namespace names are compared /// List of classes within that namespace. /// - /// If this method is called within using (pc.Synchronize()), then the returned enumerable is valid + /// If this method is called within using (var spc = pc.Synchronize()), then the returned enumerable is valid /// only until the end of the synchronize block. /// IEnumerable GetClasses(string nameSpace, StringComparer nameComparer); @@ -53,7 +53,7 @@ namespace ICSharpCode.NRefactory.TypeSystem /// Retrieves all namespaces. /// /// - /// If this method is called within using (pc.Synchronize()), then the returned enumerable is valid + /// If this method is called within using (var spc = pc.Synchronize()), then the returned enumerable is valid /// only until the end of the synchronize block. /// IEnumerable GetNamespaces();