Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enforce unconditional attributes within method bodies #40789

Merged
merged 13 commits into from
Jan 18, 2020
4 changes: 2 additions & 2 deletions src/Compilers/CSharp/Portable/CSharpResources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions src/Compilers/CSharp/Portable/CSharpResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -5439,10 +5439,10 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
<value>Argument cannot be used as an output for parameter due to differences in the nullability of reference types.</value>
</data>
<data name="WRN_DisallowNullAttributeForbidsMaybeNullAssignment" xml:space="preserve">
<value>A possible null value may not be assigned to a target marked with the [DisallowNull] attribute</value>
<value>A possible null value may not be used for a type marked with [NotNull] or [DisallowNull]</value>
</data>
<data name="WRN_DisallowNullAttributeForbidsMaybeNullAssignment_Title" xml:space="preserve">
<value>A possible null value may not be assigned to a target marked with the [DisallowNull] attribute</value>
<value>A possible null value may not be used for a type marked with [NotNull] or [DisallowNull]</value>
</data>
<data name="WRN_NullabilityMismatchInReturnTypeOfTargetDelegate" xml:space="preserve">
<value>Nullability of reference types in return type of '{0}' doesn't match the target delegate '{1}'.</value>
Expand Down
111 changes: 78 additions & 33 deletions src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1057,7 +1057,6 @@ private void ReportNullableAssignmentIfNecessary(
}

