Skip to content

Commit

Permalink
Use [NotNullWhenFalse] and [EnsuresNotNull] to drive nullability anal…
Browse files Browse the repository at this point in the history
…ysis (#26656)
  • Loading branch information
jcouv authored May 15, 2018
1 parent 4355cc4 commit 4d28557
Show file tree
Hide file tree
Showing 18 changed files with 2,212 additions and 133 deletions.
9 changes: 9 additions & 0 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.

11 changes: 10 additions & 1 deletion src/Compilers/CSharp/Portable/FlowAnalysis/DataFlowPassBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,16 @@ protected int GetOrCreateSlot(Symbol symbol, int containingSlot = 0)
variableBySlot[slot] = identifier;
}

Normalize(ref this.State);
if (IsConditionalState)
{
Normalize(ref this.StateWhenTrue);
Normalize(ref this.StateWhenFalse);
}
else
{
Normalize(ref this.State);
}

return slot;
}

Expand Down
229 changes: 209 additions & 20 deletions src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs

Large diffs are not rendered by default.

202 changes: 202 additions & 0 deletions src/Compilers/CSharp/Portable/Symbols/ExtraAnnotations.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Text;
using Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE;
using Microsoft.CodeAnalysis.PooledObjects;
using static Microsoft.CodeAnalysis.CSharp.Symbols.AttributeAnnotations;

