未验证 提交 a82ed0ed 编写于 作者: C Charles Stoner 提交者: GitHub

Separate nullability flow analysis from DataFlowPass (#22932)

Separate nullability flow analysis from DataFlowPass
上级 e25fa14b
......@@ -70,7 +70,7 @@ protected override void WriteArgument(BoundExpression arg, RefKind refKind, Meth
// ref parameter does not "always" assign.
if (refKind == RefKind.Out)
{
Assign(arg, value: null, valueIsNotNull: null);
Assign(arg, value: null);
}
}
......
......@@ -72,8 +72,8 @@ private void ReplayReads(ref BitVector reads, SyntaxNode syntax)
/// </summary>
/// <remarks>
/// Specifying the slot manually may be necessary if the symbol is a field,
/// in which case <see cref="VariableSlot(Symbol, int)"/> will not know
/// which containing slot to look for.
/// in which case <see cref="DataFlowPassBase{TLocalState}.VariableSlot(Symbol, int)"/>
/// will not know which containing slot to look for.
/// </remarks>
private void CheckIfAssignedDuringLocalFunctionReplay(Symbol symbol, SyntaxNode node, int slot)
{
......@@ -86,7 +86,7 @@ private void CheckIfAssignedDuringLocalFunctionReplay(Symbol symbol, SyntaxNode
{
if (slot >= this.State.Assigned.Capacity)
{
NormalizeAssigned(ref this.State);
Normalize(ref this.State);
}
if (slot > 0 && !this.State.IsAssigned(slot))
......
......@@ -6,7 +6,7 @@
namespace Microsoft.CodeAnalysis.CSharp
{
internal partial class DataFlowPass
internal partial class DataFlowPassBase<TLocalState>
{
protected struct VariableIdentifier : IEquatable<VariableIdentifier>
{
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics;
using System.Linq;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp
{
internal abstract partial class DataFlowPassBase<TLocalState> : AbstractFlowPass<TLocalState>
where TLocalState : PreciseAbstractFlowPass<TLocalState>.AbstractLocalState
{
/// <summary>
/// A mapping from local variables to the index of their slot in a flow analysis local state.
/// </summary>
private readonly PooledDictionary<VariableIdentifier, int> _variableSlot = PooledDictionary<VariableIdentifier, int>.GetInstance();
/// <summary>
/// A mapping from the local variable slot to the symbol for the local variable itself. This
/// is used in the implementation of region analysis (support for extract method) to compute
/// the set of variables "always assigned" in a region of code.
/// </summary>
protected VariableIdentifier[] variableBySlot = new VariableIdentifier[1];
/// <summary>
/// Variable slots are allocated to local variables sequentially and never reused. This is
/// the index of the next slot number to use.
/// </summary>
protected int nextVariableSlot = 1;
/// <summary>
/// A cache for remember which structs are empty.
/// </summary>
protected readonly EmptyStructTypeCache _emptyStructTypeCache;
protected DataFlowPassBase(
CSharpCompilation compilation,
Symbol member,
BoundNode node,
EmptyStructTypeCache emptyStructs,
bool trackUnassignments,
bool trackClassFields)
: base(compilation, member, node, trackUnassignments: trackUnassignments, trackClassFields: trackClassFields)
{
_emptyStructTypeCache = emptyStructs;
}
protected DataFlowPassBase(
CSharpCompilation compilation,
Symbol member,
BoundNode node,
EmptyStructTypeCache emptyStructs,
BoundNode firstInRegion,
BoundNode lastInRegion,
bool trackRegions,
bool trackUnassignments)
: base(compilation, member, node, firstInRegion, lastInRegion, trackRegions: trackRegions, trackUnassignments: trackUnassignments)
{
_emptyStructTypeCache = emptyStructs;
}
protected override void Free()
{
_variableSlot.Free();
base.Free();
}
/// <summary>
/// Locals are given slots when their declarations are encountered. We only need give slots
/// to local variables, out parameters, and the "this" variable of a struct constructs.
/// Other variables are not given slots, and are therefore not tracked by the analysis. This
/// returns -1 for a variable that is not tracked, for fields of structs that have the same
/// assigned status as the container, and for structs that (recursively) contain no data members.
/// We do not need to track references to
/// variables that occur before the variable is declared, as those are reported in an
/// earlier phase as "use before declaration". That allows us to avoid giving slots to local
/// variables before processing their declarations.
/// </summary>
protected int VariableSlot(Symbol symbol, int containingSlot = 0)
{
containingSlot = DescendThroughTupleRestFields(ref symbol, containingSlot, forceContainingSlotsToExist: false);
int slot;
return (_variableSlot.TryGetValue(new VariableIdentifier(symbol, containingSlot), out slot)) ? slot : -1;
}
/// <summary>
/// Force a variable to have a slot. Returns -1 if the variable has an empty struct type.
/// </summary>
protected int GetOrCreateSlot(Symbol symbol, int containingSlot = 0)
{
if (symbol is RangeVariableSymbol) return -1;
containingSlot = DescendThroughTupleRestFields(ref symbol, containingSlot, forceContainingSlotsToExist: true);
VariableIdentifier identifier = new VariableIdentifier(symbol, containingSlot);
int slot;
// Since analysis may proceed in multiple passes, it is possible the slot is already assigned.
if (!_variableSlot.TryGetValue(identifier, out slot))
{
var variableType = VariableType(symbol)?.TypeSymbol;
if (_emptyStructTypeCache.IsEmptyStructType(variableType))
{
return -1;
}
slot = nextVariableSlot++;
_variableSlot.Add(identifier, slot);
if (slot >= variableBySlot.Length)
{
Array.Resize(ref this.variableBySlot, slot * 2);
}
variableBySlot[slot] = identifier;
}
Normalize(ref this.State);
return slot;
}
protected abstract void Normalize(ref TLocalState state);
/// <summary>
/// Descends through Rest fields of a tuple if "symbol" is an extended field
/// As a result the "symbol" will be adjusted to be the field of the innermost tuple
/// and a corresponding containingSlot is returned.
/// Return value -1 indicates a failure which could happen for the following reasons
/// a) Rest field does not exist, which could happen in rare error scenarios involving broken ValueTuple types
/// b) Rest is not tracked already and forceSlotsToExist is false (otherwise we create slots on demand)
/// </summary>
private int DescendThroughTupleRestFields(ref Symbol symbol, int containingSlot, bool forceContainingSlotsToExist)
{
var fieldSymbol = symbol as TupleFieldSymbol;
if ((object)fieldSymbol != null)
{
TypeSymbol containingType = ((TupleTypeSymbol)symbol.ContainingType).UnderlyingNamedType;
// for tuple fields the variable identifier represents the underlying field
symbol = fieldSymbol.TupleUnderlyingField;
// descend through Rest fields
// force corresponding slots if do not exist
while (containingType != symbol.ContainingType)
{
var restField = containingType.GetMembers(TupleTypeSymbol.RestFieldName).FirstOrDefault() as FieldSymbol;
if ((object)restField == null)
{
return -1;
}
if (forceContainingSlotsToExist)
{
containingSlot = GetOrCreateSlot(restField, containingSlot);
}
else
{
if (!_variableSlot.TryGetValue(new VariableIdentifier(restField, containingSlot), out containingSlot))
{
return -1;
}
}
containingType = restField.Type.TypeSymbol.TupleUnderlyingTypeOrSelf();
}
}
return containingSlot;
}
protected virtual bool TryGetReceiverAndMember(BoundExpression expr, out BoundExpression receiver, out Symbol member)
{
receiver = null;
member = null;
switch (expr.Kind)
{
case BoundKind.FieldAccess:
{
var fieldAccess = (BoundFieldAccess)expr;
var fieldSymbol = fieldAccess.FieldSymbol;
if (fieldSymbol.IsStatic || fieldSymbol.IsFixed)
{
return false;
}
member = fieldSymbol;
receiver = fieldAccess.ReceiverOpt;
break;
}
case BoundKind.EventAccess:
{
var eventAccess = (BoundEventAccess)expr;
var eventSymbol = eventAccess.EventSymbol;
if (eventSymbol.IsStatic || !eventSymbol.HasAssociatedField)
{
return false;
}
member = eventSymbol.AssociatedField;
receiver = eventAccess.ReceiverOpt;
break;
}
case BoundKind.PropertyAccess:
{
var propAccess = (BoundPropertyAccess)expr;
var propSymbol = propAccess.PropertySymbol;
if (propSymbol.IsStatic)
{
return false;
}
member = (propSymbol as SourcePropertySymbol)?.BackingField;
receiver = propAccess.ReceiverOpt;
break;
}
}
return (object)member != null &&
(object)receiver != null &&
receiver.Kind != BoundKind.TypeExpression &&
MayRequireTrackingReceiverType(receiver.Type);
}
/// <summary>
/// Return the slot for a variable, or -1 if it is not tracked (because, for example, it is an empty struct).
/// </summary>
/// <param name="node"></param>
/// <returns></returns>
protected virtual int MakeSlot(BoundExpression node)
{
switch (node.Kind)
{
case BoundKind.ThisReference:
return (object)MethodThisParameter != null ? GetOrCreateSlot(MethodThisParameter) : -1;
case BoundKind.BaseReference:
return GetOrCreateSlot(MethodThisParameter);
case BoundKind.Local:
return GetOrCreateSlot(((BoundLocal)node).LocalSymbol);
case BoundKind.Parameter:
return GetOrCreateSlot(((BoundParameter)node).ParameterSymbol);
case BoundKind.RangeVariable:
return MakeSlot(((BoundRangeVariable)node).Value);
case BoundKind.FieldAccess:
case BoundKind.EventAccess:
case BoundKind.PropertyAccess:
if (TryGetReceiverAndMember(node, out BoundExpression receiver, out Symbol member))
{
int containingSlot = MakeSlot(receiver);
return (containingSlot == -1) ? -1 : GetOrCreateSlot(member, containingSlot);
}
break;
case BoundKind.AssignmentOperator:
return MakeSlot(((BoundAssignmentOperator)node).Left);
}
return -1;
}
protected void VisitStatementsWithLocalFunctions(BoundBlock block)
{
// Visit the statements in two phases:
// 1. Local function declarations
// 2. Everything else
//
// The idea behind visiting local functions first is
// that we may be able to gather the captured variables
// they read and write ahead of time in a single pass, so
// when they are used by other statements in the block we
// won't have to recompute the set by doing multiple passes.
//
// If the local functions contain forward calls to other local
// functions then we may have to do another pass regardless,
// but hopefully that will be an uncommon case in real-world code.
// First phase
if (!block.LocalFunctions.IsDefaultOrEmpty)
{
foreach (var stmt in block.Statements)
{
if (stmt.Kind == BoundKind.LocalFunctionStatement)
{
VisitAlways(stmt);
}
}
}
// Second phase
foreach (var stmt in block.Statements)
{
if (stmt.Kind != BoundKind.LocalFunctionStatement)
{
VisitStatement(stmt);
}
}
}
protected static TypeSymbolWithAnnotations VariableType(Symbol s)
{
switch (s.Kind)
{
case SymbolKind.Local:
return ((LocalSymbol)s).Type;
case SymbolKind.Field:
return ((FieldSymbol)s).Type;
case SymbolKind.Parameter:
return ((ParameterSymbol)s).Type;
case SymbolKind.Method:
Debug.Assert(((MethodSymbol)s).MethodKind == MethodKind.LocalFunction);
return null;
case SymbolKind.Property:
Debug.Assert(s.ContainingType.IsAnonymousType);
return ((PropertySymbol)s).Type;
default:
throw ExceptionUtilities.UnexpectedValue(s.Kind);
}
}
}
}
......@@ -183,7 +183,7 @@ private Symbol GetNodeSymbol(BoundNode node)
}
#endif
protected override void AssignImpl(BoundNode node, BoundExpression value, bool? valueIsNotNull, RefKind refKind, bool written, bool read)
protected override void AssignImpl(BoundNode node, BoundExpression value, RefKind refKind, bool written, bool read)
{
if (IsInside)
{
......@@ -209,7 +209,7 @@ protected override void AssignImpl(BoundNode node, BoundExpression value, bool?
}
}
base.AssignImpl(node, value, valueIsNotNull, refKind, written, read);
base.AssignImpl(node, value, refKind, written, read);
}
private bool FlowsOut(ParameterSymbol param)
......
......@@ -8,7 +8,7 @@
namespace Microsoft.CodeAnalysis.CSharp
{
internal partial class DataFlowPass
internal partial class NullableWalker
{
/// <summary>
/// A symbol to represent a placeholder for an instance being constructed by
......@@ -162,4 +162,4 @@ internal override LocalSymbol WithSynthesizedLocalKindAndSyntax(SynthesizedLocal
}
}
}
}
\ No newline at end of file
}
......@@ -172,7 +172,7 @@ private void NoteReceiverReadOrWritten(BoundFieldAccess expr, HashSet<Symbol> re
}
}
protected override void AssignImpl(BoundNode node, BoundExpression value, bool? valueIsNotNull, RefKind refKind, bool written, bool read)
protected override void AssignImpl(BoundNode node, BoundExpression value, RefKind refKind, bool written, bool read)
{
switch (node.Kind)
{
......@@ -182,7 +182,7 @@ protected override void AssignImpl(BoundNode node, BoundExpression value, bool?
case BoundKind.QueryClause:
{
base.AssignImpl(node, value, valueIsNotNull, refKind, written, read);
base.AssignImpl(node, value, refKind, written, read);
var symbol = ((BoundQueryClause)node).DefinedSymbol;
if ((object)symbol != null)
{
......@@ -193,7 +193,7 @@ protected override void AssignImpl(BoundNode node, BoundExpression value, bool?
case BoundKind.FieldAccess:
{
base.AssignImpl(node, value, valueIsNotNull, refKind, written, read);
base.AssignImpl(node, value, refKind, written, read);
var fieldAccess = node as BoundFieldAccess;
if (!IsInside && node.Syntax != null && node.Syntax.Span.Contains(RegionSpan))
{
......@@ -203,7 +203,7 @@ protected override void AssignImpl(BoundNode node, BoundExpression value, bool?
break;
default:
base.AssignImpl(node, value, valueIsNotNull, refKind, written, read);
base.AssignImpl(node, value, refKind, written, read);
break;
}
}
......@@ -246,7 +246,7 @@ private static ParameterSymbol GetRangeVariableUnderlyingParameter(BoundNode und
public override BoundNode VisitQueryClause(BoundQueryClause node)
{
Assign(node, value: null, valueIsNotNull: null);
Assign(node, value: null);
return base.VisitQueryClause(node);
}
}
......
......@@ -3251,12 +3251,12 @@ struct S2
// (150,15): warning CS8601: Possible null reference assignment.
// u16 = y16.F4;
Diagnostic(ErrorCode.WRN_NullReferenceAssignment, "y16.F4").WithLocation(150, 15),
// (159,15): warning CS8601: Possible null reference assignment.
// u17 = y17.F4;
Diagnostic(ErrorCode.WRN_NullReferenceAssignment, "y17.F4").WithLocation(159, 15),
// (161,15): error CS0165: Use of unassigned local variable 'x17'
// y17 = x17;
Diagnostic(ErrorCode.ERR_UseDefViolation, "x17").WithArguments("x17").WithLocation(161, 15),
// (159,15): warning CS8601: Possible null reference assignment.
// u17 = y17.F4;
Diagnostic(ErrorCode.WRN_NullReferenceAssignment, "y17.F4").WithLocation(159, 15),
// (170,18): error CS0165: Use of unassigned local variable 'x18'
// S1 y18 = x18;
Diagnostic(ErrorCode.ERR_UseDefViolation, "x18").WithArguments("x18").WithLocation(170, 18),
......@@ -6433,30 +6433,30 @@ struct S1
", parseOptions: TestOptions.Regular8);
c.VerifyDiagnostics(
// (11,17): error CS0165: Use of unassigned local variable 'y1'
// S1 z1 = y1;
Diagnostic(ErrorCode.ERR_UseDefViolation, "y1").WithArguments("y1").WithLocation(11, 17),
// (34,19): error CS0170: Use of possibly unassigned field 'F3'
// CL1? z3 = y3.F3;
Diagnostic(ErrorCode.ERR_UseDefViolationField, "y3.F3").WithArguments("F3").WithLocation(34, 19),
// (42,14): error CS0170: Use of possibly unassigned field 'F3'
// z4 = y4.F3;
Diagnostic(ErrorCode.ERR_UseDefViolationField, "y4.F3").WithArguments("F3").WithLocation(42, 14),
// (25,18): error CS0165: Use of unassigned local variable 'y2'
// z2 = y2;
Diagnostic(ErrorCode.ERR_UseDefViolation, "y2").WithArguments("y2").WithLocation(25, 18),
// (50,29): error CS0170: Use of possibly unassigned field 'F3'
// var z5 = new { F3 = y5.F3 };
Diagnostic(ErrorCode.ERR_UseDefViolationField, "y5.F3").WithArguments("F3").WithLocation(50, 29),
// (59,14): error CS0165: Use of unassigned local variable 'y6'
// z6 = y6;
Diagnostic(ErrorCode.ERR_UseDefViolation, "y6").WithArguments("y6").WithLocation(59, 14),
// (68,29): error CS0165: Use of unassigned local variable 'y7'
// var z7 = new { F3 = y7 };
Diagnostic(ErrorCode.ERR_UseDefViolation, "y7").WithArguments("y7").WithLocation(68, 29),
// (81,18): error CS0170: Use of possibly unassigned field 'F3'
// z8 = y8.F3;
Diagnostic(ErrorCode.ERR_UseDefViolationField, "y8.F3").WithArguments("F3").WithLocation(81, 18)
// (11,17): error CS0165: Use of unassigned local variable 'y1'
// S1 z1 = y1;
Diagnostic(ErrorCode.ERR_UseDefViolation, "y1").WithArguments("y1").WithLocation(11, 17),
// (25,18): error CS0165: Use of unassigned local variable 'y2'
// z2 = y2;
Diagnostic(ErrorCode.ERR_UseDefViolation, "y2").WithArguments("y2").WithLocation(25, 18),
// (34,19): error CS0170: Use of possibly unassigned field 'F3'
// CL1? z3 = y3.F3;
Diagnostic(ErrorCode.ERR_UseDefViolationField, "y3.F3").WithArguments("F3").WithLocation(34, 19),
// (42,14): error CS0170: Use of possibly unassigned field 'F3'
// z4 = y4.F3;
Diagnostic(ErrorCode.ERR_UseDefViolationField, "y4.F3").WithArguments("F3").WithLocation(42, 14),
// (50,29): error CS0170: Use of possibly unassigned field 'F3'
// var z5 = new { F3 = y5.F3 };
Diagnostic(ErrorCode.ERR_UseDefViolationField, "y5.F3").WithArguments("F3").WithLocation(50, 29),
// (59,14): error CS0165: Use of unassigned local variable 'y6'
// z6 = y6;
Diagnostic(ErrorCode.ERR_UseDefViolation, "y6").WithArguments("y6").WithLocation(59, 14),
// (68,29): error CS0165: Use of unassigned local variable 'y7'
// var z7 = new { F3 = y7 };
Diagnostic(ErrorCode.ERR_UseDefViolation, "y7").WithArguments("y7").WithLocation(68, 29),
// (81,18): error CS0170: Use of possibly unassigned field 'F3'
// z8 = y8.F3;
Diagnostic(ErrorCode.ERR_UseDefViolationField, "y8.F3").WithArguments("F3").WithLocation(81, 18)
);
}
......@@ -16827,5 +16827,91 @@ class A : System.Attribute
// string P => null;
Diagnostic(ErrorCode.WRN_NullAsNonNullable, "null").WithLocation(4, 17));
}
[Fact]
public void UnassignedParameterField()
{
var source =
@"class C
{
static void F(out S s)
{
C c;
c = s.F;
s.F.ToString();
}
}
struct S
{
internal C? F;
}";
var comp = CreateStandardCompilation(source, parseOptions: TestOptions.Regular8);
comp.VerifyDiagnostics(
// (6,13): error CS0170: Use of possibly unassigned field 'F'
// c = s.F;
Diagnostic(ErrorCode.ERR_UseDefViolationField, "s.F").WithArguments("F").WithLocation(6, 13),
// (3,17): error CS0177: The out parameter 's' must be assigned to before control leaves the current method
// static void F(out S s)
Diagnostic(ErrorCode.ERR_ParamUnassigned, "F").WithArguments("s").WithLocation(3, 17),
// (12,17): warning CS0649: Field 'S.F' is never assigned to, and will always have its default value null
// internal C? F;
Diagnostic(ErrorCode.WRN_UnassignedInternalField, "F").WithArguments("S.F", "null").WithLocation(12, 17));
}
[Fact]
public void UnassignedLocalField()
{
var source =
@"class C
{
static void F()
{
S s;
C c;
c = s.F;
s.F.ToString();
}
}
struct S
{
internal C? F;
}";
var comp = CreateStandardCompilation(source, parseOptions: TestOptions.Regular8);
comp.VerifyDiagnostics(
// (7,13): error CS0170: Use of possibly unassigned field 'F'
// c = s.F;
Diagnostic(ErrorCode.ERR_UseDefViolationField, "s.F").WithArguments("F").WithLocation(7, 13),
// (13,17): warning CS0649: Field 'S.F' is never assigned to, and will always have its default value null
// internal C? F;
Diagnostic(ErrorCode.WRN_UnassignedInternalField, "F").WithArguments("S.F", "null").WithLocation(13, 17));
}
[Fact]
public void UnassignedLocalProperty()
{
var source =
@"class C
{
static void F()
{
S s;
C c;
c = s.P;
s.P.ToString();
}
}
struct S
{
internal C? P { get => null; }
}";
var comp = CreateStandardCompilation(source, parseOptions: TestOptions.Regular8);
comp.VerifyDiagnostics(
// (7,13): warning CS8601: Possible null reference assignment.
// c = s.P;
Diagnostic(ErrorCode.WRN_NullReferenceAssignment, "s.P").WithLocation(7, 13),
// (8,9): warning CS8602: Possible dereference of a null reference.
// s.P.ToString();
Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "s.P").WithLocation(8, 9));
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册