未验证 提交 e4019b2a 编写于 作者: R Rikki Gibson 提交者: GitHub

Learn from calls to Equals methods in NullableWalker (#36722)

上级 54142de3
......@@ -39,6 +39,12 @@ If the analysis determines that a null check always (or never) passes, a hidden
A number of null checks affect the flow state when tested for:
- comparisons to `null`: `x == null` and `x != null`
- `is` operator: `x is null`, `x is K` (where `K` is a constant), `x is string`, `x is string s`
- calls to well-known equality methods, including:
- `static bool object.Equals(object, object)`
- `static bool object.ReferenceEquals(object, object)`
- `bool object.Equals(object)` and overrides
- `bool IEquatable<T>(T)` and implementations
- `bool IEqualityComparer<T>(T, T)` and implementations
Invocation of methods annotated with the following attributes will also affect flow analysis:
- simple pre-conditions: `[AllowNull]` and `[DisallowNull]`
......
......@@ -2168,8 +2168,6 @@ protected override void AfterLeftChildHasBeenVisited(BoundBinaryOperator binary)
if (operandComparedToNull != null)
{
operandComparedToNull = SkipReferenceConversions(operandComparedToNull);
// Set all nested conditional slots. For example in a?.b?.c we'll set a, b, and c.
bool nonNullCase = op != BinaryOperatorKind.Equal; // true represents WhenTrue
splitAndLearnFromNonNullTest(operandComparedToNull, whenTrue: nonNullCase);
......@@ -2817,8 +2815,11 @@ private void ReinferMethodAndVisitArguments(BoundCall node, TypeWithState receiv
method = (MethodSymbol)AsMemberOfType(receiverType.Type, method);
}
method = VisitArguments(node, node.Arguments, refKindsOpt, method.Parameters, node.ArgsToParamsOpt,
node.Expanded, node.InvokedAsExtensionMethod, method).method;
ImmutableArray<VisitArgumentResult> results;
(method, results) = VisitArguments(node, node.Arguments, refKindsOpt, method.Parameters, node.ArgsToParamsOpt,
node.Expanded, node.InvokedAsExtensionMethod, method);
LearnFromEqualsMethod(method, node, receiverType, results);
if (method.MethodKind == MethodKind.LocalFunction)
{
......@@ -2829,6 +2830,104 @@ private void ReinferMethodAndVisitArguments(BoundCall node, TypeWithState receiv
SetResult(node, GetReturnTypeWithState(method), method.ReturnTypeWithAnnotations);
}
private void LearnFromEqualsMethod(MethodSymbol method, BoundCall node, TypeWithState receiverType, ImmutableArray<VisitArgumentResult> results)
{
// easy out
var parameterCount = method.ParameterCount;
if ((parameterCount != 1 && parameterCount != 2)
|| method.MethodKind != MethodKind.Ordinary
|| method.ReturnType.SpecialType != SpecialType.System_Boolean
|| (method.Name != SpecialMembers.GetDescriptor(SpecialMember.System_Object__Equals).Name
&& method.Name != SpecialMembers.GetDescriptor(SpecialMember.System_Object__ReferenceEquals).Name))
{
return;
}
var arguments = node.Arguments;
var isStaticEqualsMethod = method.Equals(compilation.GetSpecialTypeMember(SpecialMember.System_Object__EqualsObjectObject))
|| method.Equals(compilation.GetSpecialTypeMember(SpecialMember.System_Object__ReferenceEquals));
if (isStaticEqualsMethod ||
isWellKnownEqualityMethodOrImplementation(compilation, method, WellKnownMember.System_Collections_Generic_IEqualityComparer_T__Equals))
{
Debug.Assert(arguments.Length == 2);
learnFromEqualsMethodArguments(arguments[0], results[0].RValueType, arguments[1], results[1].RValueType);
return;
}
var isObjectEqualsMethodOrOverride = method.GetLeastOverriddenMethod(accessingTypeOpt: null)
.Equals(compilation.GetSpecialTypeMember(SpecialMember.System_Object__Equals));
if (isObjectEqualsMethodOrOverride ||
isWellKnownEqualityMethodOrImplementation(compilation, method, WellKnownMember.System_IEquatable_T__Equals))
{
Debug.Assert(arguments.Length == 1);
learnFromEqualsMethodArguments(node.ReceiverOpt, receiverType, arguments[0], results[0].RValueType);
return;
}
static bool isWellKnownEqualityMethodOrImplementation(CSharpCompilation compilation, MethodSymbol method, WellKnownMember wellKnownMember)
{
var wellKnownMethod = compilation.GetWellKnownTypeMember(wellKnownMember);
if (wellKnownMethod is null)
{
return false;
}
var wellKnownType = wellKnownMethod.ContainingType;
var parameterType = method.Parameters[0].TypeWithAnnotations;
var constructedType = wellKnownType.Construct(ImmutableArray.Create(parameterType));
Symbol constructedMethod = null;
foreach (var member in constructedType.GetMembers(WellKnownMemberNames.ObjectEquals))
{
if (member.OriginalDefinition.Equals(wellKnownMethod))
{
constructedMethod = member;
break;
}
}
Debug.Assert(constructedMethod != null, "the original definition is present but the constructed method isn't present");
// FindImplementationForInterfaceMember doesn't check if this method is itself the interface method we're looking for
if (constructedMethod.Equals(method))
{
return true;
}
var implementationMethod = method.ContainingType.FindImplementationForInterfaceMember(constructedMethod);
return method.Equals(implementationMethod);
}
void learnFromEqualsMethodArguments(BoundExpression left, TypeWithState leftType, BoundExpression right, TypeWithState rightType)
{
// comparing anything to a null literal gives maybe-null when true and not-null when false
// comparing a maybe-null to a not-null gives us not-null when true, nothing learned when false
if (left.ConstantValue?.IsNull == true)
{
Split();
LearnFromNullTest(right, ref StateWhenTrue);
LearnFromNonNullTest(right, ref StateWhenFalse);
}
else if (right.ConstantValue?.IsNull == true)
{
Split();
LearnFromNullTest(left, ref StateWhenTrue);
LearnFromNonNullTest(left, ref StateWhenFalse);
}
else if (leftType.MayBeNull && rightType.IsNotNull)
{
Split();
LearnFromNonNullTest(left, ref StateWhenTrue);
}
else if (rightType.MayBeNull && leftType.IsNotNull)
{
Split();
LearnFromNonNullTest(right, ref StateWhenTrue);
}
}
}
private TypeWithState VisitCallReceiver(BoundCall node)
{
var receiverOpt = node.ReceiverOpt;
......
......@@ -1707,7 +1707,7 @@ private static Location GetInterfaceLocation(Symbol interfaceMember, TypeSymbol
snt = implementingType as SourceMemberContainerTypeSymbol;
}
return snt?.GetImplementsLocation(@interface) ?? implementingType.Locations[0];
return snt?.GetImplementsLocation(@interface) ?? implementingType.Locations.FirstOrNone();
}
private static bool ReportAnyMismatchedConstraints(MethodSymbol interfaceMethod, TypeSymbol implementingType, MethodSymbol implicitImpl, DiagnosticBag diagnostics)
......
......@@ -122,6 +122,7 @@ internal enum SpecialMember
System_Object__GetHashCode,
System_Object__Equals,
System_Object__EqualsObjectObject,
System_Object__ToString,
System_Object__ReferenceEquals,
......
......@@ -834,6 +834,15 @@ static SpecialMembers()
(byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Boolean,
(byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Object,
// System_Object__EqualsObjectObject
(byte)(MemberFlags.Method | MemberFlags.Static), // Flags
(byte)SpecialType.System_Object, // DeclaringTypeId
0, // Arity
2, // Method Signature
(byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Boolean,
(byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Object,
(byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Object,
// System_Object__ToString
(byte)(MemberFlags.Method | MemberFlags.Virtual), // Flags
(byte)SpecialType.System_Object, // DeclaringTypeId
......@@ -1099,6 +1108,7 @@ static SpecialMembers()
"GetUpperBound", // System_Array__GetUpperBound
"GetHashCode", // System_Object__GetHashCode
"Equals", // System_Object__Equals
"Equals", // System_Object__EqualsObjectObject
"ToString", // System_Object__ToString
"ReferenceEquals", // System_Object__ReferenceEquals
"op_Explicit", // System_IntPtr__op_Explicit_ToPointer
......
......@@ -68,6 +68,8 @@ internal enum WellKnownMember
System_IEquatable_T__Equals,
System_Collections_Generic_IEqualityComparer_T__Equals,
System_Collections_Generic_EqualityComparer_T__Equals,
System_Collections_Generic_EqualityComparer_T__GetHashCode,
System_Collections_Generic_EqualityComparer_T__get_Default,
......
......@@ -448,6 +448,15 @@ static WellKnownMembers()
(byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Boolean, // Return Type
(byte)SignatureTypeCode.GenericTypeParameter, 0,
// System_Collections_Generic_IEqualityComparer_T__Equals
(byte)(MemberFlags.Method | MemberFlags.Virtual), // Flags
(byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_Collections_Generic_IEqualityComparer_T - WellKnownType.ExtSentinel), // DeclaringTypeId
0, // Arity
2, // Method Signature
(byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Boolean, // Return Type
(byte)SignatureTypeCode.GenericTypeParameter, 0,
(byte)SignatureTypeCode.GenericTypeParameter, 0,
// System_Collections_Generic_EqualityComparer_T__Equals
(byte)(MemberFlags.Method | MemberFlags.Virtual), // Flags
(byte)WellKnownType.System_Collections_Generic_EqualityComparer_T, // DeclaringTypeId
......@@ -3494,6 +3503,7 @@ static WellKnownMembers()
"GetFieldFromHandle", // System_Reflection_FieldInfo__GetFieldFromHandle2
"Value", // System_Reflection_Missing__Value
"Equals", // System_IEquatable_T__Equals
"Equals", // System_Collections_Generic_IEqualityComparer_T__Equals
"Equals", // System_Collections_Generic_EqualityComparer_T__Equals
"GetHashCode", // System_Collections_Generic_EqualityComparer_T__GetHashCode
"get_Default", // System_Collections_Generic_EqualityComparer_T__get_Default
......
......@@ -303,6 +303,7 @@ internal enum WellKnownType
System_InvalidOperationException,
System_Runtime_CompilerServices_SwitchExpressionException,
System_Collections_Generic_IEqualityComparer_T,
NextAvailable,
......@@ -602,7 +603,8 @@ internal static class WellKnownTypes
"System.Threading.CancellationTokenSource",
"System.InvalidOperationException",
"System.Runtime.CompilerServices.SwitchExpressionException"
"System.Runtime.CompilerServices.SwitchExpressionException",
"System.Collections.Generic.IEqualityComparer`1",
};
private readonly static Dictionary<string, WellKnownType> s_nameToTypeIdMap = new Dictionary<string, WellKnownType>((int)Count);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册