未验证 提交 4d285574 编写于 作者: J Julien Couvreur 提交者: GitHub

Use [NotNullWhenFalse] and [EnsuresNotNull] to drive nullability analysis (#26656)

上级 4355cc42
......@@ -4660,6 +4660,15 @@ internal class CSharpResources {
}
}
/// <summary>
/// Looks up a localized string similar to Feature &apos;{0}&apos; is not available in C# 7.3. Please use language version {1} or greater..
/// </summary>
internal static string ERR_FeatureNotAvailableInVersion7_3 {
get {
return ResourceManager.GetString("ERR_FeatureNotAvailableInVersion7_3", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Feature &apos;{0}&apos; is not available in C# 8. Please use language version {1} or greater..
/// </summary>
......
......@@ -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;
}
......
......@@ -59,6 +59,12 @@ internal sealed partial class NullableWalker : DataFlowPassBase<NullableWalker.L
/// </summary>
private PooledDictionary<BoundExpression, ObjectCreationPlaceholderLocal> _placeholderLocals;
/// <summary>
/// For methods with annotations, we'll need to visit the arguments twice.
/// Once for diagnostics and once for result state (but disabling diagnostics).
/// </summary>
private bool _disableDiagnostics = false;
protected override void Free()
{
_variableTypes.Free();
......@@ -464,7 +470,10 @@ private bool IsByRefTarget(int slot)
private void ReportStaticNullCheckingDiagnostics(ErrorCode errorCode, SyntaxNode syntaxNode, params object[] arguments)
{
Diagnostics.Add(errorCode, syntaxNode.GetLocation(), arguments);
if (!_disableDiagnostics)
{
Diagnostics.Add(errorCode, syntaxNode.GetLocation(), arguments);
}
}
private void InheritNullableStateOfTrackableStruct(TypeSymbol targetType, int targetSlot, int valueSlot, bool isByRefTarget, int slotWatermark)
......@@ -1578,12 +1587,14 @@ public override BoundNode VisitCall(BoundCall node)
{
ImmutableArray<RefKind> refKindsOpt = node.ArgumentRefKindsOpt;
ImmutableArray<BoundExpression> arguments = RemoveArgumentConversions(node.Arguments, refKindsOpt);
ImmutableArray<Result> results = VisitArgumentsEvaluate(arguments, refKindsOpt, node.Expanded);
ImmutableArray<int> argsToParamsOpt = node.ArgsToParamsOpt;
ImmutableArray<Result> results = VisitArgumentsEvaluate(arguments, refKindsOpt, method.Parameters, argsToParamsOpt, node.Expanded);
if (method.IsGenericMethod && HasImplicitTypeArguments(node))
{
method = InferMethod(node, method, results.SelectAsArray(r => r.Type));
}
VisitArgumentsWarn(arguments, refKindsOpt, method.Parameters, node.ArgsToParamsOpt, node.Expanded, results);
VisitArgumentsWarn(arguments, refKindsOpt, method.Parameters, argsToParamsOpt, node.Expanded, results);
}
UpdateStateForCall(node);
......@@ -1594,7 +1605,6 @@ public override BoundNode VisitCall(BoundCall node)
ReplayReadsAndWrites(localFunc, node.Syntax, writes: true);
}
Debug.Assert(!IsConditionalState);
//if (this.State.Reachable) // PROTOTYPE(NullableReferenceTypes): Consider reachability?
{
_result = method.ReturnType;
......@@ -1603,6 +1613,49 @@ public override BoundNode VisitCall(BoundCall node)
return null;
}
/// <summary>
/// For each argument, figure out if its corresponding parameter is annotated with NotNullWhenFalse or
/// EnsuresNotNull.
/// </summary>
private static ImmutableArray<AttributeAnnotations> GetAnnotations(int numArguments,
bool expanded, ImmutableArray<ParameterSymbol> parameters, ImmutableArray<int> argsToParamsOpt)
{
ArrayBuilder<AttributeAnnotations> builder = null;
for (int i = 0; i < numArguments; i++)
{
(ParameterSymbol parameter, _) = GetCorrespondingParameter(i, parameters, argsToParamsOpt, expanded);
AttributeAnnotations annotations = parameter?.FlowAnalysisAnnotations ?? AttributeAnnotations.None;
// We'll ignore NotNullWhenFalse that is misused in metadata
if ((annotations & AttributeAnnotations.NotNullWhenFalse) != 0 &&
parameter.ContainingSymbol.GetTypeOrReturnType().SpecialType != SpecialType.System_Boolean)
{
annotations &= ~AttributeAnnotations.NotNullWhenFalse;
}
// We'll ignore EnsuresNotNull that is misused in metadata
if ((annotations & AttributeAnnotations.EnsuresNotNull) != 0 &&
(parameter.Type?.IsValueType != false || parameter.IsParams))
{
annotations &= ~AttributeAnnotations.EnsuresNotNull;
}
if (annotations != AttributeAnnotations.None && builder == null)
{
builder = ArrayBuilder<AttributeAnnotations>.GetInstance(numArguments);
builder.AddMany(AttributeAnnotations.None, i);
}
if (builder != null)
{
builder.Add(annotations);
}
}
return builder == null ? default : builder.ToImmutableAndFree();
}
// PROTOTYPE(NullableReferenceTypes): Record in the node whether type
// arguments were implicit, to allow for cases where the syntax is not an
// invocation (such as a synthesized call from a query interpretation).
......@@ -1661,7 +1714,7 @@ protected override void VisitArguments(ImmutableArray<BoundExpression> arguments
bool expanded)
{
Debug.Assert(!arguments.IsDefault);
ImmutableArray<Result> results = VisitArgumentsEvaluate(arguments, refKindsOpt, expanded);
ImmutableArray<Result> results = VisitArgumentsEvaluate(arguments, refKindsOpt, parameters, argsToParamsOpt, expanded);
// PROTOTYPE(NullableReferenceTypes): Can we handle some error cases?
// (Compare with CSharpOperationFactory.CreateBoundCallOperation.)
if (!node.HasErrors && !parameters.IsDefault)
......@@ -1673,7 +1726,31 @@ protected override void VisitArguments(ImmutableArray<BoundExpression> arguments
private ImmutableArray<Result> VisitArgumentsEvaluate(
ImmutableArray<BoundExpression> arguments,
ImmutableArray<RefKind> refKindsOpt,
ImmutableArray<ParameterSymbol> parameters,
ImmutableArray<int> argsToParamsOpt,
bool expanded)
{
var savedState = this.State.Clone();
// We do a first pass to work through the arguments without making any assumptions
var results = VisitArgumentsEvaluate(arguments, refKindsOpt);
// We do a second pass through the arguments, ignoring any diagnostics produced, but honoring the annotations,
// to get the proper result state.
ImmutableArray<AttributeAnnotations> annotations = GetAnnotations(arguments.Length,
expanded, parameters, argsToParamsOpt);
if (!annotations.IsDefault)
{
this.SetState(savedState);
VisitArgumentsEvaluateHonoringAnnotations(arguments, refKindsOpt, annotations);
}
return results;
}
private ImmutableArray<Result> VisitArgumentsEvaluate(
ImmutableArray<BoundExpression> arguments,
ImmutableArray<RefKind> refKindsOpt)
{
Debug.Assert(!IsConditionalState);
int n = arguments.Length;
......@@ -1684,22 +1761,118 @@ protected override void VisitArguments(ImmutableArray<BoundExpression> arguments
var builder = ArrayBuilder<Result>.GetInstance(n);
for (int i = 0; i < n; i++)
{
RefKind refKind = GetRefKind(refKindsOpt, i);
var argument = arguments[i];
if (refKind != RefKind.Out)
VisitArgumentEvaluate(arguments, refKindsOpt, i);
builder.Add(_result);
}
_result = _invalidType;
return builder.ToImmutableAndFree();
}
private void VisitArgumentEvaluate(ImmutableArray<BoundExpression> arguments, ImmutableArray<RefKind> refKindsOpt, int i)
{
RefKind refKind = GetRefKind(refKindsOpt, i);
var argument = arguments[i];
if (refKind != RefKind.Out)
{
// PROTOTYPE(NullReferenceTypes): `ref` arguments should be treated as l-values
// for assignment. See `ref x3` in StaticNullChecking.PassingParameters_01.
VisitRvalue(argument);
}
else
{
VisitLvalue(argument);
}
}
/// <summary>
/// Visit all the arguments for the purpose of computing the exit state of the method,
/// given the annotations, but disabling warnings.
/// </summary>
private void VisitArgumentsEvaluateHonoringAnnotations(
ImmutableArray<BoundExpression> arguments,
ImmutableArray<RefKind> refKindsOpt,
ImmutableArray<AttributeAnnotations> annotations)
{
Debug.Assert(!IsConditionalState);
bool saveDisableDiagnostics = _disableDiagnostics;
_disableDiagnostics = true;
Debug.Assert(annotations.Length == arguments.Length);
for (int i = 0; i < arguments.Length; i++)
{
if (this.IsConditionalState)
{
// PROTOTYPE(NullReferenceTypes): `ref` arguments should be treated as l-values
// for assignment. See `ref x3` in StaticNullChecking.PassingParameters_01.
VisitRvalue(argument);
// We could be in a conditional state because of NotNullWhenFalse annotation
// Then WhenTrue/False states correspond to the invocation returning true/false
// We'll assume that we're in the unconditional state where the method returns true,
// then we'll repeat assuming the method returns false.
LocalState whenTrue = this.StateWhenTrue.Clone();
LocalState whenFalse = this.StateWhenFalse.Clone();
this.SetState(whenTrue);
VisitArgumentEvaluate(arguments, refKindsOpt, i);
Debug.Assert(!IsConditionalState);
whenTrue = this.State; // LocalState may be a struct
this.SetState(whenFalse);
VisitArgumentEvaluate(arguments, refKindsOpt, i);
Debug.Assert(!IsConditionalState);
whenFalse = this.State; // LocalState may be a struct
this.SetConditionalState(whenTrue, whenFalse);
}
else
{
VisitLvalue(argument);
VisitArgumentEvaluate(arguments, refKindsOpt, i);
}
var argument = arguments[i];
if (argument.Type?.IsReferenceType != true)
{
continue;
}
int slot = MakeSlot(argument);
if (slot <= 0)
{
continue;
}
AttributeAnnotations annotation = annotations[i];
bool notNullWhenFalse = (annotation & AttributeAnnotations.NotNullWhenFalse) != 0;
bool ensuresNotNull = (annotation & AttributeAnnotations.EnsuresNotNull) != 0;
if (ensuresNotNull)
{
// The variable in this slot is not null
if (this.IsConditionalState)
{
this.StateWhenTrue[slot] = true;
this.StateWhenFalse[slot] = true;
}
else
{
this.State[slot] = true;
}
}
else if (notNullWhenFalse)
{
// We'll use the WhenTrue/False states to represent whether the invocation returns true/false
// PROTOTYPE(NullableReferenceTypes): Consider splitting for the entire method, not just once the first annotated argument is encountered
Split();
// The variable in this slot is not null when the method returns false
this.StateWhenFalse[slot] = true;
}
builder.Add(_result);
}
_result = _invalidType;
return builder.ToImmutableAndFree();
_disableDiagnostics = saveDisableDiagnostics;
}
private void VisitArgumentsWarn(
......@@ -1813,7 +1986,10 @@ private static ImmutableArray<BoundExpression> RemoveArgumentConversions(Immutab
private static (ParameterSymbol, TypeSymbolWithAnnotations) GetCorrespondingParameter(int argumentOrdinal, ImmutableArray<ParameterSymbol> parameters, ImmutableArray<int> argsToParamsOpt, bool expanded)
{
Debug.Assert(!parameters.IsDefault);
if (parameters.IsDefault)
{
return (null, null);
}
int n = parameters.Length;
ParameterSymbol parameter;
......@@ -3068,7 +3244,7 @@ public override BoundNode VisitArgList(BoundArgList node)
public override BoundNode VisitArgListOperator(BoundArgListOperator node)
{
VisitArgumentsEvaluate(node.Arguments, node.ArgumentRefKindsOpt, expanded: false);
VisitArgumentsEvaluate(node.Arguments, node.ArgumentRefKindsOpt);
Debug.Assert((object)node.Type == null);
SetResult(node);
return null;
......@@ -3148,7 +3324,7 @@ public override BoundNode VisitDynamicMemberAccess(BoundDynamicMemberAccess node
public override BoundNode VisitDynamicInvocation(BoundDynamicInvocation node)
{
VisitRvalue(node.Expression);
VisitArgumentsEvaluate(node.Arguments, node.ArgumentRefKindsOpt, expanded: false);
VisitArgumentsEvaluate(node.Arguments, node.ArgumentRefKindsOpt);
Debug.Assert(node.Type.IsDynamic());
Debug.Assert(node.Type.IsReferenceType);
......@@ -3176,7 +3352,7 @@ public override BoundNode VisitEventAssignmentOperator(BoundEventAssignmentOpera
public override BoundNode VisitDynamicObjectCreationExpression(BoundDynamicObjectCreationExpression node)
{
Debug.Assert(!IsConditionalState);
VisitArgumentsEvaluate(node.Arguments, node.ArgumentRefKindsOpt, expanded: false);
VisitArgumentsEvaluate(node.Arguments, node.ArgumentRefKindsOpt);
VisitObjectOrDynamicObjectCreation(node, node.InitializerExpressionOpt);
return null;
}
......@@ -3255,7 +3431,7 @@ public override BoundNode VisitDynamicIndexerAccess(BoundDynamicIndexerAccess no
var receiver = node.ReceiverOpt;
VisitRvalue(receiver);
CheckPossibleNullReceiver(receiver);
VisitArgumentsEvaluate(node.Arguments, node.ArgumentRefKindsOpt, expanded: false);
VisitArgumentsEvaluate(node.Arguments, node.ArgumentRefKindsOpt);
Debug.Assert(node.Type.IsDynamic());
......@@ -3421,7 +3597,7 @@ public override BoundNode VisitThrowExpression(BoundThrowExpression node)
return result;
}
#endregion Visitors
#endregion Visitors
protected override string Dump(LocalState state)
{
......@@ -3573,6 +3749,19 @@ public bool Reachable
return _knownNullState.Capacity > 0;
}
}
public override string ToString()
{
var pooledBuilder = PooledStringBuilder.GetInstance();
var builder = pooledBuilder.Builder;
builder.Append(" ");
for (int i = this.Capacity - 1; i >= 0; i--)
{
builder.Append(_knownNullState[i] ? (_notNull[i] ? "!" : "?") : "_");
}
return pooledBuilder.ToStringAndFree();
}
}
}
}
// 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.Metadata.PE
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).
......@@ -16,19 +27,28 @@ 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)", Parameters(skip, Nullable(false)) },
{ "System.Void System.Buffer.BlockCopy(System.Array, System.Int32, System.Array, System.Int32, System.Int32)", Parameters(skip, Nullable(false), skip, Nullable(false), skip, skip) },
{ "System.Int32 System.Buffer.ByteLength(System.Array)", Parameters(skip, Nullable(false)) },
{ "System.Byte System.Buffer.GetByte(System.Array, System.Int32)", Parameters(skip, Nullable(false), skip) },
{ "System.Void System.Buffer.SetByte(System.Array, System.Int32, System.Byte)", Parameters(skip, Nullable(false), skip, skip) },
{ "System.Byte System.Byte.Parse(System.String)", Parameters(skip, Nullable(false)) },
{ "System.Byte System.Byte.Parse(System.String, System.IFormatProvider)", Parameters(skip, Nullable(false), skip) },
{ "System.Byte System.Byte.Parse(System.String, System.Globalization.NumberStyles)", Parameters(skip, Nullable(false), Nullable(false)) },
{ "System.Byte System.Byte.Parse(System.String, System.Globalization.NumberStyles, System.IFormatProvider)", Parameters(skip, Nullable(false), Nullable(false), skip) },
{ "System.String System.String.Concat(System.String, System.String)", Parameters(Nullable(false), Nullable(true), Nullable(true)) },
{ "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)
......@@ -65,12 +85,51 @@ internal static string MakeMethodKey(PEMethodSymbol method, ParamInfo<TypeSymbol
// use assembly qualified name format (used in metadata) rather than symbol display?
}
/// <summary>
/// All types in a member which can be annotated should be annotated. Value types and void can be skipped.
/// </summary>
private static readonly ImmutableArray<bool> skip = default;
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>> Parameters(params ImmutableArray<bool>[] values)
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)
......@@ -81,6 +140,11 @@ private static ImmutableArray<bool> Nullable(params bool[] values)
internal static ImmutableArray<ImmutableArray<bool>> GetExtraAnnotations(string key)
{
if (key is null)
{
return default;
}
if (!Annotations.TryGetValue(key, out var flags))
{
return default;
......@@ -97,5 +161,42 @@ private static void Add(TypeSymbol type, StringBuilder builder)
.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;
}
}
}
......@@ -31,27 +31,29 @@ private enum WellKnownAttributeFlags
IsCallerFilePath = 0x1 << 5,
IsCallerLineNumber = 0x1 << 6,
IsCallerMemberName = 0x1 << 7,
NotNullWhenFalse = 0x1 << 8,
EnsuresNotNull = 0x1 << 9,
}
private struct PackedFlags
{
// Layout:
// |.............|n|rr|cccccccc|vvvvvvvv|
// |.........|n|rr|cccccccccc|vvvvvvvvvv|
//
// v = decoded well known attribute values. 8 bits.
// c = completion states for well known attributes. 1 if given attribute has been decoded, 0 otherwise. 8 bits.
// v = decoded well known attribute values. 10 bits.
// c = completion states for well known attributes. 1 if given attribute has been decoded, 0 otherwise. 10 bits.
// r = RefKind. 2 bits.
// n = hasNameInMetadata. 1 bit.
private const int WellKnownAttributeDataOffset = 0;
private const int WellKnownAttributeCompletionFlagOffset = 8;
private const int RefKindOffset = 16;
private const int WellKnownAttributeCompletionFlagOffset = 10;
private const int RefKindOffset = 20;
private const int RefKindMask = 0x3;
private const int WellKnownAttributeDataMask = 0xFF;
private const int WellKnownAttributeDataMask = 0x3FF;
private const int WellKnownAttributeCompletionFlagMask = WellKnownAttributeDataMask;
private const int HasNameInMetadataBit = 0x1 << 18;
private const int HasNameInMetadataBit = 0x1 << 22;
private const int AllWellKnownAttributesCompleteNoData = WellKnownAttributeCompletionFlagMask << WellKnownAttributeCompletionFlagOffset;
......@@ -645,6 +647,44 @@ internal override bool IsCallerMemberName
}
}
internal override AttributeAnnotations FlowAnalysisAnnotations
{
get
{
const WellKnownAttributeFlags notNullWhenFalse = WellKnownAttributeFlags.NotNullWhenFalse;
const WellKnownAttributeFlags ensuresNotNull = WellKnownAttributeFlags.EnsuresNotNull;
// PROTOTYPE(NullableReferenceTypes): the flags could be packed more
if (!_packedFlags.TryGetWellKnownAttribute(notNullWhenFalse, out bool hasNotNullWhenFalse) ||
!_packedFlags.TryGetWellKnownAttribute(ensuresNotNull, out bool hasEnsuresNotNull))
{
(bool memberHasAny, AttributeAnnotations annotations) = TryGetExtraAttributeAnnotations();
if (memberHasAny)
{
// External annotations win, if any is present on the member
hasNotNullWhenFalse = (annotations & AttributeAnnotations.NotNullWhenFalse) != 0;
hasEnsuresNotNull = (annotations & AttributeAnnotations.EnsuresNotNull) != 0;
}
else
{
hasNotNullWhenFalse = _moduleSymbol.Module.HasAttribute(_handle, AttributeDescription.NotNullWhenFalseAttribute);
hasEnsuresNotNull = _moduleSymbol.Module.HasAttribute(_handle, AttributeDescription.EnsuresNotNullAttribute);
}
_packedFlags.SetWellKnownAttribute(notNullWhenFalse, hasNotNullWhenFalse);
_packedFlags.SetWellKnownAttribute(ensuresNotNull, hasEnsuresNotNull);
if (memberHasAny)
{
return annotations;
}
}
return AttributeAnnotations.None.With(notNullWhenFalse: hasNotNullWhenFalse, ensuresNotNull: hasEnsuresNotNull);
}
}
public override TypeSymbolWithAnnotations Type
{
get
......@@ -653,7 +693,6 @@ public override TypeSymbolWithAnnotations Type
}
}
public override ImmutableArray<CustomModifier> RefCustomModifiers
{
get
......
......@@ -385,6 +385,27 @@ internal sealed override ObsoleteAttributeData ObsoleteAttributeData
internal abstract bool IsCallerMemberName { get; }
internal abstract AttributeAnnotations FlowAnalysisAnnotations { get; }
/// <summary>
/// If there are any annotations on the member (not just that parameter), then memberHasExtra is true. The purpose is to ensure
/// that if some annotations are present on the member, then annotations win over the attributes on the member in all positions.
/// That could mean removing an attribute.
/// </summary>
protected (bool memberHasExtra, AttributeAnnotations annotations) TryGetExtraAttributeAnnotations()
{
ParameterSymbol originalParameter = this.OriginalDefinition;
var containingMethod = originalParameter.ContainingSymbol as MethodSymbol;
if (containingMethod is null)
{
return (false, AttributeAnnotations.None);
}
string key = ExtraAnnotations.MakeMethodKey(containingMethod);
return ExtraAnnotations.GetExtraAttributes(key, this.Ordinal);
}
protected sealed override int HighestPriorityUseSiteError
{
get
......
......@@ -71,6 +71,8 @@ public override bool IsImplicitlyDeclared
internal override bool IsCallerMemberName { get { throw ExceptionUtilities.Unreachable; } }
internal override AttributeAnnotations FlowAnalysisAnnotations { get { throw ExceptionUtilities.Unreachable; } }
public override Symbol ContainingSymbol { get { throw ExceptionUtilities.Unreachable; } }
public override ImmutableArray<Location> Locations { get { throw ExceptionUtilities.Unreachable; } }
......
......@@ -151,6 +151,11 @@ internal override bool IsCallerMemberName
get { return _originalParam.IsCallerMemberName; }
}
internal override AttributeAnnotations FlowAnalysisAnnotations
{
get { return AttributeAnnotations.None; }
}
#endregion
}
}
......@@ -127,6 +127,25 @@ private bool HasCallerMemberNameAttribute
&& !HasCallerFilePathAttribute
&& HasCallerMemberNameAttribute;
internal override AttributeAnnotations FlowAnalysisAnnotations
{
get
{
(bool memberHasAny, AttributeAnnotations annotations) = TryGetExtraAttributeAnnotations();
if (memberHasAny)
{
// PROTOTYPE(NullableReferenceTypes): Make sure this is covered by test
return annotations;
}
CommonParameterWellKnownAttributeData attributeData = GetDecodedWellKnownAttributeData();
return AttributeAnnotations.None
.With(notNullWhenFalse: attributeData?.HasNotNullWhenFalseAttribute == true,
ensuresNotNull: attributeData?.HasEnsuresNotNullAttribute == true);
}
}
private ConstantValue DefaultSyntaxValue
{
get
......@@ -605,6 +624,14 @@ internal override void DecodeWellKnownAttribute(ref DecodeWellKnownAttributeArgu
// NullableAttribute should not be set explicitly.
arguments.Diagnostics.Add(ErrorCode.ERR_ExplicitNullableAttribute, arguments.AttributeSyntaxOpt.Location);
}
else if (attribute.IsTargetAttribute(this, AttributeDescription.NotNullWhenFalseAttribute))
{
arguments.GetOrCreateData<CommonParameterWellKnownAttributeData>().HasNotNullWhenFalseAttribute = true;
}
else if (attribute.IsTargetAttribute(this, AttributeDescription.EnsuresNotNullAttribute))
{
arguments.GetOrCreateData<CommonParameterWellKnownAttributeData>().HasEnsuresNotNullAttribute = true;
}
}
private void DecodeDefaultParameterValueAttribute(AttributeDescription description, ref DecodeWellKnownAttributeArguments<AttributeSyntax, CSharpAttributeData, AttributeLocation> arguments)
......
......@@ -82,6 +82,15 @@ internal override bool IsCallerMemberName
get { return false; }
}
internal override AttributeAnnotations FlowAnalysisAnnotations
{
get
{
(_, AttributeAnnotations annotations) = TryGetExtraAttributeAnnotations();
return annotations;
}
}
internal override MarshalPseudoCustomAttributeData MarshallingInformation
{
get { return null; }
......
......@@ -114,6 +114,11 @@ internal override bool IsCallerMemberName
get { return false; }
}
internal override AttributeAnnotations FlowAnalysisAnnotations
{
get { return AttributeAnnotations.None; }
}
public override int Ordinal
{
get { return -1; }
......
......@@ -115,6 +115,11 @@ internal override bool IsCallerMemberName
get { return false; }
}
internal override AttributeAnnotations FlowAnalysisAnnotations
{
get { return AttributeAnnotations.None; }
}
public override Symbol ContainingSymbol
{
get { return _container; }
......
......@@ -153,7 +153,13 @@ internal override bool IsCallerMemberName
get { return _underlyingParameter.IsCallerMemberName; }
}
public override string GetDocumentationCommentXml(CultureInfo preferredCulture = null, bool expandIncludes = false, CancellationToken cancellationToken = default(CancellationToken))
internal override AttributeAnnotations FlowAnalysisAnnotations
{
// PROTOTYPE(NullableReferenceTypes): Consider moving to leaf types
get { return _underlyingParameter.FlowAnalysisAnnotations; }
}
public override string GetDocumentationCommentXml(CultureInfo preferredCulture = null, bool expandIncludes = false, CancellationToken cancellationToken = default)
{
return _underlyingParameter.GetDocumentationCommentXml(preferredCulture, expandIncludes, cancellationToken);
}
......
......@@ -258,6 +258,8 @@ static AttributeDescription()
private static readonly byte[][] s_signaturesOfOutAttribute = { s_signature_HasThis_Void };
private static readonly byte[][] s_signaturesOfIsReadOnlyAttribute = { s_signature_HasThis_Void };
private static readonly byte[][] s_signaturesOfIsUnmanagedAttribute = { s_signature_HasThis_Void };
private static readonly byte[][] s_signaturesOfNotNullWhenFalseAttribute = { s_signature_HasThis_Void };
private static readonly byte[][] s_signaturesOfEnsuresNotNullAttribute = { s_signature_HasThis_Void };
private static readonly byte[][] s_signaturesOfFixedBufferAttribute = { s_signature_HasThis_Void_Type_Int32 };
private static readonly byte[][] s_signaturesOfSuppressUnmanagedCodeSecurityAttribute = { s_signature_HasThis_Void };
private static readonly byte[][] s_signaturesOfPrincipalPermissionAttribute = { s_signature_HasThis_Void_SecurityAction };
......@@ -454,6 +456,8 @@ static AttributeDescription()
internal static readonly AttributeDescription StructLayoutAttribute = new AttributeDescription("System.Runtime.InteropServices", "StructLayoutAttribute", s_signaturesOfStructLayoutAttribute);
internal static readonly AttributeDescription FieldOffsetAttribute = new AttributeDescription("System.Runtime.InteropServices", "FieldOffsetAttribute", s_signaturesOfFieldOffsetAttribute);
internal static readonly AttributeDescription FixedBufferAttribute = new AttributeDescription("System.Runtime.CompilerServices", "FixedBufferAttribute", s_signaturesOfFixedBufferAttribute);
internal static readonly AttributeDescription NotNullWhenFalseAttribute = new AttributeDescription("System.Runtime.CompilerServices", "NotNullWhenFalseAttribute", s_signaturesOfNotNullWhenFalseAttribute);
internal static readonly AttributeDescription EnsuresNotNullAttribute = new AttributeDescription("System.Runtime.CompilerServices", "EnsuresNotNullAttribute", s_signaturesOfEnsuresNotNullAttribute);
internal static readonly AttributeDescription MarshalAsAttribute = new AttributeDescription("System.Runtime.InteropServices", "MarshalAsAttribute", s_signaturesOfMarshalAsAttribute);
internal static readonly AttributeDescription InAttribute = new AttributeDescription("System.Runtime.InteropServices", "InAttribute", s_signaturesOfInAttribute);
internal static readonly AttributeDescription OutAttribute = new AttributeDescription("System.Runtime.InteropServices", "OutAttribute", s_signaturesOfOutAttribute);
......
......@@ -109,5 +109,38 @@ public bool HasIUnknownConstantAttribute
}
}
#endregion
// PROTOTYPE(NullableReferenceTypes): Consider moving the attribute for nullability to C#-specific type
private bool _hasNotNullWhenFalseAttribute;
public bool HasNotNullWhenFalseAttribute
{
get
{
VerifySealed(expected: true);
return _hasNotNullWhenFalseAttribute;
}
set
{
VerifySealed(expected: false);
_hasNotNullWhenFalseAttribute = value;
SetDataStored();
}
}
private bool _hasEnsuresNotNullAttribute;
public bool HasEnsuresNotNullAttribute
{
get
{
VerifySealed(expected: true);
return _hasEnsuresNotNullAttribute;
}
set
{
VerifySealed(expected: false);
_hasEnsuresNotNullAttribute = value;
SetDataStored();
}
}
}
}
......@@ -306,7 +306,11 @@ public static SyntaxTree ParseWithRoundTripCheck(string text, CSharpParseOptions
string ilSource,
IEnumerable<MetadataReference> references = null,
CSharpCompilationOptions options = null,
bool appendDefaultHeader = true) => CreateCompilationWithILAndMscorlib40(source, ilSource, TargetFramework.Standard, references, options, appendDefaultHeader);
CSharpParseOptions parseOptions = null,
bool appendDefaultHeader = true)
{
return CreateCompilationWithILAndMscorlib40(source, ilSource, TargetFramework.Standard, references, options, parseOptions, appendDefaultHeader);
}
public static CSharpCompilation CreateCompilationWithILAndMscorlib40(
CSharpTestSource source,
......@@ -314,11 +318,12 @@ public static SyntaxTree ParseWithRoundTripCheck(string text, CSharpParseOptions
TargetFramework targetFramework = TargetFramework.Mscorlib40,
IEnumerable<MetadataReference> references = null,
CSharpCompilationOptions options = null,
CSharpParseOptions parseOptions = null,
bool appendDefaultHeader = true)
{
MetadataReference ilReference = CompileIL(ilSource, appendDefaultHeader);
var allReferences = TargetFrameworkUtil.GetReferences(targetFramework, references).Add(ilReference);
return CreateEmptyCompilation(source, allReferences, options);
return CreateEmptyCompilation(source, allReferences, options, parseOptions);
}
public static CSharpCompilation CreateCompilationWithMscorlib40(
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册