if (value == null ||
value.WasCompilerGenerated ||
!targetType.HasType ||
targetType.Type.IsValueType)
{
Expand Down Expand Up @@ -1420,9 +1419,17 @@ private void EnterParameter(ParameterSymbol parameter, TypeWithAnnotations param

private static TypeWithState GetParameterState(TypeWithAnnotations parameterType, FlowAnalysisAnnotations parameterAnnotations)
{
return ((parameterAnnotations & FlowAnalysisAnnotations.AllowNull) != 0) ?
TypeWithState.Create(parameterType.Type, NullableFlowState.MaybeDefault) :
parameterType.ToTypeWithState();
if ((parameterAnnotations & FlowAnalysisAnnotations.AllowNull) != 0)
{
return TypeWithState.Create(parameterType.Type, NullableFlowState.MaybeDefault);
}

if ((parameterAnnotations & FlowAnalysisAnnotations.DisallowNull) != 0)
{
return TypeWithState.Create(parameterType.Type, NullableFlowState.NotNull);
}

return parameterType.ToTypeWithState();
}

protected override BoundNode VisitReturnStatementNoAdjust(BoundReturnStatement node)
Expand All @@ -1437,17 +1444,21 @@ protected override BoundNode VisitReturnStatementNoAdjust(BoundReturnStatement n

// Should not convert to method return type when inferring return type (when _returnTypesOpt != null).
if (_returnTypesOpt == null &&
TryGetReturnType(out TypeWithAnnotations returnType))
TryGetReturnType(out TypeWithAnnotations returnType, out FlowAnalysisAnnotations returnAnnotations))
{
TypeWithState returnState;
if (node.RefKind == RefKind.None)
{
VisitOptionalImplicitConversion(expr, returnType, useLegacyWarnings: false, trackMembers: false, AssignmentKind.Return);
returnState = VisitOptionalImplicitConversion(expr, returnType, useLegacyWarnings: false, trackMembers: false, AssignmentKind.Return);
}
else
{
// return ref expr;
VisitRefExpression(expr, returnType);
returnState = VisitRefExpression(expr, returnType);
}

// If the return has annotations, we perform an additional check for nullable value types
CheckDisallowedNullAssignment(returnState, returnAnnotations, node.Syntax.Location, forReturn: true);
}
else
{
Expand Down Expand Up @@ -1483,12 +1494,13 @@ private TypeWithState VisitRefExpression(BoundExpression expr, TypeWithAnnotatio
return resultType;
}

private bool TryGetReturnType(out TypeWithAnnotations type)
private bool TryGetReturnType(out TypeWithAnnotations type, out FlowAnalysisAnnotations annotations)
{
var method = _symbol as MethodSymbol;
if (method is null)
{
type = default;
annotations = FlowAnalysisAnnotations.None;
return false;
}

Expand All @@ -1499,22 +1511,26 @@ private bool TryGetReturnType(out TypeWithAnnotations type)
if (returnType.IsVoidType())
{
type = default;
annotations = FlowAnalysisAnnotations.None;
return false;
}

if (!method.IsAsync)
{
type = ApplyUnconditionalAnnotations(returnType, delegateOrMethod.ReturnTypeFlowAnalysisAnnotations);
annotations = delegateOrMethod.ReturnTypeFlowAnalysisAnnotations;
type = ApplyUnconditionalAnnotations(returnType, annotations);
return true;
}

if (returnType.Type.IsGenericTaskType(compilation))
{
type = ((NamedTypeSymbol)returnType.Type).TypeArgumentsWithAnnotationsNoUseSiteDiagnostics.Single();
annotations = FlowAnalysisAnnotations.None;
return true;
}

type = default;
annotations = FlowAnalysisAnnotations.None;
return false;
}

Expand Down Expand Up @@ -3224,21 +3240,11 @@ private void ReinferMethodAndVisitArguments(BoundCall node, TypeWithState receiv
{
returnState = returnState.WithNotNullState();
}
ReportMaybeNullFromTypeParameterValueIfNeeded(node, returnState, GetRValueAnnotations(method));

SetResult(node, returnState, method.ReturnTypeWithAnnotations);
SetUpdatedSymbol(node, node.Method, method);
}

/// <summary>
/// Members that return [MaybeNull]T values (for an unconstrained type parameter) produce a warning upon usage,
/// just like default(T).
/// </summary>
private void ReportMaybeNullFromTypeParameterValueIfNeeded(BoundExpression expr, TypeWithState typeWithState, FlowAnalysisAnnotations annotations)
{
// Not implemented. See https://github.com/dotnet/roslyn/issues/38638 for an alternative.
}

private void LearnFromEqualsMethod(MethodSymbol method, BoundCall node, TypeWithState receiverType, ImmutableArray<VisitArgumentResult> results)
{
// easy out
Expand All @@ -3254,7 +3260,6 @@ private void LearnFromEqualsMethod(MethodSymbol method, BoundCall node, TypeWith
return;
}


var isStaticEqualsMethod = method.Equals(compilation.GetSpecialTypeMember(SpecialMember.System_Object__EqualsObjectObject))
|| method.Equals(compilation.GetSpecialTypeMember(SpecialMember.System_Object__ReferenceEquals));
if (isStaticEqualsMethod ||
Expand Down Expand Up @@ -3498,6 +3503,11 @@ private static TypeWithAnnotations ApplyUnconditionalAnnotations(TypeWithAnnotat
return declaredType.AsAnnotated();
}

if ((annotations & FlowAnalysisAnnotations.NotNull) == FlowAnalysisAnnotations.NotNull)
{
return declaredType.AsNotAnnotated();
}

return declaredType;
}

Expand Down Expand Up @@ -3883,12 +3893,39 @@ private void VisitArgumentConversionAndInboundAssignmentsAndPreConditions(
Debug.Assert(!this.IsConditionalState);
}

private void CheckDisallowedNullAssignment(TypeWithState state, FlowAnalysisAnnotations annotations, Location location)
private void CheckDisallowedNullAssignment(TypeWithState state, FlowAnalysisAnnotations annotations, Location location, bool forReturn = false)
{
if (((annotations & FlowAnalysisAnnotations.DisallowNull) != 0) && state.Type.IsNullableTypeOrTypeParameter() && state.MayBeNull)
if (forReturn)
{
annotations = ToInwardAnnotations(annotations);
}

// We do this extra check for types whose non-nullable version cannot be represented
if (((annotations & FlowAnalysisAnnotations.DisallowNull) != 0) && hasNoNonNullableCounterpart(state.Type) && state.MayBeNull)
{
ReportDiagnostic(ErrorCode.WRN_DisallowNullAttributeForbidsMaybeNullAssignment, location);
}

static bool hasNoNonNullableCounterpart(TypeSymbol type)
{
if (type is null)
Copy link
Member Author

@jcouv jcouv Jan 15, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 not sure if we can hit this. Just included for safety. #Resolved

{
return false;
}

// Some types that could receive a maybe-null value have a NotNull counterpart:
// [NotNull]string? -> string
// [NotNull]string -> string
// [NotNull]TClass -> TClass
// [NotNull]TClass? -> TClass
//
// While others don't:
// [NotNull]int? -> X
// [NotNull]TNullable -> X
// [NotNull]TStruct? -> X
// [NotNull]TOpen -> X
return (type.Kind == SymbolKind.TypeParameter && !type.IsReferenceType) || type.IsNullableTypeOrTypeParameter();
}
}

/// <summary>
Expand Down Expand Up @@ -3930,8 +3967,6 @@ private void VisitArgumentOutboundAssignmentsAndPostConditions(
{
ReportNullableAssignmentIfNecessary(parameterValue, lValueType, applyPostConditionsUnconditionally(parameterWithState, parameterAnnotations), UseLegacyWarnings(argument, result.LValueType));
}

ReportMaybeNullFromTypeParameterValueIfNeeded(argument, parameterWithState, parameterAnnotations);
}
break;
case RefKind.Out:
Expand Down Expand Up @@ -3977,8 +4012,6 @@ private void VisitArgumentOutboundAssignmentsAndPostConditions(
ReportNullabilityMismatchInArgument(argument.Syntax, lValueType.Type, parameter, parameterType.Type, forOutput: true);
}
}

ReportMaybeNullFromTypeParameterValueIfNeeded(argument, parameterWithState, parameterAnnotations);
}
break;
default:
Expand Down Expand Up @@ -6058,6 +6091,7 @@ private FlowAnalysisAnnotations GetLValueAnnotations(BoundExpression expr)
BoundFieldAccess field => getFieldAnnotations(field.FieldSymbol),
BoundObjectInitializerMember { MemberSymbol: PropertySymbol prop } => getSetterAnnotations(prop),
BoundObjectInitializerMember { MemberSymbol: FieldSymbol field } => getFieldAnnotations(field),
BoundParameter { ParameterSymbol: ParameterSymbol parameter } => ToInwardAnnotations(GetParameterAnnotations(parameter)),
_ => FlowAnalysisAnnotations.None
};

Expand Down Expand Up @@ -6099,6 +6133,21 @@ static FlowAnalysisAnnotations getPropertyAnnotations(SourcePropertySymbol prope
}
}

