.NET Decompiler with support for PDB generation, ReadyToRun, Metadata (&more) - cross-platform!
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.
 
 
 
 

2215 lines
90 KiB

//
// NullValueAnalysis.cs
//
// Author:
// Luís Reis <luiscubal@gmail.com>
//
// Copyright (c) 2013 Luís Reis
//
// 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;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Text;
using ICSharpCode.NRefactory.CSharp.Resolver;
using ICSharpCode.NRefactory.Semantics;
using ICSharpCode.NRefactory.TypeSystem;
using ICSharpCode.NRefactory.CSharp.Refactoring;
using ICSharpCode.NRefactory.PatternMatching;
using ICSharpCode.NRefactory.CSharp;
using ICSharpCode.NRefactory.Utils;
namespace ICSharpCode.NRefactory.CSharp.Analysis
{
public class NullValueAnalysis
{
sealed class VariableStatusInfo : IEquatable<VariableStatusInfo>, IEnumerable<KeyValuePair<string, NullValueStatus>>
{
readonly Dictionary<string, NullValueStatus> VariableStatus = new Dictionary<string, NullValueStatus>();
public NullValueStatus this[string name]
{
get {
NullValueStatus status;
if (VariableStatus.TryGetValue(name, out status)) {
return status;
}
return NullValueStatus.UnreachableOrInexistent;
}
set {
if (value == NullValueStatus.UnreachableOrInexistent) {
VariableStatus.Remove(name);
} else {
VariableStatus [name] = value;
}
}
}
/// <summary>
/// Modifies the variable state to consider a new incoming path
/// </summary>
/// <returns><c>true</c>, if the state has changed, <c>false</c> otherwise.</returns>
/// <param name="incomingState">The variable state of the incoming path</param>
public bool ReceiveIncoming(VariableStatusInfo incomingState)
{
bool changed = false;
var listOfVariables = VariableStatus.Keys.Concat(incomingState.VariableStatus.Keys).ToList();
foreach (string variable in listOfVariables)
{
var newValue = CombineStatus(this [variable], incomingState [variable]);
if (this [variable] != newValue) {
this [variable] = newValue;
changed = true;
}
}
return changed;
}
public static NullValueStatus CombineStatus(NullValueStatus oldValue, NullValueStatus incomingValue)
{
if (oldValue == NullValueStatus.Error || incomingValue == NullValueStatus.Error)
return NullValueStatus.Error;
if (oldValue == NullValueStatus.UnreachableOrInexistent ||
oldValue == NullValueStatus.Unassigned)
return incomingValue;
if (incomingValue == NullValueStatus.Unassigned) {
return NullValueStatus.Unassigned;
}
if (oldValue == NullValueStatus.CapturedUnknown || incomingValue == NullValueStatus.CapturedUnknown) {
//TODO: Check if this is right
return NullValueStatus.CapturedUnknown;
}
if (oldValue == NullValueStatus.Unknown) {
return NullValueStatus.Unknown;
}
if (oldValue == NullValueStatus.DefinitelyNull) {
return incomingValue == NullValueStatus.DefinitelyNull ?
NullValueStatus.DefinitelyNull : NullValueStatus.PotentiallyNull;
}
if (oldValue == NullValueStatus.DefinitelyNotNull) {
if (incomingValue == NullValueStatus.Unknown)
return NullValueStatus.Unknown;
if (incomingValue == NullValueStatus.DefinitelyNotNull)
return NullValueStatus.DefinitelyNotNull;
return NullValueStatus.PotentiallyNull;
}
Debug.Assert(oldValue == NullValueStatus.PotentiallyNull);
return NullValueStatus.PotentiallyNull;
}
public bool HasVariable(string variable) {
return VariableStatus.ContainsKey(variable);
}
public VariableStatusInfo Clone() {
var clone = new VariableStatusInfo();
foreach (var item in VariableStatus) {
clone.VariableStatus.Add(item.Key, item.Value);
}
return clone;
}
public override bool Equals(object obj)
{
return Equals(obj as VariableStatusInfo);
}
public bool Equals(VariableStatusInfo obj)
{
if (obj == null) {
return false;
}
if (VariableStatus.Count != obj.VariableStatus.Count)
return false;
return VariableStatus.All(item => item.Value == obj[item.Key]);
}
public override int GetHashCode()
{
//STUB
return VariableStatus.Count.GetHashCode();
}
public static bool operator ==(VariableStatusInfo obj1, VariableStatusInfo obj2) {
return object.ReferenceEquals(obj1, null) ?
object.ReferenceEquals(obj2, null) : obj1.Equals(obj2);
}
public static bool operator !=(VariableStatusInfo obj1, VariableStatusInfo obj2) {
return !(obj1 == obj2);
}
public IEnumerator<KeyValuePair<string, NullValueStatus>> GetEnumerator()
{
return VariableStatus.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public override string ToString()
{
var builder = new StringBuilder("[");
foreach (var item in this) {
builder.Append(item.Key);
builder.Append("=");
builder.Append(item.Value);
}
builder.Append("]");
return builder.ToString();
}
}
sealed class NullAnalysisNode : ControlFlowNode
{
public readonly VariableStatusInfo VariableState = new VariableStatusInfo();
public bool Visited { get; private set; }
public NullAnalysisNode(Statement previousStatement, Statement nextStatement, ControlFlowNodeType type)
: base(previousStatement, nextStatement, type)
{
}
public bool ReceiveIncoming(VariableStatusInfo incomingState)
{
bool changed = VariableState.ReceiveIncoming(incomingState);
if (!Visited) {
Visited = true;
return true;
}
return changed;
}
}
sealed class NullAnalysisGraphBuilder : ControlFlowGraphBuilder
{
protected override ControlFlowNode CreateNode(Statement previousStatement, Statement nextStatement, ControlFlowNodeType type)
{
return new NullAnalysisNode(previousStatement, nextStatement, type);
}
}
class PendingNode : IEquatable<PendingNode> {
internal readonly NullAnalysisNode nodeToVisit;
internal readonly VariableStatusInfo statusInfo;
internal readonly ComparableList<NullAnalysisNode> pendingTryFinallyNodes;
internal readonly NullAnalysisNode nodeAfterFinally;
internal PendingNode(NullAnalysisNode nodeToVisit, VariableStatusInfo statusInfo)
: this(nodeToVisit, statusInfo, new ComparableList<NullAnalysisNode>(), null)
{
}
public PendingNode(NullAnalysisNode nodeToVisit, VariableStatusInfo statusInfo, ComparableList<NullAnalysisNode> pendingFinallyNodes, NullAnalysisNode nodeAfterFinally)
{
this.nodeToVisit = nodeToVisit;
this.statusInfo = statusInfo;
this.pendingTryFinallyNodes = pendingFinallyNodes;
this.nodeAfterFinally = nodeAfterFinally;
}
public override bool Equals(object obj)
{
return Equals(obj as PendingNode);
}
public bool Equals(PendingNode obj) {
if (obj == null) return false;
if (nodeToVisit != obj.nodeToVisit) return false;
if (statusInfo != obj.statusInfo) return false;
if (pendingTryFinallyNodes != obj.pendingTryFinallyNodes) return false;
if (nodeAfterFinally != obj.nodeAfterFinally) return false;
return true;
}
public override int GetHashCode()
{
return nodeToVisit.GetHashCode() ^
statusInfo.GetHashCode() ^
pendingTryFinallyNodes.GetHashCode() ^
(nodeAfterFinally == null ? 0 : nodeAfterFinally.GetHashCode());
}
}
readonly BaseRefactoringContext context;
readonly NullAnalysisVisitor visitor;
List<NullAnalysisNode> allNodes;
readonly HashSet<PendingNode> nodesToVisit = new HashSet<PendingNode>();
Dictionary<Statement, NullAnalysisNode> nodeBeforeStatementDict;
Dictionary<Statement, NullAnalysisNode> nodeAfterStatementDict;
readonly Dictionary<Expression, NullValueStatus> expressionResult = new Dictionary<Expression, NullValueStatus>();
public NullValueAnalysis(BaseRefactoringContext context, MethodDeclaration methodDeclaration, CancellationToken cancellationToken)
: this(context, methodDeclaration.Body, methodDeclaration.Parameters, cancellationToken)
{
}
readonly IEnumerable<ParameterDeclaration> parameters;
readonly Statement rootStatement;
readonly CancellationToken cancellationToken;
public NullValueAnalysis(BaseRefactoringContext context, Statement rootStatement, IEnumerable<ParameterDeclaration> parameters, CancellationToken cancellationToken)
{
if (rootStatement == null)
throw new ArgumentNullException("rootStatement");
if (context == null)
throw new ArgumentNullException("context");
this.context = context;
this.rootStatement = rootStatement;
this.parameters = parameters;
this.visitor = new NullAnalysisVisitor(this);
this.cancellationToken = cancellationToken;
}
/// <summary>
/// Sets the local variable value.
/// This method does not change anything if identifier does not refer to a local variable.
/// Do not use this in variable declarations since resolving the variable won't work yet.
/// </summary>
/// <returns><c>true</c>, if local variable value was set, <c>false</c> otherwise.</returns>
/// <param name="data">The variable status data to change.</param>
/// <param name="identifierNode">The identifier to set.</param>
/// <param name="identifierName">The name of the identifier to set.</param>
/// <param name="value">The value to set the identifier.</param>
bool SetLocalVariableValue (VariableStatusInfo data, AstNode identifierNode, string identifierName, NullValueStatus value) {
var resolveResult = context.Resolve(identifierNode);
if (resolveResult is LocalResolveResult) {
if (data [identifierName] != NullValueStatus.CapturedUnknown) {
data [identifierName] = value;
return true;
}
}
return false;
}
bool SetLocalVariableValue (VariableStatusInfo data, IdentifierExpression identifierExpression, NullValueStatus value) {
return SetLocalVariableValue(data, identifierExpression, identifierExpression.Identifier, value);
}
bool SetLocalVariableValue (VariableStatusInfo data, Identifier identifier, NullValueStatus value) {
return SetLocalVariableValue(data, identifier, identifier.Name, value);
}
void SetupNode(NullAnalysisNode node)
{
foreach (var parameter in parameters) {
var resolveResult = context.Resolve(parameter.Type);
node.VariableState[parameter.Name] = GetInitialVariableStatus(resolveResult);
}
nodesToVisit.Add(new PendingNode(node, node.VariableState));
}
static bool IsTypeNullable(IType type)
{
return type.IsReferenceType == true || type.FullName == "System.Nullable";
}
public bool IsParametersAreUninitialized {
get;
set;
}
NullValueStatus GetInitialVariableStatus(ResolveResult resolveResult)
{
var typeResolveResult = resolveResult as TypeResolveResult;
if (typeResolveResult == null) {
return NullValueStatus.Error;
}
var type = typeResolveResult.Type;
if (type.IsReferenceType == null) {
return NullValueStatus.Error;
}
if (!IsParametersAreUninitialized)
return NullValueStatus.DefinitelyNotNull;
return IsTypeNullable(type) ? NullValueStatus.PotentiallyNull : NullValueStatus.DefinitelyNotNull;
}
public void Analyze()
{
var cfgBuilder = new NullAnalysisGraphBuilder();
allNodes = cfgBuilder.BuildControlFlowGraph(rootStatement, cancellationToken).Cast<NullAnalysisNode>().ToList();
nodeBeforeStatementDict = allNodes.Where(node => node.Type == ControlFlowNodeType.StartNode || node.Type == ControlFlowNodeType.BetweenStatements)
.ToDictionary(node => node.NextStatement);
nodeAfterStatementDict = allNodes.Where(node => node.Type == ControlFlowNodeType.BetweenStatements || node.Type == ControlFlowNodeType.EndNode)
.ToDictionary(node => node.PreviousStatement);
foreach (var node in allNodes) {
if (node.Type == ControlFlowNodeType.StartNode && node.NextStatement == rootStatement) {
Debug.Assert(!nodesToVisit.Any());
SetupNode(node);
}
}
while (nodesToVisit.Any()) {
var nodeToVisit = nodesToVisit.First();
nodesToVisit.Remove(nodeToVisit);
Visit(nodeToVisit);
}
}
int visits = 0;
public int NodeVisits
{
get {
return visits;
}
}
void Visit(PendingNode nodeInfo)
{
cancellationToken.ThrowIfCancellationRequested();
var node = nodeInfo.nodeToVisit;
var statusInfo = nodeInfo.statusInfo;
visits++;
if (visits > 100) {
//Visiting way too often, let's enter fast mode
//Fast mode is slighly less accurate but visits each node less times
nodesToVisit.RemoveWhere(candidate => candidate.nodeToVisit == nodeInfo.nodeToVisit &&
candidate.pendingTryFinallyNodes.Equals(nodeInfo.pendingTryFinallyNodes) &&
candidate.nodeAfterFinally == nodeInfo.nodeAfterFinally);
statusInfo = node.VariableState;
}
var nextStatement = node.NextStatement;
VariableStatusInfo outgoingStatusInfo = statusInfo;
VisitorResult result = null;
if (nextStatement != null && (!(nextStatement is DoWhileStatement) || node.Type == ControlFlowNodeType.LoopCondition)) {
result = nextStatement.AcceptVisitor(visitor, statusInfo);
if (result == null) {
Console.WriteLine("Failure in {0}", nextStatement);
throw new InvalidOperationException();
}
outgoingStatusInfo = result.Variables;
}
if ((result == null || !result.ThrowsException) && node.Outgoing.Any()) {
var tryFinallyStatement = nextStatement as TryCatchStatement;
foreach (var outgoingEdge in node.Outgoing) {
VariableStatusInfo edgeInfo;
edgeInfo = outgoingStatusInfo.Clone();
if (node.Type == ControlFlowNodeType.EndNode) {
var previousBlock = node.PreviousStatement as BlockStatement;
if (previousBlock != null) {
//We're leaving a block statement.
//As such, we'll remove the variables that were declared *in* the loop
//This helps GetVariableStatusAfter/BeforeStatement be more accurate
//and prevents some redundant revisiting.
foreach (var variableInitializer in previousBlock.Statements
.OfType<VariableDeclarationStatement>()
.SelectMany(declaration => declaration.Variables)) {
edgeInfo [variableInitializer.Name] = NullValueStatus.UnreachableOrInexistent;
}
}
}
if (tryFinallyStatement != null) {
//With the exception of try statements, this needs special handling:
//we'll set all changed variables to Unknown or CapturedUnknown
if (outgoingEdge.To.NextStatement == tryFinallyStatement.FinallyBlock) {
foreach (var identifierExpression in tryFinallyStatement.TryBlock.Descendants.OfType<IdentifierExpression>()) {
//TODO: Investigate CaptureUnknown
SetLocalVariableValue(edgeInfo, identifierExpression, NullValueStatus.Unknown);
}
} else {
var clause = tryFinallyStatement.CatchClauses
.FirstOrDefault(candidateClause => candidateClause.Body == outgoingEdge.To.NextStatement);
if (clause != null) {
SetLocalVariableValue(edgeInfo, clause.VariableNameToken, NullValueStatus.DefinitelyNotNull);
foreach (var identifierExpression in tryFinallyStatement.TryBlock.Descendants.OfType<IdentifierExpression>()) {
//TODO: Investigate CaptureUnknown
SetLocalVariableValue(edgeInfo, identifierExpression, NullValueStatus.Unknown);
}
}
}
}
if (result != null) {
switch (outgoingEdge.Type) {
case ControlFlowEdgeType.ConditionTrue:
if (result.KnownBoolResult == false) {
//No need to explore this path -- expression is known to be false
continue;
}
edgeInfo = result.TruePathVariables;
break;
case ControlFlowEdgeType.ConditionFalse:
if (result.KnownBoolResult == true) {
//No need to explore this path -- expression is known to be true
continue;
}
edgeInfo = result.FalsePathVariables;
break;
}
}
if (outgoingEdge.IsLeavingTryFinally) {
var nodeAfterFinally = (NullAnalysisNode)outgoingEdge.To;
var finallyNodes = outgoingEdge.TryFinallyStatements.Select(tryFinally => nodeBeforeStatementDict [tryFinally.FinallyBlock]).ToList();
var nextNode = finallyNodes.First();
var remainingFinallyNodes = new ComparableList<NullAnalysisNode>(finallyNodes.Skip(1));
//We have to visit the node even if ReceiveIncoming returns false
//since the finallyNodes/nodeAfterFinally might be different even if the values of variables are the same -- and they need to be visited either way!
//TODO 1: Is there any point in visiting the finally statement here?
//TODO 2: Do we need the ReceiveIncoming at all?
nextNode.ReceiveIncoming(edgeInfo);
nodesToVisit.Add(new PendingNode(nextNode, edgeInfo, remainingFinallyNodes, nodeAfterFinally));
} else {
var outgoingNode = (NullAnalysisNode)outgoingEdge.To;
if (outgoingNode.ReceiveIncoming(edgeInfo)) {
nodesToVisit.Add(new PendingNode(outgoingNode, edgeInfo));
}
}
}
} else {
//We found a return/throw/yield break or some other termination node
var finallyBlockStarts = nodeInfo.pendingTryFinallyNodes;
var nodeAfterFinally = nodeInfo.nodeAfterFinally;
if (finallyBlockStarts.Any()) {
var nextNode = finallyBlockStarts.First();
if (nextNode.ReceiveIncoming(outgoingStatusInfo))
nodesToVisit.Add(new PendingNode(nextNode, outgoingStatusInfo, new ComparableList<NullAnalysisNode>(finallyBlockStarts.Skip(1)), nodeInfo.nodeAfterFinally));
} else if (nodeAfterFinally != null && nodeAfterFinally.ReceiveIncoming(outgoingStatusInfo)) {
nodesToVisit.Add(new PendingNode(nodeAfterFinally, outgoingStatusInfo));
} else {
//Maybe we finished a try/catch/finally statement the "normal" way (no direct jumps)
//so let's check that case
var statement = node.PreviousStatement ?? node.NextStatement;
Debug.Assert(statement != null);
var parent = statement.GetParent<Statement>();
var parentTryCatch = parent as TryCatchStatement;
if (parentTryCatch != null) {
var nextNode = nodeAfterStatementDict [parentTryCatch];
if (nextNode.ReceiveIncoming(outgoingStatusInfo)) {
nodesToVisit.Add(new PendingNode(nextNode, outgoingStatusInfo));
}
}
}
}
}
public NullValueStatus GetExpressionResult(Expression expr)
{
if (expr == null)
throw new ArgumentNullException("expr");
NullValueStatus info;
if (expressionResult.TryGetValue(expr, out info)) {
return info;
}
return NullValueStatus.UnreachableOrInexistent;
}
public NullValueStatus GetVariableStatusBeforeStatement(Statement stmt, string variableName)
{
if (stmt == null)
throw new ArgumentNullException("stmt");
if (variableName == null)
throw new ArgumentNullException("variableName");
NullAnalysisNode node;
if (nodeBeforeStatementDict.TryGetValue(stmt, out node)) {
return node.VariableState [variableName];
}
return NullValueStatus.UnreachableOrInexistent;
}
public NullValueStatus GetVariableStatusAfterStatement(Statement stmt, string variableName)
{
if (stmt == null)
throw new ArgumentNullException("stmt");
if (variableName == null)
throw new ArgumentNullException("variableName");
NullAnalysisNode node;
if (nodeAfterStatementDict.TryGetValue(stmt, out node)) {
return node.VariableState [variableName];
}
return NullValueStatus.UnreachableOrInexistent;
}
class ConditionalBranchInfo
{
/// <summary>
/// True if the variable is null for the true path, false if it is false for the true path.
/// </summary>
public Dictionary<string, bool> TrueResultVariableNullStates = new Dictionary<string, bool>();
/// <summary>
/// True if the variable is null for the false path, false if it is false for the false path.
/// </summary>
public Dictionary<string, bool> FalseResultVariableNullStates = new Dictionary<string, bool>();
}
class VisitorResult
{
/// <summary>
/// Indicates the return value of the expression.
/// </summary>
/// <remarks>
/// Only applicable for expressions.
/// </remarks>
public NullValueStatus NullableReturnResult;
/// <summary>
/// Indicates the value of each item in an array or linq query.
/// </summary>
public NullValueStatus EnumeratedValueResult;
/// <summary>
/// Information that indicates the restrictions to add
/// when branching.
/// </summary>
/// <remarks>
/// Used in if/else statements, conditional expressions and
/// while statements.
/// </remarks>
public ConditionalBranchInfo ConditionalBranchInfo;
/// <summary>
/// The state of the variables after the expression is executed.
/// </summary>
public VariableStatusInfo Variables;
/// <summary>
/// The expression is known to be invalid and trigger an error
/// (e.g. a NullReferenceException)
/// </summary>
public bool ThrowsException;
/// <summary>
/// The known bool result of an expression.
/// </summary>
public bool? KnownBoolResult;
public static VisitorResult ForEnumeratedValue(VariableStatusInfo variables, NullValueStatus itemValues)
{
var result = new VisitorResult();
result.NullableReturnResult = NullValueStatus.DefinitelyNotNull;
result.EnumeratedValueResult = itemValues;
result.Variables = variables.Clone();
return result;
}
public static VisitorResult ForValue(VariableStatusInfo variables, NullValueStatus returnValue)
{
var result = new VisitorResult();
result.NullableReturnResult = returnValue;
result.Variables = variables.Clone();
return result;
}
public static VisitorResult ForBoolValue(VariableStatusInfo variables, bool newValue)
{
var result = new VisitorResult();
result.NullableReturnResult = NullValueStatus.DefinitelyNotNull; //Bool expressions are never null
result.KnownBoolResult = newValue;
result.Variables = variables.Clone();
return result;
}
public static VisitorResult ForException(VariableStatusInfo variables) {
var result = new VisitorResult();
result.NullableReturnResult = NullValueStatus.UnreachableOrInexistent;
result.ThrowsException = true;
result.Variables = variables.Clone();
return result;
}
public VisitorResult Negated {
get {
var result = new VisitorResult();
if (NullableReturnResult.IsDefiniteValue()) {
result.NullableReturnResult = NullableReturnResult == NullValueStatus.DefinitelyNull
? NullValueStatus.DefinitelyNotNull : NullValueStatus.DefinitelyNull;
} else {
result.NullableReturnResult = NullableReturnResult;
}
result.Variables = Variables.Clone();
result.KnownBoolResult = !KnownBoolResult;
if (ConditionalBranchInfo != null) {
result.ConditionalBranchInfo = new ConditionalBranchInfo();
foreach (var item in ConditionalBranchInfo.TrueResultVariableNullStates) {
result.ConditionalBranchInfo.FalseResultVariableNullStates [item.Key] = item.Value;
}
foreach (var item in ConditionalBranchInfo.FalseResultVariableNullStates) {
result.ConditionalBranchInfo.TrueResultVariableNullStates [item.Key] = item.Value;
}
}
return result;
}
}
public VariableStatusInfo TruePathVariables {
get {
var variables = Variables.Clone();
if (ConditionalBranchInfo != null) {
foreach (var item in ConditionalBranchInfo.TrueResultVariableNullStates) {
variables [item.Key] = item.Value ? NullValueStatus.DefinitelyNull : NullValueStatus.DefinitelyNotNull;
}
}
return variables;
}
}
public VariableStatusInfo FalsePathVariables {
get {
var variables = Variables.Clone();
if (ConditionalBranchInfo != null) {
foreach (var item in ConditionalBranchInfo.FalseResultVariableNullStates) {
variables [item.Key] = item.Value ? NullValueStatus.DefinitelyNull : NullValueStatus.DefinitelyNotNull;
}
}
return variables;
}
}
public static VisitorResult AndOperation(VisitorResult tentativeLeftResult, VisitorResult tentativeRightResult)
{
var result = new VisitorResult();
result.KnownBoolResult = tentativeLeftResult.KnownBoolResult & tentativeRightResult.KnownBoolResult;
var trueTruePath = tentativeRightResult.TruePathVariables;
var trueFalsePath = tentativeRightResult.FalsePathVariables;
var falsePath = tentativeLeftResult.FalsePathVariables;
var trueVariables = trueTruePath;
var falseVariables = trueFalsePath.Clone();
falseVariables.ReceiveIncoming(falsePath);
result.Variables = trueVariables.Clone();
result.Variables.ReceiveIncoming(falseVariables);
result.ConditionalBranchInfo = new ConditionalBranchInfo();
foreach (var variable in trueVariables) {
if (!variable.Value.IsDefiniteValue())
continue;
string variableName = variable.Key;
if (variable.Value != result.Variables[variableName]) {
bool isNull = variable.Value == NullValueStatus.DefinitelyNull;
result.ConditionalBranchInfo.TrueResultVariableNullStates.Add(variableName, isNull);
}
}
foreach (var variable in falseVariables) {
if (!variable.Value.IsDefiniteValue())
continue;
string variableName = variable.Key;
if (variable.Value != result.Variables [variableName]) {
bool isNull = variable.Value == NullValueStatus.DefinitelyNull;
result.ConditionalBranchInfo.FalseResultVariableNullStates.Add(variableName, isNull);
}
}
return result;
}
public static VisitorResult OrOperation(VisitorResult tentativeLeftResult, VisitorResult tentativeRightResult)
{
return VisitorResult.AndOperation(tentativeLeftResult.Negated, tentativeRightResult.Negated).Negated;
}
}
class NullAnalysisVisitor : DepthFirstAstVisitor<VariableStatusInfo, VisitorResult>
{
NullValueAnalysis analysis;
public NullAnalysisVisitor(NullValueAnalysis analysis) {
this.analysis = analysis;
}
protected override VisitorResult VisitChildren(AstNode node, VariableStatusInfo data)
{
Debug.Fail("Missing override for " + node.GetType().Name);
return VisitorResult.ForValue(data, NullValueStatus.Unknown);
}
public override VisitorResult VisitNullNode(AstNode nullNode, VariableStatusInfo data)
{
// can occur due to syntax errors
return VisitorResult.ForValue(data, NullValueStatus.Unknown);
}
public override VisitorResult VisitEmptyStatement(EmptyStatement emptyStatement, VariableStatusInfo data)
{
return VisitorResult.ForValue(data, NullValueStatus.Unknown);
}
public override VisitorResult VisitBlockStatement(BlockStatement blockStatement, VariableStatusInfo data)
{
//We'll visit the child statements later (we'll visit each one directly from the CFG)
//As such this is mostly a dummy node.
return new VisitorResult { Variables = data };
}
public override VisitorResult VisitVariableDeclarationStatement(VariableDeclarationStatement variableDeclarationStatement, VariableStatusInfo data)
{
foreach (var variable in variableDeclarationStatement.Variables) {
var result = variable.AcceptVisitor(this, data);
if (result.ThrowsException)
return result;
data = result.Variables;
}
return VisitorResult.ForValue(data, NullValueStatus.Unknown);
}
public override VisitorResult VisitVariableInitializer(VariableInitializer variableInitializer, VariableStatusInfo data)
{
if (variableInitializer.Initializer.IsNull) {
data = data.Clone();
data[variableInitializer.Name] = NullValueStatus.Unassigned;
} else {
var result = variableInitializer.Initializer.AcceptVisitor(this, data);
if (result.ThrowsException)
return result;
data = result.Variables.Clone();
data[variableInitializer.Name] = result.NullableReturnResult;
}
return VisitorResult.ForValue(data, data [variableInitializer.Name]);
}
public override VisitorResult VisitIfElseStatement(IfElseStatement ifElseStatement, VariableStatusInfo data)
{
//We'll visit the true/false statements later (directly from the CFG)
return ifElseStatement.Condition.AcceptVisitor(this, data);
}
public override VisitorResult VisitWhileStatement(WhileStatement whileStatement, VariableStatusInfo data)
{
return whileStatement.Condition.AcceptVisitor(this, data);
}
public override VisitorResult VisitDoWhileStatement(DoWhileStatement doWhileStatement, VariableStatusInfo data)
{
return doWhileStatement.Condition.AcceptVisitor(this, data);
}
public override VisitorResult VisitForStatement(ForStatement forStatement, VariableStatusInfo data)
{
//The initializers, the embedded statement and the iterators aren't visited here
//because they have their own CFG nodes.
if (forStatement.Condition.IsNull)
return VisitorResult.ForValue(data, NullValueStatus.Unknown);
return forStatement.Condition.AcceptVisitor(this, data);
}
public override VisitorResult VisitForeachStatement(ForeachStatement foreachStatement, VariableStatusInfo data)
{
var newVariable = foreachStatement.VariableNameToken;
var inExpressionResult = foreachStatement.InExpression.AcceptVisitor(this, data);
if (inExpressionResult.ThrowsException)
return inExpressionResult;
var newData = inExpressionResult.Variables.Clone();
var resolveResult = analysis.context.Resolve(foreachStatement.VariableNameToken) as LocalResolveResult;
if (resolveResult != null) {
//C# 5.0 changed the meaning of foreach so that each iteration declares a new variable
//as such, the variable is "uncaptured" only for C# >= 5.0
if (analysis.context.Supports(new Version(5, 0)) || data[newVariable.Name] != NullValueStatus.CapturedUnknown) {
newData[newVariable.Name] = NullValueAnalysis.IsTypeNullable(resolveResult.Type) ? inExpressionResult.EnumeratedValueResult : NullValueStatus.DefinitelyNotNull;
}
}
return VisitorResult.ForValue(newData, NullValueStatus.Unknown);
}
public override VisitorResult VisitUsingStatement(UsingStatement usingStatement, VariableStatusInfo data)
{
return usingStatement.ResourceAcquisition.AcceptVisitor(this, data);
}
public override VisitorResult VisitFixedStatement(FixedStatement fixedStatement, VariableStatusInfo data)
{
foreach (var variable in fixedStatement.Variables) {
var result = variable.AcceptVisitor(this, data);
if (result.ThrowsException)
return result;
data = result.Variables;
}
return VisitorResult.ForValue(data, NullValueStatus.Unknown);
}
public override VisitorResult VisitSwitchStatement(SwitchStatement switchStatement, VariableStatusInfo data)
{
//We could do better than this, but it would require special handling outside the visitor
//so for now, for simplicity, we'll just take the easy way
var tentativeResult = switchStatement.Expression.AcceptVisitor(this, data);
if (tentativeResult.ThrowsException) {
return tentativeResult;
}
foreach (var section in switchStatement.SwitchSections) {
//No need to check for ThrowsException, since it will always be false (see VisitSwitchSection)
section.AcceptVisitor(this, tentativeResult.Variables);
}
return VisitorResult.ForValue(tentativeResult.Variables, NullValueStatus.Unknown);
}
public override VisitorResult VisitSwitchSection(SwitchSection switchSection, VariableStatusInfo data)
{
return VisitorResult.ForValue(data, NullValueStatus.Unknown);
}
public override VisitorResult VisitExpressionStatement(ExpressionStatement expressionStatement, VariableStatusInfo data)
{
return expressionStatement.Expression.AcceptVisitor(this, data);
}
public override VisitorResult VisitReturnStatement(ReturnStatement returnStatement, VariableStatusInfo data)
{
if (returnStatement.Expression.IsNull)
return VisitorResult.ForValue(data, NullValueStatus.Unknown);
return returnStatement.Expression.AcceptVisitor(this, data);
}
public override VisitorResult VisitTryCatchStatement(TryCatchStatement tryCatchStatement, VariableStatusInfo data)
{
//The needs special treatment in the analyser itself
return VisitorResult.ForValue(data, NullValueStatus.Unknown);
}
public override VisitorResult VisitBreakStatement(BreakStatement breakStatement, VariableStatusInfo data)
{
return VisitorResult.ForValue(data, NullValueStatus.Unknown);
}
public override VisitorResult VisitContinueStatement(ContinueStatement continueStatement, VariableStatusInfo data)
{
return VisitorResult.ForValue(data, NullValueStatus.Unknown);
}
public override VisitorResult VisitGotoStatement(GotoStatement gotoStatement, VariableStatusInfo data)
{
return VisitorResult.ForValue(data, NullValueStatus.Unknown);
}
public override VisitorResult VisitGotoCaseStatement(GotoCaseStatement gotoCaseStatement, VariableStatusInfo data)
{
return VisitorResult.ForValue(data, NullValueStatus.Unknown);
}
public override VisitorResult VisitGotoDefaultStatement(GotoDefaultStatement gotoDefaultStatement, VariableStatusInfo data)
{
return VisitorResult.ForValue(data, NullValueStatus.Unknown);
}
public override VisitorResult VisitLabelStatement(LabelStatement labelStatement, VariableStatusInfo data)
{
return VisitorResult.ForValue(data, NullValueStatus.Unknown);
}
public override VisitorResult VisitUnsafeStatement(UnsafeStatement unsafeStatement, VariableStatusInfo data)
{
return VisitorResult.ForValue(data, NullValueStatus.Unknown);
}
public override VisitorResult VisitLockStatement(LockStatement lockStatement, VariableStatusInfo data)
{
var expressionResult = lockStatement.Expression.AcceptVisitor(this, data);
if (expressionResult.ThrowsException)
return expressionResult;
if (expressionResult.NullableReturnResult == NullValueStatus.DefinitelyNull) {
return VisitorResult.ForException(expressionResult.Variables);
}
var identifier = CSharpUtil.GetInnerMostExpression(lockStatement.Expression) as IdentifierExpression;
if (identifier != null) {
var identifierValue = expressionResult.Variables [identifier.Identifier];
if (identifierValue != NullValueStatus.CapturedUnknown) {
var newVariables = expressionResult.Variables.Clone();
analysis.SetLocalVariableValue(newVariables, identifier, NullValueStatus.DefinitelyNotNull);
return VisitorResult.ForValue(newVariables, NullValueStatus.Unknown);
}
}
return VisitorResult.ForValue(expressionResult.Variables, NullValueStatus.Unknown);
}
public override VisitorResult VisitThrowStatement(ThrowStatement throwStatement, VariableStatusInfo data)
{
if (throwStatement.Expression.IsNull)
return VisitorResult.ForValue(data, NullValueStatus.DefinitelyNotNull);
return throwStatement.Expression.AcceptVisitor(this, data);
}
public override VisitorResult VisitYieldBreakStatement(YieldBreakStatement yieldBreakStatement, VariableStatusInfo data)
{
return VisitorResult.ForValue(data, NullValueStatus.Unknown);
}
public override VisitorResult VisitYieldReturnStatement(YieldReturnStatement yieldReturnStatement, VariableStatusInfo data)
{
return yieldReturnStatement.Expression.AcceptVisitor(this, data);
}
public override VisitorResult VisitCheckedStatement(CheckedStatement checkedStatement, VariableStatusInfo data)
{
return VisitorResult.ForValue(data, NullValueStatus.Unknown);
}
public override VisitorResult VisitUncheckedStatement(UncheckedStatement uncheckedStatement, VariableStatusInfo data)
{
return VisitorResult.ForValue(data, NullValueStatus.Unknown);
}
void RegisterExpressionResult(Expression expression, NullValueStatus expressionResult)
{
NullValueStatus oldStatus;
if (analysis.expressionResult.TryGetValue(expression, out oldStatus)) {
analysis.expressionResult[expression] = VariableStatusInfo.CombineStatus(analysis.expressionResult[expression], expressionResult);
}
else {
analysis.expressionResult[expression] = expressionResult;
}
}
VisitorResult HandleExpressionResult(Expression expression, VariableStatusInfo dataAfterExpression, NullValueStatus expressionResult) {
RegisterExpressionResult(expression, expressionResult);
return VisitorResult.ForValue(dataAfterExpression, expressionResult);
}
VisitorResult HandleExpressionResult(Expression expression, VariableStatusInfo dataAfterExpression, bool expressionResult) {
RegisterExpressionResult(expression, NullValueStatus.DefinitelyNotNull);
return VisitorResult.ForBoolValue(dataAfterExpression, expressionResult);
}
VisitorResult HandleExpressionResult(Expression expression, VisitorResult result) {
RegisterExpressionResult(expression, result.NullableReturnResult);
return result;
}
public override VisitorResult VisitAssignmentExpression(AssignmentExpression assignmentExpression, VariableStatusInfo data)
{
var tentativeResult = assignmentExpression.Left.AcceptVisitor(this, data);
if (tentativeResult.ThrowsException)
return HandleExpressionResult(assignmentExpression, tentativeResult);
tentativeResult = assignmentExpression.Right.AcceptVisitor(this, tentativeResult.Variables);
if (tentativeResult.ThrowsException)
return HandleExpressionResult(assignmentExpression, tentativeResult);
var leftIdentifier = assignmentExpression.Left as IdentifierExpression;
if (leftIdentifier != null) {
var resolveResult = analysis.context.Resolve(leftIdentifier);
if (resolveResult.IsError) {
return HandleExpressionResult(assignmentExpression, data, NullValueStatus.Error);
}
if (resolveResult is LocalResolveResult) {
var result = new VisitorResult();
result.NullableReturnResult = tentativeResult.NullableReturnResult;
result.Variables = tentativeResult.Variables.Clone();
var oldValue = result.Variables [leftIdentifier.Identifier];
if (assignmentExpression.Operator == AssignmentOperatorType.Assign ||
oldValue == NullValueStatus.Unassigned ||
oldValue == NullValueStatus.DefinitelyNotNull ||
tentativeResult.NullableReturnResult == NullValueStatus.Error ||
tentativeResult.NullableReturnResult == NullValueStatus.Unknown) {
analysis.SetLocalVariableValue(result.Variables, leftIdentifier, tentativeResult.NullableReturnResult);
} else {
if (oldValue == NullValueStatus.DefinitelyNull) {
//Do nothing --it'll remain null
} else {
analysis.SetLocalVariableValue(result.Variables, leftIdentifier, NullValueStatus.PotentiallyNull);
}
}
return HandleExpressionResult(assignmentExpression, result);
}
}
return HandleExpressionResult(assignmentExpression, tentativeResult);
}
public override VisitorResult VisitIdentifierExpression(IdentifierExpression identifierExpression, VariableStatusInfo data)
{
var resolveResult = analysis.context.Resolve(identifierExpression);
if (resolveResult.IsError) {
return HandleExpressionResult(identifierExpression, data, NullValueStatus.Error);
}
var local = resolveResult as LocalResolveResult;
if (local != null) {
var value = data [local.Variable.Name];
if (value == NullValueStatus.CapturedUnknown)
value = NullValueStatus.Unknown;
return HandleExpressionResult(identifierExpression, data, value);
}
if (resolveResult.IsCompileTimeConstant) {
object value = resolveResult.ConstantValue;
if (value == null) {
return HandleExpressionResult(identifierExpression, data, NullValueStatus.DefinitelyNull);
}
var boolValue = value as bool?;
if (boolValue != null) {
return VisitorResult.ForBoolValue(data, (bool)boolValue);
}
return HandleExpressionResult(identifierExpression, data, NullValueStatus.DefinitelyNotNull);
}
var memberResolveResult = resolveResult as MemberResolveResult;
var returnValue = GetFieldReturnValue(memberResolveResult, data);
return HandleExpressionResult(identifierExpression, data, returnValue);
}
public override VisitorResult VisitDefaultValueExpression(DefaultValueExpression defaultValueExpression, VariableStatusInfo data)
{
var resolveResult = analysis.context.Resolve(defaultValueExpression);
if (resolveResult.IsError) {
return HandleExpressionResult(defaultValueExpression, data, NullValueStatus.Unknown);
}
Debug.Assert(resolveResult.IsCompileTimeConstant);
var status = resolveResult.ConstantValue == null && resolveResult.Type.IsReferenceType != false ? NullValueStatus.DefinitelyNull : NullValueStatus.DefinitelyNotNull;
return HandleExpressionResult(defaultValueExpression, data, status);
}
public override VisitorResult VisitNullReferenceExpression(NullReferenceExpression nullReferenceExpression, VariableStatusInfo data)
{
return HandleExpressionResult(nullReferenceExpression, data, NullValueStatus.DefinitelyNull);
}
public override VisitorResult VisitPrimitiveExpression(PrimitiveExpression primitiveExpression, VariableStatusInfo data)
{
return HandleExpressionResult(primitiveExpression, data, NullValueStatus.DefinitelyNotNull);
}
public override VisitorResult VisitParenthesizedExpression(ParenthesizedExpression parenthesizedExpression, VariableStatusInfo data)
{
return HandleExpressionResult(parenthesizedExpression, parenthesizedExpression.Expression.AcceptVisitor(this, data));
}
public override VisitorResult VisitConditionalExpression(ConditionalExpression conditionalExpression, VariableStatusInfo data)
{
var tentativeBaseResult = conditionalExpression.Condition.AcceptVisitor(this, data);
if (tentativeBaseResult.ThrowsException)
return HandleExpressionResult(conditionalExpression, tentativeBaseResult);
var conditionResolveResult = analysis.context.Resolve(conditionalExpression.Condition);
if (tentativeBaseResult.KnownBoolResult == true || true.Equals(conditionResolveResult.ConstantValue)) {
return HandleExpressionResult(conditionalExpression, conditionalExpression.TrueExpression.AcceptVisitor(this, tentativeBaseResult.TruePathVariables));
}
if (tentativeBaseResult.KnownBoolResult == false || false.Equals(conditionResolveResult.ConstantValue)) {
return HandleExpressionResult(conditionalExpression, conditionalExpression.FalseExpression.AcceptVisitor(this, tentativeBaseResult.FalsePathVariables));
}
//No known bool result
var trueCaseResult = conditionalExpression.TrueExpression.AcceptVisitor(this, tentativeBaseResult.TruePathVariables);
if (trueCaseResult.ThrowsException) {
//We know that the true case will never be completed, then the right case is the only possible route.
return HandleExpressionResult(conditionalExpression, conditionalExpression.FalseExpression.AcceptVisitor(this, tentativeBaseResult.FalsePathVariables));
}
var falseCaseResult = conditionalExpression.FalseExpression.AcceptVisitor(this, tentativeBaseResult.FalsePathVariables);
if (falseCaseResult.ThrowsException) {
return HandleExpressionResult(conditionalExpression, trueCaseResult.Variables, true);
}
return HandleExpressionResult(conditionalExpression, VisitorResult.OrOperation(trueCaseResult, falseCaseResult));
}
public override VisitorResult VisitBinaryOperatorExpression(BinaryOperatorExpression binaryOperatorExpression, VariableStatusInfo data)
{
//Let's not evaluate the sides just yet because of ??, && and ||
//We'll register the results here (with HandleExpressionResult)
//so each Visit*Expression won't have to do it itself
switch (binaryOperatorExpression.Operator) {
case BinaryOperatorType.ConditionalAnd:
return HandleExpressionResult(binaryOperatorExpression, VisitConditionalAndExpression(binaryOperatorExpression, data));
case BinaryOperatorType.ConditionalOr:
return HandleExpressionResult(binaryOperatorExpression, VisitConditionalOrExpression(binaryOperatorExpression, data));
case BinaryOperatorType.NullCoalescing:
return HandleExpressionResult(binaryOperatorExpression, VisitNullCoalescing(binaryOperatorExpression, data));
case BinaryOperatorType.Equality:
return HandleExpressionResult(binaryOperatorExpression, VisitEquality(binaryOperatorExpression, data));
case BinaryOperatorType.InEquality:
return HandleExpressionResult(binaryOperatorExpression, VisitEquality(binaryOperatorExpression, data).Negated);
default:
return HandleExpressionResult(binaryOperatorExpression, VisitOtherBinaryExpression(binaryOperatorExpression, data));
}
}
VisitorResult VisitOtherBinaryExpression(BinaryOperatorExpression binaryOperatorExpression, VariableStatusInfo data)
{
var leftTentativeResult = binaryOperatorExpression.Left.AcceptVisitor(this, data);
if (leftTentativeResult.ThrowsException)
return leftTentativeResult;
var rightTentativeResult = binaryOperatorExpression.Right.AcceptVisitor(this, leftTentativeResult.Variables);
if (rightTentativeResult.ThrowsException)
return rightTentativeResult;
//TODO: Assuming operators are not overloaded by users
// (or, if they are, that they retain similar behavior to the default ones)
switch (binaryOperatorExpression.Operator) {
case BinaryOperatorType.LessThan:
case BinaryOperatorType.GreaterThan:
//Operations < and > with nulls always return false
//Those same operations will other values may or may not return false
if (leftTentativeResult.NullableReturnResult == NullValueStatus.DefinitelyNull &&
rightTentativeResult.NullableReturnResult == NullValueStatus.DefinitelyNull) {
return VisitorResult.ForBoolValue(rightTentativeResult.Variables, false);
}
//We don't know what the value is, but we know that both true and false are != null.
return VisitorResult.ForValue(rightTentativeResult.Variables, NullValueStatus.DefinitelyNotNull);
case BinaryOperatorType.LessThanOrEqual:
case BinaryOperatorType.GreaterThanOrEqual:
if (leftTentativeResult.NullableReturnResult == NullValueStatus.DefinitelyNull) {
if (rightTentativeResult.NullableReturnResult == NullValueStatus.DefinitelyNull)
return VisitorResult.ForBoolValue(rightTentativeResult.Variables, true);
if (rightTentativeResult.NullableReturnResult == NullValueStatus.DefinitelyNotNull)
return VisitorResult.ForBoolValue(rightTentativeResult.Variables, false);
} else if (leftTentativeResult.NullableReturnResult == NullValueStatus.DefinitelyNotNull) {
if (rightTentativeResult.NullableReturnResult == NullValueStatus.DefinitelyNull)
return VisitorResult.ForBoolValue(rightTentativeResult.Variables, false);
}
return VisitorResult.ForValue(rightTentativeResult.Variables, NullValueStatus.Unknown);
default:
//Anything else: null + anything == anything + null == null.
//not null + not null = not null
if (leftTentativeResult.NullableReturnResult == NullValueStatus.DefinitelyNull) {
return VisitorResult.ForValue(rightTentativeResult.Variables, NullValueStatus.DefinitelyNull);
}
if (leftTentativeResult.NullableReturnResult == NullValueStatus.DefinitelyNotNull) {
if (rightTentativeResult.NullableReturnResult == NullValueStatus.DefinitelyNull)
return VisitorResult.ForValue(rightTentativeResult.Variables, NullValueStatus.DefinitelyNull);
if (rightTentativeResult.NullableReturnResult == NullValueStatus.DefinitelyNotNull)
return VisitorResult.ForValue(rightTentativeResult.Variables, NullValueStatus.DefinitelyNotNull);
}
return VisitorResult.ForValue(rightTentativeResult.Variables, NullValueStatus.Unknown);
}
}
VisitorResult WithVariableValue(VisitorResult result, IdentifierExpression identifier, bool isNull)
{
var localVariableResult = analysis.context.Resolve(identifier) as LocalResolveResult;
if (localVariableResult != null) {
result.ConditionalBranchInfo.TrueResultVariableNullStates[identifier.Identifier] = isNull;
if (isNull) {
result.ConditionalBranchInfo.FalseResultVariableNullStates[identifier.Identifier] = false;
}
}
return result;
}
VisitorResult VisitEquality(BinaryOperatorExpression binaryOperatorExpression, VariableStatusInfo data)
{
//TODO: Should this check for user operators?
var tentativeLeftResult = binaryOperatorExpression.Left.AcceptVisitor(this, data);
if (tentativeLeftResult.ThrowsException)
return tentativeLeftResult;
var tentativeRightResult = binaryOperatorExpression.Right.AcceptVisitor(this, tentativeLeftResult.Variables);
if (tentativeRightResult.ThrowsException)
return tentativeRightResult;
if (tentativeLeftResult.KnownBoolResult != null && tentativeLeftResult.KnownBoolResult == tentativeRightResult.KnownBoolResult) {
return VisitorResult.ForBoolValue(tentativeRightResult.Variables, true);
}
if (tentativeLeftResult.KnownBoolResult != null && tentativeLeftResult.KnownBoolResult == !tentativeRightResult.KnownBoolResult) {
return VisitorResult.ForBoolValue(tentativeRightResult.Variables, false);
}
if (tentativeLeftResult.NullableReturnResult.IsDefiniteValue()) {
if (tentativeRightResult.NullableReturnResult.IsDefiniteValue()) {
if (tentativeLeftResult.NullableReturnResult == NullValueStatus.DefinitelyNull || tentativeRightResult.NullableReturnResult == NullValueStatus.DefinitelyNull) {
return VisitorResult.ForBoolValue(tentativeRightResult.Variables, tentativeLeftResult.NullableReturnResult == tentativeRightResult.NullableReturnResult);
}
}
}
var result = new VisitorResult();
result.Variables = tentativeRightResult.Variables;
result.NullableReturnResult = NullValueStatus.Unknown;
result.ConditionalBranchInfo = new ConditionalBranchInfo();
if (tentativeRightResult.NullableReturnResult.IsDefiniteValue()) {
var identifier = CSharpUtil.GetInnerMostExpression(binaryOperatorExpression.Left) as IdentifierExpression;
if (identifier != null) {
bool isNull = (tentativeRightResult.NullableReturnResult == NullValueStatus.DefinitelyNull);
WithVariableValue(result, identifier, isNull);
}
}
if (tentativeLeftResult.NullableReturnResult.IsDefiniteValue()) {
var identifier = CSharpUtil.GetInnerMostExpression(binaryOperatorExpression.Right) as IdentifierExpression;
if (identifier != null) {
bool isNull = (tentativeLeftResult.NullableReturnResult == NullValueStatus.DefinitelyNull);
WithVariableValue(result, identifier, isNull);
}
}
return result;
}
VisitorResult VisitConditionalAndExpression(BinaryOperatorExpression binaryOperatorExpression, VariableStatusInfo data)
{
var tentativeLeftResult = binaryOperatorExpression.Left.AcceptVisitor(this, data);
if (tentativeLeftResult.KnownBoolResult == false || tentativeLeftResult.ThrowsException) {
return tentativeLeftResult;
}
var truePath = tentativeLeftResult.TruePathVariables;
var tentativeRightResult = binaryOperatorExpression.Right.AcceptVisitor(this, truePath);
if (tentativeRightResult.ThrowsException) {
//If the true path throws an exception, then the only way for the expression to complete
//successfully is if the left expression is false
return VisitorResult.ForBoolValue(tentativeLeftResult.FalsePathVariables, false);
}
return VisitorResult.AndOperation(tentativeLeftResult, tentativeRightResult);
}
VisitorResult VisitConditionalOrExpression(BinaryOperatorExpression binaryOperatorExpression, VariableStatusInfo data)
{
var tentativeLeftResult = binaryOperatorExpression.Left.AcceptVisitor(this, data);
if (tentativeLeftResult.KnownBoolResult == true || tentativeLeftResult.ThrowsException) {
return tentativeLeftResult;
}
var falsePath = tentativeLeftResult.FalsePathVariables;
var tentativeRightResult = binaryOperatorExpression.Right.AcceptVisitor(this, falsePath);
if (tentativeRightResult.ThrowsException) {
//If the false path throws an exception, then the only way for the expression to complete
//successfully is if the left expression is true
return VisitorResult.ForBoolValue(tentativeLeftResult.TruePathVariables, true);
}
return VisitorResult.OrOperation(tentativeLeftResult, tentativeRightResult);
}
VisitorResult VisitNullCoalescing(BinaryOperatorExpression binaryOperatorExpression, VariableStatusInfo data)
{
var leftTentativeResult = binaryOperatorExpression.Left.AcceptVisitor(this, data);
if (leftTentativeResult.NullableReturnResult == NullValueStatus.DefinitelyNotNull || leftTentativeResult.ThrowsException) {
return leftTentativeResult;
}
//If the right side is found, then the left side is known to be null
var newData = leftTentativeResult.Variables;
var leftIdentifier = CSharpUtil.GetInnerMostExpression(binaryOperatorExpression.Left) as IdentifierExpression;
if (leftIdentifier != null) {
newData = newData.Clone();
analysis.SetLocalVariableValue(newData, leftIdentifier, NullValueStatus.DefinitelyNull);
}
var rightTentativeResult = binaryOperatorExpression.Right.AcceptVisitor(this, newData);
if (rightTentativeResult.ThrowsException) {
//This means the left expression was not null all along (or else the expression will throw an exception)
if (leftIdentifier != null) {
newData = newData.Clone();
analysis.SetLocalVariableValue(newData, leftIdentifier, NullValueStatus.DefinitelyNotNull);
return VisitorResult.ForValue(newData, NullValueStatus.DefinitelyNotNull);
}
return VisitorResult.ForValue(leftTentativeResult.Variables, NullValueStatus.DefinitelyNotNull);
}
var mergedVariables = rightTentativeResult.Variables;
var nullValue = rightTentativeResult.NullableReturnResult;
if (leftTentativeResult.NullableReturnResult != NullValueStatus.DefinitelyNull) {
mergedVariables = mergedVariables.Clone();
mergedVariables.ReceiveIncoming(leftTentativeResult.Variables);
if (nullValue == NullValueStatus.DefinitelyNull) {
nullValue = NullValueStatus.PotentiallyNull;
}
}
return VisitorResult.ForValue(mergedVariables, nullValue);
}
public override VisitorResult VisitUnaryOperatorExpression(UnaryOperatorExpression unaryOperatorExpression, VariableStatusInfo data)
{
//TODO: Again, what to do when overloaded operators are found?
var tentativeResult = unaryOperatorExpression.Expression.AcceptVisitor(this, data);
if (tentativeResult.ThrowsException)
return HandleExpressionResult(unaryOperatorExpression, tentativeResult);
if (unaryOperatorExpression.Operator == UnaryOperatorType.Not) {
return HandleExpressionResult(unaryOperatorExpression, tentativeResult.Negated);
}
return HandleExpressionResult(unaryOperatorExpression, tentativeResult);
}
public override VisitorResult VisitInvocationExpression(InvocationExpression invocationExpression, VariableStatusInfo data)
{
//TODO: Handle some common methods such as string.IsNullOrEmpty
var targetResult = invocationExpression.Target.AcceptVisitor(this, data);
if (targetResult.ThrowsException)
return HandleExpressionResult(invocationExpression, targetResult);
data = targetResult.Variables;
var methodResolveResult = analysis.context.Resolve(invocationExpression) as CSharpInvocationResolveResult;
List<VisitorResult> parameterResults = new List<VisitorResult>();
foreach (var argumentToHandle in invocationExpression.Arguments.Select((argument, parameterIndex) => new { argument, parameterIndex })) {
var argument = argumentToHandle.argument;
var parameterIndex = argumentToHandle.parameterIndex;
var result = argument.AcceptVisitor(this, data);
if (result.ThrowsException)
return HandleExpressionResult(invocationExpression, result);
parameterResults.Add(result);
var namedArgument = argument as NamedArgumentExpression;
var directionExpression = (namedArgument == null ? argument : namedArgument.Expression) as DirectionExpression;
if (directionExpression != null && methodResolveResult != null) {
var identifier = directionExpression.Expression as IdentifierExpression;
if (identifier != null) {
//out and ref parameters do *NOT* capture the variable (since they must stop changing it by the time they return)
var identifierResolveResult = analysis.context.Resolve(identifier) as LocalResolveResult;
if (identifierResolveResult != null && IsTypeNullable(identifierResolveResult.Type)) {
data = data.Clone();
FixParameter(argument, methodResolveResult.Member.Parameters, parameterIndex, identifier, data);
}
}
continue;
}
data = result.Variables;
}
var identifierExpression = CSharpUtil.GetInnerMostExpression(invocationExpression.Target) as IdentifierExpression;
if (identifierExpression != null) {
if (targetResult.NullableReturnResult == NullValueStatus.DefinitelyNull) {
return HandleExpressionResult(invocationExpression, VisitorResult.ForException(data));
}
var descendentIdentifiers = invocationExpression.Arguments.SelectMany(argument => argument.DescendantsAndSelf).OfType<IdentifierExpression>();
if (!descendentIdentifiers.Any(identifier => identifier.Identifier == identifierExpression.Identifier)) {
//TODO: We can make this check better (see VisitIndexerExpression for more details)
data = data.Clone();
analysis.SetLocalVariableValue(data, identifierExpression, NullValueStatus.DefinitelyNotNull);
}
}
return HandleExpressionResult(invocationExpression, GetMethodVisitorResult(methodResolveResult, data, parameterResults));
}
static VisitorResult GetMethodVisitorResult(CSharpInvocationResolveResult methodResolveResult, VariableStatusInfo data, List<VisitorResult> parameterResults)
{
if (methodResolveResult == null)
return VisitorResult.ForValue(data, NullValueStatus.Unknown);
var method = methodResolveResult.Member as IMethod;
if (method != null) {
if (method.GetAttribute(new FullTypeName(AnnotationNames.AssertionMethodAttribute)) != null) {
var assertionParameters = method.Parameters.Select((parameter, index) => new { index, parameter })
.Select(parameter => new { parameter.index, parameter.parameter, attributes = parameter.parameter.Attributes.Where(attribute => attribute.AttributeType.FullName == AnnotationNames.AssertionConditionAttribute).ToList() })
.Where(parameter => parameter.attributes.Count() == 1)
.Select(parameter => new { parameter.index, parameter.parameter, attribute = parameter.attributes[0] })
.ToList();
//Unclear what should be done if there are multiple assertion conditions
if (assertionParameters.Count() == 1) {
Debug.Assert(methodResolveResult.Arguments.Count == parameterResults.Count);
var assertionParameter = assertionParameters [0];
VisitorResult assertionParameterResult = null;
object intendedResult = true;
var positionalArgument = assertionParameter.attribute.PositionalArguments.FirstOrDefault() as MemberResolveResult;
if (positionalArgument != null && positionalArgument.Type.FullName == AnnotationNames.AssertionConditionTypeAttribute) {
switch (positionalArgument.Member.FullName) {
case AnnotationNames.AssertionConditionTypeIsTrue:
intendedResult = true;
break;
case AnnotationNames.AssertionConditionTypeIsFalse:
intendedResult = false;
break;
case AnnotationNames.AssertionConditionTypeIsNull:
intendedResult = null;
break;
case AnnotationNames.AssertionConditionTypeIsNotNull:
intendedResult = "<not-null>";
break;
}
}
int parameterIndex = assertionParameter.index;
if (assertionParameter.index < methodResolveResult.Arguments.Count && !(methodResolveResult.Arguments [assertionParameter.index] is NamedArgumentResolveResult)) {
//Use index
assertionParameterResult = parameterResults [assertionParameter.index];
} else {
//Use named argument
int? nameIndex = methodResolveResult.Arguments.Select((argument, index) => new { argument, index})
.Where(argument => {
var namedArgument = argument.argument as NamedArgumentResolveResult;
return namedArgument != null && namedArgument.ParameterName == assertionParameter.parameter.Name;
}).Select(argument => (int?)argument.index).FirstOrDefault();
if (nameIndex != null) {
parameterIndex = nameIndex.Value;
assertionParameterResult = parameterResults [nameIndex.Value];
} else if (assertionParameter.parameter.IsOptional) {
//Try to use default value
if (intendedResult is string) {
if (assertionParameter.parameter.ConstantValue == null) {
return VisitorResult.ForException(data);
}
} else {
if (!object.Equals(assertionParameter.parameter.ConstantValue, intendedResult)) {
return VisitorResult.ForException(data);
}
}
} else {
//The parameter was not specified, yet it is not optional?
return VisitorResult.ForException(data);
}
}
//Now check assertion
if (assertionParameterResult != null) {
if (intendedResult is bool) {
if (assertionParameterResult.KnownBoolResult == !(bool)intendedResult) {
return VisitorResult.ForException(data);
}
data = (bool)intendedResult ? assertionParameterResult.TruePathVariables : assertionParameterResult.FalsePathVariables;
} else {
bool shouldBeNull = intendedResult == null;
if (assertionParameterResult.NullableReturnResult == (shouldBeNull ? NullValueStatus.DefinitelyNotNull : NullValueStatus.DefinitelyNull)) {
return VisitorResult.ForException(data);
}
var parameterResolveResult = methodResolveResult.Arguments [parameterIndex];
LocalResolveResult localVariableResult = null;
var conversionResolveResult = parameterResolveResult as ConversionResolveResult;
if (conversionResolveResult != null) {
if (!IsTypeNullable(conversionResolveResult.Type)) {
if (intendedResult == null) {
return VisitorResult.ForException(data);
}
} else {
localVariableResult = conversionResolveResult.Input as LocalResolveResult;
}
} else {
localVariableResult = parameterResolveResult as LocalResolveResult;
}
if (localVariableResult != null && data[localVariableResult.Variable.Name] != NullValueStatus.CapturedUnknown) {
data = data.Clone();
data [localVariableResult.Variable.Name] = shouldBeNull ? NullValueStatus.DefinitelyNull : NullValueStatus.DefinitelyNotNull;
}
}
}
}
}
}
bool isNullable = IsTypeNullable(methodResolveResult.Type);
if (!isNullable) {
return VisitorResult.ForValue(data, NullValueStatus.DefinitelyNotNull);
}
if (method != null)
return VisitorResult.ForValue(data, GetNullableStatus(method));
return VisitorResult.ForValue(data, GetNullableStatus(methodResolveResult.TargetResult.Type.GetDefinition()));
}
static NullValueStatus GetNullableStatus(IEntity entity)
{
if (entity.DeclaringType != null && entity.DeclaringType.Kind == TypeKind.Delegate) {
//Handle Delegate.Invoke method
return GetNullableStatus(entity.DeclaringTypeDefinition);
}
return GetNullableStatus(fullTypeName => entity.GetAttribute(new FullTypeName(fullTypeName)));
}
static NullValueStatus GetNullableStatus(IParameter parameter)
{
return GetNullableStatus(fullTypeName => parameter.Attributes.FirstOrDefault(attribute => attribute.AttributeType.FullName == fullTypeName));
}
static NullValueStatus GetNullableStatus(Func<string, IAttribute> attributeGetter)
{
if (attributeGetter(AnnotationNames.NotNullAttribute) != null) {
return NullValueStatus.DefinitelyNotNull;
}
if (attributeGetter(AnnotationNames.CanBeNullAttribute) != null) {
return NullValueStatus.PotentiallyNull;
}
return NullValueStatus.Unknown;
}
public override VisitorResult VisitMemberReferenceExpression(MemberReferenceExpression memberReferenceExpression, VariableStatusInfo data)
{
var targetResult = memberReferenceExpression.Target.AcceptVisitor(this, data);
if (targetResult.ThrowsException)
return HandleExpressionResult(memberReferenceExpression, targetResult);
var variables = targetResult.Variables;
var memberResolveResult = analysis.context.Resolve(memberReferenceExpression) as MemberResolveResult;
var targetIdentifier = CSharpUtil.GetInnerMostExpression(memberReferenceExpression.Target) as IdentifierExpression;
if (targetIdentifier != null) {
if (memberResolveResult == null) {
var invocation = memberReferenceExpression.Parent as InvocationExpression;
if (invocation != null) {
memberResolveResult = analysis.context.Resolve(invocation) as MemberResolveResult;
}
}
if (memberResolveResult != null && memberResolveResult.Member.FullName != "System.Nullable.HasValue") {
var method = memberResolveResult.Member as IMethod;
if (method == null || !method.IsExtensionMethod) {
if (targetResult.NullableReturnResult == NullValueStatus.DefinitelyNull) {
return HandleExpressionResult(memberReferenceExpression, VisitorResult.ForException(variables));
}
if (variables [targetIdentifier.Identifier] != NullValueStatus.CapturedUnknown) {
variables = variables.Clone();
analysis.SetLocalVariableValue(variables, targetIdentifier, NullValueStatus.DefinitelyNotNull);
}
}
}
}
var returnValue = GetFieldReturnValue(memberResolveResult, data);
return HandleExpressionResult(memberReferenceExpression, variables, returnValue);
}
static NullValueStatus GetFieldReturnValue(MemberResolveResult memberResolveResult, VariableStatusInfo data)
{
bool isNullable = memberResolveResult == null || IsTypeNullable(memberResolveResult.Type);
if (!isNullable) {
return NullValueStatus.DefinitelyNotNull;
}
if (memberResolveResult != null) {
return GetNullableStatus(memberResolveResult.Member);
}
return NullValueStatus.Unknown;
}
public override VisitorResult VisitTypeReferenceExpression(TypeReferenceExpression typeReferenceExpression, VariableStatusInfo data)
{
return HandleExpressionResult(typeReferenceExpression, data, NullValueStatus.Unknown);
}
void FixParameter(Expression argument, IList<IParameter> parameters, int parameterIndex, IdentifierExpression identifier, VariableStatusInfo data)
{
NullValueStatus newValue = NullValueStatus.Unknown;
if (argument is NamedArgumentExpression) {
var namedResolveResult = analysis.context.Resolve(argument) as NamedArgumentResolveResult;
if (namedResolveResult != null) {
newValue = GetNullableStatus(namedResolveResult.Parameter);
}
}
else {
var parameter = parameters[parameterIndex];
newValue = GetNullableStatus(parameter);
}
analysis.SetLocalVariableValue(data, identifier, newValue);
}
public override VisitorResult VisitObjectCreateExpression(ObjectCreateExpression objectCreateExpression, VariableStatusInfo data)
{
foreach (var argumentToHandle in objectCreateExpression.Arguments.Select((argument, parameterIndex) => new { argument, parameterIndex })) {
var argument = argumentToHandle.argument;
var parameterIndex = argumentToHandle.parameterIndex;
var namedArgument = argument as NamedArgumentExpression;
var directionExpression = (namedArgument == null ? argument : namedArgument.Expression) as DirectionExpression;
if (directionExpression != null) {
var identifier = directionExpression.Expression as IdentifierExpression;
if (identifier != null && data [identifier.Identifier] != NullValueStatus.CapturedUnknown) {
//out and ref parameters do *NOT* capture the variable (since they must stop changing it by the time they return)
data = data.Clone();
var constructorResolveResult = analysis.context.Resolve(objectCreateExpression) as CSharpInvocationResolveResult;
if (constructorResolveResult != null)
FixParameter(argument, constructorResolveResult.Member.Parameters, parameterIndex, identifier, data);
}
continue;
}
var argumentResult = argument.AcceptVisitor(this, data);
if (argumentResult.ThrowsException)
return argumentResult;
data = argumentResult.Variables;
}
//Constructors never return null
return HandleExpressionResult(objectCreateExpression, data, NullValueStatus.DefinitelyNotNull);
}
public override VisitorResult VisitArrayCreateExpression(ArrayCreateExpression arrayCreateExpression, VariableStatusInfo data)
{
foreach (var argument in arrayCreateExpression.Arguments) {
var result = argument.AcceptVisitor(this, data);
if (result.ThrowsException)
return result;
data = result.Variables.Clone();
}
if (arrayCreateExpression.Initializer.IsNull) {
return HandleExpressionResult(arrayCreateExpression, data, NullValueStatus.DefinitelyNotNull);
}
return HandleExpressionResult(arrayCreateExpression, arrayCreateExpression.Initializer.AcceptVisitor(this, data));
}
public override VisitorResult VisitArrayInitializerExpression(ArrayInitializerExpression arrayInitializerExpression, VariableStatusInfo data)
{
if (arrayInitializerExpression.IsSingleElement) {
return HandleExpressionResult(arrayInitializerExpression, arrayInitializerExpression.Elements.Single().AcceptVisitor(this, data));
}
if (!arrayInitializerExpression.Elements.Any()) {
//Empty array
return HandleExpressionResult(arrayInitializerExpression, VisitorResult.ForValue(data, NullValueStatus.Unknown));
}
NullValueStatus enumeratedValue = NullValueStatus.UnreachableOrInexistent;
foreach (var element in arrayInitializerExpression.Elements) {
var result = element.AcceptVisitor(this, data);
if (result.ThrowsException)
return result;
data = result.Variables.Clone();
enumeratedValue = VariableStatusInfo.CombineStatus(enumeratedValue, result.NullableReturnResult);
}
return HandleExpressionResult(arrayInitializerExpression, VisitorResult.ForEnumeratedValue(data, enumeratedValue));
}
public override VisitorResult VisitAnonymousTypeCreateExpression(AnonymousTypeCreateExpression anonymousTypeCreateExpression, VariableStatusInfo data)
{
foreach (var initializer in anonymousTypeCreateExpression.Initializers) {
var result = initializer.AcceptVisitor(this, data);
if (result.ThrowsException)
return result;
data = result.Variables;
}
return HandleExpressionResult(anonymousTypeCreateExpression, data, NullValueStatus.DefinitelyNotNull);
}
public override VisitorResult VisitLambdaExpression(LambdaExpression lambdaExpression, VariableStatusInfo data)
{
var newData = data.Clone();
var identifiers = lambdaExpression.Descendants.OfType<IdentifierExpression>();
foreach (var identifier in identifiers) {
//Check if it is in a "change-null-state" context
//For instance, x++ does not change the null state
//but `x = y` does.
if (identifier.Parent is AssignmentExpression && identifier.Role == AssignmentExpression.LeftRole) {
var parent = (AssignmentExpression)identifier.Parent;
if (parent.Operator != AssignmentOperatorType.Assign) {
continue;
}
} else {
//No other context matters
//Captured variables are never passed by reference (out/ref)
continue;
}
//At this point, we know there's a good chance the variable has been changed
var identifierResolveResult = analysis.context.Resolve(identifier) as LocalResolveResult;
if (identifierResolveResult != null && IsTypeNullable(identifierResolveResult.Type)) {
analysis.SetLocalVariableValue(newData, identifier, NullValueStatus.CapturedUnknown);
}
}
//The lambda itself is known not to be null
return HandleExpressionResult(lambdaExpression, newData, NullValueStatus.DefinitelyNotNull);
}
public override VisitorResult VisitAnonymousMethodExpression(AnonymousMethodExpression anonymousMethodExpression, VariableStatusInfo data)
{
var newData = data.Clone();
var identifiers = anonymousMethodExpression.Descendants.OfType<IdentifierExpression>();
foreach (var identifier in identifiers) {
//Check if it is in a "change-null-state" context
//For instance, x++ does not change the null state
//but `x = y` does.
if (identifier.Parent is AssignmentExpression && identifier.Role == AssignmentExpression.LeftRole) {
var parent = (AssignmentExpression)identifier.Parent;
if (parent.Operator != AssignmentOperatorType.Assign) {
continue;
}
} else {
//No other context matters
//Captured variables are never passed by reference (out/ref)
continue;
}
//At this point, we know there's a good chance the variable has been changed
var identifierResolveResult = analysis.context.Resolve(identifier) as LocalResolveResult;
if (identifierResolveResult != null && IsTypeNullable(identifierResolveResult.Type)) {
analysis.SetLocalVariableValue(newData, identifier, NullValueStatus.CapturedUnknown);
}
}
//The anonymous method itself is known not to be null
return HandleExpressionResult(anonymousMethodExpression, newData, NullValueStatus.DefinitelyNotNull);
}
public override VisitorResult VisitNamedExpression(NamedExpression namedExpression, VariableStatusInfo data)
{
return HandleExpressionResult(namedExpression, namedExpression.Expression.AcceptVisitor(this, data));
}
public override VisitorResult VisitAsExpression(AsExpression asExpression, VariableStatusInfo data)
{
var tentativeResult = asExpression.Expression.AcceptVisitor(this, data);
if (tentativeResult.ThrowsException)
return tentativeResult;
NullValueStatus result;
if (tentativeResult.NullableReturnResult == NullValueStatus.DefinitelyNull) {
result = NullValueStatus.DefinitelyNull;
} else {
var asResolveResult = analysis.context.Resolve(asExpression) as CastResolveResult;
if (asResolveResult == null ||
asResolveResult.IsError ||
asResolveResult.Input.Type.Kind == TypeKind.Unknown ||
asResolveResult.Type.Kind == TypeKind.Unknown) {
result = NullValueStatus.Unknown;
} else {
var conversion = new CSharpConversions(analysis.context.Compilation);
var foundConversion = conversion.ExplicitConversion(asResolveResult.Input.Type, asResolveResult.Type);
if (foundConversion == Conversion.None) {
result = NullValueStatus.DefinitelyNull;
} else if (foundConversion == Conversion.IdentityConversion) {
result = tentativeResult.NullableReturnResult;
} else {
result = NullValueStatus.PotentiallyNull;
}
}
}
return HandleExpressionResult(asExpression, tentativeResult.Variables, result);
}
public override VisitorResult VisitCastExpression(CastExpression castExpression, VariableStatusInfo data)
{
var tentativeResult = castExpression.Expression.AcceptVisitor(this, data);
if (tentativeResult.ThrowsException)
return tentativeResult;
NullValueStatus result;
if (tentativeResult.NullableReturnResult == NullValueStatus.DefinitelyNull) {
result = NullValueStatus.DefinitelyNull;
} else {
result = NullValueStatus.Unknown;
}
VariableStatusInfo variables = tentativeResult.Variables;
var resolveResult = analysis.context.Resolve(castExpression) as CastResolveResult;
if (resolveResult != null && !IsTypeNullable(resolveResult.Type)) {
if (result == NullValueStatus.DefinitelyNull) {
return HandleExpressionResult(castExpression, VisitorResult.ForException(tentativeResult.Variables));
}
var identifierExpression = CSharpUtil.GetInnerMostExpression(castExpression.Expression) as IdentifierExpression;
if (identifierExpression != null) {
var currentValue = variables [identifierExpression.Identifier];
if (currentValue != NullValueStatus.CapturedUnknown &&
currentValue != NullValueStatus.UnreachableOrInexistent &&
currentValue != NullValueStatus.DefinitelyNotNull) {
//DefinitelyNotNull is included in this list because if that's the status
// then we don't need to change anything
variables = variables.Clone();
variables [identifierExpression.Identifier] = NullValueStatus.DefinitelyNotNull;
}
}
result = NullValueStatus.DefinitelyNotNull;
}
return HandleExpressionResult(castExpression, variables, result);
}
public override VisitorResult VisitIsExpression(IsExpression isExpression, VariableStatusInfo data)
{
var tentativeResult = isExpression.Expression.AcceptVisitor(this, data);
if (tentativeResult.ThrowsException)
return tentativeResult;
//TODO: Consider, for instance: new X() is X. The result is known to be true, so we can use KnownBoolValue
return HandleExpressionResult(isExpression, tentativeResult.Variables, NullValueStatus.DefinitelyNotNull);
}
public override VisitorResult VisitDirectionExpression(DirectionExpression directionExpression, VariableStatusInfo data)
{
return HandleExpressionResult(directionExpression, directionExpression.Expression.AcceptVisitor(this, data));
}
public override VisitorResult VisitCheckedExpression(CheckedExpression checkedExpression, VariableStatusInfo data)
{
return HandleExpressionResult(checkedExpression, checkedExpression.Expression.AcceptVisitor(this, data));
}
public override VisitorResult VisitUncheckedExpression(UncheckedExpression uncheckedExpression, VariableStatusInfo data)
{
return HandleExpressionResult(uncheckedExpression, uncheckedExpression.Expression.AcceptVisitor(this, data));
}
public override VisitorResult VisitThisReferenceExpression(ThisReferenceExpression thisReferenceExpression, VariableStatusInfo data)
{
return HandleExpressionResult(thisReferenceExpression, data, NullValueStatus.DefinitelyNotNull);
}
public override VisitorResult VisitIndexerExpression(IndexerExpression indexerExpression, VariableStatusInfo data)
{
var tentativeResult = indexerExpression.Target.AcceptVisitor(this, data);
if (tentativeResult.ThrowsException)
return tentativeResult;
data = tentativeResult.Variables;
foreach (var argument in indexerExpression.Arguments) {
var result = argument.AcceptVisitor(this, data);
if (result.ThrowsException)
return result;
data = result.Variables.Clone();
}
IdentifierExpression targetAsIdentifier = CSharpUtil.GetInnerMostExpression(indexerExpression.Target) as IdentifierExpression;
if (targetAsIdentifier != null) {
if (tentativeResult.NullableReturnResult == NullValueStatus.DefinitelyNull)
return HandleExpressionResult(indexerExpression, VisitorResult.ForException(data));
//If this doesn't cause an exception, then the target is not null
//But we won't set it if it has been changed
var descendentIdentifiers = indexerExpression.Arguments
.SelectMany(argument => argument.DescendantsAndSelf).OfType<IdentifierExpression>();
if (!descendentIdentifiers.Any(identifier => identifier.Identifier == targetAsIdentifier.Identifier)) {
//TODO: this check might be improved to include more legitimate cases
//A good check will necessarily have to consider captured variables
data = data.Clone();
analysis.SetLocalVariableValue(data, targetAsIdentifier, NullValueStatus.DefinitelyNotNull);
}
}
var indexerResolveResult = analysis.context.Resolve(indexerExpression) as CSharpInvocationResolveResult;
bool isNullable = indexerResolveResult == null || IsTypeNullable(indexerResolveResult.Type);
var returnValue = isNullable ? NullValueStatus.Unknown : NullValueStatus.DefinitelyNotNull;
return HandleExpressionResult(indexerExpression, data, returnValue);
}
public override VisitorResult VisitBaseReferenceExpression(BaseReferenceExpression baseReferenceExpression, VariableStatusInfo data)
{
return HandleExpressionResult(baseReferenceExpression, data, NullValueStatus.DefinitelyNotNull);
}
public override VisitorResult VisitTypeOfExpression(TypeOfExpression typeOfExpression, VariableStatusInfo data)
{
return HandleExpressionResult(typeOfExpression, data, NullValueStatus.DefinitelyNotNull);
}
public override VisitorResult VisitSizeOfExpression(SizeOfExpression sizeOfExpression, VariableStatusInfo data)
{
return HandleExpressionResult(sizeOfExpression, data, NullValueStatus.DefinitelyNotNull);
}
public override VisitorResult VisitPointerReferenceExpression(PointerReferenceExpression pointerReferenceExpression, VariableStatusInfo data)
{
var targetResult = pointerReferenceExpression.Target.AcceptVisitor(this, data);
if (targetResult.ThrowsException)
return targetResult;
return HandleExpressionResult(pointerReferenceExpression, targetResult.Variables, NullValueStatus.DefinitelyNotNull);
}
public override VisitorResult VisitStackAllocExpression(StackAllocExpression stackAllocExpression, VariableStatusInfo data)
{
var countResult = stackAllocExpression.CountExpression.AcceptVisitor(this, data);
if (countResult.ThrowsException)
return countResult;
return HandleExpressionResult(stackAllocExpression, countResult.Variables, NullValueStatus.DefinitelyNotNull);
}
public override VisitorResult VisitNamedArgumentExpression(NamedArgumentExpression namedArgumentExpression, VariableStatusInfo data)
{
return HandleExpressionResult(namedArgumentExpression, namedArgumentExpression.Expression.AcceptVisitor(this, data));
}
public override VisitorResult VisitUndocumentedExpression(UndocumentedExpression undocumentedExpression, VariableStatusInfo data)
{
throw new NotImplementedException();
}
public override VisitorResult VisitQueryExpression(QueryExpression queryExpression, VariableStatusInfo data)
{
VariableStatusInfo outgoingData = data.Clone();
NullValueStatus? outgoingEnumeratedValue = null;
var clauses = queryExpression.Clauses.ToList();
var backtracingClauses = (from item in clauses.Select((clause, i) => new { clause, i })
where item.clause is QueryFromClause || item.clause is QueryJoinClause || item.clause is QueryContinuationClause
select item.i).ToList();
var beforeClauseVariableStates = Enumerable.Range(0, clauses.Count).ToDictionary(clauseIndex => clauseIndex,
clauseIndex => new VariableStatusInfo());
var afterClauseVariableStates = Enumerable.Range(0, clauses.Count).ToDictionary(clauseIndex => clauseIndex,
clauseIndex => new VariableStatusInfo());
VisitorResult lastValidResult = null;
int currentClauseIndex = 0;
for (;;) {
VisitorResult result = null;
QueryClause clause = null;
bool backtrack = false;
if (currentClauseIndex >= clauses.Count) {
backtrack = true;
} else {
clause = clauses [currentClauseIndex];
beforeClauseVariableStates [currentClauseIndex].ReceiveIncoming(data);
result = clause.AcceptVisitor(this, data);
data = result.Variables;
lastValidResult = result;
if (result.KnownBoolResult == false) {
backtrack = true;
}
if (result.ThrowsException) {
//Don't backtrack. Exceptions completely stop the query.
break;
}
else {
afterClauseVariableStates [currentClauseIndex].ReceiveIncoming(data);
}
}
if (backtrack) {
int? newIndex;
for (;;) {
newIndex = backtracingClauses.LastOrDefault(index => index < currentClauseIndex);
if (newIndex == null) {
//We've reached the end
break;
}
currentClauseIndex = (int)newIndex + 1;
if (!beforeClauseVariableStates[currentClauseIndex].ReceiveIncoming(lastValidResult.Variables)) {
newIndex = null;
break;
}
}
if (newIndex == null) {
break;
}
} else {
if (clause is QuerySelectClause) {
outgoingData.ReceiveIncoming(data);
if (outgoingEnumeratedValue == null)
outgoingEnumeratedValue = result.EnumeratedValueResult;
else
outgoingEnumeratedValue = VariableStatusInfo.CombineStatus(outgoingEnumeratedValue.Value, result.EnumeratedValueResult);
}
++currentClauseIndex;
}
}
var finalData = new VariableStatusInfo();
var endingClauseIndices = from item in clauses.Select((clause, i) => new { clause, i })
let clause = item.clause
where clause is QueryFromClause ||
clause is QueryContinuationClause ||
clause is QueryJoinClause ||
clause is QuerySelectClause ||
clause is QueryWhereClause
select item.i;
foreach (var clauseIndex in endingClauseIndices) {
finalData.ReceiveIncoming(afterClauseVariableStates [clauseIndex]);
}
return VisitorResult.ForEnumeratedValue(finalData, outgoingEnumeratedValue ?? NullValueStatus.Unknown);
}
public override VisitorResult VisitQueryContinuationClause(QueryContinuationClause queryContinuationClause, VariableStatusInfo data)
{
return IntroduceVariableFromEnumeratedValue(queryContinuationClause.Identifier, queryContinuationClause.PrecedingQuery, data);
}
VisitorResult IntroduceVariableFromEnumeratedValue(string newVariable, Expression expression, VariableStatusInfo data)
{
var result = expression.AcceptVisitor(this, data);
var newVariables = result.Variables.Clone();
newVariables[newVariable] = result.EnumeratedValueResult;
return VisitorResult.ForValue(newVariables, NullValueStatus.Unknown);
}
public override VisitorResult VisitQueryFromClause(QueryFromClause queryFromClause, VariableStatusInfo data)
{
return IntroduceVariableFromEnumeratedValue(queryFromClause.Identifier, queryFromClause.Expression, data);
}
public override VisitorResult VisitQueryJoinClause(QueryJoinClause queryJoinClause, VariableStatusInfo data)
{
//TODO: Check if this really works in weird edge-cases.
var tentativeResult = IntroduceVariableFromEnumeratedValue(queryJoinClause.JoinIdentifier, queryJoinClause.InExpression, data);
tentativeResult = queryJoinClause.OnExpression.AcceptVisitor(this, tentativeResult.Variables);
tentativeResult = queryJoinClause.EqualsExpression.AcceptVisitor(this, tentativeResult.Variables);
if (queryJoinClause.IsGroupJoin) {
var newVariables = tentativeResult.Variables.Clone();
analysis.SetLocalVariableValue(newVariables, queryJoinClause.IntoIdentifierToken, NullValueStatus.DefinitelyNotNull);
return VisitorResult.ForValue(newVariables, NullValueStatus.Unknown);
}
return tentativeResult;
}
public override VisitorResult VisitQueryLetClause(QueryLetClause queryLetClause, VariableStatusInfo data)
{
var result = queryLetClause.Expression.AcceptVisitor(this, data);
string newVariable = queryLetClause.Identifier;
var newVariables = result.Variables.Clone();
newVariables [newVariable] = result.NullableReturnResult;
return VisitorResult.ForValue(newVariables, NullValueStatus.Unknown);
}
public override VisitorResult VisitQuerySelectClause(QuerySelectClause querySelectClause, VariableStatusInfo data)
{
var result = querySelectClause.Expression.AcceptVisitor(this, data);
//The value of the expression in select becomes the "enumerated" value
return VisitorResult.ForEnumeratedValue(result.Variables, result.NullableReturnResult);
}
public override VisitorResult VisitQueryWhereClause(QueryWhereClause queryWhereClause, VariableStatusInfo data)
{
var result = queryWhereClause.Condition.AcceptVisitor(this, data);
return VisitorResult.ForEnumeratedValue(result.TruePathVariables, NullValueStatus.Unknown);
}
public override VisitorResult VisitQueryOrderClause(QueryOrderClause queryOrderClause, VariableStatusInfo data)
{
foreach (var ordering in queryOrderClause.Orderings) {
data = ordering.AcceptVisitor(this, data).Variables;
}
return VisitorResult.ForValue(data, NullValueStatus.Unknown);
}
public override VisitorResult VisitQueryOrdering(QueryOrdering queryOrdering, VariableStatusInfo data)
{
return VisitorResult.ForValue(queryOrdering.Expression.AcceptVisitor(this, data).Variables, NullValueStatus.Unknown);
}
public override VisitorResult VisitQueryGroupClause(QueryGroupClause queryGroupClause, VariableStatusInfo data)
{
var projectionResult = queryGroupClause.Projection.AcceptVisitor(this, data);
data = projectionResult.Variables;
data = queryGroupClause.Key.AcceptVisitor(this, data).Variables;
return VisitorResult.ForEnumeratedValue(data, projectionResult.NullableReturnResult);
}
}
}
}