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 NotNull on method exit #42827

Merged
merged 10 commits into from
Apr 2, 2020
Merged

Enforce NotNull on method exit #42827

merged 10 commits into from
Apr 2, 2020

Conversation

jcouv
Copy link
Member

@jcouv jcouv commented Mar 27, 2020

Fixes #42386
Closes #42981

Relates to PR #40789 (enforce unconditional attributes in method bodies)

@jcouv jcouv added Area-Compilers PR For Personal Review Only The PR doesn’t require anyone other than the developer to review it. Feature - Nullable Reference Types Nullable Reference Types labels Mar 27, 2020
@jcouv jcouv self-assigned this Mar 27, 2020
@jcouv jcouv force-pushed the enforce-notnull branch 2 times, most recently from a69138b to 68f2edd Compare March 30, 2020 22:26
@jcouv jcouv marked this pull request as ready for review March 31, 2020 01:24
@jcouv jcouv requested review from a team as code owners March 31, 2020 01:24
@jcouv jcouv force-pushed the enforce-notnull branch from 68f2edd to a04952a Compare March 31, 2020 01:25
@jcouv jcouv removed the PR For Personal Review Only The PR doesn’t require anyone other than the developer to review it. label Mar 31, 2020
@jcouv
Copy link
Member Author

jcouv commented Mar 31, 2020

@dotnet/roslyn-compiler for review. Thanks

@jasonmalinowski jasonmalinowski removed the request for review from a team March 31, 2020 18:26
var slot = GetOrCreateSlot(parameter);
if (slot > 0)
{
var parameterState = state[slot];
Copy link
Member

Choose a reason for hiding this comment

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

Consider inlining this variable to avoid calculating value in most cases.

if (value is null)
{
throw new InvalidOperationException("This program location is unreachable.");
}
Copy link
Member

Choose a reason for hiding this comment

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

Is the throw needed since the assert above will have failed?

Copy link
Member Author

@jcouv jcouv Mar 31, 2020

Choose a reason for hiding this comment

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

The assert above is not necessarily annotated, in some of our projects. The stricter new enforcement implemented in this PR caused bootstrap build to fail some projects...

Copy link
Contributor

Choose a reason for hiding this comment

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

Could you implement in terms RoslynDebug.Assert instead of Debug.Assert to solve this?

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah, indeed :-) I'll try using RoslynDebug.Assert

@@ -5506,6 +5506,12 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
<data name="WRN_ParameterConditionallyDisallowsNull_Title" xml:space="preserve">
<value>Parameter may not have a null value when exiting in some condition.</value>
</data>
<data name="WRN_ParameterDisallowsNull" xml:space="preserve">
<value>Parameter '{0}' may not have a null value when exiting.</value>
</data>
Copy link
Member

@cston cston Mar 31, 2020

Choose a reason for hiding this comment

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

"Parameter may not have a null value when exiting" could imply the parameter must have a null value but might not. How about "Parameter must have a non-null value when exiting"? Similar comment for WRN_ParameterConditionallyDisallowsNull.

@RikkiGibson
Copy link
Contributor

interesting how the approach we came up with specifically to solve NotNullWhenAttribute ended up being useful for NotNullAttribute as well. 😄


class C<TStruct, TClass, TNotNull>
where TStruct : struct
where TClass : class
Copy link
Member

Choose a reason for hiding this comment

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

TClass is not used.

@jcouv
Copy link
Member Author

jcouv commented Apr 1, 2020

@RikkiGibson @dotnet/roslyn-compiler for second review. Thanks

}
x = null;
(x, _) = (null, 1);
} // 2
Copy link
Contributor

Choose a reason for hiding this comment

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

hmm. this makes it start to feel like a tradeoff for more complex methods. Imagine a complex method with many return points and many writes to some [NotNull] out param. Would you feel lost if the nullability warnings were on the returns instead of the original assignments?

Are we also trying to improve the experience in scenarios where users write a value that might be null to the out param, and if it is null, they write some fallback value that's known to be non-null?

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, there is a bit of trade-off. Warning sooner points to the location of the problematic assignment, but it also means that we flag temporary assignments (annoying).
For nullability feature in general, we lean away from anything that could be annoying and could hold you back from annotating your correct code as it already exists.

// (x, _) = (null, 1); // 5
Diagnostic(ErrorCode.WRN_ConvertingNullableToNonNullable, "null").WithLocation(41, 19),
// (42,5): warning CS8777: Parameter 'x' must have a non-null value when exiting.
// } // 6
Copy link
Contributor

Choose a reason for hiding this comment

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

Why enforce this on parameters with RefKind.None? Isn't the method just asserting about the value originally passed as an argument in that case?

On the other hand, it doesn't feel like there's a use case for assigning maybe-null to a NotNull, non-ref parameter, so it probably isn't very important to remove this warning.

Copy link
Contributor

Choose a reason for hiding this comment

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

Thinking about it for a moment, it seems like unnecessary effort to attempt to remove this warning--but it might not be what we want to prevent this warning by just assigning not-null to the parameter. e.g.

void MyAssert1([NotNull] object? obj)
{
    // hooray, we know the argument passed by the caller is non-null after the method returns
    if (obj is null)
        throw new ArgumentNullException(nameof(obj));
}

void MyAssert2([NotNull] object? obj)
{
    obj = new object(); // we fixed the warning, hooray!
    // but caller still might be holding a null..
}

Copy link
Member Author

Choose a reason for hiding this comment

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

Good point. I don't have a strategy to distinguish between those two cases :-(

=> Debug.Assert(b, message);

[Conditional("DEBUG")]
public static void AssertNotNull<T>([NotNull]T value) where T : class?
=> Debug.Assert(value is object, "Unexpected null reference");
public static void AssertNotNull<T>([NotNull] T value) where T : class?
Copy link
Contributor

Choose a reason for hiding this comment

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

This is not really something to fix in this PR, but I don't understand why this method is generic.

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 suspect it is so that it cannot be called with a struct parameter.
If it just took an object parameter, then structs would box, rather than produce an error.

@jcouv jcouv merged commit 7be257a into dotnet:master Apr 2, 2020
@ghost ghost added this to the Next milestone Apr 2, 2020
@jcouv jcouv deleted the enforce-notnull branch April 2, 2020 15:27
@sharwell sharwell modified the milestones: Next, temp, 16.6.P3 Apr 6, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
4 participants