// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) // This code is distributed under MIT X11 license (for details please see \doc\license.txt) using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; using ICSharpCode.NRefactory.CSharp.Resolver; using ICSharpCode.NRefactory.TypeSystem; using ICSharpCode.NRefactory.Utils; namespace ICSharpCode.NRefactory.CSharp.Analysis { /// /// Represents the definite assignment status of a variable at a specific location. /// public enum DefiniteAssignmentStatus { /// /// The variable might be assigned or unassigned. /// PotentiallyAssigned, /// /// The variable is definitely assigned. /// DefinitelyAssigned, /// /// The variable is definitely assigned iff the expression results in the value 'true'. /// AssignedAfterTrueExpression, /// /// The variable is definitely assigned iff the expression results in the value 'false'. /// AssignedAfterFalseExpression, /// /// The code is unreachable. /// CodeUnreachable } /// /// Implements the C# definite assignment analysis (C# 4.0 Spec: §5.3 Definite assignment) /// public class DefiniteAssignmentAnalysis { sealed class DefiniteAssignmentNode : ControlFlowNode { public int Index; public DefiniteAssignmentStatus NodeStatus; public DefiniteAssignmentNode(Statement previousStatement, Statement nextStatement, ControlFlowNodeType type) : base(previousStatement, nextStatement, type) { } } sealed class DerivedControlFlowGraphBuilder : ControlFlowGraphBuilder { protected override ControlFlowNode CreateNode(Statement previousStatement, Statement nextStatement, ControlFlowNodeType type) { return new DefiniteAssignmentNode(previousStatement, nextStatement, type); } } readonly DerivedControlFlowGraphBuilder cfgBuilder = new DerivedControlFlowGraphBuilder(); readonly DefiniteAssignmentVisitor visitor = new DefiniteAssignmentVisitor(); readonly List allNodes = new List(); readonly Dictionary beginNodeDict = new Dictionary(); readonly Dictionary endNodeDict = new Dictionary(); readonly Dictionary conditionNodeDict = new Dictionary(); readonly ResolveVisitor resolveVisitor; readonly CancellationToken cancellationToken; Dictionary edgeStatus = new Dictionary(); string variableName; List unassignedVariableUses = new List(); int analyzedRangeStart, analyzedRangeEnd; Queue nodesWithModifiedInput = new Queue(); public DefiniteAssignmentAnalysis(Statement rootStatement, CancellationToken cancellationToken = default(CancellationToken)) : this(rootStatement, null, cancellationToken) { } public DefiniteAssignmentAnalysis(Statement rootStatement, ITypeResolveContext context, CancellationToken cancellationToken = default(CancellationToken)) : this(rootStatement, new ResolveVisitor(new CSharpResolver(context ?? MinimalResolveContext.Instance, cancellationToken), null, ConstantModeResolveVisitorNavigator.Skip)) { } public DefiniteAssignmentAnalysis(Statement rootStatement, ResolveVisitor resolveVisitor) { if (rootStatement == null) throw new ArgumentNullException("rootStatement"); if (resolveVisitor == null) throw new ArgumentNullException("resolveVisitor"); this.resolveVisitor = resolveVisitor; this.cancellationToken = resolveVisitor.CancellationToken; visitor.analysis = this; if (resolveVisitor.TypeResolveContext is MinimalResolveContext) { cfgBuilder.EvaluateOnlyPrimitiveConstants = true; } allNodes.AddRange(cfgBuilder.BuildControlFlowGraph(rootStatement, resolveVisitor).Cast()); for (int i = 0; i < allNodes.Count; i++) { DefiniteAssignmentNode node = allNodes[i]; node.Index = i; // assign numbers to the nodes if (node.Type == ControlFlowNodeType.StartNode || node.Type == ControlFlowNodeType.BetweenStatements) { // Anonymous methods have separate control flow graphs, but we also need to analyze those. // Iterate backwards so that anonymous methods are inserted in the correct order for (AstNode child = node.NextStatement.LastChild; child != null; child = child.PrevSibling) { InsertAnonymousMethods(i + 1, child); } } // Now register the node in the dictionaries: if (node.Type == ControlFlowNodeType.StartNode || node.Type == ControlFlowNodeType.BetweenStatements) beginNodeDict.Add(node.NextStatement, node); if (node.Type == ControlFlowNodeType.BetweenStatements || node.Type == ControlFlowNodeType.EndNode) endNodeDict.Add(node.PreviousStatement, node); if (node.Type == ControlFlowNodeType.LoopCondition) conditionNodeDict.Add(node.NextStatement, node); } // Verify that we created nodes for all statements: Debug.Assert(!rootStatement.DescendantsAndSelf.OfType().Except(allNodes.Select(n => n.NextStatement)).Any()); this.analyzedRangeStart = 0; this.analyzedRangeEnd = allNodes.Count - 1; } void InsertAnonymousMethods(int insertPos, AstNode node) { // Ignore any statements, as those have their own ControlFlowNode and get handled separately if (node is Statement) return; AnonymousMethodExpression ame = node as AnonymousMethodExpression; if (ame != null) { allNodes.InsertRange(insertPos, cfgBuilder.BuildControlFlowGraph(ame.Body, resolveVisitor).Cast()); return; } LambdaExpression lambda = node as LambdaExpression; if (lambda != null && lambda.Body is Statement) { allNodes.InsertRange(insertPos, cfgBuilder.BuildControlFlowGraph((Statement)lambda.Body, resolveVisitor).Cast()); return; } // Descend into child expressions // Iterate backwards so that anonymous methods are inserted in the correct order for (AstNode child = node.LastChild; child != null; child = child.PrevSibling) { InsertAnonymousMethods(insertPos, child); } } /// /// Gets the unassigned usages of the previously analyzed variable. /// public IList UnassignedVariableUses { get { return unassignedVariableUses.AsReadOnly(); } } /// /// Sets the range of statements to be analyzed. /// This method can be used to restrict the analysis to only a part of the method. /// Only the control flow paths that are fully contained within the selected part will be analyzed. /// /// Both 'start' and 'end' are inclusive. public void SetAnalyzedRange(Statement start, Statement end) { int startIndex = beginNodeDict[start].Index; int endIndex = endNodeDict[end].Index; if (startIndex > endIndex) throw new ArgumentException("The start statement must be lexically preceding the end statement"); this.analyzedRangeStart = startIndex; this.analyzedRangeEnd = endIndex; } public void Analyze(string variable, DefiniteAssignmentStatus initialStatus = DefiniteAssignmentStatus.PotentiallyAssigned) { this.variableName = variable; // Reset the status: unassignedVariableUses.Clear(); foreach (DefiniteAssignmentNode node in allNodes) { node.NodeStatus = DefiniteAssignmentStatus.CodeUnreachable; foreach (ControlFlowEdge edge in node.Outgoing) edgeStatus[edge] = DefiniteAssignmentStatus.CodeUnreachable; } ChangeNodeStatus(allNodes[analyzedRangeStart], initialStatus); // Iterate as long as the input status of some nodes is changing: while (nodesWithModifiedInput.Count > 0) { DefiniteAssignmentNode node = nodesWithModifiedInput.Dequeue(); DefiniteAssignmentStatus inputStatus = DefiniteAssignmentStatus.CodeUnreachable; foreach (ControlFlowEdge edge in node.Incoming) { inputStatus = MergeStatus(inputStatus, edgeStatus[edge]); } ChangeNodeStatus(node, inputStatus); } } public DefiniteAssignmentStatus GetStatusBefore(Statement statement) { return beginNodeDict[statement].NodeStatus; } public DefiniteAssignmentStatus GetStatusAfter(Statement statement) { return endNodeDict[statement].NodeStatus; } public DefiniteAssignmentStatus GetStatusBeforeLoopCondition(Statement statement) { return conditionNodeDict[statement].NodeStatus; } /// /// Exports the CFG. This method is intended to help debugging issues related to definite assignment. /// public GraphVizGraph ExportGraph() { GraphVizGraph g = new GraphVizGraph(); g.Title = "DefiniteAssignment - " + variableName; for (int i = 0; i < allNodes.Count; i++) { string name = "#" + i + " = " + allNodes[i].NodeStatus.ToString() + Environment.NewLine; switch (allNodes[i].Type) { case ControlFlowNodeType.StartNode: case ControlFlowNodeType.BetweenStatements: name += allNodes[i].NextStatement.ToString(); break; case ControlFlowNodeType.EndNode: name += "End of " + allNodes[i].PreviousStatement.ToString(); break; case ControlFlowNodeType.LoopCondition: name += "Condition in " + allNodes[i].NextStatement.ToString(); break; default: name += allNodes[i].Type.ToString(); break; } g.AddNode(new GraphVizNode(i) { label = name }); foreach (ControlFlowEdge edge in allNodes[i].Outgoing) { GraphVizEdge ge = new GraphVizEdge(i, ((DefiniteAssignmentNode)edge.To).Index); if (edgeStatus.Count > 0) ge.label = edgeStatus[edge].ToString(); if (edge.IsLeavingTryFinally) ge.style = "dashed"; switch (edge.Type) { case ControlFlowEdgeType.ConditionTrue: ge.color = "green"; break; case ControlFlowEdgeType.ConditionFalse: ge.color = "red"; break; case ControlFlowEdgeType.Jump: ge.color = "blue"; break; } g.AddEdge(ge); } } return g; } static DefiniteAssignmentStatus MergeStatus(DefiniteAssignmentStatus a, DefiniteAssignmentStatus b) { // The result will be DefinitelyAssigned if at least one incoming edge is DefinitelyAssigned and all others are unreachable. // The result will be DefinitelyUnassigned if at least one incoming edge is DefinitelyUnassigned and all others are unreachable. // The result will be Unreachable if all incoming edges are unreachable. // Otherwise, the result will be PotentiallyAssigned. if (a == b) return a; else if (a == DefiniteAssignmentStatus.CodeUnreachable) return b; else if (b == DefiniteAssignmentStatus.CodeUnreachable) return a; else return DefiniteAssignmentStatus.PotentiallyAssigned; } void ChangeNodeStatus(DefiniteAssignmentNode node, DefiniteAssignmentStatus inputStatus) { if (node.NodeStatus == inputStatus) return; node.NodeStatus = inputStatus; DefiniteAssignmentStatus outputStatus; switch (node.Type) { case ControlFlowNodeType.StartNode: case ControlFlowNodeType.BetweenStatements: if (node.NextStatement is IfElseStatement) { // Handle if-else as a condition node goto case ControlFlowNodeType.LoopCondition; } if (inputStatus == DefiniteAssignmentStatus.DefinitelyAssigned) { // There isn't any way to un-assign variables, so we don't have to check the expression // if the status already is definitely assigned. outputStatus = DefiniteAssignmentStatus.DefinitelyAssigned; } else { outputStatus = CleanSpecialValues(node.NextStatement.AcceptVisitor(visitor, inputStatus)); } break; case ControlFlowNodeType.EndNode: outputStatus = inputStatus; if (node.PreviousStatement.Role == TryCatchStatement.FinallyBlockRole && (outputStatus == DefiniteAssignmentStatus.DefinitelyAssigned || outputStatus == DefiniteAssignmentStatus.PotentiallyAssigned)) { TryCatchStatement tryFinally = (TryCatchStatement)node.PreviousStatement.Parent; // Changing the status on a finally block potentially changes the status of all edges leaving that finally block: foreach (ControlFlowEdge edge in allNodes.SelectMany(n => n.Outgoing)) { if (edge.IsLeavingTryFinally && edge.TryFinallyStatements.Contains(tryFinally)) { DefiniteAssignmentStatus s = edgeStatus[edge]; if (s == DefiniteAssignmentStatus.PotentiallyAssigned) { ChangeEdgeStatus(edge, outputStatus); } } } } break; case ControlFlowNodeType.LoopCondition: ForeachStatement foreachStmt = node.NextStatement as ForeachStatement; if (foreachStmt != null) { outputStatus = CleanSpecialValues(foreachStmt.InExpression.AcceptVisitor(visitor, inputStatus)); if (foreachStmt.VariableName == this.variableName) outputStatus = DefiniteAssignmentStatus.DefinitelyAssigned; break; } else { Debug.Assert(node.NextStatement is IfElseStatement || node.NextStatement is WhileStatement || node.NextStatement is ForStatement || node.NextStatement is DoWhileStatement); Expression condition = node.NextStatement.GetChildByRole(AstNode.Roles.Condition); if (condition.IsNull) outputStatus = inputStatus; else outputStatus = condition.AcceptVisitor(visitor, inputStatus); foreach (ControlFlowEdge edge in node.Outgoing) { if (edge.Type == ControlFlowEdgeType.ConditionTrue && outputStatus == DefiniteAssignmentStatus.AssignedAfterTrueExpression) { ChangeEdgeStatus(edge, DefiniteAssignmentStatus.DefinitelyAssigned); } else if (edge.Type == ControlFlowEdgeType.ConditionFalse && outputStatus == DefiniteAssignmentStatus.AssignedAfterFalseExpression) { ChangeEdgeStatus(edge, DefiniteAssignmentStatus.DefinitelyAssigned); } else { ChangeEdgeStatus(edge, CleanSpecialValues(outputStatus)); } } return; } default: throw new InvalidOperationException(); } foreach (ControlFlowEdge edge in node.Outgoing) { ChangeEdgeStatus(edge, outputStatus); } } void ChangeEdgeStatus(ControlFlowEdge edge, DefiniteAssignmentStatus newStatus) { DefiniteAssignmentStatus oldStatus = edgeStatus[edge]; if (oldStatus == newStatus) return; // Ensure that status can change only in one direction: // CodeUnreachable -> PotentiallyAssigned -> DefinitelyAssigned // Going against this direction indicates a bug and could cause infinite loops. switch (oldStatus) { case DefiniteAssignmentStatus.PotentiallyAssigned: if (newStatus != DefiniteAssignmentStatus.DefinitelyAssigned) throw new InvalidOperationException("Invalid state transition"); break; case DefiniteAssignmentStatus.CodeUnreachable: if (!(newStatus == DefiniteAssignmentStatus.PotentiallyAssigned || newStatus == DefiniteAssignmentStatus.DefinitelyAssigned)) throw new InvalidOperationException("Invalid state transition"); break; case DefiniteAssignmentStatus.DefinitelyAssigned: throw new InvalidOperationException("Invalid state transition"); default: throw new InvalidOperationException("Invalid value for DefiniteAssignmentStatus"); } edgeStatus[edge] = newStatus; DefiniteAssignmentNode targetNode = (DefiniteAssignmentNode)edge.To; if (analyzedRangeStart <= targetNode.Index && targetNode.Index <= analyzedRangeEnd) nodesWithModifiedInput.Enqueue(targetNode); } /// /// Evaluates an expression. /// /// The constant value of the expression; or null if the expression is not a constant. ConstantResolveResult EvaluateConstant(Expression expr) { if (resolveVisitor.TypeResolveContext is MinimalResolveContext) { if (!(expr is PrimitiveExpression || expr is NullReferenceExpression)) return null; } return resolveVisitor.Resolve(expr) as ConstantResolveResult; } /// /// Evaluates an expression. /// /// The value of the constant boolean expression; or null if the value is not a constant boolean expression. bool? EvaluateCondition(Expression expr) { ConstantResolveResult rr = EvaluateConstant(expr); if (rr != null) return rr.ConstantValue as bool?; else return null; } static DefiniteAssignmentStatus CleanSpecialValues(DefiniteAssignmentStatus status) { if (status == DefiniteAssignmentStatus.AssignedAfterTrueExpression) return DefiniteAssignmentStatus.PotentiallyAssigned; else if (status == DefiniteAssignmentStatus.AssignedAfterFalseExpression) return DefiniteAssignmentStatus.PotentiallyAssigned; else return status; } sealed class DefiniteAssignmentVisitor : DepthFirstAstVisitor { internal DefiniteAssignmentAnalysis analysis; // The general approach for unknown nodes is to pass the status through all child nodes in order protected override DefiniteAssignmentStatus VisitChildren(AstNode node, DefiniteAssignmentStatus data) { // the special values are valid as output only, not as input Debug.Assert(data == CleanSpecialValues(data)); DefiniteAssignmentStatus status = data; foreach (AstNode child in node.Children) { Debug.Assert(!(child is Statement)); // statements are visited with the CFG, not with the visitor pattern status = child.AcceptVisitor(this, status); status = CleanSpecialValues(status); } return status; } #region Statements // For statements, the visitor only describes the effect of the statement itself; // we do not consider the effect of any nested statements. // This is done because the nested statements will be reached using the control flow graph. // In fact, these methods are present so that the default logic in VisitChildren does not try to visit the nested statements. public override DefiniteAssignmentStatus VisitBlockStatement(BlockStatement blockStatement, DefiniteAssignmentStatus data) { return data; } public override DefiniteAssignmentStatus VisitCheckedStatement(CheckedStatement checkedStatement, DefiniteAssignmentStatus data) { return data; } public override DefiniteAssignmentStatus VisitUncheckedStatement(UncheckedStatement uncheckedStatement, DefiniteAssignmentStatus data) { return data; } // ExpressionStatement handled by default logic // VariableDeclarationStatement handled by default logic public override DefiniteAssignmentStatus VisitVariableInitializer(VariableInitializer variableInitializer, DefiniteAssignmentStatus data) { if (variableInitializer.Initializer.IsNull) { return data; } else { DefiniteAssignmentStatus status = variableInitializer.Initializer.AcceptVisitor(this, data); if (variableInitializer.Name == analysis.variableName) return DefiniteAssignmentStatus.DefinitelyAssigned; else return status; } } // IfStatement not handled by visitor, but special-cased in the code consuming the control flow graph public override DefiniteAssignmentStatus VisitSwitchStatement(SwitchStatement switchStatement, DefiniteAssignmentStatus data) { return switchStatement.Expression.AcceptVisitor(this, data); } public override DefiniteAssignmentStatus VisitWhileStatement(WhileStatement whileStatement, DefiniteAssignmentStatus data) { return data; // condition is handled by special condition CFG node } public override DefiniteAssignmentStatus VisitDoWhileStatement(DoWhileStatement doWhileStatement, DefiniteAssignmentStatus data) { return data; // condition is handled by special condition CFG node } public override DefiniteAssignmentStatus VisitForStatement(ForStatement forStatement, DefiniteAssignmentStatus data) { return data; // condition is handled by special condition CFG node; initializer and iterator statements are handled by CFG } // Break/Continue/Goto: handled by default logic // ThrowStatement: handled by default logic (just visit the expression) // ReturnStatement: handled by default logic (just visit the expression) public override DefiniteAssignmentStatus VisitTryCatchStatement(TryCatchStatement tryCatchStatement, DefiniteAssignmentStatus data) { return data; // no special logic when entering the try-catch-finally statement // TODO: where to put the special logic when exiting the try-finally statement? } public override DefiniteAssignmentStatus VisitForeachStatement(ForeachStatement foreachStatement, DefiniteAssignmentStatus data) { return data; // assignment of the foreach loop variable is done when handling the condition node } public override DefiniteAssignmentStatus VisitUsingStatement(UsingStatement usingStatement, DefiniteAssignmentStatus data) { if (usingStatement.ResourceAcquisition is Expression) return usingStatement.ResourceAcquisition.AcceptVisitor(this, data); else return data; // don't handle resource acquisition statements, as those are connected in the control flow graph } public override DefiniteAssignmentStatus VisitLockStatement(LockStatement lockStatement, DefiniteAssignmentStatus data) { return lockStatement.Expression.AcceptVisitor(this, data); } // Yield statements use the default logic public override DefiniteAssignmentStatus VisitUnsafeStatement(UnsafeStatement unsafeStatement, DefiniteAssignmentStatus data) { return data; } public override DefiniteAssignmentStatus VisitFixedStatement(FixedStatement fixedStatement, DefiniteAssignmentStatus data) { DefiniteAssignmentStatus status = data; foreach (var variable in fixedStatement.Variables) status = variable.AcceptVisitor(this, status); return status; } #endregion public override DefiniteAssignmentStatus VisitDirectionExpression(DirectionExpression directionExpression, DefiniteAssignmentStatus data) { if (directionExpression.FieldDirection == FieldDirection.Out) { return HandleAssignment(directionExpression.Expression, null, data); } else { // use default logic for 'ref' return VisitChildren(directionExpression, data); } } public override DefiniteAssignmentStatus VisitAssignmentExpression(AssignmentExpression assignmentExpression, DefiniteAssignmentStatus data) { if (assignmentExpression.Operator == AssignmentOperatorType.Assign) { return HandleAssignment(assignmentExpression.Left, assignmentExpression.Right, data); } else { // use default logic for compound assignment operators return VisitChildren(assignmentExpression, data); } } DefiniteAssignmentStatus HandleAssignment(Expression left, Expression right, DefiniteAssignmentStatus initialStatus) { IdentifierExpression ident = left as IdentifierExpression; if (ident != null && ident.Identifier == analysis.variableName) { // right==null is special case when handling 'out' expressions if (right != null) right.AcceptVisitor(this, initialStatus); return DefiniteAssignmentStatus.DefinitelyAssigned; } else { DefiniteAssignmentStatus status = left.AcceptVisitor(this, initialStatus); if (right != null) status = right.AcceptVisitor(this, CleanSpecialValues(status)); return CleanSpecialValues(status); } } public override DefiniteAssignmentStatus VisitParenthesizedExpression(ParenthesizedExpression parenthesizedExpression, DefiniteAssignmentStatus data) { // Don't use the default logic here because we don't want to clean up the special values. return parenthesizedExpression.Expression.AcceptVisitor(this, data); } public override DefiniteAssignmentStatus VisitCheckedExpression(CheckedExpression checkedExpression, DefiniteAssignmentStatus data) { return checkedExpression.Expression.AcceptVisitor(this, data); } public override DefiniteAssignmentStatus VisitUncheckedExpression(UncheckedExpression uncheckedExpression, DefiniteAssignmentStatus data) { return uncheckedExpression.Expression.AcceptVisitor(this, data); } public override DefiniteAssignmentStatus VisitBinaryOperatorExpression(BinaryOperatorExpression binaryOperatorExpression, DefiniteAssignmentStatus data) { if (binaryOperatorExpression.Operator == BinaryOperatorType.ConditionalAnd) { // Handle constant left side of && expressions (not in the C# spec, but done by the MS compiler) bool? cond = analysis.EvaluateCondition(binaryOperatorExpression.Left); if (cond == true) return binaryOperatorExpression.Right.AcceptVisitor(this, data); // right operand gets evaluated unconditionally else if (cond == false) return data; // right operand never gets evaluated // C# 4.0 spec: §5.3.3.24 Definite Assignment for && expressions DefiniteAssignmentStatus afterLeft = binaryOperatorExpression.Left.AcceptVisitor(this, data); DefiniteAssignmentStatus beforeRight; if (afterLeft == DefiniteAssignmentStatus.AssignedAfterTrueExpression) beforeRight = DefiniteAssignmentStatus.DefinitelyAssigned; else if (afterLeft == DefiniteAssignmentStatus.AssignedAfterFalseExpression) beforeRight = DefiniteAssignmentStatus.PotentiallyAssigned; else beforeRight = afterLeft; DefiniteAssignmentStatus afterRight = binaryOperatorExpression.Right.AcceptVisitor(this, beforeRight); if (afterLeft == DefiniteAssignmentStatus.DefinitelyAssigned) return DefiniteAssignmentStatus.DefinitelyAssigned; else if (afterRight == DefiniteAssignmentStatus.DefinitelyAssigned && afterLeft == DefiniteAssignmentStatus.AssignedAfterFalseExpression) return DefiniteAssignmentStatus.DefinitelyAssigned; else if (afterRight == DefiniteAssignmentStatus.DefinitelyAssigned || afterRight == DefiniteAssignmentStatus.AssignedAfterTrueExpression) return DefiniteAssignmentStatus.AssignedAfterTrueExpression; else if (afterLeft == DefiniteAssignmentStatus.AssignedAfterFalseExpression && afterRight == DefiniteAssignmentStatus.AssignedAfterFalseExpression) return DefiniteAssignmentStatus.AssignedAfterFalseExpression; else return DefiniteAssignmentStatus.PotentiallyAssigned; } else if (binaryOperatorExpression.Operator == BinaryOperatorType.ConditionalOr) { // C# 4.0 spec: §5.3.3.25 Definite Assignment for || expressions bool? cond = analysis.EvaluateCondition(binaryOperatorExpression.Left); if (cond == false) return binaryOperatorExpression.Right.AcceptVisitor(this, data); // right operand gets evaluated unconditionally else if (cond == true) return data; // right operand never gets evaluated DefiniteAssignmentStatus afterLeft = binaryOperatorExpression.Left.AcceptVisitor(this, data); DefiniteAssignmentStatus beforeRight; if (afterLeft == DefiniteAssignmentStatus.AssignedAfterTrueExpression) beforeRight = DefiniteAssignmentStatus.PotentiallyAssigned; else if (afterLeft == DefiniteAssignmentStatus.AssignedAfterFalseExpression) beforeRight = DefiniteAssignmentStatus.DefinitelyAssigned; else beforeRight = afterLeft; DefiniteAssignmentStatus afterRight = binaryOperatorExpression.Right.AcceptVisitor(this, beforeRight); if (afterLeft == DefiniteAssignmentStatus.DefinitelyAssigned) return DefiniteAssignmentStatus.DefinitelyAssigned; else if (afterRight == DefiniteAssignmentStatus.DefinitelyAssigned && afterLeft == DefiniteAssignmentStatus.AssignedAfterTrueExpression) return DefiniteAssignmentStatus.DefinitelyAssigned; else if (afterRight == DefiniteAssignmentStatus.DefinitelyAssigned || afterRight == DefiniteAssignmentStatus.AssignedAfterFalseExpression) return DefiniteAssignmentStatus.AssignedAfterFalseExpression; else if (afterLeft == DefiniteAssignmentStatus.AssignedAfterTrueExpression && afterRight == DefiniteAssignmentStatus.AssignedAfterTrueExpression) return DefiniteAssignmentStatus.AssignedAfterTrueExpression; else return DefiniteAssignmentStatus.PotentiallyAssigned; } else if (binaryOperatorExpression.Operator == BinaryOperatorType.NullCoalescing) { // C# 4.0 spec: §5.3.3.27 Definite assignment for ?? expressions ConstantResolveResult crr = analysis.EvaluateConstant(binaryOperatorExpression.Left); if (crr != null && crr.ConstantValue == null) return binaryOperatorExpression.Right.AcceptVisitor(this, data); DefiniteAssignmentStatus status = CleanSpecialValues(binaryOperatorExpression.Left.AcceptVisitor(this, data)); binaryOperatorExpression.Right.AcceptVisitor(this, status); return status; } else { // use default logic for other operators return VisitChildren(binaryOperatorExpression, data); } } public override DefiniteAssignmentStatus VisitUnaryOperatorExpression(UnaryOperatorExpression unaryOperatorExpression, DefiniteAssignmentStatus data) { if (unaryOperatorExpression.Operator == UnaryOperatorType.Not) { // C# 4.0 spec: §5.3.3.26 Definite assignment for ! expressions DefiniteAssignmentStatus status = unaryOperatorExpression.Expression.AcceptVisitor(this, data); if (status == DefiniteAssignmentStatus.AssignedAfterFalseExpression) return DefiniteAssignmentStatus.AssignedAfterTrueExpression; else if (status == DefiniteAssignmentStatus.AssignedAfterTrueExpression) return DefiniteAssignmentStatus.AssignedAfterFalseExpression; else return status; } else { // use default logic for other operators return VisitChildren(unaryOperatorExpression, data); } } public override DefiniteAssignmentStatus VisitConditionalExpression(ConditionalExpression conditionalExpression, DefiniteAssignmentStatus data) { // C# 4.0 spec: §5.3.3.28 Definite assignment for ?: expressions bool? cond = analysis.EvaluateCondition(conditionalExpression.Condition); if (cond == true) { return conditionalExpression.TrueExpression.AcceptVisitor(this, data); } else if (cond == false) { return conditionalExpression.FalseExpression.AcceptVisitor(this, data); } else { DefiniteAssignmentStatus afterCondition = conditionalExpression.Condition.AcceptVisitor(this, data); DefiniteAssignmentStatus beforeTrue, beforeFalse; if (afterCondition == DefiniteAssignmentStatus.AssignedAfterTrueExpression) { beforeTrue = DefiniteAssignmentStatus.DefinitelyAssigned; beforeFalse = DefiniteAssignmentStatus.PotentiallyAssigned; } else if (afterCondition == DefiniteAssignmentStatus.AssignedAfterFalseExpression) { beforeTrue = DefiniteAssignmentStatus.PotentiallyAssigned; beforeFalse = DefiniteAssignmentStatus.DefinitelyAssigned; } else { beforeTrue = afterCondition; beforeFalse = afterCondition; } DefiniteAssignmentStatus afterTrue = conditionalExpression.TrueExpression.AcceptVisitor(this, beforeTrue); DefiniteAssignmentStatus afterFalse = conditionalExpression.TrueExpression.AcceptVisitor(this, beforeFalse); return MergeStatus(CleanSpecialValues(afterTrue), CleanSpecialValues(afterFalse)); } } public override DefiniteAssignmentStatus VisitAnonymousMethodExpression(AnonymousMethodExpression anonymousMethodExpression, DefiniteAssignmentStatus data) { BlockStatement body = anonymousMethodExpression.Body; analysis.ChangeNodeStatus(analysis.beginNodeDict[body], data); return data; } public override DefiniteAssignmentStatus VisitLambdaExpression(LambdaExpression lambdaExpression, DefiniteAssignmentStatus data) { Statement body = lambdaExpression.Body as Statement; if (body != null) { analysis.ChangeNodeStatus(analysis.beginNodeDict[body], data); } else { lambdaExpression.Body.AcceptVisitor(this, data); } return data; } public override DefiniteAssignmentStatus VisitIdentifierExpression(IdentifierExpression identifierExpression, DefiniteAssignmentStatus data) { if (data != DefiniteAssignmentStatus.DefinitelyAssigned && identifierExpression.Identifier == analysis.variableName && identifierExpression.TypeArguments.Count == 0) { analysis.unassignedVariableUses.Add(identifierExpression); } return data; } } } }