dotnet / roslyn

The Roslyn .NET compiler provides C# and Visual Basic languages with rich code analysis APIs.
https://docs.microsoft.com/dotnet/csharp/roslyn-sdk/
MIT License
19.05k stars 4.03k forks source link

NullableWalker.LearnFromNullTest should not blindly strip conversions #36164

Open jcouv opened 5 years ago

jcouv commented 5 years ago
        private int LearnFromNullTest(BoundExpression expression, ref LocalState state)
        {
            var expressionWithoutConversion = RemoveConversion(expression, includeExplicitConversions: true).expression;
            var slot = MakeSlot(expressionWithoutConversion);
            return LearnFromNullTest(slot, expressionWithoutConversion.Type, ref state);
        }

Depending on the conversion, we can learn something about x when we learn that (C)x is null. For instance, in an implicit reference conversion.

Also, in a scenario with a user-defined conversion operator C(X x) with non-null input and output, I think we can infer from the contract that x was null if the output was null.

        [Fact]
        public void MaybeNull_OnExplicitConversion()
        {
            var c = CreateCompilation(new[] { @"
using System.Runtime.CompilerServices;
public class A
{
    public static explicit operator C(A? a) => throw null!;
}
public class C
{
    public void Main()
    {
        A a = new A();
        _ = IsNull((C)a)
            ? a.ToString()
            : a.ToString();
    }
    public static bool IsNull([MaybeNull] C c) => throw null!;
}
", MaybeNullAttributeDefinition }, options: WithNonNullTypesTrue());

            // Both diagnostics are unexpected 
            // We should not blindly strip conversions when learning that a value is null.
            // In this case, we shouldn't infer that `a` may be null from the fact that `(C)a` may be null.
            // Tracked by https://github.com/dotnet/roslyn/issues/36164
            c.VerifyDiagnostics(
                // (13,15): warning CS8602: Dereference of a possibly null reference.
                //             ? a.ToString()
                Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "a").WithLocation(13, 15),
                // (14,15): warning CS8602: Dereference of a possibly null reference.
                //             : a.ToString();
                Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "a").WithLocation(14, 15)
                );
        }
RikkiGibson commented 3 years ago

I think I figured out the answer for this inadvertently. May solve at least part of this as part of "improved definite assignment".

RikkiGibson commented 3 years ago

I think we should see what happens when we make the analysis a little more strict. RemoveConversion should not unwrap user-defined conversions except in the following scenarios:

If the impact of this on real-world code seems minimal, then I'd like to try shipping it as a bug fix level change. But if the impact is pretty severe, then we should look at what scenarios we broke, and whether those breaks are indicative of bugs in user code, or of an essentially valid usage pattern that wasn't properly annotated or that we didn't account for.

/cc @jcouv @333fred