mirror of https://github.com/icsharpcode/ILSpy.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
750 lines
34 KiB
750 lines
34 KiB
// 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.Decompiler.CSharp.Syntax; |
|
using ICSharpCode.Decompiler.CSharp.TypeSystem; |
|
using ICSharpCode.Decompiler.Semantics; |
|
using ICSharpCode.Decompiler.TypeSystem.Implementation; |
|
|
|
namespace ICSharpCode.Decompiler.CSharp.Analysis |
|
{ |
|
/// <summary> |
|
/// Represents the definite assignment status of a variable at a specific location. |
|
/// </summary> |
|
public enum DefiniteAssignmentStatus |
|
{ |
|
/// <summary> |
|
/// The variable might be assigned or unassigned. |
|
/// </summary> |
|
PotentiallyAssigned, |
|
/// <summary> |
|
/// The variable is definitely assigned. |
|
/// </summary> |
|
DefinitelyAssigned, |
|
/// <summary> |
|
/// The variable is definitely assigned iff the expression results in the value 'true'. |
|
/// </summary> |
|
AssignedAfterTrueExpression, |
|
/// <summary> |
|
/// The variable is definitely assigned iff the expression results in the value 'false'. |
|
/// </summary> |
|
AssignedAfterFalseExpression, |
|
/// <summary> |
|
/// The code is unreachable. |
|
/// </summary> |
|
CodeUnreachable |
|
} |
|
|
|
/// <summary> |
|
/// Implements the C# definite assignment analysis (C# 4.0 Spec: §5.3 Definite assignment) |
|
/// </summary> |
|
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<DefiniteAssignmentNode> allNodes = new List<DefiniteAssignmentNode>(); |
|
readonly Dictionary<Statement, DefiniteAssignmentNode> beginNodeDict = new Dictionary<Statement, DefiniteAssignmentNode>(); |
|
readonly Dictionary<Statement, DefiniteAssignmentNode> endNodeDict = new Dictionary<Statement, DefiniteAssignmentNode>(); |
|
readonly Dictionary<Statement, DefiniteAssignmentNode> conditionNodeDict = new Dictionary<Statement, DefiniteAssignmentNode>(); |
|
readonly Func<AstNode, CancellationToken, ResolveResult> resolve; |
|
Dictionary<ControlFlowEdge, DefiniteAssignmentStatus> edgeStatus = new Dictionary<ControlFlowEdge, DefiniteAssignmentStatus>(); |
|
|
|
string variableName; |
|
List<IdentifierExpression> unassignedVariableUses = new List<IdentifierExpression>(); |
|
int analyzedRangeStart, analyzedRangeEnd; |
|
CancellationToken analysisCancellationToken; |
|
|
|
Queue<DefiniteAssignmentNode> nodesWithModifiedInput = new Queue<DefiniteAssignmentNode>(); |
|
|
|
public DefiniteAssignmentAnalysis(Statement rootStatement, Func<AstNode, CancellationToken, ResolveResult> resolve, CSharpTypeResolveContext context, CancellationToken cancellationToken) |
|
{ |
|
if (rootStatement == null) |
|
throw new ArgumentNullException("rootStatement"); |
|
if (resolve == null) |
|
throw new ArgumentNullException("resolve"); |
|
this.resolve = resolve; |
|
|
|
visitor.analysis = this; |
|
DerivedControlFlowGraphBuilder cfgBuilder = new DerivedControlFlowGraphBuilder(); |
|
if (context.Compilation.MainAssembly.UnresolvedAssembly is MinimalCorlib) { |
|
cfgBuilder.EvaluateOnlyPrimitiveConstants = true; |
|
} |
|
allNodes.AddRange(cfgBuilder.BuildControlFlowGraph(rootStatement, resolve, context, cancellationToken).Cast<DefiniteAssignmentNode>()); |
|
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, context, 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<Statement>().Except(allNodes.Select(n => n.NextStatement)).Any()); |
|
// Verify that we put all nodes into the dictionaries: |
|
Debug.Assert(rootStatement.DescendantsAndSelf.OfType<Statement>().All(stmt => beginNodeDict.ContainsKey(stmt))); |
|
Debug.Assert(rootStatement.DescendantsAndSelf.OfType<Statement>().All(stmt => endNodeDict.ContainsKey(stmt))); |
|
|
|
this.analyzedRangeStart = 0; |
|
this.analyzedRangeEnd = allNodes.Count - 1; |
|
} |
|
|
|
void InsertAnonymousMethods(int insertPos, AstNode node, ControlFlowGraphBuilder cfgBuilder, CSharpTypeResolveContext context, 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, resolve, context, cancellationToken).Cast<DefiniteAssignmentNode>()); |
|
return; |
|
} |
|
LambdaExpression lambda = node as LambdaExpression; |
|
if (lambda != null && lambda.Body is Statement) { |
|
allNodes.InsertRange(insertPos, cfgBuilder.BuildControlFlowGraph((Statement)lambda.Body, resolve, context, cancellationToken).Cast<DefiniteAssignmentNode>()); |
|
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, context, cancellationToken); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Gets the unassigned usages of the previously analyzed variable. |
|
/// </summary> |
|
public IList<IdentifierExpression> UnassignedVariableUses { |
|
get { |
|
return unassignedVariableUses.AsReadOnly(); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// 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. |
|
/// </summary> |
|
/// <remarks>By default, both 'start' and 'end' are inclusive.</remarks> |
|
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; |
|
} |
|
|
|
/*/// <summary> |
|
/// Exports the CFG. This method is intended to help debugging issues related to definite assignment. |
|
/// </summary> |
|
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); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Evaluates an expression. |
|
/// </summary> |
|
/// <returns>The constant value of the expression; or null if the expression is not a constant.</returns> |
|
ResolveResult EvaluateConstant(Expression expr) |
|
{ |
|
return resolve(expr, analysisCancellationToken); |
|
} |
|
|
|
/// <summary> |
|
/// Evaluates an expression. |
|
/// </summary> |
|
/// <returns>The value of the constant boolean expression; or null if the value is not a constant boolean expression.</returns> |
|
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<DefiniteAssignmentStatus, DefiniteAssignmentStatus> |
|
{ |
|
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 |
|
} |
|
} |
|
}
|
|
|