// // MultipleEnumerationIssue.cs // // Author: // Mansheng Yang // // Copyright (c) 2012 Mansheng Yang // // 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.Linq; using ICSharpCode.NRefactory.CSharp.Resolver; using ICSharpCode.NRefactory.Semantics; using ICSharpCode.NRefactory.TypeSystem; namespace ICSharpCode.NRefactory.CSharp.Refactoring { [IssueDescription ("Possible mutiple enumeration of IEnuemrable", Description = "Possible multiple enumeration of IEnumerable.", Category = IssueCategories.CodeQualityIssues, Severity = Severity.Warning, IssueMarker = IssueMarker.Underline)] public class MultipleEnumerationIssue : ICodeIssueProvider { public IEnumerable GetIssues (BaseRefactoringContext context) { var unit = context.RootNode as SyntaxTree; if (unit == null) return Enumerable.Empty (); return new GatherVisitor (context, unit).GetIssues (); } class AnalysisStatementCollector : DepthFirstAstVisitor { List statements; AstNode variableDecl; AnalysisStatementCollector (AstNode variableDecl) { this.variableDecl = variableDecl; } IList GetStatements () { if (statements != null) return statements; statements = new List (); var parent = variableDecl.Parent; while (parent != null) { if (parent is BlockStatement || parent is MethodDeclaration || parent is AnonymousMethodExpression || parent is LambdaExpression) { parent.AcceptVisitor (this); if (parent is BlockStatement) statements.Add ((BlockStatement)parent); break; } parent = parent.Parent; } return statements; } public override void VisitMethodDeclaration (MethodDeclaration methodDeclaration) { statements.Add (methodDeclaration.Body); base.VisitMethodDeclaration (methodDeclaration); } public override void VisitAnonymousMethodExpression (AnonymousMethodExpression anonymousMethodExpression) { statements.Add (anonymousMethodExpression.Body); base.VisitAnonymousMethodExpression (anonymousMethodExpression); } public override void VisitLambdaExpression (LambdaExpression lambdaExpression) { var body = lambdaExpression.Body as BlockStatement; if (body != null) statements.Add (body); base.VisitLambdaExpression (lambdaExpression); } public static IList Collect (AstNode variableDecl) { return new AnalysisStatementCollector (variableDecl).GetStatements (); } } class GatherVisitor : GatherVisitorBase { static FindReferences refFinder = new FindReferences (); SyntaxTree unit; HashSet collectedAstNodes; public GatherVisitor (BaseRefactoringContext ctx, SyntaxTree unit) : base (ctx) { this.unit = unit; this.collectedAstNodes = new HashSet (); } void AddIssue (AstNode node) { if (collectedAstNodes.Add (node)) AddIssue (node, ctx.TranslateString ("Possible multiple enumeration of IEnumerable")); } void AddIssues (IEnumerable nodes) { foreach (var node in nodes) AddIssue (node); } public override void VisitParameterDeclaration (ParameterDeclaration parameterDeclaration) { base.VisitParameterDeclaration (parameterDeclaration); var resolveResult = ctx.Resolve (parameterDeclaration) as LocalResolveResult; CollectIssues (parameterDeclaration, resolveResult); } public override void VisitVariableInitializer (VariableInitializer variableInitializer) { base.VisitVariableInitializer (variableInitializer); var resolveResult = ctx.Resolve (variableInitializer) as LocalResolveResult; CollectIssues (variableInitializer, resolveResult); } static bool IsAssignment (AstNode node) { var assignment = node.Parent as AssignmentExpression; if (assignment != null) return assignment.Left == node; var direction = node.Parent as DirectionExpression; if (direction != null) return direction.FieldDirection == FieldDirection.Out && direction.Expression == node; return false; } bool IsEnumeration (AstNode node) { var foreachStatement = node.Parent as ForeachStatement; if (foreachStatement != null && foreachStatement.InExpression == node) { return true; } var memberRef = node.Parent as MemberReferenceExpression; if (memberRef != null && memberRef.Target == node) { var invocation = memberRef.Parent as InvocationExpression; if (invocation == null || invocation.Target != memberRef) return false; var methodGroup = ctx.Resolve (memberRef) as MethodGroupResolveResult; if (methodGroup == null) return false; var method = methodGroup.Methods.FirstOrDefault (); if (method != null) { var declaringTypeDef = method.DeclaringTypeDefinition; if (declaringTypeDef != null && declaringTypeDef.KnownTypeCode == KnownTypeCode.Object) return false; } return true; } return false; } HashSet references; HashSet refStatements; HashSet lambdaExpressions; HashSet visitedNodes; HashSet collectedNodes; Dictionary nodeDegree; // number of enumerations a node can reach void FindReferences (AstNode variableDecl, IVariable variable) { references = new HashSet (); refStatements = new HashSet (); lambdaExpressions = new HashSet (); refFinder.FindLocalReferences (variable, ctx.UnresolvedFile, unit, ctx.Compilation, (astNode, resolveResult) => { if (astNode == variableDecl) return; var parent = astNode.Parent; while (!(parent == null || parent is Statement || parent is LambdaExpression)) parent = parent.Parent; if (parent == null) return; // lambda expression with expression body, should be analyzed separately var expr = parent as LambdaExpression; if (expr != null) { if (IsAssignment (astNode) || IsEnumeration (astNode)) { references.Add (astNode); lambdaExpressions.Add (expr); } return; } var statement = (Statement)parent; if (IsAssignment (astNode) || IsEnumeration (astNode)) { references.Add (astNode); refStatements.Add (statement); } }, ctx.CancellationToken); } void CollectIssues (AstNode variableDecl, LocalResolveResult resolveResult) { if (resolveResult == null) return; var type = resolveResult.Type; var typeDef = type.GetDefinition (); if (typeDef == null || (typeDef.KnownTypeCode != KnownTypeCode.IEnumerable && typeDef.KnownTypeCode != KnownTypeCode.IEnumerableOfT)) return; FindReferences (variableDecl, resolveResult.Variable); var statements = AnalysisStatementCollector.Collect (variableDecl); foreach (var statement in statements) { var vrNode = VariableReferenceGraphBuilder.Build (statement, references, refStatements, ctx); FindMultipleEnumeration (vrNode); } foreach (var lambda in lambdaExpressions) { var vrNode = VariableReferenceGraphBuilder.Build (references, ctx.Resolver, (Expression)lambda.Body); FindMultipleEnumeration (vrNode); } } /// /// split references in the specified node into sub nodes according to the value they uses /// /// node to split /// list of sub nodes static IList SplitNode (VariableReferenceNode node) { var subNodes = new List (); // find indices of all assignments in node and use them to split references var assignmentIndices = new List { -1 }; for (int i = 0; i < node.References.Count; i++) { if (IsAssignment (node.References [i])) assignmentIndices.Add (i); } assignmentIndices.Add (node.References.Count); for (int i = 0; i < assignmentIndices.Count - 1; i++) { var index1 = assignmentIndices [i]; var index2 = assignmentIndices [i + 1]; if (index1 + 1 >= index2) continue; var subNode = new VariableReferenceNode (); for (int refIndex = index1 + 1; refIndex < index2; refIndex++) subNode.References.Add (node.References [refIndex]); subNodes.Add (subNode); } if (subNodes.Count == 0) subNodes.Add (new VariableReferenceNode ()); var firstNode = subNodes [0]; foreach (var prevNode in node.PreviousNodes) { prevNode.NextNodes.Remove (node); // connect two nodes if the first ref is not an assignment if (firstNode.References.FirstOrDefault () == node.References.FirstOrDefault ()) prevNode.NextNodes.Add (firstNode); } var lastNode = subNodes [subNodes.Count - 1]; foreach (var nextNode in node.NextNodes) { nextNode.PreviousNodes.Remove (node); lastNode.AddNextNode (nextNode); } return subNodes; } /// /// convert a variable reference graph starting from the specified node to an assignment usage graph, /// in which nodes are connect if and only if they contains references using the same assigned value /// /// starting node of the variable reference graph /// /// list of VariableReferenceNode, each of which is a starting node of a sub-graph in which references all /// use the same assigned value /// static IEnumerable GetAssignmentUsageGraph (VariableReferenceNode startNode) { var graph = new List (); var visited = new HashSet (); var stack = new Stack (); stack.Push (startNode); while (stack.Count > 0) { var node = stack.Pop (); if (!visited.Add (node)) continue; var nodes = SplitNode (node); graph.AddRange (nodes); foreach (var addedNode in nodes) visited.Add (addedNode); foreach (var nextNode in nodes.Last ().NextNodes) stack.Push (nextNode); } return graph; } void FindMultipleEnumeration (VariableReferenceNode startNode) { var vrg = GetAssignmentUsageGraph (startNode); visitedNodes = new HashSet (); collectedNodes = new HashSet (); // degree of a node is the number of references that can be reached by the node nodeDegree = new Dictionary (); foreach (var node in vrg) { if (node.References.Count == 0 || !visitedNodes.Add (node)) continue; ProcessNode (node); if (nodeDegree [node] > 1) collectedNodes.Add (node); } foreach (var node in collectedNodes) AddIssues (node.References); } void ProcessNode (VariableReferenceNode node) { var degree = nodeDegree [node] = 0; foreach (var nextNode in node.NextNodes) { collectedNodes.Add (nextNode); if (visitedNodes.Add (nextNode)) ProcessNode (nextNode); degree = Math.Max (degree, nodeDegree [nextNode]); } nodeDegree [node] = degree + node.References.Count; } } } }