namespace Microsoft.CodeAnalysis.CSharp.Symbols
{
[Flags]
internal enum AttributeAnnotations
{
None = 0,
NotNullWhenFalse,
EnsuresNotNull,
}

// PROTOTYPE(NullableReferenceTypes): external annotations should be removed or fully designed/productized
// If we choose to stick with an ad-hoc key (rather than annotations as source or as PE/ref assembly),
// we should consider the assembly qualified name format used in metadata (with backticks and such).
internal static class ExtraAnnotations
{
// APIs that are useful to annotate:
// 1) don't accept null input
// 2) return a reference type
// All types in a member which can be annotated should be annotated. Value types and void can be skipped (with a `default`)
private static readonly ImmutableDictionary<string, ImmutableArray<ImmutableArray<bool>>> Annotations =
new Dictionary<string, ImmutableArray<ImmutableArray<bool>>>
{
{ "System.Boolean System.Boolean.Parse(System.String)", Array(default, Nullable(false)) },
{ "System.Void System.Buffer.BlockCopy(System.Array, System.Int32, System.Array, System.Int32, System.Int32)", Array(default, Nullable(false), default, Nullable(false), default, default) },
{ "System.Int32 System.Buffer.ByteLength(System.Array)", Array(default, Nullable(false)) },
{ "System.Byte System.Buffer.GetByte(System.Array, System.Int32)", Array(default, Nullable(false), default) },
{ "System.Void System.Buffer.SetByte(System.Array, System.Int32, System.Byte)", Array(default, Nullable(false), default, default) },
{ "System.Byte System.Byte.Parse(System.String)", Array(default, Nullable(false)) },
{ "System.Byte System.Byte.Parse(System.String, System.IFormatProvider)", Array(default, Nullable(false), default) },
{ "System.Byte System.Byte.Parse(System.String, System.Globalization.NumberStyles)", Array(default, Nullable(false), Nullable(false)) },
{ "System.Byte System.Byte.Parse(System.String, System.Globalization.NumberStyles, System.IFormatProvider)", Array(default, Nullable(false), Nullable(false), default) },
{ "System.String System.String.Concat(System.String, System.String)", Array(Nullable(false), Nullable(true), Nullable(true)) },
}.ToImmutableDictionary();

private static readonly ImmutableDictionary<string, ImmutableArray<AttributeAnnotations>> Attributes =
new Dictionary<string, ImmutableArray<AttributeAnnotations>>
{
{ "System.Boolean System.String.IsNullOrEmpty(System.String)", Array(default, NotNullWhenFalse) },
{ "System.Boolean System.String.IsNullOrWhiteSpace(System.String)", Array(default, NotNullWhenFalse) },
{ "System.Boolean System.String.Contains(System.String)", Array(default, EnsuresNotNull) },
}.ToImmutableDictionary();

internal static string MakeMethodKey(PEMethodSymbol method, ParamInfo<TypeSymbol>[] paramInfo)
{
var pooledBuilder = PooledStringBuilder.GetInstance();

StringBuilder builder = pooledBuilder.Builder;
Add(paramInfo[0].Type, builder);
builder.Append(' ');

Add(method.ContainingType, builder);
builder.Append('.');

builder.Append(method.Name);
builder.Append('(');

for (int i = 1; i < paramInfo.Length; i++)
{
Add(paramInfo[i].Type, builder);
if (i < paramInfo.Length - 1)
{
builder.Append(", ");
}
}

builder.Append(')');
return pooledBuilder.ToStringAndFree();

// PROTOTYPE(NullableReferenceTypes): Many cases are not yet handled
// generic type args
// ref kind
// 'this'
// static vs. instance
// use assembly qualified name format (used in metadata) rather than symbol display?
}

internal static string MakeMethodKey(MethodSymbol method)
{
var containingType = method.ContainingSymbol as TypeSymbol;
if (containingType is null)
{
return null;
}

var pooledBuilder = PooledStringBuilder.GetInstance();

StringBuilder builder = pooledBuilder.Builder;
Add(method.ReturnType.TypeSymbol, builder);
builder.Append(' ');

Add(containingType, builder);
builder.Append('.');

builder.Append(method.Name);
builder.Append('(');

var parameterTypes = method.ParameterTypes;
for (int i = 0; i < parameterTypes.Length; i++)
{
Add(parameterTypes[i].TypeSymbol, builder);
if (i < parameterTypes.Length - 1)
{
builder.Append(", ");
}
}

builder.Append(')');
return pooledBuilder.ToStringAndFree();

// PROTOTYPE(NullableReferenceTypes): Many cases are not yet handled
// generic type args
// ref kind
// 'this'
// static vs. instance
// use assembly qualified name format (used in metadata) rather than symbol display?
}

private static ImmutableArray<ImmutableArray<bool>> Array(params ImmutableArray<bool>[] values)
=> values.ToImmutableArray();

private static ImmutableArray<AttributeAnnotations> Array(params AttributeAnnotations[] values)
=> values.ToImmutableArray();

private static ImmutableArray<bool> Nullable(params bool[] values)
{
Debug.Assert(values.Length > 0);
return values.ToImmutableArray();
}

internal static ImmutableArray<ImmutableArray<bool>> GetExtraAnnotations(string key)
{
if (key is null)
{
return default;
}

if (!Annotations.TryGetValue(key, out var flags))
{
return default;
}

return flags;
}

private static void Add(TypeSymbol type, StringBuilder builder)
=> builder.Append(
type.ToDisplayString(
SymbolDisplayFormat.CSharpErrorMessageFormat
.RemoveMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.UseSpecialTypes)
.AddMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier)
// displaying tuple syntax causes to load the members of ValueTuple, which can cause a cycle, so we use long-hand format instead
.WithCompilerInternalOptions(SymbolDisplayCompilerInternalOptions.UseValueTuple)));

/// <summary>
/// index 0 is used for return type
/// other parameters follow
/// </summary>
internal static (bool hasAny, AttributeAnnotations annotations) GetExtraAttributes(string key, int parameterIndex)
{
if (key is null)
{
return (false, default);
}

if (!Attributes.TryGetValue(key, out var extraAttributes))
{
return (false, default);
}

return (true, extraAttributes[parameterIndex + 1]);
}
}

internal static class ParameterAnnotationsExtensions
{
internal static AttributeAnnotations With(this AttributeAnnotations value, bool notNullWhenFalse, bool ensuresNotNull)
{
if (notNullWhenFalse)
{
value |= NotNullWhenFalse;
}

if (ensuresNotNull)
{
value |= EnsuresNotNull;
}

return value;
}
}
}

This file was deleted.

Loading

0 comments on commit 4d28557

Please sign in to comment.