Search code examples
c#.netnullable-reference-types

How to indicate that a property of a return value from a method is not null because it was checked inside the method?


How to indicate to static null flow analysis in C# that a property from a method's return object is not null because it has been validated? See the code below.

Let's have this validation method

public static class AuthenticationHeader
{
    public static bool TryParse(
        HttpRequest request,
        [NotNullWhen(returnValue: true)] out AuthenticationHeaderValue? value) =>
        AuthenticationHeaderValue.TryParse(
            request.Headers.Authorization,
            out value) && !string.IsNullOrEmpty(value.Parameter);
}

and its use in some other method

protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
    if (!AuthenticationHeader.TryParse(Request, out var authHeader))
    {
        return AuthenticateResult.Fail("Missing Authorization header");
    }

    _client.DefaultRequestHeaders.Authorization = authHeader;
    var apiClient = new CommonApiClient(_client);

    var userInfo = await TryGetUserInfo(apiClient);

    if (userInfo is null)
    {
        return AuthenticateResult.Fail("Unauthorized");
    }

    var identity = new ClaimsIdentity(
        BuildClaims(userInfo, authHeader.Parameter), // Possible null reference argument for parameter 'token' in ...

    var ticket = new AuthenticationTicket(new ClaimsPrincipal(identity), Scheme.Name);
    return AuthenticateResult.Success(ticket);
}

[NotNullWhen(returnValue: true)] works great on value, but static analysis does not know that value.Parameter has been checked already.


Solution

  • AFAIK this is not possible with current state of attributes for null-state static analysis interpreted by the C# compiler. You need to workaround with in-place checks:

    if (!AuthenticationHeader.TryParse(Request, out var authHeader) || string.IsNullOrEmpty(authHeader.Parameter))
    {
        return AuthenticateResult.Fail("Missing Authorization header");
    }
    

    Or return both values from the method (changing the usage accordingly):

    public static bool TryParse(
        HttpRequest request,
        [NotNullWhen(returnValue: true)] out AuthenticationHeaderValue? value, 
        [NotNullWhen(returnValue: true)] out string? par)
    {
        var tryParse = AuthenticationHeaderValue.TryParse(
            request.Headers.Authorization,
            out value);
    
        par = value?.Parameter;
        
        return tryParse && !string.IsNullOrEmpty(par);
    }
    

    Or use null-forgiving operator.

    P.S.

    Created discussion at the language repo to support MemberNotNullWhenAttribute usage on method parameters.