private static FlowAnalysisAnnotations ToInwardAnnotations(FlowAnalysisAnnotations outwardAnnotations)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ToInwardAnnotations [](start = 47, length = 19)

Consider naming ToInboundAnnotations to resemble similar terminology used in VisitArguments

{
var annotations = FlowAnalysisAnnotations.None;
if ((outwardAnnotations & FlowAnalysisAnnotations.MaybeNull) != 0)
{
// MaybeNull and MaybeNullWhen count as MaybeNull
annotations |= FlowAnalysisAnnotations.AllowNull;
}
if ((outwardAnnotations & FlowAnalysisAnnotations.NotNull) == FlowAnalysisAnnotations.NotNull)
Copy link
Contributor

@RikkiGibson RikkiGibson Jan 17, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style between this and the above if feels inconsistent #Closed

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's on purpose. MaybeNull is two bits, either of which should count (see comment above).
NotNull is also two bits, but we are lenient (we ignore NotNullWhenTrue and NotNullWhenFalse on their own here).


In reply to: 368161275 [](ancestors = 368161275)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll add a comment


In reply to: 368183067 [](ancestors = 368183067,368161275)

{
annotations |= FlowAnalysisAnnotations.DisallowNull;
}
return annotations;
}

private static bool UseLegacyWarnings(BoundExpression expr, TypeWithAnnotations exprType)
{
switch (expr.Kind)
Expand Down Expand Up @@ -6521,6 +6570,7 @@ public override BoundNode VisitIncrementOperator(BoundIncrementOperator node)
public override BoundNode VisitCompoundAssignmentOperator(BoundCompoundAssignmentOperator node)
{
var left = node.Left;
var right = node.Right;
Visit(left);
TypeWithAnnotations declaredType = LvalueResultType;
TypeWithAnnotations leftLValueType = declaredType;
Expand Down Expand Up @@ -6553,13 +6603,13 @@ public override BoundNode VisitCompoundAssignmentOperator(BoundCompoundAssignmen
}

TypeWithState resultType;
TypeWithState rightType = VisitRvalueWithState(node.Right);
TypeWithState rightType = VisitRvalueWithState(right);
if ((object)node.Operator.ReturnType != null)
{
if (node.Operator.Kind.IsUserDefined() && (object)node.Operator.Method != null && node.Operator.Method.ParameterCount == 2)
{
MethodSymbol method = node.Operator.Method;
VisitArguments(node, ImmutableArray.Create(node.Left, node.Right), method.ParameterRefKinds, method.Parameters, argsToParamsOpt: default,
VisitArguments(node, ImmutableArray.Create<BoundExpression>(node.Left, right), method.ParameterRefKinds, method.Parameters, argsToParamsOpt: default,
expanded: true, invokedAsExtensionMethod: false, method);
}

Expand Down Expand Up @@ -6782,11 +6832,6 @@ private Symbol VisitMemberAccess(BoundExpression node, BoundExpression receiverO
}
}

if (_expressionIsRead)
{
ReportMaybeNullFromTypeParameterValueIfNeeded(node, resultType, memberAnnotations);
Copy link
Member Author

@jcouv jcouv Jan 13, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 removed empty method #Resolved

}

SetResult(node, resultType, type);
return member;
}
Expand Down
4 changes: 2 additions & 2 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -1375,12 +1375,12 @@
<note />
</trans-unit>
<trans-unit id="WRN_DisallowNullAttributeForbidsMaybeNullAssignment">
<source>A possible null value may not be assigned to a target marked with the [DisallowNull] attribute</source>
<source>A possible null value may not be used for a type marked with [NotNull] or [DisallowNull]</source>
<target state="needs-review-translation">Do cíle označeného atributem [DisallowNull] se nedá předat možná hodnota null</target>
<note />
</trans-unit>
<trans-unit id="WRN_DisallowNullAttributeForbidsMaybeNullAssignment_Title">
<source>A possible null value may not be assigned to a target marked with the [DisallowNull] attribute</source>
<source>A possible null value may not be used for a type marked with [NotNull] or [DisallowNull]</source>
<target state="needs-review-translation">Do cíle označeného atributem [DisallowNull] se nedá předat možná hodnota null</target>
<note />
</trans-unit>
Expand Down
4 changes: 2 additions & 2 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -1375,12 +1375,12 @@
<note />
</trans-unit>
<trans-unit id="WRN_DisallowNullAttributeForbidsMaybeNullAssignment">
<source>A possible null value may not be assigned to a target marked with the [DisallowNull] attribute</source>
<source>A possible null value may not be used for a type marked with [NotNull] or [DisallowNull]</source>
<target state="needs-review-translation">Ein möglicher NULL-Wert darf nicht an ein Ziel übergeben werden, das mit dem [DisallowNull]-Attribut markiert ist.</target>
<note />
</trans-unit>
<trans-unit id="WRN_DisallowNullAttributeForbidsMaybeNullAssignment_Title">
<source>A possible null value may not be assigned to a target marked with the [DisallowNull] attribute</source>
<source>A possible null value may not be used for a type marked with [NotNull] or [DisallowNull]</source>
<target state="needs-review-translation">Ein möglicher NULL-Wert darf nicht an ein Ziel übergeben werden, das mit dem [DisallowNull]-Attribut markiert ist.</target>
<note />
</trans-unit>
Expand Down
4 changes: 2 additions & 2 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -1376,12 +1376,12 @@
<note />
</trans-unit>
<trans-unit id="WRN_DisallowNullAttributeForbidsMaybeNullAssignment">
<source>A possible null value may not be assigned to a target marked with the [DisallowNull] attribute</source>
<source>A possible null value may not be used for a type marked with [NotNull] or [DisallowNull]</source>
<target state="needs-review-translation">No se puede pasar un posible valor NULL a un destino marcado con el atributo [DisallowNull]</target>
<note />
</trans-unit>
<trans-unit id="WRN_DisallowNullAttributeForbidsMaybeNullAssignment_Title">
<source>A possible null value may not be assigned to a target marked with the [DisallowNull] attribute</source>
<source>A possible null value may not be used for a type marked with [NotNull] or [DisallowNull]</source>
<target state="needs-review-translation">No se puede pasar un posible valor NULL a un destino marcado con el atributo [DisallowNull]</target>
<note />
</trans-unit>
Expand Down
4 changes: 2 additions & 2 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -1375,12 +1375,12 @@
<note />
</trans-unit>
<trans-unit id="WRN_DisallowNullAttributeForbidsMaybeNullAssignment">
<source>A possible null value may not be assigned to a target marked with the [DisallowNull] attribute</source>
<source>A possible null value may not be used for a type marked with [NotNull] or [DisallowNull]</source>
<target state="needs-review-translation">Vous ne devez pas passer une possible valeur null à une cible marquée avec l'attribut [DisallowNull]</target>
<note />
</trans-unit>
<trans-unit id="WRN_DisallowNullAttributeForbidsMaybeNullAssignment_Title">
<source>A possible null value may not be assigned to a target marked with the [DisallowNull] attribute</source>
<source>A possible null value may not be used for a type marked with [NotNull] or [DisallowNull]</source>
<target state="needs-review-translation">Vous ne devez pas passer une possible valeur null à une cible marquée avec l'attribut [DisallowNull]</target>
<note />
</trans-unit>
Expand Down
Loading