// Copyright (c) 2010-2013 AlphaSierraPapa for the SharpDevelop Team // // Permission is hereby granted, free of charge, to any person obtaining a copy of this // software and associated documentation files (the "Software"), to deal in the Software // without restriction, including without limitation the rights to use, copy, modify, merge, // publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons // to whom the Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all copies or // substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; using ICSharpCode.NRefactory.CSharp.Resolver; using ICSharpCode.NRefactory.Semantics; using ICSharpCode.NRefactory.TypeSystem; using ICSharpCode.NRefactory.TypeSystem.Implementation; 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 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 CSharpAstResolver resolver; Dictionary edgeStatus = new Dictionary(); string variableName; List unassignedVariableUses = new List(); int analyzedRangeStart, analyzedRangeEnd; CancellationToken analysisCancellationToken; Queue nodesWithModifiedInput = new Queue(); public DefiniteAssignmentAnalysis(Statement rootStatement, CancellationToken cancellationToken) : this(rootStatement, new CSharpAstResolver(new CSharpResolver(MinimalCorlib.Instance.CreateCompilation()), rootStatement), cancellationToken) { } public DefiniteAssignmentAnalysis(Statement rootStatement, CSharpAstResolver resolver, CancellationToken cancellationToken) { if (rootStatement == null) throw new ArgumentNullException("rootStatement"); if (resolver == null) throw new ArgumentNullException("resolver"); this.resolver = resolver; visitor.analysis = this; DerivedControlFlowGraphBuilder cfgBuilder = new DerivedControlFlowGraphBuilder(); if (resolver.TypeResolveContext.Compilation.MainAssembly.UnresolvedAssembly is MinimalCorlib) { cfgBuilder.EvaluateOnlyPrimitiveConstants = true; } allNodes.AddRange(cfgBuilder.BuildControlFlowGraph(rootStatement, resolver, cancellationToken).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, cfgBuilder, cancellationToken); } } // 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()); // Verify that we put all nodes into the dictionaries: Debug.Assert(rootStatement.DescendantsAndSelf.OfType().All(stmt => beginNodeDict.ContainsKey(stmt))); Debug.Assert(rootStatement.DescendantsAndSelf.OfType().All(stmt => endNodeDict.ContainsKey(stmt))); this.analyzedRangeStart = 0; this.analyzedRangeEnd = allNodes.Count - 1; } void InsertAnonymousMethods(int insertPos, AstNode node, ControlFlowGraphBuilder cfgBuilder, CancellationToken cancellationToken) { // 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, resolver, cancellationToken).Cast()); return; } LambdaExpression lambda = node as LambdaExpression; if (lambda != null && lambda.Body is Statement) { allNodes.InsertRange(insertPos, cfgBuilder.BuildControlFlowGraph((Statement)lambda.Body, resolver, cancellationToken).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, cfgBuilder, cancellationToken); } } /// /// 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. /// /// By default, both 'start' and 'end' are inclusive. public void SetAnalyzedRange(Statement start, Statement end, bool startInclusive = true, bool endInclusive = true) { var dictForStart = startInclusive ? beginNodeDict : endNodeDict; var dictForEnd = endInclusive ? endNodeDict : beginNodeDict; Debug.Assert(dictForStart.ContainsKey(start) && dictForEnd.ContainsKey(end)); int startIndex = dictForStart[start].Index; int endIndex = dictForEnd[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, CancellationToken cancellationToken = default(CancellationToken)) { this.analysisCancellationToken = cancellationToken; this.variableName = variable; try { // 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); } } finally { this.analysisCancellationToken = CancellationToken.None; this.variableName = null; } } 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 (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 cannot change back to CodeUnreachable after it once was reachable. // Also, don't ever use AssignedAfter... for statements. if (newStatus == DefiniteAssignmentStatus.CodeUnreachable || newStatus == DefiniteAssignmentStatus.AssignedAfterFalseExpression || newStatus == DefiniteAssignmentStatus.AssignedAfterTrueExpression) { throw new InvalidOperationException(); } // Note that the status can change from DefinitelyAssigned // back to PotentiallyAssigned as unreachable input edges are // discovered to be reachable. edgeStatus[edge] = newStatus; DefiniteAssignmentNode targetNode = (DefiniteAssignmentNode)edge.To; if (analyzedRangeStart <= targetNode.Index && targetNode.Index <= analyzedRangeEnd) { // TODO: potential optimization: visit previously unreachable nodes with higher priority // (e.g. use Deque and enqueue previously unreachable nodes at the front, but // other nodes at the end) nodesWithModifiedInput.Enqueue(targetNode); } } /// /// Evaluates an expression. /// /// The constant value of the expression; or null if the expression is not a constant. ResolveResult EvaluateConstant(Expression expr) { return resolver.Resolve(expr, analysisCancellationToken); } /// /// 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) { ResolveResult rr = EvaluateConstant(expr); if (rr != null && rr.IsCompileTimeConstant) 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; for (AstNode child = node.FirstChild; child != null; child = child.NextSibling) { analysis.analysisCancellationToken.ThrowIfCancellationRequested(); 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 #region Expressions 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 ResolveResult crr = analysis.EvaluateConstant(binaryOperatorExpression.Left); if (crr != null && crr.IsCompileTimeConstant && 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.FalseExpression.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; } #endregion } } }