/NRefactory/ICSharpCode.NRefactory.CSharp/Analysis/DefiniteAssignmentAnalysis.cs

http://github.com/icsharpcode/ILSpy · C# · 759 lines · 581 code · 68 blank · 110 comment · 220 complexity · e51e36fc01486cc45869364c81e42fa4 MD5 · raw file

  1. // Copyright (c) 2010-2013 AlphaSierraPapa for the SharpDevelop Team
  2. //
  3. // Permission is hereby granted, free of charge, to any person obtaining a copy of this
  4. // software and associated documentation files (the "Software"), to deal in the Software
  5. // without restriction, including without limitation the rights to use, copy, modify, merge,
  6. // publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
  7. // to whom the Software is furnished to do so, subject to the following conditions:
  8. //
  9. // The above copyright notice and this permission notice shall be included in all copies or
  10. // substantial portions of the Software.
  11. //
  12. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
  13. // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
  14. // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
  15. // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  16. // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  17. // DEALINGS IN THE SOFTWARE.
  18. using System;
  19. using System.Collections.Generic;
  20. using System.Diagnostics;
  21. using System.Linq;
  22. using System.Threading;
  23. using ICSharpCode.NRefactory.CSharp.Resolver;
  24. using ICSharpCode.NRefactory.Semantics;
  25. using ICSharpCode.NRefactory.TypeSystem;
  26. using ICSharpCode.NRefactory.TypeSystem.Implementation;
  27. using ICSharpCode.NRefactory.Utils;
  28. namespace ICSharpCode.NRefactory.CSharp.Analysis
  29. {
  30. /// <summary>
  31. /// Represents the definite assignment status of a variable at a specific location.
  32. /// </summary>
  33. public enum DefiniteAssignmentStatus
  34. {
  35. /// <summary>
  36. /// The variable might be assigned or unassigned.
  37. /// </summary>
  38. PotentiallyAssigned,
  39. /// <summary>
  40. /// The variable is definitely assigned.
  41. /// </summary>
  42. DefinitelyAssigned,
  43. /// <summary>
  44. /// The variable is definitely assigned iff the expression results in the value 'true'.
  45. /// </summary>
  46. AssignedAfterTrueExpression,
  47. /// <summary>
  48. /// The variable is definitely assigned iff the expression results in the value 'false'.
  49. /// </summary>
  50. AssignedAfterFalseExpression,
  51. /// <summary>
  52. /// The code is unreachable.
  53. /// </summary>
  54. CodeUnreachable
  55. }
  56. /// <summary>
  57. /// Implements the C# definite assignment analysis (C# 4.0 Spec: §5.3 Definite assignment)
  58. /// </summary>
  59. public class DefiniteAssignmentAnalysis
  60. {
  61. sealed class DefiniteAssignmentNode : ControlFlowNode
  62. {
  63. public int Index;
  64. public DefiniteAssignmentStatus NodeStatus;
  65. public DefiniteAssignmentNode(Statement previousStatement, Statement nextStatement, ControlFlowNodeType type)
  66. : base(previousStatement, nextStatement, type)
  67. {
  68. }
  69. }
  70. sealed class DerivedControlFlowGraphBuilder : ControlFlowGraphBuilder
  71. {
  72. protected override ControlFlowNode CreateNode(Statement previousStatement, Statement nextStatement, ControlFlowNodeType type)
  73. {
  74. return new DefiniteAssignmentNode(previousStatement, nextStatement, type);
  75. }
  76. }
  77. readonly DefiniteAssignmentVisitor visitor = new DefiniteAssignmentVisitor();
  78. readonly List<DefiniteAssignmentNode> allNodes = new List<DefiniteAssignmentNode>();
  79. readonly Dictionary<Statement, DefiniteAssignmentNode> beginNodeDict = new Dictionary<Statement, DefiniteAssignmentNode>();
  80. readonly Dictionary<Statement, DefiniteAssignmentNode> endNodeDict = new Dictionary<Statement, DefiniteAssignmentNode>();
  81. readonly Dictionary<Statement, DefiniteAssignmentNode> conditionNodeDict = new Dictionary<Statement, DefiniteAssignmentNode>();
  82. readonly CSharpAstResolver resolver;
  83. Dictionary<ControlFlowEdge, DefiniteAssignmentStatus> edgeStatus = new Dictionary<ControlFlowEdge, DefiniteAssignmentStatus>();
  84. string variableName;
  85. List<IdentifierExpression> unassignedVariableUses = new List<IdentifierExpression>();
  86. int analyzedRangeStart, analyzedRangeEnd;
  87. CancellationToken analysisCancellationToken;
  88. Queue<DefiniteAssignmentNode> nodesWithModifiedInput = new Queue<DefiniteAssignmentNode>();
  89. public DefiniteAssignmentAnalysis(Statement rootStatement, CancellationToken cancellationToken)
  90. : this(rootStatement,
  91. new CSharpAstResolver(new CSharpResolver(MinimalCorlib.Instance.CreateCompilation()), rootStatement),
  92. cancellationToken)
  93. {
  94. }
  95. public DefiniteAssignmentAnalysis(Statement rootStatement, CSharpAstResolver resolver, CancellationToken cancellationToken)
  96. {
  97. if (rootStatement == null)
  98. throw new ArgumentNullException("rootStatement");
  99. if (resolver == null)
  100. throw new ArgumentNullException("resolver");
  101. this.resolver = resolver;
  102. visitor.analysis = this;
  103. DerivedControlFlowGraphBuilder cfgBuilder = new DerivedControlFlowGraphBuilder();
  104. if (resolver.TypeResolveContext.Compilation.MainAssembly.UnresolvedAssembly is MinimalCorlib) {
  105. cfgBuilder.EvaluateOnlyPrimitiveConstants = true;
  106. }
  107. allNodes.AddRange(cfgBuilder.BuildControlFlowGraph(rootStatement, resolver, cancellationToken).Cast<DefiniteAssignmentNode>());
  108. for (int i = 0; i < allNodes.Count; i++) {
  109. DefiniteAssignmentNode node = allNodes[i];
  110. node.Index = i; // assign numbers to the nodes
  111. if (node.Type == ControlFlowNodeType.StartNode || node.Type == ControlFlowNodeType.BetweenStatements) {
  112. // Anonymous methods have separate control flow graphs, but we also need to analyze those.
  113. // Iterate backwards so that anonymous methods are inserted in the correct order
  114. for (AstNode child = node.NextStatement.LastChild; child != null; child = child.PrevSibling) {
  115. InsertAnonymousMethods(i + 1, child, cfgBuilder, cancellationToken);
  116. }
  117. }
  118. // Now register the node in the dictionaries:
  119. if (node.Type == ControlFlowNodeType.StartNode || node.Type == ControlFlowNodeType.BetweenStatements)
  120. beginNodeDict.Add(node.NextStatement, node);
  121. if (node.Type == ControlFlowNodeType.BetweenStatements || node.Type == ControlFlowNodeType.EndNode)
  122. endNodeDict.Add(node.PreviousStatement, node);
  123. if (node.Type == ControlFlowNodeType.LoopCondition)
  124. conditionNodeDict.Add(node.NextStatement, node);
  125. }
  126. // Verify that we created nodes for all statements:
  127. Debug.Assert(!rootStatement.DescendantsAndSelf.OfType<Statement>().Except(allNodes.Select(n => n.NextStatement)).Any());
  128. // Verify that we put all nodes into the dictionaries:
  129. Debug.Assert(rootStatement.DescendantsAndSelf.OfType<Statement>().All(stmt => beginNodeDict.ContainsKey(stmt)));
  130. Debug.Assert(rootStatement.DescendantsAndSelf.OfType<Statement>().All(stmt => endNodeDict.ContainsKey(stmt)));
  131. this.analyzedRangeStart = 0;
  132. this.analyzedRangeEnd = allNodes.Count - 1;
  133. }
  134. void InsertAnonymousMethods(int insertPos, AstNode node, ControlFlowGraphBuilder cfgBuilder, CancellationToken cancellationToken)
  135. {
  136. // Ignore any statements, as those have their own ControlFlowNode and get handled separately
  137. if (node is Statement)
  138. return;
  139. AnonymousMethodExpression ame = node as AnonymousMethodExpression;
  140. if (ame != null) {
  141. allNodes.InsertRange(insertPos, cfgBuilder.BuildControlFlowGraph(ame.Body, resolver, cancellationToken).Cast<DefiniteAssignmentNode>());
  142. return;
  143. }
  144. LambdaExpression lambda = node as LambdaExpression;
  145. if (lambda != null && lambda.Body is Statement) {
  146. allNodes.InsertRange(insertPos, cfgBuilder.BuildControlFlowGraph((Statement)lambda.Body, resolver, cancellationToken).Cast<DefiniteAssignmentNode>());
  147. return;
  148. }
  149. // Descend into child expressions
  150. // Iterate backwards so that anonymous methods are inserted in the correct order
  151. for (AstNode child = node.LastChild; child != null; child = child.PrevSibling) {
  152. InsertAnonymousMethods(insertPos, child, cfgBuilder, cancellationToken);
  153. }
  154. }
  155. /// <summary>
  156. /// Gets the unassigned usages of the previously analyzed variable.
  157. /// </summary>
  158. public IList<IdentifierExpression> UnassignedVariableUses {
  159. get {
  160. return unassignedVariableUses.AsReadOnly();
  161. }
  162. }
  163. /// <summary>
  164. /// Sets the range of statements to be analyzed.
  165. /// This method can be used to restrict the analysis to only a part of the method.
  166. /// Only the control flow paths that are fully contained within the selected part will be analyzed.
  167. /// </summary>
  168. /// <remarks>By default, both 'start' and 'end' are inclusive.</remarks>
  169. public void SetAnalyzedRange(Statement start, Statement end, bool startInclusive = true, bool endInclusive = true)
  170. {
  171. var dictForStart = startInclusive ? beginNodeDict : endNodeDict;
  172. var dictForEnd = endInclusive ? endNodeDict : beginNodeDict;
  173. Debug.Assert(dictForStart.ContainsKey(start) && dictForEnd.ContainsKey(end));
  174. int startIndex = dictForStart[start].Index;
  175. int endIndex = dictForEnd[end].Index;
  176. if (startIndex > endIndex)
  177. throw new ArgumentException("The start statement must be lexically preceding the end statement");
  178. this.analyzedRangeStart = startIndex;
  179. this.analyzedRangeEnd = endIndex;
  180. }
  181. public void Analyze(string variable, DefiniteAssignmentStatus initialStatus = DefiniteAssignmentStatus.PotentiallyAssigned, CancellationToken cancellationToken = default(CancellationToken))
  182. {
  183. this.analysisCancellationToken = cancellationToken;
  184. this.variableName = variable;
  185. try {
  186. // Reset the status:
  187. unassignedVariableUses.Clear();
  188. foreach (DefiniteAssignmentNode node in allNodes) {
  189. node.NodeStatus = DefiniteAssignmentStatus.CodeUnreachable;
  190. foreach (ControlFlowEdge edge in node.Outgoing)
  191. edgeStatus[edge] = DefiniteAssignmentStatus.CodeUnreachable;
  192. }
  193. ChangeNodeStatus(allNodes[analyzedRangeStart], initialStatus);
  194. // Iterate as long as the input status of some nodes is changing:
  195. while (nodesWithModifiedInput.Count > 0) {
  196. DefiniteAssignmentNode node = nodesWithModifiedInput.Dequeue();
  197. DefiniteAssignmentStatus inputStatus = DefiniteAssignmentStatus.CodeUnreachable;
  198. foreach (ControlFlowEdge edge in node.Incoming) {
  199. inputStatus = MergeStatus(inputStatus, edgeStatus[edge]);
  200. }
  201. ChangeNodeStatus(node, inputStatus);
  202. }
  203. } finally {
  204. this.analysisCancellationToken = CancellationToken.None;
  205. this.variableName = null;
  206. }
  207. }
  208. public DefiniteAssignmentStatus GetStatusBefore(Statement statement)
  209. {
  210. return beginNodeDict[statement].NodeStatus;
  211. }
  212. public DefiniteAssignmentStatus GetStatusAfter(Statement statement)
  213. {
  214. return endNodeDict[statement].NodeStatus;
  215. }
  216. public DefiniteAssignmentStatus GetStatusBeforeLoopCondition(Statement statement)
  217. {
  218. return conditionNodeDict[statement].NodeStatus;
  219. }
  220. /// <summary>
  221. /// Exports the CFG. This method is intended to help debugging issues related to definite assignment.
  222. /// </summary>
  223. public GraphVizGraph ExportGraph()
  224. {
  225. GraphVizGraph g = new GraphVizGraph();
  226. g.Title = "DefiniteAssignment - " + variableName;
  227. for (int i = 0; i < allNodes.Count; i++) {
  228. string name = "#" + i + " = " + allNodes[i].NodeStatus.ToString() + Environment.NewLine;
  229. switch (allNodes[i].Type) {
  230. case ControlFlowNodeType.StartNode:
  231. case ControlFlowNodeType.BetweenStatements:
  232. name += allNodes[i].NextStatement.ToString();
  233. break;
  234. case ControlFlowNodeType.EndNode:
  235. name += "End of " + allNodes[i].PreviousStatement.ToString();
  236. break;
  237. case ControlFlowNodeType.LoopCondition:
  238. name += "Condition in " + allNodes[i].NextStatement.ToString();
  239. break;
  240. default:
  241. name += allNodes[i].Type.ToString();
  242. break;
  243. }
  244. g.AddNode(new GraphVizNode(i) { label = name });
  245. foreach (ControlFlowEdge edge in allNodes[i].Outgoing) {
  246. GraphVizEdge ge = new GraphVizEdge(i, ((DefiniteAssignmentNode)edge.To).Index);
  247. if (edgeStatus.Count > 0)
  248. ge.label = edgeStatus[edge].ToString();
  249. if (edge.IsLeavingTryFinally)
  250. ge.style = "dashed";
  251. switch (edge.Type) {
  252. case ControlFlowEdgeType.ConditionTrue:
  253. ge.color = "green";
  254. break;
  255. case ControlFlowEdgeType.ConditionFalse:
  256. ge.color = "red";
  257. break;
  258. case ControlFlowEdgeType.Jump:
  259. ge.color = "blue";
  260. break;
  261. }
  262. g.AddEdge(ge);
  263. }
  264. }
  265. return g;
  266. }
  267. static DefiniteAssignmentStatus MergeStatus(DefiniteAssignmentStatus a, DefiniteAssignmentStatus b)
  268. {
  269. // The result will be DefinitelyAssigned if at least one incoming edge is DefinitelyAssigned and all others are unreachable.
  270. // The result will be DefinitelyUnassigned if at least one incoming edge is DefinitelyUnassigned and all others are unreachable.
  271. // The result will be Unreachable if all incoming edges are unreachable.
  272. // Otherwise, the result will be PotentiallyAssigned.
  273. if (a == b)
  274. return a;
  275. else if (a == DefiniteAssignmentStatus.CodeUnreachable)
  276. return b;
  277. else if (b == DefiniteAssignmentStatus.CodeUnreachable)
  278. return a;
  279. else
  280. return DefiniteAssignmentStatus.PotentiallyAssigned;
  281. }
  282. void ChangeNodeStatus (DefiniteAssignmentNode node, DefiniteAssignmentStatus inputStatus)
  283. {
  284. if (node.NodeStatus == inputStatus)
  285. return;
  286. node.NodeStatus = inputStatus;
  287. DefiniteAssignmentStatus outputStatus;
  288. switch (node.Type) {
  289. case ControlFlowNodeType.StartNode:
  290. case ControlFlowNodeType.BetweenStatements:
  291. if (node.NextStatement is IfElseStatement) {
  292. // Handle if-else as a condition node
  293. goto case ControlFlowNodeType.LoopCondition;
  294. }
  295. if (inputStatus == DefiniteAssignmentStatus.DefinitelyAssigned) {
  296. // There isn't any way to un-assign variables, so we don't have to check the expression
  297. // if the status already is definitely assigned.
  298. outputStatus = DefiniteAssignmentStatus.DefinitelyAssigned;
  299. } else {
  300. outputStatus = CleanSpecialValues (node.NextStatement.AcceptVisitor (visitor, inputStatus));
  301. }
  302. break;
  303. case ControlFlowNodeType.EndNode:
  304. outputStatus = inputStatus;
  305. if (node.PreviousStatement.Role == TryCatchStatement.FinallyBlockRole
  306. && (outputStatus == DefiniteAssignmentStatus.DefinitelyAssigned || outputStatus == DefiniteAssignmentStatus.PotentiallyAssigned)) {
  307. TryCatchStatement tryFinally = (TryCatchStatement)node.PreviousStatement.Parent;
  308. // Changing the status on a finally block potentially changes the status of all edges leaving that finally block:
  309. foreach (ControlFlowEdge edge in allNodes.SelectMany(n => n.Outgoing)) {
  310. if (edge.IsLeavingTryFinally && edge.TryFinallyStatements.Contains (tryFinally)) {
  311. DefiniteAssignmentStatus s = edgeStatus [edge];
  312. if (s == DefiniteAssignmentStatus.PotentiallyAssigned) {
  313. ChangeEdgeStatus (edge, outputStatus);
  314. }
  315. }
  316. }
  317. }
  318. break;
  319. case ControlFlowNodeType.LoopCondition:
  320. ForeachStatement foreachStmt = node.NextStatement as ForeachStatement;
  321. if (foreachStmt != null) {
  322. outputStatus = CleanSpecialValues (foreachStmt.InExpression.AcceptVisitor (visitor, inputStatus));
  323. if (foreachStmt.VariableName == this.variableName)
  324. outputStatus = DefiniteAssignmentStatus.DefinitelyAssigned;
  325. break;
  326. } else {
  327. Debug.Assert (node.NextStatement is IfElseStatement || node.NextStatement is WhileStatement || node.NextStatement is ForStatement || node.NextStatement is DoWhileStatement);
  328. Expression condition = node.NextStatement.GetChildByRole (Roles.Condition);
  329. if (condition.IsNull)
  330. outputStatus = inputStatus;
  331. else
  332. outputStatus = condition.AcceptVisitor(visitor, inputStatus);
  333. foreach (ControlFlowEdge edge in node.Outgoing) {
  334. if (edge.Type == ControlFlowEdgeType.ConditionTrue && outputStatus == DefiniteAssignmentStatus.AssignedAfterTrueExpression) {
  335. ChangeEdgeStatus(edge, DefiniteAssignmentStatus.DefinitelyAssigned);
  336. } else if (edge.Type == ControlFlowEdgeType.ConditionFalse && outputStatus == DefiniteAssignmentStatus.AssignedAfterFalseExpression) {
  337. ChangeEdgeStatus(edge, DefiniteAssignmentStatus.DefinitelyAssigned);
  338. } else {
  339. ChangeEdgeStatus(edge, CleanSpecialValues(outputStatus));
  340. }
  341. }
  342. return;
  343. }
  344. default:
  345. throw new InvalidOperationException();
  346. }
  347. foreach (ControlFlowEdge edge in node.Outgoing) {
  348. ChangeEdgeStatus(edge, outputStatus);
  349. }
  350. }
  351. void ChangeEdgeStatus(ControlFlowEdge edge, DefiniteAssignmentStatus newStatus)
  352. {
  353. DefiniteAssignmentStatus oldStatus = edgeStatus[edge];
  354. if (oldStatus == newStatus)
  355. return;
  356. // Ensure that status can cannot change back to CodeUnreachable after it once was reachable.
  357. // Also, don't ever use AssignedAfter... for statements.
  358. if (newStatus == DefiniteAssignmentStatus.CodeUnreachable
  359. || newStatus == DefiniteAssignmentStatus.AssignedAfterFalseExpression
  360. || newStatus == DefiniteAssignmentStatus.AssignedAfterTrueExpression)
  361. {
  362. throw new InvalidOperationException();
  363. }
  364. // Note that the status can change from DefinitelyAssigned
  365. // back to PotentiallyAssigned as unreachable input edges are
  366. // discovered to be reachable.
  367. edgeStatus[edge] = newStatus;
  368. DefiniteAssignmentNode targetNode = (DefiniteAssignmentNode)edge.To;
  369. if (analyzedRangeStart <= targetNode.Index && targetNode.Index <= analyzedRangeEnd) {
  370. // TODO: potential optimization: visit previously unreachable nodes with higher priority
  371. // (e.g. use Deque and enqueue previously unreachable nodes at the front, but
  372. // other nodes at the end)
  373. nodesWithModifiedInput.Enqueue(targetNode);
  374. }
  375. }
  376. /// <summary>
  377. /// Evaluates an expression.
  378. /// </summary>
  379. /// <returns>The constant value of the expression; or null if the expression is not a constant.</returns>
  380. ResolveResult EvaluateConstant(Expression expr)
  381. {
  382. return resolver.Resolve(expr, analysisCancellationToken);
  383. }
  384. /// <summary>
  385. /// Evaluates an expression.
  386. /// </summary>
  387. /// <returns>The value of the constant boolean expression; or null if the value is not a constant boolean expression.</returns>
  388. bool? EvaluateCondition(Expression expr)
  389. {
  390. ResolveResult rr = EvaluateConstant(expr);
  391. if (rr != null && rr.IsCompileTimeConstant)
  392. return rr.ConstantValue as bool?;
  393. else
  394. return null;
  395. }
  396. static DefiniteAssignmentStatus CleanSpecialValues(DefiniteAssignmentStatus status)
  397. {
  398. if (status == DefiniteAssignmentStatus.AssignedAfterTrueExpression)
  399. return DefiniteAssignmentStatus.PotentiallyAssigned;
  400. else if (status == DefiniteAssignmentStatus.AssignedAfterFalseExpression)
  401. return DefiniteAssignmentStatus.PotentiallyAssigned;
  402. else
  403. return status;
  404. }
  405. sealed class DefiniteAssignmentVisitor : DepthFirstAstVisitor<DefiniteAssignmentStatus, DefiniteAssignmentStatus>
  406. {
  407. internal DefiniteAssignmentAnalysis analysis;
  408. // The general approach for unknown nodes is to pass the status through all child nodes in order
  409. protected override DefiniteAssignmentStatus VisitChildren(AstNode node, DefiniteAssignmentStatus data)
  410. {
  411. // the special values are valid as output only, not as input
  412. Debug.Assert(data == CleanSpecialValues(data));
  413. DefiniteAssignmentStatus status = data;
  414. for (AstNode child = node.FirstChild; child != null; child = child.NextSibling) {
  415. analysis.analysisCancellationToken.ThrowIfCancellationRequested();
  416. Debug.Assert(!(child is Statement)); // statements are visited with the CFG, not with the visitor pattern
  417. status = child.AcceptVisitor(this, status);
  418. status = CleanSpecialValues(status);
  419. }
  420. return status;
  421. }
  422. #region Statements
  423. // For statements, the visitor only describes the effect of the statement itself;
  424. // we do not consider the effect of any nested statements.
  425. // This is done because the nested statements will be reached using the control flow graph.
  426. // In fact, these methods are present so that the default logic in VisitChildren does not try to visit the nested statements.
  427. public override DefiniteAssignmentStatus VisitBlockStatement(BlockStatement blockStatement, DefiniteAssignmentStatus data)
  428. {
  429. return data;
  430. }
  431. public override DefiniteAssignmentStatus VisitCheckedStatement(CheckedStatement checkedStatement, DefiniteAssignmentStatus data)
  432. {
  433. return data;
  434. }
  435. public override DefiniteAssignmentStatus VisitUncheckedStatement(UncheckedStatement uncheckedStatement, DefiniteAssignmentStatus data)
  436. {
  437. return data;
  438. }
  439. // ExpressionStatement handled by default logic
  440. // VariableDeclarationStatement handled by default logic
  441. public override DefiniteAssignmentStatus VisitVariableInitializer(VariableInitializer variableInitializer, DefiniteAssignmentStatus data)
  442. {
  443. if (variableInitializer.Initializer.IsNull) {
  444. return data;
  445. } else {
  446. DefiniteAssignmentStatus status = variableInitializer.Initializer.AcceptVisitor(this, data);
  447. if (variableInitializer.Name == analysis.variableName)
  448. return DefiniteAssignmentStatus.DefinitelyAssigned;
  449. else
  450. return status;
  451. }
  452. }
  453. // IfStatement not handled by visitor, but special-cased in the code consuming the control flow graph
  454. public override DefiniteAssignmentStatus VisitSwitchStatement(SwitchStatement switchStatement, DefiniteAssignmentStatus data)
  455. {
  456. return switchStatement.Expression.AcceptVisitor(this, data);
  457. }
  458. public override DefiniteAssignmentStatus VisitWhileStatement(WhileStatement whileStatement, DefiniteAssignmentStatus data)
  459. {
  460. return data; // condition is handled by special condition CFG node
  461. }
  462. public override DefiniteAssignmentStatus VisitDoWhileStatement(DoWhileStatement doWhileStatement, DefiniteAssignmentStatus data)
  463. {
  464. return data; // condition is handled by special condition CFG node
  465. }
  466. public override DefiniteAssignmentStatus VisitForStatement(ForStatement forStatement, DefiniteAssignmentStatus data)
  467. {
  468. return data; // condition is handled by special condition CFG node; initializer and iterator statements are handled by CFG
  469. }
  470. // Break/Continue/Goto: handled by default logic
  471. // ThrowStatement: handled by default logic (just visit the expression)
  472. // ReturnStatement: handled by default logic (just visit the expression)
  473. public override DefiniteAssignmentStatus VisitTryCatchStatement(TryCatchStatement tryCatchStatement, DefiniteAssignmentStatus data)
  474. {
  475. return data; // no special logic when entering the try-catch-finally statement
  476. // TODO: where to put the special logic when exiting the try-finally statement?
  477. }
  478. public override DefiniteAssignmentStatus VisitForeachStatement(ForeachStatement foreachStatement, DefiniteAssignmentStatus data)
  479. {
  480. return data; // assignment of the foreach loop variable is done when handling the condition node
  481. }
  482. public override DefiniteAssignmentStatus VisitUsingStatement(UsingStatement usingStatement, DefiniteAssignmentStatus data)
  483. {
  484. if (usingStatement.ResourceAcquisition is Expression)
  485. return usingStatement.ResourceAcquisition.AcceptVisitor(this, data);
  486. else
  487. return data; // don't handle resource acquisition statements, as those are connected in the control flow graph
  488. }
  489. public override DefiniteAssignmentStatus VisitLockStatement(LockStatement lockStatement, DefiniteAssignmentStatus data)
  490. {
  491. return lockStatement.Expression.AcceptVisitor(this, data);
  492. }
  493. // Yield statements use the default logic
  494. public override DefiniteAssignmentStatus VisitUnsafeStatement(UnsafeStatement unsafeStatement, DefiniteAssignmentStatus data)
  495. {
  496. return data;
  497. }
  498. public override DefiniteAssignmentStatus VisitFixedStatement(FixedStatement fixedStatement, DefiniteAssignmentStatus data)
  499. {
  500. DefiniteAssignmentStatus status = data;
  501. foreach (var variable in fixedStatement.Variables)
  502. status = variable.AcceptVisitor(this, status);
  503. return status;
  504. }
  505. #endregion
  506. #region Expressions
  507. public override DefiniteAssignmentStatus VisitDirectionExpression(DirectionExpression directionExpression, DefiniteAssignmentStatus data)
  508. {
  509. if (directionExpression.FieldDirection == FieldDirection.Out) {
  510. return HandleAssignment(directionExpression.Expression, null, data);
  511. } else {
  512. // use default logic for 'ref'
  513. return VisitChildren(directionExpression, data);
  514. }
  515. }
  516. public override DefiniteAssignmentStatus VisitAssignmentExpression(AssignmentExpression assignmentExpression, DefiniteAssignmentStatus data)
  517. {
  518. if (assignmentExpression.Operator == AssignmentOperatorType.Assign) {
  519. return HandleAssignment(assignmentExpression.Left, assignmentExpression.Right, data);
  520. } else {
  521. // use default logic for compound assignment operators
  522. return VisitChildren(assignmentExpression, data);
  523. }
  524. }
  525. DefiniteAssignmentStatus HandleAssignment(Expression left, Expression right, DefiniteAssignmentStatus initialStatus)
  526. {
  527. IdentifierExpression ident = left as IdentifierExpression;
  528. if (ident != null && ident.Identifier == analysis.variableName) {
  529. // right==null is special case when handling 'out' expressions
  530. if (right != null)
  531. right.AcceptVisitor(this, initialStatus);
  532. return DefiniteAssignmentStatus.DefinitelyAssigned;
  533. } else {
  534. DefiniteAssignmentStatus status = left.AcceptVisitor(this, initialStatus);
  535. if (right != null)
  536. status = right.AcceptVisitor(this, CleanSpecialValues(status));
  537. return CleanSpecialValues(status);
  538. }
  539. }
  540. public override DefiniteAssignmentStatus VisitParenthesizedExpression(ParenthesizedExpression parenthesizedExpression, DefiniteAssignmentStatus data)
  541. {
  542. // Don't use the default logic here because we don't want to clean up the special values.
  543. return parenthesizedExpression.Expression.AcceptVisitor(this, data);
  544. }
  545. public override DefiniteAssignmentStatus VisitCheckedExpression(CheckedExpression checkedExpression, DefiniteAssignmentStatus data)
  546. {
  547. return checkedExpression.Expression.AcceptVisitor(this, data);
  548. }
  549. public override DefiniteAssignmentStatus VisitUncheckedExpression(UncheckedExpression uncheckedExpression, DefiniteAssignmentStatus data)
  550. {
  551. return uncheckedExpression.Expression.AcceptVisitor(this, data);
  552. }
  553. public override DefiniteAssignmentStatus VisitBinaryOperatorExpression(BinaryOperatorExpression binaryOperatorExpression, DefiniteAssignmentStatus data)
  554. {
  555. if (binaryOperatorExpression.Operator == BinaryOperatorType.ConditionalAnd) {
  556. // Handle constant left side of && expressions (not in the C# spec, but done by the MS compiler)
  557. bool? cond = analysis.EvaluateCondition(binaryOperatorExpression.Left);
  558. if (cond == true)
  559. return binaryOperatorExpression.Right.AcceptVisitor(this, data); // right operand gets evaluated unconditionally
  560. else if (cond == false)
  561. return data; // right operand never gets evaluated
  562. // C# 4.0 spec: §5.3.3.24 Definite Assignment for && expressions
  563. DefiniteAssignmentStatus afterLeft = binaryOperatorExpression.Left.AcceptVisitor(this, data);
  564. DefiniteAssignmentStatus beforeRight;
  565. if (afterLeft == DefiniteAssignmentStatus.AssignedAfterTrueExpression)
  566. beforeRight = DefiniteAssignmentStatus.DefinitelyAssigned;
  567. else if (afterLeft == DefiniteAssignmentStatus.AssignedAfterFalseExpression)
  568. beforeRight = DefiniteAssignmentStatus.PotentiallyAssigned;
  569. else
  570. beforeRight = afterLeft;
  571. DefiniteAssignmentStatus afterRight = binaryOperatorExpression.Right.AcceptVisitor(this, beforeRight);
  572. if (afterLeft == DefiniteAssignmentStatus.DefinitelyAssigned)
  573. return DefiniteAssignmentStatus.DefinitelyAssigned;
  574. else if (afterRight == DefiniteAssignmentStatus.DefinitelyAssigned && afterLeft == DefiniteAssignmentStatus.AssignedAfterFalseExpression)
  575. return DefiniteAssignmentStatus.DefinitelyAssigned;
  576. else if (afterRight == DefiniteAssignmentStatus.DefinitelyAssigned || afterRight == DefiniteAssignmentStatus.AssignedAfterTrueExpression)
  577. return DefiniteAssignmentStatus.AssignedAfterTrueExpression;
  578. else if (afterLeft == DefiniteAssignmentStatus.AssignedAfterFalseExpression && afterRight == DefiniteAssignmentStatus.AssignedAfterFalseExpression)
  579. return DefiniteAssignmentStatus.AssignedAfterFalseExpression;
  580. else
  581. return DefiniteAssignmentStatus.PotentiallyAssigned;
  582. } else if (binaryOperatorExpression.Operator == BinaryOperatorType.ConditionalOr) {
  583. // C# 4.0 spec: §5.3.3.25 Definite Assignment for || expressions
  584. bool? cond = analysis.EvaluateCondition(binaryOperatorExpression.Left);
  585. if (cond == false)
  586. return binaryOperatorExpression.Right.AcceptVisitor(this, data); // right operand gets evaluated unconditionally
  587. else if (cond == true)
  588. return data; // right operand never gets evaluated
  589. DefiniteAssignmentStatus afterLeft = binaryOperatorExpression.Left.AcceptVisitor(this, data);
  590. DefiniteAssignmentStatus beforeRight;
  591. if (afterLeft == DefiniteAssignmentStatus.AssignedAfterTrueExpression)
  592. beforeRight = DefiniteAssignmentStatus.PotentiallyAssigned;
  593. else if (afterLeft == DefiniteAssignmentStatus.AssignedAfterFalseExpression)
  594. beforeRight = DefiniteAssignmentStatus.DefinitelyAssigned;
  595. else
  596. beforeRight = afterLeft;
  597. DefiniteAssignmentStatus afterRight = binaryOperatorExpression.Right.AcceptVisitor(this, beforeRight);
  598. if (afterLeft == DefiniteAssignmentStatus.DefinitelyAssigned)
  599. return DefiniteAssignmentStatus.DefinitelyAssigned;
  600. else if (afterRight == DefiniteAssignmentStatus.DefinitelyAssigned && afterLeft == DefiniteAssignmentStatus.AssignedAfterTrueExpression)
  601. return DefiniteAssignmentStatus.DefinitelyAssigned;
  602. else if (afterRight == DefiniteAssignmentStatus.DefinitelyAssigned || afterRight == DefiniteAssignmentStatus.AssignedAfterFalseExpression)
  603. return DefiniteAssignmentStatus.AssignedAfterFalseExpression;
  604. else if (afterLeft == DefiniteAssignmentStatus.AssignedAfterTrueExpression && afterRight == DefiniteAssignmentStatus.AssignedAfterTrueExpression)
  605. return DefiniteAssignmentStatus.AssignedAfterTrueExpression;
  606. else
  607. return DefiniteAssignmentStatus.PotentiallyAssigned;
  608. } else if (binaryOperatorExpression.Operator == BinaryOperatorType.NullCoalescing) {
  609. // C# 4.0 spec: §5.3.3.27 Definite assignment for ?? expressions
  610. ResolveResult crr = analysis.EvaluateConstant(binaryOperatorExpression.Left);
  611. if (crr != null && crr.IsCompileTimeConstant && crr.ConstantValue == null)
  612. return binaryOperatorExpression.Right.AcceptVisitor(this, data);
  613. DefiniteAssignmentStatus status = CleanSpecialValues(binaryOperatorExpression.Left.AcceptVisitor(this, data));
  614. binaryOperatorExpression.Right.AcceptVisitor(this, status);
  615. return status;
  616. } else {
  617. // use default logic for other operators
  618. return VisitChildren(binaryOperatorExpression, data);
  619. }
  620. }
  621. public override DefiniteAssignmentStatus VisitUnaryOperatorExpression(UnaryOperatorExpression unaryOperatorExpression, DefiniteAssignmentStatus data)
  622. {
  623. if (unaryOperatorExpression.Operator == UnaryOperatorType.Not) {
  624. // C# 4.0 spec: §5.3.3.26 Definite assignment for ! expressions
  625. DefiniteAssignmentStatus status = unaryOperatorExpression.Expression.AcceptVisitor(this, data);
  626. if (status == DefiniteAssignmentStatus.AssignedAfterFalseExpression)
  627. return DefiniteAssignmentStatus.AssignedAfterTrueExpression;
  628. else if (status == DefiniteAssignmentStatus.AssignedAfterTrueExpression)
  629. return DefiniteAssignmentStatus.AssignedAfterFalseExpression;
  630. else
  631. return status;
  632. } else {
  633. // use default logic for other operators
  634. return VisitChildren(unaryOperatorExpression, data);
  635. }
  636. }
  637. public override DefiniteAssignmentStatus VisitConditionalExpression(ConditionalExpression conditionalExpression, DefiniteAssignmentStatus data)
  638. {
  639. // C# 4.0 spec: §5.3.3.28 Definite assignment for ?: expressions
  640. bool? cond = analysis.EvaluateCondition(conditionalExpression.Condition);
  641. if (cond == true) {
  642. return conditionalExpression.TrueExpression.AcceptVisitor(this, data);
  643. } else if (cond == false) {
  644. return conditionalExpression.FalseExpression.AcceptVisitor(this, data);
  645. } else {
  646. DefiniteAssignmentStatus afterCondition = conditionalExpression.Condition.AcceptVisitor(this, data);
  647. DefiniteAssignmentStatus beforeTrue, beforeFalse;
  648. if (afterCondition == DefiniteAssignmentStatus.AssignedAfterTrueExpression) {
  649. beforeTrue = DefiniteAssignmentStatus.DefinitelyAssigned;
  650. beforeFalse = DefiniteAssignmentStatus.PotentiallyAssigned;
  651. } else if (afterCondition == DefiniteAssignmentStatus.AssignedAfterFalseExpression) {
  652. beforeTrue = DefiniteAssignmentStatus.PotentiallyAssigned;
  653. beforeFalse = DefiniteAssignmentStatus.DefinitelyAssigned;
  654. } else {
  655. beforeTrue = afterCondition;
  656. beforeFalse = afterCondition;
  657. }
  658. DefiniteAssignmentStatus afterTrue = conditionalExpression.TrueExpression.AcceptVisitor(this, beforeTrue);
  659. DefiniteAssignmentStatus afterFalse = conditionalExpression.FalseExpression.AcceptVisitor(this, beforeFalse);
  660. return MergeStatus(CleanSpecialValues(afterTrue), CleanSpecialValues(afterFalse));
  661. }
  662. }
  663. public override DefiniteAssignmentStatus VisitAnonymousMethodExpression(AnonymousMethodExpression anonymousMethodExpression, DefiniteAssignmentStatus data)
  664. {
  665. BlockStatement body = anonymousMethodExpression.Body;
  666. analysis.ChangeNodeStatus(analysis.beginNodeDict[body], data);
  667. return data;
  668. }
  669. public override DefiniteAssignmentStatus VisitLambdaExpression(LambdaExpression lambdaExpression, DefiniteAssignmentStatus data)
  670. {
  671. Statement body = lambdaExpression.Body as Statement;
  672. if (body != null) {
  673. analysis.ChangeNodeStatus(analysis.beginNodeDict[body], data);
  674. } else {
  675. lambdaExpression.Body.AcceptVisitor(this, data);
  676. }
  677. return data;
  678. }
  679. public override DefiniteAssignmentStatus VisitIdentifierExpression(IdentifierExpression identifierExpression, DefiniteAssignmentStatus data)
  680. {
  681. if (data != DefiniteAssignmentStatus.DefinitelyAssigned
  682. && identifierExpression.Identifier == analysis.variableName && identifierExpression.TypeArguments.Count == 0)
  683. {
  684. analysis.unassignedVariableUses.Add(identifierExpression);
  685. }
  686. return data;
  687. }
  688. #endregion
  689. }
  690. }
  691. }