1 | // Copyright (c) 2010-2013 AlphaSierraPapa for the SharpDevelop Team |
---|
2 | // |
---|
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this |
---|
4 | // software and associated documentation files (the "Software"), to deal in the Software |
---|
5 | // without restriction, including without limitation the rights to use, copy, modify, merge, |
---|
6 | // publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons |
---|
7 | // to whom the Software is furnished to do so, subject to the following conditions: |
---|
8 | // |
---|
9 | // The above copyright notice and this permission notice shall be included in all copies or |
---|
10 | // substantial portions of the Software. |
---|
11 | // |
---|
12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, |
---|
13 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR |
---|
14 | // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE |
---|
15 | // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR |
---|
16 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
---|
17 | // DEALINGS IN THE SOFTWARE. |
---|
18 | |
---|
19 | using System; |
---|
20 | using System.Collections.Generic; |
---|
21 | using System.Linq; |
---|
22 | using System.Threading; |
---|
23 | using ICSharpCode.NRefactory.CSharp.Resolver; |
---|
24 | using ICSharpCode.NRefactory.CSharp.TypeSystem; |
---|
25 | using ICSharpCode.NRefactory.Semantics; |
---|
26 | using ICSharpCode.NRefactory.CSharp; |
---|
27 | |
---|
28 | namespace ICSharpCode.NRefactory.CSharp.Analysis |
---|
29 | { |
---|
30 | /// <summary> |
---|
31 | /// Statement reachability analysis. |
---|
32 | /// </summary> |
---|
33 | public sealed class ReachabilityAnalysis |
---|
34 | { |
---|
35 | HashSet<Statement> reachableStatements = new HashSet<Statement>(); |
---|
36 | HashSet<Statement> reachableEndPoints = new HashSet<Statement>(); |
---|
37 | HashSet<ControlFlowNode> visitedNodes = new HashSet<ControlFlowNode>(); |
---|
38 | Stack<ControlFlowNode> stack = new Stack<ControlFlowNode>(); |
---|
39 | RecursiveDetectorVisitor recursiveDetectorVisitor = null; |
---|
40 | |
---|
41 | private ReachabilityAnalysis() {} |
---|
42 | |
---|
43 | public static ReachabilityAnalysis Create(Statement statement, CSharpAstResolver resolver = null, RecursiveDetectorVisitor recursiveDetectorVisitor = null, CancellationToken cancellationToken = default(CancellationToken)) |
---|
44 | { |
---|
45 | var cfgBuilder = new ControlFlowGraphBuilder(); |
---|
46 | var cfg = cfgBuilder.BuildControlFlowGraph(statement, resolver, cancellationToken); |
---|
47 | return Create(cfg, recursiveDetectorVisitor, cancellationToken); |
---|
48 | } |
---|
49 | |
---|
50 | internal static ReachabilityAnalysis Create(Statement statement, Func<AstNode, CancellationToken, ResolveResult> resolver, CSharpTypeResolveContext typeResolveContext, CancellationToken cancellationToken) |
---|
51 | { |
---|
52 | var cfgBuilder = new ControlFlowGraphBuilder(); |
---|
53 | var cfg = cfgBuilder.BuildControlFlowGraph(statement, resolver, typeResolveContext, cancellationToken); |
---|
54 | return Create(cfg, null, cancellationToken); |
---|
55 | } |
---|
56 | |
---|
57 | public static ReachabilityAnalysis Create(IList<ControlFlowNode> controlFlowGraph, RecursiveDetectorVisitor recursiveDetectorVisitor = null, CancellationToken cancellationToken = default(CancellationToken)) |
---|
58 | { |
---|
59 | if (controlFlowGraph == null) |
---|
60 | throw new ArgumentNullException("controlFlowGraph"); |
---|
61 | ReachabilityAnalysis ra = new ReachabilityAnalysis(); |
---|
62 | ra.recursiveDetectorVisitor = recursiveDetectorVisitor; |
---|
63 | // Analysing a null node can result in an empty control flow graph |
---|
64 | if (controlFlowGraph.Count > 0) { |
---|
65 | ra.stack.Push(controlFlowGraph[0]); |
---|
66 | while (ra.stack.Count > 0) { |
---|
67 | cancellationToken.ThrowIfCancellationRequested(); |
---|
68 | ra.MarkReachable(ra.stack.Pop()); |
---|
69 | } |
---|
70 | } |
---|
71 | ra.stack = null; |
---|
72 | ra.visitedNodes = null; |
---|
73 | return ra; |
---|
74 | } |
---|
75 | |
---|
76 | void MarkReachable(ControlFlowNode node) |
---|
77 | { |
---|
78 | if (node.PreviousStatement != null) { |
---|
79 | if (node.PreviousStatement is LabelStatement) { |
---|
80 | reachableStatements.Add(node.PreviousStatement); |
---|
81 | } |
---|
82 | reachableEndPoints.Add(node.PreviousStatement); |
---|
83 | } |
---|
84 | if (node.NextStatement != null) { |
---|
85 | reachableStatements.Add(node.NextStatement); |
---|
86 | if (IsRecursive(node.NextStatement)) { |
---|
87 | return; |
---|
88 | } |
---|
89 | } |
---|
90 | foreach (var edge in node.Outgoing) { |
---|
91 | if (visitedNodes.Add(edge.To)) |
---|
92 | stack.Push(edge.To); |
---|
93 | } |
---|
94 | } |
---|
95 | |
---|
96 | bool IsRecursive(Statement statement) |
---|
97 | { |
---|
98 | return recursiveDetectorVisitor != null && statement.AcceptVisitor(recursiveDetectorVisitor); |
---|
99 | } |
---|
100 | |
---|
101 | public IEnumerable<Statement> ReachableStatements { |
---|
102 | get { return reachableStatements; } |
---|
103 | } |
---|
104 | |
---|
105 | public bool IsReachable(Statement statement) |
---|
106 | { |
---|
107 | return reachableStatements.Contains(statement); |
---|
108 | } |
---|
109 | |
---|
110 | public bool IsEndpointReachable(Statement statement) |
---|
111 | { |
---|
112 | return reachableEndPoints.Contains(statement); |
---|
113 | } |
---|
114 | |
---|
115 | public class RecursiveDetectorVisitor : DepthFirstAstVisitor<bool> |
---|
116 | { |
---|
117 | public override bool VisitConditionalExpression(ConditionalExpression conditionalExpression) |
---|
118 | { |
---|
119 | if (conditionalExpression.Condition.AcceptVisitor(this)) |
---|
120 | return true; |
---|
121 | |
---|
122 | if (!conditionalExpression.TrueExpression.AcceptVisitor(this)) |
---|
123 | return false; |
---|
124 | |
---|
125 | return conditionalExpression.FalseExpression.AcceptVisitor(this); |
---|
126 | } |
---|
127 | |
---|
128 | public override bool VisitBinaryOperatorExpression(BinaryOperatorExpression binaryOperatorExpression) |
---|
129 | { |
---|
130 | if (binaryOperatorExpression.Operator == BinaryOperatorType.NullCoalescing) { |
---|
131 | return binaryOperatorExpression.Left.AcceptVisitor(this); |
---|
132 | } |
---|
133 | return base.VisitBinaryOperatorExpression(binaryOperatorExpression); |
---|
134 | } |
---|
135 | |
---|
136 | public override bool VisitIfElseStatement(IfElseStatement ifElseStatement) |
---|
137 | { |
---|
138 | if (ifElseStatement.Condition.AcceptVisitor(this)) |
---|
139 | return true; |
---|
140 | |
---|
141 | if (!ifElseStatement.TrueStatement.AcceptVisitor(this)) |
---|
142 | return false; |
---|
143 | |
---|
144 | //No need to worry about null ast nodes, since AcceptVisitor will just |
---|
145 | //return false in those cases |
---|
146 | return ifElseStatement.FalseStatement.AcceptVisitor(this); |
---|
147 | } |
---|
148 | |
---|
149 | public override bool VisitForeachStatement(ForeachStatement foreachStatement) |
---|
150 | { |
---|
151 | //Even if the body is always recursive, the function may stop if the collection |
---|
152 | // is empty. |
---|
153 | return foreachStatement.InExpression.AcceptVisitor(this); |
---|
154 | } |
---|
155 | |
---|
156 | public override bool VisitForStatement(ForStatement forStatement) |
---|
157 | { |
---|
158 | if (forStatement.Initializers.Any(initializer => initializer.AcceptVisitor(this))) |
---|
159 | return true; |
---|
160 | |
---|
161 | return forStatement.Condition.AcceptVisitor(this); |
---|
162 | } |
---|
163 | |
---|
164 | public override bool VisitSwitchStatement(SwitchStatement switchStatement) |
---|
165 | { |
---|
166 | if (switchStatement.Expression.AcceptVisitor(this)) { |
---|
167 | return true; |
---|
168 | } |
---|
169 | |
---|
170 | bool foundDefault = false; |
---|
171 | foreach (var section in switchStatement.SwitchSections) { |
---|
172 | foundDefault = foundDefault || section.CaseLabels.Any(label => label.Expression.IsNull); |
---|
173 | if (!section.AcceptVisitor(this)) |
---|
174 | return false; |
---|
175 | } |
---|
176 | |
---|
177 | return foundDefault; |
---|
178 | } |
---|
179 | |
---|
180 | public override bool VisitBlockStatement(BlockStatement blockStatement) |
---|
181 | { |
---|
182 | //If the block has a recursive statement, then that statement will be visited |
---|
183 | //individually by the CFG construction algorithm later. |
---|
184 | return false; |
---|
185 | } |
---|
186 | |
---|
187 | protected override bool VisitChildren(AstNode node) |
---|
188 | { |
---|
189 | return VisitNodeList(node.Children); |
---|
190 | } |
---|
191 | |
---|
192 | bool VisitNodeList(IEnumerable<AstNode> nodes) { |
---|
193 | return nodes.Any(node => node.AcceptVisitor(this)); |
---|
194 | } |
---|
195 | |
---|
196 | public override bool VisitQueryExpression(QueryExpression queryExpression) |
---|
197 | { |
---|
198 | //We only care about the first from clause because: |
---|
199 | //in "from x in Method() select x", Method() might be recursive |
---|
200 | //but in "from x in Bar() from y in Method() select x + y", even if Method() is recursive |
---|
201 | //Bar might still be empty. |
---|
202 | var queryFromClause = queryExpression.Clauses.OfType<QueryFromClause>().FirstOrDefault(); |
---|
203 | if (queryFromClause == null) |
---|
204 | return true; |
---|
205 | return queryFromClause.AcceptVisitor(this); |
---|
206 | } |
---|
207 | } |
---|
208 | } |
---|
209 | } |
---|