1 | // |
---|
2 | // NullValueAnalysis.cs |
---|
3 | // |
---|
4 | // Author: |
---|
5 | // LuÃs Reis <luiscubal@gmail.com> |
---|
6 | // |
---|
7 | // Copyright (c) 2013 LuÃs Reis |
---|
8 | // |
---|
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy |
---|
10 | // of this software and associated documentation files (the "Software"), to deal |
---|
11 | // in the Software without restriction, including without limitation the rights |
---|
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
---|
13 | // copies of the Software, and to permit persons to whom the Software is |
---|
14 | // furnished to do so, subject to the following conditions: |
---|
15 | // |
---|
16 | // The above copyright notice and this permission notice shall be included in |
---|
17 | // all copies or substantial portions of the Software. |
---|
18 | // |
---|
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
---|
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
---|
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
---|
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
---|
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
---|
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
---|
25 | // THE SOFTWARE. |
---|
26 | |
---|
27 | using System; |
---|
28 | using System.Collections; |
---|
29 | using System.Collections.Generic; |
---|
30 | using System.Diagnostics; |
---|
31 | using System.Linq; |
---|
32 | using System.Threading; |
---|
33 | using System.Text; |
---|
34 | using ICSharpCode.NRefactory.CSharp.Resolver; |
---|
35 | using ICSharpCode.NRefactory.Semantics; |
---|
36 | using ICSharpCode.NRefactory.TypeSystem; |
---|
37 | using ICSharpCode.NRefactory.CSharp.Refactoring; |
---|
38 | using ICSharpCode.NRefactory.PatternMatching; |
---|
39 | using ICSharpCode.NRefactory.CSharp; |
---|
40 | using ICSharpCode.NRefactory.Utils; |
---|
41 | |
---|
42 | namespace ICSharpCode.NRefactory.CSharp.Analysis |
---|
43 | { |
---|
44 | public class NullValueAnalysis |
---|
45 | { |
---|
46 | sealed class VariableStatusInfo : IEquatable<VariableStatusInfo>, IEnumerable<KeyValuePair<string, NullValueStatus>> |
---|
47 | { |
---|
48 | readonly Dictionary<string, NullValueStatus> VariableStatus = new Dictionary<string, NullValueStatus>(); |
---|
49 | |
---|
50 | public NullValueStatus this[string name] |
---|
51 | { |
---|
52 | get { |
---|
53 | NullValueStatus status; |
---|
54 | if (VariableStatus.TryGetValue(name, out status)) { |
---|
55 | return status; |
---|
56 | } |
---|
57 | return NullValueStatus.UnreachableOrInexistent; |
---|
58 | } |
---|
59 | set { |
---|
60 | if (value == NullValueStatus.UnreachableOrInexistent) { |
---|
61 | VariableStatus.Remove(name); |
---|
62 | } else { |
---|
63 | VariableStatus [name] = value; |
---|
64 | } |
---|
65 | } |
---|
66 | } |
---|
67 | |
---|
68 | /// <summary> |
---|
69 | /// Modifies the variable state to consider a new incoming path |
---|
70 | /// </summary> |
---|
71 | /// <returns><c>true</c>, if the state has changed, <c>false</c> otherwise.</returns> |
---|
72 | /// <param name="incomingState">The variable state of the incoming path</param> |
---|
73 | public bool ReceiveIncoming(VariableStatusInfo incomingState) |
---|
74 | { |
---|
75 | bool changed = false; |
---|
76 | var listOfVariables = VariableStatus.Keys.Concat(incomingState.VariableStatus.Keys).ToList(); |
---|
77 | foreach (string variable in listOfVariables) |
---|
78 | { |
---|
79 | var newValue = CombineStatus(this [variable], incomingState [variable]); |
---|
80 | if (this [variable] != newValue) { |
---|
81 | this [variable] = newValue; |
---|
82 | changed = true; |
---|
83 | } |
---|
84 | } |
---|
85 | |
---|
86 | return changed; |
---|
87 | } |
---|
88 | |
---|
89 | public static NullValueStatus CombineStatus(NullValueStatus oldValue, NullValueStatus incomingValue) |
---|
90 | { |
---|
91 | if (oldValue == NullValueStatus.Error || incomingValue == NullValueStatus.Error) |
---|
92 | return NullValueStatus.Error; |
---|
93 | |
---|
94 | if (oldValue == NullValueStatus.UnreachableOrInexistent || |
---|
95 | oldValue == NullValueStatus.Unassigned) |
---|
96 | return incomingValue; |
---|
97 | |
---|
98 | if (incomingValue == NullValueStatus.Unassigned) { |
---|
99 | return NullValueStatus.Unassigned; |
---|
100 | } |
---|
101 | |
---|
102 | if (oldValue == NullValueStatus.CapturedUnknown || incomingValue == NullValueStatus.CapturedUnknown) { |
---|
103 | //TODO: Check if this is right |
---|
104 | return NullValueStatus.CapturedUnknown; |
---|
105 | } |
---|
106 | |
---|
107 | if (oldValue == NullValueStatus.Unknown) { |
---|
108 | return NullValueStatus.Unknown; |
---|
109 | } |
---|
110 | |
---|
111 | if (oldValue == NullValueStatus.DefinitelyNull) { |
---|
112 | return incomingValue == NullValueStatus.DefinitelyNull ? |
---|
113 | NullValueStatus.DefinitelyNull : NullValueStatus.PotentiallyNull; |
---|
114 | } |
---|
115 | |
---|
116 | if (oldValue == NullValueStatus.DefinitelyNotNull) { |
---|
117 | if (incomingValue == NullValueStatus.Unknown) |
---|
118 | return NullValueStatus.Unknown; |
---|
119 | if (incomingValue == NullValueStatus.DefinitelyNotNull) |
---|
120 | return NullValueStatus.DefinitelyNotNull; |
---|
121 | return NullValueStatus.PotentiallyNull; |
---|
122 | } |
---|
123 | |
---|
124 | Debug.Assert(oldValue == NullValueStatus.PotentiallyNull); |
---|
125 | return NullValueStatus.PotentiallyNull; |
---|
126 | } |
---|
127 | |
---|
128 | public bool HasVariable(string variable) { |
---|
129 | return VariableStatus.ContainsKey(variable); |
---|
130 | } |
---|
131 | |
---|
132 | public VariableStatusInfo Clone() { |
---|
133 | var clone = new VariableStatusInfo(); |
---|
134 | foreach (var item in VariableStatus) { |
---|
135 | clone.VariableStatus.Add(item.Key, item.Value); |
---|
136 | } |
---|
137 | return clone; |
---|
138 | } |
---|
139 | |
---|
140 | public override bool Equals(object obj) |
---|
141 | { |
---|
142 | return Equals(obj as VariableStatusInfo); |
---|
143 | } |
---|
144 | |
---|
145 | public bool Equals(VariableStatusInfo obj) |
---|
146 | { |
---|
147 | if (obj == null) { |
---|
148 | return false; |
---|
149 | } |
---|
150 | |
---|
151 | if (VariableStatus.Count != obj.VariableStatus.Count) |
---|
152 | return false; |
---|
153 | |
---|
154 | return VariableStatus.All(item => item.Value == obj[item.Key]); |
---|
155 | } |
---|
156 | |
---|
157 | public override int GetHashCode() |
---|
158 | { |
---|
159 | //STUB |
---|
160 | return VariableStatus.Count.GetHashCode(); |
---|
161 | } |
---|
162 | |
---|
163 | public static bool operator ==(VariableStatusInfo obj1, VariableStatusInfo obj2) { |
---|
164 | return object.ReferenceEquals(obj1, null) ? |
---|
165 | object.ReferenceEquals(obj2, null) : obj1.Equals(obj2); |
---|
166 | } |
---|
167 | |
---|
168 | public static bool operator !=(VariableStatusInfo obj1, VariableStatusInfo obj2) { |
---|
169 | return !(obj1 == obj2); |
---|
170 | } |
---|
171 | |
---|
172 | public IEnumerator<KeyValuePair<string, NullValueStatus>> GetEnumerator() |
---|
173 | { |
---|
174 | return VariableStatus.GetEnumerator(); |
---|
175 | } |
---|
176 | |
---|
177 | IEnumerator IEnumerable.GetEnumerator() |
---|
178 | { |
---|
179 | return GetEnumerator(); |
---|
180 | } |
---|
181 | |
---|
182 | public override string ToString() |
---|
183 | { |
---|
184 | var builder = new StringBuilder("["); |
---|
185 | foreach (var item in this) { |
---|
186 | builder.Append(item.Key); |
---|
187 | builder.Append("="); |
---|
188 | builder.Append(item.Value); |
---|
189 | } |
---|
190 | builder.Append("]"); |
---|
191 | return builder.ToString(); |
---|
192 | } |
---|
193 | } |
---|
194 | |
---|
195 | sealed class NullAnalysisNode : ControlFlowNode |
---|
196 | { |
---|
197 | public readonly VariableStatusInfo VariableState = new VariableStatusInfo(); |
---|
198 | public bool Visited { get; private set; } |
---|
199 | |
---|
200 | public NullAnalysisNode(Statement previousStatement, Statement nextStatement, ControlFlowNodeType type) |
---|
201 | : base(previousStatement, nextStatement, type) |
---|
202 | { |
---|
203 | } |
---|
204 | |
---|
205 | public bool ReceiveIncoming(VariableStatusInfo incomingState) |
---|
206 | { |
---|
207 | bool changed = VariableState.ReceiveIncoming(incomingState); |
---|
208 | if (!Visited) { |
---|
209 | Visited = true; |
---|
210 | return true; |
---|
211 | } |
---|
212 | return changed; |
---|
213 | } |
---|
214 | } |
---|
215 | |
---|
216 | sealed class NullAnalysisGraphBuilder : ControlFlowGraphBuilder |
---|
217 | { |
---|
218 | protected override ControlFlowNode CreateNode(Statement previousStatement, Statement nextStatement, ControlFlowNodeType type) |
---|
219 | { |
---|
220 | return new NullAnalysisNode(previousStatement, nextStatement, type); |
---|
221 | } |
---|
222 | } |
---|
223 | |
---|
224 | class PendingNode : IEquatable<PendingNode> { |
---|
225 | internal readonly NullAnalysisNode nodeToVisit; |
---|
226 | internal readonly VariableStatusInfo statusInfo; |
---|
227 | internal readonly ComparableList<NullAnalysisNode> pendingTryFinallyNodes; |
---|
228 | internal readonly NullAnalysisNode nodeAfterFinally; |
---|
229 | |
---|
230 | internal PendingNode(NullAnalysisNode nodeToVisit, VariableStatusInfo statusInfo) |
---|
231 | : this(nodeToVisit, statusInfo, new ComparableList<NullAnalysisNode>(), null) |
---|
232 | { |
---|
233 | } |
---|
234 | |
---|
235 | public PendingNode(NullAnalysisNode nodeToVisit, VariableStatusInfo statusInfo, ComparableList<NullAnalysisNode> pendingFinallyNodes, NullAnalysisNode nodeAfterFinally) |
---|
236 | { |
---|
237 | this.nodeToVisit = nodeToVisit; |
---|
238 | this.statusInfo = statusInfo; |
---|
239 | this.pendingTryFinallyNodes = pendingFinallyNodes; |
---|
240 | this.nodeAfterFinally = nodeAfterFinally; |
---|
241 | } |
---|
242 | |
---|
243 | public override bool Equals(object obj) |
---|
244 | { |
---|
245 | return Equals(obj as PendingNode); |
---|
246 | } |
---|
247 | |
---|
248 | public bool Equals(PendingNode obj) { |
---|
249 | if (obj == null) return false; |
---|
250 | |
---|
251 | if (nodeToVisit != obj.nodeToVisit) return false; |
---|
252 | if (statusInfo != obj.statusInfo) return false; |
---|
253 | if (pendingTryFinallyNodes != obj.pendingTryFinallyNodes) return false; |
---|
254 | if (nodeAfterFinally != obj.nodeAfterFinally) return false; |
---|
255 | |
---|
256 | return true; |
---|
257 | } |
---|
258 | |
---|
259 | public override int GetHashCode() |
---|
260 | { |
---|
261 | return nodeToVisit.GetHashCode() ^ |
---|
262 | statusInfo.GetHashCode() ^ |
---|
263 | pendingTryFinallyNodes.GetHashCode() ^ |
---|
264 | (nodeAfterFinally == null ? 0 : nodeAfterFinally.GetHashCode()); |
---|
265 | } |
---|
266 | } |
---|
267 | |
---|
268 | readonly BaseRefactoringContext context; |
---|
269 | readonly NullAnalysisVisitor visitor; |
---|
270 | List<NullAnalysisNode> allNodes; |
---|
271 | readonly HashSet<PendingNode> nodesToVisit = new HashSet<PendingNode>(); |
---|
272 | Dictionary<Statement, NullAnalysisNode> nodeBeforeStatementDict; |
---|
273 | Dictionary<Statement, NullAnalysisNode> nodeAfterStatementDict; |
---|
274 | readonly Dictionary<Expression, NullValueStatus> expressionResult = new Dictionary<Expression, NullValueStatus>(); |
---|
275 | |
---|
276 | public NullValueAnalysis(BaseRefactoringContext context, MethodDeclaration methodDeclaration, CancellationToken cancellationToken) |
---|
277 | : this(context, methodDeclaration.Body, methodDeclaration.Parameters, cancellationToken) |
---|
278 | { |
---|
279 | } |
---|
280 | |
---|
281 | readonly IEnumerable<ParameterDeclaration> parameters; |
---|
282 | readonly Statement rootStatement; |
---|
283 | |
---|
284 | readonly CancellationToken cancellationToken; |
---|
285 | |
---|
286 | public NullValueAnalysis(BaseRefactoringContext context, Statement rootStatement, IEnumerable<ParameterDeclaration> parameters, CancellationToken cancellationToken) |
---|
287 | { |
---|
288 | if (rootStatement == null) |
---|
289 | throw new ArgumentNullException("rootStatement"); |
---|
290 | if (context == null) |
---|
291 | throw new ArgumentNullException("context"); |
---|
292 | |
---|
293 | this.context = context; |
---|
294 | this.rootStatement = rootStatement; |
---|
295 | this.parameters = parameters; |
---|
296 | this.visitor = new NullAnalysisVisitor(this); |
---|
297 | this.cancellationToken = cancellationToken; |
---|
298 | } |
---|
299 | |
---|
300 | /// <summary> |
---|
301 | /// Sets the local variable value. |
---|
302 | /// This method does not change anything if identifier does not refer to a local variable. |
---|
303 | /// Do not use this in variable declarations since resolving the variable won't work yet. |
---|
304 | /// </summary> |
---|
305 | /// <returns><c>true</c>, if local variable value was set, <c>false</c> otherwise.</returns> |
---|
306 | /// <param name="data">The variable status data to change.</param> |
---|
307 | /// <param name="identifierNode">The identifier to set.</param> |
---|
308 | /// <param name="identifierName">The name of the identifier to set.</param> |
---|
309 | /// <param name="value">The value to set the identifier.</param> |
---|
310 | bool SetLocalVariableValue (VariableStatusInfo data, AstNode identifierNode, string identifierName, NullValueStatus value) { |
---|
311 | var resolveResult = context.Resolve(identifierNode); |
---|
312 | if (resolveResult is LocalResolveResult) { |
---|
313 | if (data [identifierName] != NullValueStatus.CapturedUnknown) { |
---|
314 | data [identifierName] = value; |
---|
315 | |
---|
316 | return true; |
---|
317 | } |
---|
318 | } |
---|
319 | return false; |
---|
320 | } |
---|
321 | |
---|
322 | bool SetLocalVariableValue (VariableStatusInfo data, IdentifierExpression identifierExpression, NullValueStatus value) { |
---|
323 | return SetLocalVariableValue(data, identifierExpression, identifierExpression.Identifier, value); |
---|
324 | } |
---|
325 | |
---|
326 | bool SetLocalVariableValue (VariableStatusInfo data, Identifier identifier, NullValueStatus value) { |
---|
327 | return SetLocalVariableValue(data, identifier, identifier.Name, value); |
---|
328 | } |
---|
329 | |
---|
330 | void SetupNode(NullAnalysisNode node) |
---|
331 | { |
---|
332 | foreach (var parameter in parameters) { |
---|
333 | var resolveResult = context.Resolve(parameter.Type); |
---|
334 | node.VariableState[parameter.Name] = GetInitialVariableStatus(resolveResult); |
---|
335 | } |
---|
336 | |
---|
337 | nodesToVisit.Add(new PendingNode(node, node.VariableState)); |
---|
338 | } |
---|
339 | |
---|
340 | static bool IsTypeNullable(IType type) |
---|
341 | { |
---|
342 | return type.IsReferenceType == true || type.FullName == "System.Nullable"; |
---|
343 | } |
---|
344 | |
---|
345 | public bool IsParametersAreUninitialized { |
---|
346 | get; |
---|
347 | set; |
---|
348 | } |
---|
349 | |
---|
350 | NullValueStatus GetInitialVariableStatus(ResolveResult resolveResult) |
---|
351 | { |
---|
352 | var typeResolveResult = resolveResult as TypeResolveResult; |
---|
353 | if (typeResolveResult == null) { |
---|
354 | return NullValueStatus.Error; |
---|
355 | } |
---|
356 | var type = typeResolveResult.Type; |
---|
357 | if (type.IsReferenceType == null) { |
---|
358 | return NullValueStatus.Error; |
---|
359 | } |
---|
360 | if (!IsParametersAreUninitialized) |
---|
361 | return NullValueStatus.DefinitelyNotNull; |
---|
362 | return IsTypeNullable(type) ? NullValueStatus.PotentiallyNull : NullValueStatus.DefinitelyNotNull; |
---|
363 | } |
---|
364 | |
---|
365 | public void Analyze() |
---|
366 | { |
---|
367 | var cfgBuilder = new NullAnalysisGraphBuilder(); |
---|
368 | allNodes = cfgBuilder.BuildControlFlowGraph(rootStatement, cancellationToken).Cast<NullAnalysisNode>().ToList(); |
---|
369 | nodeBeforeStatementDict = allNodes.Where(node => node.Type == ControlFlowNodeType.StartNode || node.Type == ControlFlowNodeType.BetweenStatements) |
---|
370 | .ToDictionary(node => node.NextStatement); |
---|
371 | nodeAfterStatementDict = allNodes.Where(node => node.Type == ControlFlowNodeType.BetweenStatements || node.Type == ControlFlowNodeType.EndNode) |
---|
372 | .ToDictionary(node => node.PreviousStatement); |
---|
373 | |
---|
374 | foreach (var node in allNodes) { |
---|
375 | if (node.Type == ControlFlowNodeType.StartNode && node.NextStatement == rootStatement) { |
---|
376 | Debug.Assert(!nodesToVisit.Any()); |
---|
377 | |
---|
378 | SetupNode(node); |
---|
379 | } |
---|
380 | } |
---|
381 | |
---|
382 | while (nodesToVisit.Any()) { |
---|
383 | var nodeToVisit = nodesToVisit.First(); |
---|
384 | nodesToVisit.Remove(nodeToVisit); |
---|
385 | |
---|
386 | Visit(nodeToVisit); |
---|
387 | } |
---|
388 | } |
---|
389 | |
---|
390 | int visits = 0; |
---|
391 | |
---|
392 | public int NodeVisits |
---|
393 | { |
---|
394 | get { |
---|
395 | return visits; |
---|
396 | } |
---|
397 | } |
---|
398 | |
---|
399 | void Visit(PendingNode nodeInfo) |
---|
400 | { |
---|
401 | cancellationToken.ThrowIfCancellationRequested(); |
---|
402 | |
---|
403 | var node = nodeInfo.nodeToVisit; |
---|
404 | var statusInfo = nodeInfo.statusInfo; |
---|
405 | |
---|
406 | visits++; |
---|
407 | if (visits > 100) { |
---|
408 | //Visiting way too often, let's enter fast mode |
---|
409 | //Fast mode is slighly less accurate but visits each node less times |
---|
410 | nodesToVisit.RemoveWhere(candidate => candidate.nodeToVisit == nodeInfo.nodeToVisit && |
---|
411 | candidate.pendingTryFinallyNodes.Equals(nodeInfo.pendingTryFinallyNodes) && |
---|
412 | candidate.nodeAfterFinally == nodeInfo.nodeAfterFinally); |
---|
413 | statusInfo = node.VariableState; |
---|
414 | } |
---|
415 | |
---|
416 | var nextStatement = node.NextStatement; |
---|
417 | VariableStatusInfo outgoingStatusInfo = statusInfo; |
---|
418 | VisitorResult result = null; |
---|
419 | |
---|
420 | if (nextStatement != null && (!(nextStatement is DoWhileStatement) || node.Type == ControlFlowNodeType.LoopCondition)) { |
---|
421 | result = nextStatement.AcceptVisitor(visitor, statusInfo); |
---|
422 | if (result == null) { |
---|
423 | Console.WriteLine("Failure in {0}", nextStatement); |
---|
424 | throw new InvalidOperationException(); |
---|
425 | } |
---|
426 | |
---|
427 | outgoingStatusInfo = result.Variables; |
---|
428 | } |
---|
429 | |
---|
430 | if ((result == null || !result.ThrowsException) && node.Outgoing.Any()) { |
---|
431 | var tryFinallyStatement = nextStatement as TryCatchStatement; |
---|
432 | |
---|
433 | foreach (var outgoingEdge in node.Outgoing) { |
---|
434 | VariableStatusInfo edgeInfo; |
---|
435 | edgeInfo = outgoingStatusInfo.Clone(); |
---|
436 | |
---|
437 | if (node.Type == ControlFlowNodeType.EndNode) { |
---|
438 | var previousBlock = node.PreviousStatement as BlockStatement; |
---|
439 | if (previousBlock != null) { |
---|
440 | //We're leaving a block statement. |
---|
441 | //As such, we'll remove the variables that were declared *in* the loop |
---|
442 | //This helps GetVariableStatusAfter/BeforeStatement be more accurate |
---|
443 | //and prevents some redundant revisiting. |
---|
444 | |
---|
445 | foreach (var variableInitializer in previousBlock.Statements |
---|
446 | .OfType<VariableDeclarationStatement>() |
---|
447 | .SelectMany(declaration => declaration.Variables)) { |
---|
448 | |
---|
449 | edgeInfo [variableInitializer.Name] = NullValueStatus.UnreachableOrInexistent; |
---|
450 | } |
---|
451 | } |
---|
452 | } |
---|
453 | |
---|
454 | if (tryFinallyStatement != null) { |
---|
455 | //With the exception of try statements, this needs special handling: |
---|
456 | //we'll set all changed variables to Unknown or CapturedUnknown |
---|
457 | if (outgoingEdge.To.NextStatement == tryFinallyStatement.FinallyBlock) { |
---|
458 | foreach (var identifierExpression in tryFinallyStatement.TryBlock.Descendants.OfType<IdentifierExpression>()) { |
---|
459 | //TODO: Investigate CaptureUnknown |
---|
460 | SetLocalVariableValue(edgeInfo, identifierExpression, NullValueStatus.Unknown); |
---|
461 | } |
---|
462 | } else { |
---|
463 | var clause = tryFinallyStatement.CatchClauses |
---|
464 | .FirstOrDefault(candidateClause => candidateClause.Body == outgoingEdge.To.NextStatement); |
---|
465 | |
---|
466 | if (clause != null) { |
---|
467 | SetLocalVariableValue(edgeInfo, clause.VariableNameToken, NullValueStatus.DefinitelyNotNull); |
---|
468 | |
---|
469 | foreach (var identifierExpression in tryFinallyStatement.TryBlock.Descendants.OfType<IdentifierExpression>()) { |
---|
470 | //TODO: Investigate CaptureUnknown |
---|
471 | SetLocalVariableValue(edgeInfo, identifierExpression, NullValueStatus.Unknown); |
---|
472 | } |
---|
473 | } |
---|
474 | } |
---|
475 | } |
---|
476 | |
---|
477 | if (result != null) { |
---|
478 | switch (outgoingEdge.Type) { |
---|
479 | case ControlFlowEdgeType.ConditionTrue: |
---|
480 | if (result.KnownBoolResult == false) { |
---|
481 | //No need to explore this path -- expression is known to be false |
---|
482 | continue; |
---|
483 | } |
---|
484 | edgeInfo = result.TruePathVariables; |
---|
485 | break; |
---|
486 | case ControlFlowEdgeType.ConditionFalse: |
---|
487 | if (result.KnownBoolResult == true) { |
---|
488 | //No need to explore this path -- expression is known to be true |
---|
489 | continue; |
---|
490 | } |
---|
491 | edgeInfo = result.FalsePathVariables; |
---|
492 | break; |
---|
493 | } |
---|
494 | } |
---|
495 | |
---|
496 | if (outgoingEdge.IsLeavingTryFinally) { |
---|
497 | var nodeAfterFinally = (NullAnalysisNode)outgoingEdge.To; |
---|
498 | var finallyNodes = outgoingEdge.TryFinallyStatements.Select(tryFinally => nodeBeforeStatementDict [tryFinally.FinallyBlock]).ToList(); |
---|
499 | var nextNode = finallyNodes.First(); |
---|
500 | var remainingFinallyNodes = new ComparableList<NullAnalysisNode>(finallyNodes.Skip(1)); |
---|
501 | //We have to visit the node even if ReceiveIncoming returns false |
---|
502 | //since the finallyNodes/nodeAfterFinally might be different even if the values of variables are the same -- and they need to be visited either way! |
---|
503 | //TODO 1: Is there any point in visiting the finally statement here? |
---|
504 | //TODO 2: Do we need the ReceiveIncoming at all? |
---|
505 | nextNode.ReceiveIncoming(edgeInfo); |
---|
506 | nodesToVisit.Add(new PendingNode(nextNode, edgeInfo, remainingFinallyNodes, nodeAfterFinally)); |
---|
507 | } else { |
---|
508 | var outgoingNode = (NullAnalysisNode)outgoingEdge.To; |
---|
509 | if (outgoingNode.ReceiveIncoming(edgeInfo)) { |
---|
510 | nodesToVisit.Add(new PendingNode(outgoingNode, edgeInfo)); |
---|
511 | } |
---|
512 | } |
---|
513 | } |
---|
514 | } else { |
---|
515 | //We found a return/throw/yield break or some other termination node |
---|
516 | var finallyBlockStarts = nodeInfo.pendingTryFinallyNodes; |
---|
517 | var nodeAfterFinally = nodeInfo.nodeAfterFinally; |
---|
518 | |
---|
519 | if (finallyBlockStarts.Any()) { |
---|
520 | var nextNode = finallyBlockStarts.First(); |
---|
521 | if (nextNode.ReceiveIncoming(outgoingStatusInfo)) |
---|
522 | nodesToVisit.Add(new PendingNode(nextNode, outgoingStatusInfo, new ComparableList<NullAnalysisNode>(finallyBlockStarts.Skip(1)), nodeInfo.nodeAfterFinally)); |
---|
523 | } else if (nodeAfterFinally != null && nodeAfterFinally.ReceiveIncoming(outgoingStatusInfo)) { |
---|
524 | nodesToVisit.Add(new PendingNode(nodeAfterFinally, outgoingStatusInfo)); |
---|
525 | } else { |
---|
526 | //Maybe we finished a try/catch/finally statement the "normal" way (no direct jumps) |
---|
527 | //so let's check that case |
---|
528 | var statement = node.PreviousStatement ?? node.NextStatement; |
---|
529 | Debug.Assert(statement != null); |
---|
530 | var parent = statement.GetParent<Statement>(); |
---|
531 | var parentTryCatch = parent as TryCatchStatement; |
---|
532 | if (parentTryCatch != null) { |
---|
533 | var nextNode = nodeAfterStatementDict [parentTryCatch]; |
---|
534 | if (nextNode.ReceiveIncoming(outgoingStatusInfo)) { |
---|
535 | nodesToVisit.Add(new PendingNode(nextNode, outgoingStatusInfo)); |
---|
536 | } |
---|
537 | } |
---|
538 | } |
---|
539 | } |
---|
540 | } |
---|
541 | |
---|
542 | public NullValueStatus GetExpressionResult(Expression expr) |
---|
543 | { |
---|
544 | if (expr == null) |
---|
545 | throw new ArgumentNullException("expr"); |
---|
546 | |
---|
547 | NullValueStatus info; |
---|
548 | if (expressionResult.TryGetValue(expr, out info)) { |
---|
549 | return info; |
---|
550 | } |
---|
551 | |
---|
552 | return NullValueStatus.UnreachableOrInexistent; |
---|
553 | } |
---|
554 | |
---|
555 | public NullValueStatus GetVariableStatusBeforeStatement(Statement stmt, string variableName) |
---|
556 | { |
---|
557 | if (stmt == null) |
---|
558 | throw new ArgumentNullException("stmt"); |
---|
559 | if (variableName == null) |
---|
560 | throw new ArgumentNullException("variableName"); |
---|
561 | |
---|
562 | NullAnalysisNode node; |
---|
563 | if (nodeBeforeStatementDict.TryGetValue(stmt, out node)) { |
---|
564 | return node.VariableState [variableName]; |
---|
565 | } |
---|
566 | |
---|
567 | return NullValueStatus.UnreachableOrInexistent; |
---|
568 | } |
---|
569 | |
---|
570 | public NullValueStatus GetVariableStatusAfterStatement(Statement stmt, string variableName) |
---|
571 | { |
---|
572 | if (stmt == null) |
---|
573 | throw new ArgumentNullException("stmt"); |
---|
574 | if (variableName == null) |
---|
575 | throw new ArgumentNullException("variableName"); |
---|
576 | |
---|
577 | NullAnalysisNode node; |
---|
578 | if (nodeAfterStatementDict.TryGetValue(stmt, out node)) { |
---|
579 | return node.VariableState [variableName]; |
---|
580 | } |
---|
581 | |
---|
582 | return NullValueStatus.UnreachableOrInexistent; |
---|
583 | } |
---|
584 | |
---|
585 | class ConditionalBranchInfo |
---|
586 | { |
---|
587 | /// <summary> |
---|
588 | /// True if the variable is null for the true path, false if it is false for the true path. |
---|
589 | /// </summary> |
---|
590 | public Dictionary<string, bool> TrueResultVariableNullStates = new Dictionary<string, bool>(); |
---|
591 | /// <summary> |
---|
592 | /// True if the variable is null for the false path, false if it is false for the false path. |
---|
593 | /// </summary> |
---|
594 | public Dictionary<string, bool> FalseResultVariableNullStates = new Dictionary<string, bool>(); |
---|
595 | } |
---|
596 | |
---|
597 | class VisitorResult |
---|
598 | { |
---|
599 | /// <summary> |
---|
600 | /// Indicates the return value of the expression. |
---|
601 | /// </summary> |
---|
602 | /// <remarks> |
---|
603 | /// Only applicable for expressions. |
---|
604 | /// </remarks> |
---|
605 | public NullValueStatus NullableReturnResult; |
---|
606 | |
---|
607 | /// <summary> |
---|
608 | /// Indicates the value of each item in an array or linq query. |
---|
609 | /// </summary> |
---|
610 | public NullValueStatus EnumeratedValueResult; |
---|
611 | |
---|
612 | /// <summary> |
---|
613 | /// Information that indicates the restrictions to add |
---|
614 | /// when branching. |
---|
615 | /// </summary> |
---|
616 | /// <remarks> |
---|
617 | /// Used in if/else statements, conditional expressions and |
---|
618 | /// while statements. |
---|
619 | /// </remarks> |
---|
620 | public ConditionalBranchInfo ConditionalBranchInfo; |
---|
621 | |
---|
622 | /// <summary> |
---|
623 | /// The state of the variables after the expression is executed. |
---|
624 | /// </summary> |
---|
625 | public VariableStatusInfo Variables; |
---|
626 | |
---|
627 | /// <summary> |
---|
628 | /// The expression is known to be invalid and trigger an error |
---|
629 | /// (e.g. a NullReferenceException) |
---|
630 | /// </summary> |
---|
631 | public bool ThrowsException; |
---|
632 | |
---|
633 | /// <summary> |
---|
634 | /// The known bool result of an expression. |
---|
635 | /// </summary> |
---|
636 | public bool? KnownBoolResult; |
---|
637 | |
---|
638 | public static VisitorResult ForEnumeratedValue(VariableStatusInfo variables, NullValueStatus itemValues) |
---|
639 | { |
---|
640 | var result = new VisitorResult(); |
---|
641 | result.NullableReturnResult = NullValueStatus.DefinitelyNotNull; |
---|
642 | result.EnumeratedValueResult = itemValues; |
---|
643 | result.Variables = variables.Clone(); |
---|
644 | return result; |
---|
645 | } |
---|
646 | |
---|
647 | public static VisitorResult ForValue(VariableStatusInfo variables, NullValueStatus returnValue) |
---|
648 | { |
---|
649 | var result = new VisitorResult(); |
---|
650 | result.NullableReturnResult = returnValue; |
---|
651 | result.Variables = variables.Clone(); |
---|
652 | return result; |
---|
653 | } |
---|
654 | |
---|
655 | public static VisitorResult ForBoolValue(VariableStatusInfo variables, bool newValue) |
---|
656 | { |
---|
657 | var result = new VisitorResult(); |
---|
658 | result.NullableReturnResult = NullValueStatus.DefinitelyNotNull; //Bool expressions are never null |
---|
659 | result.KnownBoolResult = newValue; |
---|
660 | result.Variables = variables.Clone(); |
---|
661 | return result; |
---|
662 | } |
---|
663 | |
---|
664 | public static VisitorResult ForException(VariableStatusInfo variables) { |
---|
665 | var result = new VisitorResult(); |
---|
666 | result.NullableReturnResult = NullValueStatus.UnreachableOrInexistent; |
---|
667 | result.ThrowsException = true; |
---|
668 | result.Variables = variables.Clone(); |
---|
669 | return result; |
---|
670 | } |
---|
671 | |
---|
672 | public VisitorResult Negated { |
---|
673 | get { |
---|
674 | var result = new VisitorResult(); |
---|
675 | if (NullableReturnResult.IsDefiniteValue()) { |
---|
676 | result.NullableReturnResult = NullableReturnResult == NullValueStatus.DefinitelyNull |
---|
677 | ? NullValueStatus.DefinitelyNotNull : NullValueStatus.DefinitelyNull; |
---|
678 | } else { |
---|
679 | result.NullableReturnResult = NullableReturnResult; |
---|
680 | } |
---|
681 | result.Variables = Variables.Clone(); |
---|
682 | result.KnownBoolResult = !KnownBoolResult; |
---|
683 | if (ConditionalBranchInfo != null) { |
---|
684 | result.ConditionalBranchInfo = new ConditionalBranchInfo(); |
---|
685 | foreach (var item in ConditionalBranchInfo.TrueResultVariableNullStates) { |
---|
686 | result.ConditionalBranchInfo.FalseResultVariableNullStates [item.Key] = item.Value; |
---|
687 | } |
---|
688 | foreach (var item in ConditionalBranchInfo.FalseResultVariableNullStates) { |
---|
689 | result.ConditionalBranchInfo.TrueResultVariableNullStates [item.Key] = item.Value; |
---|
690 | } |
---|
691 | } |
---|
692 | return result; |
---|
693 | } |
---|
694 | } |
---|
695 | |
---|
696 | public VariableStatusInfo TruePathVariables { |
---|
697 | get { |
---|
698 | var variables = Variables.Clone(); |
---|
699 | if (ConditionalBranchInfo != null) { |
---|
700 | foreach (var item in ConditionalBranchInfo.TrueResultVariableNullStates) { |
---|
701 | variables [item.Key] = item.Value ? NullValueStatus.DefinitelyNull : NullValueStatus.DefinitelyNotNull; |
---|
702 | } |
---|
703 | } |
---|
704 | return variables; |
---|
705 | } |
---|
706 | } |
---|
707 | |
---|
708 | public VariableStatusInfo FalsePathVariables { |
---|
709 | get { |
---|
710 | var variables = Variables.Clone(); |
---|
711 | if (ConditionalBranchInfo != null) { |
---|
712 | foreach (var item in ConditionalBranchInfo.FalseResultVariableNullStates) { |
---|
713 | variables [item.Key] = item.Value ? NullValueStatus.DefinitelyNull : NullValueStatus.DefinitelyNotNull; |
---|
714 | } |
---|
715 | } |
---|
716 | return variables; |
---|
717 | } |
---|
718 | } |
---|
719 | |
---|
720 | public static VisitorResult AndOperation(VisitorResult tentativeLeftResult, VisitorResult tentativeRightResult) |
---|
721 | { |
---|
722 | var result = new VisitorResult(); |
---|
723 | result.KnownBoolResult = tentativeLeftResult.KnownBoolResult & tentativeRightResult.KnownBoolResult; |
---|
724 | |
---|
725 | var trueTruePath = tentativeRightResult.TruePathVariables; |
---|
726 | var trueFalsePath = tentativeRightResult.FalsePathVariables; |
---|
727 | var falsePath = tentativeLeftResult.FalsePathVariables; |
---|
728 | |
---|
729 | var trueVariables = trueTruePath; |
---|
730 | |
---|
731 | var falseVariables = trueFalsePath.Clone(); |
---|
732 | falseVariables.ReceiveIncoming(falsePath); |
---|
733 | result.Variables = trueVariables.Clone(); |
---|
734 | result.Variables.ReceiveIncoming(falseVariables); |
---|
735 | |
---|
736 | result.ConditionalBranchInfo = new ConditionalBranchInfo(); |
---|
737 | |
---|
738 | foreach (var variable in trueVariables) { |
---|
739 | if (!variable.Value.IsDefiniteValue()) |
---|
740 | continue; |
---|
741 | |
---|
742 | string variableName = variable.Key; |
---|
743 | |
---|
744 | if (variable.Value != result.Variables[variableName]) { |
---|
745 | bool isNull = variable.Value == NullValueStatus.DefinitelyNull; |
---|
746 | result.ConditionalBranchInfo.TrueResultVariableNullStates.Add(variableName, isNull); |
---|
747 | } |
---|
748 | } |
---|
749 | |
---|
750 | foreach (var variable in falseVariables) { |
---|
751 | if (!variable.Value.IsDefiniteValue()) |
---|
752 | continue; |
---|
753 | |
---|
754 | string variableName = variable.Key; |
---|
755 | |
---|
756 | if (variable.Value != result.Variables [variableName]) { |
---|
757 | bool isNull = variable.Value == NullValueStatus.DefinitelyNull; |
---|
758 | result.ConditionalBranchInfo.FalseResultVariableNullStates.Add(variableName, isNull); |
---|
759 | } |
---|
760 | } |
---|
761 | |
---|
762 | return result; |
---|
763 | } |
---|
764 | |
---|
765 | public static VisitorResult OrOperation(VisitorResult tentativeLeftResult, VisitorResult tentativeRightResult) |
---|
766 | { |
---|
767 | return VisitorResult.AndOperation(tentativeLeftResult.Negated, tentativeRightResult.Negated).Negated; |
---|
768 | } |
---|
769 | } |
---|
770 | |
---|
771 | class NullAnalysisVisitor : DepthFirstAstVisitor<VariableStatusInfo, VisitorResult> |
---|
772 | { |
---|
773 | NullValueAnalysis analysis; |
---|
774 | |
---|
775 | public NullAnalysisVisitor(NullValueAnalysis analysis) { |
---|
776 | this.analysis = analysis; |
---|
777 | } |
---|
778 | |
---|
779 | protected override VisitorResult VisitChildren(AstNode node, VariableStatusInfo data) |
---|
780 | { |
---|
781 | Debug.Fail("Missing override for " + node.GetType().Name); |
---|
782 | return VisitorResult.ForValue(data, NullValueStatus.Unknown); |
---|
783 | } |
---|
784 | |
---|
785 | public override VisitorResult VisitNullNode(AstNode nullNode, VariableStatusInfo data) |
---|
786 | { |
---|
787 | // can occur due to syntax errors |
---|
788 | return VisitorResult.ForValue(data, NullValueStatus.Unknown); |
---|
789 | } |
---|
790 | |
---|
791 | public override VisitorResult VisitEmptyStatement(EmptyStatement emptyStatement, VariableStatusInfo data) |
---|
792 | { |
---|
793 | return VisitorResult.ForValue(data, NullValueStatus.Unknown); |
---|
794 | } |
---|
795 | |
---|
796 | public override VisitorResult VisitBlockStatement(BlockStatement blockStatement, VariableStatusInfo data) |
---|
797 | { |
---|
798 | //We'll visit the child statements later (we'll visit each one directly from the CFG) |
---|
799 | //As such this is mostly a dummy node. |
---|
800 | return new VisitorResult { Variables = data }; |
---|
801 | } |
---|
802 | |
---|
803 | public override VisitorResult VisitVariableDeclarationStatement(VariableDeclarationStatement variableDeclarationStatement, VariableStatusInfo data) |
---|
804 | { |
---|
805 | foreach (var variable in variableDeclarationStatement.Variables) { |
---|
806 | var result = variable.AcceptVisitor(this, data); |
---|
807 | if (result.ThrowsException) |
---|
808 | return result; |
---|
809 | data = result.Variables; |
---|
810 | } |
---|
811 | |
---|
812 | return VisitorResult.ForValue(data, NullValueStatus.Unknown); |
---|
813 | } |
---|
814 | |
---|
815 | public override VisitorResult VisitVariableInitializer(VariableInitializer variableInitializer, VariableStatusInfo data) |
---|
816 | { |
---|
817 | if (variableInitializer.Initializer.IsNull) { |
---|
818 | data = data.Clone(); |
---|
819 | data[variableInitializer.Name] = NullValueStatus.Unassigned; |
---|
820 | } else { |
---|
821 | var result = variableInitializer.Initializer.AcceptVisitor(this, data); |
---|
822 | if (result.ThrowsException) |
---|
823 | return result; |
---|
824 | data = result.Variables.Clone(); |
---|
825 | data[variableInitializer.Name] = result.NullableReturnResult; |
---|
826 | } |
---|
827 | |
---|
828 | return VisitorResult.ForValue(data, data [variableInitializer.Name]); |
---|
829 | } |
---|
830 | |
---|
831 | public override VisitorResult VisitIfElseStatement(IfElseStatement ifElseStatement, VariableStatusInfo data) |
---|
832 | { |
---|
833 | //We'll visit the true/false statements later (directly from the CFG) |
---|
834 | return ifElseStatement.Condition.AcceptVisitor(this, data); |
---|
835 | } |
---|
836 | |
---|
837 | public override VisitorResult VisitWhileStatement(WhileStatement whileStatement, VariableStatusInfo data) |
---|
838 | { |
---|
839 | return whileStatement.Condition.AcceptVisitor(this, data); |
---|
840 | } |
---|
841 | |
---|
842 | public override VisitorResult VisitDoWhileStatement(DoWhileStatement doWhileStatement, VariableStatusInfo data) |
---|
843 | { |
---|
844 | return doWhileStatement.Condition.AcceptVisitor(this, data); |
---|
845 | } |
---|
846 | |
---|
847 | public override VisitorResult VisitForStatement(ForStatement forStatement, VariableStatusInfo data) |
---|
848 | { |
---|
849 | //The initializers, the embedded statement and the iterators aren't visited here |
---|
850 | //because they have their own CFG nodes. |
---|
851 | if (forStatement.Condition.IsNull) |
---|
852 | return VisitorResult.ForValue(data, NullValueStatus.Unknown); |
---|
853 | return forStatement.Condition.AcceptVisitor(this, data); |
---|
854 | } |
---|
855 | |
---|
856 | public override VisitorResult VisitForeachStatement(ForeachStatement foreachStatement, VariableStatusInfo data) |
---|
857 | { |
---|
858 | var newVariable = foreachStatement.VariableNameToken; |
---|
859 | var inExpressionResult = foreachStatement.InExpression.AcceptVisitor(this, data); |
---|
860 | if (inExpressionResult.ThrowsException) |
---|
861 | return inExpressionResult; |
---|
862 | |
---|
863 | var newData = inExpressionResult.Variables.Clone(); |
---|
864 | |
---|
865 | var resolveResult = analysis.context.Resolve(foreachStatement.VariableNameToken) as LocalResolveResult; |
---|
866 | if (resolveResult != null) { |
---|
867 | //C# 5.0 changed the meaning of foreach so that each iteration declares a new variable |
---|
868 | //as such, the variable is "uncaptured" only for C# >= 5.0 |
---|
869 | if (analysis.context.Supports(new Version(5, 0)) || data[newVariable.Name] != NullValueStatus.CapturedUnknown) { |
---|
870 | newData[newVariable.Name] = NullValueAnalysis.IsTypeNullable(resolveResult.Type) ? inExpressionResult.EnumeratedValueResult : NullValueStatus.DefinitelyNotNull; |
---|
871 | } |
---|
872 | } |
---|
873 | |
---|
874 | return VisitorResult.ForValue(newData, NullValueStatus.Unknown); |
---|
875 | } |
---|
876 | |
---|
877 | public override VisitorResult VisitUsingStatement(UsingStatement usingStatement, VariableStatusInfo data) |
---|
878 | { |
---|
879 | return usingStatement.ResourceAcquisition.AcceptVisitor(this, data); |
---|
880 | } |
---|
881 | |
---|
882 | public override VisitorResult VisitFixedStatement(FixedStatement fixedStatement, VariableStatusInfo data) |
---|
883 | { |
---|
884 | foreach (var variable in fixedStatement.Variables) { |
---|
885 | var result = variable.AcceptVisitor(this, data); |
---|
886 | if (result.ThrowsException) |
---|
887 | return result; |
---|
888 | data = result.Variables; |
---|
889 | } |
---|
890 | |
---|
891 | return VisitorResult.ForValue(data, NullValueStatus.Unknown); |
---|
892 | } |
---|
893 | |
---|
894 | public override VisitorResult VisitSwitchStatement(SwitchStatement switchStatement, VariableStatusInfo data) |
---|
895 | { |
---|
896 | //We could do better than this, but it would require special handling outside the visitor |
---|
897 | //so for now, for simplicity, we'll just take the easy way |
---|
898 | |
---|
899 | var tentativeResult = switchStatement.Expression.AcceptVisitor(this, data); |
---|
900 | if (tentativeResult.ThrowsException) { |
---|
901 | return tentativeResult; |
---|
902 | } |
---|
903 | |
---|
904 | foreach (var section in switchStatement.SwitchSections) { |
---|
905 | //No need to check for ThrowsException, since it will always be false (see VisitSwitchSection) |
---|
906 | section.AcceptVisitor(this, tentativeResult.Variables); |
---|
907 | } |
---|
908 | |
---|
909 | return VisitorResult.ForValue(tentativeResult.Variables, NullValueStatus.Unknown); |
---|
910 | } |
---|
911 | |
---|
912 | public override VisitorResult VisitSwitchSection(SwitchSection switchSection, VariableStatusInfo data) |
---|
913 | { |
---|
914 | return VisitorResult.ForValue(data, NullValueStatus.Unknown); |
---|
915 | } |
---|
916 | |
---|
917 | public override VisitorResult VisitExpressionStatement(ExpressionStatement expressionStatement, VariableStatusInfo data) |
---|
918 | { |
---|
919 | return expressionStatement.Expression.AcceptVisitor(this, data); |
---|
920 | } |
---|
921 | |
---|
922 | public override VisitorResult VisitReturnStatement(ReturnStatement returnStatement, VariableStatusInfo data) |
---|
923 | { |
---|
924 | if (returnStatement.Expression.IsNull) |
---|
925 | return VisitorResult.ForValue(data, NullValueStatus.Unknown); |
---|
926 | return returnStatement.Expression.AcceptVisitor(this, data); |
---|
927 | } |
---|
928 | |
---|
929 | public override VisitorResult VisitTryCatchStatement(TryCatchStatement tryCatchStatement, VariableStatusInfo data) |
---|
930 | { |
---|
931 | //The needs special treatment in the analyser itself |
---|
932 | return VisitorResult.ForValue(data, NullValueStatus.Unknown); |
---|
933 | } |
---|
934 | |
---|
935 | public override VisitorResult VisitBreakStatement(BreakStatement breakStatement, VariableStatusInfo data) |
---|
936 | { |
---|
937 | return VisitorResult.ForValue(data, NullValueStatus.Unknown); |
---|
938 | } |
---|
939 | |
---|
940 | public override VisitorResult VisitContinueStatement(ContinueStatement continueStatement, VariableStatusInfo data) |
---|
941 | { |
---|
942 | return VisitorResult.ForValue(data, NullValueStatus.Unknown); |
---|
943 | } |
---|
944 | |
---|
945 | public override VisitorResult VisitGotoStatement(GotoStatement gotoStatement, VariableStatusInfo data) |
---|
946 | { |
---|
947 | return VisitorResult.ForValue(data, NullValueStatus.Unknown); |
---|
948 | } |
---|
949 | |
---|
950 | public override VisitorResult VisitGotoCaseStatement(GotoCaseStatement gotoCaseStatement, VariableStatusInfo data) |
---|
951 | { |
---|
952 | return VisitorResult.ForValue(data, NullValueStatus.Unknown); |
---|
953 | } |
---|
954 | |
---|
955 | public override VisitorResult VisitGotoDefaultStatement(GotoDefaultStatement gotoDefaultStatement, VariableStatusInfo data) |
---|
956 | { |
---|
957 | return VisitorResult.ForValue(data, NullValueStatus.Unknown); |
---|
958 | } |
---|
959 | |
---|
960 | public override VisitorResult VisitLabelStatement(LabelStatement labelStatement, VariableStatusInfo data) |
---|
961 | { |
---|
962 | return VisitorResult.ForValue(data, NullValueStatus.Unknown); |
---|
963 | } |
---|
964 | |
---|
965 | public override VisitorResult VisitUnsafeStatement(UnsafeStatement unsafeStatement, VariableStatusInfo data) |
---|
966 | { |
---|
967 | return VisitorResult.ForValue(data, NullValueStatus.Unknown); |
---|
968 | } |
---|
969 | |
---|
970 | public override VisitorResult VisitLockStatement(LockStatement lockStatement, VariableStatusInfo data) |
---|
971 | { |
---|
972 | var expressionResult = lockStatement.Expression.AcceptVisitor(this, data); |
---|
973 | if (expressionResult.ThrowsException) |
---|
974 | return expressionResult; |
---|
975 | |
---|
976 | if (expressionResult.NullableReturnResult == NullValueStatus.DefinitelyNull) { |
---|
977 | return VisitorResult.ForException(expressionResult.Variables); |
---|
978 | } |
---|
979 | |
---|
980 | var identifier = CSharpUtil.GetInnerMostExpression(lockStatement.Expression) as IdentifierExpression; |
---|
981 | if (identifier != null) { |
---|
982 | var identifierValue = expressionResult.Variables [identifier.Identifier]; |
---|
983 | if (identifierValue != NullValueStatus.CapturedUnknown) { |
---|
984 | var newVariables = expressionResult.Variables.Clone(); |
---|
985 | analysis.SetLocalVariableValue(newVariables, identifier, NullValueStatus.DefinitelyNotNull); |
---|
986 | |
---|
987 | return VisitorResult.ForValue(newVariables, NullValueStatus.Unknown); |
---|
988 | } |
---|
989 | } |
---|
990 | |
---|
991 | return VisitorResult.ForValue(expressionResult.Variables, NullValueStatus.Unknown); |
---|
992 | } |
---|
993 | |
---|
994 | public override VisitorResult VisitThrowStatement(ThrowStatement throwStatement, VariableStatusInfo data) |
---|
995 | { |
---|
996 | if (throwStatement.Expression.IsNull) |
---|
997 | return VisitorResult.ForValue(data, NullValueStatus.DefinitelyNotNull); |
---|
998 | return throwStatement.Expression.AcceptVisitor(this, data); |
---|
999 | } |
---|
1000 | |
---|
1001 | public override VisitorResult VisitYieldBreakStatement(YieldBreakStatement yieldBreakStatement, VariableStatusInfo data) |
---|
1002 | { |
---|
1003 | return VisitorResult.ForValue(data, NullValueStatus.Unknown); |
---|
1004 | } |
---|
1005 | |
---|
1006 | public override VisitorResult VisitYieldReturnStatement(YieldReturnStatement yieldReturnStatement, VariableStatusInfo data) |
---|
1007 | { |
---|
1008 | return yieldReturnStatement.Expression.AcceptVisitor(this, data); |
---|
1009 | } |
---|
1010 | |
---|
1011 | public override VisitorResult VisitCheckedStatement(CheckedStatement checkedStatement, VariableStatusInfo data) |
---|
1012 | { |
---|
1013 | return VisitorResult.ForValue(data, NullValueStatus.Unknown); |
---|
1014 | } |
---|
1015 | |
---|
1016 | public override VisitorResult VisitUncheckedStatement(UncheckedStatement uncheckedStatement, VariableStatusInfo data) |
---|
1017 | { |
---|
1018 | return VisitorResult.ForValue(data, NullValueStatus.Unknown); |
---|
1019 | } |
---|
1020 | |
---|
1021 | void RegisterExpressionResult(Expression expression, NullValueStatus expressionResult) |
---|
1022 | { |
---|
1023 | NullValueStatus oldStatus; |
---|
1024 | if (analysis.expressionResult.TryGetValue(expression, out oldStatus)) { |
---|
1025 | analysis.expressionResult[expression] = VariableStatusInfo.CombineStatus(analysis.expressionResult[expression], expressionResult); |
---|
1026 | } |
---|
1027 | else { |
---|
1028 | analysis.expressionResult[expression] = expressionResult; |
---|
1029 | } |
---|
1030 | } |
---|
1031 | |
---|
1032 | VisitorResult HandleExpressionResult(Expression expression, VariableStatusInfo dataAfterExpression, NullValueStatus expressionResult) { |
---|
1033 | RegisterExpressionResult(expression, expressionResult); |
---|
1034 | |
---|
1035 | return VisitorResult.ForValue(dataAfterExpression, expressionResult); |
---|
1036 | } |
---|
1037 | |
---|
1038 | VisitorResult HandleExpressionResult(Expression expression, VariableStatusInfo dataAfterExpression, bool expressionResult) { |
---|
1039 | RegisterExpressionResult(expression, NullValueStatus.DefinitelyNotNull); |
---|
1040 | |
---|
1041 | return VisitorResult.ForBoolValue(dataAfterExpression, expressionResult); |
---|
1042 | } |
---|
1043 | |
---|
1044 | VisitorResult HandleExpressionResult(Expression expression, VisitorResult result) { |
---|
1045 | RegisterExpressionResult(expression, result.NullableReturnResult); |
---|
1046 | |
---|
1047 | return result; |
---|
1048 | } |
---|
1049 | |
---|
1050 | public override VisitorResult VisitAssignmentExpression(AssignmentExpression assignmentExpression, VariableStatusInfo data) |
---|
1051 | { |
---|
1052 | var tentativeResult = assignmentExpression.Left.AcceptVisitor(this, data); |
---|
1053 | if (tentativeResult.ThrowsException) |
---|
1054 | return HandleExpressionResult(assignmentExpression, tentativeResult); |
---|
1055 | tentativeResult = assignmentExpression.Right.AcceptVisitor(this, tentativeResult.Variables); |
---|
1056 | if (tentativeResult.ThrowsException) |
---|
1057 | return HandleExpressionResult(assignmentExpression, tentativeResult); |
---|
1058 | |
---|
1059 | var leftIdentifier = assignmentExpression.Left as IdentifierExpression; |
---|
1060 | if (leftIdentifier != null) { |
---|
1061 | var resolveResult = analysis.context.Resolve(leftIdentifier); |
---|
1062 | if (resolveResult.IsError) { |
---|
1063 | return HandleExpressionResult(assignmentExpression, data, NullValueStatus.Error); |
---|
1064 | } |
---|
1065 | |
---|
1066 | if (resolveResult is LocalResolveResult) { |
---|
1067 | var result = new VisitorResult(); |
---|
1068 | result.NullableReturnResult = tentativeResult.NullableReturnResult; |
---|
1069 | result.Variables = tentativeResult.Variables.Clone(); |
---|
1070 | var oldValue = result.Variables [leftIdentifier.Identifier]; |
---|
1071 | |
---|
1072 | if (assignmentExpression.Operator == AssignmentOperatorType.Assign || |
---|
1073 | oldValue == NullValueStatus.Unassigned || |
---|
1074 | oldValue == NullValueStatus.DefinitelyNotNull || |
---|
1075 | tentativeResult.NullableReturnResult == NullValueStatus.Error || |
---|
1076 | tentativeResult.NullableReturnResult == NullValueStatus.Unknown) { |
---|
1077 | analysis.SetLocalVariableValue(result.Variables, leftIdentifier, tentativeResult.NullableReturnResult); |
---|
1078 | } else { |
---|
1079 | if (oldValue == NullValueStatus.DefinitelyNull) { |
---|
1080 | //Do nothing --it'll remain null |
---|
1081 | } else { |
---|
1082 | analysis.SetLocalVariableValue(result.Variables, leftIdentifier, NullValueStatus.PotentiallyNull); |
---|
1083 | } |
---|
1084 | } |
---|
1085 | |
---|
1086 | return HandleExpressionResult(assignmentExpression, result); |
---|
1087 | } |
---|
1088 | } |
---|
1089 | |
---|
1090 | return HandleExpressionResult(assignmentExpression, tentativeResult); |
---|
1091 | } |
---|
1092 | |
---|
1093 | public override VisitorResult VisitIdentifierExpression(IdentifierExpression identifierExpression, VariableStatusInfo data) |
---|
1094 | { |
---|
1095 | var resolveResult = analysis.context.Resolve(identifierExpression); |
---|
1096 | if (resolveResult.IsError) { |
---|
1097 | return HandleExpressionResult(identifierExpression, data, NullValueStatus.Error); |
---|
1098 | } |
---|
1099 | var local = resolveResult as LocalResolveResult; |
---|
1100 | if (local != null) { |
---|
1101 | var value = data [local.Variable.Name]; |
---|
1102 | if (value == NullValueStatus.CapturedUnknown) |
---|
1103 | value = NullValueStatus.Unknown; |
---|
1104 | return HandleExpressionResult(identifierExpression, data, value); |
---|
1105 | } |
---|
1106 | if (resolveResult.IsCompileTimeConstant) { |
---|
1107 | object value = resolveResult.ConstantValue; |
---|
1108 | if (value == null) { |
---|
1109 | return HandleExpressionResult(identifierExpression, data, NullValueStatus.DefinitelyNull); |
---|
1110 | } |
---|
1111 | var boolValue = value as bool?; |
---|
1112 | if (boolValue != null) { |
---|
1113 | return VisitorResult.ForBoolValue(data, (bool)boolValue); |
---|
1114 | } |
---|
1115 | return HandleExpressionResult(identifierExpression, data, NullValueStatus.DefinitelyNotNull); |
---|
1116 | } |
---|
1117 | |
---|
1118 | var memberResolveResult = resolveResult as MemberResolveResult; |
---|
1119 | |
---|
1120 | var returnValue = GetFieldReturnValue(memberResolveResult, data); |
---|
1121 | |
---|
1122 | return HandleExpressionResult(identifierExpression, data, returnValue); |
---|
1123 | } |
---|
1124 | |
---|
1125 | public override VisitorResult VisitDefaultValueExpression(DefaultValueExpression defaultValueExpression, VariableStatusInfo data) |
---|
1126 | { |
---|
1127 | var resolveResult = analysis.context.Resolve(defaultValueExpression); |
---|
1128 | if (resolveResult.IsError) { |
---|
1129 | return HandleExpressionResult(defaultValueExpression, data, NullValueStatus.Unknown); |
---|
1130 | } |
---|
1131 | |
---|
1132 | Debug.Assert(resolveResult.IsCompileTimeConstant); |
---|
1133 | |
---|
1134 | var status = resolveResult.ConstantValue == null && resolveResult.Type.IsReferenceType != false ? NullValueStatus.DefinitelyNull : NullValueStatus.DefinitelyNotNull; |
---|
1135 | return HandleExpressionResult(defaultValueExpression, data, status); |
---|
1136 | } |
---|
1137 | |
---|
1138 | public override VisitorResult VisitNullReferenceExpression(NullReferenceExpression nullReferenceExpression, VariableStatusInfo data) |
---|
1139 | { |
---|
1140 | return HandleExpressionResult(nullReferenceExpression, data, NullValueStatus.DefinitelyNull); |
---|
1141 | } |
---|
1142 | |
---|
1143 | public override VisitorResult VisitPrimitiveExpression(PrimitiveExpression primitiveExpression, VariableStatusInfo data) |
---|
1144 | { |
---|
1145 | return HandleExpressionResult(primitiveExpression, data, NullValueStatus.DefinitelyNotNull); |
---|
1146 | } |
---|
1147 | |
---|
1148 | public override VisitorResult VisitParenthesizedExpression(ParenthesizedExpression parenthesizedExpression, VariableStatusInfo data) |
---|
1149 | { |
---|
1150 | return HandleExpressionResult(parenthesizedExpression, parenthesizedExpression.Expression.AcceptVisitor(this, data)); |
---|
1151 | } |
---|
1152 | |
---|
1153 | public override VisitorResult VisitConditionalExpression(ConditionalExpression conditionalExpression, VariableStatusInfo data) |
---|
1154 | { |
---|
1155 | var tentativeBaseResult = conditionalExpression.Condition.AcceptVisitor(this, data); |
---|
1156 | if (tentativeBaseResult.ThrowsException) |
---|
1157 | return HandleExpressionResult(conditionalExpression, tentativeBaseResult); |
---|
1158 | |
---|
1159 | var conditionResolveResult = analysis.context.Resolve(conditionalExpression.Condition); |
---|
1160 | |
---|
1161 | if (tentativeBaseResult.KnownBoolResult == true || true.Equals(conditionResolveResult.ConstantValue)) { |
---|
1162 | return HandleExpressionResult(conditionalExpression, conditionalExpression.TrueExpression.AcceptVisitor(this, tentativeBaseResult.TruePathVariables)); |
---|
1163 | } |
---|
1164 | if (tentativeBaseResult.KnownBoolResult == false || false.Equals(conditionResolveResult.ConstantValue)) { |
---|
1165 | return HandleExpressionResult(conditionalExpression, conditionalExpression.FalseExpression.AcceptVisitor(this, tentativeBaseResult.FalsePathVariables)); |
---|
1166 | } |
---|
1167 | |
---|
1168 | //No known bool result |
---|
1169 | var trueCaseResult = conditionalExpression.TrueExpression.AcceptVisitor(this, tentativeBaseResult.TruePathVariables); |
---|
1170 | if (trueCaseResult.ThrowsException) { |
---|
1171 | //We know that the true case will never be completed, then the right case is the only possible route. |
---|
1172 | return HandleExpressionResult(conditionalExpression, conditionalExpression.FalseExpression.AcceptVisitor(this, tentativeBaseResult.FalsePathVariables)); |
---|
1173 | } |
---|
1174 | var falseCaseResult = conditionalExpression.FalseExpression.AcceptVisitor(this, tentativeBaseResult.FalsePathVariables); |
---|
1175 | if (falseCaseResult.ThrowsException) { |
---|
1176 | return HandleExpressionResult(conditionalExpression, trueCaseResult.Variables, true); |
---|
1177 | } |
---|
1178 | |
---|
1179 | return HandleExpressionResult(conditionalExpression, VisitorResult.OrOperation(trueCaseResult, falseCaseResult)); |
---|
1180 | } |
---|
1181 | |
---|
1182 | public override VisitorResult VisitBinaryOperatorExpression(BinaryOperatorExpression binaryOperatorExpression, VariableStatusInfo data) |
---|
1183 | { |
---|
1184 | //Let's not evaluate the sides just yet because of ??, && and || |
---|
1185 | |
---|
1186 | //We'll register the results here (with HandleExpressionResult) |
---|
1187 | //so each Visit*Expression won't have to do it itself |
---|
1188 | switch (binaryOperatorExpression.Operator) { |
---|
1189 | case BinaryOperatorType.ConditionalAnd: |
---|
1190 | return HandleExpressionResult(binaryOperatorExpression, VisitConditionalAndExpression(binaryOperatorExpression, data)); |
---|
1191 | case BinaryOperatorType.ConditionalOr: |
---|
1192 | return HandleExpressionResult(binaryOperatorExpression, VisitConditionalOrExpression(binaryOperatorExpression, data)); |
---|
1193 | case BinaryOperatorType.NullCoalescing: |
---|
1194 | return HandleExpressionResult(binaryOperatorExpression, VisitNullCoalescing(binaryOperatorExpression, data)); |
---|
1195 | case BinaryOperatorType.Equality: |
---|
1196 | return HandleExpressionResult(binaryOperatorExpression, VisitEquality(binaryOperatorExpression, data)); |
---|
1197 | case BinaryOperatorType.InEquality: |
---|
1198 | return HandleExpressionResult(binaryOperatorExpression, VisitEquality(binaryOperatorExpression, data).Negated); |
---|
1199 | default: |
---|
1200 | return HandleExpressionResult(binaryOperatorExpression, VisitOtherBinaryExpression(binaryOperatorExpression, data)); |
---|
1201 | } |
---|
1202 | } |
---|
1203 | |
---|
1204 | VisitorResult VisitOtherBinaryExpression(BinaryOperatorExpression binaryOperatorExpression, VariableStatusInfo data) |
---|
1205 | { |
---|
1206 | var leftTentativeResult = binaryOperatorExpression.Left.AcceptVisitor(this, data); |
---|
1207 | if (leftTentativeResult.ThrowsException) |
---|
1208 | return leftTentativeResult; |
---|
1209 | var rightTentativeResult = binaryOperatorExpression.Right.AcceptVisitor(this, leftTentativeResult.Variables); |
---|
1210 | if (rightTentativeResult.ThrowsException) |
---|
1211 | return rightTentativeResult; |
---|
1212 | |
---|
1213 | //TODO: Assuming operators are not overloaded by users |
---|
1214 | // (or, if they are, that they retain similar behavior to the default ones) |
---|
1215 | |
---|
1216 | switch (binaryOperatorExpression.Operator) { |
---|
1217 | case BinaryOperatorType.LessThan: |
---|
1218 | case BinaryOperatorType.GreaterThan: |
---|
1219 | //Operations < and > with nulls always return false |
---|
1220 | //Those same operations will other values may or may not return false |
---|
1221 | if (leftTentativeResult.NullableReturnResult == NullValueStatus.DefinitelyNull && |
---|
1222 | rightTentativeResult.NullableReturnResult == NullValueStatus.DefinitelyNull) { |
---|
1223 | return VisitorResult.ForBoolValue(rightTentativeResult.Variables, false); |
---|
1224 | } |
---|
1225 | //We don't know what the value is, but we know that both true and false are != null. |
---|
1226 | return VisitorResult.ForValue(rightTentativeResult.Variables, NullValueStatus.DefinitelyNotNull); |
---|
1227 | case BinaryOperatorType.LessThanOrEqual: |
---|
1228 | case BinaryOperatorType.GreaterThanOrEqual: |
---|
1229 | if (leftTentativeResult.NullableReturnResult == NullValueStatus.DefinitelyNull) { |
---|
1230 | if (rightTentativeResult.NullableReturnResult == NullValueStatus.DefinitelyNull) |
---|
1231 | return VisitorResult.ForBoolValue(rightTentativeResult.Variables, true); |
---|
1232 | if (rightTentativeResult.NullableReturnResult == NullValueStatus.DefinitelyNotNull) |
---|
1233 | return VisitorResult.ForBoolValue(rightTentativeResult.Variables, false); |
---|
1234 | } else if (leftTentativeResult.NullableReturnResult == NullValueStatus.DefinitelyNotNull) { |
---|
1235 | if (rightTentativeResult.NullableReturnResult == NullValueStatus.DefinitelyNull) |
---|
1236 | return VisitorResult.ForBoolValue(rightTentativeResult.Variables, false); |
---|
1237 | } |
---|
1238 | |
---|
1239 | return VisitorResult.ForValue(rightTentativeResult.Variables, NullValueStatus.Unknown); |
---|
1240 | default: |
---|
1241 | //Anything else: null + anything == anything + null == null. |
---|
1242 | //not null + not null = not null |
---|
1243 | if (leftTentativeResult.NullableReturnResult == NullValueStatus.DefinitelyNull) { |
---|
1244 | return VisitorResult.ForValue(rightTentativeResult.Variables, NullValueStatus.DefinitelyNull); |
---|
1245 | } |
---|
1246 | if (leftTentativeResult.NullableReturnResult == NullValueStatus.DefinitelyNotNull) { |
---|
1247 | if (rightTentativeResult.NullableReturnResult == NullValueStatus.DefinitelyNull) |
---|
1248 | return VisitorResult.ForValue(rightTentativeResult.Variables, NullValueStatus.DefinitelyNull); |
---|
1249 | if (rightTentativeResult.NullableReturnResult == NullValueStatus.DefinitelyNotNull) |
---|
1250 | return VisitorResult.ForValue(rightTentativeResult.Variables, NullValueStatus.DefinitelyNotNull); |
---|
1251 | } |
---|
1252 | |
---|
1253 | return VisitorResult.ForValue(rightTentativeResult.Variables, NullValueStatus.Unknown); |
---|
1254 | } |
---|
1255 | } |
---|
1256 | |
---|
1257 | VisitorResult WithVariableValue(VisitorResult result, IdentifierExpression identifier, bool isNull) |
---|
1258 | { |
---|
1259 | var localVariableResult = analysis.context.Resolve(identifier) as LocalResolveResult; |
---|
1260 | if (localVariableResult != null) { |
---|
1261 | result.ConditionalBranchInfo.TrueResultVariableNullStates[identifier.Identifier] = isNull; |
---|
1262 | if (isNull) { |
---|
1263 | result.ConditionalBranchInfo.FalseResultVariableNullStates[identifier.Identifier] = false; |
---|
1264 | } |
---|
1265 | } |
---|
1266 | return result; |
---|
1267 | } |
---|
1268 | |
---|
1269 | VisitorResult VisitEquality(BinaryOperatorExpression binaryOperatorExpression, VariableStatusInfo data) |
---|
1270 | { |
---|
1271 | //TODO: Should this check for user operators? |
---|
1272 | |
---|
1273 | var tentativeLeftResult = binaryOperatorExpression.Left.AcceptVisitor(this, data); |
---|
1274 | if (tentativeLeftResult.ThrowsException) |
---|
1275 | return tentativeLeftResult; |
---|
1276 | var tentativeRightResult = binaryOperatorExpression.Right.AcceptVisitor(this, tentativeLeftResult.Variables); |
---|
1277 | if (tentativeRightResult.ThrowsException) |
---|
1278 | return tentativeRightResult; |
---|
1279 | |
---|
1280 | if (tentativeLeftResult.KnownBoolResult != null && tentativeLeftResult.KnownBoolResult == tentativeRightResult.KnownBoolResult) { |
---|
1281 | return VisitorResult.ForBoolValue(tentativeRightResult.Variables, true); |
---|
1282 | } |
---|
1283 | |
---|
1284 | if (tentativeLeftResult.KnownBoolResult != null && tentativeLeftResult.KnownBoolResult == !tentativeRightResult.KnownBoolResult) { |
---|
1285 | return VisitorResult.ForBoolValue(tentativeRightResult.Variables, false); |
---|
1286 | } |
---|
1287 | |
---|
1288 | if (tentativeLeftResult.NullableReturnResult.IsDefiniteValue()) { |
---|
1289 | if (tentativeRightResult.NullableReturnResult.IsDefiniteValue()) { |
---|
1290 | if (tentativeLeftResult.NullableReturnResult == NullValueStatus.DefinitelyNull || tentativeRightResult.NullableReturnResult == NullValueStatus.DefinitelyNull) { |
---|
1291 | return VisitorResult.ForBoolValue(tentativeRightResult.Variables, tentativeLeftResult.NullableReturnResult == tentativeRightResult.NullableReturnResult); |
---|
1292 | } |
---|
1293 | } |
---|
1294 | } |
---|
1295 | |
---|
1296 | var result = new VisitorResult(); |
---|
1297 | result.Variables = tentativeRightResult.Variables; |
---|
1298 | result.NullableReturnResult = NullValueStatus.Unknown; |
---|
1299 | result.ConditionalBranchInfo = new ConditionalBranchInfo(); |
---|
1300 | |
---|
1301 | if (tentativeRightResult.NullableReturnResult.IsDefiniteValue()) { |
---|
1302 | var identifier = CSharpUtil.GetInnerMostExpression(binaryOperatorExpression.Left) as IdentifierExpression; |
---|
1303 | |
---|
1304 | if (identifier != null) { |
---|
1305 | bool isNull = (tentativeRightResult.NullableReturnResult == NullValueStatus.DefinitelyNull); |
---|
1306 | |
---|
1307 | WithVariableValue(result, identifier, isNull); |
---|
1308 | } |
---|
1309 | } |
---|
1310 | |
---|
1311 | if (tentativeLeftResult.NullableReturnResult.IsDefiniteValue()) { |
---|
1312 | var identifier = CSharpUtil.GetInnerMostExpression(binaryOperatorExpression.Right) as IdentifierExpression; |
---|
1313 | |
---|
1314 | if (identifier != null) { |
---|
1315 | bool isNull = (tentativeLeftResult.NullableReturnResult == NullValueStatus.DefinitelyNull); |
---|
1316 | |
---|
1317 | WithVariableValue(result, identifier, isNull); |
---|
1318 | } |
---|
1319 | } |
---|
1320 | |
---|
1321 | return result; |
---|
1322 | } |
---|
1323 | |
---|
1324 | VisitorResult VisitConditionalAndExpression(BinaryOperatorExpression binaryOperatorExpression, VariableStatusInfo data) |
---|
1325 | { |
---|
1326 | var tentativeLeftResult = binaryOperatorExpression.Left.AcceptVisitor(this, data); |
---|
1327 | if (tentativeLeftResult.KnownBoolResult == false || tentativeLeftResult.ThrowsException) { |
---|
1328 | return tentativeLeftResult; |
---|
1329 | } |
---|
1330 | |
---|
1331 | var truePath = tentativeLeftResult.TruePathVariables; |
---|
1332 | var tentativeRightResult = binaryOperatorExpression.Right.AcceptVisitor(this, truePath); |
---|
1333 | if (tentativeRightResult.ThrowsException) { |
---|
1334 | //If the true path throws an exception, then the only way for the expression to complete |
---|
1335 | //successfully is if the left expression is false |
---|
1336 | return VisitorResult.ForBoolValue(tentativeLeftResult.FalsePathVariables, false); |
---|
1337 | } |
---|
1338 | |
---|
1339 | return VisitorResult.AndOperation(tentativeLeftResult, tentativeRightResult); |
---|
1340 | } |
---|
1341 | |
---|
1342 | VisitorResult VisitConditionalOrExpression(BinaryOperatorExpression binaryOperatorExpression, VariableStatusInfo data) |
---|
1343 | { |
---|
1344 | var tentativeLeftResult = binaryOperatorExpression.Left.AcceptVisitor(this, data); |
---|
1345 | if (tentativeLeftResult.KnownBoolResult == true || tentativeLeftResult.ThrowsException) { |
---|
1346 | return tentativeLeftResult; |
---|
1347 | } |
---|
1348 | |
---|
1349 | var falsePath = tentativeLeftResult.FalsePathVariables; |
---|
1350 | var tentativeRightResult = binaryOperatorExpression.Right.AcceptVisitor(this, falsePath); |
---|
1351 | if (tentativeRightResult.ThrowsException) { |
---|
1352 | //If the false path throws an exception, then the only way for the expression to complete |
---|
1353 | //successfully is if the left expression is true |
---|
1354 | return VisitorResult.ForBoolValue(tentativeLeftResult.TruePathVariables, true); |
---|
1355 | } |
---|
1356 | |
---|
1357 | return VisitorResult.OrOperation(tentativeLeftResult, tentativeRightResult); |
---|
1358 | } |
---|
1359 | |
---|
1360 | VisitorResult VisitNullCoalescing(BinaryOperatorExpression binaryOperatorExpression, VariableStatusInfo data) |
---|
1361 | { |
---|
1362 | var leftTentativeResult = binaryOperatorExpression.Left.AcceptVisitor(this, data); |
---|
1363 | if (leftTentativeResult.NullableReturnResult == NullValueStatus.DefinitelyNotNull || leftTentativeResult.ThrowsException) { |
---|
1364 | return leftTentativeResult; |
---|
1365 | } |
---|
1366 | |
---|
1367 | //If the right side is found, then the left side is known to be null |
---|
1368 | var newData = leftTentativeResult.Variables; |
---|
1369 | var leftIdentifier = CSharpUtil.GetInnerMostExpression(binaryOperatorExpression.Left) as IdentifierExpression; |
---|
1370 | if (leftIdentifier != null) { |
---|
1371 | newData = newData.Clone(); |
---|
1372 | analysis.SetLocalVariableValue(newData, leftIdentifier, NullValueStatus.DefinitelyNull); |
---|
1373 | } |
---|
1374 | |
---|
1375 | var rightTentativeResult = binaryOperatorExpression.Right.AcceptVisitor(this, newData); |
---|
1376 | if (rightTentativeResult.ThrowsException) { |
---|
1377 | //This means the left expression was not null all along (or else the expression will throw an exception) |
---|
1378 | |
---|
1379 | if (leftIdentifier != null) { |
---|
1380 | newData = newData.Clone(); |
---|
1381 | analysis.SetLocalVariableValue(newData, leftIdentifier, NullValueStatus.DefinitelyNotNull); |
---|
1382 | return VisitorResult.ForValue(newData, NullValueStatus.DefinitelyNotNull); |
---|
1383 | } |
---|
1384 | |
---|
1385 | return VisitorResult.ForValue(leftTentativeResult.Variables, NullValueStatus.DefinitelyNotNull); |
---|
1386 | } |
---|
1387 | |
---|
1388 | var mergedVariables = rightTentativeResult.Variables; |
---|
1389 | var nullValue = rightTentativeResult.NullableReturnResult; |
---|
1390 | |
---|
1391 | if (leftTentativeResult.NullableReturnResult != NullValueStatus.DefinitelyNull) { |
---|
1392 | mergedVariables = mergedVariables.Clone(); |
---|
1393 | mergedVariables.ReceiveIncoming(leftTentativeResult.Variables); |
---|
1394 | if (nullValue == NullValueStatus.DefinitelyNull) { |
---|
1395 | nullValue = NullValueStatus.PotentiallyNull; |
---|
1396 | } |
---|
1397 | } |
---|
1398 | |
---|
1399 | return VisitorResult.ForValue(mergedVariables, nullValue); |
---|
1400 | } |
---|
1401 | |
---|
1402 | public override VisitorResult VisitUnaryOperatorExpression(UnaryOperatorExpression unaryOperatorExpression, VariableStatusInfo data) |
---|
1403 | { |
---|
1404 | //TODO: Again, what to do when overloaded operators are found? |
---|
1405 | |
---|
1406 | var tentativeResult = unaryOperatorExpression.Expression.AcceptVisitor(this, data); |
---|
1407 | if (tentativeResult.ThrowsException) |
---|
1408 | return HandleExpressionResult(unaryOperatorExpression, tentativeResult); |
---|
1409 | |
---|
1410 | if (unaryOperatorExpression.Operator == UnaryOperatorType.Not) { |
---|
1411 | return HandleExpressionResult(unaryOperatorExpression, tentativeResult.Negated); |
---|
1412 | } |
---|
1413 | return HandleExpressionResult(unaryOperatorExpression, tentativeResult); |
---|
1414 | } |
---|
1415 | |
---|
1416 | public override VisitorResult VisitInvocationExpression(InvocationExpression invocationExpression, VariableStatusInfo data) |
---|
1417 | { |
---|
1418 | //TODO: Handle some common methods such as string.IsNullOrEmpty |
---|
1419 | |
---|
1420 | var targetResult = invocationExpression.Target.AcceptVisitor(this, data); |
---|
1421 | if (targetResult.ThrowsException) |
---|
1422 | return HandleExpressionResult(invocationExpression, targetResult); |
---|
1423 | |
---|
1424 | data = targetResult.Variables; |
---|
1425 | |
---|
1426 | var methodResolveResult = analysis.context.Resolve(invocationExpression) as CSharpInvocationResolveResult; |
---|
1427 | |
---|
1428 | List<VisitorResult> parameterResults = new List<VisitorResult>(); |
---|
1429 | |
---|
1430 | foreach (var argumentToHandle in invocationExpression.Arguments.Select((argument, parameterIndex) => new { argument, parameterIndex })) { |
---|
1431 | var argument = argumentToHandle.argument; |
---|
1432 | var parameterIndex = argumentToHandle.parameterIndex; |
---|
1433 | |
---|
1434 | var result = argument.AcceptVisitor(this, data); |
---|
1435 | if (result.ThrowsException) |
---|
1436 | return HandleExpressionResult(invocationExpression, result); |
---|
1437 | parameterResults.Add(result); |
---|
1438 | |
---|
1439 | var namedArgument = argument as NamedArgumentExpression; |
---|
1440 | |
---|
1441 | var directionExpression = (namedArgument == null ? argument : namedArgument.Expression) as DirectionExpression; |
---|
1442 | if (directionExpression != null && methodResolveResult != null) { |
---|
1443 | var identifier = directionExpression.Expression as IdentifierExpression; |
---|
1444 | if (identifier != null) { |
---|
1445 | //out and ref parameters do *NOT* capture the variable (since they must stop changing it by the time they return) |
---|
1446 | var identifierResolveResult = analysis.context.Resolve(identifier) as LocalResolveResult; |
---|
1447 | if (identifierResolveResult != null && IsTypeNullable(identifierResolveResult.Type)) { |
---|
1448 | data = data.Clone(); |
---|
1449 | |
---|
1450 | FixParameter(argument, methodResolveResult.Member.Parameters, parameterIndex, identifier, data); |
---|
1451 | } |
---|
1452 | } |
---|
1453 | |
---|
1454 | |
---|
1455 | continue; |
---|
1456 | } |
---|
1457 | |
---|
1458 | data = result.Variables; |
---|
1459 | } |
---|
1460 | |
---|
1461 | var identifierExpression = CSharpUtil.GetInnerMostExpression(invocationExpression.Target) as IdentifierExpression; |
---|
1462 | if (identifierExpression != null) { |
---|
1463 | if (targetResult.NullableReturnResult == NullValueStatus.DefinitelyNull) { |
---|
1464 | return HandleExpressionResult(invocationExpression, VisitorResult.ForException(data)); |
---|
1465 | } |
---|
1466 | |
---|
1467 | var descendentIdentifiers = invocationExpression.Arguments.SelectMany(argument => argument.DescendantsAndSelf).OfType<IdentifierExpression>(); |
---|
1468 | if (!descendentIdentifiers.Any(identifier => identifier.Identifier == identifierExpression.Identifier)) { |
---|
1469 | //TODO: We can make this check better (see VisitIndexerExpression for more details) |
---|
1470 | data = data.Clone(); |
---|
1471 | analysis.SetLocalVariableValue(data, identifierExpression, NullValueStatus.DefinitelyNotNull); |
---|
1472 | } |
---|
1473 | } |
---|
1474 | |
---|
1475 | return HandleExpressionResult(invocationExpression, GetMethodVisitorResult(methodResolveResult, data, parameterResults)); |
---|
1476 | } |
---|
1477 | |
---|
1478 | static VisitorResult GetMethodVisitorResult(CSharpInvocationResolveResult methodResolveResult, VariableStatusInfo data, List<VisitorResult> parameterResults) |
---|
1479 | { |
---|
1480 | if (methodResolveResult == null) |
---|
1481 | return VisitorResult.ForValue(data, NullValueStatus.Unknown); |
---|
1482 | |
---|
1483 | var method = methodResolveResult.Member as IMethod; |
---|
1484 | if (method != null) { |
---|
1485 | if (method.GetAttribute(new FullTypeName(AnnotationNames.AssertionMethodAttribute)) != null) { |
---|
1486 | var assertionParameters = method.Parameters.Select((parameter, index) => new { index, parameter }) |
---|
1487 | .Select(parameter => new { parameter.index, parameter.parameter, attributes = parameter.parameter.Attributes.Where(attribute => attribute.AttributeType.FullName == AnnotationNames.AssertionConditionAttribute).ToList() }) |
---|
1488 | .Where(parameter => parameter.attributes.Count() == 1) |
---|
1489 | .Select(parameter => new { parameter.index, parameter.parameter, attribute = parameter.attributes[0] }) |
---|
1490 | .ToList(); |
---|
1491 | |
---|
1492 | //Unclear what should be done if there are multiple assertion conditions |
---|
1493 | if (assertionParameters.Count() == 1) { |
---|
1494 | Debug.Assert(methodResolveResult.Arguments.Count == parameterResults.Count); |
---|
1495 | |
---|
1496 | var assertionParameter = assertionParameters [0]; |
---|
1497 | VisitorResult assertionParameterResult = null; |
---|
1498 | |
---|
1499 | object intendedResult = true; |
---|
1500 | var positionalArgument = assertionParameter.attribute.PositionalArguments.FirstOrDefault() as MemberResolveResult; |
---|
1501 | if (positionalArgument != null && positionalArgument.Type.FullName == AnnotationNames.AssertionConditionTypeAttribute) { |
---|
1502 | switch (positionalArgument.Member.FullName) { |
---|
1503 | case AnnotationNames.AssertionConditionTypeIsTrue: |
---|
1504 | intendedResult = true; |
---|
1505 | break; |
---|
1506 | case AnnotationNames.AssertionConditionTypeIsFalse: |
---|
1507 | intendedResult = false; |
---|
1508 | break; |
---|
1509 | case AnnotationNames.AssertionConditionTypeIsNull: |
---|
1510 | intendedResult = null; |
---|
1511 | break; |
---|
1512 | case AnnotationNames.AssertionConditionTypeIsNotNull: |
---|
1513 | intendedResult = "<not-null>"; |
---|
1514 | break; |
---|
1515 | } |
---|
1516 | } |
---|
1517 | |
---|
1518 | int parameterIndex = assertionParameter.index; |
---|
1519 | if (assertionParameter.index < methodResolveResult.Arguments.Count && !(methodResolveResult.Arguments [assertionParameter.index] is NamedArgumentResolveResult)) { |
---|
1520 | //Use index |
---|
1521 | assertionParameterResult = parameterResults [assertionParameter.index]; |
---|
1522 | } else { |
---|
1523 | //Use named argument |
---|
1524 | int? nameIndex = methodResolveResult.Arguments.Select((argument, index) => new { argument, index}) |
---|
1525 | .Where(argument => { |
---|
1526 | var namedArgument = argument.argument as NamedArgumentResolveResult; |
---|
1527 | return namedArgument != null && namedArgument.ParameterName == assertionParameter.parameter.Name; |
---|
1528 | }).Select(argument => (int?)argument.index).FirstOrDefault(); |
---|
1529 | |
---|
1530 | if (nameIndex != null) { |
---|
1531 | parameterIndex = nameIndex.Value; |
---|
1532 | assertionParameterResult = parameterResults [nameIndex.Value]; |
---|
1533 | } else if (assertionParameter.parameter.IsOptional) { |
---|
1534 | //Try to use default value |
---|
1535 | |
---|
1536 | if (intendedResult is string) { |
---|
1537 | if (assertionParameter.parameter.ConstantValue == null) { |
---|
1538 | return VisitorResult.ForException(data); |
---|
1539 | } |
---|
1540 | } else { |
---|
1541 | if (!object.Equals(assertionParameter.parameter.ConstantValue, intendedResult)) { |
---|
1542 | return VisitorResult.ForException(data); |
---|
1543 | } |
---|
1544 | } |
---|
1545 | } else { |
---|
1546 | //The parameter was not specified, yet it is not optional? |
---|
1547 | return VisitorResult.ForException(data); |
---|
1548 | } |
---|
1549 | } |
---|
1550 | |
---|
1551 | //Now check assertion |
---|
1552 | if (assertionParameterResult != null) { |
---|
1553 | if (intendedResult is bool) { |
---|
1554 | if (assertionParameterResult.KnownBoolResult == !(bool)intendedResult) { |
---|
1555 | return VisitorResult.ForException(data); |
---|
1556 | } |
---|
1557 | |
---|
1558 | data = (bool)intendedResult ? assertionParameterResult.TruePathVariables : assertionParameterResult.FalsePathVariables; |
---|
1559 | } else { |
---|
1560 | bool shouldBeNull = intendedResult == null; |
---|
1561 | |
---|
1562 | if (assertionParameterResult.NullableReturnResult == (shouldBeNull ? NullValueStatus.DefinitelyNotNull : NullValueStatus.DefinitelyNull)) { |
---|
1563 | return VisitorResult.ForException(data); |
---|
1564 | } |
---|
1565 | |
---|
1566 | var parameterResolveResult = methodResolveResult.Arguments [parameterIndex]; |
---|
1567 | |
---|
1568 | LocalResolveResult localVariableResult = null; |
---|
1569 | |
---|
1570 | var conversionResolveResult = parameterResolveResult as ConversionResolveResult; |
---|
1571 | if (conversionResolveResult != null) { |
---|
1572 | if (!IsTypeNullable(conversionResolveResult.Type)) { |
---|
1573 | if (intendedResult == null) { |
---|
1574 | return VisitorResult.ForException(data); |
---|
1575 | } |
---|
1576 | } else { |
---|
1577 | localVariableResult = conversionResolveResult.Input as LocalResolveResult; |
---|
1578 | } |
---|
1579 | } else { |
---|
1580 | localVariableResult = parameterResolveResult as LocalResolveResult; |
---|
1581 | } |
---|
1582 | |
---|
1583 | if (localVariableResult != null && data[localVariableResult.Variable.Name] != NullValueStatus.CapturedUnknown) { |
---|
1584 | data = data.Clone(); |
---|
1585 | data [localVariableResult.Variable.Name] = shouldBeNull ? NullValueStatus.DefinitelyNull : NullValueStatus.DefinitelyNotNull; |
---|
1586 | } |
---|
1587 | } |
---|
1588 | } |
---|
1589 | } |
---|
1590 | } |
---|
1591 | } |
---|
1592 | |
---|
1593 | bool isNullable = IsTypeNullable(methodResolveResult.Type); |
---|
1594 | if (!isNullable) { |
---|
1595 | return VisitorResult.ForValue(data, NullValueStatus.DefinitelyNotNull); |
---|
1596 | } |
---|
1597 | |
---|
1598 | if (method != null) |
---|
1599 | return VisitorResult.ForValue(data, GetNullableStatus(method)); |
---|
1600 | |
---|
1601 | return VisitorResult.ForValue(data, GetNullableStatus(methodResolveResult.TargetResult.Type.GetDefinition())); |
---|
1602 | } |
---|
1603 | |
---|
1604 | static NullValueStatus GetNullableStatus(IEntity entity) |
---|
1605 | { |
---|
1606 | if (entity.DeclaringType != null && entity.DeclaringType.Kind == TypeKind.Delegate) { |
---|
1607 | //Handle Delegate.Invoke method |
---|
1608 | return GetNullableStatus(entity.DeclaringTypeDefinition); |
---|
1609 | } |
---|
1610 | |
---|
1611 | return GetNullableStatus(fullTypeName => entity.GetAttribute(new FullTypeName(fullTypeName))); |
---|
1612 | } |
---|
1613 | |
---|
1614 | static NullValueStatus GetNullableStatus(IParameter parameter) |
---|
1615 | { |
---|
1616 | return GetNullableStatus(fullTypeName => parameter.Attributes.FirstOrDefault(attribute => attribute.AttributeType.FullName == fullTypeName)); |
---|
1617 | } |
---|
1618 | |
---|
1619 | static NullValueStatus GetNullableStatus(Func<string, IAttribute> attributeGetter) |
---|
1620 | { |
---|
1621 | if (attributeGetter(AnnotationNames.NotNullAttribute) != null) { |
---|
1622 | return NullValueStatus.DefinitelyNotNull; |
---|
1623 | } |
---|
1624 | if (attributeGetter(AnnotationNames.CanBeNullAttribute) != null) { |
---|
1625 | return NullValueStatus.PotentiallyNull; |
---|
1626 | } |
---|
1627 | return NullValueStatus.Unknown; |
---|
1628 | } |
---|
1629 | |
---|
1630 | public override VisitorResult VisitMemberReferenceExpression(MemberReferenceExpression memberReferenceExpression, VariableStatusInfo data) |
---|
1631 | { |
---|
1632 | var targetResult = memberReferenceExpression.Target.AcceptVisitor(this, data); |
---|
1633 | if (targetResult.ThrowsException) |
---|
1634 | return HandleExpressionResult(memberReferenceExpression, targetResult); |
---|
1635 | |
---|
1636 | var variables = targetResult.Variables; |
---|
1637 | |
---|
1638 | var memberResolveResult = analysis.context.Resolve(memberReferenceExpression) as MemberResolveResult; |
---|
1639 | |
---|
1640 | var targetIdentifier = CSharpUtil.GetInnerMostExpression(memberReferenceExpression.Target) as IdentifierExpression; |
---|
1641 | if (targetIdentifier != null) { |
---|
1642 | if (memberResolveResult == null) { |
---|
1643 | var invocation = memberReferenceExpression.Parent as InvocationExpression; |
---|
1644 | if (invocation != null) { |
---|
1645 | memberResolveResult = analysis.context.Resolve(invocation) as MemberResolveResult; |
---|
1646 | } |
---|
1647 | } |
---|
1648 | |
---|
1649 | if (memberResolveResult != null && memberResolveResult.Member.FullName != "System.Nullable.HasValue") { |
---|
1650 | var method = memberResolveResult.Member as IMethod; |
---|
1651 | if (method == null || !method.IsExtensionMethod) { |
---|
1652 | if (targetResult.NullableReturnResult == NullValueStatus.DefinitelyNull) { |
---|
1653 | return HandleExpressionResult(memberReferenceExpression, VisitorResult.ForException(variables)); |
---|
1654 | } |
---|
1655 | if (variables [targetIdentifier.Identifier] != NullValueStatus.CapturedUnknown) { |
---|
1656 | variables = variables.Clone(); |
---|
1657 | analysis.SetLocalVariableValue(variables, targetIdentifier, NullValueStatus.DefinitelyNotNull); |
---|
1658 | } |
---|
1659 | } |
---|
1660 | } |
---|
1661 | } |
---|
1662 | |
---|
1663 | var returnValue = GetFieldReturnValue(memberResolveResult, data); |
---|
1664 | return HandleExpressionResult(memberReferenceExpression, variables, returnValue); |
---|
1665 | } |
---|
1666 | |
---|
1667 | static NullValueStatus GetFieldReturnValue(MemberResolveResult memberResolveResult, VariableStatusInfo data) |
---|
1668 | { |
---|
1669 | bool isNullable = memberResolveResult == null || IsTypeNullable(memberResolveResult.Type); |
---|
1670 | if (!isNullable) { |
---|
1671 | return NullValueStatus.DefinitelyNotNull; |
---|
1672 | } |
---|
1673 | |
---|
1674 | if (memberResolveResult != null) { |
---|
1675 | return GetNullableStatus(memberResolveResult.Member); |
---|
1676 | } |
---|
1677 | |
---|
1678 | return NullValueStatus.Unknown; |
---|
1679 | } |
---|
1680 | |
---|
1681 | public override VisitorResult VisitTypeReferenceExpression(TypeReferenceExpression typeReferenceExpression, VariableStatusInfo data) |
---|
1682 | { |
---|
1683 | return HandleExpressionResult(typeReferenceExpression, data, NullValueStatus.Unknown); |
---|
1684 | |
---|
1685 | } |
---|
1686 | |
---|
1687 | void FixParameter(Expression argument, IList<IParameter> parameters, int parameterIndex, IdentifierExpression identifier, VariableStatusInfo data) |
---|
1688 | { |
---|
1689 | NullValueStatus newValue = NullValueStatus.Unknown; |
---|
1690 | if (argument is NamedArgumentExpression) { |
---|
1691 | var namedResolveResult = analysis.context.Resolve(argument) as NamedArgumentResolveResult; |
---|
1692 | if (namedResolveResult != null) { |
---|
1693 | newValue = GetNullableStatus(namedResolveResult.Parameter); |
---|
1694 | } |
---|
1695 | } |
---|
1696 | else { |
---|
1697 | var parameter = parameters[parameterIndex]; |
---|
1698 | newValue = GetNullableStatus(parameter); |
---|
1699 | } |
---|
1700 | analysis.SetLocalVariableValue(data, identifier, newValue); |
---|
1701 | } |
---|
1702 | |
---|
1703 | public override VisitorResult VisitObjectCreateExpression(ObjectCreateExpression objectCreateExpression, VariableStatusInfo data) |
---|
1704 | { |
---|
1705 | foreach (var argumentToHandle in objectCreateExpression.Arguments.Select((argument, parameterIndex) => new { argument, parameterIndex })) { |
---|
1706 | var argument = argumentToHandle.argument; |
---|
1707 | var parameterIndex = argumentToHandle.parameterIndex; |
---|
1708 | |
---|
1709 | var namedArgument = argument as NamedArgumentExpression; |
---|
1710 | |
---|
1711 | var directionExpression = (namedArgument == null ? argument : namedArgument.Expression) as DirectionExpression; |
---|
1712 | if (directionExpression != null) { |
---|
1713 | var identifier = directionExpression.Expression as IdentifierExpression; |
---|
1714 | if (identifier != null && data [identifier.Identifier] != NullValueStatus.CapturedUnknown) { |
---|
1715 | //out and ref parameters do *NOT* capture the variable (since they must stop changing it by the time they return) |
---|
1716 | data = data.Clone(); |
---|
1717 | |
---|
1718 | var constructorResolveResult = analysis.context.Resolve(objectCreateExpression) as CSharpInvocationResolveResult; |
---|
1719 | if (constructorResolveResult != null) |
---|
1720 | FixParameter(argument, constructorResolveResult.Member.Parameters, parameterIndex, identifier, data); |
---|
1721 | } |
---|
1722 | continue; |
---|
1723 | } |
---|
1724 | |
---|
1725 | var argumentResult = argument.AcceptVisitor(this, data); |
---|
1726 | if (argumentResult.ThrowsException) |
---|
1727 | return argumentResult; |
---|
1728 | |
---|
1729 | data = argumentResult.Variables; |
---|
1730 | } |
---|
1731 | |
---|
1732 | //Constructors never return null |
---|
1733 | return HandleExpressionResult(objectCreateExpression, data, NullValueStatus.DefinitelyNotNull); |
---|
1734 | } |
---|
1735 | |
---|
1736 | public override VisitorResult VisitArrayCreateExpression(ArrayCreateExpression arrayCreateExpression, VariableStatusInfo data) |
---|
1737 | { |
---|
1738 | foreach (var argument in arrayCreateExpression.Arguments) { |
---|
1739 | var result = argument.AcceptVisitor(this, data); |
---|
1740 | if (result.ThrowsException) |
---|
1741 | return result; |
---|
1742 | data = result.Variables.Clone(); |
---|
1743 | } |
---|
1744 | |
---|
1745 | if (arrayCreateExpression.Initializer.IsNull) { |
---|
1746 | return HandleExpressionResult(arrayCreateExpression, data, NullValueStatus.DefinitelyNotNull); |
---|
1747 | } |
---|
1748 | |
---|
1749 | return HandleExpressionResult(arrayCreateExpression, arrayCreateExpression.Initializer.AcceptVisitor(this, data)); |
---|
1750 | } |
---|
1751 | |
---|
1752 | public override VisitorResult VisitArrayInitializerExpression(ArrayInitializerExpression arrayInitializerExpression, VariableStatusInfo data) |
---|
1753 | { |
---|
1754 | if (arrayInitializerExpression.IsSingleElement) { |
---|
1755 | return HandleExpressionResult(arrayInitializerExpression, arrayInitializerExpression.Elements.Single().AcceptVisitor(this, data)); |
---|
1756 | } |
---|
1757 | if (!arrayInitializerExpression.Elements.Any()) { |
---|
1758 | //Empty array |
---|
1759 | return HandleExpressionResult(arrayInitializerExpression, VisitorResult.ForValue(data, NullValueStatus.Unknown)); |
---|
1760 | } |
---|
1761 | |
---|
1762 | NullValueStatus enumeratedValue = NullValueStatus.UnreachableOrInexistent; |
---|
1763 | foreach (var element in arrayInitializerExpression.Elements) { |
---|
1764 | var result = element.AcceptVisitor(this, data); |
---|
1765 | if (result.ThrowsException) |
---|
1766 | return result; |
---|
1767 | data = result.Variables.Clone(); |
---|
1768 | enumeratedValue = VariableStatusInfo.CombineStatus(enumeratedValue, result.NullableReturnResult); |
---|
1769 | |
---|
1770 | } |
---|
1771 | return HandleExpressionResult(arrayInitializerExpression, VisitorResult.ForEnumeratedValue(data, enumeratedValue)); |
---|
1772 | } |
---|
1773 | |
---|
1774 | public override VisitorResult VisitAnonymousTypeCreateExpression(AnonymousTypeCreateExpression anonymousTypeCreateExpression, VariableStatusInfo data) |
---|
1775 | { |
---|
1776 | foreach (var initializer in anonymousTypeCreateExpression.Initializers) { |
---|
1777 | var result = initializer.AcceptVisitor(this, data); |
---|
1778 | if (result.ThrowsException) |
---|
1779 | return result; |
---|
1780 | data = result.Variables; |
---|
1781 | } |
---|
1782 | |
---|
1783 | return HandleExpressionResult(anonymousTypeCreateExpression, data, NullValueStatus.DefinitelyNotNull); |
---|
1784 | } |
---|
1785 | |
---|
1786 | public override VisitorResult VisitLambdaExpression(LambdaExpression lambdaExpression, VariableStatusInfo data) |
---|
1787 | { |
---|
1788 | var newData = data.Clone(); |
---|
1789 | |
---|
1790 | var identifiers = lambdaExpression.Descendants.OfType<IdentifierExpression>(); |
---|
1791 | foreach (var identifier in identifiers) { |
---|
1792 | //Check if it is in a "change-null-state" context |
---|
1793 | //For instance, x++ does not change the null state |
---|
1794 | //but `x = y` does. |
---|
1795 | if (identifier.Parent is AssignmentExpression && identifier.Role == AssignmentExpression.LeftRole) { |
---|
1796 | var parent = (AssignmentExpression)identifier.Parent; |
---|
1797 | if (parent.Operator != AssignmentOperatorType.Assign) { |
---|
1798 | continue; |
---|
1799 | } |
---|
1800 | } else { |
---|
1801 | //No other context matters |
---|
1802 | //Captured variables are never passed by reference (out/ref) |
---|
1803 | continue; |
---|
1804 | } |
---|
1805 | |
---|
1806 | //At this point, we know there's a good chance the variable has been changed |
---|
1807 | var identifierResolveResult = analysis.context.Resolve(identifier) as LocalResolveResult; |
---|
1808 | if (identifierResolveResult != null && IsTypeNullable(identifierResolveResult.Type)) { |
---|
1809 | analysis.SetLocalVariableValue(newData, identifier, NullValueStatus.CapturedUnknown); |
---|
1810 | } |
---|
1811 | } |
---|
1812 | |
---|
1813 | //The lambda itself is known not to be null |
---|
1814 | return HandleExpressionResult(lambdaExpression, newData, NullValueStatus.DefinitelyNotNull); |
---|
1815 | } |
---|
1816 | |
---|
1817 | public override VisitorResult VisitAnonymousMethodExpression(AnonymousMethodExpression anonymousMethodExpression, VariableStatusInfo data) |
---|
1818 | { |
---|
1819 | var newData = data.Clone(); |
---|
1820 | |
---|
1821 | var identifiers = anonymousMethodExpression.Descendants.OfType<IdentifierExpression>(); |
---|
1822 | foreach (var identifier in identifiers) { |
---|
1823 | //Check if it is in a "change-null-state" context |
---|
1824 | //For instance, x++ does not change the null state |
---|
1825 | //but `x = y` does. |
---|
1826 | if (identifier.Parent is AssignmentExpression && identifier.Role == AssignmentExpression.LeftRole) { |
---|
1827 | var parent = (AssignmentExpression)identifier.Parent; |
---|
1828 | if (parent.Operator != AssignmentOperatorType.Assign) { |
---|
1829 | continue; |
---|
1830 | } |
---|
1831 | } else { |
---|
1832 | //No other context matters |
---|
1833 | //Captured variables are never passed by reference (out/ref) |
---|
1834 | continue; |
---|
1835 | } |
---|
1836 | |
---|
1837 | //At this point, we know there's a good chance the variable has been changed |
---|
1838 | var identifierResolveResult = analysis.context.Resolve(identifier) as LocalResolveResult; |
---|
1839 | if (identifierResolveResult != null && IsTypeNullable(identifierResolveResult.Type)) { |
---|
1840 | analysis.SetLocalVariableValue(newData, identifier, NullValueStatus.CapturedUnknown); |
---|
1841 | } |
---|
1842 | } |
---|
1843 | |
---|
1844 | //The anonymous method itself is known not to be null |
---|
1845 | return HandleExpressionResult(anonymousMethodExpression, newData, NullValueStatus.DefinitelyNotNull); |
---|
1846 | } |
---|
1847 | |
---|
1848 | |
---|
1849 | public override VisitorResult VisitNamedExpression(NamedExpression namedExpression, VariableStatusInfo data) |
---|
1850 | { |
---|
1851 | return HandleExpressionResult(namedExpression, namedExpression.Expression.AcceptVisitor(this, data)); |
---|
1852 | } |
---|
1853 | |
---|
1854 | public override VisitorResult VisitAsExpression(AsExpression asExpression, VariableStatusInfo data) |
---|
1855 | { |
---|
1856 | var tentativeResult = asExpression.Expression.AcceptVisitor(this, data); |
---|
1857 | if (tentativeResult.ThrowsException) |
---|
1858 | return tentativeResult; |
---|
1859 | |
---|
1860 | NullValueStatus result; |
---|
1861 | if (tentativeResult.NullableReturnResult == NullValueStatus.DefinitelyNull) { |
---|
1862 | result = NullValueStatus.DefinitelyNull; |
---|
1863 | } else { |
---|
1864 | var asResolveResult = analysis.context.Resolve(asExpression) as CastResolveResult; |
---|
1865 | if (asResolveResult == null || |
---|
1866 | asResolveResult.IsError || |
---|
1867 | asResolveResult.Input.Type.Kind == TypeKind.Unknown || |
---|
1868 | asResolveResult.Type.Kind == TypeKind.Unknown) { |
---|
1869 | |
---|
1870 | result = NullValueStatus.Unknown; |
---|
1871 | } else { |
---|
1872 | var conversion = new CSharpConversions(analysis.context.Compilation); |
---|
1873 | var foundConversion = conversion.ExplicitConversion(asResolveResult.Input.Type, asResolveResult.Type); |
---|
1874 | |
---|
1875 | if (foundConversion == Conversion.None) { |
---|
1876 | result = NullValueStatus.DefinitelyNull; |
---|
1877 | } else if (foundConversion == Conversion.IdentityConversion) { |
---|
1878 | result = tentativeResult.NullableReturnResult; |
---|
1879 | } else { |
---|
1880 | result = NullValueStatus.PotentiallyNull; |
---|
1881 | } |
---|
1882 | } |
---|
1883 | } |
---|
1884 | return HandleExpressionResult(asExpression, tentativeResult.Variables, result); |
---|
1885 | } |
---|
1886 | |
---|
1887 | public override VisitorResult VisitCastExpression(CastExpression castExpression, VariableStatusInfo data) |
---|
1888 | { |
---|
1889 | var tentativeResult = castExpression.Expression.AcceptVisitor(this, data); |
---|
1890 | if (tentativeResult.ThrowsException) |
---|
1891 | return tentativeResult; |
---|
1892 | |
---|
1893 | NullValueStatus result; |
---|
1894 | if (tentativeResult.NullableReturnResult == NullValueStatus.DefinitelyNull) { |
---|
1895 | result = NullValueStatus.DefinitelyNull; |
---|
1896 | } else { |
---|
1897 | result = NullValueStatus.Unknown; |
---|
1898 | } |
---|
1899 | |
---|
1900 | VariableStatusInfo variables = tentativeResult.Variables; |
---|
1901 | |
---|
1902 | var resolveResult = analysis.context.Resolve(castExpression) as CastResolveResult; |
---|
1903 | if (resolveResult != null && !IsTypeNullable(resolveResult.Type)) { |
---|
1904 | if (result == NullValueStatus.DefinitelyNull) { |
---|
1905 | return HandleExpressionResult(castExpression, VisitorResult.ForException(tentativeResult.Variables)); |
---|
1906 | } |
---|
1907 | |
---|
1908 | var identifierExpression = CSharpUtil.GetInnerMostExpression(castExpression.Expression) as IdentifierExpression; |
---|
1909 | if (identifierExpression != null) { |
---|
1910 | var currentValue = variables [identifierExpression.Identifier]; |
---|
1911 | if (currentValue != NullValueStatus.CapturedUnknown && |
---|
1912 | currentValue != NullValueStatus.UnreachableOrInexistent && |
---|
1913 | currentValue != NullValueStatus.DefinitelyNotNull) { |
---|
1914 | //DefinitelyNotNull is included in this list because if that's the status |
---|
1915 | // then we don't need to change anything |
---|
1916 | |
---|
1917 | variables = variables.Clone(); |
---|
1918 | variables [identifierExpression.Identifier] = NullValueStatus.DefinitelyNotNull; |
---|
1919 | } |
---|
1920 | } |
---|
1921 | |
---|
1922 | result = NullValueStatus.DefinitelyNotNull; |
---|
1923 | } |
---|
1924 | |
---|
1925 | return HandleExpressionResult(castExpression, variables, result); |
---|
1926 | } |
---|
1927 | |
---|
1928 | public override VisitorResult VisitIsExpression(IsExpression isExpression, VariableStatusInfo data) |
---|
1929 | { |
---|
1930 | var tentativeResult = isExpression.Expression.AcceptVisitor(this, data); |
---|
1931 | if (tentativeResult.ThrowsException) |
---|
1932 | return tentativeResult; |
---|
1933 | |
---|
1934 | //TODO: Consider, for instance: new X() is X. The result is known to be true, so we can use KnownBoolValue |
---|
1935 | return HandleExpressionResult(isExpression, tentativeResult.Variables, NullValueStatus.DefinitelyNotNull); |
---|
1936 | } |
---|
1937 | |
---|
1938 | public override VisitorResult VisitDirectionExpression(DirectionExpression directionExpression, VariableStatusInfo data) |
---|
1939 | { |
---|
1940 | return HandleExpressionResult(directionExpression, directionExpression.Expression.AcceptVisitor(this, data)); |
---|
1941 | } |
---|
1942 | |
---|
1943 | public override VisitorResult VisitCheckedExpression(CheckedExpression checkedExpression, VariableStatusInfo data) |
---|
1944 | { |
---|
1945 | return HandleExpressionResult(checkedExpression, checkedExpression.Expression.AcceptVisitor(this, data)); |
---|
1946 | } |
---|
1947 | |
---|
1948 | public override VisitorResult VisitUncheckedExpression(UncheckedExpression uncheckedExpression, VariableStatusInfo data) |
---|
1949 | { |
---|
1950 | return HandleExpressionResult(uncheckedExpression, uncheckedExpression.Expression.AcceptVisitor(this, data)); |
---|
1951 | } |
---|
1952 | |
---|
1953 | public override VisitorResult VisitThisReferenceExpression(ThisReferenceExpression thisReferenceExpression, VariableStatusInfo data) |
---|
1954 | { |
---|
1955 | return HandleExpressionResult(thisReferenceExpression, data, NullValueStatus.DefinitelyNotNull); |
---|
1956 | } |
---|
1957 | |
---|
1958 | public override VisitorResult VisitIndexerExpression(IndexerExpression indexerExpression, VariableStatusInfo data) |
---|
1959 | { |
---|
1960 | var tentativeResult = indexerExpression.Target.AcceptVisitor(this, data); |
---|
1961 | if (tentativeResult.ThrowsException) |
---|
1962 | return tentativeResult; |
---|
1963 | |
---|
1964 | data = tentativeResult.Variables; |
---|
1965 | |
---|
1966 | foreach (var argument in indexerExpression.Arguments) { |
---|
1967 | var result = argument.AcceptVisitor(this, data); |
---|
1968 | if (result.ThrowsException) |
---|
1969 | return result; |
---|
1970 | data = result.Variables.Clone(); |
---|
1971 | } |
---|
1972 | |
---|
1973 | IdentifierExpression targetAsIdentifier = CSharpUtil.GetInnerMostExpression(indexerExpression.Target) as IdentifierExpression; |
---|
1974 | if (targetAsIdentifier != null) { |
---|
1975 | if (tentativeResult.NullableReturnResult == NullValueStatus.DefinitelyNull) |
---|
1976 | return HandleExpressionResult(indexerExpression, VisitorResult.ForException(data)); |
---|
1977 | |
---|
1978 | //If this doesn't cause an exception, then the target is not null |
---|
1979 | //But we won't set it if it has been changed |
---|
1980 | var descendentIdentifiers = indexerExpression.Arguments |
---|
1981 | .SelectMany(argument => argument.DescendantsAndSelf).OfType<IdentifierExpression>(); |
---|
1982 | if (!descendentIdentifiers.Any(identifier => identifier.Identifier == targetAsIdentifier.Identifier)) { |
---|
1983 | //TODO: this check might be improved to include more legitimate cases |
---|
1984 | //A good check will necessarily have to consider captured variables |
---|
1985 | data = data.Clone(); |
---|
1986 | analysis.SetLocalVariableValue(data, targetAsIdentifier, NullValueStatus.DefinitelyNotNull); |
---|
1987 | } |
---|
1988 | } |
---|
1989 | |
---|
1990 | var indexerResolveResult = analysis.context.Resolve(indexerExpression) as CSharpInvocationResolveResult; |
---|
1991 | bool isNullable = indexerResolveResult == null || IsTypeNullable(indexerResolveResult.Type); |
---|
1992 | |
---|
1993 | var returnValue = isNullable ? NullValueStatus.Unknown : NullValueStatus.DefinitelyNotNull; |
---|
1994 | return HandleExpressionResult(indexerExpression, data, returnValue); |
---|
1995 | } |
---|
1996 | |
---|
1997 | public override VisitorResult VisitBaseReferenceExpression(BaseReferenceExpression baseReferenceExpression, VariableStatusInfo data) |
---|
1998 | { |
---|
1999 | return HandleExpressionResult(baseReferenceExpression, data, NullValueStatus.DefinitelyNotNull); |
---|
2000 | } |
---|
2001 | |
---|
2002 | public override VisitorResult VisitTypeOfExpression(TypeOfExpression typeOfExpression, VariableStatusInfo data) |
---|
2003 | { |
---|
2004 | return HandleExpressionResult(typeOfExpression, data, NullValueStatus.DefinitelyNotNull); |
---|
2005 | } |
---|
2006 | |
---|
2007 | public override VisitorResult VisitSizeOfExpression(SizeOfExpression sizeOfExpression, VariableStatusInfo data) |
---|
2008 | { |
---|
2009 | return HandleExpressionResult(sizeOfExpression, data, NullValueStatus.DefinitelyNotNull); |
---|
2010 | } |
---|
2011 | |
---|
2012 | public override VisitorResult VisitPointerReferenceExpression(PointerReferenceExpression pointerReferenceExpression, VariableStatusInfo data) |
---|
2013 | { |
---|
2014 | var targetResult = pointerReferenceExpression.Target.AcceptVisitor(this, data); |
---|
2015 | if (targetResult.ThrowsException) |
---|
2016 | return targetResult; |
---|
2017 | return HandleExpressionResult(pointerReferenceExpression, targetResult.Variables, NullValueStatus.DefinitelyNotNull); |
---|
2018 | } |
---|
2019 | |
---|
2020 | public override VisitorResult VisitStackAllocExpression(StackAllocExpression stackAllocExpression, VariableStatusInfo data) |
---|
2021 | { |
---|
2022 | var countResult = stackAllocExpression.CountExpression.AcceptVisitor(this, data); |
---|
2023 | if (countResult.ThrowsException) |
---|
2024 | return countResult; |
---|
2025 | return HandleExpressionResult(stackAllocExpression, countResult.Variables, NullValueStatus.DefinitelyNotNull); |
---|
2026 | } |
---|
2027 | |
---|
2028 | public override VisitorResult VisitNamedArgumentExpression(NamedArgumentExpression namedArgumentExpression, VariableStatusInfo data) |
---|
2029 | { |
---|
2030 | return HandleExpressionResult(namedArgumentExpression, namedArgumentExpression.Expression.AcceptVisitor(this, data)); |
---|
2031 | } |
---|
2032 | |
---|
2033 | public override VisitorResult VisitUndocumentedExpression(UndocumentedExpression undocumentedExpression, VariableStatusInfo data) |
---|
2034 | { |
---|
2035 | throw new NotImplementedException(); |
---|
2036 | } |
---|
2037 | |
---|
2038 | public override VisitorResult VisitQueryExpression(QueryExpression queryExpression, VariableStatusInfo data) |
---|
2039 | { |
---|
2040 | VariableStatusInfo outgoingData = data.Clone(); |
---|
2041 | NullValueStatus? outgoingEnumeratedValue = null; |
---|
2042 | var clauses = queryExpression.Clauses.ToList(); |
---|
2043 | |
---|
2044 | var backtracingClauses = (from item in clauses.Select((clause, i) => new { clause, i }) |
---|
2045 | where item.clause is QueryFromClause || item.clause is QueryJoinClause || item.clause is QueryContinuationClause |
---|
2046 | select item.i).ToList(); |
---|
2047 | |
---|
2048 | var beforeClauseVariableStates = Enumerable.Range(0, clauses.Count).ToDictionary(clauseIndex => clauseIndex, |
---|
2049 | clauseIndex => new VariableStatusInfo()); |
---|
2050 | var afterClauseVariableStates = Enumerable.Range(0, clauses.Count).ToDictionary(clauseIndex => clauseIndex, |
---|
2051 | clauseIndex => new VariableStatusInfo()); |
---|
2052 | |
---|
2053 | VisitorResult lastValidResult = null; |
---|
2054 | int currentClauseIndex = 0; |
---|
2055 | for (;;) { |
---|
2056 | VisitorResult result = null; |
---|
2057 | QueryClause clause = null; |
---|
2058 | bool backtrack = false; |
---|
2059 | |
---|
2060 | if (currentClauseIndex >= clauses.Count) { |
---|
2061 | backtrack = true; |
---|
2062 | } else { |
---|
2063 | clause = clauses [currentClauseIndex]; |
---|
2064 | beforeClauseVariableStates [currentClauseIndex].ReceiveIncoming(data); |
---|
2065 | result = clause.AcceptVisitor(this, data); |
---|
2066 | data = result.Variables; |
---|
2067 | lastValidResult = result; |
---|
2068 | if (result.KnownBoolResult == false) { |
---|
2069 | backtrack = true; |
---|
2070 | } |
---|
2071 | if (result.ThrowsException) { |
---|
2072 | //Don't backtrack. Exceptions completely stop the query. |
---|
2073 | break; |
---|
2074 | } |
---|
2075 | else { |
---|
2076 | afterClauseVariableStates [currentClauseIndex].ReceiveIncoming(data); |
---|
2077 | } |
---|
2078 | } |
---|
2079 | |
---|
2080 | if (backtrack) { |
---|
2081 | int? newIndex; |
---|
2082 | for (;;) { |
---|
2083 | newIndex = backtracingClauses.LastOrDefault(index => index < currentClauseIndex); |
---|
2084 | if (newIndex == null) { |
---|
2085 | //We've reached the end |
---|
2086 | break; |
---|
2087 | } |
---|
2088 | |
---|
2089 | currentClauseIndex = (int)newIndex + 1; |
---|
2090 | |
---|
2091 | if (!beforeClauseVariableStates[currentClauseIndex].ReceiveIncoming(lastValidResult.Variables)) { |
---|
2092 | newIndex = null; |
---|
2093 | break; |
---|
2094 | } |
---|
2095 | } |
---|
2096 | |
---|
2097 | if (newIndex == null) { |
---|
2098 | break; |
---|
2099 | } |
---|
2100 | |
---|
2101 | } else { |
---|
2102 | if (clause is QuerySelectClause) { |
---|
2103 | outgoingData.ReceiveIncoming(data); |
---|
2104 | if (outgoingEnumeratedValue == null) |
---|
2105 | outgoingEnumeratedValue = result.EnumeratedValueResult; |
---|
2106 | else |
---|
2107 | outgoingEnumeratedValue = VariableStatusInfo.CombineStatus(outgoingEnumeratedValue.Value, result.EnumeratedValueResult); |
---|
2108 | } |
---|
2109 | |
---|
2110 | ++currentClauseIndex; |
---|
2111 | } |
---|
2112 | } |
---|
2113 | |
---|
2114 | var finalData = new VariableStatusInfo(); |
---|
2115 | var endingClauseIndices = from item in clauses.Select((clause, i) => new { clause, i }) |
---|
2116 | let clause = item.clause |
---|
2117 | where clause is QueryFromClause || |
---|
2118 | clause is QueryContinuationClause || |
---|
2119 | clause is QueryJoinClause || |
---|
2120 | clause is QuerySelectClause || |
---|
2121 | clause is QueryWhereClause |
---|
2122 | select item.i; |
---|
2123 | foreach (var clauseIndex in endingClauseIndices) { |
---|
2124 | finalData.ReceiveIncoming(afterClauseVariableStates [clauseIndex]); |
---|
2125 | } |
---|
2126 | |
---|
2127 | return VisitorResult.ForEnumeratedValue(finalData, outgoingEnumeratedValue ?? NullValueStatus.Unknown); |
---|
2128 | } |
---|
2129 | |
---|
2130 | public override VisitorResult VisitQueryContinuationClause(QueryContinuationClause queryContinuationClause, VariableStatusInfo data) |
---|
2131 | { |
---|
2132 | return IntroduceVariableFromEnumeratedValue(queryContinuationClause.Identifier, queryContinuationClause.PrecedingQuery, data); |
---|
2133 | } |
---|
2134 | |
---|
2135 | VisitorResult IntroduceVariableFromEnumeratedValue(string newVariable, Expression expression, VariableStatusInfo data) |
---|
2136 | { |
---|
2137 | var result = expression.AcceptVisitor(this, data); |
---|
2138 | var newVariables = result.Variables.Clone(); |
---|
2139 | newVariables[newVariable] = result.EnumeratedValueResult; |
---|
2140 | return VisitorResult.ForValue(newVariables, NullValueStatus.Unknown); |
---|
2141 | } |
---|
2142 | |
---|
2143 | public override VisitorResult VisitQueryFromClause(QueryFromClause queryFromClause, VariableStatusInfo data) |
---|
2144 | { |
---|
2145 | return IntroduceVariableFromEnumeratedValue(queryFromClause.Identifier, queryFromClause.Expression, data); |
---|
2146 | } |
---|
2147 | |
---|
2148 | public override VisitorResult VisitQueryJoinClause(QueryJoinClause queryJoinClause, VariableStatusInfo data) |
---|
2149 | { |
---|
2150 | //TODO: Check if this really works in weird edge-cases. |
---|
2151 | var tentativeResult = IntroduceVariableFromEnumeratedValue(queryJoinClause.JoinIdentifier, queryJoinClause.InExpression, data); |
---|
2152 | tentativeResult = queryJoinClause.OnExpression.AcceptVisitor(this, tentativeResult.Variables); |
---|
2153 | tentativeResult = queryJoinClause.EqualsExpression.AcceptVisitor(this, tentativeResult.Variables); |
---|
2154 | |
---|
2155 | if (queryJoinClause.IsGroupJoin) { |
---|
2156 | var newVariables = tentativeResult.Variables.Clone(); |
---|
2157 | analysis.SetLocalVariableValue(newVariables, queryJoinClause.IntoIdentifierToken, NullValueStatus.DefinitelyNotNull); |
---|
2158 | return VisitorResult.ForValue(newVariables, NullValueStatus.Unknown); |
---|
2159 | } |
---|
2160 | |
---|
2161 | return tentativeResult; |
---|
2162 | } |
---|
2163 | |
---|
2164 | public override VisitorResult VisitQueryLetClause(QueryLetClause queryLetClause, VariableStatusInfo data) |
---|
2165 | { |
---|
2166 | var result = queryLetClause.Expression.AcceptVisitor(this, data); |
---|
2167 | |
---|
2168 | string newVariable = queryLetClause.Identifier; |
---|
2169 | var newVariables = result.Variables.Clone(); |
---|
2170 | newVariables [newVariable] = result.NullableReturnResult; |
---|
2171 | |
---|
2172 | return VisitorResult.ForValue(newVariables, NullValueStatus.Unknown); |
---|
2173 | } |
---|
2174 | |
---|
2175 | public override VisitorResult VisitQuerySelectClause(QuerySelectClause querySelectClause, VariableStatusInfo data) |
---|
2176 | { |
---|
2177 | var result = querySelectClause.Expression.AcceptVisitor(this, data); |
---|
2178 | |
---|
2179 | //The value of the expression in select becomes the "enumerated" value |
---|
2180 | return VisitorResult.ForEnumeratedValue(result.Variables, result.NullableReturnResult); |
---|
2181 | } |
---|
2182 | |
---|
2183 | public override VisitorResult VisitQueryWhereClause(QueryWhereClause queryWhereClause, VariableStatusInfo data) |
---|
2184 | { |
---|
2185 | var result = queryWhereClause.Condition.AcceptVisitor(this, data); |
---|
2186 | |
---|
2187 | return VisitorResult.ForEnumeratedValue(result.TruePathVariables, NullValueStatus.Unknown); |
---|
2188 | } |
---|
2189 | |
---|
2190 | public override VisitorResult VisitQueryOrderClause(QueryOrderClause queryOrderClause, VariableStatusInfo data) |
---|
2191 | { |
---|
2192 | foreach (var ordering in queryOrderClause.Orderings) { |
---|
2193 | data = ordering.AcceptVisitor(this, data).Variables; |
---|
2194 | } |
---|
2195 | |
---|
2196 | return VisitorResult.ForValue(data, NullValueStatus.Unknown); |
---|
2197 | } |
---|
2198 | |
---|
2199 | public override VisitorResult VisitQueryOrdering(QueryOrdering queryOrdering, VariableStatusInfo data) |
---|
2200 | { |
---|
2201 | return VisitorResult.ForValue(queryOrdering.Expression.AcceptVisitor(this, data).Variables, NullValueStatus.Unknown); |
---|
2202 | } |
---|
2203 | |
---|
2204 | public override VisitorResult VisitQueryGroupClause(QueryGroupClause queryGroupClause, VariableStatusInfo data) |
---|
2205 | { |
---|
2206 | var projectionResult = queryGroupClause.Projection.AcceptVisitor(this, data); |
---|
2207 | data = projectionResult.Variables; |
---|
2208 | data = queryGroupClause.Key.AcceptVisitor(this, data).Variables; |
---|
2209 | |
---|
2210 | return VisitorResult.ForEnumeratedValue(data, projectionResult.NullableReturnResult); |
---|
2211 | } |
---|
2212 | } |
---|
2213 | } |
---|
2214 | } |
---|
2215 | |
---|